From d9c471600ec1c7bff89ee4143018731f4c9edd20 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 30 May 2017 11:16:20 +0200 Subject: [PATCH 001/196] ADD: command plugins --- src/cli/index.ts | 3 ++ src/cli/plugins/core/loadPlugins.ts | 40 +++++++++++++++++++++++ src/cli/plugins/core/readConfig.ts | 18 +++++++++++ src/cli/plugins/index.ts | 50 +++++++++++++++++++++++++++++ src/cli/utilities/logger.ts | 6 ++++ 5 files changed, 117 insertions(+) create mode 100644 src/cli/plugins/core/loadPlugins.ts create mode 100644 src/cli/plugins/core/readConfig.ts create mode 100644 src/cli/plugins/index.ts create mode 100644 src/cli/utilities/logger.ts diff --git a/src/cli/index.ts b/src/cli/index.ts index e59dac5..4fa405e 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -4,9 +4,12 @@ import './utilities/config' import { init as initDeployCommand } from './commands/deploy' import { init as initLocalCommand } from './commands/local' import { init as initMetadataCommand } from './commands/metadata' +import { init as initPlugins } from './plugins' export const init = (commander) => { initDeployCommand(commander) initLocalCommand(commander) initMetadataCommand(commander) + + initPlugins(commander) } \ No newline at end of file diff --git a/src/cli/plugins/core/loadPlugins.ts b/src/cli/plugins/core/loadPlugins.ts new file mode 100644 index 0000000..ac79c97 --- /dev/null +++ b/src/cli/plugins/core/loadPlugins.ts @@ -0,0 +1,40 @@ +import { readFileSync } from 'fs' +import { logger } from '../../utilities/logger' +import { resolvePath } from '../../utilities/cli' + +export const PLUGIN_FOLDER = './node_modules/' +export const PLUGIN_PREFIX = 'functionly-plugin-' +export const PLUGIN_PATH_REGEXP = /^\./ + +export const loadPlugins = (config) => { + const pluginInitializers: any[] = [] + + const cwd = process.cwd() + + if (Array.isArray(config.plugins)) { + for (const pluginPath of config.plugins) { + + let resolvedPluginPath = null + if (PLUGIN_PATH_REGEXP.test(pluginPath)) { + resolvedPluginPath = resolvePath(pluginPath) + } else { + resolvedPluginPath = resolvePath(`${PLUGIN_FOLDER}${PLUGIN_PREFIX}${pluginPath}`) + } + + try { + const pluginInit = require(resolvedPluginPath) + + pluginInitializers.push({ + init: pluginInit.default, + path: pluginPath, + resolvedPath: resolvedPluginPath + }) + + } catch (e) { + logger.error(`Plugin '${pluginPath}' not exists`, e) + } + } + } + + return pluginInitializers; +} diff --git a/src/cli/plugins/core/readConfig.ts b/src/cli/plugins/core/readConfig.ts new file mode 100644 index 0000000..45d28ab --- /dev/null +++ b/src/cli/plugins/core/readConfig.ts @@ -0,0 +1,18 @@ +import { join } from 'path' +import { readFileSync } from 'fs' +import { logger } from '../../utilities/logger' + +export const projectConfig = './functionly.json' + +export const readConfig = () => { + const cwd = process.cwd() + const configPath = join(cwd, projectConfig) + + try { + const fileContent = readFileSync(configPath, 'utf8') + return JSON.parse(fileContent) + } catch (e) { + return {} + } + +} \ No newline at end of file diff --git a/src/cli/plugins/index.ts b/src/cli/plugins/index.ts new file mode 100644 index 0000000..5ab6b75 --- /dev/null +++ b/src/cli/plugins/index.ts @@ -0,0 +1,50 @@ +import { readConfig } from './core/readConfig' +import { loadPlugins } from './core/loadPlugins' + +import { logger } from '../utilities/logger' +import { resolvePath } from '../utilities/cli' + + +export const pluginDefinition = [] + +export const init = (commander) => { + + const config = readConfig() + const plugins = loadPlugins(config) + + // init plugins + for (const pluginInfo of plugins) { + try { + + const pluginConfig = pluginInfo.init({ + logger, + resolvePath + }) + + pluginDefinition.push({ + pluginInfo, + config: pluginConfig || {} + }) + + } catch (e) { + logger.error(`Plugin '${pluginInfo.path}' not loaded correctly`, e) + } + } + + + // init commands + for (const plugin of pluginDefinition) { + try { + if (plugin.config.commands) { + + plugin.config.commands({ + commander + }) + } + } catch (e) { + logger.error(`Plugin '${plugin.pluginInfo.path}' commands not loaded correctly`, e) + } + } + +} + diff --git a/src/cli/utilities/logger.ts b/src/cli/utilities/logger.ts new file mode 100644 index 0000000..4ac379c --- /dev/null +++ b/src/cli/utilities/logger.ts @@ -0,0 +1,6 @@ +export const logger = { + warn(msg?, ...params) { console.log(msg, ...params) }, + error(msg?, ...params) { console.log(msg, ...params) }, + debug(msg?, ...params) { console.log(msg, ...params) }, + info(msg?, ...params) { console.log(msg, ...params) }, +} \ No newline at end of file From cbec6807d2e412560337688f458b6d105f7b32ed Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Wed, 31 May 2017 11:05:53 +0200 Subject: [PATCH 002/196] ADD: extensions for onHandle, onInvoke, onInject --- src/classes/core/classExtensions.ts | 7 ++ src/classes/index.ts | 3 +- src/providers/aws.ts | 94 +++++++++---------- src/providers/core/provider.ts | 38 ++++++++ src/providers/deploy.ts | 9 +- src/providers/index.ts | 54 ++++++++--- src/providers/local.ts | 135 ++++++++++++++-------------- 7 files changed, 203 insertions(+), 137 deletions(-) create mode 100644 src/classes/core/classExtensions.ts create mode 100644 src/providers/core/provider.ts diff --git a/src/classes/core/classExtensions.ts b/src/classes/core/classExtensions.ts new file mode 100644 index 0000000..338eebf --- /dev/null +++ b/src/classes/core/classExtensions.ts @@ -0,0 +1,7 @@ + + +export const callExtension = async (target: any, method: string, ...params) => { + if (typeof target[method] === 'function') { + return await target[method].call(target, ...params) + } +} \ No newline at end of file diff --git a/src/classes/index.ts b/src/classes/index.ts index 5f73f03..5fb1b6e 100644 --- a/src/classes/index.ts +++ b/src/classes/index.ts @@ -1,4 +1,5 @@ export { Service } from './service' export { FunctionalApi } from './functionalApi' export { FunctionalService } from './functionalService' -export { DynamoDB } from './externals/dynamoDB' \ No newline at end of file +export { DynamoDB } from './externals/dynamoDB' +export { callExtension } from './core/classExtensions' \ No newline at end of file diff --git a/src/providers/aws.ts b/src/providers/aws.ts index c09a5e4..73f5486 100644 --- a/src/providers/aws.ts +++ b/src/providers/aws.ts @@ -1,59 +1,53 @@ import { Lambda } from 'aws-sdk' -import { constants, getOwnMetadata, getMetadata, getFunctionName } from '../annotations' - -let lambda = new Lambda(); - -export const getInvoker = (serviceType, params) => { - const serviceInstance = new serviceType(...params) - const parameterMapping = (getOwnMetadata(constants.PARAMETER_PARAMKEY, serviceType, 'handle') || []) - .filter(t => t && typeof t.parameterIndex === 'number'); - - const invoker = async (event, context, cb) => { - try { - const params = [] - parameterMapping.forEach((target) => { - params[target.parameterIndex] = parameterResolver(event, context, target) - }) - - const r = await serviceInstance.handle.apply(serviceInstance, params) - cb(null, r) - return r - } catch (e) { - cb(e) +import { Provider } from './core/provider' +import { getFunctionName } from '../annotations' + +const lambda = new Lambda(); + +export class AWSProvider extends Provider { + public getInvoker(serviceType, serviceInstance, params): Function { + const parameters = this.getParameters(serviceType, 'handle') + + const invoker = async (event, context, cb) => { + try { + const params = [] + for (const parameter of parameters) { + params[parameter.parameterIndex] = await this.awsParameterResolver(event, context, parameter) + } + + const r = await serviceInstance.handle.apply(serviceInstance, params) + cb(null, r) + return r + } catch (e) { + cb(e) + } } + return invoker } - return invoker -} -const parameterResolver = (event, context, target) => { - switch (target.type) { - case 'inject': - let serviceType = target.serviceType - return new serviceType(...target.params.map((p) => typeof p === 'function' ? p() : p)) - default: - return event[target.from] + protected async awsParameterResolver(event, context, parameter) { + switch (parameter.type) { + case 'param': + return event[parameter.from] + default: + return await super.parameterResolver(parameter) + } } -} -export const invoke = async (serviceInstance, params?, invokeConfig?) => { - return new Promise((resolve, reject) => { - let lambdaParams = {} + public async invoke(serviceInstance, params, invokeConfig?) { + return new Promise((resolve, reject) => { - let parameterMapping = getOwnMetadata(constants.PARAMETER_PARAMKEY, serviceInstance.constructor, 'handle') || []; - parameterMapping.forEach((target) => { - if (params && target && target.type === 'param') { - lambdaParams[target.from] = params[target.from] - } + const invokeParams = { + FunctionName: getFunctionName(serviceInstance), + Payload: JSON.stringify(params) + }; + + lambda.invoke(invokeParams, function (err, data) { + if (err) reject(err) + else resolve(JSON.parse(data.Payload.toString())); + }); }) + } +} - let invokeParams = { - FunctionName: getFunctionName(serviceInstance), - Payload: JSON.stringify(lambdaParams) - }; - - lambda.invoke(invokeParams, function (err, data) { - if (err) reject(err) - else resolve(JSON.parse(data.Payload.toString())); - }); - }) -} \ No newline at end of file +export const provider = new AWSProvider() diff --git a/src/providers/core/provider.ts b/src/providers/core/provider.ts new file mode 100644 index 0000000..e9ce875 --- /dev/null +++ b/src/providers/core/provider.ts @@ -0,0 +1,38 @@ +import { constants, getOwnMetadata } from '../../annotations' +import { callExtension } from '../../classes' + +export abstract class Provider { + public getInvoker(serviceType, serviceInstance, params): Function { + const invoker = () => { } + return invoker + } + + public async invoke(serviceInstance, params, invokeConfig?): Promise { + + } + + + protected async parameterResolver(parameter): Promise { + switch (parameter.type) { + case 'inject': + const serviceType = parameter.serviceType + + const staticInstance = await callExtension(serviceType, 'onInject', { parameter }) + if (typeof staticInstance !== 'undefined') { + return staticInstance + } + + const instance = new serviceType(...parameter.params.map((p) => typeof p === 'function' ? p() : p)) + await callExtension(instance, 'onInject', { parameter }) + return instance + default: + return undefined + } + } + + protected getParameters(target, method) { + return (getOwnMetadata(constants.PARAMETER_PARAMKEY, target, 'handle') || []) + .filter(t => t && typeof t.parameterIndex === 'number'); + } +} + diff --git a/src/providers/deploy.ts b/src/providers/deploy.ts index 2b4f658..2bfb4e6 100644 --- a/src/providers/deploy.ts +++ b/src/providers/deploy.ts @@ -1,9 +1,6 @@ +import { Provider } from './core/provider' -export const getInvoker = (serviceType, params) => { - let invoker = () => {} - return invoker +export class DeployProvider extends Provider { } -export const invoke = async (serviceInstance, params?, invokeConfig?) => { - -} \ No newline at end of file +export const provider = new DeployProvider() diff --git a/src/providers/index.ts b/src/providers/index.ts index 020e65d..ca370d3 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1,33 +1,50 @@ import { get } from 'lodash' +import { constants, getOwnMetadata } from '../annotations' +import { callExtension } from '../classes' -import * as aws from './aws' -import * as local from './local' -import * as deploy from './deploy' +import { provider as aws } from './aws' +import { provider as local } from './local' +import { provider as deploy } from './deploy' let environments = { aws, local, deploy } let invokeEnvironments = { aws, local } export const getInvoker = (serviceType, params) => { + const environment = process.env.FUNCTIONAL_ENVIRONMENT; - if (!process.env.FUNCTIONAL_ENVIRONMENT || !environments[process.env.FUNCTIONAL_ENVIRONMENT]) { - throw new Error(`missing environment: process.env.FUNCTIONAL_ENVIRONMENT`) + if (!environment || !environments[environment]) { + throw new Error(`missing environment: '${environment}'`) } - let currentEnvironment = environments[process.env.FUNCTIONAL_ENVIRONMENT] + const currentEnvironment = environments[environment] - let invoker = currentEnvironment.getInvoker(serviceType, params) + const serviceInstance = new serviceType(...params) + const invoker = currentEnvironment.getInvoker(serviceType, serviceInstance, params) - Object.defineProperty(invoker, 'serviceType', { + const invokeHandler = async (...params) => { + const onHandleResult = await callExtension(serviceInstance, `onHandle_${environment}`, ...params) + if (typeof onHandleResult !== 'undefined') { + return onHandleResult + } + return await invoker.apply(this, params) + } + + Object.defineProperty(invokeHandler, 'serviceType', { enumerable: false, configurable: false, writable: false, value: serviceType, }) - return invoker + return invokeHandler } export const invoke = async (serviceInstance, params?, invokeConfig?) => { + await callExtension(serviceInstance, 'onInvoke', { + params, + invokeConfig, + }) + const environmentMode = (invokeConfig && invokeConfig.mode) || process.env.FUNCTIONAL_ENVIRONMENT if (!environmentMode || !invokeEnvironments[environmentMode]) { @@ -36,5 +53,22 @@ export const invoke = async (serviceInstance, params?, invokeConfig?) => { let currentEnvironment = invokeEnvironments[environmentMode] - return await currentEnvironment.invoke(serviceInstance, params, invokeConfig) + const availableParams = {} + const parameterMapping = (getOwnMetadata(constants.PARAMETER_PARAMKEY, serviceInstance.constructor, 'handle') || []) + parameterMapping.forEach((target) => { + if (params && target && target.type === 'param') { + availableParams[target.from] = params[target.from] + } + }) + + await callExtension(serviceInstance, `onInvoke_${environmentMode}`, { + invokeParams: params, + params: availableParams, + invokeConfig, + parameterMapping, + currentEnvironment, + environmentMode + }) + + return await currentEnvironment.invoke(serviceInstance, availableParams, invokeConfig) } \ No newline at end of file diff --git a/src/providers/local.ts b/src/providers/local.ts index f12ec4b..6fdae3e 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -1,90 +1,85 @@ import * as request from 'request' +import { Provider } from './core/provider' import { constants, getOwnMetadata, getMetadata, getFunctionName } from '../annotations' -export const getInvoker = (serviceType, params) => { - const serviceInstance = new serviceType(...params) - const parameterMapping = (getOwnMetadata(constants.PARAMETER_PARAMKEY, serviceType, 'handle') || []) - .filter(t => t && typeof t.parameterIndex === 'number'); - - const invoker = async (req, res, next) => { - try { - const params = [] - parameterMapping.forEach((target) => { - params[target.parameterIndex] = parameterResolver(req, target) - }) - - const r = await serviceInstance.handle.apply(serviceInstance, params) - res.send(r) - return r - } catch (e) { - next(e) + +export class LocalProvider extends Provider { + public getInvoker(serviceType, serviceInstance, params) { + const parameters = this.getParameters(serviceType, 'handle') + + const invoker = async (req, res, next) => { + try { + const params = [] + for (const parameter of parameters){ + params[parameter.parameterIndex] = await this.localParameterResolver(req, parameter) + } + + const r = await serviceInstance.handle.apply(serviceInstance, params) + res.send(r) + return r + } catch (e) { + next(e) + } } + return invoker } - return invoker -} -const parameterResolver = (req, target) => { - switch (target.type) { - case 'inject': - let serviceType = target.serviceType - return new serviceType(...target.params.map((p) => typeof p === 'function' ? p() : p)) - default: - if (req.body && req.body[target.from]) return req.body[target.from] - if (req.query && req.query[target.from]) return req.query[target.from] - if (req.params && req.params[target.from]) return req.params[target.from] + protected async localParameterResolver(req, parameter) { + switch (parameter.type) { + case 'param': + if (req.body && req.body[parameter.from]) return req.body[parameter.from] + if (req.query && req.query[parameter.from]) return req.query[parameter.from] + if (req.params && req.params[parameter.from]) return req.params[parameter.from] + default: + return await super.parameterResolver(parameter) + } } -} -export const invoke = async (serviceInstance, params?, invokeConfig?) => { - return new Promise((resolve, reject) => { - let lambdaParams = {} + public async invoke(serviceInstance, params, invokeConfig?) { + return new Promise((resolve, reject) => { - let parameterMapping = getOwnMetadata(constants.PARAMETER_PARAMKEY, serviceInstance.constructor, 'handle') || []; - parameterMapping.forEach((target) => { - if (params && target && target.type === 'param') { - lambdaParams[target.from] = params[target.from] + const httpAttr = getMetadata(constants.CLASS_APIGATEWAYKEY, serviceInstance)[0] + if (!httpAttr) { + return reject(new Error('missing http configuration')) } - }) - let httpAttr = getMetadata(constants.CLASS_APIGATEWAYKEY, serviceInstance)[0] - if (!httpAttr) { - return reject(new Error('missing http configuration')) - } + const invokeParams: any = { + method: httpAttr.method || 'GET', + url: `http://localhost:${process.env.FUNCTIONAL_LOCAL_PORT}${httpAttr.path}`, + }; - let invokeParams: any = { - method: httpAttr.method || 'GET', - url: `http://localhost:${process.env.FUNCTIONAL_LOCAL_PORT}${httpAttr.path}`, - }; + if (!httpAttr.method || httpAttr.method.toLowerCase() === 'get') { + invokeParams.qs = params + } else { + invokeParams.body = params + invokeParams.json = true + } - if (!httpAttr.method || httpAttr.method.toLowerCase() === 'get') { - invokeParams.qs = lambdaParams - } else { - invokeParams.body = lambdaParams - invokeParams.json = true - } + try { - try { + const isLoggingEnabled = getMetadata(constants.CLASS_LOGKEY, serviceInstance) + if (isLoggingEnabled) { + console.log(`${new Date().toISOString()} request to ${getFunctionName(serviceInstance)}`, JSON.stringify(invokeParams, null, 2)) + } - const isLoggingEnabled = getMetadata(constants.CLASS_LOGKEY, serviceInstance) - if (isLoggingEnabled) { - console.log(`${new Date().toISOString()} request to ${getFunctionName(serviceInstance)}`, JSON.stringify(invokeParams, null, 2)) - } + request(invokeParams, (error, response, body) => { - request(invokeParams, (error, response, body) => { + if (error) return reject(error) - if (error) return reject(error) + let result = body + try { + result = JSON.parse(result) + } + catch (e) { } - let result = body - try { - result = JSON.parse(result) - } - catch (e) { } + return resolve(result) - return resolve(result) + }) + } catch (e) { + return reject(e); + } + }) + } +} - }) - } catch (e) { - return reject(e); - } - }) -} \ No newline at end of file +export const provider = new LocalProvider() From c8335cec2440d8e0f2ade8fd28d42916e27c524e Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Thu, 1 Jun 2017 13:18:27 +0200 Subject: [PATCH 003/196] ADD: context hooks; REFACTOR: context --- src/classes/core/classExtensions.ts | 2 +- src/cli/commands/deploy.ts | 47 +++---- src/cli/commands/local.ts | 120 +++++++++++++++--- src/cli/commands/metadata.ts | 67 +++++----- src/cli/context/core/contextStep.ts | 17 +++ src/cli/context/core/serviceDiscovery.ts | 72 ----------- .../context/core/setFunctionalEnvironment.ts | 8 -- src/cli/context/core/tableDiscovery.ts | 32 ----- src/cli/context/index.ts | 79 ++++++++++-- src/cli/context/steppes/serviceDiscovery.ts | 75 +++++++++++ .../steppes/setFunctionalEnvironment.ts | 12 ++ src/cli/context/steppes/tableDiscovery.ts | 37 ++++++ src/cli/extensions/classExtensions.ts | 14 ++ src/cli/extensions/hooks.ts | 55 ++++++++ src/cli/index.ts | 19 +-- src/cli/project/config.ts | 8 ++ .../{plugins => project}/core/loadPlugins.ts | 36 +++++- .../{plugins => project}/core/readConfig.ts | 5 +- src/cli/{plugins/index.ts => project/init.ts} | 31 +++-- src/cli/providers/index.ts | 25 ++-- src/cli/providers/local.ts | 2 +- src/cli/utilities/deploy.ts | 5 - src/cli/utilities/local.ts | 80 ------------ src/providers/aws.ts | 2 +- src/providers/index.ts | 2 +- src/providers/local.ts | 2 +- 26 files changed, 526 insertions(+), 328 deletions(-) create mode 100644 src/cli/context/core/contextStep.ts delete mode 100644 src/cli/context/core/serviceDiscovery.ts delete mode 100644 src/cli/context/core/setFunctionalEnvironment.ts delete mode 100644 src/cli/context/core/tableDiscovery.ts create mode 100644 src/cli/context/steppes/serviceDiscovery.ts create mode 100644 src/cli/context/steppes/setFunctionalEnvironment.ts create mode 100644 src/cli/context/steppes/tableDiscovery.ts create mode 100644 src/cli/extensions/classExtensions.ts create mode 100644 src/cli/extensions/hooks.ts create mode 100644 src/cli/project/config.ts rename src/cli/{plugins => project}/core/loadPlugins.ts (57%) rename src/cli/{plugins => project}/core/readConfig.ts (65%) rename src/cli/{plugins/index.ts => project/init.ts} (52%) delete mode 100644 src/cli/utilities/deploy.ts delete mode 100644 src/cli/utilities/local.ts diff --git a/src/classes/core/classExtensions.ts b/src/classes/core/classExtensions.ts index 338eebf..1881ecc 100644 --- a/src/classes/core/classExtensions.ts +++ b/src/classes/core/classExtensions.ts @@ -2,6 +2,6 @@ export const callExtension = async (target: any, method: string, ...params) => { if (typeof target[method] === 'function') { - return await target[method].call(target, ...params) + return await target[method](...params) } } \ No newline at end of file diff --git a/src/cli/commands/deploy.ts b/src/cli/commands/deploy.ts index 6ed76ea..9331214 100644 --- a/src/cli/commands/deploy.ts +++ b/src/cli/commands/deploy.ts @@ -1,27 +1,28 @@ -import { deploy } from '../utilities/deploy' -import { createContext } from '../context' +export default ({ createContext, contextSteppes: { createEnvironment } }) => { + return { + commands({ commander }) { + commander + .command('deploy ') + .description('deploy functional services') + .option('--aws-region ', 'AWS_REGION') + .option('--aws-bucket ', 'aws bucket') + .action(async (target, path, command) => { + process.env.FUNCTIONAL_ENVIRONMENT = 'deploy' -export const init = (commander) => { - commander - .command('deploy ') - .description('deploy functional services') - .option('--aws-region ', 'AWS_REGION') - .option('--aws-bucket ', 'aws bucket') - .action(async (target, path, command) => { - process.env.FUNCTIONAL_ENVIRONMENT = 'deploy' + const context = await createContext(path, { + deployTarget: target, + awsRegion: command.awsRegion, + awsBucket: command.awsBucket + }) - const context = await createContext(path, { - deployTarget: target, - awsRegion: command.awsRegion, - awsBucket: command.awsBucket - }) + try { + await context.runStep(createEnvironment) - try { - await deploy(context) - - console.log(`done`) - } catch (e) { - console.log(`error`, e) - } - }); + console.log(`done`) + } catch (e) { + console.log(`error`, e) + } + }); + } + } } \ No newline at end of file diff --git a/src/cli/commands/local.ts b/src/cli/commands/local.ts index 9cff99d..d5f5986 100644 --- a/src/cli/commands/local.ts +++ b/src/cli/commands/local.ts @@ -1,24 +1,104 @@ -import { local } from '../utilities/local' -import { createContext } from '../context' - -export const init = (commander) => { - commander - .command('local ') - .description('run functional service local') - .action(async (port, path, command) => { - process.env.FUNCTIONAL_ENVIRONMENT = 'local' - - const context = await createContext(path, { - deployTarget: 'local', - localPort: port - }) +import * as express from 'express' +import * as bodyParser from 'body-parser' +import * as cors from 'cors' + +export default ({ createContext, annotations: { getMetadata, constants, getFunctionName } }) => { + + const startLocal = async (context) => { + let app = express() + app.use(bodyParser.json()) + + for (let serviceDefinition of context.publishedFunctions) { + let httpMetadata = getMetadata(constants.CLASS_APIGATEWAYKEY, serviceDefinition.service) + + for (let event of httpMetadata) { + const isLoggingEnabled = getMetadata(constants.CLASS_LOGKEY, serviceDefinition.service) + console.log(`${new Date().toISOString()} ${getFunctionName(serviceDefinition.service)} listening { path: '${event.path}', method: '${event.method}', cors: ${event.cors ? true : false} }`) - try { - await local(context) + if (event.cors) { + app.use(event.path, cors()) + } - console.log(`done`) - } catch (e) { - console.log(`error`, e) + app[event.method]( + event.path, + logMiddleware(isLoggingEnabled, serviceDefinition.service), + environmentConfigMiddleware(serviceDefinition.service), + serviceDefinition.invoker + ) } - }); + } + + app.listen(context.localPort, function () { + process.env.FUNCTIONAL_LOCAL_PORT = context.localPort + console.log(`Example app listening on port ${process.env.FUNCTIONAL_LOCAL_PORT}!`) + }) + } + + const logMiddleware = (enabled, serviceType) => { + return (req, res, next) => { + if (!enabled) return next() + + console.log(`${new Date().toISOString()} ${getFunctionName(serviceType)}`, JSON.stringify({ + url: req.url, + query: req.query, + params: req.params, + body: req.body, + headers: req.headers + }, null, 2)) + + next() + } + } + + const environmentConfigMiddleware = (serviceType) => { + return (req, res, next) => { + const environmentVariables = getMetadata(constants.CLASS_ENVIRONMENTKEY, serviceType) || {} + + let originals = {} + Object.keys(environmentVariables).forEach((key) => { + if (key in process.env) { + originals[key] = process.env[key] + } + + process.env[key] = environmentVariables[key] + }) + + + req.on("end", function () { + Object.keys(environmentVariables).forEach((key) => { + if (key in originals) { + process.env[key] = originals[key] + } else { + delete process.env[key] + } + }) + }); + + next() + } + } + + return { + commands({ commander }) { + commander + .command('local ') + .description('run functional service local') + .action(async (port, path, command) => { + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + const context = await createContext(path, { + deployTarget: 'local', + localPort: port + }) + + try { + await context.runStep({ name: 'startLocal', execute: startLocal }) + + console.log(`done`) + } catch (e) { + console.log(`error`, e) + } + }); + } + } } \ No newline at end of file diff --git a/src/cli/commands/metadata.ts b/src/cli/commands/metadata.ts index c6e8dca..4c94403 100644 --- a/src/cli/commands/metadata.ts +++ b/src/cli/commands/metadata.ts @@ -1,36 +1,37 @@ -import { local } from '../utilities/local' -import { createContext } from '../context' -import { getMetadata, getMetadataKeys } from '../../annotations' - -export const init = (commander) => { - commander - .command('metadata ') - .description('service metadata') - .action(async (target, path, command) => { - process.env.FUNCTIONAL_ENVIRONMENT = 'deploy' - - const context = await createContext(path, { - deployTarget: target - }) - - try { - - console.log(JSON.stringify(context, null, 4)) - - for (let serviceDefinitions of context.publishedFunctions) { - let keys = getMetadataKeys(serviceDefinitions.service) - let metadata = {} - for (let key of keys) { - metadata[key] = getMetadata(key, serviceDefinitions.service) - } +export default ({ createContext, annotations: { getMetadata, getMetadataKeys } }) => { + + return { + commands({ commander }) { + commander + .command('metadata ') + .description('service metadata') + .action(async (target, path, command) => { + process.env.FUNCTIONAL_ENVIRONMENT = 'deploy' + + const context = await createContext(path, { + deployTarget: target + }) + + try { - console.log(serviceDefinitions.handler, JSON.stringify(metadata, null, 4)) - } + console.log(JSON.stringify(context, null, 4)) + for (let serviceDefinitions of context.publishedFunctions) { + let keys = getMetadataKeys(serviceDefinitions.service) + let metadata = {} + for (let key of keys) { + metadata[key] = getMetadata(key, serviceDefinitions.service) + } - console.log(`done`) - } catch (e) { - console.log(`error`, e) - } - }); -} \ No newline at end of file + console.log(serviceDefinitions.handler, JSON.stringify(metadata, null, 4)) + } + + + console.log(`done`) + } catch (e) { + console.log(`error`, e) + } + }); + } + } +} diff --git a/src/cli/context/core/contextStep.ts b/src/cli/context/core/contextStep.ts new file mode 100644 index 0000000..bf6542e --- /dev/null +++ b/src/cli/context/core/contextStep.ts @@ -0,0 +1,17 @@ +export const contextSteppes: { [x: string]: ContextStep } = {} + +export class ContextStep { + public name: string + public constructor(name) { + this.name = name + + if (contextSteppes[this.name]) { + throw new Error(`step name '${this.name}' already defined`) + } + contextSteppes[this.name] = this + } + + public execute(context) { + + } +} \ No newline at end of file diff --git a/src/cli/context/core/serviceDiscovery.ts b/src/cli/context/core/serviceDiscovery.ts deleted file mode 100644 index ca472c5..0000000 --- a/src/cli/context/core/serviceDiscovery.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { lstat, readdirSync } from 'fs' -import { FunctionalService } from '../../../classes/functionalService' - -import { join, basename, extname } from 'path' -import { set } from 'lodash' - - - -export const serviceDiscovery = async (context) => { - context.files = context.files || [] - context.publishedFunctions = context.publishedFunctions || [] - - const path = context.serviceRoot - - let isDir = await isDirectory(path) - - let files = [path] - if (isDir) { - files = await getJsFiles(path) - } - - for (let file of files) { - collectFromFile(file, context) - } - - return context -} - -export const collectFromFile = (file, context) => { - const module = require(file) - - const name = basename(file) - const ext = extname(name) - const nameKey = name.substring(0, name.length - ext.length) - - Object.keys(module).forEach((key) => { - let exportItem = module[key] - - if (exportItem.serviceType && exportItem.serviceType.createInvoker) { - const item = { - service: exportItem.serviceType, - exportName: key, - invoker: exportItem, - handler: `${nameKey}.${key}`, - } - - if (context.files.indexOf(file) < 0) { - context.files.push(file) - } - - context.publishedFunctions.push(item) - } - - }) -} - -export const getJsFiles = (folder) => { - let files = readdirSync(folder); - let filter = /\.js$/ - return files.filter(name => filter.test(name)).map(file => join(folder, file)) -} - - - -export const isDirectory = (path) => { - return new Promise((resolve, reject) => { - lstat(path, (err, stats) => { - if (err) return reject(err); - return resolve(stats.isDirectory()) - }); - }) -} \ No newline at end of file diff --git a/src/cli/context/core/setFunctionalEnvironment.ts b/src/cli/context/core/setFunctionalEnvironment.ts deleted file mode 100644 index ba600ad..0000000 --- a/src/cli/context/core/setFunctionalEnvironment.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { environment } from '../../../annotations' - -export const setFunctionalEnvironment = async (context) => { - for (let serviceDefinition of context.publishedFunctions) { - const setEnvAttrib = environment('FUNCTIONAL_ENVIRONMENT', context.FUNCTIONAL_ENVIRONMENT) - setEnvAttrib(serviceDefinition.service) - } -} \ No newline at end of file diff --git a/src/cli/context/core/tableDiscovery.ts b/src/cli/context/core/tableDiscovery.ts deleted file mode 100644 index ed09770..0000000 --- a/src/cli/context/core/tableDiscovery.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { merge } from 'lodash' -import { getMetadata, constants, __dynamoDBDefaults } from '../../../annotations' - -export const tableNameEnvRegexp = /_TABLE_NAME$/ -export const tableDiscovery = async (context) => { - let tablesToCreate = new Map() - - for (let serviceDefinition of context.publishedFunctions) { - let tableConfigs = getMetadata(constants.CLASS_DYNAMOTABLECONFIGURATIONKEY, serviceDefinition.service) || [] - for (const tableConfig of tableConfigs) { - if (tablesToCreate.has(tableConfig.tableName)) { - continue - } - - tablesToCreate.set(tableConfig.tableName, tableConfig) - } - - let metadata = getMetadata(constants.CLASS_ENVIRONMENTKEY, serviceDefinition.service) - if (metadata) { - let keys = Object.keys(metadata) - for (const key of keys) { - if (tableNameEnvRegexp.test(key) && !tablesToCreate.has(metadata[key])) { - tablesToCreate.set(metadata[key], { - TableName: metadata[key] - }) - } - } - } - } - - context.tableConfigs = Array.from(tablesToCreate.values()) -} \ No newline at end of file diff --git a/src/cli/context/index.ts b/src/cli/context/index.ts index 5ab08b8..6489bef 100644 --- a/src/cli/context/index.ts +++ b/src/cli/context/index.ts @@ -1,27 +1,82 @@ -export { serviceDiscovery } from './core/serviceDiscovery' -export { tableDiscovery } from './core/tableDiscovery' -export { setFunctionalEnvironment } from './core/setFunctionalEnvironment' +export { contextSteppes, ContextStep } from './core/contextStep' + +import { serviceDiscovery } from './steppes/serviceDiscovery' +import { tableDiscovery } from './steppes/tableDiscovery' +import './steppes/setFunctionalEnvironment' import { resolvePath } from '../utilities/cli' +import { logger } from '../utilities/logger' import { defaults } from 'lodash' -import { serviceDiscovery } from './core/serviceDiscovery' -import { tableDiscovery } from './core/tableDiscovery' +import { projectConfig } from '../project/config' +import { + cliCallContextHooks, hasCliCallContextHooks, CONTEXT_HOOK_MODIFIER_BEFORE, CONTEXT_HOOK_MODIFIER_AFTER, + callContextHookStep +} from '../extensions/hooks' -export const tasks = [ - serviceDiscovery, - tableDiscovery -] +export const getDefaultSteppes = (): any[] => { + return [ + callContextHookStep, + serviceDiscovery, + tableDiscovery + ] +} export const createContext = async (path, defaultValues) => { - const context = defaults({}, { + const context = defaults(new Context(), { serviceRoot: resolvePath(path), date: new Date() }, defaultValues) - for (const t of tasks) { - await t(context) + const defaultSteppes = getDefaultSteppes() + for (const step of defaultSteppes) { + await context.runStep(step) } return context } + +let depth = 0 +export class Context { + [p: string]: any + + public async runStep(step: Function | any) { + const tab = depth++ + + const separator = (s = ' ') => { + let result = '' + for (var i = 0; i < tab; i++) { + result += s + } + return result + } + + + if (typeof step === 'function') { + await step(this) + } else if (step && step.name && typeof step.execute === 'function') { + if (projectConfig.debug) logger.debug(`Context step run -----------${separator('--')}> ${step.name}`) + if (projectConfig.debug) logger.debug(`Context step before start ${separator()} ${step.name}`) + await cliCallContextHooks(step.name, this, CONTEXT_HOOK_MODIFIER_BEFORE) + if (projectConfig.debug) logger.debug(`Context step before end ${separator()} ${step.name}`) + + if (projectConfig.debug) logger.debug(`Context step start ${separator()} ${step.name}`) + if (hasCliCallContextHooks(step.name, this)) { + await cliCallContextHooks(step.name, this) + } else { + await step.execute(this) + } + if (projectConfig.debug) logger.debug(`Context step end ${separator()} ${step.name}`) + + if (projectConfig.debug) logger.debug(`Context step after start ${separator()} ${step.name}`) + await cliCallContextHooks(step.name, this, CONTEXT_HOOK_MODIFIER_AFTER) + if (projectConfig.debug) logger.debug(`Context step after end ${separator()} ${step.name}`) + if (projectConfig.debug) logger.debug(`Context step complete ------${separator('--')}> ${step.name}`) + } else { + throw new Error(`context.runStep has invalid parameter '${step}'`) + } + depth-- + } +} + + diff --git a/src/cli/context/steppes/serviceDiscovery.ts b/src/cli/context/steppes/serviceDiscovery.ts new file mode 100644 index 0000000..87d77fc --- /dev/null +++ b/src/cli/context/steppes/serviceDiscovery.ts @@ -0,0 +1,75 @@ +import { lstat, readdirSync } from 'fs' +import { FunctionalService } from '../../../classes/functionalService' +import { ContextStep } from '../core/contextStep' + +import { join, basename, extname } from 'path' +import { set } from 'lodash' + +export class ServiceDiscoveryStep extends ContextStep { + public async execute(context) { + + context.files = context.files || [] + context.publishedFunctions = context.publishedFunctions || [] + + const path = context.serviceRoot + + let isDir = await this.isDirectory(path) + + let files = [path] + if (isDir) { + files = await this.getJsFiles(path) + } + + for (let file of files) { + this.collectFromFile(file, context) + } + + return context + } + + + private collectFromFile(file, context) { + const module = require(file) + + const name = basename(file) + const ext = extname(name) + const nameKey = name.substring(0, name.length - ext.length) + + Object.keys(module).forEach((key) => { + let exportItem = module[key] + + if (exportItem.serviceType && exportItem.serviceType.createInvoker) { + const item = { + service: exportItem.serviceType, + exportName: key, + invoker: exportItem, + handler: `${nameKey}.${key}`, + } + + if (context.files.indexOf(file) < 0) { + context.files.push(file) + } + + context.publishedFunctions.push(item) + } + + }) + } + + private getJsFiles(folder) { + let files = readdirSync(folder); + let filter = /\.js$/ + return files.filter(name => filter.test(name)).map(file => join(folder, file)) + } + private isDirectory(path) { + return new Promise((resolve, reject) => { + lstat(path, (err, stats) => { + if (err) return reject(err); + return resolve(stats.isDirectory()) + }); + }) + } + +} + +export const serviceDiscovery = new ServiceDiscoveryStep('serviceDiscovery') diff --git a/src/cli/context/steppes/setFunctionalEnvironment.ts b/src/cli/context/steppes/setFunctionalEnvironment.ts new file mode 100644 index 0000000..3cf2acf --- /dev/null +++ b/src/cli/context/steppes/setFunctionalEnvironment.ts @@ -0,0 +1,12 @@ +import { environment } from '../../../annotations' +import { ContextStep } from '../core/contextStep' +export class SetFunctionalEnvironmentStep extends ContextStep { + public async execute(context) { + for (let serviceDefinition of context.publishedFunctions) { + const setEnvAttrib = environment('FUNCTIONAL_ENVIRONMENT', context.FUNCTIONAL_ENVIRONMENT) + setEnvAttrib(serviceDefinition.service) + } + } +} + +export const setFunctionalEnvironment = new SetFunctionalEnvironmentStep('setFunctionalEnvironment') diff --git a/src/cli/context/steppes/tableDiscovery.ts b/src/cli/context/steppes/tableDiscovery.ts new file mode 100644 index 0000000..ed374b3 --- /dev/null +++ b/src/cli/context/steppes/tableDiscovery.ts @@ -0,0 +1,37 @@ +import { getMetadata, constants, __dynamoDBDefaults } from '../../../annotations' +import { ContextStep } from '../core/contextStep' + +export class TableDiscoveryStep extends ContextStep { + protected tableNameEnvRegexp = /_TABLE_NAME$/ + public async execute(context) { + const tablesToCreate = new Map() + + for (let serviceDefinition of context.publishedFunctions) { + let tableConfigs = getMetadata(constants.CLASS_DYNAMOTABLECONFIGURATIONKEY, serviceDefinition.service) || [] + for (const tableConfig of tableConfigs) { + if (tablesToCreate.has(tableConfig.tableName)) { + continue + } + + tablesToCreate.set(tableConfig.tableName, tableConfig) + } + + let metadata = getMetadata(constants.CLASS_ENVIRONMENTKEY, serviceDefinition.service) + if (metadata) { + let keys = Object.keys(metadata) + for (const key of keys) { + if (this.tableNameEnvRegexp.test(key) && !tablesToCreate.has(metadata[key])) { + tablesToCreate.set(metadata[key], { + TableName: metadata[key] + }) + } + } + } + } + + context.tableConfigs = Array.from(tablesToCreate.values()) + } +} + + +export const tableDiscovery = new TableDiscoveryStep('tableDiscovery') diff --git a/src/cli/extensions/classExtensions.ts b/src/cli/extensions/classExtensions.ts new file mode 100644 index 0000000..54420f4 --- /dev/null +++ b/src/cli/extensions/classExtensions.ts @@ -0,0 +1,14 @@ +import { callExtension } from '../../classes' +import { getPluginDefinitions } from '../project/config' + +export const cliCallExtension = (target: any, method: string, ...params) => { + callExtension(target, method, ...params) + + const pluginDefinitions = getPluginDefinitions() + for (const plugin of pluginDefinitions) { + if (plugin.config.extensions && typeof plugin.config.extensions[method] === 'function') { + + plugin.config.extensions[method](...params) + } + } +} \ No newline at end of file diff --git a/src/cli/extensions/hooks.ts b/src/cli/extensions/hooks.ts new file mode 100644 index 0000000..5edf021 --- /dev/null +++ b/src/cli/extensions/hooks.ts @@ -0,0 +1,55 @@ +import { ContextStep } from '../context/core/contextStep' +import { getPluginDefinitions, projectConfig } from '../project/config' +import { logger } from '../utilities/logger' + +export const CONTEXT_HOOK_MODIFIER_BEFORE = 'before' +export const CONTEXT_HOOK_MODIFIER_AFTER = 'after' +export const CONTEXT_HOOK_PROPERTIES = '__hooks' +export const CONTEXT_HOOK_DEFAULT_MODIFIER = 'default' +export const CONTEXT_HOOK_SEPARATOR = ':' + +export const hasCliCallContextHooks = (name, context, modifier = CONTEXT_HOOK_DEFAULT_MODIFIER) => { + return context && + context[CONTEXT_HOOK_PROPERTIES] && + context[CONTEXT_HOOK_PROPERTIES][modifier] && + typeof context[CONTEXT_HOOK_PROPERTIES][modifier][name] === 'function' ? true : false +} + +export const cliCallContextHooks = async (name, context, modifier = CONTEXT_HOOK_DEFAULT_MODIFIER) => { + if (context && + context[CONTEXT_HOOK_PROPERTIES] && + context[CONTEXT_HOOK_PROPERTIES][modifier] && + typeof context[CONTEXT_HOOK_PROPERTIES][modifier][name] === 'function' + ) { + return await context[CONTEXT_HOOK_PROPERTIES][modifier][name](context) + } +} + +export const callContextHookStep = (context) => { + context[CONTEXT_HOOK_PROPERTIES] = {} + + const pluginDefinitions = getPluginDefinitions() + for (const plugin of pluginDefinitions) { + if (plugin.config.hooks && typeof plugin.config.hooks === 'object') { + const keys = Object.keys(plugin.config.hooks) + + for (const key of keys) { + if (typeof plugin.config.hooks[key] !== 'function') { + logger.warn(`hook '${key}' in '${plugin.pluginInfo.path}' is invalid`) + continue + } + + let [modifier, stepName] = key.split(CONTEXT_HOOK_SEPARATOR) + if (!stepName) { + stepName = modifier + modifier = CONTEXT_HOOK_DEFAULT_MODIFIER + } + if (!stepName) continue + + context[CONTEXT_HOOK_PROPERTIES][modifier] = context[CONTEXT_HOOK_PROPERTIES][modifier] || {} + context[CONTEXT_HOOK_PROPERTIES][modifier][stepName] = plugin.config.hooks[key] + } + + } + } +} diff --git a/src/cli/index.ts b/src/cli/index.ts index 4fa405e..2ce490d 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -1,15 +1,18 @@ import { normalize, join } from 'path' import './utilities/config' +import './providers' -import { init as initDeployCommand } from './commands/deploy' -import { init as initLocalCommand } from './commands/local' -import { init as initMetadataCommand } from './commands/metadata' -import { init as initPlugins } from './plugins' +//built-in commands +import * as deploy from './commands/deploy' +import * as local from './commands/local' +import * as metadata from './commands/metadata' + +import { init as initProjectConfig, internalPluginLoad } from './project/init' export const init = (commander) => { - initDeployCommand(commander) - initLocalCommand(commander) - initMetadataCommand(commander) + internalPluginLoad(deploy) + internalPluginLoad(local) + internalPluginLoad(metadata) - initPlugins(commander) + initProjectConfig(commander) } \ No newline at end of file diff --git a/src/cli/project/config.ts b/src/cli/project/config.ts new file mode 100644 index 0000000..b67dc0c --- /dev/null +++ b/src/cli/project/config.ts @@ -0,0 +1,8 @@ +import { merge } from 'lodash' + +export const projectConfig: any = {} +export const updateConfig = (values) => merge(projectConfig, values) + +export const pluginDefinitions = [] +export const setPluginDefinitions = (pluginDefinition) => pluginDefinitions.push(pluginDefinition) +export const getPluginDefinitions = () => pluginDefinitions \ No newline at end of file diff --git a/src/cli/plugins/core/loadPlugins.ts b/src/cli/project/core/loadPlugins.ts similarity index 57% rename from src/cli/plugins/core/loadPlugins.ts rename to src/cli/project/core/loadPlugins.ts index ac79c97..21b2eca 100644 --- a/src/cli/plugins/core/loadPlugins.ts +++ b/src/cli/project/core/loadPlugins.ts @@ -6,14 +6,25 @@ export const PLUGIN_FOLDER = './node_modules/' export const PLUGIN_PREFIX = 'functionly-plugin-' export const PLUGIN_PATH_REGEXP = /^\./ +const pluginToLoad = [] + export const loadPlugins = (config) => { const pluginInitializers: any[] = [] const cwd = process.cwd() + for (const plugin of pluginToLoad) { + createPluginInitializer({ + pluginInitializers, + pluginInit: plugin, + pluginPath: null, + resolvedPluginPath: null + }) + } + if (Array.isArray(config.plugins)) { for (const pluginPath of config.plugins) { - + let resolvedPluginPath = null if (PLUGIN_PATH_REGEXP.test(pluginPath)) { resolvedPluginPath = resolvePath(pluginPath) @@ -24,10 +35,11 @@ export const loadPlugins = (config) => { try { const pluginInit = require(resolvedPluginPath) - pluginInitializers.push({ - init: pluginInit.default, - path: pluginPath, - resolvedPath: resolvedPluginPath + createPluginInitializer({ + pluginInitializers, + pluginInit, + pluginPath, + resolvedPluginPath }) } catch (e) { @@ -38,3 +50,17 @@ export const loadPlugins = (config) => { return pluginInitializers; } + +export const createPluginInitializer = ({ + pluginInitializers, pluginInit, pluginPath, resolvedPluginPath +}) => { + pluginInitializers.push({ + init: pluginInit.default, + path: pluginPath, + resolvedPath: resolvedPluginPath + }) +} + +export const internalPluginLoad = (plugin) => { + pluginToLoad.push(plugin) +} \ No newline at end of file diff --git a/src/cli/plugins/core/readConfig.ts b/src/cli/project/core/readConfig.ts similarity index 65% rename from src/cli/plugins/core/readConfig.ts rename to src/cli/project/core/readConfig.ts index 45d28ab..33e08a1 100644 --- a/src/cli/plugins/core/readConfig.ts +++ b/src/cli/project/core/readConfig.ts @@ -2,15 +2,14 @@ import { join } from 'path' import { readFileSync } from 'fs' import { logger } from '../../utilities/logger' -export const projectConfig = './functionly.json' +export const projectConfig = './functionly' export const readConfig = () => { const cwd = process.cwd() const configPath = join(cwd, projectConfig) try { - const fileContent = readFileSync(configPath, 'utf8') - return JSON.parse(fileContent) + return require(configPath) } catch (e) { return {} } diff --git a/src/cli/plugins/index.ts b/src/cli/project/init.ts similarity index 52% rename from src/cli/plugins/index.ts rename to src/cli/project/init.ts index 5ab6b75..0979057 100644 --- a/src/cli/plugins/index.ts +++ b/src/cli/project/init.ts @@ -1,27 +1,35 @@ +export { internalPluginLoad } from './core/loadPlugins' import { readConfig } from './core/readConfig' import { loadPlugins } from './core/loadPlugins' +import { projectConfig, updateConfig, setPluginDefinitions, getPluginDefinitions } from './config' import { logger } from '../utilities/logger' import { resolvePath } from '../utilities/cli' - - -export const pluginDefinition = [] +import { contextSteppes, createContext } from '../context' +import * as annotations from '../../annotations' + +export const pluginInitParam: any = { + logger, + resolvePath, + contextSteppes, createContext, + annotations, + projectConfig, + getPluginDefinitions +} export const init = (commander) => { - const config = readConfig() - const plugins = loadPlugins(config) + const configJson = readConfig() + updateConfig(configJson) + const plugins = loadPlugins(projectConfig) // init plugins for (const pluginInfo of plugins) { try { - const pluginConfig = pluginInfo.init({ - logger, - resolvePath - }) + const pluginConfig = pluginInfo.init(pluginInitParam) - pluginDefinition.push({ + setPluginDefinitions({ pluginInfo, config: pluginConfig || {} }) @@ -33,7 +41,8 @@ export const init = (commander) => { // init commands - for (const plugin of pluginDefinition) { + const pluginDefinitions = getPluginDefinitions() + for (const plugin of pluginDefinitions) { try { if (plugin.config.commands) { diff --git a/src/cli/providers/index.ts b/src/cli/providers/index.ts index 9da9552..6bce68d 100644 --- a/src/cli/providers/index.ts +++ b/src/cli/providers/index.ts @@ -1,22 +1,25 @@ import * as aws from './aws' import * as local from './local' import * as cf from './cloudFormation' -import { setFunctionalEnvironment } from '../context' +import { contextSteppes, ContextStep } from '../context' let environments = { aws, local, cf } -export const createEnvironment = async (context) => { +export class CreateEnvironmentStep extends ContextStep { + public async execute(context) { + let currentEnvironment = environments[context.deployTarget] - let currentEnvironment = environments[context.deployTarget] + if (!currentEnvironment) { + throw new Error(`unhandled deploy target: '${context.deployTarget}'`) + } - if (!currentEnvironment) { - throw new Error(`unhandled deploy target: '${context.deployTarget}'`) - } + if (currentEnvironment.FUNCTIONAL_ENVIRONMENT) { + context.FUNCTIONAL_ENVIRONMENT = currentEnvironment.FUNCTIONAL_ENVIRONMENT + await context.runStep(contextSteppes.setFunctionalEnvironment) + } - if (currentEnvironment.FUNCTIONAL_ENVIRONMENT) { - context.FUNCTIONAL_ENVIRONMENT = currentEnvironment.FUNCTIONAL_ENVIRONMENT - setFunctionalEnvironment(context) + await currentEnvironment.createEnvironment(context) } +} - await currentEnvironment.createEnvironment(context) -} \ No newline at end of file +export const createEnvironment = new CreateEnvironmentStep('createEnvironment') \ No newline at end of file diff --git a/src/cli/providers/local.ts b/src/cli/providers/local.ts index e151625..b84d204 100644 --- a/src/cli/providers/local.ts +++ b/src/cli/providers/local.ts @@ -4,5 +4,5 @@ import { getMetadata, constants } from '../../annotations' export const FUNCTIONAL_ENVIRONMENT = 'local' export const createEnvironment = async (context) => { - await createTables(context) + await context.runStep({ name: 'createTables', execute: createTables }) } diff --git a/src/cli/utilities/deploy.ts b/src/cli/utilities/deploy.ts deleted file mode 100644 index 191c0b9..0000000 --- a/src/cli/utilities/deploy.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createEnvironment } from '../providers' - -export const deploy = async (context) => { - await createEnvironment(context) -} \ No newline at end of file diff --git a/src/cli/utilities/local.ts b/src/cli/utilities/local.ts deleted file mode 100644 index e2112b3..0000000 --- a/src/cli/utilities/local.ts +++ /dev/null @@ -1,80 +0,0 @@ -import * as express from 'express' -import * as bodyParser from 'body-parser' -import * as cors from 'cors' -import { getMetadata, constants, getFunctionName } from '../../annotations' -import { config } from './config' - -const environmentConfigMiddleware = (serviceType) => { - return (req, res, next) => { - const environmentVariables = getMetadata(constants.CLASS_ENVIRONMENTKEY, serviceType) || {} - - let originals = {} - Object.keys(environmentVariables).forEach((key) => { - if (key in process.env) { - originals[key] = process.env[key] - } - - process.env[key] = environmentVariables[key] - }) - - - req.on("end", function () { - Object.keys(environmentVariables).forEach((key) => { - if (key in originals) { - process.env[key] = originals[key] - } else { - delete process.env[key] - } - }) - }); - - next() - } -} - -const logMiddleware = (enabled, serviceType) => { - return (req, res, next) => { - if (!enabled) return next() - - console.log(`${new Date().toISOString()} ${getFunctionName(serviceType)}`, JSON.stringify({ - url: req.url, - query: req.query, - params: req.params, - body: req.body, - headers: req.headers - }, null, 2)) - - next() - } -} - - -export const local = async (context) => { - let app = express() - app.use(bodyParser.json()) - - for (let serviceDefinition of context.publishedFunctions) { - let httpMetadata = getMetadata(constants.CLASS_APIGATEWAYKEY, serviceDefinition.service) - - for (let event of httpMetadata) { - const isLoggingEnabled = getMetadata(constants.CLASS_LOGKEY, serviceDefinition.service) - console.log(`${new Date().toISOString()} ${getFunctionName(serviceDefinition.service)} listening { path: '${event.path}', method: '${event.method}', cors: ${event.cors ? true : false} }`) - - if (event.cors) { - app.use(event.path, cors()) - } - - app[event.method]( - event.path, - logMiddleware(isLoggingEnabled, serviceDefinition.service), - environmentConfigMiddleware(serviceDefinition.service), - serviceDefinition.invoker - ) - } - } - - app.listen(context.localPort, function () { - process.env.FUNCTIONAL_LOCAL_PORT = context.localPort - console.log(`Example app listening on port ${process.env.FUNCTIONAL_LOCAL_PORT}!`) - }) -} diff --git a/src/providers/aws.ts b/src/providers/aws.ts index 73f5486..b446a3c 100644 --- a/src/providers/aws.ts +++ b/src/providers/aws.ts @@ -15,7 +15,7 @@ export class AWSProvider extends Provider { params[parameter.parameterIndex] = await this.awsParameterResolver(event, context, parameter) } - const r = await serviceInstance.handle.apply(serviceInstance, params) + const r = await serviceInstance.handle(...params) cb(null, r) return r } catch (e) { diff --git a/src/providers/index.ts b/src/providers/index.ts index ca370d3..d539593 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -26,7 +26,7 @@ export const getInvoker = (serviceType, params) => { if (typeof onHandleResult !== 'undefined') { return onHandleResult } - return await invoker.apply(this, params) + return await invoker(...params) } Object.defineProperty(invokeHandler, 'serviceType', { diff --git a/src/providers/local.ts b/src/providers/local.ts index 6fdae3e..b5012fd 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -14,7 +14,7 @@ export class LocalProvider extends Provider { params[parameter.parameterIndex] = await this.localParameterResolver(req, parameter) } - const r = await serviceInstance.handle.apply(serviceInstance, params) + const r = await serviceInstance.handle(...params) res.send(r) return r } catch (e) { From da34526a156fbbf82a1037f39285a9b9fc713665 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Fri, 2 Jun 2017 14:22:50 +0200 Subject: [PATCH 004/196] CHANGE: hook can apply to deploy steps --- src/cli/context/core/contextStep.ts | 17 ++++- src/cli/context/index.ts | 63 +++++++++------- src/cli/providers/aws.ts | 29 +++---- .../context/cloudFormationInit.ts | 14 ++-- .../cloudFormation/context/resources.ts | 13 ++-- .../cloudFormation/context/uploadTemplate.ts | 9 ++- src/cli/providers/cloudFormation/index.ts | 25 +++---- src/cli/providers/index.ts | 2 +- src/cli/providers/local.ts | 7 +- src/cli/utilities/aws/cloudFormation.ts | 13 ++-- src/cli/utilities/aws/dynamoDB.ts | 5 +- src/cli/utilities/aws/lambda.ts | 75 ++++++++++--------- src/cli/utilities/aws/s3Upload.ts | 34 ++++++--- src/cli/utilities/compress.ts | 5 +- src/cli/utilities/webpack.ts | 13 ++-- 15 files changed, 185 insertions(+), 139 deletions(-) diff --git a/src/cli/context/core/contextStep.ts b/src/cli/context/core/contextStep.ts index bf6542e..5f36673 100644 --- a/src/cli/context/core/contextStep.ts +++ b/src/cli/context/core/contextStep.ts @@ -1,4 +1,4 @@ -export const contextSteppes: { [x: string]: ContextStep } = {} +export const contextSteppes: { [x: string]: ContextStep | { name: string, execute: (c) => any } } = {} export class ContextStep { public name: string @@ -14,4 +14,19 @@ export class ContextStep { public execute(context) { } + + public static register(name, execute) { + if (contextSteppes[name]) { + throw new Error(`step name '${this.name}' already defined`) + } + + const step = { + name, + execute + } + + contextSteppes[name] = step + + return step + } } \ No newline at end of file diff --git a/src/cli/context/index.ts b/src/cli/context/index.ts index 6489bef..c515043 100644 --- a/src/cli/context/index.ts +++ b/src/cli/context/index.ts @@ -41,41 +41,52 @@ export class Context { [p: string]: any public async runStep(step: Function | any) { - const tab = depth++ - - const separator = (s = ' ') => { - let result = '' - for (var i = 0; i < tab; i++) { - result += s - } - return result - } + let result = undefined if (typeof step === 'function') { - await step(this) + result = await step(this) } else if (step && step.name && typeof step.execute === 'function') { - if (projectConfig.debug) logger.debug(`Context step run -----------${separator('--')}> ${step.name}`) - if (projectConfig.debug) logger.debug(`Context step before start ${separator()} ${step.name}`) - await cliCallContextHooks(step.name, this, CONTEXT_HOOK_MODIFIER_BEFORE) - if (projectConfig.debug) logger.debug(`Context step before end ${separator()} ${step.name}`) - - if (projectConfig.debug) logger.debug(`Context step start ${separator()} ${step.name}`) - if (hasCliCallContextHooks(step.name, this)) { - await cliCallContextHooks(step.name, this) - } else { - await step.execute(this) + + const tab = depth++ + const separator = (s = ' ') => { + let result = '' + for (var i = 0; i < tab; i++) { + result += s + } + return result } - if (projectConfig.debug) logger.debug(`Context step end ${separator()} ${step.name}`) - if (projectConfig.debug) logger.debug(`Context step after start ${separator()} ${step.name}`) - await cliCallContextHooks(step.name, this, CONTEXT_HOOK_MODIFIER_AFTER) - if (projectConfig.debug) logger.debug(`Context step after end ${separator()} ${step.name}`) - if (projectConfig.debug) logger.debug(`Context step complete ------${separator('--')}> ${step.name}`) + try { + + if (projectConfig.debug) logger.debug(`Context step run -----------${separator('--')}> ${step.name}`) + if (projectConfig.debug) logger.debug(`Context step before start ${separator()} ${step.name}`) + await cliCallContextHooks(step.name, this, CONTEXT_HOOK_MODIFIER_BEFORE) + if (projectConfig.debug) logger.debug(`Context step before end ${separator()} ${step.name}`) + + if (projectConfig.debug) logger.debug(`Context step start ${separator()} ${step.name}`) + if (hasCliCallContextHooks(step.name, this)) { + result = await cliCallContextHooks(step.name, this) + } else { + result = await step.execute(this) + } + if (projectConfig.debug) logger.debug(`Context step end ${separator()} ${step.name}`) + + if (projectConfig.debug) logger.debug(`Context step after start ${separator()} ${step.name}`) + await cliCallContextHooks(step.name, this, CONTEXT_HOOK_MODIFIER_AFTER) + if (projectConfig.debug) logger.debug(`Context step after end ${separator()} ${step.name}`) + if (projectConfig.debug) logger.debug(`Context step complete ------${separator('--')}> ${step.name}`) + } + catch (e) { + if (projectConfig.debug) logger.debug(`Context step exited --------${separator('--')}> ${step.name}`) + depth-- + throw e + } + depth-- } else { throw new Error(`context.runStep has invalid parameter '${step}'`) } - depth-- + return result } } diff --git a/src/cli/providers/aws.ts b/src/cli/providers/aws.ts index bb2e39b..03593f6 100644 --- a/src/cli/providers/aws.ts +++ b/src/cli/providers/aws.ts @@ -1,7 +1,7 @@ import { getFunctionName } from '../../annotations' import { bundle } from '../utilities/webpack' import { zip } from '../utilities/compress' -import { uploadZip } from '../utilities/aws/s3Upload' +import { uploadZipStep } from '../utilities/aws/s3Upload' import { createTables } from '../utilities/aws/dynamoDB' import { getLambdaFunction, @@ -12,37 +12,38 @@ import { updateLambdaFunctionConfiguration, updateLambdaFunctionTags } from '../utilities/aws/lambda' +import { ContextStep } from '../context' export const FUNCTIONAL_ENVIRONMENT = 'aws' -export const createEnvironment = async (context) => { - const date = new Date() - await bundle(context) - await zip(context) - await uploadZip(context, `services-${date.toISOString()}.zip`, context.zipData()) - await createTables(context) +export const createEnvironment = ContextStep.register('createEnvironment_aws', async (context) => { + await context.runStep(bundle) + await context.runStep(zip) + await context.runStep(uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData())) + await context.runStep(createTables) for (let serviceDefinition of context.publishedFunctions) { const serviceName = getFunctionName(serviceDefinition.service) if (serviceName) { - console.log(`${serviceName} deploying...`) + context.serviceDefinition = serviceDefinition try { - await getLambdaFunction(serviceDefinition, context) - await updateLambdaFunctionCode(serviceDefinition, context) - await updateLambdaFunctionConfiguration(serviceDefinition, context) - await updateLambdaFunctionTags(serviceDefinition, context) + await context.runStep(getLambdaFunction) + await context.runStep(updateLambdaFunctionCode) + await context.runStep(updateLambdaFunctionConfiguration) + await context.runStep(updateLambdaFunctionTags) } catch (e) { if (e.code === "ResourceNotFoundException") { - await createLambdaFunction(serviceDefinition, context) + await context.runStep(createLambdaFunction) } else { throw e } } + delete context.serviceDefinition console.log('completed') } } -} +}) diff --git a/src/cli/providers/cloudFormation/context/cloudFormationInit.ts b/src/cli/providers/cloudFormation/context/cloudFormationInit.ts index 6cc31d7..055f5f0 100644 --- a/src/cli/providers/cloudFormation/context/cloudFormationInit.ts +++ b/src/cli/providers/cloudFormation/context/cloudFormationInit.ts @@ -1,16 +1,12 @@ import { resolvePath } from '../../../utilities/cli' +import { projectConfig } from '../../../project/config' +import { ContextStep } from '../../../context' -export const cloudFormationInit = async (context) => { - const path = resolvePath('./package.json') - try { - const packageJson = require(path) - context.CloudFormationConfig = packageJson.CloudFormation || {} - } catch (e) { - context.CloudFormationConfig = {} - } +export const cloudFormationInit = ContextStep.register('cloudFormationInit', async (context) => { + context.CloudFormationConfig = projectConfig.CloudFormation || {} context.CloudFormationTemplate = { "AWSTemplateFormatVersion": "2010-09-09", "Resources": {} } -} \ No newline at end of file +}) diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 1123137..d45f30a 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -2,6 +2,7 @@ import { merge } from 'lodash' import { getMetadata, constants, getFunctionName, __dynamoDBDefaults } from '../../../../annotations' const { CLASS_DESCRIPTIONKEY, CLASS_ROLEKEY, CLASS_MEMORYSIZEKEY, CLASS_RUNTIMEKEY, CLASS_TIMEOUTKEY, CLASS_ENVIRONMENTKEY, CLASS_TAGKEY } = constants +import { ContextStep } from '../../../context' export const nameReplaceRegexp = /[^a-zA-Z0-9]/g export const normalizeName = (name: string) => { @@ -30,7 +31,7 @@ export const setResource = (context, name, resource) => { return name; } -export const roleResources = async (context) => { +export const roleResources = ContextStep.register('roleResources', async (context) => { const roleMap = new Map() @@ -129,9 +130,9 @@ export const roleResources = async (context) => { } -} +}) -export const tableResources = async (context) => { +export const tableResources = ContextStep.register('tableResources', async (context) => { for (const tableConfig of context.tableConfigs) { @@ -150,9 +151,9 @@ export const tableResources = async (context) => { setResource(context, resourceName, tableResource) } -} +}) -export const lambdaResources = async (context) => { +export const lambdaResources = ContextStep.register('lambdaResources', async (context) => { for (const serviceDefinition of context.publishedFunctions) { @@ -183,4 +184,4 @@ export const lambdaResources = async (context) => { setResource(context, resourceName, lambdaResource) } -} +}) diff --git a/src/cli/providers/cloudFormation/context/uploadTemplate.ts b/src/cli/providers/cloudFormation/context/uploadTemplate.ts index 1b4d25b..49717e3 100644 --- a/src/cli/providers/cloudFormation/context/uploadTemplate.ts +++ b/src/cli/providers/cloudFormation/context/uploadTemplate.ts @@ -1,8 +1,9 @@ -import { upload } from '../../../utilities/aws/s3Upload' +import { uploaderStep } from '../../../utilities/aws/s3Upload' +import { ContextStep } from '../../../context' -export const uploadTemplate = async (context) => { +export const uploadTemplate = ContextStep.register('uploadTemplate', async (context) => { const templateData = JSON.stringify(context.CloudFormationTemplate, null, 2); - const uploadresult = await upload(context, `services-${context.date.toISOString()}.template`, templateData, 'application/octet-stream') + const uploadresult = await context.runStep(uploaderStep(`services-${context.date.toISOString()}.template`, templateData, 'application/octet-stream')) context.S3CloudFormationTemplate = uploadresult.Key return uploadresult -} \ No newline at end of file +}) \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/index.ts b/src/cli/providers/cloudFormation/index.ts index e3922bd..3641404 100644 --- a/src/cli/providers/cloudFormation/index.ts +++ b/src/cli/providers/cloudFormation/index.ts @@ -1,7 +1,7 @@ import { getFunctionName } from '../../../annotations' import { bundle } from '../../utilities/webpack' import { zip } from '../../utilities/compress' -import { uploadZip } from '../../utilities/aws/s3Upload' +import { uploadZipStep } from '../../utilities/aws/s3Upload' import { createStack, updateStack, getTemplate } from '../../utilities/aws/cloudFormation' import { cloudFormationInit } from './context/cloudFormationInit' @@ -11,25 +11,24 @@ import { uploadTemplate } from './context/uploadTemplate' export const FUNCTIONAL_ENVIRONMENT = 'aws' export const createEnvironment = async (context) => { - await bundle(context) - await zip(context) - await uploadZip(context, `services-${context.date.toISOString()}.zip`, context.zipData()) + await context.runStep(bundle) + await context.runStep(zip) + await context.runStep(uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData())) + await context.runStep(cloudFormationInit) + await context.runStep(roleResources) + await context.runStep(tableResources) + await context.runStep(lambdaResources) - await cloudFormationInit(context) - await roleResources(context) - await tableResources(context) - await lambdaResources(context) - - await uploadTemplate(context) + await context.runStep(uploadTemplate) try { - await getTemplate(context) - await updateStack(context) + await context.runStep(getTemplate) + await context.runStep(updateStack) console.log('updated') } catch (e) { if (/^Stack with id .* does not exist$/.test(e.message)) { - await createStack(context) + await context.runStep(createStack) console.log('created') } else { console.log(e) diff --git a/src/cli/providers/index.ts b/src/cli/providers/index.ts index 6bce68d..26575a0 100644 --- a/src/cli/providers/index.ts +++ b/src/cli/providers/index.ts @@ -18,7 +18,7 @@ export class CreateEnvironmentStep extends ContextStep { await context.runStep(contextSteppes.setFunctionalEnvironment) } - await currentEnvironment.createEnvironment(context) + await context.runStep(currentEnvironment.createEnvironment) } } diff --git a/src/cli/providers/local.ts b/src/cli/providers/local.ts index b84d204..4fe8f19 100644 --- a/src/cli/providers/local.ts +++ b/src/cli/providers/local.ts @@ -1,8 +1,9 @@ import { createTables } from '../utilities/aws/dynamoDB' import { getMetadata, constants } from '../../annotations' +import { ContextStep } from '../context' export const FUNCTIONAL_ENVIRONMENT = 'local' -export const createEnvironment = async (context) => { - await context.runStep({ name: 'createTables', execute: createTables }) -} +export const createEnvironment = ContextStep.register('createEnvironment_local', async (context) => { + await context.runStep(createTables) +}) diff --git a/src/cli/utilities/aws/cloudFormation.ts b/src/cli/utilities/aws/cloudFormation.ts index 28c6745..8e8536b 100644 --- a/src/cli/utilities/aws/cloudFormation.ts +++ b/src/cli/utilities/aws/cloudFormation.ts @@ -1,6 +1,7 @@ import { CloudFormation } from 'aws-sdk' import { merge, pick } from 'lodash' import { config } from '../../utilities/config' +import { ContextStep } from '../../context' export const UPDATE_STACK_PRPERTIES = ['StackName', 'Capabilities', 'ClientRequestToken', 'Parameters', 'ResourceTypes', 'RoleARN', 'StackPolicyBody', @@ -23,7 +24,7 @@ const initAWSSDK = (context) => { -export const createStack = (context) => { +export const createStack = ContextStep.register('createStack', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { const cfConfig = pick(context.CloudFormationConfig, CREATE_STACK_PRPERTIES) @@ -36,9 +37,9 @@ export const createStack = (context) => { else resolve(data); }); }) -} +}) -export const updateStack = (context) => { +export const updateStack = ContextStep.register('updateStack', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { const cfConfig = pick(context.CloudFormationConfig, UPDATE_STACK_PRPERTIES) @@ -52,9 +53,9 @@ export const updateStack = (context) => { else resolve(data); }); }) -} +}) -export const getTemplate = (context) => { +export const getTemplate = ContextStep.register('getTemplate', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { @@ -66,4 +67,4 @@ export const getTemplate = (context) => { else resolve(data); }); }) -} \ No newline at end of file +}) \ No newline at end of file diff --git a/src/cli/utilities/aws/dynamoDB.ts b/src/cli/utilities/aws/dynamoDB.ts index e73d65b..fa1501f 100644 --- a/src/cli/utilities/aws/dynamoDB.ts +++ b/src/cli/utilities/aws/dynamoDB.ts @@ -2,6 +2,7 @@ import { DynamoDB } from 'aws-sdk' import { merge } from 'lodash' import { config } from '../config' import { __dynamoDBDefaults } from '../../../annotations' +import { ContextStep } from '../../context' let dynamoDB = null; const initAWSSDK = (context) => { @@ -21,7 +22,7 @@ const initAWSSDK = (context) => { } -export const createTables = async (context) => { +export const createTables = ContextStep.register('createTables', async (context) => { initAWSSDK(context) for (let tableConfig of context.tableConfigs) { @@ -34,7 +35,7 @@ export const createTables = async (context) => { } } } -} +}) export const createTable = (tableConfig, context) => { initAWSSDK(context) diff --git a/src/cli/utilities/aws/lambda.ts b/src/cli/utilities/aws/lambda.ts index 246d0be..e5995d6 100644 --- a/src/cli/utilities/aws/lambda.ts +++ b/src/cli/utilities/aws/lambda.ts @@ -2,6 +2,7 @@ import { Lambda } from 'aws-sdk' import { merge, difference } from 'lodash' import { config } from '../../utilities/config' import { getMetadata, constants, getFunctionName } from '../../../annotations' +import { ContextStep } from '../../context' let lambda = null; @@ -19,7 +20,7 @@ const initAWSSDK = (context) => { -export const createLambdaFunction = (serviceDefinition, context) => { +export const createLambdaFunction = ContextStep.register('createLambdaFunction', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { @@ -27,18 +28,18 @@ export const createLambdaFunction = (serviceDefinition, context) => { S3Bucket: context.awsBucket, S3Key: context.S3Zip }, - Description: getMetadata(constants.CLASS_DESCRIPTIONKEY, serviceDefinition.service), - FunctionName: getFunctionName(serviceDefinition.service), - Handler: serviceDefinition.handler, - MemorySize: getMetadata(constants.CLASS_MEMORYSIZEKEY, serviceDefinition.service), + Description: getMetadata(constants.CLASS_DESCRIPTIONKEY, context.serviceDefinition.service), + FunctionName: getFunctionName(context.serviceDefinition.service), + Handler: context.serviceDefinition.handler, + MemorySize: getMetadata(constants.CLASS_MEMORYSIZEKEY, context.serviceDefinition.service), Publish: true, - Role: getMetadata(constants.CLASS_ROLEKEY, serviceDefinition.service), - Runtime: getMetadata(constants.CLASS_RUNTIMEKEY, serviceDefinition.service) || "nodejs6.10", - Timeout: getMetadata(constants.CLASS_TIMEOUTKEY, serviceDefinition.service), + Role: getMetadata(constants.CLASS_ROLEKEY, context.serviceDefinition.service), + Runtime: getMetadata(constants.CLASS_RUNTIMEKEY, context.serviceDefinition.service) || "nodejs6.10", + Timeout: getMetadata(constants.CLASS_TIMEOUTKEY, context.serviceDefinition.service), Environment: { - Variables: getMetadata(constants.CLASS_ENVIRONMENTKEY, serviceDefinition.service) + Variables: getMetadata(constants.CLASS_ENVIRONMENTKEY, context.serviceDefinition.service) }, - Tags: getMetadata(constants.CLASS_TAGKEY, serviceDefinition.service), + Tags: getMetadata(constants.CLASS_TAGKEY, context.serviceDefinition.service), VpcConfig: { } }; @@ -48,52 +49,52 @@ export const createLambdaFunction = (serviceDefinition, context) => { else resolve(data); }); }) -} +}) -export const getLambdaFunction = (serviceDefinition, context) => { +export const getLambdaFunction = ContextStep.register('getLambdaFunction', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { - FunctionName: getFunctionName(serviceDefinition.service) + FunctionName: getFunctionName(context.serviceDefinition.service) }; lambda.getFunction(params, function (err, data) { if (err) reject(err) else resolve(data); }); }) -} +}) -export const deleteLambdaFunction = (serviceDefinition, context) => { +export const deleteLambdaFunction = ContextStep.register('deleteLambdaFunction', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { - FunctionName: getFunctionName(serviceDefinition.service) + FunctionName: getFunctionName(context.serviceDefinition.service) }; lambda.deleteFunction(params, function (err, data) { if (err) reject(err) else resolve(data); }); }) -} +}) -export const publishLambdaFunction = (serviceDefinition, context) => { +export const publishLambdaFunction = ContextStep.register('publishLambdaFunction', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { - FunctionName: getFunctionName(serviceDefinition.service) + FunctionName: getFunctionName(context.serviceDefinition.service) }; lambda.publishVersion(params, function (err, data) { if (err) reject(err) else resolve(data); }); }) -} +}) -export const updateLambdaFunctionCode = (serviceDefinition, context) => { +export const updateLambdaFunctionCode = ContextStep.register('updateLambdaFunctionCode', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { - FunctionName: getFunctionName(serviceDefinition.service), + FunctionName: getFunctionName(context.serviceDefinition.service), S3Bucket: context.awsBucket, S3Key: context.S3Zip, Publish: true @@ -103,22 +104,22 @@ export const updateLambdaFunctionCode = (serviceDefinition, context) => { else resolve(data); }); }) -} +}) -export const updateLambdaFunctionConfiguration = (serviceDefinition, context) => { +export const updateLambdaFunctionConfiguration = ContextStep.register('updateLambdaFunctionConfiguration', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { - FunctionName: getFunctionName(serviceDefinition.service), - Description: getMetadata(constants.CLASS_DESCRIPTIONKEY, serviceDefinition.service), + FunctionName: getFunctionName(context.serviceDefinition.service), + Description: getMetadata(constants.CLASS_DESCRIPTIONKEY, context.serviceDefinition.service), Environment: { - Variables: getMetadata(constants.CLASS_ENVIRONMENTKEY, serviceDefinition.service) + Variables: getMetadata(constants.CLASS_ENVIRONMENTKEY, context.serviceDefinition.service) }, - Handler: serviceDefinition.handler, - MemorySize: getMetadata(constants.CLASS_MEMORYSIZEKEY, serviceDefinition.service), - Role: getMetadata(constants.CLASS_ROLEKEY, serviceDefinition.service), - Runtime: getMetadata(constants.CLASS_RUNTIMEKEY, serviceDefinition.service) || "nodejs6.10", - Timeout: getMetadata(constants.CLASS_TIMEOUTKEY, serviceDefinition.service), + Handler: context.serviceDefinition.handler, + MemorySize: getMetadata(constants.CLASS_MEMORYSIZEKEY, context.serviceDefinition.service), + Role: getMetadata(constants.CLASS_ROLEKEY, context.serviceDefinition.service), + Runtime: getMetadata(constants.CLASS_RUNTIMEKEY, context.serviceDefinition.service) || "nodejs6.10", + Timeout: getMetadata(constants.CLASS_TIMEOUTKEY, context.serviceDefinition.service), VpcConfig: { } }; @@ -127,12 +128,12 @@ export const updateLambdaFunctionConfiguration = (serviceDefinition, context) => else resolve(data); }); }) -} +}) -export const updateLambdaFunctionTags = async (serviceDefinition, context) => { +export const updateLambdaFunctionTags = ContextStep.register('updateLambdaFunctionTags', async (context) => { initAWSSDK(context) - const getLambdaFunctionResult = await getLambdaFunction(serviceDefinition, context) - const Tags = getMetadata(constants.CLASS_TAGKEY, serviceDefinition.service) || {} + const getLambdaFunctionResult = await context.runStep(getLambdaFunction) + const Tags = getMetadata(constants.CLASS_TAGKEY, context.serviceDefinition.service) || {} const listTagParams = { Resource: getLambdaFunctionResult.Configuration.FunctionArn @@ -156,7 +157,7 @@ export const updateLambdaFunctionTags = async (serviceDefinition, context) => { await tagResource(tagResourceParams, context) } -} +}) export const listTags = (params, context) => { initAWSSDK(context) diff --git a/src/cli/utilities/aws/s3Upload.ts b/src/cli/utilities/aws/s3Upload.ts index b351cf1..4ef0109 100644 --- a/src/cli/utilities/aws/s3Upload.ts +++ b/src/cli/utilities/aws/s3Upload.ts @@ -1,6 +1,7 @@ import { S3 } from 'aws-sdk' import { merge } from 'lodash' import { config } from '../config' +import { ContextStep } from '../../context' let s3 = null; const initAWSSDK = (context) => { @@ -15,21 +16,36 @@ const initAWSSDK = (context) => { return s3 } -export const uploadZip = async (context, name, data) => { - const uploadResult = await upload(context, name, data, 'application/zip') - context.S3Zip = uploadResult.Key - return uploadResult +export const uploadZipStep = (name, data) => { + return async (context) => { + const step = uploaderStep(name, data, 'application/zip') + const uploadResult = await context.runStep(step) + context.S3Zip = uploadResult.Key + return uploadResult + } } +export const uploaderStep = (name, data, contentType) => { + return async (context) => { + context.upload = { + name, + data, + contentType + } + const uploadResult = await context.runStep(uploadToAws) + delete context.upload + return uploadResult + } +} -export const upload = (context, name, data, contentType) => { +export const uploadToAws = ContextStep.register('uploadToAws', async (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = merge({}, config.S3, { Bucket: context.awsBucket, - Body: new Buffer(data, 'binary'), - Key: name, - ContentType: contentType + Body: new Buffer(context.upload.data, 'binary'), + Key: context.upload.name, + ContentType: context.upload.contentType }) s3.putObject(params, (err, res) => { @@ -37,4 +53,4 @@ export const upload = (context, name, data, contentType) => { return resolve(params) }) }) -} \ No newline at end of file +}) \ No newline at end of file diff --git a/src/cli/utilities/compress.ts b/src/cli/utilities/compress.ts index 0b01a21..88183de 100644 --- a/src/cli/utilities/compress.ts +++ b/src/cli/utilities/compress.ts @@ -1,8 +1,9 @@ import * as nodeZip from 'node-zip' import { basename, join } from 'path' import { readFileSync, writeFileSync } from 'fs' +import { ContextStep } from '../context' -export const zip = (context) => { +export const zip = ContextStep.register('zip', (context) => { let compressor = new nodeZip() for (const file of context.files) { @@ -11,4 +12,4 @@ export const zip = (context) => { let zipData = compressor.generate({ base64: false, compression: 'DEFLATE' }); context.zipData = () => zipData -} \ No newline at end of file +}) \ No newline at end of file diff --git a/src/cli/utilities/webpack.ts b/src/cli/utilities/webpack.ts index 21fcdad..e3f93b5 100644 --- a/src/cli/utilities/webpack.ts +++ b/src/cli/utilities/webpack.ts @@ -2,12 +2,13 @@ import { merge } from 'lodash' import * as webpack from 'webpack' import { config } from './config' import { basename, extname, join } from 'path' +import { ContextStep } from '../context' -export const bundle = (context) => { +export const bundle = ContextStep.register('bundle', (context) => { - return new Promise((resolve, reject) => { - const webpackConfig = createConfig(context) + return new Promise(async (resolve, reject) => { + const webpackConfig = await context.runStep(bundleConfig) webpack(webpackConfig, function (err, stats) { if (err) return reject() @@ -34,9 +35,9 @@ export const bundle = (context) => { resolve() }); }) -} +}) -export const createConfig = (context) => { +export const bundleConfig = ContextStep.register('bundleConfig', (context) => { let entry = {} context.files.forEach((file) => { let name = basename(file) @@ -55,4 +56,4 @@ export const createConfig = (context) => { }) return webpackConfig -} \ No newline at end of file +}) \ No newline at end of file From db11f0538005986b6b2864150f769ea37f1c76b6 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 6 Jun 2017 11:09:25 +0200 Subject: [PATCH 005/196] ADD: deploy provider plugins --- src/cli/providers/aws.ts | 57 +++++++++++------------ src/cli/providers/cloudFormation/index.ts | 52 ++++++++++----------- src/cli/providers/index.ts | 21 +++++++-- src/cli/providers/local.ts | 11 +++-- 4 files changed, 78 insertions(+), 63 deletions(-) diff --git a/src/cli/providers/aws.ts b/src/cli/providers/aws.ts index 03593f6..569345c 100644 --- a/src/cli/providers/aws.ts +++ b/src/cli/providers/aws.ts @@ -14,36 +14,35 @@ import { } from '../utilities/aws/lambda' import { ContextStep } from '../context' -export const FUNCTIONAL_ENVIRONMENT = 'aws' +export const aws = { + FUNCTIONAL_ENVIRONMENT: 'aws', + createEnvironment: ContextStep.register('createEnvironment_aws', async (context) => { + await context.runStep(bundle) + await context.runStep(zip) + await context.runStep(uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData())) + await context.runStep(createTables) -export const createEnvironment = ContextStep.register('createEnvironment_aws', async (context) => { - await context.runStep(bundle) - await context.runStep(zip) - await context.runStep(uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData())) - await context.runStep(createTables) - - for (let serviceDefinition of context.publishedFunctions) { - const serviceName = getFunctionName(serviceDefinition.service) - if (serviceName) { - console.log(`${serviceName} deploying...`) - context.serviceDefinition = serviceDefinition - try { - await context.runStep(getLambdaFunction) - await context.runStep(updateLambdaFunctionCode) - await context.runStep(updateLambdaFunctionConfiguration) - await context.runStep(updateLambdaFunctionTags) - } catch (e) { - if (e.code === "ResourceNotFoundException") { - await context.runStep(createLambdaFunction) - } else { - throw e + for (let serviceDefinition of context.publishedFunctions) { + const serviceName = getFunctionName(serviceDefinition.service) + if (serviceName) { + console.log(`${serviceName} deploying...`) + context.serviceDefinition = serviceDefinition + try { + await context.runStep(getLambdaFunction) + await context.runStep(updateLambdaFunctionCode) + await context.runStep(updateLambdaFunctionConfiguration) + await context.runStep(updateLambdaFunctionTags) + } catch (e) { + if (e.code === "ResourceNotFoundException") { + await context.runStep(createLambdaFunction) + } else { + throw e + } } - } - delete context.serviceDefinition + delete context.serviceDefinition - console.log('completed') + console.log('completed') + } } - } -}) - - + }) +} diff --git a/src/cli/providers/cloudFormation/index.ts b/src/cli/providers/cloudFormation/index.ts index 3641404..f27db88 100644 --- a/src/cli/providers/cloudFormation/index.ts +++ b/src/cli/providers/cloudFormation/index.ts @@ -8,33 +8,33 @@ import { cloudFormationInit } from './context/cloudFormationInit' import { tableResources, lambdaResources, roleResources } from './context/resources' import { uploadTemplate } from './context/uploadTemplate' -export const FUNCTIONAL_ENVIRONMENT = 'aws' - -export const createEnvironment = async (context) => { - await context.runStep(bundle) - await context.runStep(zip) - await context.runStep(uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData())) - - await context.runStep(cloudFormationInit) - await context.runStep(roleResources) - await context.runStep(tableResources) - await context.runStep(lambdaResources) - - await context.runStep(uploadTemplate) - - try { - await context.runStep(getTemplate) - await context.runStep(updateStack) - console.log('updated') - } catch (e) { - if (/^Stack with id .* does not exist$/.test(e.message)) { - await context.runStep(createStack) - console.log('created') - } else { - console.log(e) - throw e +export const cloudFormation = { + FUNCTIONAL_ENVIRONMENT: 'aws', + createEnvironment: async (context) => { + await context.runStep(bundle) + await context.runStep(zip) + await context.runStep(uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData())) + + await context.runStep(cloudFormationInit) + await context.runStep(roleResources) + await context.runStep(tableResources) + await context.runStep(lambdaResources) + + await context.runStep(uploadTemplate) + + try { + await context.runStep(getTemplate) + await context.runStep(updateStack) + console.log('updated') + } catch (e) { + if (/^Stack with id .* does not exist$/.test(e.message)) { + await context.runStep(createStack) + console.log('created') + } else { + console.log(e) + throw e + } } } } - diff --git a/src/cli/providers/index.ts b/src/cli/providers/index.ts index 26575a0..47e994b 100644 --- a/src/cli/providers/index.ts +++ b/src/cli/providers/index.ts @@ -1,7 +1,8 @@ -import * as aws from './aws' -import * as local from './local' -import * as cf from './cloudFormation' +import { aws } from './aws' +import { local } from './local' +import { cloudFormation as cf } from './cloudFormation' import { contextSteppes, ContextStep } from '../context' +import { getPluginDefinitions } from '../project/config' let environments = { aws, local, cf } @@ -9,6 +10,20 @@ export class CreateEnvironmentStep extends ContextStep { public async execute(context) { let currentEnvironment = environments[context.deployTarget] + if (!currentEnvironment) { + const plugins = getPluginDefinitions() + for (const plugin of plugins) { + if (plugin.config.deployProviders && + plugin.config.deployProviders[context.deployTarget] && + plugin.config.deployProviders[context.deployTarget].createEnvironment + ) { + currentEnvironment = plugin.config.deployProviders[context.deployTarget] + break; + } + } + } + + if (!currentEnvironment) { throw new Error(`unhandled deploy target: '${context.deployTarget}'`) } diff --git a/src/cli/providers/local.ts b/src/cli/providers/local.ts index 4fe8f19..b498cf0 100644 --- a/src/cli/providers/local.ts +++ b/src/cli/providers/local.ts @@ -2,8 +2,9 @@ import { createTables } from '../utilities/aws/dynamoDB' import { getMetadata, constants } from '../../annotations' import { ContextStep } from '../context' -export const FUNCTIONAL_ENVIRONMENT = 'local' - -export const createEnvironment = ContextStep.register('createEnvironment_local', async (context) => { - await context.runStep(createTables) -}) +export const local = { + FUNCTIONAL_ENVIRONMENT: 'local', + createEnvironment: ContextStep.register('createEnvironment_local', async (context) => { + await context.runStep(createTables) + }) +} From 92844e0fe49deca7e3216090f47fc8a756db3856 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 6 Jun 2017 13:44:46 +0200 Subject: [PATCH 006/196] ADD: runtime provider plugins --- src/index.ts | 3 ++- src/providers/index.ts | 24 ++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 3020dec..dbac77c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ -export * from './classes' +export { Service, FunctionalApi, FunctionalService, DynamoDB, callExtension } from './classes' +export { addProvider, removeProvider, Provider, AWSProvider, LocalProvider, DeployProvider } from './providers' import * as _annotations from './annotations' export const annotations = _annotations diff --git a/src/providers/index.ts b/src/providers/index.ts index d539593..113fa6b 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -2,12 +2,32 @@ import { get } from 'lodash' import { constants, getOwnMetadata } from '../annotations' import { callExtension } from '../classes' +export { Provider } from './core/provider' +export { AWSProvider } from './aws' +export { LocalProvider } from './local' +export { DeployProvider } from './deploy' + +import { Provider } from './core/provider' import { provider as aws } from './aws' import { provider as local } from './local' import { provider as deploy } from './deploy' -let environments = { aws, local, deploy } -let invokeEnvironments = { aws, local } +let environments = {} +let invokeEnvironments = {} + +export const addProvider = (name, provider: Provider) => { + environments[name] = provider + invokeEnvironments[name] = provider +} + +export const removeProvider = (name) => { + delete environments[name] + delete invokeEnvironments[name] +} + +addProvider('aws', aws) +addProvider('local', local) +addProvider('deploy', deploy) export const getInvoker = (serviceType, params) => { const environment = process.env.FUNCTIONAL_ENVIRONMENT; From 0dbb436a4de7e34a3dd556f6024b068f5609179f Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 6 Jun 2017 14:52:17 +0200 Subject: [PATCH 007/196] ADD: command line argument defaults from functionly.json --- src/cli/commands/deploy.ts | 21 +++++++++++++-------- src/cli/commands/local.ts | 17 ++++++++++------- src/cli/commands/metadata.ts | 15 +++++++++------ src/cli/project/init.ts | 3 ++- src/cli/utilities/cli.ts | 8 +++++++- 5 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/cli/commands/deploy.ts b/src/cli/commands/deploy.ts index 9331214..0453f74 100644 --- a/src/cli/commands/deploy.ts +++ b/src/cli/commands/deploy.ts @@ -1,21 +1,26 @@ -export default ({ createContext, contextSteppes: { createEnvironment } }) => { +export default ({ createContext, contextSteppes: { createEnvironment }, projectConfig, requireValue }) => { return { commands({ commander }) { commander - .command('deploy ') + .command('deploy [target] [path]') .description('deploy functional services') .option('--aws-region ', 'AWS_REGION') .option('--aws-bucket ', 'aws bucket') .action(async (target, path, command) => { process.env.FUNCTIONAL_ENVIRONMENT = 'deploy' - const context = await createContext(path, { - deployTarget: target, - awsRegion: command.awsRegion, - awsBucket: command.awsBucket - }) - try { + const entryPoint = requireValue(path || projectConfig.main, 'entry point') + const deployTarget = requireValue(target || projectConfig.deployTarget, 'missing deploy target') + const awsRegion = requireValue(command.awsRegion || projectConfig.awsRegion, 'awsRegion') + const awsBucket = requireValue(command.awsBucket || projectConfig.awsBucket, 'awsBucket') + + const context = await createContext(entryPoint, { + deployTarget, + awsRegion, + awsBucket + }) + await context.runStep(createEnvironment) console.log(`done`) diff --git a/src/cli/commands/local.ts b/src/cli/commands/local.ts index d5f5986..de2a0d7 100644 --- a/src/cli/commands/local.ts +++ b/src/cli/commands/local.ts @@ -2,7 +2,7 @@ import * as express from 'express' import * as bodyParser from 'body-parser' import * as cors from 'cors' -export default ({ createContext, annotations: { getMetadata, constants, getFunctionName } }) => { +export default ({ createContext, annotations: { getMetadata, constants, getFunctionName }, projectConfig, requireValue }) => { const startLocal = async (context) => { let app = express() @@ -81,17 +81,20 @@ export default ({ createContext, annotations: { getMetadata, constants, getFunct return { commands({ commander }) { commander - .command('local ') + .command('local [port] [path]') .description('run functional service local') .action(async (port, path, command) => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' - const context = await createContext(path, { - deployTarget: 'local', - localPort: port - }) - try { + const entryPoint = requireValue(path || projectConfig.main, 'entry point') + const localPort = requireValue(port || projectConfig.localPort, 'localPort') + + const context = await createContext(entryPoint, { + deployTarget: 'local', + localPort + }) + await context.runStep({ name: 'startLocal', execute: startLocal }) console.log(`done`) diff --git a/src/cli/commands/metadata.ts b/src/cli/commands/metadata.ts index 4c94403..afed9de 100644 --- a/src/cli/commands/metadata.ts +++ b/src/cli/commands/metadata.ts @@ -1,18 +1,21 @@ -export default ({ createContext, annotations: { getMetadata, getMetadataKeys } }) => { +export default ({ createContext, annotations: { getMetadata, getMetadataKeys }, projectConfig, requireValue }) => { return { commands({ commander }) { commander - .command('metadata ') + .command('metadata [target] [path]') .description('service metadata') .action(async (target, path, command) => { process.env.FUNCTIONAL_ENVIRONMENT = 'deploy' - const context = await createContext(path, { - deployTarget: target - }) - try { + const entryPoint = requireValue(path || projectConfig.main, 'entry point') + const deployTarget = requireValue(target || projectConfig.deployTarget, 'missing deploy target') + + + const context = await createContext(entryPoint, { + deployTarget + }) console.log(JSON.stringify(context, null, 4)) diff --git a/src/cli/project/init.ts b/src/cli/project/init.ts index 0979057..04ad314 100644 --- a/src/cli/project/init.ts +++ b/src/cli/project/init.ts @@ -4,13 +4,14 @@ import { loadPlugins } from './core/loadPlugins' import { projectConfig, updateConfig, setPluginDefinitions, getPluginDefinitions } from './config' import { logger } from '../utilities/logger' -import { resolvePath } from '../utilities/cli' +import { resolvePath, requireValue } from '../utilities/cli' import { contextSteppes, createContext } from '../context' import * as annotations from '../../annotations' export const pluginInitParam: any = { logger, resolvePath, + requireValue, contextSteppes, createContext, annotations, projectConfig, diff --git a/src/cli/utilities/cli.ts b/src/cli/utilities/cli.ts index 38dee5c..d189a0e 100644 --- a/src/cli/utilities/cli.ts +++ b/src/cli/utilities/cli.ts @@ -6,4 +6,10 @@ export let resolvePath = (path) => { } return path; -} \ No newline at end of file +} + +export const requireValue = (value, msg) => { + if(typeof value !== 'undefined') return value + + throw new Error(`missing value '${msg}'`) +} From 4e5a314581d62fdfebf53f55520750fc8774b6a1 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Wed, 7 Jun 2017 16:53:56 +0200 Subject: [PATCH 008/196] ADD: serverless deploytarget and command --- package.json | 9 +- src/annotations/parameters/inject.ts | 18 +- src/cli/commands/local.ts | 7 +- src/cli/commands/serverless.ts | 237 ++++++++++++++++++++ src/cli/context/steppes/serviceDiscovery.ts | 1 + src/cli/index.ts | 2 + src/cli/project/init.ts | 7 +- src/providers/aws.ts | 5 +- src/providers/local.ts | 3 +- 9 files changed, 271 insertions(+), 18 deletions(-) create mode 100644 src/cli/commands/serverless.ts diff --git a/package.json b/package.json index 17f199e..d303bf0 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ "reflect-metadata": "^0.1.10", "request": "^2.81.0", "webpack": "^2.5.1", - "winston": "^2.3.0" + "winston": "^2.3.0", + "yamljs": "^0.2.10" }, "bin": { "functionly": "./lib/src/cli/cli.js" @@ -54,6 +55,8 @@ "StackName": "TestStack", "OnFailure": "ROLLBACK", "TimeoutInMinutes": 10, - "Capabilities": ["CAPABILITY_NAMED_IAM"] + "Capabilities": [ + "CAPABILITY_NAMED_IAM" + ] } -} \ No newline at end of file +} diff --git a/src/annotations/parameters/inject.ts b/src/annotations/parameters/inject.ts index 0680227..7bb0dc8 100644 --- a/src/annotations/parameters/inject.ts +++ b/src/annotations/parameters/inject.ts @@ -21,17 +21,23 @@ export const inject = (type: any, ...params): any => { defineMetadata(PARAMETER_PARAMKEY, [...existingParameters], target, targetKey); const injectTarget = type - const injectMetadata = getMetadata(CLASS_ENVIRONMENTKEY, injectTarget) || {} const metadata = getMetadata(CLASS_ENVIRONMENTKEY, target) || {} - if (injectMetadata) { - Object.keys(injectMetadata).forEach((key) => { - metadata[key] = injectMetadata[key] - }) + if (injectTarget.createInvoker) { + const funcName = getFunctionName(injectTarget) || 'undefined' + metadata[`FUNCTIONAL_SERVICE_${funcName.toUpperCase()}`] = funcName defineMetadata(CLASS_ENVIRONMENTKEY, { ...metadata }, target); + } else { + const injectMetadata = getMetadata(CLASS_ENVIRONMENTKEY, injectTarget) || {} + if (injectMetadata) { + Object.keys(injectMetadata).forEach((key) => { + metadata[key] = injectMetadata[key] + }) + defineMetadata(CLASS_ENVIRONMENTKEY, { ...metadata }, target); + } } const injectTableConfig = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, injectTarget) || [] const tableConfig = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, target) || [] - defineMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, [ ...tableConfig, ...injectTableConfig ], target); + defineMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, [...tableConfig, ...injectTableConfig], target); } } diff --git a/src/cli/commands/local.ts b/src/cli/commands/local.ts index de2a0d7..abdcebf 100644 --- a/src/cli/commands/local.ts +++ b/src/cli/commands/local.ts @@ -12,15 +12,16 @@ export default ({ createContext, annotations: { getMetadata, constants, getFunct let httpMetadata = getMetadata(constants.CLASS_APIGATEWAYKEY, serviceDefinition.service) for (let event of httpMetadata) { + const normalizedPath = /^\//.test( event.path) ? event.path : `/${event.path}` const isLoggingEnabled = getMetadata(constants.CLASS_LOGKEY, serviceDefinition.service) - console.log(`${new Date().toISOString()} ${getFunctionName(serviceDefinition.service)} listening { path: '${event.path}', method: '${event.method}', cors: ${event.cors ? true : false} }`) + console.log(`${new Date().toISOString()} ${getFunctionName(serviceDefinition.service)} listening { path: '${normalizedPath}', method: '${event.method}', cors: ${event.cors ? true : false} }`) if (event.cors) { - app.use(event.path, cors()) + app.use(normalizedPath, cors()) } app[event.method]( - event.path, + normalizedPath, logMiddleware(isLoggingEnabled, serviceDefinition.service), environmentConfigMiddleware(serviceDefinition.service), serviceDefinition.invoker diff --git a/src/cli/commands/serverless.ts b/src/cli/commands/serverless.ts new file mode 100644 index 0000000..9f57036 --- /dev/null +++ b/src/cli/commands/serverless.ts @@ -0,0 +1,237 @@ +import { writeFileSync } from 'fs' +import { extname } from 'path' +import { merge } from 'lodash' +import { stringify } from 'yamljs' + +export const FUNCTIONAL_ENVIRONMENT = 'aws' + +export default (api) => { + const { + createContext, + annotations: { + getMetadata, getMetadataKeys, getFunctionName, + constants: { + CLASS_RUNTIMEKEY, + CLASS_ENVIRONMENTKEY, + CLASS_APIGATEWAYKEY + }, + __dynamoDBDefaults + }, + projectConfig, + requireValue, resolvePath, + contextSteppes + } = api + + const serverlessConfig = async (context) => { + let projectName = 'unknown' + try { + projectName = require(resolvePath('./package.json')).name || projectName + } catch (e) { } + + context.serverless = { + service: projectName + } + } + + const providerConfig = async (context) => { + context.serverless.provider = { + name: FUNCTIONAL_ENVIRONMENT, + region: context.awsRegion, + stage: 'dev', + environment: {} + } + await context.runStep({ name: 'iamRoleConfig', execute: iamRoleConfig }) + } + + const iamRoleConfig = async (context) => { + context.serverless.provider.iamRoleStatements = [ + { + Effect: 'Allow', + Action: [ + 'lambda:InvokeAsync', + 'lambda:InvokeFunction' + ], + Resource: ['*'] + } + ] + if (context.tableConfigs && context.tableConfigs.length) { + const dynamoStatement = { + Effect: 'Allow', + Action: [ + 'dynamodb:Query', + 'dynamodb:Scan', + 'dynamodb:GetItem', + 'dynamodb:PutItem', + 'dynamodb:UpdateItem' + ], + Resource: [] + } + + for (const tableConfig of context.tableConfigs) { + console.log(tableConfig) + const properties = merge({}, { TableName: tableConfig.tableName }, tableConfig.nativeConfig) + dynamoStatement.Resource.push("arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/" + properties.TableName) + } + + context.serverless.provider.iamRoleStatements.push(dynamoStatement) + } + } + + const functionsConfig = async (context) => { + context.serverless.functions = {} + + for (const serviceDefinition of context.publishedFunctions) { + context.serviceDefinition = serviceDefinition + await context.runStep({ name: 'functionExport', execute: functionExport }) + delete context.serviceDefinition + } + } + + const functionExport = async (context) => { + const { serviceDefinition, serverless } = context + + const functionName = getFunctionName(serviceDefinition.service) + const executePath = process.cwd() + let handler: string = serviceDefinition.file.replace(executePath, '') + const ext = extname(handler) + const nameKey = handler.substring(1, handler.length - ext.length).replace(/\\/g, '/') + + const def = serverless.functions[functionName] = { + handler: `${nameKey}.${serviceDefinition.exportName}`, + runtime: getMetadata(CLASS_RUNTIMEKEY, serviceDefinition.service) || "nodejs6.10" + } + + await context.runStep({ name: 'funtionEnvironments', execute: funtionEnvironments }) + await context.runStep({ name: 'funtionEvents', execute: funtionEvents }) + } + + const functional_service_regexp = /^FUNCTIONAL_SERVICE_/ + const funtionEnvironments = async ({ serviceDefinition, serverless }) => { + const functionName = getFunctionName(serviceDefinition.service) + const environments = getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) + if (environments) { + serverless.functions[functionName].environment = {} + for (const key in environments) { + let environmentValue = environments[key] + if (functional_service_regexp.test(key)) { + environmentValue = `${serverless.service}-${serverless.provider.stage}-${environmentValue}` + } + serverless.functions[functionName].environment[key] = environmentValue + } + } + + } + + const funtionEvents = ({ serviceDefinition, serverless }) => { + const functionName = getFunctionName(serviceDefinition.service) + let httpMetadata = getMetadata(CLASS_APIGATEWAYKEY, serviceDefinition.service) + const environments = getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) + for (const { method, path, cors } of httpMetadata) { + const normalizedPath = /^\//.test(path) ? path.substring(1, path.length) : path + const defs = serverless.functions[functionName].events = serverless.functions[functionName].events || [] + const def: any = { + http: { + method, + path: normalizedPath + } + } + + if (cors) { + def.http.cors = { + origins: '*' + } + } + defs.push(def) + } + } + + const resourcesConfig = async (context) => { + context.serverless.resources = { + Resources: {} + } + + for (const tableDefinition of context.tableConfigs) { + context.tableConfig = tableDefinition + await context.runStep({ name: 'tableConfig', execute: tableConfig }) + delete context.tableConfig + } + + } + + const tableConfig = async ({ tableConfig, serverless }) => { + const properties = merge({}, { + TableName: tableConfig.tableName + }, tableConfig.nativeConfig, __dynamoDBDefaults); + + + const tableResource = { + "Type": "AWS::DynamoDB::Table", + "Properties": properties + } + + const name = normalizeName(properties.TableName) + + serverless.resources.Resources[name] = tableResource + } + + const saveConfig = async ({ serverless }) => { + writeFileSync('serverless.yml', stringify(serverless, Number.MAX_SAFE_INTEGER)) + } + + const build = async (context) => { + await context.runStep({ name: 'serverlessConfig', execute: serverlessConfig }) + await context.runStep({ name: 'providerConfig', execute: providerConfig }) + await context.runStep({ name: 'functionsConfig', execute: functionsConfig }) + await context.runStep({ name: 'resourcesConfig', execute: resourcesConfig }) + await context.runStep({ name: 'saveConfig', execute: saveConfig }) + } + + + + return { + commands({ commander }) { + commander + .command('serverless [path]') + .alias('sls') + .option('--aws-region ', 'AWS_REGION') + .description('serverless config') + .action(async (path, command) => { + process.env.FUNCTIONAL_ENVIRONMENT = 'deploy' + + try { + const entryPoint = requireValue(path || projectConfig.main, 'entry point') + const awsRegion = requireValue(command.awsRegion || projectConfig.awsRegion, 'awsRegion') + + const context = await createContext(entryPoint, { + deployTarget: FUNCTIONAL_ENVIRONMENT, + awsRegion, + FUNCTIONAL_ENVIRONMENT + }) + + await context.runStep(contextSteppes.setFunctionalEnvironment) + + await build(context) + + console.log(`done`) + } catch (e) { + console.log(`error`, e) + } + }); + }, + deployProviders: { + sls: { + FUNCTIONAL_ENVIRONMENT, + createEnvironment: build + } + } + } +} + +export const nameReplaceRegexp = /[^a-zA-Z0-9]/g +export const normalizeName = (name: string) => { + const result = name.replace(nameReplaceRegexp, '') + if (!result) { + throw new Error(`'invalid name '${name}'`) + } + return result +} diff --git a/src/cli/context/steppes/serviceDiscovery.ts b/src/cli/context/steppes/serviceDiscovery.ts index 87d77fc..3506f6f 100644 --- a/src/cli/context/steppes/serviceDiscovery.ts +++ b/src/cli/context/steppes/serviceDiscovery.ts @@ -44,6 +44,7 @@ export class ServiceDiscoveryStep extends ContextStep { exportName: key, invoker: exportItem, handler: `${nameKey}.${key}`, + file } if (context.files.indexOf(file) < 0) { diff --git a/src/cli/index.ts b/src/cli/index.ts index 2ce490d..088f148 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -6,6 +6,7 @@ import './providers' import * as deploy from './commands/deploy' import * as local from './commands/local' import * as metadata from './commands/metadata' +import * as serverless from './commands/serverless' import { init as initProjectConfig, internalPluginLoad } from './project/init' @@ -13,6 +14,7 @@ export const init = (commander) => { internalPluginLoad(deploy) internalPluginLoad(local) internalPluginLoad(metadata) + internalPluginLoad(serverless) initProjectConfig(commander) } \ No newline at end of file diff --git a/src/cli/project/init.ts b/src/cli/project/init.ts index 04ad314..bd85834 100644 --- a/src/cli/project/init.ts +++ b/src/cli/project/init.ts @@ -5,14 +5,13 @@ import { projectConfig, updateConfig, setPluginDefinitions, getPluginDefinitions import { logger } from '../utilities/logger' import { resolvePath, requireValue } from '../utilities/cli' -import { contextSteppes, createContext } from '../context' +import { contextSteppes, createContext, ContextStep } from '../context' import * as annotations from '../../annotations' export const pluginInitParam: any = { logger, - resolvePath, - requireValue, - contextSteppes, createContext, + resolvePath, requireValue, + contextSteppes, createContext, ContextStep, annotations, projectConfig, getPluginDefinitions diff --git a/src/providers/aws.ts b/src/providers/aws.ts index b446a3c..944868a 100644 --- a/src/providers/aws.ts +++ b/src/providers/aws.ts @@ -37,8 +37,11 @@ export class AWSProvider extends Provider { public async invoke(serviceInstance, params, invokeConfig?) { return new Promise((resolve, reject) => { + const funcName = getFunctionName(serviceInstance) + const resolvedFuncName = process.env[`FUNCTIONAL_SERVICE_${funcName.toUpperCase()}`] || funcName + const invokeParams = { - FunctionName: getFunctionName(serviceInstance), + FunctionName: resolvedFuncName, Payload: JSON.stringify(params) }; diff --git a/src/providers/local.ts b/src/providers/local.ts index b5012fd..66c217a 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -43,9 +43,10 @@ export class LocalProvider extends Provider { return reject(new Error('missing http configuration')) } + const normalizedPath = /^\//.test(httpAttr.path) ? httpAttr.path : `/${httpAttr.path}` const invokeParams: any = { method: httpAttr.method || 'GET', - url: `http://localhost:${process.env.FUNCTIONAL_LOCAL_PORT}${httpAttr.path}`, + url: `http://localhost:${process.env.FUNCTIONAL_LOCAL_PORT}${normalizedPath}`, }; if (!httpAttr.method || httpAttr.method.toLowerCase() === 'get') { From 6dc2f644f2978d594dfe1164c7b820eb5dd8bdaf Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Fri, 9 Jun 2017 16:35:56 +0200 Subject: [PATCH 009/196] CHANGE: cloudformation deploy - dynamic s3 bucket creation --- src/cli/commands/deploy.ts | 5 +- src/cli/commands/serverless.ts | 7 +- src/cli/project/config.ts | 13 ++- .../context/cloudFormationInit.ts | 9 +- .../cloudFormation/context/resources.ts | 42 ++++++++- src/cli/providers/cloudFormation/index.ts | 34 ++++--- src/cli/utilities/aws/cloudFormation.ts | 93 +++++++++++++++++-- src/cli/utilities/aws/s3Upload.ts | 3 +- src/cli/utilities/compress.ts | 8 ++ 9 files changed, 179 insertions(+), 35 deletions(-) diff --git a/src/cli/commands/deploy.ts b/src/cli/commands/deploy.ts index 0453f74..7c91a9b 100644 --- a/src/cli/commands/deploy.ts +++ b/src/cli/commands/deploy.ts @@ -13,12 +13,13 @@ export default ({ createContext, contextSteppes: { createEnvironment }, projectC const entryPoint = requireValue(path || projectConfig.main, 'entry point') const deployTarget = requireValue(target || projectConfig.deployTarget, 'missing deploy target') const awsRegion = requireValue(command.awsRegion || projectConfig.awsRegion, 'awsRegion') - const awsBucket = requireValue(command.awsBucket || projectConfig.awsBucket, 'awsBucket') + const awsBucket = command.awsBucket || projectConfig.awsBucket const context = await createContext(entryPoint, { deployTarget, awsRegion, - awsBucket + awsBucket, + version: projectConfig.version }) await context.runStep(createEnvironment) diff --git a/src/cli/commands/serverless.ts b/src/cli/commands/serverless.ts index 9f57036..b9bbc58 100644 --- a/src/cli/commands/serverless.ts +++ b/src/cli/commands/serverless.ts @@ -23,13 +23,8 @@ export default (api) => { } = api const serverlessConfig = async (context) => { - let projectName = 'unknown' - try { - projectName = require(resolvePath('./package.json')).name || projectName - } catch (e) { } - context.serverless = { - service: projectName + service: projectConfig.name || 'unknown' } } diff --git a/src/cli/project/config.ts b/src/cli/project/config.ts index b67dc0c..0bd0ec5 100644 --- a/src/cli/project/config.ts +++ b/src/cli/project/config.ts @@ -1,8 +1,19 @@ import { merge } from 'lodash' +import { join } from 'path' export const projectConfig: any = {} export const updateConfig = (values) => merge(projectConfig, values) export const pluginDefinitions = [] export const setPluginDefinitions = (pluginDefinition) => pluginDefinitions.push(pluginDefinition) -export const getPluginDefinitions = () => pluginDefinitions \ No newline at end of file +export const getPluginDefinitions = () => pluginDefinitions + +try { + const packageJson = require(join(process.cwd(), 'package')) + if (packageJson && packageJson.version) { + projectConfig.version = packageJson.version + } + if (packageJson && packageJson.name) { + projectConfig.name = packageJson.name + } +} catch (e) { } diff --git a/src/cli/providers/cloudFormation/context/cloudFormationInit.ts b/src/cli/providers/cloudFormation/context/cloudFormationInit.ts index 055f5f0..7f22dea 100644 --- a/src/cli/providers/cloudFormation/context/cloudFormationInit.ts +++ b/src/cli/providers/cloudFormation/context/cloudFormationInit.ts @@ -2,11 +2,16 @@ import { resolvePath } from '../../../utilities/cli' import { projectConfig } from '../../../project/config' import { ContextStep } from '../../../context' +import { merge } from 'lodash' + export const cloudFormationInit = ContextStep.register('cloudFormationInit', async (context) => { - context.CloudFormationConfig = projectConfig.CloudFormation || {} + context.CloudFormationConfig = merge({}, { + StackName: projectConfig.name + }, projectConfig.cloudFormation) context.CloudFormationTemplate = { "AWSTemplateFormatVersion": "2010-09-09", - "Resources": {} + "Resources": {}, + "Outputs": {} } }) diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index d45f30a..c0fa87d 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -154,12 +154,16 @@ export const tableResources = ContextStep.register('tableResources', async (cont }) export const lambdaResources = ContextStep.register('lambdaResources', async (context) => { + const awsBucket = context.__userAWSBucket ? context.awsBucket : { + "Ref": "FunctionlyDeploymentBucket" + } + for (const serviceDefinition of context.publishedFunctions) { const properties: any = { Code: { - S3Bucket: context.awsBucket, + S3Bucket: awsBucket, S3Key: context.S3Zip }, Description: serviceDefinition[CLASS_DESCRIPTIONKEY] || getMetadata(CLASS_DESCRIPTIONKEY, serviceDefinition.service), @@ -181,7 +185,41 @@ export const lambdaResources = ContextStep.register('lambdaResources', async (co } const resourceName = `lambda_${properties.FunctionName}` - setResource(context, resourceName, lambdaResource) + const name = setResource(context, resourceName, lambdaResource) + + + const versionResource = { + "Type": "AWS::Lambda::Version", + "DeletionPolicy": "Retain", + "Properties": { + "FunctionName": { + "Ref": name + }, + "CodeSha256": context.zipCodeSha256 + } + } + setResource(context, `${name}${context.zipCodeSha256}`, versionResource) + + } + +}) + +export const s3BucketResources = ContextStep.register('s3BucketResources', async (context) => { + if (context.awsBucket) { + context.__userAWSBucket = true + } + + const s3BucketResources = { + "Type": "AWS::S3::Bucket" + } + + const resourceName = `FunctionlyDeploymentBucket` + const name = setResource(context, resourceName, s3BucketResources) + + context.CloudFormationTemplate.Outputs[`${name}Name`] = { + "Value": { + "Ref": "FunctionlyDeploymentBucket" + } } }) diff --git a/src/cli/providers/cloudFormation/index.ts b/src/cli/providers/cloudFormation/index.ts index f27db88..9bea2d6 100644 --- a/src/cli/providers/cloudFormation/index.ts +++ b/src/cli/providers/cloudFormation/index.ts @@ -1,40 +1,52 @@ import { getFunctionName } from '../../../annotations' import { bundle } from '../../utilities/webpack' +import { logger } from '../../utilities/logger' import { zip } from '../../utilities/compress' import { uploadZipStep } from '../../utilities/aws/s3Upload' -import { createStack, updateStack, getTemplate } from '../../utilities/aws/cloudFormation' +import { createStack, updateStack, getTemplate, getStackBucketName, describeStacks } from '../../utilities/aws/cloudFormation' import { cloudFormationInit } from './context/cloudFormationInit' -import { tableResources, lambdaResources, roleResources } from './context/resources' +import { tableResources, lambdaResources, roleResources, s3BucketResources } from './context/resources' import { uploadTemplate } from './context/uploadTemplate' export const cloudFormation = { FUNCTIONAL_ENVIRONMENT: 'aws', createEnvironment: async (context) => { + logger.info(`Functionly: Packgaging...`) await context.runStep(bundle) await context.runStep(zip) - await context.runStep(uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData())) await context.runStep(cloudFormationInit) - await context.runStep(roleResources) - await context.runStep(tableResources) - await context.runStep(lambdaResources) - - await context.runStep(uploadTemplate) + await context.runStep(s3BucketResources) try { await context.runStep(getTemplate) - await context.runStep(updateStack) - console.log('updated') } catch (e) { if (/^Stack with id .* does not exist$/.test(e.message)) { + logger.info(`Functionly: Creating stack...`) + await context.runStep(createStack) - console.log('created') } else { console.log(e) throw e } } + if (!context.awsBucket) { + await context.runStep(getStackBucketName) + } + + logger.info(`Functionly: Uploading binary...`) + await context.runStep(uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData())) + + await context.runStep(roleResources) + await context.runStep(tableResources) + await context.runStep(lambdaResources) + + logger.info(`Functionly: Uploading template...`) + await context.runStep(uploadTemplate) + logger.info(`Functionly: Updating stack...`) + await context.runStep(updateStack) + logger.info(`Functionly: Complete`) } } diff --git a/src/cli/utilities/aws/cloudFormation.ts b/src/cli/utilities/aws/cloudFormation.ts index 8e8536b..89e5660 100644 --- a/src/cli/utilities/aws/cloudFormation.ts +++ b/src/cli/utilities/aws/cloudFormation.ts @@ -27,14 +27,21 @@ const initAWSSDK = (context) => { export const createStack = ContextStep.register('createStack', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { - const cfConfig = pick(context.CloudFormationConfig, CREATE_STACK_PRPERTIES) + const cfConfig: any = pick(context.CloudFormationConfig, CREATE_STACK_PRPERTIES) const params = merge({}, cfConfig, { - TemplateURL: `https://s3.eu-central-1.amazonaws.com/${context.awsBucket}/${context.S3CloudFormationTemplate}` + TemplateBody: JSON.stringify(context.CloudFormationTemplate, null, 2) }) - cloudFormation.createStack(params, function (err, data) { - if (err) reject(err) - else resolve(data); + cloudFormation.createStack(params, async (err, data) => { + if (err) return reject(err) + + try { + const result = await stackStateWaiter('CREATE_COMPLETE', context) + resolve(result) + } catch (e) { + reject(e) + } + }); }) }) @@ -42,15 +49,22 @@ export const createStack = ContextStep.register('createStack', (context) => { export const updateStack = ContextStep.register('updateStack', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { - const cfConfig = pick(context.CloudFormationConfig, UPDATE_STACK_PRPERTIES) + const cfConfig: any = pick(context.CloudFormationConfig, UPDATE_STACK_PRPERTIES) const params = merge({}, cfConfig, { TemplateURL: `https://s3.eu-central-1.amazonaws.com/${context.awsBucket}/${context.S3CloudFormationTemplate}`, UsePreviousTemplate: false }) - cloudFormation.updateStack(params, function (err, data) { - if (err) reject(err) - else resolve(data); + cloudFormation.updateStack(params, async (err, data) => { + if (err) return reject(err) + + try { + const result = await stackStateWaiter('UPDATE_COMPLETE', context, 100) + resolve(result) + } catch (e) { + reject(e) + } + }); }) }) @@ -67,4 +81,63 @@ export const getTemplate = ContextStep.register('getTemplate', (context) => { else resolve(data); }); }) -}) \ No newline at end of file +}) + + +export const getStackBucketName = ContextStep.register('getStackBucketName', (context) => { + if (context.awsBucket) return context.awsBucket + + initAWSSDK(context) + return new Promise((resolve, reject) => { + let params = { + StackName: context.CloudFormationConfig.StackName, + LogicalResourceId: 'FunctionlyDeploymentBucket' + } + + cloudFormation.describeStackResource(params, function (err, data) { + if (err) return reject(err) + context.awsBucket = data.StackResourceDetail.PhysicalResourceId + return resolve(data.StackResourceDetail.PhysicalResourceId); + }); + }) +}) + +export const describeStacks = ContextStep.register('describeStacks', (context) => { + initAWSSDK(context) + return new Promise((resolve, reject) => { + let params = { + StackName: context.CloudFormationConfig.StackName + } + + cloudFormation.describeStacks(params, function (err, data) { + if (err) return reject(err) + const result = data.Stacks.find(s => s.StackName === context.CloudFormationConfig.StackName) + return resolve(result); + }); + }) +}) + +export const stackStateWaiter = (status, context, tries = 20, timeout = 5000) => { + const waitregexp = /_PROGRESS$/ + return new Promise((resolve, reject) => { + let iteration = 0 + const waiter = () => { + if (iteration >= tries) return reject(new Error(`stackStateWaiter timeout '${status}'`)) + iteration++ + setTimeout(async () => { + const stack = await describeStacks.execute(context) + console.log(' >', stack.StackStatus) + if (waitregexp.test(stack.StackStatus)) { + return waiter() + } + + if (stack.StackStatus === status) { + return resolve(stack) + } + return reject(stack) + }, timeout) + } + + waiter() + }) +} \ No newline at end of file diff --git a/src/cli/utilities/aws/s3Upload.ts b/src/cli/utilities/aws/s3Upload.ts index 4ef0109..07c3d77 100644 --- a/src/cli/utilities/aws/s3Upload.ts +++ b/src/cli/utilities/aws/s3Upload.ts @@ -41,10 +41,11 @@ export const uploaderStep = (name, data, contentType) => { export const uploadToAws = ContextStep.register('uploadToAws', async (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { + const version = context.version ? `${context.version}/` : '' let params = merge({}, config.S3, { Bucket: context.awsBucket, Body: new Buffer(context.upload.data, 'binary'), - Key: context.upload.name, + Key: `functionly/${version}${context.upload.name}`, ContentType: context.upload.contentType }) diff --git a/src/cli/utilities/compress.ts b/src/cli/utilities/compress.ts index 88183de..b0efa7e 100644 --- a/src/cli/utilities/compress.ts +++ b/src/cli/utilities/compress.ts @@ -1,5 +1,6 @@ import * as nodeZip from 'node-zip' import { basename, join } from 'path' +import { createHash } from 'crypto' import { readFileSync, writeFileSync } from 'fs' import { ContextStep } from '../context' @@ -11,5 +12,12 @@ export const zip = ContextStep.register('zip', (context) => { } let zipData = compressor.generate({ base64: false, compression: 'DEFLATE' }); + + const hash = createHash('sha256'); + hash.setEncoding('base64'); + hash.write(zipData); + hash.end(); + context.zipData = () => zipData + context.zipCodeSha256 = hash.read() }) \ No newline at end of file From e4f080e803218ec14fbe58de3dc40b87cc796414 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Fri, 9 Jun 2017 16:59:35 +0200 Subject: [PATCH 010/196] CHANGE: cloudformation deploy - generate role if not defined; stack default values --- .../cloudFormation/context/cloudFormationInit.ts | 4 +++- src/cli/providers/cloudFormation/context/resources.ts | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/cli/providers/cloudFormation/context/cloudFormationInit.ts b/src/cli/providers/cloudFormation/context/cloudFormationInit.ts index 7f22dea..eb12cf2 100644 --- a/src/cli/providers/cloudFormation/context/cloudFormationInit.ts +++ b/src/cli/providers/cloudFormation/context/cloudFormationInit.ts @@ -6,7 +6,9 @@ import { merge } from 'lodash' export const cloudFormationInit = ContextStep.register('cloudFormationInit', async (context) => { context.CloudFormationConfig = merge({}, { - StackName: projectConfig.name + StackName: projectConfig.name, + OnFailure: "ROLLBACK", + TimeoutInMinutes: 10 }, projectConfig.cloudFormation) context.CloudFormationTemplate = { diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index c0fa87d..a9475f2 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -38,11 +38,15 @@ export const roleResources = ContextStep.register('roleResources', async (contex for (const serviceDefinition of context.publishedFunctions) { - const role = getMetadata(CLASS_ROLEKEY, serviceDefinition.service) + let role = getMetadata(CLASS_ROLEKEY, serviceDefinition.service) if (typeof role === 'string' && /^arn:/.test(role)) { continue } + if (!role) { + role = context.CloudFormationConfig.StackName + } + if (!roleMap.has(role)) { const properties = { @@ -123,6 +127,10 @@ export const roleResources = ContextStep.register('roleResources', async (contex }, resourceName }) + + context.CloudFormationConfig.Capabilities = context.CloudFormationConfig.Capabilities || [ + "CAPABILITY_NAMED_IAM" + ] } const iam_role = roleMap.get(role) From da5899733be3d2fe5b6a2a224ca47c86ee07d1a7 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Wed, 14 Jun 2017 12:54:03 +0200 Subject: [PATCH 011/196] ADD: CloudFormation ApiGateway; CHANGE: aws EventSource support; Persist upload data; event parameter decorator --- config/default.js | 1 + src/annotations/classes/apiGateway.ts | 6 +- src/annotations/index.ts | 2 +- src/annotations/parameters/param.ts | 15 +- src/cli/commands/local.ts | 7 +- src/cli/commands/serverless.ts | 7 +- src/cli/context/core/contextStep.ts | 2 +- src/cli/context/index.ts | 100 +++--- src/cli/providers/aws.ts | 4 +- .../cloudFormation/context/apiGateway.ts | 306 ++++++++++++++++++ .../context/cloudFormationInit.ts | 2 +- .../cloudFormation/context/resources.ts | 47 +-- .../cloudFormation/context/uploadTemplate.ts | 6 +- src/cli/providers/cloudFormation/index.ts | 7 +- src/cli/providers/cloudFormation/utils.ts | 34 ++ src/cli/utilities/aws/cloudFormation.ts | 10 +- src/cli/utilities/aws/s3Upload.ts | 20 +- src/providers/aws/eventSources/apiGateway.ts | 43 +++ src/providers/aws/eventSources/eventSource.ts | 14 + src/providers/aws/eventSources/lambdaCall.ts | 12 + src/providers/{aws.ts => aws/index.ts} | 38 ++- src/providers/core/provider.ts | 4 +- src/providers/local.ts | 12 +- 23 files changed, 574 insertions(+), 125 deletions(-) create mode 100644 src/cli/providers/cloudFormation/context/apiGateway.ts create mode 100644 src/cli/providers/cloudFormation/utils.ts create mode 100644 src/providers/aws/eventSources/apiGateway.ts create mode 100644 src/providers/aws/eventSources/eventSource.ts create mode 100644 src/providers/aws/eventSources/lambdaCall.ts rename src/providers/{aws.ts => aws/index.ts} (54%) diff --git a/config/default.js b/config/default.js index dcb355e..f17c3d7 100644 --- a/config/default.js +++ b/config/default.js @@ -15,6 +15,7 @@ module.exports = { }, target: 'node' }, + tempDirectory: path.join(process.cwd(), 'dist'), S3: { ACL: 'public-read' } diff --git a/src/annotations/classes/apiGateway.ts b/src/annotations/classes/apiGateway.ts index 6942375..fab127b 100644 --- a/src/annotations/classes/apiGateway.ts +++ b/src/annotations/classes/apiGateway.ts @@ -4,10 +4,12 @@ import { defaults } from 'lodash' export const defaultEndpoint = { method: 'get', - cors: false + cors: false, + authorization: 'AWS_IAM' } -export const apiGateway = (endpoint: { path: string, method?: string, cors?: boolean }) => { +export const apiGateway = (endpoint: { path: string, method?: string, cors?: boolean, + authorization?: 'AWS_IAM' | 'NONE' | 'CUSTOM' | 'COGNITO_USER_POOLS' }) => { return (target: Function) => { let metadata = getMetadata(CLASS_APIGATEWAYKEY, target) || [] metadata.push(defaults({}, endpoint, defaultEndpoint)) diff --git a/src/annotations/index.ts b/src/annotations/index.ts index 6f8feee..2bdcad9 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -14,7 +14,7 @@ export const description = simpleClassAnnotation(CLASS_DESCRIPTIONKEY) export const role = simpleClassAnnotation(CLASS_ROLEKEY) -export { param } from './parameters/param' +export { param, event } from './parameters/param' export { inject } from './parameters/inject' import * as _constants from './constants' diff --git a/src/annotations/parameters/param.ts b/src/annotations/parameters/param.ts index c9e853b..6b16df9 100644 --- a/src/annotations/parameters/param.ts +++ b/src/annotations/parameters/param.ts @@ -25,4 +25,17 @@ export const param = (target: any, targetKey?: string, parameterIndex?: number): } else { return decorator(target, targetKey, parameterIndex); } -} \ No newline at end of file +} + +export const event = (target, targetKey, parameterIndex: number) => { + let parameterNames = getFunctionParameters(target, targetKey); + + let existingParameters: any[] = getOwnMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; + let paramName = parameterNames[parameterIndex]; + existingParameters.push({ + from: paramName, + parameterIndex, + type: 'event' + }); + defineMetadata(PARAMETER_PARAMKEY, existingParameters, target, targetKey); +} diff --git a/src/cli/commands/local.ts b/src/cli/commands/local.ts index abdcebf..de2a0d7 100644 --- a/src/cli/commands/local.ts +++ b/src/cli/commands/local.ts @@ -12,16 +12,15 @@ export default ({ createContext, annotations: { getMetadata, constants, getFunct let httpMetadata = getMetadata(constants.CLASS_APIGATEWAYKEY, serviceDefinition.service) for (let event of httpMetadata) { - const normalizedPath = /^\//.test( event.path) ? event.path : `/${event.path}` const isLoggingEnabled = getMetadata(constants.CLASS_LOGKEY, serviceDefinition.service) - console.log(`${new Date().toISOString()} ${getFunctionName(serviceDefinition.service)} listening { path: '${normalizedPath}', method: '${event.method}', cors: ${event.cors ? true : false} }`) + console.log(`${new Date().toISOString()} ${getFunctionName(serviceDefinition.service)} listening { path: '${event.path}', method: '${event.method}', cors: ${event.cors ? true : false} }`) if (event.cors) { - app.use(normalizedPath, cors()) + app.use(event.path, cors()) } app[event.method]( - normalizedPath, + event.path, logMiddleware(isLoggingEnabled, serviceDefinition.service), environmentConfigMiddleware(serviceDefinition.service), serviceDefinition.invoker diff --git a/src/cli/commands/serverless.ts b/src/cli/commands/serverless.ts index b9bbc58..97a240b 100644 --- a/src/cli/commands/serverless.ts +++ b/src/cli/commands/serverless.ts @@ -119,15 +119,14 @@ export default (api) => { const funtionEvents = ({ serviceDefinition, serverless }) => { const functionName = getFunctionName(serviceDefinition.service) - let httpMetadata = getMetadata(CLASS_APIGATEWAYKEY, serviceDefinition.service) - const environments = getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) + let httpMetadata = getMetadata(CLASS_APIGATEWAYKEY, serviceDefinition.service) || [] for (const { method, path, cors } of httpMetadata) { - const normalizedPath = /^\//.test(path) ? path.substring(1, path.length) : path + const resourcePath = /^\//.test(path) ? path.substring(1, path.length) : path const defs = serverless.functions[functionName].events = serverless.functions[functionName].events || [] const def: any = { http: { method, - path: normalizedPath + path: resourcePath } } diff --git a/src/cli/context/core/contextStep.ts b/src/cli/context/core/contextStep.ts index 5f36673..f9d37e3 100644 --- a/src/cli/context/core/contextStep.ts +++ b/src/cli/context/core/contextStep.ts @@ -17,7 +17,7 @@ export class ContextStep { public static register(name, execute) { if (contextSteppes[name]) { - throw new Error(`step name '${this.name}' already defined`) + throw new Error(`step name '${name}' already defined`) } const step = { diff --git a/src/cli/context/index.ts b/src/cli/context/index.ts index c515043..ee3529b 100644 --- a/src/cli/context/index.ts +++ b/src/cli/context/index.ts @@ -23,71 +23,81 @@ export const getDefaultSteppes = (): any[] => { } export const createContext = async (path, defaultValues) => { - const context = defaults(new Context(), { + const context = defaults({}, { serviceRoot: resolvePath(path), - date: new Date() + date: new Date(), + runStep: async function (step) { return await executor(this, step) } }, defaultValues) const defaultSteppes = getDefaultSteppes() for (const step of defaultSteppes) { - await context.runStep(step) + await executor(context, step) } return context } let depth = 0 -export class Context { - [p: string]: any - - public async runStep(step: Function | any) { +// export class Context { +// [p: string]: any + +// public async runStep(step: Function | any) { +// return await runStep(this, step) +// } +// } + +export const executor = async (context, step?: Function | any) => { + if (!step) { + step = context; + context = context.context + } + let result = undefined + if (typeof step === 'function') { + result = await step(context) + } else if (step && step.name && typeof (step.execute === 'function' || step.method === 'function')) { - let result = undefined - if (typeof step === 'function') { - result = await step(this) - } else if (step && step.name && typeof step.execute === 'function') { + const method = step.method || step.execute - const tab = depth++ - const separator = (s = ' ') => { - let result = '' - for (var i = 0; i < tab; i++) { - result += s - } - return result + const tab = depth++ + const separator = (s = ' ') => { + let result = '' + for (var i = 0; i < tab; i++) { + result += s } + return result + } - try { - - if (projectConfig.debug) logger.debug(`Context step run -----------${separator('--')}> ${step.name}`) - if (projectConfig.debug) logger.debug(`Context step before start ${separator()} ${step.name}`) - await cliCallContextHooks(step.name, this, CONTEXT_HOOK_MODIFIER_BEFORE) - if (projectConfig.debug) logger.debug(`Context step before end ${separator()} ${step.name}`) - - if (projectConfig.debug) logger.debug(`Context step start ${separator()} ${step.name}`) - if (hasCliCallContextHooks(step.name, this)) { - result = await cliCallContextHooks(step.name, this) - } else { - result = await step.execute(this) - } - if (projectConfig.debug) logger.debug(`Context step end ${separator()} ${step.name}`) - - if (projectConfig.debug) logger.debug(`Context step after start ${separator()} ${step.name}`) - await cliCallContextHooks(step.name, this, CONTEXT_HOOK_MODIFIER_AFTER) - if (projectConfig.debug) logger.debug(`Context step after end ${separator()} ${step.name}`) - if (projectConfig.debug) logger.debug(`Context step complete ------${separator('--')}> ${step.name}`) - } - catch (e) { - if (projectConfig.debug) logger.debug(`Context step exited --------${separator('--')}> ${step.name}`) - depth-- - throw e + try { + + if (projectConfig.debug) logger.debug(`Context step run -----------${separator('--')}> ${step.name}`) + if (projectConfig.debug) logger.debug(`Context step before start ${separator()} ${step.name}`) + await cliCallContextHooks(step.name, context, CONTEXT_HOOK_MODIFIER_BEFORE) + if (projectConfig.debug) logger.debug(`Context step before end ${separator()} ${step.name}`) + + if (projectConfig.debug) logger.debug(`Context step start ${separator()} ${step.name}`) + if (hasCliCallContextHooks(step.name, context)) { + result = await cliCallContextHooks(step.name, context) + } else { + result = await method.call(step, context) } + if (projectConfig.debug) logger.debug(`Context step end ${separator()} ${step.name}`) + + if (projectConfig.debug) logger.debug(`Context step after start ${separator()} ${step.name}`) + await cliCallContextHooks(step.name, context, CONTEXT_HOOK_MODIFIER_AFTER) + if (projectConfig.debug) logger.debug(`Context step after end ${separator()} ${step.name}`) + if (projectConfig.debug) logger.debug(`Context step complete ------${separator('--')}> ${step.name}`) + } + catch (e) { + if (projectConfig.debug) logger.debug(`Context step exited --------${separator('--')}> ${step.name}`) depth-- - } else { - throw new Error(`context.runStep has invalid parameter '${step}'`) + throw e } - return result + depth-- + } else { + throw new Error(`runStep has invalid parameter '${step}'`) } + return result } diff --git a/src/cli/providers/aws.ts b/src/cli/providers/aws.ts index 569345c..4c6280e 100644 --- a/src/cli/providers/aws.ts +++ b/src/cli/providers/aws.ts @@ -13,13 +13,15 @@ import { updateLambdaFunctionTags } from '../utilities/aws/lambda' import { ContextStep } from '../context' +import { projectConfig } from '../project/config' export const aws = { FUNCTIONAL_ENVIRONMENT: 'aws', createEnvironment: ContextStep.register('createEnvironment_aws', async (context) => { await context.runStep(bundle) await context.runStep(zip) - await context.runStep(uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData())) + const localName = projectConfig.name ? `${projectConfig.name}.zip` : 'project.zip' + await context.runStep(uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData(), localName)) await context.runStep(createTables) for (let serviceDefinition of context.publishedFunctions) { diff --git a/src/cli/providers/cloudFormation/context/apiGateway.ts b/src/cli/providers/cloudFormation/context/apiGateway.ts new file mode 100644 index 0000000..1ec5b9f --- /dev/null +++ b/src/cli/providers/cloudFormation/context/apiGateway.ts @@ -0,0 +1,306 @@ +import { getMetadata, constants } from '../../../../annotations' +const { CLASS_APIGATEWAYKEY } = constants +import { ContextStep, executor } from '../../../context' +import { setResource } from '../utils' + +export const API_GATEWAY_REST_API = 'ApiGatewayRestApi' + +export const apiGateway = ContextStep.register('ApiGateway', async (context) => { + const deploymentResources = [] + await executor({ ...context, deploymentResources }, gatewayRestApi) + await executor({ ...context, deploymentResources }, gatewayResources) + await executor({ ...context, deploymentResources }, gatewayDeployment) +}) + +export const gatewayRestApi = ContextStep.register('ApiGateway-RestApi', async (context) => { + const RestApi = { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Name": context.CloudFormationConfig.StackName + } + } + + const apiName = setResource(context, API_GATEWAY_REST_API, RestApi) + + context.CloudFormationTemplate.Outputs[`ServiceEndpoint`] = { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": apiName + }, + ".execute-api.eu-central-1.amazonaws.com/dev" + ] + ] + } + } + +}) + +export const gatewayResources = ContextStep.register('ApiGateway-Resources', async (context) => { + const endpointsCors = new Map() + const endpoints = new Map() + for (const serviceDefinition of context.publishedFunctions) { + await executor({ + context: { ...context, serviceDefinition, endpointsCors, endpoints }, + name: `ApiGateway-Methods-${serviceDefinition.service.name}`, + method: apiGatewayMethods + }) + } + + for (const [endpointResourceName, methods] of endpointsCors) { + await executor({ + context: { ...context, endpointResourceName, methods }, + name: `ApiGateway-Method-Options-${endpointResourceName}`, + method: setOptionsMethodResource + }) + } +}) + +export const apiGatewayMethods = async (context) => { + const { serviceDefinition, endpoints, endpointsCors } = context + + let httpMetadata = getMetadata(CLASS_APIGATEWAYKEY, serviceDefinition.service) || [] + for (let metadata of httpMetadata) { + await executor({ + context: { ...context, ...metadata }, + name: `ApiGateway-Method-${metadata.method}-${metadata.path}`, + method: apiGatewayMethod + }) + } +} + +export const apiGatewayMethod = async (context) => { + const { path, cors, authorization, endpoints, endpointsCors, serviceDefinition } = context + const method = context.method.toUpperCase() + + const pathParts = path.split('/') + let pathFragment = '' + let endpointResourceName; + for (const pathPart of pathParts) { + if (!pathPart) continue + + const rootPathFragment = pathFragment + pathFragment += `/${pathPart}` + + if (endpoints.has(pathFragment)) { + endpointResourceName = endpoints.get(pathFragment) + } else { + endpointResourceName = await executor({ + context: { ...context, pathFragment, rootPathFragment, endpoints, pathPart }, + name: `ApiGateway-ResourcePath-${pathFragment}`, + method: apiGatewayPathPart + }) + } + } + + if (cors) { + let values = ['OPTIONS'] + if (endpointsCors.has(endpointResourceName)) { + values = endpointsCors.get(endpointResourceName) + } + values.push(method) + endpointsCors.set(endpointResourceName, values) + } + + const properties = { + "HttpMethod": method, + "RequestParameters": {}, + "ResourceId": { + "Ref": endpointResourceName + }, + "RestApiId": { + "Ref": API_GATEWAY_REST_API + }, + "AuthorizationType": authorization, + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:aws:apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + serviceDefinition.resourceName, + "Arn" + ] + }, + "/invocations" + ] + ] + } + }, + "MethodResponses": [] + } + + const methodConfig = { + "Type": "AWS::ApiGateway::Method", + "Properties": properties + } + const resourceName = `ApiGateway${pathFragment}${method}` + const name = setResource(context, resourceName, methodConfig) + + setGatewayPermissions({ serviceDefinition, context }) +} + +export const apiGatewayPathPart = async (context) => { + const { pathFragment, rootPathFragment, endpoints, pathPart } = context + + const properties = { + "ParentId": getAGResourceParentId(rootPathFragment, endpoints), + "PathPart": pathPart, + "RestApiId": { + "Ref": API_GATEWAY_REST_API + } + } + + const resourceConfig = { + "Type": "AWS::ApiGateway::Resource", + "Properties": properties + } + const resourceName = `ApiGateway${pathFragment}` + const endpointResourceName = setResource(context, resourceName, resourceConfig) + endpoints.set(pathFragment, endpointResourceName) + return endpointResourceName +} + +export const gatewayDeployment = ContextStep.register('ApiGateway-Deployment', async (context) => { + + const deploymentResources = context.deploymentResources + .filter(r => r.type === 'AWS::ApiGateway::Method') + .map(r => r.name) + + const ApiGatewayDeployment = { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": API_GATEWAY_REST_API + }, + "StageName": "dev" + }, + "DependsOn": [ + ...deploymentResources + ] + } + + const resourceName = `ApiGateway${context.date.valueOf()}` + const apiName = setResource(context, resourceName, ApiGatewayDeployment) + +}) + +export const getAGResourceParentId = (rootPathFragment, endpoints) => { + if (rootPathFragment && endpoints.has(rootPathFragment)) { + return { + "Ref": endpoints.get(rootPathFragment) + } + } else { + return { + "Fn::GetAtt": [ + API_GATEWAY_REST_API, + "RootResourceId" + ] + } + } +} + +export const setOptionsMethodResource = (context) => { + const { endpointResourceName, methods } = context + const properties = { + "AuthorizationType": "NONE", + "HttpMethod": "OPTIONS", + "MethodResponses": [ + { + "StatusCode": "200", + "ResponseParameters": { + "method.response.header.Access-Control-Allow-Origin": true, + "method.response.header.Access-Control-Allow-Headers": true, + "method.response.header.Access-Control-Allow-Methods": true, + "method.response.header.Access-Control-Allow-Credentials": true + }, + "ResponseModels": {} + } + ], + "RequestParameters": {}, + "Integration": { + "Type": "MOCK", + "RequestTemplates": { + "application/json": "{statusCode:200}" + }, + "IntegrationResponses": [ + { + "StatusCode": "200", + "ResponseParameters": { + "method.response.header.Access-Control-Allow-Origin": "'*'", + "method.response.header.Access-Control-Allow-Headers": "'Content-Type,Authorization,X-Requested-With'", + "method.response.header.Access-Control-Allow-Methods": `'${methods.join(',')}'`, + "method.response.header.Access-Control-Allow-Credentials": "'true'" + }, + "ResponseTemplates": { + "application/json": "" + } + } + ] + }, + "ResourceId": { + "Ref": endpointResourceName + }, + "RestApiId": { + "Ref": API_GATEWAY_REST_API + } + } + + const methodConfig = { + "Type": "AWS::ApiGateway::Method", + "Properties": properties + } + const resourceName = `${endpointResourceName}Options` + setResource(context, resourceName, methodConfig) +} + +export const setGatewayPermissions = ({ serviceDefinition, context }) => { + const properties = { + "FunctionName": { + "Fn::GetAtt": [ + serviceDefinition.resourceName, + "Arn" + ] + }, + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:aws:execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": API_GATEWAY_REST_API + }, + "/*/*" + ] + ] + } + } + + const methodConfig = { + "Type": "AWS::Lambda::Permission", + "Properties": properties + } + const resourceName = `ApiGateway${serviceDefinition.resourceName}Permission` + setResource(context, resourceName, methodConfig, true) +} diff --git a/src/cli/providers/cloudFormation/context/cloudFormationInit.ts b/src/cli/providers/cloudFormation/context/cloudFormationInit.ts index eb12cf2..1553e31 100644 --- a/src/cli/providers/cloudFormation/context/cloudFormationInit.ts +++ b/src/cli/providers/cloudFormation/context/cloudFormationInit.ts @@ -4,7 +4,7 @@ import { ContextStep } from '../../../context' import { merge } from 'lodash' -export const cloudFormationInit = ContextStep.register('cloudFormationInit', async (context) => { +export const cloudFormationInit = ContextStep.register('CloudFormationInit', async (context) => { context.CloudFormationConfig = merge({}, { StackName: projectConfig.name, OnFailure: "ROLLBACK", diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index a9475f2..bbfafe4 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -1,37 +1,12 @@ import { merge } from 'lodash' import { getMetadata, constants, getFunctionName, __dynamoDBDefaults } from '../../../../annotations' const { CLASS_DESCRIPTIONKEY, CLASS_ROLEKEY, CLASS_MEMORYSIZEKEY, CLASS_RUNTIMEKEY, CLASS_TIMEOUTKEY, - CLASS_ENVIRONMENTKEY, CLASS_TAGKEY } = constants -import { ContextStep } from '../../../context' - -export const nameReplaceRegexp = /[^a-zA-Z0-9]/g -export const normalizeName = (name: string) => { - const result = name.replace(nameReplaceRegexp, '') - if (!result) { - throw new Error(`'invalid name '${name}'`) - } - return result -} + CLASS_ENVIRONMENTKEY, CLASS_TAGKEY, CLASS_APIGATEWAYKEY } = constants +import { ContextStep, executor } from '../../../context' +import { setResource } from '../utils' +export { apiGateway } from './apiGateway' -export const setResource = (context, name, resource) => { - if (!name) { - throw new Error(`invalid resource name '${name}'`) - } - name = normalizeName(name) - if (context.CloudFormationTemplate.Resources[name]) { - throw new Error(`resource name '${name}' already exists`) - } - - context.usedAwsResources = context.usedAwsResources || []; - if (context.usedAwsResources.indexOf(resource.Type) < 0) { - context.usedAwsResources.push(resource.type) - } - - context.CloudFormationTemplate.Resources[name] = resource - return name; -} - -export const roleResources = ContextStep.register('roleResources', async (context) => { +export const roleResources = ContextStep.register('IAM-Role', async (context) => { const roleMap = new Map() @@ -140,7 +115,7 @@ export const roleResources = ContextStep.register('roleResources', async (contex }) -export const tableResources = ContextStep.register('tableResources', async (context) => { +export const tableResources = ContextStep.register('DynamoDB-Tables', async (context) => { for (const tableConfig of context.tableConfigs) { @@ -156,12 +131,14 @@ export const tableResources = ContextStep.register('tableResources', async (cont const resourceName = `dynamo_${properties.TableName}` - setResource(context, resourceName, tableResource) + const name = setResource(context, resourceName, tableResource) + + tableConfig.resourceName = name } }) -export const lambdaResources = ContextStep.register('lambdaResources', async (context) => { +export const lambdaResources = ContextStep.register('Lambda-Functions', async (context) => { const awsBucket = context.__userAWSBucket ? context.awsBucket : { "Ref": "FunctionlyDeploymentBucket" } @@ -194,7 +171,7 @@ export const lambdaResources = ContextStep.register('lambdaResources', async (co const resourceName = `lambda_${properties.FunctionName}` const name = setResource(context, resourceName, lambdaResource) - + serviceDefinition.resourceName = name const versionResource = { "Type": "AWS::Lambda::Version", @@ -212,7 +189,7 @@ export const lambdaResources = ContextStep.register('lambdaResources', async (co }) -export const s3BucketResources = ContextStep.register('s3BucketResources', async (context) => { +export const s3BucketResources = ContextStep.register('S3-Bucket', async (context) => { if (context.awsBucket) { context.__userAWSBucket = true } diff --git a/src/cli/providers/cloudFormation/context/uploadTemplate.ts b/src/cli/providers/cloudFormation/context/uploadTemplate.ts index 49717e3..e534a1b 100644 --- a/src/cli/providers/cloudFormation/context/uploadTemplate.ts +++ b/src/cli/providers/cloudFormation/context/uploadTemplate.ts @@ -1,9 +1,11 @@ import { uploaderStep } from '../../../utilities/aws/s3Upload' import { ContextStep } from '../../../context' +import { projectConfig } from '../../../project/config' -export const uploadTemplate = ContextStep.register('uploadTemplate', async (context) => { +export const uploadTemplate = ContextStep.register('UploadTemplate', async (context) => { const templateData = JSON.stringify(context.CloudFormationTemplate, null, 2); - const uploadresult = await context.runStep(uploaderStep(`services-${context.date.toISOString()}.template`, templateData, 'application/octet-stream')) + const localName = projectConfig.name ? `${projectConfig.name}.template` : 'cloudformation.template' + const uploadresult = await context.runStep(uploaderStep(`services-${context.date.toISOString()}.template`, templateData, 'application/octet-stream', localName)) context.S3CloudFormationTemplate = uploadresult.Key return uploadresult }) \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/index.ts b/src/cli/providers/cloudFormation/index.ts index 9bea2d6..e6e21a4 100644 --- a/src/cli/providers/cloudFormation/index.ts +++ b/src/cli/providers/cloudFormation/index.ts @@ -4,9 +4,10 @@ import { logger } from '../../utilities/logger' import { zip } from '../../utilities/compress' import { uploadZipStep } from '../../utilities/aws/s3Upload' import { createStack, updateStack, getTemplate, getStackBucketName, describeStacks } from '../../utilities/aws/cloudFormation' +import { projectConfig } from '../../project/config' import { cloudFormationInit } from './context/cloudFormationInit' -import { tableResources, lambdaResources, roleResources, s3BucketResources } from './context/resources' +import { tableResources, lambdaResources, roleResources, s3BucketResources, apiGateway } from './context/resources' import { uploadTemplate } from './context/uploadTemplate' export const cloudFormation = { @@ -36,11 +37,13 @@ export const cloudFormation = { } logger.info(`Functionly: Uploading binary...`) - await context.runStep(uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData())) + const localName = projectConfig.name ? `${projectConfig.name}.zip` : 'project.zip' + await context.runStep(uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData(), localName)) await context.runStep(roleResources) await context.runStep(tableResources) await context.runStep(lambdaResources) + await context.runStep(apiGateway) logger.info(`Functionly: Uploading template...`) await context.runStep(uploadTemplate) diff --git a/src/cli/providers/cloudFormation/utils.ts b/src/cli/providers/cloudFormation/utils.ts new file mode 100644 index 0000000..a17b9d0 --- /dev/null +++ b/src/cli/providers/cloudFormation/utils.ts @@ -0,0 +1,34 @@ +export const nameReplaceRegexp = /[^a-zA-Z0-9]/g +export const normalizeName = (name: string) => { + const result = name.replace(nameReplaceRegexp, '') + if (!result) { + throw new Error(`'invalid name '${name}'`) + } + return result +} + +export const setResource = (context, name, resource, allowOverride = false) => { + if (!name) { + throw new Error(`invalid resource name '${name}'`) + } + name = normalizeName(name) + if (allowOverride === false && context.CloudFormationTemplate.Resources[name]) { + throw new Error(`resource name '${name}' already exists`) + } + + context.usedAwsResources = context.usedAwsResources || []; + if (context.usedAwsResources.indexOf(resource.Type) < 0) { + context.usedAwsResources.push(resource.type) + } + + context.CloudFormationTemplate.Resources[name] = resource + + if (Array.isArray(context.deploymentResources)) { + context.deploymentResources.push({ + name, + type: resource.Type + }) + } + + return name; +} \ No newline at end of file diff --git a/src/cli/utilities/aws/cloudFormation.ts b/src/cli/utilities/aws/cloudFormation.ts index 89e5660..28a37b4 100644 --- a/src/cli/utilities/aws/cloudFormation.ts +++ b/src/cli/utilities/aws/cloudFormation.ts @@ -24,7 +24,7 @@ const initAWSSDK = (context) => { -export const createStack = ContextStep.register('createStack', (context) => { +export const createStack = ContextStep.register('CF-CreateStack', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { const cfConfig: any = pick(context.CloudFormationConfig, CREATE_STACK_PRPERTIES) @@ -46,7 +46,7 @@ export const createStack = ContextStep.register('createStack', (context) => { }) }) -export const updateStack = ContextStep.register('updateStack', (context) => { +export const updateStack = ContextStep.register('CF-UpdateStack', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { const cfConfig: any = pick(context.CloudFormationConfig, UPDATE_STACK_PRPERTIES) @@ -69,7 +69,7 @@ export const updateStack = ContextStep.register('updateStack', (context) => { }) }) -export const getTemplate = ContextStep.register('getTemplate', (context) => { +export const getTemplate = ContextStep.register('CF-GetTemplate', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { @@ -84,7 +84,7 @@ export const getTemplate = ContextStep.register('getTemplate', (context) => { }) -export const getStackBucketName = ContextStep.register('getStackBucketName', (context) => { +export const getStackBucketName = ContextStep.register('CF-GetStackBucketName', (context) => { if (context.awsBucket) return context.awsBucket initAWSSDK(context) @@ -102,7 +102,7 @@ export const getStackBucketName = ContextStep.register('getStackBucketName', (co }) }) -export const describeStacks = ContextStep.register('describeStacks', (context) => { +export const describeStacks = ContextStep.register('CF-DescribeStacks', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { diff --git a/src/cli/utilities/aws/s3Upload.ts b/src/cli/utilities/aws/s3Upload.ts index 07c3d77..d5d3859 100644 --- a/src/cli/utilities/aws/s3Upload.ts +++ b/src/cli/utilities/aws/s3Upload.ts @@ -3,6 +3,9 @@ import { merge } from 'lodash' import { config } from '../config' import { ContextStep } from '../../context' +import { writeFileSync } from 'fs' +import { join, normalize } from 'path' + let s3 = null; const initAWSSDK = (context) => { if (!s3) { @@ -16,21 +19,22 @@ const initAWSSDK = (context) => { return s3 } -export const uploadZipStep = (name, data) => { +export const uploadZipStep = (name, data, localName?) => { return async (context) => { - const step = uploaderStep(name, data, 'application/zip') + const step = uploaderStep(name, data, 'application/zip', localName) const uploadResult = await context.runStep(step) context.S3Zip = uploadResult.Key return uploadResult } } -export const uploaderStep = (name, data, contentType) => { +export const uploaderStep = (name, data, contentType, localName?) => { return async (context) => { context.upload = { name, data, - contentType + contentType, + localName } const uploadResult = await context.runStep(uploadToAws) delete context.upload @@ -42,15 +46,21 @@ export const uploadToAws = ContextStep.register('uploadToAws', async (context) = initAWSSDK(context) return new Promise((resolve, reject) => { const version = context.version ? `${context.version}/` : '' + const binary = new Buffer(context.upload.data, 'binary') let params = merge({}, config.S3, { Bucket: context.awsBucket, - Body: new Buffer(context.upload.data, 'binary'), + Body: binary, Key: `functionly/${version}${context.upload.name}`, ContentType: context.upload.contentType }) s3.putObject(params, (err, res) => { if (err) return reject(err) + + if (config.tempDirectory && context.upload.localName) { + writeFileSync(join(config.tempDirectory, context.upload.localName), binary) + } + return resolve(params) }) }) diff --git a/src/providers/aws/eventSources/apiGateway.ts b/src/providers/aws/eventSources/apiGateway.ts new file mode 100644 index 0000000..d9bc203 --- /dev/null +++ b/src/providers/aws/eventSources/apiGateway.ts @@ -0,0 +1,43 @@ +import { EventSource } from './eventSource' + +export class ApiGateway extends EventSource { + public available(eventContext: any): boolean { + const { event } = eventContext + return event && event.requestContext && event.requestContext.apiId ? true : false + } + + public async parameterResolver(parameter, event) { + const body = event.event.body + const query = event.event.queryStringParameters + const params = event.event.pathParameters + const headers = event.event.headers + + switch (parameter.type) { + case 'param': + if (body && body[parameter.from]) return body[parameter.from] + if (query && query[parameter.from]) return query[parameter.from] + if (params && params[parameter.from]) return params[parameter.from] + if (headers && headers[parameter.from]) return headers[parameter.from] + default: + return await super.parameterResolver(parameter, event) + } + } + + public async resultTransform(err, result, event) { + if (err) { + return { + statusCode: 500, + body: JSON.stringify(err) + } + } + + if (result && typeof result.statusCode === 'number' && typeof result.body === 'string') { + return result + } + + return { + statusCode: 200, + body: JSON.stringify(result) + } + } +} \ No newline at end of file diff --git a/src/providers/aws/eventSources/eventSource.ts b/src/providers/aws/eventSources/eventSource.ts new file mode 100644 index 0000000..e910e59 --- /dev/null +++ b/src/providers/aws/eventSources/eventSource.ts @@ -0,0 +1,14 @@ +export abstract class EventSource { + public available(event: any) { + return true + } + + public async parameterResolver(parameter, event: any) { + return undefined + } + + public async resultTransform(err, result, event: any) { + if (err) throw err + return result + } +} \ No newline at end of file diff --git a/src/providers/aws/eventSources/lambdaCall.ts b/src/providers/aws/eventSources/lambdaCall.ts new file mode 100644 index 0000000..a22ef8a --- /dev/null +++ b/src/providers/aws/eventSources/lambdaCall.ts @@ -0,0 +1,12 @@ +import { EventSource } from './eventSource' + +export class LambdaCall extends EventSource { + public async parameterResolver(parameter, event) { + switch (parameter.type) { + case 'param': + return event.event[parameter.from] + default: + return await super.parameterResolver(parameter, event) + } + } +} \ No newline at end of file diff --git a/src/providers/aws.ts b/src/providers/aws/index.ts similarity index 54% rename from src/providers/aws.ts rename to src/providers/aws/index.ts index 944868a..2ea179e 100644 --- a/src/providers/aws.ts +++ b/src/providers/aws/index.ts @@ -1,23 +1,43 @@ import { Lambda } from 'aws-sdk' -import { Provider } from './core/provider' -import { getFunctionName } from '../annotations' +import { Provider } from '../core/provider' +import { getFunctionName } from '../../annotations' +import { ApiGateway } from './eventSources/apiGateway' +import { LambdaCall } from './eventSources/lambdaCall' const lambda = new Lambda(); +const eventSourceHandlers = [ + new ApiGateway(), + new LambdaCall() +] + + export class AWSProvider extends Provider { public getInvoker(serviceType, serviceInstance, params): Function { const parameters = this.getParameters(serviceType, 'handle') const invoker = async (event, context, cb) => { try { + const eventContext = { event, context, cb } + + const eventSourceHandler = eventSourceHandlers.find(h => h.available(eventContext)) + const params = [] for (const parameter of parameters) { - params[parameter.parameterIndex] = await this.awsParameterResolver(event, context, parameter) + params[parameter.parameterIndex] = await this.parameterResolver(parameter, { eventSourceHandler, eventContext }) + } + + let result + let error + try { + result = await serviceInstance.handle(...params) + } catch (err) { + error = err } + const response = await eventSourceHandler.resultTransform(error, result, eventContext) - const r = await serviceInstance.handle(...params) - cb(null, r) - return r + cb(null, response) + return response } catch (e) { cb(e) } @@ -25,12 +45,12 @@ export class AWSProvider extends Provider { return invoker } - protected async awsParameterResolver(event, context, parameter) { + protected async parameterResolver(parameter, event) { switch (parameter.type) { case 'param': - return event[parameter.from] + return event.eventSourceHandler.parameterResolver(parameter, event.eventContext) default: - return await super.parameterResolver(parameter) + return await super.parameterResolver(parameter, event.eventContext) } } diff --git a/src/providers/core/provider.ts b/src/providers/core/provider.ts index e9ce875..d5441f8 100644 --- a/src/providers/core/provider.ts +++ b/src/providers/core/provider.ts @@ -12,7 +12,7 @@ export abstract class Provider { } - protected async parameterResolver(parameter): Promise { + protected async parameterResolver(parameter, event): Promise { switch (parameter.type) { case 'inject': const serviceType = parameter.serviceType @@ -25,6 +25,8 @@ export abstract class Provider { const instance = new serviceType(...parameter.params.map((p) => typeof p === 'function' ? p() : p)) await callExtension(instance, 'onInject', { parameter }) return instance + case 'event': + return event; default: return undefined } diff --git a/src/providers/local.ts b/src/providers/local.ts index 66c217a..9f0bf25 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -10,8 +10,8 @@ export class LocalProvider extends Provider { const invoker = async (req, res, next) => { try { const params = [] - for (const parameter of parameters){ - params[parameter.parameterIndex] = await this.localParameterResolver(req, parameter) + for (const parameter of parameters) { + params[parameter.parameterIndex] = await this.parameterResolver(parameter, { req, res, next }) } const r = await serviceInstance.handle(...params) @@ -24,14 +24,15 @@ export class LocalProvider extends Provider { return invoker } - protected async localParameterResolver(req, parameter) { + protected async parameterResolver(parameter, event) { + const req = event.req switch (parameter.type) { case 'param': if (req.body && req.body[parameter.from]) return req.body[parameter.from] if (req.query && req.query[parameter.from]) return req.query[parameter.from] if (req.params && req.params[parameter.from]) return req.params[parameter.from] default: - return await super.parameterResolver(parameter) + return await super.parameterResolver(parameter, event) } } @@ -43,10 +44,9 @@ export class LocalProvider extends Provider { return reject(new Error('missing http configuration')) } - const normalizedPath = /^\//.test(httpAttr.path) ? httpAttr.path : `/${httpAttr.path}` const invokeParams: any = { method: httpAttr.method || 'GET', - url: `http://localhost:${process.env.FUNCTIONAL_LOCAL_PORT}${normalizedPath}`, + url: `http://localhost:${process.env.FUNCTIONAL_LOCAL_PORT}${httpAttr.path}`, }; if (!httpAttr.method || httpAttr.method.toLowerCase() === 'get') { From 61d940d409d1103c01658d3cfd5bb85ffa767622 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Wed, 14 Jun 2017 15:46:33 +0200 Subject: [PATCH 012/196] REFACTOR: context, naming --- src/cli/commands/deploy.ts | 4 +- src/cli/commands/local.ts | 4 +- src/cli/commands/serverless.ts | 36 +++-- src/cli/context/core/contextStep.ts | 32 ---- src/cli/context/core/executeStep.ts | 37 +++++ src/cli/context/index.ts | 35 ++--- src/cli/context/steppes/serviceDiscovery.ts | 8 +- .../steppes/setFunctionalEnvironment.ts | 8 +- src/cli/context/steppes/tableDiscovery.ts | 8 +- src/cli/extensions/hooks.ts | 1 - src/cli/project/init.ts | 4 +- src/cli/providers/aws.ts | 22 +-- .../cloudFormation/context/apiGateway.ts | 10 +- .../context/cloudFormationInit.ts | 4 +- .../cloudFormation/context/resources.ts | 147 ++++++++++-------- .../cloudFormation/context/uploadTemplate.ts | 6 +- src/cli/providers/cloudFormation/index.ts | 29 ++-- src/cli/providers/index.ts | 12 +- src/cli/providers/local.ts | 6 +- src/cli/utilities/aws/cloudFormation.ts | 14 +- src/cli/utilities/aws/dynamoDB.ts | 13 +- src/cli/utilities/aws/lambda.ts | 18 +-- src/cli/utilities/aws/s3Upload.ts | 8 +- src/cli/utilities/compress.ts | 4 +- src/cli/utilities/webpack.ts | 8 +- 25 files changed, 249 insertions(+), 229 deletions(-) delete mode 100644 src/cli/context/core/contextStep.ts create mode 100644 src/cli/context/core/executeStep.ts diff --git a/src/cli/commands/deploy.ts b/src/cli/commands/deploy.ts index 7c91a9b..61b8e9f 100644 --- a/src/cli/commands/deploy.ts +++ b/src/cli/commands/deploy.ts @@ -1,4 +1,4 @@ -export default ({ createContext, contextSteppes: { createEnvironment }, projectConfig, requireValue }) => { +export default ({ createContext, executor, ExecuteStep, projectConfig, requireValue }) => { return { commands({ commander }) { commander @@ -22,7 +22,7 @@ export default ({ createContext, contextSteppes: { createEnvironment }, projectC version: projectConfig.version }) - await context.runStep(createEnvironment) + await executor(context, ExecuteStep.get('CreateEnvironment')) console.log(`done`) } catch (e) { diff --git a/src/cli/commands/local.ts b/src/cli/commands/local.ts index de2a0d7..6bc1dfa 100644 --- a/src/cli/commands/local.ts +++ b/src/cli/commands/local.ts @@ -2,7 +2,7 @@ import * as express from 'express' import * as bodyParser from 'body-parser' import * as cors from 'cors' -export default ({ createContext, annotations: { getMetadata, constants, getFunctionName }, projectConfig, requireValue }) => { +export default ({ createContext, annotations: { getMetadata, constants, getFunctionName }, projectConfig, requireValue, executor }) => { const startLocal = async (context) => { let app = express() @@ -95,7 +95,7 @@ export default ({ createContext, annotations: { getMetadata, constants, getFunct localPort }) - await context.runStep({ name: 'startLocal', execute: startLocal }) + await executor(context, { name: 'startLocal', method: startLocal }) console.log(`done`) } catch (e) { diff --git a/src/cli/commands/serverless.ts b/src/cli/commands/serverless.ts index 97a240b..63510a4 100644 --- a/src/cli/commands/serverless.ts +++ b/src/cli/commands/serverless.ts @@ -19,7 +19,7 @@ export default (api) => { }, projectConfig, requireValue, resolvePath, - contextSteppes + ExecuteStep, executor } = api const serverlessConfig = async (context) => { @@ -35,7 +35,7 @@ export default (api) => { stage: 'dev', environment: {} } - await context.runStep({ name: 'iamRoleConfig', execute: iamRoleConfig }) + await executor({ context, name: 'iamRoleConfig', method: iamRoleConfig }) } const iamRoleConfig = async (context) => { @@ -76,9 +76,11 @@ export default (api) => { context.serverless.functions = {} for (const serviceDefinition of context.publishedFunctions) { - context.serviceDefinition = serviceDefinition - await context.runStep({ name: 'functionExport', execute: functionExport }) - delete context.serviceDefinition + await executor({ + context: { ...context, serviceDefinition }, + name: 'functionExport', + method: functionExport + }) } } @@ -96,8 +98,8 @@ export default (api) => { runtime: getMetadata(CLASS_RUNTIMEKEY, serviceDefinition.service) || "nodejs6.10" } - await context.runStep({ name: 'funtionEnvironments', execute: funtionEnvironments }) - await context.runStep({ name: 'funtionEvents', execute: funtionEvents }) + await executor({ context, name: 'funtionEnvironments', method: funtionEnvironments }) + await executor({ context, name: 'funtionEvents', method: funtionEvents }) } const functional_service_regexp = /^FUNCTIONAL_SERVICE_/ @@ -145,9 +147,11 @@ export default (api) => { } for (const tableDefinition of context.tableConfigs) { - context.tableConfig = tableDefinition - await context.runStep({ name: 'tableConfig', execute: tableConfig }) - delete context.tableConfig + await executor({ + context: { ...context, tableConfig }, + name: 'tableConfig', + method: tableConfig + }) } } @@ -173,11 +177,11 @@ export default (api) => { } const build = async (context) => { - await context.runStep({ name: 'serverlessConfig', execute: serverlessConfig }) - await context.runStep({ name: 'providerConfig', execute: providerConfig }) - await context.runStep({ name: 'functionsConfig', execute: functionsConfig }) - await context.runStep({ name: 'resourcesConfig', execute: resourcesConfig }) - await context.runStep({ name: 'saveConfig', execute: saveConfig }) + await executor({ context, name: 'serverlessConfig', method: serverlessConfig }) + await executor({ context, name: 'providerConfig', method: providerConfig }) + await executor({ context, name: 'functionsConfig', method: functionsConfig }) + await executor({ context, name: 'resourcesConfig', method: resourcesConfig }) + await executor({ context, name: 'saveConfig', method: saveConfig }) } @@ -202,7 +206,7 @@ export default (api) => { FUNCTIONAL_ENVIRONMENT }) - await context.runStep(contextSteppes.setFunctionalEnvironment) + await executor(context, ExecuteStep.get('SetFunctionalEnvironment')) await build(context) diff --git a/src/cli/context/core/contextStep.ts b/src/cli/context/core/contextStep.ts deleted file mode 100644 index f9d37e3..0000000 --- a/src/cli/context/core/contextStep.ts +++ /dev/null @@ -1,32 +0,0 @@ -export const contextSteppes: { [x: string]: ContextStep | { name: string, execute: (c) => any } } = {} - -export class ContextStep { - public name: string - public constructor(name) { - this.name = name - - if (contextSteppes[this.name]) { - throw new Error(`step name '${this.name}' already defined`) - } - contextSteppes[this.name] = this - } - - public execute(context) { - - } - - public static register(name, execute) { - if (contextSteppes[name]) { - throw new Error(`step name '${name}' already defined`) - } - - const step = { - name, - execute - } - - contextSteppes[name] = step - - return step - } -} \ No newline at end of file diff --git a/src/cli/context/core/executeStep.ts b/src/cli/context/core/executeStep.ts new file mode 100644 index 0000000..fb22fa5 --- /dev/null +++ b/src/cli/context/core/executeStep.ts @@ -0,0 +1,37 @@ +export const executeSteppes = new Map any }>() + +export class ExecuteStep { + public name: string + public constructor(name) { + this.name = name + + if (executeSteppes.has(this.name)) { + throw new Error(`step name '${this.name}' already defined`) + } + executeSteppes.set(this.name, this) + } + + public method(context) { + + } + + public static register(name, method) { + if (executeSteppes.get(name)) { + throw new Error(`step name '${name}' already defined`) + } + + const step = { + name, + method + } + + executeSteppes.set(name, step) + return step + } + + public static get(name: string) { + if (executeSteppes.has(name)) { + return executeSteppes.get(name) + } + } +} \ No newline at end of file diff --git a/src/cli/context/index.ts b/src/cli/context/index.ts index ee3529b..f260c24 100644 --- a/src/cli/context/index.ts +++ b/src/cli/context/index.ts @@ -1,4 +1,4 @@ -export { contextSteppes, ContextStep } from './core/contextStep' +export { ExecuteStep } from './core/executeStep' import { serviceDiscovery } from './steppes/serviceDiscovery' import { tableDiscovery } from './steppes/tableDiscovery' @@ -38,14 +38,6 @@ export const createContext = async (path, defaultValues) => { } let depth = 0 -// export class Context { -// [p: string]: any - -// public async runStep(step: Function | any) { -// return await runStep(this, step) -// } -// } - export const executor = async (context, step?: Function | any) => { if (!step) { step = context; @@ -55,10 +47,7 @@ export const executor = async (context, step?: Function | any) => { let result = undefined if (typeof step === 'function') { result = await step(context) - } else if (step && step.name && typeof (step.execute === 'function' || step.method === 'function')) { - - const method = step.method || step.execute - + } else if (step && step.name && typeof step.method === 'function') { const tab = depth++ const separator = (s = ' ') => { let result = '' @@ -70,26 +59,26 @@ export const executor = async (context, step?: Function | any) => { try { - if (projectConfig.debug) logger.debug(`Context step run -----------${separator('--')}> ${step.name}`) - if (projectConfig.debug) logger.debug(`Context step before start ${separator()} ${step.name}`) + if (projectConfig.debug) logger.debug(`executor | run -----------${separator('--')}> ${step.name}`) + if (projectConfig.debug) logger.debug(`executor | before start ${separator()} ${step.name}`) await cliCallContextHooks(step.name, context, CONTEXT_HOOK_MODIFIER_BEFORE) - if (projectConfig.debug) logger.debug(`Context step before end ${separator()} ${step.name}`) + if (projectConfig.debug) logger.debug(`executor | before end ${separator()} ${step.name}`) - if (projectConfig.debug) logger.debug(`Context step start ${separator()} ${step.name}`) + if (projectConfig.debug) logger.debug(`executor | start ${separator()} ${step.name}`) if (hasCliCallContextHooks(step.name, context)) { result = await cliCallContextHooks(step.name, context) } else { - result = await method.call(step, context) + result = await step.method(context) } - if (projectConfig.debug) logger.debug(`Context step end ${separator()} ${step.name}`) + if (projectConfig.debug) logger.debug(`executor | end ${separator()} ${step.name}`) - if (projectConfig.debug) logger.debug(`Context step after start ${separator()} ${step.name}`) + if (projectConfig.debug) logger.debug(`executor | after start ${separator()} ${step.name}`) await cliCallContextHooks(step.name, context, CONTEXT_HOOK_MODIFIER_AFTER) - if (projectConfig.debug) logger.debug(`Context step after end ${separator()} ${step.name}`) - if (projectConfig.debug) logger.debug(`Context step complete ------${separator('--')}> ${step.name}`) + if (projectConfig.debug) logger.debug(`executor | after end ${separator()} ${step.name}`) + if (projectConfig.debug) logger.debug(`executor | complete ------${separator('--')}> ${step.name}`) } catch (e) { - if (projectConfig.debug) logger.debug(`Context step exited --------${separator('--')}> ${step.name}`) + if (projectConfig.debug) logger.debug(`executor | exited --------${separator('--')}> ${step.name}`) depth-- throw e } diff --git a/src/cli/context/steppes/serviceDiscovery.ts b/src/cli/context/steppes/serviceDiscovery.ts index 3506f6f..8aa2808 100644 --- a/src/cli/context/steppes/serviceDiscovery.ts +++ b/src/cli/context/steppes/serviceDiscovery.ts @@ -1,12 +1,12 @@ import { lstat, readdirSync } from 'fs' import { FunctionalService } from '../../../classes/functionalService' -import { ContextStep } from '../core/contextStep' +import { ExecuteStep } from '../core/executeStep' import { join, basename, extname } from 'path' import { set } from 'lodash' -export class ServiceDiscoveryStep extends ContextStep { - public async execute(context) { +export class ServiceDiscoveryStep extends ExecuteStep { + public async method(context) { context.files = context.files || [] context.publishedFunctions = context.publishedFunctions || [] @@ -73,4 +73,4 @@ export class ServiceDiscoveryStep extends ContextStep { } -export const serviceDiscovery = new ServiceDiscoveryStep('serviceDiscovery') +export const serviceDiscovery = new ServiceDiscoveryStep('ServiceDiscovery') diff --git a/src/cli/context/steppes/setFunctionalEnvironment.ts b/src/cli/context/steppes/setFunctionalEnvironment.ts index 3cf2acf..cc893ff 100644 --- a/src/cli/context/steppes/setFunctionalEnvironment.ts +++ b/src/cli/context/steppes/setFunctionalEnvironment.ts @@ -1,7 +1,7 @@ import { environment } from '../../../annotations' -import { ContextStep } from '../core/contextStep' -export class SetFunctionalEnvironmentStep extends ContextStep { - public async execute(context) { +import { ExecuteStep } from '../core/executeStep' +export class SetFunctionalEnvironmentStep extends ExecuteStep { + public async method(context) { for (let serviceDefinition of context.publishedFunctions) { const setEnvAttrib = environment('FUNCTIONAL_ENVIRONMENT', context.FUNCTIONAL_ENVIRONMENT) setEnvAttrib(serviceDefinition.service) @@ -9,4 +9,4 @@ export class SetFunctionalEnvironmentStep extends ContextStep { } } -export const setFunctionalEnvironment = new SetFunctionalEnvironmentStep('setFunctionalEnvironment') +export const setFunctionalEnvironment = new SetFunctionalEnvironmentStep('SetFunctionalEnvironment') diff --git a/src/cli/context/steppes/tableDiscovery.ts b/src/cli/context/steppes/tableDiscovery.ts index ed374b3..595aacb 100644 --- a/src/cli/context/steppes/tableDiscovery.ts +++ b/src/cli/context/steppes/tableDiscovery.ts @@ -1,9 +1,9 @@ import { getMetadata, constants, __dynamoDBDefaults } from '../../../annotations' -import { ContextStep } from '../core/contextStep' +import { ExecuteStep } from '../core/executeStep' -export class TableDiscoveryStep extends ContextStep { +export class TableDiscoveryStep extends ExecuteStep { protected tableNameEnvRegexp = /_TABLE_NAME$/ - public async execute(context) { + public async method(context) { const tablesToCreate = new Map() for (let serviceDefinition of context.publishedFunctions) { @@ -34,4 +34,4 @@ export class TableDiscoveryStep extends ContextStep { } -export const tableDiscovery = new TableDiscoveryStep('tableDiscovery') +export const tableDiscovery = new TableDiscoveryStep('TableDiscovery') diff --git a/src/cli/extensions/hooks.ts b/src/cli/extensions/hooks.ts index 5edf021..4ebb0d7 100644 --- a/src/cli/extensions/hooks.ts +++ b/src/cli/extensions/hooks.ts @@ -1,4 +1,3 @@ -import { ContextStep } from '../context/core/contextStep' import { getPluginDefinitions, projectConfig } from '../project/config' import { logger } from '../utilities/logger' diff --git a/src/cli/project/init.ts b/src/cli/project/init.ts index bd85834..899def2 100644 --- a/src/cli/project/init.ts +++ b/src/cli/project/init.ts @@ -5,13 +5,13 @@ import { projectConfig, updateConfig, setPluginDefinitions, getPluginDefinitions import { logger } from '../utilities/logger' import { resolvePath, requireValue } from '../utilities/cli' -import { contextSteppes, createContext, ContextStep } from '../context' +import { createContext, ExecuteStep, executor } from '../context' import * as annotations from '../../annotations' export const pluginInitParam: any = { logger, resolvePath, requireValue, - contextSteppes, createContext, ContextStep, + createContext, ExecuteStep, executor, annotations, projectConfig, getPluginDefinitions diff --git a/src/cli/providers/aws.ts b/src/cli/providers/aws.ts index 4c6280e..36bb3f4 100644 --- a/src/cli/providers/aws.ts +++ b/src/cli/providers/aws.ts @@ -12,17 +12,17 @@ import { updateLambdaFunctionConfiguration, updateLambdaFunctionTags } from '../utilities/aws/lambda' -import { ContextStep } from '../context' +import { ExecuteStep, executor } from '../context' import { projectConfig } from '../project/config' export const aws = { FUNCTIONAL_ENVIRONMENT: 'aws', - createEnvironment: ContextStep.register('createEnvironment_aws', async (context) => { - await context.runStep(bundle) - await context.runStep(zip) + createEnvironment: ExecuteStep.register('CreateEnvironment_aws', async (context) => { + await executor(context, bundle) + await executor(context, zip) const localName = projectConfig.name ? `${projectConfig.name}.zip` : 'project.zip' - await context.runStep(uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData(), localName)) - await context.runStep(createTables) + await executor(context, uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData(), localName)) + await executor(context, createTables) for (let serviceDefinition of context.publishedFunctions) { const serviceName = getFunctionName(serviceDefinition.service) @@ -30,13 +30,13 @@ export const aws = { console.log(`${serviceName} deploying...`) context.serviceDefinition = serviceDefinition try { - await context.runStep(getLambdaFunction) - await context.runStep(updateLambdaFunctionCode) - await context.runStep(updateLambdaFunctionConfiguration) - await context.runStep(updateLambdaFunctionTags) + await executor(context, getLambdaFunction) + await executor(context, updateLambdaFunctionCode) + await executor(context, updateLambdaFunctionConfiguration) + await executor(context, updateLambdaFunctionTags) } catch (e) { if (e.code === "ResourceNotFoundException") { - await context.runStep(createLambdaFunction) + await executor(context, createLambdaFunction) } else { throw e } diff --git a/src/cli/providers/cloudFormation/context/apiGateway.ts b/src/cli/providers/cloudFormation/context/apiGateway.ts index 1ec5b9f..87e8be7 100644 --- a/src/cli/providers/cloudFormation/context/apiGateway.ts +++ b/src/cli/providers/cloudFormation/context/apiGateway.ts @@ -1,18 +1,18 @@ import { getMetadata, constants } from '../../../../annotations' const { CLASS_APIGATEWAYKEY } = constants -import { ContextStep, executor } from '../../../context' +import { ExecuteStep, executor } from '../../../context' import { setResource } from '../utils' export const API_GATEWAY_REST_API = 'ApiGatewayRestApi' -export const apiGateway = ContextStep.register('ApiGateway', async (context) => { +export const apiGateway = ExecuteStep.register('ApiGateway', async (context) => { const deploymentResources = [] await executor({ ...context, deploymentResources }, gatewayRestApi) await executor({ ...context, deploymentResources }, gatewayResources) await executor({ ...context, deploymentResources }, gatewayDeployment) }) -export const gatewayRestApi = ContextStep.register('ApiGateway-RestApi', async (context) => { +export const gatewayRestApi = ExecuteStep.register('ApiGateway-RestApi', async (context) => { const RestApi = { "Type": "AWS::ApiGateway::RestApi", "Properties": { @@ -39,7 +39,7 @@ export const gatewayRestApi = ContextStep.register('ApiGateway-RestApi', async ( }) -export const gatewayResources = ContextStep.register('ApiGateway-Resources', async (context) => { +export const gatewayResources = ExecuteStep.register('ApiGateway-Resources', async (context) => { const endpointsCors = new Map() const endpoints = new Map() for (const serviceDefinition of context.publishedFunctions) { @@ -172,7 +172,7 @@ export const apiGatewayPathPart = async (context) => { return endpointResourceName } -export const gatewayDeployment = ContextStep.register('ApiGateway-Deployment', async (context) => { +export const gatewayDeployment = ExecuteStep.register('ApiGateway-Deployment', async (context) => { const deploymentResources = context.deploymentResources .filter(r => r.type === 'AWS::ApiGateway::Method') diff --git a/src/cli/providers/cloudFormation/context/cloudFormationInit.ts b/src/cli/providers/cloudFormation/context/cloudFormationInit.ts index 1553e31..49679c1 100644 --- a/src/cli/providers/cloudFormation/context/cloudFormationInit.ts +++ b/src/cli/providers/cloudFormation/context/cloudFormationInit.ts @@ -1,10 +1,10 @@ import { resolvePath } from '../../../utilities/cli' import { projectConfig } from '../../../project/config' -import { ContextStep } from '../../../context' +import { ExecuteStep } from '../../../context' import { merge } from 'lodash' -export const cloudFormationInit = ContextStep.register('CloudFormationInit', async (context) => { +export const cloudFormationInit = ExecuteStep.register('CloudFormationInit', async (context) => { context.CloudFormationConfig = merge({}, { StackName: projectConfig.name, OnFailure: "ROLLBACK", diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index bbfafe4..79bab39 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -2,17 +2,13 @@ import { merge } from 'lodash' import { getMetadata, constants, getFunctionName, __dynamoDBDefaults } from '../../../../annotations' const { CLASS_DESCRIPTIONKEY, CLASS_ROLEKEY, CLASS_MEMORYSIZEKEY, CLASS_RUNTIMEKEY, CLASS_TIMEOUTKEY, CLASS_ENVIRONMENTKEY, CLASS_TAGKEY, CLASS_APIGATEWAYKEY } = constants -import { ContextStep, executor } from '../../../context' +import { ExecuteStep, executor } from '../../../context' import { setResource } from '../utils' export { apiGateway } from './apiGateway' -export const roleResources = ContextStep.register('IAM-Role', async (context) => { +export const roleResources = ExecuteStep.register('IAM-Role', async (context) => { const roleMap = new Map() - - for (const serviceDefinition of context.publishedFunctions) { - - let role = getMetadata(CLASS_ROLEKEY, serviceDefinition.service) if (typeof role === 'string' && /^arn:/.test(role)) { continue @@ -23,7 +19,6 @@ export const roleResources = ContextStep.register('IAM-Role', async (context) => } if (!roleMap.has(role)) { - const properties = { "RoleName": role, "AssumeRolePolicyDocument": { @@ -85,14 +80,14 @@ export const roleResources = ContextStep.register('IAM-Role', async (context) => }] } - const tableResource = { + const iamRole = { "Type": "AWS::IAM::Role", "Properties": properties } - const roleResourceName = `iam_${properties.RoleName}` - const resourceName = setResource(context, roleResourceName, tableResource) + const roleResourceName = `IAM${properties.RoleName}` + const resourceName = setResource(context, roleResourceName, iamRole) roleMap.set(role, { ref: { "Fn::GetAtt": [ @@ -112,84 +107,106 @@ export const roleResources = ContextStep.register('IAM-Role', async (context) => serviceDefinition[CLASS_ROLEKEY] = iam_role.ref } - }) -export const tableResources = ContextStep.register('DynamoDB-Tables', async (context) => { - +export const tableResources = ExecuteStep.register('DynamoDB-Tables', async (context) => { for (const tableConfig of context.tableConfigs) { + await executor({ + context: { ...context, tableConfig }, + name: `DynamoDB-Table-${tableConfig.tableName}`, + method: tableResource + }) + } +}) - const properties = merge({}, { - TableName: tableConfig.tableName - }, tableConfig.nativeConfig, __dynamoDBDefaults); +export const tableResource = async (context) => { + const { tableConfig } = context + const properties = merge({}, { + TableName: tableConfig.tableName + }, tableConfig.nativeConfig, __dynamoDBDefaults); - const tableResource = { - "Type": "AWS::DynamoDB::Table", - "Properties": properties - } + const dynamoDb = { + "Type": "AWS::DynamoDB::Table", + "Properties": properties + } - const resourceName = `dynamo_${properties.TableName}` - const name = setResource(context, resourceName, tableResource) - tableConfig.resourceName = name - } + const resourceName = `Dynamo${properties.TableName}` + const name = setResource(context, resourceName, dynamoDb) -}) + tableConfig.resourceName = name + +} -export const lambdaResources = ContextStep.register('Lambda-Functions', async (context) => { +export const lambdaResources = ExecuteStep.register('Lambda-Functions', async (context) => { const awsBucket = context.__userAWSBucket ? context.awsBucket : { "Ref": "FunctionlyDeploymentBucket" } - for (const serviceDefinition of context.publishedFunctions) { + await executor({ + context: { ...context, serviceDefinition, awsBucket }, + name: `Lambda-Function-${serviceDefinition.service.name}`, + method: lambdaResource + }) + await executor({ + context: { ...context, serviceDefinition }, + name: `Lambda-Version-${serviceDefinition.service.name}`, + method: lambdaVersionResource + }) + } +}) - const properties: any = { - Code: { - S3Bucket: awsBucket, - S3Key: context.S3Zip - }, - Description: serviceDefinition[CLASS_DESCRIPTIONKEY] || getMetadata(CLASS_DESCRIPTIONKEY, serviceDefinition.service), - FunctionName: getFunctionName(serviceDefinition.service), - Handler: serviceDefinition.handler, - MemorySize: serviceDefinition[CLASS_MEMORYSIZEKEY] || getMetadata(CLASS_MEMORYSIZEKEY, serviceDefinition.service), - Role: serviceDefinition[CLASS_ROLEKEY] || getMetadata(CLASS_ROLEKEY, serviceDefinition.service), - Runtime: serviceDefinition[CLASS_RUNTIMEKEY] || getMetadata(CLASS_RUNTIMEKEY, serviceDefinition.service) || "nodejs6.10", - Timeout: serviceDefinition[CLASS_TIMEOUTKEY] || getMetadata(CLASS_TIMEOUTKEY, serviceDefinition.service), - Environment: { - Variables: serviceDefinition[CLASS_ENVIRONMENTKEY] || getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) - }, - Tags: serviceDefinition[CLASS_TAGKEY] || getMetadata(CLASS_TAGKEY, serviceDefinition.service) - }; +export const lambdaResource = async (context) => { + const { serviceDefinition, awsBucket } = context + + const properties: any = { + Code: { + S3Bucket: awsBucket, + S3Key: context.S3Zip + }, + Description: serviceDefinition[CLASS_DESCRIPTIONKEY] || getMetadata(CLASS_DESCRIPTIONKEY, serviceDefinition.service), + FunctionName: getFunctionName(serviceDefinition.service), + Handler: serviceDefinition.handler, + MemorySize: serviceDefinition[CLASS_MEMORYSIZEKEY] || getMetadata(CLASS_MEMORYSIZEKEY, serviceDefinition.service), + Role: serviceDefinition[CLASS_ROLEKEY] || getMetadata(CLASS_ROLEKEY, serviceDefinition.service), + Runtime: serviceDefinition[CLASS_RUNTIMEKEY] || getMetadata(CLASS_RUNTIMEKEY, serviceDefinition.service) || "nodejs6.10", + Timeout: serviceDefinition[CLASS_TIMEOUTKEY] || getMetadata(CLASS_TIMEOUTKEY, serviceDefinition.service), + Environment: { + Variables: serviceDefinition[CLASS_ENVIRONMENTKEY] || getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) + }, + Tags: serviceDefinition[CLASS_TAGKEY] || getMetadata(CLASS_TAGKEY, serviceDefinition.service) + }; + + const lambdaResource = { + "Type": "AWS::Lambda::Function", + "Properties": properties + } - const lambdaResource = { - "Type": "AWS::Lambda::Function", - "Properties": properties - } + const resourceName = `Lambda${properties.FunctionName}` + const name = setResource(context, resourceName, lambdaResource) + serviceDefinition.resourceName = name +} - const resourceName = `lambda_${properties.FunctionName}` - const name = setResource(context, resourceName, lambdaResource) - serviceDefinition.resourceName = name +export const lambdaVersionResource = async (context) => { + const { serviceDefinition } = context - const versionResource = { - "Type": "AWS::Lambda::Version", - "DeletionPolicy": "Retain", - "Properties": { - "FunctionName": { - "Ref": name - }, - "CodeSha256": context.zipCodeSha256 - } + const versionResource = { + "Type": "AWS::Lambda::Version", + "DeletionPolicy": "Retain", + "Properties": { + "FunctionName": { + "Ref": serviceDefinition.resourceName + }, + "CodeSha256": context.zipCodeSha256 } - setResource(context, `${name}${context.zipCodeSha256}`, versionResource) - } + setResource(context, `${serviceDefinition.resourceName}${context.zipCodeSha256}`, versionResource) +} -}) - -export const s3BucketResources = ContextStep.register('S3-Bucket', async (context) => { +export const s3BucketResources = ExecuteStep.register('S3-Bucket', async (context) => { if (context.awsBucket) { context.__userAWSBucket = true } diff --git a/src/cli/providers/cloudFormation/context/uploadTemplate.ts b/src/cli/providers/cloudFormation/context/uploadTemplate.ts index e534a1b..29dbe0d 100644 --- a/src/cli/providers/cloudFormation/context/uploadTemplate.ts +++ b/src/cli/providers/cloudFormation/context/uploadTemplate.ts @@ -1,11 +1,11 @@ import { uploaderStep } from '../../../utilities/aws/s3Upload' -import { ContextStep } from '../../../context' +import { ExecuteStep, executor } from '../../../context' import { projectConfig } from '../../../project/config' -export const uploadTemplate = ContextStep.register('UploadTemplate', async (context) => { +export const uploadTemplate = ExecuteStep.register('UploadTemplate', async (context) => { const templateData = JSON.stringify(context.CloudFormationTemplate, null, 2); const localName = projectConfig.name ? `${projectConfig.name}.template` : 'cloudformation.template' - const uploadresult = await context.runStep(uploaderStep(`services-${context.date.toISOString()}.template`, templateData, 'application/octet-stream', localName)) + const uploadresult = await executor(context, uploaderStep(`services-${context.date.toISOString()}.template`, templateData, 'application/octet-stream', localName)) context.S3CloudFormationTemplate = uploadresult.Key return uploadresult }) \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/index.ts b/src/cli/providers/cloudFormation/index.ts index e6e21a4..e10ac73 100644 --- a/src/cli/providers/cloudFormation/index.ts +++ b/src/cli/providers/cloudFormation/index.ts @@ -5,6 +5,7 @@ import { zip } from '../../utilities/compress' import { uploadZipStep } from '../../utilities/aws/s3Upload' import { createStack, updateStack, getTemplate, getStackBucketName, describeStacks } from '../../utilities/aws/cloudFormation' import { projectConfig } from '../../project/config' +import { executor } from '../../context' import { cloudFormationInit } from './context/cloudFormationInit' import { tableResources, lambdaResources, roleResources, s3BucketResources, apiGateway } from './context/resources' @@ -14,41 +15,41 @@ export const cloudFormation = { FUNCTIONAL_ENVIRONMENT: 'aws', createEnvironment: async (context) => { logger.info(`Functionly: Packgaging...`) - await context.runStep(bundle) - await context.runStep(zip) + await executor(context, bundle) + await executor(context, zip) - await context.runStep(cloudFormationInit) - await context.runStep(s3BucketResources) + await executor(context, cloudFormationInit) + await executor(context, s3BucketResources) try { - await context.runStep(getTemplate) + await executor(context, getTemplate) } catch (e) { if (/^Stack with id .* does not exist$/.test(e.message)) { logger.info(`Functionly: Creating stack...`) - await context.runStep(createStack) + await executor(context, createStack) } else { console.log(e) throw e } } if (!context.awsBucket) { - await context.runStep(getStackBucketName) + await executor(context, getStackBucketName) } logger.info(`Functionly: Uploading binary...`) const localName = projectConfig.name ? `${projectConfig.name}.zip` : 'project.zip' - await context.runStep(uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData(), localName)) + await executor(context, uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData(), localName)) - await context.runStep(roleResources) - await context.runStep(tableResources) - await context.runStep(lambdaResources) - await context.runStep(apiGateway) + await executor(context, roleResources) + await executor(context, tableResources) + await executor(context, lambdaResources) + await executor(context, apiGateway) logger.info(`Functionly: Uploading template...`) - await context.runStep(uploadTemplate) + await executor(context, uploadTemplate) logger.info(`Functionly: Updating stack...`) - await context.runStep(updateStack) + await executor(context, updateStack) logger.info(`Functionly: Complete`) } } diff --git a/src/cli/providers/index.ts b/src/cli/providers/index.ts index 47e994b..d9d7869 100644 --- a/src/cli/providers/index.ts +++ b/src/cli/providers/index.ts @@ -1,13 +1,13 @@ import { aws } from './aws' import { local } from './local' import { cloudFormation as cf } from './cloudFormation' -import { contextSteppes, ContextStep } from '../context' +import { ExecuteStep, executor } from '../context' import { getPluginDefinitions } from '../project/config' let environments = { aws, local, cf } -export class CreateEnvironmentStep extends ContextStep { - public async execute(context) { +export class CreateEnvironmentStep extends ExecuteStep { + public async method(context) { let currentEnvironment = environments[context.deployTarget] if (!currentEnvironment) { @@ -30,11 +30,11 @@ export class CreateEnvironmentStep extends ContextStep { if (currentEnvironment.FUNCTIONAL_ENVIRONMENT) { context.FUNCTIONAL_ENVIRONMENT = currentEnvironment.FUNCTIONAL_ENVIRONMENT - await context.runStep(contextSteppes.setFunctionalEnvironment) + await executor(context, ExecuteStep.get('SetFunctionalEnvironment')) } - await context.runStep(currentEnvironment.createEnvironment) + await executor(context, currentEnvironment.createEnvironment) } } -export const createEnvironment = new CreateEnvironmentStep('createEnvironment') \ No newline at end of file +export const createEnvironment = new CreateEnvironmentStep('CreateEnvironment') \ No newline at end of file diff --git a/src/cli/providers/local.ts b/src/cli/providers/local.ts index b498cf0..eeeb95e 100644 --- a/src/cli/providers/local.ts +++ b/src/cli/providers/local.ts @@ -1,10 +1,10 @@ import { createTables } from '../utilities/aws/dynamoDB' import { getMetadata, constants } from '../../annotations' -import { ContextStep } from '../context' +import { ExecuteStep, executor } from '../context' export const local = { FUNCTIONAL_ENVIRONMENT: 'local', - createEnvironment: ContextStep.register('createEnvironment_local', async (context) => { - await context.runStep(createTables) + createEnvironment: ExecuteStep.register('CreateEnvironment_local', async (context) => { + await executor(context, createTables) }) } diff --git a/src/cli/utilities/aws/cloudFormation.ts b/src/cli/utilities/aws/cloudFormation.ts index 28a37b4..2ab3673 100644 --- a/src/cli/utilities/aws/cloudFormation.ts +++ b/src/cli/utilities/aws/cloudFormation.ts @@ -1,7 +1,7 @@ import { CloudFormation } from 'aws-sdk' import { merge, pick } from 'lodash' import { config } from '../../utilities/config' -import { ContextStep } from '../../context' +import { ExecuteStep } from '../../context' export const UPDATE_STACK_PRPERTIES = ['StackName', 'Capabilities', 'ClientRequestToken', 'Parameters', 'ResourceTypes', 'RoleARN', 'StackPolicyBody', @@ -24,7 +24,7 @@ const initAWSSDK = (context) => { -export const createStack = ContextStep.register('CF-CreateStack', (context) => { +export const createStack = ExecuteStep.register('CF-CreateStack', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { const cfConfig: any = pick(context.CloudFormationConfig, CREATE_STACK_PRPERTIES) @@ -46,7 +46,7 @@ export const createStack = ContextStep.register('CF-CreateStack', (context) => { }) }) -export const updateStack = ContextStep.register('CF-UpdateStack', (context) => { +export const updateStack = ExecuteStep.register('CF-UpdateStack', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { const cfConfig: any = pick(context.CloudFormationConfig, UPDATE_STACK_PRPERTIES) @@ -69,7 +69,7 @@ export const updateStack = ContextStep.register('CF-UpdateStack', (context) => { }) }) -export const getTemplate = ContextStep.register('CF-GetTemplate', (context) => { +export const getTemplate = ExecuteStep.register('CF-GetTemplate', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { @@ -84,7 +84,7 @@ export const getTemplate = ContextStep.register('CF-GetTemplate', (context) => { }) -export const getStackBucketName = ContextStep.register('CF-GetStackBucketName', (context) => { +export const getStackBucketName = ExecuteStep.register('CF-GetStackBucketName', (context) => { if (context.awsBucket) return context.awsBucket initAWSSDK(context) @@ -102,7 +102,7 @@ export const getStackBucketName = ContextStep.register('CF-GetStackBucketName', }) }) -export const describeStacks = ContextStep.register('CF-DescribeStacks', (context) => { +export const describeStacks = ExecuteStep.register('CF-DescribeStacks', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { @@ -125,7 +125,7 @@ export const stackStateWaiter = (status, context, tries = 20, timeout = 5000) => if (iteration >= tries) return reject(new Error(`stackStateWaiter timeout '${status}'`)) iteration++ setTimeout(async () => { - const stack = await describeStacks.execute(context) + const stack = await describeStacks.method(context) console.log(' >', stack.StackStatus) if (waitregexp.test(stack.StackStatus)) { return waiter() diff --git a/src/cli/utilities/aws/dynamoDB.ts b/src/cli/utilities/aws/dynamoDB.ts index fa1501f..cb35768 100644 --- a/src/cli/utilities/aws/dynamoDB.ts +++ b/src/cli/utilities/aws/dynamoDB.ts @@ -2,7 +2,7 @@ import { DynamoDB } from 'aws-sdk' import { merge } from 'lodash' import { config } from '../config' import { __dynamoDBDefaults } from '../../../annotations' -import { ContextStep } from '../../context' +import { ExecuteStep, executor } from '../../context' let dynamoDB = null; const initAWSSDK = (context) => { @@ -22,12 +22,16 @@ const initAWSSDK = (context) => { } -export const createTables = ContextStep.register('createTables', async (context) => { +export const createTables = ExecuteStep.register('CreateTables', async (context) => { initAWSSDK(context) for (let tableConfig of context.tableConfigs) { try { - let data = await createTable(tableConfig, context) + let data = await executor({ + context: { ...context, tableConfig }, + name: `CreateTable-${tableConfig.tableName}`, + method: createTable + }) console.log(`${data.TableDescription.TableName} DynamoDB table created.`) } catch (e) { if (e.code !== 'ResourceInUseException') { @@ -37,8 +41,9 @@ export const createTables = ContextStep.register('createTables', async (context) } }) -export const createTable = (tableConfig, context) => { +export const createTable = (context) => { initAWSSDK(context) + const { tableConfig } = context return new Promise((resolve, reject) => { let params = merge({}, { diff --git a/src/cli/utilities/aws/lambda.ts b/src/cli/utilities/aws/lambda.ts index e5995d6..6b6694a 100644 --- a/src/cli/utilities/aws/lambda.ts +++ b/src/cli/utilities/aws/lambda.ts @@ -2,7 +2,7 @@ import { Lambda } from 'aws-sdk' import { merge, difference } from 'lodash' import { config } from '../../utilities/config' import { getMetadata, constants, getFunctionName } from '../../../annotations' -import { ContextStep } from '../../context' +import { ExecuteStep, executor } from '../../context' let lambda = null; @@ -20,7 +20,7 @@ const initAWSSDK = (context) => { -export const createLambdaFunction = ContextStep.register('createLambdaFunction', (context) => { +export const createLambdaFunction = ExecuteStep.register('CreateLambdaFunction', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { @@ -51,7 +51,7 @@ export const createLambdaFunction = ContextStep.register('createLambdaFunction', }) }) -export const getLambdaFunction = ContextStep.register('getLambdaFunction', (context) => { +export const getLambdaFunction = ExecuteStep.register('GetLambdaFunction', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { @@ -64,7 +64,7 @@ export const getLambdaFunction = ContextStep.register('getLambdaFunction', (cont }) }) -export const deleteLambdaFunction = ContextStep.register('deleteLambdaFunction', (context) => { +export const deleteLambdaFunction = ExecuteStep.register('DeleteLambdaFunction', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { @@ -77,7 +77,7 @@ export const deleteLambdaFunction = ContextStep.register('deleteLambdaFunction', }) }) -export const publishLambdaFunction = ContextStep.register('publishLambdaFunction', (context) => { +export const publishLambdaFunction = ExecuteStep.register('PublishLambdaFunction', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { @@ -90,7 +90,7 @@ export const publishLambdaFunction = ContextStep.register('publishLambdaFunction }) }) -export const updateLambdaFunctionCode = ContextStep.register('updateLambdaFunctionCode', (context) => { +export const updateLambdaFunctionCode = ExecuteStep.register('UpdateLambdaFunctionCode', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { @@ -106,7 +106,7 @@ export const updateLambdaFunctionCode = ContextStep.register('updateLambdaFuncti }) }) -export const updateLambdaFunctionConfiguration = ContextStep.register('updateLambdaFunctionConfiguration', (context) => { +export const updateLambdaFunctionConfiguration = ExecuteStep.register('UpdateLambdaFunctionConfiguration', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { @@ -130,9 +130,9 @@ export const updateLambdaFunctionConfiguration = ContextStep.register('updateLam }) }) -export const updateLambdaFunctionTags = ContextStep.register('updateLambdaFunctionTags', async (context) => { +export const updateLambdaFunctionTags = ExecuteStep.register('UpdateLambdaFunctionTags', async (context) => { initAWSSDK(context) - const getLambdaFunctionResult = await context.runStep(getLambdaFunction) + const getLambdaFunctionResult = await executor(context, getLambdaFunction) const Tags = getMetadata(constants.CLASS_TAGKEY, context.serviceDefinition.service) || {} const listTagParams = { diff --git a/src/cli/utilities/aws/s3Upload.ts b/src/cli/utilities/aws/s3Upload.ts index d5d3859..62f49d7 100644 --- a/src/cli/utilities/aws/s3Upload.ts +++ b/src/cli/utilities/aws/s3Upload.ts @@ -1,7 +1,7 @@ import { S3 } from 'aws-sdk' import { merge } from 'lodash' import { config } from '../config' -import { ContextStep } from '../../context' +import { ExecuteStep, executor } from '../../context' import { writeFileSync } from 'fs' import { join, normalize } from 'path' @@ -22,7 +22,7 @@ const initAWSSDK = (context) => { export const uploadZipStep = (name, data, localName?) => { return async (context) => { const step = uploaderStep(name, data, 'application/zip', localName) - const uploadResult = await context.runStep(step) + const uploadResult = await executor(context, step) context.S3Zip = uploadResult.Key return uploadResult } @@ -36,13 +36,13 @@ export const uploaderStep = (name, data, contentType, localName?) => { contentType, localName } - const uploadResult = await context.runStep(uploadToAws) + const uploadResult = await executor(context, uploadToAws) delete context.upload return uploadResult } } -export const uploadToAws = ContextStep.register('uploadToAws', async (context) => { +export const uploadToAws = ExecuteStep.register('S3-Upload', async (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { const version = context.version ? `${context.version}/` : '' diff --git a/src/cli/utilities/compress.ts b/src/cli/utilities/compress.ts index b0efa7e..cd30ca9 100644 --- a/src/cli/utilities/compress.ts +++ b/src/cli/utilities/compress.ts @@ -2,9 +2,9 @@ import * as nodeZip from 'node-zip' import { basename, join } from 'path' import { createHash } from 'crypto' import { readFileSync, writeFileSync } from 'fs' -import { ContextStep } from '../context' +import { ExecuteStep } from '../context' -export const zip = ContextStep.register('zip', (context) => { +export const zip = ExecuteStep.register('Compress-zip', (context) => { let compressor = new nodeZip() for (const file of context.files) { diff --git a/src/cli/utilities/webpack.ts b/src/cli/utilities/webpack.ts index e3f93b5..d366987 100644 --- a/src/cli/utilities/webpack.ts +++ b/src/cli/utilities/webpack.ts @@ -2,13 +2,13 @@ import { merge } from 'lodash' import * as webpack from 'webpack' import { config } from './config' import { basename, extname, join } from 'path' -import { ContextStep } from '../context' +import { ExecuteStep, executor } from '../context' -export const bundle = ContextStep.register('bundle', (context) => { +export const bundle = ExecuteStep.register('WebpackBundle', (context) => { return new Promise(async (resolve, reject) => { - const webpackConfig = await context.runStep(bundleConfig) + const webpackConfig = await executor(context, bundleConfig) webpack(webpackConfig, function (err, stats) { if (err) return reject() @@ -37,7 +37,7 @@ export const bundle = ContextStep.register('bundle', (context) => { }) }) -export const bundleConfig = ContextStep.register('bundleConfig', (context) => { +export const bundleConfig = ExecuteStep.register('WebpackBundleConfig', (context) => { let entry = {} context.files.forEach((file) => { let name = basename(file) From 7b1f68f40f3eab6eb9a73579e609e9ba21a1ce05 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Thu, 15 Jun 2017 12:54:26 +0200 Subject: [PATCH 013/196] REFACTOR: cloudformation IAM role creation; ADD: package command --- src/cli/commands/package.ts | 35 +++ src/cli/commands/serverless.ts | 1 - src/cli/context/steppes/tableDiscovery.ts | 16 +- src/cli/index.ts | 2 + .../cloudFormation/context/resources.ts | 255 ++++++++++++------ src/cli/providers/cloudFormation/index.ts | 23 +- src/cli/providers/index.ts | 10 +- src/cli/utilities/aws/s3Upload.ts | 9 +- 8 files changed, 258 insertions(+), 93 deletions(-) create mode 100644 src/cli/commands/package.ts diff --git a/src/cli/commands/package.ts b/src/cli/commands/package.ts new file mode 100644 index 0000000..c6d4156 --- /dev/null +++ b/src/cli/commands/package.ts @@ -0,0 +1,35 @@ +export default ({ createContext, executor, ExecuteStep, projectConfig, requireValue }) => { + return { + commands({ commander }) { + commander + .command('package [target] [path]') + .description('package functional services') + .option('--aws-region ', 'AWS_REGION') + .option('--aws-bucket ', 'aws bucket') + .action(async (target, path, command) => { + process.env.FUNCTIONAL_ENVIRONMENT = 'deploy' + + try { + const entryPoint = requireValue(path || projectConfig.main, 'entry point') + const deployTarget = requireValue(target || projectConfig.deployTarget, 'missing deploy target') + const awsRegion = requireValue(command.awsRegion || projectConfig.awsRegion, 'awsRegion') + const awsBucket = command.awsBucket || projectConfig.awsBucket + + const context = await createContext(entryPoint, { + deployTarget, + awsRegion, + awsBucket, + version: projectConfig.version, + packageOnly: true + }) + + await executor(context, ExecuteStep.get('CreateEnvironment')) + + console.log(`done`) + } catch (e) { + console.log(`error`, e) + } + }); + } + } +} \ No newline at end of file diff --git a/src/cli/commands/serverless.ts b/src/cli/commands/serverless.ts index 63510a4..e5fdff9 100644 --- a/src/cli/commands/serverless.ts +++ b/src/cli/commands/serverless.ts @@ -63,7 +63,6 @@ export default (api) => { } for (const tableConfig of context.tableConfigs) { - console.log(tableConfig) const properties = merge({}, { TableName: tableConfig.tableName }, tableConfig.nativeConfig) dynamoStatement.Resource.push("arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/" + properties.TableName) } diff --git a/src/cli/context/steppes/tableDiscovery.ts b/src/cli/context/steppes/tableDiscovery.ts index 595aacb..1120888 100644 --- a/src/cli/context/steppes/tableDiscovery.ts +++ b/src/cli/context/steppes/tableDiscovery.ts @@ -10,20 +10,26 @@ export class TableDiscoveryStep extends ExecuteStep { let tableConfigs = getMetadata(constants.CLASS_DYNAMOTABLECONFIGURATIONKEY, serviceDefinition.service) || [] for (const tableConfig of tableConfigs) { if (tablesToCreate.has(tableConfig.tableName)) { + const def = tablesToCreate.get(tableConfig.tableName) + def.usedBy.push(serviceDefinition.service) continue } - tablesToCreate.set(tableConfig.tableName, tableConfig) + tablesToCreate.set(tableConfig.tableName, { ...tableConfig, usedBy: [serviceDefinition.service] }) } let metadata = getMetadata(constants.CLASS_ENVIRONMENTKEY, serviceDefinition.service) if (metadata) { let keys = Object.keys(metadata) for (const key of keys) { - if (this.tableNameEnvRegexp.test(key) && !tablesToCreate.has(metadata[key])) { - tablesToCreate.set(metadata[key], { - TableName: metadata[key] - }) + if (this.tableNameEnvRegexp.test(key)) { + if (tablesToCreate.has(metadata[key])) { + const def = tablesToCreate.get(metadata[key]) + def.usedBy.push(serviceDefinition.service) + continue + } + + tablesToCreate.set(metadata[key], { TableName: metadata[key], usedBy: [serviceDefinition.service] }) } } } diff --git a/src/cli/index.ts b/src/cli/index.ts index 088f148..4e4aae7 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -4,6 +4,7 @@ import './providers' //built-in commands import * as deploy from './commands/deploy' +import * as deployPackage from './commands/package' import * as local from './commands/local' import * as metadata from './commands/metadata' import * as serverless from './commands/serverless' @@ -12,6 +13,7 @@ import { init as initProjectConfig, internalPluginLoad } from './project/init' export const init = (commander) => { internalPluginLoad(deploy) + internalPluginLoad(deployPackage) internalPluginLoad(local) internalPluginLoad(metadata) internalPluginLoad(serverless) diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 79bab39..7ea3935 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -1,4 +1,4 @@ -import { merge } from 'lodash' +import { merge, intersection } from 'lodash' import { getMetadata, constants, getFunctionName, __dynamoDBDefaults } from '../../../../annotations' const { CLASS_DESCRIPTIONKEY, CLASS_ROLEKEY, CLASS_MEMORYSIZEKEY, CLASS_RUNTIMEKEY, CLASS_TIMEOUTKEY, CLASS_ENVIRONMENTKEY, CLASS_TAGKEY, CLASS_APIGATEWAYKEY } = constants @@ -7,107 +7,194 @@ import { setResource } from '../utils' export { apiGateway } from './apiGateway' export const roleResources = ExecuteStep.register('IAM-Role', async (context) => { - const roleMap = new Map() + const roleMap = new Map() for (const serviceDefinition of context.publishedFunctions) { let role = getMetadata(CLASS_ROLEKEY, serviceDefinition.service) if (typeof role === 'string' && /^arn:/.test(role)) { continue } - if (!role) { role = context.CloudFormationConfig.StackName } - if (!roleMap.has(role)) { - const properties = { - "RoleName": role, - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Principal": { - "Service": ["lambda.amazonaws.com"] - }, - "Action": ["sts:AssumeRole"] - }] - }, - "Path": "/", - "Policies": [{ - "PolicyName": { - "Fn::Join": [ - "-", - [ - "functionly", - role, - "lambda" - ] - ] - }, - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Resource": ["*"] - }, { - "Effect": "Allow", - "Action": [ - "lambda:InvokeAsync", - "lambda:InvokeFunction" - ], - "Resource": ["*"] - }, { - "Effect": "Allow", - "Action": [ - "dynamodb:Query", - "dynamodb:Scan", - "dynamodb:GetItem", - "dynamodb:PutItem", - "dynamodb:UpdateItem" - ], - "Resource": [ - { - "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/*" - } - ] - }] - } - }] - } + roleMap.set(role, [serviceDefinition]) + } - const iamRole = { - "Type": "AWS::IAM::Role", - "Properties": properties - } + const roleDef = roleMap.get(role) + roleDef.push(serviceDefinition) + } + + for (const [roleName, serviceDefinitions] of roleMap) { + await executor({ + context: { ...context, roleName, serviceDefinitions }, + name: `IAM-Role-${roleName}`, + method: roleResource + }) + } +}) + +export const roleResource = async (context) => { + const { roleName, serviceDefinitions } = context - const roleResourceName = `IAM${properties.RoleName}` - const resourceName = setResource(context, roleResourceName, iamRole) - roleMap.set(role, { - ref: { - "Fn::GetAtt": [ - resourceName, - "Arn" - ] + const roleProperties = { + "RoleName": roleName, + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Principal": { + "Service": ["lambda.amazonaws.com"] }, - resourceName - }) + "Action": ["sts:AssumeRole"] + }] + }, + "Path": "/", + "Policies": [] + } + + await executor({ + context: { ...context, roleName, serviceDefinitions, roleProperties }, + name: `IAM-Lambda-Policy-${roleName}`, + method: lambdaPolicy + }) + + await executor({ + context: { ...context, roleName, serviceDefinitions, roleProperties }, + name: `IAM-Log-Policy-${roleName}`, + method: logPolicy + }) + + await executor({ + context: { ...context, roleName, serviceDefinitions, roleProperties }, + name: `IAM-Dynamo-Policy-${roleName}`, + method: dynamoPolicy + }) + + if (roleProperties.Policies.length) { + const iamRole = { + "Type": "AWS::IAM::Role", + "Properties": roleProperties + } + + + const roleResourceName = `IAM${roleProperties.RoleName}` + const resourceName = setResource(context, roleResourceName, iamRole) + + context.CloudFormationConfig.Capabilities = context.CloudFormationConfig.Capabilities || [ + "CAPABILITY_NAMED_IAM" + ] - context.CloudFormationConfig.Capabilities = context.CloudFormationConfig.Capabilities || [ - "CAPABILITY_NAMED_IAM" + for (const serviceDefinition of serviceDefinitions) { + serviceDefinition[CLASS_ROLEKEY] = { + "Fn::GetAtt": [ + resourceName, + "Arn" + ] + } + } + } +} + +export const lambdaPolicy = async (context) => { + const { roleName, roleProperties } = context + const policy = { + "PolicyName": { + "Fn::Join": [ + "-", + [ + "functionly", + roleName, + "lambda" + ] + ] + }, + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Action": [ + "lambda:InvokeAsync", + "lambda:InvokeFunction" + ], + "Resource": ["*"] + }] + } + } + roleProperties.Policies.push(policy) +} + +export const logPolicy = async (context) => { + const { roleName, roleProperties } = context + const policy = { + "PolicyName": { + "Fn::Join": [ + "-", + [ + "functionly", + roleName, + "logs" + ] ] + }, + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Resource": ["*"] + }] } + } + roleProperties.Policies.push(policy) +} + +export const dynamoPolicy = async (context) => { + const { roleName, serviceDefinitions, roleProperties } = context + const tables = [] + const services = serviceDefinitions.map(s => s.service) + const usedTableConfigs = context.tableConfigs.filter(t => intersection(t.usedBy, services).length) - const iam_role = roleMap.get(role) - serviceDefinition[CLASS_ROLEKEY] = iam_role.ref + const policy = { + "PolicyName": { + "Fn::Join": [ + "-", + [ + "functionly", + roleName, + "dynamo" + ] + ] + }, + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Action": [ + "dynamodb:Query", + "dynamodb:Scan", + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem" + ], + "Resource": usedTableConfigs.map(t => { + return { + "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/" + t.tableName + } + }) + }] + } + } + if (usedTableConfigs.length) { + roleProperties.Policies.push(policy) } -}) +} + export const tableResources = ExecuteStep.register('DynamoDB-Tables', async (context) => { for (const tableConfig of context.tableConfigs) { @@ -126,13 +213,13 @@ export const tableResource = async (context) => { TableName: tableConfig.tableName }, tableConfig.nativeConfig, __dynamoDBDefaults); + tableConfig.tableName = properties.TableName const dynamoDb = { "Type": "AWS::DynamoDB::Table", "Properties": properties } - const resourceName = `Dynamo${properties.TableName}` const name = setResource(context, resourceName, dynamoDb) diff --git a/src/cli/providers/cloudFormation/index.ts b/src/cli/providers/cloudFormation/index.ts index e10ac73..66b13e3 100644 --- a/src/cli/providers/cloudFormation/index.ts +++ b/src/cli/providers/cloudFormation/index.ts @@ -41,8 +41,8 @@ export const cloudFormation = { const localName = projectConfig.name ? `${projectConfig.name}.zip` : 'project.zip' await executor(context, uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData(), localName)) - await executor(context, roleResources) await executor(context, tableResources) + await executor(context, roleResources) await executor(context, lambdaResources) await executor(context, apiGateway) @@ -51,6 +51,27 @@ export const cloudFormation = { logger.info(`Functionly: Updating stack...`) await executor(context, updateStack) logger.info(`Functionly: Complete`) + }, + package: async (context) => { + logger.info(`Functionly: Packgaging...`) + await executor(context, bundle) + await executor(context, zip) + + await executor(context, cloudFormationInit) + await executor(context, s3BucketResources) + + logger.info(`Functionly: Save binary...`) + const localName = projectConfig.name ? `${projectConfig.name}.zip` : 'project.zip' + await executor({ ...context, skipUpload: true }, uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData(), localName)) + + await executor(context, tableResources) + await executor(context, roleResources) + await executor(context, lambdaResources) + await executor(context, apiGateway) + + logger.info(`Functionly: Save template...`) + await executor({ ...context, skipUpload: true }, uploadTemplate) + logger.info(`Functionly: Complete`) } } diff --git a/src/cli/providers/index.ts b/src/cli/providers/index.ts index d9d7869..490a605 100644 --- a/src/cli/providers/index.ts +++ b/src/cli/providers/index.ts @@ -33,7 +33,15 @@ export class CreateEnvironmentStep extends ExecuteStep { await executor(context, ExecuteStep.get('SetFunctionalEnvironment')) } - await executor(context, currentEnvironment.createEnvironment) + if (context.packageOnly) { + if (currentEnvironment.package) { + await executor(context, currentEnvironment.package) + } else { + throw new Error('package creation not implemented') + } + } else { + await executor(context, currentEnvironment.createEnvironment) + } } } diff --git a/src/cli/utilities/aws/s3Upload.ts b/src/cli/utilities/aws/s3Upload.ts index 62f49d7..c2ddec5 100644 --- a/src/cli/utilities/aws/s3Upload.ts +++ b/src/cli/utilities/aws/s3Upload.ts @@ -54,6 +54,13 @@ export const uploadToAws = ExecuteStep.register('S3-Upload', async (context) => ContentType: context.upload.contentType }) + if (context.skipUpload) { + if (config.tempDirectory && context.upload.localName) { + writeFileSync(join(config.tempDirectory, context.upload.localName), binary) + } + return resolve(params) + } + s3.putObject(params, (err, res) => { if (err) return reject(err) @@ -64,4 +71,4 @@ export const uploadToAws = ExecuteStep.register('S3-Upload', async (context) => return resolve(params) }) }) -}) \ No newline at end of file +}) From 3e19db051e10030dc91b8fa87b82ab3917e96ecc Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Thu, 15 Jun 2017 13:40:49 +0200 Subject: [PATCH 014/196] REAFACTOR: object merge; CHANGE: cloudformation naming --- src/annotations/classes/apiGateway.ts | 9 +++---- src/annotations/classes/dynamoTable.ts | 9 ++++--- src/classes/externals/dynamoDB.ts | 3 +-- src/cli/commands/serverless.ts | 11 +++++---- src/cli/context/index.ts | 6 ++--- src/cli/context/steppes/serviceDiscovery.ts | 1 - .../context/cloudFormationInit.ts | 9 ++++--- .../cloudFormation/context/resources.ts | 10 ++++---- src/cli/utilities/aws/cloudFormation.ts | 24 ++++++++++--------- src/cli/utilities/aws/dynamoDB.ts | 11 +++++---- src/cli/utilities/aws/lambda.ts | 4 ++-- src/cli/utilities/aws/s3Upload.ts | 8 +++---- src/cli/utilities/webpack.ts | 6 ++--- src/providers/index.ts | 1 - 14 files changed, 57 insertions(+), 55 deletions(-) diff --git a/src/annotations/classes/apiGateway.ts b/src/annotations/classes/apiGateway.ts index fab127b..9a021d9 100644 --- a/src/annotations/classes/apiGateway.ts +++ b/src/annotations/classes/apiGateway.ts @@ -1,6 +1,5 @@ import { CLASS_APIGATEWAYKEY } from '../constants' import { getMetadata, defineMetadata } from '../metadata' -import { defaults } from 'lodash' export const defaultEndpoint = { method: 'get', @@ -8,11 +7,13 @@ export const defaultEndpoint = { authorization: 'AWS_IAM' } -export const apiGateway = (endpoint: { path: string, method?: string, cors?: boolean, - authorization?: 'AWS_IAM' | 'NONE' | 'CUSTOM' | 'COGNITO_USER_POOLS' }) => { +export const apiGateway = (endpoint: { + path: string, method?: string, cors?: boolean, + authorization?: 'AWS_IAM' | 'NONE' | 'CUSTOM' | 'COGNITO_USER_POOLS' +}) => { return (target: Function) => { let metadata = getMetadata(CLASS_APIGATEWAYKEY, target) || [] - metadata.push(defaults({}, endpoint, defaultEndpoint)) + metadata.push({ ...defaultEndpoint, ...endpoint }) defineMetadata(CLASS_APIGATEWAYKEY, [...metadata], target); } } diff --git a/src/annotations/classes/dynamoTable.ts b/src/annotations/classes/dynamoTable.ts index 0113163..c8bb912 100644 --- a/src/annotations/classes/dynamoTable.ts +++ b/src/annotations/classes/dynamoTable.ts @@ -1,5 +1,3 @@ -import { merge } from 'lodash' - import { CLASS_DYNAMOTABLECONFIGURATIONKEY } from '../constants' import { getMetadata, defineMetadata } from '../metadata' import { applyTemplates, environment } from './environment' @@ -33,14 +31,15 @@ export const dynamoTable = (tableConfig: { let tableDefinitions = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, target) || []; tableConfig.environmentKey = tableConfig.environmentKey || '%ClassName%_TABLE_NAME' - tableConfig.nativeConfig = merge({}, __dynamoDBDefaults, tableConfig.nativeConfig) + tableConfig.nativeConfig = { ...__dynamoDBDefaults, ...tableConfig.nativeConfig } const { templatedKey, templatedValue } = applyTemplates(tableConfig.environmentKey, tableConfig.tableName, target) - tableDefinitions.push(merge({}, tableConfig, { + tableDefinitions.push({ + ...tableConfig, environmentKey: templatedKey, tableName: templatedValue, definedBy: target.name - })) + }) defineMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, [...tableDefinitions], target) diff --git a/src/classes/externals/dynamoDB.ts b/src/classes/externals/dynamoDB.ts index 009fe63..ba31d51 100644 --- a/src/classes/externals/dynamoDB.ts +++ b/src/classes/externals/dynamoDB.ts @@ -1,5 +1,4 @@ import * as AWS from 'aws-sdk' -import { merge } from 'lodash' import { DocumentClient } from 'aws-sdk/lib/dynamodb/document_client' import { Service } from '../service' @@ -109,6 +108,6 @@ export class DynamoDB extends Service { TableName: process.env[`${this.constructor.name}_TABLE_NAME`] || tableName } - return merge({}, initParams, params) + return { ...initParams, ...params } } } \ No newline at end of file diff --git a/src/cli/commands/serverless.ts b/src/cli/commands/serverless.ts index e5fdff9..548d5e7 100644 --- a/src/cli/commands/serverless.ts +++ b/src/cli/commands/serverless.ts @@ -1,6 +1,5 @@ import { writeFileSync } from 'fs' import { extname } from 'path' -import { merge } from 'lodash' import { stringify } from 'yamljs' export const FUNCTIONAL_ENVIRONMENT = 'aws' @@ -63,7 +62,7 @@ export default (api) => { } for (const tableConfig of context.tableConfigs) { - const properties = merge({}, { TableName: tableConfig.tableName }, tableConfig.nativeConfig) + const properties = { TableName: tableConfig.tableName, ...tableConfig.nativeConfig } dynamoStatement.Resource.push("arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/" + properties.TableName) } @@ -156,9 +155,11 @@ export default (api) => { } const tableConfig = async ({ tableConfig, serverless }) => { - const properties = merge({}, { - TableName: tableConfig.tableName - }, tableConfig.nativeConfig, __dynamoDBDefaults); + const properties = { + ...__dynamoDBDefaults, + TableName: tableConfig.tableName, + ...tableConfig.nativeConfig + } const tableResource = { diff --git a/src/cli/context/index.ts b/src/cli/context/index.ts index f260c24..297e23d 100644 --- a/src/cli/context/index.ts +++ b/src/cli/context/index.ts @@ -6,7 +6,6 @@ import './steppes/setFunctionalEnvironment' import { resolvePath } from '../utilities/cli' import { logger } from '../utilities/logger' -import { defaults } from 'lodash' import { projectConfig } from '../project/config' import { @@ -23,11 +22,12 @@ export const getDefaultSteppes = (): any[] => { } export const createContext = async (path, defaultValues) => { - const context = defaults({}, { + const context = { + ...defaultValues, serviceRoot: resolvePath(path), date: new Date(), runStep: async function (step) { return await executor(this, step) } - }, defaultValues) + } const defaultSteppes = getDefaultSteppes() for (const step of defaultSteppes) { diff --git a/src/cli/context/steppes/serviceDiscovery.ts b/src/cli/context/steppes/serviceDiscovery.ts index 8aa2808..00554b2 100644 --- a/src/cli/context/steppes/serviceDiscovery.ts +++ b/src/cli/context/steppes/serviceDiscovery.ts @@ -3,7 +3,6 @@ import { FunctionalService } from '../../../classes/functionalService' import { ExecuteStep } from '../core/executeStep' import { join, basename, extname } from 'path' -import { set } from 'lodash' export class ServiceDiscoveryStep extends ExecuteStep { public async method(context) { diff --git a/src/cli/providers/cloudFormation/context/cloudFormationInit.ts b/src/cli/providers/cloudFormation/context/cloudFormationInit.ts index 49679c1..3b83adb 100644 --- a/src/cli/providers/cloudFormation/context/cloudFormationInit.ts +++ b/src/cli/providers/cloudFormation/context/cloudFormationInit.ts @@ -2,14 +2,13 @@ import { resolvePath } from '../../../utilities/cli' import { projectConfig } from '../../../project/config' import { ExecuteStep } from '../../../context' -import { merge } from 'lodash' - export const cloudFormationInit = ExecuteStep.register('CloudFormationInit', async (context) => { - context.CloudFormationConfig = merge({}, { + context.CloudFormationConfig = { StackName: projectConfig.name, OnFailure: "ROLLBACK", - TimeoutInMinutes: 10 - }, projectConfig.cloudFormation) + TimeoutInMinutes: 10, + ...projectConfig.cloudFormation + } context.CloudFormationTemplate = { "AWSTemplateFormatVersion": "2010-09-09", diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 7ea3935..c43162b 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -1,4 +1,4 @@ -import { merge, intersection } from 'lodash' +import { intersection } from 'lodash' import { getMetadata, constants, getFunctionName, __dynamoDBDefaults } from '../../../../annotations' const { CLASS_DESCRIPTIONKEY, CLASS_ROLEKEY, CLASS_MEMORYSIZEKEY, CLASS_RUNTIMEKEY, CLASS_TIMEOUTKEY, CLASS_ENVIRONMENTKEY, CLASS_TAGKEY, CLASS_APIGATEWAYKEY } = constants @@ -209,9 +209,11 @@ export const tableResources = ExecuteStep.register('DynamoDB-Tables', async (con export const tableResource = async (context) => { const { tableConfig } = context - const properties = merge({}, { - TableName: tableConfig.tableName - }, tableConfig.nativeConfig, __dynamoDBDefaults); + const properties = { + ...__dynamoDBDefaults, + TableName: tableConfig.tableName, + ...tableConfig.nativeConfig + }; tableConfig.tableName = properties.TableName diff --git a/src/cli/utilities/aws/cloudFormation.ts b/src/cli/utilities/aws/cloudFormation.ts index 2ab3673..1588e3b 100644 --- a/src/cli/utilities/aws/cloudFormation.ts +++ b/src/cli/utilities/aws/cloudFormation.ts @@ -1,5 +1,5 @@ import { CloudFormation } from 'aws-sdk' -import { merge, pick } from 'lodash' +import { pick } from 'lodash' import { config } from '../../utilities/config' import { ExecuteStep } from '../../context' @@ -12,7 +12,7 @@ export const CREATE_STACK_PRPERTIES = ['StackName', 'Capabilities', 'ClientReque let cloudFormation = null; const initAWSSDK = (context) => { if (!cloudFormation) { - let awsConfig = merge({}, config.aws.CloudFormation) + let awsConfig = { ...config.aws.CloudFormation } if (context.awsRegion) { awsConfig.region = context.awsRegion } @@ -24,13 +24,14 @@ const initAWSSDK = (context) => { -export const createStack = ExecuteStep.register('CF-CreateStack', (context) => { +export const createStack = ExecuteStep.register('CloudFormation-CreateStack', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { const cfConfig: any = pick(context.CloudFormationConfig, CREATE_STACK_PRPERTIES) - const params = merge({}, cfConfig, { + const params = { + ...cfConfig, TemplateBody: JSON.stringify(context.CloudFormationTemplate, null, 2) - }) + } cloudFormation.createStack(params, async (err, data) => { if (err) return reject(err) @@ -46,14 +47,15 @@ export const createStack = ExecuteStep.register('CF-CreateStack', (context) => { }) }) -export const updateStack = ExecuteStep.register('CF-UpdateStack', (context) => { +export const updateStack = ExecuteStep.register('CloudFormation-UpdateStack', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { const cfConfig: any = pick(context.CloudFormationConfig, UPDATE_STACK_PRPERTIES) - const params = merge({}, cfConfig, { + const params = { + ...cfConfig, TemplateURL: `https://s3.eu-central-1.amazonaws.com/${context.awsBucket}/${context.S3CloudFormationTemplate}`, UsePreviousTemplate: false - }) + } cloudFormation.updateStack(params, async (err, data) => { if (err) return reject(err) @@ -69,7 +71,7 @@ export const updateStack = ExecuteStep.register('CF-UpdateStack', (context) => { }) }) -export const getTemplate = ExecuteStep.register('CF-GetTemplate', (context) => { +export const getTemplate = ExecuteStep.register('CloudFormation-GetTemplate', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { @@ -84,7 +86,7 @@ export const getTemplate = ExecuteStep.register('CF-GetTemplate', (context) => { }) -export const getStackBucketName = ExecuteStep.register('CF-GetStackBucketName', (context) => { +export const getStackBucketName = ExecuteStep.register('CloudFormation-GetStackBucketName', (context) => { if (context.awsBucket) return context.awsBucket initAWSSDK(context) @@ -102,7 +104,7 @@ export const getStackBucketName = ExecuteStep.register('CF-GetStackBucketName', }) }) -export const describeStacks = ExecuteStep.register('CF-DescribeStacks', (context) => { +export const describeStacks = ExecuteStep.register('CloudFormation-DescribeStacks', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { diff --git a/src/cli/utilities/aws/dynamoDB.ts b/src/cli/utilities/aws/dynamoDB.ts index cb35768..c04b534 100644 --- a/src/cli/utilities/aws/dynamoDB.ts +++ b/src/cli/utilities/aws/dynamoDB.ts @@ -1,5 +1,4 @@ import { DynamoDB } from 'aws-sdk' -import { merge } from 'lodash' import { config } from '../config' import { __dynamoDBDefaults } from '../../../annotations' import { ExecuteStep, executor } from '../../context' @@ -7,7 +6,7 @@ import { ExecuteStep, executor } from '../../context' let dynamoDB = null; const initAWSSDK = (context) => { if (!dynamoDB) { - let awsConfig = merge({}, config.aws.DynamoDB) + let awsConfig = { ...config.aws.DynamoDB } if (context.awsRegion) { awsConfig.region = context.awsRegion } @@ -46,9 +45,11 @@ export const createTable = (context) => { const { tableConfig } = context return new Promise((resolve, reject) => { - let params = merge({}, { - TableName: tableConfig.tableName - }, tableConfig.nativeConfig, __dynamoDBDefaults); + let params = { + ...__dynamoDBDefaults, + TableName: tableConfig.tableName, + ...tableConfig.nativeConfig + }; dynamoDB.createTable(params, function (err, data) { if (err) reject(err) diff --git a/src/cli/utilities/aws/lambda.ts b/src/cli/utilities/aws/lambda.ts index 6b6694a..b29df0f 100644 --- a/src/cli/utilities/aws/lambda.ts +++ b/src/cli/utilities/aws/lambda.ts @@ -1,5 +1,5 @@ import { Lambda } from 'aws-sdk' -import { merge, difference } from 'lodash' +import { difference } from 'lodash' import { config } from '../../utilities/config' import { getMetadata, constants, getFunctionName } from '../../../annotations' import { ExecuteStep, executor } from '../../context' @@ -8,7 +8,7 @@ import { ExecuteStep, executor } from '../../context' let lambda = null; const initAWSSDK = (context) => { if (!lambda) { - let awsConfig = merge({}, config.aws.Lambda) + let awsConfig = { ...config.aws.Lambda } if (context.awsRegion) { awsConfig.region = context.awsRegion } diff --git a/src/cli/utilities/aws/s3Upload.ts b/src/cli/utilities/aws/s3Upload.ts index c2ddec5..fda7ab4 100644 --- a/src/cli/utilities/aws/s3Upload.ts +++ b/src/cli/utilities/aws/s3Upload.ts @@ -1,5 +1,4 @@ import { S3 } from 'aws-sdk' -import { merge } from 'lodash' import { config } from '../config' import { ExecuteStep, executor } from '../../context' @@ -9,7 +8,7 @@ import { join, normalize } from 'path' let s3 = null; const initAWSSDK = (context) => { if (!s3) { - let awsConfig = merge({}, config.aws.S3) + let awsConfig = { ...config.aws.S3 } if (context.awsRegion) { awsConfig.region = context.awsRegion } @@ -47,12 +46,13 @@ export const uploadToAws = ExecuteStep.register('S3-Upload', async (context) => return new Promise((resolve, reject) => { const version = context.version ? `${context.version}/` : '' const binary = new Buffer(context.upload.data, 'binary') - let params = merge({}, config.S3, { + let params = { + ...config.S3, Bucket: context.awsBucket, Body: binary, Key: `functionly/${version}${context.upload.name}`, ContentType: context.upload.contentType - }) + } if (context.skipUpload) { if (config.tempDirectory && context.upload.localName) { diff --git a/src/cli/utilities/webpack.ts b/src/cli/utilities/webpack.ts index d366987..fae93ba 100644 --- a/src/cli/utilities/webpack.ts +++ b/src/cli/utilities/webpack.ts @@ -1,4 +1,3 @@ -import { merge } from 'lodash' import * as webpack from 'webpack' import { config } from './config' import { basename, extname, join } from 'path' @@ -46,14 +45,15 @@ export const bundleConfig = ExecuteStep.register('WebpackBundleConfig', (context entry[nameKey] = file }) - const webpackConfig = merge({}, config.webpack, { + const webpackConfig = { + ...config.webpack, entry: entry, externals: [ { 'aws-sdk': 'commonjs aws-sdk' } ] - }) + } return webpackConfig }) \ No newline at end of file diff --git a/src/providers/index.ts b/src/providers/index.ts index 113fa6b..172f993 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1,4 +1,3 @@ -import { get } from 'lodash' import { constants, getOwnMetadata } from '../annotations' import { callExtension } from '../classes' From 0c1df8fddd41e5db60c8759d3b7010ead2f2887a Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Fri, 16 Jun 2017 13:33:26 +0200 Subject: [PATCH 015/196] ADD: SNS event source; fixes --- src/annotations/classes/dynamoTable.ts | 3 +- src/annotations/classes/environment.ts | 24 +----- src/annotations/classes/sns.ts | 26 ++++++ src/annotations/constants.ts | 3 +- src/annotations/index.ts | 4 +- src/annotations/templates.ts | 22 +++++ .../cloudFormation/context/apiGateway.ts | 9 +- .../cloudFormation/context/resources.ts | 1 + .../providers/cloudFormation/context/sns.ts | 84 +++++++++++++++++++ src/cli/providers/cloudFormation/index.ts | 4 +- src/providers/aws/eventSources/apiGateway.ts | 1 + src/providers/aws/eventSources/sns.ts | 20 +++++ 12 files changed, 172 insertions(+), 29 deletions(-) create mode 100644 src/annotations/classes/sns.ts create mode 100644 src/annotations/templates.ts create mode 100644 src/cli/providers/cloudFormation/context/sns.ts create mode 100644 src/providers/aws/eventSources/sns.ts diff --git a/src/annotations/classes/dynamoTable.ts b/src/annotations/classes/dynamoTable.ts index c8bb912..e77652b 100644 --- a/src/annotations/classes/dynamoTable.ts +++ b/src/annotations/classes/dynamoTable.ts @@ -1,6 +1,7 @@ import { CLASS_DYNAMOTABLECONFIGURATIONKEY } from '../constants' import { getMetadata, defineMetadata } from '../metadata' -import { applyTemplates, environment } from './environment' +import { applyTemplates } from '../templates' +import { environment } from './environment' export const __dynamoDBDefaults = { AttributeDefinitions: [ diff --git a/src/annotations/classes/environment.ts b/src/annotations/classes/environment.ts index 5f06a07..75dbea5 100644 --- a/src/annotations/classes/environment.ts +++ b/src/annotations/classes/environment.ts @@ -1,5 +1,6 @@ import { CLASS_ENVIRONMENTKEY } from '../constants' import { getMetadata, defineMetadata } from '../metadata' +import { applyTemplates } from '../templates' export const environment = (key: string, value: string) => { return (target: Function) => { @@ -11,26 +12,3 @@ export const environment = (key: string, value: string) => { defineMetadata(CLASS_ENVIRONMENTKEY, { ...metadata }, target); } } - -export const applyTemplates = (key, value, target) => { - let templatedKey = key; - let templatedValue = value; - for (let template of environmentTemplates) { - if (template.regexp.test(templatedKey)) { - templatedKey = templatedKey.replace(template.regexp, template.resolution(target)) - } - if (template.regexp.test(templatedValue)) { - templatedValue = templatedValue.replace(template.regexp, template.resolution(target)) - } - } - - return { templatedKey, templatedValue } -} - -export const environmentTemplates = [ - { - name: 'ClassName', - regexp: /%ClassName%/g, - resolution: (target) => target.name - } -] diff --git a/src/annotations/classes/sns.ts b/src/annotations/classes/sns.ts new file mode 100644 index 0000000..388f811 --- /dev/null +++ b/src/annotations/classes/sns.ts @@ -0,0 +1,26 @@ +import { CLASS_SNSCONFIGURATIONKEY } from '../constants' +import { getMetadata, defineMetadata } from '../metadata' +import { applyTemplates } from '../templates' +import { environment } from './environment' + +export const sns = (snsConfig: { + topicName: string, + environmentKey?: string +}) => (target: Function) => { + let snsDefinitions = getMetadata(CLASS_SNSCONFIGURATIONKEY, target) || []; + + snsConfig.environmentKey = snsConfig.environmentKey || '%ClassName%_QUEUE_NAME' + + const { templatedKey, templatedValue } = applyTemplates(snsConfig.environmentKey, snsConfig.topicName, target) + snsDefinitions.push({ + ...snsConfig, + environmentKey: templatedKey, + topicName: templatedValue, + definedBy: target.name + }) + + defineMetadata(CLASS_SNSCONFIGURATIONKEY, [...snsDefinitions], target) + + const environmentSetter = environment(templatedKey, templatedValue) + environmentSetter(target) +} diff --git a/src/annotations/constants.ts b/src/annotations/constants.ts index 281f023..63d1075 100644 --- a/src/annotations/constants.ts +++ b/src/annotations/constants.ts @@ -10,4 +10,5 @@ export const CLASS_TAGKEY = 'functionly:class:tag' export const CLASS_LOGKEY = 'functionly:class:log' export const CLASS_INJECTABLEKEY = 'functionly:class:injectable' export const CLASS_NAMEKEY = 'functionly:class:name' -export const CLASS_DYNAMOTABLECONFIGURATIONKEY = 'functionly:class:dynamoTableConfiguration' \ No newline at end of file +export const CLASS_DYNAMOTABLECONFIGURATIONKEY = 'functionly:class:dynamoTableConfiguration' +export const CLASS_SNSCONFIGURATIONKEY = 'functionly:class:snsConfiguration' \ No newline at end of file diff --git a/src/annotations/index.ts b/src/annotations/index.ts index 2bdcad9..6bc8d3c 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -1,11 +1,13 @@ +export { templates, applyTemplates } from './templates' export { injectable } from './classes/injectable' export { apiGateway } from './classes/apiGateway' -export { environment, environmentTemplates } from './classes/environment' +export { environment } from './classes/environment' export { tag } from './classes/tag' export { log } from './classes/log' export { runtime } from './classes/runtime' export { functionName, getFunctionName } from './classes/functionName' export { dynamoTable, __dynamoDBDefaults } from './classes/dynamoTable' +export { sns } from './classes/sns' import { simpleClassAnnotation } from './classes/simpleAnnotation' diff --git a/src/annotations/templates.ts b/src/annotations/templates.ts new file mode 100644 index 0000000..005a2d0 --- /dev/null +++ b/src/annotations/templates.ts @@ -0,0 +1,22 @@ +export const applyTemplates = (key, value, target) => { + let templatedKey = key; + let templatedValue = value; + for (let template of templates) { + if (template.regexp.test(templatedKey)) { + templatedKey = templatedKey.replace(template.regexp, template.resolution(target)) + } + if (template.regexp.test(templatedValue)) { + templatedValue = templatedValue.replace(template.regexp, template.resolution(target)) + } + } + + return { templatedKey, templatedValue } +} + +export const templates = [ + { + name: 'ClassName', + regexp: /%ClassName%/g, + resolution: (target) => target.name + } +] \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/context/apiGateway.ts b/src/cli/providers/cloudFormation/context/apiGateway.ts index 87e8be7..ddab569 100644 --- a/src/cli/providers/cloudFormation/context/apiGateway.ts +++ b/src/cli/providers/cloudFormation/context/apiGateway.ts @@ -148,7 +148,11 @@ export const apiGatewayMethod = async (context) => { const resourceName = `ApiGateway${pathFragment}${method}` const name = setResource(context, resourceName, methodConfig) - setGatewayPermissions({ serviceDefinition, context }) + await executor({ + context, + name: `ApiGateway-Method-Permission-${pathFragment}`, + method: setGatewayPermissions + }) } export const apiGatewayPathPart = async (context) => { @@ -265,7 +269,8 @@ export const setOptionsMethodResource = (context) => { setResource(context, resourceName, methodConfig) } -export const setGatewayPermissions = ({ serviceDefinition, context }) => { +export const setGatewayPermissions = (context) => { + const { serviceDefinition } = context const properties = { "FunctionName": { "Fn::GetAtt": [ diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index c43162b..5bd720f 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -5,6 +5,7 @@ const { CLASS_DESCRIPTIONKEY, CLASS_ROLEKEY, CLASS_MEMORYSIZEKEY, CLASS_RUNTIMEK import { ExecuteStep, executor } from '../../../context' import { setResource } from '../utils' export { apiGateway } from './apiGateway' +export { sns } from './sns' export const roleResources = ExecuteStep.register('IAM-Role', async (context) => { const roleMap = new Map() diff --git a/src/cli/providers/cloudFormation/context/sns.ts b/src/cli/providers/cloudFormation/context/sns.ts new file mode 100644 index 0000000..545e91d --- /dev/null +++ b/src/cli/providers/cloudFormation/context/sns.ts @@ -0,0 +1,84 @@ +import { getMetadata, constants } from '../../../../annotations' +const { CLASS_SNSCONFIGURATIONKEY } = constants +import { ExecuteStep, executor } from '../../../context' +import { setResource } from '../utils' + +export const sns = ExecuteStep.register('SNS', async (context) => { + await executor(context, snsTopics) +}) + +export const snsTopics = ExecuteStep.register('SNS-Topics', async (context) => { + for (const serviceDefinition of context.publishedFunctions) { + await executor({ + context: { ...context, serviceDefinition }, + name: `SNS-Topic-${serviceDefinition.service.name}`, + method: snsTopic + }) + } +}) + +export const snsTopic = async (context) => { + const { serviceDefinition } = context + let snsConfigs = getMetadata(CLASS_SNSCONFIGURATIONKEY, serviceDefinition.service) || [] + + for (const snsConfig of snsConfigs) { + await executor({ + context: { ...context, snsConfig }, + name: `SNS-Topic-Subscription-${serviceDefinition.service.name}-${snsConfig.topicName}`, + method: snsTopicSubscription + }) + } +} + +export const snsTopicSubscription = async (context) => { + const { serviceDefinition, snsConfig } = context + + const snsProperties = { + "TopicName": snsConfig.topicName, + "Subscription": [ + { + "Endpoint": { + "Fn::GetAtt": [serviceDefinition.resourceName, "Arn"] + }, + "Protocol": "lambda" + } + ] + } + + const snsTopic = { + "Type": "AWS::SNS::Topic", + "Properties": snsProperties, + "DependsOn": [serviceDefinition.resourceName] + } + + const resourceName = `SNS${serviceDefinition.resourceName}${snsConfig.topicName}${context.date.valueOf()}` + const topicName = setResource(context, resourceName, snsTopic) + + await executor({ + context: { ...context, topicName }, + name: `SNS-Topic-Permission-${serviceDefinition.service.name}-${snsConfig.topicName}`, + method: snsPermissions + }) +} + +export const snsPermissions = (context) => { + const { serviceDefinition, topicName } = context + const properties = { + "FunctionName": { + "Fn::GetAtt": [ + serviceDefinition.resourceName, + "Arn" + ] + }, + "Action": "lambda:InvokeFunction", + "Principal": "sns.amazonaws.com", + "SourceArn": { "Ref": topicName } + } + + const snsPermission = { + "Type": "AWS::Lambda::Permission", + "Properties": properties + } + const resourceName = `${topicName}Permission` + setResource(context, resourceName, snsPermission, true) +} \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/index.ts b/src/cli/providers/cloudFormation/index.ts index 66b13e3..46f7211 100644 --- a/src/cli/providers/cloudFormation/index.ts +++ b/src/cli/providers/cloudFormation/index.ts @@ -8,7 +8,7 @@ import { projectConfig } from '../../project/config' import { executor } from '../../context' import { cloudFormationInit } from './context/cloudFormationInit' -import { tableResources, lambdaResources, roleResources, s3BucketResources, apiGateway } from './context/resources' +import { tableResources, lambdaResources, roleResources, s3BucketResources, apiGateway, sns } from './context/resources' import { uploadTemplate } from './context/uploadTemplate' export const cloudFormation = { @@ -45,6 +45,7 @@ export const cloudFormation = { await executor(context, roleResources) await executor(context, lambdaResources) await executor(context, apiGateway) + await executor(context, sns) logger.info(`Functionly: Uploading template...`) await executor(context, uploadTemplate) @@ -68,6 +69,7 @@ export const cloudFormation = { await executor(context, roleResources) await executor(context, lambdaResources) await executor(context, apiGateway) + await executor(context, sns) logger.info(`Functionly: Save template...`) await executor({ ...context, skipUpload: true }, uploadTemplate) diff --git a/src/providers/aws/eventSources/apiGateway.ts b/src/providers/aws/eventSources/apiGateway.ts index d9bc203..90d7b8d 100644 --- a/src/providers/aws/eventSources/apiGateway.ts +++ b/src/providers/aws/eventSources/apiGateway.ts @@ -18,6 +18,7 @@ export class ApiGateway extends EventSource { if (query && query[parameter.from]) return query[parameter.from] if (params && params[parameter.from]) return params[parameter.from] if (headers && headers[parameter.from]) return headers[parameter.from] + break default: return await super.parameterResolver(parameter, event) } diff --git a/src/providers/aws/eventSources/sns.ts b/src/providers/aws/eventSources/sns.ts new file mode 100644 index 0000000..5720028 --- /dev/null +++ b/src/providers/aws/eventSources/sns.ts @@ -0,0 +1,20 @@ +import { EventSource } from './eventSource' + +export class SNS extends EventSource { + public available(eventContext: any): boolean { + const { event } = eventContext + return event && Array.isArray(event.Records) ? true : false + } + + public async parameterResolver(parameter, event) { + const data = event.event.Records[0] + + switch (parameter.type) { + case 'param': + if (data && data[parameter.from]) return data[parameter.from] + break + default: + return await super.parameterResolver(parameter, event) + } + } +} \ No newline at end of file From 689517f968ed33cc2be6b99138325cb8fa62807f Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 19 Jun 2017 13:28:56 +0200 Subject: [PATCH 016/196] CHANGE: use Cloudformation Stacks --- src/cli/providers/aws.ts | 4 +- .../cloudFormation/context/apiGateway.ts | 136 ++++++++++++------ .../context/cloudFormationInit.ts | 1 + .../cloudFormation/context/resources.ts | 65 +++++++-- .../providers/cloudFormation/context/sns.ts | 5 +- .../providers/cloudFormation/context/stack.ts | 95 ++++++++++++ .../cloudFormation/context/uploadTemplate.ts | 19 ++- src/cli/providers/cloudFormation/index.ts | 19 ++- src/cli/providers/cloudFormation/utils.ts | 44 ++++-- src/cli/utilities/aws/s3Upload.ts | 20 +-- 10 files changed, 320 insertions(+), 88 deletions(-) create mode 100644 src/cli/providers/cloudFormation/context/stack.ts diff --git a/src/cli/providers/aws.ts b/src/cli/providers/aws.ts index 36bb3f4..e1e42f1 100644 --- a/src/cli/providers/aws.ts +++ b/src/cli/providers/aws.ts @@ -20,8 +20,8 @@ export const aws = { createEnvironment: ExecuteStep.register('CreateEnvironment_aws', async (context) => { await executor(context, bundle) await executor(context, zip) - const localName = projectConfig.name ? `${projectConfig.name}.zip` : 'project.zip' - await executor(context, uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData(), localName)) + const fileName = projectConfig.name ? `${projectConfig.name}.zip` : 'project.zip' + await executor(context, uploadZipStep(fileName, context.zipData())) await executor(context, createTables) for (let serviceDefinition of context.publishedFunctions) { diff --git a/src/cli/providers/cloudFormation/context/apiGateway.ts b/src/cli/providers/cloudFormation/context/apiGateway.ts index ddab569..504666f 100644 --- a/src/cli/providers/cloudFormation/context/apiGateway.ts +++ b/src/cli/providers/cloudFormation/context/apiGateway.ts @@ -2,6 +2,7 @@ import { getMetadata, constants } from '../../../../annotations' const { CLASS_APIGATEWAYKEY } = constants import { ExecuteStep, executor } from '../../../context' import { setResource } from '../utils' +import { setStackParameter, getStackName } from './stack' export const API_GATEWAY_REST_API = 'ApiGatewayRestApi' @@ -20,7 +21,18 @@ export const gatewayRestApi = ExecuteStep.register('ApiGateway-RestApi', async ( } } - const apiName = setResource(context, API_GATEWAY_REST_API, RestApi) + const resourceName = setResource(context, API_GATEWAY_REST_API, RestApi) + + await setStackParameter({ + ...context, + resourceName + }) + + await setStackParameter({ + ...context, + resourceName, + attr: 'RootResourceId' + }) context.CloudFormationTemplate.Outputs[`ServiceEndpoint`] = { "Value": { @@ -29,7 +41,7 @@ export const gatewayRestApi = ExecuteStep.register('ApiGateway-RestApi', async ( [ "https://", { - "Ref": apiName + "Ref": resourceName }, ".execute-api.eu-central-1.amazonaws.com/dev" ] @@ -40,7 +52,7 @@ export const gatewayRestApi = ExecuteStep.register('ApiGateway-RestApi', async ( }) export const gatewayResources = ExecuteStep.register('ApiGateway-Resources', async (context) => { - const endpointsCors = new Map() + const endpointsCors = new Map() const endpoints = new Map() for (const serviceDefinition of context.publishedFunctions) { await executor({ @@ -50,9 +62,9 @@ export const gatewayResources = ExecuteStep.register('ApiGateway-Resources', asy }) } - for (const [endpointResourceName, methods] of endpointsCors) { + for (const [endpointResourceName, { serviceDefinition, methods }] of endpointsCors) { await executor({ - context: { ...context, endpointResourceName, methods }, + context: { ...context, endpointResourceName, serviceDefinition, methods }, name: `ApiGateway-Method-Options-${endpointResourceName}`, method: setOptionsMethodResource }) @@ -60,7 +72,7 @@ export const gatewayResources = ExecuteStep.register('ApiGateway-Resources', asy }) export const apiGatewayMethods = async (context) => { - const { serviceDefinition, endpoints, endpointsCors } = context + const { serviceDefinition } = context let httpMetadata = getMetadata(CLASS_APIGATEWAYKEY, serviceDefinition.service) || [] for (let metadata of httpMetadata) { @@ -78,7 +90,7 @@ export const apiGatewayMethod = async (context) => { const pathParts = path.split('/') let pathFragment = '' - let endpointResourceName; + let endpoint; for (const pathPart of pathParts) { if (!pathPart) continue @@ -86,9 +98,9 @@ export const apiGatewayMethod = async (context) => { pathFragment += `/${pathPart}` if (endpoints.has(pathFragment)) { - endpointResourceName = endpoints.get(pathFragment) + endpoint = endpoints.get(pathFragment) } else { - endpointResourceName = await executor({ + endpoint = await executor({ context: { ...context, pathFragment, rootPathFragment, endpoints, pathPart }, name: `ApiGateway-ResourcePath-${pathFragment}`, method: apiGatewayPathPart @@ -96,20 +108,37 @@ export const apiGatewayMethod = async (context) => { } } + if (!endpoint) { + // handle / (root path) + throw new Error('TODO missing endpoint') + } + + if (endpoint.serviceDefinition !== serviceDefinition) { + await setStackParameter({ + ...context, + resourceName: endpoint.endpointResourceName, + sourceStackName: getStackName(endpoint.serviceDefinition), + targetStackName: getStackName(serviceDefinition) + }) + } + if (cors) { - let values = ['OPTIONS'] - if (endpointsCors.has(endpointResourceName)) { - values = endpointsCors.get(endpointResourceName) + let value = { + serviceDefinition, + methods: ['OPTIONS'] } - values.push(method) - endpointsCors.set(endpointResourceName, values) + if (endpointsCors.has(endpoint.endpointResourceName)) { + value = endpointsCors.get(endpoint.endpointResourceName) + } + value.methods.push(method) + endpointsCors.set(endpoint.endpointResourceName, value) } const properties = { "HttpMethod": method, "RequestParameters": {}, "ResourceId": { - "Ref": endpointResourceName + "Ref": endpoint.endpointResourceName }, "RestApiId": { "Ref": API_GATEWAY_REST_API @@ -146,7 +175,7 @@ export const apiGatewayMethod = async (context) => { "Properties": properties } const resourceName = `ApiGateway${pathFragment}${method}` - const name = setResource(context, resourceName, methodConfig) + const name = setResource(context, resourceName, methodConfig, getStackName(serviceDefinition)) await executor({ context, @@ -156,31 +185,57 @@ export const apiGatewayMethod = async (context) => { } export const apiGatewayPathPart = async (context) => { - const { pathFragment, rootPathFragment, endpoints, pathPart } = context + const { pathFragment, rootPathFragment, endpoints, pathPart, serviceDefinition } = context + + let parentId + if (rootPathFragment && endpoints.has(rootPathFragment)) { + const endpoint = endpoints.get(rootPathFragment) + if (endpoint.serviceDefinition !== serviceDefinition) { + await setStackParameter({ + ...context, + resourceName: endpoint.endpointResourceName, + sourceStackName: getStackName(endpoint.serviceDefinition), + targetStackName: getStackName(serviceDefinition) + }) + } + + parentId = { + "Ref": endpoint.endpointResourceName + } + } else { + parentId = { + "Ref": `${API_GATEWAY_REST_API}RootResourceId` + } + } const properties = { - "ParentId": getAGResourceParentId(rootPathFragment, endpoints), + "ParentId": parentId, "PathPart": pathPart, "RestApiId": { "Ref": API_GATEWAY_REST_API } } + const resourceConfig = { "Type": "AWS::ApiGateway::Resource", "Properties": properties } const resourceName = `ApiGateway${pathFragment}` - const endpointResourceName = setResource(context, resourceName, resourceConfig) - endpoints.set(pathFragment, endpointResourceName) - return endpointResourceName + const endpointResourceName = setResource(context, resourceName, resourceConfig, getStackName(serviceDefinition)) + endpoints.set(pathFragment, { endpointResourceName, serviceDefinition }) + return { endpointResourceName, serviceDefinition } } export const gatewayDeployment = ExecuteStep.register('ApiGateway-Deployment', async (context) => { - const deploymentResources = context.deploymentResources - .filter(r => r.type === 'AWS::ApiGateway::Method') - .map(r => r.name) + const lambdaDependsOn = context.deploymentResources + .filter(r => !r.stackName && r.type === 'AWS::ApiGateway::Method') + .map(r => r.resourceName) + + const stackDependsOn = context.deploymentResources + .filter(r => r.stackName && r.type === 'AWS::ApiGateway::Method') + .map(r => r.stackName) const ApiGatewayDeployment = { "Type": "AWS::ApiGateway::Deployment", @@ -191,32 +246,21 @@ export const gatewayDeployment = ExecuteStep.register('ApiGateway-Deployment', a "StageName": "dev" }, "DependsOn": [ - ...deploymentResources + ...[...lambdaDependsOn, ...stackDependsOn].filter((v, i, self) => self.indexOf(v) === i) ] } - const resourceName = `ApiGateway${context.date.valueOf()}` - const apiName = setResource(context, resourceName, ApiGatewayDeployment) + const deploymentResourceName = `ApiGateway${context.date.valueOf()}` + const resourceName = setResource(context, deploymentResourceName, ApiGatewayDeployment) + // await setStackParameter({ + // ...context, + // resourceName + // }) }) -export const getAGResourceParentId = (rootPathFragment, endpoints) => { - if (rootPathFragment && endpoints.has(rootPathFragment)) { - return { - "Ref": endpoints.get(rootPathFragment) - } - } else { - return { - "Fn::GetAtt": [ - API_GATEWAY_REST_API, - "RootResourceId" - ] - } - } -} - -export const setOptionsMethodResource = (context) => { - const { endpointResourceName, methods } = context +export const setOptionsMethodResource = async (context) => { + const { endpointResourceName, serviceDefinition, methods } = context const properties = { "AuthorizationType": "NONE", "HttpMethod": "OPTIONS", @@ -266,7 +310,7 @@ export const setOptionsMethodResource = (context) => { "Properties": properties } const resourceName = `${endpointResourceName}Options` - setResource(context, resourceName, methodConfig) + setResource(context, resourceName, methodConfig, getStackName(serviceDefinition)) } export const setGatewayPermissions = (context) => { @@ -307,5 +351,5 @@ export const setGatewayPermissions = (context) => { "Properties": properties } const resourceName = `ApiGateway${serviceDefinition.resourceName}Permission` - setResource(context, resourceName, methodConfig, true) + setResource(context, resourceName, methodConfig, getStackName(serviceDefinition), true) } diff --git a/src/cli/providers/cloudFormation/context/cloudFormationInit.ts b/src/cli/providers/cloudFormation/context/cloudFormationInit.ts index 3b83adb..9893e9a 100644 --- a/src/cli/providers/cloudFormation/context/cloudFormationInit.ts +++ b/src/cli/providers/cloudFormation/context/cloudFormationInit.ts @@ -15,4 +15,5 @@ export const cloudFormationInit = ExecuteStep.register('CloudFormationInit', asy "Resources": {}, "Outputs": {} } + context.CloudFormationStacks = {} }) diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 5bd720f..fb9fde1 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -4,9 +4,25 @@ const { CLASS_DESCRIPTIONKEY, CLASS_ROLEKEY, CLASS_MEMORYSIZEKEY, CLASS_RUNTIMEK CLASS_ENVIRONMENTKEY, CLASS_TAGKEY, CLASS_APIGATEWAYKEY } = constants import { ExecuteStep, executor } from '../../../context' import { setResource } from '../utils' +import { createStack, setStackParameter, getStackName } from './stack' + export { apiGateway } from './apiGateway' export { sns } from './sns' +export const DYNAMODB_TABLE_STACK = 'DynamoDBTableStack' + + +export const initStacks = ExecuteStep.register('CloudFormation-Stack-init', async (context) => { + for (const serviceDefinition of context.publishedFunctions) { + const stackName = getStackName(serviceDefinition) + await executor({ + context: { ...context, stackName }, + name: `CloudFormation-Stack-init-${stackName}`, + method: createStack + }) + } +}) + export const roleResources = ExecuteStep.register('IAM-Role', async (context) => { const roleMap = new Map() for (const serviceDefinition of context.publishedFunctions) { @@ -82,16 +98,19 @@ export const roleResource = async (context) => { const roleResourceName = `IAM${roleProperties.RoleName}` const resourceName = setResource(context, roleResourceName, iamRole) + await setStackParameter({ + ...context, + resourceName, + attr: 'Arn' + }) + context.CloudFormationConfig.Capabilities = context.CloudFormationConfig.Capabilities || [ "CAPABILITY_NAMED_IAM" ] for (const serviceDefinition of serviceDefinitions) { serviceDefinition[CLASS_ROLEKEY] = { - "Fn::GetAtt": [ - resourceName, - "Arn" - ] + "Ref": `${resourceName}Arn` } } } @@ -198,6 +217,12 @@ export const dynamoPolicy = async (context) => { export const tableResources = ExecuteStep.register('DynamoDB-Tables', async (context) => { + await executor({ + context: { ...context, stackName: DYNAMODB_TABLE_STACK }, + name: `CloudFormation-Stack-init-${DYNAMODB_TABLE_STACK}`, + method: createStack + }) + for (const tableConfig of context.tableConfigs) { await executor({ context: { ...context, tableConfig }, @@ -223,10 +248,16 @@ export const tableResource = async (context) => { "Properties": properties } - const resourceName = `Dynamo${properties.TableName}` - const name = setResource(context, resourceName, dynamoDb) + const tableResourceName = `Dynamo${properties.TableName}` + const resourceName = setResource(context, tableResourceName, dynamoDb, DYNAMODB_TABLE_STACK) + + await setStackParameter({ + ...context, + resourceName, + sourceStackName: DYNAMODB_TABLE_STACK + }) - tableConfig.resourceName = name + tableConfig.resourceName = resourceName } @@ -276,7 +307,7 @@ export const lambdaResource = async (context) => { } const resourceName = `Lambda${properties.FunctionName}` - const name = setResource(context, resourceName, lambdaResource) + const name = setResource(context, resourceName, lambdaResource, getStackName(serviceDefinition)) serviceDefinition.resourceName = name } @@ -293,7 +324,7 @@ export const lambdaVersionResource = async (context) => { "CodeSha256": context.zipCodeSha256 } } - setResource(context, `${serviceDefinition.resourceName}${context.zipCodeSha256}`, versionResource) + setResource(context, `${serviceDefinition.resourceName}${context.zipCodeSha256}`, versionResource, getStackName(serviceDefinition)) } export const s3BucketResources = ExecuteStep.register('S3-Bucket', async (context) => { @@ -305,13 +336,21 @@ export const s3BucketResources = ExecuteStep.register('S3-Bucket', async (contex "Type": "AWS::S3::Bucket" } - const resourceName = `FunctionlyDeploymentBucket` - const name = setResource(context, resourceName, s3BucketResources) + const bucketResourceName = `FunctionlyDeploymentBucket` + const resourceName = setResource(context, bucketResourceName, s3BucketResources) - context.CloudFormationTemplate.Outputs[`${name}Name`] = { + context.CloudFormationTemplate.Outputs[`${resourceName}Name`] = { "Value": { - "Ref": "FunctionlyDeploymentBucket" + "Ref": bucketResourceName } } }) + +export const s3BucketParameter = ExecuteStep.register('S3-Bucket-Parameter', async (context) => { + const resourceName = `FunctionlyDeploymentBucket` + await setStackParameter({ + ...context, + resourceName + }) +}) diff --git a/src/cli/providers/cloudFormation/context/sns.ts b/src/cli/providers/cloudFormation/context/sns.ts index 545e91d..8d803d6 100644 --- a/src/cli/providers/cloudFormation/context/sns.ts +++ b/src/cli/providers/cloudFormation/context/sns.ts @@ -2,6 +2,7 @@ import { getMetadata, constants } from '../../../../annotations' const { CLASS_SNSCONFIGURATIONKEY } = constants import { ExecuteStep, executor } from '../../../context' import { setResource } from '../utils' +import { getStackName } from './stack' export const sns = ExecuteStep.register('SNS', async (context) => { await executor(context, snsTopics) @@ -52,7 +53,7 @@ export const snsTopicSubscription = async (context) => { } const resourceName = `SNS${serviceDefinition.resourceName}${snsConfig.topicName}${context.date.valueOf()}` - const topicName = setResource(context, resourceName, snsTopic) + const topicName = setResource(context, resourceName, snsTopic, getStackName(serviceDefinition)) await executor({ context: { ...context, topicName }, @@ -80,5 +81,5 @@ export const snsPermissions = (context) => { "Properties": properties } const resourceName = `${topicName}Permission` - setResource(context, resourceName, snsPermission, true) + setResource(context, resourceName, snsPermission, getStackName(serviceDefinition), true) } \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/context/stack.ts b/src/cli/providers/cloudFormation/context/stack.ts new file mode 100644 index 0000000..8ef7477 --- /dev/null +++ b/src/cli/providers/cloudFormation/context/stack.ts @@ -0,0 +1,95 @@ +import { ExecuteStep, executor } from '../../../context' +import { setResource, getResourceName } from '../utils' +import { getFunctionName } from '../../../../annotations' + +export const createStack = async (context) => { + const { stackName } = context + + context.CloudFormationStacks[stackName] = { + "AWSTemplateFormatVersion": "2010-09-09", + "Parameters": {}, + "Resources": {}, + "Outputs": {} + } + + const folderPah = context.version ? `${context.version}/${context.date.toISOString()}` : `${context.date.toISOString()}` + const awsBucket = context.__userAWSBucket ? context.awsBucket : { + "Ref": "FunctionlyDeploymentBucket" + } + + const properties = { + "Parameters": {}, + "TemplateURL": { + "Fn::Join": [ + "/", + [ + "https://s3.amazonaws.com", + awsBucket, + `functionly`, + folderPah, + `${stackName}.template` + ] + ] + } + } + + + const stack = { + "Type": "AWS::CloudFormation::Stack", + "Properties": properties, + "DependsOn": [] + } + + return setResource(context, stackName, stack) +} + +export const setStackParameter = (context) => { + const { sourceStackName, resourceName, targetStackName, attr } = context + + let parameterReference: any = { + "Ref": resourceName + } + let attrName = '' + + if (sourceStackName) { + parameterReference = { + "Fn::GetAtt": [ + sourceStackName, + `Outputs.${resourceName}` + ] + } + } else if (attr) { + attrName = attr + parameterReference = { + "Fn::GetAtt": [ + resourceName, + attr + ] + } + } + + for (const stackName in context.CloudFormationStacks) { + const template = context.CloudFormationStacks[stackName] + const stackDefinition = context.CloudFormationTemplate.Resources[stackName] + if (stackName !== sourceStackName && (!targetStackName || targetStackName === stackName)) { + template.Parameters[`${resourceName}${attrName}`] = { + "Type": "String" + } + stackDefinition.Properties.Parameters[`${resourceName}${attrName}`] = parameterReference + + if (sourceStackName) { + if (stackDefinition.DependsOn.indexOf(sourceStackName) < 0) { + stackDefinition.DependsOn.push(sourceStackName) + } + } else if (resourceName) { + stackDefinition.DependsOn.push(resourceName) + } + } + } + +} + +export const getStackName = (serviceDefinition) => { + const resourceName = getFunctionName(serviceDefinition.service) + return getResourceName(`Stack${resourceName}`) +} \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/context/uploadTemplate.ts b/src/cli/providers/cloudFormation/context/uploadTemplate.ts index 29dbe0d..fd361e8 100644 --- a/src/cli/providers/cloudFormation/context/uploadTemplate.ts +++ b/src/cli/providers/cloudFormation/context/uploadTemplate.ts @@ -3,9 +3,24 @@ import { ExecuteStep, executor } from '../../../context' import { projectConfig } from '../../../project/config' export const uploadTemplate = ExecuteStep.register('UploadTemplate', async (context) => { + for (const stackName in context.CloudFormationStacks) { + const stack = context.CloudFormationStacks[stackName] + if(!Object.keys(stack.Resources).length){ + delete context.CloudFormationStacks[stackName] + delete context.CloudFormationTemplate.Resources[stackName] + } + } + const templateData = JSON.stringify(context.CloudFormationTemplate, null, 2); - const localName = projectConfig.name ? `${projectConfig.name}.template` : 'cloudformation.template' - const uploadresult = await executor(context, uploaderStep(`services-${context.date.toISOString()}.template`, templateData, 'application/octet-stream', localName)) + const fileName = projectConfig.name ? `${projectConfig.name}.template` : 'cloudformation.template' + const uploadresult = await executor(context, uploaderStep(fileName, templateData, 'application/octet-stream')) context.S3CloudFormationTemplate = uploadresult.Key + + for (const stackName in context.CloudFormationStacks) { + const templateData = JSON.stringify(context.CloudFormationStacks[stackName], null, 2); + const templateFileName = `${stackName}.template` + const uploadresult = await executor(context, uploaderStep(templateFileName, templateData, 'application/octet-stream')) + } + return uploadresult }) \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/index.ts b/src/cli/providers/cloudFormation/index.ts index 46f7211..89b7778 100644 --- a/src/cli/providers/cloudFormation/index.ts +++ b/src/cli/providers/cloudFormation/index.ts @@ -8,7 +8,10 @@ import { projectConfig } from '../../project/config' import { executor } from '../../context' import { cloudFormationInit } from './context/cloudFormationInit' -import { tableResources, lambdaResources, roleResources, s3BucketResources, apiGateway, sns } from './context/resources' +import { + tableResources, lambdaResources, roleResources, s3BucketResources, s3BucketParameter, + apiGateway, sns, initStacks +} from './context/resources' import { uploadTemplate } from './context/uploadTemplate' export const cloudFormation = { @@ -38,8 +41,11 @@ export const cloudFormation = { } logger.info(`Functionly: Uploading binary...`) - const localName = projectConfig.name ? `${projectConfig.name}.zip` : 'project.zip' - await executor(context, uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData(), localName)) + const fileName = projectConfig.name ? `${projectConfig.name}.zip` : 'project.zip' + await executor(context, uploadZipStep(fileName, context.zipData())) + + await executor(context, initStacks) + await executor(context, s3BucketParameter) await executor(context, tableResources) await executor(context, roleResources) @@ -62,8 +68,11 @@ export const cloudFormation = { await executor(context, s3BucketResources) logger.info(`Functionly: Save binary...`) - const localName = projectConfig.name ? `${projectConfig.name}.zip` : 'project.zip' - await executor({ ...context, skipUpload: true }, uploadZipStep(`services-${context.date.toISOString()}.zip`, context.zipData(), localName)) + const fileName = projectConfig.name ? `${projectConfig.name}.zip` : 'project.zip' + await executor({ ...context, skipUpload: true }, uploadZipStep(fileName, context.zipData())) + + await executor(context, initStacks) + await executor(context, s3BucketParameter) await executor(context, tableResources) await executor(context, roleResources) diff --git a/src/cli/providers/cloudFormation/utils.ts b/src/cli/providers/cloudFormation/utils.ts index a17b9d0..ec838be 100644 --- a/src/cli/providers/cloudFormation/utils.ts +++ b/src/cli/providers/cloudFormation/utils.ts @@ -7,13 +7,33 @@ export const normalizeName = (name: string) => { return result } -export const setResource = (context, name, resource, allowOverride = false) => { +export const getResourceName = (name) => { if (!name) { throw new Error(`invalid resource name '${name}'`) } - name = normalizeName(name) - if (allowOverride === false && context.CloudFormationTemplate.Resources[name]) { - throw new Error(`resource name '${name}' already exists`) + return normalizeName(name) +} + +export const setResource = (context: any, name: string, resource: any, stackName: string = null, allowOverride = false) => { + const resourceName = getResourceName(name) + + let resources + let outputs + if (stackName) { + if (context.CloudFormationStacks[stackName]) { + resources = context.CloudFormationStacks[stackName].Resources + outputs = context.CloudFormationStacks[stackName].Outputs + } + } else { + resources = context.CloudFormationTemplate.Resources + } + + if (!resources) { + throw new Error(`Stack with name '${stackName}' not defined`) + } + + if (allowOverride === false && resources[resourceName]) { + throw new Error(`resource name '${resourceName}' already exists`) } context.usedAwsResources = context.usedAwsResources || []; @@ -21,14 +41,22 @@ export const setResource = (context, name, resource, allowOverride = false) => { context.usedAwsResources.push(resource.type) } - context.CloudFormationTemplate.Resources[name] = resource + resources[resourceName] = resource + if (outputs) { + outputs[resourceName] = { + "Value": { + "Ref": resourceName + } + } + } if (Array.isArray(context.deploymentResources)) { context.deploymentResources.push({ - name, - type: resource.Type + resourceName, + type: resource.Type, + stackName }) } - return name; + return resourceName; } \ No newline at end of file diff --git a/src/cli/utilities/aws/s3Upload.ts b/src/cli/utilities/aws/s3Upload.ts index fda7ab4..c60246b 100644 --- a/src/cli/utilities/aws/s3Upload.ts +++ b/src/cli/utilities/aws/s3Upload.ts @@ -18,22 +18,21 @@ const initAWSSDK = (context) => { return s3 } -export const uploadZipStep = (name, data, localName?) => { +export const uploadZipStep = (name, data) => { return async (context) => { - const step = uploaderStep(name, data, 'application/zip', localName) + const step = uploaderStep(name, data, 'application/zip') const uploadResult = await executor(context, step) context.S3Zip = uploadResult.Key return uploadResult } } -export const uploaderStep = (name, data, contentType, localName?) => { +export const uploaderStep = (name, data, contentType) => { return async (context) => { context.upload = { name, data, - contentType, - localName + contentType } const uploadResult = await executor(context, uploadToAws) delete context.upload @@ -45,18 +44,19 @@ export const uploadToAws = ExecuteStep.register('S3-Upload', async (context) => initAWSSDK(context) return new Promise((resolve, reject) => { const version = context.version ? `${context.version}/` : '' + const folderPah = context.version ? `${context.version}/${context.date.toISOString()}` : `${context.date.toISOString()}` const binary = new Buffer(context.upload.data, 'binary') let params = { ...config.S3, Bucket: context.awsBucket, Body: binary, - Key: `functionly/${version}${context.upload.name}`, + Key: `functionly/${folderPah}/${context.upload.name}`, ContentType: context.upload.contentType } if (context.skipUpload) { - if (config.tempDirectory && context.upload.localName) { - writeFileSync(join(config.tempDirectory, context.upload.localName), binary) + if (config.tempDirectory) { + writeFileSync(join(config.tempDirectory, context.upload.name), binary) } return resolve(params) } @@ -64,8 +64,8 @@ export const uploadToAws = ExecuteStep.register('S3-Upload', async (context) => s3.putObject(params, (err, res) => { if (err) return reject(err) - if (config.tempDirectory && context.upload.localName) { - writeFileSync(join(config.tempDirectory, context.upload.localName), binary) + if (config.tempDirectory) { + writeFileSync(join(config.tempDirectory, context.upload.name), binary) } return resolve(params) From 2409644f5d52e2cf181c0e37d30ef69ab6376d14 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 19 Jun 2017 15:42:53 +0200 Subject: [PATCH 017/196] ADD: CloudFormation LogGroup --- .../cloudFormation/context/apiGateway.ts | 7 +-- .../context/cloudFormationInit.ts | 1 + .../cloudFormation/context/resources.ts | 58 +++++++++++++++++-- src/cli/providers/cloudFormation/index.ts | 4 +- src/cli/providers/cloudFormation/utils.ts | 3 +- 5 files changed, 63 insertions(+), 10 deletions(-) diff --git a/src/cli/providers/cloudFormation/context/apiGateway.ts b/src/cli/providers/cloudFormation/context/apiGateway.ts index 504666f..5e272ae 100644 --- a/src/cli/providers/cloudFormation/context/apiGateway.ts +++ b/src/cli/providers/cloudFormation/context/apiGateway.ts @@ -7,10 +7,9 @@ import { setStackParameter, getStackName } from './stack' export const API_GATEWAY_REST_API = 'ApiGatewayRestApi' export const apiGateway = ExecuteStep.register('ApiGateway', async (context) => { - const deploymentResources = [] - await executor({ ...context, deploymentResources }, gatewayRestApi) - await executor({ ...context, deploymentResources }, gatewayResources) - await executor({ ...context, deploymentResources }, gatewayDeployment) + await executor(context, gatewayRestApi) + await executor(context, gatewayResources) + await executor(context, gatewayDeployment) }) export const gatewayRestApi = ExecuteStep.register('ApiGateway-RestApi', async (context) => { diff --git a/src/cli/providers/cloudFormation/context/cloudFormationInit.ts b/src/cli/providers/cloudFormation/context/cloudFormationInit.ts index 9893e9a..2e9fa68 100644 --- a/src/cli/providers/cloudFormation/context/cloudFormationInit.ts +++ b/src/cli/providers/cloudFormation/context/cloudFormationInit.ts @@ -16,4 +16,5 @@ export const cloudFormationInit = ExecuteStep.register('CloudFormationInit', asy "Outputs": {} } context.CloudFormationStacks = {} + context.deploymentResources = [] }) diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index fb9fde1..ca2ff30 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -145,7 +145,13 @@ export const lambdaPolicy = async (context) => { } export const logPolicy = async (context) => { - const { roleName, roleProperties } = context + const { roleName, roleProperties, serviceDefinitions } = context + + const logGroupResourceNames = serviceDefinitions.map(s => s.logGroupResourceName) + const logGroupNames = context.deploymentResources + .filter(r => r.type === 'AWS::Logs::LogGroup' && logGroupResourceNames.indexOf(r.resourceName) >= 0) + .map(r => r.resource.Properties.LogGroupName) + const policy = { "PolicyName": { "Fn::Join": [ @@ -162,11 +168,23 @@ export const logPolicy = async (context) => { "Statement": [{ "Effect": "Allow", "Action": [ - "logs:CreateLogGroup", "logs:CreateLogStream", + ], + "Resource": logGroupNames.map(n => { + return { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:" + n + ":*" + } + }) + }, { + "Effect": "Allow", + "Action": [ "logs:PutLogEvents" ], - "Resource": ["*"] + "Resource": logGroupNames.map(n => { + return { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:" + n + ":*:*" + } + }) }] } } @@ -303,7 +321,10 @@ export const lambdaResource = async (context) => { const lambdaResource = { "Type": "AWS::Lambda::Function", - "Properties": properties + "Properties": properties, + "DependsOn": [ + serviceDefinition.logGroupResourceName + ] } const resourceName = `Lambda${properties.FunctionName}` @@ -327,6 +348,35 @@ export const lambdaVersionResource = async (context) => { setResource(context, `${serviceDefinition.resourceName}${context.zipCodeSha256}`, versionResource, getStackName(serviceDefinition)) } +export const lambdaLogResources = ExecuteStep.register('Lambda-LogGroups', async (context) => { + for (const serviceDefinition of context.publishedFunctions) { + await executor({ + context: { ...context, serviceDefinition }, + name: `Lambda-Function-${serviceDefinition.service.name}`, + method: lambdaLogResource + }) + } +}) + +export const lambdaLogResource = async (context) => { + const { serviceDefinition } = context + + const functionName = getFunctionName(serviceDefinition.service) + + const properties: any = { + "LogGroupName": `/aws/lambda/${functionName}` + }; + + const lambdaResource = { + "Type": "AWS::Logs::LogGroup", + "Properties": properties + } + + const resourceName = `${functionName}LogGroup` + const name = setResource(context, resourceName, lambdaResource, getStackName(serviceDefinition)) + serviceDefinition.logGroupResourceName = name +} + export const s3BucketResources = ExecuteStep.register('S3-Bucket', async (context) => { if (context.awsBucket) { context.__userAWSBucket = true diff --git a/src/cli/providers/cloudFormation/index.ts b/src/cli/providers/cloudFormation/index.ts index 89b7778..ddaaa0e 100644 --- a/src/cli/providers/cloudFormation/index.ts +++ b/src/cli/providers/cloudFormation/index.ts @@ -10,7 +10,7 @@ import { executor } from '../../context' import { cloudFormationInit } from './context/cloudFormationInit' import { tableResources, lambdaResources, roleResources, s3BucketResources, s3BucketParameter, - apiGateway, sns, initStacks + apiGateway, sns, initStacks, lambdaLogResources } from './context/resources' import { uploadTemplate } from './context/uploadTemplate' @@ -48,6 +48,7 @@ export const cloudFormation = { await executor(context, s3BucketParameter) await executor(context, tableResources) + await executor(context, lambdaLogResources) await executor(context, roleResources) await executor(context, lambdaResources) await executor(context, apiGateway) @@ -75,6 +76,7 @@ export const cloudFormation = { await executor(context, s3BucketParameter) await executor(context, tableResources) + await executor(context, lambdaLogResources) await executor(context, roleResources) await executor(context, lambdaResources) await executor(context, apiGateway) diff --git a/src/cli/providers/cloudFormation/utils.ts b/src/cli/providers/cloudFormation/utils.ts index ec838be..a460798 100644 --- a/src/cli/providers/cloudFormation/utils.ts +++ b/src/cli/providers/cloudFormation/utils.ts @@ -54,7 +54,8 @@ export const setResource = (context: any, name: string, resource: any, stackName context.deploymentResources.push({ resourceName, type: resource.Type, - stackName + stackName, + resource }) } From 31d025c1790e619158831be0e0c04c6aeb590b15 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 20 Jun 2017 16:08:18 +0200 Subject: [PATCH 018/196] ADD: SNS Service, Publish; REFACTOR: SNS stack, updateable template --- config/default.js | 1 + src/annotations/classes/sns.ts | 2 +- src/annotations/parameters/inject.ts | 11 +- src/classes/externals/dynamoDB.ts | 11 +- src/classes/externals/sns.ts | 62 +++++++++ src/classes/index.ts | 1 + .../cloudFormation/context/resources.ts | 3 +- .../providers/cloudFormation/context/sns.ts | 129 +++++++++++++----- src/cli/providers/cloudFormation/utils.ts | 22 +++ src/cli/utilities/aws/sns.ts | 46 +++++++ src/index.ts | 2 +- 11 files changed, 245 insertions(+), 45 deletions(-) create mode 100644 src/classes/externals/sns.ts create mode 100644 src/cli/utilities/aws/sns.ts diff --git a/config/default.js b/config/default.js index f17c3d7..6422d16 100644 --- a/config/default.js +++ b/config/default.js @@ -6,6 +6,7 @@ module.exports = { Lambda: { apiVersion: '2015-03-31', signatureVersion: 'v4' }, DynamoDB: { apiVersion: '2012-08-10', signatureVersion: 'v4' }, CloudFormation: { apiVersion: '2010-05-15', signatureVersion: 'v4' }, + SNS: { apiVersion: '2010-03-31', signatureVersion: 'v4' }, }, webpack: { output: { diff --git a/src/annotations/classes/sns.ts b/src/annotations/classes/sns.ts index 388f811..a2bb65a 100644 --- a/src/annotations/classes/sns.ts +++ b/src/annotations/classes/sns.ts @@ -9,7 +9,7 @@ export const sns = (snsConfig: { }) => (target: Function) => { let snsDefinitions = getMetadata(CLASS_SNSCONFIGURATIONKEY, target) || []; - snsConfig.environmentKey = snsConfig.environmentKey || '%ClassName%_QUEUE_NAME' + snsConfig.environmentKey = snsConfig.environmentKey || '%ClassName%_SNS_TOPICNAME' const { templatedKey, templatedValue } = applyTemplates(snsConfig.environmentKey, snsConfig.topicName, target) snsDefinitions.push({ diff --git a/src/annotations/parameters/inject.ts b/src/annotations/parameters/inject.ts index 7bb0dc8..6b44ded 100644 --- a/src/annotations/parameters/inject.ts +++ b/src/annotations/parameters/inject.ts @@ -1,4 +1,4 @@ -import { PARAMETER_PARAMKEY, CLASS_ENVIRONMENTKEY, CLASS_INJECTABLEKEY, CLASS_DYNAMOTABLECONFIGURATIONKEY } from '../constants' +import { PARAMETER_PARAMKEY, CLASS_ENVIRONMENTKEY, CLASS_INJECTABLEKEY, CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_SNSCONFIGURATIONKEY } from '../constants' import { getOwnMetadata, defineMetadata, getMetadata } from '../metadata' import { getFunctionParameters } from '../utils' import { getFunctionName } from '../classes/functionName' @@ -36,8 +36,11 @@ export const inject = (type: any, ...params): any => { } } - const injectTableConfig = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, injectTarget) || [] - const tableConfig = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, target) || [] - defineMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, [...tableConfig, ...injectTableConfig], target); + [CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_SNSCONFIGURATIONKEY].forEach(key => { + const injectKeyConfig = (getMetadata(key, injectTarget) || []) + .map(c => { return { ...c, injected: true } }) + const keyConfig = getMetadata(key, target) || [] + defineMetadata(key, [...keyConfig, ...injectKeyConfig], target); + }) } } diff --git a/src/classes/externals/dynamoDB.ts b/src/classes/externals/dynamoDB.ts index ba31d51..5a98819 100644 --- a/src/classes/externals/dynamoDB.ts +++ b/src/classes/externals/dynamoDB.ts @@ -12,6 +12,13 @@ const initAWSSDK = () => { awsConfig.apiVersion = '2012-08-10' awsConfig.region = process.env.AWS_REGION || 'eu-central-1' awsConfig.endpoint = process.env.DYNAMODB_LOCAL_ENDPOINT || 'http://localhost:8000' + + console.log('Local DynamoDB configuration') + console.log(JSON.stringify({ + apiVersion: awsConfig.apiVersion, + 'region (process.env.AWS_REGION)': awsConfig.region, + 'endpoint (process.env.SNS_LOCAL_ENDPOINT)': awsConfig.endpoint, + }, null, 2)) } dynamoDB = new AWS.DynamoDB(awsConfig); @@ -102,8 +109,8 @@ export class DynamoDB extends Service { } protected setDefaultValues(params, command) { - const tableConfig = getMetadata(constants.CLASS_DYNAMOTABLECONFIGURATIONKEY, this) || {} - const tableName = (tableConfig[this.constructor.name] && tableConfig[this.constructor.name].TableName) + const tableConfig = (getMetadata(constants.CLASS_DYNAMOTABLECONFIGURATIONKEY, this) || [])[0] + const tableName = ({ TableName: tableConfig && tableConfig.tableName, ...tableConfig.nativeConfig }).TableName const initParams = { TableName: process.env[`${this.constructor.name}_TABLE_NAME`] || tableName } diff --git a/src/classes/externals/sns.ts b/src/classes/externals/sns.ts new file mode 100644 index 0000000..525dd32 --- /dev/null +++ b/src/classes/externals/sns.ts @@ -0,0 +1,62 @@ +import { SNS } from 'aws-sdk' +export { SNS } from 'aws-sdk' +// import { } from 'aws-sdk/clients/sns' + +import { Service } from '../service' +import { constants, getMetadata } from '../../annotations' +const { CLASS_SNSCONFIGURATIONKEY } = constants + +let sns = null; +const initAWSSDK = () => { + if (!sns) { + let awsConfig: any = {} + if (process.env.FUNCTIONAL_ENVIRONMENT === 'local') { + awsConfig.apiVersion = '2010-03-31' + awsConfig.region = process.env.AWS_REGION || 'eu-central-1' + awsConfig.endpoint = process.env.SNS_LOCAL_ENDPOINT || 'http://localhost:4100' + + console.log('Local SNS configuration') + console.log(JSON.stringify({ + apiVersion: awsConfig.apiVersion, + 'region (process.env.AWS_REGION)': awsConfig.region, + 'endpoint (process.env.SNS_LOCAL_ENDPOINT)': awsConfig.endpoint, + }, null, 2)) + } + + sns = new SNS(awsConfig); + } + return sns +} + +export class SimpleNotificationService extends Service { + private _snsClient: SNS + constructor() { + initAWSSDK() + + super() + this._snsClient = sns + } + public getSNS() { + return this._snsClient + } + + + public async publish(params: SNS.PublishInput) { + return new Promise((resolve, reject) => { + this._snsClient.publish(this.setDefaultValues(params, 'publish'), (err, result) => { + if (err) reject(err) + else resolve(result) + }) + }) + } + + protected setDefaultValues(params, command) { + const snsConfig = (getMetadata(CLASS_SNSCONFIGURATIONKEY, this) || [])[0] + const topicName = snsConfig && snsConfig.topicName + const initParams = { + TopicArn: 'arn:aws:sns:null:null:' + process.env[`${this.constructor.name}_SNS_TOPICNAME`] || topicName + } + + return { ...initParams, ...params } + } +} \ No newline at end of file diff --git a/src/classes/index.ts b/src/classes/index.ts index 5fb1b6e..afb50d5 100644 --- a/src/classes/index.ts +++ b/src/classes/index.ts @@ -2,4 +2,5 @@ export { Service } from './service' export { FunctionalApi } from './functionalApi' export { FunctionalService } from './functionalService' export { DynamoDB } from './externals/dynamoDB' +export { SimpleNotificationService } from './externals/sns' export { callExtension } from './core/classExtensions' \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index ca2ff30..cf4d34b 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -135,7 +135,8 @@ export const lambdaPolicy = async (context) => { "Effect": "Allow", "Action": [ "lambda:InvokeAsync", - "lambda:InvokeFunction" + "lambda:InvokeFunction", + "sns:Publish" ], "Resource": ["*"] }] diff --git a/src/cli/providers/cloudFormation/context/sns.ts b/src/cli/providers/cloudFormation/context/sns.ts index 8d803d6..71497f7 100644 --- a/src/cli/providers/cloudFormation/context/sns.ts +++ b/src/cli/providers/cloudFormation/context/sns.ts @@ -1,69 +1,115 @@ -import { getMetadata, constants } from '../../../../annotations' -const { CLASS_SNSCONFIGURATIONKEY } = constants +import { getMetadata, constants, defineMetadata } from '../../../../annotations' +const { CLASS_SNSCONFIGURATIONKEY, CLASS_ENVIRONMENTKEY } = constants import { ExecuteStep, executor } from '../../../context' -import { setResource } from '../utils' -import { getStackName } from './stack' +import { setResource, collectConfig } from '../utils' +import { createStack, setStackParameter, getStackName } from './stack' + +export const SNS_TABLE_STACK = 'SNSStack' export const sns = ExecuteStep.register('SNS', async (context) => { + await executor({ + context: { ...context, stackName: SNS_TABLE_STACK }, + name: `CloudFormation-Stack-init-${SNS_TABLE_STACK}`, + method: createStack + }) + await executor(context, snsTopics) }) export const snsTopics = ExecuteStep.register('SNS-Topics', async (context) => { - for (const serviceDefinition of context.publishedFunctions) { + const configs = collectConfig(context, CLASS_SNSCONFIGURATIONKEY, (c) => c.topicName) + + for (const snsConfig of configs) { await executor({ - context: { ...context, serviceDefinition }, - name: `SNS-Topic-${serviceDefinition.service.name}`, + context: { ...context, snsConfig }, + name: `SNS-Topic-${snsConfig.topicName}`, method: snsTopic }) + + await executor({ + context: { ...context, snsConfig }, + name: `SNS-Topic-Subscription-${snsConfig.topicName}`, + method: snsTopicSubscriptions + }) } }) export const snsTopic = async (context) => { - const { serviceDefinition } = context - let snsConfigs = getMetadata(CLASS_SNSCONFIGURATIONKEY, serviceDefinition.service) || [] + const { snsConfig } = context + + snsConfig.advTopicName = `${snsConfig.topicName}${context.date.valueOf()}` + + await executor({ + context, + name: `SNS-Topic-${snsConfig.topicName}-DynamicName`, + method: updateSNSEnvironmentVariables + }) + + const snsProperties = { + "TopicName": snsConfig.advTopicName + } + + const snsTopic = { + "Type": "AWS::SNS::Topic", + "Properties": snsProperties + } + + const resourceName = `SNS${snsConfig.advTopicName}` + const topicResourceName = setResource(context, resourceName, snsTopic, SNS_TABLE_STACK) + snsConfig.resourceName = topicResourceName +} + +export const snsTopicSubscriptions = async (context) => { + const { snsConfig } = context + + for (const { serviceDefinition, serviceConfig } of snsConfig.services) { + if (serviceConfig.injected) continue - for (const snsConfig of snsConfigs) { await executor({ - context: { ...context, snsConfig }, - name: `SNS-Topic-Subscription-${serviceDefinition.service.name}-${snsConfig.topicName}`, + context: { ...context, serviceDefinition }, + name: `SNS-Topic-Subscription-${snsConfig.topicName}-${serviceDefinition.service.name}`, method: snsTopicSubscription }) + + await executor({ + context: { ...context, serviceDefinition }, + name: `SNS-Topic-Permission-${snsConfig.topicName}-${serviceDefinition.service.name}`, + method: snsPermissions + }) } } -export const snsTopicSubscription = async (context) => { +export const snsTopicSubscription = (context) => { const { serviceDefinition, snsConfig } = context + setStackParameter({ + ...context, + sourceStackName: SNS_TABLE_STACK, + resourceName: snsConfig.resourceName, + targetStackName: getStackName(serviceDefinition) + }) + const snsProperties = { - "TopicName": snsConfig.topicName, - "Subscription": [ - { - "Endpoint": { - "Fn::GetAtt": [serviceDefinition.resourceName, "Arn"] - }, - "Protocol": "lambda" - } - ] + "Endpoint": { + "Fn::GetAtt": [serviceDefinition.resourceName, "Arn"] + }, + "Protocol": "lambda", + "TopicArn": { + "Ref": snsConfig.resourceName + } } const snsTopic = { - "Type": "AWS::SNS::Topic", - "Properties": snsProperties, - "DependsOn": [serviceDefinition.resourceName] + "Type": "AWS::SNS::Subscription", + "Properties": snsProperties } - const resourceName = `SNS${serviceDefinition.resourceName}${snsConfig.topicName}${context.date.valueOf()}` - const topicName = setResource(context, resourceName, snsTopic, getStackName(serviceDefinition)) - - await executor({ - context: { ...context, topicName }, - name: `SNS-Topic-Permission-${serviceDefinition.service.name}-${snsConfig.topicName}`, - method: snsPermissions - }) + const resourceName = `SNS${serviceDefinition.resourceName}${snsConfig.resourceName}` + const topicResourceName = setResource(context, resourceName, snsTopic, getStackName(serviceDefinition)) } export const snsPermissions = (context) => { - const { serviceDefinition, topicName } = context + const { serviceDefinition, snsConfig } = context const properties = { "FunctionName": { "Fn::GetAtt": [ @@ -73,13 +119,24 @@ export const snsPermissions = (context) => { }, "Action": "lambda:InvokeFunction", "Principal": "sns.amazonaws.com", - "SourceArn": { "Ref": topicName } + "SourceArn": { "Ref": snsConfig.resourceName } } const snsPermission = { "Type": "AWS::Lambda::Permission", "Properties": properties } - const resourceName = `${topicName}Permission` + const resourceName = `${snsConfig.resourceName}Permission` setResource(context, resourceName, snsPermission, getStackName(serviceDefinition), true) +} + +const updateSNSEnvironmentVariables = (context) => { + const { snsConfig } = context + + for (const { serviceDefinition, serviceConfig } of snsConfig.services) { + const environmentVariables = getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) || {} + environmentVariables[serviceConfig.environmentKey] = snsConfig.advTopicName + defineMetadata(CLASS_ENVIRONMENTKEY, { ...environmentVariables }, serviceDefinition.service) + } + } \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/utils.ts b/src/cli/providers/cloudFormation/utils.ts index a460798..128567f 100644 --- a/src/cli/providers/cloudFormation/utils.ts +++ b/src/cli/providers/cloudFormation/utils.ts @@ -1,3 +1,5 @@ +import { getMetadata } from '../../../annotations' + export const nameReplaceRegexp = /[^a-zA-Z0-9]/g export const normalizeName = (name: string) => { const result = name.replace(nameReplaceRegexp, '') @@ -60,4 +62,24 @@ export const setResource = (context: any, name: string, resource: any, stackName } return resourceName; +} + +export const collectConfig = (context, metadataKey, getHash: (c) => string) => { + const configs = [] + for (const serviceDefinition of context.publishedFunctions) { + let partialConfigs = (getMetadata(metadataKey, serviceDefinition.service) || []) + + for (const serviceConfig of partialConfigs) { + const hash = getHash(serviceConfig) + const config = configs.find(c => c.hash === hash) + if (config) { + config.services.push({ serviceDefinition, serviceConfig }) + continue + } + + configs.push({ ...serviceConfig, hash, services: [{ serviceDefinition, serviceConfig }] }) + } + } + + return configs } \ No newline at end of file diff --git a/src/cli/utilities/aws/sns.ts b/src/cli/utilities/aws/sns.ts new file mode 100644 index 0000000..92bb0a5 --- /dev/null +++ b/src/cli/utilities/aws/sns.ts @@ -0,0 +1,46 @@ +import { SNS } from 'aws-sdk' +import { config } from '../config' +import { __dynamoDBDefaults } from '../../../annotations' +import { ExecuteStep, executor } from '../../context' + +let sns = null; +const initAWSSDK = (context) => { + if (!sns) { + let awsConfig = { ...config.aws.SNS } + if (context.awsRegion) { + awsConfig.region = context.awsRegion + } + + if (context.deployTarget === 'local') { + awsConfig.endpoint = process.env.DYNAMODB_LOCAL_ENDPOINT || 'http://localhost:8000' + } + + sns = new SNS(awsConfig); + } + return sns +} + +export const getTopics = async (context) => { + let data = await getTopicPage(context) + const topics = [...data.Topics] + while (data.NextToken) { + data = await getTopicPage({ context, NextToken: data.NextToken }) + topics.push(...data.Topics) + } + return topics +} + + +export const getTopicPage = (context) => { + const { NextToken } = context + initAWSSDK(context) + return new Promise((resolve, reject) => { + let params = { + NextToken + }; + sns.listTopics(params, function (err, data) { + if (err) reject(err) + else resolve(data); + }); + }) +} diff --git a/src/index.ts b/src/index.ts index dbac77c..9593870 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export { Service, FunctionalApi, FunctionalService, DynamoDB, callExtension } from './classes' +export { Service, FunctionalApi, FunctionalService, DynamoDB, SimpleNotificationService, callExtension } from './classes' export { addProvider, removeProvider, Provider, AWSProvider, LocalProvider, DeployProvider } from './providers' import * as _annotations from './annotations' export const annotations = _annotations From 63acb8922e709744c9b67a4994af00f87f5308c2 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 20 Jun 2017 16:28:43 +0200 Subject: [PATCH 019/196] CHANGE: SNS publish default TopicArn from environment variables --- src/classes/externals/sns.ts | 4 +--- .../providers/cloudFormation/context/sns.ts | 24 ++++++++++++++----- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/classes/externals/sns.ts b/src/classes/externals/sns.ts index 525dd32..2d4a924 100644 --- a/src/classes/externals/sns.ts +++ b/src/classes/externals/sns.ts @@ -51,10 +51,8 @@ export class SimpleNotificationService extends Service { } protected setDefaultValues(params, command) { - const snsConfig = (getMetadata(CLASS_SNSCONFIGURATIONKEY, this) || [])[0] - const topicName = snsConfig && snsConfig.topicName const initParams = { - TopicArn: 'arn:aws:sns:null:null:' + process.env[`${this.constructor.name}_SNS_TOPICNAME`] || topicName + TopicArn: process.env[`${this.constructor.name}_SNS_TOPICNAME_ARN`] } return { ...initParams, ...params } diff --git a/src/cli/providers/cloudFormation/context/sns.ts b/src/cli/providers/cloudFormation/context/sns.ts index 71497f7..9159496 100644 --- a/src/cli/providers/cloudFormation/context/sns.ts +++ b/src/cli/providers/cloudFormation/context/sns.ts @@ -39,12 +39,6 @@ export const snsTopic = async (context) => { snsConfig.advTopicName = `${snsConfig.topicName}${context.date.valueOf()}` - await executor({ - context, - name: `SNS-Topic-${snsConfig.topicName}-DynamicName`, - method: updateSNSEnvironmentVariables - }) - const snsProperties = { "TopicName": snsConfig.advTopicName } @@ -57,6 +51,12 @@ export const snsTopic = async (context) => { const resourceName = `SNS${snsConfig.advTopicName}` const topicResourceName = setResource(context, resourceName, snsTopic, SNS_TABLE_STACK) snsConfig.resourceName = topicResourceName + + await executor({ + context, + name: `SNS-Topic-${snsConfig.topicName}-DynamicName`, + method: updateSNSEnvironmentVariables + }) } export const snsTopicSubscriptions = async (context) => { @@ -136,6 +136,18 @@ const updateSNSEnvironmentVariables = (context) => { for (const { serviceDefinition, serviceConfig } of snsConfig.services) { const environmentVariables = getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) || {} environmentVariables[serviceConfig.environmentKey] = snsConfig.advTopicName + + setStackParameter({ + ...context, + sourceStackName: SNS_TABLE_STACK, + resourceName: snsConfig.resourceName, + targetStackName: getStackName(serviceDefinition) + }) + + environmentVariables[`${serviceConfig.environmentKey}_ARN`] = { + "Ref": snsConfig.resourceName + } + defineMetadata(CLASS_ENVIRONMENTKEY, { ...environmentVariables }, serviceDefinition.service) } From 35a52f4294e084cf821c5685bd84302c9f9ce384 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Thu, 22 Jun 2017 16:19:48 +0200 Subject: [PATCH 020/196] ADD: S3 service and eventsource; FIX: sns eventsource --- src/annotations/classes/s3Storage.ts | 33 +++ src/annotations/constants.ts | 3 +- src/annotations/index.ts | 1 + src/annotations/parameters/inject.ts | 4 +- src/classes/externals/s3Storage.ts | 66 +++++ src/classes/externals/sns.ts | 1 - src/classes/index.ts | 1 + .../cloudFormation/context/apiGateway.ts | 5 - .../cloudFormation/context/resources.ts | 37 +-- .../cloudFormation/context/s3Storage.ts | 226 ++++++++++++++++++ .../providers/cloudFormation/context/stack.ts | 32 ++- src/cli/providers/cloudFormation/index.ts | 19 +- src/cli/utilities/aws/cloudFormation.ts | 10 +- src/index.ts | 2 +- src/providers/aws/eventSources/s3.ts | 20 ++ src/providers/aws/eventSources/sns.ts | 2 +- src/providers/aws/index.ts | 4 + 17 files changed, 399 insertions(+), 67 deletions(-) create mode 100644 src/annotations/classes/s3Storage.ts create mode 100644 src/classes/externals/s3Storage.ts create mode 100644 src/cli/providers/cloudFormation/context/s3Storage.ts create mode 100644 src/providers/aws/eventSources/s3.ts diff --git a/src/annotations/classes/s3Storage.ts b/src/annotations/classes/s3Storage.ts new file mode 100644 index 0000000..850a2d8 --- /dev/null +++ b/src/annotations/classes/s3Storage.ts @@ -0,0 +1,33 @@ +import { CLASS_S3CONFIGURATIONKEY } from '../constants' +import { getMetadata, defineMetadata } from '../metadata' +import { applyTemplates } from '../templates' +import { environment } from './environment' + +export const S3_BUCKET_PREFIX = '_S3_BUCKET' + +export const s3Storage = (s3Config: { + bucketName: string, + environmentKey?: string, + eventSourceConfiguration?: { + Event?: any, + Filter?: any + } +}) => (target: Function) => { + let s3Definitions = getMetadata(CLASS_S3CONFIGURATIONKEY, target) || []; + + s3Config.environmentKey = s3Config.environmentKey || `%ClassName%${S3_BUCKET_PREFIX}` + + const { templatedKey, templatedValue } = applyTemplates(s3Config.environmentKey, s3Config.bucketName, target) + const lowerCaseTemplatedValue = templatedValue ? templatedValue.toLowerCase() : templatedValue + s3Definitions.push({ + ...s3Config, + environmentKey: templatedKey, + bucketName: lowerCaseTemplatedValue, + definedBy: target.name + }) + + defineMetadata(CLASS_S3CONFIGURATIONKEY, [...s3Definitions], target) + + const environmentSetter = environment(templatedKey, lowerCaseTemplatedValue) + environmentSetter(target) +} diff --git a/src/annotations/constants.ts b/src/annotations/constants.ts index 63d1075..ed7e2b6 100644 --- a/src/annotations/constants.ts +++ b/src/annotations/constants.ts @@ -11,4 +11,5 @@ export const CLASS_LOGKEY = 'functionly:class:log' export const CLASS_INJECTABLEKEY = 'functionly:class:injectable' export const CLASS_NAMEKEY = 'functionly:class:name' export const CLASS_DYNAMOTABLECONFIGURATIONKEY = 'functionly:class:dynamoTableConfiguration' -export const CLASS_SNSCONFIGURATIONKEY = 'functionly:class:snsConfiguration' \ No newline at end of file +export const CLASS_SNSCONFIGURATIONKEY = 'functionly:class:snsConfiguration' +export const CLASS_S3CONFIGURATIONKEY = 'functionly:class:s3Configuration' \ No newline at end of file diff --git a/src/annotations/index.ts b/src/annotations/index.ts index 6bc8d3c..366a6c3 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -8,6 +8,7 @@ export { runtime } from './classes/runtime' export { functionName, getFunctionName } from './classes/functionName' export { dynamoTable, __dynamoDBDefaults } from './classes/dynamoTable' export { sns } from './classes/sns' +export { s3Storage } from './classes/s3Storage' import { simpleClassAnnotation } from './classes/simpleAnnotation' diff --git a/src/annotations/parameters/inject.ts b/src/annotations/parameters/inject.ts index 6b44ded..900d21f 100644 --- a/src/annotations/parameters/inject.ts +++ b/src/annotations/parameters/inject.ts @@ -1,4 +1,4 @@ -import { PARAMETER_PARAMKEY, CLASS_ENVIRONMENTKEY, CLASS_INJECTABLEKEY, CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_SNSCONFIGURATIONKEY } from '../constants' +import { PARAMETER_PARAMKEY, CLASS_ENVIRONMENTKEY, CLASS_INJECTABLEKEY, CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_SNSCONFIGURATIONKEY, CLASS_S3CONFIGURATIONKEY } from '../constants' import { getOwnMetadata, defineMetadata, getMetadata } from '../metadata' import { getFunctionParameters } from '../utils' import { getFunctionName } from '../classes/functionName' @@ -36,7 +36,7 @@ export const inject = (type: any, ...params): any => { } } - [CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_SNSCONFIGURATIONKEY].forEach(key => { + [CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_SNSCONFIGURATIONKEY, CLASS_S3CONFIGURATIONKEY].forEach(key => { const injectKeyConfig = (getMetadata(key, injectTarget) || []) .map(c => { return { ...c, injected: true } }) const keyConfig = getMetadata(key, target) || [] diff --git a/src/classes/externals/s3Storage.ts b/src/classes/externals/s3Storage.ts new file mode 100644 index 0000000..5e79c3c --- /dev/null +++ b/src/classes/externals/s3Storage.ts @@ -0,0 +1,66 @@ +import { S3 } from 'aws-sdk' +import { Service } from '../service' +import { constants, getMetadata } from '../../annotations' +import { S3_BUCKET_PREFIX } from '../../annotations/classes/s3Storage' +const { CLASS_S3CONFIGURATIONKEY } = constants + +export { S3 } from 'aws-sdk' + +let s3 = null; +const initAWSSDK = () => { + if (!s3) { + let awsConfig: any = {} + if (process.env.FUNCTIONAL_ENVIRONMENT === 'local') { + awsConfig.apiVersion = '2006-03-01' + awsConfig.region = process.env.AWS_REGION || 'eu-central-1' + awsConfig.endpoint = process.env.S3_LOCAL_ENDPOINT || 'http://localhost:4572' + + console.log('Local S3 configuration') + console.log(JSON.stringify({ + apiVersion: awsConfig.apiVersion, + 'region (process.env.AWS_REGION)': awsConfig.region, + 'endpoint (process.env.S3_LOCAL_ENDPOINT)': awsConfig.endpoint, + }, null, 2)) + } + + s3 = new S3(awsConfig); + } + return s3 +} + +export class S3Storage extends Service { + private _s3Client: S3 + constructor() { + initAWSSDK() + + super() + this._s3Client = s3 + } + public getS3() { + return this._s3Client + } + + + public async putObject(params: Partial) { + return new Promise((resolve, reject) => { + if (typeof params.Key === 'string') { + params = { + ...params, + Key: /^\//.test(params.Key) ? params.Key.substring(1, params.Key.length) : params.Key + } + } + this._s3Client.putObject(this.setDefaultValues(params, 'putObject'), (err, result) => { + if (err) reject(err) + else resolve(result) + }) + }) + } + + protected setDefaultValues(params, command) { + const initParams = { + Bucket: process.env[`${this.constructor.name}${S3_BUCKET_PREFIX}`] + } + + return { ...initParams, ...params } + } +} \ No newline at end of file diff --git a/src/classes/externals/sns.ts b/src/classes/externals/sns.ts index 2d4a924..308e4fa 100644 --- a/src/classes/externals/sns.ts +++ b/src/classes/externals/sns.ts @@ -1,6 +1,5 @@ import { SNS } from 'aws-sdk' export { SNS } from 'aws-sdk' -// import { } from 'aws-sdk/clients/sns' import { Service } from '../service' import { constants, getMetadata } from '../../annotations' diff --git a/src/classes/index.ts b/src/classes/index.ts index afb50d5..60b11ca 100644 --- a/src/classes/index.ts +++ b/src/classes/index.ts @@ -3,4 +3,5 @@ export { FunctionalApi } from './functionalApi' export { FunctionalService } from './functionalService' export { DynamoDB } from './externals/dynamoDB' export { SimpleNotificationService } from './externals/sns' +export { S3Storage } from './externals/s3Storage' export { callExtension } from './core/classExtensions' \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/context/apiGateway.ts b/src/cli/providers/cloudFormation/context/apiGateway.ts index 5e272ae..cf61fe3 100644 --- a/src/cli/providers/cloudFormation/context/apiGateway.ts +++ b/src/cli/providers/cloudFormation/context/apiGateway.ts @@ -251,11 +251,6 @@ export const gatewayDeployment = ExecuteStep.register('ApiGateway-Deployment', a const deploymentResourceName = `ApiGateway${context.date.valueOf()}` const resourceName = setResource(context, deploymentResourceName, ApiGatewayDeployment) - - // await setStackParameter({ - // ...context, - // resourceName - // }) }) export const setOptionsMethodResource = async (context) => { diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index cf4d34b..48d6603 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -5,7 +5,9 @@ const { CLASS_DESCRIPTIONKEY, CLASS_ROLEKEY, CLASS_MEMORYSIZEKEY, CLASS_RUNTIMEK import { ExecuteStep, executor } from '../../../context' import { setResource } from '../utils' import { createStack, setStackParameter, getStackName } from './stack' +import { getBucketReference } from './s3Storage' +export { s3DeploymentBucket, s3DeploymentBucketParameter, s3, S3_DEPLOYMENT_BUCKET_RESOURCE_NAME } from './s3Storage' export { apiGateway } from './apiGateway' export { sns } from './sns' @@ -112,6 +114,7 @@ export const roleResource = async (context) => { serviceDefinition[CLASS_ROLEKEY] = { "Ref": `${resourceName}Arn` } + serviceDefinition.roleResource = iamRole } } } @@ -281,9 +284,7 @@ export const tableResource = async (context) => { } export const lambdaResources = ExecuteStep.register('Lambda-Functions', async (context) => { - const awsBucket = context.__userAWSBucket ? context.awsBucket : { - "Ref": "FunctionlyDeploymentBucket" - } + const awsBucket = await getBucketReference(context) for (const serviceDefinition of context.publishedFunctions) { await executor({ @@ -376,32 +377,4 @@ export const lambdaLogResource = async (context) => { const resourceName = `${functionName}LogGroup` const name = setResource(context, resourceName, lambdaResource, getStackName(serviceDefinition)) serviceDefinition.logGroupResourceName = name -} - -export const s3BucketResources = ExecuteStep.register('S3-Bucket', async (context) => { - if (context.awsBucket) { - context.__userAWSBucket = true - } - - const s3BucketResources = { - "Type": "AWS::S3::Bucket" - } - - const bucketResourceName = `FunctionlyDeploymentBucket` - const resourceName = setResource(context, bucketResourceName, s3BucketResources) - - context.CloudFormationTemplate.Outputs[`${resourceName}Name`] = { - "Value": { - "Ref": bucketResourceName - } - } - -}) - -export const s3BucketParameter = ExecuteStep.register('S3-Bucket-Parameter', async (context) => { - const resourceName = `FunctionlyDeploymentBucket` - await setStackParameter({ - ...context, - resourceName - }) -}) +} \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/context/s3Storage.ts b/src/cli/providers/cloudFormation/context/s3Storage.ts new file mode 100644 index 0000000..b1a48e0 --- /dev/null +++ b/src/cli/providers/cloudFormation/context/s3Storage.ts @@ -0,0 +1,226 @@ +import { getMetadata, constants, defineMetadata, getFunctionName } from '../../../../annotations' +const { CLASS_S3CONFIGURATIONKEY, CLASS_ENVIRONMENTKEY } = constants +import { ExecuteStep, executor } from '../../../context' +import { setResource, collectConfig } from '../utils' +import { createStack, setStackParameter, getStackName } from './stack' + +export const S3_STORAGE_STACK = 'S3Stack' +export const S3_DEPLOYMENT_BUCKET_RESOURCE_NAME = 'FunctionlyDeploymentBucket' + + +export const getBucketReference = async (context) => { + return context.__userAWSBucket ? context.awsBucket : { + "Ref": S3_DEPLOYMENT_BUCKET_RESOURCE_NAME + } +} + +export const s3DeploymentBucket = ExecuteStep.register('S3-Deployment-Bucket', async (context) => { + if (context.awsBucket) { + context.__userAWSBucket = true + } + + const s3BucketResource = { + "Type": "AWS::S3::Bucket" + } + + const bucketResourceName = S3_DEPLOYMENT_BUCKET_RESOURCE_NAME + const resourceName = setResource(context, bucketResourceName, s3BucketResource) + + context.CloudFormationTemplate.Outputs[`${resourceName}Name`] = { + "Value": { + "Ref": bucketResourceName + } + } + +}) + + +export const s3DeploymentBucketParameter = ExecuteStep.register('S3-Deployment-Bucket-Parameter', async (context) => { + const resourceName = S3_DEPLOYMENT_BUCKET_RESOURCE_NAME + await setStackParameter({ + ...context, + resourceName + }) +}) + + + +export const s3 = ExecuteStep.register('S3', async (context) => { + await executor({ + context: { ...context, stackName: S3_STORAGE_STACK }, + name: `CloudFormation-Stack-init-${S3_STORAGE_STACK}`, + method: createStack + }) + + await executor(context, s3Storages) +}) + +export const s3Storages = ExecuteStep.register('S3-Storages', async (context) => { + const configs = collectConfig(context, CLASS_S3CONFIGURATIONKEY, (c) => c.bucketName) + + for (const s3Config of configs) { + const s3BucketDefinition = await executor({ + context: { ...context, s3Config }, + name: `S3-Storage-${s3Config.bucketName}`, + method: s3Storage + }) + + await executor({ + context: { ...context, s3Config, s3BucketDefinition }, + name: `S3-Storage-Subscription-${s3Config.bucketName}`, + method: s3StorageSubscriptions + }) + } +}) + +export const s3Storage = async (context) => { + const { s3Config } = context + + const s3Properties = { + "BucketName": s3Config.bucketName + } + + const s3Bucket = { + "Type": "AWS::S3::Bucket", + "Properties": s3Properties + } + + const resourceName = `S3${s3Config.bucketName}` + const bucketResourceName = setResource(context, resourceName, s3Bucket, S3_STORAGE_STACK) + s3Config.resourceName = bucketResourceName + + + for (const { serviceDefinition, serviceConfig } of s3Config.services) { + if (!serviceConfig.injected) continue + + await executor({ + context: { ...context, serviceDefinition, serviceConfig }, + name: `S3-Storage-Policy-${s3Config.bucketName}-${serviceDefinition.service.name}`, + method: s3StoragePolicy + }) + } + + return s3Bucket +} + + +export const s3StoragePolicy = async (context) => { + const { s3Config, serviceDefinition, serviceConfig } = context + + let policy = serviceDefinition.roleResource.Properties.Policies.find(p => p.PolicyDocument.Statement[0].Action.includes('s3:PutObject')) + if (!policy) { + policy = { + "PolicyName": { + "Fn::Join": [ + "-", + [ + "functionly", + serviceDefinition.roleResource.Properties.RoleName, + "s3" + ] + ] + }, + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutObjectTagging", + "s3:PutObjectVersionAcl", + "s3:PutObjectVersionTagging" + ], + "Resource": [] + }] + } + } + serviceDefinition.roleResource.Properties.Policies.push(policy) + } + + policy.PolicyDocument.Statement[0].Resource.push({ + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + s3Config.bucketName, + "/*" + ] + ] + }) +} + +export const s3StorageSubscriptions = async (context) => { + const { s3Config } = context + + for (const { serviceDefinition, serviceConfig } of s3Config.services) { + if (serviceConfig.injected) continue + + await executor({ + context: { ...context, serviceDefinition, serviceConfig }, + name: `S3-Storage-Subscription-${s3Config.bucketName}-${serviceDefinition.service.name}`, + method: s3BucketSubscription + }) + + await executor({ + context: { ...context, serviceDefinition, serviceConfig }, + name: `S3-Storage-Permission-${s3Config.bucketName}-${serviceDefinition.service.name}`, + method: s3Permissions + }) + } +} + +export const s3BucketSubscription = (context) => { + const { serviceDefinition, serviceConfig, s3Config, s3BucketDefinition } = context + + setStackParameter({ + ...context, + sourceStackName: getStackName(serviceDefinition), + resourceName: serviceDefinition.resourceName, + targetStackName: S3_STORAGE_STACK + }) + + setStackParameter({ + ...context, + sourceStackName: getStackName(serviceDefinition), + resourceName: serviceDefinition.resourceName, + targetStackName: S3_STORAGE_STACK, + attr: 'Arn' + }) + + s3BucketDefinition.Properties.NotificationConfiguration = + s3BucketDefinition.Properties.NotificationConfiguration || {} + s3BucketDefinition.Properties.NotificationConfiguration.LambdaConfigurations = + s3BucketDefinition.Properties.NotificationConfiguration.LambdaConfigurations || [] + + s3BucketDefinition.Properties.NotificationConfiguration.LambdaConfigurations.push({ + "Event": "s3:ObjectCreated:*", + ...serviceConfig.eventSourceConfiguration, + "Function": { + // "Fn::Sub": "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:" + serviceDefinition.resourceName + "Ref": `${serviceDefinition.resourceName}Arn` + } + }) +} + +export const s3Permissions = (context) => { + const { serviceDefinition, serviceConfig, s3Config, s3BucketDefinition } = context + const properties = { + "FunctionName": { + "Ref": serviceDefinition.resourceName + }, + "Action": "lambda:InvokeFunction", + "Principal": "s3.amazonaws.com", + "SourceArn": { + "Fn::Join": [":", [ + "arn", "aws", "s3", "", "", serviceConfig.bucketName]] + } + } + + const s3Permission = { + "Type": "AWS::Lambda::Permission", + "Properties": properties + } + const resourceName = `${s3Config.resourceName}Permission` + const permissionResourceName = setResource(context, resourceName, s3Permission, getStackName(serviceDefinition), true) +} diff --git a/src/cli/providers/cloudFormation/context/stack.ts b/src/cli/providers/cloudFormation/context/stack.ts index 8ef7477..718760e 100644 --- a/src/cli/providers/cloudFormation/context/stack.ts +++ b/src/cli/providers/cloudFormation/context/stack.ts @@ -1,6 +1,7 @@ import { ExecuteStep, executor } from '../../../context' import { setResource, getResourceName } from '../utils' import { getFunctionName } from '../../../../annotations' +import { getBucketReference } from './s3Storage' export const createStack = async (context) => { const { stackName } = context @@ -13,9 +14,7 @@ export const createStack = async (context) => { } const folderPah = context.version ? `${context.version}/${context.date.toISOString()}` : `${context.date.toISOString()}` - const awsBucket = context.__userAWSBucket ? context.awsBucket : { - "Ref": "FunctionlyDeploymentBucket" - } + const awsBucket = await getBucketReference(context) const properties = { "Parameters": {}, @@ -58,6 +57,23 @@ export const setStackParameter = (context) => { `Outputs.${resourceName}` ] } + if (attr) { + attrName = attr + parameterReference = { + "Fn::GetAtt": [ + sourceStackName, + `Outputs.${resourceName}${attr}` + ] + } + context.CloudFormationStacks[sourceStackName].Outputs[`${resourceName}${attr}`] = { + "Value": { + "Fn::GetAtt": [ + resourceName, + attr + ] + } + } + } } else if (attr) { attrName = attr parameterReference = { @@ -77,12 +93,10 @@ export const setStackParameter = (context) => { } stackDefinition.Properties.Parameters[`${resourceName}${attrName}`] = parameterReference - if (sourceStackName) { - if (stackDefinition.DependsOn.indexOf(sourceStackName) < 0) { - stackDefinition.DependsOn.push(sourceStackName) - } - } else if (resourceName) { - stackDefinition.DependsOn.push(resourceName) + + const dependsOnResourceName = sourceStackName ? sourceStackName : resourceName + if (stackDefinition.DependsOn.indexOf(dependsOnResourceName) < 0) { + stackDefinition.DependsOn.push(dependsOnResourceName) } } } diff --git a/src/cli/providers/cloudFormation/index.ts b/src/cli/providers/cloudFormation/index.ts index ddaaa0e..65b31c3 100644 --- a/src/cli/providers/cloudFormation/index.ts +++ b/src/cli/providers/cloudFormation/index.ts @@ -3,14 +3,14 @@ import { bundle } from '../../utilities/webpack' import { logger } from '../../utilities/logger' import { zip } from '../../utilities/compress' import { uploadZipStep } from '../../utilities/aws/s3Upload' -import { createStack, updateStack, getTemplate, getStackBucketName, describeStacks } from '../../utilities/aws/cloudFormation' +import { createStack, updateStack, getTemplate, describeStackResouce, describeStacks } from '../../utilities/aws/cloudFormation' import { projectConfig } from '../../project/config' import { executor } from '../../context' import { cloudFormationInit } from './context/cloudFormationInit' import { - tableResources, lambdaResources, roleResources, s3BucketResources, s3BucketParameter, - apiGateway, sns, initStacks, lambdaLogResources + tableResources, lambdaResources, roleResources, s3DeploymentBucket, s3DeploymentBucketParameter, + apiGateway, sns, s3, initStacks, lambdaLogResources, S3_DEPLOYMENT_BUCKET_RESOURCE_NAME } from './context/resources' import { uploadTemplate } from './context/uploadTemplate' @@ -22,7 +22,7 @@ export const cloudFormation = { await executor(context, zip) await executor(context, cloudFormationInit) - await executor(context, s3BucketResources) + await executor(context, s3DeploymentBucket) try { await executor(context, getTemplate) @@ -37,7 +37,8 @@ export const cloudFormation = { } } if (!context.awsBucket) { - await executor(context, getStackBucketName) + const bucketData = await executor({ ...context, LogicalResourceId: S3_DEPLOYMENT_BUCKET_RESOURCE_NAME }, describeStackResouce) + context.awsBucket = bucketData.StackResourceDetail.PhysicalResourceId } logger.info(`Functionly: Uploading binary...`) @@ -45,7 +46,7 @@ export const cloudFormation = { await executor(context, uploadZipStep(fileName, context.zipData())) await executor(context, initStacks) - await executor(context, s3BucketParameter) + await executor(context, s3DeploymentBucketParameter) await executor(context, tableResources) await executor(context, lambdaLogResources) @@ -53,6 +54,7 @@ export const cloudFormation = { await executor(context, lambdaResources) await executor(context, apiGateway) await executor(context, sns) + await executor(context, s3) logger.info(`Functionly: Uploading template...`) await executor(context, uploadTemplate) @@ -66,14 +68,14 @@ export const cloudFormation = { await executor(context, zip) await executor(context, cloudFormationInit) - await executor(context, s3BucketResources) + await executor(context, s3DeploymentBucket) logger.info(`Functionly: Save binary...`) const fileName = projectConfig.name ? `${projectConfig.name}.zip` : 'project.zip' await executor({ ...context, skipUpload: true }, uploadZipStep(fileName, context.zipData())) await executor(context, initStacks) - await executor(context, s3BucketParameter) + await executor(context, s3DeploymentBucketParameter) await executor(context, tableResources) await executor(context, lambdaLogResources) @@ -81,6 +83,7 @@ export const cloudFormation = { await executor(context, lambdaResources) await executor(context, apiGateway) await executor(context, sns) + await executor(context, s3) logger.info(`Functionly: Save template...`) await executor({ ...context, skipUpload: true }, uploadTemplate) diff --git a/src/cli/utilities/aws/cloudFormation.ts b/src/cli/utilities/aws/cloudFormation.ts index 1588e3b..00aa1a4 100644 --- a/src/cli/utilities/aws/cloudFormation.ts +++ b/src/cli/utilities/aws/cloudFormation.ts @@ -85,21 +85,17 @@ export const getTemplate = ExecuteStep.register('CloudFormation-GetTemplate', (c }) }) - -export const getStackBucketName = ExecuteStep.register('CloudFormation-GetStackBucketName', (context) => { - if (context.awsBucket) return context.awsBucket - +export const describeStackResouce = ExecuteStep.register('CloudFormation-DescribeStackResouce', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { let params = { StackName: context.CloudFormationConfig.StackName, - LogicalResourceId: 'FunctionlyDeploymentBucket' + LogicalResourceId: context.LogicalResourceId } cloudFormation.describeStackResource(params, function (err, data) { if (err) return reject(err) - context.awsBucket = data.StackResourceDetail.PhysicalResourceId - return resolve(data.StackResourceDetail.PhysicalResourceId); + return resolve(data); }); }) }) diff --git a/src/index.ts b/src/index.ts index 9593870..9d699aa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export { Service, FunctionalApi, FunctionalService, DynamoDB, SimpleNotificationService, callExtension } from './classes' +export { Service, FunctionalApi, FunctionalService, DynamoDB, SimpleNotificationService, S3Storage, callExtension } from './classes' export { addProvider, removeProvider, Provider, AWSProvider, LocalProvider, DeployProvider } from './providers' import * as _annotations from './annotations' export const annotations = _annotations diff --git a/src/providers/aws/eventSources/s3.ts b/src/providers/aws/eventSources/s3.ts new file mode 100644 index 0000000..1e3acd3 --- /dev/null +++ b/src/providers/aws/eventSources/s3.ts @@ -0,0 +1,20 @@ +import { EventSource } from './eventSource' + +export class S3 extends EventSource { + public available(eventContext: any): boolean { + const { event } = eventContext + return event && Array.isArray(event.Records) && event.Records.length && event.Records[0].EventSource === "aws:s3" ? true : false + } + + public async parameterResolver(parameter, event) { + const data = event.event.Records[0] + + switch (parameter.type) { + case 'param': + if (data && data[parameter.from]) return data[parameter.from] + break + default: + return await super.parameterResolver(parameter, event) + } + } +} \ No newline at end of file diff --git a/src/providers/aws/eventSources/sns.ts b/src/providers/aws/eventSources/sns.ts index 5720028..6d762f7 100644 --- a/src/providers/aws/eventSources/sns.ts +++ b/src/providers/aws/eventSources/sns.ts @@ -3,7 +3,7 @@ import { EventSource } from './eventSource' export class SNS extends EventSource { public available(eventContext: any): boolean { const { event } = eventContext - return event && Array.isArray(event.Records) ? true : false + return event && Array.isArray(event.Records) && event.Records.length && event.Records[0].EventSource === "aws:sns" ? true : false } public async parameterResolver(parameter, event) { diff --git a/src/providers/aws/index.ts b/src/providers/aws/index.ts index 2ea179e..5efcddf 100644 --- a/src/providers/aws/index.ts +++ b/src/providers/aws/index.ts @@ -3,11 +3,15 @@ import { Provider } from '../core/provider' import { getFunctionName } from '../../annotations' import { ApiGateway } from './eventSources/apiGateway' import { LambdaCall } from './eventSources/lambdaCall' +import { SNS } from './eventSources/sns' +import { S3 } from './eventSources/s3' const lambda = new Lambda(); const eventSourceHandlers = [ new ApiGateway(), + new SNS(), + new S3(), new LambdaCall() ] From cb8c1b3ef674fab8b2a54e89eb2e7d3bb5222a41 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Fri, 23 Jun 2017 15:46:08 +0200 Subject: [PATCH 021/196] ADD param property decorator: deep property getter; FIX: s3 eventsource --- src/helpers/property.ts | 5 +++++ src/providers/aws/eventSources/apiGateway.ts | 12 +++++++----- src/providers/aws/eventSources/lambdaCall.ts | 3 ++- src/providers/aws/eventSources/s3.ts | 6 +++--- src/providers/aws/eventSources/sns.ts | 4 ++-- 5 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 src/helpers/property.ts diff --git a/src/helpers/property.ts b/src/helpers/property.ts new file mode 100644 index 0000000..9ea7868 --- /dev/null +++ b/src/helpers/property.ts @@ -0,0 +1,5 @@ +export const get = (obj, path) => { + return path.split('.').reduce(function (prev, curr) { + return prev ? prev[curr] : undefined; + }, obj); +} \ No newline at end of file diff --git a/src/providers/aws/eventSources/apiGateway.ts b/src/providers/aws/eventSources/apiGateway.ts index 90d7b8d..189eeac 100644 --- a/src/providers/aws/eventSources/apiGateway.ts +++ b/src/providers/aws/eventSources/apiGateway.ts @@ -1,4 +1,5 @@ import { EventSource } from './eventSource' +import { get } from '../../../helpers/property' export class ApiGateway extends EventSource { public available(eventContext: any): boolean { @@ -14,11 +15,12 @@ export class ApiGateway extends EventSource { switch (parameter.type) { case 'param': - if (body && body[parameter.from]) return body[parameter.from] - if (query && query[parameter.from]) return query[parameter.from] - if (params && params[parameter.from]) return params[parameter.from] - if (headers && headers[parameter.from]) return headers[parameter.from] - break + let value + if (typeof (value = get(body, parameter.from)) !== 'undefined') return value + if (typeof (value = get(query, parameter.from)) !== 'undefined') return value + if (typeof (value = get(params, parameter.from)) !== 'undefined') return value + if (typeof (value = get(headers, parameter.from)) !== 'undefined') return value + return value; default: return await super.parameterResolver(parameter, event) } diff --git a/src/providers/aws/eventSources/lambdaCall.ts b/src/providers/aws/eventSources/lambdaCall.ts index a22ef8a..03453ab 100644 --- a/src/providers/aws/eventSources/lambdaCall.ts +++ b/src/providers/aws/eventSources/lambdaCall.ts @@ -1,10 +1,11 @@ import { EventSource } from './eventSource' +import { get } from '../../../helpers/property' export class LambdaCall extends EventSource { public async parameterResolver(parameter, event) { switch (parameter.type) { case 'param': - return event.event[parameter.from] + return get(event.event, parameter.from) default: return await super.parameterResolver(parameter, event) } diff --git a/src/providers/aws/eventSources/s3.ts b/src/providers/aws/eventSources/s3.ts index 1e3acd3..33a73ec 100644 --- a/src/providers/aws/eventSources/s3.ts +++ b/src/providers/aws/eventSources/s3.ts @@ -1,9 +1,10 @@ import { EventSource } from './eventSource' +import { get } from '../../../helpers/property' export class S3 extends EventSource { public available(eventContext: any): boolean { const { event } = eventContext - return event && Array.isArray(event.Records) && event.Records.length && event.Records[0].EventSource === "aws:s3" ? true : false + return event && Array.isArray(event.Records) && event.Records.length && event.Records[0].eventSource === "aws:s3" ? true : false } public async parameterResolver(parameter, event) { @@ -11,8 +12,7 @@ export class S3 extends EventSource { switch (parameter.type) { case 'param': - if (data && data[parameter.from]) return data[parameter.from] - break + return get(data, parameter.from) default: return await super.parameterResolver(parameter, event) } diff --git a/src/providers/aws/eventSources/sns.ts b/src/providers/aws/eventSources/sns.ts index 6d762f7..8cb2946 100644 --- a/src/providers/aws/eventSources/sns.ts +++ b/src/providers/aws/eventSources/sns.ts @@ -1,4 +1,5 @@ import { EventSource } from './eventSource' +import { get } from '../../../helpers/property' export class SNS extends EventSource { public available(eventContext: any): boolean { @@ -11,8 +12,7 @@ export class SNS extends EventSource { switch (parameter.type) { case 'param': - if (data && data[parameter.from]) return data[parameter.from] - break + return get(data, parameter.from) default: return await super.parameterResolver(parameter, event) } From 7770ee2fd20a6bc0edd9610d16dbc414c342a91f Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 26 Jun 2017 13:28:28 +0200 Subject: [PATCH 022/196] ADD: Annotation tests --- package.json | 8 +- test/annotation.tests.ts | 699 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 703 insertions(+), 4 deletions(-) create mode 100644 test/annotation.tests.ts diff --git a/package.json b/package.json index d303bf0..3bf46ff 100644 --- a/package.json +++ b/package.json @@ -23,13 +23,13 @@ "homepage": "https://github.com/jaystack/functionly#readme", "devDependencies": { "@types/async": "^2.0.33", - "@types/chai": "^3.4.34", + "@types/chai": "^4.0.1", "@types/lodash": "^4.14.38", - "@types/mocha": "^2.2.33", + "@types/mocha": "^2.2.41", "@types/node": "^6.0.46", "@types/winston": "0.0.30", - "chai": "^3.5.0", - "mocha": "^3.2.0", + "chai": "^4.0.2", + "mocha": "^3.4.2", "typescript": "^2.3.0" }, "dependencies": { diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts new file mode 100644 index 0000000..b549064 --- /dev/null +++ b/test/annotation.tests.ts @@ -0,0 +1,699 @@ +import 'mocha' +import { expect } from 'chai' + +import { + CLASS_APIGATEWAYKEY, CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_ENVIRONMENTKEY, CLASS_NAMEKEY, + CLASS_INJECTABLEKEY, CLASS_LOGKEY, CLASS_RUNTIMEKEY, CLASS_MEMORYSIZEKEY, CLASS_TIMEOUTKEY, + CLASS_S3CONFIGURATIONKEY, CLASS_SNSCONFIGURATIONKEY, CLASS_TAGKEY, CLASS_ROLEKEY, CLASS_DESCRIPTIONKEY, + PARAMETER_PARAMKEY +} from '../src/annotations/constants' +import { applyTemplates, templates } from '../src/annotations/templates' +import { getFunctionParameters } from '../src/annotations/utils' +import { getMetadata, getOwnMetadata } from '../src/annotations/metadata' +import { apiGateway } from '../src/annotations/classes/apiGateway' +import { dynamoTable, __dynamoDBDefaults } from '../src/annotations/classes/dynamoTable' +import { environment } from '../src/annotations/classes/environment' +import { functionName, getFunctionName } from '../src/annotations/classes/functionName' +import { injectable } from '../src/annotations/classes/injectable' +import { log } from '../src/annotations/classes/log' +import { runtime } from '../src/annotations/classes/runtime' +import { s3Storage } from '../src/annotations/classes/s3Storage' +import { sns } from '../src/annotations/classes/sns' +import { tag } from '../src/annotations/classes/tag' +import { role, description } from '../src/annotations' + +import { inject } from '../src/annotations/parameters/inject' +import { param } from '../src/annotations/parameters/param' +import { FunctionalService, Service } from '../src/classes' + + + +describe('annotations', () => { + + describe("templates", () => { + class TemplateTestClass { } + + it("simple", () => { + + const { templatedKey, templatedValue } = applyTemplates('key', 'value', TemplateTestClass) + + expect(templatedKey).to.equal('key') + expect(templatedValue).to.equal('value') + }) + it("key param", () => { + const { templatedKey, templatedValue } = applyTemplates('%ClassName%_key', 'value', TemplateTestClass) + + expect(templatedKey).to.equal('TemplateTestClass_key') + expect(templatedValue).to.equal('value') + }) + it("value param", () => { + const { templatedKey, templatedValue } = applyTemplates('key', '%ClassName%_value', TemplateTestClass) + + expect(templatedKey).to.equal('key') + expect(templatedValue).to.equal('TemplateTestClass_value') + }) + it("key-value param", () => { + const { templatedKey, templatedValue } = applyTemplates('%ClassName%_key', '%ClassName%_value', TemplateTestClass) + + expect(templatedKey).to.equal('TemplateTestClass_key') + expect(templatedValue).to.equal('TemplateTestClass_value') + }) + + describe("custom templates", () => { + + before(() => { + templates.push({ + name: 'templateName', + regexp: /%myClassName%/g, + resolution: (target) => 'my' + target.name + }) + }) + + after(() => { + templates.length = 1 + }) + + it("simple", () => { + + const { templatedKey, templatedValue } = applyTemplates('key', 'value', TemplateTestClass) + + expect(templatedKey).to.equal('key') + expect(templatedValue).to.equal('value') + }) + it("key param", () => { + const { templatedKey, templatedValue } = applyTemplates('%myClassName%_key', 'value', TemplateTestClass) + + expect(templatedKey).to.equal('myTemplateTestClass_key') + expect(templatedValue).to.equal('value') + }) + it("value param", () => { + const { templatedKey, templatedValue } = applyTemplates('key', '%myClassName%_value', TemplateTestClass) + + expect(templatedKey).to.equal('key') + expect(templatedValue).to.equal('myTemplateTestClass_value') + }) + it("key-value param", () => { + const { templatedKey, templatedValue } = applyTemplates('%myClassName%_key', '%myClassName%_value', TemplateTestClass) + + expect(templatedKey).to.equal('myTemplateTestClass_key') + expect(templatedValue).to.equal('myTemplateTestClass_value') + }) + }) + }) + + describe("utils", () => { + it("getFunctionParameters", () => { + class GetFunctionParameterClass { + method(p1, p2, p3) { } + } + + const parameters = getFunctionParameters(GetFunctionParameterClass, 'method') + + expect(parameters).to.have.ordered.members(['p1', 'p2', 'p3']) + expect(parameters).to.be.an('array').that.does.not.include('p4'); + }) + }) + + describe("classes", () => { + describe("apiGateway", () => { + it("path", () => { + @apiGateway({ path: '/v1/test' }) + class ApiGatewayTestClass { } + + const value = getMetadata(CLASS_APIGATEWAYKEY, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('path', '/v1/test') + expect(matadata).to.have.property('method', 'get') + expect(matadata).to.have.property('cors', false) + expect(matadata).to.have.property('authorization', 'AWS_IAM') + }) + it("method", () => { + @apiGateway({ path: '/v1/test', method: 'post' }) + class ApiGatewayTestClass { } + + const value = getMetadata(CLASS_APIGATEWAYKEY, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('path', '/v1/test') + expect(matadata).to.have.property('method', 'post') + expect(matadata).to.have.property('cors', false) + expect(matadata).to.have.property('authorization', 'AWS_IAM') + }) + it("cors", () => { + @apiGateway({ path: '/v1/test', cors: true }) + class ApiGatewayTestClass { } + + const value = getMetadata(CLASS_APIGATEWAYKEY, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('path', '/v1/test') + expect(matadata).to.have.property('method', 'get') + expect(matadata).to.have.property('cors', true) + expect(matadata).to.have.property('authorization', 'AWS_IAM') + }) + it("authorization", () => { + @apiGateway({ path: '/v1/test', authorization: 'NONE' }) + class ApiGatewayTestClass { } + + const value = getMetadata(CLASS_APIGATEWAYKEY, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('path', '/v1/test') + expect(matadata).to.have.property('method', 'get') + expect(matadata).to.have.property('cors', false) + expect(matadata).to.have.property('authorization', 'NONE') + }) + }) + describe("dynamoTable", () => { + it("tableName", () => { + @dynamoTable({ tableName: 'mytablename' }) + class DynamoTableTestClass { } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, DynamoTableTestClass) + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('tableName', 'mytablename') + expect(matadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') + expect(matadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(matadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); + }) + it("environmentKey", () => { + @dynamoTable({ tableName: 'mytablename', environmentKey: 'myenvkey' }) + class DynamoTableTestClass { } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, DynamoTableTestClass) + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('tableName', 'mytablename') + expect(matadata).to.have.property('environmentKey', 'myenvkey') + expect(matadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(matadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); + }) + it("nativeConfig", () => { + @dynamoTable({ + tableName: 'mytablename', nativeConfig: { + ProvisionedThroughput: { + ReadCapacityUnits: 4, + WriteCapacityUnits: 4 + } + } + }) + class DynamoTableTestClass { } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, DynamoTableTestClass) + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('tableName', 'mytablename') + expect(matadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') + expect(matadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(matadata).to.have.deep.property('nativeConfig').that.deep.equal({ + ...__dynamoDBDefaults, + ProvisionedThroughput: { + ReadCapacityUnits: 4, + WriteCapacityUnits: 4 + } + }); + }) + }) + describe("environment", () => { + it("key - value", () => { + @environment('key', 'value') + @environment('key2', 'value2') + class EnvironmentTestClass { } + + const matadata = getMetadata(CLASS_ENVIRONMENTKEY, EnvironmentTestClass) + + expect(matadata).to.have.all.keys('key', 'key2') + expect(matadata).to.have.property('key', 'value') + expect(matadata).to.have.property('key2', 'value2') + expect(matadata).to.not.have.property('key3') + }) + it("template", () => { + @environment('%ClassName%_env', 'value') + @environment('key', '%ClassName%_value') + @environment('%ClassName%_env2', '%ClassName%_value2') + class EnvironmentTestClass { } + + const matadata = getMetadata(CLASS_ENVIRONMENTKEY, EnvironmentTestClass) + + expect(matadata).to.have.all.keys('EnvironmentTestClass_env', 'key', 'EnvironmentTestClass_env2') + expect(matadata).to.have.property('EnvironmentTestClass_env', 'value') + expect(matadata).to.have.property('key', 'EnvironmentTestClass_value') + expect(matadata).to.have.property('EnvironmentTestClass_env2', 'EnvironmentTestClass_value2') + }) + }) + describe("functionName", () => { + it("metadata", () => { + @functionName('fnName') + class FunctionNameTestClass { } + + const name = getMetadata(CLASS_NAMEKEY, FunctionNameTestClass) + + expect(name).to.equal('fnName') + }) + it("functionName", () => { + class FunctionNameTestClass { } + + const name = getFunctionName(FunctionNameTestClass) + + expect(name).to.equal(FunctionNameTestClass.name) + }) + it("attribute", () => { + @functionName('fnName') + class FunctionNameTestClass { } + + const name = getFunctionName(FunctionNameTestClass) + + expect(name).to.equal('fnName') + }) + it("instance functionName", () => { + class FunctionNameTestClass { } + + const name = getFunctionName(new FunctionNameTestClass()) + + expect(name).to.equal(FunctionNameTestClass.name) + }) + it("instance attribute", () => { + @functionName('fnName') + class FunctionNameTestClass { } + + const name = getFunctionName(new FunctionNameTestClass()) + + expect(name).to.equal('fnName') + }) + }) + describe("injectable", () => { + it("injectable", () => { + @injectable + class InjectableTestClass { } + + const value = getMetadata(CLASS_INJECTABLEKEY, InjectableTestClass) + + expect(value).to.equal(true) + }) + it("non injectable", () => { + class InjectableTestClass { } + + const value = getMetadata(CLASS_INJECTABLEKEY, InjectableTestClass) + + expect(value).to.undefined + }) + }) + describe("log", () => { + it("log", () => { + @log() + class LogTestClass { } + + const value = getMetadata(CLASS_LOGKEY, LogTestClass) + + expect(value).to.equal(true) + }) + it("non log", () => { + class LogTestClass { } + + const value = getMetadata(CLASS_LOGKEY, LogTestClass) + + expect(value).to.undefined + }) + }) + describe("runtime", () => { + it("type", () => { + @runtime({ type: 'nodejs6.10' }) + class RuntimeTestClass { } + + const runtimeValue = getMetadata(CLASS_RUNTIMEKEY, RuntimeTestClass) + const memoryValue = getMetadata(CLASS_MEMORYSIZEKEY, RuntimeTestClass) + const timeoutValue = getMetadata(CLASS_TIMEOUTKEY, RuntimeTestClass) + + expect(runtimeValue).to.equal('nodejs6.10') + expect(memoryValue).to.undefined + expect(timeoutValue).to.undefined + }) + it("memorySize", () => { + @runtime({ memorySize: 100 }) + class RuntimeTestClass { } + + const runtimeValue = getMetadata(CLASS_RUNTIMEKEY, RuntimeTestClass) + const memoryValue = getMetadata(CLASS_MEMORYSIZEKEY, RuntimeTestClass) + const timeoutValue = getMetadata(CLASS_TIMEOUTKEY, RuntimeTestClass) + + expect(runtimeValue).to.undefined + expect(memoryValue).to.equal(100) + expect(timeoutValue).to.undefined + }) + it("timeout", () => { + @runtime({ timeout: 3 }) + class RuntimeTestClass { } + + const runtimeValue = getMetadata(CLASS_RUNTIMEKEY, RuntimeTestClass) + const memoryValue = getMetadata(CLASS_MEMORYSIZEKEY, RuntimeTestClass) + const timeoutValue = getMetadata(CLASS_TIMEOUTKEY, RuntimeTestClass) + + expect(runtimeValue).to.undefined + expect(memoryValue).to.undefined + expect(timeoutValue).to.equal(3) + }) + it("all", () => { + @runtime({ type: 'nodejs6.10', memorySize: 100, timeout: 3 }) + class RuntimeTestClass { } + + const runtimeValue = getMetadata(CLASS_RUNTIMEKEY, RuntimeTestClass) + const memoryValue = getMetadata(CLASS_MEMORYSIZEKEY, RuntimeTestClass) + const timeoutValue = getMetadata(CLASS_TIMEOUTKEY, RuntimeTestClass) + + expect(runtimeValue).to.equal('nodejs6.10') + expect(memoryValue).to.equal(100) + expect(timeoutValue).to.equal(3) + }) + }) + describe("s3Storage", () => { + it("bucketName", () => { + @s3Storage({ bucketName: 'mybucketname' }) + class S3StorageTestClass { } + + const value = getMetadata(CLASS_S3CONFIGURATIONKEY, S3StorageTestClass) + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('bucketName', 'mybucketname') + expect(matadata).to.have.property('environmentKey', 'S3StorageTestClass_S3_BUCKET') + expect(matadata).to.have.property('definedBy', S3StorageTestClass.name) + expect(matadata).to.not.have.property('eventSourceConfiguration') + }) + it("bucketName LowerCase", () => { + @s3Storage({ bucketName: 'myBucketName' }) + class S3StorageTestClass { } + + const value = getMetadata(CLASS_S3CONFIGURATIONKEY, S3StorageTestClass) + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('bucketName').that.not.equal('myBucketName') + expect(matadata).to.have.property('bucketName', 'mybucketname') + expect(matadata).to.have.property('environmentKey', 'S3StorageTestClass_S3_BUCKET') + expect(matadata).to.have.property('definedBy', S3StorageTestClass.name) + expect(matadata).to.not.have.property('eventSourceConfiguration') + }) + it("environmentKey", () => { + @s3Storage({ bucketName: 'mybucketname', environmentKey: 'myenvkey' }) + class S3StorageTestClass { } + + const value = getMetadata(CLASS_S3CONFIGURATIONKEY, S3StorageTestClass) + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('bucketName', 'mybucketname') + expect(matadata).to.have.property('environmentKey', 'myenvkey') + expect(matadata).to.have.property('definedBy', S3StorageTestClass.name) + expect(matadata).to.not.have.property('eventSourceConfiguration') + }) + it("eventSourceConfiguration", () => { + @s3Storage({ + bucketName: 'mybucketname', + eventSourceConfiguration: { + Event: 'eventName', + Filter: {} + } + }) + class S3StorageTestClass { } + + const value = getMetadata(CLASS_S3CONFIGURATIONKEY, S3StorageTestClass) + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('bucketName', 'mybucketname') + expect(matadata).to.have.property('environmentKey', 'S3StorageTestClass_S3_BUCKET') + expect(matadata).to.have.property('definedBy', S3StorageTestClass.name) + expect(matadata).to.have.deep.property('eventSourceConfiguration').that.deep.equal({ + Event: 'eventName', + Filter: {} + }); + }) + }) + describe("sns", () => { + it("topicName", () => { + @sns({ topicName: 'myTopicName' }) + class SNSTestClass { } + + const value = getMetadata(CLASS_SNSCONFIGURATIONKEY, SNSTestClass) + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('topicName', 'myTopicName') + expect(matadata).to.have.property('environmentKey', 'SNSTestClass_SNS_TOPICNAME') + expect(matadata).to.have.property('definedBy', SNSTestClass.name) + }) + it("environmentKey", () => { + @sns({ topicName: 'myTopicName', environmentKey: 'myenvkey' }) + class SNSTestClass { } + + const value = getMetadata(CLASS_SNSCONFIGURATIONKEY, SNSTestClass) + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('topicName', 'myTopicName') + expect(matadata).to.have.property('environmentKey', 'myenvkey') + expect(matadata).to.have.property('definedBy', SNSTestClass.name) + }) + }) + describe("tag", () => { + it("key - value", () => { + @tag('key', 'value') + @tag('key2', 'value2') + class TagTestClass { } + + const matadata = getMetadata(CLASS_TAGKEY, TagTestClass) + + expect(matadata).to.have.all.keys('key', 'key2') + expect(matadata).to.have.property('key', 'value') + expect(matadata).to.have.property('key2', 'value2') + expect(matadata).to.not.have.property('key3') + }) + }) + describe("role", () => { + it("role", () => { + @role('rolename') + class RoleTestClass { } + + const roleValue = getMetadata(CLASS_ROLEKEY, RoleTestClass) + + expect(roleValue).to.equal('rolename') + }) + }) + describe("description", () => { + it("description", () => { + @description('desc...') + class DescriptionTestClass { } + + const descriptionValue = getMetadata(CLASS_DESCRIPTIONKEY, DescriptionTestClass) + + expect(descriptionValue).to.equal('desc...') + }) + }) + }) + + describe("parameters", () => { + describe("inject", () => { + it("inject", () => { + @injectable + class ATestClass { } + class BTestClass { + method( @inject(ATestClass) a) { } + } + + const value = getOwnMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('serviceType', ATestClass) + expect(matadata).to.have.property('parameterIndex', 0) + expect(matadata).to.have.property('type', 'inject') + }) + + it("functional service inject", () => { + @injectable + class ATestClass extends FunctionalService { } + class BTestClass { + method( @inject(ATestClass) a) { } + } + + const value = getOwnMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('serviceType', ATestClass) + expect(matadata).to.have.property('parameterIndex', 0) + expect(matadata).to.have.property('type', 'inject') + + const environmentMetadata = getMetadata(CLASS_ENVIRONMENTKEY, BTestClass) + expect(environmentMetadata).to.have + .property(`FUNCTIONAL_SERVICE_${ATestClass.name.toUpperCase()}`, getFunctionName(ATestClass)) + }) + + it("service inject", () => { + @injectable + @environment('%ClassName%_defined_environment', 'value') + class ATestClass extends Service { } + class BTestClass { + method( @inject(ATestClass) a) { } + } + + const value = getOwnMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('serviceType', ATestClass) + expect(matadata).to.have.property('parameterIndex', 0) + expect(matadata).to.have.property('type', 'inject') + + const environmentMetadata = getMetadata(CLASS_ENVIRONMENTKEY, BTestClass) + expect(environmentMetadata).to.have + .property(`ATestClass_defined_environment`, 'value') + }); + + it("injected CLASS_DYNAMOTABLECONFIGURATIONKEY", () => { + @injectable + @dynamoTable({ tableName: 'ATable' }) + class ATestClass extends Service { } + class BTestClass { + method( @inject(ATestClass) a) { } + } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, BTestClass) + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('tableName', 'ATable') + }) + + it("injected CLASS_SNSCONFIGURATIONKEY", () => { + @injectable + @sns({ topicName: 'ATopic' }) + class ATestClass extends Service { } + class BTestClass { + method( @inject(ATestClass) a) { } + } + + const value = getMetadata(CLASS_SNSCONFIGURATIONKEY, BTestClass) + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('topicName', 'ATopic') + }) + + it("injected CLASS_S3CONFIGURATIONKEY", () => { + @injectable + @s3Storage({ bucketName: 'ABucket' }) + class ATestClass extends Service { } + class BTestClass { + method( @inject(ATestClass) a) { } + } + + const value = getMetadata(CLASS_S3CONFIGURATIONKEY, BTestClass) + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('bucketName', 'abucket') + }) + }) + describe("param", () => { + it("inject", () => { + class ParamClass { + method( @param name) { } + } + + const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('from', 'name') + expect(matadata).to.have.property('parameterIndex', 0) + expect(matadata).to.have.property('type', 'param') + }) + it("inject custom name", () => { + class ParamClass { + method( @param('fullName') name) { } + } + + const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('from', 'fullName') + expect(matadata).to.have.property('parameterIndex', 0) + expect(matadata).to.have.property('type', 'param') + }) + it("inject param index", () => { + class ParamClass { + method( @param name, @param fullName) { } + } + + const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(2); + + const matadata1 = value[0] + + expect(matadata1).to.have.property('from', 'fullName') + expect(matadata1).to.have.property('parameterIndex', 1) + expect(matadata1).to.have.property('type', 'param') + + const matadata2 = value[1] + + expect(matadata2).to.have.property('from', 'name') + expect(matadata2).to.have.property('parameterIndex', 0) + expect(matadata2).to.have.property('type', 'param') + }) + }) + }) +}) \ No newline at end of file From ae3620f4af256997277ed9a5249a888c80cff29e Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 26 Jun 2017 14:50:07 +0200 Subject: [PATCH 023/196] ADD: param decorator config parameter --- src/annotations/parameters/param.ts | 6 ++++ src/helpers/property.ts | 11 ++++-- src/providers/aws/eventSources/eventSource.ts | 12 +++++++ src/providers/aws/eventSources/s3.ts | 4 +-- src/providers/aws/eventSources/sns.ts | 4 +-- src/providers/local.ts | 19 ++++++++--- test/annotation.tests.ts | 34 +++++++++++++++++++ 7 files changed, 79 insertions(+), 11 deletions(-) diff --git a/src/annotations/parameters/param.ts b/src/annotations/parameters/param.ts index 6b16df9..6812ec1 100644 --- a/src/annotations/parameters/param.ts +++ b/src/annotations/parameters/param.ts @@ -4,6 +4,7 @@ import { getFunctionParameters } from '../utils' export const param = (target: any, targetKey?: string, parameterIndex?: number): any => { let name; + let config = {}; let decorator = function (target, targetKey, parameterIndex: number) { let parameterNames = getFunctionParameters(target, targetKey); @@ -11,6 +12,7 @@ export const param = (target: any, targetKey?: string, parameterIndex?: number): let paramName = parameterNames[parameterIndex]; existingParameters.push({ + ...config, from: name || paramName, parameterIndex, type: 'param' @@ -22,6 +24,10 @@ export const param = (target: any, targetKey?: string, parameterIndex?: number): if (typeof target == "string" || typeof target == "undefined" || !target) { name = target; return decorator; + } else if (typeof target === 'object' && target && !targetKey) { + name = target.name; + config = target + return decorator } else { return decorator(target, targetKey, parameterIndex); } diff --git a/src/helpers/property.ts b/src/helpers/property.ts index 9ea7868..0648b96 100644 --- a/src/helpers/property.ts +++ b/src/helpers/property.ts @@ -1,5 +1,10 @@ export const get = (obj, path) => { - return path.split('.').reduce(function (prev, curr) { - return prev ? prev[curr] : undefined; - }, obj); + if (typeof path === 'function') { + return path(obj) + } + if (typeof path === 'string') { + return path.split('.').reduce(function (prev, curr) { + return prev ? prev[curr] : undefined; + }, obj) + } } \ No newline at end of file diff --git a/src/providers/aws/eventSources/eventSource.ts b/src/providers/aws/eventSources/eventSource.ts index e910e59..ecf5691 100644 --- a/src/providers/aws/eventSources/eventSource.ts +++ b/src/providers/aws/eventSources/eventSource.ts @@ -1,3 +1,5 @@ +import { get } from '../../../helpers/property' + export abstract class EventSource { public available(event: any) { return true @@ -11,4 +13,14 @@ export abstract class EventSource { if (err) throw err return result } + + protected getHolder(suggestedHolder, data, parameter) { + const source = parameter.source; + if (typeof source !== 'undefined') { + const holder = !source ? data : get(data, source) + return holder && get(holder, parameter.from) + } else { + return suggestedHolder + } + } } \ No newline at end of file diff --git a/src/providers/aws/eventSources/s3.ts b/src/providers/aws/eventSources/s3.ts index 33a73ec..9e0726a 100644 --- a/src/providers/aws/eventSources/s3.ts +++ b/src/providers/aws/eventSources/s3.ts @@ -8,11 +8,11 @@ export class S3 extends EventSource { } public async parameterResolver(parameter, event) { - const data = event.event.Records[0] + const holder = this.getHolder(event.event.Records[0], event.event, parameter) switch (parameter.type) { case 'param': - return get(data, parameter.from) + return get(holder, parameter.from) default: return await super.parameterResolver(parameter, event) } diff --git a/src/providers/aws/eventSources/sns.ts b/src/providers/aws/eventSources/sns.ts index 8cb2946..e3e7e4f 100644 --- a/src/providers/aws/eventSources/sns.ts +++ b/src/providers/aws/eventSources/sns.ts @@ -8,11 +8,11 @@ export class SNS extends EventSource { } public async parameterResolver(parameter, event) { - const data = event.event.Records[0] + const holder = this.getHolder(event.event.Records[0], event.event, parameter) switch (parameter.type) { case 'param': - return get(data, parameter.from) + return get(holder, parameter.from) default: return await super.parameterResolver(parameter, event) } diff --git a/src/providers/local.ts b/src/providers/local.ts index 9f0bf25..ea54dcc 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -1,7 +1,7 @@ import * as request from 'request' import { Provider } from './core/provider' import { constants, getOwnMetadata, getMetadata, getFunctionName } from '../annotations' - +import { get } from '../helpers/property' export class LocalProvider extends Provider { public getInvoker(serviceType, serviceInstance, params) { @@ -26,11 +26,22 @@ export class LocalProvider extends Provider { protected async parameterResolver(parameter, event) { const req = event.req + const source = parameter.source; switch (parameter.type) { case 'param': - if (req.body && req.body[parameter.from]) return req.body[parameter.from] - if (req.query && req.query[parameter.from]) return req.query[parameter.from] - if (req.params && req.params[parameter.from]) return req.params[parameter.from] + if (typeof source !== 'undefined') { + const holder = !source ? req : get(req, source) + if (holder) { + return get(holder, parameter.from) + } + } else { + let value = undefined + if (typeof (value = get(req.body, parameter.from)) !== 'undefined') return value + if (typeof (value = get(req.query, parameter.from)) !== 'undefined') return value + if (typeof (value = get(req.params, parameter.from)) !== 'undefined') return value + return value + } + return undefined default: return await super.parameterResolver(parameter, event) } diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index b549064..9858016 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -694,6 +694,40 @@ describe('annotations', () => { expect(matadata2).to.have.property('parameterIndex', 0) expect(matadata2).to.have.property('type', 'param') }) + it("inject with config", () => { + class ParamClass { + method( @param({ name: 'fullName', p1: 1, p2: 'p2' }) name) { } + } + + const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('from', 'fullName') + expect(matadata).to.have.property('parameterIndex', 0) + expect(matadata).to.have.property('type', 'param') + expect(matadata).to.have.property('p1', 1) + expect(matadata).to.have.property('p2', 'p2') + }) + it("inject with config without name", () => { + class ParamClass { + method( @param({ p1: 1, p2: 'p2' }) shortName) { } + } + + const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(1); + + const matadata = value[0] + + expect(matadata).to.have.property('from', 'shortName') + expect(matadata).to.have.property('parameterIndex', 0) + expect(matadata).to.have.property('type', 'param') + expect(matadata).to.have.property('p1', 1) + expect(matadata).to.have.property('p2', 'p2') + }) }) }) }) \ No newline at end of file From 3f1c48c16a5fcae59229e66b8d97f890e0ca440e Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 26 Jun 2017 16:35:24 +0200 Subject: [PATCH 024/196] ADD: local provider header param resolution --- src/providers/local.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/providers/local.ts b/src/providers/local.ts index ea54dcc..0509374 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -39,6 +39,7 @@ export class LocalProvider extends Provider { if (typeof (value = get(req.body, parameter.from)) !== 'undefined') return value if (typeof (value = get(req.query, parameter.from)) !== 'undefined') return value if (typeof (value = get(req.params, parameter.from)) !== 'undefined') return value + if (typeof (value = get(req.headers, parameter.from)) !== 'undefined') return value return value } return undefined From 03db8a242a7852518d2eb2224f6d0e991a4472fd Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 26 Jun 2017 16:36:12 +0200 Subject: [PATCH 025/196] ADD: aws api gateway event source parameter config --- src/providers/aws/eventSources/apiGateway.ts | 23 ++++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/providers/aws/eventSources/apiGateway.ts b/src/providers/aws/eventSources/apiGateway.ts index 189eeac..1845016 100644 --- a/src/providers/aws/eventSources/apiGateway.ts +++ b/src/providers/aws/eventSources/apiGateway.ts @@ -12,15 +12,24 @@ export class ApiGateway extends EventSource { const query = event.event.queryStringParameters const params = event.event.pathParameters const headers = event.event.headers - + switch (parameter.type) { case 'param': - let value - if (typeof (value = get(body, parameter.from)) !== 'undefined') return value - if (typeof (value = get(query, parameter.from)) !== 'undefined') return value - if (typeof (value = get(params, parameter.from)) !== 'undefined') return value - if (typeof (value = get(headers, parameter.from)) !== 'undefined') return value - return value; + const source = parameter.source; + if (typeof source !== 'undefined') { + const holder = !source ? event.event : get(event.event, source) + if (holder) { + return get(holder, parameter.from) + } + } else { + let value + if (typeof (value = get(body, parameter.from)) !== 'undefined') return value + if (typeof (value = get(query, parameter.from)) !== 'undefined') return value + if (typeof (value = get(params, parameter.from)) !== 'undefined') return value + if (typeof (value = get(headers, parameter.from)) !== 'undefined') return value + return value; + } + return undefined default: return await super.parameterResolver(parameter, event) } From eeaf2d2b6ef827297167bee46f9f095ff67dfb52 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 26 Jun 2017 16:37:51 +0200 Subject: [PATCH 026/196] ADD: local and aws invocation tests --- test/invoker.tests.ts | 691 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 691 insertions(+) create mode 100644 test/invoker.tests.ts diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts new file mode 100644 index 0000000..c26d319 --- /dev/null +++ b/test/invoker.tests.ts @@ -0,0 +1,691 @@ +import { expect } from 'chai' + +import { getInvoker } from '../src/providers' +import { FunctionalService, Service } from '../src/classes' +import { param, inject, injectable, event } from '../src/annotations' + + +describe('invoker', () => { + + describe("general", () => { + class MockService extends FunctionalService { } + + afterEach(() => { + delete process.env.FUNCTIONAL_ENVIRONMENT + }) + + it("Functional Service createInvoker", () => { + expect(MockService).to.has.property('createInvoker').that.to.be.a('function') + }) + + it("Service createInvoker", () => { + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + const invoker = MockService.createInvoker() + expect(invoker).to.be.a('function') + }) + }) + + describe("local", () => { + + afterEach(() => { + delete process.env.FUNCTIONAL_ENVIRONMENT + }) + + it("invoke", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + class MockService extends FunctionalService { + handle() { + counter++ + } + } + + const invoker = MockService.createInvoker() + invoker({}, { + send: () => { + expect(counter).to.equal(1) + done() + } + }, () => { }) + }) + + it("query param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('v1') + expect(p2).to.equal('v2') + } + } + + const invoker = MockService.createInvoker() + const req = { + query: { + p1: 'v1', + p2: 'v2' + } + } + invoker(req, { + send: () => { + expect(counter).to.equal(1) + done() + } + }, () => { }) + }) + + it("body param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('v1') + expect(p2).to.equal('v2') + } + } + + const invoker = MockService.createInvoker() + const req = { + body: { + p1: 'v1', + p2: 'v2' + } + } + invoker(req, { + send: () => { + expect(counter).to.equal(1) + done() + } + }, () => { }) + }) + + it("params param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('v1') + expect(p2).to.equal('v2') + } + } + + const invoker = MockService.createInvoker() + const req = { + params: { + p1: 'v1', + p2: 'v2' + } + } + invoker(req, { + send: () => { + expect(counter).to.equal(1) + done() + } + }, () => { }) + }) + + it("headers param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('v1') + expect(p2).to.equal('v2') + } + } + + const invoker = MockService.createInvoker() + const req = { + headers: { + p1: 'v1', + p2: 'v2' + } + } + invoker(req, { + send: () => { + expect(counter).to.equal(1) + done() + } + }, () => { }) + }) + + it("missing param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('v1') + expect(p2).to.undefined + } + } + + const invoker = MockService.createInvoker() + const req = { + params: { + p1: 'v1' + } + } + invoker(req, { + send: () => { + expect(counter).to.equal(1) + done() + } + }, () => { }) + }) + + it("params resolve order", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + class MockService extends FunctionalService { + handle( @param p1, @param p2, @param p3, @param p4) { + counter++ + expect(p1).to.equal('body') + expect(p2).to.equal('query') + expect(p3).to.equal('params') + expect(p4).to.equal('headers') + } + } + + const invoker = MockService.createInvoker() + const req = { + body: { + p1: 'body' + }, + query: { + p1: 'query', + p2: 'query' + }, + params: { + p1: 'params', + p2: 'params', + p3: 'params' + }, + headers: { + p1: 'headers', + p2: 'headers', + p3: 'headers', + p4: 'headers' + } + + } + invoker(req, { + send: () => { + expect(counter).to.equal(1) + done() + } + }, () => { }) + }) + + it("params resolve hint", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + class MockService extends FunctionalService { + handle( @param({ source: 'query' }) p1, @param({ source: 'params' }) p2, @param({ source: 'headers' }) p3, @param p4) { + counter++ + expect(p1).to.equal('query') + expect(p2).to.equal('params') + expect(p3).to.equal('headers') + expect(p4).to.equal('headers') + } + } + + const invoker = MockService.createInvoker() + const req = { + body: { + p1: 'body' + }, + query: { + p1: 'query', + p2: 'query' + }, + params: { + p1: 'params', + p2: 'params', + p3: 'params' + }, + headers: { + p1: 'headers', + p2: 'headers', + p3: 'headers', + p4: 'headers' + } + + } + invoker(req, { + send: () => { + expect(counter).to.equal(1) + done() + } + }, () => { }) + }) + + it("inject param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + @injectable + class MockInjectable extends Service { } + + class MockService extends FunctionalService { + handle( @param p1, @inject(MockInjectable) p2) { + counter++ + expect(p1).to.undefined + expect(p2).to.instanceof(Service) + expect(p2).to.instanceof(MockInjectable) + } + } + + const invoker = MockService.createInvoker() + invoker({}, { + send: () => { + expect(counter).to.equal(1) + done() + } + }, () => { }) + }) + + it("event param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + @injectable + class MockInjectable extends Service { } + + const req = {} + const res = { + send: () => { + expect(counter).to.equal(1) + done() + } + } + const next = () => { } + + class MockService extends FunctionalService { + handle( @param p1, @event p2) { + counter++ + expect(p1).to.undefined + expect(p2).to.have.property('req').that.to.equal(req) + expect(p2).to.have.property('res').that.to.equal(res) + expect(p2).to.have.property('next').that.to.equal(next) + } + } + + const invoker = MockService.createInvoker() + invoker(req, res, next) + }) + }) + + describe("aws", () => { + + afterEach(() => { + delete process.env.FUNCTIONAL_ENVIRONMENT + }) + + it("invoke", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + class MockService extends FunctionalService { + handle() { + counter++ + } + } + + const invoker = MockService.createInvoker() + invoker({}, {}, () => { + expect(counter).to.equal(1) + done() + }) + }) + + describe("eventSources", () => { + it("lambda param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('v1') + expect(p2).to.equal('v2') + } + } + + const invoker = MockService.createInvoker() + const awsEvent = { + p1: 'v1', + p2: 'v2' + } + invoker(awsEvent, {}, () => { + expect(counter).to.equal(1) + done() + }) + }) + + it("api gateway body param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('v1') + expect(p2).to.equal('v2') + } + } + + const invoker = MockService.createInvoker() + const awsEvent = { + requestContext: { apiId: 'apiId' }, + body: { + p1: 'v1', + p2: 'v2' + } + } + invoker(awsEvent, {}, () => { + expect(counter).to.equal(1) + done() + }) + }) + + it("api gateway query param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('v1') + expect(p2).to.equal('v2') + } + } + + const invoker = MockService.createInvoker() + const awsEvent = { + requestContext: { apiId: 'apiId' }, + queryStringParameters: { + p1: 'v1', + p2: 'v2' + } + } + invoker(awsEvent, {}, () => { + expect(counter).to.equal(1) + done() + }) + }) + + it("api gateway pathParameters param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('v1') + expect(p2).to.equal('v2') + } + } + + const invoker = MockService.createInvoker() + const awsEvent = { + requestContext: { apiId: 'apiId' }, + pathParameters: { + p1: 'v1', + p2: 'v2' + } + } + invoker(awsEvent, {}, () => { + expect(counter).to.equal(1) + done() + }) + }) + + it("api gateway header param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('v1') + expect(p2).to.equal('v2') + } + } + + const invoker = MockService.createInvoker() + const awsEvent = { + requestContext: { apiId: 'apiId' }, + headers: { + p1: 'v1', + p2: 'v2' + } + } + invoker(awsEvent, {}, () => { + expect(counter).to.equal(1) + done() + }) + }) + + it("api gateway params resolve order", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + class MockService extends FunctionalService { + handle( @param p1, @param p2, @param p3, @param p4) { + counter++ + expect(p1).to.equal('body') + expect(p2).to.equal('queryStringParameters') + expect(p3).to.equal('pathParameters') + expect(p4).to.equal('headers') + } + } + + const invoker = MockService.createInvoker() + const awsEvent = { + requestContext: { apiId: 'apiId' }, + body: { + p1: 'body' + }, + queryStringParameters: { + p1: 'queryStringParameters', + p2: 'queryStringParameters' + }, + pathParameters: { + p1: 'pathParameters', + p2: 'pathParameters', + p3: 'pathParameters' + }, + headers: { + p1: 'headers', + p2: 'headers', + p3: 'headers', + p4: 'headers' + } + } + invoker(awsEvent, {}, () => { + expect(counter).to.equal(1) + done() + }) + }) + + it("api gateway params resolve hint", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + class MockService extends FunctionalService { + handle( @param({ source: 'queryStringParameters' }) p1, @param({ source: 'pathParameters' }) p2, @param({ source: 'headers' }) p3, @param p4) { + counter++ + expect(p1).to.equal('queryStringParameters') + expect(p2).to.equal('pathParameters') + expect(p3).to.equal('headers') + expect(p4).to.equal('headers') + } + } + + const invoker = MockService.createInvoker() + const awsEvent = { + requestContext: { apiId: 'apiId' }, + body: { + p1: 'body' + }, + queryStringParameters: { + p1: 'queryStringParameters', + p2: 'queryStringParameters' + }, + pathParameters: { + p1: 'pathParameters', + p2: 'pathParameters', + p3: 'pathParameters' + }, + headers: { + p1: 'headers', + p2: 'headers', + p3: 'headers', + p4: 'headers' + } + } + invoker(awsEvent, {}, () => { + expect(counter).to.equal(1) + done() + }) + }) + + it("s3 param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + const awsEvent = { + Records: [{ + eventSource: 'aws:s3', + s3: { + "object": { + "key": "filename", + "size": 1234, + "eTag": "rnd", + "sequencer": "sssss" + } + } + }] + } + + class MockService extends FunctionalService { + handle( @param s3, @param('s3.object.key') p2) { + counter++ + expect(s3).to.deep.equal(awsEvent.Records[0].s3) + expect(p2).to.equal('filename') + } + } + + const invoker = MockService.createInvoker() + + invoker(awsEvent, {}, () => { + expect(counter).to.equal(1) + done() + }) + }) + + it("sns param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + const awsEvent = { + Records: [{ + EventSource: 'aws:sns', + Sns: { + "Type": "Notification", + "Subject": "subject", + "Message": "message" + } + }] + } + + class MockService extends FunctionalService { + handle( @param Sns, @param('Sns.Message') p2) { + counter++ + expect(Sns).to.deep.equal(awsEvent.Records[0].Sns) + expect(p2).to.equal('message') + } + } + + const invoker = MockService.createInvoker() + + invoker(awsEvent, {}, () => { + expect(counter).to.equal(1) + done() + }) + }) + }) + + it("inject param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + @injectable + class MockInjectable extends Service { } + + class MockService extends FunctionalService { + handle( @param p1, @inject(MockInjectable) p2) { + counter++ + expect(p1).to.undefined + expect(p2).to.instanceof(Service) + expect(p2).to.instanceof(MockInjectable) + } + } + + const invoker = MockService.createInvoker() + + invoker({}, {}, () => { + expect(counter).to.equal(1) + done() + }) + }) + + it("event param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + @injectable + class MockInjectable extends Service { } + + const awsEvent = { p1: 1, p2: "2" } + const awsContext = {} + const cb = () => { + expect(counter).to.equal(1) + done() + } + + class MockService extends FunctionalService { + handle( @param p1, @event p2) { + counter++ + expect(p1).to.undefined + expect(p2).to.have.property('event').that.to.equal(awsEvent) + expect(p2).to.have.property('context').that.to.equal(awsContext) + expect(p2).to.have.property('cb').that.to.equal(cb) + } + } + + const invoker = MockService.createInvoker() + invoker(awsEvent, awsContext, cb) + }) + }) +}) \ No newline at end of file From ccc8637c7b75ae5822fb27e4061dae30e138475a Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 27 Jun 2017 14:12:06 +0200 Subject: [PATCH 027/196] FIX: EventSource parameter getHolder --- src/providers/aws/eventSources/eventSource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/aws/eventSources/eventSource.ts b/src/providers/aws/eventSources/eventSource.ts index ecf5691..a7ce84b 100644 --- a/src/providers/aws/eventSources/eventSource.ts +++ b/src/providers/aws/eventSources/eventSource.ts @@ -18,7 +18,7 @@ export abstract class EventSource { const source = parameter.source; if (typeof source !== 'undefined') { const holder = !source ? data : get(data, source) - return holder && get(holder, parameter.from) + return holder } else { return suggestedHolder } From f2e93da993afaa13e0d4a57b1587c743722606b3 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 27 Jun 2017 14:13:27 +0200 Subject: [PATCH 028/196] ADD: event unit tests, return value tests; unit test fixes --- test/annotation.tests.ts | 234 ++++++------- test/invoker.tests.ts | 326 ++++++++++++++++-- test/serviceOnEvent.tests.ts | 634 +++++++++++++++++++++++++++++++++++ 3 files changed, 1043 insertions(+), 151 deletions(-) create mode 100644 test/serviceOnEvent.tests.ts diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index 9858016..64eb91e 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -124,12 +124,12 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('path', '/v1/test') - expect(matadata).to.have.property('method', 'get') - expect(matadata).to.have.property('cors', false) - expect(matadata).to.have.property('authorization', 'AWS_IAM') + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.property('method', 'get') + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('authorization', 'AWS_IAM') }) it("method", () => { @apiGateway({ path: '/v1/test', method: 'post' }) @@ -139,12 +139,12 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('path', '/v1/test') - expect(matadata).to.have.property('method', 'post') - expect(matadata).to.have.property('cors', false) - expect(matadata).to.have.property('authorization', 'AWS_IAM') + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.property('method', 'post') + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('authorization', 'AWS_IAM') }) it("cors", () => { @apiGateway({ path: '/v1/test', cors: true }) @@ -154,12 +154,12 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('path', '/v1/test') - expect(matadata).to.have.property('method', 'get') - expect(matadata).to.have.property('cors', true) - expect(matadata).to.have.property('authorization', 'AWS_IAM') + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.property('method', 'get') + expect(metadata).to.have.property('cors', true) + expect(metadata).to.have.property('authorization', 'AWS_IAM') }) it("authorization", () => { @apiGateway({ path: '/v1/test', authorization: 'NONE' }) @@ -169,12 +169,12 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('path', '/v1/test') - expect(matadata).to.have.property('method', 'get') - expect(matadata).to.have.property('cors', false) - expect(matadata).to.have.property('authorization', 'NONE') + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.property('method', 'get') + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('authorization', 'NONE') }) }) describe("dynamoTable", () => { @@ -186,12 +186,12 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('tableName', 'mytablename') - expect(matadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') - expect(matadata).to.have.property('definedBy', DynamoTableTestClass.name) - expect(matadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); + expect(metadata).to.have.property('tableName', 'mytablename') + expect(metadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') + expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); }) it("environmentKey", () => { @dynamoTable({ tableName: 'mytablename', environmentKey: 'myenvkey' }) @@ -201,12 +201,12 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('tableName', 'mytablename') - expect(matadata).to.have.property('environmentKey', 'myenvkey') - expect(matadata).to.have.property('definedBy', DynamoTableTestClass.name) - expect(matadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); + expect(metadata).to.have.property('tableName', 'mytablename') + expect(metadata).to.have.property('environmentKey', 'myenvkey') + expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); }) it("nativeConfig", () => { @dynamoTable({ @@ -223,12 +223,12 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('tableName', 'mytablename') - expect(matadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') - expect(matadata).to.have.property('definedBy', DynamoTableTestClass.name) - expect(matadata).to.have.deep.property('nativeConfig').that.deep.equal({ + expect(metadata).to.have.property('tableName', 'mytablename') + expect(metadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') + expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal({ ...__dynamoDBDefaults, ProvisionedThroughput: { ReadCapacityUnits: 4, @@ -243,12 +243,12 @@ describe('annotations', () => { @environment('key2', 'value2') class EnvironmentTestClass { } - const matadata = getMetadata(CLASS_ENVIRONMENTKEY, EnvironmentTestClass) + const metadata = getMetadata(CLASS_ENVIRONMENTKEY, EnvironmentTestClass) - expect(matadata).to.have.all.keys('key', 'key2') - expect(matadata).to.have.property('key', 'value') - expect(matadata).to.have.property('key2', 'value2') - expect(matadata).to.not.have.property('key3') + expect(metadata).to.have.all.keys('key', 'key2') + expect(metadata).to.have.property('key', 'value') + expect(metadata).to.have.property('key2', 'value2') + expect(metadata).to.not.have.property('key3') }) it("template", () => { @environment('%ClassName%_env', 'value') @@ -256,12 +256,12 @@ describe('annotations', () => { @environment('%ClassName%_env2', '%ClassName%_value2') class EnvironmentTestClass { } - const matadata = getMetadata(CLASS_ENVIRONMENTKEY, EnvironmentTestClass) + const metadata = getMetadata(CLASS_ENVIRONMENTKEY, EnvironmentTestClass) - expect(matadata).to.have.all.keys('EnvironmentTestClass_env', 'key', 'EnvironmentTestClass_env2') - expect(matadata).to.have.property('EnvironmentTestClass_env', 'value') - expect(matadata).to.have.property('key', 'EnvironmentTestClass_value') - expect(matadata).to.have.property('EnvironmentTestClass_env2', 'EnvironmentTestClass_value2') + expect(metadata).to.have.all.keys('EnvironmentTestClass_env', 'key', 'EnvironmentTestClass_env2') + expect(metadata).to.have.property('EnvironmentTestClass_env', 'value') + expect(metadata).to.have.property('key', 'EnvironmentTestClass_value') + expect(metadata).to.have.property('EnvironmentTestClass_env2', 'EnvironmentTestClass_value2') }) }) describe("functionName", () => { @@ -397,12 +397,12 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('bucketName', 'mybucketname') - expect(matadata).to.have.property('environmentKey', 'S3StorageTestClass_S3_BUCKET') - expect(matadata).to.have.property('definedBy', S3StorageTestClass.name) - expect(matadata).to.not.have.property('eventSourceConfiguration') + expect(metadata).to.have.property('bucketName', 'mybucketname') + expect(metadata).to.have.property('environmentKey', 'S3StorageTestClass_S3_BUCKET') + expect(metadata).to.have.property('definedBy', S3StorageTestClass.name) + expect(metadata).to.not.have.property('eventSourceConfiguration') }) it("bucketName LowerCase", () => { @s3Storage({ bucketName: 'myBucketName' }) @@ -412,13 +412,13 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('bucketName').that.not.equal('myBucketName') - expect(matadata).to.have.property('bucketName', 'mybucketname') - expect(matadata).to.have.property('environmentKey', 'S3StorageTestClass_S3_BUCKET') - expect(matadata).to.have.property('definedBy', S3StorageTestClass.name) - expect(matadata).to.not.have.property('eventSourceConfiguration') + expect(metadata).to.have.property('bucketName').that.not.equal('myBucketName') + expect(metadata).to.have.property('bucketName', 'mybucketname') + expect(metadata).to.have.property('environmentKey', 'S3StorageTestClass_S3_BUCKET') + expect(metadata).to.have.property('definedBy', S3StorageTestClass.name) + expect(metadata).to.not.have.property('eventSourceConfiguration') }) it("environmentKey", () => { @s3Storage({ bucketName: 'mybucketname', environmentKey: 'myenvkey' }) @@ -428,12 +428,12 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('bucketName', 'mybucketname') - expect(matadata).to.have.property('environmentKey', 'myenvkey') - expect(matadata).to.have.property('definedBy', S3StorageTestClass.name) - expect(matadata).to.not.have.property('eventSourceConfiguration') + expect(metadata).to.have.property('bucketName', 'mybucketname') + expect(metadata).to.have.property('environmentKey', 'myenvkey') + expect(metadata).to.have.property('definedBy', S3StorageTestClass.name) + expect(metadata).to.not.have.property('eventSourceConfiguration') }) it("eventSourceConfiguration", () => { @s3Storage({ @@ -449,12 +449,12 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('bucketName', 'mybucketname') - expect(matadata).to.have.property('environmentKey', 'S3StorageTestClass_S3_BUCKET') - expect(matadata).to.have.property('definedBy', S3StorageTestClass.name) - expect(matadata).to.have.deep.property('eventSourceConfiguration').that.deep.equal({ + expect(metadata).to.have.property('bucketName', 'mybucketname') + expect(metadata).to.have.property('environmentKey', 'S3StorageTestClass_S3_BUCKET') + expect(metadata).to.have.property('definedBy', S3StorageTestClass.name) + expect(metadata).to.have.deep.property('eventSourceConfiguration').that.deep.equal({ Event: 'eventName', Filter: {} }); @@ -469,11 +469,11 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('topicName', 'myTopicName') - expect(matadata).to.have.property('environmentKey', 'SNSTestClass_SNS_TOPICNAME') - expect(matadata).to.have.property('definedBy', SNSTestClass.name) + expect(metadata).to.have.property('topicName', 'myTopicName') + expect(metadata).to.have.property('environmentKey', 'SNSTestClass_SNS_TOPICNAME') + expect(metadata).to.have.property('definedBy', SNSTestClass.name) }) it("environmentKey", () => { @sns({ topicName: 'myTopicName', environmentKey: 'myenvkey' }) @@ -483,11 +483,11 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('topicName', 'myTopicName') - expect(matadata).to.have.property('environmentKey', 'myenvkey') - expect(matadata).to.have.property('definedBy', SNSTestClass.name) + expect(metadata).to.have.property('topicName', 'myTopicName') + expect(metadata).to.have.property('environmentKey', 'myenvkey') + expect(metadata).to.have.property('definedBy', SNSTestClass.name) }) }) describe("tag", () => { @@ -496,12 +496,12 @@ describe('annotations', () => { @tag('key2', 'value2') class TagTestClass { } - const matadata = getMetadata(CLASS_TAGKEY, TagTestClass) + const metadata = getMetadata(CLASS_TAGKEY, TagTestClass) - expect(matadata).to.have.all.keys('key', 'key2') - expect(matadata).to.have.property('key', 'value') - expect(matadata).to.have.property('key2', 'value2') - expect(matadata).to.not.have.property('key3') + expect(metadata).to.have.all.keys('key', 'key2') + expect(metadata).to.have.property('key', 'value') + expect(metadata).to.have.property('key2', 'value2') + expect(metadata).to.not.have.property('key3') }) }) describe("role", () => { @@ -539,11 +539,11 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('serviceType', ATestClass) - expect(matadata).to.have.property('parameterIndex', 0) - expect(matadata).to.have.property('type', 'inject') + expect(metadata).to.have.property('serviceType', ATestClass) + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'inject') }) it("functional service inject", () => { @@ -557,11 +557,11 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('serviceType', ATestClass) - expect(matadata).to.have.property('parameterIndex', 0) - expect(matadata).to.have.property('type', 'inject') + expect(metadata).to.have.property('serviceType', ATestClass) + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'inject') const environmentMetadata = getMetadata(CLASS_ENVIRONMENTKEY, BTestClass) expect(environmentMetadata).to.have @@ -580,11 +580,11 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('serviceType', ATestClass) - expect(matadata).to.have.property('parameterIndex', 0) - expect(matadata).to.have.property('type', 'inject') + expect(metadata).to.have.property('serviceType', ATestClass) + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'inject') const environmentMetadata = getMetadata(CLASS_ENVIRONMENTKEY, BTestClass) expect(environmentMetadata).to.have @@ -603,9 +603,9 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('tableName', 'ATable') + expect(metadata).to.have.property('tableName', 'ATable') }) it("injected CLASS_SNSCONFIGURATIONKEY", () => { @@ -620,9 +620,9 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('topicName', 'ATopic') + expect(metadata).to.have.property('topicName', 'ATopic') }) it("injected CLASS_S3CONFIGURATIONKEY", () => { @@ -637,9 +637,9 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('bucketName', 'abucket') + expect(metadata).to.have.property('bucketName', 'abucket') }) }) describe("param", () => { @@ -652,11 +652,11 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('from', 'name') - expect(matadata).to.have.property('parameterIndex', 0) - expect(matadata).to.have.property('type', 'param') + expect(metadata).to.have.property('from', 'name') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'param') }) it("inject custom name", () => { class ParamClass { @@ -667,11 +667,11 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('from', 'fullName') - expect(matadata).to.have.property('parameterIndex', 0) - expect(matadata).to.have.property('type', 'param') + expect(metadata).to.have.property('from', 'fullName') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'param') }) it("inject param index", () => { class ParamClass { @@ -703,13 +703,13 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('from', 'fullName') - expect(matadata).to.have.property('parameterIndex', 0) - expect(matadata).to.have.property('type', 'param') - expect(matadata).to.have.property('p1', 1) - expect(matadata).to.have.property('p2', 'p2') + expect(metadata).to.have.property('from', 'fullName') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'param') + expect(metadata).to.have.property('p1', 1) + expect(metadata).to.have.property('p2', 'p2') }) it("inject with config without name", () => { class ParamClass { @@ -720,13 +720,13 @@ describe('annotations', () => { expect(value).to.have.lengthOf(1); - const matadata = value[0] + const metadata = value[0] - expect(matadata).to.have.property('from', 'shortName') - expect(matadata).to.have.property('parameterIndex', 0) - expect(matadata).to.have.property('type', 'param') - expect(matadata).to.have.property('p1', 1) - expect(matadata).to.have.property('p2', 'p2') + expect(metadata).to.have.property('from', 'shortName') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'param') + expect(metadata).to.have.property('p1', 1) + expect(metadata).to.have.property('p2', 'p2') }) }) }) diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index c26d319..580345c 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -48,7 +48,52 @@ describe('invoker', () => { expect(counter).to.equal(1) done() } - }, () => { }) + }, (e) => { e && done(e) }) + }) + + it("return value", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + class MockService extends FunctionalService { + handle() { + counter++ + return { ok: 1 } + } + } + + const invoker = MockService.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(1) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + }) + + it("handler throw error", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + class MockService extends FunctionalService { + handle() { + counter++ + throw new Error('error in handle') + } + } + + const invoker = MockService.createInvoker() + invoker({}, { + send: (result) => { + expect(false).to.equal(true, 'skippable code') + done() + } + }, (e) => { + expect(counter).to.equal(1) + expect(e.message).to.equal('error in handle') + done() + }) }) it("query param", (done) => { @@ -75,7 +120,7 @@ describe('invoker', () => { expect(counter).to.equal(1) done() } - }, () => { }) + }, (e) => { e && done(e) }) }) it("body param", (done) => { @@ -102,7 +147,7 @@ describe('invoker', () => { expect(counter).to.equal(1) done() } - }, () => { }) + }, (e) => { e && done(e) }) }) it("params param", (done) => { @@ -129,7 +174,7 @@ describe('invoker', () => { expect(counter).to.equal(1) done() } - }, () => { }) + }, (e) => { e && done(e) }) }) it("headers param", (done) => { @@ -156,7 +201,7 @@ describe('invoker', () => { expect(counter).to.equal(1) done() } - }, () => { }) + }, (e) => { e && done(e) }) }) it("missing param", (done) => { @@ -182,7 +227,7 @@ describe('invoker', () => { expect(counter).to.equal(1) done() } - }, () => { }) + }, (e) => { e && done(e) }) }) it("params resolve order", (done) => { @@ -226,7 +271,7 @@ describe('invoker', () => { expect(counter).to.equal(1) done() } - }, () => { }) + }, (e) => { e && done(e) }) }) it("params resolve hint", (done) => { @@ -270,7 +315,7 @@ describe('invoker', () => { expect(counter).to.equal(1) done() } - }, () => { }) + }, (e) => { e && done(e) }) }) it("inject param", (done) => { @@ -296,7 +341,7 @@ describe('invoker', () => { expect(counter).to.equal(1) done() } - }, () => { }) + }, (e) => { e && done(e) }) }) it("event param", (done) => { @@ -314,7 +359,7 @@ describe('invoker', () => { done() } } - const next = () => { } + const next = (e) => { e && done(e) } class MockService extends FunctionalService { handle( @param p1, @event p2) { @@ -348,8 +393,46 @@ describe('invoker', () => { } const invoker = MockService.createInvoker() - invoker({}, {}, () => { + invoker({}, {}, (e) => { + expect(counter).to.equal(1) + done(e) + }) + }) + + it("return value", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + class MockService extends FunctionalService { + handle() { + counter++ + return { ok: 1 } + } + } + + const invoker = MockService.createInvoker() + invoker({}, {}, (e, result) => { + expect(counter).to.equal(1) + expect(result).to.deep.equal({ ok: 1 }) + done(e) + }) + }) + + it("handler throw error", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + class MockService extends FunctionalService { + handle() { + counter++ + throw new Error('error in handle') + } + } + + const invoker = MockService.createInvoker() + invoker({}, {}, (e, result) => { expect(counter).to.equal(1) + expect(e.message).to.equal('error in handle') done() }) }) @@ -372,9 +455,9 @@ describe('invoker', () => { p1: 'v1', p2: 'v2' } - invoker(awsEvent, {}, () => { + invoker(awsEvent, {}, (e) => { expect(counter).to.equal(1) - done() + done(e) }) }) @@ -398,9 +481,9 @@ describe('invoker', () => { p2: 'v2' } } - invoker(awsEvent, {}, () => { + invoker(awsEvent, {}, (e) => { expect(counter).to.equal(1) - done() + done(e) }) }) @@ -424,9 +507,9 @@ describe('invoker', () => { p2: 'v2' } } - invoker(awsEvent, {}, () => { + invoker(awsEvent, {}, (e) => { expect(counter).to.equal(1) - done() + done(e) }) }) @@ -450,9 +533,9 @@ describe('invoker', () => { p2: 'v2' } } - invoker(awsEvent, {}, () => { + invoker(awsEvent, {}, (e) => { expect(counter).to.equal(1) - done() + done(e) }) }) @@ -476,9 +559,9 @@ describe('invoker', () => { p2: 'v2' } } - invoker(awsEvent, {}, () => { + invoker(awsEvent, {}, (e) => { expect(counter).to.equal(1) - done() + done(e) }) }) @@ -518,9 +601,9 @@ describe('invoker', () => { p4: 'headers' } } - invoker(awsEvent, {}, () => { + invoker(awsEvent, {}, (e) => { expect(counter).to.equal(1) - done() + done(e) }) }) @@ -560,9 +643,115 @@ describe('invoker', () => { p4: 'headers' } } - invoker(awsEvent, {}, () => { + invoker(awsEvent, {}, (e) => { expect(counter).to.equal(1) - done() + done(e) + }) + }) + + it("api gateway return value", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + class MockService extends FunctionalService { + handle() { + counter++ + return { ok: 1 } + } + } + + const invoker = MockService.createInvoker() + const awsEvent = { + requestContext: { apiId: 'apiId' }, + } + invoker(awsEvent, {}, (e, result) => { + expect(counter).to.equal(1) + expect(result).to.deep.equal({ + statusCode: 200, + body: '{"ok":1}' + }) + done(e) + }) + }) + + it("api gateway return value advanced", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + class MockService extends FunctionalService { + handle() { + counter++ + return { + statusCode: 200, + body: 'myresult' + } + } + } + + const invoker = MockService.createInvoker() + const awsEvent = { + requestContext: { apiId: 'apiId' }, + } + invoker(awsEvent, {}, (e, result) => { + expect(counter).to.equal(1) + expect(result).to.deep.equal({ + statusCode: 200, + body: 'myresult' + }) + done(e) + }) + }) + + it("api gateway return value advanced - error", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + class MockService extends FunctionalService { + handle() { + counter++ + return { + statusCode: 500, + body: 'myerror' + } + } + } + + const invoker = MockService.createInvoker() + const awsEvent = { + requestContext: { apiId: 'apiId' }, + } + invoker(awsEvent, {}, (e, result) => { + expect(counter).to.equal(1) + expect(result).to.deep.equal({ + statusCode: 500, + body: 'myerror' + }) + done(e) + }) + }) + + it("api gateway return value advanced - throw error", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + class MockService extends FunctionalService { + handle() { + counter++ + throw new Error('error in handle') + } + } + + const invoker = MockService.createInvoker() + const awsEvent = { + requestContext: { apiId: 'apiId' }, + } + invoker(awsEvent, {}, (e, result) => { + expect(counter).to.equal(1) + expect(result).to.deep.equal({ + statusCode: 500, + body: JSON.stringify(new Error('error in handle')) + }) + done(e) }) }) @@ -595,9 +784,45 @@ describe('invoker', () => { const invoker = MockService.createInvoker() - invoker(awsEvent, {}, () => { + invoker(awsEvent, {}, (e) => { expect(counter).to.equal(1) - done() + done(e) + }) + }) + + it("s3 param source", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + const awsEvent = { + Records: [{ + eventSource: 'aws:s3', + s3: { + "object": { + "key": "filename", + "size": 1234, + "eTag": "rnd", + "sequencer": "sssss" + } + } + }] + } + + class MockService extends FunctionalService { + handle( @param s3, @param({ name: 'Records', source: null }) p2) { + counter++ + expect(s3).to.deep.equal(awsEvent.Records[0].s3) + expect(p2).to.have.lengthOf(1); + expect(p2).to.deep.equal(awsEvent.Records) + } + } + + const invoker = MockService.createInvoker() + + invoker(awsEvent, {}, (e) => { + expect(counter).to.equal(1) + done(e) }) }) @@ -627,9 +852,42 @@ describe('invoker', () => { const invoker = MockService.createInvoker() - invoker(awsEvent, {}, () => { + invoker(awsEvent, {}, (e) => { expect(counter).to.equal(1) - done() + done(e) + }) + }) + + it("sns param source", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + const awsEvent = { + Records: [{ + EventSource: 'aws:sns', + Sns: { + "Type": "Notification", + "Subject": "subject", + "Message": "message" + } + }] + } + + class MockService extends FunctionalService { + handle( @param Sns, @param({ name: 'Records', source: null }) p2) { + counter++ + expect(Sns).to.deep.equal(awsEvent.Records[0].Sns) + expect(p2).to.have.lengthOf(1); + expect(p2).to.deep.equal(awsEvent.Records) + } + } + + const invoker = MockService.createInvoker() + + invoker(awsEvent, {}, (e) => { + expect(counter).to.equal(1) + done(e) }) }) }) @@ -653,9 +911,9 @@ describe('invoker', () => { const invoker = MockService.createInvoker() - invoker({}, {}, () => { + invoker({}, {}, (e) => { expect(counter).to.equal(1) - done() + done(e) }) }) @@ -667,11 +925,11 @@ describe('invoker', () => { @injectable class MockInjectable extends Service { } - const awsEvent = { p1: 1, p2: "2" } + const awsEvent = {} const awsContext = {} - const cb = () => { + const cb = (e) => { expect(counter).to.equal(1) - done() + done(e) } class MockService extends FunctionalService { diff --git a/test/serviceOnEvent.tests.ts b/test/serviceOnEvent.tests.ts new file mode 100644 index 0000000..a1c819c --- /dev/null +++ b/test/serviceOnEvent.tests.ts @@ -0,0 +1,634 @@ +import { expect } from 'chai' + +import { FunctionalService, Service } from '../src/classes' +import { param, inject, injectable, event } from '../src/annotations' +import { addProvider, removeProvider, LocalProvider } from '../src/providers' + +import { PARAMETER_PARAMKEY } from '../src/annotations/constants' +import { getMetadata, getOwnMetadata } from '../src/annotations/metadata' + +describe('service events', () => { + afterEach(() => { + delete process.env.FUNCTIONAL_ENVIRONMENT + }) + + describe("local", () => { + describe("onInject", () => { + it("static onInject", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + @injectable + class MockInjectable extends Service { + public static async onInject({ parameter }) { + counter++ + + const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + expect(value).to.have.lengthOf(1); + const metadata = value[0] + + expect(metadata).to.deep.equal(parameter) + } + } + + class MockService extends FunctionalService { + handle( @inject(MockInjectable) p1) { + counter++ + expect(p1).to.instanceof(Service) + expect(p1).to.instanceof(MockInjectable) + } + } + + const invoker = MockService.createInvoker() + invoker({}, { + send: () => { + expect(counter).to.equal(2) + done() + } + }, (e) => { e && done(e) }) + }) + + it("static onInject return", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + @injectable + class MockInjectable extends Service { + public static async onInject({ parameter }) { + counter++ + + const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + expect(value).to.have.lengthOf(1); + const metadata = value[0] + + expect(metadata).to.deep.equal(parameter) + + return { mockRetrurn: 1 } + } + } + + class MockService extends FunctionalService { + handle( @inject(MockInjectable) p1) { + counter++ + expect(p1).to.not.instanceof(Service) + expect(p1).to.not.instanceof(MockInjectable) + expect(p1).to.deep.equal({ mockRetrurn: 1 }) + } + } + + const invoker = MockService.createInvoker() + invoker({}, { + send: () => { + expect(counter).to.equal(2) + done() + } + }, (e) => { e && done(e) }) + }) + + it("onInject", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + @injectable + class MockInjectable extends Service { + public async onInject({ parameter }) { + counter++ + + const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + expect(value).to.have.lengthOf(1); + const metadata = value[0] + + expect(metadata).to.deep.equal(parameter) + } + } + + class MockService extends FunctionalService { + handle( @inject(MockInjectable) p1) { + counter++ + expect(p1).to.instanceof(Service) + expect(p1).to.instanceof(MockInjectable) + } + } + + const invoker = MockService.createInvoker() + invoker({}, { + send: () => { + expect(counter).to.equal(2) + done() + } + }, (e) => { e && done(e) }) + }) + + it("static onInject - onInject both called", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + @injectable + class MockInjectable extends Service { + public static async onInject({ parameter }) { + counter++ + + const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + expect(value).to.have.lengthOf(1); + const metadata = value[0] + + expect(metadata).to.deep.equal(parameter) + } + public async onInject({ parameter }) { + counter++ + + const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + expect(value).to.have.lengthOf(1); + const metadata = value[0] + + expect(metadata).to.deep.equal(parameter) + } + } + + class MockService extends FunctionalService { + handle( @inject(MockInjectable) p1) { + counter++ + expect(p1).to.instanceof(Service) + expect(p1).to.instanceof(MockInjectable) + } + } + + const invoker = MockService.createInvoker() + invoker({}, { + send: () => { + expect(counter).to.equal(3) + done() + } + }, (e) => { e && done(e) }) + }) + + it("static onInject return - onInject skipped", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + @injectable + class MockInjectable extends Service { + public static async onInject({ parameter }) { + counter++ + + const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + expect(value).to.have.lengthOf(1); + const metadata = value[0] + + expect(metadata).to.deep.equal(parameter) + + return { mockRetrurn: 1 } + } + public async onInject({ parameter }) { + counter++ + expect(false).to.equal(true, 'skippable code') + } + } + + class MockService extends FunctionalService { + handle( @inject(MockInjectable) p1) { + counter++ + expect(p1).to.not.instanceof(Service) + expect(p1).to.not.instanceof(MockInjectable) + expect(p1).to.deep.equal({ mockRetrurn: 1 }) + } + } + + const invoker = MockService.createInvoker() + invoker({}, { + send: () => { + expect(counter).to.equal(2) + done() + } + }, (e) => { e && done(e) }) + }) + }) + + describe("onHandle", () => { + it("onHandle call", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + + return { ok: 1 } + } + + public async onHandle_local(req, res, next) { + counter++ + + expect(req).to.deep.equal({ body: { p1: 1, p2: 2 } }) + } + } + + const invoker = MockService.createInvoker() + + const req = { + body: { p1: 1, p2: 2 } + } + invoker(req, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + }) + + it("onHandle call return", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + expect(false).to.equal(true, 'skippable code') + + return { ok: 1 } + } + + public async onHandle_local(req, res, next) { + counter++ + + expect(req).to.deep.equal({ body: { p1: 1, p2: 2 } }) + + res.send({ ok: 2 }) + return true + } + } + + const invoker = MockService.createInvoker() + + const req = { + body: { p1: 1, p2: 2 } + } + invoker(req, { + send: (result) => { + expect(counter).to.equal(1) + expect(result).to.deep.equal({ ok: 2 }) + done() + } + }, (e) => { e && done(e) }) + }) + }) + }) + + describe("aws", () => { + describe("onInject", () => { + it("static onInject", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + @injectable + class MockInjectable extends Service { + public static async onInject({ parameter }) { + counter++ + + const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + expect(value).to.have.lengthOf(1); + const metadata = value[0] + + expect(metadata).to.deep.equal(parameter) + } + } + + class MockService extends FunctionalService { + handle( @inject(MockInjectable) p1) { + counter++ + expect(p1).to.instanceof(Service) + expect(p1).to.instanceof(MockInjectable) + } + } + + const invoker = MockService.createInvoker() + invoker({}, {}, (e) => { + expect(counter).to.equal(2) + done(e) + }) + }) + + it("static onInject return", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + @injectable + class MockInjectable extends Service { + public static async onInject({ parameter }) { + counter++ + + const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + expect(value).to.have.lengthOf(1); + const metadata = value[0] + + expect(metadata).to.deep.equal(parameter) + + return { mockRetrurn: 1 } + } + } + + class MockService extends FunctionalService { + handle( @inject(MockInjectable) p1) { + counter++ + expect(p1).to.not.instanceof(Service) + expect(p1).to.not.instanceof(MockInjectable) + expect(p1).to.deep.equal({ mockRetrurn: 1 }) + } + } + + const invoker = MockService.createInvoker() + invoker({}, {}, (e) => { + expect(counter).to.equal(2) + done(e) + }) + }) + + it("onInject", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + @injectable + class MockInjectable extends Service { + public async onInject({ parameter }) { + counter++ + + const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + expect(value).to.have.lengthOf(1); + const metadata = value[0] + + expect(metadata).to.deep.equal(parameter) + } + } + + class MockService extends FunctionalService { + handle( @inject(MockInjectable) p1) { + counter++ + expect(p1).to.instanceof(Service) + expect(p1).to.instanceof(MockInjectable) + } + } + + const invoker = MockService.createInvoker() + invoker({}, {}, (e) => { + expect(counter).to.equal(2) + done(e) + }) + }) + + it("static onInject - onInject both called", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + @injectable + class MockInjectable extends Service { + public static async onInject({ parameter }) { + counter++ + + const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + expect(value).to.have.lengthOf(1); + const metadata = value[0] + + expect(metadata).to.deep.equal(parameter) + } + public async onInject({ parameter }) { + counter++ + + const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + expect(value).to.have.lengthOf(1); + const metadata = value[0] + + expect(metadata).to.deep.equal(parameter) + } + } + + class MockService extends FunctionalService { + handle( @inject(MockInjectable) p1) { + counter++ + expect(p1).to.instanceof(Service) + expect(p1).to.instanceof(MockInjectable) + } + } + + const invoker = MockService.createInvoker() + invoker({}, {}, (e) => { + expect(counter).to.equal(3) + done(e) + }) + }) + + it("static onInject return - onInject skipped", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + @injectable + class MockInjectable extends Service { + public static async onInject({ parameter }) { + counter++ + + const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + expect(value).to.have.lengthOf(1); + const metadata = value[0] + + expect(metadata).to.deep.equal(parameter) + + return { mockRetrurn: 1 } + } + public async onInject({ parameter }) { + counter++ + expect(false).to.equal(true, 'skippable code') + } + } + + class MockService extends FunctionalService { + handle( @inject(MockInjectable) p1) { + counter++ + expect(p1).to.not.instanceof(Service) + expect(p1).to.not.instanceof(MockInjectable) + expect(p1).to.deep.equal({ mockRetrurn: 1 }) + } + } + + const invoker = MockService.createInvoker() + invoker({}, {}, (e) => { + expect(counter).to.equal(2) + done(e) + }) + }) + }) + + describe("onHandle", () => { + it("onHandle call", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + + return { ok: 1 } + } + + public async onHandle_aws(event, context, cb) { + counter++ + + expect(event).to.deep.equal({ p1: 1, p2: 2 }) + } + } + + const invoker = MockService.createInvoker() + + const awsEvent = { + p1: 1, + p2: 2 + } + invoker(awsEvent, {}, (e, result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done(e) + }) + }) + + it("onHandle call return", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + expect(false).to.equal(true, 'skippable code') + + return { ok: 1 } + } + + public async onHandle_aws(event, context, cb) { + counter++ + + expect(event).to.deep.equal({ p1: 1, p2: 2 }) + + cb(null, { ok: 2 }) + return true + } + } + + const invoker = MockService.createInvoker() + + const awsEvent = { + p1: 1, + p2: 2 + } + invoker(awsEvent, {}, (e, result) => { + expect(counter).to.equal(1) + expect(result).to.deep.equal({ ok: 2 }) + done(e) + }) + }) + }) + }) + + + describe("mock", () => { + class MockProvider extends LocalProvider { + public async invoke(serviceInstance, params, invokeConfig?): Promise { } + } + before(() => { + addProvider('mock', new MockProvider()) + }) + after(() => { + removeProvider('mock') + }) + + + describe("onInvoke", () => { + it("onInvoke", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'mock' + + @injectable + class MockInjectable extends FunctionalService { + onInvoke({ params, invokeConfig }) { + counter++ + + expect(params).to.deep.equal({ p1: 1, p2: 2 }) + expect(invokeConfig).to.deep.equal({ config: 1 }) + } + } + + class MockService extends FunctionalService { + async handle( @inject(MockInjectable) p1) { + counter++ + expect(p1).to.instanceof(Service) + expect(p1).to.instanceof(MockInjectable) + + await p1.invoke({ p1: 1, p2: 2 }, { config: 1 }) + } + } + + const invoker = MockService.createInvoker() + invoker({}, { + send: () => { + expect(counter).to.equal(2) + done() + } + }, (e) => { e && done(e) }) + }) + it("onInvoke mock", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'mock' + + @injectable + class MockInjectable extends FunctionalService { + handle( @param p1) { } + onInvoke_mock({ invokeParams, params, invokeConfig, parameterMapping, currentEnvironment, environmentMode }) { + counter++ + + expect(invokeParams).to.deep.equal({ p1: 1, p2: 2 }) + expect(params).to.deep.equal({ p1: 1 }) + expect(parameterMapping).to.have.lengthOf(1); + expect(parameterMapping[0]).to.deep.equal({ + from: 'p1', + parameterIndex: 0, + type: 'param' + }) + expect(currentEnvironment).to.instanceof(MockProvider) + expect(environmentMode).to.equal('mock') + } + } + + class MockService extends FunctionalService { + async handle( @inject(MockInjectable) p1) { + counter++ + expect(p1).to.instanceof(Service) + expect(p1).to.instanceof(MockInjectable) + + await p1.invoke({ p1: 1, p2: 2 }, { config: 1 }) + } + } + + const invoker = MockService.createInvoker() + invoker({}, { + send: () => { + expect(counter).to.equal(2) + done() + } + }, (e) => { e && done(e) }) + }) + }) + }) +}) \ No newline at end of file From 9da7a171351121bfcb9252f95236fade0c0e335f Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Wed, 28 Jun 2017 16:35:15 +0200 Subject: [PATCH 029/196] ADD: Stage support --- src/cli/commands/deploy.ts | 6 +++- src/cli/commands/package.ts | 6 +++- src/cli/commands/serverless.ts | 7 ++-- .../cloudFormation/context/apiGateway.ts | 7 ++-- .../cloudFormation/context/resources.ts | 32 +++++++++++++------ .../cloudFormation/context/s3Storage.ts | 9 +++--- .../providers/cloudFormation/context/sns.ts | 4 +-- .../providers/cloudFormation/context/stack.ts | 5 +-- .../cloudFormation/context/uploadTemplate.ts | 14 +++++--- src/cli/providers/cloudFormation/index.ts | 8 ++++- src/cli/utilities/aws/cloudFormation.ts | 11 ++++--- src/cli/utilities/aws/s3Upload.ts | 16 ++++++---- 12 files changed, 85 insertions(+), 40 deletions(-) diff --git a/src/cli/commands/deploy.ts b/src/cli/commands/deploy.ts index 61b8e9f..ede6c84 100644 --- a/src/cli/commands/deploy.ts +++ b/src/cli/commands/deploy.ts @@ -6,6 +6,7 @@ export default ({ createContext, executor, ExecuteStep, projectConfig, requireVa .description('deploy functional services') .option('--aws-region ', 'AWS_REGION') .option('--aws-bucket ', 'aws bucket') + .option('--stage ', 'stage') .action(async (target, path, command) => { process.env.FUNCTIONAL_ENVIRONMENT = 'deploy' @@ -14,12 +15,15 @@ export default ({ createContext, executor, ExecuteStep, projectConfig, requireVa const deployTarget = requireValue(target || projectConfig.deployTarget, 'missing deploy target') const awsRegion = requireValue(command.awsRegion || projectConfig.awsRegion, 'awsRegion') const awsBucket = command.awsBucket || projectConfig.awsBucket + const stage = command.stage || projectConfig.stage || 'dev' const context = await createContext(entryPoint, { deployTarget, awsRegion, awsBucket, - version: projectConfig.version + version: projectConfig.version, + projectName: projectConfig.name, + stage }) await executor(context, ExecuteStep.get('CreateEnvironment')) diff --git a/src/cli/commands/package.ts b/src/cli/commands/package.ts index c6d4156..80bc0f0 100644 --- a/src/cli/commands/package.ts +++ b/src/cli/commands/package.ts @@ -6,6 +6,7 @@ export default ({ createContext, executor, ExecuteStep, projectConfig, requireVa .description('package functional services') .option('--aws-region ', 'AWS_REGION') .option('--aws-bucket ', 'aws bucket') + .option('--stage ', 'stage') .action(async (target, path, command) => { process.env.FUNCTIONAL_ENVIRONMENT = 'deploy' @@ -14,13 +15,16 @@ export default ({ createContext, executor, ExecuteStep, projectConfig, requireVa const deployTarget = requireValue(target || projectConfig.deployTarget, 'missing deploy target') const awsRegion = requireValue(command.awsRegion || projectConfig.awsRegion, 'awsRegion') const awsBucket = command.awsBucket || projectConfig.awsBucket + const stage = command.stage || projectConfig.stage || 'dev' const context = await createContext(entryPoint, { deployTarget, awsRegion, awsBucket, version: projectConfig.version, - packageOnly: true + projectName: projectConfig.name, + packageOnly: true, + stage }) await executor(context, ExecuteStep.get('CreateEnvironment')) diff --git a/src/cli/commands/serverless.ts b/src/cli/commands/serverless.ts index 548d5e7..15febdb 100644 --- a/src/cli/commands/serverless.ts +++ b/src/cli/commands/serverless.ts @@ -31,7 +31,7 @@ export default (api) => { context.serverless.provider = { name: FUNCTIONAL_ENVIRONMENT, region: context.awsRegion, - stage: 'dev', + stage: context.stage, environment: {} } await executor({ context, name: 'iamRoleConfig', method: iamRoleConfig }) @@ -192,6 +192,7 @@ export default (api) => { .command('serverless [path]') .alias('sls') .option('--aws-region ', 'AWS_REGION') + .option('--stage ', 'stage') .description('serverless config') .action(async (path, command) => { process.env.FUNCTIONAL_ENVIRONMENT = 'deploy' @@ -199,11 +200,13 @@ export default (api) => { try { const entryPoint = requireValue(path || projectConfig.main, 'entry point') const awsRegion = requireValue(command.awsRegion || projectConfig.awsRegion, 'awsRegion') + const stage = command.stage || projectConfig.stage || 'dev' const context = await createContext(entryPoint, { deployTarget: FUNCTIONAL_ENVIRONMENT, awsRegion, - FUNCTIONAL_ENVIRONMENT + FUNCTIONAL_ENVIRONMENT, + stage }) await executor(context, ExecuteStep.get('SetFunctionalEnvironment')) diff --git a/src/cli/providers/cloudFormation/context/apiGateway.ts b/src/cli/providers/cloudFormation/context/apiGateway.ts index cf61fe3..68b3f76 100644 --- a/src/cli/providers/cloudFormation/context/apiGateway.ts +++ b/src/cli/providers/cloudFormation/context/apiGateway.ts @@ -16,7 +16,7 @@ export const gatewayRestApi = ExecuteStep.register('ApiGateway-RestApi', async ( const RestApi = { "Type": "AWS::ApiGateway::RestApi", "Properties": { - "Name": context.CloudFormationConfig.StackName + "Name": `${context.CloudFormationConfig.StackName}-${context.stage}` } } @@ -42,7 +42,8 @@ export const gatewayRestApi = ExecuteStep.register('ApiGateway-RestApi', async ( { "Ref": resourceName }, - ".execute-api.eu-central-1.amazonaws.com/dev" + ".execute-api.eu-central-1.amazonaws.com/", + context.stage ] ] } @@ -242,7 +243,7 @@ export const gatewayDeployment = ExecuteStep.register('ApiGateway-Deployment', a "RestApiId": { "Ref": API_GATEWAY_REST_API }, - "StageName": "dev" + "StageName": context.stage }, "DependsOn": [ ...[...lambdaDependsOn, ...stackDependsOn].filter((v, i, self) => self.indexOf(v) === i) diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 48d6603..16ba285 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -57,7 +57,7 @@ export const roleResource = async (context) => { const { roleName, serviceDefinitions } = context const roleProperties = { - "RoleName": roleName, + "RoleName": `${roleName}-${context.stage}`, "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [{ @@ -97,7 +97,7 @@ export const roleResource = async (context) => { } - const roleResourceName = `IAM${roleProperties.RoleName}` + const roleResourceName = `IAM${roleName}` const resourceName = setResource(context, roleResourceName, iamRole) await setStackParameter({ @@ -115,6 +115,7 @@ export const roleResource = async (context) => { "Ref": `${resourceName}Arn` } serviceDefinition.roleResource = iamRole + serviceDefinition.roleName = roleName } } } @@ -259,26 +260,26 @@ export const tableResource = async (context) => { const properties = { ...__dynamoDBDefaults, - TableName: tableConfig.tableName, + TableName: `${tableConfig.tableName}-${context.stage}`, ...tableConfig.nativeConfig }; - tableConfig.tableName = properties.TableName const dynamoDb = { "Type": "AWS::DynamoDB::Table", "Properties": properties } - const tableResourceName = `Dynamo${properties.TableName}` + const tableResourceName = `Dynamo${tableConfig.tableName}` const resourceName = setResource(context, tableResourceName, dynamoDb, DYNAMODB_TABLE_STACK) - + await setStackParameter({ ...context, resourceName, sourceStackName: DYNAMODB_TABLE_STACK }) + tableConfig.tableName = properties.TableName tableConfig.resourceName = resourceName } @@ -309,7 +310,7 @@ export const lambdaResource = async (context) => { S3Key: context.S3Zip }, Description: serviceDefinition[CLASS_DESCRIPTIONKEY] || getMetadata(CLASS_DESCRIPTIONKEY, serviceDefinition.service), - FunctionName: getFunctionName(serviceDefinition.service), + FunctionName: `${getFunctionName(serviceDefinition.service)}-${context.stage}`, Handler: serviceDefinition.handler, MemorySize: serviceDefinition[CLASS_MEMORYSIZEKEY] || getMetadata(CLASS_MEMORYSIZEKEY, serviceDefinition.service), Role: serviceDefinition[CLASS_ROLEKEY] || getMetadata(CLASS_ROLEKEY, serviceDefinition.service), @@ -321,6 +322,8 @@ export const lambdaResource = async (context) => { Tags: serviceDefinition[CLASS_TAGKEY] || getMetadata(CLASS_TAGKEY, serviceDefinition.service) }; + updateEnvironmentVariable({ ...context, environments: properties.Environment.Variables }) + const lambdaResource = { "Type": "AWS::Lambda::Function", "Properties": properties, @@ -329,7 +332,7 @@ export const lambdaResource = async (context) => { ] } - const resourceName = `Lambda${properties.FunctionName}` + const resourceName = `Lambda${getFunctionName(serviceDefinition.service)}` const name = setResource(context, resourceName, lambdaResource, getStackName(serviceDefinition)) serviceDefinition.resourceName = name } @@ -366,7 +369,7 @@ export const lambdaLogResource = async (context) => { const functionName = getFunctionName(serviceDefinition.service) const properties: any = { - "LogGroupName": `/aws/lambda/${functionName}` + "LogGroupName": `/aws/lambda/${functionName}-${context.stage}` }; const lambdaResource = { @@ -377,4 +380,15 @@ export const lambdaLogResource = async (context) => { const resourceName = `${functionName}LogGroup` const name = setResource(context, resourceName, lambdaResource, getStackName(serviceDefinition)) serviceDefinition.logGroupResourceName = name +} + +const UPDATEABLE_ENVIRONMENT_REGEXP = /^FUNCTIONAL_SERVICE_|_TABLE_NAME$|_S3_BUCKET$|_SNS_TOPICNAME$/ +const updateEnvironmentVariable = async ({ environments, stage }) => { + if (environments) { + for (const key in environments) { + if (UPDATEABLE_ENVIRONMENT_REGEXP.test(key)) { + environments[key] = `${environments[key]}-${stage}` + } + } + } } \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/context/s3Storage.ts b/src/cli/providers/cloudFormation/context/s3Storage.ts index b1a48e0..f8327bf 100644 --- a/src/cli/providers/cloudFormation/context/s3Storage.ts +++ b/src/cli/providers/cloudFormation/context/s3Storage.ts @@ -77,7 +77,7 @@ export const s3Storage = async (context) => { const { s3Config } = context const s3Properties = { - "BucketName": s3Config.bucketName + "BucketName": `${s3Config.bucketName}-${context.stage}` } const s3Bucket = { @@ -115,7 +115,7 @@ export const s3StoragePolicy = async (context) => { "-", [ "functionly", - serviceDefinition.roleResource.Properties.RoleName, + serviceDefinition.roleName, "s3" ] ] @@ -135,6 +135,7 @@ export const s3StoragePolicy = async (context) => { }] } } + serviceDefinition.roleResource.Properties.Policies = serviceDefinition.roleResource.Properties.Policies || [] serviceDefinition.roleResource.Properties.Policies.push(policy) } @@ -143,7 +144,7 @@ export const s3StoragePolicy = async (context) => { "", [ "arn:aws:s3:::", - s3Config.bucketName, + `${s3Config.bucketName}-${context.stage}`, "/*" ] ] @@ -213,7 +214,7 @@ export const s3Permissions = (context) => { "Principal": "s3.amazonaws.com", "SourceArn": { "Fn::Join": [":", [ - "arn", "aws", "s3", "", "", serviceConfig.bucketName]] + "arn", "aws", "s3", "", "", `${serviceConfig.bucketName}-${context.stage}`]] } } diff --git a/src/cli/providers/cloudFormation/context/sns.ts b/src/cli/providers/cloudFormation/context/sns.ts index 9159496..ec54abb 100644 --- a/src/cli/providers/cloudFormation/context/sns.ts +++ b/src/cli/providers/cloudFormation/context/sns.ts @@ -40,7 +40,7 @@ export const snsTopic = async (context) => { snsConfig.advTopicName = `${snsConfig.topicName}${context.date.valueOf()}` const snsProperties = { - "TopicName": snsConfig.advTopicName + "TopicName": `${snsConfig.advTopicName}-${context.stage}` } const snsTopic = { @@ -135,7 +135,7 @@ const updateSNSEnvironmentVariables = (context) => { for (const { serviceDefinition, serviceConfig } of snsConfig.services) { const environmentVariables = getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) || {} - environmentVariables[serviceConfig.environmentKey] = snsConfig.advTopicName + environmentVariables[serviceConfig.environmentKey] = `${snsConfig.advTopicName}-${context.stage}` setStackParameter({ ...context, diff --git a/src/cli/providers/cloudFormation/context/stack.ts b/src/cli/providers/cloudFormation/context/stack.ts index 718760e..cbaaf3e 100644 --- a/src/cli/providers/cloudFormation/context/stack.ts +++ b/src/cli/providers/cloudFormation/context/stack.ts @@ -24,9 +24,10 @@ export const createStack = async (context) => { [ "https://s3.amazonaws.com", awsBucket, - `functionly`, + context.projectName || `functionly`, + context.stage, folderPah, - `${stackName}.template` + `${stackName}.template.json` ] ] } diff --git a/src/cli/providers/cloudFormation/context/uploadTemplate.ts b/src/cli/providers/cloudFormation/context/uploadTemplate.ts index fd361e8..f19e260 100644 --- a/src/cli/providers/cloudFormation/context/uploadTemplate.ts +++ b/src/cli/providers/cloudFormation/context/uploadTemplate.ts @@ -1,24 +1,30 @@ -import { uploaderStep } from '../../../utilities/aws/s3Upload' +import { uploaderStep, writeFile } from '../../../utilities/aws/s3Upload' import { ExecuteStep, executor } from '../../../context' import { projectConfig } from '../../../project/config' +export const persistCreateTemplate = ExecuteStep.register('PersistCreateTemplate', async (context) => { + const templateData = JSON.stringify(context.CloudFormationTemplate, null, 2); + const fileName = projectConfig.name ? `${projectConfig.name}.create.template.json` : 'cloudformation.create.template.json' + writeFile(fileName, new Buffer(templateData, 'binary')) +}) + export const uploadTemplate = ExecuteStep.register('UploadTemplate', async (context) => { for (const stackName in context.CloudFormationStacks) { const stack = context.CloudFormationStacks[stackName] - if(!Object.keys(stack.Resources).length){ + if (!Object.keys(stack.Resources).length) { delete context.CloudFormationStacks[stackName] delete context.CloudFormationTemplate.Resources[stackName] } } const templateData = JSON.stringify(context.CloudFormationTemplate, null, 2); - const fileName = projectConfig.name ? `${projectConfig.name}.template` : 'cloudformation.template' + const fileName = projectConfig.name ? `${projectConfig.name}.template.json` : 'cloudformation.template.json' const uploadresult = await executor(context, uploaderStep(fileName, templateData, 'application/octet-stream')) context.S3CloudFormationTemplate = uploadresult.Key for (const stackName in context.CloudFormationStacks) { const templateData = JSON.stringify(context.CloudFormationStacks[stackName], null, 2); - const templateFileName = `${stackName}.template` + const templateFileName = `${stackName}.template.json` const uploadresult = await executor(context, uploaderStep(templateFileName, templateData, 'application/octet-stream')) } diff --git a/src/cli/providers/cloudFormation/index.ts b/src/cli/providers/cloudFormation/index.ts index 65b31c3..e06cb1d 100644 --- a/src/cli/providers/cloudFormation/index.ts +++ b/src/cli/providers/cloudFormation/index.ts @@ -12,7 +12,7 @@ import { tableResources, lambdaResources, roleResources, s3DeploymentBucket, s3DeploymentBucketParameter, apiGateway, sns, s3, initStacks, lambdaLogResources, S3_DEPLOYMENT_BUCKET_RESOURCE_NAME } from './context/resources' -import { uploadTemplate } from './context/uploadTemplate' +import { uploadTemplate, persistCreateTemplate } from './context/uploadTemplate' export const cloudFormation = { FUNCTIONAL_ENVIRONMENT: 'aws', @@ -24,6 +24,9 @@ export const cloudFormation = { await executor(context, cloudFormationInit) await executor(context, s3DeploymentBucket) + logger.info(`Functionly: Save create template...`) + await executor(context, persistCreateTemplate) + try { await executor(context, getTemplate) } catch (e) { @@ -70,6 +73,9 @@ export const cloudFormation = { await executor(context, cloudFormationInit) await executor(context, s3DeploymentBucket) + logger.info(`Functionly: Save create template...`) + await executor(context, persistCreateTemplate) + logger.info(`Functionly: Save binary...`) const fileName = projectConfig.name ? `${projectConfig.name}.zip` : 'project.zip' await executor({ ...context, skipUpload: true }, uploadZipStep(fileName, context.zipData())) diff --git a/src/cli/utilities/aws/cloudFormation.ts b/src/cli/utilities/aws/cloudFormation.ts index 00aa1a4..04231d9 100644 --- a/src/cli/utilities/aws/cloudFormation.ts +++ b/src/cli/utilities/aws/cloudFormation.ts @@ -30,6 +30,7 @@ export const createStack = ExecuteStep.register('CloudFormation-CreateStack', (c const cfConfig: any = pick(context.CloudFormationConfig, CREATE_STACK_PRPERTIES) const params = { ...cfConfig, + StackName: `${cfConfig.StackName}-${context.stage}`, TemplateBody: JSON.stringify(context.CloudFormationTemplate, null, 2) } @@ -53,6 +54,7 @@ export const updateStack = ExecuteStep.register('CloudFormation-UpdateStack', (c const cfConfig: any = pick(context.CloudFormationConfig, UPDATE_STACK_PRPERTIES) const params = { ...cfConfig, + StackName: `${cfConfig.StackName}-${context.stage}`, TemplateURL: `https://s3.eu-central-1.amazonaws.com/${context.awsBucket}/${context.S3CloudFormationTemplate}`, UsePreviousTemplate: false } @@ -75,7 +77,7 @@ export const getTemplate = ExecuteStep.register('CloudFormation-GetTemplate', (c initAWSSDK(context) return new Promise((resolve, reject) => { let params = { - StackName: context.CloudFormationConfig.StackName + StackName: `${context.CloudFormationConfig.StackName}-${context.stage}` } cloudFormation.getTemplate(params, function (err, data) { @@ -89,7 +91,7 @@ export const describeStackResouce = ExecuteStep.register('CloudFormation-Describ initAWSSDK(context) return new Promise((resolve, reject) => { let params = { - StackName: context.CloudFormationConfig.StackName, + StackName: `${context.CloudFormationConfig.StackName}-${context.stage}`, LogicalResourceId: context.LogicalResourceId } @@ -103,13 +105,14 @@ export const describeStackResouce = ExecuteStep.register('CloudFormation-Describ export const describeStacks = ExecuteStep.register('CloudFormation-DescribeStacks', (context) => { initAWSSDK(context) return new Promise((resolve, reject) => { + const stackName = `${context.CloudFormationConfig.StackName}-${context.stage}` let params = { - StackName: context.CloudFormationConfig.StackName + StackName: stackName } cloudFormation.describeStacks(params, function (err, data) { if (err) return reject(err) - const result = data.Stacks.find(s => s.StackName === context.CloudFormationConfig.StackName) + const result = data.Stacks.find(s => s.StackName === stackName) return resolve(result); }); }) diff --git a/src/cli/utilities/aws/s3Upload.ts b/src/cli/utilities/aws/s3Upload.ts index c60246b..3399863 100644 --- a/src/cli/utilities/aws/s3Upload.ts +++ b/src/cli/utilities/aws/s3Upload.ts @@ -50,25 +50,27 @@ export const uploadToAws = ExecuteStep.register('S3-Upload', async (context) => ...config.S3, Bucket: context.awsBucket, Body: binary, - Key: `functionly/${folderPah}/${context.upload.name}`, + Key: `${context.projectName || 'functionly'}/${context.stage}/${folderPah}/${context.upload.name}`, ContentType: context.upload.contentType } if (context.skipUpload) { - if (config.tempDirectory) { - writeFileSync(join(config.tempDirectory, context.upload.name), binary) - } + writeFile(context.upload.name, binary) return resolve(params) } s3.putObject(params, (err, res) => { if (err) return reject(err) - if (config.tempDirectory) { - writeFileSync(join(config.tempDirectory, context.upload.name), binary) - } + writeFile(context.upload.name, binary) return resolve(params) }) }) }) + +export const writeFile = (fileName, binary) => { + if (config.tempDirectory) { + writeFileSync(join(config.tempDirectory, fileName), binary) + } +} From fe317ff5901a3de4d99d0f8fb073c3837254b53f Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Thu, 29 Jun 2017 12:23:35 +0200 Subject: [PATCH 030/196] REFACTOR: service inject --- src/annotations/classes/dynamoTable.ts | 6 ++--- src/annotations/classes/s3Storage.ts | 4 +-- src/annotations/classes/sns.ts | 4 ++- src/annotations/parameters/inject.ts | 26 +++---------------- src/classes/externals/dynamoDB.ts | 15 +++++++---- src/classes/externals/s3Storage.ts | 12 +++++---- src/classes/externals/sns.ts | 8 +++--- src/classes/functionalService.ts | 11 ++++++++ src/classes/index.ts | 1 + src/classes/injectService.ts | 19 ++++++++++++++ src/classes/service.ts | 16 +++++++++++- src/cli/context/steppes/tableDiscovery.ts | 3 ++- .../cloudFormation/context/resources.ts | 3 ++- test/annotation.tests.ts | 14 +++++----- 14 files changed, 90 insertions(+), 52 deletions(-) create mode 100644 src/classes/injectService.ts diff --git a/src/annotations/classes/dynamoTable.ts b/src/annotations/classes/dynamoTable.ts index e77652b..4d6167f 100644 --- a/src/annotations/classes/dynamoTable.ts +++ b/src/annotations/classes/dynamoTable.ts @@ -3,6 +3,8 @@ import { getMetadata, defineMetadata } from '../metadata' import { applyTemplates } from '../templates' import { environment } from './environment' +export const DYNAMO_TABLE_NAME_SUFFIX = '_TABLE_NAME' + export const __dynamoDBDefaults = { AttributeDefinitions: [ { @@ -22,8 +24,6 @@ export const __dynamoDBDefaults = { } } - - export const dynamoTable = (tableConfig: { tableName: string, environmentKey?: string, @@ -31,7 +31,7 @@ export const dynamoTable = (tableConfig: { }) => (target: Function) => { let tableDefinitions = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, target) || []; - tableConfig.environmentKey = tableConfig.environmentKey || '%ClassName%_TABLE_NAME' + tableConfig.environmentKey = tableConfig.environmentKey || `%ClassName%${DYNAMO_TABLE_NAME_SUFFIX}` tableConfig.nativeConfig = { ...__dynamoDBDefaults, ...tableConfig.nativeConfig } const { templatedKey, templatedValue } = applyTemplates(tableConfig.environmentKey, tableConfig.tableName, target) diff --git a/src/annotations/classes/s3Storage.ts b/src/annotations/classes/s3Storage.ts index 850a2d8..83c58be 100644 --- a/src/annotations/classes/s3Storage.ts +++ b/src/annotations/classes/s3Storage.ts @@ -3,7 +3,7 @@ import { getMetadata, defineMetadata } from '../metadata' import { applyTemplates } from '../templates' import { environment } from './environment' -export const S3_BUCKET_PREFIX = '_S3_BUCKET' +export const S3_BUCKET_SUFFIX = '_S3_BUCKET' export const s3Storage = (s3Config: { bucketName: string, @@ -15,7 +15,7 @@ export const s3Storage = (s3Config: { }) => (target: Function) => { let s3Definitions = getMetadata(CLASS_S3CONFIGURATIONKEY, target) || []; - s3Config.environmentKey = s3Config.environmentKey || `%ClassName%${S3_BUCKET_PREFIX}` + s3Config.environmentKey = s3Config.environmentKey || `%ClassName%${S3_BUCKET_SUFFIX}` const { templatedKey, templatedValue } = applyTemplates(s3Config.environmentKey, s3Config.bucketName, target) const lowerCaseTemplatedValue = templatedValue ? templatedValue.toLowerCase() : templatedValue diff --git a/src/annotations/classes/sns.ts b/src/annotations/classes/sns.ts index a2bb65a..74e99a1 100644 --- a/src/annotations/classes/sns.ts +++ b/src/annotations/classes/sns.ts @@ -3,13 +3,15 @@ import { getMetadata, defineMetadata } from '../metadata' import { applyTemplates } from '../templates' import { environment } from './environment' +export const SNS_TOPICNAME_SUFFIX = '_SNS_TOPICNAME' + export const sns = (snsConfig: { topicName: string, environmentKey?: string }) => (target: Function) => { let snsDefinitions = getMetadata(CLASS_SNSCONFIGURATIONKEY, target) || []; - snsConfig.environmentKey = snsConfig.environmentKey || '%ClassName%_SNS_TOPICNAME' + snsConfig.environmentKey = snsConfig.environmentKey || `%ClassName%${SNS_TOPICNAME_SUFFIX}` const { templatedKey, templatedValue } = applyTemplates(snsConfig.environmentKey, snsConfig.topicName, target) snsDefinitions.push({ diff --git a/src/annotations/parameters/inject.ts b/src/annotations/parameters/inject.ts index 900d21f..692992f 100644 --- a/src/annotations/parameters/inject.ts +++ b/src/annotations/parameters/inject.ts @@ -1,6 +1,5 @@ -import { PARAMETER_PARAMKEY, CLASS_ENVIRONMENTKEY, CLASS_INJECTABLEKEY, CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_SNSCONFIGURATIONKEY, CLASS_S3CONFIGURATIONKEY } from '../constants' +import { PARAMETER_PARAMKEY, CLASS_ENVIRONMENTKEY, CLASS_INJECTABLEKEY } from '../constants' import { getOwnMetadata, defineMetadata, getMetadata } from '../metadata' -import { getFunctionParameters } from '../utils' import { getFunctionName } from '../classes/functionName' export const inject = (type: any, ...params): any => { @@ -20,27 +19,8 @@ export const inject = (type: any, ...params): any => { defineMetadata(PARAMETER_PARAMKEY, [...existingParameters], target, targetKey); - const injectTarget = type - const metadata = getMetadata(CLASS_ENVIRONMENTKEY, target) || {} - if (injectTarget.createInvoker) { - const funcName = getFunctionName(injectTarget) || 'undefined' - metadata[`FUNCTIONAL_SERVICE_${funcName.toUpperCase()}`] = funcName - defineMetadata(CLASS_ENVIRONMENTKEY, { ...metadata }, target); - } else { - const injectMetadata = getMetadata(CLASS_ENVIRONMENTKEY, injectTarget) || {} - if (injectMetadata) { - Object.keys(injectMetadata).forEach((key) => { - metadata[key] = injectMetadata[key] - }) - defineMetadata(CLASS_ENVIRONMENTKEY, { ...metadata }, target); - } + if (typeof type.onDefineInjectTo === 'function') { + type.onDefineInjectTo(target, targetKey, parameterIndex) } - - [CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_SNSCONFIGURATIONKEY, CLASS_S3CONFIGURATIONKEY].forEach(key => { - const injectKeyConfig = (getMetadata(key, injectTarget) || []) - .map(c => { return { ...c, injected: true } }) - const keyConfig = getMetadata(key, target) || [] - defineMetadata(key, [...keyConfig, ...injectKeyConfig], target); - }) } } diff --git a/src/classes/externals/dynamoDB.ts b/src/classes/externals/dynamoDB.ts index 5a98819..adf850c 100644 --- a/src/classes/externals/dynamoDB.ts +++ b/src/classes/externals/dynamoDB.ts @@ -1,8 +1,11 @@ import * as AWS from 'aws-sdk' import { DocumentClient } from 'aws-sdk/lib/dynamodb/document_client' -import { Service } from '../service' +import { InjectService } from '../injectService' import { constants, getMetadata } from '../../annotations' +import { DYNAMO_TABLE_NAME_SUFFIX } from '../../annotations/classes/dynamoTable' + +const { CLASS_DYNAMOTABLECONFIGURATIONKEY } = constants let dynamoDB = null; const initAWSSDK = () => { @@ -26,9 +29,11 @@ const initAWSSDK = () => { return dynamoDB } -export class DynamoDB extends Service { +export class DynamoDB extends InjectService { + public static ConfigEnvironmentKey = CLASS_DYNAMOTABLECONFIGURATIONKEY + private _documentClient: DocumentClient - constructor() { + public constructor() { initAWSSDK() super() @@ -109,10 +114,10 @@ export class DynamoDB extends Service { } protected setDefaultValues(params, command) { - const tableConfig = (getMetadata(constants.CLASS_DYNAMOTABLECONFIGURATIONKEY, this) || [])[0] + const tableConfig = (getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, this) || [])[0] const tableName = ({ TableName: tableConfig && tableConfig.tableName, ...tableConfig.nativeConfig }).TableName const initParams = { - TableName: process.env[`${this.constructor.name}_TABLE_NAME`] || tableName + TableName: process.env[`${this.constructor.name}${DYNAMO_TABLE_NAME_SUFFIX}`] || tableName } return { ...initParams, ...params } diff --git a/src/classes/externals/s3Storage.ts b/src/classes/externals/s3Storage.ts index 5e79c3c..0f20b92 100644 --- a/src/classes/externals/s3Storage.ts +++ b/src/classes/externals/s3Storage.ts @@ -1,7 +1,7 @@ import { S3 } from 'aws-sdk' -import { Service } from '../service' +import { InjectService } from '../injectService' import { constants, getMetadata } from '../../annotations' -import { S3_BUCKET_PREFIX } from '../../annotations/classes/s3Storage' +import { S3_BUCKET_SUFFIX } from '../../annotations/classes/s3Storage' const { CLASS_S3CONFIGURATIONKEY } = constants export { S3 } from 'aws-sdk' @@ -28,9 +28,11 @@ const initAWSSDK = () => { return s3 } -export class S3Storage extends Service { +export class S3Storage extends InjectService { + public static ConfigEnvironmentKey = CLASS_S3CONFIGURATIONKEY + private _s3Client: S3 - constructor() { + public constructor() { initAWSSDK() super() @@ -58,7 +60,7 @@ export class S3Storage extends Service { protected setDefaultValues(params, command) { const initParams = { - Bucket: process.env[`${this.constructor.name}${S3_BUCKET_PREFIX}`] + Bucket: process.env[`${this.constructor.name}${S3_BUCKET_SUFFIX}`] } return { ...initParams, ...params } diff --git a/src/classes/externals/sns.ts b/src/classes/externals/sns.ts index 308e4fa..23e13fe 100644 --- a/src/classes/externals/sns.ts +++ b/src/classes/externals/sns.ts @@ -1,7 +1,7 @@ import { SNS } from 'aws-sdk' export { SNS } from 'aws-sdk' -import { Service } from '../service' +import { InjectService } from '../injectService' import { constants, getMetadata } from '../../annotations' const { CLASS_SNSCONFIGURATIONKEY } = constants @@ -27,9 +27,11 @@ const initAWSSDK = () => { return sns } -export class SimpleNotificationService extends Service { +export class SimpleNotificationService extends InjectService { + public static ConfigEnvironmentKey = CLASS_SNSCONFIGURATIONKEY + private _snsClient: SNS - constructor() { + public constructor() { initAWSSDK() super() diff --git a/src/classes/functionalService.ts b/src/classes/functionalService.ts index e70d756..d2052ce 100644 --- a/src/classes/functionalService.ts +++ b/src/classes/functionalService.ts @@ -1,5 +1,9 @@ import { Service } from './service' import { getInvoker, invoke } from '../providers' +import { defineMetadata, getMetadata, constants, getFunctionName } from '../annotations' +const { CLASS_ENVIRONMENTKEY } = constants + +export const FUNCTIONAL_SERVICE_PREFIX = 'FUNCTIONAL_SERVICE_' export class FunctionalService extends Service { public handle(...params) { @@ -14,4 +18,11 @@ export class FunctionalService extends Service { let invoker = getInvoker(this, params) return invoker } + + public static onDefineInjectTo(target) { + const metadata = getMetadata(CLASS_ENVIRONMENTKEY, target) || {} + const funcName = getFunctionName(this) || 'undefined' + metadata[`${FUNCTIONAL_SERVICE_PREFIX}${funcName.toUpperCase()}`] = funcName + defineMetadata(CLASS_ENVIRONMENTKEY, { ...metadata }, target); + } } \ No newline at end of file diff --git a/src/classes/index.ts b/src/classes/index.ts index 60b11ca..8ced6b1 100644 --- a/src/classes/index.ts +++ b/src/classes/index.ts @@ -1,4 +1,5 @@ export { Service } from './service' +export { InjectService } from './injectService' export { FunctionalApi } from './functionalApi' export { FunctionalService } from './functionalService' export { DynamoDB } from './externals/dynamoDB' diff --git a/src/classes/injectService.ts b/src/classes/injectService.ts new file mode 100644 index 0000000..09f2462 --- /dev/null +++ b/src/classes/injectService.ts @@ -0,0 +1,19 @@ +import { Service } from './service' +import { getFunctionName } from '../annotations/classes/functionName' +import { defineMetadata, getMetadata, constants } from '../annotations' +const { CLASS_ENVIRONMENTKEY } = constants + +export class InjectService extends Service { + public static ConfigEnvironmentKey: string + + public static onDefineInjectTo(target, targetKey, parameterIndex: number) { + super.onDefineInjectTo(target, targetKey, parameterIndex) + + if (this.ConfigEnvironmentKey) { + const injectKeyConfig = (getMetadata(this.ConfigEnvironmentKey, this) || []) + .map(c => { return { ...c, injected: true } }) + const keyConfig = getMetadata(this.ConfigEnvironmentKey, target) || [] + defineMetadata(this.ConfigEnvironmentKey, [...keyConfig, ...injectKeyConfig], target); + } + } +} \ No newline at end of file diff --git a/src/classes/service.ts b/src/classes/service.ts index 46203db..abd9be9 100644 --- a/src/classes/service.ts +++ b/src/classes/service.ts @@ -1,4 +1,18 @@ +import { defineMetadata, getMetadata, constants } from '../annotations' +const { CLASS_ENVIRONMENTKEY } = constants + export class Service { - constructor(...params) { } + public constructor(...params) { } public static factory(...params) { return new this(...params) } + + public static onDefineInjectTo(target, targetKey, parameterIndex: number) { + const metadata = getMetadata(CLASS_ENVIRONMENTKEY, target) || {} + const injectMetadata = getMetadata(CLASS_ENVIRONMENTKEY, this) || {} + if (injectMetadata) { + Object.keys(injectMetadata).forEach((key) => { + metadata[key] = injectMetadata[key] + }) + defineMetadata(CLASS_ENVIRONMENTKEY, { ...metadata }, target); + } + } } \ No newline at end of file diff --git a/src/cli/context/steppes/tableDiscovery.ts b/src/cli/context/steppes/tableDiscovery.ts index 1120888..8e483b1 100644 --- a/src/cli/context/steppes/tableDiscovery.ts +++ b/src/cli/context/steppes/tableDiscovery.ts @@ -1,8 +1,9 @@ import { getMetadata, constants, __dynamoDBDefaults } from '../../../annotations' +import { DYNAMO_TABLE_NAME_SUFFIX } from '../../../annotations/classes/dynamoTable' import { ExecuteStep } from '../core/executeStep' export class TableDiscoveryStep extends ExecuteStep { - protected tableNameEnvRegexp = /_TABLE_NAME$/ + protected tableNameEnvRegexp = new RegExp(`${DYNAMO_TABLE_NAME_SUFFIX}$`) public async method(context) { const tablesToCreate = new Map() diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 16ba285..13e5405 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -1,4 +1,5 @@ import { intersection } from 'lodash' + import { getMetadata, constants, getFunctionName, __dynamoDBDefaults } from '../../../../annotations' const { CLASS_DESCRIPTIONKEY, CLASS_ROLEKEY, CLASS_MEMORYSIZEKEY, CLASS_RUNTIMEKEY, CLASS_TIMEOUTKEY, CLASS_ENVIRONMENTKEY, CLASS_TAGKEY, CLASS_APIGATEWAYKEY } = constants @@ -272,7 +273,7 @@ export const tableResource = async (context) => { const tableResourceName = `Dynamo${tableConfig.tableName}` const resourceName = setResource(context, tableResourceName, dynamoDb, DYNAMODB_TABLE_STACK) - + await setStackParameter({ ...context, resourceName, diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index 64eb91e..16aa73d 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -24,7 +24,7 @@ import { role, description } from '../src/annotations' import { inject } from '../src/annotations/parameters/inject' import { param } from '../src/annotations/parameters/param' -import { FunctionalService, Service } from '../src/classes' +import { FunctionalService, Service, DynamoDB, SimpleNotificationService, S3Storage } from '../src/classes' @@ -591,10 +591,10 @@ describe('annotations', () => { .property(`ATestClass_defined_environment`, 'value') }); - it("injected CLASS_DYNAMOTABLECONFIGURATIONKEY", () => { + it("injected DynamoDB", () => { @injectable @dynamoTable({ tableName: 'ATable' }) - class ATestClass extends Service { } + class ATestClass extends DynamoDB { } class BTestClass { method( @inject(ATestClass) a) { } } @@ -608,10 +608,10 @@ describe('annotations', () => { expect(metadata).to.have.property('tableName', 'ATable') }) - it("injected CLASS_SNSCONFIGURATIONKEY", () => { + it("injected SimpleNotificationService", () => { @injectable @sns({ topicName: 'ATopic' }) - class ATestClass extends Service { } + class ATestClass extends SimpleNotificationService { } class BTestClass { method( @inject(ATestClass) a) { } } @@ -625,10 +625,10 @@ describe('annotations', () => { expect(metadata).to.have.property('topicName', 'ATopic') }) - it("injected CLASS_S3CONFIGURATIONKEY", () => { + it("injected S3Storage", () => { @injectable @s3Storage({ bucketName: 'ABucket' }) - class ATestClass extends Service { } + class ATestClass extends S3Storage { } class BTestClass { method( @inject(ATestClass) a) { } } From d0740db84d56efcd4766eaf2f1f3458b965e26d6 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Fri, 30 Jun 2017 12:03:28 +0200 Subject: [PATCH 031/196] ADD: dynamo event source; REFACTOR: tableDiscovery, collectMetadata --- src/cli/context/steppes/tableDiscovery.ts | 43 ++--- .../cloudFormation/context/dynamoTable.ts | 154 ++++++++++++++++++ .../cloudFormation/context/resources.ts | 51 +----- .../cloudFormation/context/s3Storage.ts | 14 +- .../providers/cloudFormation/context/sns.ts | 16 +- src/cli/providers/cloudFormation/index.ts | 4 +- src/cli/providers/cloudFormation/utils.ts | 22 --- src/cli/utilities/collectMetadata.ts | 64 ++++++++ src/providers/aws/eventSources/dynamoTable.ts | 20 +++ src/providers/aws/index.ts | 2 + 10 files changed, 274 insertions(+), 116 deletions(-) create mode 100644 src/cli/providers/cloudFormation/context/dynamoTable.ts create mode 100644 src/cli/utilities/collectMetadata.ts create mode 100644 src/providers/aws/eventSources/dynamoTable.ts diff --git a/src/cli/context/steppes/tableDiscovery.ts b/src/cli/context/steppes/tableDiscovery.ts index 8e483b1..00f3758 100644 --- a/src/cli/context/steppes/tableDiscovery.ts +++ b/src/cli/context/steppes/tableDiscovery.ts @@ -1,42 +1,19 @@ import { getMetadata, constants, __dynamoDBDefaults } from '../../../annotations' import { DYNAMO_TABLE_NAME_SUFFIX } from '../../../annotations/classes/dynamoTable' import { ExecuteStep } from '../core/executeStep' +import { collectMetadata } from '../../utilities/collectMetadata' +const { CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_ENVIRONMENTKEY } = constants export class TableDiscoveryStep extends ExecuteStep { - protected tableNameEnvRegexp = new RegExp(`${DYNAMO_TABLE_NAME_SUFFIX}$`) public async method(context) { - const tablesToCreate = new Map() - - for (let serviceDefinition of context.publishedFunctions) { - let tableConfigs = getMetadata(constants.CLASS_DYNAMOTABLECONFIGURATIONKEY, serviceDefinition.service) || [] - for (const tableConfig of tableConfigs) { - if (tablesToCreate.has(tableConfig.tableName)) { - const def = tablesToCreate.get(tableConfig.tableName) - def.usedBy.push(serviceDefinition.service) - continue - } - - tablesToCreate.set(tableConfig.tableName, { ...tableConfig, usedBy: [serviceDefinition.service] }) - } - - let metadata = getMetadata(constants.CLASS_ENVIRONMENTKEY, serviceDefinition.service) - if (metadata) { - let keys = Object.keys(metadata) - for (const key of keys) { - if (this.tableNameEnvRegexp.test(key)) { - if (tablesToCreate.has(metadata[key])) { - const def = tablesToCreate.get(metadata[key]) - def.usedBy.push(serviceDefinition.service) - continue - } - - tablesToCreate.set(metadata[key], { TableName: metadata[key], usedBy: [serviceDefinition.service] }) - } - } - } - } - - context.tableConfigs = Array.from(tablesToCreate.values()) + context.tableConfigs = collectMetadata(context, { + metadataKey: CLASS_DYNAMOTABLECONFIGURATIONKEY, + selector: (c) => c.tableName, + environmentRegexp: new RegExp(`${DYNAMO_TABLE_NAME_SUFFIX}$`), + keyProperty: 'environmentKey', + valueProperty: 'tableName', + defaultServiceConfig: { nativeConfig: __dynamoDBDefaults } + }) } } diff --git a/src/cli/providers/cloudFormation/context/dynamoTable.ts b/src/cli/providers/cloudFormation/context/dynamoTable.ts new file mode 100644 index 0000000..a38ba94 --- /dev/null +++ b/src/cli/providers/cloudFormation/context/dynamoTable.ts @@ -0,0 +1,154 @@ +import { defaultsDeep } from 'lodash' +import { __dynamoDBDefaults } from '../../../../annotations' +import { ExecuteStep, executor } from '../../../context' +import { setResource } from '../utils' +import { createStack, setStackParameter, getStackName } from './stack' + +export const DYNAMODB_TABLE_STACK = 'DynamoDBTableStack' + +export const tableResources = ExecuteStep.register('DynamoDB-Tables', async (context) => { + await executor({ + context: { ...context, stackName: DYNAMODB_TABLE_STACK }, + name: `CloudFormation-Stack-init-${DYNAMODB_TABLE_STACK}`, + method: createStack + }) + + for (const tableConfig of context.tableConfigs) { + await executor({ + context: { ...context, tableConfig }, + name: `DynamoDB-Table-${tableConfig.tableName}`, + method: tableResource + }) + } +}) + +export const tableResource = async (context) => { + const { tableConfig } = context + + const properties = { + ...__dynamoDBDefaults, + TableName: `${tableConfig.tableName}-${context.stage}`, + ...tableConfig.nativeConfig + }; + + + const subscribers = tableConfig.services.filter(s => s.serviceConfig.definedBy === s.serviceDefinition.service.name) + if (subscribers.length) { + defaultsDeep(properties, { + StreamSpecification: { + "StreamViewType": "NEW_AND_OLD_IMAGES" + } + }) + } + + const dynamoDb = { + "Type": "AWS::DynamoDB::Table", + "Properties": properties + } + + const tableResourceName = `Dynamo${tableConfig.tableName}` + const resourceName = setResource(context, tableResourceName, dynamoDb, DYNAMODB_TABLE_STACK) + + await setStackParameter({ + ...context, + resourceName, + sourceStackName: DYNAMODB_TABLE_STACK + }) + + tableConfig.tableName = properties.TableName + tableConfig.resourceName = resourceName + +} + +export const tableSubscribers = ExecuteStep.register('DynamoDB-Table-Subscriptions', async (context) => { + for (const tableConfig of context.tableConfigs) { + const subscribers = tableConfig.services.filter(s => s.serviceConfig.definedBy === s.serviceDefinition.service.name) + for (const subscriber of subscribers) { + await executor({ + context: { ...context, tableConfig, subscriber }, + name: `DynamoDB-Table-Subscriptions-${tableConfig.tableName}-${subscriber.serviceDefinition.service.name}`, + method: tableSubscriber + }) + } + } +}) + +export const tableSubscriber = async (context) => { + const { tableConfig, subscriber } = context + + const properties = { + "BatchSize": 1, + "EventSourceArn": { + "Ref": `${tableConfig.resourceName}StreamArn` + }, + "FunctionName": { + "Fn::GetAtt": [ + subscriber.serviceDefinition.resourceName, + "Arn" + ] + }, + "StartingPosition": "TRIM_HORIZON" + }; + + const dynamoDb = { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": properties + } + + await setStackParameter({ + ...context, + resourceName: tableConfig.resourceName, + sourceStackName: DYNAMODB_TABLE_STACK, + targetStackName: getStackName(subscriber.serviceDefinition), + attr: 'StreamArn' + }) + + const eventSourceResourceName = `EventSource${tableConfig.tableName}${subscriber.serviceDefinition.resourceName}Subscription` + const resourceName = setResource(context, eventSourceResourceName, dynamoDb, getStackName(subscriber.serviceDefinition)) + + await executor({ + context: { ...context, tableConfig, serviceDefinition: subscriber.serviceDefinition }, + name: `DynamoDB-Table-Streaming-Policy-${tableConfig.tableName}-${subscriber.serviceDefinition.service.name}`, + method: dynamoStreamingPolicy + }) +} + +export const dynamoStreamingPolicy = async (context) => { + const { tableConfig, serviceDefinition } = context + + let policy = serviceDefinition.roleResource.Properties.Policies.find(p => p.PolicyDocument.Statement[0].Action.includes('dynamodb:GetRecords')) + if (!policy) { + policy = { + "PolicyName": { + "Fn::Join": [ + "-", + [ + "functionly", + serviceDefinition.roleName, + "dynamo", + "streams" + ] + ] + }, + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Action": [ + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:DescribeStream", + "dynamodb:ListStreams" + ], + "Resource": [] + }] + } + } + serviceDefinition.roleResource.Properties.Policies = serviceDefinition.roleResource.Properties.Policies || [] + serviceDefinition.roleResource.Properties.Policies.push(policy) + } + + policy.PolicyDocument.Statement[0].Resource.push({ + "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/" + tableConfig.tableName + "/stream/*" + }) +} diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 13e5405..c8f5717 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -11,8 +11,7 @@ import { getBucketReference } from './s3Storage' export { s3DeploymentBucket, s3DeploymentBucketParameter, s3, S3_DEPLOYMENT_BUCKET_RESOURCE_NAME } from './s3Storage' export { apiGateway } from './apiGateway' export { sns } from './sns' - -export const DYNAMODB_TABLE_STACK = 'DynamoDBTableStack' +export { tableResources, tableSubscribers } from './dynamoTable' export const initStacks = ExecuteStep.register('CloudFormation-Stack-init', async (context) => { @@ -201,7 +200,7 @@ export const dynamoPolicy = async (context) => { const { roleName, serviceDefinitions, roleProperties } = context const tables = [] const services = serviceDefinitions.map(s => s.service) - const usedTableConfigs = context.tableConfigs.filter(t => intersection(t.usedBy, services).length) + const usedTableConfigs = context.tableConfigs.filter(t => intersection(t.services.map(s => s.serviceDefinition.service), services).length) const policy = { "PolicyName": { @@ -239,52 +238,6 @@ export const dynamoPolicy = async (context) => { } } - -export const tableResources = ExecuteStep.register('DynamoDB-Tables', async (context) => { - await executor({ - context: { ...context, stackName: DYNAMODB_TABLE_STACK }, - name: `CloudFormation-Stack-init-${DYNAMODB_TABLE_STACK}`, - method: createStack - }) - - for (const tableConfig of context.tableConfigs) { - await executor({ - context: { ...context, tableConfig }, - name: `DynamoDB-Table-${tableConfig.tableName}`, - method: tableResource - }) - } -}) - -export const tableResource = async (context) => { - const { tableConfig } = context - - const properties = { - ...__dynamoDBDefaults, - TableName: `${tableConfig.tableName}-${context.stage}`, - ...tableConfig.nativeConfig - }; - - - const dynamoDb = { - "Type": "AWS::DynamoDB::Table", - "Properties": properties - } - - const tableResourceName = `Dynamo${tableConfig.tableName}` - const resourceName = setResource(context, tableResourceName, dynamoDb, DYNAMODB_TABLE_STACK) - - await setStackParameter({ - ...context, - resourceName, - sourceStackName: DYNAMODB_TABLE_STACK - }) - - tableConfig.tableName = properties.TableName - tableConfig.resourceName = resourceName - -} - export const lambdaResources = ExecuteStep.register('Lambda-Functions', async (context) => { const awsBucket = await getBucketReference(context) diff --git a/src/cli/providers/cloudFormation/context/s3Storage.ts b/src/cli/providers/cloudFormation/context/s3Storage.ts index f8327bf..cc8b94e 100644 --- a/src/cli/providers/cloudFormation/context/s3Storage.ts +++ b/src/cli/providers/cloudFormation/context/s3Storage.ts @@ -1,7 +1,8 @@ import { getMetadata, constants, defineMetadata, getFunctionName } from '../../../../annotations' const { CLASS_S3CONFIGURATIONKEY, CLASS_ENVIRONMENTKEY } = constants import { ExecuteStep, executor } from '../../../context' -import { setResource, collectConfig } from '../utils' +import { collectMetadata } from '../../../utilities/collectMetadata' +import { setResource } from '../utils' import { createStack, setStackParameter, getStackName } from './stack' export const S3_STORAGE_STACK = 'S3Stack' @@ -56,7 +57,10 @@ export const s3 = ExecuteStep.register('S3', async (context) => { }) export const s3Storages = ExecuteStep.register('S3-Storages', async (context) => { - const configs = collectConfig(context, CLASS_S3CONFIGURATIONKEY, (c) => c.bucketName) + const configs = collectMetadata(context, { + metadataKey: CLASS_S3CONFIGURATIONKEY, + selector: (c) => c.bucketName + }) for (const s3Config of configs) { const s3BucketDefinition = await executor({ @@ -171,17 +175,17 @@ export const s3StorageSubscriptions = async (context) => { } } -export const s3BucketSubscription = (context) => { +export const s3BucketSubscription = async (context) => { const { serviceDefinition, serviceConfig, s3Config, s3BucketDefinition } = context - setStackParameter({ + await setStackParameter({ ...context, sourceStackName: getStackName(serviceDefinition), resourceName: serviceDefinition.resourceName, targetStackName: S3_STORAGE_STACK }) - setStackParameter({ + await setStackParameter({ ...context, sourceStackName: getStackName(serviceDefinition), resourceName: serviceDefinition.resourceName, diff --git a/src/cli/providers/cloudFormation/context/sns.ts b/src/cli/providers/cloudFormation/context/sns.ts index ec54abb..b49ab37 100644 --- a/src/cli/providers/cloudFormation/context/sns.ts +++ b/src/cli/providers/cloudFormation/context/sns.ts @@ -1,7 +1,8 @@ import { getMetadata, constants, defineMetadata } from '../../../../annotations' const { CLASS_SNSCONFIGURATIONKEY, CLASS_ENVIRONMENTKEY } = constants import { ExecuteStep, executor } from '../../../context' -import { setResource, collectConfig } from '../utils' +import { collectMetadata } from '../../../utilities/collectMetadata' +import { setResource } from '../utils' import { createStack, setStackParameter, getStackName } from './stack' export const SNS_TABLE_STACK = 'SNSStack' @@ -17,7 +18,10 @@ export const sns = ExecuteStep.register('SNS', async (context) => { }) export const snsTopics = ExecuteStep.register('SNS-Topics', async (context) => { - const configs = collectConfig(context, CLASS_SNSCONFIGURATIONKEY, (c) => c.topicName) + const configs = collectMetadata(context, { + metadataKey: CLASS_SNSCONFIGURATIONKEY, + selector: (c) => c.topicName + }) for (const snsConfig of configs) { await executor({ @@ -79,10 +83,10 @@ export const snsTopicSubscriptions = async (context) => { } } -export const snsTopicSubscription = (context) => { +export const snsTopicSubscription = async (context) => { const { serviceDefinition, snsConfig } = context - setStackParameter({ + await setStackParameter({ ...context, sourceStackName: SNS_TABLE_STACK, resourceName: snsConfig.resourceName, @@ -130,14 +134,14 @@ export const snsPermissions = (context) => { setResource(context, resourceName, snsPermission, getStackName(serviceDefinition), true) } -const updateSNSEnvironmentVariables = (context) => { +const updateSNSEnvironmentVariables = async (context) => { const { snsConfig } = context for (const { serviceDefinition, serviceConfig } of snsConfig.services) { const environmentVariables = getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) || {} environmentVariables[serviceConfig.environmentKey] = `${snsConfig.advTopicName}-${context.stage}` - setStackParameter({ + await setStackParameter({ ...context, sourceStackName: SNS_TABLE_STACK, resourceName: snsConfig.resourceName, diff --git a/src/cli/providers/cloudFormation/index.ts b/src/cli/providers/cloudFormation/index.ts index e06cb1d..635c1e8 100644 --- a/src/cli/providers/cloudFormation/index.ts +++ b/src/cli/providers/cloudFormation/index.ts @@ -10,7 +10,7 @@ import { executor } from '../../context' import { cloudFormationInit } from './context/cloudFormationInit' import { tableResources, lambdaResources, roleResources, s3DeploymentBucket, s3DeploymentBucketParameter, - apiGateway, sns, s3, initStacks, lambdaLogResources, S3_DEPLOYMENT_BUCKET_RESOURCE_NAME + apiGateway, sns, s3, initStacks, lambdaLogResources, S3_DEPLOYMENT_BUCKET_RESOURCE_NAME, tableSubscribers } from './context/resources' import { uploadTemplate, persistCreateTemplate } from './context/uploadTemplate' @@ -58,6 +58,7 @@ export const cloudFormation = { await executor(context, apiGateway) await executor(context, sns) await executor(context, s3) + await executor(context, tableSubscribers) logger.info(`Functionly: Uploading template...`) await executor(context, uploadTemplate) @@ -90,6 +91,7 @@ export const cloudFormation = { await executor(context, apiGateway) await executor(context, sns) await executor(context, s3) + await executor(context, tableSubscribers) logger.info(`Functionly: Save template...`) await executor({ ...context, skipUpload: true }, uploadTemplate) diff --git a/src/cli/providers/cloudFormation/utils.ts b/src/cli/providers/cloudFormation/utils.ts index 128567f..0626a09 100644 --- a/src/cli/providers/cloudFormation/utils.ts +++ b/src/cli/providers/cloudFormation/utils.ts @@ -1,5 +1,3 @@ -import { getMetadata } from '../../../annotations' - export const nameReplaceRegexp = /[^a-zA-Z0-9]/g export const normalizeName = (name: string) => { const result = name.replace(nameReplaceRegexp, '') @@ -63,23 +61,3 @@ export const setResource = (context: any, name: string, resource: any, stackName return resourceName; } - -export const collectConfig = (context, metadataKey, getHash: (c) => string) => { - const configs = [] - for (const serviceDefinition of context.publishedFunctions) { - let partialConfigs = (getMetadata(metadataKey, serviceDefinition.service) || []) - - for (const serviceConfig of partialConfigs) { - const hash = getHash(serviceConfig) - const config = configs.find(c => c.hash === hash) - if (config) { - config.services.push({ serviceDefinition, serviceConfig }) - continue - } - - configs.push({ ...serviceConfig, hash, services: [{ serviceDefinition, serviceConfig }] }) - } - } - - return configs -} \ No newline at end of file diff --git a/src/cli/utilities/collectMetadata.ts b/src/cli/utilities/collectMetadata.ts new file mode 100644 index 0000000..fe11c3e --- /dev/null +++ b/src/cli/utilities/collectMetadata.ts @@ -0,0 +1,64 @@ +import { getMetadata, constants } from '../../annotations' +const { CLASS_ENVIRONMENTKEY } = constants + +export const collectMetadata = (context, config: { + metadataKey?: string, + selector?: (c) => string, + environmentRegexp?: RegExp, + keyProperty?: string, + valueProperty?: string, + defaultServiceConfig?: { [key: string]: any } +}) => { + const result = new Map() + for (const serviceDefinition of context.publishedFunctions) { + if (config.metadataKey && config.selector) { + let partialConfigs = (getMetadata(config.metadataKey, serviceDefinition.service) || []) + for (const serviceConfig of partialConfigs) { + const hash = config.selector(serviceConfig) + + if (result.has(hash)) { + const item = result.get(hash) + item.services.push({ serviceDefinition, serviceConfig }) + continue + } + + result.set(hash, { + ...serviceConfig, + hash, + services: [{ serviceDefinition, serviceConfig }] + }) + } + } + + if (config.environmentRegexp && config.keyProperty && config.valueProperty) { + let metadata = getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) + if (metadata) { + let keys = Object.keys(metadata) + for (const key of keys) { + const hash = metadata[key] + if (config.environmentRegexp.test(key)) { + const serviceConfig = { + ...(config.defaultServiceConfig || {}), + [config.keyProperty]: key, + [config.valueProperty]: hash + } + + if (result.has(hash)) { + const item = result.get(hash) + item.services.push({ serviceDefinition, serviceConfig }) + continue + } + + result.set(hash, { + ...serviceConfig, + hash, + services: [{ serviceDefinition, serviceConfig }] + }) + } + } + } + } + } + + return Array.from(result.values()) +} diff --git a/src/providers/aws/eventSources/dynamoTable.ts b/src/providers/aws/eventSources/dynamoTable.ts new file mode 100644 index 0000000..9fb7479 --- /dev/null +++ b/src/providers/aws/eventSources/dynamoTable.ts @@ -0,0 +1,20 @@ +import { EventSource } from './eventSource' +import { get } from '../../../helpers/property' + +export class DynamoTable extends EventSource { + public available(eventContext: any): boolean { + const { event } = eventContext + return event && Array.isArray(event.Records) && event.Records.length && event.Records[0].eventSource === "aws:dynamodb" ? true : false + } + + public async parameterResolver(parameter, event) { + const holder = this.getHolder(event.event.Records[0], event.event, parameter) + + switch (parameter.type) { + case 'param': + return get(holder, parameter.from) + default: + return await super.parameterResolver(parameter, event) + } + } +} \ No newline at end of file diff --git a/src/providers/aws/index.ts b/src/providers/aws/index.ts index 5efcddf..c5e2c7a 100644 --- a/src/providers/aws/index.ts +++ b/src/providers/aws/index.ts @@ -5,6 +5,7 @@ import { ApiGateway } from './eventSources/apiGateway' import { LambdaCall } from './eventSources/lambdaCall' import { SNS } from './eventSources/sns' import { S3 } from './eventSources/s3' +import { DynamoTable } from './eventSources/dynamoTable' const lambda = new Lambda(); @@ -12,6 +13,7 @@ const eventSourceHandlers = [ new ApiGateway(), new SNS(), new S3(), + new DynamoTable(), new LambdaCall() ] From 7a738427fc31d0858cf67f667a1d995a21157446 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Fri, 30 Jun 2017 13:35:23 +0200 Subject: [PATCH 032/196] ADD: cloudformtion extension from functionly config --- .../cloudFormation/context/cloudFormationInit.ts | 7 +++++++ src/cli/providers/cloudFormation/context/stack.ts | 10 +++++++--- .../providers/cloudFormation/context/uploadTemplate.ts | 2 +- src/cli/providers/cloudFormation/index.ts | 6 +++++- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/cli/providers/cloudFormation/context/cloudFormationInit.ts b/src/cli/providers/cloudFormation/context/cloudFormationInit.ts index 2e9fa68..1fddb53 100644 --- a/src/cli/providers/cloudFormation/context/cloudFormationInit.ts +++ b/src/cli/providers/cloudFormation/context/cloudFormationInit.ts @@ -2,6 +2,8 @@ import { resolvePath } from '../../../utilities/cli' import { projectConfig } from '../../../project/config' import { ExecuteStep } from '../../../context' +import { defaultsDeep } from 'lodash' + export const cloudFormationInit = ExecuteStep.register('CloudFormationInit', async (context) => { context.CloudFormationConfig = { StackName: projectConfig.name, @@ -18,3 +20,8 @@ export const cloudFormationInit = ExecuteStep.register('CloudFormationInit', asy context.CloudFormationStacks = {} context.deploymentResources = [] }) + +export const cloudFormationMerge = ExecuteStep.register('CloudFormationAfterCreateDefaults', async (context) => { + defaultsDeep(context.CloudFormationTemplate, projectConfig.cloudFormationTemplate || {}) + defaultsDeep(context.CloudFormationStacks, projectConfig.cloudFormationStacksTemplate || {}) +}) \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/context/stack.ts b/src/cli/providers/cloudFormation/context/stack.ts index cbaaf3e..ab218eb 100644 --- a/src/cli/providers/cloudFormation/context/stack.ts +++ b/src/cli/providers/cloudFormation/context/stack.ts @@ -3,15 +3,19 @@ import { setResource, getResourceName } from '../utils' import { getFunctionName } from '../../../../annotations' import { getBucketReference } from './s3Storage' +import { defaultsDeep } from 'lodash' +export const __createdStackNames = [] + export const createStack = async (context) => { const { stackName } = context - context.CloudFormationStacks[stackName] = { + __createdStackNames.push(stackName) + context.CloudFormationStacks[stackName] = defaultsDeep({ "AWSTemplateFormatVersion": "2010-09-09", "Parameters": {}, "Resources": {}, "Outputs": {} - } + }, context.CloudFormationStacks[stackName] || {}) const folderPah = context.version ? `${context.version}/${context.date.toISOString()}` : `${context.date.toISOString()}` const awsBucket = await getBucketReference(context) @@ -85,7 +89,7 @@ export const setStackParameter = (context) => { } } - for (const stackName in context.CloudFormationStacks) { + for (const stackName of __createdStackNames) { const template = context.CloudFormationStacks[stackName] const stackDefinition = context.CloudFormationTemplate.Resources[stackName] if (stackName !== sourceStackName && (!targetStackName || targetStackName === stackName)) { diff --git a/src/cli/providers/cloudFormation/context/uploadTemplate.ts b/src/cli/providers/cloudFormation/context/uploadTemplate.ts index f19e260..6f791e3 100644 --- a/src/cli/providers/cloudFormation/context/uploadTemplate.ts +++ b/src/cli/providers/cloudFormation/context/uploadTemplate.ts @@ -11,7 +11,7 @@ export const persistCreateTemplate = ExecuteStep.register('PersistCreateTemplate export const uploadTemplate = ExecuteStep.register('UploadTemplate', async (context) => { for (const stackName in context.CloudFormationStacks) { const stack = context.CloudFormationStacks[stackName] - if (!Object.keys(stack.Resources).length) { + if (!stack.Resources || !Object.keys(stack.Resources).length) { delete context.CloudFormationStacks[stackName] delete context.CloudFormationTemplate.Resources[stackName] } diff --git a/src/cli/providers/cloudFormation/index.ts b/src/cli/providers/cloudFormation/index.ts index 635c1e8..d8b21c5 100644 --- a/src/cli/providers/cloudFormation/index.ts +++ b/src/cli/providers/cloudFormation/index.ts @@ -7,7 +7,7 @@ import { createStack, updateStack, getTemplate, describeStackResouce, describeSt import { projectConfig } from '../../project/config' import { executor } from '../../context' -import { cloudFormationInit } from './context/cloudFormationInit' +import { cloudFormationInit, cloudFormationMerge } from './context/cloudFormationInit' import { tableResources, lambdaResources, roleResources, s3DeploymentBucket, s3DeploymentBucketParameter, apiGateway, sns, s3, initStacks, lambdaLogResources, S3_DEPLOYMENT_BUCKET_RESOURCE_NAME, tableSubscribers @@ -48,6 +48,8 @@ export const cloudFormation = { const fileName = projectConfig.name ? `${projectConfig.name}.zip` : 'project.zip' await executor(context, uploadZipStep(fileName, context.zipData())) + await executor(context, cloudFormationMerge) + await executor(context, initStacks) await executor(context, s3DeploymentBucketParameter) @@ -81,6 +83,8 @@ export const cloudFormation = { const fileName = projectConfig.name ? `${projectConfig.name}.zip` : 'project.zip' await executor({ ...context, skipUpload: true }, uploadZipStep(fileName, context.zipData())) + await executor(context, cloudFormationMerge) + await executor(context, initStacks) await executor(context, s3DeploymentBucketParameter) From 7cbd770147bea1a2bb1257c7ed330b3c843ebab1 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Fri, 30 Jun 2017 13:45:53 +0200 Subject: [PATCH 033/196] CHANGE: deploy target remaps, aws -> awssdk, cf -> aws --- src/cli/providers/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/providers/index.ts b/src/cli/providers/index.ts index 490a605..90780d8 100644 --- a/src/cli/providers/index.ts +++ b/src/cli/providers/index.ts @@ -1,10 +1,10 @@ -import { aws } from './aws' +import { aws as awssdk } from './aws' import { local } from './local' -import { cloudFormation as cf } from './cloudFormation' +import { cloudFormation as aws } from './cloudFormation' import { ExecuteStep, executor } from '../context' import { getPluginDefinitions } from '../project/config' -let environments = { aws, local, cf } +let environments = { aws, local, awssdk } export class CreateEnvironmentStep extends ExecuteStep { public async method(context) { From 58c16361071f6252b8377cecb2c18cd06299049f Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 3 Jul 2017 15:42:21 +0200 Subject: [PATCH 034/196] ADD: improved eventSource subscribe; class config decorator; REFACTOR: static config properties --- src/annotations/classes/classConfig.ts | 13 ++ src/annotations/classes/eventSource.ts | 13 ++ src/annotations/constants.ts | 3 +- src/annotations/index.ts | 2 + src/classes/externals/apiGateway.ts | 9 ++ src/classes/externals/dynamoDB.ts | 8 +- src/classes/externals/s3Storage.ts | 8 +- src/classes/externals/sns.ts | 8 +- src/classes/index.ts | 1 + src/classes/injectService.ts | 25 +++- .../cloudFormation/context/dynamoTable.ts | 6 +- .../cloudFormation/context/s3Storage.ts | 2 +- .../providers/cloudFormation/context/sns.ts | 2 +- src/index.ts | 2 +- test/annotation.tests.ts | 129 +++++++++++++++++- 15 files changed, 207 insertions(+), 24 deletions(-) create mode 100644 src/annotations/classes/classConfig.ts create mode 100644 src/annotations/classes/eventSource.ts create mode 100644 src/classes/externals/apiGateway.ts diff --git a/src/annotations/classes/classConfig.ts b/src/annotations/classes/classConfig.ts new file mode 100644 index 0000000..884fa5f --- /dev/null +++ b/src/annotations/classes/classConfig.ts @@ -0,0 +1,13 @@ +import { CLASS_CLASSCONFIGKEY } from '../constants' +import { getMetadata, defineMetadata } from '../metadata' + +export const classConfig = (config: { [key: string]: any }) => { + return (target: Function) => { + let metadata = getMetadata(CLASS_CLASSCONFIGKEY, target) || {} + defineMetadata(CLASS_CLASSCONFIGKEY, { ...metadata, ...config }, target); + } +} + +export const getClassConfigValue = (key: string, target) => { + return (getMetadata(CLASS_CLASSCONFIGKEY, target) || {})[key] +} \ No newline at end of file diff --git a/src/annotations/classes/eventSource.ts b/src/annotations/classes/eventSource.ts new file mode 100644 index 0000000..9d23e1f --- /dev/null +++ b/src/annotations/classes/eventSource.ts @@ -0,0 +1,13 @@ +import { CLASS_CLASSCONFIGKEY } from '../constants' +import { getMetadata, defineMetadata } from '../metadata' +import { simpleClassAnnotation } from './simpleAnnotation' + +export const eventSource = (...eventSourceTargets) => { + return (target: Function) => { + for (const eventSourceTarget of eventSourceTargets) { + if (eventSourceTarget && typeof eventSourceTarget.toEventSource === 'function') { + eventSourceTarget.toEventSource(target) + } + } + } +} diff --git a/src/annotations/constants.ts b/src/annotations/constants.ts index ed7e2b6..4127a52 100644 --- a/src/annotations/constants.ts +++ b/src/annotations/constants.ts @@ -12,4 +12,5 @@ export const CLASS_INJECTABLEKEY = 'functionly:class:injectable' export const CLASS_NAMEKEY = 'functionly:class:name' export const CLASS_DYNAMOTABLECONFIGURATIONKEY = 'functionly:class:dynamoTableConfiguration' export const CLASS_SNSCONFIGURATIONKEY = 'functionly:class:snsConfiguration' -export const CLASS_S3CONFIGURATIONKEY = 'functionly:class:s3Configuration' \ No newline at end of file +export const CLASS_S3CONFIGURATIONKEY = 'functionly:class:s3Configuration' +export const CLASS_CLASSCONFIGKEY = 'functionly:class:classConfig' \ No newline at end of file diff --git a/src/annotations/index.ts b/src/annotations/index.ts index 366a6c3..b6a83e5 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -9,6 +9,8 @@ export { functionName, getFunctionName } from './classes/functionName' export { dynamoTable, __dynamoDBDefaults } from './classes/dynamoTable' export { sns } from './classes/sns' export { s3Storage } from './classes/s3Storage' +export { eventSource } from './classes/eventSource' +export { classConfig, getClassConfigValue } from './classes/classConfig' import { simpleClassAnnotation } from './classes/simpleAnnotation' diff --git a/src/classes/externals/apiGateway.ts b/src/classes/externals/apiGateway.ts new file mode 100644 index 0000000..10d3a81 --- /dev/null +++ b/src/classes/externals/apiGateway.ts @@ -0,0 +1,9 @@ + +import { InjectService } from '../injectService' +import { constants, classConfig } from '../../annotations' +const { CLASS_APIGATEWAYKEY } = constants + +@classConfig({ + injectServiceEventSourceKey: CLASS_APIGATEWAYKEY +}) +export class ApiGateway extends InjectService { } diff --git a/src/classes/externals/dynamoDB.ts b/src/classes/externals/dynamoDB.ts index adf850c..97102aa 100644 --- a/src/classes/externals/dynamoDB.ts +++ b/src/classes/externals/dynamoDB.ts @@ -2,7 +2,7 @@ import * as AWS from 'aws-sdk' import { DocumentClient } from 'aws-sdk/lib/dynamodb/document_client' import { InjectService } from '../injectService' -import { constants, getMetadata } from '../../annotations' +import { constants, getMetadata, classConfig } from '../../annotations' import { DYNAMO_TABLE_NAME_SUFFIX } from '../../annotations/classes/dynamoTable' const { CLASS_DYNAMOTABLECONFIGURATIONKEY } = constants @@ -29,9 +29,11 @@ const initAWSSDK = () => { return dynamoDB } +@classConfig({ + injectServiceCopyMetadataKey: CLASS_DYNAMOTABLECONFIGURATIONKEY, + injectServiceEventSourceKey: CLASS_DYNAMOTABLECONFIGURATIONKEY +}) export class DynamoDB extends InjectService { - public static ConfigEnvironmentKey = CLASS_DYNAMOTABLECONFIGURATIONKEY - private _documentClient: DocumentClient public constructor() { initAWSSDK() diff --git a/src/classes/externals/s3Storage.ts b/src/classes/externals/s3Storage.ts index 0f20b92..d7caf9a 100644 --- a/src/classes/externals/s3Storage.ts +++ b/src/classes/externals/s3Storage.ts @@ -1,6 +1,6 @@ import { S3 } from 'aws-sdk' import { InjectService } from '../injectService' -import { constants, getMetadata } from '../../annotations' +import { constants, getMetadata, classConfig } from '../../annotations' import { S3_BUCKET_SUFFIX } from '../../annotations/classes/s3Storage' const { CLASS_S3CONFIGURATIONKEY } = constants @@ -28,9 +28,11 @@ const initAWSSDK = () => { return s3 } +@classConfig({ + injectServiceCopyMetadataKey: CLASS_S3CONFIGURATIONKEY, + injectServiceEventSourceKey: CLASS_S3CONFIGURATIONKEY +}) export class S3Storage extends InjectService { - public static ConfigEnvironmentKey = CLASS_S3CONFIGURATIONKEY - private _s3Client: S3 public constructor() { initAWSSDK() diff --git a/src/classes/externals/sns.ts b/src/classes/externals/sns.ts index 23e13fe..8b13dfc 100644 --- a/src/classes/externals/sns.ts +++ b/src/classes/externals/sns.ts @@ -2,7 +2,7 @@ import { SNS } from 'aws-sdk' export { SNS } from 'aws-sdk' import { InjectService } from '../injectService' -import { constants, getMetadata } from '../../annotations' +import { constants, getMetadata, classConfig } from '../../annotations' const { CLASS_SNSCONFIGURATIONKEY } = constants let sns = null; @@ -27,9 +27,11 @@ const initAWSSDK = () => { return sns } +@classConfig({ + injectServiceCopyMetadataKey: CLASS_SNSCONFIGURATIONKEY, + injectServiceEventSourceKey: CLASS_SNSCONFIGURATIONKEY +}) export class SimpleNotificationService extends InjectService { - public static ConfigEnvironmentKey = CLASS_SNSCONFIGURATIONKEY - private _snsClient: SNS public constructor() { initAWSSDK() diff --git a/src/classes/index.ts b/src/classes/index.ts index 8ced6b1..5759148 100644 --- a/src/classes/index.ts +++ b/src/classes/index.ts @@ -5,4 +5,5 @@ export { FunctionalService } from './functionalService' export { DynamoDB } from './externals/dynamoDB' export { SimpleNotificationService } from './externals/sns' export { S3Storage } from './externals/s3Storage' +export { ApiGateway } from './externals/apiGateway' export { callExtension } from './core/classExtensions' \ No newline at end of file diff --git a/src/classes/injectService.ts b/src/classes/injectService.ts index 09f2462..74b701a 100644 --- a/src/classes/injectService.ts +++ b/src/classes/injectService.ts @@ -1,7 +1,7 @@ import { Service } from './service' import { getFunctionName } from '../annotations/classes/functionName' -import { defineMetadata, getMetadata, constants } from '../annotations' -const { CLASS_ENVIRONMENTKEY } = constants +import { defineMetadata, getMetadata, constants, getClassConfigValue } from '../annotations' +const { CLASS_ENVIRONMENTKEY, CLASS_CLASSCONFIGKEY } = constants export class InjectService extends Service { public static ConfigEnvironmentKey: string @@ -9,11 +9,24 @@ export class InjectService extends Service { public static onDefineInjectTo(target, targetKey, parameterIndex: number) { super.onDefineInjectTo(target, targetKey, parameterIndex) - if (this.ConfigEnvironmentKey) { - const injectKeyConfig = (getMetadata(this.ConfigEnvironmentKey, this) || []) + const configEnvironmentKey = getClassConfigValue('injectServiceCopyMetadataKey', this) + if (configEnvironmentKey) { + const injectKeyConfig = (getMetadata(configEnvironmentKey, this) || []) .map(c => { return { ...c, injected: true } }) - const keyConfig = getMetadata(this.ConfigEnvironmentKey, target) || [] - defineMetadata(this.ConfigEnvironmentKey, [...keyConfig, ...injectKeyConfig], target); + const keyConfig = getMetadata(configEnvironmentKey, target) || [] + defineMetadata(configEnvironmentKey, [...keyConfig, ...injectKeyConfig], target); + } + } + + public static toEventSource(target: Function) { + const environmentKey = getClassConfigValue('injectServiceEventSourceKey', this) + if (environmentKey) { + const metadataCollection = getMetadata(environmentKey, target) || [] + + const eventSourceConfigs = (getMetadata(environmentKey, this) || []) + .map(config => { return { ...config, eventSource: true } }) + + defineMetadata(environmentKey, [...metadataCollection, ...eventSourceConfigs], target) } } } \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/context/dynamoTable.ts b/src/cli/providers/cloudFormation/context/dynamoTable.ts index a38ba94..6dabc0c 100644 --- a/src/cli/providers/cloudFormation/context/dynamoTable.ts +++ b/src/cli/providers/cloudFormation/context/dynamoTable.ts @@ -32,8 +32,8 @@ export const tableResource = async (context) => { }; - const subscribers = tableConfig.services.filter(s => s.serviceConfig.definedBy === s.serviceDefinition.service.name) - if (subscribers.length) { + const hasSubscribers = tableConfig.services.some(s => s.serviceConfig.eventSource) + if (hasSubscribers) { defaultsDeep(properties, { StreamSpecification: { "StreamViewType": "NEW_AND_OLD_IMAGES" @@ -62,7 +62,7 @@ export const tableResource = async (context) => { export const tableSubscribers = ExecuteStep.register('DynamoDB-Table-Subscriptions', async (context) => { for (const tableConfig of context.tableConfigs) { - const subscribers = tableConfig.services.filter(s => s.serviceConfig.definedBy === s.serviceDefinition.service.name) + const subscribers = tableConfig.services.filter(s => s.serviceConfig.eventSource) for (const subscriber of subscribers) { await executor({ context: { ...context, tableConfig, subscriber }, diff --git a/src/cli/providers/cloudFormation/context/s3Storage.ts b/src/cli/providers/cloudFormation/context/s3Storage.ts index cc8b94e..9ee6433 100644 --- a/src/cli/providers/cloudFormation/context/s3Storage.ts +++ b/src/cli/providers/cloudFormation/context/s3Storage.ts @@ -159,7 +159,7 @@ export const s3StorageSubscriptions = async (context) => { const { s3Config } = context for (const { serviceDefinition, serviceConfig } of s3Config.services) { - if (serviceConfig.injected) continue + if (!serviceConfig.eventSource) continue await executor({ context: { ...context, serviceDefinition, serviceConfig }, diff --git a/src/cli/providers/cloudFormation/context/sns.ts b/src/cli/providers/cloudFormation/context/sns.ts index b49ab37..daf0858 100644 --- a/src/cli/providers/cloudFormation/context/sns.ts +++ b/src/cli/providers/cloudFormation/context/sns.ts @@ -67,7 +67,7 @@ export const snsTopicSubscriptions = async (context) => { const { snsConfig } = context for (const { serviceDefinition, serviceConfig } of snsConfig.services) { - if (serviceConfig.injected) continue + if (!serviceConfig.eventSource) continue await executor({ context: { ...context, serviceDefinition }, diff --git a/src/index.ts b/src/index.ts index 9d699aa..f8efb00 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export { Service, FunctionalApi, FunctionalService, DynamoDB, SimpleNotificationService, S3Storage, callExtension } from './classes' +export { Service, FunctionalApi, FunctionalService, DynamoDB, SimpleNotificationService, S3Storage, ApiGateway, callExtension } from './classes' export { addProvider, removeProvider, Provider, AWSProvider, LocalProvider, DeployProvider } from './providers' import * as _annotations from './annotations' export const annotations = _annotations diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index 16aa73d..21ac2d6 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -5,7 +5,7 @@ import { CLASS_APIGATEWAYKEY, CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_ENVIRONMENTKEY, CLASS_NAMEKEY, CLASS_INJECTABLEKEY, CLASS_LOGKEY, CLASS_RUNTIMEKEY, CLASS_MEMORYSIZEKEY, CLASS_TIMEOUTKEY, CLASS_S3CONFIGURATIONKEY, CLASS_SNSCONFIGURATIONKEY, CLASS_TAGKEY, CLASS_ROLEKEY, CLASS_DESCRIPTIONKEY, - PARAMETER_PARAMKEY + PARAMETER_PARAMKEY, CLASS_CLASSCONFIGKEY } from '../src/annotations/constants' import { applyTemplates, templates } from '../src/annotations/templates' import { getFunctionParameters } from '../src/annotations/utils' @@ -20,11 +20,13 @@ import { runtime } from '../src/annotations/classes/runtime' import { s3Storage } from '../src/annotations/classes/s3Storage' import { sns } from '../src/annotations/classes/sns' import { tag } from '../src/annotations/classes/tag' +import { eventSource } from '../src/annotations/classes/eventSource' +import { classConfig } from '../src/annotations/classes/classConfig' import { role, description } from '../src/annotations' import { inject } from '../src/annotations/parameters/inject' import { param } from '../src/annotations/parameters/param' -import { FunctionalService, Service, DynamoDB, SimpleNotificationService, S3Storage } from '../src/classes' +import { FunctionalService, Service, DynamoDB, SimpleNotificationService, S3Storage, InjectService } from '../src/classes' @@ -524,6 +526,129 @@ describe('annotations', () => { expect(descriptionValue).to.equal('desc...') }) }) + describe("classConfig", () => { + it("classConfig", () => { + @classConfig({ customValue: 'v1' }) + class EventSourceTestClass { } + + const config = getMetadata(CLASS_CLASSCONFIGKEY, EventSourceTestClass) + + expect(config).to.deep.equal({ customValue: 'v1' }) + }) + + it("classConfig inherited", () => { + @classConfig({ customValue: 'v1' }) + class ClassConfigTestClass { } + + class ClassConfigTestClassInherited extends ClassConfigTestClass { } + + const config = getMetadata(CLASS_CLASSCONFIGKEY, ClassConfigTestClassInherited) + + expect(config).to.deep.equal({ customValue: 'v1' }) + }) + + it("classConfig extended", () => { + @classConfig({ customValue: 'v1' }) + class ClassConfigTestClass { } + + @classConfig({ customValue2: 'v2' }) + class ClassConfigTestClassInherited extends ClassConfigTestClass { } + + const config = getMetadata(CLASS_CLASSCONFIGKEY, ClassConfigTestClassInherited) + + expect(config).to.deep.equal({ customValue: 'v1', customValue2: 'v2' }) + }) + + it("classConfig override", () => { + @classConfig({ customValue: 'v1' }) + class ClassConfigTestClass { } + + @classConfig({ customValue: 'v2' }) + class ClassConfigTestClassInherited extends ClassConfigTestClass { } + + const config = getMetadata(CLASS_CLASSCONFIGKEY, ClassConfigTestClassInherited) + + expect(config).to.deep.equal({ customValue: 'v2' }) + }) + }) + describe("eventSource", () => { + it("eventSource", () => { + let counter = 0 + + class ATestClass { + public static toEventSource(target: Function, definitionConfig: any) { + counter++ + } + } + + @eventSource(ATestClass) + class BTestClass { } + + expect(counter).to.equal(1) + }) + + it("multiple eventSource", () => { + let counter = 0 + + class ATestClass { + public static toEventSource(target: Function) { + counter++ + } + } + + class AATestClass extends ATestClass { + public static toEventSource(target: Function) { + counter++ + } + } + + @eventSource(ATestClass) + @eventSource(AATestClass) + @eventSource(AATestClass) + class BTestClass { } + + expect(counter).to.equal(3) + }) + + it("multiple eventSource single definition", () => { + let counter = 0 + + class ATestClass { + public static toEventSource(target: Function) { + counter++ + } + } + + class AATestClass extends ATestClass { + public static toEventSource(target: Function) { + counter++ + } + } + + @eventSource(ATestClass, AATestClass, AATestClass) + class BTestClass { } + + expect(counter).to.equal(3) + }) + + it("eventSource inject service", () => { + @dynamoTable({ tableName: 't1' }) + @classConfig({ injectServiceEventSourceKey: CLASS_DYNAMOTABLECONFIGURATIONKEY }) + class ATestClass extends InjectService { } + + @eventSource(ATestClass) + class BTestClass extends FunctionalService { } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, BTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('tableName', 't1') + expect(metadata).to.have.property('eventSource', true) + }) + }) }) describe("parameters", () => { From 8f01439c23bc558703e34841819eb5657345658d Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 10 Jul 2017 16:44:49 +0200 Subject: [PATCH 035/196] ADD: azure package, functions --- package.json | 9 +- src/cli/context/steppes/serviceDiscovery.ts | 1 + .../providers/azureARM/context/functions.ts | 150 +++++++ src/cli/providers/azureARM/context/init.ts | 146 ++++++ src/cli/providers/azureARM/index.ts | 42 ++ .../cloudFormation/context/uploadTemplate.ts | 3 +- src/cli/providers/index.ts | 3 +- src/cli/utilities/aws/s3Upload.ts | 10 +- src/cli/utilities/local/file.ts | 30 ++ src/cli/utilities/webpack.ts | 13 +- src/providers/aws/eventSources/apiGateway.ts | 2 +- src/providers/aws/eventSources/dynamoTable.ts | 2 +- src/providers/aws/eventSources/lambdaCall.ts | 2 +- src/providers/aws/eventSources/s3.ts | 2 +- src/providers/aws/eventSources/sns.ts | 2 +- src/providers/aws/index.ts | 11 +- .../azure/eventSources/httpTrigger.ts | 50 +++ src/providers/azure/index.ts | 70 +++ .../{aws/eventSources => core}/eventSource.ts | 2 +- src/providers/index.ts | 9 +- test/invoker.tests.ts | 425 ++++++++++++++++++ test/serviceOnEvent.tests.ts | 3 +- 22 files changed, 952 insertions(+), 35 deletions(-) create mode 100644 src/cli/providers/azureARM/context/functions.ts create mode 100644 src/cli/providers/azureARM/context/init.ts create mode 100644 src/cli/providers/azureARM/index.ts create mode 100644 src/cli/utilities/local/file.ts create mode 100644 src/providers/azure/eventSources/httpTrigger.ts create mode 100644 src/providers/azure/index.ts rename src/providers/{aws/eventSources => core}/eventSource.ts (92%) diff --git a/package.json b/package.json index 3bf46ff..9233948 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "config": "^1.26.1", "cors": "^2.8.3", "express": "^4.15.2", + "fs-extra": "^3.0.1", "lodash": "^4.14.1", "node-zip": "^1.1.1", "reflect-metadata": "^0.1.10", @@ -50,13 +51,5 @@ }, "bin": { "functionly": "./lib/src/cli/cli.js" - }, - "CloudFormation": { - "StackName": "TestStack", - "OnFailure": "ROLLBACK", - "TimeoutInMinutes": 10, - "Capabilities": [ - "CAPABILITY_NAMED_IAM" - ] } } diff --git a/src/cli/context/steppes/serviceDiscovery.ts b/src/cli/context/steppes/serviceDiscovery.ts index 00554b2..b4dd78b 100644 --- a/src/cli/context/steppes/serviceDiscovery.ts +++ b/src/cli/context/steppes/serviceDiscovery.ts @@ -41,6 +41,7 @@ export class ServiceDiscoveryStep extends ExecuteStep { const item = { service: exportItem.serviceType, exportName: key, + fileName: nameKey, invoker: exportItem, handler: `${nameKey}.${key}`, file diff --git a/src/cli/providers/azureARM/context/functions.ts b/src/cli/providers/azureARM/context/functions.ts new file mode 100644 index 0000000..c5323da --- /dev/null +++ b/src/cli/providers/azureARM/context/functions.ts @@ -0,0 +1,150 @@ +import { basename, join } from 'path' +import { readFileSync } from 'fs' +import { getFunctionName, getMetadata, constants } from '../../../../annotations' +const { CLASS_APIGATEWAYKEY, CLASS_ENVIRONMENTKEY } = constants +import { ExecuteStep, executor } from '../../../context' +import { writeFile, copyFile, removePath } from '../../../utilities/local/file' + +export const azureFunctions = ExecuteStep.register('AzureFunctions', async (context) => { + const site = context.ARMTemplate.resources.find(r => r.type === "Microsoft.Web/sites") + if (site) { + const config = site.resources.find(r => r.type === "config") || { properties: {} } + const appsettings = site.properties.siteConfig.appSettings + + for (const serviceDefinition of context.publishedFunctions) { + const funcname = getFunctionName(serviceDefinition.service) + await executor({ + context: { ...context, serviceDefinition, site, appsettings }, + name: `Azure-ARM-Function-${funcname}`, + method: azureFunction + }) + } + } +}) + +export const azureFunction = async (context) => { + const { serviceDefinition, appsettings } = context + + const environmentVariables = getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) || {} + for (const key in environmentVariables) { + appsettings.push({ + name: key, + value: environmentVariables[key] + }) + } + + const httpMetadata = getMetadata(CLASS_APIGATEWAYKEY, serviceDefinition.service) || [] + for (let metadata of httpMetadata) { + await executor({ + context: { ...context, metadata, endpointCache: {} }, + name: `Azure-ARM-Function-Endpoint-${metadata.method}-${metadata.path}`, + method: azureFunctionEndpoint + }) + } +} +export const azureFunctionEndpoint = async (context) => { + const { serviceDefinition, site, metadata, endpointCache } = context + + const funcname = getFunctionName(serviceDefinition.service) + + + const resourceDefinition = { + "apiVersion": "2015-08-01", + "name": funcname, + "type": "functions", + "dependsOn": [ + "[resourceId('Microsoft.Web/Sites', variables('functionAppName'))]" + ], + "properties": { + "config": { + "bindings": [] + }, + "files": { + } + } + } + + await executor({ + context: { ...context, serviceDefinition, resourceDefinition }, + name: `Azure-ARM-Function-HttpBindings-${metadata.method}-${metadata.path}-${funcname}`, + method: functionBindings + }) + + await executor({ + context: { ...context, serviceDefinition, resourceDefinition }, + name: `Azure-ARM-Function-HttpHandler-${metadata.method}-${metadata.path}-${funcname}`, + method: functionFiles + }) + + site.resources.push(resourceDefinition) +} + +export const functionBindings = async (context) => { + const { serviceDefinition, metadata, resourceDefinition } = context + + let pathParts = metadata.path.split('/').filter(p => p) + + if (pathParts.length) { + resourceDefinition.properties.config.bindings = [ + ...resourceDefinition.properties.config.bindings, + { + "authLevel": metadata.authorization === 'NONE' ? "anonymous" : "function", + "name": "req", + "type": "httpTrigger", + "direction": "in", + "methods": [metadata.method], + "route": pathParts.join('/') + }, + { + "name": "res", + "type": "http", + "direction": "out" + } + ] + } +} + +export const functionFiles = async (context) => { + const { serviceDefinition, resourceDefinition } = context + + resourceDefinition.properties.files = { + ...resourceDefinition.properties.files, + "index.js": `module.exports = require('../${serviceDefinition.fileName}')['${serviceDefinition.exportName}']` + } +} + +export const persistAzureGithubRepo = ExecuteStep.register('PersistAzureGithubRepo', async (context) => { + const site = context.ARMTemplate.resources.find(r => r.type === "Microsoft.Web/sites") + if (site) { + removePath('azuregithubrepo') + + for (const file of context.files) { + copyFile(file, join('azuregithubrepo', basename(file))) + } + + const functions = site.resources.filter(f => f.type === 'functions') + for (const functionResource of functions) { + await executor({ + context: { ...context, site, functionResource }, + name: `PersistAzureGithubRepo-${functionResource.name}`, + method: persistFunction + }) + } + } +}) + +export const persistFunction = (context) => { + const { functionResource, site } = context + const basePath = join('azuregithubrepo', functionResource.name) + + + for (const file in functionResource.properties.files) { + writeFile(join(basePath, file), new Buffer(functionResource.properties.files[file], 'utf8')) + } + + const bindings = JSON.stringify(functionResource.properties.config, null, 4) + writeFile(join(basePath, 'function.json'), new Buffer(bindings, 'utf8')) + + const idx = site.resources.indexOf(functionResource) + site.resources.splice(idx, 1) +} \ No newline at end of file diff --git a/src/cli/providers/azureARM/context/init.ts b/src/cli/providers/azureARM/context/init.ts new file mode 100644 index 0000000..904500c --- /dev/null +++ b/src/cli/providers/azureARM/context/init.ts @@ -0,0 +1,146 @@ +import { resolvePath } from '../../../utilities/cli' +import { projectConfig } from '../../../project/config' +import { ExecuteStep } from '../../../context' + +import { defaultsDeep } from 'lodash' + +export const ARMInit = ExecuteStep.register('ARMInit', async (context) => { + context.ARMConfig = { + ...projectConfig.ARM + } + + context.ARMTemplate = { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "functionAppName": { + "type": "string", + "defaultValue": projectConfig.name, + "metadata": { + "description": "The name of the function app that you wish to create." + } + }, + "storageAccountType": { + "type": "string", + "defaultValue": "Standard_LRS", + "allowedValues": [ + "Standard_LRS", + "Standard_GRS", + "Standard_ZRS", + "Premium_LRS" + ], + "metadata": { + "description": "Storage Account type" + } + }, + "gitUrl": { + "type": "string", + // "defaultValue": "https://github.com/borzav/azure-function-test", + "metadata": { + "description": "Git repository URL" + } + }, + "branch": { + "type": "string", + // "defaultValue": "master", + "metadata": { + "description": "Git repository branch" + } + } + }, + "variables": { + "functionAppName": "[parameters('functionAppName')]", + "hostingPlanName": "[parameters('functionAppName')]", + "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'azfunctions')]", + "storageAccountid": "[concat(resourceGroup().id,'/providers/','Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]", + "gitRepoUrl": "[parameters('gitUrl')]", + "gitBranch": "[parameters('branch')]" + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "name": "[variables('storageAccountName')]", + "apiVersion": "2015-06-15", + "location": "[resourceGroup().location]", + "properties": { + "accountType": "[parameters('storageAccountType')]" + } + }, + { + "type": "Microsoft.Web/serverfarms", + "apiVersion": "2015-04-01", + "name": "[variables('hostingPlanName')]", + "location": "[resourceGroup().location]", + "properties": { + "name": "[variables('hostingPlanName')]", + "computeMode": "Dynamic", + "sku": "Dynamic" + } + }, + { + "apiVersion": "2015-08-01", + "type": "Microsoft.Web/sites", + "name": "[variables('functionAppName')]", + "location": "[resourceGroup().location]", + "kind": "functionapp", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", + "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" + ], + "properties": { + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", + "siteConfig": { + "appSettings": [ + { + "name": "AzureWebJobsDashboard", + "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]" + }, + { + "name": "AzureWebJobsStorage", + "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]" + }, + { + "name": "FUNCTIONS_EXTENSION_VERSION", + "value": "~1" + }, + { + "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", + "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]" + }, + { + "name": "WEBSITE_CONTENTSHARE", + "value": "[toLower(variables('functionAppName'))]" + }, + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "6.5.0" + } + ] + } + }, + "resources": [ + { + "apiVersion": "2015-08-01", + "name": "web", + "type": "sourcecontrols", + "dependsOn": [ + "[resourceId('Microsoft.Web/Sites', parameters('functionAppName'))]" + ], + "properties": { + "RepoUrl": "[variables('gitRepoUrl')]", + "branch": "[variables('gitBranch')]", + "IsManualIntegration": true + } + } + ] + } + ] + } + context.ARMStacks = {} + context.deploymentResources = [] +}) + +export const ARMMerge = ExecuteStep.register('ARMAfterCreateDefaults', async (context) => { + defaultsDeep(context.ARMTemplate, projectConfig.ARMTemplate || {}) + defaultsDeep(context.ARMStacks, projectConfig.ARMStacksTemplate || {}) +}) \ No newline at end of file diff --git a/src/cli/providers/azureARM/index.ts b/src/cli/providers/azureARM/index.ts new file mode 100644 index 0000000..054aa6d --- /dev/null +++ b/src/cli/providers/azureARM/index.ts @@ -0,0 +1,42 @@ +import { getFunctionName } from '../../../annotations' +import { bundle } from '../../utilities/webpack' +import { logger } from '../../utilities/logger' +import { zip } from '../../utilities/compress' +import { writeFile } from '../../utilities/local/file' +import { projectConfig } from '../../project/config' +import { executor } from '../../context' + +import { ARMInit, ARMMerge } from './context/init' +import { azureFunctions, persistAzureGithubRepo } from './context/functions' + + +export const azure = { + FUNCTIONAL_ENVIRONMENT: 'azure', + createEnvironment: async (context) => { + throw new Error('deploy not implemented, use package command') + }, + package: async (context) => { + logger.info(`Functionly: Packgaging...`) + await executor(context, bundle) + await executor(context, zip) + + await executor(context, ARMInit) + + + await executor(context, ARMMerge) + await executor(context, azureFunctions) + + + logger.info(`Functionly: Save template...`) + await executor(context, persistAzureGithubRepo) + await executor(context, persistFile) + + logger.info(`Functionly: Complete`) + } +} + +export const persistFile = async (context) => { + const fileName = projectConfig.name ? `${projectConfig.name}.template.json` : 'azurearm.template.json' + const templateData = JSON.stringify(context.ARMTemplate, null, 2); + writeFile(fileName, new Buffer(templateData, 'binary')) +} \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/context/uploadTemplate.ts b/src/cli/providers/cloudFormation/context/uploadTemplate.ts index 6f791e3..a04cf56 100644 --- a/src/cli/providers/cloudFormation/context/uploadTemplate.ts +++ b/src/cli/providers/cloudFormation/context/uploadTemplate.ts @@ -1,4 +1,5 @@ -import { uploaderStep, writeFile } from '../../../utilities/aws/s3Upload' +import { uploaderStep } from '../../../utilities/aws/s3Upload' +import { writeFile } from '../../../utilities/local/file' import { ExecuteStep, executor } from '../../../context' import { projectConfig } from '../../../project/config' diff --git a/src/cli/providers/index.ts b/src/cli/providers/index.ts index 90780d8..1a24068 100644 --- a/src/cli/providers/index.ts +++ b/src/cli/providers/index.ts @@ -1,10 +1,11 @@ import { aws as awssdk } from './aws' import { local } from './local' import { cloudFormation as aws } from './cloudFormation' +import { azure } from './azureARM' import { ExecuteStep, executor } from '../context' import { getPluginDefinitions } from '../project/config' -let environments = { aws, local, awssdk } +let environments = { aws, local, awssdk, azure } export class CreateEnvironmentStep extends ExecuteStep { public async method(context) { diff --git a/src/cli/utilities/aws/s3Upload.ts b/src/cli/utilities/aws/s3Upload.ts index 3399863..cb67798 100644 --- a/src/cli/utilities/aws/s3Upload.ts +++ b/src/cli/utilities/aws/s3Upload.ts @@ -1,9 +1,7 @@ import { S3 } from 'aws-sdk' import { config } from '../config' import { ExecuteStep, executor } from '../../context' - -import { writeFileSync } from 'fs' -import { join, normalize } from 'path' +import { writeFile } from '../local/file' let s3 = null; const initAWSSDK = (context) => { @@ -68,9 +66,3 @@ export const uploadToAws = ExecuteStep.register('S3-Upload', async (context) => }) }) }) - -export const writeFile = (fileName, binary) => { - if (config.tempDirectory) { - writeFileSync(join(config.tempDirectory, fileName), binary) - } -} diff --git a/src/cli/utilities/local/file.ts b/src/cli/utilities/local/file.ts new file mode 100644 index 0000000..65389d1 --- /dev/null +++ b/src/cli/utilities/local/file.ts @@ -0,0 +1,30 @@ +import { config } from '../config' + +import { writeFileSync, mkdirSync, existsSync } from 'fs' +import { ensureDirSync, copySync, removeSync } from 'fs-extra' +import { join, normalize, dirname } from 'path' + +export const writeFile = (fileName, binary) => { + if (config.tempDirectory) { + const filePath = join(config.tempDirectory, fileName) + const dirPath = dirname(filePath) + ensureDirSync(dirPath) + writeFileSync(filePath, binary) + } +} + +export const copyFile = (from, to) => { + if (config.tempDirectory) { + const destinationFilePath = join(config.tempDirectory, to) + const dirPath = dirname(destinationFilePath) + ensureDirSync(dirPath) + copySync(from, destinationFilePath) + } +} + +export const removePath = (path) => { + if (config.tempDirectory) { + const targetFilePath = join(config.tempDirectory, path) + removeSync(targetFilePath) + } +} diff --git a/src/cli/utilities/webpack.ts b/src/cli/utilities/webpack.ts index fae93ba..7a87b1b 100644 --- a/src/cli/utilities/webpack.ts +++ b/src/cli/utilities/webpack.ts @@ -45,14 +45,17 @@ export const bundleConfig = ExecuteStep.register('WebpackBundleConfig', (context entry[nameKey] = file }) + const externals = [] + if (context.deployTarget === 'aws') { + externals.push({ + 'aws-sdk': 'commonjs aws-sdk' + }) + } + const webpackConfig = { ...config.webpack, entry: entry, - externals: [ - { - 'aws-sdk': 'commonjs aws-sdk' - } - ] + externals } return webpackConfig diff --git a/src/providers/aws/eventSources/apiGateway.ts b/src/providers/aws/eventSources/apiGateway.ts index 1845016..2e7c352 100644 --- a/src/providers/aws/eventSources/apiGateway.ts +++ b/src/providers/aws/eventSources/apiGateway.ts @@ -1,4 +1,4 @@ -import { EventSource } from './eventSource' +import { EventSource } from '../../core/eventSource' import { get } from '../../../helpers/property' export class ApiGateway extends EventSource { diff --git a/src/providers/aws/eventSources/dynamoTable.ts b/src/providers/aws/eventSources/dynamoTable.ts index 9fb7479..857f426 100644 --- a/src/providers/aws/eventSources/dynamoTable.ts +++ b/src/providers/aws/eventSources/dynamoTable.ts @@ -1,4 +1,4 @@ -import { EventSource } from './eventSource' +import { EventSource } from '../../core/eventSource' import { get } from '../../../helpers/property' export class DynamoTable extends EventSource { diff --git a/src/providers/aws/eventSources/lambdaCall.ts b/src/providers/aws/eventSources/lambdaCall.ts index 03453ab..d9e0f0e 100644 --- a/src/providers/aws/eventSources/lambdaCall.ts +++ b/src/providers/aws/eventSources/lambdaCall.ts @@ -1,4 +1,4 @@ -import { EventSource } from './eventSource' +import { EventSource } from '../../core/eventSource' import { get } from '../../../helpers/property' export class LambdaCall extends EventSource { diff --git a/src/providers/aws/eventSources/s3.ts b/src/providers/aws/eventSources/s3.ts index 9e0726a..1091ac5 100644 --- a/src/providers/aws/eventSources/s3.ts +++ b/src/providers/aws/eventSources/s3.ts @@ -1,4 +1,4 @@ -import { EventSource } from './eventSource' +import { EventSource } from '../../core/eventSource' import { get } from '../../../helpers/property' export class S3 extends EventSource { diff --git a/src/providers/aws/eventSources/sns.ts b/src/providers/aws/eventSources/sns.ts index e3e7e4f..85da46f 100644 --- a/src/providers/aws/eventSources/sns.ts +++ b/src/providers/aws/eventSources/sns.ts @@ -1,4 +1,4 @@ -import { EventSource } from './eventSource' +import { EventSource } from '../../core/eventSource' import { get } from '../../../helpers/property' export class SNS extends EventSource { diff --git a/src/providers/aws/index.ts b/src/providers/aws/index.ts index c5e2c7a..2616472 100644 --- a/src/providers/aws/index.ts +++ b/src/providers/aws/index.ts @@ -7,7 +7,14 @@ import { SNS } from './eventSources/sns' import { S3 } from './eventSources/s3' import { DynamoTable } from './eventSources/dynamoTable' -const lambda = new Lambda(); +let lambda = null; +const initAWSSDK = () => { + if (!lambda) { + lambda = new Lambda(); + } + return lambda +} + const eventSourceHandlers = [ new ApiGateway(), @@ -61,6 +68,8 @@ export class AWSProvider extends Provider { } public async invoke(serviceInstance, params, invokeConfig?) { + initAWSSDK() + return new Promise((resolve, reject) => { const funcName = getFunctionName(serviceInstance) diff --git a/src/providers/azure/eventSources/httpTrigger.ts b/src/providers/azure/eventSources/httpTrigger.ts new file mode 100644 index 0000000..2c60408 --- /dev/null +++ b/src/providers/azure/eventSources/httpTrigger.ts @@ -0,0 +1,50 @@ +import { EventSource } from '../../core/eventSource' +import { get } from '../../../helpers/property' + +export class HttpTrigger extends EventSource { + public async parameterResolver(parameter, event) { + const body = event.req.body + const query = event.req.query + const params = event.req.params + const headers = event.req.headers + + switch (parameter.type) { + case 'param': + const source = parameter.source; + if (typeof source !== 'undefined') { + const holder = !source ? event.req : get(event.req, source) + if (holder) { + return get(holder, parameter.from) + } + } else { + let value + if (typeof (value = get(body, parameter.from)) !== 'undefined') return value + if (typeof (value = get(query, parameter.from)) !== 'undefined') return value + if (typeof (value = get(params, parameter.from)) !== 'undefined') return value + if (typeof (value = get(headers, parameter.from)) !== 'undefined') return value + return value; + } + return undefined + default: + return await super.parameterResolver(parameter, event) + } + } + + public async resultTransform(error, result, eventContext) { + if (error) { + return { + status: 500, + body: `${error.message} - ${error.stack}` + } + } + + if (result && typeof result.status === 'number' && typeof result.body === 'string') { + return result + } + + return { + status: 200, + body: JSON.stringify(result) + } + } +} \ No newline at end of file diff --git a/src/providers/azure/index.ts b/src/providers/azure/index.ts new file mode 100644 index 0000000..82edfe2 --- /dev/null +++ b/src/providers/azure/index.ts @@ -0,0 +1,70 @@ +import { Provider } from '../core/provider' +import { getFunctionName } from '../../annotations' +import { HttpTrigger } from './eventSources/httpTrigger' + +const eventSourceHandlers = [ + new HttpTrigger() +] + +export class AzureProvider extends Provider { + public getInvoker(serviceType, serviceInstance, params): Function { + const parameters = this.getParameters(serviceType, 'handle') + + const invoker = async (context, req) => { + try { + const eventContext = { context, req } + + const eventSourceHandler = eventSourceHandlers.find(h => h.available(eventContext)) + + const params = [] + for (const parameter of parameters) { + params[parameter.parameterIndex] = await this.parameterResolver(parameter, { eventSourceHandler, eventContext }) + } + + let result + let error + try { + result = await serviceInstance.handle(...params) + } catch (err) { + error = err + } + const response = await eventSourceHandler.resultTransform(error, result, eventContext) + + context.res = response + return response + } catch (e) { + context.res = { + status: 500, + body: `${e.message} - ${e.stack}` + } + } + } + return invoker + } + + protected async parameterResolver(parameter, event) { + switch (parameter.type) { + case 'param': + return event.eventSourceHandler.parameterResolver(parameter, event.eventContext) + default: + return await super.parameterResolver(parameter, event.eventContext) + } + } + + public async invoke(serviceInstance, params, invokeConfig?) { + return new Promise((resolve, reject) => { + + const funcName = getFunctionName(serviceInstance) + const resolvedFuncName = process.env[`FUNCTIONAL_SERVICE_${funcName.toUpperCase()}`] || funcName + + const invokeParams = { + FunctionName: resolvedFuncName, + Payload: JSON.stringify(params) + }; + + // TODO invoke + }) + } +} + +export const provider = new AzureProvider() diff --git a/src/providers/aws/eventSources/eventSource.ts b/src/providers/core/eventSource.ts similarity index 92% rename from src/providers/aws/eventSources/eventSource.ts rename to src/providers/core/eventSource.ts index a7ce84b..4e87307 100644 --- a/src/providers/aws/eventSources/eventSource.ts +++ b/src/providers/core/eventSource.ts @@ -1,4 +1,4 @@ -import { get } from '../../../helpers/property' +import { get } from '../../helpers/property' export abstract class EventSource { public available(event: any) { diff --git a/src/providers/index.ts b/src/providers/index.ts index 172f993..3563a54 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -5,14 +5,16 @@ export { Provider } from './core/provider' export { AWSProvider } from './aws' export { LocalProvider } from './local' export { DeployProvider } from './deploy' +export { AzureProvider } from './azure' import { Provider } from './core/provider' import { provider as aws } from './aws' import { provider as local } from './local' import { provider as deploy } from './deploy' +import { provider as azure } from './azure' -let environments = {} -let invokeEnvironments = {} +const environments = {} +const invokeEnvironments = {} export const addProvider = (name, provider: Provider) => { environments[name] = provider @@ -27,6 +29,7 @@ export const removeProvider = (name) => { addProvider('aws', aws) addProvider('local', local) addProvider('deploy', deploy) +addProvider('azure', azure) export const getInvoker = (serviceType, params) => { const environment = process.env.FUNCTIONAL_ENVIRONMENT; @@ -90,4 +93,4 @@ export const invoke = async (serviceInstance, params?, invokeConfig?) => { }) return await currentEnvironment.invoke(serviceInstance, availableParams, invokeConfig) -} \ No newline at end of file +} diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index 580345c..4aeaab0 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -946,4 +946,429 @@ describe('invoker', () => { invoker(awsEvent, awsContext, cb) }) }) + + describe("azure", () => { + + afterEach(() => { + delete process.env.FUNCTIONAL_ENVIRONMENT + }) + + it("invoke", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + class MockService extends FunctionalService { + handle() { + counter++ + } + } + + const invoker = MockService.createInvoker() + + const context = {} + const req = {} + + await invoker(context, req) + + expect(counter).to.equal(1) + }) + + it("return value", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + class MockService extends FunctionalService { + handle() { + counter++ + return { ok: 1 } + } + } + + const invoker = MockService.createInvoker() + + const context = {} + const req = {} + + await invoker(context, req) + + expect(counter).to.equal(1) + expect(context).to.have.nested.property('res.status', 200) + expect(context).to.have.nested.property('res.body', JSON.stringify({ ok: 1 })) + }) + + it("handler throw error", async () => { + let counter = 0 + let ex + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + class MockService extends FunctionalService { + handle() { + counter++ + ex = new Error('error in handle') + throw ex + } + } + + const invoker = MockService.createInvoker() + + const context = {} + const req = {} + + await invoker(context, req) + + expect(counter).to.equal(1) + expect(context).to.have.nested.property('res.status', 500) + expect(context).to.have.nested.property('res.body', `${ex.message} - ${ex.stack}`) + }) + + describe("eventSources", () => { + it("httpTrigger body param", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('v1') + expect(p2).to.equal('v2') + } + } + + const invoker = MockService.createInvoker() + + const context = {} + const req = { + body: { + p1: 'v1', + p2: 'v2' + } + } + + await invoker(context, req) + + expect(counter).to.equal(1) + }) + + it("httpTrigger query param", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('v1') + expect(p2).to.equal('v2') + } + } + + const invoker = MockService.createInvoker() + + const context = {} + const req = { + query: { + p1: 'v1', + p2: 'v2' + } + } + + await invoker(context, req) + + expect(counter).to.equal(1) + }) + + it("httpTrigger pathParameters param", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('v1') + expect(p2).to.equal('v2') + } + } + + const invoker = MockService.createInvoker() + + const context = {} + const req = { + params: { + p1: 'v1', + p2: 'v2' + } + } + + await invoker(context, req) + + expect(counter).to.equal(1) + }) + + it("httpTrigger header param", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('v1') + expect(p2).to.equal('v2') + } + } + + const invoker = MockService.createInvoker() + + const context = {} + const req = { + headers: { + p1: 'v1', + p2: 'v2' + } + } + + await invoker(context, req) + + expect(counter).to.equal(1) + }) + + it("httpTrigger params resolve order", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + class MockService extends FunctionalService { + handle( @param p1, @param p2, @param p3, @param p4) { + counter++ + expect(p1).to.equal('body') + expect(p2).to.equal('queryStringParameters') + expect(p3).to.equal('pathParameters') + expect(p4).to.equal('headers') + } + } + + const invoker = MockService.createInvoker() + + const context = {} + const req = { + body: { + p1: 'body' + }, + query: { + p1: 'queryStringParameters', + p2: 'queryStringParameters' + }, + params: { + p1: 'pathParameters', + p2: 'pathParameters', + p3: 'pathParameters' + }, + headers: { + p1: 'headers', + p2: 'headers', + p3: 'headers', + p4: 'headers' + } + } + + await invoker(context, req) + + expect(counter).to.equal(1) + }) + + it("httpTrigger params resolve hint", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + class MockService extends FunctionalService { + handle( @param({ source: 'queryStringParameters' }) p1, @param({ source: 'pathParameters' }) p2, @param({ source: 'headers' }) p3, @param p4) { + counter++ + expect(p1).to.equal('queryStringParameters') + expect(p2).to.equal('pathParameters') + expect(p3).to.equal('headers') + expect(p4).to.equal('headers') + } + } + + const invoker = MockService.createInvoker() + + const context = {} + const req = { + body: { + p1: 'body' + }, + query: { + p1: 'queryStringParameters', + p2: 'queryStringParameters' + }, + params: { + p1: 'pathParameters', + p2: 'pathParameters', + p3: 'pathParameters' + }, + headers: { + p1: 'headers', + p2: 'headers', + p3: 'headers', + p4: 'headers' + } + } + + await invoker(context, req) + + expect(counter).to.equal(1) + }) + + it("httpTrigger return value", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + class MockService extends FunctionalService { + handle() { + counter++ + return { ok: 1 } + } + } + + const invoker = MockService.createInvoker() + + const context = {} + const req = {} + + await invoker(context, req) + + expect(counter).to.equal(1) + expect(context).to.have.nested.property('res.status', 200) + expect(context).to.have.nested.property('res.body', JSON.stringify({ ok: 1 })) + }) + + it("httpTrigger return value advanced", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + class MockService extends FunctionalService { + handle() { + counter++ + return { + status: 200, + body: 'myresult' + } + } + } + + const invoker = MockService.createInvoker() + + const context = {} + const req = {} + + await invoker(context, req) + + expect(counter).to.equal(1) + expect(context).to.have.nested.property('res.status', 200) + expect(context).to.have.nested.property('res.body', 'myresult') + }) + + it("httpTrigger return value advanced - error", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + class MockService extends FunctionalService { + handle() { + counter++ + return { + status: 500, + body: 'myerror' + } + } + } + + const invoker = MockService.createInvoker() + + const context = {} + const req = {} + + await invoker(context, req) + + expect(counter).to.equal(1) + expect(context).to.have.nested.property('res.status', 500) + expect(context).to.have.nested.property('res.body', 'myerror') + }) + + it("httpTrigger return value advanced - throw error", async () => { + let counter = 0 + let ex + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + class MockService extends FunctionalService { + handle() { + counter++ + ex = new Error('error in handle') + throw ex + } + } + + const invoker = MockService.createInvoker() + + const context = {} + const req = {} + + await invoker(context, req) + + expect(counter).to.equal(1) + expect(context).to.have.nested.property('res.status', 500) + expect(context).to.have.nested.property('res.body', `${ex.message} - ${ex.stack}`) + }) + }) + + it("inject param", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + + @injectable + class MockInjectable extends Service { } + + class MockService extends FunctionalService { + handle( @param p1, @inject(MockInjectable) p2) { + counter++ + expect(p1).to.undefined + expect(p2).to.instanceof(Service) + expect(p2).to.instanceof(MockInjectable) + } + } + + const invoker = MockService.createInvoker() + + const context = {} + const req = {} + + await invoker(context, req) + + expect(counter).to.equal(1) + }) + + it("event param", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + + @injectable + class MockInjectable extends Service { } + + const context = {} + const req = {} + + class MockService extends FunctionalService { + handle( @param p1, @event p2) { + counter++ + expect(p1).to.undefined + expect(p2).to.have.property('context').that.to.equal(context) + expect(p2).to.have.property('req').that.to.equal(req) + } + } + + const invoker = MockService.createInvoker() + + await invoker(context, req) + + expect(counter).to.equal(1) + }) + }) }) \ No newline at end of file diff --git a/test/serviceOnEvent.tests.ts b/test/serviceOnEvent.tests.ts index a1c819c..39d8e85 100644 --- a/test/serviceOnEvent.tests.ts +++ b/test/serviceOnEvent.tests.ts @@ -2,7 +2,8 @@ import { expect } from 'chai' import { FunctionalService, Service } from '../src/classes' import { param, inject, injectable, event } from '../src/annotations' -import { addProvider, removeProvider, LocalProvider } from '../src/providers' +import { addProvider, removeProvider } from '../src/providers' +import { LocalProvider } from '../src/providers/local' import { PARAMETER_PARAMKEY } from '../src/annotations/constants' import { getMetadata, getOwnMetadata } from '../src/annotations/metadata' From 47830f4b1c48618ce3603717ac1336ad05d915f0 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 11 Jul 2017 17:06:49 +0200 Subject: [PATCH 036/196] ADD: decorators, tests; REFACTOR: http endpoint decorators --- .../classes/{ => aws}/apiGateway.ts | 17 +- src/annotations/classes/azure/httpTrigger.ts | 32 +++ .../classes/expandableDecorator.ts | 48 ++++ src/annotations/classes/rest.ts | 10 + src/annotations/constants.ts | 6 +- src/annotations/index.ts | 6 +- src/cli/commands/deploy.ts | 4 +- src/cli/commands/metadata.ts | 2 +- src/cli/commands/package.ts | 4 +- src/cli/commands/serverless.ts | 3 +- .../providers/azureARM/context/functions.ts | 44 ++-- src/index.ts | 2 +- src/providers/deploy.ts | 6 - src/providers/index.ts | 3 - test/annotation.tests.ts | 208 +++++++++++++++++- 15 files changed, 348 insertions(+), 47 deletions(-) rename src/annotations/classes/{ => aws}/apiGateway.ts (52%) create mode 100644 src/annotations/classes/azure/httpTrigger.ts create mode 100644 src/annotations/classes/expandableDecorator.ts create mode 100644 src/annotations/classes/rest.ts delete mode 100644 src/providers/deploy.ts diff --git a/src/annotations/classes/apiGateway.ts b/src/annotations/classes/aws/apiGateway.ts similarity index 52% rename from src/annotations/classes/apiGateway.ts rename to src/annotations/classes/aws/apiGateway.ts index 9a021d9..acdac6a 100644 --- a/src/annotations/classes/apiGateway.ts +++ b/src/annotations/classes/aws/apiGateway.ts @@ -1,5 +1,6 @@ -import { CLASS_APIGATEWAYKEY } from '../constants' -import { getMetadata, defineMetadata } from '../metadata' +import { CLASS_APIGATEWAYKEY } from '../../constants' +import { getMetadata, defineMetadata } from '../../metadata' +import { rest } from '../rest' export const defaultEndpoint = { method: 'get', @@ -17,3 +18,15 @@ export const apiGateway = (endpoint: { defineMetadata(CLASS_APIGATEWAYKEY, [...metadata], target); } } + +rest.extension('aws', (target, config) => { + for(const method of config.methods){ + const decorator = apiGateway({ + path: config.path, + method, + cors: config.cors, + authorization: config.anonymous ? 'NONE' : 'AWS_IAM' + }) + decorator(target) + } +}) \ No newline at end of file diff --git a/src/annotations/classes/azure/httpTrigger.ts b/src/annotations/classes/azure/httpTrigger.ts new file mode 100644 index 0000000..88c2ede --- /dev/null +++ b/src/annotations/classes/azure/httpTrigger.ts @@ -0,0 +1,32 @@ +import { CLASS_HTTPTRIGGER } from '../../constants' +import { getMetadata, defineMetadata } from '../../metadata' +import { rest } from '../rest' + +export const defaultEndpoint = { + methods: ['get'], + cors: false, + authLevel: 'function' +} + +export const httpTrigger = (endpoint: { + route: string, + methods?: string[], + cors?: boolean, + authLevel?: 'anonymous' | 'function' | 'admin' +}) => { + return (target: Function) => { + let metadata = getMetadata(CLASS_HTTPTRIGGER, target) || [] + metadata.push({ ...defaultEndpoint, ...endpoint }) + defineMetadata(CLASS_HTTPTRIGGER, [...metadata], target); + } +} + +rest.extension('azure', (target, config) => { + const decorator = httpTrigger({ + route: config.path, + methods: config.methods, + cors: config.cors, + authLevel: config.anonymous ? 'anonymous' : 'function' + }) + decorator(target) +}) \ No newline at end of file diff --git a/src/annotations/classes/expandableDecorator.ts b/src/annotations/classes/expandableDecorator.ts new file mode 100644 index 0000000..62e2864 --- /dev/null +++ b/src/annotations/classes/expandableDecorator.ts @@ -0,0 +1,48 @@ +import { getMetadata, defineMetadata } from '../metadata' +import { CLASS_KEY_PREFIX } from '../constants' + +export const expandableDecorator = function (decoratorConfig: { + name: string, + defaultValues?: Partial + environmentKey?: string +}) { + const environmentKey = decoratorConfig.environmentKey || `${CLASS_KEY_PREFIX}${decoratorConfig.name}` + const environmentExtensions = new Map void)[]>() + + const decorator = Object.assign( + (config: T) => { + const _dv: any = decoratorConfig.defaultValues || {}; + const _c: any = config + const handlerConfig = { ..._dv, ..._c } + + return (target: Function) => { + if (environmentKey) { + let metadata = getMetadata(environmentKey, target) || [] + metadata.push(handlerConfig) + defineMetadata(environmentKey, [...metadata], target); + } + + const environment = process.env.FUNCTIONAL_ENVIRONMENT + console.log(`expandableDecorator '${decoratorConfig.name}' environment: ${environment}`) + if (!environment || !environmentExtensions.has(environment)) { + return; + } + + const handlers = environmentExtensions.get(environment) + for (const handler of handlers) { + handler(target, handlerConfig) + } + } + }, + { + extension: (environmentMode: string, handler: (target: Function, config: T) => any) => { + const handlers = environmentExtensions.get(environmentMode) || [] + environmentExtensions.set(environmentMode, [...handlers, handler]) + }, + environmentKey + } + ); + + return decorator + +} \ No newline at end of file diff --git a/src/annotations/classes/rest.ts b/src/annotations/classes/rest.ts new file mode 100644 index 0000000..7f52242 --- /dev/null +++ b/src/annotations/classes/rest.ts @@ -0,0 +1,10 @@ +import { expandableDecorator } from './expandableDecorator' + +export const rest = expandableDecorator<{ path: string, methods?: string[], cors?: boolean, anonymous?: boolean }>({ + name: 'rest', + defaultValues: { + methods: ['get'], + cors: false, + anonymous: false + } +}) \ No newline at end of file diff --git a/src/annotations/constants.ts b/src/annotations/constants.ts index 4127a52..dd440e7 100644 --- a/src/annotations/constants.ts +++ b/src/annotations/constants.ts @@ -1,3 +1,5 @@ +export const CLASS_KEY_PREFIX = 'functionly:class:' + export const PARAMETER_PARAMKEY = 'functionly:param:parameters' export const CLASS_DESCRIPTIONKEY = 'functionly:class:description' export const CLASS_ROLEKEY = 'functionly:class:role' @@ -13,4 +15,6 @@ export const CLASS_NAMEKEY = 'functionly:class:name' export const CLASS_DYNAMOTABLECONFIGURATIONKEY = 'functionly:class:dynamoTableConfiguration' export const CLASS_SNSCONFIGURATIONKEY = 'functionly:class:snsConfiguration' export const CLASS_S3CONFIGURATIONKEY = 'functionly:class:s3Configuration' -export const CLASS_CLASSCONFIGKEY = 'functionly:class:classConfig' \ No newline at end of file +export const CLASS_CLASSCONFIGKEY = 'functionly:class:classConfig' + +export const CLASS_HTTPTRIGGER = 'functionly:class:httpTrigger' \ No newline at end of file diff --git a/src/annotations/index.ts b/src/annotations/index.ts index b6a83e5..fe5eb46 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -1,6 +1,8 @@ export { templates, applyTemplates } from './templates' export { injectable } from './classes/injectable' -export { apiGateway } from './classes/apiGateway' +export { apiGateway } from './classes/aws/apiGateway' +export { httpTrigger } from './classes/azure/httpTrigger' +export { rest } from './classes/rest' export { environment } from './classes/environment' export { tag } from './classes/tag' export { log } from './classes/log' @@ -11,6 +13,8 @@ export { sns } from './classes/sns' export { s3Storage } from './classes/s3Storage' export { eventSource } from './classes/eventSource' export { classConfig, getClassConfigValue } from './classes/classConfig' +export { simpleClassAnnotation } from './classes/simpleAnnotation' +export { expandableDecorator } from './classes/expandableDecorator' import { simpleClassAnnotation } from './classes/simpleAnnotation' diff --git a/src/cli/commands/deploy.ts b/src/cli/commands/deploy.ts index ede6c84..d0d16a9 100644 --- a/src/cli/commands/deploy.ts +++ b/src/cli/commands/deploy.ts @@ -8,8 +8,6 @@ export default ({ createContext, executor, ExecuteStep, projectConfig, requireVa .option('--aws-bucket ', 'aws bucket') .option('--stage ', 'stage') .action(async (target, path, command) => { - process.env.FUNCTIONAL_ENVIRONMENT = 'deploy' - try { const entryPoint = requireValue(path || projectConfig.main, 'entry point') const deployTarget = requireValue(target || projectConfig.deployTarget, 'missing deploy target') @@ -17,6 +15,8 @@ export default ({ createContext, executor, ExecuteStep, projectConfig, requireVa const awsBucket = command.awsBucket || projectConfig.awsBucket const stage = command.stage || projectConfig.stage || 'dev' + process.env.FUNCTIONAL_ENVIRONMENT = deployTarget + const context = await createContext(entryPoint, { deployTarget, awsRegion, diff --git a/src/cli/commands/metadata.ts b/src/cli/commands/metadata.ts index afed9de..08355a6 100644 --- a/src/cli/commands/metadata.ts +++ b/src/cli/commands/metadata.ts @@ -6,12 +6,12 @@ export default ({ createContext, annotations: { getMetadata, getMetadataKeys }, .command('metadata [target] [path]') .description('service metadata') .action(async (target, path, command) => { - process.env.FUNCTIONAL_ENVIRONMENT = 'deploy' try { const entryPoint = requireValue(path || projectConfig.main, 'entry point') const deployTarget = requireValue(target || projectConfig.deployTarget, 'missing deploy target') + process.env.FUNCTIONAL_ENVIRONMENT = deployTarget const context = await createContext(entryPoint, { deployTarget diff --git a/src/cli/commands/package.ts b/src/cli/commands/package.ts index 80bc0f0..0ef1e29 100644 --- a/src/cli/commands/package.ts +++ b/src/cli/commands/package.ts @@ -8,8 +8,6 @@ export default ({ createContext, executor, ExecuteStep, projectConfig, requireVa .option('--aws-bucket ', 'aws bucket') .option('--stage ', 'stage') .action(async (target, path, command) => { - process.env.FUNCTIONAL_ENVIRONMENT = 'deploy' - try { const entryPoint = requireValue(path || projectConfig.main, 'entry point') const deployTarget = requireValue(target || projectConfig.deployTarget, 'missing deploy target') @@ -17,6 +15,8 @@ export default ({ createContext, executor, ExecuteStep, projectConfig, requireVa const awsBucket = command.awsBucket || projectConfig.awsBucket const stage = command.stage || projectConfig.stage || 'dev' + process.env.FUNCTIONAL_ENVIRONMENT = deployTarget + const context = await createContext(entryPoint, { deployTarget, awsRegion, diff --git a/src/cli/commands/serverless.ts b/src/cli/commands/serverless.ts index 15febdb..f58f81b 100644 --- a/src/cli/commands/serverless.ts +++ b/src/cli/commands/serverless.ts @@ -195,13 +195,14 @@ export default (api) => { .option('--stage ', 'stage') .description('serverless config') .action(async (path, command) => { - process.env.FUNCTIONAL_ENVIRONMENT = 'deploy' try { const entryPoint = requireValue(path || projectConfig.main, 'entry point') const awsRegion = requireValue(command.awsRegion || projectConfig.awsRegion, 'awsRegion') const stage = command.stage || projectConfig.stage || 'dev' + process.env.FUNCTIONAL_ENVIRONMENT = FUNCTIONAL_ENVIRONMENT + const context = await createContext(entryPoint, { deployTarget: FUNCTIONAL_ENVIRONMENT, awsRegion, diff --git a/src/cli/providers/azureARM/context/functions.ts b/src/cli/providers/azureARM/context/functions.ts index c5323da..fc35e67 100644 --- a/src/cli/providers/azureARM/context/functions.ts +++ b/src/cli/providers/azureARM/context/functions.ts @@ -1,7 +1,7 @@ import { basename, join } from 'path' import { readFileSync } from 'fs' import { getFunctionName, getMetadata, constants } from '../../../../annotations' -const { CLASS_APIGATEWAYKEY, CLASS_ENVIRONMENTKEY } = constants +const { CLASS_HTTPTRIGGER, CLASS_ENVIRONMENTKEY } = constants import { ExecuteStep, executor } from '../../../context' import { writeFile, copyFile, removePath } from '../../../utilities/local/file' @@ -33,17 +33,17 @@ export const azureFunction = async (context) => { }) } - const httpMetadata = getMetadata(CLASS_APIGATEWAYKEY, serviceDefinition.service) || [] + const httpMetadata = getMetadata(CLASS_HTTPTRIGGER, serviceDefinition.service) || [] for (let metadata of httpMetadata) { await executor({ - context: { ...context, metadata, endpointCache: {} }, + context: { ...context, metadata }, name: `Azure-ARM-Function-Endpoint-${metadata.method}-${metadata.path}`, method: azureFunctionEndpoint }) } } export const azureFunctionEndpoint = async (context) => { - const { serviceDefinition, site, metadata, endpointCache } = context + const { serviceDefinition, site, metadata } = context const funcname = getFunctionName(serviceDefinition.service) @@ -82,26 +82,22 @@ export const azureFunctionEndpoint = async (context) => { export const functionBindings = async (context) => { const { serviceDefinition, metadata, resourceDefinition } = context - let pathParts = metadata.path.split('/').filter(p => p) - - if (pathParts.length) { - resourceDefinition.properties.config.bindings = [ - ...resourceDefinition.properties.config.bindings, - { - "authLevel": metadata.authorization === 'NONE' ? "anonymous" : "function", - "name": "req", - "type": "httpTrigger", - "direction": "in", - "methods": [metadata.method], - "route": pathParts.join('/') - }, - { - "name": "res", - "type": "http", - "direction": "out" - } - ] - } + resourceDefinition.properties.config.bindings = [ + ...resourceDefinition.properties.config.bindings, + { + "authLevel": metadata.authLevel, + "name": "req", + "type": "httpTrigger", + "direction": "in", + "methods": metadata.methods, + "route": metadata.route + }, + { + "name": "res", + "type": "http", + "direction": "out" + } + ] } export const functionFiles = async (context) => { diff --git a/src/index.ts b/src/index.ts index f8efb00..6794b56 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ export { Service, FunctionalApi, FunctionalService, DynamoDB, SimpleNotificationService, S3Storage, ApiGateway, callExtension } from './classes' -export { addProvider, removeProvider, Provider, AWSProvider, LocalProvider, DeployProvider } from './providers' +export { addProvider, removeProvider, Provider, AWSProvider, LocalProvider } from './providers' import * as _annotations from './annotations' export const annotations = _annotations diff --git a/src/providers/deploy.ts b/src/providers/deploy.ts deleted file mode 100644 index 2bfb4e6..0000000 --- a/src/providers/deploy.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Provider } from './core/provider' - -export class DeployProvider extends Provider { -} - -export const provider = new DeployProvider() diff --git a/src/providers/index.ts b/src/providers/index.ts index 3563a54..d66623a 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -4,13 +4,11 @@ import { callExtension } from '../classes' export { Provider } from './core/provider' export { AWSProvider } from './aws' export { LocalProvider } from './local' -export { DeployProvider } from './deploy' export { AzureProvider } from './azure' import { Provider } from './core/provider' import { provider as aws } from './aws' import { provider as local } from './local' -import { provider as deploy } from './deploy' import { provider as azure } from './azure' const environments = {} @@ -28,7 +26,6 @@ export const removeProvider = (name) => { addProvider('aws', aws) addProvider('local', local) -addProvider('deploy', deploy) addProvider('azure', azure) export const getInvoker = (serviceType, params) => { diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index 21ac2d6..3a83b1f 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -5,12 +5,15 @@ import { CLASS_APIGATEWAYKEY, CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_ENVIRONMENTKEY, CLASS_NAMEKEY, CLASS_INJECTABLEKEY, CLASS_LOGKEY, CLASS_RUNTIMEKEY, CLASS_MEMORYSIZEKEY, CLASS_TIMEOUTKEY, CLASS_S3CONFIGURATIONKEY, CLASS_SNSCONFIGURATIONKEY, CLASS_TAGKEY, CLASS_ROLEKEY, CLASS_DESCRIPTIONKEY, - PARAMETER_PARAMKEY, CLASS_CLASSCONFIGKEY + PARAMETER_PARAMKEY, CLASS_CLASSCONFIGKEY, CLASS_HTTPTRIGGER } from '../src/annotations/constants' import { applyTemplates, templates } from '../src/annotations/templates' import { getFunctionParameters } from '../src/annotations/utils' import { getMetadata, getOwnMetadata } from '../src/annotations/metadata' -import { apiGateway } from '../src/annotations/classes/apiGateway' +import { expandableDecorator } from '../src/annotations/classes/expandableDecorator' +import { apiGateway } from '../src/annotations/classes/aws/apiGateway' +import { httpTrigger } from '../src/annotations/classes/azure/httpTrigger' +import { rest } from '../src/annotations/classes/rest' import { dynamoTable, __dynamoDBDefaults } from '../src/annotations/classes/dynamoTable' import { environment } from '../src/annotations/classes/environment' import { functionName, getFunctionName } from '../src/annotations/classes/functionName' @@ -117,6 +120,80 @@ describe('annotations', () => { }) describe("classes", () => { + describe("expandableDecorator", () => { + it("interface", () => { + const mockDecorator = expandableDecorator<{ name: string, p1?: number }>({ name: 'mockDecorator', defaultValues: { p1: 2 } }) + + expect(mockDecorator).to.be.a('function') + expect(mockDecorator).to.have.property('environmentKey', 'functionly:class:mockDecorator') + expect(mockDecorator).to.have.property('extension').to.be.a('function') + }) + it("environmentKey", () => { + const mockDecorator = expandableDecorator<{ name: string, p1?: number }>({ name: 'mockDecorator', defaultValues: { p1: 2 }, environmentKey: 'mockDecorator_environment_key' }) + + expect(mockDecorator).to.be.a('function') + expect(mockDecorator).to.have.property('environmentKey', 'mockDecorator_environment_key') + expect(mockDecorator).to.have.property('extension').to.be.a('function') + }) + it("default value", () => { + const mockDecorator = expandableDecorator<{ name: string, p1?: number }>({ name: 'mockDecorator', defaultValues: { p1: 2 } }) + + @mockDecorator({ name: 'n1' }) + class TestClass { } + + const value = getMetadata(mockDecorator.environmentKey, TestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('name', 'n1') + expect(metadata).to.have.property('p1', 2) + }) + it("configured value", () => { + const mockDecorator = expandableDecorator<{ name: string, p1?: number }>({ name: 'mockDecorator', defaultValues: { p1: 2 } }) + + @mockDecorator({ name: 'n1', p1: 3 }) + class TestClass { } + + const value = getMetadata(mockDecorator.environmentKey, TestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('name', 'n1') + expect(metadata).to.have.property('p1', 3) + }) + describe("extension", () => { + afterEach(() => { + delete process.env.FUNCTIONAL_ENVIRONMENT + }) + it("environment extension", () => { + let counter = 0 + const mockDecorator = expandableDecorator<{ name: string, p1?: number }>({ name: 'mockDecorator', defaultValues: { p1: 2 } }) + mockDecorator.extension('custom', (target, config) => { + expect(target).to.equal(TestClass) + expect(config).to.deep.equal({ name: 'n1', p1: 2 }) + counter++ + }) + + process.env.FUNCTIONAL_ENVIRONMENT = 'custom' + @mockDecorator({ name: 'n1' }) + class TestClass { } + + const value = getMetadata(mockDecorator.environmentKey, TestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('name', 'n1') + expect(metadata).to.have.property('p1', 2) + expect(counter).to.equal(1) + }) + }) + }) describe("apiGateway", () => { it("path", () => { @apiGateway({ path: '/v1/test' }) @@ -179,6 +256,131 @@ describe('annotations', () => { expect(metadata).to.have.property('authorization', 'NONE') }) }) + describe("httpTrigger", () => { + it("path", () => { + @httpTrigger({ route: '/v1/test' }) + class HttpTriggerTestClass { } + + const value = getMetadata(CLASS_HTTPTRIGGER, HttpTriggerTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('route', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['get']) + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('authLevel', 'function') + }) + it("method", () => { + @httpTrigger({ route: '/v1/test', methods: ['post'] }) + class HttpTriggerTestClass { } + + const value = getMetadata(CLASS_HTTPTRIGGER, HttpTriggerTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('route', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['post']) + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('authLevel', 'function') + }) + it("cors", () => { + @httpTrigger({ route: '/v1/test', cors: true }) + class HttpTriggerTestClass { } + + const value = getMetadata(CLASS_HTTPTRIGGER, HttpTriggerTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('route', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['get']) + expect(metadata).to.have.property('cors', true) + expect(metadata).to.have.property('authLevel', 'function') + }) + it("authorization", () => { + @httpTrigger({ route: '/v1/test', authLevel: 'anonymous' }) + class HttpTriggerTestClass { } + + const value = getMetadata(CLASS_HTTPTRIGGER, HttpTriggerTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + + expect(metadata).to.have.property('route', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['get']) + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('authLevel', 'anonymous') + }) + }) + describe("rest", () => { + it("path", () => { + @rest({ path: '/v1/test' }) + class ApiGatewayTestClass { } + + const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['get']) + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('anonymous', false) + }) + it("method", () => { + @rest({ path: '/v1/test', methods: ['post'] }) + class ApiGatewayTestClass { } + + const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['post']) + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('anonymous', false) + }) + it("cors", () => { + @rest({ path: '/v1/test', cors: true }) + class ApiGatewayTestClass { } + + const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['get']) + expect(metadata).to.have.property('cors', true) + expect(metadata).to.have.property('anonymous', false) + }) + it("authorization", () => { + @rest({ path: '/v1/test', anonymous: true }) + class ApiGatewayTestClass { } + + const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['get']) + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('anonymous', true) + }) + }) describe("dynamoTable", () => { it("tableName", () => { @dynamoTable({ tableName: 'mytablename' }) @@ -535,7 +737,7 @@ describe('annotations', () => { expect(config).to.deep.equal({ customValue: 'v1' }) }) - + it("classConfig inherited", () => { @classConfig({ customValue: 'v1' }) class ClassConfigTestClass { } From e395d5e29442eca4d710f7a70586dd162931fe6b Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Thu, 13 Jul 2017 13:33:24 +0200 Subject: [PATCH 037/196] ADD: azure injected function invoke; ADD: support for azure dropbox deploy; FIX: local run --- .../classes/expandableDecorator.ts | 1 - src/cli/commands/local.ts | 20 +++-- .../providers/azureARM/context/functions.ts | 31 +++---- src/cli/providers/azureARM/context/init.ts | 89 ++++++++++++------- src/cli/providers/azureARM/index.ts | 25 +++++- src/cli/utilities/local/file.ts | 24 +++-- .../azure/eventSources/httpTrigger.ts | 2 +- src/providers/azure/index.ts | 54 +++++++++-- src/providers/local.ts | 12 +-- test/invoker.tests.ts | 4 +- 10 files changed, 177 insertions(+), 85 deletions(-) diff --git a/src/annotations/classes/expandableDecorator.ts b/src/annotations/classes/expandableDecorator.ts index 62e2864..35e4ab6 100644 --- a/src/annotations/classes/expandableDecorator.ts +++ b/src/annotations/classes/expandableDecorator.ts @@ -23,7 +23,6 @@ export const expandableDecorator = function (decoratorConfig: { } const environment = process.env.FUNCTIONAL_ENVIRONMENT - console.log(`expandableDecorator '${decoratorConfig.name}' environment: ${environment}`) if (!environment || !environmentExtensions.has(environment)) { return; } diff --git a/src/cli/commands/local.ts b/src/cli/commands/local.ts index 6bc1dfa..e69c11e 100644 --- a/src/cli/commands/local.ts +++ b/src/cli/commands/local.ts @@ -2,29 +2,31 @@ import * as express from 'express' import * as bodyParser from 'body-parser' import * as cors from 'cors' -export default ({ createContext, annotations: { getMetadata, constants, getFunctionName }, projectConfig, requireValue, executor }) => { +export default ({ createContext, annotations: { getMetadata, constants, getFunctionName, rest }, projectConfig, requireValue, executor }) => { const startLocal = async (context) => { let app = express() app.use(bodyParser.json()) for (let serviceDefinition of context.publishedFunctions) { - let httpMetadata = getMetadata(constants.CLASS_APIGATEWAYKEY, serviceDefinition.service) + let httpMetadata = getMetadata(rest.environmentKey, serviceDefinition.service) || [] for (let event of httpMetadata) { const isLoggingEnabled = getMetadata(constants.CLASS_LOGKEY, serviceDefinition.service) - console.log(`${new Date().toISOString()} ${getFunctionName(serviceDefinition.service)} listening { path: '${event.path}', method: '${event.method}', cors: ${event.cors ? true : false} }`) + console.log(`${new Date().toISOString()} ${getFunctionName(serviceDefinition.service)} listening { path: '${event.path}', methods: '${event.methods}', cors: ${event.cors ? true : false} }`) if (event.cors) { app.use(event.path, cors()) } - app[event.method]( - event.path, - logMiddleware(isLoggingEnabled, serviceDefinition.service), - environmentConfigMiddleware(serviceDefinition.service), - serviceDefinition.invoker - ) + for(const method of event.methods){ + app[method]( + event.path, + logMiddleware(isLoggingEnabled, serviceDefinition.service), + environmentConfigMiddleware(serviceDefinition.service), + serviceDefinition.invoker + ) + } } } diff --git a/src/cli/providers/azureARM/context/functions.ts b/src/cli/providers/azureARM/context/functions.ts index fc35e67..66c12c4 100644 --- a/src/cli/providers/azureARM/context/functions.ts +++ b/src/cli/providers/azureARM/context/functions.ts @@ -4,17 +4,19 @@ import { getFunctionName, getMetadata, constants } from '../../../../annotations const { CLASS_HTTPTRIGGER, CLASS_ENVIRONMENTKEY } = constants import { ExecuteStep, executor } from '../../../context' import { writeFile, copyFile, removePath } from '../../../utilities/local/file' +import { addEnvironmentSetting } from './init' export const azureFunctions = ExecuteStep.register('AzureFunctions', async (context) => { const site = context.ARMTemplate.resources.find(r => r.type === "Microsoft.Web/sites") if (site) { const config = site.resources.find(r => r.type === "config") || { properties: {} } - const appsettings = site.properties.siteConfig.appSettings + + addEnvironmentSetting('FUNCION_APP_BASEURL', `[concat('https://', toLower(variables('functionAppName')), '.azurewebsites.net/api')]`, site) for (const serviceDefinition of context.publishedFunctions) { const funcname = getFunctionName(serviceDefinition.service) await executor({ - context: { ...context, serviceDefinition, site, appsettings }, + context: { ...context, serviceDefinition, site }, name: `Azure-ARM-Function-${funcname}`, method: azureFunction }) @@ -23,14 +25,11 @@ export const azureFunctions = ExecuteStep.register('AzureFunctions', async (cont }) export const azureFunction = async (context) => { - const { serviceDefinition, appsettings } = context + const { serviceDefinition, site } = context const environmentVariables = getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) || {} for (const key in environmentVariables) { - appsettings.push({ - name: key, - value: environmentVariables[key] - }) + addEnvironmentSetting(key, environmentVariables[key], site) } const httpMetadata = getMetadata(CLASS_HTTPTRIGGER, serviceDefinition.service) || [] @@ -82,6 +81,8 @@ export const azureFunctionEndpoint = async (context) => { export const functionBindings = async (context) => { const { serviceDefinition, metadata, resourceDefinition } = context + const route = metadata.route.split('/').filter(p => p).join('/') + resourceDefinition.properties.config.bindings = [ ...resourceDefinition.properties.config.bindings, { @@ -90,7 +91,7 @@ export const functionBindings = async (context) => { "type": "httpTrigger", "direction": "in", "methods": metadata.methods, - "route": metadata.route + "route": route }, { "name": "res", @@ -110,12 +111,13 @@ export const functionFiles = async (context) => { } export const persistAzureGithubRepo = ExecuteStep.register('PersistAzureGithubRepo', async (context) => { + const { deploymentFolder } = context const site = context.ARMTemplate.resources.find(r => r.type === "Microsoft.Web/sites") if (site) { - removePath('azuregithubrepo') + removePath(context.projectName || `functionly`) for (const file of context.files) { - copyFile(file, join('azuregithubrepo', basename(file))) + copyFile(file, join(context.projectName || `functionly`, basename(file)), deploymentFolder) } const functions = site.resources.filter(f => f.type === 'functions') @@ -130,16 +132,15 @@ export const persistAzureGithubRepo = ExecuteStep.register('PersistAzureGithubRe }) export const persistFunction = (context) => { - const { functionResource, site } = context - const basePath = join('azuregithubrepo', functionResource.name) - + const { functionResource, site, deploymentFolder } = context + const basePath = join(context.projectName || `functionly`, functionResource.name) for (const file in functionResource.properties.files) { - writeFile(join(basePath, file), new Buffer(functionResource.properties.files[file], 'utf8')) + writeFile(join(basePath, file), new Buffer(functionResource.properties.files[file], 'utf8'), deploymentFolder) } const bindings = JSON.stringify(functionResource.properties.config, null, 4) - writeFile(join(basePath, 'function.json'), new Buffer(bindings, 'utf8')) + writeFile(join(basePath, 'function.json'), new Buffer(bindings, 'utf8'), deploymentFolder) const idx = site.resources.indexOf(functionResource) site.resources.splice(idx, 1) diff --git a/src/cli/providers/azureARM/context/init.ts b/src/cli/providers/azureARM/context/init.ts index 904500c..3343ee7 100644 --- a/src/cli/providers/azureARM/context/init.ts +++ b/src/cli/providers/azureARM/context/init.ts @@ -32,29 +32,13 @@ export const ARMInit = ExecuteStep.register('ARMInit', async (context) => { "metadata": { "description": "Storage Account type" } - }, - "gitUrl": { - "type": "string", - // "defaultValue": "https://github.com/borzav/azure-function-test", - "metadata": { - "description": "Git repository URL" - } - }, - "branch": { - "type": "string", - // "defaultValue": "master", - "metadata": { - "description": "Git repository branch" - } } }, "variables": { "functionAppName": "[parameters('functionAppName')]", "hostingPlanName": "[parameters('functionAppName')]", "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'azfunctions')]", - "storageAccountid": "[concat(resourceGroup().id,'/providers/','Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]", - "gitRepoUrl": "[parameters('gitUrl')]", - "gitBranch": "[parameters('branch')]" + "storageAccountid": "[concat(resourceGroup().id,'/providers/','Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]" }, "resources": [ { @@ -118,21 +102,7 @@ export const ARMInit = ExecuteStep.register('ARMInit', async (context) => { ] } }, - "resources": [ - { - "apiVersion": "2015-08-01", - "name": "web", - "type": "sourcecontrols", - "dependsOn": [ - "[resourceId('Microsoft.Web/Sites', parameters('functionAppName'))]" - ], - "properties": { - "RepoUrl": "[variables('gitRepoUrl')]", - "branch": "[variables('gitBranch')]", - "IsManualIntegration": true - } - } - ] + "resources": [] } ] } @@ -143,4 +113,57 @@ export const ARMInit = ExecuteStep.register('ARMInit', async (context) => { export const ARMMerge = ExecuteStep.register('ARMAfterCreateDefaults', async (context) => { defaultsDeep(context.ARMTemplate, projectConfig.ARMTemplate || {}) defaultsDeep(context.ARMStacks, projectConfig.ARMStacksTemplate || {}) -}) \ No newline at end of file +}) + +export const initGitTemplate = (context) => { + const { site } = context + + if (context.ARMConfig.deploymentOptions === 'git') { + const gitUrl = context.ARMTemplate.parameters.gitUrl || {} + context.ARMTemplate.parameters.gitUrl = { + ...gitUrl, + "type": "string", + // "defaultValue": "https://github.com/borzav/azure-function-test", + "metadata": { + "description": "Git repository URL" + } + } + + const branch = context.ARMTemplate.parameters.branch || {} + context.ARMTemplate.parameters.branch = { + ...branch, + "type": "string", + // "defaultValue": "master", + "metadata": { + "description": "Git repository branch" + } + } + + context.ARMTemplate.variables.gitRepoUrl = "[parameters('gitUrl')]" + context.ARMTemplate.variables.gitBranch = "[parameters('branch')]" + + const site = context.ARMTemplate.resources.find(r => r.type === 'Microsoft.Web/sites') + if (site) { + site.resources.push({ + "apiVersion": "2015-08-01", + "name": "web", + "type": "sourcecontrols", + "dependsOn": [ + "[resourceId('Microsoft.Web/Sites', parameters('functionAppName'))]" + ], + "properties": { + "RepoUrl": "[variables('gitRepoUrl')]", + "branch": "[variables('gitBranch')]", + "IsManualIntegration": true + } + }) + } + } +} + +export const addEnvironmentSetting = (name, value, site) => { + const exists = site.properties.siteConfig.appSettings.find(s => s.name === name && s.value === value) + if (!exists) { + site.properties.siteConfig.appSettings.push({ name, value }) + } +} \ No newline at end of file diff --git a/src/cli/providers/azureARM/index.ts b/src/cli/providers/azureARM/index.ts index 054aa6d..797a38a 100644 --- a/src/cli/providers/azureARM/index.ts +++ b/src/cli/providers/azureARM/index.ts @@ -6,14 +6,31 @@ import { writeFile } from '../../utilities/local/file' import { projectConfig } from '../../project/config' import { executor } from '../../context' -import { ARMInit, ARMMerge } from './context/init' +import { ARMInit, ARMMerge, initGitTemplate } from './context/init' import { azureFunctions, persistAzureGithubRepo } from './context/functions' export const azure = { FUNCTIONAL_ENVIRONMENT: 'azure', createEnvironment: async (context) => { - throw new Error('deploy not implemented, use package command') + logger.info(`Functionly: Packgaging...`) + await executor(context, bundle) + await executor(context, zip) + + await executor(context, ARMInit) + + + await executor(context, ARMMerge) + await executor(context, azureFunctions) + await executor(context, initGitTemplate) + + + logger.info(`Functionly: Create project...`) + await executor({ ...context, deploymentFolder: projectConfig.ARM.deploymentFolder }, persistAzureGithubRepo) + logger.info(`Functionly: Save template...`) + await executor(context, persistFile) + + logger.info(`Functionly: Complete`) }, package: async (context) => { logger.info(`Functionly: Packgaging...`) @@ -25,10 +42,12 @@ export const azure = { await executor(context, ARMMerge) await executor(context, azureFunctions) + await executor(context, initGitTemplate) - logger.info(`Functionly: Save template...`) + logger.info(`Functionly: Create project...`) await executor(context, persistAzureGithubRepo) + logger.info(`Functionly: Save template...`) await executor(context, persistFile) logger.info(`Functionly: Complete`) diff --git a/src/cli/utilities/local/file.ts b/src/cli/utilities/local/file.ts index 65389d1..11d714a 100644 --- a/src/cli/utilities/local/file.ts +++ b/src/cli/utilities/local/file.ts @@ -4,27 +4,33 @@ import { writeFileSync, mkdirSync, existsSync } from 'fs' import { ensureDirSync, copySync, removeSync } from 'fs-extra' import { join, normalize, dirname } from 'path' -export const writeFile = (fileName, binary) => { - if (config.tempDirectory) { - const filePath = join(config.tempDirectory, fileName) +export const writeFile = (fileName, binary, basePath?) => { + if (!basePath) basePath = config.tempDirectory + + if (basePath) { + const filePath = join(basePath, fileName) const dirPath = dirname(filePath) ensureDirSync(dirPath) writeFileSync(filePath, binary) } } -export const copyFile = (from, to) => { - if (config.tempDirectory) { - const destinationFilePath = join(config.tempDirectory, to) +export const copyFile = (from, to, basePath?) => { + if (!basePath) basePath = config.tempDirectory + + if (basePath) { + const destinationFilePath = join(basePath, to) const dirPath = dirname(destinationFilePath) ensureDirSync(dirPath) copySync(from, destinationFilePath) } } -export const removePath = (path) => { - if (config.tempDirectory) { - const targetFilePath = join(config.tempDirectory, path) +export const removePath = (path, basePath?) => { + if (!basePath) basePath = config.tempDirectory + + if (basePath) { + const targetFilePath = join(basePath, path) removeSync(targetFilePath) } } diff --git a/src/providers/azure/eventSources/httpTrigger.ts b/src/providers/azure/eventSources/httpTrigger.ts index 2c60408..9467e3b 100644 --- a/src/providers/azure/eventSources/httpTrigger.ts +++ b/src/providers/azure/eventSources/httpTrigger.ts @@ -44,7 +44,7 @@ export class HttpTrigger extends EventSource { return { status: 200, - body: JSON.stringify(result) + body: result } } } \ No newline at end of file diff --git a/src/providers/azure/index.ts b/src/providers/azure/index.ts index 82edfe2..d4a432f 100644 --- a/src/providers/azure/index.ts +++ b/src/providers/azure/index.ts @@ -1,11 +1,16 @@ import { Provider } from '../core/provider' -import { getFunctionName } from '../../annotations' +import { getFunctionName, constants, getMetadata } from '../../annotations' +const { CLASS_HTTPTRIGGER } = constants import { HttpTrigger } from './eventSources/httpTrigger' +import * as request from 'request' const eventSourceHandlers = [ new HttpTrigger() ] +export const FUNCTIONLY_FUNCTION_KEY = 'FUNCTIONLY_FUNCTION_KEY' + + export class AzureProvider extends Provider { public getInvoker(serviceType, serviceInstance, params): Function { const parameters = this.getParameters(serviceType, 'handle') @@ -54,15 +59,50 @@ export class AzureProvider extends Provider { public async invoke(serviceInstance, params, invokeConfig?) { return new Promise((resolve, reject) => { - const funcName = getFunctionName(serviceInstance) - const resolvedFuncName = process.env[`FUNCTIONAL_SERVICE_${funcName.toUpperCase()}`] || funcName + const httpAttr = (getMetadata(CLASS_HTTPTRIGGER, serviceInstance) || [])[0] + if (!httpAttr) { + return reject(new Error('missing http configuration')) + } - const invokeParams = { - FunctionName: resolvedFuncName, - Payload: JSON.stringify(params) + const method = httpAttr.methods[0] || 'GET' + const invokeParams: any = { + method, + url: `${process.env.FUNCION_APP_BASEURL}${httpAttr.route}`, }; - // TODO invoke + if (method.toLowerCase() === 'get') { + invokeParams.qs = params + } else { + invokeParams.body = params + invokeParams.json = true + } + + if (httpAttr.authLevel !== 'anonymous') { + if (!process.env.FUNCTIONLY_FUNCTION_KEY) { + return reject(new Error(`process.env.FUNCTIONLY_FUNCTION_KEY is not set, create host key to all functions`)) + } + invokeParams.qs = { ...(invokeParams.qs || {}), code: process.env.FUNCTIONLY_FUNCTION_KEY } + } + + try { + + request(invokeParams, (error, response, body) => { + + if (error) return reject(error) + + let result = body + try { + result = JSON.parse(result) + } + catch (e) { } + + return resolve(result) + + }) + + } catch (e) { + return reject(e); + } }) } } diff --git a/src/providers/local.ts b/src/providers/local.ts index 0509374..ced30ad 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -1,6 +1,7 @@ import * as request from 'request' import { Provider } from './core/provider' -import { constants, getOwnMetadata, getMetadata, getFunctionName } from '../annotations' +import { constants, getOwnMetadata, getMetadata, getFunctionName, rest } from '../annotations' +const { CLASS_LOGKEY } = constants import { get } from '../helpers/property' export class LocalProvider extends Provider { @@ -51,17 +52,18 @@ export class LocalProvider extends Provider { public async invoke(serviceInstance, params, invokeConfig?) { return new Promise((resolve, reject) => { - const httpAttr = getMetadata(constants.CLASS_APIGATEWAYKEY, serviceInstance)[0] + const httpAttr = (getMetadata(rest.environmentKey, serviceInstance) || [])[0] if (!httpAttr) { return reject(new Error('missing http configuration')) } + const method = httpAttr.methods[0] || 'GET' const invokeParams: any = { - method: httpAttr.method || 'GET', + method, url: `http://localhost:${process.env.FUNCTIONAL_LOCAL_PORT}${httpAttr.path}`, }; - if (!httpAttr.method || httpAttr.method.toLowerCase() === 'get') { + if (method.toLowerCase() === 'get') { invokeParams.qs = params } else { invokeParams.body = params @@ -70,7 +72,7 @@ export class LocalProvider extends Provider { try { - const isLoggingEnabled = getMetadata(constants.CLASS_LOGKEY, serviceInstance) + const isLoggingEnabled = getMetadata(CLASS_LOGKEY, serviceInstance) if (isLoggingEnabled) { console.log(`${new Date().toISOString()} request to ${getFunctionName(serviceInstance)}`, JSON.stringify(invokeParams, null, 2)) } diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index 4aeaab0..4a5e909 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -993,7 +993,7 @@ describe('invoker', () => { expect(counter).to.equal(1) expect(context).to.have.nested.property('res.status', 200) - expect(context).to.have.nested.property('res.body', JSON.stringify({ ok: 1 })) + expect(context).to.have.nested.property('res.body').that.deep.equal({ ok: 1 }) }) it("handler throw error", async () => { @@ -1236,7 +1236,7 @@ describe('invoker', () => { expect(counter).to.equal(1) expect(context).to.have.nested.property('res.status', 200) - expect(context).to.have.nested.property('res.body', JSON.stringify({ ok: 1 })) + expect(context).to.have.nested.property('res.body').that.deep.equal({ ok: 1 }) }) it("httpTrigger return value advanced", async () => { From 2136511d9f2671b02fd32f61897bfab6892feeee Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Fri, 14 Jul 2017 11:41:57 +0200 Subject: [PATCH 038/196] ADD: inject invoke unit tests --- src/providers/aws/index.ts | 19 +- src/providers/azure/index.ts | 51 +-- src/providers/core/provider.ts | 4 + src/providers/local.ts | 50 ++- test/invoke.tests.ts | 745 +++++++++++++++++++++++++++++++++ 5 files changed, 814 insertions(+), 55 deletions(-) create mode 100644 test/invoke.tests.ts diff --git a/src/providers/aws/index.ts b/src/providers/aws/index.ts index 2616472..4b7c768 100644 --- a/src/providers/aws/index.ts +++ b/src/providers/aws/index.ts @@ -70,17 +70,20 @@ export class AWSProvider extends Provider { public async invoke(serviceInstance, params, invokeConfig?) { initAWSSDK() - return new Promise((resolve, reject) => { + const funcName = getFunctionName(serviceInstance) + const resolvedFuncName = process.env[`FUNCTIONAL_SERVICE_${funcName.toUpperCase()}`] || funcName - const funcName = getFunctionName(serviceInstance) - const resolvedFuncName = process.env[`FUNCTIONAL_SERVICE_${funcName.toUpperCase()}`] || funcName + const invokeParams = { + FunctionName: resolvedFuncName, + Payload: JSON.stringify(params) + }; - const invokeParams = { - FunctionName: resolvedFuncName, - Payload: JSON.stringify(params) - }; + return await this.invokeExec(invokeParams) + } - lambda.invoke(invokeParams, function (err, data) { + public async invokeExec(config: any): Promise { + return new Promise((resolve, reject) => { + lambda.invoke(config, function (err, data) { if (err) reject(err) else resolve(JSON.parse(data.Payload.toString())); }); diff --git a/src/providers/azure/index.ts b/src/providers/azure/index.ts index d4a432f..8b86fbe 100644 --- a/src/providers/azure/index.ts +++ b/src/providers/azure/index.ts @@ -57,36 +57,39 @@ export class AzureProvider extends Provider { } public async invoke(serviceInstance, params, invokeConfig?) { - return new Promise((resolve, reject) => { - const httpAttr = (getMetadata(CLASS_HTTPTRIGGER, serviceInstance) || [])[0] - if (!httpAttr) { - return reject(new Error('missing http configuration')) - } + const httpAttr = (getMetadata(CLASS_HTTPTRIGGER, serviceInstance) || [])[0] + if (!httpAttr) { + throw new Error('missing http configuration') + } - const method = httpAttr.methods[0] || 'GET' - const invokeParams: any = { - method, - url: `${process.env.FUNCION_APP_BASEURL}${httpAttr.route}`, - }; - - if (method.toLowerCase() === 'get') { - invokeParams.qs = params - } else { - invokeParams.body = params - invokeParams.json = true - } + const method = httpAttr.methods[0] || 'GET' + const invokeParams: any = { + method, + url: `${process.env.FUNCION_APP_BASEURL}${httpAttr.route}`, + }; + + if (method.toLowerCase() === 'get') { + invokeParams.qs = params + } else { + invokeParams.body = params + invokeParams.json = true + } - if (httpAttr.authLevel !== 'anonymous') { - if (!process.env.FUNCTIONLY_FUNCTION_KEY) { - return reject(new Error(`process.env.FUNCTIONLY_FUNCTION_KEY is not set, create host key to all functions`)) - } - invokeParams.qs = { ...(invokeParams.qs || {}), code: process.env.FUNCTIONLY_FUNCTION_KEY } + if (httpAttr.authLevel !== 'anonymous') { + if (!process.env.FUNCTIONLY_FUNCTION_KEY) { + throw new Error(`process.env.FUNCTIONLY_FUNCTION_KEY is not set, create host key to all functions`) } + invokeParams.qs = { ...(invokeParams.qs || {}), code: process.env.FUNCTIONLY_FUNCTION_KEY } + } - try { + return await this.invokeExec(invokeParams) + } - request(invokeParams, (error, response, body) => { + public async invokeExec(config: any): Promise { + return new Promise((resolve, reject) => { + try { + request(config, (error, response, body) => { if (error) return reject(error) diff --git a/src/providers/core/provider.ts b/src/providers/core/provider.ts index d5441f8..ce44626 100644 --- a/src/providers/core/provider.ts +++ b/src/providers/core/provider.ts @@ -11,6 +11,10 @@ export abstract class Provider { } + public async invokeExec(config: any): Promise { + + } + protected async parameterResolver(parameter, event): Promise { switch (parameter.type) { diff --git a/src/providers/local.ts b/src/providers/local.ts index ced30ad..827f3f4 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -50,34 +50,38 @@ export class LocalProvider extends Provider { } public async invoke(serviceInstance, params, invokeConfig?) { - return new Promise((resolve, reject) => { - const httpAttr = (getMetadata(rest.environmentKey, serviceInstance) || [])[0] - if (!httpAttr) { - return reject(new Error('missing http configuration')) - } + const httpAttr = (getMetadata(rest.environmentKey, serviceInstance) || [])[0] + if (!httpAttr) { + throw new Error('missing http configuration') + } - const method = httpAttr.methods[0] || 'GET' - const invokeParams: any = { - method, - url: `http://localhost:${process.env.FUNCTIONAL_LOCAL_PORT}${httpAttr.path}`, - }; - - if (method.toLowerCase() === 'get') { - invokeParams.qs = params - } else { - invokeParams.body = params - invokeParams.json = true - } + const method = httpAttr.methods[0] || 'GET' + const invokeParams: any = { + method, + url: `http://localhost:${process.env.FUNCTIONAL_LOCAL_PORT}${httpAttr.path}`, + }; + + if (method.toLowerCase() === 'get') { + invokeParams.qs = params + } else { + invokeParams.body = params + invokeParams.json = true + } - try { - const isLoggingEnabled = getMetadata(CLASS_LOGKEY, serviceInstance) - if (isLoggingEnabled) { - console.log(`${new Date().toISOString()} request to ${getFunctionName(serviceInstance)}`, JSON.stringify(invokeParams, null, 2)) - } + const isLoggingEnabled = getMetadata(CLASS_LOGKEY, serviceInstance) + if (isLoggingEnabled) { + console.log(`${new Date().toISOString()} request to ${getFunctionName(serviceInstance)}`, JSON.stringify(invokeParams, null, 2)) + } + + return await this.invokeExec(invokeParams) + } - request(invokeParams, (error, response, body) => { + public async invokeExec(config: any): Promise { + return new Promise((resolve, reject) => { + try { + request(config, (error, response, body) => { if (error) return reject(error) diff --git a/test/invoke.tests.ts b/test/invoke.tests.ts new file mode 100644 index 0000000..c5e6b76 --- /dev/null +++ b/test/invoke.tests.ts @@ -0,0 +1,745 @@ +import { expect } from 'chai' + +import { AzureProvider } from '../src/providers/azure' +import { LocalProvider } from '../src/providers/local' +import { AWSProvider } from '../src/providers/aws' +import { addProvider, removeProvider } from '../src/providers' +import { FunctionalService } from '../src/classes/functionalService' +import { httpTrigger, inject, injectable, param, rest } from '../src/annotations' + +describe('invoke', () => { + const FUNCTIONAL_ENVIRONMENT = 'custom' + + describe('general', () => { + beforeEach(() => { + process.env.FUNCTIONAL_ENVIRONMENT = FUNCTIONAL_ENVIRONMENT + }) + afterEach(() => { + delete process.env.FUNCTIONAL_ENVIRONMENT + removeProvider(FUNCTIONAL_ENVIRONMENT) + }) + + it("no params", async () => { + let counter = 0 + + class TestProvider extends LocalProvider { + public async invoke(serviceInstance, params, invokeConfig?) { + counter++ + + expect(serviceInstance).to.instanceof(A) + expect(params).to.have.deep.equal({}) + expect(invokeConfig).is.undefined + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @injectable + class A extends FunctionalService { + public async handle() { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({}) + } + } + + const invoker = B.createInvoker() + await invoker({}, { + send: () => { counter++ } + }, (e) => { expect(false).to.equal(true, e.message) }) + + expect(counter).to.equal(3) + }) + + it("with params", async () => { + let counter = 0 + + class TestProvider extends LocalProvider { + public async invoke(serviceInstance, params, invokeConfig?) { + counter++ + + expect(serviceInstance).to.instanceof(A) + expect(params).to.have.deep.equal({ p1: 'p1' }) + expect(invokeConfig).is.undefined + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @injectable + class A extends FunctionalService { + public async handle( @param p1) { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({ p1: 'p1' }) + } + } + + const invoker = B.createInvoker() + await invoker({}, { + send: () => { counter++ } + }, (e) => { expect(false).to.equal(true, e.message) }) + + expect(counter).to.equal(3) + }) + + it("additional property that not listed as param", async () => { + let counter = 0 + + class TestProvider extends LocalProvider { + public async invoke(serviceInstance, params, invokeConfig?) { + counter++ + + expect(serviceInstance).to.instanceof(A) + expect(params).to.have.deep.equal({ p1: 'p1' }) + expect(invokeConfig).is.undefined + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @injectable + class A extends FunctionalService { + public async handle( @param p1) { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({ p1: 'p1', p2: 'p2' }) + } + } + + const invoker = B.createInvoker() + await invoker({}, { + send: () => { counter++ } + }, (e) => { expect(false).to.equal(true, e.message) }) + + expect(counter).to.equal(3) + }) + + it("invokeConfig", async () => { + let counter = 0 + + class TestProvider extends LocalProvider { + public async invoke(serviceInstance, params, invokeConfig?) { + counter++ + + expect(serviceInstance).to.instanceof(A) + expect(params).to.deep.equal({}) + expect(invokeConfig).to.deep.equal({ a: 1, b: 2, c: 3 }) + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @injectable + class A extends FunctionalService { + public async handle() { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({}, { a: 1, b: 2, c: 3 }) + } + } + + const invoker = B.createInvoker() + await invoker({}, { + send: () => { counter++ } + }, (e) => { expect(false).to.equal(true, e.message) }) + + expect(counter).to.equal(3) + }) + + it("unknown environment", async () => { + let counter = 0 + + class TestProvider extends LocalProvider { + public async invoke(serviceInstance, params, invokeConfig?) { + counter++ + + expect(false).to.equal(true, 'unknown provider have to select') + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @injectable + class A extends FunctionalService { + public async handle() { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({}) + } + } + + const invoker = B.createInvoker() + process.env.FUNCTIONAL_ENVIRONMENT = 'unknown' + await invoker({}, { + send: () => { expect(false).to.equal(true, 'error required') } + }, (e) => { + counter++ + expect(e.message).to.equal("missing environment: 'unknown' for invoke") + }) + + expect(counter).to.equal(2) + }) + + it("unknown environment mode", async () => { + let counter = 0 + + class TestProvider extends LocalProvider { + public async invoke(serviceInstance, params, invokeConfig?) { + counter++ + + expect(false).to.equal(true, 'unknown provider have to select') + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @injectable + class A extends FunctionalService { + public async handle() { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({}, { mode: 'unknown' }) + } + } + + const invoker = B.createInvoker() + await invoker({}, { + send: () => { expect(false).to.equal(true, 'error required') } + }, (e) => { + counter++ + expect(e.message).to.equal("missing environment: 'unknown' for invoke") + }) + + expect(counter).to.equal(2) + }) + }) + + describe('azure', () => { + const FUNCTIONLY_FUNCTION_KEY = 'xxxxx' + const FUNCION_APP_BASEURL = 'http://example/api' + + beforeEach(() => { + process.env.FUNCTIONAL_ENVIRONMENT = FUNCTIONAL_ENVIRONMENT + process.env.FUNCTIONLY_FUNCTION_KEY = FUNCTIONLY_FUNCTION_KEY + process.env.FUNCION_APP_BASEURL = FUNCION_APP_BASEURL + }) + afterEach(() => { + delete process.env.FUNCTIONAL_ENVIRONMENT + delete process.env.FUNCTIONLY_FUNCTION_KEY + delete process.env.FUNCION_APP_BASEURL + removeProvider(FUNCTIONAL_ENVIRONMENT) + }) + + it("route get /v1/a1", async () => { + let counter = 0 + + class TestProvider extends AzureProvider { + public async invokeExec(config: any): Promise { + counter++ + + expect(config).to.have.property('method', 'get') + expect(config).to.have.property('url', `${FUNCION_APP_BASEURL}/v1/a1`) + expect(config).to.have.property('qs').that.deep.equal({ code: FUNCTIONLY_FUNCTION_KEY, p1: 'p1' }) + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @httpTrigger({ route: '/v1/a1' }) + @injectable + class A extends FunctionalService { + public async handle( @param p1) { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({ p1: 'p1' }) + } + } + + const invoker = B.createInvoker() + const context: any = {} + await invoker(context, {}) + + expect(context).to.have.nested.property('res.status', 200, context.res.body) + expect(counter).to.equal(2) + }) + + it("route post /v1/a1", async () => { + let counter = 0 + + class TestProvider extends AzureProvider { + public async invokeExec(config: any): Promise { + counter++ + + expect(config).to.have.property('method', 'post') + expect(config).to.have.property('url', `${FUNCION_APP_BASEURL}/v1/a1`) + expect(config).to.have.property('qs').that.deep.equal({ code: FUNCTIONLY_FUNCTION_KEY }) + expect(config).to.have.property('body').that.deep.equal({ p1: 'p1' }) + expect(config).to.have.property('json', true) + + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @httpTrigger({ route: '/v1/a1', methods: ['post'] }) + @injectable + class A extends FunctionalService { + public async handle( @param p1) { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({ p1: 'p1' }) + } + } + + const invoker = B.createInvoker() + const context: any = {} + await invoker(context, {}) + + expect(context).to.have.nested.property('res.status', 200, context.res.body) + expect(counter).to.equal(2) + }) + + it("multiple method get/post", async () => { + let counter = 0 + + class TestProvider extends AzureProvider { + public async invokeExec(config: any): Promise { + counter++ + + expect(config).to.have.property('method', 'get') + expect(config).to.have.property('url', `${FUNCION_APP_BASEURL}/v1/a1`) + expect(config).to.have.property('qs').that.deep.equal({ code: FUNCTIONLY_FUNCTION_KEY, p1: 'p1' }) + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @httpTrigger({ route: '/v1/a1', methods: ['get', 'post'] }) + @injectable + class A extends FunctionalService { + public async handle( @param p1) { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({ p1: 'p1' }) + } + } + + const invoker = B.createInvoker() + const context: any = {} + await invoker(context, {}) + + expect(context).to.have.nested.property('res.status', 200, context.res.body) + expect(counter).to.equal(2) + }) + + it("multiple method post/get", async () => { + let counter = 0 + + class TestProvider extends AzureProvider { + public async invokeExec(config: any): Promise { + counter++ + + expect(config).to.have.property('method', 'post') + expect(config).to.have.property('url', `${FUNCION_APP_BASEURL}/v1/a1`) + expect(config).to.have.property('qs').that.deep.equal({ code: FUNCTIONLY_FUNCTION_KEY }) + expect(config).to.have.property('body').that.deep.equal({ p1: 'p1' }) + expect(config).to.have.property('json', true) + + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @httpTrigger({ route: '/v1/a1', methods: ['post', 'get'] }) + @injectable + class A extends FunctionalService { + public async handle( @param p1) { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({ p1: 'p1' }) + } + } + + const invoker = B.createInvoker() + const context: any = {} + await invoker(context, {}) + + expect(context).to.have.nested.property('res.status', 200, context.res.body) + expect(counter).to.equal(2) + }) + + it("get anonymous", async () => { + let counter = 0 + + class TestProvider extends AzureProvider { + public async invokeExec(config: any): Promise { + counter++ + + expect(config).to.have.property('method', 'get') + expect(config).to.have.property('url', `${FUNCION_APP_BASEURL}/v1/a1`) + expect(config).to.have.property('qs').that.deep.equal({ p1: 'p1' }) + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @httpTrigger({ route: '/v1/a1', authLevel: 'anonymous' }) + @injectable + class A extends FunctionalService { + public async handle( @param p1) { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({ p1: 'p1' }) + } + } + + const invoker = B.createInvoker() + const context: any = {} + await invoker(context, {}) + + expect(context).to.have.nested.property('res.status', 200, context.res.body) + expect(counter).to.equal(2) + }) + + it("post anonymous", async () => { + let counter = 0 + + class TestProvider extends AzureProvider { + public async invokeExec(config: any): Promise { + counter++ + + expect(config).to.have.property('method', 'post') + expect(config).to.have.property('url', `${FUNCION_APP_BASEURL}/v1/a1`) + expect(config).to.not.have.property('qs') + expect(config).to.have.property('body').that.deep.equal({ p1: 'p1' }) + expect(config).to.have.property('json', true) + + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @httpTrigger({ route: '/v1/a1', methods: ['post'], authLevel: 'anonymous' }) + @injectable + class A extends FunctionalService { + public async handle( @param p1) { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({ p1: 'p1' }) + } + } + + const invoker = B.createInvoker() + const context: any = {} + await invoker(context, {}) + + expect(context).to.have.nested.property('res.status', 200, context.res.body) + expect(counter).to.equal(2) + }) + + it("authLevel function no function key", async () => { + let counter = 0 + delete process.env.FUNCTIONLY_FUNCTION_KEY + + class TestProvider extends AzureProvider { + public async invokeExec(config: any): Promise { + counter++ + + expect(config).to.have.property('method', 'get') + expect(config).to.have.property('url', `${FUNCION_APP_BASEURL}/v1/a1`) + expect(config).to.have.property('qs').that.deep.equal({ p1: 'p1' }) + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @httpTrigger({ route: '/v1/a1' }) + @injectable + class A extends FunctionalService { + public async handle( @param p1) { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({ p1: 'p1' }) + } + } + + const invoker = B.createInvoker() + const context: any = {} + await invoker(context, {}) + + expect(context).to.have.nested.property('res.status', 500, context.res.body) + expect(context).to.have.nested.property('res.body').that.contains('process.env.FUNCTIONLY_FUNCTION_KEY is not set') + expect(counter).to.equal(1) + }) + + it("authLevel anonymous no function key", async () => { + let counter = 0 + delete process.env.FUNCTIONLY_FUNCTION_KEY + + class TestProvider extends AzureProvider { + public async invokeExec(config: any): Promise { + counter++ + + expect(config).to.have.property('method', 'get') + expect(config).to.have.property('url', `${FUNCION_APP_BASEURL}/v1/a1`) + expect(config).to.have.property('qs').that.deep.equal({ p1: 'p1' }) + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @httpTrigger({ route: '/v1/a1', authLevel: 'anonymous' }) + @injectable + class A extends FunctionalService { + public async handle( @param p1) { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({ p1: 'p1' }) + } + } + + const invoker = B.createInvoker() + const context: any = {} + await invoker(context, {}) + + expect(context).to.have.nested.property('res.status', 200, context.res.body) + expect(counter).to.equal(2) + }) + + }) + + describe('local', () => { + const APP_BASE_URL = 'http://localhost:3000' + const FUNCTIONAL_LOCAL_PORT = 3000 + + beforeEach(() => { + process.env.FUNCTIONAL_ENVIRONMENT = FUNCTIONAL_ENVIRONMENT + process.env.FUNCTIONAL_LOCAL_PORT = FUNCTIONAL_LOCAL_PORT + }) + afterEach(() => { + delete process.env.FUNCTIONAL_ENVIRONMENT + delete process.env.FUNCTIONAL_LOCAL_PORT + removeProvider(FUNCTIONAL_ENVIRONMENT) + }) + + it("route get /v1/a1", async () => { + let counter = 0 + + class TestProvider extends LocalProvider { + public async invokeExec(config: any): Promise { + counter++ + + expect(config).to.have.property('method', 'get') + expect(config).to.have.property('url', `${APP_BASE_URL}/v1/a1`) + expect(config).to.have.property('qs').that.deep.equal({ p1: 'p1' }) + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @rest({ path: '/v1/a1' }) + @injectable + class A extends FunctionalService { + public async handle( @param p1) { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({ p1: 'p1' }) + } + } + + const invoker = B.createInvoker() + await invoker({}, { + send: () => { counter++ } + }, (e) => { expect(false).to.equal(true, e.message) }) + + expect(counter).to.equal(3) + }) + + it("route post /v1/a1", async () => { + let counter = 0 + + class TestProvider extends LocalProvider { + public async invokeExec(config: any): Promise { + counter++ + + expect(config).to.have.property('method', 'post') + expect(config).to.have.property('url', `${APP_BASE_URL}/v1/a1`) + expect(config).to.have.not.property('qs') + expect(config).to.have.property('body').that.deep.equal({ p1: 'p1' }) + expect(config).to.have.property('json', true) + + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @rest({ path: '/v1/a1', methods: ['post'] }) + @injectable + class A extends FunctionalService { + public async handle( @param p1) { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({ p1: 'p1' }) + } + } + + const invoker = B.createInvoker() + await invoker({}, { + send: () => { counter++ } + }, (e) => { expect(false).to.equal(true, e.message) }) + + expect(counter).to.equal(3) + }) + + it("multiple method get/post", async () => { + let counter = 0 + + class TestProvider extends LocalProvider { + public async invokeExec(config: any): Promise { + counter++ + + expect(config).to.have.property('method', 'get') + expect(config).to.have.property('url', `${APP_BASE_URL}/v1/a1`) + expect(config).to.have.property('qs').that.deep.equal({ p1: 'p1' }) + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @rest({ path: '/v1/a1', methods: ['get', 'post'] }) + @injectable + class A extends FunctionalService { + public async handle( @param p1) { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({ p1: 'p1' }) + } + } + + const invoker = B.createInvoker() + await invoker({}, { + send: () => { counter++ } + }, (e) => { expect(false).to.equal(true, e.message) }) + + expect(counter).to.equal(3) + }) + + it("multiple method post/get", async () => { + let counter = 0 + + class TestProvider extends LocalProvider { + public async invokeExec(config: any): Promise { + counter++ + + expect(config).to.have.property('method', 'post') + expect(config).to.have.property('url', `${APP_BASE_URL}/v1/a1`) + expect(config).to.have.not.property('qs') + expect(config).to.have.property('body').that.deep.equal({ p1: 'p1' }) + expect(config).to.have.property('json', true) + + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @rest({ path: '/v1/a1', methods: ['post', 'get'] }) + @injectable + class A extends FunctionalService { + public async handle( @param p1) { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({ p1: 'p1' }) + } + } + + const invoker = B.createInvoker() + await invoker({}, { + send: () => { counter++ } + }, (e) => { expect(false).to.equal(true, e.message) }) + + expect(counter).to.equal(3) + }) + }) + + describe('aws', () => { + beforeEach(() => { + process.env.FUNCTIONAL_ENVIRONMENT = FUNCTIONAL_ENVIRONMENT + }) + afterEach(() => { + delete process.env.FUNCTIONAL_ENVIRONMENT + removeProvider(FUNCTIONAL_ENVIRONMENT) + }) + + it("lambda call", async () => { + let counter = 0 + + class TestProvider extends AWSProvider { + public async invokeExec(config: any): Promise { + counter++ + + expect(config).to.have.property('FunctionName', 'A') + expect(config).to.have.property('Payload', JSON.stringify({ p1: 'p1' })) + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @injectable + class A extends FunctionalService { + public async handle( @param p1) { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({ p1: 'p1' }) + } + } + + const invoker = B.createInvoker() + + await invoker({}, {}, (e, result) => { + counter++ + expect(!e).to.equal(true, e && e.message) + }) + + expect(counter).to.equal(3) + }) + }) +}) \ No newline at end of file From d0c3659d322bde55fa97e225354eaa827c3d4dc6 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 18 Jul 2017 12:14:59 +0200 Subject: [PATCH 039/196] CHANGE: root cli.js target --- cli.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli.js b/cli.js index ed89af5..acc6dd3 100644 --- a/cli.js +++ b/cli.js @@ -1 +1 @@ -module.exports = require('./lib/src/cli') \ No newline at end of file +module.exports = require('./lib/src/cli/cli') \ No newline at end of file From 5008f17d7b9759e014c430f3084fd4be0eaff662 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 18 Jul 2017 12:15:58 +0200 Subject: [PATCH 040/196] CHANGE: file persist to deployEnvironment folders --- src/cli/utilities/local/file.ts | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/cli/utilities/local/file.ts b/src/cli/utilities/local/file.ts index 11d714a..ecd7bbd 100644 --- a/src/cli/utilities/local/file.ts +++ b/src/cli/utilities/local/file.ts @@ -5,7 +5,13 @@ import { ensureDirSync, copySync, removeSync } from 'fs-extra' import { join, normalize, dirname } from 'path' export const writeFile = (fileName, binary, basePath?) => { - if (!basePath) basePath = config.tempDirectory + if (!basePath) { + basePath = config.tempDirectory + const mode = process.env.FUNCTIONAL_ENVIRONMENT + if (mode) { + basePath = join(basePath, mode) + } + } if (basePath) { const filePath = join(basePath, fileName) @@ -16,7 +22,13 @@ export const writeFile = (fileName, binary, basePath?) => { } export const copyFile = (from, to, basePath?) => { - if (!basePath) basePath = config.tempDirectory + if (!basePath) { + basePath = config.tempDirectory + const mode = process.env.FUNCTIONAL_ENVIRONMENT + if (mode) { + basePath = join(basePath, mode) + } + } if (basePath) { const destinationFilePath = join(basePath, to) @@ -27,7 +39,13 @@ export const copyFile = (from, to, basePath?) => { } export const removePath = (path, basePath?) => { - if (!basePath) basePath = config.tempDirectory + if (!basePath) { + basePath = config.tempDirectory + const mode = process.env.FUNCTIONAL_ENVIRONMENT + if (mode) { + basePath = join(basePath, mode) + } + } if (basePath) { const targetFilePath = join(basePath, path) From 525972e8f7db921721a00f05461efafc9df0b39c Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 18 Jul 2017 12:38:48 +0200 Subject: [PATCH 041/196] ADD: aws s3 decorator bucket name validation --- src/annotations/classes/s3Storage.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/annotations/classes/s3Storage.ts b/src/annotations/classes/s3Storage.ts index 83c58be..08d16ca 100644 --- a/src/annotations/classes/s3Storage.ts +++ b/src/annotations/classes/s3Storage.ts @@ -4,6 +4,7 @@ import { applyTemplates } from '../templates' import { environment } from './environment' export const S3_BUCKET_SUFFIX = '_S3_BUCKET' +export const S3_BUCKET_NAME_REGEXP = /^[a-z0-9][a-z0-9-.]{1,61}[a-z0-9]$/ export const s3Storage = (s3Config: { bucketName: string, @@ -19,6 +20,11 @@ export const s3Storage = (s3Config: { const { templatedKey, templatedValue } = applyTemplates(s3Config.environmentKey, s3Config.bucketName, target) const lowerCaseTemplatedValue = templatedValue ? templatedValue.toLowerCase() : templatedValue + + if (!S3_BUCKET_NAME_REGEXP.test(lowerCaseTemplatedValue)) { + throw new Error(`invalid bucket name '${lowerCaseTemplatedValue}' validator: ${S3_BUCKET_NAME_REGEXP}`) + } + s3Definitions.push({ ...s3Config, environmentKey: templatedKey, From 3fa894ba9cef6e608e393a52a59d18a65b627e7c Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 18 Jul 2017 15:52:54 +0200 Subject: [PATCH 042/196] ADD: Azure staging; ADD: host config in azure --- src/cli/providers/azureARM/context/functions.ts | 10 ++++++---- src/cli/providers/azureARM/context/init.ts | 17 ++++++++++++++++- src/cli/providers/azureARM/index.ts | 4 +++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/cli/providers/azureARM/context/functions.ts b/src/cli/providers/azureARM/context/functions.ts index 66c12c4..27ef75d 100644 --- a/src/cli/providers/azureARM/context/functions.ts +++ b/src/cli/providers/azureARM/context/functions.ts @@ -10,8 +10,9 @@ export const azureFunctions = ExecuteStep.register('AzureFunctions', async (cont const site = context.ARMTemplate.resources.find(r => r.type === "Microsoft.Web/sites") if (site) { const config = site.resources.find(r => r.type === "config") || { properties: {} } + const routePrefix = context.ARMHost.http.routePrefix ? `/${context.ARMHost.http.routePrefix}` : '' - addEnvironmentSetting('FUNCION_APP_BASEURL', `[concat('https://', toLower(variables('functionAppName')), '.azurewebsites.net/api')]`, site) + addEnvironmentSetting('FUNCION_APP_BASEURL', `[concat('https://', toLower(variables('functionAppName')), '.azurewebsites.net${routePrefix}')]`, site) for (const serviceDefinition of context.publishedFunctions) { const funcname = getFunctionName(serviceDefinition.service) @@ -114,10 +115,11 @@ export const persistAzureGithubRepo = ExecuteStep.register('PersistAzureGithubRe const { deploymentFolder } = context const site = context.ARMTemplate.resources.find(r => r.type === "Microsoft.Web/sites") if (site) { - removePath(context.projectName || `functionly`) + context.projectFolder = `${context.projectName || 'functionly'}-${context.stage}` + removePath(context.projectFolder) for (const file of context.files) { - copyFile(file, join(context.projectName || `functionly`, basename(file)), deploymentFolder) + copyFile(file, join(context.projectFolder, basename(file)), deploymentFolder) } const functions = site.resources.filter(f => f.type === 'functions') @@ -133,7 +135,7 @@ export const persistAzureGithubRepo = ExecuteStep.register('PersistAzureGithubRe export const persistFunction = (context) => { const { functionResource, site, deploymentFolder } = context - const basePath = join(context.projectName || `functionly`, functionResource.name) + const basePath = join(context.projectFolder, functionResource.name) for (const file in functionResource.properties.files) { writeFile(join(basePath, file), new Buffer(functionResource.properties.files[file], 'utf8'), deploymentFolder) diff --git a/src/cli/providers/azureARM/context/init.ts b/src/cli/providers/azureARM/context/init.ts index 3343ee7..7ce00c4 100644 --- a/src/cli/providers/azureARM/context/init.ts +++ b/src/cli/providers/azureARM/context/init.ts @@ -2,7 +2,9 @@ import { resolvePath } from '../../../utilities/cli' import { projectConfig } from '../../../project/config' import { ExecuteStep } from '../../../context' +import { writeFile } from '../../../utilities/local/file' import { defaultsDeep } from 'lodash' +import { join } from 'path' export const ARMInit = ExecuteStep.register('ARMInit', async (context) => { context.ARMConfig = { @@ -15,7 +17,7 @@ export const ARMInit = ExecuteStep.register('ARMInit', async (context) => { "parameters": { "functionAppName": { "type": "string", - "defaultValue": projectConfig.name, + "defaultValue": `${context.projectName || 'functionly'}-${context.stage}`, "metadata": { "description": "The name of the function app that you wish to create." } @@ -108,11 +110,17 @@ export const ARMInit = ExecuteStep.register('ARMInit', async (context) => { } context.ARMStacks = {} context.deploymentResources = [] + context.ARMHost = { + "http": { + "routePrefix": "" + } + } }) export const ARMMerge = ExecuteStep.register('ARMAfterCreateDefaults', async (context) => { defaultsDeep(context.ARMTemplate, projectConfig.ARMTemplate || {}) defaultsDeep(context.ARMStacks, projectConfig.ARMStacksTemplate || {}) + defaultsDeep(context.ARMHost, projectConfig.ARMHost || {}) }) export const initGitTemplate = (context) => { @@ -166,4 +174,11 @@ export const addEnvironmentSetting = (name, value, site) => { if (!exists) { site.properties.siteConfig.appSettings.push({ name, value }) } +} + + +export const persistHostJson = async (context) => { + const { deploymentFolder } = context + const host = JSON.stringify(context.ARMHost, null, 4) + writeFile(join(`${context.projectName || 'functionly'}-${context.stage}`, 'host.json'), new Buffer(host, 'utf8'), deploymentFolder) } \ No newline at end of file diff --git a/src/cli/providers/azureARM/index.ts b/src/cli/providers/azureARM/index.ts index 797a38a..94181c7 100644 --- a/src/cli/providers/azureARM/index.ts +++ b/src/cli/providers/azureARM/index.ts @@ -6,7 +6,7 @@ import { writeFile } from '../../utilities/local/file' import { projectConfig } from '../../project/config' import { executor } from '../../context' -import { ARMInit, ARMMerge, initGitTemplate } from './context/init' +import { ARMInit, ARMMerge, initGitTemplate, persistHostJson } from './context/init' import { azureFunctions, persistAzureGithubRepo } from './context/functions' @@ -27,6 +27,7 @@ export const azure = { logger.info(`Functionly: Create project...`) await executor({ ...context, deploymentFolder: projectConfig.ARM.deploymentFolder }, persistAzureGithubRepo) + await executor({ ...context, deploymentFolder: projectConfig.ARM.deploymentFolder }, persistHostJson) logger.info(`Functionly: Save template...`) await executor(context, persistFile) @@ -47,6 +48,7 @@ export const azure = { logger.info(`Functionly: Create project...`) await executor(context, persistAzureGithubRepo) + await executor(context, persistHostJson) logger.info(`Functionly: Save template...`) await executor(context, persistFile) From 5246149ed60b4ad9a85c46101a977bfc651c2acf Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 18 Jul 2017 16:05:24 +0200 Subject: [PATCH 043/196] ADD: azure decorator --- src/annotations/classes/azure/azure.ts | 10 ++++++++++ src/annotations/constants.ts | 4 +++- src/annotations/index.ts | 1 + src/cli/providers/azureARM/context/functions.ts | 7 ++++++- 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 src/annotations/classes/azure/azure.ts diff --git a/src/annotations/classes/azure/azure.ts b/src/annotations/classes/azure/azure.ts new file mode 100644 index 0000000..eca9e28 --- /dev/null +++ b/src/annotations/classes/azure/azure.ts @@ -0,0 +1,10 @@ +import { CLASS_AZURENODEKEY } from '../../constants' +import { defineMetadata } from '../../metadata' + +export const azure = (config: { + node?: '6.5.0' +}) => (target: Function) => { + if (typeof config.node === 'string') { + defineMetadata(CLASS_AZURENODEKEY, config.node, target); + } +} \ No newline at end of file diff --git a/src/annotations/constants.ts b/src/annotations/constants.ts index dd440e7..d4e07cd 100644 --- a/src/annotations/constants.ts +++ b/src/annotations/constants.ts @@ -17,4 +17,6 @@ export const CLASS_SNSCONFIGURATIONKEY = 'functionly:class:snsConfiguration' export const CLASS_S3CONFIGURATIONKEY = 'functionly:class:s3Configuration' export const CLASS_CLASSCONFIGKEY = 'functionly:class:classConfig' -export const CLASS_HTTPTRIGGER = 'functionly:class:httpTrigger' \ No newline at end of file +export const CLASS_HTTPTRIGGER = 'functionly:class:httpTrigger' + +export const CLASS_AZURENODEKEY = 'functionly:class:azurenode' diff --git a/src/annotations/index.ts b/src/annotations/index.ts index fe5eb46..ee8d901 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -22,6 +22,7 @@ import { CLASS_DESCRIPTIONKEY, CLASS_ROLEKEY, CLASS_RUNTIMEKEY } from './constan export const description = simpleClassAnnotation(CLASS_DESCRIPTIONKEY) export const role = simpleClassAnnotation(CLASS_ROLEKEY) +export { azure } from './classes/azure/azure' export { param, event } from './parameters/param' export { inject } from './parameters/inject' diff --git a/src/cli/providers/azureARM/context/functions.ts b/src/cli/providers/azureARM/context/functions.ts index 27ef75d..b450bae 100644 --- a/src/cli/providers/azureARM/context/functions.ts +++ b/src/cli/providers/azureARM/context/functions.ts @@ -1,7 +1,7 @@ import { basename, join } from 'path' import { readFileSync } from 'fs' import { getFunctionName, getMetadata, constants } from '../../../../annotations' -const { CLASS_HTTPTRIGGER, CLASS_ENVIRONMENTKEY } = constants +const { CLASS_HTTPTRIGGER, CLASS_ENVIRONMENTKEY, CLASS_AZURENODEKEY } = constants import { ExecuteStep, executor } from '../../../context' import { writeFile, copyFile, removePath } from '../../../utilities/local/file' import { addEnvironmentSetting } from './init' @@ -33,6 +33,11 @@ export const azureFunction = async (context) => { addEnvironmentSetting(key, environmentVariables[key], site) } + const nodeVersion = getMetadata(CLASS_AZURENODEKEY, serviceDefinition.service) + if (nodeVersion) { + addEnvironmentSetting('WEBSITE_NODE_DEFAULT_VERSION', nodeVersion, site) + } + const httpMetadata = getMetadata(CLASS_HTTPTRIGGER, serviceDefinition.service) || [] for (let metadata of httpMetadata) { await executor({ From 8a0324665db78f90c23f6ff5220b1010af09facc Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Wed, 19 Jul 2017 12:47:31 +0200 Subject: [PATCH 044/196] REFACTOR: runtime decorator to aws --- src/annotations/classes/aws/aws.ts | 18 +++ src/annotations/classes/runtime.ts | 18 --- src/annotations/constants.ts | 7 +- src/annotations/index.ts | 5 +- src/cli/commands/serverless.ts | 4 +- .../cloudFormation/context/resources.ts | 8 +- src/cli/utilities/aws/lambda.ts | 12 +- test/annotation.tests.ts | 119 ++++++++++-------- 8 files changed, 103 insertions(+), 88 deletions(-) create mode 100644 src/annotations/classes/aws/aws.ts delete mode 100644 src/annotations/classes/runtime.ts diff --git a/src/annotations/classes/aws/aws.ts b/src/annotations/classes/aws/aws.ts new file mode 100644 index 0000000..599239b --- /dev/null +++ b/src/annotations/classes/aws/aws.ts @@ -0,0 +1,18 @@ +import { CLASS_AWSMEMORYSIZEKEY, CLASS_AWSTIMEOUTKEY, CLASS_AWSRUNTIMEKEY } from '../../constants' +import { defineMetadata } from '../../metadata' + +export const aws = (config: { + type?: 'nodejs6.10', + memorySize?: number, + timeout?: number +}) => (target: Function) => { + if (typeof config.type === 'string') { + defineMetadata(CLASS_AWSRUNTIMEKEY, config.type, target); + } + if (typeof config.memorySize === 'number') { + defineMetadata(CLASS_AWSMEMORYSIZEKEY, config.memorySize, target); + } + if (typeof config.timeout === 'number') { + defineMetadata(CLASS_AWSTIMEOUTKEY, config.timeout, target); + } +} \ No newline at end of file diff --git a/src/annotations/classes/runtime.ts b/src/annotations/classes/runtime.ts deleted file mode 100644 index 22cc596..0000000 --- a/src/annotations/classes/runtime.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CLASS_MEMORYSIZEKEY, CLASS_TIMEOUTKEY, CLASS_RUNTIMEKEY } from '../constants' -import { defineMetadata } from '../metadata' - -export const runtime = (config: { - type?: 'nodejs6.10', - memorySize?: number, - timeout?: number -}) => (target: Function) => { - if (typeof config.type === 'string') { - defineMetadata(CLASS_RUNTIMEKEY, config.type, target); - } - if (typeof config.memorySize === 'number') { - defineMetadata(CLASS_MEMORYSIZEKEY, config.memorySize, target); - } - if (typeof config.timeout === 'number') { - defineMetadata(CLASS_TIMEOUTKEY, config.timeout, target); - } -} \ No newline at end of file diff --git a/src/annotations/constants.ts b/src/annotations/constants.ts index d4e07cd..165461b 100644 --- a/src/annotations/constants.ts +++ b/src/annotations/constants.ts @@ -3,10 +3,7 @@ export const CLASS_KEY_PREFIX = 'functionly:class:' export const PARAMETER_PARAMKEY = 'functionly:param:parameters' export const CLASS_DESCRIPTIONKEY = 'functionly:class:description' export const CLASS_ROLEKEY = 'functionly:class:role' -export const CLASS_RUNTIMEKEY = 'functionly:class:runtime' export const CLASS_ENVIRONMENTKEY = 'functionly:class:environment' -export const CLASS_MEMORYSIZEKEY = 'functionly:class:memorysize' -export const CLASS_TIMEOUTKEY = 'functionly:class:timeout' export const CLASS_APIGATEWAYKEY = 'functionly:class:apigateway' export const CLASS_TAGKEY = 'functionly:class:tag' export const CLASS_LOGKEY = 'functionly:class:log' @@ -20,3 +17,7 @@ export const CLASS_CLASSCONFIGKEY = 'functionly:class:classConfig' export const CLASS_HTTPTRIGGER = 'functionly:class:httpTrigger' export const CLASS_AZURENODEKEY = 'functionly:class:azurenode' + +export const CLASS_AWSRUNTIMEKEY = 'functionly:class:awsruntime' +export const CLASS_AWSMEMORYSIZEKEY = 'functionly:class:awsmemorysize' +export const CLASS_AWSTIMEOUTKEY = 'functionly:class:awstimeout' diff --git a/src/annotations/index.ts b/src/annotations/index.ts index ee8d901..666f797 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -6,7 +6,6 @@ export { rest } from './classes/rest' export { environment } from './classes/environment' export { tag } from './classes/tag' export { log } from './classes/log' -export { runtime } from './classes/runtime' export { functionName, getFunctionName } from './classes/functionName' export { dynamoTable, __dynamoDBDefaults } from './classes/dynamoTable' export { sns } from './classes/sns' @@ -18,10 +17,12 @@ export { expandableDecorator } from './classes/expandableDecorator' import { simpleClassAnnotation } from './classes/simpleAnnotation' -import { CLASS_DESCRIPTIONKEY, CLASS_ROLEKEY, CLASS_RUNTIMEKEY } from './constants' +import { CLASS_DESCRIPTIONKEY, CLASS_ROLEKEY } from './constants' export const description = simpleClassAnnotation(CLASS_DESCRIPTIONKEY) export const role = simpleClassAnnotation(CLASS_ROLEKEY) +export { aws } from './classes/aws/aws' + export { azure } from './classes/azure/azure' export { param, event } from './parameters/param' diff --git a/src/cli/commands/serverless.ts b/src/cli/commands/serverless.ts index f58f81b..65bfa0e 100644 --- a/src/cli/commands/serverless.ts +++ b/src/cli/commands/serverless.ts @@ -10,7 +10,7 @@ export default (api) => { annotations: { getMetadata, getMetadataKeys, getFunctionName, constants: { - CLASS_RUNTIMEKEY, + CLASS_AWSRUNTIMEKEY, CLASS_ENVIRONMENTKEY, CLASS_APIGATEWAYKEY }, @@ -93,7 +93,7 @@ export default (api) => { const def = serverless.functions[functionName] = { handler: `${nameKey}.${serviceDefinition.exportName}`, - runtime: getMetadata(CLASS_RUNTIMEKEY, serviceDefinition.service) || "nodejs6.10" + runtime: getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs6.10" } await executor({ context, name: 'funtionEnvironments', method: funtionEnvironments }) diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index c8f5717..26a283d 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -1,7 +1,7 @@ import { intersection } from 'lodash' import { getMetadata, constants, getFunctionName, __dynamoDBDefaults } from '../../../../annotations' -const { CLASS_DESCRIPTIONKEY, CLASS_ROLEKEY, CLASS_MEMORYSIZEKEY, CLASS_RUNTIMEKEY, CLASS_TIMEOUTKEY, +const { CLASS_DESCRIPTIONKEY, CLASS_ROLEKEY, CLASS_AWSMEMORYSIZEKEY, CLASS_AWSRUNTIMEKEY, CLASS_AWSTIMEOUTKEY, CLASS_ENVIRONMENTKEY, CLASS_TAGKEY, CLASS_APIGATEWAYKEY } = constants import { ExecuteStep, executor } from '../../../context' import { setResource } from '../utils' @@ -266,10 +266,10 @@ export const lambdaResource = async (context) => { Description: serviceDefinition[CLASS_DESCRIPTIONKEY] || getMetadata(CLASS_DESCRIPTIONKEY, serviceDefinition.service), FunctionName: `${getFunctionName(serviceDefinition.service)}-${context.stage}`, Handler: serviceDefinition.handler, - MemorySize: serviceDefinition[CLASS_MEMORYSIZEKEY] || getMetadata(CLASS_MEMORYSIZEKEY, serviceDefinition.service), + MemorySize: serviceDefinition[CLASS_AWSMEMORYSIZEKEY] || getMetadata(CLASS_AWSMEMORYSIZEKEY, serviceDefinition.service), Role: serviceDefinition[CLASS_ROLEKEY] || getMetadata(CLASS_ROLEKEY, serviceDefinition.service), - Runtime: serviceDefinition[CLASS_RUNTIMEKEY] || getMetadata(CLASS_RUNTIMEKEY, serviceDefinition.service) || "nodejs6.10", - Timeout: serviceDefinition[CLASS_TIMEOUTKEY] || getMetadata(CLASS_TIMEOUTKEY, serviceDefinition.service), + Runtime: serviceDefinition[CLASS_AWSRUNTIMEKEY] || getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs6.10", + Timeout: serviceDefinition[CLASS_AWSTIMEOUTKEY] || getMetadata(CLASS_AWSTIMEOUTKEY, serviceDefinition.service), Environment: { Variables: serviceDefinition[CLASS_ENVIRONMENTKEY] || getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) }, diff --git a/src/cli/utilities/aws/lambda.ts b/src/cli/utilities/aws/lambda.ts index b29df0f..2cd12a1 100644 --- a/src/cli/utilities/aws/lambda.ts +++ b/src/cli/utilities/aws/lambda.ts @@ -31,11 +31,11 @@ export const createLambdaFunction = ExecuteStep.register('CreateLambdaFunction', Description: getMetadata(constants.CLASS_DESCRIPTIONKEY, context.serviceDefinition.service), FunctionName: getFunctionName(context.serviceDefinition.service), Handler: context.serviceDefinition.handler, - MemorySize: getMetadata(constants.CLASS_MEMORYSIZEKEY, context.serviceDefinition.service), + MemorySize: getMetadata(constants.CLASS_AWSMEMORYSIZEKEY, context.serviceDefinition.service), Publish: true, Role: getMetadata(constants.CLASS_ROLEKEY, context.serviceDefinition.service), - Runtime: getMetadata(constants.CLASS_RUNTIMEKEY, context.serviceDefinition.service) || "nodejs6.10", - Timeout: getMetadata(constants.CLASS_TIMEOUTKEY, context.serviceDefinition.service), + Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs6.10", + Timeout: getMetadata(constants.CLASS_AWSTIMEOUTKEY, context.serviceDefinition.service), Environment: { Variables: getMetadata(constants.CLASS_ENVIRONMENTKEY, context.serviceDefinition.service) }, @@ -116,10 +116,10 @@ export const updateLambdaFunctionConfiguration = ExecuteStep.register('UpdateLam Variables: getMetadata(constants.CLASS_ENVIRONMENTKEY, context.serviceDefinition.service) }, Handler: context.serviceDefinition.handler, - MemorySize: getMetadata(constants.CLASS_MEMORYSIZEKEY, context.serviceDefinition.service), + MemorySize: getMetadata(constants.CLASS_AWSMEMORYSIZEKEY, context.serviceDefinition.service), Role: getMetadata(constants.CLASS_ROLEKEY, context.serviceDefinition.service), - Runtime: getMetadata(constants.CLASS_RUNTIMEKEY, context.serviceDefinition.service) || "nodejs6.10", - Timeout: getMetadata(constants.CLASS_TIMEOUTKEY, context.serviceDefinition.service), + Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs6.10", + Timeout: getMetadata(constants.CLASS_AWSTIMEOUTKEY, context.serviceDefinition.service), VpcConfig: { } }; diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index 3a83b1f..8d79bf6 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -3,9 +3,9 @@ import { expect } from 'chai' import { CLASS_APIGATEWAYKEY, CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_ENVIRONMENTKEY, CLASS_NAMEKEY, - CLASS_INJECTABLEKEY, CLASS_LOGKEY, CLASS_RUNTIMEKEY, CLASS_MEMORYSIZEKEY, CLASS_TIMEOUTKEY, + CLASS_INJECTABLEKEY, CLASS_LOGKEY, CLASS_AWSRUNTIMEKEY, CLASS_AWSMEMORYSIZEKEY, CLASS_AWSTIMEOUTKEY, CLASS_S3CONFIGURATIONKEY, CLASS_SNSCONFIGURATIONKEY, CLASS_TAGKEY, CLASS_ROLEKEY, CLASS_DESCRIPTIONKEY, - PARAMETER_PARAMKEY, CLASS_CLASSCONFIGKEY, CLASS_HTTPTRIGGER + PARAMETER_PARAMKEY, CLASS_CLASSCONFIGKEY, CLASS_HTTPTRIGGER, CLASS_AZURENODEKEY } from '../src/annotations/constants' import { applyTemplates, templates } from '../src/annotations/templates' import { getFunctionParameters } from '../src/annotations/utils' @@ -19,7 +19,6 @@ import { environment } from '../src/annotations/classes/environment' import { functionName, getFunctionName } from '../src/annotations/classes/functionName' import { injectable } from '../src/annotations/classes/injectable' import { log } from '../src/annotations/classes/log' -import { runtime } from '../src/annotations/classes/runtime' import { s3Storage } from '../src/annotations/classes/s3Storage' import { sns } from '../src/annotations/classes/sns' import { tag } from '../src/annotations/classes/tag' @@ -27,6 +26,10 @@ import { eventSource } from '../src/annotations/classes/eventSource' import { classConfig } from '../src/annotations/classes/classConfig' import { role, description } from '../src/annotations' +import { aws } from '../src/annotations/classes/aws/aws' +import { azure } from '../src/annotations/classes/azure/azure' + + import { inject } from '../src/annotations/parameters/inject' import { param } from '../src/annotations/parameters/param' import { FunctionalService, Service, DynamoDB, SimpleNotificationService, S3Storage, InjectService } from '../src/classes' @@ -542,56 +545,6 @@ describe('annotations', () => { expect(value).to.undefined }) }) - describe("runtime", () => { - it("type", () => { - @runtime({ type: 'nodejs6.10' }) - class RuntimeTestClass { } - - const runtimeValue = getMetadata(CLASS_RUNTIMEKEY, RuntimeTestClass) - const memoryValue = getMetadata(CLASS_MEMORYSIZEKEY, RuntimeTestClass) - const timeoutValue = getMetadata(CLASS_TIMEOUTKEY, RuntimeTestClass) - - expect(runtimeValue).to.equal('nodejs6.10') - expect(memoryValue).to.undefined - expect(timeoutValue).to.undefined - }) - it("memorySize", () => { - @runtime({ memorySize: 100 }) - class RuntimeTestClass { } - - const runtimeValue = getMetadata(CLASS_RUNTIMEKEY, RuntimeTestClass) - const memoryValue = getMetadata(CLASS_MEMORYSIZEKEY, RuntimeTestClass) - const timeoutValue = getMetadata(CLASS_TIMEOUTKEY, RuntimeTestClass) - - expect(runtimeValue).to.undefined - expect(memoryValue).to.equal(100) - expect(timeoutValue).to.undefined - }) - it("timeout", () => { - @runtime({ timeout: 3 }) - class RuntimeTestClass { } - - const runtimeValue = getMetadata(CLASS_RUNTIMEKEY, RuntimeTestClass) - const memoryValue = getMetadata(CLASS_MEMORYSIZEKEY, RuntimeTestClass) - const timeoutValue = getMetadata(CLASS_TIMEOUTKEY, RuntimeTestClass) - - expect(runtimeValue).to.undefined - expect(memoryValue).to.undefined - expect(timeoutValue).to.equal(3) - }) - it("all", () => { - @runtime({ type: 'nodejs6.10', memorySize: 100, timeout: 3 }) - class RuntimeTestClass { } - - const runtimeValue = getMetadata(CLASS_RUNTIMEKEY, RuntimeTestClass) - const memoryValue = getMetadata(CLASS_MEMORYSIZEKEY, RuntimeTestClass) - const timeoutValue = getMetadata(CLASS_TIMEOUTKEY, RuntimeTestClass) - - expect(runtimeValue).to.equal('nodejs6.10') - expect(memoryValue).to.equal(100) - expect(timeoutValue).to.equal(3) - }) - }) describe("s3Storage", () => { it("bucketName", () => { @s3Storage({ bucketName: 'mybucketname' }) @@ -851,6 +804,66 @@ describe('annotations', () => { expect(metadata).to.have.property('eventSource', true) }) }) + describe("aws", () => { + it("type", () => { + @aws({ type: 'nodejs6.10' }) + class RuntimeTestClass { } + + const runtimeValue = getMetadata(CLASS_AWSRUNTIMEKEY, RuntimeTestClass) + const memoryValue = getMetadata(CLASS_AWSMEMORYSIZEKEY, RuntimeTestClass) + const timeoutValue = getMetadata(CLASS_AWSTIMEOUTKEY, RuntimeTestClass) + + expect(runtimeValue).to.equal('nodejs6.10') + expect(memoryValue).to.undefined + expect(timeoutValue).to.undefined + }) + it("memorySize", () => { + @aws({ memorySize: 100 }) + class RuntimeTestClass { } + + const runtimeValue = getMetadata(CLASS_AWSRUNTIMEKEY, RuntimeTestClass) + const memoryValue = getMetadata(CLASS_AWSMEMORYSIZEKEY, RuntimeTestClass) + const timeoutValue = getMetadata(CLASS_AWSTIMEOUTKEY, RuntimeTestClass) + + expect(runtimeValue).to.undefined + expect(memoryValue).to.equal(100) + expect(timeoutValue).to.undefined + }) + it("timeout", () => { + @aws({ timeout: 3 }) + class RuntimeTestClass { } + + const runtimeValue = getMetadata(CLASS_AWSRUNTIMEKEY, RuntimeTestClass) + const memoryValue = getMetadata(CLASS_AWSMEMORYSIZEKEY, RuntimeTestClass) + const timeoutValue = getMetadata(CLASS_AWSTIMEOUTKEY, RuntimeTestClass) + + expect(runtimeValue).to.undefined + expect(memoryValue).to.undefined + expect(timeoutValue).to.equal(3) + }) + it("all", () => { + @aws({ type: 'nodejs6.10', memorySize: 100, timeout: 3 }) + class RuntimeTestClass { } + + const runtimeValue = getMetadata(CLASS_AWSRUNTIMEKEY, RuntimeTestClass) + const memoryValue = getMetadata(CLASS_AWSMEMORYSIZEKEY, RuntimeTestClass) + const timeoutValue = getMetadata(CLASS_AWSTIMEOUTKEY, RuntimeTestClass) + + expect(runtimeValue).to.equal('nodejs6.10') + expect(memoryValue).to.equal(100) + expect(timeoutValue).to.equal(3) + }) + }) + describe("azure", () => { + it("node", () => { + @azure({ node: '6.5.0' }) + class RuntimeTestClass { } + + const nodeValue = getMetadata(CLASS_AZURENODEKEY, RuntimeTestClass) + + expect(nodeValue).to.equal('6.5.0') + }) + }) }) describe("parameters", () => { From 1171021234f854e41c3269efdd32843283255adc Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Wed, 19 Jul 2017 17:31:28 +0200 Subject: [PATCH 045/196] ADD: rest decorator http shortcuts --- src/annotations/classes/rest.ts | 22 ++++- src/annotations/index.ts | 2 +- src/index.ts | 3 + test/annotation.tests.ts | 162 +++++++++++++++++++++++++++++++- 4 files changed, 186 insertions(+), 3 deletions(-) diff --git a/src/annotations/classes/rest.ts b/src/annotations/classes/rest.ts index 7f52242..71926a3 100644 --- a/src/annotations/classes/rest.ts +++ b/src/annotations/classes/rest.ts @@ -7,4 +7,24 @@ export const rest = expandableDecorator<{ path: string, methods?: string[], cors cors: false, anonymous: false } -}) \ No newline at end of file +}) + +export interface IHttpMethod { + (path: string): Function + (config: { path: string, cors?: boolean, anonymous?: boolean }): Function +} + +export const resolveParam = (p: any, defaults) => { + if (typeof p === 'string') { + return { ...defaults, path: p } + } else { + return { ...defaults, ...p } + } +} + + +export const httpGet: IHttpMethod = (p) => rest(resolveParam(p, { methods: ['get'] })) +export const httpPost: IHttpMethod = (p) => rest(resolveParam(p, { methods: ['post'] })) +export const httpPut: IHttpMethod = (p) => rest(resolveParam(p, { methods: ['put'] })) +export const httpPatch: IHttpMethod = (p) => rest(resolveParam(p, { methods: ['patch'] })) +export const httpDelete: IHttpMethod = (p) => rest(resolveParam(p, { methods: ['delete'] })) diff --git a/src/annotations/index.ts b/src/annotations/index.ts index 666f797..3999a17 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -2,7 +2,7 @@ export { templates, applyTemplates } from './templates' export { injectable } from './classes/injectable' export { apiGateway } from './classes/aws/apiGateway' export { httpTrigger } from './classes/azure/httpTrigger' -export { rest } from './classes/rest' +export { rest, httpGet, httpPost, httpPut, httpPatch, httpDelete, IHttpMethod } from './classes/rest' export { environment } from './classes/environment' export { tag } from './classes/tag' export { log } from './classes/log' diff --git a/src/index.ts b/src/index.ts index 6794b56..47cee3d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,3 +2,6 @@ export { Service, FunctionalApi, FunctionalService, DynamoDB, SimpleNotification export { addProvider, removeProvider, Provider, AWSProvider, LocalProvider } from './providers' import * as _annotations from './annotations' export const annotations = _annotations + + +export { IHttpMethod } from './annotations' diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index 8d79bf6..e9b1e87 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -13,7 +13,7 @@ import { getMetadata, getOwnMetadata } from '../src/annotations/metadata' import { expandableDecorator } from '../src/annotations/classes/expandableDecorator' import { apiGateway } from '../src/annotations/classes/aws/apiGateway' import { httpTrigger } from '../src/annotations/classes/azure/httpTrigger' -import { rest } from '../src/annotations/classes/rest' +import { rest, httpGet, httpPost, httpPut, httpPatch, httpDelete } from '../src/annotations/classes/rest' import { dynamoTable, __dynamoDBDefaults } from '../src/annotations/classes/dynamoTable' import { environment } from '../src/annotations/classes/environment' import { functionName, getFunctionName } from '../src/annotations/classes/functionName' @@ -384,6 +384,166 @@ describe('annotations', () => { expect(metadata).to.have.property('anonymous', true) }) }) + describe("httpGet", () => { + it("path", () => { + @httpGet('/v1/test') + class ApiGatewayTestClass { } + + const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['get']) + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('anonymous', false) + }) + it("config", () => { + @httpGet({ path: '/v1/test', anonymous: true }) + class ApiGatewayTestClass { } + + const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['get']) + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('anonymous', true) + }) + }) + describe("httpPost", () => { + it("path", () => { + @httpPost('/v1/test') + class ApiGatewayTestClass { } + + const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['post']) + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('anonymous', false) + }) + it("config", () => { + @httpPost({ path: '/v1/test', anonymous: true }) + class ApiGatewayTestClass { } + + const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['post']) + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('anonymous', true) + }) + }) + describe("httpPut", () => { + it("path", () => { + @httpPut('/v1/test') + class ApiGatewayTestClass { } + + const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['put']) + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('anonymous', false) + }) + it("config", () => { + @httpPut({ path: '/v1/test', anonymous: true }) + class ApiGatewayTestClass { } + + const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['put']) + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('anonymous', true) + }) + }) + describe("httpPatch", () => { + it("path", () => { + @httpPatch('/v1/test') + class ApiGatewayTestClass { } + + const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['patch']) + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('anonymous', false) + }) + it("config", () => { + @httpPatch({ path: '/v1/test', anonymous: true }) + class ApiGatewayTestClass { } + + const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['patch']) + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('anonymous', true) + }) + }) + describe("httpDelete", () => { + it("path", () => { + @httpDelete('/v1/test') + class ApiGatewayTestClass { } + + const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['delete']) + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('anonymous', false) + }) + it("config", () => { + @httpDelete({ path: '/v1/test', anonymous: true }) + class ApiGatewayTestClass { } + + const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['delete']) + expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('anonymous', true) + }) + }) describe("dynamoTable", () => { it("tableName", () => { @dynamoTable({ tableName: 'mytablename' }) From c3be88dc228e7cadd9968e6d262800dca60ca0ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Hauszknecht?= Date: Wed, 19 Jul 2017 17:40:42 +0200 Subject: [PATCH 046/196] comment lambdaVersionResource --- src/cli/providers/cloudFormation/context/resources.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 26a283d..86e5f39 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -247,11 +247,11 @@ export const lambdaResources = ExecuteStep.register('Lambda-Functions', async (c name: `Lambda-Function-${serviceDefinition.service.name}`, method: lambdaResource }) - await executor({ + /* await executor({ context: { ...context, serviceDefinition }, name: `Lambda-Version-${serviceDefinition.service.name}`, method: lambdaVersionResource - }) + }) */ } }) From 5e2bf4c046c18ff267e0e4f7be4de68338f7919e Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Thu, 20 Jul 2017 10:17:19 +0200 Subject: [PATCH 047/196] REFACTOR: event parameter decorator renamed to serviceParams --- src/annotations/index.ts | 2 +- src/annotations/parameters/param.ts | 4 ++-- src/providers/core/provider.ts | 2 +- test/invoker.tests.ts | 14 +++++++------- test/serviceOnEvent.tests.ts | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/annotations/index.ts b/src/annotations/index.ts index 3999a17..ec4dd9a 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -25,7 +25,7 @@ export { aws } from './classes/aws/aws' export { azure } from './classes/azure/azure' -export { param, event } from './parameters/param' +export { param, serviceParams } from './parameters/param' export { inject } from './parameters/inject' import * as _constants from './constants' diff --git a/src/annotations/parameters/param.ts b/src/annotations/parameters/param.ts index 6812ec1..ed59dda 100644 --- a/src/annotations/parameters/param.ts +++ b/src/annotations/parameters/param.ts @@ -33,7 +33,7 @@ export const param = (target: any, targetKey?: string, parameterIndex?: number): } } -export const event = (target, targetKey, parameterIndex: number) => { +export const serviceParams = (target, targetKey, parameterIndex: number) => { let parameterNames = getFunctionParameters(target, targetKey); let existingParameters: any[] = getOwnMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; @@ -41,7 +41,7 @@ export const event = (target, targetKey, parameterIndex: number) => { existingParameters.push({ from: paramName, parameterIndex, - type: 'event' + type: 'serviceParams' }); defineMetadata(PARAMETER_PARAMKEY, existingParameters, target, targetKey); } diff --git a/src/providers/core/provider.ts b/src/providers/core/provider.ts index ce44626..368a190 100644 --- a/src/providers/core/provider.ts +++ b/src/providers/core/provider.ts @@ -29,7 +29,7 @@ export abstract class Provider { const instance = new serviceType(...parameter.params.map((p) => typeof p === 'function' ? p() : p)) await callExtension(instance, 'onInject', { parameter }) return instance - case 'event': + case 'serviceParams': return event; default: return undefined diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index 4a5e909..5bfdfa1 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -2,7 +2,7 @@ import { expect } from 'chai' import { getInvoker } from '../src/providers' import { FunctionalService, Service } from '../src/classes' -import { param, inject, injectable, event } from '../src/annotations' +import { param, inject, injectable, serviceParams } from '../src/annotations' describe('invoker', () => { @@ -344,7 +344,7 @@ describe('invoker', () => { }, (e) => { e && done(e) }) }) - it("event param", (done) => { + it("serviceParams param", (done) => { let counter = 0 process.env.FUNCTIONAL_ENVIRONMENT = 'local' @@ -362,7 +362,7 @@ describe('invoker', () => { const next = (e) => { e && done(e) } class MockService extends FunctionalService { - handle( @param p1, @event p2) { + handle( @param p1, @serviceParams p2) { counter++ expect(p1).to.undefined expect(p2).to.have.property('req').that.to.equal(req) @@ -917,7 +917,7 @@ describe('invoker', () => { }) }) - it("event param", (done) => { + it("serviceParams param", (done) => { let counter = 0 process.env.FUNCTIONAL_ENVIRONMENT = 'aws' @@ -933,7 +933,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle( @param p1, @event p2) { + handle( @param p1, @serviceParams p2) { counter++ expect(p1).to.undefined expect(p2).to.have.property('event').that.to.equal(awsEvent) @@ -1344,7 +1344,7 @@ describe('invoker', () => { expect(counter).to.equal(1) }) - it("event param", async () => { + it("serviceParams param", async () => { let counter = 0 process.env.FUNCTIONAL_ENVIRONMENT = 'azure' @@ -1356,7 +1356,7 @@ describe('invoker', () => { const req = {} class MockService extends FunctionalService { - handle( @param p1, @event p2) { + handle( @param p1, @serviceParams p2) { counter++ expect(p1).to.undefined expect(p2).to.have.property('context').that.to.equal(context) diff --git a/test/serviceOnEvent.tests.ts b/test/serviceOnEvent.tests.ts index 39d8e85..cc2d8b5 100644 --- a/test/serviceOnEvent.tests.ts +++ b/test/serviceOnEvent.tests.ts @@ -1,7 +1,7 @@ import { expect } from 'chai' import { FunctionalService, Service } from '../src/classes' -import { param, inject, injectable, event } from '../src/annotations' +import { param, inject, injectable } from '../src/annotations' import { addProvider, removeProvider } from '../src/providers' import { LocalProvider } from '../src/providers/local' From a9783dd339f0b0a14349851e049d0e794f4fbf4f Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Thu, 20 Jul 2017 10:46:30 +0200 Subject: [PATCH 048/196] update metadata command --- src/cli/commands/metadata.ts | 15 +++--------- src/cli/context/index.ts | 1 + .../steppes/metadata/serviceMetadata.ts | 24 +++++++++++++++++++ 3 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 src/cli/context/steppes/metadata/serviceMetadata.ts diff --git a/src/cli/commands/metadata.ts b/src/cli/commands/metadata.ts index 08355a6..281d2b5 100644 --- a/src/cli/commands/metadata.ts +++ b/src/cli/commands/metadata.ts @@ -1,4 +1,4 @@ -export default ({ createContext, annotations: { getMetadata, getMetadataKeys }, projectConfig, requireValue }) => { +export default ({ createContext, executor, ExecuteStep, annotations: { getMetadata, getMetadataKeys }, projectConfig, requireValue }) => { return { commands({ commander }) { @@ -17,18 +17,9 @@ export default ({ createContext, annotations: { getMetadata, getMetadataKeys }, deployTarget }) - console.log(JSON.stringify(context, null, 4)) - - for (let serviceDefinitions of context.publishedFunctions) { - let keys = getMetadataKeys(serviceDefinitions.service) - let metadata = {} - for (let key of keys) { - metadata[key] = getMetadata(key, serviceDefinitions.service) - } - - console.log(serviceDefinitions.handler, JSON.stringify(metadata, null, 4)) - } + await executor(context, ExecuteStep.get('ServiceMetadata')) + console.log(JSON.stringify(context.serviceMetadata, null, 4)) console.log(`done`) } catch (e) { diff --git a/src/cli/context/index.ts b/src/cli/context/index.ts index 297e23d..ddd94be 100644 --- a/src/cli/context/index.ts +++ b/src/cli/context/index.ts @@ -3,6 +3,7 @@ export { ExecuteStep } from './core/executeStep' import { serviceDiscovery } from './steppes/serviceDiscovery' import { tableDiscovery } from './steppes/tableDiscovery' import './steppes/setFunctionalEnvironment' +import './steppes/metadata/serviceMetadata' import { resolvePath } from '../utilities/cli' import { logger } from '../utilities/logger' diff --git a/src/cli/context/steppes/metadata/serviceMetadata.ts b/src/cli/context/steppes/metadata/serviceMetadata.ts new file mode 100644 index 0000000..2356e1e --- /dev/null +++ b/src/cli/context/steppes/metadata/serviceMetadata.ts @@ -0,0 +1,24 @@ +import { getMetadata, constants, __dynamoDBDefaults, getMetadataKeys } from '../../../../annotations' +import { ExecuteStep } from '../../core/executeStep' + +export class ServiceMetadata extends ExecuteStep { + public async method(context) { + context.serviceMetadata = [] + + for (const serviceDefinition of context.publishedFunctions) { + const metadataKeys = getMetadataKeys(serviceDefinition.service) + const metadata = {} + for (const key of metadataKeys) { + metadata[key] = getMetadata(key, serviceDefinition.service) + } + + context.serviceMetadata.push({ + serviceDefinition, + metadata + }) + } + } +} + + +export const serviceMetadata = new ServiceMetadata('ServiceMetadata') From 822eadc37db9f65ac90f02d7bbf71e52811660f1 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Fri, 21 Jul 2017 13:50:04 +0200 Subject: [PATCH 049/196] update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9233948..ccb94df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.3", + "version": "0.0.4", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 1cdf486d94128926ba56ef17d4e2361e547ceab7 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 24 Jul 2017 17:04:36 +0200 Subject: [PATCH 050/196] CHANGE: local run path parameter transforms --- src/cli/commands/local.ts | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/cli/commands/local.ts b/src/cli/commands/local.ts index e69c11e..fce59ab 100644 --- a/src/cli/commands/local.ts +++ b/src/cli/commands/local.ts @@ -4,6 +4,26 @@ import * as cors from 'cors' export default ({ createContext, annotations: { getMetadata, constants, getFunctionName, rest }, projectConfig, requireValue, executor }) => { + const pathPattern = /\{([^+\}]+)(\+?)\}/ + const pathTransform = (path, middlewares, idx = 0) => { + const match = pathPattern.exec(path) + if (match) { + if (match[2]) { + path = path.replace(match[0], '*') + middlewares.push((req, res, next) => { + req.params[match[1]] = req.params[idx] + next() + }) + return pathTransform(path, middlewares, idx + 1) + } else { + path = path.replace(match[0], `:${match[1]}`) + return pathTransform(path, middlewares, idx) + } + } + + return path; + } + const startLocal = async (context) => { let app = express() app.use(bodyParser.json()) @@ -13,17 +33,20 @@ export default ({ createContext, annotations: { getMetadata, constants, getFunct for (let event of httpMetadata) { const isLoggingEnabled = getMetadata(constants.CLASS_LOGKEY, serviceDefinition.service) - console.log(`${new Date().toISOString()} ${getFunctionName(serviceDefinition.service)} listening { path: '${event.path}', methods: '${event.methods}', cors: ${event.cors ? true : false} }`) + const transformMiddlewares = [] + const path = pathTransform(event.path, transformMiddlewares) + console.log(`${new Date().toISOString()} ${getFunctionName(serviceDefinition.service)} listening { path: '${path}', methods: '${event.methods}', cors: ${event.cors ? true : false} }`) if (event.cors) { - app.use(event.path, cors()) + app.use(path, cors()) } - for(const method of event.methods){ + for (const method of event.methods) { app[method]( - event.path, + path, logMiddleware(isLoggingEnabled, serviceDefinition.service), environmentConfigMiddleware(serviceDefinition.service), + ...transformMiddlewares, serviceDefinition.invoker ) } From d4ad08648c0bee996cb7f0050af3ee27711e7408 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Thu, 27 Jul 2017 10:54:06 +0200 Subject: [PATCH 051/196] ADD: dynamic decorators for providers, request decorator --- src/annotations/index.ts | 2 +- src/annotations/parameters/param.ts | 39 ++++-- src/providers/aws/index.ts | 28 ++-- src/providers/azure/index.ts | 30 +++-- src/providers/core/provider.ts | 56 +++++--- src/providers/local.ts | 57 ++++---- test/annotation.tests.ts | 183 ++++++++++++++++++++++++- test/invoke.tests.ts | 143 +++++++++++++++++++- test/invoker.tests.ts | 199 +++++++++++++++++++++++++++- 9 files changed, 657 insertions(+), 80 deletions(-) diff --git a/src/annotations/index.ts b/src/annotations/index.ts index ec4dd9a..bbb6851 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -25,7 +25,7 @@ export { aws } from './classes/aws/aws' export { azure } from './classes/azure/azure' -export { param, serviceParams } from './parameters/param' +export { param, serviceParams, createParameterDecorator, request } from './parameters/param' export { inject } from './parameters/inject' import * as _constants from './constants' diff --git a/src/annotations/parameters/param.ts b/src/annotations/parameters/param.ts index ed59dda..5a61208 100644 --- a/src/annotations/parameters/param.ts +++ b/src/annotations/parameters/param.ts @@ -33,15 +33,32 @@ export const param = (target: any, targetKey?: string, parameterIndex?: number): } } -export const serviceParams = (target, targetKey, parameterIndex: number) => { - let parameterNames = getFunctionParameters(target, targetKey); - - let existingParameters: any[] = getOwnMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; - let paramName = parameterNames[parameterIndex]; - existingParameters.push({ - from: paramName, - parameterIndex, - type: 'serviceParams' - }); - defineMetadata(PARAMETER_PARAMKEY, existingParameters, target, targetKey); +export const createParameterDecorator = (type: string, defaultConfig?: any) => (target?, targetKey?, parameterIndex?: number): any => { + let config = { ...(defaultConfig || {}) }; + + const decorator = function (target, targetKey, parameterIndex: number) { + const parameterNames = getFunctionParameters(target, targetKey); + + const existingParameters: any[] = getOwnMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; + const paramName = parameterNames[parameterIndex]; + existingParameters.push({ + from: paramName, + parameterIndex, + type, + config + }); + defineMetadata(PARAMETER_PARAMKEY, existingParameters, target, targetKey); + } + + if (typeof target == "undefined" || !target) { + return decorator + } else if (typeof target === 'object' && target && !targetKey) { + config = { ...config, ...target } + return decorator + } else { + return decorator(target, targetKey, parameterIndex); + } } + +export const serviceParams = createParameterDecorator('serviceParams') +export const request = createParameterDecorator('request') diff --git a/src/providers/aws/index.ts b/src/providers/aws/index.ts index 4b7c768..281e76c 100644 --- a/src/providers/aws/index.ts +++ b/src/providers/aws/index.ts @@ -6,6 +6,7 @@ import { LambdaCall } from './eventSources/lambdaCall' import { SNS } from './eventSources/sns' import { S3 } from './eventSources/s3' import { DynamoTable } from './eventSources/dynamoTable' +import { parse } from 'url' let lambda = null; const initAWSSDK = () => { @@ -37,7 +38,7 @@ export class AWSProvider extends Provider { const params = [] for (const parameter of parameters) { - params[parameter.parameterIndex] = await this.parameterResolver(parameter, { eventSourceHandler, eventContext }) + params[parameter.parameterIndex] = await this.parameterResolver(parameter, { eventSourceHandler, event: eventContext }) } let result @@ -58,15 +59,6 @@ export class AWSProvider extends Provider { return invoker } - protected async parameterResolver(parameter, event) { - switch (parameter.type) { - case 'param': - return event.eventSourceHandler.parameterResolver(parameter, event.eventContext) - default: - return await super.parameterResolver(parameter, event.eventContext) - } - } - public async invoke(serviceInstance, params, invokeConfig?) { initAWSSDK() @@ -91,4 +83,20 @@ export class AWSProvider extends Provider { } } +AWSProvider.addParameterDecoratorImplementation("param", async (parameter, context, provider) => { + return await context.eventSourceHandler.parameterResolver(parameter, context.event) +}) +AWSProvider.addParameterDecoratorImplementation("request", async (parameter, context, provider) => { + if (context.eventSourceHandler.constructor.name === 'ApiGateway') { + return { + url: parse(context.event.event.path), + method: context.event.event.httpMethod, + body: context.event.event.body, + query: context.event.event.queryStringParameters, + params: context.event.event.pathParameters, + headers: context.event.event.headers + } + } +}) + export const provider = new AWSProvider() diff --git a/src/providers/azure/index.ts b/src/providers/azure/index.ts index 8b86fbe..53ab214 100644 --- a/src/providers/azure/index.ts +++ b/src/providers/azure/index.ts @@ -3,6 +3,7 @@ import { getFunctionName, constants, getMetadata } from '../../annotations' const { CLASS_HTTPTRIGGER } = constants import { HttpTrigger } from './eventSources/httpTrigger' import * as request from 'request' +import { parse } from 'url' const eventSourceHandlers = [ new HttpTrigger() @@ -23,7 +24,7 @@ export class AzureProvider extends Provider { const params = [] for (const parameter of parameters) { - params[parameter.parameterIndex] = await this.parameterResolver(parameter, { eventSourceHandler, eventContext }) + params[parameter.parameterIndex] = await this.parameterResolver(parameter, { eventSourceHandler, event: eventContext }) } let result @@ -47,15 +48,6 @@ export class AzureProvider extends Provider { return invoker } - protected async parameterResolver(parameter, event) { - switch (parameter.type) { - case 'param': - return event.eventSourceHandler.parameterResolver(parameter, event.eventContext) - default: - return await super.parameterResolver(parameter, event.eventContext) - } - } - public async invoke(serviceInstance, params, invokeConfig?) { const httpAttr = (getMetadata(CLASS_HTTPTRIGGER, serviceInstance) || [])[0] @@ -110,4 +102,22 @@ export class AzureProvider extends Provider { } } +AzureProvider.addParameterDecoratorImplementation("param", async (parameter, context, provider) => { + return await context.eventSourceHandler.parameterResolver(parameter, context.event) +}) + + +AzureProvider.addParameterDecoratorImplementation("request", async (parameter, context, provider) => { + if (context.eventSourceHandler.constructor.name === 'HttpTrigger') { + return { + url: parse(context.event.req.originalUrl), + method: context.event.req.method, + body: context.event.req.body, + query: context.event.req.query, + params: context.event.req.params, + headers: context.event.req.headers + } + } +}) + export const provider = new AzureProvider() diff --git a/src/providers/core/provider.ts b/src/providers/core/provider.ts index 368a190..7b7656b 100644 --- a/src/providers/core/provider.ts +++ b/src/providers/core/provider.ts @@ -15,30 +15,48 @@ export abstract class Provider { } - - protected async parameterResolver(parameter, event): Promise { - switch (parameter.type) { - case 'inject': - const serviceType = parameter.serviceType - - const staticInstance = await callExtension(serviceType, 'onInject', { parameter }) - if (typeof staticInstance !== 'undefined') { - return staticInstance - } - - const instance = new serviceType(...parameter.params.map((p) => typeof p === 'function' ? p() : p)) - await callExtension(instance, 'onInject', { parameter }) - return instance - case 'serviceParams': - return event; - default: - return undefined - } + protected async parameterResolver(parameter, context): Promise { + const implementation = this.getParameterDecoratorImplementation(parameter.type) || (() => {}) + return implementation(parameter, context, this) } protected getParameters(target, method) { return (getOwnMetadata(constants.PARAMETER_PARAMKEY, target, 'handle') || []) .filter(t => t && typeof t.parameterIndex === 'number'); } + + + public static __supportedDecorators: { [key: string]: Function } + public static __getDecoratorHolder() { + return this.__supportedDecorators = this.hasOwnProperty('__supportedDecorators') ? this.__supportedDecorators : {} + } + public static addParameterDecoratorImplementation(parameterType: string, implementation: Function) { + this.__getDecoratorHolder()[parameterType] = implementation + } + + public static getParameterDecoratorImplementation(parameterType: string) { + return this.__getDecoratorHolder()[parameterType] || + (this['__proto__'].getParameterDecoratorImplementation && this['__proto__'].getParameterDecoratorImplementation(parameterType)) + } + protected getParameterDecoratorImplementation(parameterType: string) { + return this.constructor['getParameterDecoratorImplementation'](parameterType) + } } +Provider.addParameterDecoratorImplementation("inject", async (parameter, context, provider) => { + const event = context.event + const serviceType = parameter.serviceType + + const staticInstance = await callExtension(serviceType, 'onInject', { parameter }) + if (typeof staticInstance !== 'undefined') { + return staticInstance + } + + const instance = new serviceType(...parameter.params.map((p) => typeof p === 'function' ? p() : p)) + await callExtension(instance, 'onInject', { parameter }) + return instance +}) + +Provider.addParameterDecoratorImplementation("serviceParams", async (parameter, context, provider) => { + return context.event +}) diff --git a/src/providers/local.ts b/src/providers/local.ts index 827f3f4..00f47e9 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -3,6 +3,7 @@ import { Provider } from './core/provider' import { constants, getOwnMetadata, getMetadata, getFunctionName, rest } from '../annotations' const { CLASS_LOGKEY } = constants import { get } from '../helpers/property' +import { parse } from 'url' export class LocalProvider extends Provider { public getInvoker(serviceType, serviceInstance, params) { @@ -12,7 +13,7 @@ export class LocalProvider extends Provider { try { const params = [] for (const parameter of parameters) { - params[parameter.parameterIndex] = await this.parameterResolver(parameter, { req, res, next }) + params[parameter.parameterIndex] = await this.parameterResolver(parameter, { event: { req, res, next } }) } const r = await serviceInstance.handle(...params) @@ -25,30 +26,6 @@ export class LocalProvider extends Provider { return invoker } - protected async parameterResolver(parameter, event) { - const req = event.req - const source = parameter.source; - switch (parameter.type) { - case 'param': - if (typeof source !== 'undefined') { - const holder = !source ? req : get(req, source) - if (holder) { - return get(holder, parameter.from) - } - } else { - let value = undefined - if (typeof (value = get(req.body, parameter.from)) !== 'undefined') return value - if (typeof (value = get(req.query, parameter.from)) !== 'undefined') return value - if (typeof (value = get(req.params, parameter.from)) !== 'undefined') return value - if (typeof (value = get(req.headers, parameter.from)) !== 'undefined') return value - return value - } - return undefined - default: - return await super.parameterResolver(parameter, event) - } - } - public async invoke(serviceInstance, params, invokeConfig?) { const httpAttr = (getMetadata(rest.environmentKey, serviceInstance) || [])[0] @@ -101,4 +78,34 @@ export class LocalProvider extends Provider { } } +LocalProvider.addParameterDecoratorImplementation("param", async (parameter, context, provider) => { + const req = context.event.req + const source = parameter.source; + if (typeof source !== 'undefined') { + const holder = !source ? req : get(req, source) + if (holder) { + return get(holder, parameter.from) + } + } else { + let value = undefined + if (typeof (value = get(req.body, parameter.from)) !== 'undefined') return value + if (typeof (value = get(req.query, parameter.from)) !== 'undefined') return value + if (typeof (value = get(req.params, parameter.from)) !== 'undefined') return value + if (typeof (value = get(req.headers, parameter.from)) !== 'undefined') return value + return value + } + return undefined +}) + +LocalProvider.addParameterDecoratorImplementation("request", async (parameter, context, provider) => { + return { + url: context.event.req._parsedUrl || parse(context.event.req.originalUrl), + method: context.event.req.method, + body: context.event.req.body, + query: context.event.req.query, + params: context.event.req.params, + headers: context.event.req.headers + } +}) + export const provider = new LocalProvider() diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index e9b1e87..e02a14c 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -31,7 +31,7 @@ import { azure } from '../src/annotations/classes/azure/azure' import { inject } from '../src/annotations/parameters/inject' -import { param } from '../src/annotations/parameters/param' +import { param, request, serviceParams, createParameterDecorator } from '../src/annotations/parameters/param' import { FunctionalService, Service, DynamoDB, SimpleNotificationService, S3Storage, InjectService } from '../src/classes' @@ -1228,6 +1228,187 @@ describe('annotations', () => { expect(metadata).to.have.property('p1', 1) expect(metadata).to.have.property('p2', 'p2') }) + + describe('createParameterDecorator', () => { + it("inject", () => { + + const myDecorator = createParameterDecorator('myDecorator') + + class ParamClass { + method( @myDecorator p1) { } + } + + const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('from', 'p1') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'myDecorator') + }) + it("inject call", () => { + + const myDecorator = createParameterDecorator('myDecorator') + + class ParamClass { + method( @myDecorator() p1) { } + } + + const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('from', 'p1') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'myDecorator') + }) + it("inject default config", () => { + const defaultConfig = { c: 1 } + const myDecorator = createParameterDecorator('myDecorator', defaultConfig) + + class ParamClass { + method( @myDecorator p1) { } + } + + const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('from', 'p1') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'myDecorator') + expect(metadata).to.have.property('config').that.deep.equal(defaultConfig) + }) + it("inject call default config", () => { + const defaultConfig = { c: 1 } + const myDecorator = createParameterDecorator('myDecorator', defaultConfig) + + class ParamClass { + method( @myDecorator() p1) { } + } + + const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('from', 'p1') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'myDecorator') + expect(metadata).to.have.property('config').that.deep.equal(defaultConfig) + }) + it("inject config", () => { + const defaultConfig = { c: 1 } + const myDecorator = createParameterDecorator('myDecorator', defaultConfig) + + class ParamClass { + method( @myDecorator({ d: 3 }) p1) { } + } + + const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('from', 'p1') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'myDecorator') + expect(metadata).to.have.property('config').that.deep.equal({ c: 1, d: 3 }) + }) + it("inject config override", () => { + const defaultConfig = { c: 1 } + const myDecorator = createParameterDecorator('myDecorator', defaultConfig) + + class ParamClass { + method( @myDecorator({ c: 2 }) p1) { } + } + + const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('from', 'p1') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'myDecorator') + expect(metadata).to.have.property('config').that.deep.equal({ c: 2 }) + }) + }) + + describe('request', () => { + it("inject", () => { + class ParamClass { + method( @request p1) { } + } + + const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('from', 'p1') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'request') + }) + it("inject call", () => { + class ParamClass { + method( @request() p1) { } + } + + const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('from', 'p1') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'request') + }) + }) + + describe('serviceParams', () => { + it("inject", () => { + class ParamClass { + method( @serviceParams p1) { } + } + + const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('from', 'p1') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'serviceParams') + }) + it("inject call", () => { + class ParamClass { + method( @serviceParams() p1) { } + } + + const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('from', 'p1') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'serviceParams') + }) + }) }) }) }) \ No newline at end of file diff --git a/test/invoke.tests.ts b/test/invoke.tests.ts index c5e6b76..59d645a 100644 --- a/test/invoke.tests.ts +++ b/test/invoke.tests.ts @@ -5,7 +5,7 @@ import { LocalProvider } from '../src/providers/local' import { AWSProvider } from '../src/providers/aws' import { addProvider, removeProvider } from '../src/providers' import { FunctionalService } from '../src/classes/functionalService' -import { httpTrigger, inject, injectable, param, rest } from '../src/annotations' +import { httpTrigger, inject, injectable, param, rest, createParameterDecorator } from '../src/annotations' describe('invoke', () => { const FUNCTIONAL_ENVIRONMENT = 'custom' @@ -180,7 +180,7 @@ describe('invoke', () => { } const invoker = B.createInvoker() - process.env.FUNCTIONAL_ENVIRONMENT = 'unknown' + process.env.FUNCTIONAL_ENVIRONMENT = 'unknown' await invoker({}, { send: () => { expect(false).to.equal(true, 'error required') } }, (e) => { @@ -225,6 +225,145 @@ describe('invoke', () => { expect(counter).to.equal(2) }) + + describe('addParameterDecoratorImplementation', () => { + describe('inheritance', () => { + it("get not defined", () => { + class A extends LocalProvider { } + class B extends A { } + + expect(LocalProvider.getParameterDecoratorImplementation('aa')).is.undefined + expect(A.getParameterDecoratorImplementation('aa')).is.undefined + expect(B.getParameterDecoratorImplementation('aa')).is.undefined + }) + it("get defined", () => { + class A extends LocalProvider { } + class B extends A { } + + const f1 = () => { } + A.addParameterDecoratorImplementation('aa', f1) + + expect(LocalProvider.getParameterDecoratorImplementation('aa')).is.undefined + expect(A.getParameterDecoratorImplementation('aa')).to.equal(f1) + expect(B.getParameterDecoratorImplementation('aa')).to.equal(f1) + }) + }) + + it("add to provider", async () => { + let counter = 0 + + class TestProvider extends LocalProvider { } + const testProviderInstance = new TestProvider() + addProvider(FUNCTIONAL_ENVIRONMENT, testProviderInstance) + TestProvider.addParameterDecoratorImplementation('myDecorator', (parameter, context, provider) => { + counter++ + + expect(parameter).to.have.property("from", 'p1') + expect(parameter).to.have.property("type", 'myDecorator') + expect(parameter).to.have.property("parameterIndex", 0) + + expect(context).to.have.property("event") + expect(context).to.have.nested.property("event.req") + expect(context).to.have.nested.property("event.res") + expect(context).to.have.nested.property("event.next") + + expect(provider).to.equal(testProviderInstance) + + return { instance: 1 } + }) + + const myDecorator = createParameterDecorator('myDecorator') + + class A extends FunctionalService { + public async handle( @myDecorator p1) { + counter++ + + expect(p1).to.deep.equal({ instance: 1 }) + } + } + + const invoker = A.createInvoker() + await invoker({}, { + send: () => { counter++ } + }, (e) => { expect(false).to.equal(true, e.message) }) + + expect(counter).to.equal(3) + }) + it("add to provider base", async () => { + let counter = 0 + + class TestProviderBase extends LocalProvider { } + class TestProvider extends TestProviderBase { } + const testProviderInstance = new TestProvider() + addProvider(FUNCTIONAL_ENVIRONMENT, testProviderInstance) + TestProviderBase.addParameterDecoratorImplementation('myDecorator', (parameter, context, provider) => { + counter++ + + expect(parameter).to.have.property("from", 'p1') + expect(parameter).to.have.property("type", 'myDecorator') + expect(parameter).to.have.property("parameterIndex", 0) + + expect(context).to.have.property("event") + expect(context).to.have.nested.property("event.req") + expect(context).to.have.nested.property("event.res") + expect(context).to.have.nested.property("event.next") + + expect(provider).to.equal(testProviderInstance) + + return { instance: 1 } + }) + + const myDecorator = createParameterDecorator('myDecorator') + + class A extends FunctionalService { + public async handle( @myDecorator p1) { + counter++ + + expect(p1).to.deep.equal({ instance: 1 }) + } + } + + const invoker = A.createInvoker() + await invoker({}, { + send: () => { counter++ } + }, (e) => { + expect(false).to.equal(true, e.message) + }) + + expect(counter).to.equal(3) + }) + + it("exception in decorator", async () => { + let counter = 0 + + class TestProvider extends LocalProvider { } + const testProviderInstance = new TestProvider() + addProvider(FUNCTIONAL_ENVIRONMENT, testProviderInstance) + TestProvider.addParameterDecoratorImplementation('myDecorator', (parameter, context, provider) => { + counter++ + + throw new Error('e1') + }) + + const myDecorator = createParameterDecorator('myDecorator') + + class A extends FunctionalService { + public async handle( @myDecorator p1) { + expect(false).to.equal(true, 'error not catched') + } + } + + const invoker = A.createInvoker() + await invoker({}, { + send: () => { expect(false).to.equal(true, 'error not catched') } + }, (e) => { + counter++ + expect(e.message).to.equal('e1') + }) + + expect(counter).to.equal(2) + }) + }) }) describe('azure', () => { diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index 5bfdfa1..bebb262 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -2,7 +2,8 @@ import { expect } from 'chai' import { getInvoker } from '../src/providers' import { FunctionalService, Service } from '../src/classes' -import { param, inject, injectable, serviceParams } from '../src/annotations' +import { param, inject, injectable, serviceParams, request } from '../src/annotations' +import { parse } from 'url' describe('invoker', () => { @@ -374,6 +375,107 @@ describe('invoker', () => { const invoker = MockService.createInvoker() invoker(req, res, next) }) + + it("request originalUrl param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + @injectable + class MockInjectable extends Service { } + + const req = { + originalUrl: '/a/b', + method: 'GET', + body: { + p1: 'body' + }, + query: { + p2: 'query' + }, + params: { + p3: 'params' + }, + headers: { + p4: 'headers' + }, + anyprop: {} + } + const res = { + send: () => { + expect(counter).to.equal(1) + done() + } + } + const next = (e) => { e && done(e) } + + class MockService extends FunctionalService { + handle( @param p1, @request r) { + counter++ + expect(r).to.have.property('url').that.deep.equal(parse(req.originalUrl)) + expect(r).to.have.property('method', req.method) + expect(r).to.have.property('body').that.deep.equal(req.body) + expect(r).to.have.property('query').that.deep.equal(req.query) + expect(r).to.have.property('params').that.deep.equal(req.params) + expect(r).to.have.property('headers').that.deep.equal(req.headers) + expect(r).to.not.have.property('anyprop') + } + } + + const invoker = MockService.createInvoker() + invoker(req, res, next) + }) + + it("request _parsedUrl param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + @injectable + class MockInjectable extends Service { } + + const req = { + originalUrl: '/a/b?a=b', + _parsedUrl: parse('/b/c?a=b2'), + method: 'GET', + body: { + p1: 'body' + }, + query: { + p2: 'query' + }, + params: { + p3: 'params' + }, + headers: { + p4: 'headers' + }, + anyprop: {} + } + const res = { + send: () => { + expect(counter).to.equal(1) + done() + } + } + const next = (e) => { e && done(e) } + + class MockService extends FunctionalService { + handle( @param p1, @request r) { + counter++ + expect(r).to.have.property('url').that.deep.equal(req._parsedUrl) + expect(r).to.have.property('method', req.method) + expect(r).to.have.property('body').that.deep.equal(req.body) + expect(r).to.have.property('query').that.deep.equal(req.query) + expect(r).to.have.property('params').that.deep.equal(req.params) + expect(r).to.have.property('headers').that.deep.equal(req.headers) + expect(r).to.not.have.property('anyprop') + } + } + + const invoker = MockService.createInvoker() + invoker(req, res, next) + }) }) describe("aws", () => { @@ -945,6 +1047,55 @@ describe('invoker', () => { const invoker = MockService.createInvoker() invoker(awsEvent, awsContext, cb) }) + + it("request param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + @injectable + class MockInjectable extends Service { } + + const awsEvent = { + path: '/a/b', + httpMethod: 'GET', + requestContext: { apiId: 'apiId' }, + body: { + p1: 'body' + }, + queryStringParameters: { + p2: 'queryStringParameters' + }, + pathParameters: { + p3: 'pathParameters' + }, + headers: { + p4: 'headers' + }, + anyprop: {} + } + const awsContext = {} + const cb = (e) => { + expect(counter).to.equal(1) + done(e) + } + + class MockService extends FunctionalService { + handle( @param p1, @request r) { + counter++ + expect(r).to.have.property('url').that.deep.equal(parse(awsEvent.path)) + expect(r).to.have.property('method', awsEvent.httpMethod) + expect(r).to.have.property('body').that.deep.equal(awsEvent.body) + expect(r).to.have.property('query').that.deep.equal(awsEvent.queryStringParameters) + expect(r).to.have.property('params').that.deep.equal(awsEvent.pathParameters) + expect(r).to.have.property('headers').that.deep.equal(awsEvent.headers) + expect(r).to.not.have.property('anyprop') + } + } + + const invoker = MockService.createInvoker() + invoker(awsEvent, awsContext, cb) + }) }) describe("azure", () => { @@ -1370,5 +1521,51 @@ describe('invoker', () => { expect(counter).to.equal(1) }) + + it("request param", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + + @injectable + class MockInjectable extends Service { } + + const context = {} + const req = { + originalUrl: 'https://asd.azure.com/api/a/b?code=a&a=b', + method: 'GET', + body: { + p1: 'body' + }, + query: { + p2: 'queryStringParameters' + }, + params: { + p3: 'pathParameters' + }, + headers: { + p4: 'headers' + } + } + + class MockService extends FunctionalService { + handle( @param p1, @request r) { + counter++ + expect(r).to.have.property('url').that.deep.equal(parse(req.originalUrl)) + expect(r).to.have.property('method', req.method) + expect(r).to.have.property('body').that.deep.equal(req.body) + expect(r).to.have.property('query').that.deep.equal(req.query) + expect(r).to.have.property('params').that.deep.equal(req.params) + expect(r).to.have.property('headers').that.deep.equal(req.headers) + expect(r).to.not.have.property('anyprop') + } + } + + const invoker = MockService.createInvoker() + + await invoker(context, req) + + expect(counter).to.equal(1) + }) }) }) \ No newline at end of file From 2350371d04967f798b2f306d1aa3506cbad16a4b Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Thu, 27 Jul 2017 18:35:42 +0200 Subject: [PATCH 052/196] ADD: use decorator to use pre/post hooks on functional services; ADD: context, error decorators --- src/annotations/classes/use.ts | 20 ++++++ src/annotations/constants.ts | 1 + src/annotations/index.ts | 3 +- src/annotations/metadata.ts | 4 +- src/annotations/parameters/inject.ts | 4 +- src/annotations/parameters/param.ts | 9 ++- src/classes/index.ts | 5 +- src/classes/middleware/hook.ts | 39 +++++++++++ src/classes/middleware/postHook.ts | 26 +++++++ src/classes/middleware/preHook.ts | 3 + src/index.ts | 2 +- src/providers/aws/eventSources/apiGateway.ts | 15 ++-- src/providers/aws/eventSources/dynamoTable.ts | 19 +++-- src/providers/aws/eventSources/lambdaCall.ts | 18 ++++- src/providers/aws/eventSources/s3.ts | 20 ++++-- src/providers/aws/eventSources/sns.ts | 20 ++++-- src/providers/aws/index.ts | 13 ++-- .../azure/eventSources/httpTrigger.ts | 15 ++-- src/providers/azure/index.ts | 13 ++-- src/providers/core/eventSource.ts | 12 +--- src/providers/core/provider.ts | 70 +++++++++++++++++-- src/providers/index.ts | 7 +- src/providers/local.ts | 16 ++--- test/annotation.tests.ts | 38 +++++----- test/invoker.tests.ts | 21 ++++-- test/serviceOnEvent.tests.ts | 26 +++---- 26 files changed, 312 insertions(+), 127 deletions(-) create mode 100644 src/annotations/classes/use.ts create mode 100644 src/classes/middleware/hook.ts create mode 100644 src/classes/middleware/postHook.ts create mode 100644 src/classes/middleware/preHook.ts diff --git a/src/annotations/classes/use.ts b/src/annotations/classes/use.ts new file mode 100644 index 0000000..1a94f5f --- /dev/null +++ b/src/annotations/classes/use.ts @@ -0,0 +1,20 @@ +import { CLASS_MIDDLEWAREKEY } from '../constants' +import { getMetadata, defineMetadata } from '../metadata' +import { applyTemplates } from '../templates' + +export const use = (...middlewares) => { + return (target: Function) => { + const metadata = getMiddlewares(target) + defineMetadata(CLASS_MIDDLEWAREKEY, [...middlewares, ...metadata], target); + + for (const middleware of middlewares) { + if (typeof middleware.onDefineMiddlewareTo === 'function') { + middleware.onDefineMiddlewareTo(target) + } + } + } +} + +export const getMiddlewares = (target) => { + return getMetadata(CLASS_MIDDLEWAREKEY, target) || [] +} diff --git a/src/annotations/constants.ts b/src/annotations/constants.ts index 165461b..b76c387 100644 --- a/src/annotations/constants.ts +++ b/src/annotations/constants.ts @@ -4,6 +4,7 @@ export const PARAMETER_PARAMKEY = 'functionly:param:parameters' export const CLASS_DESCRIPTIONKEY = 'functionly:class:description' export const CLASS_ROLEKEY = 'functionly:class:role' export const CLASS_ENVIRONMENTKEY = 'functionly:class:environment' +export const CLASS_MIDDLEWAREKEY = 'functionly:class:middleware' export const CLASS_APIGATEWAYKEY = 'functionly:class:apigateway' export const CLASS_TAGKEY = 'functionly:class:tag' export const CLASS_LOGKEY = 'functionly:class:log' diff --git a/src/annotations/index.ts b/src/annotations/index.ts index bbb6851..79b1da8 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -14,6 +14,7 @@ export { eventSource } from './classes/eventSource' export { classConfig, getClassConfigValue } from './classes/classConfig' export { simpleClassAnnotation } from './classes/simpleAnnotation' export { expandableDecorator } from './classes/expandableDecorator' +export { use } from './classes/use' import { simpleClassAnnotation } from './classes/simpleAnnotation' @@ -25,7 +26,7 @@ export { aws } from './classes/aws/aws' export { azure } from './classes/azure/azure' -export { param, serviceParams, createParameterDecorator, request } from './parameters/param' +export { param, serviceParams, createParameterDecorator, request, context, error } from './parameters/param' export { inject } from './parameters/inject' import * as _constants from './constants' diff --git a/src/annotations/metadata.ts b/src/annotations/metadata.ts index 1bff7b8..4503b0a 100644 --- a/src/annotations/metadata.ts +++ b/src/annotations/metadata.ts @@ -4,8 +4,8 @@ export const defineMetadata = (metadataKey, metadataValue, target, propertyKey?) return Reflect.defineMetadata(metadataKey, metadataValue, target.prototype ? target.prototype : target, propertyKey) } -export const getMetadata = (metadataKey, target) => { - return Reflect.getMetadata(metadataKey, target.prototype ? target.prototype : target) +export const getMetadata = (metadataKey, target, propertyKey?) => { + return Reflect.getMetadata(metadataKey, target.prototype ? target.prototype : target, propertyKey) } export const getMetadataKeys = (target) => { diff --git a/src/annotations/parameters/inject.ts b/src/annotations/parameters/inject.ts index 692992f..9b94e69 100644 --- a/src/annotations/parameters/inject.ts +++ b/src/annotations/parameters/inject.ts @@ -1,5 +1,5 @@ import { PARAMETER_PARAMKEY, CLASS_ENVIRONMENTKEY, CLASS_INJECTABLEKEY } from '../constants' -import { getOwnMetadata, defineMetadata, getMetadata } from '../metadata' +import { defineMetadata, getMetadata } from '../metadata' import { getFunctionName } from '../classes/functionName' export const inject = (type: any, ...params): any => { @@ -8,7 +8,7 @@ export const inject = (type: any, ...params): any => { throw new Error(`type '${getFunctionName(type)}' not marked as injectable`) } - const existingParameters: any[] = getOwnMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; + const existingParameters: any[] = getMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; existingParameters.push({ serviceType: type, diff --git a/src/annotations/parameters/param.ts b/src/annotations/parameters/param.ts index 5a61208..c52a4bb 100644 --- a/src/annotations/parameters/param.ts +++ b/src/annotations/parameters/param.ts @@ -1,5 +1,5 @@ import { PARAMETER_PARAMKEY } from '../constants' -import { getOwnMetadata, defineMetadata } from '../metadata' +import { getMetadata, defineMetadata } from '../metadata' import { getFunctionParameters } from '../utils' export const param = (target: any, targetKey?: string, parameterIndex?: number): any => { @@ -8,7 +8,7 @@ export const param = (target: any, targetKey?: string, parameterIndex?: number): let decorator = function (target, targetKey, parameterIndex: number) { let parameterNames = getFunctionParameters(target, targetKey); - let existingParameters: any[] = getOwnMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; + let existingParameters: any[] = getMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; let paramName = parameterNames[parameterIndex]; existingParameters.push({ @@ -39,11 +39,12 @@ export const createParameterDecorator = (type: string, defaultConfig?: any) => ( const decorator = function (target, targetKey, parameterIndex: number) { const parameterNames = getFunctionParameters(target, targetKey); - const existingParameters: any[] = getOwnMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; + const existingParameters: any[] = getMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; const paramName = parameterNames[parameterIndex]; existingParameters.push({ from: paramName, parameterIndex, + targetKey, type, config }); @@ -62,3 +63,5 @@ export const createParameterDecorator = (type: string, defaultConfig?: any) => ( export const serviceParams = createParameterDecorator('serviceParams') export const request = createParameterDecorator('request') +export const context = createParameterDecorator('context') +export const error = createParameterDecorator('error') diff --git a/src/classes/index.ts b/src/classes/index.ts index 5759148..e3ee939 100644 --- a/src/classes/index.ts +++ b/src/classes/index.ts @@ -6,4 +6,7 @@ export { DynamoDB } from './externals/dynamoDB' export { SimpleNotificationService } from './externals/sns' export { S3Storage } from './externals/s3Storage' export { ApiGateway } from './externals/apiGateway' -export { callExtension } from './core/classExtensions' \ No newline at end of file +export { callExtension } from './core/classExtensions' + +export { PreHook } from './middleware/preHook' +export { PostHook } from './middleware/postHook' \ No newline at end of file diff --git a/src/classes/middleware/hook.ts b/src/classes/middleware/hook.ts new file mode 100644 index 0000000..bb79e80 --- /dev/null +++ b/src/classes/middleware/hook.ts @@ -0,0 +1,39 @@ +import { getMetadata, defineMetadata, constants, context } from '../../annotations' +import { getMiddlewares } from '../../annotations/classes/use' +const { PARAMETER_PARAMKEY, CLASS_ENVIRONMENTKEY } = constants + +export class Hook { + public handle(...params); + public handle( @context context) { + return context.result + } + + public static onDefineMiddlewareTo(target) { + const targetTkey = 'handle' + + const injectedDefinitions: any[] = (getMetadata(PARAMETER_PARAMKEY, this, targetTkey) || []) + .filter(p => p.type === 'inject') + + for (const { serviceType, targetKey, parameterIndex } of injectedDefinitions) { + if (typeof serviceType.onDefineInjectTo === 'function') { + serviceType.onDefineInjectTo(target, targetTkey, parameterIndex) + } + } + + const middlewares = getMiddlewares(this) + for (const middleware of middlewares) { + if (typeof middleware.onDefineMiddlewareTo === 'function') { + middleware.onDefineMiddlewareTo(target) + } + } + + const metadata = getMetadata(CLASS_ENVIRONMENTKEY, target) || {} + const injectMetadata = getMetadata(CLASS_ENVIRONMENTKEY, this) || {} + if (injectMetadata) { + Object.keys(injectMetadata).forEach((key) => { + metadata[key] = injectMetadata[key] + }) + defineMetadata(CLASS_ENVIRONMENTKEY, { ...metadata }, target); + } + } +} \ No newline at end of file diff --git a/src/classes/middleware/postHook.ts b/src/classes/middleware/postHook.ts new file mode 100644 index 0000000..2b4a127 --- /dev/null +++ b/src/classes/middleware/postHook.ts @@ -0,0 +1,26 @@ +import { Hook } from './hook' +import { param, getMetadata, constants } from '../../annotations' +const { PARAMETER_PARAMKEY } = constants + +export class PostHook extends Hook { + public catch(...params); + public catch( @param error) { + throw error + } + + + public static onDefineMiddlewareTo(target) { + super.onDefineMiddlewareTo(target) + + const targetTkey = 'catch' + + const injectedDefinitions: any[] = (getMetadata(PARAMETER_PARAMKEY, this, targetTkey) || []) + .filter(p => p.type === 'inject') + + for (const { serviceType, targetKey, parameterIndex } of injectedDefinitions) { + if (typeof serviceType.onDefineInjectTo === 'function') { + serviceType.onDefineInjectTo(target, targetTkey, parameterIndex) + } + } + } +} diff --git a/src/classes/middleware/preHook.ts b/src/classes/middleware/preHook.ts new file mode 100644 index 0000000..8ee56f4 --- /dev/null +++ b/src/classes/middleware/preHook.ts @@ -0,0 +1,3 @@ +import { Hook } from './hook' + +export class PreHook extends Hook { } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 47cee3d..144c606 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export { Service, FunctionalApi, FunctionalService, DynamoDB, SimpleNotificationService, S3Storage, ApiGateway, callExtension } from './classes' +export { Service, FunctionalApi, FunctionalService, DynamoDB, SimpleNotificationService, S3Storage, ApiGateway, callExtension, PreHook, PostHook } from './classes' export { addProvider, removeProvider, Provider, AWSProvider, LocalProvider } from './providers' import * as _annotations from './annotations' export const annotations = _annotations diff --git a/src/providers/aws/eventSources/apiGateway.ts b/src/providers/aws/eventSources/apiGateway.ts index 2e7c352..c925ffa 100644 --- a/src/providers/aws/eventSources/apiGateway.ts +++ b/src/providers/aws/eventSources/apiGateway.ts @@ -7,17 +7,17 @@ export class ApiGateway extends EventSource { return event && event.requestContext && event.requestContext.apiId ? true : false } - public async parameterResolver(parameter, event) { - const body = event.event.body - const query = event.event.queryStringParameters - const params = event.event.pathParameters - const headers = event.event.headers + public async parameterResolver(parameter, context) { + const body = context.event.event.body + const query = context.event.event.queryStringParameters + const params = context.event.event.pathParameters + const headers = context.event.event.headers switch (parameter.type) { case 'param': const source = parameter.source; if (typeof source !== 'undefined') { - const holder = !source ? event.event : get(event.event, source) + const holder = !source ? context : get(context, source) if (holder) { return get(holder, parameter.from) } @@ -27,11 +27,12 @@ export class ApiGateway extends EventSource { if (typeof (value = get(query, parameter.from)) !== 'undefined') return value if (typeof (value = get(params, parameter.from)) !== 'undefined') return value if (typeof (value = get(headers, parameter.from)) !== 'undefined') return value + if (typeof (value = get(context, parameter.from)) !== 'undefined') return value return value; } return undefined default: - return await super.parameterResolver(parameter, event) + return await super.parameterResolver(parameter, context) } } diff --git a/src/providers/aws/eventSources/dynamoTable.ts b/src/providers/aws/eventSources/dynamoTable.ts index 857f426..5f22131 100644 --- a/src/providers/aws/eventSources/dynamoTable.ts +++ b/src/providers/aws/eventSources/dynamoTable.ts @@ -7,14 +7,25 @@ export class DynamoTable extends EventSource { return event && Array.isArray(event.Records) && event.Records.length && event.Records[0].eventSource === "aws:dynamodb" ? true : false } - public async parameterResolver(parameter, event) { - const holder = this.getHolder(event.event.Records[0], event.event, parameter) + public async parameterResolver(parameter, context) { switch (parameter.type) { case 'param': - return get(holder, parameter.from) + const source = parameter.source; + if (typeof source !== 'undefined') { + const holder = !source ? context : get(context, source) + if (holder) { + return get(holder, parameter.from) + } + } else { + let value + if (typeof (value = get(context.event.event.Records[0], parameter.from)) !== 'undefined') return value + if (typeof (value = get(context, parameter.from)) !== 'undefined') return value + return value; + } + return undefined default: - return await super.parameterResolver(parameter, event) + return await super.parameterResolver(parameter, context) } } } \ No newline at end of file diff --git a/src/providers/aws/eventSources/lambdaCall.ts b/src/providers/aws/eventSources/lambdaCall.ts index d9e0f0e..545200f 100644 --- a/src/providers/aws/eventSources/lambdaCall.ts +++ b/src/providers/aws/eventSources/lambdaCall.ts @@ -2,12 +2,24 @@ import { EventSource } from '../../core/eventSource' import { get } from '../../../helpers/property' export class LambdaCall extends EventSource { - public async parameterResolver(parameter, event) { + public async parameterResolver(parameter, context) { switch (parameter.type) { case 'param': - return get(event.event, parameter.from) + const source = parameter.source; + if (typeof source !== 'undefined') { + const holder = !source ? context : get(context, source) + if (holder) { + return get(holder, parameter.from) + } + } else { + let value + if (typeof (value = get(context.event.event, parameter.from)) !== 'undefined') return value + if (typeof (value = get(context, parameter.from)) !== 'undefined') return value + return value; + } + return undefined default: - return await super.parameterResolver(parameter, event) + return await super.parameterResolver(parameter, context) } } } \ No newline at end of file diff --git a/src/providers/aws/eventSources/s3.ts b/src/providers/aws/eventSources/s3.ts index 1091ac5..3f89629 100644 --- a/src/providers/aws/eventSources/s3.ts +++ b/src/providers/aws/eventSources/s3.ts @@ -7,14 +7,24 @@ export class S3 extends EventSource { return event && Array.isArray(event.Records) && event.Records.length && event.Records[0].eventSource === "aws:s3" ? true : false } - public async parameterResolver(parameter, event) { - const holder = this.getHolder(event.event.Records[0], event.event, parameter) - + public async parameterResolver(parameter, context) { switch (parameter.type) { case 'param': - return get(holder, parameter.from) + const source = parameter.source; + if (typeof source !== 'undefined') { + const holder = !source ? context : get(context, source) + if (holder) { + return get(holder, parameter.from) + } + } else { + let value + if (typeof (value = get(context.event.event.Records[0], parameter.from)) !== 'undefined') return value + if (typeof (value = get(context, parameter.from)) !== 'undefined') return value + return value; + } + return undefined default: - return await super.parameterResolver(parameter, event) + return await super.parameterResolver(parameter, context) } } } \ No newline at end of file diff --git a/src/providers/aws/eventSources/sns.ts b/src/providers/aws/eventSources/sns.ts index 85da46f..81c6e37 100644 --- a/src/providers/aws/eventSources/sns.ts +++ b/src/providers/aws/eventSources/sns.ts @@ -7,14 +7,24 @@ export class SNS extends EventSource { return event && Array.isArray(event.Records) && event.Records.length && event.Records[0].EventSource === "aws:sns" ? true : false } - public async parameterResolver(parameter, event) { - const holder = this.getHolder(event.event.Records[0], event.event, parameter) - + public async parameterResolver(parameter, context) { switch (parameter.type) { case 'param': - return get(holder, parameter.from) + const source = parameter.source; + if (typeof source !== 'undefined') { + const holder = !source ? context : get(context, source) + if (holder) { + return get(holder, parameter.from) + } + } else { + let value + if (typeof (value = get(context.event.event.Records[0], parameter.from)) !== 'undefined') return value + if (typeof (value = get(context, parameter.from)) !== 'undefined') return value + return value; + } + return undefined default: - return await super.parameterResolver(parameter, event) + return await super.parameterResolver(parameter, context) } } } \ No newline at end of file diff --git a/src/providers/aws/index.ts b/src/providers/aws/index.ts index 281e76c..1fe5df6 100644 --- a/src/providers/aws/index.ts +++ b/src/providers/aws/index.ts @@ -27,8 +27,8 @@ const eventSourceHandlers = [ export class AWSProvider extends Provider { - public getInvoker(serviceType, serviceInstance, params): Function { - const parameters = this.getParameters(serviceType, 'handle') + public getInvoker(serviceInstance, params): Function { + const callContext = this.createCallContext(serviceInstance, 'handle') const invoker = async (event, context, cb) => { try { @@ -36,15 +36,10 @@ export class AWSProvider extends Provider { const eventSourceHandler = eventSourceHandlers.find(h => h.available(eventContext)) - const params = [] - for (const parameter of parameters) { - params[parameter.parameterIndex] = await this.parameterResolver(parameter, { eventSourceHandler, event: eventContext }) - } - let result let error try { - result = await serviceInstance.handle(...params) + result = await callContext({ eventSourceHandler, event: eventContext }) } catch (err) { error = err } @@ -84,7 +79,7 @@ export class AWSProvider extends Provider { } AWSProvider.addParameterDecoratorImplementation("param", async (parameter, context, provider) => { - return await context.eventSourceHandler.parameterResolver(parameter, context.event) + return await context.eventSourceHandler.parameterResolver(parameter, context) }) AWSProvider.addParameterDecoratorImplementation("request", async (parameter, context, provider) => { if (context.eventSourceHandler.constructor.name === 'ApiGateway') { diff --git a/src/providers/azure/eventSources/httpTrigger.ts b/src/providers/azure/eventSources/httpTrigger.ts index 9467e3b..5ed756a 100644 --- a/src/providers/azure/eventSources/httpTrigger.ts +++ b/src/providers/azure/eventSources/httpTrigger.ts @@ -2,17 +2,17 @@ import { EventSource } from '../../core/eventSource' import { get } from '../../../helpers/property' export class HttpTrigger extends EventSource { - public async parameterResolver(parameter, event) { - const body = event.req.body - const query = event.req.query - const params = event.req.params - const headers = event.req.headers + public async parameterResolver(parameter, context) { + const body = context.event.req.body + const query = context.event.req.query + const params = context.event.req.params + const headers = context.event.req.headers switch (parameter.type) { case 'param': const source = parameter.source; if (typeof source !== 'undefined') { - const holder = !source ? event.req : get(event.req, source) + const holder = !source ? context : get(context, source) if (holder) { return get(holder, parameter.from) } @@ -22,11 +22,12 @@ export class HttpTrigger extends EventSource { if (typeof (value = get(query, parameter.from)) !== 'undefined') return value if (typeof (value = get(params, parameter.from)) !== 'undefined') return value if (typeof (value = get(headers, parameter.from)) !== 'undefined') return value + if (typeof (value = get(context, parameter.from)) !== 'undefined') return value return value; } return undefined default: - return await super.parameterResolver(parameter, event) + return await super.parameterResolver(parameter, context) } } diff --git a/src/providers/azure/index.ts b/src/providers/azure/index.ts index 53ab214..a73c21a 100644 --- a/src/providers/azure/index.ts +++ b/src/providers/azure/index.ts @@ -13,8 +13,8 @@ export const FUNCTIONLY_FUNCTION_KEY = 'FUNCTIONLY_FUNCTION_KEY' export class AzureProvider extends Provider { - public getInvoker(serviceType, serviceInstance, params): Function { - const parameters = this.getParameters(serviceType, 'handle') + public getInvoker(serviceInstance, params): Function { + const callContext = this.createCallContext(serviceInstance, 'handle') const invoker = async (context, req) => { try { @@ -22,15 +22,10 @@ export class AzureProvider extends Provider { const eventSourceHandler = eventSourceHandlers.find(h => h.available(eventContext)) - const params = [] - for (const parameter of parameters) { - params[parameter.parameterIndex] = await this.parameterResolver(parameter, { eventSourceHandler, event: eventContext }) - } - let result let error try { - result = await serviceInstance.handle(...params) + result = await callContext({ eventSourceHandler, event: eventContext }) } catch (err) { error = err } @@ -103,7 +98,7 @@ export class AzureProvider extends Provider { } AzureProvider.addParameterDecoratorImplementation("param", async (parameter, context, provider) => { - return await context.eventSourceHandler.parameterResolver(parameter, context.event) + return await context.eventSourceHandler.parameterResolver(parameter, context) }) diff --git a/src/providers/core/eventSource.ts b/src/providers/core/eventSource.ts index 4e87307..e2a8542 100644 --- a/src/providers/core/eventSource.ts +++ b/src/providers/core/eventSource.ts @@ -5,7 +5,7 @@ export abstract class EventSource { return true } - public async parameterResolver(parameter, event: any) { + public async parameterResolver(parameter, context: any) { return undefined } @@ -13,14 +13,4 @@ export abstract class EventSource { if (err) throw err return result } - - protected getHolder(suggestedHolder, data, parameter) { - const source = parameter.source; - if (typeof source !== 'undefined') { - const holder = !source ? data : get(data, source) - return holder - } else { - return suggestedHolder - } - } } \ No newline at end of file diff --git a/src/providers/core/provider.ts b/src/providers/core/provider.ts index 7b7656b..5207ab3 100644 --- a/src/providers/core/provider.ts +++ b/src/providers/core/provider.ts @@ -1,8 +1,9 @@ -import { constants, getOwnMetadata } from '../../annotations' -import { callExtension } from '../../classes' +import { constants, getMetadata } from '../../annotations' +import { getMiddlewares } from '../../annotations/classes/use' +import { callExtension, PreHook, PostHook } from '../../classes' export abstract class Provider { - public getInvoker(serviceType, serviceInstance, params): Function { + public getInvoker(serviceInstance, params): Function { const invoker = () => { } return invoker } @@ -16,16 +17,66 @@ export abstract class Provider { } protected async parameterResolver(parameter, context): Promise { - const implementation = this.getParameterDecoratorImplementation(parameter.type) || (() => {}) + const implementation = this.getParameterDecoratorImplementation(parameter.type) || (() => { }) return implementation(parameter, context, this) } + protected createCallContext(target, method) { + const hooks = getMiddlewares(target).map(m => new m()) + const parameters = this.getParameters(target.constructor, method) + + const preHooks = hooks.filter(h => h instanceof PreHook) + .map(h => this.createCallContext(h, 'handle')) + const postHookInstances = hooks.filter(h => h instanceof PostHook) + const postHooks = postHookInstances.map(h => this.createCallContext(h, 'handle')) + const catchHooks = postHookInstances.map(h => this.createCallContext(h, 'catch')) + + return async (context) => { + const preic: any = {} + const preContext = { context: preic, ...context } + + try { + for (const hook of preHooks) { + await hook(preContext) + } + + const params = [] + for (const parameter of parameters) { + params[parameter.parameterIndex] = await this.parameterResolver(parameter, preContext) + } + + preic.result = await target[method](...params) + preic.error = undefined + } catch (e) { + preic.error = e + preic.result = undefined + } + + const ic = { ...preic } + const postContext = { ...preContext, context: ic } + for (let hookIndex = 0; hookIndex < postHookInstances.length; hookIndex++) { + try { + if (ic.error) { + ic.result = await catchHooks[hookIndex](postContext) + } else { + ic.result = await postHooks[hookIndex](postContext) + } + ic.error = undefined + } catch (e) { + ic.error = e + } + } + + if (ic.error) throw ic.error + return ic.result + } + } + protected getParameters(target, method) { - return (getOwnMetadata(constants.PARAMETER_PARAMKEY, target, 'handle') || []) + return (getMetadata(constants.PARAMETER_PARAMKEY, target, method) || []) .filter(t => t && typeof t.parameterIndex === 'number'); } - public static __supportedDecorators: { [key: string]: Function } public static __getDecoratorHolder() { return this.__supportedDecorators = this.hasOwnProperty('__supportedDecorators') ? this.__supportedDecorators : {} @@ -60,3 +111,10 @@ Provider.addParameterDecoratorImplementation("inject", async (parameter, context Provider.addParameterDecoratorImplementation("serviceParams", async (parameter, context, provider) => { return context.event }) + +Provider.addParameterDecoratorImplementation("context", async (parameter, context, provider) => { + return context.context +}) +Provider.addParameterDecoratorImplementation("error", async (parameter, context, provider) => { + return parameter.targetKey === 'catch' ? (context.context && context.context.error) : undefined +}) \ No newline at end of file diff --git a/src/providers/index.ts b/src/providers/index.ts index d66623a..ac7372a 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1,4 +1,5 @@ -import { constants, getOwnMetadata } from '../annotations' +import { constants, getMetadata } from '../annotations' +const { PARAMETER_PARAMKEY } = constants import { callExtension } from '../classes' export { Provider } from './core/provider' @@ -38,7 +39,7 @@ export const getInvoker = (serviceType, params) => { const currentEnvironment = environments[environment] const serviceInstance = new serviceType(...params) - const invoker = currentEnvironment.getInvoker(serviceType, serviceInstance, params) + const invoker = currentEnvironment.getInvoker(serviceInstance, params) const invokeHandler = async (...params) => { const onHandleResult = await callExtension(serviceInstance, `onHandle_${environment}`, ...params) @@ -73,7 +74,7 @@ export const invoke = async (serviceInstance, params?, invokeConfig?) => { let currentEnvironment = invokeEnvironments[environmentMode] const availableParams = {} - const parameterMapping = (getOwnMetadata(constants.PARAMETER_PARAMKEY, serviceInstance.constructor, 'handle') || []) + const parameterMapping = (getMetadata(PARAMETER_PARAMKEY, serviceInstance.constructor, 'handle') || []) parameterMapping.forEach((target) => { if (params && target && target.type === 'param') { availableParams[target.from] = params[target.from] diff --git a/src/providers/local.ts b/src/providers/local.ts index 00f47e9..a72e4e6 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -1,22 +1,17 @@ import * as request from 'request' import { Provider } from './core/provider' -import { constants, getOwnMetadata, getMetadata, getFunctionName, rest } from '../annotations' +import { constants, getMetadata, getFunctionName, rest } from '../annotations' const { CLASS_LOGKEY } = constants import { get } from '../helpers/property' import { parse } from 'url' export class LocalProvider extends Provider { - public getInvoker(serviceType, serviceInstance, params) { - const parameters = this.getParameters(serviceType, 'handle') + public getInvoker(serviceInstance, params) { + const callContext = this.createCallContext(serviceInstance, 'handle') const invoker = async (req, res, next) => { try { - const params = [] - for (const parameter of parameters) { - params[parameter.parameterIndex] = await this.parameterResolver(parameter, { event: { req, res, next } }) - } - - const r = await serviceInstance.handle(...params) + const r = await callContext({ event: { req, res, next } }) res.send(r) return r } catch (e) { @@ -82,7 +77,7 @@ LocalProvider.addParameterDecoratorImplementation("param", async (parameter, con const req = context.event.req const source = parameter.source; if (typeof source !== 'undefined') { - const holder = !source ? req : get(req, source) + const holder = !source ? context : get(context, source) if (holder) { return get(holder, parameter.from) } @@ -92,6 +87,7 @@ LocalProvider.addParameterDecoratorImplementation("param", async (parameter, con if (typeof (value = get(req.query, parameter.from)) !== 'undefined') return value if (typeof (value = get(req.params, parameter.from)) !== 'undefined') return value if (typeof (value = get(req.headers, parameter.from)) !== 'undefined') return value + if (typeof (value = get(context.context, parameter.from)) !== 'undefined') return value return value } return undefined diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index e02a14c..24add96 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -9,7 +9,7 @@ import { } from '../src/annotations/constants' import { applyTemplates, templates } from '../src/annotations/templates' import { getFunctionParameters } from '../src/annotations/utils' -import { getMetadata, getOwnMetadata } from '../src/annotations/metadata' +import { getMetadata } from '../src/annotations/metadata' import { expandableDecorator } from '../src/annotations/classes/expandableDecorator' import { apiGateway } from '../src/annotations/classes/aws/apiGateway' import { httpTrigger } from '../src/annotations/classes/azure/httpTrigger' @@ -1035,7 +1035,7 @@ describe('annotations', () => { method( @inject(ATestClass) a) { } } - const value = getOwnMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') + const value = getMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') expect(value).to.have.lengthOf(1); @@ -1053,7 +1053,7 @@ describe('annotations', () => { method( @inject(ATestClass) a) { } } - const value = getOwnMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') + const value = getMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') expect(value).to.have.lengthOf(1); @@ -1076,7 +1076,7 @@ describe('annotations', () => { method( @inject(ATestClass) a) { } } - const value = getOwnMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') + const value = getMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') expect(value).to.have.lengthOf(1); @@ -1148,7 +1148,7 @@ describe('annotations', () => { method( @param name) { } } - const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1163,7 +1163,7 @@ describe('annotations', () => { method( @param('fullName') name) { } } - const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1178,7 +1178,7 @@ describe('annotations', () => { method( @param name, @param fullName) { } } - const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(2); @@ -1199,7 +1199,7 @@ describe('annotations', () => { method( @param({ name: 'fullName', p1: 1, p2: 'p2' }) name) { } } - const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1216,7 +1216,7 @@ describe('annotations', () => { method( @param({ p1: 1, p2: 'p2' }) shortName) { } } - const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1238,7 +1238,7 @@ describe('annotations', () => { method( @myDecorator p1) { } } - const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1256,7 +1256,7 @@ describe('annotations', () => { method( @myDecorator() p1) { } } - const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1274,7 +1274,7 @@ describe('annotations', () => { method( @myDecorator p1) { } } - const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1293,7 +1293,7 @@ describe('annotations', () => { method( @myDecorator() p1) { } } - const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1312,7 +1312,7 @@ describe('annotations', () => { method( @myDecorator({ d: 3 }) p1) { } } - const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1331,7 +1331,7 @@ describe('annotations', () => { method( @myDecorator({ c: 2 }) p1) { } } - const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1350,7 +1350,7 @@ describe('annotations', () => { method( @request p1) { } } - const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1365,7 +1365,7 @@ describe('annotations', () => { method( @request() p1) { } } - const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1383,7 +1383,7 @@ describe('annotations', () => { method( @serviceParams p1) { } } - const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1398,7 +1398,7 @@ describe('annotations', () => { method( @serviceParams() p1) { } } - const value = getOwnMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index bebb262..4fae61a 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -280,7 +280,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' class MockService extends FunctionalService { - handle( @param({ source: 'query' }) p1, @param({ source: 'params' }) p2, @param({ source: 'headers' }) p3, @param p4) { + handle( @param({ source: 'event.req.query' }) p1, @param({ source: 'event.req.params' }) p2, @param({ source: 'event.req.headers' }) p3, @param p4) { counter++ expect(p1).to.equal('query') expect(p2).to.equal('params') @@ -714,7 +714,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - handle( @param({ source: 'queryStringParameters' }) p1, @param({ source: 'pathParameters' }) p2, @param({ source: 'headers' }) p3, @param p4) { + handle( @param({ source: 'event.event.queryStringParameters' }) p1, @param({ source: 'event.event.pathParameters' }) p2, @param({ source: 'event.event.headers' }) p3, @param p4) { counter++ expect(p1).to.equal('queryStringParameters') expect(p2).to.equal('pathParameters') @@ -912,7 +912,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle( @param s3, @param({ name: 'Records', source: null }) p2) { + handle( @param s3, @param({ name: 'event.event.Records', source: null }) p2) { counter++ expect(s3).to.deep.equal(awsEvent.Records[0].s3) expect(p2).to.have.lengthOf(1); @@ -977,7 +977,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle( @param Sns, @param({ name: 'Records', source: null }) p2) { + handle( @param Sns, @param({ name: 'event.event.Records', source: null }) p2) { counter++ expect(Sns).to.deep.equal(awsEvent.Records[0].Sns) expect(p2).to.have.lengthOf(1); @@ -1197,6 +1197,7 @@ describe('invoker', () => { await invoker(context, req) + expect(context).to.have.nested.property('res.status', 200) expect(counter).to.equal(1) }) @@ -1224,6 +1225,7 @@ describe('invoker', () => { await invoker(context, req) + expect(context).to.have.nested.property('res.status', 200) expect(counter).to.equal(1) }) @@ -1251,6 +1253,7 @@ describe('invoker', () => { await invoker(context, req) + expect(context).to.have.nested.property('res.status', 200) expect(counter).to.equal(1) }) @@ -1278,6 +1281,7 @@ describe('invoker', () => { await invoker(context, req) + expect(context).to.have.nested.property('res.status', 200) expect(counter).to.equal(1) }) @@ -1321,15 +1325,16 @@ describe('invoker', () => { await invoker(context, req) + expect(context).to.have.nested.property('res.status', 200) expect(counter).to.equal(1) }) - + it("httpTrigger params resolve hint", async () => { let counter = 0 process.env.FUNCTIONAL_ENVIRONMENT = 'azure' class MockService extends FunctionalService { - handle( @param({ source: 'queryStringParameters' }) p1, @param({ source: 'pathParameters' }) p2, @param({ source: 'headers' }) p3, @param p4) { + handle( @param({ source: 'event.req.query' }) p1, @param({ source: 'event.req.params' }) p2, @param({ source: 'event.req.headers' }) p3, @param p4) { counter++ expect(p1).to.equal('queryStringParameters') expect(p2).to.equal('pathParameters') @@ -1364,6 +1369,7 @@ describe('invoker', () => { await invoker(context, req) + expect(context).to.have.nested.property('res.status', 200) expect(counter).to.equal(1) }) @@ -1492,6 +1498,7 @@ describe('invoker', () => { await invoker(context, req) + expect(context).to.have.nested.property('res.status', 200) expect(counter).to.equal(1) }) @@ -1519,6 +1526,7 @@ describe('invoker', () => { await invoker(context, req) + expect(context).to.have.nested.property('res.status', 200) expect(counter).to.equal(1) }) @@ -1565,6 +1573,7 @@ describe('invoker', () => { await invoker(context, req) + expect(context).to.have.nested.property('res.status', 200) expect(counter).to.equal(1) }) }) diff --git a/test/serviceOnEvent.tests.ts b/test/serviceOnEvent.tests.ts index cc2d8b5..9820ec0 100644 --- a/test/serviceOnEvent.tests.ts +++ b/test/serviceOnEvent.tests.ts @@ -6,7 +6,7 @@ import { addProvider, removeProvider } from '../src/providers' import { LocalProvider } from '../src/providers/local' import { PARAMETER_PARAMKEY } from '../src/annotations/constants' -import { getMetadata, getOwnMetadata } from '../src/annotations/metadata' +import { getMetadata } from '../src/annotations/metadata' describe('service events', () => { afterEach(() => { @@ -25,7 +25,7 @@ describe('service events', () => { public static async onInject({ parameter }) { counter++ - const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -60,7 +60,7 @@ describe('service events', () => { public static async onInject({ parameter }) { counter++ - const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -98,7 +98,7 @@ describe('service events', () => { public async onInject({ parameter }) { counter++ - const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -133,7 +133,7 @@ describe('service events', () => { public static async onInject({ parameter }) { counter++ - const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -142,7 +142,7 @@ describe('service events', () => { public async onInject({ parameter }) { counter++ - const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -177,7 +177,7 @@ describe('service events', () => { public static async onInject({ parameter }) { counter++ - const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -295,7 +295,7 @@ describe('service events', () => { public static async onInject({ parameter }) { counter++ - const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -328,7 +328,7 @@ describe('service events', () => { public static async onInject({ parameter }) { counter++ - const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -364,7 +364,7 @@ describe('service events', () => { public async onInject({ parameter }) { counter++ - const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -397,7 +397,7 @@ describe('service events', () => { public static async onInject({ parameter }) { counter++ - const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -406,7 +406,7 @@ describe('service events', () => { public async onInject({ parameter }) { counter++ - const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -439,7 +439,7 @@ describe('service events', () => { public static async onInject({ parameter }) { counter++ - const value = getOwnMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] From 0bd0981176859f0eeeaf5db6c04d81849446dadb Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Fri, 28 Jul 2017 11:29:33 +0200 Subject: [PATCH 053/196] FIX: property decorators on overrided methods --- src/annotations/index.ts | 2 +- src/annotations/metadata.ts | 8 ++ src/annotations/parameters/inject.ts | 7 +- src/annotations/parameters/param.ts | 7 +- src/providers/core/provider.ts | 5 +- src/providers/index.ts | 4 +- test/annotation.tests.ts | 163 +++++++++++++++++++++++---- test/serviceOnEvent.tests.ts | 27 ++--- 8 files changed, 175 insertions(+), 48 deletions(-) diff --git a/src/annotations/index.ts b/src/annotations/index.ts index 79b1da8..096443d 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -32,4 +32,4 @@ export { inject } from './parameters/inject' import * as _constants from './constants' export const constants = _constants -export { defineMetadata, getMetadata, getMetadataKeys, getOwnMetadata } from './metadata' \ No newline at end of file +export { defineMetadata, getMetadata, getMetadataKeys, getOwnMetadata, getOverridableMetadata } from './metadata' \ No newline at end of file diff --git a/src/annotations/metadata.ts b/src/annotations/metadata.ts index 4503b0a..5fb7406 100644 --- a/src/annotations/metadata.ts +++ b/src/annotations/metadata.ts @@ -14,4 +14,12 @@ export const getMetadataKeys = (target) => { export const getOwnMetadata = (metadataKey, target, propertyKey?) => { return Reflect.getOwnMetadata(metadataKey, target.prototype ? target.prototype : target, propertyKey) +} + +export const getOverridableMetadata = (metadataKey, target, propertyKey) => { + if (!target.prototype || target.prototype.hasOwnProperty(propertyKey)) { + return getOwnMetadata(metadataKey, target, propertyKey) + } else { + return getMetadata(metadataKey, target, propertyKey) + } } \ No newline at end of file diff --git a/src/annotations/parameters/inject.ts b/src/annotations/parameters/inject.ts index 9b94e69..3d8894f 100644 --- a/src/annotations/parameters/inject.ts +++ b/src/annotations/parameters/inject.ts @@ -1,18 +1,19 @@ import { PARAMETER_PARAMKEY, CLASS_ENVIRONMENTKEY, CLASS_INJECTABLEKEY } from '../constants' -import { defineMetadata, getMetadata } from '../metadata' +import { defineMetadata, getOwnMetadata, getOverridableMetadata } from '../metadata' import { getFunctionName } from '../classes/functionName' export const inject = (type: any, ...params): any => { return (target, targetKey, parameterIndex: number) => { - if (!getMetadata(CLASS_INJECTABLEKEY, type)) { + if (!getOwnMetadata(CLASS_INJECTABLEKEY, type)) { throw new Error(`type '${getFunctionName(type)}' not marked as injectable`) } - const existingParameters: any[] = getMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; + const existingParameters: any[] = getOverridableMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; existingParameters.push({ serviceType: type, parameterIndex, + targetKey, type: 'inject', params }); diff --git a/src/annotations/parameters/param.ts b/src/annotations/parameters/param.ts index c52a4bb..f0e55a2 100644 --- a/src/annotations/parameters/param.ts +++ b/src/annotations/parameters/param.ts @@ -1,5 +1,5 @@ import { PARAMETER_PARAMKEY } from '../constants' -import { getMetadata, defineMetadata } from '../metadata' +import { getOverridableMetadata, defineMetadata, getMetadata } from '../metadata' import { getFunctionParameters } from '../utils' export const param = (target: any, targetKey?: string, parameterIndex?: number): any => { @@ -8,13 +8,14 @@ export const param = (target: any, targetKey?: string, parameterIndex?: number): let decorator = function (target, targetKey, parameterIndex: number) { let parameterNames = getFunctionParameters(target, targetKey); - let existingParameters: any[] = getMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; + let existingParameters: any[] = getOverridableMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; let paramName = parameterNames[parameterIndex]; existingParameters.push({ ...config, from: name || paramName, parameterIndex, + targetKey, type: 'param' }); @@ -39,7 +40,7 @@ export const createParameterDecorator = (type: string, defaultConfig?: any) => ( const decorator = function (target, targetKey, parameterIndex: number) { const parameterNames = getFunctionParameters(target, targetKey); - const existingParameters: any[] = getMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; + const existingParameters: any[] = getOverridableMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; const paramName = parameterNames[parameterIndex]; existingParameters.push({ from: paramName, diff --git a/src/providers/core/provider.ts b/src/providers/core/provider.ts index 5207ab3..44683e0 100644 --- a/src/providers/core/provider.ts +++ b/src/providers/core/provider.ts @@ -1,4 +1,5 @@ -import { constants, getMetadata } from '../../annotations' +import { constants, getMetadata, getOverridableMetadata } from '../../annotations' +const { PARAMETER_PARAMKEY } = constants import { getMiddlewares } from '../../annotations/classes/use' import { callExtension, PreHook, PostHook } from '../../classes' @@ -73,7 +74,7 @@ export abstract class Provider { } protected getParameters(target, method) { - return (getMetadata(constants.PARAMETER_PARAMKEY, target, method) || []) + return (getOverridableMetadata(PARAMETER_PARAMKEY, target, method) || []) .filter(t => t && typeof t.parameterIndex === 'number'); } diff --git a/src/providers/index.ts b/src/providers/index.ts index ac7372a..340916c 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1,4 +1,4 @@ -import { constants, getMetadata } from '../annotations' +import { constants, getMetadata, getOverridableMetadata } from '../annotations' const { PARAMETER_PARAMKEY } = constants import { callExtension } from '../classes' @@ -74,7 +74,7 @@ export const invoke = async (serviceInstance, params?, invokeConfig?) => { let currentEnvironment = invokeEnvironments[environmentMode] const availableParams = {} - const parameterMapping = (getMetadata(PARAMETER_PARAMKEY, serviceInstance.constructor, 'handle') || []) + const parameterMapping = (getOverridableMetadata(PARAMETER_PARAMKEY, serviceInstance.constructor, 'handle') || []) parameterMapping.forEach((target) => { if (params && target && target.type === 'param') { availableParams[target.from] = params[target.from] diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index 24add96..b801414 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -9,7 +9,7 @@ import { } from '../src/annotations/constants' import { applyTemplates, templates } from '../src/annotations/templates' import { getFunctionParameters } from '../src/annotations/utils' -import { getMetadata } from '../src/annotations/metadata' +import { getMetadata, getOverridableMetadata } from '../src/annotations/metadata' import { expandableDecorator } from '../src/annotations/classes/expandableDecorator' import { apiGateway } from '../src/annotations/classes/aws/apiGateway' import { httpTrigger } from '../src/annotations/classes/azure/httpTrigger' @@ -1035,7 +1035,7 @@ describe('annotations', () => { method( @inject(ATestClass) a) { } } - const value = getMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') expect(value).to.have.lengthOf(1); @@ -1053,7 +1053,7 @@ describe('annotations', () => { method( @inject(ATestClass) a) { } } - const value = getMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') expect(value).to.have.lengthOf(1); @@ -1076,7 +1076,7 @@ describe('annotations', () => { method( @inject(ATestClass) a) { } } - const value = getMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') expect(value).to.have.lengthOf(1); @@ -1141,14 +1141,73 @@ describe('annotations', () => { expect(metadata).to.have.property('bucketName', 'abucket') }) + + it("overrided class method", () => { + @injectable + class ATestClass { } + + class BaseBTestClass { + method( @inject(ATestClass) p1, @inject(ATestClass) p2) { } + } + class BTestClass extends BaseBTestClass { + method( @inject(ATestClass) a) { } + } + + const value = getOverridableMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('serviceType', ATestClass) + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'inject') + }) + + it("overrided class method no inject", () => { + @injectable + class ATestClass { } + + class BaseBTestClass { + method( @inject(ATestClass) p1, @inject(ATestClass) p2) { } + } + class BTestClass extends BaseBTestClass { + method() { } + } + + const value = getOverridableMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') + expect(value).to.undefined + }) + + it("not overrided class method", () => { + @injectable + class ATestClass { } + + class BaseBTestClass { + method( @inject(ATestClass) a) { } + } + class BTestClass extends BaseBTestClass { + + } + + const value = getOverridableMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('serviceType', ATestClass) + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'inject') + }) }) describe("param", () => { - it("inject", () => { + it("param", () => { class ParamClass { method( @param name) { } } - const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1158,12 +1217,12 @@ describe('annotations', () => { expect(metadata).to.have.property('parameterIndex', 0) expect(metadata).to.have.property('type', 'param') }) - it("inject custom name", () => { + it("custom name", () => { class ParamClass { method( @param('fullName') name) { } } - const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1173,12 +1232,12 @@ describe('annotations', () => { expect(metadata).to.have.property('parameterIndex', 0) expect(metadata).to.have.property('type', 'param') }) - it("inject param index", () => { + it("param index", () => { class ParamClass { method( @param name, @param fullName) { } } - const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(2); @@ -1194,12 +1253,12 @@ describe('annotations', () => { expect(matadata2).to.have.property('parameterIndex', 0) expect(matadata2).to.have.property('type', 'param') }) - it("inject with config", () => { + it("with config", () => { class ParamClass { method( @param({ name: 'fullName', p1: 1, p2: 'p2' }) name) { } } - const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1211,12 +1270,12 @@ describe('annotations', () => { expect(metadata).to.have.property('p1', 1) expect(metadata).to.have.property('p2', 'p2') }) - it("inject with config without name", () => { + it("with config without name", () => { class ParamClass { method( @param({ p1: 1, p2: 'p2' }) shortName) { } } - const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1238,7 +1297,7 @@ describe('annotations', () => { method( @myDecorator p1) { } } - const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1256,7 +1315,7 @@ describe('annotations', () => { method( @myDecorator() p1) { } } - const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1274,7 +1333,7 @@ describe('annotations', () => { method( @myDecorator p1) { } } - const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1293,7 +1352,7 @@ describe('annotations', () => { method( @myDecorator() p1) { } } - const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1312,7 +1371,7 @@ describe('annotations', () => { method( @myDecorator({ d: 3 }) p1) { } } - const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1331,7 +1390,7 @@ describe('annotations', () => { method( @myDecorator({ c: 2 }) p1) { } } - const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1350,7 +1409,7 @@ describe('annotations', () => { method( @request p1) { } } - const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1365,7 +1424,7 @@ describe('annotations', () => { method( @request() p1) { } } - const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1383,7 +1442,7 @@ describe('annotations', () => { method( @serviceParams p1) { } } - const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1398,7 +1457,7 @@ describe('annotations', () => { method( @serviceParams() p1) { } } - const value = getMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') expect(value).to.have.lengthOf(1); @@ -1409,6 +1468,62 @@ describe('annotations', () => { expect(metadata).to.have.property('type', 'serviceParams') }) }) + + it("overrided class method", () => { + + class BaseParamClass { + method( @param p1, @param p2) { } + } + + class ParamClass extends BaseParamClass { + method( @param name) { } + } + + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('from', 'name') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'param') + }) + + it("overrided class method no param", () => { + + class BaseParamClass { + method( @param p1, @param p2) { } + } + + class ParamClass extends BaseParamClass { + method() { } + } + + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + expect(value).to.undefined + }) + + it("not overrided class method", () => { + + class BaseParamClass { + method( @param name) { } + } + + class ParamClass extends BaseParamClass { + + } + + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('from', 'name') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'param') + }) }) }) }) \ No newline at end of file diff --git a/test/serviceOnEvent.tests.ts b/test/serviceOnEvent.tests.ts index 9820ec0..6ae5a30 100644 --- a/test/serviceOnEvent.tests.ts +++ b/test/serviceOnEvent.tests.ts @@ -6,7 +6,7 @@ import { addProvider, removeProvider } from '../src/providers' import { LocalProvider } from '../src/providers/local' import { PARAMETER_PARAMKEY } from '../src/annotations/constants' -import { getMetadata } from '../src/annotations/metadata' +import { getOverridableMetadata } from '../src/annotations/metadata' describe('service events', () => { afterEach(() => { @@ -25,7 +25,7 @@ describe('service events', () => { public static async onInject({ parameter }) { counter++ - const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -60,7 +60,7 @@ describe('service events', () => { public static async onInject({ parameter }) { counter++ - const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -98,7 +98,7 @@ describe('service events', () => { public async onInject({ parameter }) { counter++ - const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -133,7 +133,7 @@ describe('service events', () => { public static async onInject({ parameter }) { counter++ - const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -142,7 +142,7 @@ describe('service events', () => { public async onInject({ parameter }) { counter++ - const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -177,7 +177,7 @@ describe('service events', () => { public static async onInject({ parameter }) { counter++ - const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -295,7 +295,7 @@ describe('service events', () => { public static async onInject({ parameter }) { counter++ - const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -328,7 +328,7 @@ describe('service events', () => { public static async onInject({ parameter }) { counter++ - const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -364,7 +364,7 @@ describe('service events', () => { public async onInject({ parameter }) { counter++ - const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -397,7 +397,7 @@ describe('service events', () => { public static async onInject({ parameter }) { counter++ - const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -406,7 +406,7 @@ describe('service events', () => { public async onInject({ parameter }) { counter++ - const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -439,7 +439,7 @@ describe('service events', () => { public static async onInject({ parameter }) { counter++ - const value = getMetadata(PARAMETER_PARAMKEY, MockService, 'handle') + const value = getOverridableMetadata(PARAMETER_PARAMKEY, MockService, 'handle') expect(value).to.have.lengthOf(1); const metadata = value[0] @@ -605,6 +605,7 @@ describe('service events', () => { expect(parameterMapping[0]).to.deep.equal({ from: 'p1', parameterIndex: 0, + targetKey: "handle", type: 'param' }) expect(currentEnvironment).to.instanceof(MockProvider) From 5565de97098aca84f0f79e5d803481fca5cbba59 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Fri, 28 Jul 2017 14:36:38 +0200 Subject: [PATCH 054/196] ADD: hook tests --- test/hook.tests.ts | 1221 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1221 insertions(+) create mode 100644 test/hook.tests.ts diff --git a/test/hook.tests.ts b/test/hook.tests.ts new file mode 100644 index 0000000..ece606d --- /dev/null +++ b/test/hook.tests.ts @@ -0,0 +1,1221 @@ +import { expect } from 'chai' +import { FunctionalService, PreHook, PostHook, Service, DynamoDB, SimpleNotificationService, S3Storage } from '../src/classes' +import { getOverridableMetadata, constants, getMetadata, getFunctionName } from '../src/annotations' +const { PARAMETER_PARAMKEY, CLASS_ENVIRONMENTKEY, CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_SNSCONFIGURATIONKEY, CLASS_S3CONFIGURATIONKEY, + CLASS_MIDDLEWAREKEY } = constants +import { use, context, error, param, inject, injectable, environment, dynamoTable, sns, s3Storage } from '../src/annotations' + + +describe('hooks', () => { + beforeEach(() => { + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + }) + afterEach(() => { + delete process.env.FUNCTIONAL_ENVIRONMENT + }) + + describe('use annotation', () => { + describe('general', () => { + it("use", () => { + class TestHook extends PreHook { + public async handle() { } + } + + @use(TestHook) + class BTestClass extends FunctionalService { + public async handle() { } + } + + const value = getMetadata(CLASS_MIDDLEWAREKEY, BTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.equal(TestHook) + }) + it("use multiple param", () => { + class TestHook1 extends PreHook { + public async handle() { } + } + class TestHook2 extends PreHook { + public async handle() { } + } + class TestHook3 extends PreHook { + public async handle() { } + } + + @use(TestHook1, TestHook2, TestHook3) + class BTestClass extends FunctionalService { + public async handle() { } + } + + const value = getMetadata(CLASS_MIDDLEWAREKEY, BTestClass) + + expect(value).to.have.lengthOf(3); + + expect(value[0]).to.equal(TestHook1) + expect(value[1]).to.equal(TestHook2) + expect(value[2]).to.equal(TestHook3) + }) + it("multiple use", () => { + class TestHook1 extends PreHook { + public async handle() { } + } + class TestHook2 extends PreHook { + public async handle() { } + } + class TestHook3 extends PreHook { + public async handle() { } + } + + @use(TestHook1) + @use(TestHook2) + @use(TestHook3) + class BTestClass extends FunctionalService { + public async handle() { } + } + + const value = getMetadata(CLASS_MIDDLEWAREKEY, BTestClass) + + expect(value).to.have.lengthOf(3); + + expect(value[0]).to.equal(TestHook1) + expect(value[1]).to.equal(TestHook2) + expect(value[2]).to.equal(TestHook3) + }) + it("multiple use multiple param", () => { + class TestHook11 extends PreHook { + public async handle() { } + } + class TestHook12 extends PreHook { + public async handle() { } + } + class TestHook21 extends PreHook { + public async handle() { } + } + class TestHook22 extends PreHook { + public async handle() { } + } + class TestHook31 extends PreHook { + public async handle() { } + } + class TestHook32 extends PreHook { + public async handle() { } + } + + @use(TestHook11, TestHook12) + @use(TestHook21, TestHook22) + @use(TestHook31, TestHook32) + class BTestClass extends FunctionalService { + public async handle() { } + } + + const value = getMetadata(CLASS_MIDDLEWAREKEY, BTestClass) + + expect(value).to.have.lengthOf(6); + + expect(value[0]).to.equal(TestHook11) + expect(value[1]).to.equal(TestHook12) + expect(value[2]).to.equal(TestHook21) + expect(value[3]).to.equal(TestHook22) + expect(value[4]).to.equal(TestHook31) + expect(value[5]).to.equal(TestHook32) + }) + }) + + describe('inject on hooks', () => { + + it("functional service inject", () => { + @injectable + class ATestClass extends FunctionalService { } + + class TestHook extends PreHook { + public async handle( @inject(ATestClass) a) { } + } + + @use(TestHook) + class BTestClass extends FunctionalService { + public async handle() { } + } + + const environmentMetadata = getMetadata(CLASS_ENVIRONMENTKEY, BTestClass) + expect(environmentMetadata).to.have + .property(`FUNCTIONAL_SERVICE_${ATestClass.name.toUpperCase()}`, getFunctionName(ATestClass)) + }) + + it("service inject", () => { + @injectable + @environment('%ClassName%_defined_environment', 'value') + class ATestClass extends Service { } + + class TestHook extends PreHook { + public async handle( @inject(ATestClass) a) { } + } + + @use(TestHook) + class BTestClass extends FunctionalService { + public async handle() { } + } + + const environmentMetadata = getMetadata(CLASS_ENVIRONMENTKEY, BTestClass) + expect(environmentMetadata).to.have + .property(`ATestClass_defined_environment`, 'value') + }); + + it("injected DynamoDB", () => { + @injectable + @dynamoTable({ tableName: 'ATable' }) + class ATestClass extends DynamoDB { } + + class TestHook extends PreHook { + public async handle( @inject(ATestClass) a) { } + } + + @use(TestHook) + class BTestClass extends FunctionalService { + public async handle() { } + } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, BTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('tableName', 'ATable') + }) + + it("injected SimpleNotificationService", () => { + @injectable + @sns({ topicName: 'ATopic' }) + class ATestClass extends SimpleNotificationService { } + + class TestHook extends PreHook { + public async handle( @inject(ATestClass) a) { } + } + + @use(TestHook) + class BTestClass extends FunctionalService { + public async handle() { } + } + + const value = getMetadata(CLASS_SNSCONFIGURATIONKEY, BTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('topicName', 'ATopic') + }) + + it("injected S3Storage", () => { + @injectable + @s3Storage({ bucketName: 'ABucket' }) + class ATestClass extends S3Storage { } + + class TestHook extends PreHook { + public async handle( @inject(ATestClass) a) { } + } + + @use(TestHook) + class BTestClass extends FunctionalService { + public async handle() { } + } + + const value = getMetadata(CLASS_S3CONFIGURATIONKEY, BTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('bucketName', 'abucket') + }) + }) + + describe('inject on level2 hooks', () => { + + it("functional service inject", () => { + @injectable + class ATestClass extends FunctionalService { } + + class TestHookLevel2 extends PreHook { + public async handle( @inject(ATestClass) a) { } + } + + @use(TestHookLevel2) + class TestHook extends PreHook { + public async handle() { } + } + + @use(TestHook) + class BTestClass extends FunctionalService { + public async handle() { } + } + + const environmentMetadata = getMetadata(CLASS_ENVIRONMENTKEY, BTestClass) + expect(environmentMetadata).to.have + .property(`FUNCTIONAL_SERVICE_${ATestClass.name.toUpperCase()}`, getFunctionName(ATestClass)) + }) + + it("service inject", () => { + @injectable + @environment('%ClassName%_defined_environment', 'value') + class ATestClass extends Service { } + + class TestHookLevel2 extends PreHook { + public async handle( @inject(ATestClass) a) { } + } + + @use(TestHookLevel2) + class TestHook extends PreHook { + public async handle() { } + } + + @use(TestHook) + class BTestClass extends FunctionalService { + public async handle() { } + } + + const environmentMetadata = getMetadata(CLASS_ENVIRONMENTKEY, BTestClass) + expect(environmentMetadata).to.have + .property(`ATestClass_defined_environment`, 'value') + }); + + it("injected DynamoDB", () => { + @injectable + @dynamoTable({ tableName: 'ATable' }) + class ATestClass extends DynamoDB { } + + class TestHookLevel2 extends PreHook { + public async handle( @inject(ATestClass) a) { } + } + + @use(TestHookLevel2) + class TestHook extends PreHook { + public async handle() { } + } + + @use(TestHook) + class BTestClass extends FunctionalService { + public async handle() { } + } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, BTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('tableName', 'ATable') + }) + + it("injected SimpleNotificationService", () => { + @injectable + @sns({ topicName: 'ATopic' }) + class ATestClass extends SimpleNotificationService { } + + class TestHookLevel2 extends PreHook { + public async handle( @inject(ATestClass) a) { } + } + + @use(TestHookLevel2) + class TestHook extends PreHook { + public async handle() { } + } + + @use(TestHook) + class BTestClass extends FunctionalService { + public async handle() { } + } + + const value = getMetadata(CLASS_SNSCONFIGURATIONKEY, BTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('topicName', 'ATopic') + }) + + it("injected S3Storage", () => { + @injectable + @s3Storage({ bucketName: 'ABucket' }) + class ATestClass extends S3Storage { } + + class TestHookLevel2 extends PreHook { + public async handle( @inject(ATestClass) a) { } + } + + @use(TestHookLevel2) + class TestHook extends PreHook { + public async handle() { } + } + + @use(TestHook) + class BTestClass extends FunctionalService { + public async handle() { } + } + + const value = getMetadata(CLASS_S3CONFIGURATIONKEY, BTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('bucketName', 'abucket') + }) + }) + }) + + describe('usage', () => { + + it('prehook', async () => { + let counter = 0 + class TestHook extends PreHook { + public async handle() { + counter++ + expect(counter).to.equal(1) + } + } + + @use(TestHook) + class TestFunctionalService extends FunctionalService { + public async handle() { + counter++ + expect(counter).to.equal(2) + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + counter++ + expect(counter).to.equal(3) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(3) + }) + + it('posthook', async () => { + let counter = 0 + class TestHook extends PostHook { + public async handle() { + counter++ + expect(counter).to.equal(2) + } + } + + @use(TestHook) + class TestFunctionalService extends FunctionalService { + public async handle() { + counter++ + expect(counter).to.equal(1) + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + counter++ + expect(counter).to.equal(3) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(3) + }) + + it('posthook result chain', async () => { + let counter = 0 + class TestPostHook1 extends PostHook { + handle( @param result) { + counter++ + + return result + 1 + } + } + class TestPostHook2 extends PostHook { + handle( @param result) { + counter++ + + return result + 1 + } + } + class TestPostHook3 extends PostHook { + handle( @param result) { + counter++ + + return result + 1 + } + } + + @use(TestPostHook1) + @use(TestPostHook2) + @use(TestPostHook3) + class TestFunctionalService extends FunctionalService { + public async handle() { + counter++ + expect(counter).to.equal(1) + + return 10 + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + counter++ + expect(counter).to.equal(5) + + expect(result).to.equal(13) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(5) + }) + + it('posthook catch error in service', async () => { + let counter = 0 + class TestHook extends PostHook { + public async catch() { + counter++ + expect(counter).to.equal(2) + } + } + + @use(TestHook) + class TestFunctionalService extends FunctionalService { + public async handle() { + counter++ + expect(counter).to.equal(1) + throw new Error('error') + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + counter++ + expect(counter).to.equal(3) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(3) + }) + + it('posthook catch error in service with prehook', async () => { + let counter = 0 + class TestPreHook extends PostHook { + public async catch() { + counter++ + expect(counter).to.equal(1) + } + } + + class TestCatchHook extends PostHook { + public async catch() { + counter++ + expect(counter).to.equal(3) + } + } + + @use(TestPreHook) + @use(TestCatchHook) + class TestFunctionalService extends FunctionalService { + public async handle() { + counter++ + expect(counter).to.equal(2) + throw new Error('error') + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + counter++ + expect(counter).to.equal(4) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(4) + }) + + it('posthook catch error in prehook', async () => { + let counter = 0 + class TestHook extends PreHook { + public async handle() { + counter++ + expect(counter).to.equal(1) + throw new Error('error') + } + } + class TestCatchHook extends PostHook { + public async catch() { + counter++ + expect(counter).to.equal(2) + } + } + + @use(TestHook) + @use(TestCatchHook) + class TestFunctionalService extends FunctionalService { + public async handle() { + expect(true).to.equal(false, 'have to skip this') + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + counter++ + expect(counter).to.equal(3) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(3) + }) + + it('posthook catch error in posthook', async () => { + let counter = 0 + class TestHook extends PreHook { + public async handle() { + counter++ + expect(counter).to.equal(1) + } + } + class TestCatchHook extends PostHook { + public async handle() { + counter++ + expect(counter).to.equal(3) + + throw new Error('error') + } + public async catch() { + expect(true).to.equal(false, 'have to skip this') + } + } + + @use(TestHook) + @use(TestCatchHook) + class TestFunctionalService extends FunctionalService { + public async handle() { + counter++ + expect(counter).to.equal(2) + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + expect(true).to.equal(false, 'have to skip this') + } + }, (e) => { + counter++ + expect(counter).to.equal(4) + expect(e.message).to.equal('error') + }) + + expect(counter).to.equal(4) + }) + + it('posthook catch error in posthook handled', async () => { + let counter = 0 + class TestHook extends PreHook { + public async handle() { + counter++ + expect(counter).to.equal(1) + } + } + class TestCatchHook extends PostHook { + public async handle() { + counter++ + expect(counter).to.equal(3) + + throw new Error('error') + } + public async catch() { + expect(true).to.equal(false, 'have to skip this') + } + } + class TestCatchHook2 extends PostHook { + public async handle() { + expect(true).to.equal(false, 'have to skip this') + } + public async catch() { + counter++ + expect(counter).to.equal(4) + + return { catch: 1 } + } + } + + @use(TestHook) + @use(TestCatchHook) + @use(TestCatchHook2) + class TestFunctionalService extends FunctionalService { + public async handle() { + counter++ + expect(counter).to.equal(2) + + return { handle: 1 } + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + counter++ + expect(counter).to.equal(5) + expect(result).to.deep.equal({ catch: 1 }) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(5) + }) + + it('decorator resolution in hooks', async () => { + let counter = 0 + class AuthHook extends PreHook { + public async handle( @param authorization, @param Authorization, @context context) { + counter++ + const auth = authorization || Authorization + if (!auth) throw new Error('auth') + context.identity = 'me' + context.user = null + } + } + class PermissionHook extends PreHook { + public async handle( @param identity) { + counter++ + expect(identity).to.equal('me') + } + } + class ResultHook extends PostHook { + public async handle( @param result) { + counter++ + expect(result).to.deep.equal([{ a: 1 }, { a: 2 }, { a: 3 }]) + return result + } + async catch( @error e) { + expect(true).to.equal(false, e.message) + } + } + + @use(AuthHook) + @use(PermissionHook) + @use(ResultHook) + class TestFunctionalService extends FunctionalService { + public async handle() { + counter++ + + return [{ a: 1 }, { a: 2 }, { a: 3 }] + } + } + + const invoker = TestFunctionalService.createInvoker() + const req = { + headers: { authorization: 'somevalue' } + } + await invoker(req, { + send: (result) => { + counter++ + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(5) + }) + + describe('hook on hooks', () => { + it('prehook', async () => { + let counter = 0 + + class TestHookLevel2 extends PreHook { + public async handle() { + counter++ + expect(counter).to.equal(1) + } + } + + @use(TestHookLevel2) + class TestHook extends PreHook { + public async handle() { + counter++ + expect(counter).to.equal(2) + } + } + + @use(TestHook) + class TestFunctionalService extends FunctionalService { + public async handle() { + counter++ + expect(counter).to.equal(3) + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + counter++ + expect(counter).to.equal(4) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(4) + }) + + it('posthook', async () => { + let counter = 0 + + class TestHookLevel2 extends PostHook { + public async handle() { + counter++ + expect(counter).to.equal(2) + } + } + + @use(TestHookLevel2) + class TestHook extends PreHook { + public async handle() { + counter++ + expect(counter).to.equal(1) + } + } + + @use(TestHook) + class TestFunctionalService extends FunctionalService { + public async handle() { + counter++ + expect(counter).to.equal(3) + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + counter++ + expect(counter).to.equal(4) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(4) + }) + + it('pre-posthook', async () => { + let counter = 0 + + class TestPreHookLevel2 extends PreHook { + public async handle() { + counter++ + expect(counter).to.equal(1) + } + } + + class TestPostHookLevel2 extends PostHook { + public async handle() { + counter++ + expect(counter).to.equal(3) + } + } + + @use(TestPreHookLevel2) + @use(TestPostHookLevel2) + class TestHook extends PreHook { + public async handle() { + counter++ + expect(counter).to.equal(2) + } + } + + @use(TestHook) + class TestFunctionalService extends FunctionalService { + public async handle() { + counter++ + expect(counter).to.equal(4) + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + counter++ + expect(counter).to.equal(5) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(5) + }) + + it('pre-posthook pre exception', async () => { + let counter = 0 + + class TestPreHookLevel2 extends PreHook { + public async handle() { + counter++ + expect(counter).to.equal(1) + + throw new Error('error') + } + } + + class TestPostHookLevel2 extends PostHook { + public async handle() { + expect(true).to.equal(false, 'have to skip this') + } + } + + @use(TestPreHookLevel2) + @use(TestPostHookLevel2) + class TestHook extends PreHook { + public async handle() { + expect(true).to.equal(false, 'have to skip this') + } + } + + class TestCatchHook extends PostHook { + catch( @error e) { + counter++ + expect(counter).to.equal(2) + expect(e.message).to.equal('error') + } + } + + @use(TestHook) + @use(TestCatchHook) + class TestFunctionalService extends FunctionalService { + public async handle() { + expect(true).to.equal(false, 'have to skip this') + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + counter++ + expect(counter).to.equal(3) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(3) + }) + + it('pre-posthook exception', async () => { + let counter = 0 + + class TestPreHookLevel2 extends PreHook { + public async handle() { + counter++ + expect(counter).to.equal(1) + } + } + + class TestPostHookLevel2 extends PostHook { + public async handle() { + expect(true).to.equal(false, 'have to skip this') + } + } + + @use(TestPreHookLevel2) + @use(TestPostHookLevel2) + class TestHook extends PreHook { + public async handle() { + counter++ + expect(counter).to.equal(2) + + throw new Error('error') + } + } + + class TestCatchHook extends PostHook { + catch( @error e) { + counter++ + expect(counter).to.equal(3) + expect(e.message).to.equal('error') + } + } + + @use(TestHook) + @use(TestCatchHook) + class TestFunctionalService extends FunctionalService { + public async handle() { + expect(true).to.equal(false, 'have to skip this') + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + counter++ + expect(counter).to.equal(4) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(4) + }) + + it('pre-posthook post exception', async () => { + let counter = 0 + + class TestPreHookLevel2 extends PreHook { + public async handle() { + counter++ + expect(counter).to.equal(1) + } + } + + class TestPostHookLevel2 extends PostHook { + public async handle() { + counter++ + expect(counter).to.equal(3) + + throw new Error('error') + } + } + + @use(TestPreHookLevel2) + @use(TestPostHookLevel2) + class TestHook extends PreHook { + public async handle() { + counter++ + expect(counter).to.equal(2) + } + } + + class TestCatchHook extends PostHook { + catch( @error e) { + counter++ + expect(counter).to.equal(4) + expect(e.message).to.equal('error') + } + } + + @use(TestHook) + @use(TestCatchHook) + class TestFunctionalService extends FunctionalService { + public async handle() { + expect(true).to.equal(false, 'have to skip this') + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + counter++ + expect(counter).to.equal(5) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(5) + }) + }) + }) + + describe('hook decorators', () => { + it('prehook set property => get context', async () => { + let counter = 0 + class TestHook extends PreHook { + handle( @context c) { + counter++ + expect(counter).to.equal(1) + + expect(c).to.deep.equal({}) + + c.p1 = 'v1' + } + } + + @use(TestHook) + class TestFunctionalService extends FunctionalService { + public async handle( @context c) { + counter++ + expect(counter).to.equal(2) + + expect(c).to.deep.equal({ p1: 'v1' }) + + return { ok: 1 } + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + expect(result).to.deep.equal({ ok: 1 }) + counter++ + expect(counter).to.equal(3) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(3) + }) + + it('prehook set property => get param', async () => { + let counter = 0 + class TestHook extends PreHook { + handle( @context c) { + counter++ + expect(counter).to.equal(1) + + expect(c).to.deep.equal({}) + + c.p1 = 'v1' + } + } + + @use(TestHook) + class TestFunctionalService extends FunctionalService { + public async handle( @param p1) { + counter++ + expect(counter).to.equal(2) + + expect(p1).to.equal('v1') + + return { ok: 1 } + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + expect(result).to.deep.equal({ ok: 1 }) + counter++ + expect(counter).to.equal(3) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(3) + }) + + it('multiple prehook set property => get context', async () => { + let counter = 0 + class TestHook1 extends PreHook { + handle( @context c) { + expect(c).to.deep.equal({}) + + c.p1 = 'v1' + } + } + + class TestHook2 extends PreHook { + handle( @context c) { + expect(c).to.deep.equal({ p1: 'v1' }) + + c.p2 = 'v2' + } + } + class TestHook3 extends PreHook { + handle( @context c) { + expect(c).to.deep.equal({ p1: 'v1', p2: 'v2' }) + + c.p3 = 'v3' + } + } + + @use(TestHook1) + @use(TestHook2) + @use(TestHook3) + class TestFunctionalService extends FunctionalService { + public async handle( @context c) { + counter++ + + expect(c).to.deep.equal({ p1: 'v1', p2: 'v2', p3: 'v3' }) + + return { ok: 1 } + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + expect(result).to.deep.equal({ ok: 1 }) + counter++ + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(2) + }) + + it('multiple prehook set property => get param', async () => { + let counter = 0 + class TestHook1 extends PreHook { + handle( @context c) { + expect(c).to.deep.equal({}) + + c.p1 = 'v1' + } + } + + class TestHook2 extends PreHook { + handle( @context c, @param p1) { + expect(c).to.deep.equal({ p1: 'v1' }) + expect(p1).to.equal('v1') + + c.p2 = 'v2' + } + } + class TestHook3 extends PreHook { + handle( @context c, @param p1, @param p2) { + expect(c).to.deep.equal({ p1: 'v1', p2: 'v2' }) + expect(p1).to.equal('v1') + expect(p2).to.equal('v2') + + c.p3 = 'v3' + } + } + + @use(TestHook1) + @use(TestHook2) + @use(TestHook3) + class TestFunctionalService extends FunctionalService { + public async handle( @param p1, @param p2, @param p3) { + counter++ + + expect(p1).to.equal('v1') + expect(p2).to.equal('v2') + expect(p3).to.equal('v3') + + return { ok: 1 } + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + expect(result).to.deep.equal({ ok: 1 }) + counter++ + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(2) + }) + + it('@error', async () => { + let counter = 0 + class TestHook extends PostHook { + catch( @error e) { + counter++ + expect(e).instanceOf(Error) + + expect(e.message).to.deep.equal('custom error message') + } + } + + @use(TestHook) + class TestFunctionalService extends FunctionalService { + public async handle() { + counter++ + throw new Error('custom error message') + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + counter++ + expect(counter).to.equal(3) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(3) + }) + }) +}) \ No newline at end of file From 9b636652c95ae29f970348219cf9b1eee510e21b Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Fri, 28 Jul 2017 14:46:04 +0200 Subject: [PATCH 055/196] CHANGE annotation export --- src/index.ts | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 144c606..2d4bf2b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,30 @@ export { Service, FunctionalApi, FunctionalService, DynamoDB, SimpleNotificationService, S3Storage, ApiGateway, callExtension, PreHook, PostHook } from './classes' export { addProvider, removeProvider, Provider, AWSProvider, LocalProvider } from './providers' -import * as _annotations from './annotations' -export const annotations = _annotations - -export { IHttpMethod } from './annotations' +export { + templates, applyTemplates, + injectable, + apiGateway, + httpTrigger, + rest, httpGet, httpPost, httpPut, httpPatch, httpDelete, IHttpMethod, + environment, + tag, + log, + functionName, getFunctionName, + dynamoTable, __dynamoDBDefaults, + sns, + s3Storage, + eventSource, + classConfig, getClassConfigValue, + simpleClassAnnotation, + expandableDecorator, + use, + description, + role, + aws, + azure, + param, serviceParams, createParameterDecorator, request, context, error, + inject, + constants, + defineMetadata, getMetadata, getMetadataKeys, getOwnMetadata, getOverridableMetadata +} from './annotations' From 18f53045d624f46556d92e7fe5b684bbccea75cb Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 31 Jul 2017 11:38:43 +0200 Subject: [PATCH 056/196] ADD: generalized functional service return value --- src/providers/aws/eventSources/apiGateway.ts | 8 + .../azure/eventSources/httpTrigger.ts | 10 +- src/providers/core/eventSource.ts | 5 + src/providers/local.ts | 34 +++- test/invoker.tests.ts | 191 +++++++++++++++++- 5 files changed, 243 insertions(+), 5 deletions(-) diff --git a/src/providers/aws/eventSources/apiGateway.ts b/src/providers/aws/eventSources/apiGateway.ts index c925ffa..8a08d92 100644 --- a/src/providers/aws/eventSources/apiGateway.ts +++ b/src/providers/aws/eventSources/apiGateway.ts @@ -44,6 +44,14 @@ export class ApiGateway extends EventSource { } } + if (result && typeof result.status === 'number' && result.hasOwnProperty('data')) { + return { + statusCode: result.status, + headers: result.headers, + data: JSON.stringify(result.data) + } + } + if (result && typeof result.statusCode === 'number' && typeof result.body === 'string') { return result } diff --git a/src/providers/azure/eventSources/httpTrigger.ts b/src/providers/azure/eventSources/httpTrigger.ts index 5ed756a..3f97eb5 100644 --- a/src/providers/azure/eventSources/httpTrigger.ts +++ b/src/providers/azure/eventSources/httpTrigger.ts @@ -39,7 +39,15 @@ export class HttpTrigger extends EventSource { } } - if (result && typeof result.status === 'number' && typeof result.body === 'string') { + if (result && typeof result.status === 'number' && result.hasOwnProperty('data')) { + return { + status: result.status, + headers: result.headers, + body: result.data + } + } + + if (result && typeof result.status === 'number' && result.hasOwnProperty && result.hasOwnProperty('body')) { return result } diff --git a/src/providers/core/eventSource.ts b/src/providers/core/eventSource.ts index e2a8542..1641f31 100644 --- a/src/providers/core/eventSource.ts +++ b/src/providers/core/eventSource.ts @@ -11,6 +11,11 @@ export abstract class EventSource { public async resultTransform(err, result, event: any) { if (err) throw err + + if (result && typeof result.status === 'number' && result.hasOwnProperty('data')) { + return result.data + } + return result } } \ No newline at end of file diff --git a/src/providers/local.ts b/src/providers/local.ts index a72e4e6..a4e367b 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -11,9 +11,19 @@ export class LocalProvider extends Provider { const invoker = async (req, res, next) => { try { - const r = await callContext({ event: { req, res, next } }) - res.send(r) - return r + const eventContext = { req, res, next } + + let result + let error + try { + result = await callContext({ event: eventContext }) + } catch (err) { + error = err + } + const response = await this.resultTransform(error, result, eventContext) + + res.send(response) + return response } catch (e) { next(e) } @@ -21,6 +31,24 @@ export class LocalProvider extends Provider { return invoker } + protected resultTransform(error, result, eventContext) { + if (error) throw error + + if (result && typeof result.status === 'number' && result.hasOwnProperty('data')) { + const { res } = eventContext + + res.status(result.status) + + if (typeof result.headers === 'object' && result.headers) { + res.set(result.headers) + } + + return result.data + } + + return result + } + public async invoke(serviceInstance, params, invokeConfig?) { const httpAttr = (getMetadata(rest.environmentKey, serviceInstance) || [])[0] diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index 4fae61a..91e192d 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -476,6 +476,58 @@ describe('invoker', () => { const invoker = MockService.createInvoker() invoker(req, res, next) }) + + it("generic result format", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + @injectable + class MockInjectable extends Service { } + + const req = { + } + const res = { + send: (data) => { + counter++ + expect(data).to.deep.equal({ + ok: 1 + }) + }, + status: (code) => { + counter++ + expect(code).to.equal(201) + }, + set: (headers) => { + counter++ + expect(headers).to.deep.equal({ + 'content-type': 'application/json' + }) + } + } + const next = (e) => { expect(true).to.equal(false, e.message) } + + class MockService extends FunctionalService { + handle() { + counter++ + + return { + status: 201, + headers: { + 'content-type': 'application/json' + }, + data: { + ok: 1 + } + } + } + } + + const invoker = MockService.createInvoker() + await invoker(req, res, next) + + expect(counter).to.equal(4) + }) }) describe("aws", () => { @@ -992,6 +1044,57 @@ describe('invoker', () => { done(e) }) }) + + it("api gateway generic result format", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + @injectable + class MockInjectable extends Service { } + + const awsEvent = { + requestContext: { apiId: 'apiId' }, + } + const awsContext = {} + const cb = (e, result) => { + counter++ + + console.log(e && e.message) + expect(e).is.null + + expect(result).to.deep.equal({ + statusCode: 201, + headers: { + 'content-type': 'application/json' + }, + data: JSON.stringify({ + ok: 1 + }) + }) + } + + class MockService extends FunctionalService { + handle() { + counter++ + + return { + status: 201, + headers: { + 'content-type': 'application/json' + }, + data: { + ok: 1 + } + } + } + } + + const invoker = MockService.createInvoker() + await invoker(awsEvent, awsContext, cb) + + expect(counter).to.equal(2) + }) }) it("inject param", (done) => { @@ -1096,6 +1199,50 @@ describe('invoker', () => { const invoker = MockService.createInvoker() invoker(awsEvent, awsContext, cb) }) + + it("generic result format", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + @injectable + class MockInjectable extends Service { } + + const awsEvent = { + } + const awsContext = {} + const cb = (e, result) => { + counter++ + + console.log(e && e.message) + expect(e).is.null + + expect(result).to.deep.equal({ + ok: 1 + }) + } + + class MockService extends FunctionalService { + handle() { + counter++ + + return { + status: 201, + headers: { + 'content-type': 'application/json' + }, + data: { + ok: 1 + } + } + } + } + + const invoker = MockService.createInvoker() + await invoker(awsEvent, awsContext, cb) + + expect(counter).to.equal(2) + }) }) describe("azure", () => { @@ -1328,7 +1475,7 @@ describe('invoker', () => { expect(context).to.have.nested.property('res.status', 200) expect(counter).to.equal(1) }) - + it("httpTrigger params resolve hint", async () => { let counter = 0 @@ -1576,5 +1723,47 @@ describe('invoker', () => { expect(context).to.have.nested.property('res.status', 200) expect(counter).to.equal(1) }) + + it("generic result format", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + + @injectable + class MockInjectable extends Service { } + + const context = {} + const req = { + } + + class MockService extends FunctionalService { + handle() { + counter++ + + return { + status: 201, + headers: { + 'content-type': 'application/json' + }, + data: { + ok: 1 + } + } + } + } + + const invoker = MockService.createInvoker() + + await invoker(context, req) + + expect(context).to.have.nested.property('res.status', 201) + expect(context).to.have.nested.property('res.headers').that.deep.equal({ + 'content-type': 'application/json' + }) + expect(context).to.have.nested.property('res.body').that.deep.equal({ + ok: 1 + }) + expect(counter).to.equal(1) + }) }) }) \ No newline at end of file From b02c38a568d759daec6c007104c32d14bb9b6cb5 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 1 Aug 2017 14:41:13 +0200 Subject: [PATCH 057/196] add: istanbul --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ccb94df..ae0f8ee 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "build:watch": "node_modules/.bin/tsc -w", "test": "npm run build && mocha --recursive lib/test/**/*.tests.js", "test:fast": "mocha --recursive lib/test/**/*.tests.js", - "metadata": "node ./lib/src/cli/cli.js metadata aws --" + "metadata": "node ./lib/src/cli/cli.js metadata aws --", + "coverage": "node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha -- -R spec lib/test/**/*.tests.js" }, "repository": { "type": "git", @@ -29,6 +30,7 @@ "@types/node": "^6.0.46", "@types/winston": "0.0.30", "chai": "^4.0.2", + "istanbul": "^0.4.5", "mocha": "^3.4.2", "typescript": "^2.3.0" }, From e94e3f246922b1f7d12e81ab657adb9be033888e Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 1 Aug 2017 14:43:46 +0200 Subject: [PATCH 058/196] fix: remove console.log --- test/invoker.tests.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index 91e192d..e9cdaeb 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -1060,7 +1060,6 @@ describe('invoker', () => { const cb = (e, result) => { counter++ - console.log(e && e.message) expect(e).is.null expect(result).to.deep.equal({ @@ -1214,7 +1213,6 @@ describe('invoker', () => { const cb = (e, result) => { counter++ - console.log(e && e.message) expect(e).is.null expect(result).to.deep.equal({ From 5b9d0f293278f70bf990b9b58d590be220b03e1b Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 1 Aug 2017 14:44:50 +0200 Subject: [PATCH 059/196] update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ae0f8ee..b977424 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.4", + "version": "0.0.5", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From f295e55fe9adb1f70f6193213e1531e8474cf6a9 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Wed, 2 Aug 2017 13:08:57 +0200 Subject: [PATCH 060/196] ADD: functionalServiceName parameter decorator and logic --- src/annotations/index.ts | 2 +- src/annotations/parameters/param.ts | 4 +- src/index.ts | 2 +- src/providers/aws/index.ts | 2 +- src/providers/azure/index.ts | 2 +- src/providers/core/provider.ts | 5 +- src/providers/local.ts | 2 +- test/hook.tests.ts | 61 +++++++++- test/invoker.tests.ts | 177 +++++++++++++++++++++++++++- 9 files changed, 248 insertions(+), 9 deletions(-) diff --git a/src/annotations/index.ts b/src/annotations/index.ts index 096443d..2ff66db 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -26,7 +26,7 @@ export { aws } from './classes/aws/aws' export { azure } from './classes/azure/azure' -export { param, serviceParams, createParameterDecorator, request, context, error } from './parameters/param' +export { param, serviceParams, createParameterDecorator, request, context, error, functionalServiceName } from './parameters/param' export { inject } from './parameters/inject' import * as _constants from './constants' diff --git a/src/annotations/parameters/param.ts b/src/annotations/parameters/param.ts index f0e55a2..94c6af9 100644 --- a/src/annotations/parameters/param.ts +++ b/src/annotations/parameters/param.ts @@ -47,7 +47,8 @@ export const createParameterDecorator = (type: string, defaultConfig?: any) => ( parameterIndex, targetKey, type, - config + config, + target }); defineMetadata(PARAMETER_PARAMKEY, existingParameters, target, targetKey); } @@ -66,3 +67,4 @@ export const serviceParams = createParameterDecorator('serviceParams') export const request = createParameterDecorator('request') export const context = createParameterDecorator('context') export const error = createParameterDecorator('error') +export const functionalServiceName = createParameterDecorator('functionalServiceName') diff --git a/src/index.ts b/src/index.ts index 2d4bf2b..d3d9f17 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,7 +23,7 @@ export { role, aws, azure, - param, serviceParams, createParameterDecorator, request, context, error, + param, serviceParams, createParameterDecorator, request, context, error, functionalServiceName, inject, constants, defineMetadata, getMetadata, getMetadataKeys, getOwnMetadata, getOverridableMetadata diff --git a/src/providers/aws/index.ts b/src/providers/aws/index.ts index 1fe5df6..62543c2 100644 --- a/src/providers/aws/index.ts +++ b/src/providers/aws/index.ts @@ -39,7 +39,7 @@ export class AWSProvider extends Provider { let result let error try { - result = await callContext({ eventSourceHandler, event: eventContext }) + result = await callContext({ eventSourceHandler, event: eventContext, serviceInstance }) } catch (err) { error = err } diff --git a/src/providers/azure/index.ts b/src/providers/azure/index.ts index a73c21a..b8a0664 100644 --- a/src/providers/azure/index.ts +++ b/src/providers/azure/index.ts @@ -25,7 +25,7 @@ export class AzureProvider extends Provider { let result let error try { - result = await callContext({ eventSourceHandler, event: eventContext }) + result = await callContext({ eventSourceHandler, event: eventContext, serviceInstance }) } catch (err) { error = err } diff --git a/src/providers/core/provider.ts b/src/providers/core/provider.ts index 44683e0..ea61d17 100644 --- a/src/providers/core/provider.ts +++ b/src/providers/core/provider.ts @@ -1,4 +1,4 @@ -import { constants, getMetadata, getOverridableMetadata } from '../../annotations' +import { constants, getMetadata, getOverridableMetadata, getFunctionName } from '../../annotations' const { PARAMETER_PARAMKEY } = constants import { getMiddlewares } from '../../annotations/classes/use' import { callExtension, PreHook, PostHook } from '../../classes' @@ -118,4 +118,7 @@ Provider.addParameterDecoratorImplementation("context", async (parameter, contex }) Provider.addParameterDecoratorImplementation("error", async (parameter, context, provider) => { return parameter.targetKey === 'catch' ? (context.context && context.context.error) : undefined +}) +Provider.addParameterDecoratorImplementation("functionalServiceName", async (parameter, context, provider) => { + return context.serviceInstance && getFunctionName(context.serviceInstance) }) \ No newline at end of file diff --git a/src/providers/local.ts b/src/providers/local.ts index a4e367b..1b87a23 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -16,7 +16,7 @@ export class LocalProvider extends Provider { let result let error try { - result = await callContext({ event: eventContext }) + result = await callContext({ event: eventContext, serviceInstance }) } catch (err) { error = err } diff --git a/test/hook.tests.ts b/test/hook.tests.ts index ece606d..4e54a8f 100644 --- a/test/hook.tests.ts +++ b/test/hook.tests.ts @@ -3,7 +3,7 @@ import { FunctionalService, PreHook, PostHook, Service, DynamoDB, SimpleNotifica import { getOverridableMetadata, constants, getMetadata, getFunctionName } from '../src/annotations' const { PARAMETER_PARAMKEY, CLASS_ENVIRONMENTKEY, CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_SNSCONFIGURATIONKEY, CLASS_S3CONFIGURATIONKEY, CLASS_MIDDLEWAREKEY } = constants -import { use, context, error, param, inject, injectable, environment, dynamoTable, sns, s3Storage } from '../src/annotations' +import { use, context, error, param, inject, injectable, environment, dynamoTable, sns, s3Storage, functionalServiceName, functionName } from '../src/annotations' describe('hooks', () => { @@ -1217,5 +1217,64 @@ describe('hooks', () => { expect(counter).to.equal(3) }) + + it("@functionalServiceName", async () => { + let counter = 0 + class TestHook extends PreHook { + handle( @functionalServiceName serviceName) { + counter++ + expect(serviceName).to.equal('TestFunctionalService') + } + } + + @use(TestHook) + class TestFunctionalService extends FunctionalService { + public async handle() { + counter++ + return { ok: 1 } + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + expect(result).to.deep.equal({ ok: 1 }) + counter++ + expect(counter).to.equal(3) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(3) + }) + + it("@functionalServiceName with functionName decorator", async () => { + let counter = 0 + class TestHook extends PreHook { + handle( @functionalServiceName serviceName) { + counter++ + expect(serviceName).to.equal('MyTestFunctionalService') + } + } + + @use(TestHook) + @functionName('MyTestFunctionalService') + class TestFunctionalService extends FunctionalService { + public async handle() { + counter++ + return { ok: 1 } + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + expect(result).to.deep.equal({ ok: 1 }) + counter++ + expect(counter).to.equal(3) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(3) + }) }) }) \ No newline at end of file diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index e9cdaeb..1601480 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -2,7 +2,7 @@ import { expect } from 'chai' import { getInvoker } from '../src/providers' import { FunctionalService, Service } from '../src/classes' -import { param, inject, injectable, serviceParams, request } from '../src/annotations' +import { param, inject, injectable, serviceParams, request, functionalServiceName, functionName } from '../src/annotations' import { parse } from 'url' @@ -528,6 +528,67 @@ describe('invoker', () => { expect(counter).to.equal(4) }) + + it("functionalServiceName", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + const req = { + } + const res = { + send: (data) => { + counter++ + } + } + const next = (e) => { expect(true).to.equal(false, e.message) } + + class MockService extends FunctionalService { + handle( @functionalServiceName serviceName) { + counter++ + + expect(serviceName).to.equal('MockService') + + return { ok: 1 } + } + } + + const invoker = MockService.createInvoker() + await invoker(req, res, next) + + expect(counter).to.equal(2) + }) + + it("functionalServiceName with functionName decorator", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + const req = { + } + const res = { + send: (data) => { + counter++ + } + } + const next = (e) => { expect(true).to.equal(false, e.message) } + + @functionName('MyMockService') + class MockService extends FunctionalService { + handle( @functionalServiceName serviceName) { + counter++ + + expect(serviceName).to.equal('MyMockService') + + return { ok: 1 } + } + } + + const invoker = MockService.createInvoker() + await invoker(req, res, next) + + expect(counter).to.equal(2) + }) }) describe("aws", () => { @@ -1241,6 +1302,65 @@ describe('invoker', () => { expect(counter).to.equal(2) }) + + it("functionalServiceName", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + const awsEvent = {} + const awsContext = {} + const cb = (e, result) => { + counter++ + + expect(result).to.deep.equal({ ok: 1 }) + } + + class MockService extends FunctionalService { + handle( @functionalServiceName serviceName) { + counter++ + + expect(serviceName).to.equal('MockService') + + return { ok: 1 } + } + } + + const invoker = MockService.createInvoker() + await invoker(awsEvent, awsContext, cb) + + expect(counter).to.equal(2) + }) + + it("functionalServiceName with functionName decorator", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + const awsEvent = {} + const awsContext = {} + const cb = (e, result) => { + counter++ + + expect(result).to.deep.equal({ ok: 1 }) + } + + @functionName('MyMockService') + class MockService extends FunctionalService { + handle( @functionalServiceName serviceName) { + counter++ + + expect(serviceName).to.equal('MyMockService') + + return { ok: 1 } + } + } + + const invoker = MockService.createInvoker() + await invoker(awsEvent, awsContext, cb) + + expect(counter).to.equal(2) + }) }) describe("azure", () => { @@ -1763,5 +1883,60 @@ describe('invoker', () => { }) expect(counter).to.equal(1) }) + + it("functionalServiceName", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + + const context = {} + const req = {} + + class MockService extends FunctionalService { + handle( @functionalServiceName serviceName) { + counter++ + + expect(serviceName).to.equal('MockService') + + return { ok: 1 } + } + } + + const invoker = MockService.createInvoker() + + await invoker(context, req) + + expect(context).to.have.nested.property('res.status', 200) + expect(context).to.have.nested.property('res.body').that.deep.equal({ ok: 1 }) + expect(counter).to.equal(1) + }) + + it("functionalServiceName with functionName decorator", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + + const context = {} + const req = {} + + @functionName('MyMockService') + class MockService extends FunctionalService { + handle( @functionalServiceName serviceName) { + counter++ + + expect(serviceName).to.equal('MyMockService') + + return { ok: 1 } + } + } + + const invoker = MockService.createInvoker() + + await invoker(context, req) + + expect(context).to.have.nested.property('res.status', 200) + expect(context).to.have.nested.property('res.body').that.deep.equal({ ok: 1 }) + expect(counter).to.equal(1) + }) }) }) \ No newline at end of file From 80aa870a049da2abf90b625a25d1ad17c30ec565 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Thu, 3 Aug 2017 16:37:35 +0200 Subject: [PATCH 061/196] REFACTOR: stages; local use stages --- src/annotations/index.ts | 2 +- src/annotations/parameters/param.ts | 2 + src/classes/externals/dynamoDB.ts | 4 +- src/classes/externals/s3Storage.ts | 2 +- src/cli/commands/deploy.ts | 1 + src/cli/commands/local.ts | 7 +- src/cli/commands/metadata.ts | 3 + src/cli/commands/package.ts | 1 + src/cli/commands/serverless.ts | 1 + .../steppes/setFunctionalEnvironment.ts | 6 +- .../cloudFormation/context/resources.ts | 2 +- .../providers/cloudFormation/context/sns.ts | 2 +- src/cli/utilities/aws/dynamoDB.ts | 2 +- src/index.ts | 2 +- src/providers/core/provider.ts | 6 + test/invoker.tests.ts | 180 +++++++++++++++++- 16 files changed, 212 insertions(+), 11 deletions(-) diff --git a/src/annotations/index.ts b/src/annotations/index.ts index 2ff66db..4ca0372 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -26,7 +26,7 @@ export { aws } from './classes/aws/aws' export { azure } from './classes/azure/azure' -export { param, serviceParams, createParameterDecorator, request, context, error, functionalServiceName } from './parameters/param' +export { param, serviceParams, createParameterDecorator, request, context, error, functionalServiceName, provider, stage } from './parameters/param' export { inject } from './parameters/inject' import * as _constants from './constants' diff --git a/src/annotations/parameters/param.ts b/src/annotations/parameters/param.ts index 94c6af9..81ccc2c 100644 --- a/src/annotations/parameters/param.ts +++ b/src/annotations/parameters/param.ts @@ -68,3 +68,5 @@ export const request = createParameterDecorator('request') export const context = createParameterDecorator('context') export const error = createParameterDecorator('error') export const functionalServiceName = createParameterDecorator('functionalServiceName') +export const provider = createParameterDecorator('provider') +export const stage = createParameterDecorator('stage') diff --git a/src/classes/externals/dynamoDB.ts b/src/classes/externals/dynamoDB.ts index 97102aa..88c8e28 100644 --- a/src/classes/externals/dynamoDB.ts +++ b/src/classes/externals/dynamoDB.ts @@ -118,8 +118,10 @@ export class DynamoDB extends InjectService { protected setDefaultValues(params, command) { const tableConfig = (getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, this) || [])[0] const tableName = ({ TableName: tableConfig && tableConfig.tableName, ...tableConfig.nativeConfig }).TableName + + const calcTableName = process.env[`${this.constructor.name}${DYNAMO_TABLE_NAME_SUFFIX}`] + `-${process.env.FUNCTIONAL_STAGE}` const initParams = { - TableName: process.env[`${this.constructor.name}${DYNAMO_TABLE_NAME_SUFFIX}`] || tableName + TableName: calcTableName || tableName } return { ...initParams, ...params } diff --git a/src/classes/externals/s3Storage.ts b/src/classes/externals/s3Storage.ts index d7caf9a..7ba3933 100644 --- a/src/classes/externals/s3Storage.ts +++ b/src/classes/externals/s3Storage.ts @@ -62,7 +62,7 @@ export class S3Storage extends InjectService { protected setDefaultValues(params, command) { const initParams = { - Bucket: process.env[`${this.constructor.name}${S3_BUCKET_SUFFIX}`] + Bucket: process.env[`${this.constructor.name}${S3_BUCKET_SUFFIX}`] + `-${process.env.FUNCTIONAL_STAGE}` } return { ...initParams, ...params } diff --git a/src/cli/commands/deploy.ts b/src/cli/commands/deploy.ts index d0d16a9..9f07453 100644 --- a/src/cli/commands/deploy.ts +++ b/src/cli/commands/deploy.ts @@ -16,6 +16,7 @@ export default ({ createContext, executor, ExecuteStep, projectConfig, requireVa const stage = command.stage || projectConfig.stage || 'dev' process.env.FUNCTIONAL_ENVIRONMENT = deployTarget + process.env.FUNCTIONAL_STAGE = stage const context = await createContext(entryPoint, { deployTarget, diff --git a/src/cli/commands/local.ts b/src/cli/commands/local.ts index fce59ab..1697c32 100644 --- a/src/cli/commands/local.ts +++ b/src/cli/commands/local.ts @@ -108,16 +108,21 @@ export default ({ createContext, annotations: { getMetadata, constants, getFunct commander .command('local [port] [path]') .description('run functional service local') + .option('--stage ', 'stage') .action(async (port, path, command) => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' try { const entryPoint = requireValue(path || projectConfig.main, 'entry point') const localPort = requireValue(port || projectConfig.localPort, 'localPort') + const stage = command.stage || projectConfig.stage || 'dev' + + process.env.FUNCTIONAL_STAGE = stage const context = await createContext(entryPoint, { deployTarget: 'local', - localPort + localPort, + stage }) await executor(context, { name: 'startLocal', method: startLocal }) diff --git a/src/cli/commands/metadata.ts b/src/cli/commands/metadata.ts index 281d2b5..c13b928 100644 --- a/src/cli/commands/metadata.ts +++ b/src/cli/commands/metadata.ts @@ -4,14 +4,17 @@ export default ({ createContext, executor, ExecuteStep, annotations: { getMetada commands({ commander }) { commander .command('metadata [target] [path]') + .option('--stage ', 'stage') .description('service metadata') .action(async (target, path, command) => { try { const entryPoint = requireValue(path || projectConfig.main, 'entry point') const deployTarget = requireValue(target || projectConfig.deployTarget, 'missing deploy target') + const stage = command.stage || projectConfig.stage || 'dev' process.env.FUNCTIONAL_ENVIRONMENT = deployTarget + process.env.FUNCTIONAL_STAGE = stage const context = await createContext(entryPoint, { deployTarget diff --git a/src/cli/commands/package.ts b/src/cli/commands/package.ts index 0ef1e29..33869f1 100644 --- a/src/cli/commands/package.ts +++ b/src/cli/commands/package.ts @@ -16,6 +16,7 @@ export default ({ createContext, executor, ExecuteStep, projectConfig, requireVa const stage = command.stage || projectConfig.stage || 'dev' process.env.FUNCTIONAL_ENVIRONMENT = deployTarget + process.env.FUNCTIONAL_STAGE = stage const context = await createContext(entryPoint, { deployTarget, diff --git a/src/cli/commands/serverless.ts b/src/cli/commands/serverless.ts index 65bfa0e..4e8cf00 100644 --- a/src/cli/commands/serverless.ts +++ b/src/cli/commands/serverless.ts @@ -202,6 +202,7 @@ export default (api) => { const stage = command.stage || projectConfig.stage || 'dev' process.env.FUNCTIONAL_ENVIRONMENT = FUNCTIONAL_ENVIRONMENT + process.env.FUNCTIONAL_STAGE = stage const context = await createContext(entryPoint, { deployTarget: FUNCTIONAL_ENVIRONMENT, diff --git a/src/cli/context/steppes/setFunctionalEnvironment.ts b/src/cli/context/steppes/setFunctionalEnvironment.ts index cc893ff..5809be8 100644 --- a/src/cli/context/steppes/setFunctionalEnvironment.ts +++ b/src/cli/context/steppes/setFunctionalEnvironment.ts @@ -3,8 +3,10 @@ import { ExecuteStep } from '../core/executeStep' export class SetFunctionalEnvironmentStep extends ExecuteStep { public async method(context) { for (let serviceDefinition of context.publishedFunctions) { - const setEnvAttrib = environment('FUNCTIONAL_ENVIRONMENT', context.FUNCTIONAL_ENVIRONMENT) - setEnvAttrib(serviceDefinition.service) + const setFuncEnvEnvAttrib = environment('FUNCTIONAL_ENVIRONMENT', context.FUNCTIONAL_ENVIRONMENT) + const setStageEnvAttrib = environment('FUNCTIONAL_STAGE', context.stage) + setFuncEnvEnvAttrib(serviceDefinition.service) + setStageEnvAttrib(serviceDefinition.service) } } } diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 86e5f39..c0a3f51 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -336,7 +336,7 @@ export const lambdaLogResource = async (context) => { serviceDefinition.logGroupResourceName = name } -const UPDATEABLE_ENVIRONMENT_REGEXP = /^FUNCTIONAL_SERVICE_|_TABLE_NAME$|_S3_BUCKET$|_SNS_TOPICNAME$/ +const UPDATEABLE_ENVIRONMENT_REGEXP = /^FUNCTIONAL_SERVICE_/ const updateEnvironmentVariable = async ({ environments, stage }) => { if (environments) { for (const key in environments) { diff --git a/src/cli/providers/cloudFormation/context/sns.ts b/src/cli/providers/cloudFormation/context/sns.ts index daf0858..8493c49 100644 --- a/src/cli/providers/cloudFormation/context/sns.ts +++ b/src/cli/providers/cloudFormation/context/sns.ts @@ -139,7 +139,7 @@ const updateSNSEnvironmentVariables = async (context) => { for (const { serviceDefinition, serviceConfig } of snsConfig.services) { const environmentVariables = getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) || {} - environmentVariables[serviceConfig.environmentKey] = `${snsConfig.advTopicName}-${context.stage}` + environmentVariables[serviceConfig.environmentKey] = `${snsConfig.advTopicName}` await setStackParameter({ ...context, diff --git a/src/cli/utilities/aws/dynamoDB.ts b/src/cli/utilities/aws/dynamoDB.ts index c04b534..dfef836 100644 --- a/src/cli/utilities/aws/dynamoDB.ts +++ b/src/cli/utilities/aws/dynamoDB.ts @@ -47,7 +47,7 @@ export const createTable = (context) => { let params = { ...__dynamoDBDefaults, - TableName: tableConfig.tableName, + TableName: tableConfig.tableName + `-${process.env.FUNCTIONAL_STAGE}`, ...tableConfig.nativeConfig }; diff --git a/src/index.ts b/src/index.ts index d3d9f17..e51a611 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,7 +23,7 @@ export { role, aws, azure, - param, serviceParams, createParameterDecorator, request, context, error, functionalServiceName, + param, serviceParams, createParameterDecorator, request, context, error, functionalServiceName, provider, stage, inject, constants, defineMetadata, getMetadata, getMetadataKeys, getOwnMetadata, getOverridableMetadata diff --git a/src/providers/core/provider.ts b/src/providers/core/provider.ts index ea61d17..a90898b 100644 --- a/src/providers/core/provider.ts +++ b/src/providers/core/provider.ts @@ -121,4 +121,10 @@ Provider.addParameterDecoratorImplementation("error", async (parameter, context, }) Provider.addParameterDecoratorImplementation("functionalServiceName", async (parameter, context, provider) => { return context.serviceInstance && getFunctionName(context.serviceInstance) +}) +Provider.addParameterDecoratorImplementation("provider", async (parameter, context, provider) => { + return process.env.FUNCTIONAL_ENVIRONMENT +}) +Provider.addParameterDecoratorImplementation("stage", async (parameter, context, provider) => { + return process.env.FUNCTIONAL_STAGE }) \ No newline at end of file diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index 1601480..9619315 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -2,7 +2,7 @@ import { expect } from 'chai' import { getInvoker } from '../src/providers' import { FunctionalService, Service } from '../src/classes' -import { param, inject, injectable, serviceParams, request, functionalServiceName, functionName } from '../src/annotations' +import { param, inject, injectable, serviceParams, request, functionalServiceName, functionName, provider, stage } from '../src/annotations' import { parse } from 'url' @@ -31,6 +31,7 @@ describe('invoker', () => { afterEach(() => { delete process.env.FUNCTIONAL_ENVIRONMENT + delete process.env.FUNCTIONAL_STAGE }) it("invoke", (done) => { @@ -589,12 +590,74 @@ describe('invoker', () => { expect(counter).to.equal(2) }) + + it("provider", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + const req = { + } + const res = { + send: (data) => { + counter++ + } + } + const next = (e) => { expect(true).to.equal(false, e.message) } + + class MockService extends FunctionalService { + handle( @provider provider) { + counter++ + + expect(provider).to.equal('local') + + return { ok: 1 } + } + } + + const invoker = MockService.createInvoker() + await invoker(req, res, next) + + expect(counter).to.equal(2) + }) + + it("stage", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'dev' + + const req = { + } + const res = { + send: (data) => { + counter++ + } + } + const next = (e) => { expect(true).to.equal(false, e.message) } + + class MockService extends FunctionalService { + handle( @stage stage) { + counter++ + + expect(stage).to.equal('dev') + + return { ok: 1 } + } + } + + const invoker = MockService.createInvoker() + await invoker(req, res, next) + + expect(counter).to.equal(2) + }) }) describe("aws", () => { afterEach(() => { delete process.env.FUNCTIONAL_ENVIRONMENT + delete process.env.FUNCTIONAL_STAGE }) it("invoke", (done) => { @@ -1361,12 +1424,72 @@ describe('invoker', () => { expect(counter).to.equal(2) }) + + it("provider", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + const awsEvent = {} + const awsContext = {} + const cb = (e, result) => { + counter++ + + expect(result).to.deep.equal({ ok: 1 }) + } + + class MockService extends FunctionalService { + handle( @provider provider) { + counter++ + + expect(provider).to.equal('aws') + + return { ok: 1 } + } + } + + const invoker = MockService.createInvoker() + await invoker(awsEvent, awsContext, cb) + + expect(counter).to.equal(2) + }) + + it("stage", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + process.env.FUNCTIONAL_STAGE = 'dev' + + const awsEvent = {} + const awsContext = {} + const cb = (e, result) => { + counter++ + + expect(result).to.deep.equal({ ok: 1 }) + } + + class MockService extends FunctionalService { + handle( @stage stage) { + counter++ + + expect(stage).to.equal('dev') + + return { ok: 1 } + } + } + + const invoker = MockService.createInvoker() + await invoker(awsEvent, awsContext, cb) + + expect(counter).to.equal(2) + }) }) describe("azure", () => { afterEach(() => { delete process.env.FUNCTIONAL_ENVIRONMENT + delete process.env.FUNCTIONAL_STAGE }) it("invoke", async () => { @@ -1938,5 +2061,60 @@ describe('invoker', () => { expect(context).to.have.nested.property('res.body').that.deep.equal({ ok: 1 }) expect(counter).to.equal(1) }) + + it("provider", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + + const context = {} + const req = {} + + class MockService extends FunctionalService { + handle( @provider provider) { + counter++ + + expect(provider).to.equal('azure') + + return { ok: 1 } + } + } + + const invoker = MockService.createInvoker() + + await invoker(context, req) + + expect(context).to.have.nested.property('res.status', 200) + expect(context).to.have.nested.property('res.body').that.deep.equal({ ok: 1 }) + expect(counter).to.equal(1) + }) + + it("stage", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'azure' + process.env.FUNCTIONAL_STAGE = 'dev' + + const context = {} + const req = {} + + class MockService extends FunctionalService { + handle( @stage stage) { + counter++ + + expect(stage).to.equal('dev') + + return { ok: 1 } + } + } + + const invoker = MockService.createInvoker() + + await invoker(context, req) + + expect(context).to.have.nested.property('res.status', 200) + expect(context).to.have.nested.property('res.body').that.deep.equal({ ok: 1 }) + expect(counter).to.equal(1) + }) }) }) \ No newline at end of file From 465e0250e9e679376c9ce79860c8667fe974996e Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Fri, 1 Sep 2017 10:58:42 +0200 Subject: [PATCH 062/196] CHANGE: Renames: Service->Resource,InjectService->Api; ADD: injectable Service functions --- src/classes/{injectService.ts => api.ts} | 4 +- src/classes/externals/apiGateway.ts | 4 +- .../externals/{dynamoDB.ts => dynamoTable.ts} | 4 +- src/classes/externals/s3Storage.ts | 4 +- src/classes/externals/sns.ts | 4 +- src/classes/functionalService.ts | 4 +- src/classes/index.ts | 7 +- src/classes/resource.ts | 18 +++ src/classes/service.ts | 53 +++++--- src/index.ts | 2 +- src/providers/inProc.ts | 69 +++++++++++ test/annotation.tests.ts | 39 ++++-- test/hook.tests.ts | 14 +-- test/invoker.tests.ts | 117 ++++++++++++++---- test/serviceOnEvent.tests.ts | 46 +++---- 15 files changed, 297 insertions(+), 92 deletions(-) rename src/classes/{injectService.ts => api.ts} (94%) rename src/classes/externals/{dynamoDB.ts => dynamoTable.ts} (98%) create mode 100644 src/classes/resource.ts create mode 100644 src/providers/inProc.ts diff --git a/src/classes/injectService.ts b/src/classes/api.ts similarity index 94% rename from src/classes/injectService.ts rename to src/classes/api.ts index 74b701a..dff137d 100644 --- a/src/classes/injectService.ts +++ b/src/classes/api.ts @@ -1,9 +1,9 @@ -import { Service } from './service' +import { Resource } from './resource' import { getFunctionName } from '../annotations/classes/functionName' import { defineMetadata, getMetadata, constants, getClassConfigValue } from '../annotations' const { CLASS_ENVIRONMENTKEY, CLASS_CLASSCONFIGKEY } = constants -export class InjectService extends Service { +export class Api extends Resource { public static ConfigEnvironmentKey: string public static onDefineInjectTo(target, targetKey, parameterIndex: number) { diff --git a/src/classes/externals/apiGateway.ts b/src/classes/externals/apiGateway.ts index 10d3a81..76caf7c 100644 --- a/src/classes/externals/apiGateway.ts +++ b/src/classes/externals/apiGateway.ts @@ -1,9 +1,9 @@ -import { InjectService } from '../injectService' +import { Api } from '../api' import { constants, classConfig } from '../../annotations' const { CLASS_APIGATEWAYKEY } = constants @classConfig({ injectServiceEventSourceKey: CLASS_APIGATEWAYKEY }) -export class ApiGateway extends InjectService { } +export class ApiGateway extends Api { } diff --git a/src/classes/externals/dynamoDB.ts b/src/classes/externals/dynamoTable.ts similarity index 98% rename from src/classes/externals/dynamoDB.ts rename to src/classes/externals/dynamoTable.ts index 88c8e28..b79b16e 100644 --- a/src/classes/externals/dynamoDB.ts +++ b/src/classes/externals/dynamoTable.ts @@ -1,7 +1,7 @@ import * as AWS from 'aws-sdk' import { DocumentClient } from 'aws-sdk/lib/dynamodb/document_client' -import { InjectService } from '../injectService' +import { Api } from '../api' import { constants, getMetadata, classConfig } from '../../annotations' import { DYNAMO_TABLE_NAME_SUFFIX } from '../../annotations/classes/dynamoTable' @@ -33,7 +33,7 @@ const initAWSSDK = () => { injectServiceCopyMetadataKey: CLASS_DYNAMOTABLECONFIGURATIONKEY, injectServiceEventSourceKey: CLASS_DYNAMOTABLECONFIGURATIONKEY }) -export class DynamoDB extends InjectService { +export class DynamoTable extends Api { private _documentClient: DocumentClient public constructor() { initAWSSDK() diff --git a/src/classes/externals/s3Storage.ts b/src/classes/externals/s3Storage.ts index 7ba3933..4fdb6bf 100644 --- a/src/classes/externals/s3Storage.ts +++ b/src/classes/externals/s3Storage.ts @@ -1,5 +1,5 @@ import { S3 } from 'aws-sdk' -import { InjectService } from '../injectService' +import { Api } from '../api' import { constants, getMetadata, classConfig } from '../../annotations' import { S3_BUCKET_SUFFIX } from '../../annotations/classes/s3Storage' const { CLASS_S3CONFIGURATIONKEY } = constants @@ -32,7 +32,7 @@ const initAWSSDK = () => { injectServiceCopyMetadataKey: CLASS_S3CONFIGURATIONKEY, injectServiceEventSourceKey: CLASS_S3CONFIGURATIONKEY }) -export class S3Storage extends InjectService { +export class S3Storage extends Api { private _s3Client: S3 public constructor() { initAWSSDK() diff --git a/src/classes/externals/sns.ts b/src/classes/externals/sns.ts index 8b13dfc..bae1e57 100644 --- a/src/classes/externals/sns.ts +++ b/src/classes/externals/sns.ts @@ -1,7 +1,7 @@ import { SNS } from 'aws-sdk' export { SNS } from 'aws-sdk' -import { InjectService } from '../injectService' +import { Api } from '../api' import { constants, getMetadata, classConfig } from '../../annotations' const { CLASS_SNSCONFIGURATIONKEY } = constants @@ -31,7 +31,7 @@ const initAWSSDK = () => { injectServiceCopyMetadataKey: CLASS_SNSCONFIGURATIONKEY, injectServiceEventSourceKey: CLASS_SNSCONFIGURATIONKEY }) -export class SimpleNotificationService extends InjectService { +export class SimpleNotificationService extends Api { private _snsClient: SNS public constructor() { initAWSSDK() diff --git a/src/classes/functionalService.ts b/src/classes/functionalService.ts index d2052ce..b9d3fce 100644 --- a/src/classes/functionalService.ts +++ b/src/classes/functionalService.ts @@ -1,11 +1,11 @@ -import { Service } from './service' +import { Resource } from './resource' import { getInvoker, invoke } from '../providers' import { defineMetadata, getMetadata, constants, getFunctionName } from '../annotations' const { CLASS_ENVIRONMENTKEY } = constants export const FUNCTIONAL_SERVICE_PREFIX = 'FUNCTIONAL_SERVICE_' -export class FunctionalService extends Service { +export class FunctionalService extends Resource { public handle(...params) { } diff --git a/src/classes/index.ts b/src/classes/index.ts index e3ee939..ca625e9 100644 --- a/src/classes/index.ts +++ b/src/classes/index.ts @@ -1,12 +1,13 @@ -export { Service } from './service' -export { InjectService } from './injectService' +export { Resource } from './resource' +export { Api } from './api' export { FunctionalApi } from './functionalApi' export { FunctionalService } from './functionalService' -export { DynamoDB } from './externals/dynamoDB' +export { DynamoTable } from './externals/dynamoTable' export { SimpleNotificationService } from './externals/sns' export { S3Storage } from './externals/s3Storage' export { ApiGateway } from './externals/apiGateway' export { callExtension } from './core/classExtensions' +export { Service } from './service' export { PreHook } from './middleware/preHook' export { PostHook } from './middleware/postHook' \ No newline at end of file diff --git a/src/classes/resource.ts b/src/classes/resource.ts new file mode 100644 index 0000000..878e6ec --- /dev/null +++ b/src/classes/resource.ts @@ -0,0 +1,18 @@ +import { defineMetadata, getMetadata, constants } from '../annotations' +const { CLASS_ENVIRONMENTKEY } = constants + +export class Resource { + public constructor(...params) { } + public static factory(...params) { return new this(...params) } + + public static onDefineInjectTo(target, targetKey, parameterIndex: number) { + const metadata = getMetadata(CLASS_ENVIRONMENTKEY, target) || {} + const injectMetadata = getMetadata(CLASS_ENVIRONMENTKEY, this) || {} + if (injectMetadata) { + Object.keys(injectMetadata).forEach((key) => { + metadata[key] = injectMetadata[key] + }) + defineMetadata(CLASS_ENVIRONMENTKEY, { ...metadata }, target); + } + } +} \ No newline at end of file diff --git a/src/classes/service.ts b/src/classes/service.ts index abd9be9..4be60ed 100644 --- a/src/classes/service.ts +++ b/src/classes/service.ts @@ -1,18 +1,39 @@ -import { defineMetadata, getMetadata, constants } from '../annotations' -const { CLASS_ENVIRONMENTKEY } = constants - -export class Service { - public constructor(...params) { } - public static factory(...params) { return new this(...params) } - - public static onDefineInjectTo(target, targetKey, parameterIndex: number) { - const metadata = getMetadata(CLASS_ENVIRONMENTKEY, target) || {} - const injectMetadata = getMetadata(CLASS_ENVIRONMENTKEY, this) || {} - if (injectMetadata) { - Object.keys(injectMetadata).forEach((key) => { - metadata[key] = injectMetadata[key] - }) - defineMetadata(CLASS_ENVIRONMENTKEY, { ...metadata }, target); - } +import { Resource } from './resource' +import { callExtension } from '../classes' +import { constants, getMetadata, getOverridableMetadata } from '../annotations' +const { PARAMETER_PARAMKEY } = constants + +import { InProcProvider } from '../providers/inProc' +const provider = new InProcProvider() + +export class Service extends Resource { + public handle(...params) { + + } + + public async invoke(params?, invokeConfig?) { + const availableParams = {} + const parameterMapping = (getOverridableMetadata(PARAMETER_PARAMKEY, this.constructor, 'handle') || []) + parameterMapping.forEach((target) => { + if (params && target && target.type === 'param') { + availableParams[target.from] = params[target.from] + } + }) + + await callExtension(this, `onInvoke`, { + invokeParams: params, + params: availableParams, + invokeConfig, + parameterMapping + }) + + const invoker = provider.getInvoker(this, undefined) + + return await invoker(availableParams) + } + + public static async onInject({ parameter }): Promise { + const service = new this() + return (...params) => service.invoke(...params) } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index e51a611..109b987 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export { Service, FunctionalApi, FunctionalService, DynamoDB, SimpleNotificationService, S3Storage, ApiGateway, callExtension, PreHook, PostHook } from './classes' +export { Resource, FunctionalApi, FunctionalService, DynamoTable, SimpleNotificationService, S3Storage, ApiGateway, callExtension, PreHook, PostHook, Service } from './classes' export { addProvider, removeProvider, Provider, AWSProvider, LocalProvider } from './providers' export { diff --git a/src/providers/inProc.ts b/src/providers/inProc.ts new file mode 100644 index 0000000..a714e88 --- /dev/null +++ b/src/providers/inProc.ts @@ -0,0 +1,69 @@ +import * as request from 'request' +import { Provider } from './core/provider' +import { constants, getMetadata, getFunctionName, rest } from '../annotations' +const { CLASS_LOGKEY } = constants +import { get } from '../helpers/property' +import { parse } from 'url' + +export class InProcProvider extends Provider { + public getInvoker(serviceInstance, params) { + const callContext = this.createCallContext(serviceInstance, 'handle') + + const invoker = async (invokeParams) => { + const eventContext = { params: invokeParams } + + let result + let error + try { + result = await callContext({ event: eventContext, serviceInstance }) + } catch (err) { + error = err + } + const response = await this.resultTransform(error, result, eventContext) + + return response + } + return invoker + } + + protected resultTransform(error, result, eventContext) { + if (error) throw error + + if (result && typeof result.status === 'number' && result.hasOwnProperty('data')) { + return result.data + } + + return result + } + +} + +InProcProvider.addParameterDecoratorImplementation("param", async (parameter, context, provider) => { + const req = context.event.params + const source = parameter.source; + if (typeof source !== 'undefined') { + const holder = !source ? context : get(context, source) + if (holder) { + return get(holder, parameter.from) + } + } else { + let value = undefined + if (typeof (value = get(req, parameter.from)) !== 'undefined') return value + if (typeof (value = get(context.context, parameter.from)) !== 'undefined') return value + return value + } + return undefined +}) + +/*InProcProvider.addParameterDecoratorImplementation("request", async (parameter, context, provider) => { + return { + url: context.event.req._parsedUrl || parse(context.event.req.originalUrl), + method: context.event.req.method, + body: context.event.req.body, + query: context.event.req.query, + params: context.event.req.params, + headers: context.event.req.headers + } +})*/ + +export const provider = new InProcProvider() diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index b801414..34c7fd1 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -32,7 +32,7 @@ import { azure } from '../src/annotations/classes/azure/azure' import { inject } from '../src/annotations/parameters/inject' import { param, request, serviceParams, createParameterDecorator } from '../src/annotations/parameters/param' -import { FunctionalService, Service, DynamoDB, SimpleNotificationService, S3Storage, InjectService } from '../src/classes' +import { FunctionalService, Resource, DynamoTable, SimpleNotificationService, S3Storage, Api, Service } from '../src/classes' @@ -949,7 +949,7 @@ describe('annotations', () => { it("eventSource inject service", () => { @dynamoTable({ tableName: 't1' }) @classConfig({ injectServiceEventSourceKey: CLASS_DYNAMOTABLECONFIGURATIONKEY }) - class ATestClass extends InjectService { } + class ATestClass extends Api { } @eventSource(ATestClass) class BTestClass extends FunctionalService { } @@ -1068,10 +1068,10 @@ describe('annotations', () => { .property(`FUNCTIONAL_SERVICE_${ATestClass.name.toUpperCase()}`, getFunctionName(ATestClass)) }) - it("service inject", () => { + it("resource inject", () => { @injectable @environment('%ClassName%_defined_environment', 'value') - class ATestClass extends Service { } + class ATestClass extends Resource { } class BTestClass { method( @inject(ATestClass) a) { } } @@ -1089,12 +1089,12 @@ describe('annotations', () => { const environmentMetadata = getMetadata(CLASS_ENVIRONMENTKEY, BTestClass) expect(environmentMetadata).to.have .property(`ATestClass_defined_environment`, 'value') - }); + }) - it("injected DynamoDB", () => { + it("injected DynamoTable", () => { @injectable @dynamoTable({ tableName: 'ATable' }) - class ATestClass extends DynamoDB { } + class ATestClass extends DynamoTable { } class BTestClass { method( @inject(ATestClass) a) { } } @@ -1187,7 +1187,7 @@ describe('annotations', () => { method( @inject(ATestClass) a) { } } class BTestClass extends BaseBTestClass { - + } const value = getOverridableMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') @@ -1200,6 +1200,29 @@ describe('annotations', () => { expect(metadata).to.have.property('parameterIndex', 0) expect(metadata).to.have.property('type', 'inject') }) + + it("service inject", () => { + @injectable + @environment('%ClassName%_defined_environment', 'value') + class ATestClass extends Service { } + class BTestClass { + method( @inject(ATestClass) a) { } + } + + const value = getOverridableMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('serviceType', ATestClass) + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'inject') + + const environmentMetadata = getMetadata(CLASS_ENVIRONMENTKEY, BTestClass) + expect(environmentMetadata).to.have + .property(`ATestClass_defined_environment`, 'value') + }) }) describe("param", () => { it("param", () => { diff --git a/test/hook.tests.ts b/test/hook.tests.ts index 4e54a8f..93da530 100644 --- a/test/hook.tests.ts +++ b/test/hook.tests.ts @@ -1,5 +1,5 @@ import { expect } from 'chai' -import { FunctionalService, PreHook, PostHook, Service, DynamoDB, SimpleNotificationService, S3Storage } from '../src/classes' +import { FunctionalService, PreHook, PostHook, Resource, DynamoTable, SimpleNotificationService, S3Storage } from '../src/classes' import { getOverridableMetadata, constants, getMetadata, getFunctionName } from '../src/annotations' const { PARAMETER_PARAMKEY, CLASS_ENVIRONMENTKEY, CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_SNSCONFIGURATIONKEY, CLASS_S3CONFIGURATIONKEY, CLASS_MIDDLEWAREKEY } = constants @@ -147,7 +147,7 @@ describe('hooks', () => { it("service inject", () => { @injectable @environment('%ClassName%_defined_environment', 'value') - class ATestClass extends Service { } + class ATestClass extends Resource { } class TestHook extends PreHook { public async handle( @inject(ATestClass) a) { } @@ -163,10 +163,10 @@ describe('hooks', () => { .property(`ATestClass_defined_environment`, 'value') }); - it("injected DynamoDB", () => { + it("injected DynamoTable", () => { @injectable @dynamoTable({ tableName: 'ATable' }) - class ATestClass extends DynamoDB { } + class ATestClass extends DynamoTable { } class TestHook extends PreHook { public async handle( @inject(ATestClass) a) { } @@ -261,7 +261,7 @@ describe('hooks', () => { it("service inject", () => { @injectable @environment('%ClassName%_defined_environment', 'value') - class ATestClass extends Service { } + class ATestClass extends Resource { } class TestHookLevel2 extends PreHook { public async handle( @inject(ATestClass) a) { } @@ -282,10 +282,10 @@ describe('hooks', () => { .property(`ATestClass_defined_environment`, 'value') }); - it("injected DynamoDB", () => { + it("injected DynamoTable", () => { @injectable @dynamoTable({ tableName: 'ATable' }) - class ATestClass extends DynamoDB { } + class ATestClass extends DynamoTable { } class TestHookLevel2 extends PreHook { public async handle( @inject(ATestClass) a) { } diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index 9619315..6b6d56e 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -1,7 +1,7 @@ import { expect } from 'chai' import { getInvoker } from '../src/providers' -import { FunctionalService, Service } from '../src/classes' +import { FunctionalService, Resource, Service } from '../src/classes' import { param, inject, injectable, serviceParams, request, functionalServiceName, functionName, provider, stage } from '../src/annotations' import { parse } from 'url' @@ -326,13 +326,13 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' @injectable - class MockInjectable extends Service { } + class MockInjectable extends Resource { } class MockService extends FunctionalService { handle( @param p1, @inject(MockInjectable) p2) { counter++ expect(p1).to.undefined - expect(p2).to.instanceof(Service) + expect(p2).to.instanceof(Resource) expect(p2).to.instanceof(MockInjectable) } } @@ -346,13 +346,47 @@ describe('invoker', () => { }, (e) => { e && done(e) }) }) + it("inject service", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + @injectable + class CustomService extends Service { + handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('p1') + expect(p2).to.equal('p2') + } + } + + class MockService extends FunctionalService { + handle( @param p1, @inject(CustomService) p2) { + counter++ + expect(p1).to.undefined + expect(p2).to.instanceof(Function) + + p2({ p1: 'p1', p2: 'p2' }) + } + } + + const invoker = MockService.createInvoker() + invoker({}, { + send: () => { + expect(counter).to.equal(2) + done() + } + }, (e) => { e && done(e) }) + }) + + it("serviceParams param", (done) => { let counter = 0 process.env.FUNCTIONAL_ENVIRONMENT = 'local' @injectable - class MockInjectable extends Service { } + class MockInjectable extends Resource { } const req = {} const res = { @@ -383,7 +417,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' @injectable - class MockInjectable extends Service { } + class MockInjectable extends Resource { } const req = { originalUrl: '/a/b', @@ -433,7 +467,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' @injectable - class MockInjectable extends Service { } + class MockInjectable extends Resource { } const req = { originalUrl: '/a/b?a=b', @@ -484,7 +518,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' @injectable - class MockInjectable extends Service { } + class MockInjectable extends Resource { } const req = { } @@ -1175,7 +1209,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' @injectable - class MockInjectable extends Service { } + class MockInjectable extends Resource { } const awsEvent = { requestContext: { apiId: 'apiId' }, @@ -1226,13 +1260,13 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' @injectable - class MockInjectable extends Service { } + class MockInjectable extends Resource { } class MockService extends FunctionalService { handle( @param p1, @inject(MockInjectable) p2) { counter++ expect(p1).to.undefined - expect(p2).to.instanceof(Service) + expect(p2).to.instanceof(Resource) expect(p2).to.instanceof(MockInjectable) } } @@ -1245,13 +1279,45 @@ describe('invoker', () => { }) }) + it("service param", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + @injectable + class CustomService extends Service { + handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('p1') + expect(p2).to.equal('p2') + } + } + + class MockService extends FunctionalService { + handle( @param p1, @inject(CustomService) p2) { + counter++ + expect(p1).to.undefined + expect(p2).to.instanceof(Function) + + p2({ p1: 'p1', p2: 'p2' }) + } + } + + const invoker = MockService.createInvoker() + + invoker({}, {}, (e) => { + expect(counter).to.equal(2) + done(e) + }) + }) + it("serviceParams param", (done) => { let counter = 0 process.env.FUNCTIONAL_ENVIRONMENT = 'aws' @injectable - class MockInjectable extends Service { } + class MockInjectable extends Resource { } const awsEvent = {} const awsContext = {} @@ -1280,7 +1346,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' @injectable - class MockInjectable extends Service { } + class MockInjectable extends Resource { } const awsEvent = { path: '/a/b', @@ -1329,7 +1395,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' @injectable - class MockInjectable extends Service { } + class MockInjectable extends Resource { } const awsEvent = { } @@ -1862,20 +1928,27 @@ describe('invoker', () => { }) }) - it("inject param", async () => { + it("inject service", async () => { let counter = 0 process.env.FUNCTIONAL_ENVIRONMENT = 'azure' @injectable - class MockInjectable extends Service { } + class CustomService extends Service { + handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('p1') + expect(p2).to.equal('p2') + } + } class MockService extends FunctionalService { - handle( @param p1, @inject(MockInjectable) p2) { + handle( @param p1, @inject(CustomService) p2) { counter++ expect(p1).to.undefined - expect(p2).to.instanceof(Service) - expect(p2).to.instanceof(MockInjectable) + expect(p2).to.instanceof(Function) + + p2({ p1: 'p1', p2: 'p2' }) } } @@ -1887,7 +1960,7 @@ describe('invoker', () => { await invoker(context, req) expect(context).to.have.nested.property('res.status', 200) - expect(counter).to.equal(1) + expect(counter).to.equal(2) }) it("serviceParams param", async () => { @@ -1896,7 +1969,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' @injectable - class MockInjectable extends Service { } + class MockInjectable extends Resource { } const context = {} const req = {} @@ -1924,7 +1997,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' @injectable - class MockInjectable extends Service { } + class MockInjectable extends Resource { } const context = {} const req = { @@ -1971,7 +2044,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' @injectable - class MockInjectable extends Service { } + class MockInjectable extends Resource { } const context = {} const req = { diff --git a/test/serviceOnEvent.tests.ts b/test/serviceOnEvent.tests.ts index 6ae5a30..861c104 100644 --- a/test/serviceOnEvent.tests.ts +++ b/test/serviceOnEvent.tests.ts @@ -1,6 +1,6 @@ import { expect } from 'chai' -import { FunctionalService, Service } from '../src/classes' +import { FunctionalService, Resource } from '../src/classes' import { param, inject, injectable } from '../src/annotations' import { addProvider, removeProvider } from '../src/providers' import { LocalProvider } from '../src/providers/local' @@ -21,7 +21,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' @injectable - class MockInjectable extends Service { + class MockInjectable extends Resource { public static async onInject({ parameter }) { counter++ @@ -36,7 +36,7 @@ describe('service events', () => { class MockService extends FunctionalService { handle( @inject(MockInjectable) p1) { counter++ - expect(p1).to.instanceof(Service) + expect(p1).to.instanceof(Resource) expect(p1).to.instanceof(MockInjectable) } } @@ -56,7 +56,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' @injectable - class MockInjectable extends Service { + class MockInjectable extends Resource { public static async onInject({ parameter }) { counter++ @@ -73,7 +73,7 @@ describe('service events', () => { class MockService extends FunctionalService { handle( @inject(MockInjectable) p1) { counter++ - expect(p1).to.not.instanceof(Service) + expect(p1).to.not.instanceof(Resource) expect(p1).to.not.instanceof(MockInjectable) expect(p1).to.deep.equal({ mockRetrurn: 1 }) } @@ -94,7 +94,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' @injectable - class MockInjectable extends Service { + class MockInjectable extends Resource { public async onInject({ parameter }) { counter++ @@ -109,7 +109,7 @@ describe('service events', () => { class MockService extends FunctionalService { handle( @inject(MockInjectable) p1) { counter++ - expect(p1).to.instanceof(Service) + expect(p1).to.instanceof(Resource) expect(p1).to.instanceof(MockInjectable) } } @@ -129,7 +129,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' @injectable - class MockInjectable extends Service { + class MockInjectable extends Resource { public static async onInject({ parameter }) { counter++ @@ -153,7 +153,7 @@ describe('service events', () => { class MockService extends FunctionalService { handle( @inject(MockInjectable) p1) { counter++ - expect(p1).to.instanceof(Service) + expect(p1).to.instanceof(Resource) expect(p1).to.instanceof(MockInjectable) } } @@ -173,7 +173,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' @injectable - class MockInjectable extends Service { + class MockInjectable extends Resource { public static async onInject({ parameter }) { counter++ @@ -194,7 +194,7 @@ describe('service events', () => { class MockService extends FunctionalService { handle( @inject(MockInjectable) p1) { counter++ - expect(p1).to.not.instanceof(Service) + expect(p1).to.not.instanceof(Resource) expect(p1).to.not.instanceof(MockInjectable) expect(p1).to.deep.equal({ mockRetrurn: 1 }) } @@ -291,7 +291,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' @injectable - class MockInjectable extends Service { + class MockInjectable extends Resource { public static async onInject({ parameter }) { counter++ @@ -306,7 +306,7 @@ describe('service events', () => { class MockService extends FunctionalService { handle( @inject(MockInjectable) p1) { counter++ - expect(p1).to.instanceof(Service) + expect(p1).to.instanceof(Resource) expect(p1).to.instanceof(MockInjectable) } } @@ -324,7 +324,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' @injectable - class MockInjectable extends Service { + class MockInjectable extends Resource { public static async onInject({ parameter }) { counter++ @@ -341,7 +341,7 @@ describe('service events', () => { class MockService extends FunctionalService { handle( @inject(MockInjectable) p1) { counter++ - expect(p1).to.not.instanceof(Service) + expect(p1).to.not.instanceof(Resource) expect(p1).to.not.instanceof(MockInjectable) expect(p1).to.deep.equal({ mockRetrurn: 1 }) } @@ -360,7 +360,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' @injectable - class MockInjectable extends Service { + class MockInjectable extends Resource { public async onInject({ parameter }) { counter++ @@ -375,7 +375,7 @@ describe('service events', () => { class MockService extends FunctionalService { handle( @inject(MockInjectable) p1) { counter++ - expect(p1).to.instanceof(Service) + expect(p1).to.instanceof(Resource) expect(p1).to.instanceof(MockInjectable) } } @@ -393,7 +393,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' @injectable - class MockInjectable extends Service { + class MockInjectable extends Resource { public static async onInject({ parameter }) { counter++ @@ -417,7 +417,7 @@ describe('service events', () => { class MockService extends FunctionalService { handle( @inject(MockInjectable) p1) { counter++ - expect(p1).to.instanceof(Service) + expect(p1).to.instanceof(Resource) expect(p1).to.instanceof(MockInjectable) } } @@ -435,7 +435,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' @injectable - class MockInjectable extends Service { + class MockInjectable extends Resource { public static async onInject({ parameter }) { counter++ @@ -456,7 +456,7 @@ describe('service events', () => { class MockService extends FunctionalService { handle( @inject(MockInjectable) p1) { counter++ - expect(p1).to.not.instanceof(Service) + expect(p1).to.not.instanceof(Resource) expect(p1).to.not.instanceof(MockInjectable) expect(p1).to.deep.equal({ mockRetrurn: 1 }) } @@ -573,7 +573,7 @@ describe('service events', () => { class MockService extends FunctionalService { async handle( @inject(MockInjectable) p1) { counter++ - expect(p1).to.instanceof(Service) + expect(p1).to.instanceof(Resource) expect(p1).to.instanceof(MockInjectable) await p1.invoke({ p1: 1, p2: 2 }, { config: 1 }) @@ -616,7 +616,7 @@ describe('service events', () => { class MockService extends FunctionalService { async handle( @inject(MockInjectable) p1) { counter++ - expect(p1).to.instanceof(Service) + expect(p1).to.instanceof(Resource) expect(p1).to.instanceof(MockInjectable) await p1.invoke({ p1: 1, p2: 2 }, { config: 1 }) From fdf8871fa2201e3a5db58d7d533ea12de0058782 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Fri, 1 Sep 2017 16:07:58 +0200 Subject: [PATCH 063/196] ADD: injection in Api contructor; ADD: async Api with init method on Api class --- src/classes/api.ts | 7 ++ src/index.ts | 2 +- src/providers/core/provider.ts | 22 ++++- test/invoker.tests.ts | 162 ++++++++++++++++++++++++++++++++- 4 files changed, 188 insertions(+), 5 deletions(-) diff --git a/src/classes/api.ts b/src/classes/api.ts index dff137d..91ba8dc 100644 --- a/src/classes/api.ts +++ b/src/classes/api.ts @@ -4,6 +4,13 @@ import { defineMetadata, getMetadata, constants, getClassConfigValue } from '../ const { CLASS_ENVIRONMENTKEY, CLASS_CLASSCONFIGKEY } = constants export class Api extends Resource { + constructor(...params) { + super(); + } + public async init(): Promise { + + } + public static ConfigEnvironmentKey: string public static onDefineInjectTo(target, targetKey, parameterIndex: number) { diff --git a/src/index.ts b/src/index.ts index 109b987..f60be99 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export { Resource, FunctionalApi, FunctionalService, DynamoTable, SimpleNotificationService, S3Storage, ApiGateway, callExtension, PreHook, PostHook, Service } from './classes' +export { Resource, FunctionalApi, FunctionalService, DynamoTable, SimpleNotificationService, S3Storage, ApiGateway, callExtension, PreHook, PostHook, Service, Api } from './classes' export { addProvider, removeProvider, Provider, AWSProvider, LocalProvider } from './providers' export { diff --git a/src/providers/core/provider.ts b/src/providers/core/provider.ts index a90898b..b985679 100644 --- a/src/providers/core/provider.ts +++ b/src/providers/core/provider.ts @@ -17,6 +17,23 @@ export abstract class Provider { } + public async createInstance(type, context) { + const parameters = this.getParameters(type, undefined) + + const params = [] + for (const parameter of parameters) { + params[parameter.parameterIndex] = await this.parameterResolver(parameter, context) + } + + const instance = new type(...params) + + if (typeof instance.init === 'function') { + await instance.init() + } + + return instance + } + protected async parameterResolver(parameter, context): Promise { const implementation = this.getParameterDecoratorImplementation(parameter.type) || (() => { }) return implementation(parameter, context, this) @@ -96,7 +113,6 @@ export abstract class Provider { } Provider.addParameterDecoratorImplementation("inject", async (parameter, context, provider) => { - const event = context.event const serviceType = parameter.serviceType const staticInstance = await callExtension(serviceType, 'onInject', { parameter }) @@ -104,7 +120,9 @@ Provider.addParameterDecoratorImplementation("inject", async (parameter, context return staticInstance } - const instance = new serviceType(...parameter.params.map((p) => typeof p === 'function' ? p() : p)) + // const instance = new serviceType(...parameter.params.map((p) => typeof p === 'function' ? p() : p)) + const instance = await provider.createInstance(serviceType, context) + await callExtension(instance, 'onInject', { parameter }) return instance }) diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index 6b6d56e..7640e66 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -1,7 +1,7 @@ import { expect } from 'chai' import { getInvoker } from '../src/providers' -import { FunctionalService, Resource, Service } from '../src/classes' +import { FunctionalService, Resource, Service, Api } from '../src/classes' import { param, inject, injectable, serviceParams, request, functionalServiceName, functionName, provider, stage } from '../src/annotations' import { parse } from 'url' @@ -25,6 +25,164 @@ describe('invoker', () => { const invoker = MockService.createInvoker() expect(invoker).to.be.a('function') }) + + it("inject service", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + @injectable + class CustomService extends Service { + public async handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('p1', 'CustomService') + expect(p2).to.equal('p2', 'CustomService') + } + } + + class MockService extends FunctionalService { + public async handle( @param noparam, @inject(CustomService) myService) { + counter++ + expect(noparam).to.undefined + expect(myService).to.instanceof(Function) + + await myService({ p1: 'p1', p2: 'p2' }) + } + } + + const invoker = MockService.createInvoker() + await invoker({}, { + send: () => { + expect(counter).to.equal(2) + } + }, (e) => { + expect(null).to.equal(e) + throw e + }) + + expect(counter).to.equal(2) + }) + + it("inject api with service", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + @injectable + class CustomService extends Service { + public async handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('p1', 'CustomService') + expect(p2).to.equal('p2', 'CustomService') + } + } + + @injectable + class CustomApi extends Api { + private _myService + public constructor( @inject(CustomService) myService) { + super() + this._myService = myService + } + public async myMethod(p1, p2) { + counter++ + expect(p1).to.equal('p1', 'CustomApi') + expect(p2).to.equal('p2', 'CustomApi') + + await this._myService({ p1, p2 }) + } + } + + class MockService extends FunctionalService { + public async handle( @param p1, @inject(CustomApi) api) { + counter++ + expect(p1).to.undefined + expect(api).to.instanceof(CustomApi) + + await api.myMethod('p1', 'p2') + } + } + + const invoker = MockService.createInvoker() + await invoker({}, { + send: () => { + expect(counter).to.equal(3) + } + }, (e) => { + expect(null).to.equal(e) + throw e + }) + + expect(counter).to.equal(3) + }) + + it("inject async api with service", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + @injectable + class CustomService extends Service { + public async handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('p1', 'CustomService') + expect(p2).to.equal('p2', 'CustomService') + } + } + + @injectable + class CustomApi extends Api { + private _myService + public constructor( @inject(CustomService) myService) { + super() + this._myService = myService + } + + public init() { + counter++ + expect(counter).to.equal(1) + return new Promise(resolve => { + setTimeout(_ => { + counter++ + expect(counter).to.equal(2) + + resolve() + }, 10); + }) + } + + public async myMethod(p1, p2) { + counter++ + expect(p1).to.equal('p1', 'CustomApi') + expect(p2).to.equal('p2', 'CustomApi') + + await this._myService({ p1, p2 }) + } + } + + class MockService extends FunctionalService { + public async handle( @param p1, @inject(CustomApi) api) { + counter++ + expect(counter).to.equal(3) + expect(p1).to.undefined + expect(api).to.instanceof(CustomApi) + + await api.myMethod('p1', 'p2') + } + } + + const invoker = MockService.createInvoker() + await invoker({}, { + send: () => { + expect(counter).to.equal(5) + } + }, (e) => { + expect(null).to.equal(e) + throw e + }) + + expect(counter).to.equal(5) + }) }) describe("local", () => { @@ -1928,7 +2086,7 @@ describe('invoker', () => { }) }) - it("inject service", async () => { + it("inject api with service", async () => { let counter = 0 process.env.FUNCTIONAL_ENVIRONMENT = 'azure' From 89e45cd2a375020c6d9190e547975feeb8cb7079 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 4 Sep 2017 15:21:10 +0200 Subject: [PATCH 064/196] REFACTOR: hook handling --- src/annotations/index.ts | 2 +- src/annotations/parameters/param.ts | 2 +- .../{classExtensions.ts => callExtension.ts} | 0 src/classes/functionalApi.ts | 2 - src/classes/index.ts | 3 +- src/classes/middleware/hook.ts | 14 +- src/classes/middleware/postHook.ts | 4 +- src/index.ts | 48 ++-- src/providers/aws/eventSources/apiGateway.ts | 1 - src/providers/aws/eventSources/dynamoTable.ts | 1 - src/providers/aws/eventSources/lambdaCall.ts | 1 - src/providers/aws/eventSources/s3.ts | 1 - src/providers/aws/eventSources/sns.ts | 1 - .../azure/eventSources/httpTrigger.ts | 1 - src/providers/core/provider.ts | 27 +- src/providers/inProc.ts | 11 - src/providers/local.ts | 1 - test/hook.tests.ts | 256 ++++++++++++++---- 18 files changed, 252 insertions(+), 124 deletions(-) rename src/classes/core/{classExtensions.ts => callExtension.ts} (100%) delete mode 100644 src/classes/functionalApi.ts diff --git a/src/annotations/index.ts b/src/annotations/index.ts index 4ca0372..84f2b0f 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -26,7 +26,7 @@ export { aws } from './classes/aws/aws' export { azure } from './classes/azure/azure' -export { param, serviceParams, createParameterDecorator, request, context, error, functionalServiceName, provider, stage } from './parameters/param' +export { param, serviceParams, createParameterDecorator, request, error, result, functionalServiceName, provider, stage } from './parameters/param' export { inject } from './parameters/inject' import * as _constants from './constants' diff --git a/src/annotations/parameters/param.ts b/src/annotations/parameters/param.ts index 81ccc2c..f6eb64d 100644 --- a/src/annotations/parameters/param.ts +++ b/src/annotations/parameters/param.ts @@ -65,8 +65,8 @@ export const createParameterDecorator = (type: string, defaultConfig?: any) => ( export const serviceParams = createParameterDecorator('serviceParams') export const request = createParameterDecorator('request') -export const context = createParameterDecorator('context') export const error = createParameterDecorator('error') +export const result = createParameterDecorator('result') export const functionalServiceName = createParameterDecorator('functionalServiceName') export const provider = createParameterDecorator('provider') export const stage = createParameterDecorator('stage') diff --git a/src/classes/core/classExtensions.ts b/src/classes/core/callExtension.ts similarity index 100% rename from src/classes/core/classExtensions.ts rename to src/classes/core/callExtension.ts diff --git a/src/classes/functionalApi.ts b/src/classes/functionalApi.ts deleted file mode 100644 index 32842c0..0000000 --- a/src/classes/functionalApi.ts +++ /dev/null @@ -1,2 +0,0 @@ -export class FunctionalApi { -} \ No newline at end of file diff --git a/src/classes/index.ts b/src/classes/index.ts index ca625e9..5d83e2d 100644 --- a/src/classes/index.ts +++ b/src/classes/index.ts @@ -1,12 +1,11 @@ export { Resource } from './resource' export { Api } from './api' -export { FunctionalApi } from './functionalApi' export { FunctionalService } from './functionalService' export { DynamoTable } from './externals/dynamoTable' export { SimpleNotificationService } from './externals/sns' export { S3Storage } from './externals/s3Storage' export { ApiGateway } from './externals/apiGateway' -export { callExtension } from './core/classExtensions' +export { callExtension } from './core/callExtension' export { Service } from './service' export { PreHook } from './middleware/preHook' diff --git a/src/classes/middleware/hook.ts b/src/classes/middleware/hook.ts index bb79e80..f63f7f6 100644 --- a/src/classes/middleware/hook.ts +++ b/src/classes/middleware/hook.ts @@ -1,11 +1,11 @@ -import { getMetadata, defineMetadata, constants, context } from '../../annotations' +import { getMetadata, defineMetadata, constants, result } from '../../annotations' import { getMiddlewares } from '../../annotations/classes/use' const { PARAMETER_PARAMKEY, CLASS_ENVIRONMENTKEY } = constants export class Hook { public handle(...params); - public handle( @context context) { - return context.result + public handle( @result res) { + return res } public static onDefineMiddlewareTo(target) { @@ -36,4 +36,12 @@ export class Hook { defineMetadata(CLASS_ENVIRONMENTKEY, { ...metadata }, target); } } + + public static onInject({ parameter, context }) { + const key = parameter.serviceType && parameter.serviceType.name + if (key && context.context && typeof context.context[key] !== 'undefined') { + return context.context[key] + } + return null + } } \ No newline at end of file diff --git a/src/classes/middleware/postHook.ts b/src/classes/middleware/postHook.ts index 2b4a127..2e2a01e 100644 --- a/src/classes/middleware/postHook.ts +++ b/src/classes/middleware/postHook.ts @@ -1,10 +1,10 @@ import { Hook } from './hook' -import { param, getMetadata, constants } from '../../annotations' +import { error, getMetadata, constants } from '../../annotations' const { PARAMETER_PARAMKEY } = constants export class PostHook extends Hook { public catch(...params); - public catch( @param error) { + public catch( @error error) { throw error } diff --git a/src/index.ts b/src/index.ts index f60be99..e9e9d20 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,30 +1,24 @@ -export { Resource, FunctionalApi, FunctionalService, DynamoTable, SimpleNotificationService, S3Storage, ApiGateway, callExtension, PreHook, PostHook, Service, Api } from './classes' -export { addProvider, removeProvider, Provider, AWSProvider, LocalProvider } from './providers' +/* Classes */ +export { FunctionalService, Api, Service, PreHook, PostHook, Resource } from './classes' + +/* Apis */ +export { DynamoTable, SimpleNotificationService, S3Storage, ApiGateway } from './classes' + +/* Providers */ +export { Provider, AWSProvider, LocalProvider } from './providers' +export { addProvider, removeProvider } from './providers' + +/* Decorators */ +export { + injectable, apiGateway, httpTrigger, rest, httpGet, httpPost, httpPut, httpPatch, httpDelete, IHttpMethod, environment, + tag, log, functionName, dynamoTable, sns, s3Storage, eventSource, classConfig, use, description, role, aws, azure, + param, serviceParams, request, error, result, functionalServiceName, provider, stage, inject +} from './annotations' + +/* Helpers */ +export { callExtension } from './classes' export { - templates, applyTemplates, - injectable, - apiGateway, - httpTrigger, - rest, httpGet, httpPost, httpPut, httpPatch, httpDelete, IHttpMethod, - environment, - tag, - log, - functionName, getFunctionName, - dynamoTable, __dynamoDBDefaults, - sns, - s3Storage, - eventSource, - classConfig, getClassConfigValue, - simpleClassAnnotation, - expandableDecorator, - use, - description, - role, - aws, - azure, - param, serviceParams, createParameterDecorator, request, context, error, functionalServiceName, provider, stage, - inject, - constants, - defineMetadata, getMetadata, getMetadataKeys, getOwnMetadata, getOverridableMetadata + templates, applyTemplates, getFunctionName, __dynamoDBDefaults, getClassConfigValue, simpleClassAnnotation, expandableDecorator, + createParameterDecorator, constants, defineMetadata, getMetadata, getMetadataKeys, getOwnMetadata, getOverridableMetadata } from './annotations' diff --git a/src/providers/aws/eventSources/apiGateway.ts b/src/providers/aws/eventSources/apiGateway.ts index 8a08d92..6b69987 100644 --- a/src/providers/aws/eventSources/apiGateway.ts +++ b/src/providers/aws/eventSources/apiGateway.ts @@ -27,7 +27,6 @@ export class ApiGateway extends EventSource { if (typeof (value = get(query, parameter.from)) !== 'undefined') return value if (typeof (value = get(params, parameter.from)) !== 'undefined') return value if (typeof (value = get(headers, parameter.from)) !== 'undefined') return value - if (typeof (value = get(context, parameter.from)) !== 'undefined') return value return value; } return undefined diff --git a/src/providers/aws/eventSources/dynamoTable.ts b/src/providers/aws/eventSources/dynamoTable.ts index 5f22131..802d240 100644 --- a/src/providers/aws/eventSources/dynamoTable.ts +++ b/src/providers/aws/eventSources/dynamoTable.ts @@ -20,7 +20,6 @@ export class DynamoTable extends EventSource { } else { let value if (typeof (value = get(context.event.event.Records[0], parameter.from)) !== 'undefined') return value - if (typeof (value = get(context, parameter.from)) !== 'undefined') return value return value; } return undefined diff --git a/src/providers/aws/eventSources/lambdaCall.ts b/src/providers/aws/eventSources/lambdaCall.ts index 545200f..3835e44 100644 --- a/src/providers/aws/eventSources/lambdaCall.ts +++ b/src/providers/aws/eventSources/lambdaCall.ts @@ -14,7 +14,6 @@ export class LambdaCall extends EventSource { } else { let value if (typeof (value = get(context.event.event, parameter.from)) !== 'undefined') return value - if (typeof (value = get(context, parameter.from)) !== 'undefined') return value return value; } return undefined diff --git a/src/providers/aws/eventSources/s3.ts b/src/providers/aws/eventSources/s3.ts index 3f89629..acadd11 100644 --- a/src/providers/aws/eventSources/s3.ts +++ b/src/providers/aws/eventSources/s3.ts @@ -19,7 +19,6 @@ export class S3 extends EventSource { } else { let value if (typeof (value = get(context.event.event.Records[0], parameter.from)) !== 'undefined') return value - if (typeof (value = get(context, parameter.from)) !== 'undefined') return value return value; } return undefined diff --git a/src/providers/aws/eventSources/sns.ts b/src/providers/aws/eventSources/sns.ts index 81c6e37..4a48bf2 100644 --- a/src/providers/aws/eventSources/sns.ts +++ b/src/providers/aws/eventSources/sns.ts @@ -19,7 +19,6 @@ export class SNS extends EventSource { } else { let value if (typeof (value = get(context.event.event.Records[0], parameter.from)) !== 'undefined') return value - if (typeof (value = get(context, parameter.from)) !== 'undefined') return value return value; } return undefined diff --git a/src/providers/azure/eventSources/httpTrigger.ts b/src/providers/azure/eventSources/httpTrigger.ts index 3f97eb5..19b6a36 100644 --- a/src/providers/azure/eventSources/httpTrigger.ts +++ b/src/providers/azure/eventSources/httpTrigger.ts @@ -22,7 +22,6 @@ export class HttpTrigger extends EventSource { if (typeof (value = get(query, parameter.from)) !== 'undefined') return value if (typeof (value = get(params, parameter.from)) !== 'undefined') return value if (typeof (value = get(headers, parameter.from)) !== 'undefined') return value - if (typeof (value = get(context, parameter.from)) !== 'undefined') return value return value; } return undefined diff --git a/src/providers/core/provider.ts b/src/providers/core/provider.ts index b985679..158fd58 100644 --- a/src/providers/core/provider.ts +++ b/src/providers/core/provider.ts @@ -44,18 +44,21 @@ export abstract class Provider { const parameters = this.getParameters(target.constructor, method) const preHooks = hooks.filter(h => h instanceof PreHook) - .map(h => this.createCallContext(h, 'handle')) + .map(h => ({ hookKey: h.constructor.name, hook: this.createCallContext(h, 'handle') })) const postHookInstances = hooks.filter(h => h instanceof PostHook) - const postHooks = postHookInstances.map(h => this.createCallContext(h, 'handle')) - const catchHooks = postHookInstances.map(h => this.createCallContext(h, 'catch')) + const postHooks = postHookInstances.map(h => ({ hookKey: h.constructor.name, hook: this.createCallContext(h, 'handle') })) + const catchHooks = postHookInstances.map(h => ({ hookKey: h.constructor.name, hook: this.createCallContext(h, 'catch') })) return async (context) => { const preic: any = {} const preContext = { context: preic, ...context } try { - for (const hook of preHooks) { - await hook(preContext) + for (const { hookKey, hook } of preHooks) { + const result = await hook(preContext) + if (hookKey) { + preic[hookKey] = result + } } const params = [] @@ -75,9 +78,9 @@ export abstract class Provider { for (let hookIndex = 0; hookIndex < postHookInstances.length; hookIndex++) { try { if (ic.error) { - ic.result = await catchHooks[hookIndex](postContext) + ic.result = await catchHooks[hookIndex].hook(postContext) } else { - ic.result = await postHooks[hookIndex](postContext) + ic.result = await postHooks[hookIndex].hook(postContext) } ic.error = undefined } catch (e) { @@ -115,7 +118,7 @@ export abstract class Provider { Provider.addParameterDecoratorImplementation("inject", async (parameter, context, provider) => { const serviceType = parameter.serviceType - const staticInstance = await callExtension(serviceType, 'onInject', { parameter }) + const staticInstance = await callExtension(serviceType, 'onInject', { parameter, context, provider }) if (typeof staticInstance !== 'undefined') { return staticInstance } @@ -123,7 +126,7 @@ Provider.addParameterDecoratorImplementation("inject", async (parameter, context // const instance = new serviceType(...parameter.params.map((p) => typeof p === 'function' ? p() : p)) const instance = await provider.createInstance(serviceType, context) - await callExtension(instance, 'onInject', { parameter }) + await callExtension(instance, 'onInject', { parameter, context, provider }) return instance }) @@ -131,12 +134,12 @@ Provider.addParameterDecoratorImplementation("serviceParams", async (parameter, return context.event }) -Provider.addParameterDecoratorImplementation("context", async (parameter, context, provider) => { - return context.context -}) Provider.addParameterDecoratorImplementation("error", async (parameter, context, provider) => { return parameter.targetKey === 'catch' ? (context.context && context.context.error) : undefined }) +Provider.addParameterDecoratorImplementation("result", async (parameter, context, provider) => { + return context.context && context.context.result +}) Provider.addParameterDecoratorImplementation("functionalServiceName", async (parameter, context, provider) => { return context.serviceInstance && getFunctionName(context.serviceInstance) }) diff --git a/src/providers/inProc.ts b/src/providers/inProc.ts index a714e88..7ceef43 100644 --- a/src/providers/inProc.ts +++ b/src/providers/inProc.ts @@ -55,15 +55,4 @@ InProcProvider.addParameterDecoratorImplementation("param", async (parameter, co return undefined }) -/*InProcProvider.addParameterDecoratorImplementation("request", async (parameter, context, provider) => { - return { - url: context.event.req._parsedUrl || parse(context.event.req.originalUrl), - method: context.event.req.method, - body: context.event.req.body, - query: context.event.req.query, - params: context.event.req.params, - headers: context.event.req.headers - } -})*/ - export const provider = new InProcProvider() diff --git a/src/providers/local.ts b/src/providers/local.ts index 1b87a23..2d9685a 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -115,7 +115,6 @@ LocalProvider.addParameterDecoratorImplementation("param", async (parameter, con if (typeof (value = get(req.query, parameter.from)) !== 'undefined') return value if (typeof (value = get(req.params, parameter.from)) !== 'undefined') return value if (typeof (value = get(req.headers, parameter.from)) !== 'undefined') return value - if (typeof (value = get(context.context, parameter.from)) !== 'undefined') return value return value } return undefined diff --git a/test/hook.tests.ts b/test/hook.tests.ts index 93da530..7576060 100644 --- a/test/hook.tests.ts +++ b/test/hook.tests.ts @@ -3,7 +3,7 @@ import { FunctionalService, PreHook, PostHook, Resource, DynamoTable, SimpleNoti import { getOverridableMetadata, constants, getMetadata, getFunctionName } from '../src/annotations' const { PARAMETER_PARAMKEY, CLASS_ENVIRONMENTKEY, CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_SNSCONFIGURATIONKEY, CLASS_S3CONFIGURATIONKEY, CLASS_MIDDLEWAREKEY } = constants -import { use, context, error, param, inject, injectable, environment, dynamoTable, sns, s3Storage, functionalServiceName, functionName } from '../src/annotations' +import { use, error, result, param, inject, injectable, environment, dynamoTable, sns, s3Storage, functionalServiceName, functionName } from '../src/annotations' describe('hooks', () => { @@ -429,21 +429,21 @@ describe('hooks', () => { it('posthook result chain', async () => { let counter = 0 class TestPostHook1 extends PostHook { - handle( @param result) { + async handle( @result result) { counter++ return result + 1 } } class TestPostHook2 extends PostHook { - handle( @param result) { + async handle( @result result) { counter++ return result + 1 } } class TestPostHook3 extends PostHook { - handle( @param result) { + async handle( @result result) { counter++ return result + 1 @@ -676,23 +676,24 @@ describe('hooks', () => { it('decorator resolution in hooks', async () => { let counter = 0 + + @injectable class AuthHook extends PreHook { - public async handle( @param authorization, @param Authorization, @context context) { + public async handle( @param authorization, @param Authorization) { counter++ const auth = authorization || Authorization if (!auth) throw new Error('auth') - context.identity = 'me' - context.user = null + return 'me' } } class PermissionHook extends PreHook { - public async handle( @param identity) { + public async handle( @inject(AuthHook) identity) { counter++ expect(identity).to.equal('me') } } class ResultHook extends PostHook { - public async handle( @param result) { + public async handle( @result result) { counter++ expect(result).to.deep.equal([{ a: 1 }, { a: 2 }, { a: 3 }]) return result @@ -873,7 +874,7 @@ describe('hooks', () => { } class TestCatchHook extends PostHook { - catch( @error e) { + public async catch( @error e) { counter++ expect(counter).to.equal(2) expect(e.message).to.equal('error') @@ -1011,26 +1012,25 @@ describe('hooks', () => { }) describe('hook decorators', () => { - it('prehook set property => get context', async () => { + it('prehook return => get value', async () => { let counter = 0 + + @injectable class TestHook extends PreHook { - handle( @context c) { + async handle() { counter++ expect(counter).to.equal(1) - - expect(c).to.deep.equal({}) - - c.p1 = 'v1' + return 'v1' } } @use(TestHook) class TestFunctionalService extends FunctionalService { - public async handle( @context c) { + public async handle( @inject(TestHook) p1) { counter++ expect(counter).to.equal(2) - expect(c).to.deep.equal({ p1: 'v1' }) + expect(p1).to.equal('v1') return { ok: 1 } } @@ -1048,26 +1048,24 @@ describe('hooks', () => { expect(counter).to.equal(3) }) - it('prehook set property => get param', async () => { + it('prehook no return => get value', async () => { let counter = 0 + + @injectable class TestHook extends PreHook { - handle( @context c) { + async handle() { counter++ expect(counter).to.equal(1) - - expect(c).to.deep.equal({}) - - c.p1 = 'v1' } } @use(TestHook) class TestFunctionalService extends FunctionalService { - public async handle( @param p1) { + public async handle( @inject(TestHook) p1) { counter++ expect(counter).to.equal(2) - expect(p1).to.equal('v1') + expect(p1).to.null return { ok: 1 } } @@ -1085,39 +1083,97 @@ describe('hooks', () => { expect(counter).to.equal(3) }) - it('multiple prehook set property => get context', async () => { + it('posthook result decorator', async () => { let counter = 0 - class TestHook1 extends PreHook { - handle( @context c) { - expect(c).to.deep.equal({}) - c.p1 = 'v1' + class TestHook extends PostHook { + async handle( @result res) { + counter++ + expect(counter).to.equal(2) + return { ...res, p1: 'p1' } } } - class TestHook2 extends PreHook { - handle( @context c) { - expect(c).to.deep.equal({ p1: 'v1' }) + @use(TestHook) + class TestFunctionalService extends FunctionalService { + public async handle() { + counter++ + expect(counter).to.equal(1) - c.p2 = 'v2' + return { ok: 1 } + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + expect(result).to.deep.equal({ ok: 1, p1: 'p1' }) + counter++ + expect(counter).to.equal(3) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(3) + }) + + it('posthook result decorator return result', async () => { + let counter = 0 + + class TestHook extends PostHook { + async handle( @result res) { + counter++ + expect(counter).to.equal(2) + return res } } - class TestHook3 extends PreHook { - handle( @context c) { - expect(c).to.deep.equal({ p1: 'v1', p2: 'v2' }) - c.p3 = 'v3' + class TestHook2 extends PostHook { + async catch( @error e) { + throw e } } - @use(TestHook1) + @use(TestHook) @use(TestHook2) - @use(TestHook3) class TestFunctionalService extends FunctionalService { - public async handle( @context c) { + public async handle() { counter++ + expect(counter).to.equal(1) - expect(c).to.deep.equal({ p1: 'v1', p2: 'v2', p3: 'v3' }) + return { ok: 1 } + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + expect(result).to.deep.equal({ ok: 1 }) + counter++ + expect(counter).to.equal(3) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(3) + }) + + it('posthook result decorator no handle', async () => { + let counter = 0 + + class TestHook extends PostHook { + } + + class TestHook2 extends PostHook { + async catch( @error e) { + throw e + } + } + + @use(TestHook) + @use(TestHook2) + class TestFunctionalService extends FunctionalService { + public async handle() { + counter++ + expect(counter).to.equal(1) return { ok: 1 } } @@ -1128,37 +1184,125 @@ describe('hooks', () => { send: (result) => { expect(result).to.deep.equal({ ok: 1 }) counter++ + expect(counter).to.equal(2) } }, (e) => { expect(true).to.equal(false, e.message) }) expect(counter).to.equal(2) }) + it('prehook inject in posthook handle', async () => { + let counter = 0 + + @injectable + class TestPreHook extends PreHook { + async handle() { + counter++ + expect(counter).to.equal(1) + return 'p1' + } + } + + class TestPostHook extends PostHook { + async handle( @result res, @inject(TestPreHook) p1) { + counter++ + expect(counter).to.equal(3) + return { ...res, p1 } + } + } + + @use(TestPreHook) + @use(TestPostHook) + class TestFunctionalService extends FunctionalService { + public async handle() { + counter++ + expect(counter).to.equal(2) + + return { ok: 1 } + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + expect(result).to.deep.equal({ ok: 1, p1: 'p1' }) + counter++ + expect(counter).to.equal(4) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(4) + }) + + it('prehook inject in posthook catch', async () => { + let counter = 0 + + @injectable + class TestPreHook extends PreHook { + async handle() { + counter++ + expect(counter).to.equal(1) + return 'p1' + } + } + + class TestPostHook extends PostHook { + async catch( @result res, @inject(TestPreHook) p1) { + counter++ + expect(counter).to.equal(3) + return { ok: 1, p1 } + } + } + + @use(TestPreHook) + @use(TestPostHook) + class TestFunctionalService extends FunctionalService { + public async handle() { + counter++ + expect(counter).to.equal(2) + + throw new Error('error') + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + expect(result).to.deep.equal({ ok: 1, p1: 'p1' }) + counter++ + expect(counter).to.equal(4) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(4) + }) + it('multiple prehook set property => get param', async () => { let counter = 0 - class TestHook1 extends PreHook { - handle( @context c) { - expect(c).to.deep.equal({}) - c.p1 = 'v1' + @injectable + class TestHook1 extends PreHook { + async handle() { + return 'v1' } } + @injectable class TestHook2 extends PreHook { - handle( @context c, @param p1) { - expect(c).to.deep.equal({ p1: 'v1' }) + async handle( @inject(TestHook1) p1) { expect(p1).to.equal('v1') - c.p2 = 'v2' + return 'v2' } } + + @injectable class TestHook3 extends PreHook { - handle( @context c, @param p1, @param p2) { - expect(c).to.deep.equal({ p1: 'v1', p2: 'v2' }) + async handle( @inject(TestHook1) p1, @inject(TestHook2) p2) { expect(p1).to.equal('v1') expect(p2).to.equal('v2') - c.p3 = 'v3' + return 'v3' } } @@ -1166,7 +1310,7 @@ describe('hooks', () => { @use(TestHook2) @use(TestHook3) class TestFunctionalService extends FunctionalService { - public async handle( @param p1, @param p2, @param p3) { + public async handle( @inject(TestHook1) p1, @inject(TestHook2) p2, @inject(TestHook3) p3) { counter++ expect(p1).to.equal('v1') @@ -1221,7 +1365,7 @@ describe('hooks', () => { it("@functionalServiceName", async () => { let counter = 0 class TestHook extends PreHook { - handle( @functionalServiceName serviceName) { + async handle( @functionalServiceName serviceName) { counter++ expect(serviceName).to.equal('TestFunctionalService') } @@ -1250,7 +1394,7 @@ describe('hooks', () => { it("@functionalServiceName with functionName decorator", async () => { let counter = 0 class TestHook extends PreHook { - handle( @functionalServiceName serviceName) { + async handle( @functionalServiceName serviceName) { counter++ expect(serviceName).to.equal('MyTestFunctionalService') } From 9e77e0494564f62d440fff02cb063358cd0f7ed9 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 5 Sep 2017 14:42:28 +0200 Subject: [PATCH 065/196] CHANGE: injection decorator; ADD: injection scopes --- src/annotations/classes/injectable.ts | 11 +- src/annotations/index.ts | 2 +- src/classes/service.ts | 6 +- src/helpers/ioc.ts | 45 ++++ src/index.ts | 3 + src/providers/aws/index.ts | 13 +- src/providers/azure/index.ts | 5 +- src/providers/core/provider.ts | 16 +- src/providers/inProc.ts | 3 +- src/providers/index.ts | 3 +- src/providers/local.ts | 3 +- test/annotation.tests.ts | 42 ++-- test/hook.tests.ts | 36 ++-- test/invoke.tests.ts | 38 ++-- test/invoker.tests.ts | 290 ++++++++++++++++++++++++-- test/serviceOnEvent.tests.ts | 24 +-- 16 files changed, 434 insertions(+), 106 deletions(-) create mode 100644 src/helpers/ioc.ts diff --git a/src/annotations/classes/injectable.ts b/src/annotations/classes/injectable.ts index c7ed4db..eee5d55 100644 --- a/src/annotations/classes/injectable.ts +++ b/src/annotations/classes/injectable.ts @@ -1,6 +1,13 @@ import { CLASS_INJECTABLEKEY } from '../constants' import { defineMetadata } from '../metadata' -export const injectable = (target: Function) => { - defineMetadata(CLASS_INJECTABLEKEY, true, target); +export enum InjectionScope { + Transient = 1, + Singleton = 2 +} + +export const injectable = (scope: InjectionScope = InjectionScope.Transient) => { + return (target) => { + defineMetadata(CLASS_INJECTABLEKEY, scope, target); + } } diff --git a/src/annotations/index.ts b/src/annotations/index.ts index 84f2b0f..bc307e7 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -1,5 +1,5 @@ export { templates, applyTemplates } from './templates' -export { injectable } from './classes/injectable' +export { injectable, InjectionScope } from './classes/injectable' export { apiGateway } from './classes/aws/apiGateway' export { httpTrigger } from './classes/azure/httpTrigger' export { rest, httpGet, httpPost, httpPut, httpPatch, httpDelete, IHttpMethod } from './classes/rest' diff --git a/src/classes/service.ts b/src/classes/service.ts index 4be60ed..5c91dd4 100644 --- a/src/classes/service.ts +++ b/src/classes/service.ts @@ -4,7 +4,9 @@ import { constants, getMetadata, getOverridableMetadata } from '../annotations' const { PARAMETER_PARAMKEY } = constants import { InProcProvider } from '../providers/inProc' -const provider = new InProcProvider() +import { container } from '../helpers/ioc' + +const provider = container.resolve(InProcProvider) export class Service extends Resource { public handle(...params) { @@ -33,7 +35,7 @@ export class Service extends Resource { } public static async onInject({ parameter }): Promise { - const service = new this() + const service = container.resolve(this) return (...params) => service.invoke(...params) } } \ No newline at end of file diff --git a/src/helpers/ioc.ts b/src/helpers/ioc.ts new file mode 100644 index 0000000..a4ecaf7 --- /dev/null +++ b/src/helpers/ioc.ts @@ -0,0 +1,45 @@ + +import { getOwnMetadata } from '../annotations/metadata' +import { CLASS_INJECTABLEKEY } from '../annotations/constants' +import { InjectionScope } from '../annotations/classes/injectable' + +export interface ClassFunction { + new(...args): T; +} + +export class IOC { + private instances: Map, any> + public constructor() { + this.instances = new Map, any>() + } + + public register(type: ClassFunction, instance) { + this.instances.set(type, instance) + } + public resolve(type: ClassFunction, ...params): T { + const scope = getOwnMetadata(CLASS_INJECTABLEKEY, type) || InjectionScope.Transient + switch (scope) { + case InjectionScope.Singleton: + if (!this.instances.has(type)) { + this.instances.set(type, new type(...params)) + } + + return this.instances.get(type) + case InjectionScope.Transient: + default: + return new type(...params) + } + } + public contains(type: ClassFunction): boolean { + const scope = getOwnMetadata(CLASS_INJECTABLEKEY, type) || InjectionScope.Transient + switch (scope) { + case InjectionScope.Singleton: + return this.instances.has(type) + case InjectionScope.Transient: + default: + return false + } + } +} + +export const container = new IOC() diff --git a/src/index.ts b/src/index.ts index e9e9d20..b6a43f4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,9 @@ export { param, serviceParams, request, error, result, functionalServiceName, provider, stage, inject } from './annotations' +/* Enums */ +export { InjectionScope } from './annotations' + /* Helpers */ export { callExtension } from './classes' export { diff --git a/src/providers/aws/index.ts b/src/providers/aws/index.ts index 62543c2..79ccfc3 100644 --- a/src/providers/aws/index.ts +++ b/src/providers/aws/index.ts @@ -7,6 +7,7 @@ import { SNS } from './eventSources/sns' import { S3 } from './eventSources/s3' import { DynamoTable } from './eventSources/dynamoTable' import { parse } from 'url' +import { container } from '../../helpers/ioc' let lambda = null; const initAWSSDK = () => { @@ -18,11 +19,11 @@ const initAWSSDK = () => { const eventSourceHandlers = [ - new ApiGateway(), - new SNS(), - new S3(), - new DynamoTable(), - new LambdaCall() + container.resolve(ApiGateway), + container.resolve(SNS), + container.resolve(S3), + container.resolve(DynamoTable), + container.resolve(LambdaCall) ] @@ -94,4 +95,4 @@ AWSProvider.addParameterDecoratorImplementation("request", async (parameter, con } }) -export const provider = new AWSProvider() +export const provider = container.resolve(AWSProvider) diff --git a/src/providers/azure/index.ts b/src/providers/azure/index.ts index b8a0664..ac43354 100644 --- a/src/providers/azure/index.ts +++ b/src/providers/azure/index.ts @@ -2,11 +2,12 @@ import { Provider } from '../core/provider' import { getFunctionName, constants, getMetadata } from '../../annotations' const { CLASS_HTTPTRIGGER } = constants import { HttpTrigger } from './eventSources/httpTrigger' +import { container } from '../../helpers/ioc' import * as request from 'request' import { parse } from 'url' const eventSourceHandlers = [ - new HttpTrigger() + container.resolve(HttpTrigger) ] export const FUNCTIONLY_FUNCTION_KEY = 'FUNCTIONLY_FUNCTION_KEY' @@ -115,4 +116,4 @@ AzureProvider.addParameterDecoratorImplementation("request", async (parameter, c } }) -export const provider = new AzureProvider() +export const provider = container.resolve(AzureProvider) diff --git a/src/providers/core/provider.ts b/src/providers/core/provider.ts index 158fd58..5eeda49 100644 --- a/src/providers/core/provider.ts +++ b/src/providers/core/provider.ts @@ -2,6 +2,7 @@ import { constants, getMetadata, getOverridableMetadata, getFunctionName } from const { PARAMETER_PARAMKEY } = constants import { getMiddlewares } from '../../annotations/classes/use' import { callExtension, PreHook, PostHook } from '../../classes' +import { container } from '../../helpers/ioc' export abstract class Provider { public getInvoker(serviceInstance, params): Function { @@ -18,6 +19,10 @@ export abstract class Provider { } public async createInstance(type, context) { + if (container.contains(type)) { + return container.resolve(type) + } + const parameters = this.getParameters(type, undefined) const params = [] @@ -25,7 +30,7 @@ export abstract class Provider { params[parameter.parameterIndex] = await this.parameterResolver(parameter, context) } - const instance = new type(...params) + const instance = container.resolve(type, ...params) if (typeof instance.init === 'function') { await instance.init() @@ -40,7 +45,7 @@ export abstract class Provider { } protected createCallContext(target, method) { - const hooks = getMiddlewares(target).map(m => new m()) + const hooks = getMiddlewares(target).map(m => container.resolve(m)) const parameters = this.getParameters(target.constructor, method) const preHooks = hooks.filter(h => h instanceof PreHook) @@ -118,12 +123,11 @@ export abstract class Provider { Provider.addParameterDecoratorImplementation("inject", async (parameter, context, provider) => { const serviceType = parameter.serviceType - const staticInstance = await callExtension(serviceType, 'onInject', { parameter, context, provider }) - if (typeof staticInstance !== 'undefined') { - return staticInstance + const staticInjectValue = await callExtension(serviceType, 'onInject', { parameter, context, provider }) + if (typeof staticInjectValue !== 'undefined') { + return staticInjectValue } - // const instance = new serviceType(...parameter.params.map((p) => typeof p === 'function' ? p() : p)) const instance = await provider.createInstance(serviceType, context) await callExtension(instance, 'onInject', { parameter, context, provider }) diff --git a/src/providers/inProc.ts b/src/providers/inProc.ts index 7ceef43..03c2bdc 100644 --- a/src/providers/inProc.ts +++ b/src/providers/inProc.ts @@ -3,6 +3,7 @@ import { Provider } from './core/provider' import { constants, getMetadata, getFunctionName, rest } from '../annotations' const { CLASS_LOGKEY } = constants import { get } from '../helpers/property' +import { container } from '../helpers/ioc' import { parse } from 'url' export class InProcProvider extends Provider { @@ -55,4 +56,4 @@ InProcProvider.addParameterDecoratorImplementation("param", async (parameter, co return undefined }) -export const provider = new InProcProvider() +export const provider = container.resolve(InProcProvider) diff --git a/src/providers/index.ts b/src/providers/index.ts index 340916c..94bb3c4 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1,6 +1,7 @@ import { constants, getMetadata, getOverridableMetadata } from '../annotations' const { PARAMETER_PARAMKEY } = constants import { callExtension } from '../classes' +import { container } from '../helpers/ioc' export { Provider } from './core/provider' export { AWSProvider } from './aws' @@ -38,7 +39,7 @@ export const getInvoker = (serviceType, params) => { const currentEnvironment = environments[environment] - const serviceInstance = new serviceType(...params) + const serviceInstance = container.resolve(serviceType, ...params) const invoker = currentEnvironment.getInvoker(serviceInstance, params) const invokeHandler = async (...params) => { diff --git a/src/providers/local.ts b/src/providers/local.ts index 2d9685a..84b5871 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -3,6 +3,7 @@ import { Provider } from './core/provider' import { constants, getMetadata, getFunctionName, rest } from '../annotations' const { CLASS_LOGKEY } = constants import { get } from '../helpers/property' +import { container } from '../helpers/ioc' import { parse } from 'url' export class LocalProvider extends Provider { @@ -131,4 +132,4 @@ LocalProvider.addParameterDecoratorImplementation("request", async (parameter, c } }) -export const provider = new LocalProvider() +export const provider = container.resolve(LocalProvider) diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index 34c7fd1..3ae05a6 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -17,7 +17,7 @@ import { rest, httpGet, httpPost, httpPut, httpPatch, httpDelete } from '../src/ import { dynamoTable, __dynamoDBDefaults } from '../src/annotations/classes/dynamoTable' import { environment } from '../src/annotations/classes/environment' import { functionName, getFunctionName } from '../src/annotations/classes/functionName' -import { injectable } from '../src/annotations/classes/injectable' +import { injectable, InjectionScope } from '../src/annotations/classes/injectable' import { log } from '../src/annotations/classes/log' import { s3Storage } from '../src/annotations/classes/s3Storage' import { sns } from '../src/annotations/classes/sns' @@ -673,12 +673,28 @@ describe('annotations', () => { }) describe("injectable", () => { it("injectable", () => { - @injectable + @injectable() class InjectableTestClass { } const value = getMetadata(CLASS_INJECTABLEKEY, InjectableTestClass) - expect(value).to.equal(true) + expect(value).to.equal(1) + }) + it("injectable", () => { + @injectable(InjectionScope.Transient) + class InjectableTestClass { } + + const value = getMetadata(CLASS_INJECTABLEKEY, InjectableTestClass) + + expect(value).to.equal(1) + }) + it("injectable", () => { + @injectable(InjectionScope.Singleton) + class InjectableTestClass { } + + const value = getMetadata(CLASS_INJECTABLEKEY, InjectableTestClass) + + expect(value).to.equal(2) }) it("non injectable", () => { class InjectableTestClass { } @@ -1029,7 +1045,7 @@ describe('annotations', () => { describe("parameters", () => { describe("inject", () => { it("inject", () => { - @injectable + @injectable() class ATestClass { } class BTestClass { method( @inject(ATestClass) a) { } @@ -1047,7 +1063,7 @@ describe('annotations', () => { }) it("functional service inject", () => { - @injectable + @injectable() class ATestClass extends FunctionalService { } class BTestClass { method( @inject(ATestClass) a) { } @@ -1069,7 +1085,7 @@ describe('annotations', () => { }) it("resource inject", () => { - @injectable + @injectable() @environment('%ClassName%_defined_environment', 'value') class ATestClass extends Resource { } class BTestClass { @@ -1092,7 +1108,7 @@ describe('annotations', () => { }) it("injected DynamoTable", () => { - @injectable + @injectable() @dynamoTable({ tableName: 'ATable' }) class ATestClass extends DynamoTable { } class BTestClass { @@ -1109,7 +1125,7 @@ describe('annotations', () => { }) it("injected SimpleNotificationService", () => { - @injectable + @injectable() @sns({ topicName: 'ATopic' }) class ATestClass extends SimpleNotificationService { } class BTestClass { @@ -1126,7 +1142,7 @@ describe('annotations', () => { }) it("injected S3Storage", () => { - @injectable + @injectable() @s3Storage({ bucketName: 'ABucket' }) class ATestClass extends S3Storage { } class BTestClass { @@ -1143,7 +1159,7 @@ describe('annotations', () => { }) it("overrided class method", () => { - @injectable + @injectable() class ATestClass { } class BaseBTestClass { @@ -1165,7 +1181,7 @@ describe('annotations', () => { }) it("overrided class method no inject", () => { - @injectable + @injectable() class ATestClass { } class BaseBTestClass { @@ -1180,7 +1196,7 @@ describe('annotations', () => { }) it("not overrided class method", () => { - @injectable + @injectable() class ATestClass { } class BaseBTestClass { @@ -1202,7 +1218,7 @@ describe('annotations', () => { }) it("service inject", () => { - @injectable + @injectable() @environment('%ClassName%_defined_environment', 'value') class ATestClass extends Service { } class BTestClass { diff --git a/test/hook.tests.ts b/test/hook.tests.ts index 7576060..18cc599 100644 --- a/test/hook.tests.ts +++ b/test/hook.tests.ts @@ -127,7 +127,7 @@ describe('hooks', () => { describe('inject on hooks', () => { it("functional service inject", () => { - @injectable + @injectable() class ATestClass extends FunctionalService { } class TestHook extends PreHook { @@ -145,7 +145,7 @@ describe('hooks', () => { }) it("service inject", () => { - @injectable + @injectable() @environment('%ClassName%_defined_environment', 'value') class ATestClass extends Resource { } @@ -164,7 +164,7 @@ describe('hooks', () => { }); it("injected DynamoTable", () => { - @injectable + @injectable() @dynamoTable({ tableName: 'ATable' }) class ATestClass extends DynamoTable { } @@ -187,7 +187,7 @@ describe('hooks', () => { }) it("injected SimpleNotificationService", () => { - @injectable + @injectable() @sns({ topicName: 'ATopic' }) class ATestClass extends SimpleNotificationService { } @@ -210,7 +210,7 @@ describe('hooks', () => { }) it("injected S3Storage", () => { - @injectable + @injectable() @s3Storage({ bucketName: 'ABucket' }) class ATestClass extends S3Storage { } @@ -236,7 +236,7 @@ describe('hooks', () => { describe('inject on level2 hooks', () => { it("functional service inject", () => { - @injectable + @injectable() class ATestClass extends FunctionalService { } class TestHookLevel2 extends PreHook { @@ -259,7 +259,7 @@ describe('hooks', () => { }) it("service inject", () => { - @injectable + @injectable() @environment('%ClassName%_defined_environment', 'value') class ATestClass extends Resource { } @@ -283,7 +283,7 @@ describe('hooks', () => { }); it("injected DynamoTable", () => { - @injectable + @injectable() @dynamoTable({ tableName: 'ATable' }) class ATestClass extends DynamoTable { } @@ -311,7 +311,7 @@ describe('hooks', () => { }) it("injected SimpleNotificationService", () => { - @injectable + @injectable() @sns({ topicName: 'ATopic' }) class ATestClass extends SimpleNotificationService { } @@ -339,7 +339,7 @@ describe('hooks', () => { }) it("injected S3Storage", () => { - @injectable + @injectable() @s3Storage({ bucketName: 'ABucket' }) class ATestClass extends S3Storage { } @@ -677,7 +677,7 @@ describe('hooks', () => { it('decorator resolution in hooks', async () => { let counter = 0 - @injectable + @injectable() class AuthHook extends PreHook { public async handle( @param authorization, @param Authorization) { counter++ @@ -1015,7 +1015,7 @@ describe('hooks', () => { it('prehook return => get value', async () => { let counter = 0 - @injectable + @injectable() class TestHook extends PreHook { async handle() { counter++ @@ -1051,7 +1051,7 @@ describe('hooks', () => { it('prehook no return => get value', async () => { let counter = 0 - @injectable + @injectable() class TestHook extends PreHook { async handle() { counter++ @@ -1194,7 +1194,7 @@ describe('hooks', () => { it('prehook inject in posthook handle', async () => { let counter = 0 - @injectable + @injectable() class TestPreHook extends PreHook { async handle() { counter++ @@ -1237,7 +1237,7 @@ describe('hooks', () => { it('prehook inject in posthook catch', async () => { let counter = 0 - @injectable + @injectable() class TestPreHook extends PreHook { async handle() { counter++ @@ -1280,14 +1280,14 @@ describe('hooks', () => { it('multiple prehook set property => get param', async () => { let counter = 0 - @injectable + @injectable() class TestHook1 extends PreHook { async handle() { return 'v1' } } - @injectable + @injectable() class TestHook2 extends PreHook { async handle( @inject(TestHook1) p1) { expect(p1).to.equal('v1') @@ -1296,7 +1296,7 @@ describe('hooks', () => { } } - @injectable + @injectable() class TestHook3 extends PreHook { async handle( @inject(TestHook1) p1, @inject(TestHook2) p2) { expect(p1).to.equal('v1') diff --git a/test/invoke.tests.ts b/test/invoke.tests.ts index 59d645a..f724625 100644 --- a/test/invoke.tests.ts +++ b/test/invoke.tests.ts @@ -33,7 +33,7 @@ describe('invoke', () => { } addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) - @injectable + @injectable() class A extends FunctionalService { public async handle() { } } @@ -67,7 +67,7 @@ describe('invoke', () => { } addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) - @injectable + @injectable() class A extends FunctionalService { public async handle( @param p1) { } } @@ -101,7 +101,7 @@ describe('invoke', () => { } addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) - @injectable + @injectable() class A extends FunctionalService { public async handle( @param p1) { } } @@ -135,7 +135,7 @@ describe('invoke', () => { } addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) - @injectable + @injectable() class A extends FunctionalService { public async handle() { } } @@ -167,7 +167,7 @@ describe('invoke', () => { } addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) - @injectable + @injectable() class A extends FunctionalService { public async handle() { } } @@ -203,7 +203,7 @@ describe('invoke', () => { } addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) - @injectable + @injectable() class A extends FunctionalService { public async handle() { } } @@ -397,7 +397,7 @@ describe('invoke', () => { addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) @httpTrigger({ route: '/v1/a1' }) - @injectable + @injectable() class A extends FunctionalService { public async handle( @param p1) { } } @@ -435,7 +435,7 @@ describe('invoke', () => { addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) @httpTrigger({ route: '/v1/a1', methods: ['post'] }) - @injectable + @injectable() class A extends FunctionalService { public async handle( @param p1) { } } @@ -470,7 +470,7 @@ describe('invoke', () => { addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) @httpTrigger({ route: '/v1/a1', methods: ['get', 'post'] }) - @injectable + @injectable() class A extends FunctionalService { public async handle( @param p1) { } } @@ -508,7 +508,7 @@ describe('invoke', () => { addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) @httpTrigger({ route: '/v1/a1', methods: ['post', 'get'] }) - @injectable + @injectable() class A extends FunctionalService { public async handle( @param p1) { } } @@ -543,7 +543,7 @@ describe('invoke', () => { addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) @httpTrigger({ route: '/v1/a1', authLevel: 'anonymous' }) - @injectable + @injectable() class A extends FunctionalService { public async handle( @param p1) { } } @@ -581,7 +581,7 @@ describe('invoke', () => { addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) @httpTrigger({ route: '/v1/a1', methods: ['post'], authLevel: 'anonymous' }) - @injectable + @injectable() class A extends FunctionalService { public async handle( @param p1) { } } @@ -617,7 +617,7 @@ describe('invoke', () => { addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) @httpTrigger({ route: '/v1/a1' }) - @injectable + @injectable() class A extends FunctionalService { public async handle( @param p1) { } } @@ -654,7 +654,7 @@ describe('invoke', () => { addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) @httpTrigger({ route: '/v1/a1', authLevel: 'anonymous' }) - @injectable + @injectable() class A extends FunctionalService { public async handle( @param p1) { } } @@ -705,7 +705,7 @@ describe('invoke', () => { addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) @rest({ path: '/v1/a1' }) - @injectable + @injectable() class A extends FunctionalService { public async handle( @param p1) { } } @@ -743,7 +743,7 @@ describe('invoke', () => { addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) @rest({ path: '/v1/a1', methods: ['post'] }) - @injectable + @injectable() class A extends FunctionalService { public async handle( @param p1) { } } @@ -778,7 +778,7 @@ describe('invoke', () => { addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) @rest({ path: '/v1/a1', methods: ['get', 'post'] }) - @injectable + @injectable() class A extends FunctionalService { public async handle( @param p1) { } } @@ -816,7 +816,7 @@ describe('invoke', () => { addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) @rest({ path: '/v1/a1', methods: ['post', 'get'] }) - @injectable + @injectable() class A extends FunctionalService { public async handle( @param p1) { } } @@ -859,7 +859,7 @@ describe('invoke', () => { } addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) - @injectable + @injectable() class A extends FunctionalService { public async handle( @param p1) { } } diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index 7640e66..5e56eab 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -2,7 +2,7 @@ import { expect } from 'chai' import { getInvoker } from '../src/providers' import { FunctionalService, Resource, Service, Api } from '../src/classes' -import { param, inject, injectable, serviceParams, request, functionalServiceName, functionName, provider, stage } from '../src/annotations' +import { param, inject, injectable, serviceParams, request, functionalServiceName, functionName, provider, stage, InjectionScope } from '../src/annotations' import { parse } from 'url' @@ -31,7 +31,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' - @injectable + @injectable() class CustomService extends Service { public async handle( @param p1, @param p2) { counter++ @@ -68,7 +68,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' - @injectable + @injectable() class CustomService extends Service { public async handle( @param p1, @param p2) { counter++ @@ -77,7 +77,7 @@ describe('invoker', () => { } } - @injectable + @injectable() class CustomApi extends Api { private _myService public constructor( @inject(CustomService) myService) { @@ -121,7 +121,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' - @injectable + @injectable() class CustomService extends Service { public async handle( @param p1, @param p2) { counter++ @@ -130,7 +130,7 @@ describe('invoker', () => { } } - @injectable + @injectable() class CustomApi extends Api { private _myService public constructor( @inject(CustomService) myService) { @@ -183,6 +183,252 @@ describe('invoker', () => { expect(counter).to.equal(5) }) + + describe("injection modes", () => { + it("multiple inject transient api with transient service", async () => { + let counter = 0 + let instanceCreation = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + @injectable(InjectionScope.Transient) + class CustomService extends Service { + public constructor() { + super() + instanceCreation++ + } + public async handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('p1', 'CustomService') + expect(p2).to.equal('p2', 'CustomService') + } + } + + @injectable(InjectionScope.Transient) + class CustomApi extends Api { + private _myService + public constructor( @inject(CustomService) myService, @inject(CustomService) myService2) { + super() + instanceCreation++ + this._myService = myService + } + public async myMethod(p1, p2) { + counter++ + expect(p1).to.equal('p1', 'CustomApi') + expect(p2).to.equal('p2', 'CustomApi') + + await this._myService({ p1, p2 }) + } + } + + class MockService extends FunctionalService { + public async handle( @param p1, @inject(CustomApi) api, @inject(CustomApi) api2) { + counter++ + expect(p1).to.undefined + expect(api).to.instanceof(CustomApi) + + await api.myMethod('p1', 'p2') + } + } + + const invoker = MockService.createInvoker() + await invoker({}, { + send: () => { + expect(counter).to.equal(3) + expect(instanceCreation).to.equal(6) + } + }, (e) => { + expect(null).to.equal(e) + throw e + }) + + expect(counter).to.equal(3) + expect(instanceCreation).to.equal(6) + }) + + it("multiple inject transient api with singleton service", async () => { + let counter = 0 + let instanceCreation = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + @injectable(InjectionScope.Singleton) + class CustomService extends Service { + public constructor() { + super() + instanceCreation++ + } + public async handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('p1', 'CustomService') + expect(p2).to.equal('p2', 'CustomService') + } + } + + @injectable(InjectionScope.Transient) + class CustomApi extends Api { + private _myService + public constructor( @inject(CustomService) myService, @inject(CustomService) myService2) { + super() + instanceCreation++ + this._myService = myService + } + public async myMethod(p1, p2) { + counter++ + expect(p1).to.equal('p1', 'CustomApi') + expect(p2).to.equal('p2', 'CustomApi') + + await this._myService({ p1, p2 }) + } + } + + class MockService extends FunctionalService { + public async handle( @param p1, @inject(CustomApi) api, @inject(CustomApi) api2) { + counter++ + expect(p1).to.undefined + expect(api).to.instanceof(CustomApi) + + await api.myMethod('p1', 'p2') + } + } + + const invoker = MockService.createInvoker() + await invoker({}, { + send: () => { + expect(counter).to.equal(3) + expect(instanceCreation).to.equal(3) + } + }, (e) => { + expect(null).to.equal(e) + throw e + }) + + expect(counter).to.equal(3) + expect(instanceCreation).to.equal(3) + }) + + it("multiple inject singleton api with transient service", async () => { + let counter = 0 + let instanceCreation = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + @injectable(InjectionScope.Transient) + class CustomService extends Service { + public constructor() { + super() + instanceCreation++ + } + public async handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('p1', 'CustomService') + expect(p2).to.equal('p2', 'CustomService') + } + } + + @injectable(InjectionScope.Singleton) + class CustomApi extends Api { + private _myService + public constructor( @inject(CustomService) myService, @inject(CustomService) myService2) { + super() + instanceCreation++ + this._myService = myService + } + public async myMethod(p1, p2) { + counter++ + expect(p1).to.equal('p1', 'CustomApi') + expect(p2).to.equal('p2', 'CustomApi') + + await this._myService({ p1, p2 }) + } + } + + class MockService extends FunctionalService { + public async handle( @param p1, @inject(CustomApi) api, @inject(CustomApi) api2) { + counter++ + expect(p1).to.undefined + expect(api).to.instanceof(CustomApi) + + await api.myMethod('p1', 'p2') + } + } + + const invoker = MockService.createInvoker() + await invoker({}, { + send: () => { + expect(counter).to.equal(3) + expect(instanceCreation).to.equal(3) + } + }, (e) => { + expect(null).to.equal(e) + throw e + }) + + expect(counter).to.equal(3) + expect(instanceCreation).to.equal(3) + }) + + it("multiple inject singleton api with singleton service", async () => { + let counter = 0 + let instanceCreation = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + + @injectable(InjectionScope.Singleton) + class CustomService extends Service { + public constructor() { + super() + instanceCreation++ + } + public async handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('p1', 'CustomService') + expect(p2).to.equal('p2', 'CustomService') + } + } + + @injectable(InjectionScope.Singleton) + class CustomApi extends Api { + private _myService + public constructor( @inject(CustomService) myService, @inject(CustomService) myService2) { + super() + instanceCreation++ + this._myService = myService + } + public async myMethod(p1, p2) { + counter++ + expect(p1).to.equal('p1', 'CustomApi') + expect(p2).to.equal('p2', 'CustomApi') + + await this._myService({ p1, p2 }) + } + } + + class MockService extends FunctionalService { + public async handle( @param p1, @inject(CustomApi) api, @inject(CustomApi) api2) { + counter++ + expect(p1).to.undefined + expect(api).to.instanceof(CustomApi) + + await api.myMethod('p1', 'p2') + } + } + + const invoker = MockService.createInvoker() + await invoker({}, { + send: () => { + expect(counter).to.equal(3) + expect(instanceCreation).to.equal(2) + } + }, (e) => { + expect(null).to.equal(e) + throw e + }) + + expect(counter).to.equal(3) + expect(instanceCreation).to.equal(2) + }) + }) }) describe("local", () => { @@ -483,7 +729,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' - @injectable + @injectable() class MockInjectable extends Resource { } class MockService extends FunctionalService { @@ -509,7 +755,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' - @injectable + @injectable() class CustomService extends Service { handle( @param p1, @param p2) { counter++ @@ -543,7 +789,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' - @injectable + @injectable() class MockInjectable extends Resource { } const req = {} @@ -574,7 +820,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' - @injectable + @injectable() class MockInjectable extends Resource { } const req = { @@ -624,7 +870,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' - @injectable + @injectable() class MockInjectable extends Resource { } const req = { @@ -675,7 +921,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' - @injectable + @injectable() class MockInjectable extends Resource { } const req = { @@ -1366,7 +1612,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' - @injectable + @injectable() class MockInjectable extends Resource { } const awsEvent = { @@ -1417,7 +1663,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' - @injectable + @injectable() class MockInjectable extends Resource { } class MockService extends FunctionalService { @@ -1442,7 +1688,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' - @injectable + @injectable() class CustomService extends Service { handle( @param p1, @param p2) { counter++ @@ -1474,7 +1720,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' - @injectable + @injectable() class MockInjectable extends Resource { } const awsEvent = {} @@ -1503,7 +1749,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' - @injectable + @injectable() class MockInjectable extends Resource { } const awsEvent = { @@ -1552,7 +1798,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' - @injectable + @injectable() class MockInjectable extends Resource { } const awsEvent = { @@ -2091,7 +2337,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' - @injectable + @injectable() class CustomService extends Service { handle( @param p1, @param p2) { counter++ @@ -2126,7 +2372,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' - @injectable + @injectable() class MockInjectable extends Resource { } const context = {} @@ -2154,7 +2400,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' - @injectable + @injectable() class MockInjectable extends Resource { } const context = {} @@ -2201,7 +2447,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' - @injectable + @injectable() class MockInjectable extends Resource { } const context = {} diff --git a/test/serviceOnEvent.tests.ts b/test/serviceOnEvent.tests.ts index 861c104..c8a354f 100644 --- a/test/serviceOnEvent.tests.ts +++ b/test/serviceOnEvent.tests.ts @@ -20,7 +20,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' - @injectable + @injectable() class MockInjectable extends Resource { public static async onInject({ parameter }) { counter++ @@ -55,7 +55,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' - @injectable + @injectable() class MockInjectable extends Resource { public static async onInject({ parameter }) { counter++ @@ -93,7 +93,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' - @injectable + @injectable() class MockInjectable extends Resource { public async onInject({ parameter }) { counter++ @@ -128,7 +128,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' - @injectable + @injectable() class MockInjectable extends Resource { public static async onInject({ parameter }) { counter++ @@ -172,7 +172,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' - @injectable + @injectable() class MockInjectable extends Resource { public static async onInject({ parameter }) { counter++ @@ -290,7 +290,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' - @injectable + @injectable() class MockInjectable extends Resource { public static async onInject({ parameter }) { counter++ @@ -323,7 +323,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' - @injectable + @injectable() class MockInjectable extends Resource { public static async onInject({ parameter }) { counter++ @@ -359,7 +359,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' - @injectable + @injectable() class MockInjectable extends Resource { public async onInject({ parameter }) { counter++ @@ -392,7 +392,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' - @injectable + @injectable() class MockInjectable extends Resource { public static async onInject({ parameter }) { counter++ @@ -434,7 +434,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' - @injectable + @injectable() class MockInjectable extends Resource { public static async onInject({ parameter }) { counter++ @@ -560,7 +560,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'mock' - @injectable + @injectable() class MockInjectable extends FunctionalService { onInvoke({ params, invokeConfig }) { counter++ @@ -593,7 +593,7 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'mock' - @injectable + @injectable() class MockInjectable extends FunctionalService { handle( @param p1) { } onInvoke_mock({ invokeParams, params, invokeConfig, parameterMapping, currentEnvironment, environmentMode }) { From 33b64d74c6a12a7c034e9c6eee2745b20130dfcf Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Wed, 6 Sep 2017 14:12:26 +0200 Subject: [PATCH 066/196] update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b977424..239fc71 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.5", + "version": "0.0.6", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 52973dab8e232db4d4ad1e2a54920c1d4c271eae Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Thu, 7 Sep 2017 12:56:00 +0200 Subject: [PATCH 067/196] fix: aws deploy typo --- src/cli/providers/cloudFormation/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/providers/cloudFormation/utils.ts b/src/cli/providers/cloudFormation/utils.ts index 0626a09..70abd53 100644 --- a/src/cli/providers/cloudFormation/utils.ts +++ b/src/cli/providers/cloudFormation/utils.ts @@ -38,7 +38,7 @@ export const setResource = (context: any, name: string, resource: any, stackName context.usedAwsResources = context.usedAwsResources || []; if (context.usedAwsResources.indexOf(resource.Type) < 0) { - context.usedAwsResources.push(resource.type) + context.usedAwsResources.push(resource.Type) } resources[resourceName] = resource From 3e882f73bb4a326ad6c5b8615e11a8f4fa80ad8f Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 11 Sep 2017 10:47:40 +0200 Subject: [PATCH 068/196] FIX: aws api gateway body string; FIX: api gateway unit tests --- src/providers/aws/eventSources/apiGateway.ts | 10 +- test/invoker.tests.ts | 256 ++++++++++++++----- 2 files changed, 197 insertions(+), 69 deletions(-) diff --git a/src/providers/aws/eventSources/apiGateway.ts b/src/providers/aws/eventSources/apiGateway.ts index 6b69987..1af1d35 100644 --- a/src/providers/aws/eventSources/apiGateway.ts +++ b/src/providers/aws/eventSources/apiGateway.ts @@ -23,7 +23,15 @@ export class ApiGateway extends EventSource { } } else { let value - if (typeof (value = get(body, parameter.from)) !== 'undefined') return value + if (typeof body === 'string') { + try { + const parsedBody = JSON.parse(body) + if (typeof (value = get(parsedBody, parameter.from)) !== 'undefined') return value + // fallback to other options + } catch(e) { + // fallback to other options + } + } else if (typeof (value = get(body, parameter.from)) !== 'undefined') return value if (typeof (value = get(query, parameter.from)) !== 'undefined') return value if (typeof (value = get(params, parameter.from)) !== 'undefined') return value if (typeof (value = get(headers, parameter.from)) !== 'undefined') return value diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index 5e56eab..c1354bb 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -1147,13 +1147,40 @@ describe('invoker', () => { const invoker = MockService.createInvoker() invoker({}, {}, (e, result) => { - expect(counter).to.equal(1) + counter++ + expect(counter).to.equal(2) expect(e.message).to.equal('error in handle') done() }) }) describe("eventSources", () => { + + it("lambda param error", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + class MockService extends FunctionalService { + handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('v1') + expect(p2).to.equal('v2') + } + } + + const invoker = MockService.createInvoker() + const awsEvent = { + p1: undefined, + p2: 'v2' + } + invoker(awsEvent, {}, (e) => { + counter++ + expect(counter).to.equal(2) + expect(e).is.instanceof(Error) + done() + }) + }) + it("lambda param", (done) => { let counter = 0 @@ -1176,13 +1203,41 @@ describe('invoker', () => { done(e) }) }) + + it("api gateway error", async () => { + let counter = 0 - it("api gateway body param", (done) => { + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + class MockService extends FunctionalService { + async handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('v1') + expect(p2).to.equal('v2') + } + } + + const invoker = MockService.createInvoker() + const awsEvent = { + requestContext: { apiId: 'apiId' }, + body: { + p2: 'v2' + } + } + const r = await invoker(awsEvent, {}, (e) => { + counter++ + expect(counter).to.equal(2) + }) + + expect(counter).to.equal(2) + expect(r.statusCode).to.equal(500) + }) + + it("api gateway body param", async () => { let counter = 0 process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + async handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -1197,18 +1252,50 @@ describe('invoker', () => { p2: 'v2' } } - invoker(awsEvent, {}, (e) => { - expect(counter).to.equal(1) - done(e) + const r = await invoker(awsEvent, {}, (e) => { + counter++ + expect(counter).to.equal(2) + }) + + expect(counter).to.equal(2) + expect(r.statusCode).to.equal(200, r.body) + }) + + it("api gateway string body param", async () => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + class MockService extends FunctionalService { + async handle( @param p1, @param p2) { + counter++ + expect(p1).to.equal('v1') + expect(p2).to.equal('v2') + } + } + + const invoker = MockService.createInvoker() + const awsEvent = { + requestContext: { apiId: 'apiId' }, + body: JSON.stringify({ + p1: 'v1', + p2: 'v2' + }) + } + const r = await invoker(awsEvent, {}, (e) => { + counter++ + expect(counter).to.equal(2) }) + + expect(counter).to.equal(2) + expect(r.statusCode).to.equal(200, r.body) }) - it("api gateway query param", (done) => { + it("api gateway query param", async () => { let counter = 0 process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + async handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -1223,18 +1310,21 @@ describe('invoker', () => { p2: 'v2' } } - invoker(awsEvent, {}, (e) => { - expect(counter).to.equal(1) - done(e) + const r = await invoker(awsEvent, {}, (e) => { + counter++ + expect(counter).to.equal(2) }) + + expect(counter).to.equal(2) + expect(r.statusCode).to.equal(200, r.body) }) - it("api gateway pathParameters param", (done) => { + it("api gateway pathParameters param", async () => { let counter = 0 process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + async handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -1249,18 +1339,21 @@ describe('invoker', () => { p2: 'v2' } } - invoker(awsEvent, {}, (e) => { - expect(counter).to.equal(1) - done(e) + const r = await invoker(awsEvent, {}, (e) => { + counter++ + expect(counter).to.equal(2) }) + + expect(counter).to.equal(2) + expect(r.statusCode).to.equal(200, r.body) }) - it("api gateway header param", (done) => { + it("api gateway header param", async () => { let counter = 0 process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + async handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -1275,18 +1368,21 @@ describe('invoker', () => { p2: 'v2' } } - invoker(awsEvent, {}, (e) => { - expect(counter).to.equal(1) - done(e) + const r = await invoker(awsEvent, {}, (e) => { + counter++ + expect(counter).to.equal(2) }) + + expect(counter).to.equal(2) + expect(r.statusCode).to.equal(200, r.body) }) - it("api gateway params resolve order", (done) => { + it("api gateway params resolve order", async () => { let counter = 0 process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - handle( @param p1, @param p2, @param p3, @param p4) { + async handle( @param p1, @param p2, @param p3, @param p4) { counter++ expect(p1).to.equal('body') expect(p2).to.equal('queryStringParameters') @@ -1317,18 +1413,21 @@ describe('invoker', () => { p4: 'headers' } } - invoker(awsEvent, {}, (e) => { - expect(counter).to.equal(1) - done(e) + const r = await invoker(awsEvent, {}, (e) => { + counter++ + expect(counter).to.equal(2) }) + + expect(counter).to.equal(2) + expect(r.statusCode).to.equal(200, r.body) }) - it("api gateway params resolve hint", (done) => { + it("api gateway params resolve hint", async () => { let counter = 0 process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - handle( @param({ source: 'event.event.queryStringParameters' }) p1, @param({ source: 'event.event.pathParameters' }) p2, @param({ source: 'event.event.headers' }) p3, @param p4) { + async handle( @param({ source: 'event.event.queryStringParameters' }) p1, @param({ source: 'event.event.pathParameters' }) p2, @param({ source: 'event.event.headers' }) p3, @param p4) { counter++ expect(p1).to.equal('queryStringParameters') expect(p2).to.equal('pathParameters') @@ -1359,18 +1458,21 @@ describe('invoker', () => { p4: 'headers' } } - invoker(awsEvent, {}, (e) => { - expect(counter).to.equal(1) - done(e) + const r = await invoker(awsEvent, {}, (e) => { + counter++ + expect(counter).to.equal(2) }) + + expect(counter).to.equal(2) + expect(r.statusCode).to.equal(200, r.body) }) - it("api gateway return value", (done) => { + it("api gateway return value", async () => { let counter = 0 process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - handle() { + async handle() { counter++ return { ok: 1 } } @@ -1380,22 +1482,25 @@ describe('invoker', () => { const awsEvent = { requestContext: { apiId: 'apiId' }, } - invoker(awsEvent, {}, (e, result) => { - expect(counter).to.equal(1) - expect(result).to.deep.equal({ - statusCode: 200, - body: '{"ok":1}' - }) - done(e) + const r = await invoker(awsEvent, {}, (e) => { + counter++ + expect(counter).to.equal(2) + }) + + expect(counter).to.equal(2) + expect(r.statusCode).to.equal(200, r.body) + expect(r).to.deep.equal({ + statusCode: 200, + body: '{"ok":1}' }) }) - it("api gateway return value advanced", (done) => { + it("api gateway return value advanced", async () => { let counter = 0 process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - handle() { + async handle() { counter++ return { statusCode: 200, @@ -1408,22 +1513,25 @@ describe('invoker', () => { const awsEvent = { requestContext: { apiId: 'apiId' }, } - invoker(awsEvent, {}, (e, result) => { - expect(counter).to.equal(1) - expect(result).to.deep.equal({ - statusCode: 200, - body: 'myresult' - }) - done(e) + const r = await invoker(awsEvent, {}, (e) => { + counter++ + expect(counter).to.equal(2) + }) + + expect(counter).to.equal(2) + expect(r.statusCode).to.equal(200, r.body) + expect(r).to.deep.equal({ + statusCode: 200, + body: 'myresult' }) }) - it("api gateway return value advanced - error", (done) => { + it("api gateway return value advanced - error", async () => { let counter = 0 process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - handle() { + async handle() { counter++ return { statusCode: 500, @@ -1436,24 +1544,33 @@ describe('invoker', () => { const awsEvent = { requestContext: { apiId: 'apiId' }, } - invoker(awsEvent, {}, (e, result) => { - expect(counter).to.equal(1) - expect(result).to.deep.equal({ - statusCode: 500, - body: 'myerror' - }) - done(e) + const r = await invoker(awsEvent, {}, (e) => { + counter++ + expect(counter).to.equal(2) + }) + + expect(counter).to.equal(2) + expect(r.statusCode).to.equal(500, r.body) + expect(r).to.deep.equal({ + statusCode: 500, + body: 'myerror' }) }) - it("api gateway return value advanced - throw error", (done) => { + it("api gateway return value advanced - throw error", async () => { let counter = 0 + class MyError extends Error{ + constructor(public name, ...params) { + super(...params); + } + } + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - handle() { + async handle() { counter++ - throw new Error('error in handle') + throw new MyError('error in handle') } } @@ -1461,13 +1578,16 @@ describe('invoker', () => { const awsEvent = { requestContext: { apiId: 'apiId' }, } - invoker(awsEvent, {}, (e, result) => { - expect(counter).to.equal(1) - expect(result).to.deep.equal({ - statusCode: 500, - body: JSON.stringify(new Error('error in handle')) - }) - done(e) + const r = await invoker(awsEvent, {}, (e) => { + counter++ + expect(counter).to.equal(2) + }) + + expect(counter).to.equal(2) + expect(r.statusCode).to.equal(500, r.body) + expect(r).to.deep.equal({ + statusCode: 500, + body: JSON.stringify(new MyError('error in handle')) }) }) From 5b261e665cab7515abfd7b811596fc63c0ea5533 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 11 Sep 2017 15:37:17 +0200 Subject: [PATCH 069/196] gitattributes --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..07764a7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text eol=lf \ No newline at end of file From 112536cf4e5d6537750625d86930a7e56f7ca2b2 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 11 Sep 2017 16:29:20 +0200 Subject: [PATCH 070/196] update version --- package-lock.json | 3066 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 3067 insertions(+), 1 deletion(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..668bb81 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3066 @@ +{ + "name": "functionly", + "version": "0.0.6", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/async": { + "version": "2.0.42", + "resolved": "https://registry.npmjs.org/@types/async/-/async-2.0.42.tgz", + "integrity": "sha512-rmsnoIPcAHn9k0HtBktG0vFQIJlQQvLofo70pWly8itQzqG5c/ILSZqmXLsCdJpT1X5wpxO0F3rb3GB5xCiDAA==", + "dev": true + }, + "@types/chai": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.0.4.tgz", + "integrity": "sha512-cvU0HomQ7/aGDQJZsbtJXqBQ7w4J4TqLB0Z/h8mKrpRjfeZEvTbygkfJEb7fWdmwpIeDeFmIVwAEqS0OYuUv3Q==", + "dev": true + }, + "@types/lodash": { + "version": "4.14.74", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.74.tgz", + "integrity": "sha512-BZknw3E/z3JmCLqQVANcR17okqVTPZdlxvcIz0fJiJVLUCbSH1hK3zs9r634PVSmrzAxN+n/fxlVRiYoArdOIQ==", + "dev": true + }, + "@types/mocha": { + "version": "2.2.43", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.43.tgz", + "integrity": "sha512-xNlAmH+lRJdUMXClMTI9Y0pRqIojdxfm7DHsIxoB2iTzu3fnPmSMEN8SsSx0cdwV36d02PWCWaDUoZPDSln+xw==", + "dev": true + }, + "@types/node": { + "version": "6.0.88", + "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.88.tgz", + "integrity": "sha512-bYDPZTX0/s1aihdjLuAgogUAT5M+TpoWChEMea2p0yOcfn5bu3k6cJb9cp6nw268XeSNIGGr+4+/8V5K6BGzLQ==", + "dev": true + }, + "@types/winston": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/winston/-/winston-0.0.30.tgz", + "integrity": "sha1-eyo5hwv/iWPbWHY/HlNnV6mssTQ=", + "dev": true, + "requires": { + "@types/node": "6.0.88" + } + }, + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "accepts": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", + "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", + "requires": { + "mime-types": "2.1.17", + "negotiator": "0.6.1" + } + }, + "acorn": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.2.tgz", + "integrity": "sha512-o96FZLJBPY1lvTuJylGA9Bk3t/GKPPJG8H0ydQQl01crzwJgspa4AEIq/pVTXigmK0PHVQhiAtn8WMBLL9D2WA==" + }, + "acorn-dynamic-import": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", + "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", + "requires": { + "acorn": "4.0.13" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" + } + } + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=" + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "requires": { + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "requires": { + "micromatch": "2.3.11", + "normalize-path": "2.1.1" + } + }, + "argparse": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "requires": { + "sprintf-js": "1.0.3" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "requires": { + "arr-flatten": "1.1.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "asn1.js": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz", + "integrity": "sha1-SLokC0WpKA6UdImQull9IWYX/UA=", + "requires": { + "bn.js": "4.11.8", + "inherits": "2.0.3", + "minimalistic-assert": "1.0.0" + } + }, + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "requires": { + "util": "0.10.3" + } + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + }, + "assertion-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", + "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", + "dev": true + }, + "async": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", + "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==", + "requires": { + "lodash": "4.17.4" + } + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sdk": { + "version": "2.112.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.112.0.tgz", + "integrity": "sha1-mgSMDdWnQHoh9uhiNx0gSanPs1A=", + "requires": { + "buffer": "4.9.1", + "crypto-browserify": "1.0.9", + "events": "1.1.1", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.0.1", + "xml2js": "0.4.17", + "xmlbuilder": "4.2.1" + } + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base64-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", + "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==" + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "big.js": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz", + "integrity": "sha1-TK2iGTZS6zyp7I5VyQFWacmAaXg=" + }, + "binary-extensions": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.10.0.tgz", + "integrity": "sha1-muuabF6IY4qtFx4Wf1kAq+JINdA=" + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "body-parser": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.0.tgz", + "integrity": "sha1-07Ik1Gf6LOjUNYnAJFBDJnwJNjQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "1.0.3", + "debug": "2.6.8", + "depd": "1.1.1", + "http-errors": "1.6.2", + "iconv-lite": "0.4.18", + "on-finished": "2.3.0", + "qs": "6.5.0", + "raw-body": "2.3.1", + "type-is": "1.6.15" + } + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "browserify-aes": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.0.8.tgz", + "integrity": "sha512-WYCMOT/PtGTlpOKFht0YJFYcPy6pLCR98CtWfzK13zoynLlBMvAdEMSRGmgnJCw2M2j/5qxBkinZQFobieM8dQ==", + "requires": { + "buffer-xor": "1.0.3", + "cipher-base": "1.0.4", + "create-hash": "1.1.3", + "evp_bytestokey": "1.0.3", + "inherits": "2.0.3", + "safe-buffer": "5.1.1" + } + }, + "browserify-cipher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz", + "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=", + "requires": { + "browserify-aes": "1.0.8", + "browserify-des": "1.0.0", + "evp_bytestokey": "1.0.3" + } + }, + "browserify-des": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz", + "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=", + "requires": { + "cipher-base": "1.0.4", + "des.js": "1.0.0", + "inherits": "2.0.3" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "requires": { + "bn.js": "4.11.8", + "randombytes": "2.0.5" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "requires": { + "bn.js": "4.11.8", + "browserify-rsa": "4.0.1", + "create-hash": "1.1.3", + "create-hmac": "1.1.6", + "elliptic": "6.4.0", + "inherits": "2.0.3", + "parse-asn1": "5.1.0" + } + }, + "browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "requires": { + "pako": "0.2.9" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "requires": { + "base64-js": "1.2.1", + "ieee754": "1.1.8", + "isarray": "1.0.0" + } + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } + }, + "chai": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", + "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", + "dev": true, + "requires": { + "assertion-error": "1.0.2", + "check-error": "1.0.2", + "deep-eql": "3.0.1", + "get-func-name": "2.0.0", + "pathval": "1.1.0", + "type-detect": "4.0.3" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "requires": { + "anymatch": "1.3.2", + "async-each": "1.0.1", + "glob-parent": "2.0.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "2.0.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "requires": { + "inherits": "2.0.3", + "safe-buffer": "5.1.1" + } + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "requires": { + "delayed-stream": "1.0.0" + } + }, + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "config": { + "version": "1.26.2", + "resolved": "https://registry.npmjs.org/config/-/config-1.26.2.tgz", + "integrity": "sha1-JGYpEWjYr64Kroq5nqTUJy9SDK4=", + "requires": { + "json5": "0.4.0", + "os-homedir": "1.0.2" + } + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "requires": { + "date-now": "0.1.4" + } + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.3.tgz", + "integrity": "sha1-2hjvL7ZMpqzJBcxyAX0/OBhbkdE=" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cors": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", + "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", + "requires": { + "object-assign": "4.1.1", + "vary": "1.1.1" + } + }, + "create-ecdh": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", + "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=", + "requires": { + "bn.js": "4.11.8", + "elliptic": "6.4.0" + } + }, + "create-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=", + "requires": { + "cipher-base": "1.0.4", + "inherits": "2.0.3", + "ripemd160": "2.0.1", + "sha.js": "2.4.8" + } + }, + "create-hmac": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", + "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=", + "requires": { + "cipher-base": "1.0.4", + "create-hash": "1.1.3", + "inherits": "2.0.3", + "ripemd160": "2.0.1", + "safe-buffer": "5.1.1", + "sha.js": "2.4.8" + } + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "requires": { + "boom": "2.10.1" + } + }, + "crypto-browserify": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-1.0.9.tgz", + "integrity": "sha1-zFRJaF37hesRyYKKzHy4erW7/MA=" + }, + "cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=" + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "4.0.3" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "requires": { + "inherits": "2.0.3", + "minimalistic-assert": "1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "diff": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", + "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz", + "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=", + "requires": { + "bn.js": "4.11.8", + "miller-rabin": "4.0.0", + "randombytes": "2.0.5" + } + }, + "domain-browser": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", + "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=" + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "elliptic": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", + "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", + "requires": { + "bn.js": "4.11.8", + "brorand": "1.1.0", + "hash.js": "1.1.3", + "hmac-drbg": "1.0.1", + "inherits": "2.0.3", + "minimalistic-assert": "1.0.0", + "minimalistic-crypto-utils": "1.0.1" + } + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=" + }, + "encodeurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" + }, + "enhanced-resolve": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", + "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=", + "requires": { + "graceful-fs": "4.1.11", + "memory-fs": "0.4.1", + "object-assign": "4.1.1", + "tapable": "0.2.8" + } + }, + "errno": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz", + "integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=", + "requires": { + "prr": "0.0.0" + } + }, + "error-ex": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "requires": { + "is-arrayish": "0.2.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "requires": { + "esprima": "2.7.3", + "estraverse": "1.9.3", + "esutils": "2.0.2", + "optionator": "0.8.2", + "source-map": "0.2.0" + }, + "dependencies": { + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "requires": { + "amdefine": "1.0.1" + } + } + } + }, + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "etag": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", + "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=" + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "requires": { + "md5.js": "1.3.4", + "safe-buffer": "5.1.1" + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "requires": { + "is-posix-bracket": "0.1.1" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "requires": { + "fill-range": "2.2.3" + } + }, + "express": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.15.4.tgz", + "integrity": "sha1-Ay4iU0ic+PzgJma+yj0R7XotrtE=", + "requires": { + "accepts": "1.3.4", + "array-flatten": "1.1.1", + "content-disposition": "0.5.2", + "content-type": "1.0.3", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.8", + "depd": "1.1.1", + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "etag": "1.8.0", + "finalhandler": "1.0.4", + "fresh": "0.5.0", + "merge-descriptors": "1.0.1", + "methods": "1.1.2", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "1.1.5", + "qs": "6.5.0", + "range-parser": "1.2.0", + "send": "0.15.4", + "serve-static": "1.12.4", + "setprototypeof": "1.0.3", + "statuses": "1.3.1", + "type-is": "1.6.15", + "utils-merge": "1.0.0", + "vary": "1.1.1" + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "requires": { + "is-extglob": "1.0.0" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" + }, + "fill-range": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.7", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + } + }, + "finalhandler": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.4.tgz", + "integrity": "sha512-16l/r8RgzlXKmFOhZpHBztvye+lAhC5SU7hXavnerC9UfZqZxxXl3BzL8MhffPT3kF61lj9Oav2LKEzh0ei7tg==", + "requires": { + "debug": "2.6.8", + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "statuses": "1.3.1", + "unpipe": "1.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "requires": { + "for-in": "1.0.2" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "forwarded": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.1.tgz", + "integrity": "sha1-ik4wxkCwU5U5mjVJxzAldygEiWE=" + }, + "fresh": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", + "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=" + }, + "fs-extra": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", + "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "3.0.1", + "universalify": "0.1.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "get-caller-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=" + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "requires": { + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "requires": { + "is-glob": "2.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "handlebars": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.10.tgz", + "integrity": "sha1-PTDHGLCaPZbyPqTMH0A8TTup/08=", + "dev": true, + "requires": { + "async": "1.5.2", + "optimist": "0.6.1", + "source-map": "0.4.4", + "uglify-js": "2.8.29" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": "1.0.1" + } + } + } + }, + "har-schema": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" + }, + "har-validator": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "hash-base": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", + "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", + "requires": { + "inherits": "2.0.3" + } + }, + "hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "requires": { + "inherits": "2.0.3", + "minimalistic-assert": "1.0.0" + } + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "1.1.3", + "minimalistic-assert": "1.0.0", + "minimalistic-crypto-utils": "1.0.1" + } + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + }, + "hosted-git-info": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", + "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==" + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": "1.3.1" + } + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "https-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", + "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=" + }, + "iconv-lite": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", + "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==" + }, + "ieee754": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "interpret": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz", + "integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=" + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "ipaddr.js": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.4.0.tgz", + "integrity": "sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA=" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "requires": { + "binary-extensions": "1.10.0" + } + }, + "is-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", + "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=" + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "requires": { + "builtin-modules": "1.1.1" + } + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=" + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "requires": { + "is-primitive": "2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "requires": { + "is-extglob": "1.0.0" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "requires": { + "kind-of": "3.2.2" + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=" + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "dev": true, + "requires": { + "abbrev": "1.0.9", + "async": "1.5.2", + "escodegen": "1.8.1", + "esprima": "2.7.3", + "glob": "5.0.15", + "handlebars": "4.0.10", + "js-yaml": "3.10.0", + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "once": "1.4.0", + "resolve": "1.1.7", + "supports-color": "3.2.3", + "which": "1.3.0", + "wordwrap": "1.0.0" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + } + } + }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, + "js-yaml": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", + "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "dev": true, + "requires": { + "argparse": "1.0.9", + "esprima": "4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + } + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "json-loader": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", + "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "json5": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz", + "integrity": "sha1-BUNS5MTIDIbAkjh31EneF2pzLI0=" + }, + "jsonfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", + "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", + "requires": { + "graceful-fs": "4.1.11" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "jszip": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-2.5.0.tgz", + "integrity": "sha1-dET9hVHd8+XacZj+oMkbyDCMwnQ=", + "requires": { + "pako": "0.2.9" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.5" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "1.0.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + } + }, + "loader-runner": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", + "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=" + }, + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "requires": { + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1", + "object-assign": "4.1.1" + }, + "dependencies": { + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" + } + } + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash.keys": "3.1.2" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basecreate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true, + "requires": { + "lodash._baseassign": "3.2.0", + "lodash._basecreate": "3.0.3", + "lodash._isiterateecall": "3.0.9" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + }, + "md5.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", + "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", + "requires": { + "hash-base": "3.0.4", + "inherits": "2.0.3" + }, + "dependencies": { + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "requires": { + "inherits": "2.0.3", + "safe-buffer": "5.1.1" + } + } + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "requires": { + "errno": "0.1.4", + "readable-stream": "2.3.3" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" + } + }, + "miller-rabin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.0.tgz", + "integrity": "sha1-SmL7HUKTPAVYOYL0xxb2+55sbT0=", + "requires": { + "bn.js": "4.11.8", + "brorand": "1.1.0" + } + }, + "mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", + "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" + }, + "mime-db": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "requires": { + "mime-db": "1.30.0" + } + }, + "minimalistic-assert": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", + "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.2.tgz", + "integrity": "sha512-iH5zl7afRZl1GvD0pnrRlazgc9Z/o34pXWmTFi8xNIMFKXgNL1SoBTDDb9sJfbV/sJV/j8X+0gvwY1QS1He7Nw==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.6.8", + "diff": "3.2.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.1", + "growl": "1.9.2", + "he": "1.1.1", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + }, + "dependencies": { + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": "1.0.1" + } + }, + "glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "supports-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "node-libs-browser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.0.0.tgz", + "integrity": "sha1-o6WeyXAkmFtG6Vg3lkb5bEthZkY=", + "requires": { + "assert": "1.4.1", + "browserify-zlib": "0.1.4", + "buffer": "4.9.1", + "console-browserify": "1.1.0", + "constants-browserify": "1.0.0", + "crypto-browserify": "3.11.1", + "domain-browser": "1.1.7", + "events": "1.1.1", + "https-browserify": "0.0.1", + "os-browserify": "0.2.1", + "path-browserify": "0.0.0", + "process": "0.11.10", + "punycode": "1.3.2", + "querystring-es3": "0.2.1", + "readable-stream": "2.3.3", + "stream-browserify": "2.0.1", + "stream-http": "2.7.2", + "string_decoder": "0.10.31", + "timers-browserify": "2.0.4", + "tty-browserify": "0.0.0", + "url": "0.11.0", + "util": "0.10.3", + "vm-browserify": "0.0.4" + }, + "dependencies": { + "crypto-browserify": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.1.tgz", + "integrity": "sha512-Na7ZlwCOqoaW5RwUK1WpXws2kv8mNhWdTlzob0UXulk6G9BDbyiJaGTYBIX61Ozn9l1EPPJpICZb4DaOpT9NlQ==", + "requires": { + "browserify-cipher": "1.0.0", + "browserify-sign": "4.0.4", + "create-ecdh": "4.0.0", + "create-hash": "1.1.3", + "create-hmac": "1.1.6", + "diffie-hellman": "5.0.2", + "inherits": "2.0.3", + "pbkdf2": "3.0.14", + "public-encrypt": "4.0.0", + "randombytes": "2.0.5" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + } + } + }, + "node-zip": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/node-zip/-/node-zip-1.1.1.tgz", + "integrity": "sha1-lNGtZ0o81GoViN1zb0qaeMdX62I=", + "requires": { + "jszip": "2.5.0" + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1.0.9" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "requires": { + "hosted-git-info": "2.5.0", + "is-builtin-module": "1.0.0", + "semver": "5.4.1", + "validate-npm-package-license": "3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "1.1.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "0.0.8", + "wordwrap": "0.0.2" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + }, + "dependencies": { + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + } + } + }, + "os-browserify": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.2.1.tgz", + "integrity": "sha1-Y/xMzuXS13Y9Jrv4YBB45sLgBE8=" + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "1.0.0" + } + }, + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=" + }, + "parse-asn1": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", + "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", + "requires": { + "asn1.js": "4.9.1", + "browserify-aes": "1.0.8", + "create-hash": "1.1.3", + "evp_bytestokey": "1.0.3", + "pbkdf2": "3.0.14" + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "requires": { + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "1.3.1" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "path-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=" + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "pbkdf2": { + "version": "3.0.14", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz", + "integrity": "sha512-gjsZW9O34fm0R7PaLHRJmLLVfSoesxztjPjE9o6R+qtVJij90ltg1joIovN9GKrRW3t1PzhDDG3UMEMFfZ+1wA==", + "requires": { + "create-hash": "1.1.3", + "create-hmac": "1.1.6", + "ripemd160": "2.0.1", + "safe-buffer": "5.1.1", + "sha.js": "2.4.8" + } + }, + "performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "2.0.4" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "proxy-addr": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz", + "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=", + "requires": { + "forwarded": "0.1.1", + "ipaddr.js": "1.4.0" + } + }, + "prr": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz", + "integrity": "sha1-GoS4WQgyVQFBGFPQCB7j+obikmo=" + }, + "public-encrypt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", + "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=", + "requires": { + "bn.js": "4.11.8", + "browserify-rsa": "4.0.1", + "create-hash": "1.1.3", + "parse-asn1": "5.1.0", + "randombytes": "2.0.5" + } + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, + "qs": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.0.tgz", + "integrity": "sha512-fjVFjW9yhqMhVGwRExCXLhJKrLlkYSaxNWdyc9rmHlrVZbk35YHH312dFd7191uQeXkI3mKLZTIbSvIeFwFemg==" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" + }, + "randomatic": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", + "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "1.1.5" + } + } + } + }, + "randombytes": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz", + "integrity": "sha512-8T7Zn1AhMsQ/HI1SjcCfT/t4ii3eAqco3yOcSzS4mozsOz69lHLsoMXmF9nZgnFanYscnSlUSgs8uZyKzpE6kg==", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raw-body": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.1.tgz", + "integrity": "sha512-sxkd1uqaSj41SG5Vet9sNAxBMCMsmZ3LVhRkDlK8SbCpelTUB7JiMGHG70AZS6cFiCRgfNQhU2eLnTHYRFf7LA==", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.18", + "unpipe": "1.0.0" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + } + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "requires": { + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "readable-stream": "2.3.3", + "set-immediate-shim": "1.0.1" + } + }, + "reflect-metadata": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.10.tgz", + "integrity": "sha1-tPg3BEFqytiZiMmxVjXUfgO5NEo=" + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "requires": { + "is-equal-shallow": "0.1.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "request": { + "version": "2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.2", + "tunnel-agent": "0.6.0", + "uuid": "3.0.1" + }, + "dependencies": { + "qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "requires": { + "align-text": "0.1.4" + } + }, + "ripemd160": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", + "requires": { + "hash-base": "2.0.2", + "inherits": "2.0.3" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" + }, + "send": { + "version": "0.15.4", + "resolved": "https://registry.npmjs.org/send/-/send-0.15.4.tgz", + "integrity": "sha1-mF+qPihLAnPHkzZKNcZze9k5Bbk=", + "requires": { + "debug": "2.6.8", + "depd": "1.1.1", + "destroy": "1.0.4", + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "etag": "1.8.0", + "fresh": "0.5.0", + "http-errors": "1.6.2", + "mime": "1.3.4", + "ms": "2.0.0", + "on-finished": "2.3.0", + "range-parser": "1.2.0", + "statuses": "1.3.1" + } + }, + "serve-static": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.4.tgz", + "integrity": "sha1-m2qpjutyU8Tu3Ewfb9vKYJkBqWE=", + "requires": { + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "parseurl": "1.3.2", + "send": "0.15.4" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + }, + "sha.js": { + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.8.tgz", + "integrity": "sha1-NwaMLEdra69ALRSknGf1l5IfY08=", + "requires": { + "inherits": "2.0.3" + } + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "requires": { + "hoek": "2.16.3" + } + }, + "source-list-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", + "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "spdx-correct": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "requires": { + "spdx-license-ids": "1.2.2" + } + }, + "spdx-expression-parse": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=" + }, + "spdx-license-ids": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + }, + "stream-browserify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.3" + } + }, + "stream-http": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz", + "integrity": "sha512-c0yTD2rbQzXtSsFSVhtpvY/vS6u066PcXOX9kBB3mSO76RiUQzL340uJkGBWnlBg4/HZzqiUXtaVA7wcRcJgEw==", + "requires": { + "builtin-status-codes": "3.0.0", + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "to-arraybuffer": "1.0.1", + "xtend": "4.0.1" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "0.2.1" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "1.0.0" + } + }, + "tapable": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz", + "integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=" + }, + "timers-browserify": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.4.tgz", + "integrity": "sha512-uZYhyU3EX8O7HQP+J9fTVYwsq90Vr68xPEFo7yrVImIxYvHgukBEgOB/SgGoorWVTzGM/3Z+wUNnboA4M8jWrg==", + "requires": { + "setimmediate": "1.0.5" + } + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=" + }, + "tough-cookie": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "requires": { + "punycode": "1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } + }, + "type-detect": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.3.tgz", + "integrity": "sha1-Dj8mcLRAmbC0bChNE2p+9Jx0wuo=", + "dev": true + }, + "type-is": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "requires": { + "media-typer": "0.3.0", + "mime-types": "2.1.17" + } + }, + "typescript": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.2.tgz", + "integrity": "sha1-A4qV99m7tCCxvzW6MdTFwd0//jQ=", + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "requires": { + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, + "dependencies": { + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "optional": true + }, + "universalify": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", + "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "requires": { + "inherits": "2.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" + }, + "uuid": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", + "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=" + }, + "validate-npm-package-license": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.4" + } + }, + "vary": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", + "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "requires": { + "indexof": "0.0.1" + } + }, + "watchpack": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.4.0.tgz", + "integrity": "sha1-ShRyvLuVK9Cpu0A2gB+VTfs5+qw=", + "requires": { + "async": "2.5.0", + "chokidar": "1.7.0", + "graceful-fs": "4.1.11" + } + }, + "webpack": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-2.7.0.tgz", + "integrity": "sha512-MjAA0ZqO1ba7ZQJRnoCdbM56mmFpipOPUv/vQpwwfSI42p5PVDdoiuK2AL2FwFUVgT859Jr43bFZXRg/LNsqvg==", + "requires": { + "acorn": "5.1.2", + "acorn-dynamic-import": "2.0.2", + "ajv": "4.11.8", + "ajv-keywords": "1.5.1", + "async": "2.5.0", + "enhanced-resolve": "3.4.1", + "interpret": "1.0.3", + "json-loader": "0.5.7", + "json5": "0.5.1", + "loader-runner": "2.3.0", + "loader-utils": "0.2.17", + "memory-fs": "0.4.1", + "mkdirp": "0.5.1", + "node-libs-browser": "2.0.0", + "source-map": "0.5.7", + "supports-color": "3.2.3", + "tapable": "0.2.8", + "uglify-js": "2.8.29", + "watchpack": "1.4.0", + "webpack-sources": "1.0.1", + "yargs": "6.6.0" + }, + "dependencies": { + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" + } + } + }, + "webpack-sources": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.0.1.tgz", + "integrity": "sha512-05tMxipUCwHqYaVS8xc7sYPTly8PzXayRCB4dTxLhWTqlKUiwH6ezmEe0OSreL1c30LAuA3Zqmc+uEBUGFJDjw==", + "requires": { + "source-list-map": "2.0.0", + "source-map": "0.5.7" + } + }, + "which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" + }, + "winston": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.3.1.tgz", + "integrity": "sha1-C0hCDZeMAYBM8CMLZIhhWYIloRk=", + "requires": { + "async": "1.0.0", + "colors": "1.0.3", + "cycle": "1.0.3", + "eyes": "0.1.8", + "isstream": "0.1.2", + "stack-trace": "0.0.10" + }, + "dependencies": { + "async": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", + "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=" + } + } + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "xml2js": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", + "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", + "requires": { + "sax": "1.2.1", + "xmlbuilder": "4.2.1" + } + }, + "xmlbuilder": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", + "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", + "requires": { + "lodash": "4.17.4" + } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "yamljs": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.2.10.tgz", + "integrity": "sha1-SBzHwlynOvWfWR8MluPOVsdXpA8=", + "requires": { + "argparse": "1.0.9", + "glob": "7.1.2" + } + }, + "yargs": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", + "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", + "requires": { + "camelcase": "3.0.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "os-locale": "1.4.0", + "read-pkg-up": "1.0.1", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "1.0.2", + "which-module": "1.0.0", + "y18n": "3.2.1", + "yargs-parser": "4.2.1" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" + } + } + } + }, + "yargs-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", + "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", + "requires": { + "camelcase": "3.0.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" + } + } + } + } +} diff --git a/package.json b/package.json index 239fc71..ecbdac7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.6", + "version": "0.0.7", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 088696188485a8634517335f61d3971ebdc6a3b5 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 11 Sep 2017 16:39:09 +0200 Subject: [PATCH 071/196] change: tsc newline --- package.json | 2 +- tsconfig.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ecbdac7..37028c1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.7", + "version": "0.0.8", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", diff --git a/tsconfig.json b/tsconfig.json index 09e6902..82927bb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,8 @@ "declaration": true, "sourceMap": true, "allowSyntheticDefaultImports": true, - "experimentalDecorators": true + "experimentalDecorators": true, + "newLine": "LF" }, "exclude": [ "node_modules", From 9d617f5a55526003062aa689ebe6c6cea96d7c0c Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 11 Sep 2017 17:13:45 +0200 Subject: [PATCH 072/196] FIX: aws CORS --- package.json | 2 +- src/providers/aws/eventSources/apiGateway.ts | 18 ++++++++++++++++-- src/providers/aws/index.ts | 2 +- .../azure/eventSources/httpTrigger.ts | 2 +- src/providers/azure/index.ts | 2 +- src/providers/core/eventSource.ts | 2 +- src/providers/inProc.ts | 4 ++-- src/providers/local.ts | 4 ++-- test/invoker.tests.ts | 2 ++ 9 files changed, 27 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 37028c1..d0e6594 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.8", + "version": "0.0.9", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", diff --git a/src/providers/aws/eventSources/apiGateway.ts b/src/providers/aws/eventSources/apiGateway.ts index 1af1d35..9e1b054 100644 --- a/src/providers/aws/eventSources/apiGateway.ts +++ b/src/providers/aws/eventSources/apiGateway.ts @@ -1,5 +1,7 @@ import { EventSource } from '../../core/eventSource' import { get } from '../../../helpers/property' +import { CLASS_APIGATEWAYKEY } from '../../../annotations/constants' +import { getMetadata, defineMetadata } from '../../../annotations/metadata' export class ApiGateway extends EventSource { public available(eventContext: any): boolean { @@ -43,10 +45,21 @@ export class ApiGateway extends EventSource { } } - public async resultTransform(err, result, event) { + public async resultTransform(err, result, event, serviceInstance) { + let headers = {} + const metadata = getMetadata(CLASS_APIGATEWAYKEY, serviceInstance) || [] + if (serviceInstance && metadata.find(m => m.cors === true)) { + headers = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'Content-Type,Authorization,X-Requested-With', + 'Access-Control-Allow-Credentials': 'true' + } + } + if (err) { return { statusCode: 500, + headers, body: JSON.stringify(err) } } @@ -54,7 +67,7 @@ export class ApiGateway extends EventSource { if (result && typeof result.status === 'number' && result.hasOwnProperty('data')) { return { statusCode: result.status, - headers: result.headers, + headers: { ...headers, ...result.headers }, data: JSON.stringify(result.data) } } @@ -65,6 +78,7 @@ export class ApiGateway extends EventSource { return { statusCode: 200, + headers, body: JSON.stringify(result) } } diff --git a/src/providers/aws/index.ts b/src/providers/aws/index.ts index 79ccfc3..5f4968e 100644 --- a/src/providers/aws/index.ts +++ b/src/providers/aws/index.ts @@ -44,7 +44,7 @@ export class AWSProvider extends Provider { } catch (err) { error = err } - const response = await eventSourceHandler.resultTransform(error, result, eventContext) + const response = await eventSourceHandler.resultTransform(error, result, eventContext, serviceInstance) cb(null, response) return response diff --git a/src/providers/azure/eventSources/httpTrigger.ts b/src/providers/azure/eventSources/httpTrigger.ts index 19b6a36..87c7582 100644 --- a/src/providers/azure/eventSources/httpTrigger.ts +++ b/src/providers/azure/eventSources/httpTrigger.ts @@ -30,7 +30,7 @@ export class HttpTrigger extends EventSource { } } - public async resultTransform(error, result, eventContext) { + public async resultTransform(error, result, eventContext, serviceInstance) { if (error) { return { status: 500, diff --git a/src/providers/azure/index.ts b/src/providers/azure/index.ts index ac43354..36f3592 100644 --- a/src/providers/azure/index.ts +++ b/src/providers/azure/index.ts @@ -30,7 +30,7 @@ export class AzureProvider extends Provider { } catch (err) { error = err } - const response = await eventSourceHandler.resultTransform(error, result, eventContext) + const response = await eventSourceHandler.resultTransform(error, result, eventContext, serviceInstance) context.res = response return response diff --git a/src/providers/core/eventSource.ts b/src/providers/core/eventSource.ts index 1641f31..ff3c066 100644 --- a/src/providers/core/eventSource.ts +++ b/src/providers/core/eventSource.ts @@ -9,7 +9,7 @@ export abstract class EventSource { return undefined } - public async resultTransform(err, result, event: any) { + public async resultTransform(err, result, event: any, serviceInstance) { if (err) throw err if (result && typeof result.status === 'number' && result.hasOwnProperty('data')) { diff --git a/src/providers/inProc.ts b/src/providers/inProc.ts index 03c2bdc..b515441 100644 --- a/src/providers/inProc.ts +++ b/src/providers/inProc.ts @@ -20,14 +20,14 @@ export class InProcProvider extends Provider { } catch (err) { error = err } - const response = await this.resultTransform(error, result, eventContext) + const response = await this.resultTransform(error, result, eventContext, serviceInstance) return response } return invoker } - protected resultTransform(error, result, eventContext) { + protected resultTransform(error, result, eventContext, serviceInstance) { if (error) throw error if (result && typeof result.status === 'number' && result.hasOwnProperty('data')) { diff --git a/src/providers/local.ts b/src/providers/local.ts index 84b5871..c30b655 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -21,7 +21,7 @@ export class LocalProvider extends Provider { } catch (err) { error = err } - const response = await this.resultTransform(error, result, eventContext) + const response = await this.resultTransform(error, result, eventContext, serviceInstance) res.send(response) return response @@ -32,7 +32,7 @@ export class LocalProvider extends Provider { return invoker } - protected resultTransform(error, result, eventContext) { + protected resultTransform(error, result, eventContext, serviceInstance) { if (error) throw error if (result && typeof result.status === 'number' && result.hasOwnProperty('data')) { diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index c1354bb..bf86601 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -1491,6 +1491,7 @@ describe('invoker', () => { expect(r.statusCode).to.equal(200, r.body) expect(r).to.deep.equal({ statusCode: 200, + headers: {}, body: '{"ok":1}' }) }) @@ -1587,6 +1588,7 @@ describe('invoker', () => { expect(r.statusCode).to.equal(500, r.body) expect(r).to.deep.equal({ statusCode: 500, + headers: {}, body: JSON.stringify(new MyError('error in handle')) }) }) From 453d14703a46b2b551fcabf599dd12eb29145441 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Thu, 14 Sep 2017 11:42:28 +0200 Subject: [PATCH 073/196] ADD mongo runtime support --- package-lock.json | 85 ++++++- package.json | 2 + src/index.ts | 2 + src/plugins/mongo.ts | 514 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 602 insertions(+), 1 deletion(-) create mode 100644 src/plugins/mongo.ts diff --git a/package-lock.json b/package-lock.json index 668bb81..7bb9aaa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.6", + "version": "0.0.9", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -10,6 +10,15 @@ "integrity": "sha512-rmsnoIPcAHn9k0HtBktG0vFQIJlQQvLofo70pWly8itQzqG5c/ILSZqmXLsCdJpT1X5wpxO0F3rb3GB5xCiDAA==", "dev": true }, + "@types/bson": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/bson/-/bson-1.0.4.tgz", + "integrity": "sha512-/nysVvxwup1WniGHIM31UZXM+6727h4FAa2tZpFSQBooBcl2Bh1N9oQmVVg8QYnjchN/DOGi7UvVN0jpzWL6sw==", + "dev": true, + "requires": { + "@types/node": "6.0.88" + } + }, "@types/chai": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.0.4.tgz", @@ -28,6 +37,16 @@ "integrity": "sha512-xNlAmH+lRJdUMXClMTI9Y0pRqIojdxfm7DHsIxoB2iTzu3fnPmSMEN8SsSx0cdwV36d02PWCWaDUoZPDSln+xw==", "dev": true }, + "@types/mongodb": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-2.2.11.tgz", + "integrity": "sha512-kksYkn34+ENpZqUvGYq0IGOOa4SOhFnMfqCepF789CP+QBgeWok9osTCiAStbc8n4Cv3QSXGmpB5T3PGzL50bQ==", + "dev": true, + "requires": { + "@types/bson": "1.0.4", + "@types/node": "6.0.88" + } + }, "@types/node": { "version": "6.0.88", "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.88.tgz", @@ -385,6 +404,11 @@ "pako": "0.2.9" } }, + "bson": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.0.4.tgz", + "integrity": "sha1-k8ENOeqltYQVy8QFLz5T5WKwtyw=" + }, "buffer": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", @@ -395,6 +419,11 @@ "isarray": "1.0.0" } }, + "buffer-shims": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=" + }, "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", @@ -786,6 +815,11 @@ "is-arrayish": "0.2.1" } }, + "es6-promise": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", + "integrity": "sha1-7FYjOGgDKQkgcXDDlEjiREndH8Q=" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1865,6 +1899,41 @@ } } }, + "mongodb": { + "version": "2.2.31", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.31.tgz", + "integrity": "sha1-GUBEXGYeGSF7s7+CRdmFSq71SNs=", + "requires": { + "es6-promise": "3.2.1", + "mongodb-core": "2.1.15", + "readable-stream": "2.2.7" + }, + "dependencies": { + "readable-stream": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz", + "integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE=", + "requires": { + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + } + } + }, + "mongodb-core": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.15.tgz", + "integrity": "sha1-hB9TuH//9MdFgYnDXIroJ+EWl2Q=", + "requires": { + "bson": "1.0.4", + "require_optional": "1.0.1" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -2415,6 +2484,15 @@ } } }, + "require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "requires": { + "resolve-from": "2.0.0", + "semver": "5.4.1" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -2431,6 +2509,11 @@ "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", "dev": true }, + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" + }, "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", diff --git a/package.json b/package.json index d0e6594..9f5475e 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@types/chai": "^4.0.1", "@types/lodash": "^4.14.38", "@types/mocha": "^2.2.41", + "@types/mongodb": "^2.2.11", "@types/node": "^6.0.46", "@types/winston": "0.0.30", "chai": "^4.0.2", @@ -44,6 +45,7 @@ "express": "^4.15.2", "fs-extra": "^3.0.1", "lodash": "^4.14.1", + "mongodb": "^2.2.31", "node-zip": "^1.1.1", "reflect-metadata": "^0.1.10", "request": "^2.81.0", diff --git a/src/index.ts b/src/index.ts index b6a43f4..f2f8883 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ export { FunctionalService, Api, Service, PreHook, PostHook, Resource } from './ /* Apis */ export { DynamoTable, SimpleNotificationService, S3Storage, ApiGateway } from './classes' +export { MongoCollection, MongoConnection } from './plugins/mongo' /* Providers */ export { Provider, AWSProvider, LocalProvider } from './providers' @@ -15,6 +16,7 @@ export { tag, log, functionName, dynamoTable, sns, s3Storage, eventSource, classConfig, use, description, role, aws, azure, param, serviceParams, request, error, result, functionalServiceName, provider, stage, inject } from './annotations' +export { mongoCollection, mongoConnection } from './plugins/mongo' /* Enums */ export { InjectionScope } from './annotations' diff --git a/src/plugins/mongo.ts b/src/plugins/mongo.ts new file mode 100644 index 0000000..3c86e30 --- /dev/null +++ b/src/plugins/mongo.ts @@ -0,0 +1,514 @@ +import * as MongoDB from 'mongodb'; + +import { Service } from '../classes/service'; +import { Api } from '../classes/api'; +import { + getMetadata, + defineMetadata, + templates, + applyTemplates, + param, + environment, + injectable, + InjectionScope, + inject, + simpleClassAnnotation, + classConfig +} from '../annotations'; + +export const CLASS_MONGODB_TABLECONFIGURATIONKEY = 'functionly:class:mongoTableConfiguration'; +export const MONGO_TABLE_NAME_SUFFIX = '_TABLE_NAME'; +export const MONGO_CONNECTION_URL = 'MONGO_CONNECTION_URL'; + +export const __mongoDefaults = {}; +export const mongoConnection = (url: string) => environment(MONGO_CONNECTION_URL, url); +export const mongoCollection = (tableConfig: { collectionName: string; url?: string; environmentKey?: string }) => ( + target: Function +) => { + let tableDefinitions = getMetadata(CLASS_MONGODB_TABLECONFIGURATIONKEY, target) || []; + + tableConfig.environmentKey = tableConfig.environmentKey || `%ClassName%${MONGO_TABLE_NAME_SUFFIX}`; + const { templatedKey, templatedValue } = applyTemplates( + tableConfig.environmentKey, + tableConfig.collectionName, + target + ); + tableDefinitions.push({ + ...tableConfig, + environmentKey: templatedKey, + collectionName: templatedValue, + definedBy: target.name + }); + + defineMetadata(CLASS_MONGODB_TABLECONFIGURATIONKEY, [ ...tableDefinitions ], target); + + const environmentSetter = environment(templatedKey, templatedValue); + environmentSetter(target); +}; + +@injectable(InjectionScope.Singleton) +export class MongoConnection extends Api { + private _connections: Map; + private _connectionPromises: Map; + + constructor() { + super(); + this._connections = new Map(); + this._connectionPromises = new Map(); + } + + public async connect(url): Promise { + const connectionUrl = url || process.env.MONGO_CONNECTION_URL || 'mongodb://localhost:27017/test'; + + if (this._connections.has(connectionUrl)) { + return this._connections.get(connectionUrl); + } + + if (this._connectionPromises.has(connectionUrl)) { + return await this._connectionPromises.get(connectionUrl); + } + + const connectionPromise = MongoDB.MongoClient.connect(connectionUrl); + this._connections.set(connectionUrl, connectionPromise); + + const connection = await connectionPromise; + this._connections.set(connectionUrl, connection); + + return connection; + } +} + +export class MongoCollection extends Api implements MongoDB.Collection { + /* MongoDB.Collection */ + get collectionName() { + return this._collection.collectionName; + } + get namespace() { + return this._collection.namespace; + } + get writeConcern() { + return this._collection.writeConcern; + } + get readConcern() { + return this._collection.readConcern; + } + get hint() { + return this._collection.hint; + } + aggregate(pipeline: Object[], callback: MongoDB.MongoCallback): MongoDB.AggregationCursor; + aggregate( + pipeline: Object[], + options?: MongoDB.CollectionAggregationOptions, + callback?: MongoDB.MongoCallback + ): MongoDB.AggregationCursor; + aggregate(pipeline: any, options?: any, callback?: any) { + return this._collection.aggregate(pipeline, options, callback) as any; + } + bulkWrite(operations: Object[], callback: MongoDB.MongoCallback): void; + bulkWrite( + operations: Object[], + options?: MongoDB.CollectionBluckWriteOptions + ): Promise; + bulkWrite( + operations: Object[], + options: MongoDB.CollectionBluckWriteOptions, + callback: MongoDB.MongoCallback + ): void; + bulkWrite(operations: any, options?: any, callback?: any) { + return this._collection.bulkWrite(operations, options, callback) as any; + } + count(query: Object, callback: MongoDB.MongoCallback): void; + count(query: Object, options?: MongoDB.MongoCountPreferences): Promise; + count(query: Object, options: MongoDB.MongoCountPreferences, callback: MongoDB.MongoCallback): void; + count(query: any, options?: any, callback?: any) { + return this._collection.count(query, options, callback) as any; + } + createIndex(fieldOrSpec: any, callback: MongoDB.MongoCallback): void; + createIndex(fieldOrSpec: any, options?: MongoDB.IndexOptions): Promise; + createIndex(fieldOrSpec: any, options: MongoDB.IndexOptions, callback: MongoDB.MongoCallback): void; + createIndex(fieldOrSpec: any, options?: any, callback?: any) { + return this._collection.createIndex(fieldOrSpec, options, callback) as any; + } + createIndexes(indexSpecs: Object[]): Promise; + createIndexes(indexSpecs: Object[], callback: MongoDB.MongoCallback): void; + createIndexes(indexSpecs: any, callback?: any) { + return this._collection.createIndexes(indexSpecs, callback) as any; + } + deleteMany(filter: Object, callback: MongoDB.MongoCallback): void; + deleteMany(filter: Object, options?: MongoDB.CollectionOptions): Promise; + deleteMany( + filter: Object, + options: MongoDB.CollectionOptions, + callback: MongoDB.MongoCallback + ): void; + deleteMany(filter: any, options?: any, callback?: any) { + return this._collection.deleteMany(filter, options, callback) as any; + } + deleteOne(filter: Object, callback: MongoDB.MongoCallback): void; + deleteOne( + filter: Object, + options?: { w?: string | number; wtimmeout?: number; j?: boolean; bypassDocumentValidation?: boolean } + ): Promise; + deleteOne( + filter: Object, + options: { w?: string | number; wtimmeout?: number; j?: boolean; bypassDocumentValidation?: boolean }, + callback: MongoDB.MongoCallback + ): void; + deleteOne(filter: any, options?: any, callback?: any) { + return this._collection.deleteOne(filter, options, callback) as any; + } + distinct(key: string, query: Object, callback: MongoDB.MongoCallback): void; + distinct(key: string, query: Object, options?: { readPreference?: string | MongoDB.ReadPreference }): Promise; + distinct( + key: string, + query: Object, + options: { readPreference?: string | MongoDB.ReadPreference }, + callback: MongoDB.MongoCallback + ): void; + distinct(key: any, query: any, options?: any, callback?: any) { + return this._collection.distinct(key, query, options, callback) as any; + } + drop(): Promise; + drop(callback: MongoDB.MongoCallback): void; + drop(callback?: any) { + return this._collection.drop(callback) as any; + } + dropIndex(indexName: string, callback: MongoDB.MongoCallback): void; + dropIndex(indexName: string, options?: MongoDB.CollectionOptions): Promise; + dropIndex(indexName: string, options: MongoDB.CollectionOptions, callback: MongoDB.MongoCallback): void; + dropIndex(indexName: any, options?: any, callback?: any) { + return this._collection.dropIndex(indexName, options, callback) as any; + } + dropIndexes(): Promise; + dropIndexes(callback?: MongoDB.MongoCallback): void; + dropIndexes(callback?: any) { + return this._collection.dropIndexes(callback) as any; + } + find(query?: Object): MongoDB.Cursor; + find(query: Object, fields?: Object, skip?: number, limit?: number, timeout?: number): MongoDB.Cursor; + find(query?: any, fields?: any, skip?: any, limit?: any, timeout?: any) { + return this._collection.find(query, fields, skip, limit, timeout); + } + findOne(filter: Object, callback: MongoDB.MongoCallback): void; + findOne(filter: Object, options?: MongoDB.FindOneOptions): Promise; + findOne(filter: Object, options: MongoDB.FindOneOptions, callback: MongoDB.MongoCallback): void; + findOne(filter: any, options?: any, callback?: any) { + return this._collection.findOne(filter, options, callback) as any; + } + findOneAndDelete(filter: Object, callback: MongoDB.MongoCallback>): void; + findOneAndDelete( + filter: Object, + options?: { projection?: Object; sort?: Object; maxTimeMS?: number } + ): Promise>; + findOneAndDelete( + filter: Object, + options: { projection?: Object; sort?: Object; maxTimeMS?: number }, + callback: MongoDB.MongoCallback> + ): void; + findOneAndDelete(filter: any, options?: any, callback?: any) { + return this._collection.findOneAndDelete(filter, options, callback) as any; + } + findOneAndReplace( + filter: Object, + replacement: Object, + callback: MongoDB.MongoCallback> + ): void; + findOneAndReplace( + filter: Object, + replacement: Object, + options?: MongoDB.FindOneAndReplaceOption + ): Promise>; + findOneAndReplace( + filter: Object, + replacement: Object, + options: MongoDB.FindOneAndReplaceOption, + callback: MongoDB.MongoCallback> + ): void; + findOneAndReplace(filter: any, replacement: any, options?: any, callback?: any) { + return this._collection.findOneAndReplace(filter, replacement, options, callback) as any; + } + findOneAndUpdate( + filter: Object, + update: Object, + callback: MongoDB.MongoCallback> + ): void; + findOneAndUpdate( + filter: Object, + update: Object, + options?: MongoDB.FindOneAndReplaceOption + ): Promise>; + findOneAndUpdate( + filter: Object, + update: Object, + options: MongoDB.FindOneAndReplaceOption, + callback: MongoDB.MongoCallback> + ): void; + findOneAndUpdate(filter: any, update: any, options?: any, callback?: any) { + return this._collection.findOneAndUpdate(filter, update, options, callback) as any; + } + geoHaystackSearch(x: number, y: number, callback: MongoDB.MongoCallback): void; + geoHaystackSearch(x: number, y: number, options?: MongoDB.GeoHaystackSearchOptions): Promise; + geoHaystackSearch( + x: number, + y: number, + options: MongoDB.GeoHaystackSearchOptions, + callback: MongoDB.MongoCallback + ): void; + geoHaystackSearch(x: any, y: any, options?: any, callback?: any) { + return this._collection.geoHaystackSearch(x, y, options, callback) as any; + } + geoNear(x: number, y: number, callback: MongoDB.MongoCallback): void; + geoNear(x: number, y: number, options?: MongoDB.GeoNearOptions): Promise; + geoNear(x: number, y: number, options: MongoDB.GeoNearOptions, callback: MongoDB.MongoCallback): void; + geoNear(x: any, y: any, options?: any, callback?: any) { + return this._collection.geoNear(x, y, options, callback) as any; + } + group( + keys: Object | Function | any[] | MongoDB.Code, + condition: Object, + initial: Object, + reduce: Function | MongoDB.Code, + finalize: Function | MongoDB.Code, + command: boolean, + callback: MongoDB.MongoCallback + ): void; + group( + keys: Object | Function | any[] | MongoDB.Code, + condition: Object, + initial: Object, + reduce: Function | MongoDB.Code, + finalize: Function | MongoDB.Code, + command: boolean, + options?: { readPreference?: string | MongoDB.ReadPreference } + ): Promise; + group( + keys: Object | Function | any[] | MongoDB.Code, + condition: Object, + initial: Object, + reduce: Function | MongoDB.Code, + finalize: Function | MongoDB.Code, + command: boolean, + options: { readPreference?: string | MongoDB.ReadPreference }, + callback: MongoDB.MongoCallback + ): void; + group( + keys: any, + condition: any, + initial: any, + reduce: any, + finalize: any, + command: any, + options?: any, + callback?: any + ) { + return this._collection.group(keys, condition, initial, reduce, finalize, command, options, callback) as any; + } + indexes(): Promise; + indexes(callback: MongoDB.MongoCallback): void; + indexes(callback?: any) { + return this._collection.indexes(callback) as any; + } + indexExists(indexes: string | string[]): Promise; + indexExists(indexes: string | string[], callback: MongoDB.MongoCallback): void; + indexExists(indexes: any, callback?: any) { + return this._collection.indexExists(callback) as any; + } + indexInformation(callback: MongoDB.MongoCallback): void; + indexInformation(options?: { full: boolean }): Promise; + indexInformation(options: { full: boolean }, callback: MongoDB.MongoCallback): void; + indexInformation(options?: any, callback?: any) { + return this._collection.indexInformation(options, callback) as any; + } + initializeOrderedBulkOp(options?: MongoDB.CollectionOptions): MongoDB.OrderedBulkOperation { + return this._collection.initializeOrderedBulkOp(options); + } + initializeUnorderedBulkOp(options?: MongoDB.CollectionOptions): MongoDB.UnorderedBulkOperation { + return this._collection.initializeUnorderedBulkOp(options); + } + insert(docs: Object, callback: MongoDB.MongoCallback): void; + insert(docs: Object, options?: MongoDB.CollectionInsertOneOptions): Promise; + insert( + docs: Object, + options: MongoDB.CollectionInsertOneOptions, + callback: MongoDB.MongoCallback + ): void; + insert(docs: any, options?: any, callback?: any) { + return this._collection.insert(docs, options, callback) as any; + } + insertMany(docs: Object[], callback: MongoDB.MongoCallback): void; + insertMany(docs: Object[], options?: MongoDB.CollectionInsertManyOptions): Promise; + insertMany( + docs: Object[], + options: MongoDB.CollectionInsertManyOptions, + callback: MongoDB.MongoCallback + ): void; + insertMany(docs: any, options?: any, callback?: any) { + return this._collection.insertMany(docs, options, callback) as any; + } + insertOne(docs: Object, callback: MongoDB.MongoCallback): void; + insertOne(docs: Object, options?: MongoDB.CollectionInsertOneOptions): Promise; + insertOne( + docs: Object, + options: MongoDB.CollectionInsertOneOptions, + callback: MongoDB.MongoCallback + ): void; + insertOne(docs: any, options?: any, callback?: any) { + return this._collection.insertOne(docs, options, callback) as any; + } + isCapped(): Promise; + isCapped(callback: MongoDB.MongoCallback): void; + isCapped(callback?: any) { + return this._collection.isCapped(callback) as any; + } + listIndexes(options?: { + batchSize?: number; + readPreference?: string | MongoDB.ReadPreference; + }): MongoDB.CommandCursor { + return this._collection.listIndexes(options); + } + mapReduce(map: string | Function, reduce: string | Function, callback: MongoDB.MongoCallback): void; + mapReduce(map: string | Function, reduce: string | Function, options?: MongoDB.MapReduceOptions): Promise; + mapReduce( + map: string | Function, + reduce: string | Function, + options: MongoDB.MapReduceOptions, + callback: MongoDB.MongoCallback + ): void; + mapReduce(map: any, reduce: any, options?: any, callback?: any) { + return this._collection.mapReduce(map, reduce, options, callback) as any; + } + options(): Promise; + options(callback: MongoDB.MongoCallback): void; + options(callback?: any) { + return this._collection.options(callback) as any; + } + parallelCollectionScan(callback: MongoDB.MongoCallback[]>): void; + parallelCollectionScan(options?: MongoDB.ParallelCollectionScanOptions): Promise[]>; + parallelCollectionScan( + options: MongoDB.ParallelCollectionScanOptions, + callback: MongoDB.MongoCallback[]> + ): void; + parallelCollectionScan(options?: any, callback?: any) { + return this._collection.parallelCollectionScan(options, callback) as any; + } + reIndex(): Promise; + reIndex(callback: MongoDB.MongoCallback): void; + reIndex(callback?: any) { + return this._collection.reIndex(callback) as any; + } + remove(selector: Object, callback: MongoDB.MongoCallback): void; + remove(selector: Object, options?: MongoDB.CollectionOptions & { single?: boolean }): Promise; + remove( + selector: Object, + options?: MongoDB.CollectionOptions & { single?: boolean }, + callback?: MongoDB.MongoCallback + ): void; + remove(selector: any, options?: any, callback?: any) { + return this._collection.remove(selector, options, callback) as any; + } + rename(newName: string, callback: MongoDB.MongoCallback>): void; + rename(newName: string, options?: { dropTarget?: boolean }): Promise>; + rename( + newName: string, + options: { dropTarget?: boolean }, + callback: MongoDB.MongoCallback> + ): void; + rename(newName: any, options?: any, callback?: any) { + return this._collection.rename(newName, options, callback) as any; + } + replaceOne( + filter: Object, + doc: Object, + callback: MongoDB.MongoCallback + ): void; + replaceOne( + filter: Object, + doc: Object, + options?: MongoDB.ReplaceOneOptions + ): Promise; + replaceOne( + filter: Object, + doc: Object, + options: MongoDB.ReplaceOneOptions, + callback: MongoDB.MongoCallback + ): void; + replaceOne(filter: any, doc: any, options?: any, callback?: any) { + return this._collection.replaceOne(filter, doc, options, callback) as any; + } + save(doc: Object, callback: MongoDB.MongoCallback): void; + save(doc: Object, options?: MongoDB.CollectionOptions): Promise; + save(doc: Object, options: MongoDB.CollectionOptions, callback: MongoDB.MongoCallback): void; + save(doc: any, options?: any, callback?: any) { + return this._collection.save(doc, options, callback) as any; + } + stats(callback: MongoDB.MongoCallback): void; + stats(options?: { scale: number }): Promise; + stats(options: { scale: number }, callback: MongoDB.MongoCallback): void; + stats(options?: any, callback?: any) { + return this._collection.stats(options, callback) as any; + } + update(filter: Object, update: Object, callback: MongoDB.MongoCallback): void; + update( + filter: Object, + update: Object, + options?: MongoDB.ReplaceOneOptions & { multi?: boolean } + ): Promise; + update( + filter: Object, + update: Object, + options: MongoDB.ReplaceOneOptions & { multi?: boolean }, + callback: MongoDB.MongoCallback + ): void; + update(filter: any, update: any, options?: any, callback?: any) { + return this._collection.update(filter, update, options, callback) as any; + } + updateMany(filter: Object, update: Object, callback: MongoDB.MongoCallback): void; + updateMany( + filter: Object, + update: Object, + options?: { upsert?: boolean; w?: any; wtimeout?: number; j?: boolean } + ): Promise; + updateMany( + filter: Object, + update: Object, + options: { upsert?: boolean; w?: any; wtimeout?: number; j?: boolean }, + callback: MongoDB.MongoCallback + ): void; + updateMany(filter: any, update: any, options?: any, callback?: any) { + return this._collection.updateMany(filter, update, options, callback) as any; + } + updateOne(filter: Object, update: Object, callback: MongoDB.MongoCallback): void; + updateOne(filter: Object, update: Object, options?: MongoDB.ReplaceOneOptions): Promise; + updateOne( + filter: Object, + update: Object, + options: MongoDB.ReplaceOneOptions, + callback: MongoDB.MongoCallback + ): void; + updateOne(filter: any, update: any, options?: any, callback?: any) { + return this._collection.updateMany(filter, update, options, callback) as any; + } + + /* Api */ + private _collection: MongoDB.Collection; + private connection: MongoConnection; + constructor(@inject(MongoConnection) connection: MongoConnection) { + super(); + this.connection = connection; + } + + public async init() { + const tableConfig = (getMetadata(CLASS_MONGODB_TABLECONFIGURATIONKEY, this) || [])[0]; + + const tableName = + (process.env[`${this.constructor.name}${MONGO_TABLE_NAME_SUFFIX}`] || tableConfig.collectionName) + + `-${process.env.FUNCTIONAL_STAGE}`; + + const db = await this.connection.connect(tableConfig.url); + this._collection = db.collection(tableName); + } + + public getCollection() { + return this._collection; + } +} From 952bb3899f734ed120756a7249bb445d42217b9e Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Thu, 14 Sep 2017 13:02:26 +0200 Subject: [PATCH 074/196] update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9f5475e..d13fab2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.9", + "version": "0.0.10", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From c29970d2b18393a324255026baefef2b9d2f6cc4 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Fri, 15 Sep 2017 20:58:28 +0200 Subject: [PATCH 075/196] FIX: mongodb in aws --- package-lock.json | 2 +- package.json | 2 +- src/index.ts | 3 +++ src/plugins/mongo.ts | 29 ++++++++++++++++++++++++----- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7bb9aaa..a0b7ae8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.9", + "version": "0.0.11", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d13fab2..50849d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.10", + "version": "0.0.11", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", diff --git a/src/index.ts b/src/index.ts index f2f8883..c6270f2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,9 @@ export { FunctionalService, Api, Service, PreHook, PostHook, Resource } from './ export { DynamoTable, SimpleNotificationService, S3Storage, ApiGateway } from './classes' export { MongoCollection, MongoConnection } from './plugins/mongo' +/* Hooks */ +export { NoCallbackWaitsForEmptyEventLoop } from './plugins/mongo' + /* Providers */ export { Provider, AWSProvider, LocalProvider } from './providers' export { addProvider, removeProvider } from './providers' diff --git a/src/plugins/mongo.ts b/src/plugins/mongo.ts index 3c86e30..807687f 100644 --- a/src/plugins/mongo.ts +++ b/src/plugins/mongo.ts @@ -1,7 +1,5 @@ import * as MongoDB from 'mongodb'; -import { Service } from '../classes/service'; -import { Api } from '../classes/api'; import { getMetadata, defineMetadata, @@ -13,9 +11,14 @@ import { InjectionScope, inject, simpleClassAnnotation, - classConfig + classConfig, + serviceParams, + provider } from '../annotations'; +import { Service, Api, PreHook } from '../classes'; + + export const CLASS_MONGODB_TABLECONFIGURATIONKEY = 'functionly:class:mongoTableConfiguration'; export const MONGO_TABLE_NAME_SUFFIX = '_TABLE_NAME'; export const MONGO_CONNECTION_URL = 'MONGO_CONNECTION_URL'; @@ -46,6 +49,14 @@ export const mongoCollection = (tableConfig: { collectionName: string; url?: str environmentSetter(target); }; +export class NoCallbackWaitsForEmptyEventLoop extends PreHook { + public async handle(@serviceParams p, @provider prov) { + if (prov === 'aws') { + p.context.callbackWaitsForEmptyEventLoop = false; + } + } +} + @injectable(InjectionScope.Singleton) export class MongoConnection extends Api { private _connections: Map; @@ -61,7 +72,15 @@ export class MongoConnection extends Api { const connectionUrl = url || process.env.MONGO_CONNECTION_URL || 'mongodb://localhost:27017/test'; if (this._connections.has(connectionUrl)) { - return this._connections.get(connectionUrl); + const connection = this._connections.get(connectionUrl); + if (!connection.serverConfig.isConnected()) { + this._connections.delete(connectionUrl); + if (this._connectionPromises.has(connectionUrl)) { + this._connectionPromises.delete(connectionUrl); + } + } else { + return connection; + } } if (this._connectionPromises.has(connectionUrl)) { @@ -69,7 +88,7 @@ export class MongoConnection extends Api { } const connectionPromise = MongoDB.MongoClient.connect(connectionUrl); - this._connections.set(connectionUrl, connectionPromise); + this._connectionPromises.set(connectionUrl, connectionPromise); const connection = await connectionPromise; this._connections.set(connectionUrl, connection); From 692f2632836f6c618c03c2f873b0cac59e3e8b8e Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 18 Sep 2017 16:07:57 +0200 Subject: [PATCH 076/196] FIX: imports --- src/classes/service.ts | 2 +- src/cli/providers/cloudFormation/context/resources.ts | 5 +++-- src/cli/providers/cloudFormation/context/s3Storage.ts | 9 +-------- .../cloudFormation/context/s3StorageDeployment.ts | 7 +++++++ src/cli/providers/cloudFormation/context/stack.ts | 2 +- src/providers/core/provider.ts | 4 +++- src/providers/index.ts | 2 +- 7 files changed, 17 insertions(+), 14 deletions(-) create mode 100644 src/cli/providers/cloudFormation/context/s3StorageDeployment.ts diff --git a/src/classes/service.ts b/src/classes/service.ts index 5c91dd4..5bf0188 100644 --- a/src/classes/service.ts +++ b/src/classes/service.ts @@ -1,5 +1,5 @@ import { Resource } from './resource' -import { callExtension } from '../classes' +import { callExtension } from '../classes/core/callExtension' import { constants, getMetadata, getOverridableMetadata } from '../annotations' const { PARAMETER_PARAMKEY } = constants diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index c0a3f51..b6594ed 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -6,9 +6,10 @@ const { CLASS_DESCRIPTIONKEY, CLASS_ROLEKEY, CLASS_AWSMEMORYSIZEKEY, CLASS_AWSRU import { ExecuteStep, executor } from '../../../context' import { setResource } from '../utils' import { createStack, setStackParameter, getStackName } from './stack' -import { getBucketReference } from './s3Storage' +import { getBucketReference } from './s3StorageDeployment' -export { s3DeploymentBucket, s3DeploymentBucketParameter, s3, S3_DEPLOYMENT_BUCKET_RESOURCE_NAME } from './s3Storage' +export { s3DeploymentBucket, s3DeploymentBucketParameter, s3 } from './s3Storage' +export { S3_DEPLOYMENT_BUCKET_RESOURCE_NAME } from './s3StorageDeployment' export { apiGateway } from './apiGateway' export { sns } from './sns' export { tableResources, tableSubscribers } from './dynamoTable' diff --git a/src/cli/providers/cloudFormation/context/s3Storage.ts b/src/cli/providers/cloudFormation/context/s3Storage.ts index 9ee6433..ea41f5a 100644 --- a/src/cli/providers/cloudFormation/context/s3Storage.ts +++ b/src/cli/providers/cloudFormation/context/s3Storage.ts @@ -4,16 +4,9 @@ import { ExecuteStep, executor } from '../../../context' import { collectMetadata } from '../../../utilities/collectMetadata' import { setResource } from '../utils' import { createStack, setStackParameter, getStackName } from './stack' +import { S3_DEPLOYMENT_BUCKET_RESOURCE_NAME } from './s3StorageDeployment' export const S3_STORAGE_STACK = 'S3Stack' -export const S3_DEPLOYMENT_BUCKET_RESOURCE_NAME = 'FunctionlyDeploymentBucket' - - -export const getBucketReference = async (context) => { - return context.__userAWSBucket ? context.awsBucket : { - "Ref": S3_DEPLOYMENT_BUCKET_RESOURCE_NAME - } -} export const s3DeploymentBucket = ExecuteStep.register('S3-Deployment-Bucket', async (context) => { if (context.awsBucket) { diff --git a/src/cli/providers/cloudFormation/context/s3StorageDeployment.ts b/src/cli/providers/cloudFormation/context/s3StorageDeployment.ts new file mode 100644 index 0000000..51d3032 --- /dev/null +++ b/src/cli/providers/cloudFormation/context/s3StorageDeployment.ts @@ -0,0 +1,7 @@ +export const S3_DEPLOYMENT_BUCKET_RESOURCE_NAME = 'FunctionlyDeploymentBucket' + +export const getBucketReference = async (context) => { + return context.__userAWSBucket ? context.awsBucket : { + "Ref": S3_DEPLOYMENT_BUCKET_RESOURCE_NAME + } +} \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/context/stack.ts b/src/cli/providers/cloudFormation/context/stack.ts index ab218eb..d081b7e 100644 --- a/src/cli/providers/cloudFormation/context/stack.ts +++ b/src/cli/providers/cloudFormation/context/stack.ts @@ -1,7 +1,7 @@ import { ExecuteStep, executor } from '../../../context' import { setResource, getResourceName } from '../utils' import { getFunctionName } from '../../../../annotations' -import { getBucketReference } from './s3Storage' +import { getBucketReference } from './s3StorageDeployment' import { defaultsDeep } from 'lodash' export const __createdStackNames = [] diff --git a/src/providers/core/provider.ts b/src/providers/core/provider.ts index 5eeda49..e6a7ffe 100644 --- a/src/providers/core/provider.ts +++ b/src/providers/core/provider.ts @@ -1,7 +1,9 @@ import { constants, getMetadata, getOverridableMetadata, getFunctionName } from '../../annotations' const { PARAMETER_PARAMKEY } = constants import { getMiddlewares } from '../../annotations/classes/use' -import { callExtension, PreHook, PostHook } from '../../classes' +import { callExtension } from '../../classes/core/callExtension' +import { PreHook } from '../../classes/middleware/preHook' +import { PostHook } from '../../classes/middleware/postHook' import { container } from '../../helpers/ioc' export abstract class Provider { diff --git a/src/providers/index.ts b/src/providers/index.ts index 94bb3c4..0261a62 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1,6 +1,6 @@ import { constants, getMetadata, getOverridableMetadata } from '../annotations' const { PARAMETER_PARAMKEY } = constants -import { callExtension } from '../classes' +import { callExtension } from '../classes/core/callExtension' import { container } from '../helpers/ioc' export { Provider } from './core/provider' From 71cbbace44bfe0b5e9e36fdef125a5ff183314d3 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 19 Sep 2017 15:25:07 +0200 Subject: [PATCH 077/196] ADD: IOC registerType / resolveType, unit tests --- src/helpers/ioc.ts | 31 +++- src/providers/core/provider.ts | 12 +- test/ioc.tests.ts | 295 +++++++++++++++++++++++++++++++++ 3 files changed, 325 insertions(+), 13 deletions(-) create mode 100644 test/ioc.tests.ts diff --git a/src/helpers/ioc.ts b/src/helpers/ioc.ts index a4ecaf7..eb0e434 100644 --- a/src/helpers/ioc.ts +++ b/src/helpers/ioc.ts @@ -9,37 +9,50 @@ export interface ClassFunction { export class IOC { private instances: Map, any> + private classes: Map, ClassFunction> public constructor() { this.instances = new Map, any>() + this.classes = new Map, ClassFunction>() } - public register(type: ClassFunction, instance) { + public registerType(from: ClassFunction, to: ClassFunction) { + this.classes.set(from, to) + } + + public registerInstance(type: ClassFunction, instance) { this.instances.set(type, instance) } public resolve(type: ClassFunction, ...params): T { - const scope = getOwnMetadata(CLASS_INJECTABLEKEY, type) || InjectionScope.Transient + const resolveType = this.resolveType(type) + const scope = getOwnMetadata(CLASS_INJECTABLEKEY, resolveType) || InjectionScope.Transient switch (scope) { case InjectionScope.Singleton: - if (!this.instances.has(type)) { - this.instances.set(type, new type(...params)) + if (!this.instances.has(resolveType)) { + this.instances.set(resolveType, new resolveType(...params)) } - return this.instances.get(type) + return this.instances.get(resolveType) case InjectionScope.Transient: default: - return new type(...params) + return new resolveType(...params) } } - public contains(type: ClassFunction): boolean { - const scope = getOwnMetadata(CLASS_INJECTABLEKEY, type) || InjectionScope.Transient + public containsInstance(type: ClassFunction): boolean { + const resolveType = this.resolveType(type) + const scope = getOwnMetadata(CLASS_INJECTABLEKEY, resolveType) || InjectionScope.Transient switch (scope) { case InjectionScope.Singleton: - return this.instances.has(type) + return this.instances.has(resolveType) case InjectionScope.Transient: default: return false } } + + public resolveType(type: ClassFunction) { + return this.classes.has(type) ? this.resolveType(this.classes.get(type)) : type + } + } export const container = new IOC() diff --git a/src/providers/core/provider.ts b/src/providers/core/provider.ts index e6a7ffe..ada77ed 100644 --- a/src/providers/core/provider.ts +++ b/src/providers/core/provider.ts @@ -1,5 +1,5 @@ -import { constants, getMetadata, getOverridableMetadata, getFunctionName } from '../../annotations' -const { PARAMETER_PARAMKEY } = constants +import { constants, getMetadata, getOverridableMetadata, getFunctionName, getOwnMetadata } from '../../annotations' +const { PARAMETER_PARAMKEY, CLASS_INJECTABLEKEY } = constants import { getMiddlewares } from '../../annotations/classes/use' import { callExtension } from '../../classes/core/callExtension' import { PreHook } from '../../classes/middleware/preHook' @@ -21,7 +21,7 @@ export abstract class Provider { } public async createInstance(type, context) { - if (container.contains(type)) { + if (container.containsInstance(type)) { return container.resolve(type) } @@ -123,7 +123,11 @@ export abstract class Provider { } Provider.addParameterDecoratorImplementation("inject", async (parameter, context, provider) => { - const serviceType = parameter.serviceType + const serviceType = container.resolveType(parameter.serviceType) + + if (!getOwnMetadata(CLASS_INJECTABLEKEY, serviceType)) { + throw new Error(`type '${getFunctionName(serviceType)}' not marked as injectable`) + } const staticInjectValue = await callExtension(serviceType, 'onInject', { parameter, context, provider }) if (typeof staticInjectValue !== 'undefined') { diff --git a/test/ioc.tests.ts b/test/ioc.tests.ts new file mode 100644 index 0000000..92b0f37 --- /dev/null +++ b/test/ioc.tests.ts @@ -0,0 +1,295 @@ +import { injectable, InjectionScope, inject } from '../src/annotations' +import { IOC, container } from '../src/helpers/ioc' + +import { LocalProvider } from '../src/providers/local' +import { addProvider, removeProvider } from '../src/providers' +import { FunctionalService } from '../src/classes/functionalService' +import { Api } from '../src/classes/api' +import { Service } from '../src/classes/service' + +import { expect } from 'chai' + +describe('IOC', () => { + const FUNCTIONAL_ENVIRONMENT = 'custom' + let mycontainer: IOC = null; + + beforeEach(() => { + mycontainer = new IOC(); + process.env.FUNCTIONAL_ENVIRONMENT = FUNCTIONAL_ENVIRONMENT + }) + + afterEach(() => { + delete process.env.FUNCTIONAL_ENVIRONMENT + removeProvider(FUNCTIONAL_ENVIRONMENT) + }) + + it("interface", () => { + expect(mycontainer).has.property('registerType').that.to.be.a('function') + expect(mycontainer).has.property('registerInstance').that.to.be.a('function') + expect(mycontainer).has.property('resolve').that.to.be.a('function') + expect(mycontainer).has.property('containsInstance').that.to.be.a('function') + }) + + it("InjectionScope: not marked as injectable", () => { + class MyType { } + const a = mycontainer.resolve(MyType) + expect(a).instanceof(MyType) + + const b = mycontainer.resolve(MyType) + expect(b).instanceof(MyType) + + expect(a).is.not.equal(b) + }) + + it("InjectionScope: default", () => { + @injectable() + class MyType { } + const a = mycontainer.resolve(MyType) + expect(a).instanceof(MyType) + + const b = mycontainer.resolve(MyType) + expect(b).instanceof(MyType) + + expect(a).is.not.equal(b) + }) + + it("InjectionScope: Transient", () => { + @injectable(InjectionScope.Transient) + class MyType { } + const a = mycontainer.resolve(MyType) + expect(a).instanceof(MyType) + + const b = mycontainer.resolve(MyType) + expect(b).instanceof(MyType) + + expect(a).is.not.equal(b) + }) + + it("InjectionScope: Singleton", () => { + @injectable(InjectionScope.Singleton) + class MyType { } + const a = mycontainer.resolve(MyType) + expect(a).instanceof(MyType) + + const b = mycontainer.resolve(MyType) + expect(b).instanceof(MyType) + + expect(a).is.equal(b) + }) + + it("InjectionScope: Transient, conainsInstance", () => { + @injectable(InjectionScope.Transient) + class MyType { } + const a = mycontainer.resolve(MyType) + expect(a).instanceof(MyType) + + const contains = mycontainer.containsInstance(MyType) + expect(contains).is.equal(false) + }) + + it("InjectionScope: Singleton, conainsInstance", () => { + @injectable(InjectionScope.Singleton) + class MyType { } + const a = mycontainer.resolve(MyType) + expect(a).instanceof(MyType) + + const contains = mycontainer.containsInstance(MyType) + expect(contains).is.equal(true) + }) + + it("InjectionScope: Transient, registerInstance", () => { + @injectable(InjectionScope.Transient) + class MyType { } + + const instance = new MyType() + mycontainer.registerInstance(MyType, instance) + + const a = mycontainer.resolve(MyType) + expect(a).instanceof(MyType) + + const contains = mycontainer.containsInstance(MyType) + expect(contains).is.equal(false) + + const b = mycontainer.resolve(MyType) + expect(b).instanceof(MyType) + + expect(a).is.not.equal(b) + }) + + it("InjectionScope: Singleton, registerInstance", () => { + @injectable(InjectionScope.Singleton) + class MyType { } + + const instance = new MyType() + mycontainer.registerInstance(MyType, instance) + + const a = mycontainer.resolve(MyType) + expect(a).instanceof(MyType) + + const contains = mycontainer.containsInstance(MyType) + expect(contains).is.equal(true) + + const b = mycontainer.resolve(MyType) + expect(b).instanceof(MyType) + + expect(a).is.equal(b) + }) + + it("InjectionScope: Transient, registerType Transient", () => { + @injectable(InjectionScope.Transient) + class MyTypeA { } + + @injectable(InjectionScope.Transient) + class MyTypeB { } + + mycontainer.registerType(MyTypeA, MyTypeB) + + const a = mycontainer.resolve(MyTypeA) + expect(a).instanceof(MyTypeB) + + const b = mycontainer.resolve(MyTypeA) + expect(b).instanceof(MyTypeB) + + expect(a).is.not.equal(b) + }) + + it("InjectionScope: Singleton, registerType Singleton", () => { + @injectable(InjectionScope.Singleton) + class MyTypeA { } + + @injectable(InjectionScope.Singleton) + class MyTypeB { } + + mycontainer.registerType(MyTypeA, MyTypeB) + + const a = mycontainer.resolve(MyTypeA) + expect(a).instanceof(MyTypeB) + + const b = mycontainer.resolve(MyTypeA) + expect(b).instanceof(MyTypeB) + + expect(a).is.equal(b) + }) + + it("InjectionScope: Transient, registerType Singleton", () => { + @injectable(InjectionScope.Transient) + class MyTypeA { } + + @injectable(InjectionScope.Singleton) + class MyTypeB { } + + mycontainer.registerType(MyTypeA, MyTypeB) + + const a = mycontainer.resolve(MyTypeA) + expect(a).instanceof(MyTypeB) + + const b = mycontainer.resolve(MyTypeA) + expect(b).instanceof(MyTypeB) + + expect(a).is.equal(b) + }) + + it("InjectionScope: Singleton, registerType Transient", () => { + @injectable(InjectionScope.Singleton) + class MyTypeA { } + + @injectable(InjectionScope.Transient) + class MyTypeB { } + + mycontainer.registerType(MyTypeA, MyTypeB) + + const a = mycontainer.resolve(MyTypeA) + expect(a).instanceof(MyTypeB) + + const b = mycontainer.resolve(MyTypeA) + expect(b).instanceof(MyTypeB) + + expect(a).is.not.equal(b) + }) + + it("registerType depth 3", () => { + @injectable() + class MyTypeA { } + + @injectable() + class MyTypeB { } + + @injectable() + class MyTypeC { } + + mycontainer.registerType(MyTypeA, MyTypeB) + mycontainer.registerType(MyTypeB, MyTypeC) + + const a = mycontainer.resolve(MyTypeA) + expect(a).instanceof(MyTypeC) + }) + + + + it("inject remapped api class in ioc", async () => { + let counter = 0 + + addProvider(FUNCTIONAL_ENVIRONMENT, new LocalProvider()) + + @injectable() + class A extends Api { } + + @injectable() + class AOtherApi extends Api { } + + container.registerType(A, AOtherApi) + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + + expect(a).is.instanceof(AOtherApi) + } + } + + const invoker = B.createInvoker() + await invoker({}, { + send: () => { counter++ } + }, (e) => { expect(false).to.equal(true, e.message) }) + + expect(counter).to.equal(2) + }) + + it("inject remapped service class in ioc", async () => { + let counter = 0 + + addProvider(FUNCTIONAL_ENVIRONMENT, new LocalProvider()) + + @injectable() + class A extends Service { + public async handle() { + counter++ + expect(false).to.equal(true, 'remap required') + } + } + + @injectable() + class AOtherService extends Service { + public async handle() { + counter++ + } + } + + container.registerType(A, AOtherService) + + class B extends FunctionalService { + public async handle( @inject(A) a) { + counter++ + + await a() + } + } + + const invoker = B.createInvoker() + await invoker({}, { + send: () => { counter++ } + }, (e) => { expect(false).to.equal(true, e.message) }) + + expect(counter).to.equal(3) + }) +}) \ No newline at end of file From 84fb0bed6130b2657306696885a6bcafe84000b3 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Wed, 20 Sep 2017 12:47:37 +0200 Subject: [PATCH 078/196] typo --- src/cli/providers/azureARM/index.ts | 4 ++-- src/cli/providers/cloudFormation/index.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cli/providers/azureARM/index.ts b/src/cli/providers/azureARM/index.ts index 94181c7..2d3a6b2 100644 --- a/src/cli/providers/azureARM/index.ts +++ b/src/cli/providers/azureARM/index.ts @@ -13,7 +13,7 @@ import { azureFunctions, persistAzureGithubRepo } from './context/functions' export const azure = { FUNCTIONAL_ENVIRONMENT: 'azure', createEnvironment: async (context) => { - logger.info(`Functionly: Packgaging...`) + logger.info(`Functionly: Packaging...`) await executor(context, bundle) await executor(context, zip) @@ -34,7 +34,7 @@ export const azure = { logger.info(`Functionly: Complete`) }, package: async (context) => { - logger.info(`Functionly: Packgaging...`) + logger.info(`Functionly: Packaging...`) await executor(context, bundle) await executor(context, zip) diff --git a/src/cli/providers/cloudFormation/index.ts b/src/cli/providers/cloudFormation/index.ts index d8b21c5..e438487 100644 --- a/src/cli/providers/cloudFormation/index.ts +++ b/src/cli/providers/cloudFormation/index.ts @@ -17,7 +17,7 @@ import { uploadTemplate, persistCreateTemplate } from './context/uploadTemplate' export const cloudFormation = { FUNCTIONAL_ENVIRONMENT: 'aws', createEnvironment: async (context) => { - logger.info(`Functionly: Packgaging...`) + logger.info(`Functionly: Packaging...`) await executor(context, bundle) await executor(context, zip) @@ -69,7 +69,7 @@ export const cloudFormation = { logger.info(`Functionly: Complete`) }, package: async (context) => { - logger.info(`Functionly: Packgaging...`) + logger.info(`Functionly: Packaging...`) await executor(context, bundle) await executor(context, zip) From 17505a42e3375c232827d586181520425193c18b Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Wed, 20 Sep 2017 14:12:36 +0200 Subject: [PATCH 079/196] REFACTOR: aws service integration --- .../{externals => api/aws}/apiGateway.ts | 4 +-- .../{externals => api/aws}/dynamoTable.ts | 30 ++++++++++------- .../{externals => api/aws}/s3Storage.ts | 32 +++++++++++-------- src/classes/{externals => api/aws}/sns.ts | 30 ++++++++++------- src/classes/index.ts | 8 ++--- src/index.ts | 2 +- 6 files changed, 62 insertions(+), 44 deletions(-) rename src/classes/{externals => api/aws}/apiGateway.ts (62%) rename src/classes/{externals => api/aws}/dynamoTable.ts (86%) rename src/classes/{externals => api/aws}/s3Storage.ts (77%) rename src/classes/{externals => api/aws}/sns.ts (76%) diff --git a/src/classes/externals/apiGateway.ts b/src/classes/api/aws/apiGateway.ts similarity index 62% rename from src/classes/externals/apiGateway.ts rename to src/classes/api/aws/apiGateway.ts index 76caf7c..587be3e 100644 --- a/src/classes/externals/apiGateway.ts +++ b/src/classes/api/aws/apiGateway.ts @@ -1,6 +1,6 @@ -import { Api } from '../api' -import { constants, classConfig } from '../../annotations' +import { Api } from '../../api' +import { constants, classConfig } from '../../../annotations' const { CLASS_APIGATEWAYKEY } = constants @classConfig({ diff --git a/src/classes/externals/dynamoTable.ts b/src/classes/api/aws/dynamoTable.ts similarity index 86% rename from src/classes/externals/dynamoTable.ts rename to src/classes/api/aws/dynamoTable.ts index b79b16e..fa7c81a 100644 --- a/src/classes/externals/dynamoTable.ts +++ b/src/classes/api/aws/dynamoTable.ts @@ -1,15 +1,18 @@ import * as AWS from 'aws-sdk' import { DocumentClient } from 'aws-sdk/lib/dynamodb/document_client' -import { Api } from '../api' -import { constants, getMetadata, classConfig } from '../../annotations' -import { DYNAMO_TABLE_NAME_SUFFIX } from '../../annotations/classes/dynamoTable' +import { Api } from '../../api' +import { constants, getMetadata, classConfig, inject, injectable, InjectionScope } from '../../../annotations' +import { DYNAMO_TABLE_NAME_SUFFIX } from '../../../annotations/classes/dynamoTable' const { CLASS_DYNAMOTABLECONFIGURATIONKEY } = constants -let dynamoDB = null; -const initAWSSDK = () => { - if (!dynamoDB) { +@injectable(InjectionScope.Singleton) +export class DocumentClientApi extends Api { + private dynamoDB = null + public constructor() { + super(); + let awsConfig: any = {} if (process.env.FUNCTIONAL_ENVIRONMENT === 'local') { awsConfig.apiVersion = '2012-08-10' @@ -24,9 +27,12 @@ const initAWSSDK = () => { }, null, 2)) } - dynamoDB = new AWS.DynamoDB(awsConfig); + this.dynamoDB = new AWS.DynamoDB(awsConfig); + } + + public getDocumentClient() { + return new AWS.DynamoDB.DocumentClient({ service: this.dynamoDB }) } - return dynamoDB } @classConfig({ @@ -35,11 +41,11 @@ const initAWSSDK = () => { }) export class DynamoTable extends Api { private _documentClient: DocumentClient - public constructor() { - initAWSSDK() - + public constructor(@inject(DocumentClientApi) private documentClientApi: DocumentClientApi) { super() - this._documentClient = new AWS.DynamoDB.DocumentClient({ service: dynamoDB }) + } + public async init(){ + this._documentClient = this.documentClientApi.getDocumentClient() } public getDocumentClient() { diff --git a/src/classes/externals/s3Storage.ts b/src/classes/api/aws/s3Storage.ts similarity index 77% rename from src/classes/externals/s3Storage.ts rename to src/classes/api/aws/s3Storage.ts index 4fdb6bf..330fa0f 100644 --- a/src/classes/externals/s3Storage.ts +++ b/src/classes/api/aws/s3Storage.ts @@ -1,14 +1,17 @@ import { S3 } from 'aws-sdk' -import { Api } from '../api' -import { constants, getMetadata, classConfig } from '../../annotations' -import { S3_BUCKET_SUFFIX } from '../../annotations/classes/s3Storage' +import { Api } from '../../api' +import { constants, getMetadata, classConfig, inject, injectable, InjectionScope } from '../../../annotations' +import { S3_BUCKET_SUFFIX } from '../../../annotations/classes/s3Storage' const { CLASS_S3CONFIGURATIONKEY } = constants export { S3 } from 'aws-sdk' -let s3 = null; -const initAWSSDK = () => { - if (!s3) { +@injectable(InjectionScope.Singleton) +export class S3Api extends Api { + private s3 = null + public constructor() { + super(); + let awsConfig: any = {} if (process.env.FUNCTIONAL_ENVIRONMENT === 'local') { awsConfig.apiVersion = '2006-03-01' @@ -23,9 +26,12 @@ const initAWSSDK = () => { }, null, 2)) } - s3 = new S3(awsConfig); + this.s3 = new S3(awsConfig); + } + + public getS3() { + return this.s3 } - return s3 } @classConfig({ @@ -34,17 +40,17 @@ const initAWSSDK = () => { }) export class S3Storage extends Api { private _s3Client: S3 - public constructor() { - initAWSSDK() - + public constructor(@inject(S3Api) private s3Api: S3Api) { super() - this._s3Client = s3 } + public async init(){ + this._s3Client = this.s3Api.getS3() + } + public getS3() { return this._s3Client } - public async putObject(params: Partial) { return new Promise((resolve, reject) => { if (typeof params.Key === 'string') { diff --git a/src/classes/externals/sns.ts b/src/classes/api/aws/sns.ts similarity index 76% rename from src/classes/externals/sns.ts rename to src/classes/api/aws/sns.ts index bae1e57..65c76ab 100644 --- a/src/classes/externals/sns.ts +++ b/src/classes/api/aws/sns.ts @@ -1,13 +1,16 @@ import { SNS } from 'aws-sdk' export { SNS } from 'aws-sdk' -import { Api } from '../api' -import { constants, getMetadata, classConfig } from '../../annotations' +import { Api } from '../../api' +import { constants, getMetadata, classConfig, inject, injectable, InjectionScope } from '../../../annotations' const { CLASS_SNSCONFIGURATIONKEY } = constants -let sns = null; -const initAWSSDK = () => { - if (!sns) { +@injectable(InjectionScope.Singleton) +export class SNSApi extends Api { + private sns = null + public constructor() { + super(); + let awsConfig: any = {} if (process.env.FUNCTIONAL_ENVIRONMENT === 'local') { awsConfig.apiVersion = '2010-03-31' @@ -22,9 +25,12 @@ const initAWSSDK = () => { }, null, 2)) } - sns = new SNS(awsConfig); + this.sns = new SNS(awsConfig); + } + + public getSNS() { + return this.sns } - return sns } @classConfig({ @@ -33,17 +39,17 @@ const initAWSSDK = () => { }) export class SimpleNotificationService extends Api { private _snsClient: SNS - public constructor() { - initAWSSDK() - + public constructor(@inject(SNSApi) private snsApi: SNSApi) { super() - this._snsClient = sns } + public async init(){ + this._snsClient = this.snsApi.getSNS() + } + public getSNS() { return this._snsClient } - public async publish(params: SNS.PublishInput) { return new Promise((resolve, reject) => { this._snsClient.publish(this.setDefaultValues(params, 'publish'), (err, result) => { diff --git a/src/classes/index.ts b/src/classes/index.ts index 5d83e2d..3c55c80 100644 --- a/src/classes/index.ts +++ b/src/classes/index.ts @@ -1,10 +1,10 @@ export { Resource } from './resource' export { Api } from './api' export { FunctionalService } from './functionalService' -export { DynamoTable } from './externals/dynamoTable' -export { SimpleNotificationService } from './externals/sns' -export { S3Storage } from './externals/s3Storage' -export { ApiGateway } from './externals/apiGateway' +export { DynamoTable, DocumentClientApi } from './api/aws/dynamoTable' +export { SimpleNotificationService, SNSApi } from './api/aws/sns' +export { S3Storage, S3Api } from './api/aws/s3Storage' +export { ApiGateway } from './api/aws/apiGateway' export { callExtension } from './core/callExtension' export { Service } from './service' diff --git a/src/index.ts b/src/index.ts index c6270f2..fc2abe5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ export { FunctionalService, Api, Service, PreHook, PostHook, Resource } from './classes' /* Apis */ -export { DynamoTable, SimpleNotificationService, S3Storage, ApiGateway } from './classes' +export { DynamoTable, DocumentClientApi, SimpleNotificationService, SNSApi, S3Storage, S3Api, ApiGateway } from './classes' export { MongoCollection, MongoConnection } from './plugins/mongo' /* Hooks */ From db963c4d384e43899284a0e482f33a857c20353f Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Wed, 27 Sep 2017 12:18:25 +0200 Subject: [PATCH 080/196] update version --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index a0b7ae8..f96138b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.11", + "version": "0.0.12", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 50849d2..d8b9e8e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.11", + "version": "0.0.12", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 8e8a47c38fd5376a25133f8a2a3041ccd615c95e Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Wed, 27 Sep 2017 12:22:36 +0200 Subject: [PATCH 081/196] CHANGE: exports --- package-lock.json | 2 +- package.json | 2 +- src/index.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index f96138b..d6d08ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.12", + "version": "0.0.13", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d8b9e8e..4cc550b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.12", + "version": "0.0.13", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", diff --git a/src/index.ts b/src/index.ts index fc2abe5..e410cf1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,3 +30,4 @@ export { templates, applyTemplates, getFunctionName, __dynamoDBDefaults, getClassConfigValue, simpleClassAnnotation, expandableDecorator, createParameterDecorator, constants, defineMetadata, getMetadata, getMetadataKeys, getOwnMetadata, getOverridableMetadata } from './annotations' + export { container, IOC } from './helpers/ioc' \ No newline at end of file From ea459fea9361a6b35d461a2f8af54908963161c7 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 2 Oct 2017 13:32:21 +0200 Subject: [PATCH 082/196] ADD: s3Storage api getObject; CHANGE: api gateway string result --- package.json | 2 +- src/classes/api/aws/s3Storage.ts | 15 +++++++++++++++ src/providers/aws/eventSources/apiGateway.ts | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4cc550b..8caffff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.13", + "version": "0.0.14", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", diff --git a/src/classes/api/aws/s3Storage.ts b/src/classes/api/aws/s3Storage.ts index 330fa0f..d6ca96f 100644 --- a/src/classes/api/aws/s3Storage.ts +++ b/src/classes/api/aws/s3Storage.ts @@ -66,6 +66,21 @@ export class S3Storage extends Api { }) } + public async getObject(params: Partial) { + return new Promise((resolve, reject) => { + if (typeof params.Key === 'string') { + params = { + ...params, + Key: /^\//.test(params.Key) ? params.Key.substring(1, params.Key.length) : params.Key + } + } + this._s3Client.getObject(this.setDefaultValues(params, 'getObject'), (err, result) => { + if (err) reject(err) + else resolve(result) + }) + }) + } + protected setDefaultValues(params, command) { const initParams = { Bucket: process.env[`${this.constructor.name}${S3_BUCKET_SUFFIX}`] + `-${process.env.FUNCTIONAL_STAGE}` diff --git a/src/providers/aws/eventSources/apiGateway.ts b/src/providers/aws/eventSources/apiGateway.ts index 9e1b054..62d840a 100644 --- a/src/providers/aws/eventSources/apiGateway.ts +++ b/src/providers/aws/eventSources/apiGateway.ts @@ -68,7 +68,7 @@ export class ApiGateway extends EventSource { return { statusCode: result.status, headers: { ...headers, ...result.headers }, - data: JSON.stringify(result.data) + data: typeof result.data === 'string' ? result.data : JSON.stringify(result.data) } } From fe4605a08c1fd52f0de70996b2122aa77eed2f75 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 2 Oct 2017 14:03:11 +0200 Subject: [PATCH 083/196] fix: api gateway --- package-lock.json | 2 +- package.json | 2 +- src/providers/aws/eventSources/apiGateway.ts | 2 +- test/invoker.tests.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index d6d08ce..d109e3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.13", + "version": "0.0.15", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 8caffff..8d85e0a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.14", + "version": "0.0.15", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", diff --git a/src/providers/aws/eventSources/apiGateway.ts b/src/providers/aws/eventSources/apiGateway.ts index 62d840a..8e6816a 100644 --- a/src/providers/aws/eventSources/apiGateway.ts +++ b/src/providers/aws/eventSources/apiGateway.ts @@ -68,7 +68,7 @@ export class ApiGateway extends EventSource { return { statusCode: result.status, headers: { ...headers, ...result.headers }, - data: typeof result.data === 'string' ? result.data : JSON.stringify(result.data) + body: typeof result.data === 'string' ? result.data : JSON.stringify(result.data) } } diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index bf86601..b88f527 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -1751,7 +1751,7 @@ describe('invoker', () => { headers: { 'content-type': 'application/json' }, - data: JSON.stringify({ + body: JSON.stringify({ ok: 1 }) }) From caa7e8e66f8a2cdd7f8cf62c40ed2ee0e3ea421a Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Wed, 4 Oct 2017 16:12:49 +0200 Subject: [PATCH 084/196] CHANGE: aws api local url default; CHANGE: s3 default roles --- package-lock.json | 2 +- package.json | 2 +- src/classes/api/aws/dynamoTable.ts | 2 +- src/classes/api/aws/s3Storage.ts | 2 +- src/classes/api/aws/sns.ts | 2 +- src/cli/providers/cloudFormation/context/s3Storage.ts | 1 + 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index d109e3b..74ff578 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.15", + "version": "0.0.16", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 8d85e0a..46d5f9c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.15", + "version": "0.0.16", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", diff --git a/src/classes/api/aws/dynamoTable.ts b/src/classes/api/aws/dynamoTable.ts index fa7c81a..5335ea6 100644 --- a/src/classes/api/aws/dynamoTable.ts +++ b/src/classes/api/aws/dynamoTable.ts @@ -17,7 +17,7 @@ export class DocumentClientApi extends Api { if (process.env.FUNCTIONAL_ENVIRONMENT === 'local') { awsConfig.apiVersion = '2012-08-10' awsConfig.region = process.env.AWS_REGION || 'eu-central-1' - awsConfig.endpoint = process.env.DYNAMODB_LOCAL_ENDPOINT || 'http://localhost:8000' + awsConfig.endpoint = 'DYNAMODB_LOCAL_ENDPOINT' in process.env ? process.env.DYNAMODB_LOCAL_ENDPOINT : 'http://localhost:8000' console.log('Local DynamoDB configuration') console.log(JSON.stringify({ diff --git a/src/classes/api/aws/s3Storage.ts b/src/classes/api/aws/s3Storage.ts index d6ca96f..b30269a 100644 --- a/src/classes/api/aws/s3Storage.ts +++ b/src/classes/api/aws/s3Storage.ts @@ -16,7 +16,7 @@ export class S3Api extends Api { if (process.env.FUNCTIONAL_ENVIRONMENT === 'local') { awsConfig.apiVersion = '2006-03-01' awsConfig.region = process.env.AWS_REGION || 'eu-central-1' - awsConfig.endpoint = process.env.S3_LOCAL_ENDPOINT || 'http://localhost:4572' + awsConfig.endpoint = 'S3_LOCAL_ENDPOINT' in process.env ? process.env.S3_LOCAL_ENDPOINT : 'http://localhost:4572' console.log('Local S3 configuration') console.log(JSON.stringify({ diff --git a/src/classes/api/aws/sns.ts b/src/classes/api/aws/sns.ts index 65c76ab..95316cf 100644 --- a/src/classes/api/aws/sns.ts +++ b/src/classes/api/aws/sns.ts @@ -15,7 +15,7 @@ export class SNSApi extends Api { if (process.env.FUNCTIONAL_ENVIRONMENT === 'local') { awsConfig.apiVersion = '2010-03-31' awsConfig.region = process.env.AWS_REGION || 'eu-central-1' - awsConfig.endpoint = process.env.SNS_LOCAL_ENDPOINT || 'http://localhost:4100' + awsConfig.endpoint = 'SNS_LOCAL_ENDPOINT' in process.env ? process.env.SNS_LOCAL_ENDPOINT : 'http://localhost:4100' console.log('Local SNS configuration') console.log(JSON.stringify({ diff --git a/src/cli/providers/cloudFormation/context/s3Storage.ts b/src/cli/providers/cloudFormation/context/s3Storage.ts index ea41f5a..a7dc89b 100644 --- a/src/cli/providers/cloudFormation/context/s3Storage.ts +++ b/src/cli/providers/cloudFormation/context/s3Storage.ts @@ -122,6 +122,7 @@ export const s3StoragePolicy = async (context) => { "Statement": [{ "Effect": "Allow", "Action": [ + "s3:GetObject", "s3:PutObject", "s3:PutObjectAcl", "s3:PutObjectTagging", From 82ae59ddde5686dc6274f94d5d50d4b7c4fc9f7d Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Wed, 4 Oct 2017 16:29:32 +0200 Subject: [PATCH 085/196] CHANGE: s3 api init --- package-lock.json | 2 +- package.json | 2 +- src/classes/api/aws/dynamoTable.ts | 2 ++ src/classes/api/aws/s3Storage.ts | 2 ++ src/classes/api/aws/sns.ts | 2 ++ 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 74ff578..1773d74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.16", + "version": "0.0.17", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 46d5f9c..99d50fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.16", + "version": "0.0.17", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", diff --git a/src/classes/api/aws/dynamoTable.ts b/src/classes/api/aws/dynamoTable.ts index 5335ea6..dc15054 100644 --- a/src/classes/api/aws/dynamoTable.ts +++ b/src/classes/api/aws/dynamoTable.ts @@ -12,7 +12,9 @@ export class DocumentClientApi extends Api { private dynamoDB = null public constructor() { super(); + } + public async init() { let awsConfig: any = {} if (process.env.FUNCTIONAL_ENVIRONMENT === 'local') { awsConfig.apiVersion = '2012-08-10' diff --git a/src/classes/api/aws/s3Storage.ts b/src/classes/api/aws/s3Storage.ts index b30269a..8c58747 100644 --- a/src/classes/api/aws/s3Storage.ts +++ b/src/classes/api/aws/s3Storage.ts @@ -11,7 +11,9 @@ export class S3Api extends Api { private s3 = null public constructor() { super(); + } + public async init() { let awsConfig: any = {} if (process.env.FUNCTIONAL_ENVIRONMENT === 'local') { awsConfig.apiVersion = '2006-03-01' diff --git a/src/classes/api/aws/sns.ts b/src/classes/api/aws/sns.ts index 95316cf..8aff4c1 100644 --- a/src/classes/api/aws/sns.ts +++ b/src/classes/api/aws/sns.ts @@ -10,7 +10,9 @@ export class SNSApi extends Api { private sns = null public constructor() { super(); + } + public async init() { let awsConfig: any = {} if (process.env.FUNCTIONAL_ENVIRONMENT === 'local') { awsConfig.apiVersion = '2010-03-31' From c9d0f3f3ad2cec6ea9912a213705c258661101a7 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 16 Oct 2017 10:59:07 +0200 Subject: [PATCH 086/196] CHANGE: local post payload size --- src/cli/commands/local.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/commands/local.ts b/src/cli/commands/local.ts index 1697c32..bc75caa 100644 --- a/src/cli/commands/local.ts +++ b/src/cli/commands/local.ts @@ -26,7 +26,7 @@ export default ({ createContext, annotations: { getMetadata, constants, getFunct const startLocal = async (context) => { let app = express() - app.use(bodyParser.json()) + app.use(bodyParser.json({ limit: '10mb' })) for (let serviceDefinition of context.publishedFunctions) { let httpMetadata = getMetadata(rest.environmentKey, serviceDefinition.service) || [] From a1735a4b07b15db4318f396beaa95619e3a200df Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Mon, 16 Oct 2017 13:23:16 +0200 Subject: [PATCH 087/196] ADD: display stack output values after deploy to aws --- src/cli/providers/cloudFormation/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/cli/providers/cloudFormation/index.ts b/src/cli/providers/cloudFormation/index.ts index e438487..5f1851f 100644 --- a/src/cli/providers/cloudFormation/index.ts +++ b/src/cli/providers/cloudFormation/index.ts @@ -65,8 +65,13 @@ export const cloudFormation = { logger.info(`Functionly: Uploading template...`) await executor(context, uploadTemplate) logger.info(`Functionly: Updating stack...`) - await executor(context, updateStack) + const stackData = await executor(context, updateStack) logger.info(`Functionly: Complete`) + + logger.info('') + for (const {OutputKey, OutputValue} of stackData.Outputs) { + logger.info(`${OutputKey}: ${OutputValue}`) + } }, package: async (context) => { logger.info(`Functionly: Packaging...`) From 60e3b38fd1832543a78ea4bdd4122f512b6ccf70 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 17 Oct 2017 10:36:57 +0200 Subject: [PATCH 088/196] FIX: injected Service/Api metadata --- package.json | 2 +- src/classes/api.ts | 16 +- src/classes/service.ts | 14 ++ src/cli/context/steppes/tableDiscovery.ts | 8 +- src/cli/utilities/collectMetadata.ts | 35 +--- test/annotation.tests.ts | 223 ++++++++++++++++++++++ 6 files changed, 253 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index 99d50fc..eb8241c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.17", + "version": "0.0.18", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", diff --git a/src/classes/api.ts b/src/classes/api.ts index 91ba8dc..4291ff3 100644 --- a/src/classes/api.ts +++ b/src/classes/api.ts @@ -1,17 +1,15 @@ import { Resource } from './resource' import { getFunctionName } from '../annotations/classes/functionName' import { defineMetadata, getMetadata, constants, getClassConfigValue } from '../annotations' -const { CLASS_ENVIRONMENTKEY, CLASS_CLASSCONFIGKEY } = constants +const { CLASS_ENVIRONMENTKEY, CLASS_CLASSCONFIGKEY, PARAMETER_PARAMKEY } = constants export class Api extends Resource { constructor(...params) { super(); } public async init(): Promise { - - } - public static ConfigEnvironmentKey: string + } public static onDefineInjectTo(target, targetKey, parameterIndex: number) { super.onDefineInjectTo(target, targetKey, parameterIndex) @@ -23,6 +21,16 @@ export class Api extends Resource { const keyConfig = getMetadata(configEnvironmentKey, target) || [] defineMetadata(configEnvironmentKey, [...keyConfig, ...injectKeyConfig], target); } + + const targetTkey = 'handle' + const injectedDefinitions: any[] = (getMetadata(PARAMETER_PARAMKEY, this) || []) + .filter(p => p.type === 'inject') + + for (const { serviceType, targetKey, parameterIndex } of injectedDefinitions) { + if (typeof serviceType.onDefineInjectTo === 'function') { + serviceType.onDefineInjectTo(target, targetTkey, parameterIndex) + } + } } public static toEventSource(target: Function) { diff --git a/src/classes/service.ts b/src/classes/service.ts index 5bf0188..39fa8f9 100644 --- a/src/classes/service.ts +++ b/src/classes/service.ts @@ -38,4 +38,18 @@ export class Service extends Resource { const service = container.resolve(this) return (...params) => service.invoke(...params) } + + public static onDefineInjectTo(target, targetKey, parameterIndex: number) { + super.onDefineInjectTo(target, targetKey, parameterIndex) + + const targetTkey = 'handle' + const injectedDefinitions: any[] = (getMetadata(PARAMETER_PARAMKEY, this, targetTkey) || []) + .filter(p => p.type === 'inject') + + for (const { serviceType, targetKey, parameterIndex } of injectedDefinitions) { + if (typeof serviceType.onDefineInjectTo === 'function') { + serviceType.onDefineInjectTo(target, targetTkey, parameterIndex) + } + } + } } \ No newline at end of file diff --git a/src/cli/context/steppes/tableDiscovery.ts b/src/cli/context/steppes/tableDiscovery.ts index 00f3758..fd66975 100644 --- a/src/cli/context/steppes/tableDiscovery.ts +++ b/src/cli/context/steppes/tableDiscovery.ts @@ -1,4 +1,4 @@ -import { getMetadata, constants, __dynamoDBDefaults } from '../../../annotations' +import { getMetadata, constants } from '../../../annotations' import { DYNAMO_TABLE_NAME_SUFFIX } from '../../../annotations/classes/dynamoTable' import { ExecuteStep } from '../core/executeStep' import { collectMetadata } from '../../utilities/collectMetadata' @@ -8,11 +8,7 @@ export class TableDiscoveryStep extends ExecuteStep { public async method(context) { context.tableConfigs = collectMetadata(context, { metadataKey: CLASS_DYNAMOTABLECONFIGURATIONKEY, - selector: (c) => c.tableName, - environmentRegexp: new RegExp(`${DYNAMO_TABLE_NAME_SUFFIX}$`), - keyProperty: 'environmentKey', - valueProperty: 'tableName', - defaultServiceConfig: { nativeConfig: __dynamoDBDefaults } + selector: (c) => c.tableName }) } } diff --git a/src/cli/utilities/collectMetadata.ts b/src/cli/utilities/collectMetadata.ts index fe11c3e..e96aa37 100644 --- a/src/cli/utilities/collectMetadata.ts +++ b/src/cli/utilities/collectMetadata.ts @@ -3,11 +3,7 @@ const { CLASS_ENVIRONMENTKEY } = constants export const collectMetadata = (context, config: { metadataKey?: string, - selector?: (c) => string, - environmentRegexp?: RegExp, - keyProperty?: string, - valueProperty?: string, - defaultServiceConfig?: { [key: string]: any } + selector?: (c) => string }) => { const result = new Map() for (const serviceDefinition of context.publishedFunctions) { @@ -29,35 +25,6 @@ export const collectMetadata = (context, config: { }) } } - - if (config.environmentRegexp && config.keyProperty && config.valueProperty) { - let metadata = getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) - if (metadata) { - let keys = Object.keys(metadata) - for (const key of keys) { - const hash = metadata[key] - if (config.environmentRegexp.test(key)) { - const serviceConfig = { - ...(config.defaultServiceConfig || {}), - [config.keyProperty]: key, - [config.valueProperty]: hash - } - - if (result.has(hash)) { - const item = result.get(hash) - item.services.push({ serviceDefinition, serviceConfig }) - continue - } - - result.set(hash, { - ...serviceConfig, - hash, - services: [{ serviceDefinition, serviceConfig }] - }) - } - } - } - } } return Array.from(result.values()) diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index 3ae05a6..76daf5e 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -1565,4 +1565,227 @@ describe('annotations', () => { }) }) }) + + describe("service inject", () => { + it("environment param", () => { + + @environment('p1', 'v1') + @injectable() + class Service1 extends Service { + } + + class FSTest1 extends FunctionalService { + public async handle( @inject(Service1) s1) { } + } + + const environmentMetadata = getMetadata(CLASS_ENVIRONMENTKEY, FSTest1) + + expect(environmentMetadata).to.have.property('p1', 'v1') + }) + + it("dynamo param", () => { + @dynamoTable({ tableName: 't1', nativeConfig: { any: 'value' } }) + @injectable() + class DTable1 extends DynamoTable { } + + @injectable() + class Service1 extends Service { + public async handle( @inject(DTable1) table1) { } + } + + class FSTest1 extends FunctionalService { + public async handle( @inject(Service1) s1) { } + } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, FSTest1) + + expect(value).to.be.not.undefined; + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('tableName', 't1') + expect(metadata).to.have.property('nativeConfig').to.have.property('any', 'value') + }) + + it("s3 param", () => { + @s3Storage({ bucketName: 's3-bucket' }) + @injectable() + class S3Storage1 extends S3Storage { } + + @injectable() + class Service1 extends Service { + public async handle( @inject(S3Storage1) s1) { } + } + + class FSTest1 extends FunctionalService { + public async handle( @inject(Service1) s1) { } + } + + const value = getMetadata(CLASS_S3CONFIGURATIONKEY, FSTest1) + + expect(value).to.be.not.undefined; + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('bucketName', 's3-bucket') + }) + + it("dynamo param from api", () => { + @dynamoTable({ tableName: 't1', nativeConfig: { any: 'value' } }) + @injectable() + class DTable1 extends DynamoTable { } + + @injectable() + class Api1 extends Api { + public constructor( @inject(DTable1) private table1) { super() } + } + + @injectable() + class Service1 extends Service { + public async handle( @inject(Api1) a1) { } + } + + class FSTest1 extends FunctionalService { + public async handle( @inject(Service1) s1) { } + } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, FSTest1) + + expect(value).to.be.not.undefined; + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('tableName', 't1') + expect(metadata).to.have.property('nativeConfig').to.have.property('any', 'value') + }) + + it("dynamo param from api and service", () => { + @dynamoTable({ tableName: 't1', nativeConfig: { any: 'value' } }) + @injectable() + class DTable1 extends DynamoTable { } + + @injectable() + class Api1 extends Api { + public constructor( @inject(DTable1) private table1) { super() } + } + + @injectable() + class Service1 extends Service { + public async handle( @inject(Api1) a1, @inject(DTable1) table1) { } + } + + class FSTest1 extends FunctionalService { + public async handle( @inject(Service1) s1) { } + } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, FSTest1) + + expect(value).to.be.not.undefined; + expect(value).to.have.lengthOf(2); + + const metadata1 = value[0] + + expect(metadata1).to.have.property('tableName', 't1') + expect(metadata1).to.have.property('nativeConfig').to.have.property('any', 'value') + + const metadata2 = value[1] + + expect(metadata2).to.have.property('tableName', 't1') + expect(metadata2).to.have.property('nativeConfig').to.have.property('any', 'value') + }) + + it("dynamo param from api and service different", () => { + @dynamoTable({ tableName: 't1', nativeConfig: { any: 'value1' } }) + @injectable() + class DTable1 extends DynamoTable { } + + @dynamoTable({ tableName: 't2', nativeConfig: { any: 'value2' } }) + @injectable() + class DTable2 extends DynamoTable { } + + @injectable() + class Api1 extends Api { + public constructor( @inject(DTable1) private table1) { super() } + } + + @injectable() + class Service1 extends Service { + public async handle( @inject(Api1) a1, @inject(DTable2) table2) { } + } + + class FSTest1 extends FunctionalService { + public async handle( @inject(Service1) s1) { } + } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, FSTest1) + + expect(value).to.be.not.undefined; + expect(value).to.have.lengthOf(2); + + const metadata1 = value[0] + expect(metadata1).to.have.property('tableName', 't2') + expect(metadata1).to.have.property('nativeConfig').to.have.property('any', 'value2') + + const metadata2 = value[1] + expect(metadata2).to.have.property('tableName', 't1') + expect(metadata2).to.have.property('nativeConfig').to.have.property('any', 'value1') + }) + + it("environment variable chain", () => { + @environment('p1', 'v1') + @injectable() + class Api2 extends Api { } + + @injectable() + @environment('p2', 'v2') + class Api1 extends Api { + public constructor( @inject(Api2) private a2) { super() } + } + + @injectable() + @environment('p3', 'v3') + class Service1 extends Service { + public async handle( @inject(Api1) a1) { } + } + + @environment('p4', 'v4') + class FSTest1 extends FunctionalService { + public async handle( @inject(Service1) s1) { } + } + + + const api2Env = getMetadata(CLASS_ENVIRONMENTKEY, Api2) + expect(api2Env).to.have.all.keys('p1'); + expect(api2Env).to.deep.equal({ + p1: 'v1', + }) + + const api1Env = getMetadata(CLASS_ENVIRONMENTKEY, Api1) + expect(api1Env).to.have.all.keys('p1', 'p2'); + expect(api1Env).to.deep.equal({ + p1: 'v1', + p2: 'v2', + }) + + const service1Env = getMetadata(CLASS_ENVIRONMENTKEY, Service1) + expect(service1Env).to.have.all.keys('p1', 'p2', 'p3'); + expect(service1Env).to.deep.equal({ + p1: 'v1', + p2: 'v2', + p3: 'v3', + }) + + const fsTest1Env = getMetadata(CLASS_ENVIRONMENTKEY, FSTest1) + expect(fsTest1Env).to.have.all.keys('p1', 'p2', 'p3', 'p4'); + expect(fsTest1Env).to.deep.equal({ + p1: 'v1', + p2: 'v2', + p3: 'v3', + p4: 'v4', + }) + }) + }) }) \ No newline at end of file From 199f8350b2887ce381e632016d56f7509e682767 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Tue, 14 Nov 2017 16:06:38 +0100 Subject: [PATCH 089/196] FIX: mongo reconnect --- package-lock.json | 2 +- package.json | 2 +- src/plugins/mongo.ts | 13 +++++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1773d74..da8d25f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.17", + "version": "0.0.19", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index eb8241c..d9d4b70 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.18", + "version": "0.0.19", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", diff --git a/src/plugins/mongo.ts b/src/plugins/mongo.ts index 807687f..4e26b36 100644 --- a/src/plugins/mongo.ts +++ b/src/plugins/mongo.ts @@ -90,10 +90,15 @@ export class MongoConnection extends Api { const connectionPromise = MongoDB.MongoClient.connect(connectionUrl); this._connectionPromises.set(connectionUrl, connectionPromise); - const connection = await connectionPromise; - this._connections.set(connectionUrl, connection); - - return connection; + try { + const connection = await connectionPromise; + this._connections.set(connectionUrl, connection); + + return connection; + } catch (e) { + this._connectionPromises.delete(connectionUrl); + throw e + } } } From f299caa495e3726510f05bb925459d5ae54cfdb9 Mon Sep 17 00:00:00 2001 From: Borza Viktor Date: Thu, 23 Nov 2017 10:13:39 +0100 Subject: [PATCH 090/196] FIX: info text --- src/classes/api/aws/dynamoTable.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/classes/api/aws/dynamoTable.ts b/src/classes/api/aws/dynamoTable.ts index dc15054..fd8fc83 100644 --- a/src/classes/api/aws/dynamoTable.ts +++ b/src/classes/api/aws/dynamoTable.ts @@ -25,7 +25,7 @@ export class DocumentClientApi extends Api { console.log(JSON.stringify({ apiVersion: awsConfig.apiVersion, 'region (process.env.AWS_REGION)': awsConfig.region, - 'endpoint (process.env.SNS_LOCAL_ENDPOINT)': awsConfig.endpoint, + 'endpoint (process.env.DYNAMODB_LOCAL_ENDPOINT)': awsConfig.endpoint, }, null, 2)) } From 70554d07a4b298efb74d5e3e2142a9dcee413779 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Thu, 23 Nov 2017 12:22:32 +0100 Subject: [PATCH 091/196] ADD: mocha-junit-reporter --- package-lock.json | 75 ++++++++++++++++++++++++++++++++++++----------- package.json | 1 + 2 files changed, 59 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index da8d25f..fb75b56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -477,6 +477,12 @@ "type-detect": "4.0.3" } }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=", + "dev": true + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -639,6 +645,12 @@ "sha.js": "2.4.8" } }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=", + "dev": true + }, "cryptiles": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", @@ -1723,6 +1735,17 @@ "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" }, + "md5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", + "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", + "dev": true, + "requires": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "1.1.5" + } + }, "md5.js": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", @@ -1899,6 +1922,18 @@ } } }, + "mocha-junit-reporter": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-1.15.0.tgz", + "integrity": "sha1-MJ9LeiD82ibQrWnJt9CAjXcjAsI=", + "dev": true, + "requires": { + "debug": "2.6.8", + "md5": "2.2.1", + "mkdirp": "0.5.1", + "xml": "1.0.1" + } + }, "mongodb": { "version": "2.2.31", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.31.tgz", @@ -2484,15 +2519,6 @@ } } }, - "require_optional": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", - "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", - "requires": { - "resolve-from": "2.0.0", - "semver": "5.4.1" - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -2503,6 +2529,15 @@ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, + "require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "requires": { + "resolve-from": "2.0.0", + "semver": "5.4.1" + } + }, "resolve": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", @@ -2699,14 +2734,6 @@ "xtend": "4.0.1" } }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "5.1.1" - } - }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -2717,6 +2744,14 @@ "strip-ansi": "3.0.1" } }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + }, "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", @@ -3057,6 +3092,12 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", + "dev": true + }, "xml2js": { "version": "0.4.17", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", diff --git a/package.json b/package.json index d9d4b70..5ef3b43 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "chai": "^4.0.2", "istanbul": "^0.4.5", "mocha": "^3.4.2", + "mocha-junit-reporter": "^1.15.0", "typescript": "^2.3.0" }, "dependencies": { From f8bb99dc4fdec56cc54d02e43060bb843ecef6fd Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Thu, 23 Nov 2017 12:41:37 +0100 Subject: [PATCH 092/196] ADD: cobertura report to coverage --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ef3b43..88a2c00 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "test": "npm run build && mocha --recursive lib/test/**/*.tests.js", "test:fast": "mocha --recursive lib/test/**/*.tests.js", "metadata": "node ./lib/src/cli/cli.js metadata aws --", - "coverage": "node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha -- -R spec lib/test/**/*.tests.js" + "coverage": "node_modules/.bin/istanbul cover --report cobertura ./node_modules/mocha/bin/_mocha -- -R spec lib/test/**/*.tests.js" }, "repository": { "type": "git", From 85317998e664e48f51129b6a23df0c762f6172fc Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Thu, 23 Nov 2017 12:07:46 +0000 Subject: [PATCH 093/196] 0.0.20 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index fb75b56..e67193e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.19", + "version": "0.0.20", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 88a2c00..e793bcb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.19", + "version": "0.0.20", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 4b8006fec8e91d57bef5bc4ac9ee2c84a10ef943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tibor=20=C3=93tott-Kov=C3=A1cs?= Date: Thu, 23 Nov 2017 13:12:56 +0100 Subject: [PATCH 094/196] Update .npmrc --- .npmrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.npmrc b/.npmrc index bc9dcc1..d511702 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ -@types:registry=https://registry.npmjs.org \ No newline at end of file +@types:registry=https://registry.npmjs.org +//SECRET From 21543d21d725a8f625b4b51fec5a850e7b738de5 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Thu, 23 Nov 2017 12:15:57 +0000 Subject: [PATCH 095/196] 0.0.21 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e67193e..b9be4d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.20", + "version": "0.0.21", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e793bcb..2505b33 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.20", + "version": "0.0.21", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 01a77195887b7421048208ceeba055ea7b7e7adc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tibor=20=C3=93tott-Kov=C3=A1cs?= Date: Thu, 23 Nov 2017 13:23:52 +0100 Subject: [PATCH 096/196] Update .gitignore --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e69be72..5b293df 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,7 @@ __test/**/* typings -.vscode \ No newline at end of file +.vscode + +test-results.xml +version.txt From d857c0de828c2993a12bd6475967e42f15f06ff9 Mon Sep 17 00:00:00 2001 From: borzav Date: Thu, 7 Dec 2017 10:19:50 +0100 Subject: [PATCH 097/196] ADD: group decorator --- package-lock.json | 796 ++++++++++++++++++ src/annotations/classes/use.ts | 10 +- src/annotations/constants.ts | 1 + src/annotations/index.ts | 3 +- .../providers/cloudFormation/context/stack.ts | 9 +- src/index.ts | 2 +- 6 files changed, 811 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index b9be4d3..609e4a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -496,6 +496,7 @@ "requires": { "anymatch": "1.3.2", "async-each": "1.0.1", + "fsevents": "1.1.3", "glob-parent": "2.0.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -1078,6 +1079,795 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "fsevents": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", + "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", + "optional": true, + "requires": { + "nan": "2.8.0", + "node-pre-gyp": "0.6.39" + }, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "bundled": true, + "optional": true + }, + "ajv": { + "version": "4.11.8", + "bundled": true, + "optional": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "optional": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.2.9" + } + }, + "asn1": { + "version": "0.2.3", + "bundled": true, + "optional": true + }, + "assert-plus": { + "version": "0.2.0", + "bundled": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "bundled": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true, + "optional": true + }, + "balanced-match": { + "version": "0.4.2", + "bundled": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "requires": { + "inherits": "2.0.3" + } + }, + "boom": { + "version": "2.10.1", + "bundled": true, + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.7", + "bundled": true, + "requires": { + "balanced-match": "0.4.2", + "concat-map": "0.0.1" + } + }, + "buffer-shims": { + "version": "1.0.0", + "bundled": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true, + "optional": true + }, + "co": { + "version": "4.6.0", + "bundled": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "requires": { + "boom": "2.10.1" + } + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "optional": true + } + } + }, + "debug": { + "version": "2.6.8", + "bundled": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "bundled": true, + "optional": true + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "extend": { + "version": "3.0.1", + "bundled": true, + "optional": true + }, + "extsprintf": { + "version": "1.0.2", + "bundled": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "optional": true + }, + "form-data": { + "version": "2.1.4", + "bundled": true, + "optional": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.15" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "fstream": { + "version": "1.0.11", + "bundled": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.1" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "bundled": true, + "optional": true, + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "optional": true, + "requires": { + "aproba": "1.1.1", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "optional": true + } + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true + }, + "har-schema": { + "version": "1.0.5", + "bundled": true, + "optional": true + }, + "har-validator": { + "version": "4.2.1", + "bundled": true, + "optional": true, + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "hawk": { + "version": "3.1.3", + "bundled": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "bundled": true + }, + "http-signature": { + "version": "1.1.1", + "bundled": true, + "optional": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.0", + "sshpk": "1.13.0" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.4", + "bundled": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "optional": true + }, + "jodid25519": { + "version": "1.0.2", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "optional": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "bundled": true, + "optional": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "optional": true + }, + "jsonify": { + "version": "0.0.0", + "bundled": true, + "optional": true + }, + "jsprim": { + "version": "1.4.0", + "bundled": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.0.2", + "json-schema": "0.2.3", + "verror": "1.3.6" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "optional": true + } + } + }, + "mime-db": { + "version": "1.27.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.15", + "bundled": true, + "requires": { + "mime-db": "1.27.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "node-pre-gyp": { + "version": "0.6.39", + "bundled": true, + "optional": true, + "requires": { + "detect-libc": "1.0.2", + "hawk": "3.1.3", + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.0", + "rc": "1.2.1", + "request": "2.81.0", + "rimraf": "2.6.1", + "semver": "5.3.0", + "tar": "2.2.1", + "tar-pack": "3.4.0" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "optional": true, + "requires": { + "abbrev": "1.1.0", + "osenv": "0.1.4" + } + }, + "npmlog": { + "version": "4.1.0", + "bundled": true, + "optional": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "osenv": { + "version": "0.1.4", + "bundled": true, + "optional": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "performance-now": { + "version": "0.2.0", + "bundled": true, + "optional": true + }, + "process-nextick-args": { + "version": "1.0.7", + "bundled": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "optional": true + }, + "qs": { + "version": "6.4.0", + "bundled": true, + "optional": true + }, + "rc": { + "version": "1.2.1", + "bundled": true, + "optional": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.2.9", + "bundled": true, + "requires": { + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "1.0.1", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.81.0", + "bundled": true, + "optional": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.15", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.0.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.2", + "tunnel-agent": "0.6.0", + "uuid": "3.0.1" + } + }, + "rimraf": { + "version": "2.6.1", + "bundled": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.0.1", + "bundled": true + }, + "semver": { + "version": "5.3.0", + "bundled": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "optional": true + }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "requires": { + "hoek": "2.16.3" + } + }, + "sshpk": { + "version": "1.13.0", + "bundled": true, + "optional": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jodid25519": "1.0.2", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "optional": true + } + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "stringstream": { + "version": "0.0.5", + "bundled": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-pack": { + "version": "3.4.0", + "bundled": true, + "optional": true, + "requires": { + "debug": "2.6.8", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.2.9", + "rimraf": "2.6.1", + "tar": "2.2.1", + "uid-number": "0.0.6" + } + }, + "tough-cookie": { + "version": "2.3.2", + "bundled": true, + "optional": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "optional": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "uuid": { + "version": "3.0.1", + "bundled": true, + "optional": true + }, + "verror": { + "version": "1.3.6", + "bundled": true, + "optional": true, + "requires": { + "extsprintf": "1.0.2" + } + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "optional": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + } + } + }, "get-caller-file": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", @@ -1974,6 +2764,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "nan": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", + "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=", + "optional": true + }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", diff --git a/src/annotations/classes/use.ts b/src/annotations/classes/use.ts index 1a94f5f..b162109 100644 --- a/src/annotations/classes/use.ts +++ b/src/annotations/classes/use.ts @@ -2,14 +2,14 @@ import { CLASS_MIDDLEWAREKEY } from '../constants' import { getMetadata, defineMetadata } from '../metadata' import { applyTemplates } from '../templates' -export const use = (...middlewares) => { +export const use = (...middleware) => { return (target: Function) => { const metadata = getMiddlewares(target) - defineMetadata(CLASS_MIDDLEWAREKEY, [...middlewares, ...metadata], target); + defineMetadata(CLASS_MIDDLEWAREKEY, [...middleware, ...metadata], target); - for (const middleware of middlewares) { - if (typeof middleware.onDefineMiddlewareTo === 'function') { - middleware.onDefineMiddlewareTo(target) + for (const mw of middleware) { + if (typeof mw.onDefineMiddlewareTo === 'function') { + mw.onDefineMiddlewareTo(target) } } } diff --git a/src/annotations/constants.ts b/src/annotations/constants.ts index b76c387..5bbe85c 100644 --- a/src/annotations/constants.ts +++ b/src/annotations/constants.ts @@ -3,6 +3,7 @@ export const CLASS_KEY_PREFIX = 'functionly:class:' export const PARAMETER_PARAMKEY = 'functionly:param:parameters' export const CLASS_DESCRIPTIONKEY = 'functionly:class:description' export const CLASS_ROLEKEY = 'functionly:class:role' +export const CLASS_GROUP = 'functionly:class:group' export const CLASS_ENVIRONMENTKEY = 'functionly:class:environment' export const CLASS_MIDDLEWAREKEY = 'functionly:class:middleware' export const CLASS_APIGATEWAYKEY = 'functionly:class:apigateway' diff --git a/src/annotations/index.ts b/src/annotations/index.ts index bc307e7..8f24b72 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -18,9 +18,10 @@ export { use } from './classes/use' import { simpleClassAnnotation } from './classes/simpleAnnotation' -import { CLASS_DESCRIPTIONKEY, CLASS_ROLEKEY } from './constants' +import { CLASS_DESCRIPTIONKEY, CLASS_ROLEKEY, CLASS_GROUP } from './constants' export const description = simpleClassAnnotation(CLASS_DESCRIPTIONKEY) export const role = simpleClassAnnotation(CLASS_ROLEKEY) +export const group = simpleClassAnnotation(CLASS_GROUP) export { aws } from './classes/aws/aws' diff --git a/src/cli/providers/cloudFormation/context/stack.ts b/src/cli/providers/cloudFormation/context/stack.ts index d081b7e..ed81eb2 100644 --- a/src/cli/providers/cloudFormation/context/stack.ts +++ b/src/cli/providers/cloudFormation/context/stack.ts @@ -1,7 +1,8 @@ import { ExecuteStep, executor } from '../../../context' import { setResource, getResourceName } from '../utils' -import { getFunctionName } from '../../../../annotations' +import { getFunctionName, getMetadata, constants } from '../../../../annotations' import { getBucketReference } from './s3StorageDeployment' +const { CLASS_GROUP } = constants import { defaultsDeep } from 'lodash' export const __createdStackNames = [] @@ -9,6 +10,8 @@ export const __createdStackNames = [] export const createStack = async (context) => { const { stackName } = context + if (__createdStackNames.indexOf(stackName) >= 0) return + __createdStackNames.push(stackName) context.CloudFormationStacks[stackName] = defaultsDeep({ "AWSTemplateFormatVersion": "2010-09-09", @@ -109,6 +112,6 @@ export const setStackParameter = (context) => { } export const getStackName = (serviceDefinition) => { - const resourceName = getFunctionName(serviceDefinition.service) - return getResourceName(`Stack${resourceName}`) + const groupName = getMetadata(CLASS_GROUP, serviceDefinition.service) || getFunctionName(serviceDefinition.service) + return getResourceName(`Stack${groupName}`) } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index e410cf1..51b3edc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,7 @@ export { addProvider, removeProvider } from './providers' /* Decorators */ export { injectable, apiGateway, httpTrigger, rest, httpGet, httpPost, httpPut, httpPatch, httpDelete, IHttpMethod, environment, - tag, log, functionName, dynamoTable, sns, s3Storage, eventSource, classConfig, use, description, role, aws, azure, + tag, log, functionName, dynamoTable, sns, s3Storage, eventSource, classConfig, use, description, role, group, aws, azure, param, serviceParams, request, error, result, functionalServiceName, provider, stage, inject } from './annotations' export { mongoCollection, mongoConnection } from './plugins/mongo' From c3abe1013850a6f8eb1bda9af10f21784488582d Mon Sep 17 00:00:00 2001 From: borzav Date: Fri, 8 Dec 2017 13:52:57 +0100 Subject: [PATCH 098/196] FIX: environmentKey usage; ADD tests --- src/annotations/classes/dynamoTable.ts | 10 +- src/annotations/classes/s3Storage.ts | 6 +- src/annotations/classes/sns.ts | 10 +- src/classes/api/aws/dynamoTable.ts | 13 +- src/classes/api/aws/s3Storage.ts | 6 +- src/cli/context/steppes/tableDiscovery.ts | 1 - src/helpers/ioc.ts | 5 + test/annotation.tests.ts | 88 ++++ test/api.tests.ts | 584 ++++++++++++++++++++++ test/ioc.tests.ts | 33 +- 10 files changed, 733 insertions(+), 23 deletions(-) create mode 100644 test/api.tests.ts diff --git a/src/annotations/classes/dynamoTable.ts b/src/annotations/classes/dynamoTable.ts index 4d6167f..edd908c 100644 --- a/src/annotations/classes/dynamoTable.ts +++ b/src/annotations/classes/dynamoTable.ts @@ -3,8 +3,6 @@ import { getMetadata, defineMetadata } from '../metadata' import { applyTemplates } from '../templates' import { environment } from './environment' -export const DYNAMO_TABLE_NAME_SUFFIX = '_TABLE_NAME' - export const __dynamoDBDefaults = { AttributeDefinitions: [ { @@ -24,14 +22,16 @@ export const __dynamoDBDefaults = { } } -export const dynamoTable = (tableConfig: { - tableName: string, +export const dynamoTable = (tableConfig?: { + tableName?: string, environmentKey?: string, nativeConfig?: any }) => (target: Function) => { let tableDefinitions = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, target) || []; - tableConfig.environmentKey = tableConfig.environmentKey || `%ClassName%${DYNAMO_TABLE_NAME_SUFFIX}` + tableConfig = tableConfig || {} + tableConfig.tableName = tableConfig.tableName || `%ClassName%-table` + tableConfig.environmentKey = tableConfig.environmentKey || `%ClassName%_TABLE_NAME` tableConfig.nativeConfig = { ...__dynamoDBDefaults, ...tableConfig.nativeConfig } const { templatedKey, templatedValue } = applyTemplates(tableConfig.environmentKey, tableConfig.tableName, target) diff --git a/src/annotations/classes/s3Storage.ts b/src/annotations/classes/s3Storage.ts index 08d16ca..15079d1 100644 --- a/src/annotations/classes/s3Storage.ts +++ b/src/annotations/classes/s3Storage.ts @@ -6,8 +6,8 @@ import { environment } from './environment' export const S3_BUCKET_SUFFIX = '_S3_BUCKET' export const S3_BUCKET_NAME_REGEXP = /^[a-z0-9][a-z0-9-.]{1,61}[a-z0-9]$/ -export const s3Storage = (s3Config: { - bucketName: string, +export const s3Storage = (s3Config?: { + bucketName?: string, environmentKey?: string, eventSourceConfiguration?: { Event?: any, @@ -16,6 +16,8 @@ export const s3Storage = (s3Config: { }) => (target: Function) => { let s3Definitions = getMetadata(CLASS_S3CONFIGURATIONKEY, target) || []; + s3Config = s3Config || {} + s3Config.bucketName = s3Config.bucketName || `%ClassName%-bucket` s3Config.environmentKey = s3Config.environmentKey || `%ClassName%${S3_BUCKET_SUFFIX}` const { templatedKey, templatedValue } = applyTemplates(s3Config.environmentKey, s3Config.bucketName, target) diff --git a/src/annotations/classes/sns.ts b/src/annotations/classes/sns.ts index 74e99a1..d96c5d3 100644 --- a/src/annotations/classes/sns.ts +++ b/src/annotations/classes/sns.ts @@ -3,15 +3,15 @@ import { getMetadata, defineMetadata } from '../metadata' import { applyTemplates } from '../templates' import { environment } from './environment' -export const SNS_TOPICNAME_SUFFIX = '_SNS_TOPICNAME' - -export const sns = (snsConfig: { - topicName: string, +export const sns = (snsConfig?: { + topicName?: string, environmentKey?: string }) => (target: Function) => { let snsDefinitions = getMetadata(CLASS_SNSCONFIGURATIONKEY, target) || []; - snsConfig.environmentKey = snsConfig.environmentKey || `%ClassName%${SNS_TOPICNAME_SUFFIX}` + snsConfig = snsConfig || {} + snsConfig.topicName = snsConfig.topicName || `%ClassName%-topic` + snsConfig.environmentKey = snsConfig.environmentKey || `%ClassName%_SNS_TOPICNAME` const { templatedKey, templatedValue } = applyTemplates(snsConfig.environmentKey, snsConfig.topicName, target) snsDefinitions.push({ diff --git a/src/classes/api/aws/dynamoTable.ts b/src/classes/api/aws/dynamoTable.ts index fd8fc83..1dd2509 100644 --- a/src/classes/api/aws/dynamoTable.ts +++ b/src/classes/api/aws/dynamoTable.ts @@ -3,7 +3,6 @@ import { DocumentClient } from 'aws-sdk/lib/dynamodb/document_client' import { Api } from '../../api' import { constants, getMetadata, classConfig, inject, injectable, InjectionScope } from '../../../annotations' -import { DYNAMO_TABLE_NAME_SUFFIX } from '../../../annotations/classes/dynamoTable' const { CLASS_DYNAMOTABLECONFIGURATIONKEY } = constants @@ -43,10 +42,10 @@ export class DocumentClientApi extends Api { }) export class DynamoTable extends Api { private _documentClient: DocumentClient - public constructor(@inject(DocumentClientApi) private documentClientApi: DocumentClientApi) { + public constructor( @inject(DocumentClientApi) private documentClientApi: DocumentClientApi) { super() } - public async init(){ + public async init() { this._documentClient = this.documentClientApi.getDocumentClient() } @@ -124,12 +123,12 @@ export class DynamoTable extends Api { } protected setDefaultValues(params, command) { - const tableConfig = (getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, this) || [])[0] - const tableName = ({ TableName: tableConfig && tableConfig.tableName, ...tableConfig.nativeConfig }).TableName + const tableConfig = (getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, this) || [])[0] || {} + const tableName = ({ TableName: tableConfig.tableName, ...tableConfig.nativeConfig }).TableName - const calcTableName = process.env[`${this.constructor.name}${DYNAMO_TABLE_NAME_SUFFIX}`] + `-${process.env.FUNCTIONAL_STAGE}` + const calcTableName = tableConfig.environmentKey && process.env[tableConfig.environmentKey] ? process.env[tableConfig.environmentKey] : '' const initParams = { - TableName: calcTableName || tableName + TableName: (calcTableName || tableName) + `-${process.env.FUNCTIONAL_STAGE}` } return { ...initParams, ...params } diff --git a/src/classes/api/aws/s3Storage.ts b/src/classes/api/aws/s3Storage.ts index 8c58747..033bf46 100644 --- a/src/classes/api/aws/s3Storage.ts +++ b/src/classes/api/aws/s3Storage.ts @@ -84,8 +84,12 @@ export class S3Storage extends Api { } protected setDefaultValues(params, command) { + const bucketConfig = (getMetadata(CLASS_S3CONFIGURATIONKEY, this) || [])[0] || {} + const bucketName = bucketConfig.bucketName + + const calcBucketName = bucketConfig.environmentKey && process.env[bucketConfig.environmentKey] ? process.env[bucketConfig.environmentKey] : '' const initParams = { - Bucket: process.env[`${this.constructor.name}${S3_BUCKET_SUFFIX}`] + `-${process.env.FUNCTIONAL_STAGE}` + Bucket: (calcBucketName || bucketName) + `-${process.env.FUNCTIONAL_STAGE}` } return { ...initParams, ...params } diff --git a/src/cli/context/steppes/tableDiscovery.ts b/src/cli/context/steppes/tableDiscovery.ts index fd66975..21aff7a 100644 --- a/src/cli/context/steppes/tableDiscovery.ts +++ b/src/cli/context/steppes/tableDiscovery.ts @@ -1,5 +1,4 @@ import { getMetadata, constants } from '../../../annotations' -import { DYNAMO_TABLE_NAME_SUFFIX } from '../../../annotations/classes/dynamoTable' import { ExecuteStep } from '../core/executeStep' import { collectMetadata } from '../../utilities/collectMetadata' const { CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_ENVIRONMENTKEY } = constants diff --git a/src/helpers/ioc.ts b/src/helpers/ioc.ts index eb0e434..b4d48e5 100644 --- a/src/helpers/ioc.ts +++ b/src/helpers/ioc.ts @@ -19,6 +19,11 @@ export class IOC { this.classes.set(from, to) } + public clearType(type: ClassFunction) { + if (this.classes.has(type)) + this.classes.delete(type) + } + public registerInstance(type: ClassFunction, instance) { this.instances.set(type, instance) } diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index 76daf5e..92d0b76 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -545,6 +545,36 @@ describe('annotations', () => { }) }) describe("dynamoTable", () => { + it("no param", () => { + @dynamoTable() + class DynamoTableTestClass { } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, DynamoTableTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('tableName', 'DynamoTableTestClass-table') + expect(metadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') + expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); + }) + it("empty", () => { + @dynamoTable({}) + class DynamoTableTestClass { } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, DynamoTableTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('tableName', 'DynamoTableTestClass-table') + expect(metadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') + expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); + }) it("tableName", () => { @dynamoTable({ tableName: 'mytablename' }) class DynamoTableTestClass { } @@ -722,6 +752,36 @@ describe('annotations', () => { }) }) describe("s3Storage", () => { + it("no param", () => { + @s3Storage() + class S3StorageTestClass { } + + const value = getMetadata(CLASS_S3CONFIGURATIONKEY, S3StorageTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('bucketName', 's3storagetestclass-bucket') + expect(metadata).to.have.property('environmentKey', 'S3StorageTestClass_S3_BUCKET') + expect(metadata).to.have.property('definedBy', S3StorageTestClass.name) + expect(metadata).to.not.have.property('eventSourceConfiguration') + }) + it("empty", () => { + @s3Storage({}) + class S3StorageTestClass { } + + const value = getMetadata(CLASS_S3CONFIGURATIONKEY, S3StorageTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('bucketName', 's3storagetestclass-bucket') + expect(metadata).to.have.property('environmentKey', 'S3StorageTestClass_S3_BUCKET') + expect(metadata).to.have.property('definedBy', S3StorageTestClass.name) + expect(metadata).to.not.have.property('eventSourceConfiguration') + }) it("bucketName", () => { @s3Storage({ bucketName: 'mybucketname' }) class S3StorageTestClass { } @@ -794,6 +854,34 @@ describe('annotations', () => { }) }) describe("sns", () => { + it("no parm", () => { + @sns() + class SNSTestClass { } + + const value = getMetadata(CLASS_SNSCONFIGURATIONKEY, SNSTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('topicName', 'SNSTestClass-topic') + expect(metadata).to.have.property('environmentKey', 'SNSTestClass_SNS_TOPICNAME') + expect(metadata).to.have.property('definedBy', SNSTestClass.name) + }) + it("topicName", () => { + @sns({}) + class SNSTestClass { } + + const value = getMetadata(CLASS_SNSCONFIGURATIONKEY, SNSTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('topicName', 'SNSTestClass-topic') + expect(metadata).to.have.property('environmentKey', 'SNSTestClass_SNS_TOPICNAME') + expect(metadata).to.have.property('definedBy', SNSTestClass.name) + }) it("topicName", () => { @sns({ topicName: 'myTopicName' }) class SNSTestClass { } diff --git a/test/api.tests.ts b/test/api.tests.ts new file mode 100644 index 0000000..2a983b7 --- /dev/null +++ b/test/api.tests.ts @@ -0,0 +1,584 @@ +import { expect } from 'chai' +import { FunctionalService, DynamoTable, DocumentClientApi, S3Storage, S3Api } from '../src/classes' +import { injectable, dynamoTable, inject, s3Storage } from '../src/annotations' +import { container } from '../src//helpers/ioc' + +describe('api', () => { + describe('DynamoTable', () => { + afterEach(() => { + delete process.env.FUNCTIONAL_ENVIRONMENT + delete process.env.FUNCTIONAL_STAGE + delete process.env.DTClass_TABLE_NAME + delete process.env.DTCLASS_ENV_NAME + container.clearType(DocumentClientApi) + }) + + it("empty table name", (done) => { + let counter = 0 + + @injectable() + @dynamoTable() + class DTClass extends DynamoTable { } + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'customstage' + process.env.DTClass_TABLE_NAME = 'DTClass-table' + class Home extends FunctionalService { + public async handle( @inject(DTClass) a: DTClass) { + counter++ + + await a.put({ + Item: { _id: 1 } + }) + + return { ok: 1 } + } + } + + @injectable() + class DCL extends DocumentClientApi { + public async init() { } + public getDocumentClient() { + return { + put(params, cb) { + counter++ + try { + expect(params).to.deep.equal({ + Item: { _id: 1 }, + TableName: `DTClass-table-customstage` + }) + cb() + } catch (e) { + cb(e) + } + } + } + } + } + + container.registerType(DocumentClientApi, DCL) + + const invoker = Home.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + .catch((e) => { e && done(e) }) + }) + + it("empty table name no env", (done) => { + let counter = 0 + + @injectable() + @dynamoTable() + class DTClass extends DynamoTable { } + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'customstage' + class Home extends FunctionalService { + public async handle( @inject(DTClass) a: DTClass) { + counter++ + + await a.put({ + Item: { _id: 1 } + }) + + return { ok: 1 } + } + } + + @injectable() + class DCL extends DocumentClientApi { + public async init() { } + public getDocumentClient() { + return { + put(params, cb) { + counter++ + try { + expect(params).to.deep.equal({ + Item: { _id: 1 }, + TableName: `DTClass-table-customstage` + }) + cb() + } catch (e) { + cb(e) + } + } + } + } + } + + container.registerType(DocumentClientApi, DCL) + + const invoker = Home.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + .catch((e) => { e && done(e) }) + }) + + it("empty table name different env", (done) => { + let counter = 0 + + @injectable() + @dynamoTable() + class DTClass extends DynamoTable { } + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'customstage' + process.env.DTClass_TABLE_NAME = 'DTClass-table-other' + class Home extends FunctionalService { + public async handle( @inject(DTClass) a: DTClass) { + counter++ + + await a.put({ + Item: { _id: 1 } + }) + + return { ok: 1 } + } + } + + @injectable() + class DCL extends DocumentClientApi { + public async init() { } + public getDocumentClient() { + return { + put(params, cb) { + counter++ + try { + expect(params).to.deep.equal({ + Item: { _id: 1 }, + TableName: `DTClass-table-other-customstage` + }) + cb() + } catch (e) { + cb(e) + } + } + } + } + } + + container.registerType(DocumentClientApi, DCL) + + const invoker = Home.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + .catch((e) => { e && done(e) }) + }) + + it("custom table name", (done) => { + let counter = 0 + + @injectable() + @dynamoTable({ tableName: 'ATable' }) + class DTClass extends DynamoTable { } + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'customstage' + process.env.DTClass_TABLE_NAME = 'ATable' + class Home extends FunctionalService { + public async handle( @inject(DTClass) a: DTClass) { + counter++ + + await a.put({ + Item: { _id: 1 } + }) + + return { ok: 1 } + } + } + + @injectable() + class DCL extends DocumentClientApi { + public async init() { } + public getDocumentClient() { + return { + put(params, cb) { + counter++ + try { + expect(params).to.deep.equal({ + Item: { _id: 1 }, + TableName: `ATable-customstage` + }) + cb() + } catch (e) { + cb(e) + } + } + } + } + } + + container.registerType(DocumentClientApi, DCL) + + const invoker = Home.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + .catch((e) => { e && done(e) }) + }) + + it("custom environmentKey", (done) => { + let counter = 0 + + @injectable() + @dynamoTable({ environmentKey: 'DTCLASS_ENV_NAME' }) + class DTClass extends DynamoTable { } + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'customstage' + process.env.DTCLASS_ENV_NAME = 'DTClass-table' + class Home extends FunctionalService { + public async handle( @inject(DTClass) a: DTClass) { + counter++ + + await a.put({ + Item: { _id: 1 } + }) + + return { ok: 1 } + } + } + + @injectable() + class DCL extends DocumentClientApi { + public async init() { } + public getDocumentClient() { + return { + put(params, cb) { + counter++ + try { + expect(params).to.deep.equal({ + Item: { _id: 1 }, + TableName: `DTClass-table-customstage` + }) + cb() + } catch (e) { + cb(e) + } + } + } + } + } + + container.registerType(DocumentClientApi, DCL) + + const invoker = Home.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + .catch((e) => { e && done(e) }) + }) + }) + + describe('S3Storage', () => { + afterEach(() => { + delete process.env.FUNCTIONAL_ENVIRONMENT + delete process.env.FUNCTIONAL_STAGE + delete process.env.S3StorageClass_S3_BUCKET + delete process.env.S3StorageClass_ENV_NAME + container.clearType(S3Api) + }) + + it("empty table name", (done) => { + let counter = 0 + + @injectable() + @s3Storage() + class S3StorageClass extends S3Storage { } + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'customstage' + process.env.S3StorageClass_S3_BUCKET = 's3storageclass-bucket' + class Home extends FunctionalService { + public async handle( @inject(S3StorageClass) a: S3StorageClass) { + counter++ + + await a.getObject({ + Key: 'a' + }) + + return { ok: 1 } + } + } + + @injectable() + class S3A extends S3Api { + public async init() { } + public getS3() { + return { + getObject(params, cb) { + counter++ + try { + expect(params).to.deep.equal({ + Key: 'a', + Bucket: 's3storageclass-bucket-customstage' + }) + cb() + } catch (e) { + cb(e) + } + } + } + } + } + + container.registerType(S3Api, S3A) + + const invoker = Home.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + .catch((e) => { e && done(e) }) + }) + + it("empty table name no env", (done) => { + let counter = 0 + + @injectable() + @s3Storage() + class S3StorageClass extends S3Storage { } + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'customstage' + class Home extends FunctionalService { + public async handle( @inject(S3StorageClass) a: S3StorageClass) { + counter++ + + await a.getObject({ + Key: 'a' + }) + + return { ok: 1 } + } + } + + @injectable() + class S3A extends S3Api { + public async init() { } + public getS3() { + return { + getObject(params, cb) { + counter++ + try { + expect(params).to.deep.equal({ + Key: 'a', + Bucket: 's3storageclass-bucket-customstage' + }) + cb() + } catch (e) { + cb(e) + } + } + } + } + } + + container.registerType(S3Api, S3A) + + const invoker = Home.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + .catch((e) => { e && done(e) }) + }) + + it("empty table name different env", (done) => { + let counter = 0 + + @injectable() + @s3Storage() + class S3StorageClass extends S3Storage { } + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'customstage' + process.env.S3StorageClass_S3_BUCKET = 's3storageclass-bucket-custom' + class Home extends FunctionalService { + public async handle( @inject(S3StorageClass) a: S3StorageClass) { + counter++ + + await a.getObject({ + Key: 'a' + }) + + return { ok: 1 } + } + } + + @injectable() + class S3A extends S3Api { + public async init() { } + public getS3() { + return { + getObject(params, cb) { + counter++ + try { + expect(params).to.deep.equal({ + Key: 'a', + Bucket: 's3storageclass-bucket-custom-customstage' + }) + cb() + } catch (e) { + cb(e) + } + } + } + } + } + + container.registerType(S3Api, S3A) + + const invoker = Home.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + .catch((e) => { e && done(e) }) + }) + + it("custom table name", (done) => { + let counter = 0 + + @injectable() + @s3Storage({ bucketName: 'custom-name' }) + class S3StorageClass extends S3Storage { } + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'customstage' + process.env.S3StorageClass_S3_BUCKET = 'custom-name' + class Home extends FunctionalService { + public async handle( @inject(S3StorageClass) a: S3StorageClass) { + counter++ + + await a.getObject({ + Key: 'a' + }) + + return { ok: 1 } + } + } + + @injectable() + class S3A extends S3Api { + public async init() { } + public getS3() { + return { + getObject(params, cb) { + counter++ + try { + expect(params).to.deep.equal({ + Key: 'a', + Bucket: 'custom-name-customstage' + }) + cb() + } catch (e) { + cb(e) + } + } + } + } + } + + container.registerType(S3Api, S3A) + + const invoker = Home.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + .catch((e) => { e && done(e) }) + }) + + it("custom environmentKey", (done) => { + let counter = 0 + + @injectable() + @s3Storage({ environmentKey: 'S3StorageClass_ENV_NAME' }) + class S3StorageClass extends S3Storage { } + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'customstage' + process.env.S3StorageClass_ENV_NAME = 's3storageclass-other' + class Home extends FunctionalService { + public async handle( @inject(S3StorageClass) a: S3StorageClass) { + counter++ + + await a.getObject({ + Key: 'a' + }) + + return { ok: 1 } + } + } + + @injectable() + class S3A extends S3Api { + public async init() { } + public getS3() { + return { + getObject(params, cb) { + counter++ + try { + expect(params).to.deep.equal({ + Key: 'a', + Bucket: 's3storageclass-other-customstage' + }) + cb() + } catch (e) { + cb(e) + } + } + } + } + } + + container.registerType(S3Api, S3A) + + const invoker = Home.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + .catch((e) => { e && done(e) }) + }) + }) +}) \ No newline at end of file diff --git a/test/ioc.tests.ts b/test/ioc.tests.ts index 92b0f37..15fcdfa 100644 --- a/test/ioc.tests.ts +++ b/test/ioc.tests.ts @@ -25,11 +25,40 @@ describe('IOC', () => { it("interface", () => { expect(mycontainer).has.property('registerType').that.to.be.a('function') + expect(mycontainer).has.property('clearType').that.to.be.a('function') + expect(mycontainer).has.property('resolveType').that.to.be.a('function') expect(mycontainer).has.property('registerInstance').that.to.be.a('function') expect(mycontainer).has.property('resolve').that.to.be.a('function') expect(mycontainer).has.property('containsInstance').that.to.be.a('function') }) + it("resolveType", () => { + class MyType { } + const a = mycontainer.resolveType(MyType) + expect(a).to.equal(MyType) + }) + + it("registerType", () => { + class MyType1 { } + class MyType2 { } + mycontainer.registerType(MyType1, MyType2) + const a = mycontainer.resolveType(MyType1) + expect(a).to.equal(MyType2) + }) + + it("clearType", () => { + class MyType1 { } + class MyType2 { } + mycontainer.registerType(MyType1, MyType2) + const a = mycontainer.resolveType(MyType1) + expect(a).to.equal(MyType2) + + mycontainer.clearType(MyType1) + + const b = mycontainer.resolveType(MyType1) + expect(b).to.equal(MyType1) + }) + it("InjectionScope: not marked as injectable", () => { class MyType { } const a = mycontainer.resolve(MyType) @@ -265,12 +294,12 @@ describe('IOC', () => { public async handle() { counter++ expect(false).to.equal(true, 'remap required') - } + } } @injectable() class AOtherService extends Service { - public async handle() { + public async handle() { counter++ } } From e1dee857880bace63f0510c3ae83f8920cbcc470 Mon Sep 17 00:00:00 2001 From: borzav Date: Wed, 13 Dec 2017 10:48:11 +0100 Subject: [PATCH 099/196] ADD: use existing DynamoTable and S3Storage --- src/annotations/classes/dynamoTable.ts | 3 +- src/annotations/classes/s3Storage.ts | 3 +- src/classes/api/aws/dynamoTable.ts | 3 +- src/classes/api/aws/s3Storage.ts | 3 +- .../cloudFormation/context/dynamoTable.ts | 15 ++++-- .../cloudFormation/context/resources.ts | 2 +- .../cloudFormation/context/s3Storage.ts | 50 +++++++++++-------- 7 files changed, 51 insertions(+), 28 deletions(-) diff --git a/src/annotations/classes/dynamoTable.ts b/src/annotations/classes/dynamoTable.ts index edd908c..261806f 100644 --- a/src/annotations/classes/dynamoTable.ts +++ b/src/annotations/classes/dynamoTable.ts @@ -25,7 +25,8 @@ export const __dynamoDBDefaults = { export const dynamoTable = (tableConfig?: { tableName?: string, environmentKey?: string, - nativeConfig?: any + nativeConfig?: any, + exists?: boolean }) => (target: Function) => { let tableDefinitions = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, target) || []; diff --git a/src/annotations/classes/s3Storage.ts b/src/annotations/classes/s3Storage.ts index 15079d1..7612615 100644 --- a/src/annotations/classes/s3Storage.ts +++ b/src/annotations/classes/s3Storage.ts @@ -12,7 +12,8 @@ export const s3Storage = (s3Config?: { eventSourceConfiguration?: { Event?: any, Filter?: any - } + }, + exists?: boolean }) => (target: Function) => { let s3Definitions = getMetadata(CLASS_S3CONFIGURATIONKEY, target) || []; diff --git a/src/classes/api/aws/dynamoTable.ts b/src/classes/api/aws/dynamoTable.ts index 1dd2509..fc5ba41 100644 --- a/src/classes/api/aws/dynamoTable.ts +++ b/src/classes/api/aws/dynamoTable.ts @@ -127,8 +127,9 @@ export class DynamoTable extends Api { const tableName = ({ TableName: tableConfig.tableName, ...tableConfig.nativeConfig }).TableName const calcTableName = tableConfig.environmentKey && process.env[tableConfig.environmentKey] ? process.env[tableConfig.environmentKey] : '' + const suffix = tableConfig.exists ? '' : `-${process.env.FUNCTIONAL_STAGE}` const initParams = { - TableName: (calcTableName || tableName) + `-${process.env.FUNCTIONAL_STAGE}` + TableName: (calcTableName || tableName) + suffix } return { ...initParams, ...params } diff --git a/src/classes/api/aws/s3Storage.ts b/src/classes/api/aws/s3Storage.ts index 033bf46..783619c 100644 --- a/src/classes/api/aws/s3Storage.ts +++ b/src/classes/api/aws/s3Storage.ts @@ -88,8 +88,9 @@ export class S3Storage extends Api { const bucketName = bucketConfig.bucketName const calcBucketName = bucketConfig.environmentKey && process.env[bucketConfig.environmentKey] ? process.env[bucketConfig.environmentKey] : '' + const suffix = bucketConfig.exists ? '' : `-${process.env.FUNCTIONAL_STAGE}` const initParams = { - Bucket: (calcBucketName || bucketName) + `-${process.env.FUNCTIONAL_STAGE}` + Bucket: (calcBucketName || bucketName) + suffix } return { ...initParams, ...params } diff --git a/src/cli/providers/cloudFormation/context/dynamoTable.ts b/src/cli/providers/cloudFormation/context/dynamoTable.ts index 6dabc0c..4d5b98e 100644 --- a/src/cli/providers/cloudFormation/context/dynamoTable.ts +++ b/src/cli/providers/cloudFormation/context/dynamoTable.ts @@ -25,9 +25,15 @@ export const tableResources = ExecuteStep.register('DynamoDB-Tables', async (con export const tableResource = async (context) => { const { tableConfig } = context + tableConfig.AWSTableName = tableConfig.tableName + + if (tableConfig.exists) return + + tableConfig.AWSTableName = `${tableConfig.tableName}-${context.stage}` + const properties = { ...__dynamoDBDefaults, - TableName: `${tableConfig.tableName}-${context.stage}`, + TableName: tableConfig.AWSTableName, ...tableConfig.nativeConfig }; @@ -55,7 +61,6 @@ export const tableResource = async (context) => { sourceStackName: DYNAMODB_TABLE_STACK }) - tableConfig.tableName = properties.TableName tableConfig.resourceName = resourceName } @@ -75,6 +80,8 @@ export const tableSubscribers = ExecuteStep.register('DynamoDB-Table-Subscriptio export const tableSubscriber = async (context) => { const { tableConfig, subscriber } = context + + if (tableConfig.exists) return const properties = { "BatchSize": 1, @@ -115,6 +122,8 @@ export const tableSubscriber = async (context) => { export const dynamoStreamingPolicy = async (context) => { const { tableConfig, serviceDefinition } = context + + if (tableConfig.exists) return let policy = serviceDefinition.roleResource.Properties.Policies.find(p => p.PolicyDocument.Statement[0].Action.includes('dynamodb:GetRecords')) if (!policy) { @@ -149,6 +158,6 @@ export const dynamoStreamingPolicy = async (context) => { } policy.PolicyDocument.Statement[0].Resource.push({ - "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/" + tableConfig.tableName + "/stream/*" + "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/" + tableConfig.AWSTableName + "/stream/*" }) } diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index b6594ed..6201206 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -227,7 +227,7 @@ export const dynamoPolicy = async (context) => { ], "Resource": usedTableConfigs.map(t => { return { - "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/" + t.tableName + "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/" + t.AWSTableName } }) }] diff --git a/src/cli/providers/cloudFormation/context/s3Storage.ts b/src/cli/providers/cloudFormation/context/s3Storage.ts index a7dc89b..97b19ec 100644 --- a/src/cli/providers/cloudFormation/context/s3Storage.ts +++ b/src/cli/providers/cloudFormation/context/s3Storage.ts @@ -56,36 +56,41 @@ export const s3Storages = ExecuteStep.register('S3-Storages', async (context) => }) for (const s3Config of configs) { - const s3BucketDefinition = await executor({ + await executor({ context: { ...context, s3Config }, name: `S3-Storage-${s3Config.bucketName}`, method: s3Storage }) - - await executor({ - context: { ...context, s3Config, s3BucketDefinition }, - name: `S3-Storage-Subscription-${s3Config.bucketName}`, - method: s3StorageSubscriptions - }) } }) export const s3Storage = async (context) => { const { s3Config } = context - const s3Properties = { - "BucketName": `${s3Config.bucketName}-${context.stage}` - } + s3Config.AWSBucketName = s3Config.bucketName - const s3Bucket = { - "Type": "AWS::S3::Bucket", - "Properties": s3Properties - } + if (!s3Config.exists) { + s3Config.AWSBucketName = `${s3Config.bucketName}-${context.stage}` + + const s3Properties = { + "BucketName": s3Config.AWSBucketName + } - const resourceName = `S3${s3Config.bucketName}` - const bucketResourceName = setResource(context, resourceName, s3Bucket, S3_STORAGE_STACK) - s3Config.resourceName = bucketResourceName + const s3Bucket = { + "Type": "AWS::S3::Bucket", + "Properties": s3Properties + } + + const resourceName = `S3${s3Config.bucketName}` + const bucketResourceName = setResource(context, resourceName, s3Bucket, S3_STORAGE_STACK) + s3Config.resourceName = bucketResourceName + await executor({ + context: { ...context, s3BucketDefinition: s3Bucket }, + name: `S3-Storage-Subscription-${s3Config.bucketName}`, + method: s3StorageSubscriptions + }) + } for (const { serviceDefinition, serviceConfig } of s3Config.services) { if (!serviceConfig.injected) continue @@ -96,8 +101,6 @@ export const s3Storage = async (context) => { method: s3StoragePolicy }) } - - return s3Bucket } @@ -142,7 +145,7 @@ export const s3StoragePolicy = async (context) => { "", [ "arn:aws:s3:::", - `${s3Config.bucketName}-${context.stage}`, + `${s3Config.AWSBucketName}`, "/*" ] ] @@ -152,6 +155,8 @@ export const s3StoragePolicy = async (context) => { export const s3StorageSubscriptions = async (context) => { const { s3Config } = context + if (s3Config.exists) return + for (const { serviceDefinition, serviceConfig } of s3Config.services) { if (!serviceConfig.eventSource) continue @@ -171,6 +176,8 @@ export const s3StorageSubscriptions = async (context) => { export const s3BucketSubscription = async (context) => { const { serviceDefinition, serviceConfig, s3Config, s3BucketDefinition } = context + + if (s3Config.exists) return await setStackParameter({ ...context, @@ -204,6 +211,9 @@ export const s3BucketSubscription = async (context) => { export const s3Permissions = (context) => { const { serviceDefinition, serviceConfig, s3Config, s3BucketDefinition } = context + + if (s3Config.exists) return + const properties = { "FunctionName": { "Ref": serviceDefinition.resourceName From b045c20fc1dfe29fe404b04b9120a4a18bf55308 Mon Sep 17 00:00:00 2001 From: borzav Date: Wed, 13 Dec 2017 10:56:51 +0100 Subject: [PATCH 100/196] ADD tests --- test/api.tests.ts | 112 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/test/api.tests.ts b/test/api.tests.ts index 2a983b7..b339712 100644 --- a/test/api.tests.ts +++ b/test/api.tests.ts @@ -291,6 +291,62 @@ describe('api', () => { }, (e) => { e && done(e) }) .catch((e) => { e && done(e) }) }) + + it("existing table", (done) => { + let counter = 0 + + @injectable() + @dynamoTable({ tableName: 'custom-table-name', exists: true }) + class DTClass extends DynamoTable { } + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'customstage' + process.env.DTClass_TABLE_NAME = 'custom-table-name' + class Home extends FunctionalService { + public async handle( @inject(DTClass) a: DTClass) { + counter++ + + await a.put({ + Item: { _id: 1 } + }) + + return { ok: 1 } + } + } + + @injectable() + class DCL extends DocumentClientApi { + public async init() { } + public getDocumentClient() { + return { + put(params, cb) { + counter++ + try { + expect(params).to.deep.equal({ + Item: { _id: 1 }, + TableName: `custom-table-name` + }) + cb() + } catch (e) { + cb(e) + } + } + } + } + } + + container.registerType(DocumentClientApi, DCL) + + const invoker = Home.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + .catch((e) => { e && done(e) }) + }) }) describe('S3Storage', () => { @@ -580,5 +636,61 @@ describe('api', () => { }, (e) => { e && done(e) }) .catch((e) => { e && done(e) }) }) + + it("existing bucket", (done) => { + let counter = 0 + + @injectable() + @s3Storage({ bucketName: 'custom-bucket-name', exists: true }) + class S3StorageClass extends S3Storage { } + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'customstage' + process.env.S3StorageClass_S3_BUCKET = 'custom-bucket-name' + class Home extends FunctionalService { + public async handle( @inject(S3StorageClass) a: S3StorageClass) { + counter++ + + await a.getObject({ + Key: 'a' + }) + + return { ok: 1 } + } + } + + @injectable() + class S3A extends S3Api { + public async init() { } + public getS3() { + return { + getObject(params, cb) { + counter++ + try { + expect(params).to.deep.equal({ + Key: 'a', + Bucket: 'custom-bucket-name' + }) + cb() + } catch (e) { + cb(e) + } + } + } + } + } + + container.registerType(S3Api, S3A) + + const invoker = Home.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + .catch((e) => { e && done(e) }) + }) }) }) \ No newline at end of file From b9e205a6101e62b05eef70ae69467bb03f689108 Mon Sep 17 00:00:00 2001 From: borzav Date: Wed, 13 Dec 2017 13:08:12 +0100 Subject: [PATCH 101/196] ADD: sns environmentKey usage, tests --- src/classes/api/aws/sns.ts | 12 +- test/api.tests.ts | 309 ++++++++++++++++++++++++++++++++++++- 2 files changed, 314 insertions(+), 7 deletions(-) diff --git a/src/classes/api/aws/sns.ts b/src/classes/api/aws/sns.ts index 8aff4c1..ab0efd3 100644 --- a/src/classes/api/aws/sns.ts +++ b/src/classes/api/aws/sns.ts @@ -41,10 +41,10 @@ export class SNSApi extends Api { }) export class SimpleNotificationService extends Api { private _snsClient: SNS - public constructor(@inject(SNSApi) private snsApi: SNSApi) { + public constructor( @inject(SNSApi) private snsApi: SNSApi) { super() } - public async init(){ + public async init() { this._snsClient = this.snsApi.getSNS() } @@ -62,9 +62,11 @@ export class SimpleNotificationService extends Api { } protected setDefaultValues(params, command) { - const initParams = { - TopicArn: process.env[`${this.constructor.name}_SNS_TOPICNAME_ARN`] - } + const snsConfig = (getMetadata(CLASS_SNSCONFIGURATIONKEY, this) || [])[0] || {} + const envKey = snsConfig.environmentKey + '_ARN' + const TopicArn = process.env[envKey] ? process.env[envKey] : '' + + const initParams = { TopicArn } return { ...initParams, ...params } } diff --git a/test/api.tests.ts b/test/api.tests.ts index b339712..696e15c 100644 --- a/test/api.tests.ts +++ b/test/api.tests.ts @@ -1,6 +1,6 @@ import { expect } from 'chai' -import { FunctionalService, DynamoTable, DocumentClientApi, S3Storage, S3Api } from '../src/classes' -import { injectable, dynamoTable, inject, s3Storage } from '../src/annotations' +import { FunctionalService, DynamoTable, DocumentClientApi, S3Storage, S3Api, SimpleNotificationService, SNSApi } from '../src/classes' +import { injectable, dynamoTable, inject, s3Storage, sns } from '../src/annotations' import { container } from '../src//helpers/ioc' describe('api', () => { @@ -693,4 +693,309 @@ describe('api', () => { .catch((e) => { e && done(e) }) }) }) + + describe('SimpleNotificationService', () => { + afterEach(() => { + delete process.env.FUNCTIONAL_ENVIRONMENT + delete process.env.FUNCTIONAL_STAGE + delete process.env.SNSTopicClass_SNS_TOPICNAME + delete process.env.SNSTopicClass_SNS_TOPICNAME_ARN + delete process.env.SNSTopicClass_ENV_NAME + delete process.env.SNSTopicClass_ENV_NAME_ARN + container.clearType(SNSApi) + }) + + it("empty topic name", (done) => { + let counter = 0 + + @injectable() + @sns() + class SNSTopicClass extends SimpleNotificationService { } + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'customstage' + process.env.SNSTopicClass_SNS_TOPICNAME = 'SNSTopicClass-topic123456789-customstage' + process.env.SNSTopicClass_SNS_TOPICNAME_ARN = 'arn:aws:sns:Z:1:SNSTopicClass-topic123456789-customstage' + class Home extends FunctionalService { + public async handle( @inject(SNSTopicClass) a: SNSTopicClass) { + counter++ + + await a.publish({ + Message: 'message', + Subject: 'subject' + }) + + return { ok: 1 } + } + } + + @injectable() + class SNSA extends SNSApi { + public async init() { } + public getSNS() { + return { + publish(params, cb) { + counter++ + try { + expect(params).to.deep.equal({ + Message: 'message', + Subject: 'subject', + TopicArn: 'arn:aws:sns:Z:1:SNSTopicClass-topic123456789-customstage' + }) + cb() + } catch (e) { + cb(e) + } + } + } + } + } + + container.registerType(SNSApi, SNSA) + + const invoker = Home.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + .catch((e) => { e && done(e) }) + }) + + it("empty topic name no env", (done) => { + let counter = 0 + + @injectable() + @sns() + class SNSTopicClass extends SimpleNotificationService { } + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'customstage' + class Home extends FunctionalService { + public async handle( @inject(SNSTopicClass) a: SNSTopicClass) { + counter++ + + await a.publish({ + Message: 'message', + Subject: 'subject' + }) + + return { ok: 1 } + } + } + + @injectable() + class SNSA extends SNSApi { + public async init() { } + public getSNS() { + return { + publish(params, cb) { + counter++ + try { + expect(params).to.deep.equal({ + Message: 'message', + Subject: 'subject', + TopicArn: '' + }) + cb() + } catch (e) { + cb(e) + } + } + } + } + } + + container.registerType(SNSApi, SNSA) + + const invoker = Home.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + .catch((e) => { e && done(e) }) + }) + + it("empty topic name different env", (done) => { + let counter = 0 + + @injectable() + @sns() + class SNSTopicClass extends SimpleNotificationService { } + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'customstage' + process.env.SNSTopicClass_SNS_TOPICNAME = 'SNSTopicClass-topic' + process.env.SNSTopicClass_SNS_TOPICNAME_ARN = 'arn:aws:sns:Z:1:SNSTopicClass-topic' + class Home extends FunctionalService { + public async handle( @inject(SNSTopicClass) a: SNSTopicClass) { + counter++ + + await a.publish({ + Message: 'message', + Subject: 'subject' + }) + + return { ok: 1 } + } + } + + @injectable() + class SNSA extends SNSApi { + public async init() { } + public getSNS() { + return { + publish(params, cb) { + counter++ + try { + expect(params).to.deep.equal({ + Message: 'message', + Subject: 'subject', + TopicArn: 'arn:aws:sns:Z:1:SNSTopicClass-topic' + }) + cb() + } catch (e) { + cb(e) + } + } + } + } + } + + container.registerType(SNSApi, SNSA) + + const invoker = Home.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + .catch((e) => { e && done(e) }) + }) + + it("custom topic name", (done) => { + let counter = 0 + + @injectable() + @sns({ topicName: 'custom-name' }) + class SNSTopicClass extends SimpleNotificationService { } + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'customstage' + process.env.SNSTopicClass_SNS_TOPICNAME = 'custom-name123456789-customstage' + process.env.SNSTopicClass_SNS_TOPICNAME_ARN = 'arn:aws:sns:Z:1:custom-name123456789-customstage' + class Home extends FunctionalService { + public async handle( @inject(SNSTopicClass) a: SNSTopicClass) { + counter++ + + await a.publish({ + Message: 'message', + Subject: 'subject' + }) + + return { ok: 1 } + } + } + + @injectable() + class SNSA extends SNSApi { + public async init() { } + public getSNS() { + return { + publish(params, cb) { + counter++ + try { + expect(params).to.deep.equal({ + Message: 'message', + Subject: 'subject', + TopicArn: 'arn:aws:sns:Z:1:custom-name123456789-customstage' + }) + cb() + } catch (e) { + cb(e) + } + } + } + } + } + + container.registerType(SNSApi, SNSA) + + const invoker = Home.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + .catch((e) => { e && done(e) }) + }) + + it("custom environmentKey", (done) => { + let counter = 0 + + @injectable() + @sns({ environmentKey: 'SNSTopicClass_ENV_NAME' }) + class SNSTopicClass extends SimpleNotificationService { } + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'customstage' + process.env.SNSTopicClass_ENV_NAME = 'SNSTopicClass-topic123456789-customstage' + process.env.SNSTopicClass_ENV_NAME_ARN = 'arn:aws:sns:Z:1:SNSTopicClass-topic123456789-customstage' + class Home extends FunctionalService { + public async handle( @inject(SNSTopicClass) a: SNSTopicClass) { + counter++ + + await a.publish({ + Message: 'message', + Subject: 'subject' + }) + + return { ok: 1 } + } + } + + @injectable() + class SNSA extends SNSApi { + public async init() { } + public getSNS() { + return { + publish(params, cb) { + counter++ + try { + expect(params).to.deep.equal({ + Message: 'message', + Subject: 'subject', + TopicArn: 'arn:aws:sns:Z:1:SNSTopicClass-topic123456789-customstage' + }) + cb() + } catch (e) { + cb(e) + } + } + } + } + } + + container.registerType(SNSApi, SNSA) + + const invoker = Home.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + .catch((e) => { e && done(e) }) + }) + }) }) \ No newline at end of file From 8268764455cdac84b62ce91ccb29f17cdfc8ca3a Mon Sep 17 00:00:00 2001 From: borzav Date: Wed, 13 Dec 2017 15:07:39 +0100 Subject: [PATCH 102/196] ADD: use existing SNS --- src/annotations/classes/sns.ts | 3 +- .../providers/cloudFormation/context/sns.ts | 64 ++++++++++++------- test/api.tests.ts | 59 +++++++++++++++++ 3 files changed, 103 insertions(+), 23 deletions(-) diff --git a/src/annotations/classes/sns.ts b/src/annotations/classes/sns.ts index d96c5d3..30b1a3d 100644 --- a/src/annotations/classes/sns.ts +++ b/src/annotations/classes/sns.ts @@ -5,7 +5,8 @@ import { environment } from './environment' export const sns = (snsConfig?: { topicName?: string, - environmentKey?: string + environmentKey?: string, + exists?: boolean }) => (target: Function) => { let snsDefinitions = getMetadata(CLASS_SNSCONFIGURATIONKEY, target) || []; diff --git a/src/cli/providers/cloudFormation/context/sns.ts b/src/cli/providers/cloudFormation/context/sns.ts index 8493c49..20978ad 100644 --- a/src/cli/providers/cloudFormation/context/sns.ts +++ b/src/cli/providers/cloudFormation/context/sns.ts @@ -41,20 +41,27 @@ export const snsTopics = ExecuteStep.register('SNS-Topics', async (context) => { export const snsTopic = async (context) => { const { snsConfig } = context - snsConfig.advTopicName = `${snsConfig.topicName}${context.date.valueOf()}` + snsConfig.ADVTopicName = snsConfig.topicName + snsConfig.AWSTopicName = snsConfig.topicName - const snsProperties = { - "TopicName": `${snsConfig.advTopicName}-${context.stage}` - } + if (!snsConfig.exists) { - const snsTopic = { - "Type": "AWS::SNS::Topic", - "Properties": snsProperties - } + snsConfig.ADVTopicName = `${snsConfig.topicName}${context.date.valueOf()}` + snsConfig.AWSTopicName = `${snsConfig.ADVTopicName}-${context.stage}` + + const snsProperties = { + "TopicName": snsConfig.AWSTopicName + } + + const snsTopic = { + "Type": "AWS::SNS::Topic", + "Properties": snsProperties + } - const resourceName = `SNS${snsConfig.advTopicName}` - const topicResourceName = setResource(context, resourceName, snsTopic, SNS_TABLE_STACK) - snsConfig.resourceName = topicResourceName + const resourceName = `SNS${snsConfig.ADVTopicName}` + const topicResourceName = setResource(context, resourceName, snsTopic, SNS_TABLE_STACK) + snsConfig.resourceName = topicResourceName + } await executor({ context, @@ -66,6 +73,8 @@ export const snsTopic = async (context) => { export const snsTopicSubscriptions = async (context) => { const { snsConfig } = context + if (snsConfig.exists) return + for (const { serviceDefinition, serviceConfig } of snsConfig.services) { if (!serviceConfig.eventSource) continue @@ -86,6 +95,8 @@ export const snsTopicSubscriptions = async (context) => { export const snsTopicSubscription = async (context) => { const { serviceDefinition, snsConfig } = context + if (snsConfig.exists) return + await setStackParameter({ ...context, sourceStackName: SNS_TABLE_STACK, @@ -114,6 +125,9 @@ export const snsTopicSubscription = async (context) => { export const snsPermissions = (context) => { const { serviceDefinition, snsConfig } = context + + if (snsConfig.exists) return + const properties = { "FunctionName": { "Fn::GetAtt": [ @@ -139,17 +153,23 @@ const updateSNSEnvironmentVariables = async (context) => { for (const { serviceDefinition, serviceConfig } of snsConfig.services) { const environmentVariables = getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) || {} - environmentVariables[serviceConfig.environmentKey] = `${snsConfig.advTopicName}` - - await setStackParameter({ - ...context, - sourceStackName: SNS_TABLE_STACK, - resourceName: snsConfig.resourceName, - targetStackName: getStackName(serviceDefinition) - }) - - environmentVariables[`${serviceConfig.environmentKey}_ARN`] = { - "Ref": snsConfig.resourceName + environmentVariables[serviceConfig.environmentKey] = `${snsConfig.ADVTopicName}` + + if (snsConfig.exists) { + environmentVariables[`${serviceConfig.environmentKey}_ARN`] = { + "Fn::Sub": "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:" + snsConfig.AWSTopicName + } + } else { + await setStackParameter({ + ...context, + sourceStackName: SNS_TABLE_STACK, + resourceName: snsConfig.resourceName, + targetStackName: getStackName(serviceDefinition) + }) + + environmentVariables[`${serviceConfig.environmentKey}_ARN`] = { + "Ref": snsConfig.resourceName + } } defineMetadata(CLASS_ENVIRONMENTKEY, { ...environmentVariables }, serviceDefinition.service) diff --git a/test/api.tests.ts b/test/api.tests.ts index 696e15c..639835f 100644 --- a/test/api.tests.ts +++ b/test/api.tests.ts @@ -997,5 +997,64 @@ describe('api', () => { }, (e) => { e && done(e) }) .catch((e) => { e && done(e) }) }) + + it("existing topic", (done) => { + let counter = 0 + + @injectable() + @sns({ topicName: 'SNSTopicClass-topic', exists: true }) + class SNSTopicClass extends SimpleNotificationService { } + + process.env.FUNCTIONAL_ENVIRONMENT = 'local' + process.env.FUNCTIONAL_STAGE = 'customstage' + process.env.SNSTopicClass_SNS_TOPICNAME = 'SNSTopicClass-topic' + process.env.SNSTopicClass_SNS_TOPICNAME_ARN = 'arn:aws:sns:Z:1:SNSTopicClass-topic' + class Home extends FunctionalService { + public async handle( @inject(SNSTopicClass) a: SNSTopicClass) { + counter++ + + await a.publish({ + Message: 'message', + Subject: 'subject' + }) + + return { ok: 1 } + } + } + + @injectable() + class SNSA extends SNSApi { + public async init() { } + public getSNS() { + return { + publish(params, cb) { + counter++ + try { + expect(params).to.deep.equal({ + Message: 'message', + Subject: 'subject', + TopicArn: 'arn:aws:sns:Z:1:SNSTopicClass-topic' + }) + cb() + } catch (e) { + cb(e) + } + } + } + } + } + + container.registerType(SNSApi, SNSA) + + const invoker = Home.createInvoker() + invoker({}, { + send: (result) => { + expect(counter).to.equal(2) + expect(result).to.deep.equal({ ok: 1 }) + done() + } + }, (e) => { e && done(e) }) + .catch((e) => { e && done(e) }) + }) }) }) \ No newline at end of file From ddfb8c9cfb8a0f9d963d53119722cf1fbca930b4 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Wed, 13 Dec 2017 14:59:48 +0000 Subject: [PATCH 103/196] 0.0.22 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 609e4a6..0736a5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.21", + "version": "0.0.22", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2505b33..a50c2f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.21", + "version": "0.0.22", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From f0f36fde2deb83a0683b78d3c51c8437c70e4d3d Mon Sep 17 00:00:00 2001 From: borzav Date: Tue, 2 Jan 2018 13:14:13 +0100 Subject: [PATCH 104/196] add: build with webpack, watch in local --- package-lock.json | 13 ++ package.json | 1 + src/cli/commands/deploy.ts | 1 + src/cli/commands/local.ts | 34 +++- src/cli/commands/metadata.ts | 1 + src/cli/commands/package.ts | 1 + src/cli/commands/serverless.ts | 1 + src/cli/context/core/executor.ts | 61 +++++++ src/cli/context/index.ts | 81 ++------- src/cli/context/steppes/codeCompile.ts | 186 ++++++++++++++++++++ src/cli/context/steppes/serviceDiscovery.ts | 33 +--- src/cli/providers/aws.ts | 2 - src/cli/providers/azureARM/index.ts | 3 - src/cli/providers/cloudFormation/index.ts | 3 - src/cli/utilities/webpack.ts | 62 ------- 15 files changed, 317 insertions(+), 166 deletions(-) create mode 100644 src/cli/context/core/executor.ts create mode 100644 src/cli/context/steppes/codeCompile.ts delete mode 100644 src/cli/utilities/webpack.ts diff --git a/package-lock.json b/package-lock.json index 609e4a6..cbe389f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -444,6 +444,11 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" + }, "camelcase": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", @@ -698,6 +703,14 @@ "ms": "2.0.0" } }, + "decache": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/decache/-/decache-4.3.0.tgz", + "integrity": "sha1-o5XkBwlWmKyKbe8B8qaky3Y49jU=", + "requires": { + "callsite": "1.0.0" + } + }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", diff --git a/package.json b/package.json index 2505b33..4491c8f 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "commander": "^2.9.0", "config": "^1.26.1", "cors": "^2.8.3", + "decache": "^4.3.0", "express": "^4.15.2", "fs-extra": "^3.0.1", "lodash": "^4.14.1", diff --git a/src/cli/commands/deploy.ts b/src/cli/commands/deploy.ts index 9f07453..a049cae 100644 --- a/src/cli/commands/deploy.ts +++ b/src/cli/commands/deploy.ts @@ -26,6 +26,7 @@ export default ({ createContext, executor, ExecuteStep, projectConfig, requireVa projectName: projectConfig.name, stage }) + await context.init() await executor(context, ExecuteStep.get('CreateEnvironment')) diff --git a/src/cli/commands/local.ts b/src/cli/commands/local.ts index bc75caa..331d508 100644 --- a/src/cli/commands/local.ts +++ b/src/cli/commands/local.ts @@ -53,9 +53,9 @@ export default ({ createContext, annotations: { getMetadata, constants, getFunct } } - app.listen(context.localPort, function () { + return app.listen(context.localPort, function () { process.env.FUNCTIONAL_LOCAL_PORT = context.localPort - console.log(`Example app listening on port ${process.env.FUNCTIONAL_LOCAL_PORT}!`) + console.log(`App listening on port ${process.env.FUNCTIONAL_LOCAL_PORT}!`) }) } @@ -103,6 +103,15 @@ export default ({ createContext, annotations: { getMetadata, constants, getFunct } } + const startServer = async (context) => { + try { + await context.init() + return await executor(context, { name: 'startLocal', method: startLocal }) + } catch (e) { + console.log(`error`, e) + } + } + return { commands({ commander }) { commander @@ -116,18 +125,29 @@ export default ({ createContext, annotations: { getMetadata, constants, getFunct const entryPoint = requireValue(path || projectConfig.main, 'entry point') const localPort = requireValue(port || projectConfig.localPort, 'localPort') const stage = command.stage || projectConfig.stage || 'dev' - + process.env.FUNCTIONAL_STAGE = stage + let server = null const context = await createContext(entryPoint, { deployTarget: 'local', localPort, - stage - }) + stage, + watchCallback: async (ctx) => { + if (server) { + server.close() + } + + server = await startServer(ctx) - await executor(context, { name: 'startLocal', method: startLocal }) + console.log(`Compilation complete. Watching for file changes.`) + + } + }) - console.log(`done`) + await startServer(context) + + console.log(`Compilation complete.`) } catch (e) { console.log(`error`, e) } diff --git a/src/cli/commands/metadata.ts b/src/cli/commands/metadata.ts index c13b928..02f82dc 100644 --- a/src/cli/commands/metadata.ts +++ b/src/cli/commands/metadata.ts @@ -19,6 +19,7 @@ export default ({ createContext, executor, ExecuteStep, annotations: { getMetada const context = await createContext(entryPoint, { deployTarget }) + await context.init() await executor(context, ExecuteStep.get('ServiceMetadata')) diff --git a/src/cli/commands/package.ts b/src/cli/commands/package.ts index 33869f1..7925183 100644 --- a/src/cli/commands/package.ts +++ b/src/cli/commands/package.ts @@ -27,6 +27,7 @@ export default ({ createContext, executor, ExecuteStep, projectConfig, requireVa packageOnly: true, stage }) + await context.init() await executor(context, ExecuteStep.get('CreateEnvironment')) diff --git a/src/cli/commands/serverless.ts b/src/cli/commands/serverless.ts index 4e8cf00..807bbef 100644 --- a/src/cli/commands/serverless.ts +++ b/src/cli/commands/serverless.ts @@ -210,6 +210,7 @@ export default (api) => { FUNCTIONAL_ENVIRONMENT, stage }) + await context.init() await executor(context, ExecuteStep.get('SetFunctionalEnvironment')) diff --git a/src/cli/context/core/executor.ts b/src/cli/context/core/executor.ts new file mode 100644 index 0000000..923cf4e --- /dev/null +++ b/src/cli/context/core/executor.ts @@ -0,0 +1,61 @@ + +import { logger } from '../../utilities/logger' +import { projectConfig } from '../../project/config' +import { + cliCallContextHooks, hasCliCallContextHooks, CONTEXT_HOOK_MODIFIER_BEFORE, CONTEXT_HOOK_MODIFIER_AFTER, + callContextHookStep +} from '../../extensions/hooks' + +let depth = 0 +export const executor = async (context, step?: Function | any) => { + if (!step) { + step = context; + context = context.context + } + + let result = undefined + if (typeof step === 'function') { + result = await step(context) + } else if (step && step.name && typeof step.method === 'function') { + const tab = depth++ + const separator = (s = ' ') => { + let result = '' + for (var i = 0; i < tab; i++) { + result += s + } + return result + } + + try { + + if (projectConfig.debug) logger.debug(`executor | run -----------${separator('--')}> ${step.name}`) + if (projectConfig.debug) logger.debug(`executor | before start ${separator()} ${step.name}`) + await cliCallContextHooks(step.name, context, CONTEXT_HOOK_MODIFIER_BEFORE) + if (projectConfig.debug) logger.debug(`executor | before end ${separator()} ${step.name}`) + + if (projectConfig.debug) logger.debug(`executor | start ${separator()} ${step.name}`) + if (hasCliCallContextHooks(step.name, context)) { + result = await cliCallContextHooks(step.name, context) + } else { + result = await step.method(context) + } + if (projectConfig.debug) logger.debug(`executor | end ${separator()} ${step.name}`) + + if (projectConfig.debug) logger.debug(`executor | after start ${separator()} ${step.name}`) + await cliCallContextHooks(step.name, context, CONTEXT_HOOK_MODIFIER_AFTER) + if (projectConfig.debug) logger.debug(`executor | after end ${separator()} ${step.name}`) + if (projectConfig.debug) logger.debug(`executor | complete ------${separator('--')}> ${step.name}`) + } + catch (e) { + if (projectConfig.debug) logger.debug(`executor | exited --------${separator('--')}> ${step.name}`) + depth-- + throw e + } + depth-- + } else { + throw new Error(`runStep has invalid parameter '${step}'`) + } + return result +} + + diff --git a/src/cli/context/index.ts b/src/cli/context/index.ts index ddd94be..709b7b9 100644 --- a/src/cli/context/index.ts +++ b/src/cli/context/index.ts @@ -1,22 +1,24 @@ export { ExecuteStep } from './core/executeStep' +export { executor } from './core/executor' import { serviceDiscovery } from './steppes/serviceDiscovery' import { tableDiscovery } from './steppes/tableDiscovery' import './steppes/setFunctionalEnvironment' import './steppes/metadata/serviceMetadata' - import { resolvePath } from '../utilities/cli' -import { logger } from '../utilities/logger' - -import { projectConfig } from '../project/config' -import { - cliCallContextHooks, hasCliCallContextHooks, CONTEXT_HOOK_MODIFIER_BEFORE, CONTEXT_HOOK_MODIFIER_AFTER, - callContextHookStep -} from '../extensions/hooks' +import { callContextHookStep } from '../extensions/hooks' +import { executor } from './core/executor' +import { codeCompile } from './steppes/codeCompile' export const getDefaultSteppes = (): any[] => { return [ callContextHookStep, + codeCompile + ] +} + +export const getInitSteppes = (): any[] => { + return [ serviceDiscovery, tableDiscovery ] @@ -27,7 +29,15 @@ export const createContext = async (path, defaultValues) => { ...defaultValues, serviceRoot: resolvePath(path), date: new Date(), - runStep: async function (step) { return await executor(this, step) } + runStep: async function (step) { return await executor(this, step) }, + + + init: async () => { + const initSteppes = getInitSteppes() + for (const step of initSteppes) { + await executor(context, step) + } + } } const defaultSteppes = getDefaultSteppes() @@ -38,56 +48,3 @@ export const createContext = async (path, defaultValues) => { return context } -let depth = 0 -export const executor = async (context, step?: Function | any) => { - if (!step) { - step = context; - context = context.context - } - - let result = undefined - if (typeof step === 'function') { - result = await step(context) - } else if (step && step.name && typeof step.method === 'function') { - const tab = depth++ - const separator = (s = ' ') => { - let result = '' - for (var i = 0; i < tab; i++) { - result += s - } - return result - } - - try { - - if (projectConfig.debug) logger.debug(`executor | run -----------${separator('--')}> ${step.name}`) - if (projectConfig.debug) logger.debug(`executor | before start ${separator()} ${step.name}`) - await cliCallContextHooks(step.name, context, CONTEXT_HOOK_MODIFIER_BEFORE) - if (projectConfig.debug) logger.debug(`executor | before end ${separator()} ${step.name}`) - - if (projectConfig.debug) logger.debug(`executor | start ${separator()} ${step.name}`) - if (hasCliCallContextHooks(step.name, context)) { - result = await cliCallContextHooks(step.name, context) - } else { - result = await step.method(context) - } - if (projectConfig.debug) logger.debug(`executor | end ${separator()} ${step.name}`) - - if (projectConfig.debug) logger.debug(`executor | after start ${separator()} ${step.name}`) - await cliCallContextHooks(step.name, context, CONTEXT_HOOK_MODIFIER_AFTER) - if (projectConfig.debug) logger.debug(`executor | after end ${separator()} ${step.name}`) - if (projectConfig.debug) logger.debug(`executor | complete ------${separator('--')}> ${step.name}`) - } - catch (e) { - if (projectConfig.debug) logger.debug(`executor | exited --------${separator('--')}> ${step.name}`) - depth-- - throw e - } - depth-- - } else { - throw new Error(`runStep has invalid parameter '${step}'`) - } - return result -} - - diff --git a/src/cli/context/steppes/codeCompile.ts b/src/cli/context/steppes/codeCompile.ts new file mode 100644 index 0000000..347f261 --- /dev/null +++ b/src/cli/context/steppes/codeCompile.ts @@ -0,0 +1,186 @@ +import * as webpack from 'webpack' +import { config } from '../../utilities/config' +import { basename, extname, join } from 'path' +import { lstat, readdirSync } from 'fs' +import { ExecuteStep } from '../core/executeStep' +import { executor } from '../core/executor' + +import { projectConfig } from '../../project/config' + +export class CodeCompile extends ExecuteStep { + public async method(context) { + const path = context.serviceRoot + + let isDir = await this.isDirectory(path) + + let files = [path] + if (isDir) { + files = await this.getJsFiles(path) + } + context.files = files + + await executor(context, bundle) + + return context + } + + private getJsFiles(folder) { + let files = readdirSync(folder); + let filter = /\.js|\.ts$/ + return files.filter(name => filter.test(name)).map(file => join(folder, file)) + } + private isDirectory(path) { + return new Promise((resolve, reject) => { + lstat(path, (err, stats) => { + if (err) return reject(err); + return resolve(stats.isDirectory()) + }); + }) + } + +} + +export const codeCompile = new CodeCompile('CodeCompile') + +export const bundle = ExecuteStep.register('WebpackBundle', (context) => { + return new Promise(async (resolve, reject) => { + const webpackConfig = await executor(context, bundleConfig) + let buildHash = '' + + const done = (err?, res?) => { + const isWatch = context.watchCallback && webpackConfig.watch + if (err) { + if (isWatch) { + // watch mode, wait to fix + return + } + + return reject(err) + } + + if (isWatch) { + context.watchCallback(context) + } else { + return resolve(res) + } + } + + try { + webpack(webpackConfig, function (err, stats) { + if (err) return done(err) + + if (buildHash === stats.hash) return + if (buildHash && context.watchCallback) context.clearRequireCache = true + buildHash = stats.hash + + let jsonStats = stats.toJson(); + if (jsonStats.errors.length > 0) { + console.log('WEBPACK ERROR') + jsonStats.errors.forEach(msg => console.log(msg)) + return done(jsonStats.errors); + } + // if (jsonStats.warnings.length > 0) { + // console.log('WEBPACK WARNINGS') + // console.log(jsonStats.warnings) + // } + + context.originalFiles = context.files + context.files = [] + Object.keys(jsonStats.entrypoints).forEach((entryKey) => { + let entry = jsonStats.entrypoints[entryKey] + let assets = entry.assets.map((file) => join(webpackConfig.output.path, file)) + context.files.push(...assets) + }) + + done() + }); + } catch (e) { + return done(e) + } + }) +}) + +export const bundleConfig = ExecuteStep.register('WebpackBundleConfig', async (context) => { + let entry = {} + context.files.forEach((file) => { + let name = basename(file) + const ext = extname(name) + const nameKey = name.substring(0, name.length - ext.length) + entry[nameKey] = file + }) + + const externals = {} + if (context.deployTarget === 'aws') { + externals['aws-sdk'] = 'commonjs aws-sdk' + } + + let compile = {} + if (projectConfig.compile) { + compile = await executor({ context, projectConfig }, bundleCompileConfig) + } + let watch = {} + if (context.watchCallback && projectConfig.watch) { + watch = await executor({ context, projectConfig }, watchConfig) + } + + const webpackConfig = { + ...config.webpack, + ...compile, + ...watch, + ...projectConfig.webpack, + entry + } + + webpackConfig.externals = { ...webpackConfig.externals, ...externals } + + if (!context.watchCallback && webpackConfig.watch) { + delete webpackConfig.watch + } + + return webpackConfig +}) + +export const bundleCompileConfig = ExecuteStep.register('WebpackCompileConfig', (context) => { + switch (context.projectConfig.compile) { + case "ts-loader": + return { + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/ + } + ] + }, + resolve: { + extensions: ['.tsx', '.ts', '.js'] + } + } + case "babel-loader": + return { + module: { + rules: [ + { + test: /\.jsx?$/, + use: 'babel-loader', + exclude: /node_modules/ + } + ] + } + } + default: + return {} + } +}) + +export const watchConfig = ExecuteStep.register('WebpackWatchConfig', (context) => { + return { + watch: true, + watchOptions: { + aggregateTimeout: 300, + poll: 1000, + ignored: /dist/ + } + } +}) \ No newline at end of file diff --git a/src/cli/context/steppes/serviceDiscovery.ts b/src/cli/context/steppes/serviceDiscovery.ts index b4dd78b..06755e1 100644 --- a/src/cli/context/steppes/serviceDiscovery.ts +++ b/src/cli/context/steppes/serviceDiscovery.ts @@ -1,33 +1,27 @@ import { lstat, readdirSync } from 'fs' import { FunctionalService } from '../../../classes/functionalService' import { ExecuteStep } from '../core/executeStep' +import * as decache from 'decache' import { join, basename, extname } from 'path' export class ServiceDiscoveryStep extends ExecuteStep { public async method(context) { - context.files = context.files || [] context.publishedFunctions = context.publishedFunctions || [] - const path = context.serviceRoot - - let isDir = await this.isDirectory(path) - - let files = [path] - if (isDir) { - files = await this.getJsFiles(path) - } - - for (let file of files) { + for (let file of context.files) { this.collectFromFile(file, context) } return context } - private collectFromFile(file, context) { + if (context.clearRequireCache) { + decache(file); + } + const module = require(file) const name = basename(file) @@ -56,21 +50,6 @@ export class ServiceDiscoveryStep extends ExecuteStep { }) } - - private getJsFiles(folder) { - let files = readdirSync(folder); - let filter = /\.js$/ - return files.filter(name => filter.test(name)).map(file => join(folder, file)) - } - private isDirectory(path) { - return new Promise((resolve, reject) => { - lstat(path, (err, stats) => { - if (err) return reject(err); - return resolve(stats.isDirectory()) - }); - }) - } - } export const serviceDiscovery = new ServiceDiscoveryStep('ServiceDiscovery') diff --git a/src/cli/providers/aws.ts b/src/cli/providers/aws.ts index e1e42f1..0ba84bd 100644 --- a/src/cli/providers/aws.ts +++ b/src/cli/providers/aws.ts @@ -1,5 +1,4 @@ import { getFunctionName } from '../../annotations' -import { bundle } from '../utilities/webpack' import { zip } from '../utilities/compress' import { uploadZipStep } from '../utilities/aws/s3Upload' import { createTables } from '../utilities/aws/dynamoDB' @@ -18,7 +17,6 @@ import { projectConfig } from '../project/config' export const aws = { FUNCTIONAL_ENVIRONMENT: 'aws', createEnvironment: ExecuteStep.register('CreateEnvironment_aws', async (context) => { - await executor(context, bundle) await executor(context, zip) const fileName = projectConfig.name ? `${projectConfig.name}.zip` : 'project.zip' await executor(context, uploadZipStep(fileName, context.zipData())) diff --git a/src/cli/providers/azureARM/index.ts b/src/cli/providers/azureARM/index.ts index 2d3a6b2..46bad2e 100644 --- a/src/cli/providers/azureARM/index.ts +++ b/src/cli/providers/azureARM/index.ts @@ -1,5 +1,4 @@ import { getFunctionName } from '../../../annotations' -import { bundle } from '../../utilities/webpack' import { logger } from '../../utilities/logger' import { zip } from '../../utilities/compress' import { writeFile } from '../../utilities/local/file' @@ -14,7 +13,6 @@ export const azure = { FUNCTIONAL_ENVIRONMENT: 'azure', createEnvironment: async (context) => { logger.info(`Functionly: Packaging...`) - await executor(context, bundle) await executor(context, zip) await executor(context, ARMInit) @@ -35,7 +33,6 @@ export const azure = { }, package: async (context) => { logger.info(`Functionly: Packaging...`) - await executor(context, bundle) await executor(context, zip) await executor(context, ARMInit) diff --git a/src/cli/providers/cloudFormation/index.ts b/src/cli/providers/cloudFormation/index.ts index 5f1851f..79b92a0 100644 --- a/src/cli/providers/cloudFormation/index.ts +++ b/src/cli/providers/cloudFormation/index.ts @@ -1,5 +1,4 @@ import { getFunctionName } from '../../../annotations' -import { bundle } from '../../utilities/webpack' import { logger } from '../../utilities/logger' import { zip } from '../../utilities/compress' import { uploadZipStep } from '../../utilities/aws/s3Upload' @@ -18,7 +17,6 @@ export const cloudFormation = { FUNCTIONAL_ENVIRONMENT: 'aws', createEnvironment: async (context) => { logger.info(`Functionly: Packaging...`) - await executor(context, bundle) await executor(context, zip) await executor(context, cloudFormationInit) @@ -75,7 +73,6 @@ export const cloudFormation = { }, package: async (context) => { logger.info(`Functionly: Packaging...`) - await executor(context, bundle) await executor(context, zip) await executor(context, cloudFormationInit) diff --git a/src/cli/utilities/webpack.ts b/src/cli/utilities/webpack.ts deleted file mode 100644 index 7a87b1b..0000000 --- a/src/cli/utilities/webpack.ts +++ /dev/null @@ -1,62 +0,0 @@ -import * as webpack from 'webpack' -import { config } from './config' -import { basename, extname, join } from 'path' -import { ExecuteStep, executor } from '../context' - - -export const bundle = ExecuteStep.register('WebpackBundle', (context) => { - - return new Promise(async (resolve, reject) => { - const webpackConfig = await executor(context, bundleConfig) - - webpack(webpackConfig, function (err, stats) { - if (err) return reject() - - let jsonStats = stats.toJson(); - if (jsonStats.errors.length > 0) { - console.log('WEBPACK ERROR') - console.log(jsonStats.errors) - return reject(jsonStats.errors); - } - // if (jsonStats.warnings.length > 0) { - // console.log('WEBPACK WARNINGS') - // console.log(jsonStats.warnings) - // } - - context.originalFiles = context.files - context.files = [] - Object.keys(jsonStats.entrypoints).forEach((entryKey) => { - let entry = jsonStats.entrypoints[entryKey] - let assets = entry.assets.map((file) => join(webpackConfig.output.path, file)) - context.files.push(...assets) - }) - - resolve() - }); - }) -}) - -export const bundleConfig = ExecuteStep.register('WebpackBundleConfig', (context) => { - let entry = {} - context.files.forEach((file) => { - let name = basename(file) - const ext = extname(name) - const nameKey = name.substring(0, name.length - ext.length) - entry[nameKey] = file - }) - - const externals = [] - if (context.deployTarget === 'aws') { - externals.push({ - 'aws-sdk': 'commonjs aws-sdk' - }) - } - - const webpackConfig = { - ...config.webpack, - entry: entry, - externals - } - - return webpackConfig -}) \ No newline at end of file From 7836282d9c331ce7934b66ba4583778679434f52 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Tue, 2 Jan 2018 12:19:17 +0000 Subject: [PATCH 105/196] 0.0.23 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2d9e727..5530d20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.22", + "version": "0.0.23", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 94173ed..95b0b71 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.22", + "version": "0.0.23", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 478dac261cb87241727e1ca2e2422e2e0192cd64 Mon Sep 17 00:00:00 2001 From: borzav Date: Wed, 3 Jan 2018 10:30:49 +0100 Subject: [PATCH 106/196] local environment handle the any method --- src/cli/commands/local.ts | 3 ++- src/providers/local.ts | 5 +++-- test/invoke.tests.ts | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/cli/commands/local.ts b/src/cli/commands/local.ts index 331d508..e1d5b9a 100644 --- a/src/cli/commands/local.ts +++ b/src/cli/commands/local.ts @@ -42,7 +42,8 @@ export default ({ createContext, annotations: { getMetadata, constants, getFunct } for (const method of event.methods) { - app[method]( + const expressMethod = method.toLowerCase() === 'any' ? 'use' : method.toLowerCase() + app[expressMethod]( path, logMiddleware(isLoggingEnabled, serviceDefinition.service), environmentConfigMiddleware(serviceDefinition.service), diff --git a/src/providers/local.ts b/src/providers/local.ts index c30b655..2977741 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -57,13 +57,14 @@ export class LocalProvider extends Provider { throw new Error('missing http configuration') } - const method = httpAttr.methods[0] || 'GET' + let method = (httpAttr.methods[0] || 'GET').toString().toLowerCase() + method = method === 'any' ? 'post' : method const invokeParams: any = { method, url: `http://localhost:${process.env.FUNCTIONAL_LOCAL_PORT}${httpAttr.path}`, }; - if (method.toLowerCase() === 'get') { + if (method === 'get') { invokeParams.qs = params } else { invokeParams.body = params diff --git a/test/invoke.tests.ts b/test/invoke.tests.ts index f724625..a67ebda 100644 --- a/test/invoke.tests.ts +++ b/test/invoke.tests.ts @@ -835,6 +835,44 @@ describe('invoke', () => { expect(counter).to.equal(3) }) + + it("method any", async () => { + let counter = 0 + + class TestProvider extends LocalProvider { + public async invokeExec(config: any): Promise { + counter++ + + expect(config).to.have.property('method', 'post') + expect(config).to.have.property('url', `${APP_BASE_URL}/v1/a1`) + expect(config).to.have.not.property('qs') + expect(config).to.have.property('body').that.deep.equal({ p1: 'p1' }) + expect(config).to.have.property('json', true) + + } + } + addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) + + @rest({ path: '/v1/a1', methods: ['any'] }) + @injectable() + class A extends FunctionalService { + public async handle( @param p1) { } + } + + class B extends FunctionalService { + public async handle( @inject(A) a: A) { + counter++ + const aResult = await a.invoke({ p1: 'p1' }) + } + } + + const invoker = B.createInvoker() + await invoker({}, { + send: () => { counter++ } + }, (e) => { expect(false).to.equal(true, e.message) }) + + expect(counter).to.equal(3) + }) }) describe('aws', () => { From 47ff405c306779940366f492e745c4ec1a70d1f0 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Wed, 3 Jan 2018 09:34:31 +0000 Subject: [PATCH 107/196] 0.0.24 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5530d20..d10898b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.23", + "version": "0.0.24", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 95b0b71..5cda679 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.23", + "version": "0.0.24", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 143e9dccb30c1111cc2b4ccdbbfb07d65af2833b Mon Sep 17 00:00:00 2001 From: borzav Date: Mon, 8 Jan 2018 09:45:18 +0100 Subject: [PATCH 108/196] fix:deploy target aws region --- src/cli/providers/cloudFormation/context/apiGateway.ts | 4 +++- src/cli/utilities/aws/cloudFormation.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/cli/providers/cloudFormation/context/apiGateway.ts b/src/cli/providers/cloudFormation/context/apiGateway.ts index 68b3f76..80d4ab7 100644 --- a/src/cli/providers/cloudFormation/context/apiGateway.ts +++ b/src/cli/providers/cloudFormation/context/apiGateway.ts @@ -42,7 +42,9 @@ export const gatewayRestApi = ExecuteStep.register('ApiGateway-RestApi', async ( { "Ref": resourceName }, - ".execute-api.eu-central-1.amazonaws.com/", + ".execute-api.", + context.awsRegion, + ".amazonaws.com/", context.stage ] ] diff --git a/src/cli/utilities/aws/cloudFormation.ts b/src/cli/utilities/aws/cloudFormation.ts index 04231d9..49d6d6e 100644 --- a/src/cli/utilities/aws/cloudFormation.ts +++ b/src/cli/utilities/aws/cloudFormation.ts @@ -55,7 +55,7 @@ export const updateStack = ExecuteStep.register('CloudFormation-UpdateStack', (c const params = { ...cfConfig, StackName: `${cfConfig.StackName}-${context.stage}`, - TemplateURL: `https://s3.eu-central-1.amazonaws.com/${context.awsBucket}/${context.S3CloudFormationTemplate}`, + TemplateURL: `https://s3.${context.awsRegion}.amazonaws.com/${context.awsBucket}/${context.S3CloudFormationTemplate}`, UsePreviousTemplate: false } From 64be424a61107e69d8b2355b089026a55a108f85 Mon Sep 17 00:00:00 2001 From: borzav Date: Mon, 8 Jan 2018 11:27:11 +0100 Subject: [PATCH 109/196] ADD: cors config for allow headers --- src/annotations/classes/aws/apiGateway.ts | 3 +- src/annotations/classes/azure/httpTrigger.ts | 2 + src/annotations/classes/rest.ts | 4 +- .../cloudFormation/context/apiGateway.ts | 13 ++--- test/annotation.tests.ts | 50 +++++++++++++++++++ 5 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/annotations/classes/aws/apiGateway.ts b/src/annotations/classes/aws/apiGateway.ts index acdac6a..2a425f4 100644 --- a/src/annotations/classes/aws/apiGateway.ts +++ b/src/annotations/classes/aws/apiGateway.ts @@ -9,7 +9,7 @@ export const defaultEndpoint = { } export const apiGateway = (endpoint: { - path: string, method?: string, cors?: boolean, + path: string, method?: string, cors?: boolean, corsConfig?: { headers: string[] }, authorization?: 'AWS_IAM' | 'NONE' | 'CUSTOM' | 'COGNITO_USER_POOLS' }) => { return (target: Function) => { @@ -25,6 +25,7 @@ rest.extension('aws', (target, config) => { path: config.path, method, cors: config.cors, + corsConfig: config.corsConfig, authorization: config.anonymous ? 'NONE' : 'AWS_IAM' }) decorator(target) diff --git a/src/annotations/classes/azure/httpTrigger.ts b/src/annotations/classes/azure/httpTrigger.ts index 88c2ede..f7ceb8f 100644 --- a/src/annotations/classes/azure/httpTrigger.ts +++ b/src/annotations/classes/azure/httpTrigger.ts @@ -12,6 +12,7 @@ export const httpTrigger = (endpoint: { route: string, methods?: string[], cors?: boolean, + corsConfig?: { headers: string[] }, authLevel?: 'anonymous' | 'function' | 'admin' }) => { return (target: Function) => { @@ -26,6 +27,7 @@ rest.extension('azure', (target, config) => { route: config.path, methods: config.methods, cors: config.cors, + corsConfig: config.corsConfig, authLevel: config.anonymous ? 'anonymous' : 'function' }) decorator(target) diff --git a/src/annotations/classes/rest.ts b/src/annotations/classes/rest.ts index 71926a3..ab9fea1 100644 --- a/src/annotations/classes/rest.ts +++ b/src/annotations/classes/rest.ts @@ -1,6 +1,6 @@ import { expandableDecorator } from './expandableDecorator' -export const rest = expandableDecorator<{ path: string, methods?: string[], cors?: boolean, anonymous?: boolean }>({ +export const rest = expandableDecorator<{ path: string, methods?: string[], cors?: boolean, corsConfig?: { headers: string[] }, anonymous?: boolean }>({ name: 'rest', defaultValues: { methods: ['get'], @@ -11,7 +11,7 @@ export const rest = expandableDecorator<{ path: string, methods?: string[], cors export interface IHttpMethod { (path: string): Function - (config: { path: string, cors?: boolean, anonymous?: boolean }): Function + (config: { path: string, cors?: boolean, corsConfig?: { headers: string[] }, anonymous?: boolean }): Function } export const resolveParam = (p: any, defaults) => { diff --git a/src/cli/providers/cloudFormation/context/apiGateway.ts b/src/cli/providers/cloudFormation/context/apiGateway.ts index 80d4ab7..5c691a7 100644 --- a/src/cli/providers/cloudFormation/context/apiGateway.ts +++ b/src/cli/providers/cloudFormation/context/apiGateway.ts @@ -64,9 +64,9 @@ export const gatewayResources = ExecuteStep.register('ApiGateway-Resources', asy }) } - for (const [endpointResourceName, { serviceDefinition, methods }] of endpointsCors) { + for (const [endpointResourceName, { serviceDefinition, methods, headers }] of endpointsCors) { await executor({ - context: { ...context, endpointResourceName, serviceDefinition, methods }, + context: { ...context, endpointResourceName, serviceDefinition, methods, headers }, name: `ApiGateway-Method-Options-${endpointResourceName}`, method: setOptionsMethodResource }) @@ -87,7 +87,7 @@ export const apiGatewayMethods = async (context) => { } export const apiGatewayMethod = async (context) => { - const { path, cors, authorization, endpoints, endpointsCors, serviceDefinition } = context + const { path, cors, corsConfig, authorization, endpoints, endpointsCors, serviceDefinition } = context const method = context.method.toUpperCase() const pathParts = path.split('/') @@ -127,7 +127,8 @@ export const apiGatewayMethod = async (context) => { if (cors) { let value = { serviceDefinition, - methods: ['OPTIONS'] + methods: ['OPTIONS'], + headers: ['Content-Type', 'Authorization', 'X-Requested-With', ...((corsConfig || {}).headers || [])] } if (endpointsCors.has(endpoint.endpointResourceName)) { value = endpointsCors.get(endpoint.endpointResourceName) @@ -257,7 +258,7 @@ export const gatewayDeployment = ExecuteStep.register('ApiGateway-Deployment', a }) export const setOptionsMethodResource = async (context) => { - const { endpointResourceName, serviceDefinition, methods } = context + const { endpointResourceName, serviceDefinition, methods, headers } = context const properties = { "AuthorizationType": "NONE", "HttpMethod": "OPTIONS", @@ -284,7 +285,7 @@ export const setOptionsMethodResource = async (context) => { "StatusCode": "200", "ResponseParameters": { "method.response.header.Access-Control-Allow-Origin": "'*'", - "method.response.header.Access-Control-Allow-Headers": "'Content-Type,Authorization,X-Requested-With'", + "method.response.header.Access-Control-Allow-Headers": `'${headers.join(',')}'`, "method.response.header.Access-Control-Allow-Methods": `'${methods.join(',')}'`, "method.response.header.Access-Control-Allow-Credentials": "'true'" }, diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index 92d0b76..6d3a6d3 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -243,6 +243,22 @@ describe('annotations', () => { expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('authorization', 'AWS_IAM') }) + it("corsConfig", () => { + @apiGateway({ path: '/v1/test', cors: true, corsConfig: { headers: ['X-test'] } }) + class ApiGatewayTestClass { } + + const value = getMetadata(CLASS_APIGATEWAYKEY, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.property('method', 'get') + expect(metadata).to.have.property('cors', true) + expect(metadata).to.have.deep.property('corsConfig', { headers: ['X-test'] }) + expect(metadata).to.have.property('authorization', 'AWS_IAM') + }) it("authorization", () => { @apiGateway({ path: '/v1/test', authorization: 'NONE' }) class ApiGatewayTestClass { } @@ -305,6 +321,23 @@ describe('annotations', () => { expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('authLevel', 'function') }) + + it("corsConfig", () => { + @httpTrigger({ route: '/v1/test', cors: true, corsConfig: { headers: ['X-test'] } }) + class HttpTriggerTestClass { } + + const value = getMetadata(CLASS_HTTPTRIGGER, HttpTriggerTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('route', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['get']) + expect(metadata).to.have.property('cors', true) + expect(metadata).to.have.deep.property('corsConfig', { headers: ['X-test'] }) + expect(metadata).to.have.property('authLevel', 'function') + }) it("authorization", () => { @httpTrigger({ route: '/v1/test', authLevel: 'anonymous' }) class HttpTriggerTestClass { } @@ -368,6 +401,23 @@ describe('annotations', () => { expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('anonymous', false) }) + + it("corsConfig", () => { + @rest({ path: '/v1/test', cors: true, corsConfig: { headers: ['X-test'] } }) + class ApiGatewayTestClass { } + + const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('path', '/v1/test') + expect(metadata).to.have.deep.property('methods', ['get']) + expect(metadata).to.have.property('cors', true) + expect(metadata).to.have.deep.property('corsConfig', { headers: ['X-test'] }) + expect(metadata).to.have.property('anonymous', false) + }) it("authorization", () => { @rest({ path: '/v1/test', anonymous: true }) class ApiGatewayTestClass { } From a8aa09a618d6bfe2963ee5a320eeb1e62fc0a074 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Mon, 8 Jan 2018 10:55:57 +0000 Subject: [PATCH 110/196] 0.0.25 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index d10898b..d239290 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.24", + "version": "0.0.25", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 5cda679..0b8c647 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.24", + "version": "0.0.25", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 9ed20f90bf30eddb0eb51e8ad1c785fde04e63eb Mon Sep 17 00:00:00 2001 From: borzav Date: Wed, 10 Jan 2018 16:16:12 +0100 Subject: [PATCH 111/196] ADD: s3 upload method --- src/classes/api/aws/s3Storage.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/classes/api/aws/s3Storage.ts b/src/classes/api/aws/s3Storage.ts index 783619c..385c920 100644 --- a/src/classes/api/aws/s3Storage.ts +++ b/src/classes/api/aws/s3Storage.ts @@ -42,13 +42,13 @@ export class S3Api extends Api { }) export class S3Storage extends Api { private _s3Client: S3 - public constructor(@inject(S3Api) private s3Api: S3Api) { + public constructor( @inject(S3Api) private s3Api: S3Api) { super() } - public async init(){ + public async init() { this._s3Client = this.s3Api.getS3() } - + public getS3() { return this._s3Client } @@ -83,6 +83,13 @@ export class S3Storage extends Api { }) } + public upload( + params: Partial, + options?: S3.ManagedUpload.ManagedUploadOptions, + callback?: (err: Error, data: S3.ManagedUpload.SendData) => void): S3.ManagedUpload { + return this._s3Client.upload(this.setDefaultValues(params, 'upload'), options, callback) + } + protected setDefaultValues(params, command) { const bucketConfig = (getMetadata(CLASS_S3CONFIGURATIONKEY, this) || [])[0] || {} const bucketName = bucketConfig.bucketName From 44389ecb0c29646cb5d6fd2ccd0746af3339890e Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Wed, 10 Jan 2018 15:20:26 +0000 Subject: [PATCH 112/196] 0.0.26 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index d239290..c1a6761 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.25", + "version": "0.0.26", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0b8c647..a968894 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.25", + "version": "0.0.26", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 78dce8992c4636a048d029376f18855d53468b10 Mon Sep 17 00:00:00 2001 From: borzav Date: Thu, 11 Jan 2018 12:23:52 +0100 Subject: [PATCH 113/196] add dynamo DeleteItem allow action --- src/cli/providers/cloudFormation/context/resources.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 6201206..7f52a87 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -223,7 +223,8 @@ export const dynamoPolicy = async (context) => { "dynamodb:Scan", "dynamodb:GetItem", "dynamodb:PutItem", - "dynamodb:UpdateItem" + "dynamodb:UpdateItem", + "dynamodb:DeleteItem" ], "Resource": usedTableConfigs.map(t => { return { From 5c7e99af12af5ff1c417bdcc557eb37a8c708ddf Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Thu, 11 Jan 2018 11:27:12 +0000 Subject: [PATCH 114/196] 0.0.27 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c1a6761..a134c57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.26", + "version": "0.0.27", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a968894..2661305 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.26", + "version": "0.0.27", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From bf516e85145aced874de6139c014b5c3b7bbd19d Mon Sep 17 00:00:00 2001 From: borzav Date: Thu, 11 Jan 2018 15:25:54 +0100 Subject: [PATCH 115/196] change: endpoint list in local run --- src/cli/commands/local.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/cli/commands/local.ts b/src/cli/commands/local.ts index e1d5b9a..51de1aa 100644 --- a/src/cli/commands/local.ts +++ b/src/cli/commands/local.ts @@ -28,6 +28,7 @@ export default ({ createContext, annotations: { getMetadata, constants, getFunct let app = express() app.use(bodyParser.json({ limit: '10mb' })) + console.log("") for (let serviceDefinition of context.publishedFunctions) { let httpMetadata = getMetadata(rest.environmentKey, serviceDefinition.service) || [] @@ -35,13 +36,13 @@ export default ({ createContext, annotations: { getMetadata, constants, getFunct const isLoggingEnabled = getMetadata(constants.CLASS_LOGKEY, serviceDefinition.service) const transformMiddlewares = [] const path = pathTransform(event.path, transformMiddlewares) - console.log(`${new Date().toISOString()} ${getFunctionName(serviceDefinition.service)} listening { path: '${path}', methods: '${event.methods}', cors: ${event.cors ? true : false} }`) - if (event.cors) { app.use(path, cors()) } - + for (const method of event.methods) { + console.log(`${method.toUpperCase()}\t${event.path}\t${getFunctionName(serviceDefinition.service)}`) + const expressMethod = method.toLowerCase() === 'any' ? 'use' : method.toLowerCase() app[expressMethod]( path, @@ -53,6 +54,7 @@ export default ({ createContext, annotations: { getMetadata, constants, getFunct } } } + console.log("") return app.listen(context.localPort, function () { process.env.FUNCTIONAL_LOCAL_PORT = context.localPort From 5621034b9872ed28a016e43a45d17f8ab80bd210 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Thu, 11 Jan 2018 14:29:02 +0000 Subject: [PATCH 116/196] 0.0.28 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index a134c57..df584b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.27", + "version": "0.0.28", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2661305..35dba72 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.27", + "version": "0.0.28", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From f94ec74d14f2b8a86264a09b8d988e4bab47bbe5 Mon Sep 17 00:00:00 2001 From: borzav Date: Fri, 12 Jan 2018 11:58:01 +0100 Subject: [PATCH 117/196] FIX: deep injected s3 bucket policy --- .../providers/cloudFormation/context/s3Storage.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/cli/providers/cloudFormation/context/s3Storage.ts b/src/cli/providers/cloudFormation/context/s3Storage.ts index 97b19ec..0ae80be 100644 --- a/src/cli/providers/cloudFormation/context/s3Storage.ts +++ b/src/cli/providers/cloudFormation/context/s3Storage.ts @@ -140,16 +140,10 @@ export const s3StoragePolicy = async (context) => { serviceDefinition.roleResource.Properties.Policies.push(policy) } - policy.PolicyDocument.Statement[0].Resource.push({ - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - `${s3Config.AWSBucketName}`, - "/*" - ] - ] - }) + const s3Arn = `arn:aws:s3:::${s3Config.AWSBucketName}/*` + if (!policy.PolicyDocument.Statement[0].Resource.includes(s3Arn)) { + policy.PolicyDocument.Statement[0].Resource.push(s3Arn) + } } export const s3StorageSubscriptions = async (context) => { From b8b3170e570eef723d65c60fc607a96b1662a991 Mon Sep 17 00:00:00 2001 From: borzav Date: Fri, 12 Jan 2018 12:06:21 +0100 Subject: [PATCH 118/196] change: cf deploy update poll timeout --- src/cli/utilities/aws/cloudFormation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/utilities/aws/cloudFormation.ts b/src/cli/utilities/aws/cloudFormation.ts index 49d6d6e..7da203b 100644 --- a/src/cli/utilities/aws/cloudFormation.ts +++ b/src/cli/utilities/aws/cloudFormation.ts @@ -63,7 +63,7 @@ export const updateStack = ExecuteStep.register('CloudFormation-UpdateStack', (c if (err) return reject(err) try { - const result = await stackStateWaiter('UPDATE_COMPLETE', context, 100) + const result = await stackStateWaiter('UPDATE_COMPLETE', context, 1000) resolve(result) } catch (e) { reject(e) From 70d5c3d21f3c2d7ce265b803036c94c496d5ab2d Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Fri, 12 Jan 2018 11:18:24 +0000 Subject: [PATCH 119/196] 0.0.29 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index df584b2..e11e010 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.28", + "version": "0.0.29", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 35dba72..ec104c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.28", + "version": "0.0.29", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 61b2b3101d9f8a8c5dc5f6e3b9c02c9101446b4c Mon Sep 17 00:00:00 2001 From: borzav Date: Wed, 17 Jan 2018 10:06:53 +0100 Subject: [PATCH 120/196] REMOVE: unnecessary cf stack outputs --- .../providers/cloudFormation/context/apiGateway.ts | 2 +- .../providers/cloudFormation/context/dynamoTable.ts | 2 +- src/cli/providers/cloudFormation/context/s3Storage.ts | 11 ++--------- src/cli/providers/cloudFormation/context/sns.ts | 4 ++-- src/cli/providers/cloudFormation/utils.ts | 4 ++-- 5 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/cli/providers/cloudFormation/context/apiGateway.ts b/src/cli/providers/cloudFormation/context/apiGateway.ts index 5c691a7..12d51ae 100644 --- a/src/cli/providers/cloudFormation/context/apiGateway.ts +++ b/src/cli/providers/cloudFormation/context/apiGateway.ts @@ -349,5 +349,5 @@ export const setGatewayPermissions = (context) => { "Properties": properties } const resourceName = `ApiGateway${serviceDefinition.resourceName}Permission` - setResource(context, resourceName, methodConfig, getStackName(serviceDefinition), true) + setResource(context, resourceName, methodConfig, getStackName(serviceDefinition), false, true) } diff --git a/src/cli/providers/cloudFormation/context/dynamoTable.ts b/src/cli/providers/cloudFormation/context/dynamoTable.ts index 4d5b98e..19f8f6f 100644 --- a/src/cli/providers/cloudFormation/context/dynamoTable.ts +++ b/src/cli/providers/cloudFormation/context/dynamoTable.ts @@ -53,7 +53,7 @@ export const tableResource = async (context) => { } const tableResourceName = `Dynamo${tableConfig.tableName}` - const resourceName = setResource(context, tableResourceName, dynamoDb, DYNAMODB_TABLE_STACK) + const resourceName = setResource(context, tableResourceName, dynamoDb, DYNAMODB_TABLE_STACK, true) await setStackParameter({ ...context, diff --git a/src/cli/providers/cloudFormation/context/s3Storage.ts b/src/cli/providers/cloudFormation/context/s3Storage.ts index 0ae80be..24a9a06 100644 --- a/src/cli/providers/cloudFormation/context/s3Storage.ts +++ b/src/cli/providers/cloudFormation/context/s3Storage.ts @@ -82,7 +82,7 @@ export const s3Storage = async (context) => { } const resourceName = `S3${s3Config.bucketName}` - const bucketResourceName = setResource(context, resourceName, s3Bucket, S3_STORAGE_STACK) + const bucketResourceName = setResource(context, resourceName, s3Bucket, S3_STORAGE_STACK, true) s3Config.resourceName = bucketResourceName await executor({ @@ -173,13 +173,6 @@ export const s3BucketSubscription = async (context) => { if (s3Config.exists) return - await setStackParameter({ - ...context, - sourceStackName: getStackName(serviceDefinition), - resourceName: serviceDefinition.resourceName, - targetStackName: S3_STORAGE_STACK - }) - await setStackParameter({ ...context, sourceStackName: getStackName(serviceDefinition), @@ -225,5 +218,5 @@ export const s3Permissions = (context) => { "Properties": properties } const resourceName = `${s3Config.resourceName}Permission` - const permissionResourceName = setResource(context, resourceName, s3Permission, getStackName(serviceDefinition), true) + const permissionResourceName = setResource(context, resourceName, s3Permission, getStackName(serviceDefinition), false, true) } diff --git a/src/cli/providers/cloudFormation/context/sns.ts b/src/cli/providers/cloudFormation/context/sns.ts index 20978ad..ea46eae 100644 --- a/src/cli/providers/cloudFormation/context/sns.ts +++ b/src/cli/providers/cloudFormation/context/sns.ts @@ -59,7 +59,7 @@ export const snsTopic = async (context) => { } const resourceName = `SNS${snsConfig.ADVTopicName}` - const topicResourceName = setResource(context, resourceName, snsTopic, SNS_TABLE_STACK) + const topicResourceName = setResource(context, resourceName, snsTopic, SNS_TABLE_STACK, true) snsConfig.resourceName = topicResourceName } @@ -145,7 +145,7 @@ export const snsPermissions = (context) => { "Properties": properties } const resourceName = `${snsConfig.resourceName}Permission` - setResource(context, resourceName, snsPermission, getStackName(serviceDefinition), true) + setResource(context, resourceName, snsPermission, getStackName(serviceDefinition), false, true) } const updateSNSEnvironmentVariables = async (context) => { diff --git a/src/cli/providers/cloudFormation/utils.ts b/src/cli/providers/cloudFormation/utils.ts index 70abd53..533e5ea 100644 --- a/src/cli/providers/cloudFormation/utils.ts +++ b/src/cli/providers/cloudFormation/utils.ts @@ -14,7 +14,7 @@ export const getResourceName = (name) => { return normalizeName(name) } -export const setResource = (context: any, name: string, resource: any, stackName: string = null, allowOverride = false) => { +export const setResource = (context: any, name: string, resource: any, stackName: string = null, output = false, allowOverride = false) => { const resourceName = getResourceName(name) let resources @@ -42,7 +42,7 @@ export const setResource = (context: any, name: string, resource: any, stackName } resources[resourceName] = resource - if (outputs) { + if (output && outputs) { outputs[resourceName] = { "Value": { "Ref": resourceName From 318830fd27916b02a6106a4020293e74abcf416f Mon Sep 17 00:00:00 2001 From: borzav Date: Wed, 17 Jan 2018 10:08:51 +0100 Subject: [PATCH 121/196] update readme --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index cd77534..7d4d2e9 100644 --- a/README.md +++ b/README.md @@ -23,3 +23,13 @@ Use the JavaScript language and the JSON syntax to describe infrastructure and e ## examples - https://github.com/jaystack/functionly-examples + +### Typescript +- [todoDB](https://github.com/jaystack/functionly-examples/tree/master/todoDB) +- [todoDB-mongo](https://github.com/jaystack/functionly-examples/tree/master/todoDB-mongo) +- [todoDBAdvanced](https://github.com/jaystack/functionly-examples/tree/master/todoDBAdvanced) +- [eventSource](https://github.com/jaystack/functionly-examples/tree/master/eventSource) + +### ES6 +- [greeter](https://github.com/jaystack/functionly-examples/tree/master/greeter) +- [todoDB-es6](https://github.com/jaystack/functionly-examples/tree/master/todoDB-es6) From d09ed0c9ec26bcdd54a03dd18984423ec30122d8 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Wed, 17 Jan 2018 09:12:51 +0000 Subject: [PATCH 122/196] 0.0.30 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e11e010..55f4673 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.29", + "version": "0.0.30", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ec104c6..8d92abc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.29", + "version": "0.0.30", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From d962954f8ff3bb56a7dc53c3e65e7b74b4b5efa6 Mon Sep 17 00:00:00 2001 From: borzav Date: Wed, 17 Jan 2018 17:36:01 +0100 Subject: [PATCH 123/196] ADD: aws CloudWatchEvent as eventSource --- .../classes/aws/cloudWatchEvent.ts | 14 +++ src/annotations/constants.ts | 1 + src/annotations/index.ts | 1 + src/classes/api/aws/cloudWatchEvent.ts | 9 ++ src/classes/index.ts | 1 + .../cloudFormation/context/cloudWatchEvent.ts | 95 +++++++++++++++++++ .../cloudFormation/context/resources.ts | 1 + src/cli/providers/cloudFormation/index.ts | 4 +- src/index.ts | 4 +- test/annotation.tests.ts | 61 +++++++++++- 10 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 src/annotations/classes/aws/cloudWatchEvent.ts create mode 100644 src/classes/api/aws/cloudWatchEvent.ts create mode 100644 src/cli/providers/cloudFormation/context/cloudWatchEvent.ts diff --git a/src/annotations/classes/aws/cloudWatchEvent.ts b/src/annotations/classes/aws/cloudWatchEvent.ts new file mode 100644 index 0000000..37c6cd4 --- /dev/null +++ b/src/annotations/classes/aws/cloudWatchEvent.ts @@ -0,0 +1,14 @@ +import { CLASS_CLOUDWATCHEVENT } from '../../constants' +import { getMetadata, defineMetadata } from '../../metadata' +import { rest } from '../rest' + +export const cloudWatchEvent = (config: { scheduleExpression?: string, eventPattern?: any }) => { + return (target: Function) => { + let metadata = getMetadata(CLASS_CLOUDWATCHEVENT, target) || [] + metadata.push({ + ...config, + definedBy: target.name + }) + defineMetadata(CLASS_CLOUDWATCHEVENT, [...metadata], target); + } +} \ No newline at end of file diff --git a/src/annotations/constants.ts b/src/annotations/constants.ts index 5bbe85c..271ad2f 100644 --- a/src/annotations/constants.ts +++ b/src/annotations/constants.ts @@ -7,6 +7,7 @@ export const CLASS_GROUP = 'functionly:class:group' export const CLASS_ENVIRONMENTKEY = 'functionly:class:environment' export const CLASS_MIDDLEWAREKEY = 'functionly:class:middleware' export const CLASS_APIGATEWAYKEY = 'functionly:class:apigateway' +export const CLASS_CLOUDWATCHEVENT = 'functionly:class:cloudwatchevent' export const CLASS_TAGKEY = 'functionly:class:tag' export const CLASS_LOGKEY = 'functionly:class:log' export const CLASS_INJECTABLEKEY = 'functionly:class:injectable' diff --git a/src/annotations/index.ts b/src/annotations/index.ts index 8f24b72..498e3e9 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -1,6 +1,7 @@ export { templates, applyTemplates } from './templates' export { injectable, InjectionScope } from './classes/injectable' export { apiGateway } from './classes/aws/apiGateway' +export { cloudWatchEvent } from './classes/aws/cloudWatchEvent' export { httpTrigger } from './classes/azure/httpTrigger' export { rest, httpGet, httpPost, httpPut, httpPatch, httpDelete, IHttpMethod } from './classes/rest' export { environment } from './classes/environment' diff --git a/src/classes/api/aws/cloudWatchEvent.ts b/src/classes/api/aws/cloudWatchEvent.ts new file mode 100644 index 0000000..7d46958 --- /dev/null +++ b/src/classes/api/aws/cloudWatchEvent.ts @@ -0,0 +1,9 @@ + +import { Api } from '../../api' +import { constants, classConfig } from '../../../annotations' +const { CLASS_CLOUDWATCHEVENT } = constants + +@classConfig({ + injectServiceEventSourceKey: CLASS_CLOUDWATCHEVENT +}) +export class CloudWatchEvent extends Api { } diff --git a/src/classes/index.ts b/src/classes/index.ts index 3c55c80..2728f57 100644 --- a/src/classes/index.ts +++ b/src/classes/index.ts @@ -5,6 +5,7 @@ export { DynamoTable, DocumentClientApi } from './api/aws/dynamoTable' export { SimpleNotificationService, SNSApi } from './api/aws/sns' export { S3Storage, S3Api } from './api/aws/s3Storage' export { ApiGateway } from './api/aws/apiGateway' +export { CloudWatchEvent } from './api/aws/cloudWatchEvent' export { callExtension } from './core/callExtension' export { Service } from './service' diff --git a/src/cli/providers/cloudFormation/context/cloudWatchEvent.ts b/src/cli/providers/cloudFormation/context/cloudWatchEvent.ts new file mode 100644 index 0000000..c26e032 --- /dev/null +++ b/src/cli/providers/cloudFormation/context/cloudWatchEvent.ts @@ -0,0 +1,95 @@ +import { getMetadata, constants, getFunctionName } from '../../../../annotations' +const { CLASS_CLOUDWATCHEVENT } = constants +import { ExecuteStep, executor } from '../../../context' +import { setResource } from '../utils' +import { setStackParameter, getStackName } from './stack' + +export const cloudWatchEvent = ExecuteStep.register('CloudWatchEvent', async (context) => { + await executor(context, cloudWatchResources) +}) + +export const cloudWatchResources = ExecuteStep.register('CloudWatchEvent-Resources', async (context) => { + for (const serviceDefinition of context.publishedFunctions) { + await executor({ + context: { ...context, serviceDefinition }, + name: `CloudWatchEvent-${serviceDefinition.service.name}`, + method: cloudWatchEventItems + }) + } +}) + +export const cloudWatchEventItems = async (context) => { + const { serviceDefinition } = context + + let cloudWatches = getMetadata(CLASS_CLOUDWATCHEVENT, serviceDefinition.service) || [] + for (let [ruleIndex, rule] of cloudWatches.entries()) { + await executor({ + context: { ...context, rule, ruleIndex }, + name: `CloudWatchEvent-Rule-${serviceDefinition.service.name}`, + method: cloudWatchEventItem + }) + } +} + +export const cloudWatchEventItem = async (context) => { + const { serviceDefinition, rule, ruleIndex } = context + + const properties: any = { + "State": "ENABLED", + "Targets": [{ + "Arn": { "Fn::GetAtt": [serviceDefinition.resourceName, "Arn"] }, + "Id": `${getFunctionName(serviceDefinition.service)}Schedule${ruleIndex}` + }] + } + + if (rule.scheduleExpression) { + properties.ScheduleExpression = rule.scheduleExpression + } else if (rule.eventPattern) { + properties.EventPattern = rule.eventPattern + } else { + // invalid event configuration + return + } + + const methodConfig = { + "Type": "AWS::Events::Rule", + "Properties": properties, + "DependsOn": [ + serviceDefinition.resourceName + ] + } + const resourceName = `CloudWatchEvent${getFunctionName(serviceDefinition.service)}` + const name = setResource(context, resourceName, methodConfig, getStackName(serviceDefinition)) + + await executor({ + context: { ...context, cloudWatchResourceName: resourceName }, + name: `CloudWatchEvent-Rule-Permission-${serviceDefinition.service.name}`, + method: cloudWatchEvenetPermission + }) +} + +export const cloudWatchEvenetPermission = (context) => { + const { serviceDefinition, cloudWatchResourceName } = context + const properties = { + "FunctionName": { + "Fn::GetAtt": [ + serviceDefinition.resourceName, + "Arn" + ] + }, + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "SourceArn": { "Fn::GetAtt": [cloudWatchResourceName, "Arn"] } + } + + const methodConfig = { + "Type": "AWS::Lambda::Permission", + "Properties": properties, + "DependsOn": [ + serviceDefinition.resourceName, + cloudWatchResourceName + ] + } + const resourceName = `CloudWatchEvent${serviceDefinition.resourceName}Permission` + setResource(context, resourceName, methodConfig, getStackName(serviceDefinition), false, true) +} diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 7f52a87..015879d 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -12,6 +12,7 @@ export { s3DeploymentBucket, s3DeploymentBucketParameter, s3 } from './s3Storage export { S3_DEPLOYMENT_BUCKET_RESOURCE_NAME } from './s3StorageDeployment' export { apiGateway } from './apiGateway' export { sns } from './sns' +export { cloudWatchEvent } from './cloudWatchEvent' export { tableResources, tableSubscribers } from './dynamoTable' diff --git a/src/cli/providers/cloudFormation/index.ts b/src/cli/providers/cloudFormation/index.ts index 79b92a0..f9e4456 100644 --- a/src/cli/providers/cloudFormation/index.ts +++ b/src/cli/providers/cloudFormation/index.ts @@ -9,7 +9,7 @@ import { executor } from '../../context' import { cloudFormationInit, cloudFormationMerge } from './context/cloudFormationInit' import { tableResources, lambdaResources, roleResources, s3DeploymentBucket, s3DeploymentBucketParameter, - apiGateway, sns, s3, initStacks, lambdaLogResources, S3_DEPLOYMENT_BUCKET_RESOURCE_NAME, tableSubscribers + apiGateway, sns, s3, cloudWatchEvent, initStacks, lambdaLogResources, S3_DEPLOYMENT_BUCKET_RESOURCE_NAME, tableSubscribers } from './context/resources' import { uploadTemplate, persistCreateTemplate } from './context/uploadTemplate' @@ -58,6 +58,7 @@ export const cloudFormation = { await executor(context, apiGateway) await executor(context, sns) await executor(context, s3) + await executor(context, cloudWatchEvent) await executor(context, tableSubscribers) logger.info(`Functionly: Uploading template...`) @@ -97,6 +98,7 @@ export const cloudFormation = { await executor(context, apiGateway) await executor(context, sns) await executor(context, s3) + await executor(context, cloudWatchEvent) await executor(context, tableSubscribers) logger.info(`Functionly: Save template...`) diff --git a/src/index.ts b/src/index.ts index 51b3edc..89962cd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ export { FunctionalService, Api, Service, PreHook, PostHook, Resource } from './classes' /* Apis */ -export { DynamoTable, DocumentClientApi, SimpleNotificationService, SNSApi, S3Storage, S3Api, ApiGateway } from './classes' +export { DynamoTable, DocumentClientApi, SimpleNotificationService, SNSApi, S3Storage, S3Api, ApiGateway, CloudWatchEvent } from './classes' export { MongoCollection, MongoConnection } from './plugins/mongo' /* Hooks */ @@ -17,7 +17,7 @@ export { addProvider, removeProvider } from './providers' export { injectable, apiGateway, httpTrigger, rest, httpGet, httpPost, httpPut, httpPatch, httpDelete, IHttpMethod, environment, tag, log, functionName, dynamoTable, sns, s3Storage, eventSource, classConfig, use, description, role, group, aws, azure, - param, serviceParams, request, error, result, functionalServiceName, provider, stage, inject + param, serviceParams, request, error, result, functionalServiceName, provider, stage, inject, cloudWatchEvent } from './annotations' export { mongoCollection, mongoConnection } from './plugins/mongo' diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index 6d3a6d3..e9d8fcd 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -5,7 +5,7 @@ import { CLASS_APIGATEWAYKEY, CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_ENVIRONMENTKEY, CLASS_NAMEKEY, CLASS_INJECTABLEKEY, CLASS_LOGKEY, CLASS_AWSRUNTIMEKEY, CLASS_AWSMEMORYSIZEKEY, CLASS_AWSTIMEOUTKEY, CLASS_S3CONFIGURATIONKEY, CLASS_SNSCONFIGURATIONKEY, CLASS_TAGKEY, CLASS_ROLEKEY, CLASS_DESCRIPTIONKEY, - PARAMETER_PARAMKEY, CLASS_CLASSCONFIGKEY, CLASS_HTTPTRIGGER, CLASS_AZURENODEKEY + PARAMETER_PARAMKEY, CLASS_CLASSCONFIGKEY, CLASS_HTTPTRIGGER, CLASS_AZURENODEKEY, CLASS_CLOUDWATCHEVENT } from '../src/annotations/constants' import { applyTemplates, templates } from '../src/annotations/templates' import { getFunctionParameters } from '../src/annotations/utils' @@ -21,6 +21,7 @@ import { injectable, InjectionScope } from '../src/annotations/classes/injectabl import { log } from '../src/annotations/classes/log' import { s3Storage } from '../src/annotations/classes/s3Storage' import { sns } from '../src/annotations/classes/sns' +import { cloudWatchEvent } from '../src/annotations/classes/aws/cloudWatchEvent' import { tag } from '../src/annotations/classes/tag' import { eventSource } from '../src/annotations/classes/eventSource' import { classConfig } from '../src/annotations/classes/classConfig' @@ -961,6 +962,64 @@ describe('annotations', () => { expect(metadata).to.have.property('definedBy', SNSTestClass.name) }) }) + describe("scheduleEvent", () => { + it("missing config", () => { + @cloudWatchEvent({}) + class ScheduleEventTestClass { } + + const value = getMetadata(CLASS_CLOUDWATCHEVENT, ScheduleEventTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.not.have.property('scheduleExpression') + expect(metadata).to.not.have.property('eventPattern') + expect(metadata).to.have.property('definedBy', ScheduleEventTestClass.name) + }) + it("scheduleExpression", () => { + @cloudWatchEvent({ scheduleExpression: 'rate(10 minutes)' }) + class ScheduleEventTestClass { } + + const value = getMetadata(CLASS_CLOUDWATCHEVENT, ScheduleEventTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('scheduleExpression', 'rate(10 minutes)') + expect(metadata).to.not.have.property('eventPattern') + expect(metadata).to.have.property('definedBy', ScheduleEventTestClass.name) + }) + it("eventPattern", () => { + @cloudWatchEvent({ eventPattern: {} }) + class ScheduleEventTestClass { } + + const value = getMetadata(CLASS_CLOUDWATCHEVENT, ScheduleEventTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.not.have.property('scheduleExpression') + expect(metadata).to.have.deep.property('eventPattern', {}) + expect(metadata).to.have.property('definedBy', ScheduleEventTestClass.name) + }) + it("scheduleExpression and eventPattern", () => { + @cloudWatchEvent({ scheduleExpression: 'rate(10 minutes)', eventPattern: {} }) + class ScheduleEventTestClass { } + + const value = getMetadata(CLASS_CLOUDWATCHEVENT, ScheduleEventTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('scheduleExpression', 'rate(10 minutes)') + expect(metadata).to.have.deep.property('eventPattern', {}) + expect(metadata).to.have.property('definedBy', ScheduleEventTestClass.name) + }) + }) describe("tag", () => { it("key - value", () => { @tag('key', 'value') From d263aa2ecddb14657eba0d5ef8011856e2c42029 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Thu, 18 Jan 2018 09:54:33 +0000 Subject: [PATCH 124/196] 0.0.31 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 55f4673..65e6c46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.30", + "version": "0.0.31", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 8d92abc..355cdf1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.30", + "version": "0.0.31", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 3d8d9e94236cee1fd2ffaa512fa63df248e2a3bd Mon Sep 17 00:00:00 2001 From: borzav Date: Thu, 18 Jan 2018 14:04:44 +0100 Subject: [PATCH 125/196] ADD: dynamo alias to dynamoTable decorator --- src/annotations/classes/dynamoTable.ts | 2 + src/annotations/index.ts | 2 +- src/index.ts | 2 +- test/annotation.tests.ts | 117 ++++++++++++++++++++++++- 4 files changed, 120 insertions(+), 3 deletions(-) diff --git a/src/annotations/classes/dynamoTable.ts b/src/annotations/classes/dynamoTable.ts index 261806f..4710d8e 100644 --- a/src/annotations/classes/dynamoTable.ts +++ b/src/annotations/classes/dynamoTable.ts @@ -48,3 +48,5 @@ export const dynamoTable = (tableConfig?: { const environmentSetter = environment(templatedKey, templatedValue) environmentSetter(target) } + +export const dynamo = dynamoTable diff --git a/src/annotations/index.ts b/src/annotations/index.ts index 498e3e9..9cc437b 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -8,7 +8,7 @@ export { environment } from './classes/environment' export { tag } from './classes/tag' export { log } from './classes/log' export { functionName, getFunctionName } from './classes/functionName' -export { dynamoTable, __dynamoDBDefaults } from './classes/dynamoTable' +export { dynamoTable, dynamo, __dynamoDBDefaults } from './classes/dynamoTable' export { sns } from './classes/sns' export { s3Storage } from './classes/s3Storage' export { eventSource } from './classes/eventSource' diff --git a/src/index.ts b/src/index.ts index 89962cd..9ec755a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,7 +17,7 @@ export { addProvider, removeProvider } from './providers' export { injectable, apiGateway, httpTrigger, rest, httpGet, httpPost, httpPut, httpPatch, httpDelete, IHttpMethod, environment, tag, log, functionName, dynamoTable, sns, s3Storage, eventSource, classConfig, use, description, role, group, aws, azure, - param, serviceParams, request, error, result, functionalServiceName, provider, stage, inject, cloudWatchEvent + param, serviceParams, request, error, result, functionalServiceName, provider, stage, inject, cloudWatchEvent, dynamo } from './annotations' export { mongoCollection, mongoConnection } from './plugins/mongo' diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index e9d8fcd..2267ce2 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -14,7 +14,7 @@ import { expandableDecorator } from '../src/annotations/classes/expandableDecora import { apiGateway } from '../src/annotations/classes/aws/apiGateway' import { httpTrigger } from '../src/annotations/classes/azure/httpTrigger' import { rest, httpGet, httpPost, httpPut, httpPatch, httpDelete } from '../src/annotations/classes/rest' -import { dynamoTable, __dynamoDBDefaults } from '../src/annotations/classes/dynamoTable' +import { dynamoTable, dynamo, __dynamoDBDefaults } from '../src/annotations/classes/dynamoTable' import { environment } from '../src/annotations/classes/environment' import { functionName, getFunctionName } from '../src/annotations/classes/functionName' import { injectable, InjectionScope } from '../src/annotations/classes/injectable' @@ -685,6 +685,96 @@ describe('annotations', () => { }); }) }) + describe("dynamo", () => { + it("no param", () => { + @dynamo() + class DynamoTableTestClass { } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, DynamoTableTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('tableName', 'DynamoTableTestClass-table') + expect(metadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') + expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); + }) + it("empty", () => { + @dynamo({}) + class DynamoTableTestClass { } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, DynamoTableTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('tableName', 'DynamoTableTestClass-table') + expect(metadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') + expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); + }) + it("tableName", () => { + @dynamo({ tableName: 'mytablename' }) + class DynamoTableTestClass { } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, DynamoTableTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('tableName', 'mytablename') + expect(metadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') + expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); + }) + it("environmentKey", () => { + @dynamo({ tableName: 'mytablename', environmentKey: 'myenvkey' }) + class DynamoTableTestClass { } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, DynamoTableTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('tableName', 'mytablename') + expect(metadata).to.have.property('environmentKey', 'myenvkey') + expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); + }) + it("nativeConfig", () => { + @dynamo({ + tableName: 'mytablename', nativeConfig: { + ProvisionedThroughput: { + ReadCapacityUnits: 4, + WriteCapacityUnits: 4 + } + } + }) + class DynamoTableTestClass { } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, DynamoTableTestClass) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('tableName', 'mytablename') + expect(metadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') + expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal({ + ...__dynamoDBDefaults, + ProvisionedThroughput: { + ReadCapacityUnits: 4, + WriteCapacityUnits: 4 + } + }); + }) + }) describe("environment", () => { it("key - value", () => { @environment('key', 'value') @@ -1780,6 +1870,31 @@ describe('annotations', () => { expect(environmentMetadata).to.have.property('p1', 'v1') }) + it("dynamo param", () => { + @dynamo({ tableName: 't1', nativeConfig: { any: 'value' } }) + @injectable() + class DTable1 extends DynamoTable { } + + @injectable() + class Service1 extends Service { + public async handle( @inject(DTable1) table1) { } + } + + class FSTest1 extends FunctionalService { + public async handle( @inject(Service1) s1) { } + } + + const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, FSTest1) + + expect(value).to.be.not.undefined; + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('tableName', 't1') + expect(metadata).to.have.property('nativeConfig').to.have.property('any', 'value') + }) + it("dynamo param", () => { @dynamoTable({ tableName: 't1', nativeConfig: { any: 'value' } }) @injectable() From e7f8d2b40716af8ca71bab3805cc89fb9a3b4aeb Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Thu, 18 Jan 2018 13:08:21 +0000 Subject: [PATCH 126/196] 0.0.32 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 65e6c46..4c03eca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.31", + "version": "0.0.32", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 355cdf1..a6a5bba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.31", + "version": "0.0.32", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 77689323d42a9390736fc46dfb9346045e4cfc53 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Thu, 18 Jan 2018 14:22:36 +0000 Subject: [PATCH 127/196] 0.0.33 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4c03eca..ddbc7ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.32", + "version": "0.0.33", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a6a5bba..fbdc444 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.32", + "version": "0.0.33", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 935bbe968a44d6e1fc2061af395d1188888f4b5b Mon Sep 17 00:00:00 2001 From: borzav Date: Fri, 19 Jan 2018 11:45:27 +0100 Subject: [PATCH 128/196] CHANGE: default aws region to us-east-1 --- src/classes/api/aws/dynamoTable.ts | 2 +- src/classes/api/aws/s3Storage.ts | 2 +- src/classes/api/aws/sns.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/classes/api/aws/dynamoTable.ts b/src/classes/api/aws/dynamoTable.ts index fc5ba41..e012bac 100644 --- a/src/classes/api/aws/dynamoTable.ts +++ b/src/classes/api/aws/dynamoTable.ts @@ -17,7 +17,7 @@ export class DocumentClientApi extends Api { let awsConfig: any = {} if (process.env.FUNCTIONAL_ENVIRONMENT === 'local') { awsConfig.apiVersion = '2012-08-10' - awsConfig.region = process.env.AWS_REGION || 'eu-central-1' + awsConfig.region = process.env.AWS_REGION || 'us-east-1' awsConfig.endpoint = 'DYNAMODB_LOCAL_ENDPOINT' in process.env ? process.env.DYNAMODB_LOCAL_ENDPOINT : 'http://localhost:8000' console.log('Local DynamoDB configuration') diff --git a/src/classes/api/aws/s3Storage.ts b/src/classes/api/aws/s3Storage.ts index 385c920..0d9a508 100644 --- a/src/classes/api/aws/s3Storage.ts +++ b/src/classes/api/aws/s3Storage.ts @@ -17,7 +17,7 @@ export class S3Api extends Api { let awsConfig: any = {} if (process.env.FUNCTIONAL_ENVIRONMENT === 'local') { awsConfig.apiVersion = '2006-03-01' - awsConfig.region = process.env.AWS_REGION || 'eu-central-1' + awsConfig.region = process.env.AWS_REGION || 'us-east-1' awsConfig.endpoint = 'S3_LOCAL_ENDPOINT' in process.env ? process.env.S3_LOCAL_ENDPOINT : 'http://localhost:4572' console.log('Local S3 configuration') diff --git a/src/classes/api/aws/sns.ts b/src/classes/api/aws/sns.ts index ab0efd3..9e1236e 100644 --- a/src/classes/api/aws/sns.ts +++ b/src/classes/api/aws/sns.ts @@ -16,7 +16,7 @@ export class SNSApi extends Api { let awsConfig: any = {} if (process.env.FUNCTIONAL_ENVIRONMENT === 'local') { awsConfig.apiVersion = '2010-03-31' - awsConfig.region = process.env.AWS_REGION || 'eu-central-1' + awsConfig.region = process.env.AWS_REGION || 'us-east-1' awsConfig.endpoint = 'SNS_LOCAL_ENDPOINT' in process.env ? process.env.SNS_LOCAL_ENDPOINT : 'http://localhost:4100' console.log('Local SNS configuration') From 9cde6fbf568c3cb4b478c65795cddce1a7b4ef57 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Fri, 19 Jan 2018 10:48:50 +0000 Subject: [PATCH 129/196] 0.0.34 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index ddbc7ab..87e4c54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.33", + "version": "0.0.34", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index fbdc444..1fc6aea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.33", + "version": "0.0.34", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From f4ee2cf90c5d4c0de129f3481971844da94b81f6 Mon Sep 17 00:00:00 2001 From: borzav Date: Mon, 22 Jan 2018 16:31:46 +0100 Subject: [PATCH 130/196] FIX: Service discovery --- src/cli/context/steppes/serviceDiscovery.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/context/steppes/serviceDiscovery.ts b/src/cli/context/steppes/serviceDiscovery.ts index 06755e1..569ef1a 100644 --- a/src/cli/context/steppes/serviceDiscovery.ts +++ b/src/cli/context/steppes/serviceDiscovery.ts @@ -8,7 +8,7 @@ import { join, basename, extname } from 'path' export class ServiceDiscoveryStep extends ExecuteStep { public async method(context) { - context.publishedFunctions = context.publishedFunctions || [] + context.publishedFunctions = [] for (let file of context.files) { this.collectFromFile(file, context) From 173895046a92e756f614a2ec1a013e47bea3b57f Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Mon, 22 Jan 2018 15:34:36 +0000 Subject: [PATCH 131/196] 0.0.35 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 87e4c54..85eaae5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.34", + "version": "0.0.35", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1fc6aea..3b1f817 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.34", + "version": "0.0.35", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 2e0e36648c52312aefddf5574b41f6c0f3d69b20 Mon Sep 17 00:00:00 2001 From: borzav Date: Tue, 23 Jan 2018 17:27:20 +0100 Subject: [PATCH 132/196] ADD: listObjectsV2, getSignedUrl to S3 wrapper class --- src/classes/api/aws/s3Storage.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/classes/api/aws/s3Storage.ts b/src/classes/api/aws/s3Storage.ts index 0d9a508..9d53392 100644 --- a/src/classes/api/aws/s3Storage.ts +++ b/src/classes/api/aws/s3Storage.ts @@ -83,6 +83,30 @@ export class S3Storage extends Api { }) } + public async listObjectsV2(params: Partial) { + return new Promise((resolve, reject) => { + this._s3Client.listObjectsV2(this.setDefaultValues(params, 'listObjectsV2'), (err, result) => { + if (err) reject(err) + else resolve(result) + }) + }) + } + + public async getSignedUrl(operation: string, params: any) { + return new Promise((resolve, reject) => { + if (typeof params.Key === 'string') { + params = { + ...params, + Key: /^\//.test(params.Key) ? params.Key.substring(1, params.Key.length) : params.Key + } + } + this._s3Client.getSignedUrl(operation, this.setDefaultValues(params, 'getSignedUrl'), (err, result) => { + if (err) reject(err) + else resolve(result) + }) + }) + } + public upload( params: Partial, options?: S3.ManagedUpload.ManagedUploadOptions, From 687980f60521017a9d8f3272072810d60eb3b122 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Tue, 23 Jan 2018 16:30:50 +0000 Subject: [PATCH 133/196] 0.0.36 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 85eaae5..caecc7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.35", + "version": "0.0.36", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 3b1f817..1f0a8e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.35", + "version": "0.0.36", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From b2b7c483d2890a0816f0724c56c65f0afc616aa9 Mon Sep 17 00:00:00 2001 From: borzav Date: Wed, 24 Jan 2018 17:01:48 +0100 Subject: [PATCH 134/196] ADD: dynamoDB role for indices and s3 roles --- .../cloudFormation/context/resources.ts | 19 ++++++++++++++----- .../cloudFormation/context/s3Storage.ts | 15 ++++++++++++--- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 015879d..3fad002 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -227,11 +227,20 @@ export const dynamoPolicy = async (context) => { "dynamodb:UpdateItem", "dynamodb:DeleteItem" ], - "Resource": usedTableConfigs.map(t => { - return { - "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/" + t.AWSTableName - } - }) + "Resource": [ + ...usedTableConfigs.map(t => { + return { + "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/" + t.AWSTableName + } + }), + ...usedTableConfigs + .filter(t => t.nativeConfig && (t.nativeConfig.GlobalSecondaryIndexes || t.nativeConfig.LocalSecondaryIndexes)) + .map(t => { + return { + "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/" + t.AWSTableName + "/*" + } + }) + ] }] } } diff --git a/src/cli/providers/cloudFormation/context/s3Storage.ts b/src/cli/providers/cloudFormation/context/s3Storage.ts index 24a9a06..d5bf7b8 100644 --- a/src/cli/providers/cloudFormation/context/s3Storage.ts +++ b/src/cli/providers/cloudFormation/context/s3Storage.ts @@ -130,7 +130,15 @@ export const s3StoragePolicy = async (context) => { "s3:PutObjectAcl", "s3:PutObjectTagging", "s3:PutObjectVersionAcl", - "s3:PutObjectVersionTagging" + "s3:PutObjectVersionTagging", + "s3:DeleteObject" + ], + "Resource": [] + }, + { + "Effect": "Allow", + "Action": [ + "s3:ListBucket" ], "Resource": [] }] @@ -143,6 +151,7 @@ export const s3StoragePolicy = async (context) => { const s3Arn = `arn:aws:s3:::${s3Config.AWSBucketName}/*` if (!policy.PolicyDocument.Statement[0].Resource.includes(s3Arn)) { policy.PolicyDocument.Statement[0].Resource.push(s3Arn) + policy.PolicyDocument.Statement[1].Resource.push(`arn:aws:s3:::${s3Config.AWSBucketName}`) } } @@ -170,7 +179,7 @@ export const s3StorageSubscriptions = async (context) => { export const s3BucketSubscription = async (context) => { const { serviceDefinition, serviceConfig, s3Config, s3BucketDefinition } = context - + if (s3Config.exists) return await setStackParameter({ @@ -198,7 +207,7 @@ export const s3BucketSubscription = async (context) => { export const s3Permissions = (context) => { const { serviceDefinition, serviceConfig, s3Config, s3BucketDefinition } = context - + if (s3Config.exists) return const properties = { From b775122d5f71f8951fb56f1702f2bfc97d567c2c Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Wed, 24 Jan 2018 16:04:36 +0000 Subject: [PATCH 135/196] 0.0.37 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index caecc7d..bc5cb2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.36", + "version": "0.0.37", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1f0a8e7..82674fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.36", + "version": "0.0.37", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 926e08aa16e53d07ab4bf02d8868c0f3d37e0144 Mon Sep 17 00:00:00 2001 From: borzav Date: Thu, 25 Jan 2018 12:27:31 +0100 Subject: [PATCH 136/196] fix: request decorator body value in aws --- src/providers/aws/index.ts | 7 ++- test/invoker.tests.ts | 126 ++++++++++++++++++++++++++++++++----- 2 files changed, 116 insertions(+), 17 deletions(-) diff --git a/src/providers/aws/index.ts b/src/providers/aws/index.ts index 5f4968e..166b977 100644 --- a/src/providers/aws/index.ts +++ b/src/providers/aws/index.ts @@ -84,10 +84,15 @@ AWSProvider.addParameterDecoratorImplementation("param", async (parameter, conte }) AWSProvider.addParameterDecoratorImplementation("request", async (parameter, context, provider) => { if (context.eventSourceHandler.constructor.name === 'ApiGateway') { + let body = context.event.event.body + try { + body = JSON.parse(body) + } catch (e) { } + return { url: parse(context.event.event.path), method: context.event.event.httpMethod, - body: context.event.event.body, + body, query: context.event.event.queryStringParameters, params: context.event.event.pathParameters, headers: context.event.event.headers diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index b88f527..4fc9ee4 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -183,7 +183,7 @@ describe('invoker', () => { expect(counter).to.equal(5) }) - + describe("injection modes", () => { it("multiple inject transient api with transient service", async () => { let counter = 0 @@ -1155,7 +1155,7 @@ describe('invoker', () => { }) describe("eventSources", () => { - + it("lambda param error", (done) => { let counter = 0 @@ -1203,7 +1203,7 @@ describe('invoker', () => { done(e) }) }) - + it("api gateway error", async () => { let counter = 0 @@ -1256,11 +1256,11 @@ describe('invoker', () => { counter++ expect(counter).to.equal(2) }) - + expect(counter).to.equal(2) expect(r.statusCode).to.equal(200, r.body) }) - + it("api gateway string body param", async () => { let counter = 0 @@ -1285,7 +1285,7 @@ describe('invoker', () => { counter++ expect(counter).to.equal(2) }) - + expect(counter).to.equal(2) expect(r.statusCode).to.equal(200, r.body) }) @@ -1314,7 +1314,7 @@ describe('invoker', () => { counter++ expect(counter).to.equal(2) }) - + expect(counter).to.equal(2) expect(r.statusCode).to.equal(200, r.body) }) @@ -1343,7 +1343,7 @@ describe('invoker', () => { counter++ expect(counter).to.equal(2) }) - + expect(counter).to.equal(2) expect(r.statusCode).to.equal(200, r.body) }) @@ -1372,7 +1372,7 @@ describe('invoker', () => { counter++ expect(counter).to.equal(2) }) - + expect(counter).to.equal(2) expect(r.statusCode).to.equal(200, r.body) }) @@ -1417,7 +1417,7 @@ describe('invoker', () => { counter++ expect(counter).to.equal(2) }) - + expect(counter).to.equal(2) expect(r.statusCode).to.equal(200, r.body) }) @@ -1462,7 +1462,7 @@ describe('invoker', () => { counter++ expect(counter).to.equal(2) }) - + expect(counter).to.equal(2) expect(r.statusCode).to.equal(200, r.body) }) @@ -1486,7 +1486,7 @@ describe('invoker', () => { counter++ expect(counter).to.equal(2) }) - + expect(counter).to.equal(2) expect(r.statusCode).to.equal(200, r.body) expect(r).to.deep.equal({ @@ -1518,7 +1518,7 @@ describe('invoker', () => { counter++ expect(counter).to.equal(2) }) - + expect(counter).to.equal(2) expect(r.statusCode).to.equal(200, r.body) expect(r).to.deep.equal({ @@ -1549,7 +1549,7 @@ describe('invoker', () => { counter++ expect(counter).to.equal(2) }) - + expect(counter).to.equal(2) expect(r.statusCode).to.equal(500, r.body) expect(r).to.deep.equal({ @@ -1561,7 +1561,7 @@ describe('invoker', () => { it("api gateway return value advanced - throw error", async () => { let counter = 0 - class MyError extends Error{ + class MyError extends Error { constructor(public name, ...params) { super(...params); } @@ -1583,7 +1583,7 @@ describe('invoker', () => { counter++ expect(counter).to.equal(2) }) - + expect(counter).to.equal(2) expect(r.statusCode).to.equal(500, r.body) expect(r).to.deep.equal({ @@ -1915,6 +1915,100 @@ describe('invoker', () => { invoker(awsEvent, awsContext, cb) }) + it("request param - string body", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + @injectable() + class MockInjectable extends Resource { } + + const awsEvent = { + path: '/a/b', + httpMethod: 'GET', + requestContext: { apiId: 'apiId' }, + body: '{"p1":"body"}', + queryStringParameters: { + p2: 'queryStringParameters' + }, + pathParameters: { + p3: 'pathParameters' + }, + headers: { + p4: 'headers' + }, + anyprop: {} + } + const awsContext = {} + const cb = (e) => { + expect(counter).to.equal(1) + done(e) + } + + class MockService extends FunctionalService { + handle( @param p1, @request r) { + counter++ + expect(r).to.have.property('url').that.deep.equal(parse(awsEvent.path)) + expect(r).to.have.property('method', awsEvent.httpMethod) + expect(r).to.have.property('body').that.deep.equal({ "p1": "body" }) + expect(r).to.have.property('query').that.deep.equal(awsEvent.queryStringParameters) + expect(r).to.have.property('params').that.deep.equal(awsEvent.pathParameters) + expect(r).to.have.property('headers').that.deep.equal(awsEvent.headers) + expect(r).to.not.have.property('anyprop') + } + } + + const invoker = MockService.createInvoker() + invoker(awsEvent, awsContext, cb) + }) + + it("request param - string body not json", (done) => { + let counter = 0 + + process.env.FUNCTIONAL_ENVIRONMENT = 'aws' + + @injectable() + class MockInjectable extends Resource { } + + const awsEvent = { + path: '/a/b', + httpMethod: 'GET', + requestContext: { apiId: 'apiId' }, + body: 'request body', + queryStringParameters: { + p2: 'queryStringParameters' + }, + pathParameters: { + p3: 'pathParameters' + }, + headers: { + p4: 'headers' + }, + anyprop: {} + } + const awsContext = {} + const cb = (e) => { + expect(counter).to.equal(1) + done(e) + } + + class MockService extends FunctionalService { + handle( @param p1, @request r) { + counter++ + expect(r).to.have.property('url').that.deep.equal(parse(awsEvent.path)) + expect(r).to.have.property('method', awsEvent.httpMethod) + expect(r).to.have.property('body').that.equal(awsEvent.body) + expect(r).to.have.property('query').that.deep.equal(awsEvent.queryStringParameters) + expect(r).to.have.property('params').that.deep.equal(awsEvent.pathParameters) + expect(r).to.have.property('headers').that.deep.equal(awsEvent.headers) + expect(r).to.not.have.property('anyprop') + } + } + + const invoker = MockService.createInvoker() + invoker(awsEvent, awsContext, cb) + }) + it("generic result format", async () => { let counter = 0 From 6e91a9e462700f55dca8fdf5e6c119eb5cad25ac Mon Sep 17 00:00:00 2001 From: borzav Date: Thu, 25 Jan 2018 12:32:15 +0100 Subject: [PATCH 137/196] ADD: s3 wrapper deleteObject --- src/classes/api/aws/s3Storage.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/classes/api/aws/s3Storage.ts b/src/classes/api/aws/s3Storage.ts index 9d53392..3fab6be 100644 --- a/src/classes/api/aws/s3Storage.ts +++ b/src/classes/api/aws/s3Storage.ts @@ -83,6 +83,21 @@ export class S3Storage extends Api { }) } + public async deleteObject(params: Partial) { + return new Promise((resolve, reject) => { + if (typeof params.Key === 'string') { + params = { + ...params, + Key: /^\//.test(params.Key) ? params.Key.substring(1, params.Key.length) : params.Key + } + } + this._s3Client.deleteObject(this.setDefaultValues(params, 'deleteObject'), (err, result) => { + if (err) reject(err) + else resolve(result) + }) + }) + } + public async listObjectsV2(params: Partial) { return new Promise((resolve, reject) => { this._s3Client.listObjectsV2(this.setDefaultValues(params, 'listObjectsV2'), (err, result) => { From 77aede340e77eb6a973b7035254d27293083c4d9 Mon Sep 17 00:00:00 2001 From: borzav Date: Thu, 25 Jan 2018 12:39:08 +0100 Subject: [PATCH 138/196] CHANGE: AWS service classes init in local --- src/classes/api/aws/dynamoTable.ts | 4 +++- src/classes/api/aws/s3Storage.ts | 4 +++- src/classes/api/aws/sns.ts | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/classes/api/aws/dynamoTable.ts b/src/classes/api/aws/dynamoTable.ts index e012bac..e3635f1 100644 --- a/src/classes/api/aws/dynamoTable.ts +++ b/src/classes/api/aws/dynamoTable.ts @@ -18,7 +18,9 @@ export class DocumentClientApi extends Api { if (process.env.FUNCTIONAL_ENVIRONMENT === 'local') { awsConfig.apiVersion = '2012-08-10' awsConfig.region = process.env.AWS_REGION || 'us-east-1' - awsConfig.endpoint = 'DYNAMODB_LOCAL_ENDPOINT' in process.env ? process.env.DYNAMODB_LOCAL_ENDPOINT : 'http://localhost:8000' + if (process.env.DYNAMODB_LOCAL !== 'false') { + awsConfig.endpoint = 'DYNAMODB_LOCAL_ENDPOINT' in process.env ? process.env.DYNAMODB_LOCAL_ENDPOINT : 'http://localhost:8000' + } console.log('Local DynamoDB configuration') console.log(JSON.stringify({ diff --git a/src/classes/api/aws/s3Storage.ts b/src/classes/api/aws/s3Storage.ts index 3fab6be..0369266 100644 --- a/src/classes/api/aws/s3Storage.ts +++ b/src/classes/api/aws/s3Storage.ts @@ -18,7 +18,9 @@ export class S3Api extends Api { if (process.env.FUNCTIONAL_ENVIRONMENT === 'local') { awsConfig.apiVersion = '2006-03-01' awsConfig.region = process.env.AWS_REGION || 'us-east-1' - awsConfig.endpoint = 'S3_LOCAL_ENDPOINT' in process.env ? process.env.S3_LOCAL_ENDPOINT : 'http://localhost:4572' + if (process.env.S3_LOCAL !== 'false') { + awsConfig.endpoint = 'S3_LOCAL_ENDPOINT' in process.env ? process.env.S3_LOCAL_ENDPOINT : 'http://localhost:4572' + } console.log('Local S3 configuration') console.log(JSON.stringify({ diff --git a/src/classes/api/aws/sns.ts b/src/classes/api/aws/sns.ts index 9e1236e..c909ace 100644 --- a/src/classes/api/aws/sns.ts +++ b/src/classes/api/aws/sns.ts @@ -17,7 +17,9 @@ export class SNSApi extends Api { if (process.env.FUNCTIONAL_ENVIRONMENT === 'local') { awsConfig.apiVersion = '2010-03-31' awsConfig.region = process.env.AWS_REGION || 'us-east-1' - awsConfig.endpoint = 'SNS_LOCAL_ENDPOINT' in process.env ? process.env.SNS_LOCAL_ENDPOINT : 'http://localhost:4100' + if (process.env.SNS_LOCAL !== 'false') { + awsConfig.endpoint = 'SNS_LOCAL_ENDPOINT' in process.env ? process.env.SNS_LOCAL_ENDPOINT : 'http://localhost:4100' + } console.log('Local SNS configuration') console.log(JSON.stringify({ From bc1861c52786e8882e94f63370e7e59a75e398ef Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Thu, 25 Jan 2018 11:42:13 +0000 Subject: [PATCH 139/196] 0.0.38 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index bc5cb2a..4fe6d58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.37", + "version": "0.0.38", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 82674fa..43816f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.37", + "version": "0.0.38", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 08ccfe28ac6b160433dc2ab79da4cec856db23d1 Mon Sep 17 00:00:00 2001 From: borzav Date: Thu, 25 Jan 2018 17:35:31 +0100 Subject: [PATCH 140/196] ADD: more cors config --- src/annotations/classes/aws/apiGateway.ts | 4 +-- src/annotations/classes/azure/httpTrigger.ts | 4 +-- src/annotations/classes/rest.ts | 11 +++++-- .../cloudFormation/context/apiGateway.ts | 33 ++++++++++++++----- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/annotations/classes/aws/apiGateway.ts b/src/annotations/classes/aws/apiGateway.ts index 2a425f4..43686d3 100644 --- a/src/annotations/classes/aws/apiGateway.ts +++ b/src/annotations/classes/aws/apiGateway.ts @@ -1,6 +1,6 @@ import { CLASS_APIGATEWAYKEY } from '../../constants' import { getMetadata, defineMetadata } from '../../metadata' -import { rest } from '../rest' +import { rest, CorsConfig } from '../rest' export const defaultEndpoint = { method: 'get', @@ -9,7 +9,7 @@ export const defaultEndpoint = { } export const apiGateway = (endpoint: { - path: string, method?: string, cors?: boolean, corsConfig?: { headers: string[] }, + path: string, method?: string, cors?: boolean, corsConfig?: CorsConfig, authorization?: 'AWS_IAM' | 'NONE' | 'CUSTOM' | 'COGNITO_USER_POOLS' }) => { return (target: Function) => { diff --git a/src/annotations/classes/azure/httpTrigger.ts b/src/annotations/classes/azure/httpTrigger.ts index f7ceb8f..90d49dd 100644 --- a/src/annotations/classes/azure/httpTrigger.ts +++ b/src/annotations/classes/azure/httpTrigger.ts @@ -1,6 +1,6 @@ import { CLASS_HTTPTRIGGER } from '../../constants' import { getMetadata, defineMetadata } from '../../metadata' -import { rest } from '../rest' +import { rest, CorsConfig } from '../rest' export const defaultEndpoint = { methods: ['get'], @@ -12,7 +12,7 @@ export const httpTrigger = (endpoint: { route: string, methods?: string[], cors?: boolean, - corsConfig?: { headers: string[] }, + corsConfig?: CorsConfig, authLevel?: 'anonymous' | 'function' | 'admin' }) => { return (target: Function) => { diff --git a/src/annotations/classes/rest.ts b/src/annotations/classes/rest.ts index ab9fea1..47793f7 100644 --- a/src/annotations/classes/rest.ts +++ b/src/annotations/classes/rest.ts @@ -1,6 +1,13 @@ import { expandableDecorator } from './expandableDecorator' -export const rest = expandableDecorator<{ path: string, methods?: string[], cors?: boolean, corsConfig?: { headers: string[] }, anonymous?: boolean }>({ +export interface CorsConfig { + headers?: string[], + methods?: string[], + origin?: string, + credentials?: boolean +} + +export const rest = expandableDecorator<{ path: string, methods?: string[], cors?: boolean, corsConfig?: CorsConfig, anonymous?: boolean }>({ name: 'rest', defaultValues: { methods: ['get'], @@ -11,7 +18,7 @@ export const rest = expandableDecorator<{ path: string, methods?: string[], cors export interface IHttpMethod { (path: string): Function - (config: { path: string, cors?: boolean, corsConfig?: { headers: string[] }, anonymous?: boolean }): Function + (config: { path: string, cors?: boolean, corsConfig?: CorsConfig, anonymous?: boolean }): Function } export const resolveParam = (p: any, defaults) => { diff --git a/src/cli/providers/cloudFormation/context/apiGateway.ts b/src/cli/providers/cloudFormation/context/apiGateway.ts index 12d51ae..31ae439 100644 --- a/src/cli/providers/cloudFormation/context/apiGateway.ts +++ b/src/cli/providers/cloudFormation/context/apiGateway.ts @@ -64,9 +64,9 @@ export const gatewayResources = ExecuteStep.register('ApiGateway-Resources', asy }) } - for (const [endpointResourceName, { serviceDefinition, methods, headers }] of endpointsCors) { + for (const [endpointResourceName, { serviceDefinition, methods, headers, credentials, origin }] of endpointsCors) { await executor({ - context: { ...context, endpointResourceName, serviceDefinition, methods, headers }, + context: { ...context, endpointResourceName, serviceDefinition, methods, headers, credentials, origin }, name: `ApiGateway-Method-Options-${endpointResourceName}`, method: setOptionsMethodResource }) @@ -127,13 +127,28 @@ export const apiGatewayMethod = async (context) => { if (cors) { let value = { serviceDefinition, - methods: ['OPTIONS'], - headers: ['Content-Type', 'Authorization', 'X-Requested-With', ...((corsConfig || {}).headers || [])] + methods: new Set(['OPTIONS']), + headers: ['Content-Type', 'Authorization', 'X-Requested-With', ...((corsConfig || {}).headers || [])], + origin: (corsConfig || {}).origin || '*', + credentials: (corsConfig || {}).credentials } + + if (typeof value.credentials === 'undefined') value.credentials = true + if (endpointsCors.has(endpoint.endpointResourceName)) { value = endpointsCors.get(endpoint.endpointResourceName) } - value.methods.push(method) + + const corsMethods = ((corsConfig || {}).methods || []) + if (corsMethods.length) { + value.methods = new Set([...value.methods, ...corsMethods]) + } else { + if (method.toLowerCase() === 'any') { + value.methods = new Set([...value.methods, 'DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT']) + } else { + value.methods.add(method) + } + } endpointsCors.set(endpoint.endpointResourceName, value) } @@ -258,7 +273,7 @@ export const gatewayDeployment = ExecuteStep.register('ApiGateway-Deployment', a }) export const setOptionsMethodResource = async (context) => { - const { endpointResourceName, serviceDefinition, methods, headers } = context + const { endpointResourceName, serviceDefinition, methods, headers, origin, credentials } = context const properties = { "AuthorizationType": "NONE", "HttpMethod": "OPTIONS", @@ -284,10 +299,10 @@ export const setOptionsMethodResource = async (context) => { { "StatusCode": "200", "ResponseParameters": { - "method.response.header.Access-Control-Allow-Origin": "'*'", + "method.response.header.Access-Control-Allow-Origin": `'${origin}'`, "method.response.header.Access-Control-Allow-Headers": `'${headers.join(',')}'`, - "method.response.header.Access-Control-Allow-Methods": `'${methods.join(',')}'`, - "method.response.header.Access-Control-Allow-Credentials": "'true'" + "method.response.header.Access-Control-Allow-Methods": `'${[...methods].join(',')}'`, + "method.response.header.Access-Control-Allow-Credentials": `'${credentials}'` }, "ResponseTemplates": { "application/json": "" From bec817f76d93b22955f51dbb486c16d7bc76c83d Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Thu, 25 Jan 2018 16:38:47 +0000 Subject: [PATCH 141/196] 0.0.39 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4fe6d58..0da0c2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.38", + "version": "0.0.39", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 43816f1..d51f3a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.38", + "version": "0.0.39", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From f5bb61b15bd337c9b3b13b3e6db2859de11037bf Mon Sep 17 00:00:00 2001 From: borzav Date: Sat, 27 Jan 2018 15:06:12 +0100 Subject: [PATCH 142/196] update readme --- README.md | 368 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 355 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 7d4d2e9..487ba38 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,377 @@ # functionly -The `functionly library` lets you build `serverless` nodejs applications on an innovative, functional, and fun by abstraction way. +The `functionly library` lets you build `serverless` nodejs applications in an innovative, functional, and fun by abstraction way. Use the JavaScript language and the JSON syntax to describe infrastructure and entities, service dependencies, and to implement service code. Deploy your solution to cloud providers, or run containerized on your onprem servers, or locally during development time using the `functionly CLI`. -### install from npm: +Defining a rest service which listens on `/hello-world`: +```js +import { FunctionalService, rest, description, param } from 'functionly' -#### functionly CLI -- npm install functionly -g +@rest({ path: '/hello-world', anonymous: true }) +@description('hello world service') +export class HelloWorld extends FunctionalService { + async handle(@param name = 'world') { + return `hello ${name}` + } +} -#### functionly library -- npm install functionly +export const helloworld = HelloWorld.createInvoker() +``` +Running on localhost: +```sh +functionly local +``` +Try it on http://localhost:3000/hello-world?name=Joe +--- -## Examples +- [Install from npm](#install-from-npm) +- [Getting started](#getting-started) +- [Examples](#examples) -#### Serverless Hello World +# Install from npm: -#### Dependency injection: Service To Service references +## functionly CLI +```sh +npm install functionly -g +``` -#### Dependency injection: Database reference +## functionly library +```sh +npm install functionly +``` -## examples +# Getting started + +* [Create an empty Functionly project](#create-an-empty-functionly-project) +* [Create a Hello world service](#create-a-hello-world-service) + * [Resolve parameter values](#resolve-parameter-values) +* [Create a Todo application with DynamoDB](#create-a-tododb-application-with-dynamodb) + * [Create a dynamo table](#create-a-dynamo-table) + * [Read todos](#read-todos) + * [Create todo](#create-todo) + * *[Extend with Services](#extend-the-example-with-Services) - optional* +* [Run and Deploy with CLI](#run-and-deploy-with-cli) +* [AWS deployment](#aws-deployment) +* [Examples](#examples) + +## Create an empty Functionly project +It's a simple npm project. +### Dependencies +- functionly +```sh +npm install --save functionly +``` + +### Dev dependencies +Functionly uses webpack with babel for compile the code. +- babel-core +- babel-loader +- babel-plugin-functionly-annotations +- babel-plugin-transform-async-to-generator +- babel-plugin-transform-decorators-legacy +- babel-preset-es2015-node5 +```sh +npm install --save-dev babel-core babel-loader babel-plugin-functionly-annotations babel-plugin-transform-async-to-generator babel-plugin-transform-decorators-legacy babel-preset-es2015-node5 +``` + +### Babel configuration +Default `.babelrc` + +```js +{ + "plugins": [ + "functionly-annotations", + "transform-decorators-legacy", + "transform-async-to-generator" + ], + "presets": [ + "es2015-node5" + ] +} +``` + +### Functionly configuration +Default `functionly.json` +```js +{ + "awsRegion": "us-east-1", + "main": "./src/index.js", + "deployTarget": "aws", + "localPort": 3000, + "stage": "dev", + "watch": true, + "compile": "babel-loader" +} +``` + +## Create a Hello world service +We need to create a `FunctionalService` to implement the business logic of hello world application +```js +import { FunctionalService } from 'functionly' + +export class HelloWorld extends FunctionalService { + async handle() {} +} +``` +Decorate it with the [rest]() decorator. We need a `path` and have to set the `anonymous` property to `true` because we want to call it without authentication. +If we do not set the `methods` property that means it will accept `GET` requests. (default: `methods: ['get']`) +```js +@rest({ path: '/hello-world', anonymous: true }) +``` +Define a [description]() for the `HelloWorld`, which will make it easier to find in the AWS Lambda list. +```js +@description('hello world service') +``` +Now we have to create the business logic. +```js +import { FunctionalService, rest, description } from 'functionly' + +@rest({ path: '/hello-world', anonymous: true }) +@description('hello world service') +export class HelloWorld extends FunctionalService { + async handle() { + return `hello world` + } +} +``` +We are almost done, we just have to export our service from the main file. +```js +export const helloworld = HelloWorld.createInvoker() +``` +### Resolve parameter values +In the `handle` method if you use the `@param` property decorator for a parameter then it resolves the value from a request context. +```js +import { FunctionalService, rest, description, param } from 'functionly' + +@rest({ path: '/hello-world', anonymous: true }) +@description('hello world service') +export class HelloWorld extends FunctionalService { + async handle(@param name = 'world') { + return `hello ${name}` + } +} + +export const helloworld = HelloWorld.createInvoker() +``` + +## Create a TodoDB application with DynamoDB +Define a base class for FunctionalService to set basic Lambda settings in the AWS environment. +```js +import { FunctionalService, aws } from 'functionly' + +@aws({ type: 'nodejs6.10', memorySize: 512, timeout: 3 }) +export class TodoService extends FunctionalService { } +``` + +### Create a dynamo table +We need a DynamoTable, called `TodoTable` because we want to store todo items. +```js +import { DynamoTable, dynamoTable, injectable } from 'functionly' + +@injectable() +@dynamo() +export class TodoTable extends DynamoTable { } +``` + +### Read todos +We need to create a service to read todo items. +```js +export class GetAllTodos extends TodoService { + async handle() {} +} +``` +Decorate it with the [rest]() decorator. We need a `path` and have to set the `cors` and the `anonymous` properties to `true` because we want to call it without authentication and from another domain. +If we do not set the `methods` property that means it will accept `GET` requests. (default: `methods: ['get']`) +```js +@rest({ path: '/getAllTodos', cors: true, anonymous: true }) +``` +Define a [description]() for the `TodoService`, which will make it easier to find in the AWS Lambda list. +```js +@description('get all Todo service') +``` +Now we have to create the business logic. We want to read the todo items, so we need to inject the `TodoTable`. Get the items from it and return from our service. +```js +import { rest, description, inject } from 'functionly' + +@rest({ path: '/getAllTodos', cors: true, anonymous: true }) +@description('get all Todo service') +export class GetAllTodos extends TodoService { + async handle(@inject(TodoTable) db) { + let items = await db.scan() + return { ok: 1, items } + } +} +``` +We are almost done, we just have to export our service from the main file. +```js +export const getAllTodos = GetAllTodos.createInvoker() +``` + +### Create todo +We need a service to create todo items, so let's do this. We will also define a [rest]() endpoint and a [description](). +```js +import { rest, description } from 'functionly' + +@rest({ path: '/createTodo', methods: ['post'], anonymous: true, cors: true }) +@description('create Todo service') +export class CreateTodo extends TodoService { + async handle() {} +} +``` +We need some values to create a new todo item: `name`, `description` and `status`. Expect these with the [param]() decorator, and it will resolve them from the invocation context. +```js +import { rest, description, param } from 'functionly' + +@rest({ path: '/createTodo', methods: ['post'], anonymous: true, cors: true }) +@description('create Todo service') +export class CreateTodo extends TodoService { + async handle(@param name, @param description, @param staus) {} +} +``` +The business logic: save a new todo item. [Inject]() the `TodoTable` and save a new todo item with the `put` function. We need an id for the new todo, in the example, we'll use [shortid](https://www.npmjs.com/package/shortid) to generate them. +```js +import { generate } from 'shortid' +import { rest, description, param } from 'functionly' + +@rest({ path: '/createTodo', methods: ['post'], anonymous: true, cors: true }) +@description('create Todo service') +export class CreateTodo extends TodoService { + async handle(@param name, @param description, @param status, @inject(TodoTable) db) { + let item = { + id: generate(), + name, + description, + status + } + + await db.put({ Item: item }) + + return { ok: 1, item } + } +} + +export const createTodo = CreateTodo.createInvoker() +``` + +## Extend the example with Services +> **Optional** + +Create two services: validate and persist todo items. Then the CreateTodo has only to call these services. + +### Validate todo +It will be an [injectable]() service and expect the three todo values, then implement a validation logic in the service. +```js +import { injectable, param } from 'functionly' + +@injectable() +export class ValidateTodo extends Service { + async handle( @param name, @param description, @param status) { + const isValid = true + return { isValid } + } +} +``` + +### Persist todo +It will be an [injectable]() service and expect the three todo values and [inject]() a `TodoTable` then implement a persist logic in the service. +```js +import { injectable, param, inject } from 'functionly' + +@injectable() +export class PersistTodo extends Service { + async handle( @param name, @param description, @param status, @inject(TodoTable) db) { + let item = { + id: generate(), + name, + description, + status + } + await db.put({ Item: item }) + return item + } +} +``` + +### Changed CreateTodo FunctionalService +[inject]() the two new services(`ValidateTodo`, `PersistTodo`) and change the business logic +```js +import { rest, description, param, inject } from 'functionly' + +@rest({ path: '/createTodo', methods: ['post'], anonymous: true, cors: true }) +@description('create Todo service') +export class CreateTodo extends TodoService { + async handle( + @param name, + @param description, + @param status, + @inject(ValidateTodo) validateTodo, + @inject(PersistTodo) persistTodo + ) { + let validateResult = await validateTodo({ name, description, status }) + if (!validateResult.isValid) { + throw new Error('Todo validation error') + } + let persistTodoResult = await persistTodo({ name, description, status }) + return { ok: 1, persistTodoResult } + } +} +``` + +### The source code of this example is available [here](https://github.com/jaystack/functionly-examples/tree/master/todoDB-es6) + +# Install +```sh +npm install +``` + +# Run and Deploy with CLI +The CLI helps you to deploy and run the application. +1. CLI install +```sh +npm install functionly -g +``` + +## Local deployment +1. Create DynamoDB with Docker +```sh +docker run -d --name dynamodb -p 8000:8000 peopleperhour/dynamodb +``` +2. Deploy will create the tables in DynamoDB +> Note: Create the [functionly.json](https://raw.githubusercontent.com/jaystack/functionly-examples/master/todoDB/functionly.json) in the project for short commands. Also, you don't have to pass all arguments. +```sh +functionly deploy local +``` +## Run in local environment +During development, you can run the application on your local machine. +```sh +functionly local +``` + +## AWS deployment +> Disclaimer: As functionly provisions AWS services, charges may apply to your AWS account. We suggest you to visit https://aws.amazon.com/pricing/services/ to revise the possible AWS costs. + +> [Set up](http://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/setup-credentials.html) AWS Credentials before deployment. + +> Note: Create the [functionly.json](https://raw.githubusercontent.com/jaystack/functionly-examples/master/todoDB/functionly.json) in the project for short commands. Also, you don't have to pass all arguments. As the `deployTarget` is configured as `aws` (the default value configured) then the deploy command will use this as deployment target. + +Functionly will create the package and deploy the application to AWS. The package is a [CloudFormation](https://aws.amazon.com/cloudformation/) template, it contains all the AWS resources so AWS can create or update the application's resources based on the template. +```sh +functionly deploy +``` + +> Congratulations! You have just created and deployed your first `functionly` application! + +# Examples - https://github.com/jaystack/functionly-examples -### Typescript +## Typescript - [todoDB](https://github.com/jaystack/functionly-examples/tree/master/todoDB) - [todoDB-mongo](https://github.com/jaystack/functionly-examples/tree/master/todoDB-mongo) - [todoDBAdvanced](https://github.com/jaystack/functionly-examples/tree/master/todoDBAdvanced) - [eventSource](https://github.com/jaystack/functionly-examples/tree/master/eventSource) -### ES6 +## ES6 - [greeter](https://github.com/jaystack/functionly-examples/tree/master/greeter) - [todoDB-es6](https://github.com/jaystack/functionly-examples/tree/master/todoDB-es6) From f340df4cc3adf50582c994d95f1b6f42a300364e Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Sat, 27 Jan 2018 14:09:15 +0000 Subject: [PATCH 143/196] 0.0.40 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0da0c2e..c820027 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.39", + "version": "0.0.40", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d51f3a2..d0bc1da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.39", + "version": "0.0.40", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 8a8b3e2ebf676e602260c0c0b4c78637f961a489 Mon Sep 17 00:00:00 2001 From: borzav Date: Mon, 29 Jan 2018 10:32:16 +0100 Subject: [PATCH 144/196] change: readme --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 487ba38..a099eef 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ export class HelloWorld extends FunctionalService { async handle() {} } ``` -Decorate it with the [rest]() decorator. We need a `path` and have to set the `anonymous` property to `true` because we want to call it without authentication. +If you want your service to be accessible with a web request over a rest interface then you have to decorate it with the [rest]() decorator. We need a `path` and have to set the `anonymous` property to `true` because we want to call it without authentication. If we do not set the `methods` property that means it will accept `GET` requests. (default: `methods: ['get']`) ```js @rest({ path: '/hello-world', anonymous: true }) @@ -182,7 +182,7 @@ export class GetAllTodos extends TodoService { async handle() {} } ``` -Decorate it with the [rest]() decorator. We need a `path` and have to set the `cors` and the `anonymous` properties to `true` because we want to call it without authentication and from another domain. +If you want your service to be accessible with a web request over a rest interface then you have to decorate it with the [rest]() decorator. We need a `path` and have to set the `cors` and the `anonymous` properties to `true` because we want to call it without authentication and from another domain. If we do not set the `methods` property that means it will accept `GET` requests. (default: `methods: ['get']`) ```js @rest({ path: '/getAllTodos', cors: true, anonymous: true }) @@ -366,12 +366,12 @@ functionly deploy # Examples - https://github.com/jaystack/functionly-examples +## Javascript +- [greeter](https://github.com/jaystack/functionly-examples/tree/master/greeter) +- [todoDB-es6](https://github.com/jaystack/functionly-examples/tree/master/todoDB-es6) + ## Typescript - [todoDB](https://github.com/jaystack/functionly-examples/tree/master/todoDB) - [todoDB-mongo](https://github.com/jaystack/functionly-examples/tree/master/todoDB-mongo) - [todoDBAdvanced](https://github.com/jaystack/functionly-examples/tree/master/todoDBAdvanced) - [eventSource](https://github.com/jaystack/functionly-examples/tree/master/eventSource) - -## ES6 -- [greeter](https://github.com/jaystack/functionly-examples/tree/master/greeter) -- [todoDB-es6](https://github.com/jaystack/functionly-examples/tree/master/todoDB-es6) From f87ecdbb5f15e365ed2f79de66536ea5f478d788 Mon Sep 17 00:00:00 2001 From: borzav Date: Mon, 29 Jan 2018 10:41:53 +0100 Subject: [PATCH 145/196] change: CORS default true --- src/annotations/classes/aws/apiGateway.ts | 2 +- src/annotations/classes/azure/httpTrigger.ts | 2 +- src/annotations/classes/rest.ts | 2 +- test/annotation.tests.ts | 56 ++++++++++---------- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/annotations/classes/aws/apiGateway.ts b/src/annotations/classes/aws/apiGateway.ts index 43686d3..394dae0 100644 --- a/src/annotations/classes/aws/apiGateway.ts +++ b/src/annotations/classes/aws/apiGateway.ts @@ -4,7 +4,7 @@ import { rest, CorsConfig } from '../rest' export const defaultEndpoint = { method: 'get', - cors: false, + cors: true, authorization: 'AWS_IAM' } diff --git a/src/annotations/classes/azure/httpTrigger.ts b/src/annotations/classes/azure/httpTrigger.ts index 90d49dd..7a800dc 100644 --- a/src/annotations/classes/azure/httpTrigger.ts +++ b/src/annotations/classes/azure/httpTrigger.ts @@ -4,7 +4,7 @@ import { rest, CorsConfig } from '../rest' export const defaultEndpoint = { methods: ['get'], - cors: false, + cors: true, authLevel: 'function' } diff --git a/src/annotations/classes/rest.ts b/src/annotations/classes/rest.ts index 47793f7..514fa71 100644 --- a/src/annotations/classes/rest.ts +++ b/src/annotations/classes/rest.ts @@ -11,7 +11,7 @@ export const rest = expandableDecorator<{ path: string, methods?: string[], cors name: 'rest', defaultValues: { methods: ['get'], - cors: false, + cors: true, anonymous: false } }) diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index 2267ce2..0d7694a 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -211,7 +211,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.property('method', 'get') - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('authorization', 'AWS_IAM') }) it("method", () => { @@ -226,11 +226,11 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.property('method', 'post') - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('authorization', 'AWS_IAM') }) it("cors", () => { - @apiGateway({ path: '/v1/test', cors: true }) + @apiGateway({ path: '/v1/test', cors: false }) class ApiGatewayTestClass { } const value = getMetadata(CLASS_APIGATEWAYKEY, ApiGatewayTestClass) @@ -241,11 +241,11 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.property('method', 'get') - expect(metadata).to.have.property('cors', true) + expect(metadata).to.have.property('cors', false) expect(metadata).to.have.property('authorization', 'AWS_IAM') }) it("corsConfig", () => { - @apiGateway({ path: '/v1/test', cors: true, corsConfig: { headers: ['X-test'] } }) + @apiGateway({ path: '/v1/test', corsConfig: { headers: ['X-test'] } }) class ApiGatewayTestClass { } const value = getMetadata(CLASS_APIGATEWAYKEY, ApiGatewayTestClass) @@ -272,7 +272,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.property('method', 'get') - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('authorization', 'NONE') }) }) @@ -289,7 +289,7 @@ describe('annotations', () => { expect(metadata).to.have.property('route', '/v1/test') expect(metadata).to.have.deep.property('methods', ['get']) - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('authLevel', 'function') }) it("method", () => { @@ -304,11 +304,11 @@ describe('annotations', () => { expect(metadata).to.have.property('route', '/v1/test') expect(metadata).to.have.deep.property('methods', ['post']) - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('authLevel', 'function') }) it("cors", () => { - @httpTrigger({ route: '/v1/test', cors: true }) + @httpTrigger({ route: '/v1/test', cors: false }) class HttpTriggerTestClass { } const value = getMetadata(CLASS_HTTPTRIGGER, HttpTriggerTestClass) @@ -319,12 +319,12 @@ describe('annotations', () => { expect(metadata).to.have.property('route', '/v1/test') expect(metadata).to.have.deep.property('methods', ['get']) - expect(metadata).to.have.property('cors', true) + expect(metadata).to.have.property('cors', false) expect(metadata).to.have.property('authLevel', 'function') }) it("corsConfig", () => { - @httpTrigger({ route: '/v1/test', cors: true, corsConfig: { headers: ['X-test'] } }) + @httpTrigger({ route: '/v1/test', corsConfig: { headers: ['X-test'] } }) class HttpTriggerTestClass { } const value = getMetadata(CLASS_HTTPTRIGGER, HttpTriggerTestClass) @@ -352,7 +352,7 @@ describe('annotations', () => { expect(metadata).to.have.property('route', '/v1/test') expect(metadata).to.have.deep.property('methods', ['get']) - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('authLevel', 'anonymous') }) }) @@ -369,7 +369,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['get']) - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('anonymous', false) }) it("method", () => { @@ -384,11 +384,11 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['post']) - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('anonymous', false) }) it("cors", () => { - @rest({ path: '/v1/test', cors: true }) + @rest({ path: '/v1/test', cors: false }) class ApiGatewayTestClass { } const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) @@ -399,12 +399,12 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['get']) - expect(metadata).to.have.property('cors', true) + expect(metadata).to.have.property('cors', false) expect(metadata).to.have.property('anonymous', false) }) it("corsConfig", () => { - @rest({ path: '/v1/test', cors: true, corsConfig: { headers: ['X-test'] } }) + @rest({ path: '/v1/test', corsConfig: { headers: ['X-test'] } }) class ApiGatewayTestClass { } const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) @@ -431,7 +431,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['get']) - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('anonymous', true) }) }) @@ -448,7 +448,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['get']) - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('anonymous', false) }) it("config", () => { @@ -463,7 +463,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['get']) - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('anonymous', true) }) }) @@ -480,7 +480,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['post']) - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('anonymous', false) }) it("config", () => { @@ -495,7 +495,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['post']) - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('anonymous', true) }) }) @@ -512,7 +512,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['put']) - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('anonymous', false) }) it("config", () => { @@ -527,7 +527,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['put']) - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('anonymous', true) }) }) @@ -544,7 +544,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['patch']) - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('anonymous', false) }) it("config", () => { @@ -559,7 +559,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['patch']) - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('anonymous', true) }) }) @@ -576,7 +576,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['delete']) - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('anonymous', false) }) it("config", () => { @@ -591,7 +591,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['delete']) - expect(metadata).to.have.property('cors', false) + expect(metadata).to.have.property('cors', true) expect(metadata).to.have.property('anonymous', true) }) }) From d654d61ac01de61c4ffe8d3a2717371e9b090af6 Mon Sep 17 00:00:00 2001 From: borzav Date: Mon, 29 Jan 2018 17:22:24 +0100 Subject: [PATCH 146/196] change: dev dependencies readme --- README.md | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index a099eef..c322a79 100644 --- a/README.md +++ b/README.md @@ -68,12 +68,9 @@ npm install --save functionly Functionly uses webpack with babel for compile the code. - babel-core - babel-loader -- babel-plugin-functionly-annotations -- babel-plugin-transform-async-to-generator -- babel-plugin-transform-decorators-legacy -- babel-preset-es2015-node5 +- babel-preset-functionly-aws ```sh -npm install --save-dev babel-core babel-loader babel-plugin-functionly-annotations babel-plugin-transform-async-to-generator babel-plugin-transform-decorators-legacy babel-preset-es2015-node5 +npm install --save-dev babel-core babel-loader babel-preset-functionly-aws ``` ### Babel configuration @@ -81,14 +78,7 @@ Default `.babelrc` ```js { - "plugins": [ - "functionly-annotations", - "transform-decorators-legacy", - "transform-async-to-generator" - ], - "presets": [ - "es2015-node5" - ] + "presets": [ "functionly-aws" ] } ``` From 4383f43a698616f3c3adec48630cc8a04cb18f02 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Tue, 30 Jan 2018 10:11:52 +0000 Subject: [PATCH 147/196] 0.0.41 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c820027..4ecc6c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.40", + "version": "0.0.41", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d0bc1da..47792b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.40", + "version": "0.0.41", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 763eb28574c59ccba833b8ca9138e042043bb01b Mon Sep 17 00:00:00 2001 From: borzav Date: Thu, 1 Feb 2018 12:24:47 +0100 Subject: [PATCH 148/196] CHANGE: static handle methods --- src/annotations/metadata.ts | 25 +- src/annotations/parameters/inject.ts | 4 +- src/annotations/parameters/param.ts | 6 +- src/classes/functionalService.ts | 11 +- src/classes/middleware/hook.ts | 4 +- src/classes/middleware/postHook.ts | 4 +- src/classes/service.ts | 10 +- src/providers/aws/eventSources/apiGateway.ts | 6 +- src/providers/aws/index.ts | 12 +- .../azure/eventSources/httpTrigger.ts | 2 +- src/providers/azure/index.ts | 12 +- src/providers/core/eventSource.ts | 2 +- src/providers/core/provider.ts | 20 +- src/providers/inProc.ts | 10 +- src/providers/index.ts | 18 +- src/providers/local.ts | 18 +- test/annotation.tests.ts | 251 +++++++++++++- test/api.tests.ts | 38 +-- test/hook.tests.ts | 248 +++++++------- test/invoke.tests.ts | 106 +++--- test/invoker.tests.ts | 310 ++++++------------ test/ioc.tests.ts | 8 +- test/serviceOnEvent.tests.ts | 56 ++-- 23 files changed, 662 insertions(+), 519 deletions(-) diff --git a/src/annotations/metadata.ts b/src/annotations/metadata.ts index 5fb7406..1dda595 100644 --- a/src/annotations/metadata.ts +++ b/src/annotations/metadata.ts @@ -17,9 +17,28 @@ export const getOwnMetadata = (metadataKey, target, propertyKey?) => { } export const getOverridableMetadata = (metadataKey, target, propertyKey) => { - if (!target.prototype || target.prototype.hasOwnProperty(propertyKey)) { - return getOwnMetadata(metadataKey, target, propertyKey) + if (!propertyKey) { + // parameter decorators in constructors } else { - return getMetadata(metadataKey, target, propertyKey) + // parameter decorators in static methods + if (target.hasOwnProperty(propertyKey)) { + return getOwnMetadata(metadataKey, target, propertyKey) + } } + + return getParentMetadata(metadataKey, target, propertyKey) +} + +export const getParentMetadata = (metadataKey, target, propertyKey) => { + if (target === Function) return + const value = getOwnMetadata(metadataKey, target, propertyKey) + if (typeof value === 'undefined') { + if (target.prototype && target.__proto__) { + return getParentMetadata(metadataKey, target.__proto__, propertyKey) + } + if (!target.prototype && target.constructor) { + return getParentMetadata(metadataKey, target.constructor, propertyKey) + } + } + return value } \ No newline at end of file diff --git a/src/annotations/parameters/inject.ts b/src/annotations/parameters/inject.ts index 3d8894f..5215555 100644 --- a/src/annotations/parameters/inject.ts +++ b/src/annotations/parameters/inject.ts @@ -1,5 +1,5 @@ import { PARAMETER_PARAMKEY, CLASS_ENVIRONMENTKEY, CLASS_INJECTABLEKEY } from '../constants' -import { defineMetadata, getOwnMetadata, getOverridableMetadata } from '../metadata' +import { defineMetadata, getOwnMetadata } from '../metadata' import { getFunctionName } from '../classes/functionName' export const inject = (type: any, ...params): any => { @@ -8,7 +8,7 @@ export const inject = (type: any, ...params): any => { throw new Error(`type '${getFunctionName(type)}' not marked as injectable`) } - const existingParameters: any[] = getOverridableMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; + const existingParameters: any[] = getOwnMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; existingParameters.push({ serviceType: type, diff --git a/src/annotations/parameters/param.ts b/src/annotations/parameters/param.ts index f6eb64d..d16d449 100644 --- a/src/annotations/parameters/param.ts +++ b/src/annotations/parameters/param.ts @@ -1,5 +1,5 @@ import { PARAMETER_PARAMKEY } from '../constants' -import { getOverridableMetadata, defineMetadata, getMetadata } from '../metadata' +import { getOwnMetadata, defineMetadata, getMetadata } from '../metadata' import { getFunctionParameters } from '../utils' export const param = (target: any, targetKey?: string, parameterIndex?: number): any => { @@ -8,7 +8,7 @@ export const param = (target: any, targetKey?: string, parameterIndex?: number): let decorator = function (target, targetKey, parameterIndex: number) { let parameterNames = getFunctionParameters(target, targetKey); - let existingParameters: any[] = getOverridableMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; + let existingParameters: any[] = getOwnMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; let paramName = parameterNames[parameterIndex]; existingParameters.push({ @@ -40,7 +40,7 @@ export const createParameterDecorator = (type: string, defaultConfig?: any) => ( const decorator = function (target, targetKey, parameterIndex: number) { const parameterNames = getFunctionParameters(target, targetKey); - const existingParameters: any[] = getOverridableMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; + const existingParameters: any[] = getOwnMetadata(PARAMETER_PARAMKEY, target, targetKey) || []; const paramName = parameterNames[parameterIndex]; existingParameters.push({ from: paramName, diff --git a/src/classes/functionalService.ts b/src/classes/functionalService.ts index b9d3fce..7ff351e 100644 --- a/src/classes/functionalService.ts +++ b/src/classes/functionalService.ts @@ -2,15 +2,16 @@ import { Resource } from './resource' import { getInvoker, invoke } from '../providers' import { defineMetadata, getMetadata, constants, getFunctionName } from '../annotations' const { CLASS_ENVIRONMENTKEY } = constants +import { container } from '../helpers/ioc' export const FUNCTIONAL_SERVICE_PREFIX = 'FUNCTIONAL_SERVICE_' export class FunctionalService extends Resource { - public handle(...params) { + public static handle(...params) { } - public async invoke(params?, invokeConfig?) { + public static async invoke(params?, invokeConfig?) { return await invoke(this, params, invokeConfig) } @@ -19,6 +20,12 @@ export class FunctionalService extends Resource { return invoker } + public static async onInject({ parameter }): Promise { + const injectableType = container.resolveType(this) + // return (...params) => injectableType.invoke(...params) + return injectableType + } + public static onDefineInjectTo(target) { const metadata = getMetadata(CLASS_ENVIRONMENTKEY, target) || {} const funcName = getFunctionName(this) || 'undefined' diff --git a/src/classes/middleware/hook.ts b/src/classes/middleware/hook.ts index f63f7f6..e4c7b0d 100644 --- a/src/classes/middleware/hook.ts +++ b/src/classes/middleware/hook.ts @@ -3,8 +3,8 @@ import { getMiddlewares } from '../../annotations/classes/use' const { PARAMETER_PARAMKEY, CLASS_ENVIRONMENTKEY } = constants export class Hook { - public handle(...params); - public handle( @result res) { + public static handle(...params); + public static handle( @result res) { return res } diff --git a/src/classes/middleware/postHook.ts b/src/classes/middleware/postHook.ts index 2e2a01e..85c806c 100644 --- a/src/classes/middleware/postHook.ts +++ b/src/classes/middleware/postHook.ts @@ -3,8 +3,8 @@ import { error, getMetadata, constants } from '../../annotations' const { PARAMETER_PARAMKEY } = constants export class PostHook extends Hook { - public catch(...params); - public catch( @error error) { + public static catch(...params); + public static catch( @error error) { throw error } diff --git a/src/classes/service.ts b/src/classes/service.ts index 39fa8f9..d802167 100644 --- a/src/classes/service.ts +++ b/src/classes/service.ts @@ -9,13 +9,13 @@ import { container } from '../helpers/ioc' const provider = container.resolve(InProcProvider) export class Service extends Resource { - public handle(...params) { + public static handle(...params) { } - public async invoke(params?, invokeConfig?) { + public static async invoke(params?, invokeConfig?) { const availableParams = {} - const parameterMapping = (getOverridableMetadata(PARAMETER_PARAMKEY, this.constructor, 'handle') || []) + const parameterMapping = (getOverridableMetadata(PARAMETER_PARAMKEY, this, 'handle') || []) parameterMapping.forEach((target) => { if (params && target && target.type === 'param') { availableParams[target.from] = params[target.from] @@ -35,8 +35,8 @@ export class Service extends Resource { } public static async onInject({ parameter }): Promise { - const service = container.resolve(this) - return (...params) => service.invoke(...params) + const injectableType = container.resolveType(this) + return (...params) => injectableType.invoke(...params) } public static onDefineInjectTo(target, targetKey, parameterIndex: number) { diff --git a/src/providers/aws/eventSources/apiGateway.ts b/src/providers/aws/eventSources/apiGateway.ts index 8e6816a..6c92c57 100644 --- a/src/providers/aws/eventSources/apiGateway.ts +++ b/src/providers/aws/eventSources/apiGateway.ts @@ -45,10 +45,10 @@ export class ApiGateway extends EventSource { } } - public async resultTransform(err, result, event, serviceInstance) { + public async resultTransform(err, result, event, serviceType) { let headers = {} - const metadata = getMetadata(CLASS_APIGATEWAYKEY, serviceInstance) || [] - if (serviceInstance && metadata.find(m => m.cors === true)) { + const metadata = getMetadata(CLASS_APIGATEWAYKEY, serviceType) || [] + if (serviceType && metadata.find(m => m.cors === true)) { headers = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Content-Type,Authorization,X-Requested-With', diff --git a/src/providers/aws/index.ts b/src/providers/aws/index.ts index 166b977..1a97cfe 100644 --- a/src/providers/aws/index.ts +++ b/src/providers/aws/index.ts @@ -28,8 +28,8 @@ const eventSourceHandlers = [ export class AWSProvider extends Provider { - public getInvoker(serviceInstance, params): Function { - const callContext = this.createCallContext(serviceInstance, 'handle') + public getInvoker(serviceType, params): Function { + const callContext = this.createCallContext(serviceType, 'handle') const invoker = async (event, context, cb) => { try { @@ -40,11 +40,11 @@ export class AWSProvider extends Provider { let result let error try { - result = await callContext({ eventSourceHandler, event: eventContext, serviceInstance }) + result = await callContext({ eventSourceHandler, event: eventContext, serviceType }) } catch (err) { error = err } - const response = await eventSourceHandler.resultTransform(error, result, eventContext, serviceInstance) + const response = await eventSourceHandler.resultTransform(error, result, eventContext, serviceType) cb(null, response) return response @@ -55,10 +55,10 @@ export class AWSProvider extends Provider { return invoker } - public async invoke(serviceInstance, params, invokeConfig?) { + public async invoke(serviceType, params, invokeConfig?) { initAWSSDK() - const funcName = getFunctionName(serviceInstance) + const funcName = getFunctionName(serviceType) const resolvedFuncName = process.env[`FUNCTIONAL_SERVICE_${funcName.toUpperCase()}`] || funcName const invokeParams = { diff --git a/src/providers/azure/eventSources/httpTrigger.ts b/src/providers/azure/eventSources/httpTrigger.ts index 87c7582..5a299ac 100644 --- a/src/providers/azure/eventSources/httpTrigger.ts +++ b/src/providers/azure/eventSources/httpTrigger.ts @@ -30,7 +30,7 @@ export class HttpTrigger extends EventSource { } } - public async resultTransform(error, result, eventContext, serviceInstance) { + public async resultTransform(error, result, eventContext, serviceType) { if (error) { return { status: 500, diff --git a/src/providers/azure/index.ts b/src/providers/azure/index.ts index 36f3592..e8795ce 100644 --- a/src/providers/azure/index.ts +++ b/src/providers/azure/index.ts @@ -14,8 +14,8 @@ export const FUNCTIONLY_FUNCTION_KEY = 'FUNCTIONLY_FUNCTION_KEY' export class AzureProvider extends Provider { - public getInvoker(serviceInstance, params): Function { - const callContext = this.createCallContext(serviceInstance, 'handle') + public getInvoker(serviceType, params): Function { + const callContext = this.createCallContext(serviceType, 'handle') const invoker = async (context, req) => { try { @@ -26,11 +26,11 @@ export class AzureProvider extends Provider { let result let error try { - result = await callContext({ eventSourceHandler, event: eventContext, serviceInstance }) + result = await callContext({ eventSourceHandler, event: eventContext, serviceType }) } catch (err) { error = err } - const response = await eventSourceHandler.resultTransform(error, result, eventContext, serviceInstance) + const response = await eventSourceHandler.resultTransform(error, result, eventContext, serviceType) context.res = response return response @@ -44,9 +44,9 @@ export class AzureProvider extends Provider { return invoker } - public async invoke(serviceInstance, params, invokeConfig?) { + public async invoke(serviceType, params, invokeConfig?) { - const httpAttr = (getMetadata(CLASS_HTTPTRIGGER, serviceInstance) || [])[0] + const httpAttr = (getMetadata(CLASS_HTTPTRIGGER, serviceType) || [])[0] if (!httpAttr) { throw new Error('missing http configuration') } diff --git a/src/providers/core/eventSource.ts b/src/providers/core/eventSource.ts index ff3c066..251681f 100644 --- a/src/providers/core/eventSource.ts +++ b/src/providers/core/eventSource.ts @@ -9,7 +9,7 @@ export abstract class EventSource { return undefined } - public async resultTransform(err, result, event: any, serviceInstance) { + public async resultTransform(err, result, event: any, serviceType) { if (err) throw err if (result && typeof result.status === 'number' && result.hasOwnProperty('data')) { diff --git a/src/providers/core/provider.ts b/src/providers/core/provider.ts index ada77ed..37031c7 100644 --- a/src/providers/core/provider.ts +++ b/src/providers/core/provider.ts @@ -7,12 +7,12 @@ import { PostHook } from '../../classes/middleware/postHook' import { container } from '../../helpers/ioc' export abstract class Provider { - public getInvoker(serviceInstance, params): Function { + public getInvoker(serviceType, params): Function { const invoker = () => { } return invoker } - public async invoke(serviceInstance, params, invokeConfig?): Promise { + public async invoke(serviceType, params, invokeConfig?): Promise { } @@ -47,14 +47,14 @@ export abstract class Provider { } protected createCallContext(target, method) { - const hooks = getMiddlewares(target).map(m => container.resolve(m)) - const parameters = this.getParameters(target.constructor, method) + const hooks = getMiddlewares(target) + const parameters = this.getParameters(target, method) - const preHooks = hooks.filter(h => h instanceof PreHook) - .map(h => ({ hookKey: h.constructor.name, hook: this.createCallContext(h, 'handle') })) - const postHookInstances = hooks.filter(h => h instanceof PostHook) - const postHooks = postHookInstances.map(h => ({ hookKey: h.constructor.name, hook: this.createCallContext(h, 'handle') })) - const catchHooks = postHookInstances.map(h => ({ hookKey: h.constructor.name, hook: this.createCallContext(h, 'catch') })) + const preHooks = hooks.filter(h => h.prototype instanceof PreHook) + .map(h => ({ hookKey: h.name, hook: this.createCallContext(h, 'handle') })) + const postHookInstances = hooks.filter(h => h.prototype instanceof PostHook) + const postHooks = postHookInstances.map(h => ({ hookKey: h.name, hook: this.createCallContext(h, 'handle') })) + const catchHooks = postHookInstances.map(h => ({ hookKey: h.name, hook: this.createCallContext(h, 'catch') })) return async (context) => { const preic: any = {} @@ -151,7 +151,7 @@ Provider.addParameterDecoratorImplementation("result", async (parameter, context return context.context && context.context.result }) Provider.addParameterDecoratorImplementation("functionalServiceName", async (parameter, context, provider) => { - return context.serviceInstance && getFunctionName(context.serviceInstance) + return context.serviceType && getFunctionName(context.serviceType) }) Provider.addParameterDecoratorImplementation("provider", async (parameter, context, provider) => { return process.env.FUNCTIONAL_ENVIRONMENT diff --git a/src/providers/inProc.ts b/src/providers/inProc.ts index b515441..e6ad965 100644 --- a/src/providers/inProc.ts +++ b/src/providers/inProc.ts @@ -7,8 +7,8 @@ import { container } from '../helpers/ioc' import { parse } from 'url' export class InProcProvider extends Provider { - public getInvoker(serviceInstance, params) { - const callContext = this.createCallContext(serviceInstance, 'handle') + public getInvoker(serviceType, params) { + const callContext = this.createCallContext(serviceType, 'handle') const invoker = async (invokeParams) => { const eventContext = { params: invokeParams } @@ -16,18 +16,18 @@ export class InProcProvider extends Provider { let result let error try { - result = await callContext({ event: eventContext, serviceInstance }) + result = await callContext({ event: eventContext, serviceType }) } catch (err) { error = err } - const response = await this.resultTransform(error, result, eventContext, serviceInstance) + const response = await this.resultTransform(error, result, eventContext, serviceType) return response } return invoker } - protected resultTransform(error, result, eventContext, serviceInstance) { + protected resultTransform(error, result, eventContext, serviceType) { if (error) throw error if (result && typeof result.status === 'number' && result.hasOwnProperty('data')) { diff --git a/src/providers/index.ts b/src/providers/index.ts index 0261a62..bf01dc2 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -39,11 +39,11 @@ export const getInvoker = (serviceType, params) => { const currentEnvironment = environments[environment] - const serviceInstance = container.resolve(serviceType, ...params) - const invoker = currentEnvironment.getInvoker(serviceInstance, params) + const resolvedServiceType = container.resolveType(serviceType) + const invoker = currentEnvironment.getInvoker(resolvedServiceType, params) const invokeHandler = async (...params) => { - const onHandleResult = await callExtension(serviceInstance, `onHandle_${environment}`, ...params) + const onHandleResult = await callExtension(resolvedServiceType, `onHandle_${environment}`, ...params) if (typeof onHandleResult !== 'undefined') { return onHandleResult } @@ -54,14 +54,14 @@ export const getInvoker = (serviceType, params) => { enumerable: false, configurable: false, writable: false, - value: serviceType, + value: resolvedServiceType, }) return invokeHandler } -export const invoke = async (serviceInstance, params?, invokeConfig?) => { - await callExtension(serviceInstance, 'onInvoke', { +export const invoke = async (serviceType, params?, invokeConfig?) => { + await callExtension(serviceType, 'onInvoke', { params, invokeConfig, }) @@ -75,14 +75,14 @@ export const invoke = async (serviceInstance, params?, invokeConfig?) => { let currentEnvironment = invokeEnvironments[environmentMode] const availableParams = {} - const parameterMapping = (getOverridableMetadata(PARAMETER_PARAMKEY, serviceInstance.constructor, 'handle') || []) + const parameterMapping = (getOverridableMetadata(PARAMETER_PARAMKEY, serviceType, 'handle') || []) parameterMapping.forEach((target) => { if (params && target && target.type === 'param') { availableParams[target.from] = params[target.from] } }) - await callExtension(serviceInstance, `onInvoke_${environmentMode}`, { + await callExtension(serviceType, `onInvoke_${environmentMode}`, { invokeParams: params, params: availableParams, invokeConfig, @@ -91,5 +91,5 @@ export const invoke = async (serviceInstance, params?, invokeConfig?) => { environmentMode }) - return await currentEnvironment.invoke(serviceInstance, availableParams, invokeConfig) + return await currentEnvironment.invoke(serviceType, availableParams, invokeConfig) } diff --git a/src/providers/local.ts b/src/providers/local.ts index 2977741..491998f 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -7,8 +7,8 @@ import { container } from '../helpers/ioc' import { parse } from 'url' export class LocalProvider extends Provider { - public getInvoker(serviceInstance, params) { - const callContext = this.createCallContext(serviceInstance, 'handle') + public getInvoker(serviceType, params) { + const callContext = this.createCallContext(serviceType, 'handle') const invoker = async (req, res, next) => { try { @@ -17,11 +17,11 @@ export class LocalProvider extends Provider { let result let error try { - result = await callContext({ event: eventContext, serviceInstance }) + result = await callContext({ event: eventContext, serviceType }) } catch (err) { error = err } - const response = await this.resultTransform(error, result, eventContext, serviceInstance) + const response = await this.resultTransform(error, result, eventContext, serviceType) res.send(response) return response @@ -32,7 +32,7 @@ export class LocalProvider extends Provider { return invoker } - protected resultTransform(error, result, eventContext, serviceInstance) { + protected resultTransform(error, result, eventContext, serviceType) { if (error) throw error if (result && typeof result.status === 'number' && result.hasOwnProperty('data')) { @@ -50,9 +50,9 @@ export class LocalProvider extends Provider { return result } - public async invoke(serviceInstance, params, invokeConfig?) { + public async invoke(serviceType, params, invokeConfig?) { - const httpAttr = (getMetadata(rest.environmentKey, serviceInstance) || [])[0] + const httpAttr = (getMetadata(rest.environmentKey, serviceType) || [])[0] if (!httpAttr) { throw new Error('missing http configuration') } @@ -72,9 +72,9 @@ export class LocalProvider extends Provider { } - const isLoggingEnabled = getMetadata(CLASS_LOGKEY, serviceInstance) + const isLoggingEnabled = getMetadata(CLASS_LOGKEY, serviceType) if (isLoggingEnabled) { - console.log(`${new Date().toISOString()} request to ${getFunctionName(serviceInstance)}`, JSON.stringify(invokeParams, null, 2)) + console.log(`${new Date().toISOString()} request to ${getFunctionName(serviceType)}`, JSON.stringify(invokeParams, null, 2)) } return await this.invokeExec(invokeParams) diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index 2267ce2..82fb975 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -1467,15 +1467,93 @@ describe('annotations', () => { expect(metadata).to.have.property('type', 'inject') }) - it("overrided class method no inject", () => { + it("overrided class constructor", () => { @injectable() class ATestClass { } class BaseBTestClass { - method( @inject(ATestClass) p1, @inject(ATestClass) p2) { } + constructor( @inject(ATestClass) p1, @inject(ATestClass) p2) { } + } + class BTestClass extends BaseBTestClass { + constructor( @inject(ATestClass) a) { super(a, a) } + } + + const value = getOverridableMetadata(PARAMETER_PARAMKEY, BTestClass, undefined) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('serviceType', ATestClass) + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'inject') + }) + + it("overrided static class method", () => { + @injectable() + class ATestClass { } + + class BaseBTestClass { + static method( @inject(ATestClass) p1, @inject(ATestClass) p2) { } + } + class BTestClass extends BaseBTestClass { + static method( @inject(ATestClass) a) { } + } + + const value = getOverridableMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('serviceType', ATestClass) + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'inject') + }) + + // non static methods are not supported + // it("overrided class method no inject", () => { + // @injectable() + // class ATestClass { } + + // class BaseBTestClass { + // method( @inject(ATestClass) p1, @inject(ATestClass) p2) { } + // } + // class BTestClass extends BaseBTestClass { + // method() { } + // } + + // const value = getOverridableMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') + // expect(value).to.undefined + // }) + + it("overrided class constructor no inject", () => { + @injectable() + class ATestClass { } + + class BaseBTestClass { + constructor( @inject(ATestClass) p1, @inject(ATestClass) p2) { } + } + class BTestClass extends BaseBTestClass { + constructor() { super(null, null) } + } + + const value = getOverridableMetadata(PARAMETER_PARAMKEY, BTestClass, undefined) + // pass the base class params if there is not defined inject in the new ctor + // expect(value).to.undefined + + expect(value).to.have.lengthOf(2); + }) + + it("overrided static class method no inject", () => { + @injectable() + class ATestClass { } + + class BaseBTestClass { + static method( @inject(ATestClass) p1, @inject(ATestClass) p2) { } } class BTestClass extends BaseBTestClass { - method() { } + static method() { } } const value = getOverridableMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') @@ -1504,6 +1582,50 @@ describe('annotations', () => { expect(metadata).to.have.property('type', 'inject') }) + it("not overrided class constructor", () => { + @injectable() + class ATestClass { } + + class BaseBTestClass { + constructor( @inject(ATestClass) a) { } + } + class BTestClass extends BaseBTestClass { + + } + + const value = getOverridableMetadata(PARAMETER_PARAMKEY, BTestClass, undefined) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('serviceType', ATestClass) + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'inject') + }) + + it("not overrided static class method", () => { + @injectable() + class ATestClass { } + + class BaseBTestClass { + static method( @inject(ATestClass) a) { } + } + class BTestClass extends BaseBTestClass { + + } + + const value = getOverridableMetadata(PARAMETER_PARAMKEY, BTestClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('serviceType', ATestClass) + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'inject') + }) + it("service inject", () => { @injectable() @environment('%ClassName%_defined_environment', 'value') @@ -1816,14 +1938,89 @@ describe('annotations', () => { expect(metadata).to.have.property('type', 'param') }) - it("overrided class method no param", () => { + it("overrided class constructor", () => { class BaseParamClass { - method( @param p1, @param p2) { } + constructor( @param p1, @param p2) { } + } + + class ParamClass extends BaseParamClass { + constructor( @param name) { super(name, name) } + } + + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, undefined) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('from', 'name') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'param') + }) + + it("overrided static class method", () => { + + class BaseParamClass { + static method( @param p1, @param p2) { } + } + + class ParamClass extends BaseParamClass { + static method( @param name) { } + } + + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('from', 'name') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'param') + }) + + // non static methods are not supported + // it("overrided class method no param", () => { + + // class BaseParamClass { + // method( @param p1, @param p2) { } + // } + + // class ParamClass extends BaseParamClass { + // method() { } + // } + + // const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + // expect(value).to.undefined + // }) + + + it("overrided class constructor no param", () => { + + class BaseParamClass { + constructor( @param p1, @param p2) { } + } + + class ParamClass extends BaseParamClass { + constructor() { super(null, null) } + } + + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, undefined) + // pass the base class params if there is not defined inject in the new ctor + // expect(value).to.undefined + + expect(value).to.have.lengthOf(2); + }) + + it("overrided static class method no param", () => { + + class BaseParamClass { + static method( @param p1, @param p2) { } } class ParamClass extends BaseParamClass { - method() { } + static method() { } } const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') @@ -1850,6 +2047,48 @@ describe('annotations', () => { expect(metadata).to.have.property('parameterIndex', 0) expect(metadata).to.have.property('type', 'param') }) + + it("not overrided class constructor", () => { + + class BaseParamClass { + constructor( @param name) { } + } + + class ParamClass extends BaseParamClass { + + } + + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, undefined) + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('from', 'name') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'param') + }) + + it("not overrided static class method", () => { + + class BaseParamClass { + static method( @param name) { } + } + + class ParamClass extends BaseParamClass { + + } + + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, 'method') + + expect(value).to.have.lengthOf(1); + + const metadata = value[0] + + expect(metadata).to.have.property('from', 'name') + expect(metadata).to.have.property('parameterIndex', 0) + expect(metadata).to.have.property('type', 'param') + }) }) }) diff --git a/test/api.tests.ts b/test/api.tests.ts index 639835f..ed26d84 100644 --- a/test/api.tests.ts +++ b/test/api.tests.ts @@ -1,7 +1,7 @@ import { expect } from 'chai' import { FunctionalService, DynamoTable, DocumentClientApi, S3Storage, S3Api, SimpleNotificationService, SNSApi } from '../src/classes' import { injectable, dynamoTable, inject, s3Storage, sns } from '../src/annotations' -import { container } from '../src//helpers/ioc' +import { container } from '../src/helpers/ioc' describe('api', () => { describe('DynamoTable', () => { @@ -24,7 +24,7 @@ describe('api', () => { process.env.FUNCTIONAL_STAGE = 'customstage' process.env.DTClass_TABLE_NAME = 'DTClass-table' class Home extends FunctionalService { - public async handle( @inject(DTClass) a: DTClass) { + public static async handle( @inject(DTClass) a: DTClass) { counter++ await a.put({ @@ -79,7 +79,7 @@ describe('api', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' process.env.FUNCTIONAL_STAGE = 'customstage' class Home extends FunctionalService { - public async handle( @inject(DTClass) a: DTClass) { + public static async handle( @inject(DTClass) a: DTClass) { counter++ await a.put({ @@ -135,7 +135,7 @@ describe('api', () => { process.env.FUNCTIONAL_STAGE = 'customstage' process.env.DTClass_TABLE_NAME = 'DTClass-table-other' class Home extends FunctionalService { - public async handle( @inject(DTClass) a: DTClass) { + public static async handle( @inject(DTClass) a: DTClass) { counter++ await a.put({ @@ -191,7 +191,7 @@ describe('api', () => { process.env.FUNCTIONAL_STAGE = 'customstage' process.env.DTClass_TABLE_NAME = 'ATable' class Home extends FunctionalService { - public async handle( @inject(DTClass) a: DTClass) { + public static async handle( @inject(DTClass) a: DTClass) { counter++ await a.put({ @@ -247,7 +247,7 @@ describe('api', () => { process.env.FUNCTIONAL_STAGE = 'customstage' process.env.DTCLASS_ENV_NAME = 'DTClass-table' class Home extends FunctionalService { - public async handle( @inject(DTClass) a: DTClass) { + public static async handle( @inject(DTClass) a: DTClass) { counter++ await a.put({ @@ -303,7 +303,7 @@ describe('api', () => { process.env.FUNCTIONAL_STAGE = 'customstage' process.env.DTClass_TABLE_NAME = 'custom-table-name' class Home extends FunctionalService { - public async handle( @inject(DTClass) a: DTClass) { + public static async handle( @inject(DTClass) a: DTClass) { counter++ await a.put({ @@ -369,7 +369,7 @@ describe('api', () => { process.env.FUNCTIONAL_STAGE = 'customstage' process.env.S3StorageClass_S3_BUCKET = 's3storageclass-bucket' class Home extends FunctionalService { - public async handle( @inject(S3StorageClass) a: S3StorageClass) { + public static async handle( @inject(S3StorageClass) a: S3StorageClass) { counter++ await a.getObject({ @@ -424,7 +424,7 @@ describe('api', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' process.env.FUNCTIONAL_STAGE = 'customstage' class Home extends FunctionalService { - public async handle( @inject(S3StorageClass) a: S3StorageClass) { + public static async handle( @inject(S3StorageClass) a: S3StorageClass) { counter++ await a.getObject({ @@ -480,7 +480,7 @@ describe('api', () => { process.env.FUNCTIONAL_STAGE = 'customstage' process.env.S3StorageClass_S3_BUCKET = 's3storageclass-bucket-custom' class Home extends FunctionalService { - public async handle( @inject(S3StorageClass) a: S3StorageClass) { + public static async handle( @inject(S3StorageClass) a: S3StorageClass) { counter++ await a.getObject({ @@ -536,7 +536,7 @@ describe('api', () => { process.env.FUNCTIONAL_STAGE = 'customstage' process.env.S3StorageClass_S3_BUCKET = 'custom-name' class Home extends FunctionalService { - public async handle( @inject(S3StorageClass) a: S3StorageClass) { + public static async handle( @inject(S3StorageClass) a: S3StorageClass) { counter++ await a.getObject({ @@ -592,7 +592,7 @@ describe('api', () => { process.env.FUNCTIONAL_STAGE = 'customstage' process.env.S3StorageClass_ENV_NAME = 's3storageclass-other' class Home extends FunctionalService { - public async handle( @inject(S3StorageClass) a: S3StorageClass) { + public static async handle( @inject(S3StorageClass) a: S3StorageClass) { counter++ await a.getObject({ @@ -648,7 +648,7 @@ describe('api', () => { process.env.FUNCTIONAL_STAGE = 'customstage' process.env.S3StorageClass_S3_BUCKET = 'custom-bucket-name' class Home extends FunctionalService { - public async handle( @inject(S3StorageClass) a: S3StorageClass) { + public static async handle( @inject(S3StorageClass) a: S3StorageClass) { counter++ await a.getObject({ @@ -717,7 +717,7 @@ describe('api', () => { process.env.SNSTopicClass_SNS_TOPICNAME = 'SNSTopicClass-topic123456789-customstage' process.env.SNSTopicClass_SNS_TOPICNAME_ARN = 'arn:aws:sns:Z:1:SNSTopicClass-topic123456789-customstage' class Home extends FunctionalService { - public async handle( @inject(SNSTopicClass) a: SNSTopicClass) { + public static async handle( @inject(SNSTopicClass) a: SNSTopicClass) { counter++ await a.publish({ @@ -774,7 +774,7 @@ describe('api', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' process.env.FUNCTIONAL_STAGE = 'customstage' class Home extends FunctionalService { - public async handle( @inject(SNSTopicClass) a: SNSTopicClass) { + public static async handle( @inject(SNSTopicClass) a: SNSTopicClass) { counter++ await a.publish({ @@ -833,7 +833,7 @@ describe('api', () => { process.env.SNSTopicClass_SNS_TOPICNAME = 'SNSTopicClass-topic' process.env.SNSTopicClass_SNS_TOPICNAME_ARN = 'arn:aws:sns:Z:1:SNSTopicClass-topic' class Home extends FunctionalService { - public async handle( @inject(SNSTopicClass) a: SNSTopicClass) { + public static async handle( @inject(SNSTopicClass) a: SNSTopicClass) { counter++ await a.publish({ @@ -892,7 +892,7 @@ describe('api', () => { process.env.SNSTopicClass_SNS_TOPICNAME = 'custom-name123456789-customstage' process.env.SNSTopicClass_SNS_TOPICNAME_ARN = 'arn:aws:sns:Z:1:custom-name123456789-customstage' class Home extends FunctionalService { - public async handle( @inject(SNSTopicClass) a: SNSTopicClass) { + public static async handle( @inject(SNSTopicClass) a: SNSTopicClass) { counter++ await a.publish({ @@ -951,7 +951,7 @@ describe('api', () => { process.env.SNSTopicClass_ENV_NAME = 'SNSTopicClass-topic123456789-customstage' process.env.SNSTopicClass_ENV_NAME_ARN = 'arn:aws:sns:Z:1:SNSTopicClass-topic123456789-customstage' class Home extends FunctionalService { - public async handle( @inject(SNSTopicClass) a: SNSTopicClass) { + public static async handle( @inject(SNSTopicClass) a: SNSTopicClass) { counter++ await a.publish({ @@ -1010,7 +1010,7 @@ describe('api', () => { process.env.SNSTopicClass_SNS_TOPICNAME = 'SNSTopicClass-topic' process.env.SNSTopicClass_SNS_TOPICNAME_ARN = 'arn:aws:sns:Z:1:SNSTopicClass-topic' class Home extends FunctionalService { - public async handle( @inject(SNSTopicClass) a: SNSTopicClass) { + public static async handle( @inject(SNSTopicClass) a: SNSTopicClass) { counter++ await a.publish({ diff --git a/test/hook.tests.ts b/test/hook.tests.ts index 18cc599..3f6c177 100644 --- a/test/hook.tests.ts +++ b/test/hook.tests.ts @@ -18,12 +18,12 @@ describe('hooks', () => { describe('general', () => { it("use", () => { class TestHook extends PreHook { - public async handle() { } + public static async handle() { } } @use(TestHook) class BTestClass extends FunctionalService { - public async handle() { } + public static async handle() { } } const value = getMetadata(CLASS_MIDDLEWAREKEY, BTestClass) @@ -36,18 +36,18 @@ describe('hooks', () => { }) it("use multiple param", () => { class TestHook1 extends PreHook { - public async handle() { } + public static async handle() { } } class TestHook2 extends PreHook { - public async handle() { } + public static async handle() { } } class TestHook3 extends PreHook { - public async handle() { } + public static async handle() { } } @use(TestHook1, TestHook2, TestHook3) class BTestClass extends FunctionalService { - public async handle() { } + public static async handle() { } } const value = getMetadata(CLASS_MIDDLEWAREKEY, BTestClass) @@ -60,20 +60,20 @@ describe('hooks', () => { }) it("multiple use", () => { class TestHook1 extends PreHook { - public async handle() { } + public static async handle() { } } class TestHook2 extends PreHook { - public async handle() { } + public static async handle() { } } class TestHook3 extends PreHook { - public async handle() { } + public static async handle() { } } @use(TestHook1) @use(TestHook2) @use(TestHook3) class BTestClass extends FunctionalService { - public async handle() { } + public static async handle() { } } const value = getMetadata(CLASS_MIDDLEWAREKEY, BTestClass) @@ -86,29 +86,29 @@ describe('hooks', () => { }) it("multiple use multiple param", () => { class TestHook11 extends PreHook { - public async handle() { } + public static async handle() { } } class TestHook12 extends PreHook { - public async handle() { } + public static async handle() { } } class TestHook21 extends PreHook { - public async handle() { } + public static async handle() { } } class TestHook22 extends PreHook { - public async handle() { } + public static async handle() { } } class TestHook31 extends PreHook { - public async handle() { } + public static async handle() { } } class TestHook32 extends PreHook { - public async handle() { } + public static async handle() { } } @use(TestHook11, TestHook12) @use(TestHook21, TestHook22) @use(TestHook31, TestHook32) class BTestClass extends FunctionalService { - public async handle() { } + public static async handle() { } } const value = getMetadata(CLASS_MIDDLEWAREKEY, BTestClass) @@ -131,12 +131,12 @@ describe('hooks', () => { class ATestClass extends FunctionalService { } class TestHook extends PreHook { - public async handle( @inject(ATestClass) a) { } + public static async handle( @inject(ATestClass) a) { } } @use(TestHook) class BTestClass extends FunctionalService { - public async handle() { } + public static async handle() { } } const environmentMetadata = getMetadata(CLASS_ENVIRONMENTKEY, BTestClass) @@ -150,12 +150,12 @@ describe('hooks', () => { class ATestClass extends Resource { } class TestHook extends PreHook { - public async handle( @inject(ATestClass) a) { } + public static async handle( @inject(ATestClass) a) { } } @use(TestHook) class BTestClass extends FunctionalService { - public async handle() { } + public static async handle() { } } const environmentMetadata = getMetadata(CLASS_ENVIRONMENTKEY, BTestClass) @@ -169,12 +169,12 @@ describe('hooks', () => { class ATestClass extends DynamoTable { } class TestHook extends PreHook { - public async handle( @inject(ATestClass) a) { } + public static async handle( @inject(ATestClass) a) { } } @use(TestHook) class BTestClass extends FunctionalService { - public async handle() { } + public static async handle() { } } const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, BTestClass) @@ -192,12 +192,12 @@ describe('hooks', () => { class ATestClass extends SimpleNotificationService { } class TestHook extends PreHook { - public async handle( @inject(ATestClass) a) { } + public static async handle( @inject(ATestClass) a) { } } @use(TestHook) class BTestClass extends FunctionalService { - public async handle() { } + public static async handle() { } } const value = getMetadata(CLASS_SNSCONFIGURATIONKEY, BTestClass) @@ -215,12 +215,12 @@ describe('hooks', () => { class ATestClass extends S3Storage { } class TestHook extends PreHook { - public async handle( @inject(ATestClass) a) { } + public static async handle( @inject(ATestClass) a) { } } @use(TestHook) class BTestClass extends FunctionalService { - public async handle() { } + public static async handle() { } } const value = getMetadata(CLASS_S3CONFIGURATIONKEY, BTestClass) @@ -240,17 +240,17 @@ describe('hooks', () => { class ATestClass extends FunctionalService { } class TestHookLevel2 extends PreHook { - public async handle( @inject(ATestClass) a) { } + public static async handle( @inject(ATestClass) a) { } } @use(TestHookLevel2) class TestHook extends PreHook { - public async handle() { } + public static async handle() { } } @use(TestHook) class BTestClass extends FunctionalService { - public async handle() { } + public static async handle() { } } const environmentMetadata = getMetadata(CLASS_ENVIRONMENTKEY, BTestClass) @@ -264,17 +264,17 @@ describe('hooks', () => { class ATestClass extends Resource { } class TestHookLevel2 extends PreHook { - public async handle( @inject(ATestClass) a) { } + public static async handle( @inject(ATestClass) a) { } } @use(TestHookLevel2) class TestHook extends PreHook { - public async handle() { } + public static async handle() { } } @use(TestHook) class BTestClass extends FunctionalService { - public async handle() { } + public static async handle() { } } const environmentMetadata = getMetadata(CLASS_ENVIRONMENTKEY, BTestClass) @@ -288,17 +288,17 @@ describe('hooks', () => { class ATestClass extends DynamoTable { } class TestHookLevel2 extends PreHook { - public async handle( @inject(ATestClass) a) { } + public static async handle( @inject(ATestClass) a) { } } @use(TestHookLevel2) class TestHook extends PreHook { - public async handle() { } + public static async handle() { } } @use(TestHook) class BTestClass extends FunctionalService { - public async handle() { } + public static async handle() { } } const value = getMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, BTestClass) @@ -316,17 +316,17 @@ describe('hooks', () => { class ATestClass extends SimpleNotificationService { } class TestHookLevel2 extends PreHook { - public async handle( @inject(ATestClass) a) { } + public static async handle( @inject(ATestClass) a) { } } @use(TestHookLevel2) class TestHook extends PreHook { - public async handle() { } + public static async handle() { } } @use(TestHook) class BTestClass extends FunctionalService { - public async handle() { } + public static async handle() { } } const value = getMetadata(CLASS_SNSCONFIGURATIONKEY, BTestClass) @@ -344,17 +344,17 @@ describe('hooks', () => { class ATestClass extends S3Storage { } class TestHookLevel2 extends PreHook { - public async handle( @inject(ATestClass) a) { } + public static async handle( @inject(ATestClass) a) { } } @use(TestHookLevel2) class TestHook extends PreHook { - public async handle() { } + public static async handle() { } } @use(TestHook) class BTestClass extends FunctionalService { - public async handle() { } + public static async handle() { } } const value = getMetadata(CLASS_S3CONFIGURATIONKEY, BTestClass) @@ -373,7 +373,7 @@ describe('hooks', () => { it('prehook', async () => { let counter = 0 class TestHook extends PreHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) } @@ -381,7 +381,7 @@ describe('hooks', () => { @use(TestHook) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(2) } @@ -401,7 +401,7 @@ describe('hooks', () => { it('posthook', async () => { let counter = 0 class TestHook extends PostHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(2) } @@ -409,7 +409,7 @@ describe('hooks', () => { @use(TestHook) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) } @@ -429,21 +429,21 @@ describe('hooks', () => { it('posthook result chain', async () => { let counter = 0 class TestPostHook1 extends PostHook { - async handle( @result result) { + public static async handle( @result result) { counter++ return result + 1 } } class TestPostHook2 extends PostHook { - async handle( @result result) { + public static async handle( @result result) { counter++ return result + 1 } } class TestPostHook3 extends PostHook { - async handle( @result result) { + public static async handle( @result result) { counter++ return result + 1 @@ -454,7 +454,7 @@ describe('hooks', () => { @use(TestPostHook2) @use(TestPostHook3) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) @@ -478,7 +478,7 @@ describe('hooks', () => { it('posthook catch error in service', async () => { let counter = 0 class TestHook extends PostHook { - public async catch() { + public static async catch() { counter++ expect(counter).to.equal(2) } @@ -486,7 +486,7 @@ describe('hooks', () => { @use(TestHook) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) throw new Error('error') @@ -507,14 +507,14 @@ describe('hooks', () => { it('posthook catch error in service with prehook', async () => { let counter = 0 class TestPreHook extends PostHook { - public async catch() { + public static async catch() { counter++ expect(counter).to.equal(1) } } class TestCatchHook extends PostHook { - public async catch() { + public static async catch() { counter++ expect(counter).to.equal(3) } @@ -523,7 +523,7 @@ describe('hooks', () => { @use(TestPreHook) @use(TestCatchHook) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(2) throw new Error('error') @@ -544,14 +544,14 @@ describe('hooks', () => { it('posthook catch error in prehook', async () => { let counter = 0 class TestHook extends PreHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) throw new Error('error') } } class TestCatchHook extends PostHook { - public async catch() { + public static async catch() { counter++ expect(counter).to.equal(2) } @@ -560,7 +560,7 @@ describe('hooks', () => { @use(TestHook) @use(TestCatchHook) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { expect(true).to.equal(false, 'have to skip this') } } @@ -579,19 +579,19 @@ describe('hooks', () => { it('posthook catch error in posthook', async () => { let counter = 0 class TestHook extends PreHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) } } class TestCatchHook extends PostHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(3) throw new Error('error') } - public async catch() { + public static async catch() { expect(true).to.equal(false, 'have to skip this') } } @@ -599,7 +599,7 @@ describe('hooks', () => { @use(TestHook) @use(TestCatchHook) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(2) } @@ -622,27 +622,27 @@ describe('hooks', () => { it('posthook catch error in posthook handled', async () => { let counter = 0 class TestHook extends PreHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) } } class TestCatchHook extends PostHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(3) throw new Error('error') } - public async catch() { + public static async catch() { expect(true).to.equal(false, 'have to skip this') } } class TestCatchHook2 extends PostHook { - public async handle() { + public static async handle() { expect(true).to.equal(false, 'have to skip this') } - public async catch() { + public static async catch() { counter++ expect(counter).to.equal(4) @@ -654,7 +654,7 @@ describe('hooks', () => { @use(TestCatchHook) @use(TestCatchHook2) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(2) @@ -679,7 +679,7 @@ describe('hooks', () => { @injectable() class AuthHook extends PreHook { - public async handle( @param authorization, @param Authorization) { + public static async handle( @param authorization, @param Authorization) { counter++ const auth = authorization || Authorization if (!auth) throw new Error('auth') @@ -687,13 +687,13 @@ describe('hooks', () => { } } class PermissionHook extends PreHook { - public async handle( @inject(AuthHook) identity) { + public static async handle( @inject(AuthHook) identity) { counter++ expect(identity).to.equal('me') } } class ResultHook extends PostHook { - public async handle( @result result) { + public static async handle( @result result) { counter++ expect(result).to.deep.equal([{ a: 1 }, { a: 2 }, { a: 3 }]) return result @@ -707,7 +707,7 @@ describe('hooks', () => { @use(PermissionHook) @use(ResultHook) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ return [{ a: 1 }, { a: 2 }, { a: 3 }] @@ -732,7 +732,7 @@ describe('hooks', () => { let counter = 0 class TestHookLevel2 extends PreHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) } @@ -740,7 +740,7 @@ describe('hooks', () => { @use(TestHookLevel2) class TestHook extends PreHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(2) } @@ -748,7 +748,7 @@ describe('hooks', () => { @use(TestHook) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(3) } @@ -769,7 +769,7 @@ describe('hooks', () => { let counter = 0 class TestHookLevel2 extends PostHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(2) } @@ -777,7 +777,7 @@ describe('hooks', () => { @use(TestHookLevel2) class TestHook extends PreHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) } @@ -785,7 +785,7 @@ describe('hooks', () => { @use(TestHook) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(3) } @@ -806,14 +806,14 @@ describe('hooks', () => { let counter = 0 class TestPreHookLevel2 extends PreHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) } } class TestPostHookLevel2 extends PostHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(3) } @@ -822,7 +822,7 @@ describe('hooks', () => { @use(TestPreHookLevel2) @use(TestPostHookLevel2) class TestHook extends PreHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(2) } @@ -830,7 +830,7 @@ describe('hooks', () => { @use(TestHook) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(4) } @@ -851,7 +851,7 @@ describe('hooks', () => { let counter = 0 class TestPreHookLevel2 extends PreHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) @@ -860,7 +860,7 @@ describe('hooks', () => { } class TestPostHookLevel2 extends PostHook { - public async handle() { + public static async handle() { expect(true).to.equal(false, 'have to skip this') } } @@ -868,13 +868,13 @@ describe('hooks', () => { @use(TestPreHookLevel2) @use(TestPostHookLevel2) class TestHook extends PreHook { - public async handle() { + public static async handle() { expect(true).to.equal(false, 'have to skip this') } } class TestCatchHook extends PostHook { - public async catch( @error e) { + public static async catch( @error e) { counter++ expect(counter).to.equal(2) expect(e.message).to.equal('error') @@ -884,7 +884,7 @@ describe('hooks', () => { @use(TestHook) @use(TestCatchHook) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { expect(true).to.equal(false, 'have to skip this') } } @@ -904,14 +904,14 @@ describe('hooks', () => { let counter = 0 class TestPreHookLevel2 extends PreHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) } } class TestPostHookLevel2 extends PostHook { - public async handle() { + public static async handle() { expect(true).to.equal(false, 'have to skip this') } } @@ -919,7 +919,7 @@ describe('hooks', () => { @use(TestPreHookLevel2) @use(TestPostHookLevel2) class TestHook extends PreHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(2) @@ -928,7 +928,7 @@ describe('hooks', () => { } class TestCatchHook extends PostHook { - catch( @error e) { + public static async catch( @error e) { counter++ expect(counter).to.equal(3) expect(e.message).to.equal('error') @@ -938,7 +938,7 @@ describe('hooks', () => { @use(TestHook) @use(TestCatchHook) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { expect(true).to.equal(false, 'have to skip this') } } @@ -958,14 +958,14 @@ describe('hooks', () => { let counter = 0 class TestPreHookLevel2 extends PreHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) } } class TestPostHookLevel2 extends PostHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(3) @@ -976,14 +976,14 @@ describe('hooks', () => { @use(TestPreHookLevel2) @use(TestPostHookLevel2) class TestHook extends PreHook { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(2) } } class TestCatchHook extends PostHook { - catch( @error e) { + public static async catch( @error e) { counter++ expect(counter).to.equal(4) expect(e.message).to.equal('error') @@ -993,7 +993,7 @@ describe('hooks', () => { @use(TestHook) @use(TestCatchHook) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { expect(true).to.equal(false, 'have to skip this') } } @@ -1017,7 +1017,7 @@ describe('hooks', () => { @injectable() class TestHook extends PreHook { - async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) return 'v1' @@ -1026,7 +1026,7 @@ describe('hooks', () => { @use(TestHook) class TestFunctionalService extends FunctionalService { - public async handle( @inject(TestHook) p1) { + public static async handle( @inject(TestHook) p1) { counter++ expect(counter).to.equal(2) @@ -1053,7 +1053,7 @@ describe('hooks', () => { @injectable() class TestHook extends PreHook { - async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) } @@ -1061,7 +1061,7 @@ describe('hooks', () => { @use(TestHook) class TestFunctionalService extends FunctionalService { - public async handle( @inject(TestHook) p1) { + public static async handle( @inject(TestHook) p1) { counter++ expect(counter).to.equal(2) @@ -1087,7 +1087,7 @@ describe('hooks', () => { let counter = 0 class TestHook extends PostHook { - async handle( @result res) { + public static async handle( @result res) { counter++ expect(counter).to.equal(2) return { ...res, p1: 'p1' } @@ -1096,7 +1096,7 @@ describe('hooks', () => { @use(TestHook) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) @@ -1120,7 +1120,7 @@ describe('hooks', () => { let counter = 0 class TestHook extends PostHook { - async handle( @result res) { + public static async handle( @result res) { counter++ expect(counter).to.equal(2) return res @@ -1128,7 +1128,7 @@ describe('hooks', () => { } class TestHook2 extends PostHook { - async catch( @error e) { + public static async catch( @error e) { throw e } } @@ -1136,7 +1136,7 @@ describe('hooks', () => { @use(TestHook) @use(TestHook2) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) @@ -1163,7 +1163,7 @@ describe('hooks', () => { } class TestHook2 extends PostHook { - async catch( @error e) { + public static async catch( @error e) { throw e } } @@ -1171,7 +1171,7 @@ describe('hooks', () => { @use(TestHook) @use(TestHook2) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) @@ -1196,7 +1196,7 @@ describe('hooks', () => { @injectable() class TestPreHook extends PreHook { - async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) return 'p1' @@ -1204,7 +1204,7 @@ describe('hooks', () => { } class TestPostHook extends PostHook { - async handle( @result res, @inject(TestPreHook) p1) { + public static async handle( @result res, @inject(TestPreHook) p1) { counter++ expect(counter).to.equal(3) return { ...res, p1 } @@ -1214,7 +1214,7 @@ describe('hooks', () => { @use(TestPreHook) @use(TestPostHook) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(2) @@ -1239,7 +1239,7 @@ describe('hooks', () => { @injectable() class TestPreHook extends PreHook { - async handle() { + public static async handle() { counter++ expect(counter).to.equal(1) return 'p1' @@ -1247,7 +1247,7 @@ describe('hooks', () => { } class TestPostHook extends PostHook { - async catch( @result res, @inject(TestPreHook) p1) { + public static async catch( @result res, @inject(TestPreHook) p1) { counter++ expect(counter).to.equal(3) return { ok: 1, p1 } @@ -1257,7 +1257,7 @@ describe('hooks', () => { @use(TestPreHook) @use(TestPostHook) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ expect(counter).to.equal(2) @@ -1282,14 +1282,14 @@ describe('hooks', () => { @injectable() class TestHook1 extends PreHook { - async handle() { + public static async handle() { return 'v1' } } @injectable() class TestHook2 extends PreHook { - async handle( @inject(TestHook1) p1) { + public static async handle( @inject(TestHook1) p1) { expect(p1).to.equal('v1') return 'v2' @@ -1298,7 +1298,7 @@ describe('hooks', () => { @injectable() class TestHook3 extends PreHook { - async handle( @inject(TestHook1) p1, @inject(TestHook2) p2) { + public static async handle( @inject(TestHook1) p1, @inject(TestHook2) p2) { expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -1310,7 +1310,7 @@ describe('hooks', () => { @use(TestHook2) @use(TestHook3) class TestFunctionalService extends FunctionalService { - public async handle( @inject(TestHook1) p1, @inject(TestHook2) p2, @inject(TestHook3) p3) { + public static async handle( @inject(TestHook1) p1, @inject(TestHook2) p2, @inject(TestHook3) p3) { counter++ expect(p1).to.equal('v1') @@ -1335,7 +1335,7 @@ describe('hooks', () => { it('@error', async () => { let counter = 0 class TestHook extends PostHook { - catch( @error e) { + public static async catch( @error e) { counter++ expect(e).instanceOf(Error) @@ -1345,7 +1345,7 @@ describe('hooks', () => { @use(TestHook) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ throw new Error('custom error message') } @@ -1365,7 +1365,7 @@ describe('hooks', () => { it("@functionalServiceName", async () => { let counter = 0 class TestHook extends PreHook { - async handle( @functionalServiceName serviceName) { + public static async handle( @functionalServiceName serviceName) { counter++ expect(serviceName).to.equal('TestFunctionalService') } @@ -1373,7 +1373,7 @@ describe('hooks', () => { @use(TestHook) class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ return { ok: 1 } } @@ -1394,7 +1394,7 @@ describe('hooks', () => { it("@functionalServiceName with functionName decorator", async () => { let counter = 0 class TestHook extends PreHook { - async handle( @functionalServiceName serviceName) { + public static async handle( @functionalServiceName serviceName) { counter++ expect(serviceName).to.equal('MyTestFunctionalService') } @@ -1403,7 +1403,7 @@ describe('hooks', () => { @use(TestHook) @functionName('MyTestFunctionalService') class TestFunctionalService extends FunctionalService { - public async handle() { + public static async handle() { counter++ return { ok: 1 } } diff --git a/test/invoke.tests.ts b/test/invoke.tests.ts index a67ebda..87a1212 100644 --- a/test/invoke.tests.ts +++ b/test/invoke.tests.ts @@ -23,10 +23,10 @@ describe('invoke', () => { let counter = 0 class TestProvider extends LocalProvider { - public async invoke(serviceInstance, params, invokeConfig?) { + public async invoke(serviceType, params, invokeConfig?) { counter++ - expect(serviceInstance).to.instanceof(A) + expect(serviceType).to.equal(A) expect(params).to.have.deep.equal({}) expect(invokeConfig).is.undefined } @@ -35,11 +35,11 @@ describe('invoke', () => { @injectable() class A extends FunctionalService { - public async handle() { } + public static async handle() { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({}) } @@ -57,10 +57,10 @@ describe('invoke', () => { let counter = 0 class TestProvider extends LocalProvider { - public async invoke(serviceInstance, params, invokeConfig?) { + public async invoke(serviceType, params, invokeConfig?) { counter++ - expect(serviceInstance).to.instanceof(A) + expect(serviceType).to.equal(A) expect(params).to.have.deep.equal({ p1: 'p1' }) expect(invokeConfig).is.undefined } @@ -69,11 +69,11 @@ describe('invoke', () => { @injectable() class A extends FunctionalService { - public async handle( @param p1) { } + public static async handle( @param p1) { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({ p1: 'p1' }) } @@ -91,10 +91,10 @@ describe('invoke', () => { let counter = 0 class TestProvider extends LocalProvider { - public async invoke(serviceInstance, params, invokeConfig?) { + public async invoke(serviceType, params, invokeConfig?) { counter++ - expect(serviceInstance).to.instanceof(A) + expect(serviceType).to.equal(A) expect(params).to.have.deep.equal({ p1: 'p1' }) expect(invokeConfig).is.undefined } @@ -103,11 +103,11 @@ describe('invoke', () => { @injectable() class A extends FunctionalService { - public async handle( @param p1) { } + public static async handle( @param p1) { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({ p1: 'p1', p2: 'p2' }) } @@ -125,10 +125,10 @@ describe('invoke', () => { let counter = 0 class TestProvider extends LocalProvider { - public async invoke(serviceInstance, params, invokeConfig?) { + public async invoke(serviceType, params, invokeConfig?) { counter++ - expect(serviceInstance).to.instanceof(A) + expect(serviceType).to.equal(A) expect(params).to.deep.equal({}) expect(invokeConfig).to.deep.equal({ a: 1, b: 2, c: 3 }) } @@ -137,11 +137,11 @@ describe('invoke', () => { @injectable() class A extends FunctionalService { - public async handle() { } + public static async handle() { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({}, { a: 1, b: 2, c: 3 }) } @@ -159,7 +159,7 @@ describe('invoke', () => { let counter = 0 class TestProvider extends LocalProvider { - public async invoke(serviceInstance, params, invokeConfig?) { + public async invoke(serviceType, params, invokeConfig?) { counter++ expect(false).to.equal(true, 'unknown provider have to select') @@ -169,11 +169,11 @@ describe('invoke', () => { @injectable() class A extends FunctionalService { - public async handle() { } + public static async handle() { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({}) } @@ -195,7 +195,7 @@ describe('invoke', () => { let counter = 0 class TestProvider extends LocalProvider { - public async invoke(serviceInstance, params, invokeConfig?) { + public async invoke(serviceType, params, invokeConfig?) { counter++ expect(false).to.equal(true, 'unknown provider have to select') @@ -205,11 +205,11 @@ describe('invoke', () => { @injectable() class A extends FunctionalService { - public async handle() { } + public static async handle() { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({}, { mode: 'unknown' }) } @@ -275,7 +275,7 @@ describe('invoke', () => { const myDecorator = createParameterDecorator('myDecorator') class A extends FunctionalService { - public async handle( @myDecorator p1) { + public static async handle( @myDecorator p1) { counter++ expect(p1).to.deep.equal({ instance: 1 }) @@ -316,7 +316,7 @@ describe('invoke', () => { const myDecorator = createParameterDecorator('myDecorator') class A extends FunctionalService { - public async handle( @myDecorator p1) { + public static async handle( @myDecorator p1) { counter++ expect(p1).to.deep.equal({ instance: 1 }) @@ -348,7 +348,7 @@ describe('invoke', () => { const myDecorator = createParameterDecorator('myDecorator') class A extends FunctionalService { - public async handle( @myDecorator p1) { + public static async handle( @myDecorator p1) { expect(false).to.equal(true, 'error not catched') } } @@ -399,11 +399,11 @@ describe('invoke', () => { @httpTrigger({ route: '/v1/a1' }) @injectable() class A extends FunctionalService { - public async handle( @param p1) { } + public static async handle( @param p1) { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({ p1: 'p1' }) } @@ -437,11 +437,11 @@ describe('invoke', () => { @httpTrigger({ route: '/v1/a1', methods: ['post'] }) @injectable() class A extends FunctionalService { - public async handle( @param p1) { } + public static async handle( @param p1) { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({ p1: 'p1' }) } @@ -472,11 +472,11 @@ describe('invoke', () => { @httpTrigger({ route: '/v1/a1', methods: ['get', 'post'] }) @injectable() class A extends FunctionalService { - public async handle( @param p1) { } + public static async handle( @param p1) { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({ p1: 'p1' }) } @@ -510,11 +510,11 @@ describe('invoke', () => { @httpTrigger({ route: '/v1/a1', methods: ['post', 'get'] }) @injectable() class A extends FunctionalService { - public async handle( @param p1) { } + public static async handle( @param p1) { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({ p1: 'p1' }) } @@ -545,11 +545,11 @@ describe('invoke', () => { @httpTrigger({ route: '/v1/a1', authLevel: 'anonymous' }) @injectable() class A extends FunctionalService { - public async handle( @param p1) { } + public static async handle( @param p1) { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({ p1: 'p1' }) } @@ -583,11 +583,11 @@ describe('invoke', () => { @httpTrigger({ route: '/v1/a1', methods: ['post'], authLevel: 'anonymous' }) @injectable() class A extends FunctionalService { - public async handle( @param p1) { } + public static async handle( @param p1) { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({ p1: 'p1' }) } @@ -619,11 +619,11 @@ describe('invoke', () => { @httpTrigger({ route: '/v1/a1' }) @injectable() class A extends FunctionalService { - public async handle( @param p1) { } + public static async handle( @param p1) { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({ p1: 'p1' }) } @@ -656,11 +656,11 @@ describe('invoke', () => { @httpTrigger({ route: '/v1/a1', authLevel: 'anonymous' }) @injectable() class A extends FunctionalService { - public async handle( @param p1) { } + public static async handle( @param p1) { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({ p1: 'p1' }) } @@ -707,11 +707,11 @@ describe('invoke', () => { @rest({ path: '/v1/a1' }) @injectable() class A extends FunctionalService { - public async handle( @param p1) { } + public static async handle( @param p1) { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({ p1: 'p1' }) } @@ -745,11 +745,11 @@ describe('invoke', () => { @rest({ path: '/v1/a1', methods: ['post'] }) @injectable() class A extends FunctionalService { - public async handle( @param p1) { } + public static async handle( @param p1) { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({ p1: 'p1' }) } @@ -780,11 +780,11 @@ describe('invoke', () => { @rest({ path: '/v1/a1', methods: ['get', 'post'] }) @injectable() class A extends FunctionalService { - public async handle( @param p1) { } + public static async handle( @param p1) { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({ p1: 'p1' }) } @@ -818,11 +818,11 @@ describe('invoke', () => { @rest({ path: '/v1/a1', methods: ['post', 'get'] }) @injectable() class A extends FunctionalService { - public async handle( @param p1) { } + public static async handle( @param p1) { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({ p1: 'p1' }) } @@ -856,11 +856,11 @@ describe('invoke', () => { @rest({ path: '/v1/a1', methods: ['any'] }) @injectable() class A extends FunctionalService { - public async handle( @param p1) { } + public static async handle( @param p1) { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({ p1: 'p1' }) } @@ -899,11 +899,11 @@ describe('invoke', () => { @injectable() class A extends FunctionalService { - public async handle( @param p1) { } + public static async handle( @param p1) { } } class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a) { counter++ const aResult = await a.invoke({ p1: 'p1' }) } diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index 4fc9ee4..c06c160 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -33,7 +33,7 @@ describe('invoker', () => { @injectable() class CustomService extends Service { - public async handle( @param p1, @param p2) { + public static async handle( @param p1, @param p2) { counter++ expect(p1).to.equal('p1', 'CustomService') expect(p2).to.equal('p2', 'CustomService') @@ -41,7 +41,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - public async handle( @param noparam, @inject(CustomService) myService) { + public static async handle( @param noparam, @inject(CustomService) myService) { counter++ expect(noparam).to.undefined expect(myService).to.instanceof(Function) @@ -70,7 +70,7 @@ describe('invoker', () => { @injectable() class CustomService extends Service { - public async handle( @param p1, @param p2) { + public static async handle( @param p1, @param p2) { counter++ expect(p1).to.equal('p1', 'CustomService') expect(p2).to.equal('p2', 'CustomService') @@ -94,7 +94,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - public async handle( @param p1, @inject(CustomApi) api) { + public static async handle( @param p1, @inject(CustomApi) api) { counter++ expect(p1).to.undefined expect(api).to.instanceof(CustomApi) @@ -123,7 +123,7 @@ describe('invoker', () => { @injectable() class CustomService extends Service { - public async handle( @param p1, @param p2) { + public static async handle( @param p1, @param p2) { counter++ expect(p1).to.equal('p1', 'CustomService') expect(p2).to.equal('p2', 'CustomService') @@ -161,7 +161,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - public async handle( @param p1, @inject(CustomApi) api) { + public static async handle( @param p1, @inject(CustomApi) api) { counter++ expect(counter).to.equal(3) expect(p1).to.undefined @@ -185,80 +185,19 @@ describe('invoker', () => { }) describe("injection modes", () => { - it("multiple inject transient api with transient service", async () => { + it("multiple inject transient api with a service", async () => { let counter = 0 let instanceCreation = 0 process.env.FUNCTIONAL_ENVIRONMENT = 'local' - @injectable(InjectionScope.Transient) - class CustomService extends Service { - public constructor() { - super() - instanceCreation++ - } - public async handle( @param p1, @param p2) { - counter++ - expect(p1).to.equal('p1', 'CustomService') - expect(p2).to.equal('p2', 'CustomService') - } - } - - @injectable(InjectionScope.Transient) - class CustomApi extends Api { - private _myService - public constructor( @inject(CustomService) myService, @inject(CustomService) myService2) { - super() - instanceCreation++ - this._myService = myService - } - public async myMethod(p1, p2) { - counter++ - expect(p1).to.equal('p1', 'CustomApi') - expect(p2).to.equal('p2', 'CustomApi') - - await this._myService({ p1, p2 }) - } - } - - class MockService extends FunctionalService { - public async handle( @param p1, @inject(CustomApi) api, @inject(CustomApi) api2) { - counter++ - expect(p1).to.undefined - expect(api).to.instanceof(CustomApi) - - await api.myMethod('p1', 'p2') - } - } - - const invoker = MockService.createInvoker() - await invoker({}, { - send: () => { - expect(counter).to.equal(3) - expect(instanceCreation).to.equal(6) - } - }, (e) => { - expect(null).to.equal(e) - throw e - }) - - expect(counter).to.equal(3) - expect(instanceCreation).to.equal(6) - }) - - it("multiple inject transient api with singleton service", async () => { - let counter = 0 - let instanceCreation = 0 - - process.env.FUNCTIONAL_ENVIRONMENT = 'local' - - @injectable(InjectionScope.Singleton) + @injectable() class CustomService extends Service { public constructor() { super() instanceCreation++ } - public async handle( @param p1, @param p2) { + public static async handle( @param p1, @param p2) { counter++ expect(p1).to.equal('p1', 'CustomService') expect(p2).to.equal('p2', 'CustomService') @@ -283,7 +222,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - public async handle( @param p1, @inject(CustomApi) api, @inject(CustomApi) api2) { + public static async handle( @param p1, @inject(CustomApi) api, @inject(CustomApi) api2) { counter++ expect(p1).to.undefined expect(api).to.instanceof(CustomApi) @@ -296,7 +235,7 @@ describe('invoker', () => { await invoker({}, { send: () => { expect(counter).to.equal(3) - expect(instanceCreation).to.equal(3) + expect(instanceCreation).to.equal(2) } }, (e) => { expect(null).to.equal(e) @@ -304,10 +243,10 @@ describe('invoker', () => { }) expect(counter).to.equal(3) - expect(instanceCreation).to.equal(3) + expect(instanceCreation).to.equal(2) }) - it("multiple inject singleton api with transient service", async () => { + it("multiple inject singleton api with a service", async () => { let counter = 0 let instanceCreation = 0 @@ -319,7 +258,7 @@ describe('invoker', () => { super() instanceCreation++ } - public async handle( @param p1, @param p2) { + public static async handle( @param p1, @param p2) { counter++ expect(p1).to.equal('p1', 'CustomService') expect(p2).to.equal('p2', 'CustomService') @@ -344,7 +283,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - public async handle( @param p1, @inject(CustomApi) api, @inject(CustomApi) api2) { + public static async handle( @param p1, @inject(CustomApi) api, @inject(CustomApi) api2) { counter++ expect(p1).to.undefined expect(api).to.instanceof(CustomApi) @@ -357,7 +296,7 @@ describe('invoker', () => { await invoker({}, { send: () => { expect(counter).to.equal(3) - expect(instanceCreation).to.equal(3) + expect(instanceCreation).to.equal(1) } }, (e) => { expect(null).to.equal(e) @@ -365,68 +304,7 @@ describe('invoker', () => { }) expect(counter).to.equal(3) - expect(instanceCreation).to.equal(3) - }) - - it("multiple inject singleton api with singleton service", async () => { - let counter = 0 - let instanceCreation = 0 - - process.env.FUNCTIONAL_ENVIRONMENT = 'local' - - @injectable(InjectionScope.Singleton) - class CustomService extends Service { - public constructor() { - super() - instanceCreation++ - } - public async handle( @param p1, @param p2) { - counter++ - expect(p1).to.equal('p1', 'CustomService') - expect(p2).to.equal('p2', 'CustomService') - } - } - - @injectable(InjectionScope.Singleton) - class CustomApi extends Api { - private _myService - public constructor( @inject(CustomService) myService, @inject(CustomService) myService2) { - super() - instanceCreation++ - this._myService = myService - } - public async myMethod(p1, p2) { - counter++ - expect(p1).to.equal('p1', 'CustomApi') - expect(p2).to.equal('p2', 'CustomApi') - - await this._myService({ p1, p2 }) - } - } - - class MockService extends FunctionalService { - public async handle( @param p1, @inject(CustomApi) api, @inject(CustomApi) api2) { - counter++ - expect(p1).to.undefined - expect(api).to.instanceof(CustomApi) - - await api.myMethod('p1', 'p2') - } - } - - const invoker = MockService.createInvoker() - await invoker({}, { - send: () => { - expect(counter).to.equal(3) - expect(instanceCreation).to.equal(2) - } - }, (e) => { - expect(null).to.equal(e) - throw e - }) - - expect(counter).to.equal(3) - expect(instanceCreation).to.equal(2) + expect(instanceCreation).to.equal(1) }) }) }) @@ -443,7 +321,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' class MockService extends FunctionalService { - handle() { + static handle() { counter++ } } @@ -462,7 +340,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' class MockService extends FunctionalService { - handle() { + static handle() { counter++ return { ok: 1 } } @@ -483,7 +361,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' class MockService extends FunctionalService { - handle() { + static handle() { counter++ throw new Error('error in handle') } @@ -507,7 +385,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + static handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -534,7 +412,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + static handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -561,7 +439,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + static handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -588,7 +466,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + static handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -615,7 +493,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + static handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.undefined @@ -641,7 +519,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' class MockService extends FunctionalService { - handle( @param p1, @param p2, @param p3, @param p4) { + static handle( @param p1, @param p2, @param p3, @param p4) { counter++ expect(p1).to.equal('body') expect(p2).to.equal('query') @@ -685,7 +563,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' class MockService extends FunctionalService { - handle( @param({ source: 'event.req.query' }) p1, @param({ source: 'event.req.params' }) p2, @param({ source: 'event.req.headers' }) p3, @param p4) { + static handle( @param({ source: 'event.req.query' }) p1, @param({ source: 'event.req.params' }) p2, @param({ source: 'event.req.headers' }) p3, @param p4) { counter++ expect(p1).to.equal('query') expect(p2).to.equal('params') @@ -733,7 +611,7 @@ describe('invoker', () => { class MockInjectable extends Resource { } class MockService extends FunctionalService { - handle( @param p1, @inject(MockInjectable) p2) { + static handle( @param p1, @inject(MockInjectable) p2) { counter++ expect(p1).to.undefined expect(p2).to.instanceof(Resource) @@ -757,7 +635,7 @@ describe('invoker', () => { @injectable() class CustomService extends Service { - handle( @param p1, @param p2) { + static handle( @param p1, @param p2) { counter++ expect(p1).to.equal('p1') expect(p2).to.equal('p2') @@ -765,7 +643,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle( @param p1, @inject(CustomService) p2) { + static handle( @param p1, @inject(CustomService) p2) { counter++ expect(p1).to.undefined expect(p2).to.instanceof(Function) @@ -802,7 +680,7 @@ describe('invoker', () => { const next = (e) => { e && done(e) } class MockService extends FunctionalService { - handle( @param p1, @serviceParams p2) { + static handle( @param p1, @serviceParams p2) { counter++ expect(p1).to.undefined expect(p2).to.have.property('req').that.to.equal(req) @@ -849,7 +727,7 @@ describe('invoker', () => { const next = (e) => { e && done(e) } class MockService extends FunctionalService { - handle( @param p1, @request r) { + static handle( @param p1, @request r) { counter++ expect(r).to.have.property('url').that.deep.equal(parse(req.originalUrl)) expect(r).to.have.property('method', req.method) @@ -900,7 +778,7 @@ describe('invoker', () => { const next = (e) => { e && done(e) } class MockService extends FunctionalService { - handle( @param p1, @request r) { + static handle( @param p1, @request r) { counter++ expect(r).to.have.property('url').that.deep.equal(req._parsedUrl) expect(r).to.have.property('method', req.method) @@ -947,7 +825,7 @@ describe('invoker', () => { const next = (e) => { expect(true).to.equal(false, e.message) } class MockService extends FunctionalService { - handle() { + static handle() { counter++ return { @@ -983,7 +861,7 @@ describe('invoker', () => { const next = (e) => { expect(true).to.equal(false, e.message) } class MockService extends FunctionalService { - handle( @functionalServiceName serviceName) { + static handle( @functionalServiceName serviceName) { counter++ expect(serviceName).to.equal('MockService') @@ -1014,7 +892,7 @@ describe('invoker', () => { @functionName('MyMockService') class MockService extends FunctionalService { - handle( @functionalServiceName serviceName) { + static handle( @functionalServiceName serviceName) { counter++ expect(serviceName).to.equal('MyMockService') @@ -1044,7 +922,7 @@ describe('invoker', () => { const next = (e) => { expect(true).to.equal(false, e.message) } class MockService extends FunctionalService { - handle( @provider provider) { + static handle( @provider provider) { counter++ expect(provider).to.equal('local') @@ -1075,7 +953,7 @@ describe('invoker', () => { const next = (e) => { expect(true).to.equal(false, e.message) } class MockService extends FunctionalService { - handle( @stage stage) { + static handle( @stage stage) { counter++ expect(stage).to.equal('dev') @@ -1103,7 +981,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - handle() { + static handle() { counter++ } } @@ -1120,7 +998,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - handle() { + static handle() { counter++ return { ok: 1 } } @@ -1139,7 +1017,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - handle() { + static handle() { counter++ throw new Error('error in handle') } @@ -1161,7 +1039,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + static handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -1186,7 +1064,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + static handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -1209,7 +1087,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - async handle( @param p1, @param p2) { + static async handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -1237,7 +1115,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - async handle( @param p1, @param p2) { + static async handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -1266,7 +1144,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - async handle( @param p1, @param p2) { + static async handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -1295,7 +1173,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - async handle( @param p1, @param p2) { + static async handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -1324,7 +1202,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - async handle( @param p1, @param p2) { + static async handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -1353,7 +1231,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - async handle( @param p1, @param p2) { + static async handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -1382,7 +1260,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - async handle( @param p1, @param p2, @param p3, @param p4) { + static async handle( @param p1, @param p2, @param p3, @param p4) { counter++ expect(p1).to.equal('body') expect(p2).to.equal('queryStringParameters') @@ -1427,7 +1305,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - async handle( @param({ source: 'event.event.queryStringParameters' }) p1, @param({ source: 'event.event.pathParameters' }) p2, @param({ source: 'event.event.headers' }) p3, @param p4) { + static async handle( @param({ source: 'event.event.queryStringParameters' }) p1, @param({ source: 'event.event.pathParameters' }) p2, @param({ source: 'event.event.headers' }) p3, @param p4) { counter++ expect(p1).to.equal('queryStringParameters') expect(p2).to.equal('pathParameters') @@ -1472,7 +1350,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - async handle() { + static async handle() { counter++ return { ok: 1 } } @@ -1501,7 +1379,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - async handle() { + static async handle() { counter++ return { statusCode: 200, @@ -1532,7 +1410,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - async handle() { + static async handle() { counter++ return { statusCode: 500, @@ -1569,7 +1447,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - async handle() { + static async handle() { counter++ throw new MyError('error in handle') } @@ -1613,7 +1491,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle( @param s3, @param('s3.object.key') p2) { + static handle( @param s3, @param('s3.object.key') p2) { counter++ expect(s3).to.deep.equal(awsEvent.Records[0].s3) expect(p2).to.equal('filename') @@ -1648,7 +1526,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle( @param s3, @param({ name: 'event.event.Records', source: null }) p2) { + static handle( @param s3, @param({ name: 'event.event.Records', source: null }) p2) { counter++ expect(s3).to.deep.equal(awsEvent.Records[0].s3) expect(p2).to.have.lengthOf(1); @@ -1681,7 +1559,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle( @param Sns, @param('Sns.Message') p2) { + static handle( @param Sns, @param('Sns.Message') p2) { counter++ expect(Sns).to.deep.equal(awsEvent.Records[0].Sns) expect(p2).to.equal('message') @@ -1713,7 +1591,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle( @param Sns, @param({ name: 'event.event.Records', source: null }) p2) { + static handle( @param Sns, @param({ name: 'event.event.Records', source: null }) p2) { counter++ expect(Sns).to.deep.equal(awsEvent.Records[0].Sns) expect(p2).to.have.lengthOf(1); @@ -1758,7 +1636,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle() { + static handle() { counter++ return { @@ -1789,7 +1667,7 @@ describe('invoker', () => { class MockInjectable extends Resource { } class MockService extends FunctionalService { - handle( @param p1, @inject(MockInjectable) p2) { + static handle( @param p1, @inject(MockInjectable) p2) { counter++ expect(p1).to.undefined expect(p2).to.instanceof(Resource) @@ -1812,7 +1690,7 @@ describe('invoker', () => { @injectable() class CustomService extends Service { - handle( @param p1, @param p2) { + static handle( @param p1, @param p2) { counter++ expect(p1).to.equal('p1') expect(p2).to.equal('p2') @@ -1820,7 +1698,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle( @param p1, @inject(CustomService) p2) { + static handle( @param p1, @inject(CustomService) p2) { counter++ expect(p1).to.undefined expect(p2).to.instanceof(Function) @@ -1853,7 +1731,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle( @param p1, @serviceParams p2) { + static handle( @param p1, @serviceParams p2) { counter++ expect(p1).to.undefined expect(p2).to.have.property('event').that.to.equal(awsEvent) @@ -1899,7 +1777,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle( @param p1, @request r) { + static handle( @param p1, @request r) { counter++ expect(r).to.have.property('url').that.deep.equal(parse(awsEvent.path)) expect(r).to.have.property('method', awsEvent.httpMethod) @@ -1946,7 +1824,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle( @param p1, @request r) { + static handle( @param p1, @request r) { counter++ expect(r).to.have.property('url').that.deep.equal(parse(awsEvent.path)) expect(r).to.have.property('method', awsEvent.httpMethod) @@ -1993,7 +1871,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle( @param p1, @request r) { + static handle( @param p1, @request r) { counter++ expect(r).to.have.property('url').that.deep.equal(parse(awsEvent.path)) expect(r).to.have.property('method', awsEvent.httpMethod) @@ -2031,7 +1909,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle() { + static handle() { counter++ return { @@ -2066,7 +1944,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle( @functionalServiceName serviceName) { + static handle( @functionalServiceName serviceName) { counter++ expect(serviceName).to.equal('MockService') @@ -2096,7 +1974,7 @@ describe('invoker', () => { @functionName('MyMockService') class MockService extends FunctionalService { - handle( @functionalServiceName serviceName) { + static handle( @functionalServiceName serviceName) { counter++ expect(serviceName).to.equal('MyMockService') @@ -2125,7 +2003,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle( @provider provider) { + static handle( @provider provider) { counter++ expect(provider).to.equal('aws') @@ -2155,7 +2033,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle( @stage stage) { + static handle( @stage stage) { counter++ expect(stage).to.equal('dev') @@ -2183,7 +2061,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' class MockService extends FunctionalService { - handle() { + static handle() { counter++ } } @@ -2203,7 +2081,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' class MockService extends FunctionalService { - handle() { + static handle() { counter++ return { ok: 1 } } @@ -2227,7 +2105,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' class MockService extends FunctionalService { - handle() { + static handle() { counter++ ex = new Error('error in handle') throw ex @@ -2252,7 +2130,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + static handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -2280,7 +2158,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + static handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -2308,7 +2186,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + static handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -2336,7 +2214,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + static handle( @param p1, @param p2) { counter++ expect(p1).to.equal('v1') expect(p2).to.equal('v2') @@ -2364,7 +2242,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' class MockService extends FunctionalService { - handle( @param p1, @param p2, @param p3, @param p4) { + static handle( @param p1, @param p2, @param p3, @param p4) { counter++ expect(p1).to.equal('body') expect(p2).to.equal('queryStringParameters') @@ -2408,7 +2286,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' class MockService extends FunctionalService { - handle( @param({ source: 'event.req.query' }) p1, @param({ source: 'event.req.params' }) p2, @param({ source: 'event.req.headers' }) p3, @param p4) { + static handle( @param({ source: 'event.req.query' }) p1, @param({ source: 'event.req.params' }) p2, @param({ source: 'event.req.headers' }) p3, @param p4) { counter++ expect(p1).to.equal('queryStringParameters') expect(p2).to.equal('pathParameters') @@ -2452,7 +2330,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' class MockService extends FunctionalService { - handle() { + static handle() { counter++ return { ok: 1 } } @@ -2475,7 +2353,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' class MockService extends FunctionalService { - handle() { + static handle() { counter++ return { status: 200, @@ -2501,7 +2379,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' class MockService extends FunctionalService { - handle() { + static handle() { counter++ return { status: 500, @@ -2528,7 +2406,7 @@ describe('invoker', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'azure' class MockService extends FunctionalService { - handle() { + static handle() { counter++ ex = new Error('error in handle') throw ex @@ -2555,7 +2433,7 @@ describe('invoker', () => { @injectable() class CustomService extends Service { - handle( @param p1, @param p2) { + static handle( @param p1, @param p2) { counter++ expect(p1).to.equal('p1') expect(p2).to.equal('p2') @@ -2563,7 +2441,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle( @param p1, @inject(CustomService) p2) { + static handle( @param p1, @inject(CustomService) p2) { counter++ expect(p1).to.undefined expect(p2).to.instanceof(Function) @@ -2595,7 +2473,7 @@ describe('invoker', () => { const req = {} class MockService extends FunctionalService { - handle( @param p1, @serviceParams p2) { + static handle( @param p1, @serviceParams p2) { counter++ expect(p1).to.undefined expect(p2).to.have.property('context').that.to.equal(context) @@ -2638,7 +2516,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle( @param p1, @request r) { + static handle( @param p1, @request r) { counter++ expect(r).to.have.property('url').that.deep.equal(parse(req.originalUrl)) expect(r).to.have.property('method', req.method) @@ -2671,7 +2549,7 @@ describe('invoker', () => { } class MockService extends FunctionalService { - handle() { + static handle() { counter++ return { @@ -2709,7 +2587,7 @@ describe('invoker', () => { const req = {} class MockService extends FunctionalService { - handle( @functionalServiceName serviceName) { + static handle( @functionalServiceName serviceName) { counter++ expect(serviceName).to.equal('MockService') @@ -2737,7 +2615,7 @@ describe('invoker', () => { @functionName('MyMockService') class MockService extends FunctionalService { - handle( @functionalServiceName serviceName) { + static handle( @functionalServiceName serviceName) { counter++ expect(serviceName).to.equal('MyMockService') @@ -2764,7 +2642,7 @@ describe('invoker', () => { const req = {} class MockService extends FunctionalService { - handle( @provider provider) { + static handle( @provider provider) { counter++ expect(provider).to.equal('azure') @@ -2792,7 +2670,7 @@ describe('invoker', () => { const req = {} class MockService extends FunctionalService { - handle( @stage stage) { + static handle( @stage stage) { counter++ expect(stage).to.equal('dev') diff --git a/test/ioc.tests.ts b/test/ioc.tests.ts index 15fcdfa..1493d66 100644 --- a/test/ioc.tests.ts +++ b/test/ioc.tests.ts @@ -269,7 +269,7 @@ describe('IOC', () => { container.registerType(A, AOtherApi) class B extends FunctionalService { - public async handle( @inject(A) a: A) { + public static async handle( @inject(A) a: A) { counter++ expect(a).is.instanceof(AOtherApi) @@ -291,7 +291,7 @@ describe('IOC', () => { @injectable() class A extends Service { - public async handle() { + public static async handle() { counter++ expect(false).to.equal(true, 'remap required') } @@ -299,7 +299,7 @@ describe('IOC', () => { @injectable() class AOtherService extends Service { - public async handle() { + public static async handle() { counter++ } } @@ -307,7 +307,7 @@ describe('IOC', () => { container.registerType(A, AOtherService) class B extends FunctionalService { - public async handle( @inject(A) a) { + public static async handle( @inject(A) a) { counter++ await a() diff --git a/test/serviceOnEvent.tests.ts b/test/serviceOnEvent.tests.ts index c8a354f..9c88be4 100644 --- a/test/serviceOnEvent.tests.ts +++ b/test/serviceOnEvent.tests.ts @@ -34,7 +34,7 @@ describe('service events', () => { } class MockService extends FunctionalService { - handle( @inject(MockInjectable) p1) { + public static handle( @inject(MockInjectable) p1) { counter++ expect(p1).to.instanceof(Resource) expect(p1).to.instanceof(MockInjectable) @@ -71,7 +71,7 @@ describe('service events', () => { } class MockService extends FunctionalService { - handle( @inject(MockInjectable) p1) { + public static handle( @inject(MockInjectable) p1) { counter++ expect(p1).to.not.instanceof(Resource) expect(p1).to.not.instanceof(MockInjectable) @@ -107,7 +107,7 @@ describe('service events', () => { } class MockService extends FunctionalService { - handle( @inject(MockInjectable) p1) { + public static handle( @inject(MockInjectable) p1) { counter++ expect(p1).to.instanceof(Resource) expect(p1).to.instanceof(MockInjectable) @@ -151,7 +151,7 @@ describe('service events', () => { } class MockService extends FunctionalService { - handle( @inject(MockInjectable) p1) { + public static handle( @inject(MockInjectable) p1) { counter++ expect(p1).to.instanceof(Resource) expect(p1).to.instanceof(MockInjectable) @@ -192,7 +192,7 @@ describe('service events', () => { } class MockService extends FunctionalService { - handle( @inject(MockInjectable) p1) { + public static handle( @inject(MockInjectable) p1) { counter++ expect(p1).to.not.instanceof(Resource) expect(p1).to.not.instanceof(MockInjectable) @@ -217,13 +217,13 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + public static handle( @param p1, @param p2) { counter++ return { ok: 1 } } - public async onHandle_local(req, res, next) { + public static async onHandle_local(req, res, next) { counter++ expect(req).to.deep.equal({ body: { p1: 1, p2: 2 } }) @@ -250,14 +250,14 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'local' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + public static handle( @param p1, @param p2) { counter++ expect(false).to.equal(true, 'skippable code') return { ok: 1 } } - public async onHandle_local(req, res, next) { + public static async onHandle_local(req, res, next) { counter++ expect(req).to.deep.equal({ body: { p1: 1, p2: 2 } }) @@ -304,7 +304,7 @@ describe('service events', () => { } class MockService extends FunctionalService { - handle( @inject(MockInjectable) p1) { + public static async handle( @inject(MockInjectable) p1) { counter++ expect(p1).to.instanceof(Resource) expect(p1).to.instanceof(MockInjectable) @@ -339,7 +339,7 @@ describe('service events', () => { } class MockService extends FunctionalService { - handle( @inject(MockInjectable) p1) { + public static async handle( @inject(MockInjectable) p1) { counter++ expect(p1).to.not.instanceof(Resource) expect(p1).to.not.instanceof(MockInjectable) @@ -373,7 +373,7 @@ describe('service events', () => { } class MockService extends FunctionalService { - handle( @inject(MockInjectable) p1) { + public static async handle( @inject(MockInjectable) p1) { counter++ expect(p1).to.instanceof(Resource) expect(p1).to.instanceof(MockInjectable) @@ -415,7 +415,7 @@ describe('service events', () => { } class MockService extends FunctionalService { - handle( @inject(MockInjectable) p1) { + public static async handle( @inject(MockInjectable) p1) { counter++ expect(p1).to.instanceof(Resource) expect(p1).to.instanceof(MockInjectable) @@ -454,7 +454,7 @@ describe('service events', () => { } class MockService extends FunctionalService { - handle( @inject(MockInjectable) p1) { + public static async handle( @inject(MockInjectable) p1) { counter++ expect(p1).to.not.instanceof(Resource) expect(p1).to.not.instanceof(MockInjectable) @@ -477,13 +477,13 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + public static async handle( @param p1, @param p2) { counter++ return { ok: 1 } } - public async onHandle_aws(event, context, cb) { + public static async onHandle_aws(event, context, cb) { counter++ expect(event).to.deep.equal({ p1: 1, p2: 2 }) @@ -509,14 +509,14 @@ describe('service events', () => { process.env.FUNCTIONAL_ENVIRONMENT = 'aws' class MockService extends FunctionalService { - handle( @param p1, @param p2) { + public static async handle( @param p1, @param p2) { counter++ expect(false).to.equal(true, 'skippable code') return { ok: 1 } } - public async onHandle_aws(event, context, cb) { + public static async onHandle_aws(event, context, cb) { counter++ expect(event).to.deep.equal({ p1: 1, p2: 2 }) @@ -544,7 +544,7 @@ describe('service events', () => { describe("mock", () => { class MockProvider extends LocalProvider { - public async invoke(serviceInstance, params, invokeConfig?): Promise { } + public async invoke(serviceType, params, invokeConfig?): Promise { } } before(() => { addProvider('mock', new MockProvider()) @@ -562,7 +562,7 @@ describe('service events', () => { @injectable() class MockInjectable extends FunctionalService { - onInvoke({ params, invokeConfig }) { + public static async onInvoke({ params, invokeConfig }) { counter++ expect(params).to.deep.equal({ p1: 1, p2: 2 }) @@ -571,10 +571,10 @@ describe('service events', () => { } class MockService extends FunctionalService { - async handle( @inject(MockInjectable) p1) { + public static async handle( @inject(MockInjectable) p1) { counter++ - expect(p1).to.instanceof(Resource) - expect(p1).to.instanceof(MockInjectable) + expect(p1.prototype).to.instanceof(Resource) + expect(p1).to.equal(MockInjectable) await p1.invoke({ p1: 1, p2: 2 }, { config: 1 }) } @@ -595,8 +595,8 @@ describe('service events', () => { @injectable() class MockInjectable extends FunctionalService { - handle( @param p1) { } - onInvoke_mock({ invokeParams, params, invokeConfig, parameterMapping, currentEnvironment, environmentMode }) { + public static async handle( @param p1) { } + static onInvoke_mock({ invokeParams, params, invokeConfig, parameterMapping, currentEnvironment, environmentMode }) { counter++ expect(invokeParams).to.deep.equal({ p1: 1, p2: 2 }) @@ -614,10 +614,10 @@ describe('service events', () => { } class MockService extends FunctionalService { - async handle( @inject(MockInjectable) p1) { + public static async handle( @inject(MockInjectable) p1) { counter++ - expect(p1).to.instanceof(Resource) - expect(p1).to.instanceof(MockInjectable) + expect(p1.prototype).to.instanceof(Resource) + expect(p1).to.equal(MockInjectable) await p1.invoke({ p1: 1, p2: 2 }, { config: 1 }) } From cce5aca926f1864d0931694dec37128c560fd2c7 Mon Sep 17 00:00:00 2001 From: borzav Date: Thu, 1 Feb 2018 16:25:39 +0100 Subject: [PATCH 149/196] CHANGE: FunctionalService injection is an invoke function like a service --- src/classes/functionalService.ts | 3 +-- test/invoke.tests.ts | 40 ++++++++++++++++---------------- test/serviceOnEvent.tests.ts | 8 ++----- 3 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/classes/functionalService.ts b/src/classes/functionalService.ts index 7ff351e..2ba1943 100644 --- a/src/classes/functionalService.ts +++ b/src/classes/functionalService.ts @@ -22,8 +22,7 @@ export class FunctionalService extends Resource { public static async onInject({ parameter }): Promise { const injectableType = container.resolveType(this) - // return (...params) => injectableType.invoke(...params) - return injectableType + return (...params) => injectableType.invoke(...params) } public static onDefineInjectTo(target) { diff --git a/test/invoke.tests.ts b/test/invoke.tests.ts index 87a1212..1082542 100644 --- a/test/invoke.tests.ts +++ b/test/invoke.tests.ts @@ -41,7 +41,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({}) + const aResult = await a({}) } } @@ -75,7 +75,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({ p1: 'p1' }) + const aResult = await a({ p1: 'p1' }) } } @@ -109,7 +109,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({ p1: 'p1', p2: 'p2' }) + const aResult = await a({ p1: 'p1', p2: 'p2' }) } } @@ -143,7 +143,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({}, { a: 1, b: 2, c: 3 }) + const aResult = await a({}, { a: 1, b: 2, c: 3 }) } } @@ -175,7 +175,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({}) + const aResult = await a({}) } } @@ -211,7 +211,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({}, { mode: 'unknown' }) + const aResult = await a({}, { mode: 'unknown' }) } } @@ -405,7 +405,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({ p1: 'p1' }) + const aResult = await a({ p1: 'p1' }) } } @@ -443,7 +443,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({ p1: 'p1' }) + const aResult = await a({ p1: 'p1' }) } } @@ -478,7 +478,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({ p1: 'p1' }) + const aResult = await a({ p1: 'p1' }) } } @@ -516,7 +516,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({ p1: 'p1' }) + const aResult = await a({ p1: 'p1' }) } } @@ -551,7 +551,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({ p1: 'p1' }) + const aResult = await a({ p1: 'p1' }) } } @@ -589,7 +589,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({ p1: 'p1' }) + const aResult = await a({ p1: 'p1' }) } } @@ -625,7 +625,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({ p1: 'p1' }) + const aResult = await a({ p1: 'p1' }) } } @@ -662,7 +662,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({ p1: 'p1' }) + const aResult = await a({ p1: 'p1' }) } } @@ -713,7 +713,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({ p1: 'p1' }) + const aResult = await a({ p1: 'p1' }) } } @@ -751,7 +751,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({ p1: 'p1' }) + const aResult = await a({ p1: 'p1' }) } } @@ -786,7 +786,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({ p1: 'p1' }) + const aResult = await a({ p1: 'p1' }) } } @@ -824,7 +824,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({ p1: 'p1' }) + const aResult = await a({ p1: 'p1' }) } } @@ -862,7 +862,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({ p1: 'p1' }) + const aResult = await a({ p1: 'p1' }) } } @@ -905,7 +905,7 @@ describe('invoke', () => { class B extends FunctionalService { public static async handle( @inject(A) a) { counter++ - const aResult = await a.invoke({ p1: 'p1' }) + const aResult = await a({ p1: 'p1' }) } } diff --git a/test/serviceOnEvent.tests.ts b/test/serviceOnEvent.tests.ts index 9c88be4..9f45a80 100644 --- a/test/serviceOnEvent.tests.ts +++ b/test/serviceOnEvent.tests.ts @@ -573,10 +573,8 @@ describe('service events', () => { class MockService extends FunctionalService { public static async handle( @inject(MockInjectable) p1) { counter++ - expect(p1.prototype).to.instanceof(Resource) - expect(p1).to.equal(MockInjectable) - await p1.invoke({ p1: 1, p2: 2 }, { config: 1 }) + await p1({ p1: 1, p2: 2 }, { config: 1 }) } } @@ -616,10 +614,8 @@ describe('service events', () => { class MockService extends FunctionalService { public static async handle( @inject(MockInjectable) p1) { counter++ - expect(p1.prototype).to.instanceof(Resource) - expect(p1).to.equal(MockInjectable) - await p1.invoke({ p1: 1, p2: 2 }, { config: 1 }) + await p1({ p1: 1, p2: 2 }, { config: 1 }) } } From 58aec2c24802f38891133983342763e41a70cf14 Mon Sep 17 00:00:00 2001 From: borzav Date: Thu, 1 Feb 2018 16:31:48 +0100 Subject: [PATCH 150/196] Service and API can resolve PreHook injections --- src/classes/functionalService.ts | 4 +- src/classes/service.ts | 6 +- src/providers/aws/index.ts | 4 +- src/providers/azure/index.ts | 4 +- src/providers/core/provider.ts | 12 +- src/providers/inProc.ts | 4 +- src/providers/local.ts | 4 +- test/hook.tests.ts | 339 ++++++++++++++++++++++++++++++- test/invoke.tests.ts | 8 +- test/invoker.tests.ts | 1 - test/serviceOnEvent.tests.ts | 2 +- 11 files changed, 362 insertions(+), 26 deletions(-) diff --git a/src/classes/functionalService.ts b/src/classes/functionalService.ts index 2ba1943..cc18702 100644 --- a/src/classes/functionalService.ts +++ b/src/classes/functionalService.ts @@ -20,9 +20,9 @@ export class FunctionalService extends Resource { return invoker } - public static async onInject({ parameter }): Promise { + public static async onInject({ parameter, context }): Promise { const injectableType = container.resolveType(this) - return (...params) => injectableType.invoke(...params) + return (params?, invokeConfig?) => injectableType.invoke(params, { ...invokeConfig, context: context.context }) } public static onDefineInjectTo(target) { diff --git a/src/classes/service.ts b/src/classes/service.ts index d802167..f3bf466 100644 --- a/src/classes/service.ts +++ b/src/classes/service.ts @@ -29,14 +29,14 @@ export class Service extends Resource { parameterMapping }) - const invoker = provider.getInvoker(this, undefined) + const invoker = provider.getInvoker(this, undefined, invokeConfig && invokeConfig.context) return await invoker(availableParams) } - public static async onInject({ parameter }): Promise { + public static async onInject({ parameter, context }): Promise { const injectableType = container.resolveType(this) - return (...params) => injectableType.invoke(...params) + return (params?, invokeConfig?) => injectableType.invoke(params, { ...(invokeConfig || {}), context: context.context }) } public static onDefineInjectTo(target, targetKey, parameterIndex: number) { diff --git a/src/providers/aws/index.ts b/src/providers/aws/index.ts index 1a97cfe..00c0470 100644 --- a/src/providers/aws/index.ts +++ b/src/providers/aws/index.ts @@ -28,8 +28,8 @@ const eventSourceHandlers = [ export class AWSProvider extends Provider { - public getInvoker(serviceType, params): Function { - const callContext = this.createCallContext(serviceType, 'handle') + public getInvoker(serviceType, params, initContext): Function { + const callContext = this.createCallContext(serviceType, 'handle', initContext) const invoker = async (event, context, cb) => { try { diff --git a/src/providers/azure/index.ts b/src/providers/azure/index.ts index e8795ce..ca4381a 100644 --- a/src/providers/azure/index.ts +++ b/src/providers/azure/index.ts @@ -14,8 +14,8 @@ export const FUNCTIONLY_FUNCTION_KEY = 'FUNCTIONLY_FUNCTION_KEY' export class AzureProvider extends Provider { - public getInvoker(serviceType, params): Function { - const callContext = this.createCallContext(serviceType, 'handle') + public getInvoker(serviceType, params, initContext): Function { + const callContext = this.createCallContext(serviceType, 'handle', initContext) const invoker = async (context, req) => { try { diff --git a/src/providers/core/provider.ts b/src/providers/core/provider.ts index 37031c7..0609e22 100644 --- a/src/providers/core/provider.ts +++ b/src/providers/core/provider.ts @@ -7,7 +7,7 @@ import { PostHook } from '../../classes/middleware/postHook' import { container } from '../../helpers/ioc' export abstract class Provider { - public getInvoker(serviceType, params): Function { + public getInvoker(serviceType, params, initContext): Function { const invoker = () => { } return invoker } @@ -46,18 +46,18 @@ export abstract class Provider { return implementation(parameter, context, this) } - protected createCallContext(target, method) { + protected createCallContext(target, method, initContext) { const hooks = getMiddlewares(target) const parameters = this.getParameters(target, method) const preHooks = hooks.filter(h => h.prototype instanceof PreHook) - .map(h => ({ hookKey: h.name, hook: this.createCallContext(h, 'handle') })) + .map(h => ({ hookKey: h.name, hook: this.createCallContext(h, 'handle', initContext) })) const postHookInstances = hooks.filter(h => h.prototype instanceof PostHook) - const postHooks = postHookInstances.map(h => ({ hookKey: h.name, hook: this.createCallContext(h, 'handle') })) - const catchHooks = postHookInstances.map(h => ({ hookKey: h.name, hook: this.createCallContext(h, 'catch') })) + const postHooks = postHookInstances.map(h => ({ hookKey: h.name, hook: this.createCallContext(h, 'handle', initContext) })) + const catchHooks = postHookInstances.map(h => ({ hookKey: h.name, hook: this.createCallContext(h, 'catch', initContext) })) return async (context) => { - const preic: any = {} + const preic: any = { ...(initContext || {}) } const preContext = { context: preic, ...context } try { diff --git a/src/providers/inProc.ts b/src/providers/inProc.ts index e6ad965..2e03603 100644 --- a/src/providers/inProc.ts +++ b/src/providers/inProc.ts @@ -7,8 +7,8 @@ import { container } from '../helpers/ioc' import { parse } from 'url' export class InProcProvider extends Provider { - public getInvoker(serviceType, params) { - const callContext = this.createCallContext(serviceType, 'handle') + public getInvoker(serviceType, params, initContext) { + const callContext = this.createCallContext(serviceType, 'handle', initContext) const invoker = async (invokeParams) => { const eventContext = { params: invokeParams } diff --git a/src/providers/local.ts b/src/providers/local.ts index 491998f..34884fb 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -7,8 +7,8 @@ import { container } from '../helpers/ioc' import { parse } from 'url' export class LocalProvider extends Provider { - public getInvoker(serviceType, params) { - const callContext = this.createCallContext(serviceType, 'handle') + public getInvoker(serviceType, params, initContext) { + const callContext = this.createCallContext(serviceType, 'handle', initContext) const invoker = async (req, res, next) => { try { diff --git a/test/hook.tests.ts b/test/hook.tests.ts index 3f6c177..0fd7901 100644 --- a/test/hook.tests.ts +++ b/test/hook.tests.ts @@ -1,5 +1,5 @@ import { expect } from 'chai' -import { FunctionalService, PreHook, PostHook, Resource, DynamoTable, SimpleNotificationService, S3Storage } from '../src/classes' +import { FunctionalService, Service, Api, PreHook, PostHook, Resource, DynamoTable, SimpleNotificationService, S3Storage } from '../src/classes' import { getOverridableMetadata, constants, getMetadata, getFunctionName } from '../src/annotations' const { PARAMETER_PARAMKEY, CLASS_ENVIRONMENTKEY, CLASS_DYNAMOTABLECONFIGURATIONKEY, CLASS_SNSCONFIGURATIONKEY, CLASS_S3CONFIGURATIONKEY, CLASS_MIDDLEWAREKEY } = constants @@ -1421,4 +1421,341 @@ describe('hooks', () => { expect(counter).to.equal(3) }) }) + + describe('hook value deep', () => { + it('prehook value in service', async () => { + let counter = 0 + + @injectable() + class TestHook extends PreHook { + public static async handle() { + counter++ + expect(counter).to.equal(1) + return 'v1' + } + } + + @injectable() + class TestService extends Service { + public static async handle( @inject(TestHook) p1) { + counter++ + expect(counter).to.equal(3) + + expect(p1).to.equal('v1') + + return p1 + } + } + + @use(TestHook) + class TestFunctionalService extends FunctionalService { + public static async handle( @inject(TestService) testService) { + counter++ + expect(counter).to.equal(2) + + const ts = await testService() + + expect(ts).to.equal('v1') + + return { ok: 1 } + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + expect(result).to.deep.equal({ ok: 1 }) + counter++ + expect(counter).to.equal(4) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(4) + }) + + it('prehook value in service of service', async () => { + let counter = 0 + + @injectable() + class TestHook extends PreHook { + public static async handle() { + counter++ + expect(counter).to.equal(1) + return 'v1' + } + } + + @injectable() + class TestService2 extends Service { + public static async handle( @inject(TestHook) p1) { + counter++ + expect(counter).to.equal(4) + + expect(p1).to.equal('v1') + + return p1 + } + } + + @injectable() + class TestService extends Service { + public static async handle( @inject(TestService2) testService2) { + counter++ + expect(counter).to.equal(3) + + const p1 = await testService2() + + return p1 + } + } + + @use(TestHook) + class TestFunctionalService extends FunctionalService { + public static async handle( @inject(TestService) testService) { + counter++ + expect(counter).to.equal(2) + + const ts = await testService() + + expect(ts).to.equal('v1') + + return { ok: 1 } + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + expect(result).to.deep.equal({ ok: 1 }) + counter++ + expect(counter).to.equal(5) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(5) + }) + + it('prehook value in api of service', async () => { + let counter = 0 + + @injectable() + class TestHook extends PreHook { + public static async handle() { + counter++ + expect(counter).to.equal(1) + return 'v1' + } + } + + @injectable() + class TestApi2 extends Api { + public constructor( @inject(TestHook) p1) { + super() + + counter++ + expect(counter).to.equal(3) + + expect(p1).to.equal('v1') + } + } + + @injectable() + class TestService extends Service { + public static async handle( @inject(TestApi2) testApi) { + counter++ + expect(counter).to.equal(4) + } + } + + @use(TestHook) + class TestFunctionalService extends FunctionalService { + public static async handle( @inject(TestService) testService) { + counter++ + expect(counter).to.equal(2) + + await testService() + + return { ok: 1 } + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + expect(result).to.deep.equal({ ok: 1 }) + counter++ + expect(counter).to.equal(5) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(5) + }) + + it('prehook value in api', async () => { + let counter = 0 + + @injectable() + class TestHook extends PreHook { + public static async handle() { + counter++ + expect(counter).to.equal(1) + return 'v1' + } + } + + @injectable() + class TestApi extends Api { + public constructor( @inject(TestHook) p1) { + super() + + counter++ + expect(counter).to.equal(2) + + expect(p1).to.equal('v1') + } + } + + @use(TestHook) + class TestFunctionalService extends FunctionalService { + public static async handle( @inject(TestApi) testApi) { + counter++ + expect(counter).to.equal(3) + + return { ok: 1 } + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + expect(result).to.deep.equal({ ok: 1 }) + counter++ + expect(counter).to.equal(4) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(4) + }) + + it('prehook value in api of api', async () => { + let counter = 0 + + @injectable() + class TestHook extends PreHook { + public static async handle() { + counter++ + expect(counter).to.equal(1) + return 'v1' + } + } + + @injectable() + class TestApi2 extends Api { + public constructor( @inject(TestHook) p1) { + super() + + counter++ + expect(counter).to.equal(2) + + expect(p1).to.equal('v1') + } + } + + @injectable() + class TestApi extends Api { + public constructor( @inject(TestApi2) testApi2) { + super() + + counter++ + expect(counter).to.equal(3) + } + } + + @use(TestHook) + class TestFunctionalService extends FunctionalService { + public static async handle( @inject(TestApi) testApi) { + counter++ + expect(counter).to.equal(4) + + return { ok: 1 } + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + expect(result).to.deep.equal({ ok: 1 }) + counter++ + expect(counter).to.equal(5) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(5) + }) + + it('prehook value in service of api', async () => { + let counter = 0 + + @injectable() + class TestHook extends PreHook { + public static async handle() { + counter++ + expect(counter).to.equal(1) + return 'v1' + } + } + + @injectable() + class TestService extends Service { + public static async handle( @inject(TestHook) p1) { + counter++ + expect(counter).to.equal(4) + + expect(p1).to.equal('v1') + + return p1 + } + } + + @injectable() + class TestApi extends Api { + public constructor( @inject(TestService) private testService) { + super() + + counter++ + expect(counter).to.equal(2) + } + + public async init (){ + counter++ + expect(counter).to.equal(3) + + const p1 = await this.testService() + + expect(p1).to.equal('v1') + } + } + + @use(TestHook) + class TestFunctionalService extends FunctionalService { + public static async handle( @inject(TestApi) testApi) { + counter++ + expect(counter).to.equal(5) + + return { ok: 1 } + } + } + + const invoker = TestFunctionalService.createInvoker() + await invoker({}, { + send: (result) => { + expect(result).to.deep.equal({ ok: 1 }) + counter++ + expect(counter).to.equal(6) + } + }, (e) => { expect(true).to.equal(false, e.message) }) + + expect(counter).to.equal(6) + }) + }) }) \ No newline at end of file diff --git a/test/invoke.tests.ts b/test/invoke.tests.ts index 1082542..2a404fb 100644 --- a/test/invoke.tests.ts +++ b/test/invoke.tests.ts @@ -28,7 +28,7 @@ describe('invoke', () => { expect(serviceType).to.equal(A) expect(params).to.have.deep.equal({}) - expect(invokeConfig).is.undefined + expect(invokeConfig).is.deep.equal({ context: {} }) } } addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) @@ -62,7 +62,7 @@ describe('invoke', () => { expect(serviceType).to.equal(A) expect(params).to.have.deep.equal({ p1: 'p1' }) - expect(invokeConfig).is.undefined + expect(invokeConfig).is.deep.equal({ context: {} }) } } addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) @@ -96,7 +96,7 @@ describe('invoke', () => { expect(serviceType).to.equal(A) expect(params).to.have.deep.equal({ p1: 'p1' }) - expect(invokeConfig).is.undefined + expect(invokeConfig).is.deep.equal({ context: {} }) } } addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) @@ -130,7 +130,7 @@ describe('invoke', () => { expect(serviceType).to.equal(A) expect(params).to.deep.equal({}) - expect(invokeConfig).to.deep.equal({ a: 1, b: 2, c: 3 }) + expect(invokeConfig).to.deep.equal({ a: 1, b: 2, c: 3, context: {} }) } } addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) diff --git a/test/invoker.tests.ts b/test/invoker.tests.ts index c06c160..be87730 100644 --- a/test/invoker.tests.ts +++ b/test/invoker.tests.ts @@ -1,6 +1,5 @@ import { expect } from 'chai' -import { getInvoker } from '../src/providers' import { FunctionalService, Resource, Service, Api } from '../src/classes' import { param, inject, injectable, serviceParams, request, functionalServiceName, functionName, provider, stage, InjectionScope } from '../src/annotations' import { parse } from 'url' diff --git a/test/serviceOnEvent.tests.ts b/test/serviceOnEvent.tests.ts index 9f45a80..4cb25f3 100644 --- a/test/serviceOnEvent.tests.ts +++ b/test/serviceOnEvent.tests.ts @@ -566,7 +566,7 @@ describe('service events', () => { counter++ expect(params).to.deep.equal({ p1: 1, p2: 2 }) - expect(invokeConfig).to.deep.equal({ config: 1 }) + expect(invokeConfig).to.deep.equal({ config: 1, context: {} }) } } From b7a3c34cd7f2f5a35443c712b1cf4ff4f896a8df Mon Sep 17 00:00:00 2001 From: borzav Date: Thu, 1 Feb 2018 16:35:04 +0100 Subject: [PATCH 151/196] RENAME: cli command local renamed to start --- src/cli/commands/{local.ts => start.ts} | 2 +- src/cli/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/cli/commands/{local.ts => start.ts} (99%) diff --git a/src/cli/commands/local.ts b/src/cli/commands/start.ts similarity index 99% rename from src/cli/commands/local.ts rename to src/cli/commands/start.ts index 51de1aa..0fd2fd4 100644 --- a/src/cli/commands/local.ts +++ b/src/cli/commands/start.ts @@ -118,7 +118,7 @@ export default ({ createContext, annotations: { getMetadata, constants, getFunct return { commands({ commander }) { commander - .command('local [port] [path]') + .command('start [port] [path]') .description('run functional service local') .option('--stage ', 'stage') .action(async (port, path, command) => { diff --git a/src/cli/index.ts b/src/cli/index.ts index 4e4aae7..b860d31 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -5,7 +5,7 @@ import './providers' //built-in commands import * as deploy from './commands/deploy' import * as deployPackage from './commands/package' -import * as local from './commands/local' +import * as start from './commands/start' import * as metadata from './commands/metadata' import * as serverless from './commands/serverless' @@ -14,7 +14,7 @@ import { init as initProjectConfig, internalPluginLoad } from './project/init' export const init = (commander) => { internalPluginLoad(deploy) internalPluginLoad(deployPackage) - internalPluginLoad(local) + internalPluginLoad(start) internalPluginLoad(metadata) internalPluginLoad(serverless) From c699edbeefce7ef7b2ba415e8483541674626ee8 Mon Sep 17 00:00:00 2001 From: borzav Date: Thu, 1 Feb 2018 16:52:08 +0100 Subject: [PATCH 152/196] update readme --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c322a79..1625e5d 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ import { FunctionalService, rest, description, param } from 'functionly' @rest({ path: '/hello-world', anonymous: true }) @description('hello world service') export class HelloWorld extends FunctionalService { - async handle(@param name = 'world') { + static async handle(@param name = 'world') { return `hello ${name}` } } @@ -19,7 +19,7 @@ export const helloworld = HelloWorld.createInvoker() ``` Running on localhost: ```sh -functionly local +functionly start ``` Try it on http://localhost:3000/hello-world?name=Joe @@ -102,7 +102,7 @@ We need to create a `FunctionalService` to implement the business logic of hello import { FunctionalService } from 'functionly' export class HelloWorld extends FunctionalService { - async handle() {} + static async handle() {} } ``` If you want your service to be accessible with a web request over a rest interface then you have to decorate it with the [rest]() decorator. We need a `path` and have to set the `anonymous` property to `true` because we want to call it without authentication. @@ -121,7 +121,7 @@ import { FunctionalService, rest, description } from 'functionly' @rest({ path: '/hello-world', anonymous: true }) @description('hello world service') export class HelloWorld extends FunctionalService { - async handle() { + static async handle() { return `hello world` } } @@ -138,7 +138,7 @@ import { FunctionalService, rest, description, param } from 'functionly' @rest({ path: '/hello-world', anonymous: true }) @description('hello world service') export class HelloWorld extends FunctionalService { - async handle(@param name = 'world') { + static async handle(@param name = 'world') { return `hello ${name}` } } @@ -169,7 +169,7 @@ export class TodoTable extends DynamoTable { } We need to create a service to read todo items. ```js export class GetAllTodos extends TodoService { - async handle() {} + static async handle() {} } ``` If you want your service to be accessible with a web request over a rest interface then you have to decorate it with the [rest]() decorator. We need a `path` and have to set the `cors` and the `anonymous` properties to `true` because we want to call it without authentication and from another domain. @@ -188,7 +188,7 @@ import { rest, description, inject } from 'functionly' @rest({ path: '/getAllTodos', cors: true, anonymous: true }) @description('get all Todo service') export class GetAllTodos extends TodoService { - async handle(@inject(TodoTable) db) { + static async handle(@inject(TodoTable) db) { let items = await db.scan() return { ok: 1, items } } @@ -207,7 +207,7 @@ import { rest, description } from 'functionly' @rest({ path: '/createTodo', methods: ['post'], anonymous: true, cors: true }) @description('create Todo service') export class CreateTodo extends TodoService { - async handle() {} + static async handle() {} } ``` We need some values to create a new todo item: `name`, `description` and `status`. Expect these with the [param]() decorator, and it will resolve them from the invocation context. @@ -217,7 +217,7 @@ import { rest, description, param } from 'functionly' @rest({ path: '/createTodo', methods: ['post'], anonymous: true, cors: true }) @description('create Todo service') export class CreateTodo extends TodoService { - async handle(@param name, @param description, @param staus) {} + static async handle(@param name, @param description, @param staus) {} } ``` The business logic: save a new todo item. [Inject]() the `TodoTable` and save a new todo item with the `put` function. We need an id for the new todo, in the example, we'll use [shortid](https://www.npmjs.com/package/shortid) to generate them. @@ -228,7 +228,7 @@ import { rest, description, param } from 'functionly' @rest({ path: '/createTodo', methods: ['post'], anonymous: true, cors: true }) @description('create Todo service') export class CreateTodo extends TodoService { - async handle(@param name, @param description, @param status, @inject(TodoTable) db) { + static async handle(@param name, @param description, @param status, @inject(TodoTable) db) { let item = { id: generate(), name, @@ -257,7 +257,7 @@ import { injectable, param } from 'functionly' @injectable() export class ValidateTodo extends Service { - async handle( @param name, @param description, @param status) { + static async handle( @param name, @param description, @param status) { const isValid = true return { isValid } } @@ -271,7 +271,7 @@ import { injectable, param, inject } from 'functionly' @injectable() export class PersistTodo extends Service { - async handle( @param name, @param description, @param status, @inject(TodoTable) db) { + static async handle( @param name, @param description, @param status, @inject(TodoTable) db) { let item = { id: generate(), name, @@ -292,7 +292,7 @@ import { rest, description, param, inject } from 'functionly' @rest({ path: '/createTodo', methods: ['post'], anonymous: true, cors: true }) @description('create Todo service') export class CreateTodo extends TodoService { - async handle( + static async handle( @param name, @param description, @param status, @@ -336,7 +336,7 @@ functionly deploy local ## Run in local environment During development, you can run the application on your local machine. ```sh -functionly local +functionly start ``` ## AWS deployment From 336f48f39085d8d4c44a5e265be01d02a3bce683 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Fri, 2 Feb 2018 08:48:47 +0000 Subject: [PATCH 153/196] 0.0.42 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4ecc6c6..5ceafa2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.41", + "version": "0.0.42", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 47792b7..de7838d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.41", + "version": "0.0.42", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From d569c9ce31e31ef2a34dc9633247f24e6d94850f Mon Sep 17 00:00:00 2001 From: borzav Date: Fri, 2 Feb 2018 13:26:06 +0100 Subject: [PATCH 154/196] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1625e5d..c77e181 100644 --- a/README.md +++ b/README.md @@ -329,7 +329,7 @@ npm install functionly -g docker run -d --name dynamodb -p 8000:8000 peopleperhour/dynamodb ``` 2. Deploy will create the tables in DynamoDB -> Note: Create the [functionly.json](https://raw.githubusercontent.com/jaystack/functionly-examples/master/todoDB/functionly.json) in the project for short commands. Also, you don't have to pass all arguments. +> Note: Create the [functionly.json](#functionly-configuration) in the project for short commands. Also, you don't have to pass all arguments. ```sh functionly deploy local ``` @@ -344,7 +344,7 @@ functionly start > [Set up](http://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/setup-credentials.html) AWS Credentials before deployment. -> Note: Create the [functionly.json](https://raw.githubusercontent.com/jaystack/functionly-examples/master/todoDB/functionly.json) in the project for short commands. Also, you don't have to pass all arguments. As the `deployTarget` is configured as `aws` (the default value configured) then the deploy command will use this as deployment target. +> Note: Create the [functionly.json](#functionly-configuration) in the project for short commands. Also, you don't have to pass all arguments. As the `deployTarget` is configured as `aws` (the default value configured) then the deploy command will use this as deployment target. Functionly will create the package and deploy the application to AWS. The package is a [CloudFormation](https://aws.amazon.com/cloudformation/) template, it contains all the AWS resources so AWS can create or update the application's resources based on the template. ```sh From fa4eda0e64df6d7b23cb6bc8a7970247e3ce3ce2 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Fri, 2 Feb 2018 12:31:19 +0000 Subject: [PATCH 155/196] 0.0.43 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5ceafa2..428747c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.42", + "version": "0.0.43", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index de7838d..60dc22e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.42", + "version": "0.0.43", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From bcb1fdb2afea3bac2b4871d1825101517e1f3c2a Mon Sep 17 00:00:00 2001 From: borzav Date: Fri, 2 Feb 2018 15:19:47 +0100 Subject: [PATCH 156/196] change: auth related config and a defaults of rest decorators --- README.md | 24 +++---- src/annotations/classes/aws/apiGateway.ts | 4 +- src/annotations/classes/azure/httpTrigger.ts | 4 +- src/annotations/classes/rest.ts | 6 +- test/annotation.tests.ts | 66 ++++++++++---------- test/invoke.tests.ts | 10 +-- 6 files changed, 57 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index c77e181..b60b443 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Defining a rest service which listens on `/hello-world`: ```js import { FunctionalService, rest, description, param } from 'functionly' -@rest({ path: '/hello-world', anonymous: true }) +@rest({ path: '/hello-world' }) @description('hello world service') export class HelloWorld extends FunctionalService { static async handle(@param name = 'world') { @@ -105,10 +105,10 @@ export class HelloWorld extends FunctionalService { static async handle() {} } ``` -If you want your service to be accessible with a web request over a rest interface then you have to decorate it with the [rest]() decorator. We need a `path` and have to set the `anonymous` property to `true` because we want to call it without authentication. +If you want your service to be accessible with a web request over a rest interface then you have to decorate it with the [rest]() decorator. We have to set the `path` property to define the rest endpoint. If we do not set the `methods` property that means it will accept `GET` requests. (default: `methods: ['get']`) ```js -@rest({ path: '/hello-world', anonymous: true }) +@rest({ path: '/hello-world' }) ``` Define a [description]() for the `HelloWorld`, which will make it easier to find in the AWS Lambda list. ```js @@ -118,7 +118,7 @@ Now we have to create the business logic. ```js import { FunctionalService, rest, description } from 'functionly' -@rest({ path: '/hello-world', anonymous: true }) +@rest({ path: '/hello-world' }) @description('hello world service') export class HelloWorld extends FunctionalService { static async handle() { @@ -135,7 +135,7 @@ In the `handle` method if you use the `@param` property decorator for a paramete ```js import { FunctionalService, rest, description, param } from 'functionly' -@rest({ path: '/hello-world', anonymous: true }) +@rest({ path: '/hello-world' }) @description('hello world service') export class HelloWorld extends FunctionalService { static async handle(@param name = 'world') { @@ -172,10 +172,10 @@ export class GetAllTodos extends TodoService { static async handle() {} } ``` -If you want your service to be accessible with a web request over a rest interface then you have to decorate it with the [rest]() decorator. We need a `path` and have to set the `cors` and the `anonymous` properties to `true` because we want to call it without authentication and from another domain. +If you want your service to be accessible with a web request over a rest interface then you have to decorate it with the [rest]() decorator. We have to set the `path` property to define the rest endpoint. If we do not set the `methods` property that means it will accept `GET` requests. (default: `methods: ['get']`) ```js -@rest({ path: '/getAllTodos', cors: true, anonymous: true }) +@rest({ path: '/getAllTodos' }) ``` Define a [description]() for the `TodoService`, which will make it easier to find in the AWS Lambda list. ```js @@ -185,7 +185,7 @@ Now we have to create the business logic. We want to read the todo items, so we ```js import { rest, description, inject } from 'functionly' -@rest({ path: '/getAllTodos', cors: true, anonymous: true }) +@rest({ path: '/getAllTodos' }) @description('get all Todo service') export class GetAllTodos extends TodoService { static async handle(@inject(TodoTable) db) { @@ -204,7 +204,7 @@ We need a service to create todo items, so let's do this. We will also define a ```js import { rest, description } from 'functionly' -@rest({ path: '/createTodo', methods: ['post'], anonymous: true, cors: true }) +@rest({ path: '/createTodo', methods: ['post'] }) @description('create Todo service') export class CreateTodo extends TodoService { static async handle() {} @@ -214,7 +214,7 @@ We need some values to create a new todo item: `name`, `description` and `status ```js import { rest, description, param } from 'functionly' -@rest({ path: '/createTodo', methods: ['post'], anonymous: true, cors: true }) +@rest({ path: '/createTodo', methods: ['post'] }) @description('create Todo service') export class CreateTodo extends TodoService { static async handle(@param name, @param description, @param staus) {} @@ -225,7 +225,7 @@ The business logic: save a new todo item. [Inject]() the `TodoTable` and save a import { generate } from 'shortid' import { rest, description, param } from 'functionly' -@rest({ path: '/createTodo', methods: ['post'], anonymous: true, cors: true }) +@rest({ path: '/createTodo', methods: ['post'] }) @description('create Todo service') export class CreateTodo extends TodoService { static async handle(@param name, @param description, @param status, @inject(TodoTable) db) { @@ -289,7 +289,7 @@ export class PersistTodo extends Service { ```js import { rest, description, param, inject } from 'functionly' -@rest({ path: '/createTodo', methods: ['post'], anonymous: true, cors: true }) +@rest({ path: '/createTodo', methods: ['post'] }) @description('create Todo service') export class CreateTodo extends TodoService { static async handle( diff --git a/src/annotations/classes/aws/apiGateway.ts b/src/annotations/classes/aws/apiGateway.ts index 394dae0..8b64d1c 100644 --- a/src/annotations/classes/aws/apiGateway.ts +++ b/src/annotations/classes/aws/apiGateway.ts @@ -5,7 +5,7 @@ import { rest, CorsConfig } from '../rest' export const defaultEndpoint = { method: 'get', cors: true, - authorization: 'AWS_IAM' + authorization: 'NONE' } export const apiGateway = (endpoint: { @@ -26,7 +26,7 @@ rest.extension('aws', (target, config) => { method, cors: config.cors, corsConfig: config.corsConfig, - authorization: config.anonymous ? 'NONE' : 'AWS_IAM' + authorization: config.authenticated ? 'AWS_IAM' : 'NONE' }) decorator(target) } diff --git a/src/annotations/classes/azure/httpTrigger.ts b/src/annotations/classes/azure/httpTrigger.ts index 7a800dc..94b902d 100644 --- a/src/annotations/classes/azure/httpTrigger.ts +++ b/src/annotations/classes/azure/httpTrigger.ts @@ -5,7 +5,7 @@ import { rest, CorsConfig } from '../rest' export const defaultEndpoint = { methods: ['get'], cors: true, - authLevel: 'function' + authLevel: 'anonymous' } export const httpTrigger = (endpoint: { @@ -28,7 +28,7 @@ rest.extension('azure', (target, config) => { methods: config.methods, cors: config.cors, corsConfig: config.corsConfig, - authLevel: config.anonymous ? 'anonymous' : 'function' + authLevel: config.authenticated ? 'function' : 'anonymous' }) decorator(target) }) \ No newline at end of file diff --git a/src/annotations/classes/rest.ts b/src/annotations/classes/rest.ts index 514fa71..52c2eb5 100644 --- a/src/annotations/classes/rest.ts +++ b/src/annotations/classes/rest.ts @@ -7,18 +7,18 @@ export interface CorsConfig { credentials?: boolean } -export const rest = expandableDecorator<{ path: string, methods?: string[], cors?: boolean, corsConfig?: CorsConfig, anonymous?: boolean }>({ +export const rest = expandableDecorator<{ path: string, methods?: string[], cors?: boolean, corsConfig?: CorsConfig, authenticated?: boolean }>({ name: 'rest', defaultValues: { methods: ['get'], cors: true, - anonymous: false + authenticated: false } }) export interface IHttpMethod { (path: string): Function - (config: { path: string, cors?: boolean, corsConfig?: CorsConfig, anonymous?: boolean }): Function + (config: { path: string, cors?: boolean, corsConfig?: CorsConfig, authenticated?: boolean }): Function } export const resolveParam = (p: any, defaults) => { diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index 3865ac5..f3a44a4 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -212,7 +212,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.property('method', 'get') expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('authorization', 'AWS_IAM') + expect(metadata).to.have.property('authorization', 'NONE') }) it("method", () => { @apiGateway({ path: '/v1/test', method: 'post' }) @@ -227,7 +227,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.property('method', 'post') expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('authorization', 'AWS_IAM') + expect(metadata).to.have.property('authorization', 'NONE') }) it("cors", () => { @apiGateway({ path: '/v1/test', cors: false }) @@ -242,7 +242,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.property('method', 'get') expect(metadata).to.have.property('cors', false) - expect(metadata).to.have.property('authorization', 'AWS_IAM') + expect(metadata).to.have.property('authorization', 'NONE') }) it("corsConfig", () => { @apiGateway({ path: '/v1/test', corsConfig: { headers: ['X-test'] } }) @@ -258,10 +258,10 @@ describe('annotations', () => { expect(metadata).to.have.property('method', 'get') expect(metadata).to.have.property('cors', true) expect(metadata).to.have.deep.property('corsConfig', { headers: ['X-test'] }) - expect(metadata).to.have.property('authorization', 'AWS_IAM') + expect(metadata).to.have.property('authorization', 'NONE') }) it("authorization", () => { - @apiGateway({ path: '/v1/test', authorization: 'NONE' }) + @apiGateway({ path: '/v1/test', authorization: 'AWS_IAM' }) class ApiGatewayTestClass { } const value = getMetadata(CLASS_APIGATEWAYKEY, ApiGatewayTestClass) @@ -273,7 +273,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.property('method', 'get') expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('authorization', 'NONE') + expect(metadata).to.have.property('authorization', 'AWS_IAM') }) }) describe("httpTrigger", () => { @@ -290,7 +290,7 @@ describe('annotations', () => { expect(metadata).to.have.property('route', '/v1/test') expect(metadata).to.have.deep.property('methods', ['get']) expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('authLevel', 'function') + expect(metadata).to.have.property('authLevel', 'anonymous') }) it("method", () => { @httpTrigger({ route: '/v1/test', methods: ['post'] }) @@ -305,7 +305,7 @@ describe('annotations', () => { expect(metadata).to.have.property('route', '/v1/test') expect(metadata).to.have.deep.property('methods', ['post']) expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('authLevel', 'function') + expect(metadata).to.have.property('authLevel', 'anonymous') }) it("cors", () => { @httpTrigger({ route: '/v1/test', cors: false }) @@ -320,7 +320,7 @@ describe('annotations', () => { expect(metadata).to.have.property('route', '/v1/test') expect(metadata).to.have.deep.property('methods', ['get']) expect(metadata).to.have.property('cors', false) - expect(metadata).to.have.property('authLevel', 'function') + expect(metadata).to.have.property('authLevel', 'anonymous') }) it("corsConfig", () => { @@ -337,10 +337,10 @@ describe('annotations', () => { expect(metadata).to.have.deep.property('methods', ['get']) expect(metadata).to.have.property('cors', true) expect(metadata).to.have.deep.property('corsConfig', { headers: ['X-test'] }) - expect(metadata).to.have.property('authLevel', 'function') + expect(metadata).to.have.property('authLevel', 'anonymous') }) it("authorization", () => { - @httpTrigger({ route: '/v1/test', authLevel: 'anonymous' }) + @httpTrigger({ route: '/v1/test', authLevel: 'function' }) class HttpTriggerTestClass { } const value = getMetadata(CLASS_HTTPTRIGGER, HttpTriggerTestClass) @@ -353,7 +353,7 @@ describe('annotations', () => { expect(metadata).to.have.property('route', '/v1/test') expect(metadata).to.have.deep.property('methods', ['get']) expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('authLevel', 'anonymous') + expect(metadata).to.have.property('authLevel', 'function') }) }) describe("rest", () => { @@ -370,7 +370,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['get']) expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('anonymous', false) + expect(metadata).to.have.property('authenticated', false) }) it("method", () => { @rest({ path: '/v1/test', methods: ['post'] }) @@ -385,7 +385,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['post']) expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('anonymous', false) + expect(metadata).to.have.property('authenticated', false) }) it("cors", () => { @rest({ path: '/v1/test', cors: false }) @@ -400,7 +400,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['get']) expect(metadata).to.have.property('cors', false) - expect(metadata).to.have.property('anonymous', false) + expect(metadata).to.have.property('authenticated', false) }) it("corsConfig", () => { @@ -417,10 +417,10 @@ describe('annotations', () => { expect(metadata).to.have.deep.property('methods', ['get']) expect(metadata).to.have.property('cors', true) expect(metadata).to.have.deep.property('corsConfig', { headers: ['X-test'] }) - expect(metadata).to.have.property('anonymous', false) + expect(metadata).to.have.property('authenticated', false) }) it("authorization", () => { - @rest({ path: '/v1/test', anonymous: true }) + @rest({ path: '/v1/test', authenticated: true }) class ApiGatewayTestClass { } const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) @@ -432,7 +432,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['get']) expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('anonymous', true) + expect(metadata).to.have.property('authenticated', true) }) }) describe("httpGet", () => { @@ -449,10 +449,10 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['get']) expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('anonymous', false) + expect(metadata).to.have.property('authenticated', false) }) it("config", () => { - @httpGet({ path: '/v1/test', anonymous: true }) + @httpGet({ path: '/v1/test', authenticated: true }) class ApiGatewayTestClass { } const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) @@ -464,7 +464,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['get']) expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('anonymous', true) + expect(metadata).to.have.property('authenticated', true) }) }) describe("httpPost", () => { @@ -481,10 +481,10 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['post']) expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('anonymous', false) + expect(metadata).to.have.property('authenticated', false) }) it("config", () => { - @httpPost({ path: '/v1/test', anonymous: true }) + @httpPost({ path: '/v1/test', authenticated: true }) class ApiGatewayTestClass { } const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) @@ -496,7 +496,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['post']) expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('anonymous', true) + expect(metadata).to.have.property('authenticated', true) }) }) describe("httpPut", () => { @@ -513,10 +513,10 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['put']) expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('anonymous', false) + expect(metadata).to.have.property('authenticated', false) }) it("config", () => { - @httpPut({ path: '/v1/test', anonymous: true }) + @httpPut({ path: '/v1/test', authenticated: true }) class ApiGatewayTestClass { } const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) @@ -528,7 +528,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['put']) expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('anonymous', true) + expect(metadata).to.have.property('authenticated', true) }) }) describe("httpPatch", () => { @@ -545,10 +545,10 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['patch']) expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('anonymous', false) + expect(metadata).to.have.property('authenticated', false) }) it("config", () => { - @httpPatch({ path: '/v1/test', anonymous: true }) + @httpPatch({ path: '/v1/test', authenticated: true }) class ApiGatewayTestClass { } const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) @@ -560,7 +560,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['patch']) expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('anonymous', true) + expect(metadata).to.have.property('authenticated', true) }) }) describe("httpDelete", () => { @@ -577,10 +577,10 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['delete']) expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('anonymous', false) + expect(metadata).to.have.property('authenticated', false) }) it("config", () => { - @httpDelete({ path: '/v1/test', anonymous: true }) + @httpDelete({ path: '/v1/test', authenticated: true }) class ApiGatewayTestClass { } const value = getMetadata(rest.environmentKey, ApiGatewayTestClass) @@ -592,7 +592,7 @@ describe('annotations', () => { expect(metadata).to.have.property('path', '/v1/test') expect(metadata).to.have.deep.property('methods', ['delete']) expect(metadata).to.have.property('cors', true) - expect(metadata).to.have.property('anonymous', true) + expect(metadata).to.have.property('authenticated', true) }) }) describe("dynamoTable", () => { diff --git a/test/invoke.tests.ts b/test/invoke.tests.ts index 2a404fb..6e825b8 100644 --- a/test/invoke.tests.ts +++ b/test/invoke.tests.ts @@ -396,7 +396,7 @@ describe('invoke', () => { } addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) - @httpTrigger({ route: '/v1/a1' }) + @httpTrigger({ route: '/v1/a1', authLevel: 'function' }) @injectable() class A extends FunctionalService { public static async handle( @param p1) { } @@ -434,7 +434,7 @@ describe('invoke', () => { } addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) - @httpTrigger({ route: '/v1/a1', methods: ['post'] }) + @httpTrigger({ route: '/v1/a1', methods: ['post'], authLevel: 'function' }) @injectable() class A extends FunctionalService { public static async handle( @param p1) { } @@ -469,7 +469,7 @@ describe('invoke', () => { } addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) - @httpTrigger({ route: '/v1/a1', methods: ['get', 'post'] }) + @httpTrigger({ route: '/v1/a1', methods: ['get', 'post'], authLevel: 'function' }) @injectable() class A extends FunctionalService { public static async handle( @param p1) { } @@ -507,7 +507,7 @@ describe('invoke', () => { } addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) - @httpTrigger({ route: '/v1/a1', methods: ['post', 'get'] }) + @httpTrigger({ route: '/v1/a1', methods: ['post', 'get'], authLevel: 'function' }) @injectable() class A extends FunctionalService { public static async handle( @param p1) { } @@ -616,7 +616,7 @@ describe('invoke', () => { } addProvider(FUNCTIONAL_ENVIRONMENT, new TestProvider()) - @httpTrigger({ route: '/v1/a1' }) + @httpTrigger({ route: '/v1/a1', authLevel: 'function' }) @injectable() class A extends FunctionalService { public static async handle( @param p1) { } From 319aa92d09976ea210363a586f3e551d8820e1be Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Fri, 2 Feb 2018 14:23:41 +0000 Subject: [PATCH 157/196] 0.0.44 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 428747c..6acffaf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.43", + "version": "0.0.44", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 60dc22e..dd01a51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.43", + "version": "0.0.44", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 624d7201d49e2e9fdafa10e9e736d2ecf523d954 Mon Sep 17 00:00:00 2001 From: borzav Date: Mon, 5 Feb 2018 13:43:21 +0100 Subject: [PATCH 158/196] ADD: cloudFormation decorator for DynamoTable class; FIX: cli serverless command --- src/annotations/classes/aws/cloudFormation.ts | 18 +++++++++++++ src/annotations/classes/dynamoTable.ts | 2 +- src/annotations/constants.ts | 1 + src/annotations/index.ts | 1 + src/cli/commands/serverless.ts | 12 +++++---- .../cloudFormation/context/dynamoTable.ts | 25 +++++++++++++------ src/index.ts | 3 ++- test/annotation.tests.ts | 20 +++++++-------- 8 files changed, 58 insertions(+), 24 deletions(-) create mode 100644 src/annotations/classes/aws/cloudFormation.ts diff --git a/src/annotations/classes/aws/cloudFormation.ts b/src/annotations/classes/aws/cloudFormation.ts new file mode 100644 index 0000000..9312d64 --- /dev/null +++ b/src/annotations/classes/aws/cloudFormation.ts @@ -0,0 +1,18 @@ +import { CLASS_CLOUDFORMATION } from '../../constants'; +import { getMetadata, defineMetadata } from '../../metadata'; +import { rest, CorsConfig } from '../rest'; + +export const defaultConfig = { + stack: null, + resourceName: null +}; + +export const cloudFormation = (config?: { stack?: string; resourceName?: string }) => { + return (target: Function) => { + let metadata = getMetadata(CLASS_CLOUDFORMATION, target); + if (!metadata) { + metadata = { ...defaultConfig }; + } + defineMetadata(CLASS_CLOUDFORMATION, { ...metadata, ...config }, target); + }; +}; diff --git a/src/annotations/classes/dynamoTable.ts b/src/annotations/classes/dynamoTable.ts index 4710d8e..9b0c68e 100644 --- a/src/annotations/classes/dynamoTable.ts +++ b/src/annotations/classes/dynamoTable.ts @@ -40,7 +40,7 @@ export const dynamoTable = (tableConfig?: { ...tableConfig, environmentKey: templatedKey, tableName: templatedValue, - definedBy: target.name + definedBy: target }) defineMetadata(CLASS_DYNAMOTABLECONFIGURATIONKEY, [...tableDefinitions], target) diff --git a/src/annotations/constants.ts b/src/annotations/constants.ts index 271ad2f..b31e6b2 100644 --- a/src/annotations/constants.ts +++ b/src/annotations/constants.ts @@ -7,6 +7,7 @@ export const CLASS_GROUP = 'functionly:class:group' export const CLASS_ENVIRONMENTKEY = 'functionly:class:environment' export const CLASS_MIDDLEWAREKEY = 'functionly:class:middleware' export const CLASS_APIGATEWAYKEY = 'functionly:class:apigateway' +export const CLASS_CLOUDFORMATION = 'functionly:class:cloudformation' export const CLASS_CLOUDWATCHEVENT = 'functionly:class:cloudwatchevent' export const CLASS_TAGKEY = 'functionly:class:tag' export const CLASS_LOGKEY = 'functionly:class:log' diff --git a/src/annotations/index.ts b/src/annotations/index.ts index 9cc437b..4671247 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -1,6 +1,7 @@ export { templates, applyTemplates } from './templates' export { injectable, InjectionScope } from './classes/injectable' export { apiGateway } from './classes/aws/apiGateway' +export { cloudFormation } from './classes/aws/cloudFormation' export { cloudWatchEvent } from './classes/aws/cloudWatchEvent' export { httpTrigger } from './classes/azure/httpTrigger' export { rest, httpGet, httpPost, httpPut, httpPatch, httpDelete, IHttpMethod } from './classes/rest' diff --git a/src/cli/commands/serverless.ts b/src/cli/commands/serverless.ts index 807bbef..0be06b2 100644 --- a/src/cli/commands/serverless.ts +++ b/src/cli/commands/serverless.ts @@ -63,7 +63,7 @@ export default (api) => { for (const tableConfig of context.tableConfigs) { const properties = { TableName: tableConfig.tableName, ...tableConfig.nativeConfig } - dynamoStatement.Resource.push("arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/" + properties.TableName) + dynamoStatement.Resource.push("arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/" + properties.TableName + '-' + context.stage) } context.serverless.provider.iamRoleStatements.push(dynamoStatement) @@ -146,28 +146,30 @@ export default (api) => { for (const tableDefinition of context.tableConfigs) { await executor({ - context: { ...context, tableConfig }, + context: { ...context, tableConfig: tableDefinition }, name: 'tableConfig', - method: tableConfig + method: tableConfiguration }) } } - const tableConfig = async ({ tableConfig, serverless }) => { + const tableConfiguration = async ({ stage, tableConfig, serverless }) => { const properties = { ...__dynamoDBDefaults, TableName: tableConfig.tableName, ...tableConfig.nativeConfig } + const resName = properties.TableName + properties.TableName += '-' + stage const tableResource = { "Type": "AWS::DynamoDB::Table", "Properties": properties } - const name = normalizeName(properties.TableName) + const name = normalizeName(resName) serverless.resources.Resources[name] = tableResource } diff --git a/src/cli/providers/cloudFormation/context/dynamoTable.ts b/src/cli/providers/cloudFormation/context/dynamoTable.ts index 19f8f6f..f96b096 100644 --- a/src/cli/providers/cloudFormation/context/dynamoTable.ts +++ b/src/cli/providers/cloudFormation/context/dynamoTable.ts @@ -1,11 +1,14 @@ import { defaultsDeep } from 'lodash' -import { __dynamoDBDefaults } from '../../../../annotations' +import { __dynamoDBDefaults, getMetadata, constants } from '../../../../annotations' import { ExecuteStep, executor } from '../../../context' import { setResource } from '../utils' import { createStack, setStackParameter, getStackName } from './stack' +import { cloudFormation } from '../../../../annotations/classes/aws/cloudFormation'; export const DYNAMODB_TABLE_STACK = 'DynamoDBTableStack' +const { CLASS_CLOUDFORMATION } = constants + export const tableResources = ExecuteStep.register('DynamoDB-Tables', async (context) => { await executor({ context: { ...context, stackName: DYNAMODB_TABLE_STACK }, @@ -52,13 +55,21 @@ export const tableResource = async (context) => { "Properties": properties } - const tableResourceName = `Dynamo${tableConfig.tableName}` - const resourceName = setResource(context, tableResourceName, dynamoDb, DYNAMODB_TABLE_STACK, true) + tableConfig.tableStackName = DYNAMODB_TABLE_STACK + let tableResourceName = `Dynamo${tableConfig.tableName}` + + const cloudFormationConfig = getMetadata(CLASS_CLOUDFORMATION, tableConfig.definedBy) + if (cloudFormationConfig) { + tableConfig.tableStackName = cloudFormationConfig.stack + tableResourceName = cloudFormationConfig.resourceName || tableResourceName + } + + const resourceName = setResource(context, tableResourceName, dynamoDb, tableConfig.tableStackName, true) await setStackParameter({ ...context, resourceName, - sourceStackName: DYNAMODB_TABLE_STACK + sourceStackName: tableConfig.tableStackName }) tableConfig.resourceName = resourceName @@ -80,7 +91,7 @@ export const tableSubscribers = ExecuteStep.register('DynamoDB-Table-Subscriptio export const tableSubscriber = async (context) => { const { tableConfig, subscriber } = context - + if (tableConfig.exists) return const properties = { @@ -105,7 +116,7 @@ export const tableSubscriber = async (context) => { await setStackParameter({ ...context, resourceName: tableConfig.resourceName, - sourceStackName: DYNAMODB_TABLE_STACK, + sourceStackName: tableConfig.tableStackName, targetStackName: getStackName(subscriber.serviceDefinition), attr: 'StreamArn' }) @@ -122,7 +133,7 @@ export const tableSubscriber = async (context) => { export const dynamoStreamingPolicy = async (context) => { const { tableConfig, serviceDefinition } = context - + if (tableConfig.exists) return let policy = serviceDefinition.roleResource.Properties.Policies.find(p => p.PolicyDocument.Statement[0].Action.includes('dynamodb:GetRecords')) diff --git a/src/index.ts b/src/index.ts index 9ec755a..e56fd2d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,7 +17,8 @@ export { addProvider, removeProvider } from './providers' export { injectable, apiGateway, httpTrigger, rest, httpGet, httpPost, httpPut, httpPatch, httpDelete, IHttpMethod, environment, tag, log, functionName, dynamoTable, sns, s3Storage, eventSource, classConfig, use, description, role, group, aws, azure, - param, serviceParams, request, error, result, functionalServiceName, provider, stage, inject, cloudWatchEvent, dynamo + param, serviceParams, request, error, result, functionalServiceName, provider, stage, inject, cloudWatchEvent, dynamo, + cloudFormation } from './annotations' export { mongoCollection, mongoConnection } from './plugins/mongo' diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index f3a44a4..134683a 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -608,7 +608,7 @@ describe('annotations', () => { expect(metadata).to.have.property('tableName', 'DynamoTableTestClass-table') expect(metadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') - expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.property('definedBy', DynamoTableTestClass) expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); }) it("empty", () => { @@ -623,7 +623,7 @@ describe('annotations', () => { expect(metadata).to.have.property('tableName', 'DynamoTableTestClass-table') expect(metadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') - expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.property('definedBy', DynamoTableTestClass) expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); }) it("tableName", () => { @@ -638,7 +638,7 @@ describe('annotations', () => { expect(metadata).to.have.property('tableName', 'mytablename') expect(metadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') - expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.property('definedBy', DynamoTableTestClass) expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); }) it("environmentKey", () => { @@ -653,7 +653,7 @@ describe('annotations', () => { expect(metadata).to.have.property('tableName', 'mytablename') expect(metadata).to.have.property('environmentKey', 'myenvkey') - expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.property('definedBy', DynamoTableTestClass) expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); }) it("nativeConfig", () => { @@ -675,7 +675,7 @@ describe('annotations', () => { expect(metadata).to.have.property('tableName', 'mytablename') expect(metadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') - expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.property('definedBy', DynamoTableTestClass) expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal({ ...__dynamoDBDefaults, ProvisionedThroughput: { @@ -698,7 +698,7 @@ describe('annotations', () => { expect(metadata).to.have.property('tableName', 'DynamoTableTestClass-table') expect(metadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') - expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.property('definedBy', DynamoTableTestClass) expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); }) it("empty", () => { @@ -713,7 +713,7 @@ describe('annotations', () => { expect(metadata).to.have.property('tableName', 'DynamoTableTestClass-table') expect(metadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') - expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.property('definedBy', DynamoTableTestClass) expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); }) it("tableName", () => { @@ -728,7 +728,7 @@ describe('annotations', () => { expect(metadata).to.have.property('tableName', 'mytablename') expect(metadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') - expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.property('definedBy', DynamoTableTestClass) expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); }) it("environmentKey", () => { @@ -743,7 +743,7 @@ describe('annotations', () => { expect(metadata).to.have.property('tableName', 'mytablename') expect(metadata).to.have.property('environmentKey', 'myenvkey') - expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.property('definedBy', DynamoTableTestClass) expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal(__dynamoDBDefaults); }) it("nativeConfig", () => { @@ -765,7 +765,7 @@ describe('annotations', () => { expect(metadata).to.have.property('tableName', 'mytablename') expect(metadata).to.have.property('environmentKey', 'DynamoTableTestClass_TABLE_NAME') - expect(metadata).to.have.property('definedBy', DynamoTableTestClass.name) + expect(metadata).to.have.property('definedBy', DynamoTableTestClass) expect(metadata).to.have.deep.property('nativeConfig').that.deep.equal({ ...__dynamoDBDefaults, ProvisionedThroughput: { From 1f8e1bb700e3b67861ff1171a197a408d61fbd32 Mon Sep 17 00:00:00 2001 From: borzav Date: Mon, 5 Feb 2018 13:46:15 +0100 Subject: [PATCH 159/196] ADD: configurable deployment bucket resource name --- src/cli/providers/cloudFormation/context/resources.ts | 2 +- src/cli/providers/cloudFormation/context/s3Storage.ts | 6 +++--- .../cloudFormation/context/s3StorageDeployment.ts | 10 ++++++++-- src/cli/providers/cloudFormation/index.ts | 5 +++-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 3fad002..3c33033 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -9,7 +9,7 @@ import { createStack, setStackParameter, getStackName } from './stack' import { getBucketReference } from './s3StorageDeployment' export { s3DeploymentBucket, s3DeploymentBucketParameter, s3 } from './s3Storage' -export { S3_DEPLOYMENT_BUCKET_RESOURCE_NAME } from './s3StorageDeployment' +export { getDeploymentBucketResourceName } from './s3StorageDeployment' export { apiGateway } from './apiGateway' export { sns } from './sns' export { cloudWatchEvent } from './cloudWatchEvent' diff --git a/src/cli/providers/cloudFormation/context/s3Storage.ts b/src/cli/providers/cloudFormation/context/s3Storage.ts index d5bf7b8..120ea88 100644 --- a/src/cli/providers/cloudFormation/context/s3Storage.ts +++ b/src/cli/providers/cloudFormation/context/s3Storage.ts @@ -4,7 +4,7 @@ import { ExecuteStep, executor } from '../../../context' import { collectMetadata } from '../../../utilities/collectMetadata' import { setResource } from '../utils' import { createStack, setStackParameter, getStackName } from './stack' -import { S3_DEPLOYMENT_BUCKET_RESOURCE_NAME } from './s3StorageDeployment' +import { getDeploymentBucketResourceName } from './s3StorageDeployment' export const S3_STORAGE_STACK = 'S3Stack' @@ -17,7 +17,7 @@ export const s3DeploymentBucket = ExecuteStep.register('S3-Deployment-Bucket', a "Type": "AWS::S3::Bucket" } - const bucketResourceName = S3_DEPLOYMENT_BUCKET_RESOURCE_NAME + const bucketResourceName = await getDeploymentBucketResourceName() const resourceName = setResource(context, bucketResourceName, s3BucketResource) context.CloudFormationTemplate.Outputs[`${resourceName}Name`] = { @@ -30,7 +30,7 @@ export const s3DeploymentBucket = ExecuteStep.register('S3-Deployment-Bucket', a export const s3DeploymentBucketParameter = ExecuteStep.register('S3-Deployment-Bucket-Parameter', async (context) => { - const resourceName = S3_DEPLOYMENT_BUCKET_RESOURCE_NAME + const resourceName = await getDeploymentBucketResourceName() await setStackParameter({ ...context, resourceName diff --git a/src/cli/providers/cloudFormation/context/s3StorageDeployment.ts b/src/cli/providers/cloudFormation/context/s3StorageDeployment.ts index 51d3032..951ad30 100644 --- a/src/cli/providers/cloudFormation/context/s3StorageDeployment.ts +++ b/src/cli/providers/cloudFormation/context/s3StorageDeployment.ts @@ -1,7 +1,13 @@ -export const S3_DEPLOYMENT_BUCKET_RESOURCE_NAME = 'FunctionlyDeploymentBucket' +import { projectConfig } from '../../../project/config' + +const S3_DEPLOYMENT_BUCKET_RESOURCE_NAME = 'FunctionlyDeploymentBucket' + +export const getDeploymentBucketResourceName = async () => { + return projectConfig.awsBucketResourceName || S3_DEPLOYMENT_BUCKET_RESOURCE_NAME +} export const getBucketReference = async (context) => { return context.__userAWSBucket ? context.awsBucket : { - "Ref": S3_DEPLOYMENT_BUCKET_RESOURCE_NAME + "Ref": await getDeploymentBucketResourceName() } } \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/index.ts b/src/cli/providers/cloudFormation/index.ts index f9e4456..7a33abd 100644 --- a/src/cli/providers/cloudFormation/index.ts +++ b/src/cli/providers/cloudFormation/index.ts @@ -9,7 +9,7 @@ import { executor } from '../../context' import { cloudFormationInit, cloudFormationMerge } from './context/cloudFormationInit' import { tableResources, lambdaResources, roleResources, s3DeploymentBucket, s3DeploymentBucketParameter, - apiGateway, sns, s3, cloudWatchEvent, initStacks, lambdaLogResources, S3_DEPLOYMENT_BUCKET_RESOURCE_NAME, tableSubscribers + apiGateway, sns, s3, cloudWatchEvent, initStacks, lambdaLogResources, getDeploymentBucketResourceName, tableSubscribers } from './context/resources' import { uploadTemplate, persistCreateTemplate } from './context/uploadTemplate' @@ -38,7 +38,8 @@ export const cloudFormation = { } } if (!context.awsBucket) { - const bucketData = await executor({ ...context, LogicalResourceId: S3_DEPLOYMENT_BUCKET_RESOURCE_NAME }, describeStackResouce) + const logicalResourceId = await getDeploymentBucketResourceName() + const bucketData = await executor({ ...context, LogicalResourceId: logicalResourceId }, describeStackResouce) context.awsBucket = bucketData.StackResourceDetail.PhysicalResourceId } From 047bf258feba1ed3c9aec93c4a07f7229e2b0987 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Mon, 5 Feb 2018 12:49:28 +0000 Subject: [PATCH 160/196] 0.0.45 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6acffaf..72cb986 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.44", + "version": "0.0.45", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index dd01a51..318d344 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.44", + "version": "0.0.45", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From dc99023a69f8c10473facfde36f1733b9115d3fe Mon Sep 17 00:00:00 2001 From: borzav Date: Tue, 6 Feb 2018 10:20:45 +0100 Subject: [PATCH 161/196] fix: cloudFormation decorator custom stack name --- src/cli/providers/cloudFormation/context/dynamoTable.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/cli/providers/cloudFormation/context/dynamoTable.ts b/src/cli/providers/cloudFormation/context/dynamoTable.ts index f96b096..351597c 100644 --- a/src/cli/providers/cloudFormation/context/dynamoTable.ts +++ b/src/cli/providers/cloudFormation/context/dynamoTable.ts @@ -62,6 +62,14 @@ export const tableResource = async (context) => { if (cloudFormationConfig) { tableConfig.tableStackName = cloudFormationConfig.stack tableResourceName = cloudFormationConfig.resourceName || tableResourceName + + if (tableConfig.tableStackName) { + await executor({ + context: { ...context, stackName: tableConfig.tableStackName }, + name: `CloudFormation-Stack-init-${tableConfig.tableStackName}`, + method: createStack + }) + } } const resourceName = setResource(context, tableResourceName, dynamoDb, tableConfig.tableStackName, true) From 0545663981b236773d6dfb4d2257b56a58c53256 Mon Sep 17 00:00:00 2001 From: borzav Date: Tue, 6 Feb 2018 13:55:11 +0100 Subject: [PATCH 162/196] add: configurable ApiGatewayRestApi Stack --- .../cloudFormation/context/apiGateway.ts | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/src/cli/providers/cloudFormation/context/apiGateway.ts b/src/cli/providers/cloudFormation/context/apiGateway.ts index 31ae439..095cd9b 100644 --- a/src/cli/providers/cloudFormation/context/apiGateway.ts +++ b/src/cli/providers/cloudFormation/context/apiGateway.ts @@ -2,7 +2,9 @@ import { getMetadata, constants } from '../../../../annotations' const { CLASS_APIGATEWAYKEY } = constants import { ExecuteStep, executor } from '../../../context' import { setResource } from '../utils' -import { setStackParameter, getStackName } from './stack' +import { setStackParameter, getStackName, createStack } from './stack' + +import { projectConfig } from '../../../project/config' export const API_GATEWAY_REST_API = 'ApiGatewayRestApi' @@ -20,17 +22,28 @@ export const gatewayRestApi = ExecuteStep.register('ApiGateway-RestApi', async ( } } - const resourceName = setResource(context, API_GATEWAY_REST_API, RestApi) + context.ApiGatewayStackName = projectConfig.ApiGatewayStackName || null + if (context.ApiGatewayStackName) { + await executor({ + context: { ...context, stackName: context.ApiGatewayStackName }, + name: `CloudFormation-Stack-init-${context.ApiGatewayStackName}`, + method: createStack + }) + } + + const resourceName = setResource(context, API_GATEWAY_REST_API, RestApi, context.ApiGatewayStackName, true) await setStackParameter({ ...context, - resourceName + resourceName, + sourceStackName: context.ApiGatewayStackName }) await setStackParameter({ ...context, resourceName, - attr: 'RootResourceId' + attr: 'RootResourceId', + sourceStackName: context.ApiGatewayStackName }) context.CloudFormationTemplate.Outputs[`ServiceEndpoint`] = { @@ -39,9 +52,14 @@ export const gatewayRestApi = ExecuteStep.register('ApiGateway-RestApi', async ( "", [ "https://", - { - "Ref": resourceName - }, + context.ApiGatewayStackName ? { + "Fn::GetAtt": [ + context.ApiGatewayStackName, + "Outputs." + resourceName + ] + } : { + "Ref": resourceName + }, ".execute-api.", context.awsRegion, ".amazonaws.com/", @@ -258,9 +276,14 @@ export const gatewayDeployment = ExecuteStep.register('ApiGateway-Deployment', a const ApiGatewayDeployment = { "Type": "AWS::ApiGateway::Deployment", "Properties": { - "RestApiId": { - "Ref": API_GATEWAY_REST_API - }, + "RestApiId": context.ApiGatewayStackName ? { + "Fn::GetAtt": [ + context.ApiGatewayStackName, + "Outputs." + API_GATEWAY_REST_API + ] + } : { + "Ref": API_GATEWAY_REST_API + }, "StageName": context.stage }, "DependsOn": [ From 65689f76c8d83e545e5eb31d3812bac7b03ed616 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Tue, 6 Feb 2018 12:57:54 +0000 Subject: [PATCH 163/196] 0.0.46 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 72cb986..cbc6276 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.45", + "version": "0.0.46", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 318d344..6273b6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.45", + "version": "0.0.46", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 126d2526f06a66be461e4145a22ac0f416e2199a Mon Sep 17 00:00:00 2001 From: borzav Date: Thu, 8 Feb 2018 16:48:30 +0100 Subject: [PATCH 164/196] FIX: do not create api gateway without rest endpoint --- .../cloudFormation/context/apiGateway.ts | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/cli/providers/cloudFormation/context/apiGateway.ts b/src/cli/providers/cloudFormation/context/apiGateway.ts index 095cd9b..13d0b4e 100644 --- a/src/cli/providers/cloudFormation/context/apiGateway.ts +++ b/src/cli/providers/cloudFormation/context/apiGateway.ts @@ -9,9 +9,32 @@ import { projectConfig } from '../../../project/config' export const API_GATEWAY_REST_API = 'ApiGatewayRestApi' export const apiGateway = ExecuteStep.register('ApiGateway', async (context) => { - await executor(context, gatewayRestApi) await executor(context, gatewayResources) - await executor(context, gatewayDeployment) +}) + +export const gatewayResources = ExecuteStep.register('ApiGateway-Resources', async (context) => { + const endpointsCors = new Map() + const endpoints = new Map() + for (const serviceDefinition of context.publishedFunctions) { + await executor({ + context: { ...context, serviceDefinition, endpointsCors, endpoints }, + name: `ApiGateway-Methods-${serviceDefinition.service.name}`, + method: apiGatewayMethods + }) + } + + for (const [endpointResourceName, { serviceDefinition, methods, headers, credentials, origin }] of endpointsCors) { + await executor({ + context: { ...context, endpointResourceName, serviceDefinition, methods, headers, credentials, origin }, + name: `ApiGateway-Method-Options-${endpointResourceName}`, + method: setOptionsMethodResource + }) + } + + if (endpoints.size) { + await executor(context, gatewayRestApi) + await executor(context, gatewayDeployment) + } }) export const gatewayRestApi = ExecuteStep.register('ApiGateway-RestApi', async (context) => { @@ -71,26 +94,6 @@ export const gatewayRestApi = ExecuteStep.register('ApiGateway-RestApi', async ( }) -export const gatewayResources = ExecuteStep.register('ApiGateway-Resources', async (context) => { - const endpointsCors = new Map() - const endpoints = new Map() - for (const serviceDefinition of context.publishedFunctions) { - await executor({ - context: { ...context, serviceDefinition, endpointsCors, endpoints }, - name: `ApiGateway-Methods-${serviceDefinition.service.name}`, - method: apiGatewayMethods - }) - } - - for (const [endpointResourceName, { serviceDefinition, methods, headers, credentials, origin }] of endpointsCors) { - await executor({ - context: { ...context, endpointResourceName, serviceDefinition, methods, headers, credentials, origin }, - name: `ApiGateway-Method-Options-${endpointResourceName}`, - method: setOptionsMethodResource - }) - } -}) - export const apiGatewayMethods = async (context) => { const { serviceDefinition } = context @@ -121,7 +124,7 @@ export const apiGatewayMethod = async (context) => { endpoint = endpoints.get(pathFragment) } else { endpoint = await executor({ - context: { ...context, pathFragment, rootPathFragment, endpoints, pathPart }, + context: { ...context, pathFragment, rootPathFragment, pathPart }, name: `ApiGateway-ResourcePath-${pathFragment}`, method: apiGatewayPathPart }) From 05f50bb7ac79a2d672d3ecbff66308db43ad1837 Mon Sep 17 00:00:00 2001 From: borzav Date: Thu, 22 Mar 2018 10:58:34 +0100 Subject: [PATCH 165/196] fix: npmignore; change: logs inline role --- .npmignore | 1 + .../cloudFormation/context/resources.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.npmignore b/.npmignore index 1aa3797..cbb8866 100644 --- a/.npmignore +++ b/.npmignore @@ -41,6 +41,7 @@ test /src main.js main.js.map +tsconfig.json __test/**/* diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 3c33033..32152f9 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -177,21 +177,21 @@ export const logPolicy = async (context) => { "Action": [ "logs:CreateLogStream", ], - "Resource": logGroupNames.map(n => { - return { - "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:" + n + ":*" + "Resource": [ + { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*-" + context.stage + ":*" } - }) + ] }, { "Effect": "Allow", "Action": [ "logs:PutLogEvents" ], - "Resource": logGroupNames.map(n => { - return { - "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:" + n + ":*:*" + "Resource": [ + { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*-" + context.stage + ":*:*" } - }) + ] }] } } From 17fe8342108e7eb6c9ee9aa5b1c7c0e45024446c Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Thu, 22 Mar 2018 10:01:39 +0000 Subject: [PATCH 166/196] 0.0.47 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index cbc6276..fed77be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.46", + "version": "0.0.47", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6273b6e..ba17fca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.46", + "version": "0.0.47", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 1d600d19625dc045dddb1b71aba0c2fa7a1ecde1 Mon Sep 17 00:00:00 2001 From: borzav Date: Fri, 6 Apr 2018 12:44:35 +0200 Subject: [PATCH 167/196] CHANGE: lambda name --- src/cli/context/steppes/setFunctionalEnvironment.ts | 2 ++ src/cli/project/config.ts | 2 +- src/cli/providers/cloudFormation/context/resources.ts | 8 ++++---- src/providers/aws/index.ts | 2 +- test/invoke.tests.ts | 5 ++++- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/cli/context/steppes/setFunctionalEnvironment.ts b/src/cli/context/steppes/setFunctionalEnvironment.ts index 5809be8..193486b 100644 --- a/src/cli/context/steppes/setFunctionalEnvironment.ts +++ b/src/cli/context/steppes/setFunctionalEnvironment.ts @@ -5,8 +5,10 @@ export class SetFunctionalEnvironmentStep extends ExecuteStep { for (let serviceDefinition of context.publishedFunctions) { const setFuncEnvEnvAttrib = environment('FUNCTIONAL_ENVIRONMENT', context.FUNCTIONAL_ENVIRONMENT) const setStageEnvAttrib = environment('FUNCTIONAL_STAGE', context.stage) + const setProjectNameEnvAttrib = environment('FUNCTIONAL_PROJECTNAME', context.projectName) setFuncEnvEnvAttrib(serviceDefinition.service) setStageEnvAttrib(serviceDefinition.service) + setProjectNameEnvAttrib(serviceDefinition.service) } } } diff --git a/src/cli/project/config.ts b/src/cli/project/config.ts index 0bd0ec5..f860e71 100644 --- a/src/cli/project/config.ts +++ b/src/cli/project/config.ts @@ -13,7 +13,7 @@ try { if (packageJson && packageJson.version) { projectConfig.version = packageJson.version } - if (packageJson && packageJson.name) { + if (!projectConfig.name && packageJson && packageJson.name) { projectConfig.name = packageJson.name } } catch (e) { } diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 32152f9..dfd6ddf 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -179,7 +179,7 @@ export const logPolicy = async (context) => { ], "Resource": [ { - "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*-" + context.stage + ":*" + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/" + context.projectName + "-*-" + context.stage + ":*" } ] }, { @@ -189,7 +189,7 @@ export const logPolicy = async (context) => { ], "Resource": [ { - "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*-" + context.stage + ":*:*" + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/" + context.projectName + "-*-" + context.stage + ":*:*" } ] }] @@ -276,7 +276,7 @@ export const lambdaResource = async (context) => { S3Key: context.S3Zip }, Description: serviceDefinition[CLASS_DESCRIPTIONKEY] || getMetadata(CLASS_DESCRIPTIONKEY, serviceDefinition.service), - FunctionName: `${getFunctionName(serviceDefinition.service)}-${context.stage}`, + FunctionName: `${context.projectName}-${getFunctionName(serviceDefinition.service)}-${context.stage}`, Handler: serviceDefinition.handler, MemorySize: serviceDefinition[CLASS_AWSMEMORYSIZEKEY] || getMetadata(CLASS_AWSMEMORYSIZEKEY, serviceDefinition.service), Role: serviceDefinition[CLASS_ROLEKEY] || getMetadata(CLASS_ROLEKEY, serviceDefinition.service), @@ -335,7 +335,7 @@ export const lambdaLogResource = async (context) => { const functionName = getFunctionName(serviceDefinition.service) const properties: any = { - "LogGroupName": `/aws/lambda/${functionName}-${context.stage}` + "LogGroupName": `/aws/lambda/${context.projectName}-${functionName}-${context.stage}` }; const lambdaResource = { diff --git a/src/providers/aws/index.ts b/src/providers/aws/index.ts index 00c0470..3f6cf30 100644 --- a/src/providers/aws/index.ts +++ b/src/providers/aws/index.ts @@ -62,7 +62,7 @@ export class AWSProvider extends Provider { const resolvedFuncName = process.env[`FUNCTIONAL_SERVICE_${funcName.toUpperCase()}`] || funcName const invokeParams = { - FunctionName: resolvedFuncName, + FunctionName: `${process.env.FUNCTIONAL_PROJECTNAME}-${resolvedFuncName}`, Payload: JSON.stringify(params) }; diff --git a/test/invoke.tests.ts b/test/invoke.tests.ts index 6e825b8..51d9bd1 100644 --- a/test/invoke.tests.ts +++ b/test/invoke.tests.ts @@ -9,6 +9,7 @@ import { httpTrigger, inject, injectable, param, rest, createParameterDecorator describe('invoke', () => { const FUNCTIONAL_ENVIRONMENT = 'custom' + const FUNCTIONAL_PROJECTNAME = 'my-test-project' describe('general', () => { beforeEach(() => { @@ -878,9 +879,11 @@ describe('invoke', () => { describe('aws', () => { beforeEach(() => { process.env.FUNCTIONAL_ENVIRONMENT = FUNCTIONAL_ENVIRONMENT + process.env.FUNCTIONAL_PROJECTNAME = FUNCTIONAL_PROJECTNAME }) afterEach(() => { delete process.env.FUNCTIONAL_ENVIRONMENT + delete process.env.FUNCTIONAL_PROJECTNAME removeProvider(FUNCTIONAL_ENVIRONMENT) }) @@ -891,7 +894,7 @@ describe('invoke', () => { public async invokeExec(config: any): Promise { counter++ - expect(config).to.have.property('FunctionName', 'A') + expect(config).to.have.property('FunctionName', `my-test-project-A`) expect(config).to.have.property('Payload', JSON.stringify({ p1: 'p1' })) } } From 703aaeb09920f269a8cff83885d8dbdf3a8365a7 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Fri, 6 Apr 2018 10:47:29 +0000 Subject: [PATCH 168/196] 0.0.48 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index fed77be..68536bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.47", + "version": "0.0.48", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ba17fca..ef2359b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.47", + "version": "0.0.48", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 771f5c48f6c443ce347260a552607fe030062e8e Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Wed, 18 Apr 2018 14:32:18 +0000 Subject: [PATCH 169/196] 0.1.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 68536bb..505ad43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.48", + "version": "0.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ef2359b..9e49fe9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.0.48", + "version": "0.1.0", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From acaf319a271ed23324c77fd3c55ea9f2e14a0cc0 Mon Sep 17 00:00:00 2001 From: Peter Nochta Date: Tue, 24 Apr 2018 14:38:35 +0200 Subject: [PATCH 170/196] add x-www-form body parser --- src/cli/commands/start.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/commands/start.ts b/src/cli/commands/start.ts index 0fd2fd4..75f9d36 100644 --- a/src/cli/commands/start.ts +++ b/src/cli/commands/start.ts @@ -27,7 +27,7 @@ export default ({ createContext, annotations: { getMetadata, constants, getFunct const startLocal = async (context) => { let app = express() app.use(bodyParser.json({ limit: '10mb' })) - + app.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies console.log("") for (let serviceDefinition of context.publishedFunctions) { let httpMetadata = getMetadata(rest.environmentKey, serviceDefinition.service) || [] From b00f761ecf35d7107eb927b12e86c4186886ea41 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Tue, 24 Apr 2018 12:53:25 +0000 Subject: [PATCH 171/196] 0.1.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 505ad43..a9c3940 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 9e49fe9..0740881 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.1.0", + "version": "0.1.1", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From d65f99eaf6e0b495ff6e8a6d298270ac16178cee Mon Sep 17 00:00:00 2001 From: Tibor Otott-Kovacs Date: Fri, 25 May 2018 13:05:20 +0200 Subject: [PATCH 172/196] Exit with status code 1 when an error occurred. --- src/cli/commands/deploy.ts | 1 + src/cli/commands/metadata.ts | 1 + src/cli/commands/package.ts | 1 + src/cli/commands/serverless.ts | 1 + src/cli/commands/start.ts | 1 + 5 files changed, 5 insertions(+) diff --git a/src/cli/commands/deploy.ts b/src/cli/commands/deploy.ts index a049cae..2c8dfcd 100644 --- a/src/cli/commands/deploy.ts +++ b/src/cli/commands/deploy.ts @@ -33,6 +33,7 @@ export default ({ createContext, executor, ExecuteStep, projectConfig, requireVa console.log(`done`) } catch (e) { console.log(`error`, e) + process.exit(1) } }); } diff --git a/src/cli/commands/metadata.ts b/src/cli/commands/metadata.ts index 02f82dc..9cf7928 100644 --- a/src/cli/commands/metadata.ts +++ b/src/cli/commands/metadata.ts @@ -28,6 +28,7 @@ export default ({ createContext, executor, ExecuteStep, annotations: { getMetada console.log(`done`) } catch (e) { console.log(`error`, e) + process.exit(1) } }); } diff --git a/src/cli/commands/package.ts b/src/cli/commands/package.ts index 7925183..9860e9c 100644 --- a/src/cli/commands/package.ts +++ b/src/cli/commands/package.ts @@ -34,6 +34,7 @@ export default ({ createContext, executor, ExecuteStep, projectConfig, requireVa console.log(`done`) } catch (e) { console.log(`error`, e) + process.exit(1) } }); } diff --git a/src/cli/commands/serverless.ts b/src/cli/commands/serverless.ts index 0be06b2..5c38efe 100644 --- a/src/cli/commands/serverless.ts +++ b/src/cli/commands/serverless.ts @@ -221,6 +221,7 @@ export default (api) => { console.log(`done`) } catch (e) { console.log(`error`, e) + process.exit(1) } }); }, diff --git a/src/cli/commands/start.ts b/src/cli/commands/start.ts index 75f9d36..5db3269 100644 --- a/src/cli/commands/start.ts +++ b/src/cli/commands/start.ts @@ -153,6 +153,7 @@ export default ({ createContext, annotations: { getMetadata, constants, getFunct console.log(`Compilation complete.`) } catch (e) { console.log(`error`, e) + process.exit(1) } }); } From 009198b2a55675edc07ac12dd6e7964fd6a89e48 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Fri, 25 May 2018 11:08:29 +0000 Subject: [PATCH 173/196] 0.1.2 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index a9c3940..1bacab6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.1.1", + "version": "0.1.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0740881..0d5fd7b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.1.1", + "version": "0.1.2", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From f2ad73647f150db5aed76683b1b5613836318cc6 Mon Sep 17 00:00:00 2001 From: Norbert Nemeth Date: Fri, 22 Mar 2019 12:29:44 +0100 Subject: [PATCH 174/196] Set default node version to 'nodejs8.10' --- README.md | 2 +- src/annotations/classes/aws/aws.ts | 2 +- src/cli/commands/serverless.ts | 2 +- src/cli/providers/cloudFormation/context/resources.ts | 2 +- src/cli/utilities/aws/lambda.ts | 4 ++-- test/annotation.tests.ts | 8 ++++---- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b60b443..6daf9a2 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ Define a base class for FunctionalService to set basic Lambda settings in the AW ```js import { FunctionalService, aws } from 'functionly' -@aws({ type: 'nodejs6.10', memorySize: 512, timeout: 3 }) +@aws({ type: 'nodejs8.10', memorySize: 512, timeout: 3 }) export class TodoService extends FunctionalService { } ``` diff --git a/src/annotations/classes/aws/aws.ts b/src/annotations/classes/aws/aws.ts index 599239b..fd5457a 100644 --- a/src/annotations/classes/aws/aws.ts +++ b/src/annotations/classes/aws/aws.ts @@ -2,7 +2,7 @@ import { CLASS_AWSMEMORYSIZEKEY, CLASS_AWSTIMEOUTKEY, CLASS_AWSRUNTIMEKEY } from import { defineMetadata } from '../../metadata' export const aws = (config: { - type?: 'nodejs6.10', + type?: 'nodejs6.10'|'nodejs8.10', memorySize?: number, timeout?: number }) => (target: Function) => { diff --git a/src/cli/commands/serverless.ts b/src/cli/commands/serverless.ts index 5c38efe..eca5fe0 100644 --- a/src/cli/commands/serverless.ts +++ b/src/cli/commands/serverless.ts @@ -93,7 +93,7 @@ export default (api) => { const def = serverless.functions[functionName] = { handler: `${nameKey}.${serviceDefinition.exportName}`, - runtime: getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs6.10" + runtime: getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs8.10" } await executor({ context, name: 'funtionEnvironments', method: funtionEnvironments }) diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index dfd6ddf..91e1657 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -280,7 +280,7 @@ export const lambdaResource = async (context) => { Handler: serviceDefinition.handler, MemorySize: serviceDefinition[CLASS_AWSMEMORYSIZEKEY] || getMetadata(CLASS_AWSMEMORYSIZEKEY, serviceDefinition.service), Role: serviceDefinition[CLASS_ROLEKEY] || getMetadata(CLASS_ROLEKEY, serviceDefinition.service), - Runtime: serviceDefinition[CLASS_AWSRUNTIMEKEY] || getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs6.10", + Runtime: serviceDefinition[CLASS_AWSRUNTIMEKEY] || getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs8.10", Timeout: serviceDefinition[CLASS_AWSTIMEOUTKEY] || getMetadata(CLASS_AWSTIMEOUTKEY, serviceDefinition.service), Environment: { Variables: serviceDefinition[CLASS_ENVIRONMENTKEY] || getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) diff --git a/src/cli/utilities/aws/lambda.ts b/src/cli/utilities/aws/lambda.ts index 2cd12a1..2ffba7c 100644 --- a/src/cli/utilities/aws/lambda.ts +++ b/src/cli/utilities/aws/lambda.ts @@ -34,7 +34,7 @@ export const createLambdaFunction = ExecuteStep.register('CreateLambdaFunction', MemorySize: getMetadata(constants.CLASS_AWSMEMORYSIZEKEY, context.serviceDefinition.service), Publish: true, Role: getMetadata(constants.CLASS_ROLEKEY, context.serviceDefinition.service), - Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs6.10", + Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs8.10", Timeout: getMetadata(constants.CLASS_AWSTIMEOUTKEY, context.serviceDefinition.service), Environment: { Variables: getMetadata(constants.CLASS_ENVIRONMENTKEY, context.serviceDefinition.service) @@ -118,7 +118,7 @@ export const updateLambdaFunctionConfiguration = ExecuteStep.register('UpdateLam Handler: context.serviceDefinition.handler, MemorySize: getMetadata(constants.CLASS_AWSMEMORYSIZEKEY, context.serviceDefinition.service), Role: getMetadata(constants.CLASS_ROLEKEY, context.serviceDefinition.service), - Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs6.10", + Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs8.10", Timeout: getMetadata(constants.CLASS_AWSTIMEOUTKEY, context.serviceDefinition.service), VpcConfig: { } diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index 134683a..75b84da 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -1269,14 +1269,14 @@ describe('annotations', () => { }) describe("aws", () => { it("type", () => { - @aws({ type: 'nodejs6.10' }) + @aws({ type: 'nodejs8.10' }) class RuntimeTestClass { } const runtimeValue = getMetadata(CLASS_AWSRUNTIMEKEY, RuntimeTestClass) const memoryValue = getMetadata(CLASS_AWSMEMORYSIZEKEY, RuntimeTestClass) const timeoutValue = getMetadata(CLASS_AWSTIMEOUTKEY, RuntimeTestClass) - expect(runtimeValue).to.equal('nodejs6.10') + expect(runtimeValue).to.equal('nodejs8.10') expect(memoryValue).to.undefined expect(timeoutValue).to.undefined }) @@ -1305,14 +1305,14 @@ describe('annotations', () => { expect(timeoutValue).to.equal(3) }) it("all", () => { - @aws({ type: 'nodejs6.10', memorySize: 100, timeout: 3 }) + @aws({ type: 'nodejs8.10', memorySize: 100, timeout: 3 }) class RuntimeTestClass { } const runtimeValue = getMetadata(CLASS_AWSRUNTIMEKEY, RuntimeTestClass) const memoryValue = getMetadata(CLASS_AWSMEMORYSIZEKEY, RuntimeTestClass) const timeoutValue = getMetadata(CLASS_AWSTIMEOUTKEY, RuntimeTestClass) - expect(runtimeValue).to.equal('nodejs6.10') + expect(runtimeValue).to.equal('nodejs8.10') expect(memoryValue).to.equal(100) expect(timeoutValue).to.equal(3) }) From 9a3db2beb7439848857387d6bcb0d862e7a1499f Mon Sep 17 00:00:00 2001 From: Norbert Nemeth Date: Fri, 22 Mar 2019 12:45:40 +0100 Subject: [PATCH 175/196] Add new dynamodb actions to dynamodb policy --- src/cli/providers/cloudFormation/context/resources.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 91e1657..8b6103e 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -225,7 +225,11 @@ export const dynamoPolicy = async (context) => { "dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", - "dynamodb:DeleteItem" + "dynamodb:DeleteItem", + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:ListStreams" ], "Resource": [ ...usedTableConfigs.map(t => { From 02a92e9605dbb4de2baf9030dd4dd2ab0027093d Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Fri, 22 Mar 2019 12:23:15 +0000 Subject: [PATCH 176/196] 0.2.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1bacab6..48216ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.1.2", + "version": "0.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0d5fd7b..28514dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.1.2", + "version": "0.2.0", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 5e80eb5b02675b4355e2e34373d37fac403adbdf Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Fri, 22 Mar 2019 13:07:13 +0000 Subject: [PATCH 177/196] 0.2.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 48216ee..4d14d40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.2.0", + "version": "0.2.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 28514dd..084bcf6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.2.0", + "version": "0.2.1", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 219aeb28b516d1f9245bfbab9275f6145e064954 Mon Sep 17 00:00:00 2001 From: Norbert Nemeth Date: Mon, 21 Oct 2019 11:43:46 +0200 Subject: [PATCH 178/196] Set default node version to 'nodejs10.x' --- README.md | 2 +- src/annotations/classes/aws/aws.ts | 2 +- src/cli/commands/serverless.ts | 2 +- src/cli/providers/cloudFormation/context/resources.ts | 2 +- src/cli/utilities/aws/lambda.ts | 4 ++-- test/annotation.tests.ts | 8 ++++---- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 6daf9a2..e201843 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ Define a base class for FunctionalService to set basic Lambda settings in the AW ```js import { FunctionalService, aws } from 'functionly' -@aws({ type: 'nodejs8.10', memorySize: 512, timeout: 3 }) +@aws({ type: 'nodejs10.x', memorySize: 512, timeout: 3 }) export class TodoService extends FunctionalService { } ``` diff --git a/src/annotations/classes/aws/aws.ts b/src/annotations/classes/aws/aws.ts index fd5457a..be6f6c1 100644 --- a/src/annotations/classes/aws/aws.ts +++ b/src/annotations/classes/aws/aws.ts @@ -2,7 +2,7 @@ import { CLASS_AWSMEMORYSIZEKEY, CLASS_AWSTIMEOUTKEY, CLASS_AWSRUNTIMEKEY } from import { defineMetadata } from '../../metadata' export const aws = (config: { - type?: 'nodejs6.10'|'nodejs8.10', + type?: 'nodejs8.10'|'nodejs10.x', memorySize?: number, timeout?: number }) => (target: Function) => { diff --git a/src/cli/commands/serverless.ts b/src/cli/commands/serverless.ts index eca5fe0..0356715 100644 --- a/src/cli/commands/serverless.ts +++ b/src/cli/commands/serverless.ts @@ -93,7 +93,7 @@ export default (api) => { const def = serverless.functions[functionName] = { handler: `${nameKey}.${serviceDefinition.exportName}`, - runtime: getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs8.10" + runtime: getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs10.x" } await executor({ context, name: 'funtionEnvironments', method: funtionEnvironments }) diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 8b6103e..79c60a2 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -284,7 +284,7 @@ export const lambdaResource = async (context) => { Handler: serviceDefinition.handler, MemorySize: serviceDefinition[CLASS_AWSMEMORYSIZEKEY] || getMetadata(CLASS_AWSMEMORYSIZEKEY, serviceDefinition.service), Role: serviceDefinition[CLASS_ROLEKEY] || getMetadata(CLASS_ROLEKEY, serviceDefinition.service), - Runtime: serviceDefinition[CLASS_AWSRUNTIMEKEY] || getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs8.10", + Runtime: serviceDefinition[CLASS_AWSRUNTIMEKEY] || getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs10.x", Timeout: serviceDefinition[CLASS_AWSTIMEOUTKEY] || getMetadata(CLASS_AWSTIMEOUTKEY, serviceDefinition.service), Environment: { Variables: serviceDefinition[CLASS_ENVIRONMENTKEY] || getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) diff --git a/src/cli/utilities/aws/lambda.ts b/src/cli/utilities/aws/lambda.ts index 2ffba7c..75bf915 100644 --- a/src/cli/utilities/aws/lambda.ts +++ b/src/cli/utilities/aws/lambda.ts @@ -34,7 +34,7 @@ export const createLambdaFunction = ExecuteStep.register('CreateLambdaFunction', MemorySize: getMetadata(constants.CLASS_AWSMEMORYSIZEKEY, context.serviceDefinition.service), Publish: true, Role: getMetadata(constants.CLASS_ROLEKEY, context.serviceDefinition.service), - Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs8.10", + Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs10.x", Timeout: getMetadata(constants.CLASS_AWSTIMEOUTKEY, context.serviceDefinition.service), Environment: { Variables: getMetadata(constants.CLASS_ENVIRONMENTKEY, context.serviceDefinition.service) @@ -118,7 +118,7 @@ export const updateLambdaFunctionConfiguration = ExecuteStep.register('UpdateLam Handler: context.serviceDefinition.handler, MemorySize: getMetadata(constants.CLASS_AWSMEMORYSIZEKEY, context.serviceDefinition.service), Role: getMetadata(constants.CLASS_ROLEKEY, context.serviceDefinition.service), - Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs8.10", + Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs10.x", Timeout: getMetadata(constants.CLASS_AWSTIMEOUTKEY, context.serviceDefinition.service), VpcConfig: { } diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index 75b84da..786ea40 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -1269,14 +1269,14 @@ describe('annotations', () => { }) describe("aws", () => { it("type", () => { - @aws({ type: 'nodejs8.10' }) + @aws({ type: 'nodejs10.x' }) class RuntimeTestClass { } const runtimeValue = getMetadata(CLASS_AWSRUNTIMEKEY, RuntimeTestClass) const memoryValue = getMetadata(CLASS_AWSMEMORYSIZEKEY, RuntimeTestClass) const timeoutValue = getMetadata(CLASS_AWSTIMEOUTKEY, RuntimeTestClass) - expect(runtimeValue).to.equal('nodejs8.10') + expect(runtimeValue).to.equal('nodejs10.x') expect(memoryValue).to.undefined expect(timeoutValue).to.undefined }) @@ -1305,14 +1305,14 @@ describe('annotations', () => { expect(timeoutValue).to.equal(3) }) it("all", () => { - @aws({ type: 'nodejs8.10', memorySize: 100, timeout: 3 }) + @aws({ type: 'nodejs10.x', memorySize: 100, timeout: 3 }) class RuntimeTestClass { } const runtimeValue = getMetadata(CLASS_AWSRUNTIMEKEY, RuntimeTestClass) const memoryValue = getMetadata(CLASS_AWSMEMORYSIZEKEY, RuntimeTestClass) const timeoutValue = getMetadata(CLASS_AWSTIMEOUTKEY, RuntimeTestClass) - expect(runtimeValue).to.equal('nodejs8.10') + expect(runtimeValue).to.equal('nodejs10.x') expect(memoryValue).to.equal(100) expect(timeoutValue).to.equal(3) }) From f76550fe83ff8f12bfd052c0e0de66e1ccfdc0ef Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Mon, 21 Oct 2019 10:15:15 +0000 Subject: [PATCH 179/196] 0.3.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4d14d40..75ce45a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.2.1", + "version": "0.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 084bcf6..9147331 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.2.1", + "version": "0.3.0", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 906cd54794d6c41fee5f3498e8f8570178780326 Mon Sep 17 00:00:00 2001 From: Norbert Nemeth Date: Thu, 31 Oct 2019 16:19:11 +0100 Subject: [PATCH 180/196] Update depreacted Buffer methods --- src/cli/providers/azureARM/context/functions.ts | 4 ++-- src/cli/providers/azureARM/context/init.ts | 2 +- src/cli/providers/azureARM/index.ts | 2 +- src/cli/providers/cloudFormation/context/uploadTemplate.ts | 2 +- src/cli/utilities/aws/s3Upload.ts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cli/providers/azureARM/context/functions.ts b/src/cli/providers/azureARM/context/functions.ts index b450bae..9d6e449 100644 --- a/src/cli/providers/azureARM/context/functions.ts +++ b/src/cli/providers/azureARM/context/functions.ts @@ -143,11 +143,11 @@ export const persistFunction = (context) => { const basePath = join(context.projectFolder, functionResource.name) for (const file in functionResource.properties.files) { - writeFile(join(basePath, file), new Buffer(functionResource.properties.files[file], 'utf8'), deploymentFolder) + writeFile(join(basePath, file), Buffer.from(functionResource.properties.files[file], 'utf8'), deploymentFolder) } const bindings = JSON.stringify(functionResource.properties.config, null, 4) - writeFile(join(basePath, 'function.json'), new Buffer(bindings, 'utf8'), deploymentFolder) + writeFile(join(basePath, 'function.json'), Buffer.from(bindings, 'utf8'), deploymentFolder) const idx = site.resources.indexOf(functionResource) site.resources.splice(idx, 1) diff --git a/src/cli/providers/azureARM/context/init.ts b/src/cli/providers/azureARM/context/init.ts index 7ce00c4..853e62f 100644 --- a/src/cli/providers/azureARM/context/init.ts +++ b/src/cli/providers/azureARM/context/init.ts @@ -180,5 +180,5 @@ export const addEnvironmentSetting = (name, value, site) => { export const persistHostJson = async (context) => { const { deploymentFolder } = context const host = JSON.stringify(context.ARMHost, null, 4) - writeFile(join(`${context.projectName || 'functionly'}-${context.stage}`, 'host.json'), new Buffer(host, 'utf8'), deploymentFolder) + writeFile(join(`${context.projectName || 'functionly'}-${context.stage}`, 'host.json'), Buffer.from(host, 'utf8'), deploymentFolder) } \ No newline at end of file diff --git a/src/cli/providers/azureARM/index.ts b/src/cli/providers/azureARM/index.ts index 46bad2e..0d719fb 100644 --- a/src/cli/providers/azureARM/index.ts +++ b/src/cli/providers/azureARM/index.ts @@ -56,5 +56,5 @@ export const azure = { export const persistFile = async (context) => { const fileName = projectConfig.name ? `${projectConfig.name}.template.json` : 'azurearm.template.json' const templateData = JSON.stringify(context.ARMTemplate, null, 2); - writeFile(fileName, new Buffer(templateData, 'binary')) + writeFile(fileName, Buffer.from(templateData, 'binary')) } \ No newline at end of file diff --git a/src/cli/providers/cloudFormation/context/uploadTemplate.ts b/src/cli/providers/cloudFormation/context/uploadTemplate.ts index a04cf56..8a8aa4e 100644 --- a/src/cli/providers/cloudFormation/context/uploadTemplate.ts +++ b/src/cli/providers/cloudFormation/context/uploadTemplate.ts @@ -6,7 +6,7 @@ import { projectConfig } from '../../../project/config' export const persistCreateTemplate = ExecuteStep.register('PersistCreateTemplate', async (context) => { const templateData = JSON.stringify(context.CloudFormationTemplate, null, 2); const fileName = projectConfig.name ? `${projectConfig.name}.create.template.json` : 'cloudformation.create.template.json' - writeFile(fileName, new Buffer(templateData, 'binary')) + writeFile(fileName, Buffer.from(templateData, 'binary')) }) export const uploadTemplate = ExecuteStep.register('UploadTemplate', async (context) => { diff --git a/src/cli/utilities/aws/s3Upload.ts b/src/cli/utilities/aws/s3Upload.ts index cb67798..9999870 100644 --- a/src/cli/utilities/aws/s3Upload.ts +++ b/src/cli/utilities/aws/s3Upload.ts @@ -43,7 +43,7 @@ export const uploadToAws = ExecuteStep.register('S3-Upload', async (context) => return new Promise((resolve, reject) => { const version = context.version ? `${context.version}/` : '' const folderPah = context.version ? `${context.version}/${context.date.toISOString()}` : `${context.date.toISOString()}` - const binary = new Buffer(context.upload.data, 'binary') + const binary = Buffer.from(context.upload.data, 'binary') let params = { ...config.S3, Bucket: context.awsBucket, From e88ef35a71265a387eb23341de8d10754fab2c5a Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Thu, 31 Oct 2019 15:39:19 +0000 Subject: [PATCH 181/196] 0.3.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 75ce45a..7e4ed85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.3.0", + "version": "0.3.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 9147331..616d7ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.3.0", + "version": "0.3.1", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From a335a31013c6e845e6e1c2e2e75c9c009945a4b3 Mon Sep 17 00:00:00 2001 From: Imre Kiss Date: Thu, 26 Aug 2021 13:18:13 +0200 Subject: [PATCH 182/196] Update default node version to 'nodejs12.x' --- README.md | 22 +++++++++---------- src/annotations/classes/aws/aws.ts | 4 ++-- src/cli/commands/serverless.ts | 2 +- .../cloudFormation/context/resources.ts | 4 ++-- src/cli/utilities/aws/lambda.ts | 4 ++-- test/annotation.tests.ts | 20 ++++++++--------- 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index e201843..371b0b3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # functionly -The `functionly library` lets you build `serverless` nodejs applications in an innovative, functional, and fun by abstraction way. -Use the JavaScript language and the JSON syntax to describe infrastructure and entities, service dependencies, and to implement service code. Deploy your solution to cloud providers, or run containerized on your onprem servers, or locally during development time using the `functionly CLI`. +The `functionly library` lets you build `serverless` nodejs applications in an innovative, functional, and fun by abstraction way. +Use the JavaScript language and the JSON syntax to describe infrastructure and entities, service dependencies, and to implement service code. Deploy your solution to cloud providers, or run containerized on your onprem servers, or locally during development time using the `functionly CLI`. Defining a rest service which listens on `/hello-world`: ```js @@ -114,7 +114,7 @@ Define a [description]() for the `HelloWorld`, which will make it easier to find ```js @description('hello world service') ``` -Now we have to create the business logic. +Now we have to create the business logic. ```js import { FunctionalService, rest, description } from 'functionly' @@ -151,12 +151,12 @@ Define a base class for FunctionalService to set basic Lambda settings in the AW ```js import { FunctionalService, aws } from 'functionly' -@aws({ type: 'nodejs10.x', memorySize: 512, timeout: 3 }) +@aws({ type: 'nodejs12.x', memorySize: 512, timeout: 3 }) export class TodoService extends FunctionalService { } ``` ### Create a dynamo table -We need a DynamoTable, called `TodoTable` because we want to store todo items. +We need a DynamoTable, called `TodoTable` because we want to store todo items. ```js import { DynamoTable, dynamoTable, injectable } from 'functionly' @@ -181,7 +181,7 @@ Define a [description]() for the `TodoService`, which will make it easier to fin ```js @description('get all Todo service') ``` -Now we have to create the business logic. We want to read the todo items, so we need to inject the `TodoTable`. Get the items from it and return from our service. +Now we have to create the business logic. We want to read the todo items, so we need to inject the `TodoTable`. Get the items from it and return from our service. ```js import { rest, description, inject } from 'functionly' @@ -292,10 +292,10 @@ import { rest, description, param, inject } from 'functionly' @rest({ path: '/createTodo', methods: ['post'] }) @description('create Todo service') export class CreateTodo extends TodoService { - static async handle( - @param name, - @param description, - @param status, + static async handle( + @param name, + @param description, + @param status, @inject(ValidateTodo) validateTodo, @inject(PersistTodo) persistTodo ) { @@ -317,7 +317,7 @@ npm install ``` # Run and Deploy with CLI -The CLI helps you to deploy and run the application. +The CLI helps you to deploy and run the application. 1. CLI install ```sh npm install functionly -g diff --git a/src/annotations/classes/aws/aws.ts b/src/annotations/classes/aws/aws.ts index be6f6c1..847fc11 100644 --- a/src/annotations/classes/aws/aws.ts +++ b/src/annotations/classes/aws/aws.ts @@ -2,7 +2,7 @@ import { CLASS_AWSMEMORYSIZEKEY, CLASS_AWSTIMEOUTKEY, CLASS_AWSRUNTIMEKEY } from import { defineMetadata } from '../../metadata' export const aws = (config: { - type?: 'nodejs8.10'|'nodejs10.x', + type?: 'nodejs8.10'|'nodejs12.x', memorySize?: number, timeout?: number }) => (target: Function) => { @@ -15,4 +15,4 @@ export const aws = (config: { if (typeof config.timeout === 'number') { defineMetadata(CLASS_AWSTIMEOUTKEY, config.timeout, target); } -} \ No newline at end of file +} diff --git a/src/cli/commands/serverless.ts b/src/cli/commands/serverless.ts index 0356715..3cbbfc6 100644 --- a/src/cli/commands/serverless.ts +++ b/src/cli/commands/serverless.ts @@ -93,7 +93,7 @@ export default (api) => { const def = serverless.functions[functionName] = { handler: `${nameKey}.${serviceDefinition.exportName}`, - runtime: getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs10.x" + runtime: getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs12.x" } await executor({ context, name: 'funtionEnvironments', method: funtionEnvironments }) diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 79c60a2..e4d4a49 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -284,7 +284,7 @@ export const lambdaResource = async (context) => { Handler: serviceDefinition.handler, MemorySize: serviceDefinition[CLASS_AWSMEMORYSIZEKEY] || getMetadata(CLASS_AWSMEMORYSIZEKEY, serviceDefinition.service), Role: serviceDefinition[CLASS_ROLEKEY] || getMetadata(CLASS_ROLEKEY, serviceDefinition.service), - Runtime: serviceDefinition[CLASS_AWSRUNTIMEKEY] || getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs10.x", + Runtime: serviceDefinition[CLASS_AWSRUNTIMEKEY] || getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs12.x", Timeout: serviceDefinition[CLASS_AWSTIMEOUTKEY] || getMetadata(CLASS_AWSTIMEOUTKEY, serviceDefinition.service), Environment: { Variables: serviceDefinition[CLASS_ENVIRONMENTKEY] || getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) @@ -361,4 +361,4 @@ const updateEnvironmentVariable = async ({ environments, stage }) => { } } } -} \ No newline at end of file +} diff --git a/src/cli/utilities/aws/lambda.ts b/src/cli/utilities/aws/lambda.ts index 75bf915..ddecb52 100644 --- a/src/cli/utilities/aws/lambda.ts +++ b/src/cli/utilities/aws/lambda.ts @@ -34,7 +34,7 @@ export const createLambdaFunction = ExecuteStep.register('CreateLambdaFunction', MemorySize: getMetadata(constants.CLASS_AWSMEMORYSIZEKEY, context.serviceDefinition.service), Publish: true, Role: getMetadata(constants.CLASS_ROLEKEY, context.serviceDefinition.service), - Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs10.x", + Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs12.x", Timeout: getMetadata(constants.CLASS_AWSTIMEOUTKEY, context.serviceDefinition.service), Environment: { Variables: getMetadata(constants.CLASS_ENVIRONMENTKEY, context.serviceDefinition.service) @@ -118,7 +118,7 @@ export const updateLambdaFunctionConfiguration = ExecuteStep.register('UpdateLam Handler: context.serviceDefinition.handler, MemorySize: getMetadata(constants.CLASS_AWSMEMORYSIZEKEY, context.serviceDefinition.service), Role: getMetadata(constants.CLASS_ROLEKEY, context.serviceDefinition.service), - Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs10.x", + Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs12.x", Timeout: getMetadata(constants.CLASS_AWSTIMEOUTKEY, context.serviceDefinition.service), VpcConfig: { } diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index 786ea40..a59d105 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -1269,14 +1269,14 @@ describe('annotations', () => { }) describe("aws", () => { it("type", () => { - @aws({ type: 'nodejs10.x' }) + @aws({ type: 'nodejs12.x' }) class RuntimeTestClass { } const runtimeValue = getMetadata(CLASS_AWSRUNTIMEKEY, RuntimeTestClass) const memoryValue = getMetadata(CLASS_AWSMEMORYSIZEKEY, RuntimeTestClass) const timeoutValue = getMetadata(CLASS_AWSTIMEOUTKEY, RuntimeTestClass) - expect(runtimeValue).to.equal('nodejs10.x') + expect(runtimeValue).to.equal('nodejs12.x') expect(memoryValue).to.undefined expect(timeoutValue).to.undefined }) @@ -1305,14 +1305,14 @@ describe('annotations', () => { expect(timeoutValue).to.equal(3) }) it("all", () => { - @aws({ type: 'nodejs10.x', memorySize: 100, timeout: 3 }) + @aws({ type: 'nodejs12.x', memorySize: 100, timeout: 3 }) class RuntimeTestClass { } const runtimeValue = getMetadata(CLASS_AWSRUNTIMEKEY, RuntimeTestClass) const memoryValue = getMetadata(CLASS_AWSMEMORYSIZEKEY, RuntimeTestClass) const timeoutValue = getMetadata(CLASS_AWSTIMEOUTKEY, RuntimeTestClass) - expect(runtimeValue).to.equal('nodejs10.x') + expect(runtimeValue).to.equal('nodejs12.x') expect(memoryValue).to.equal(100) expect(timeoutValue).to.equal(3) }) @@ -1530,14 +1530,14 @@ describe('annotations', () => { it("overrided class constructor no inject", () => { @injectable() class ATestClass { } - + class BaseBTestClass { constructor( @inject(ATestClass) p1, @inject(ATestClass) p2) { } } class BTestClass extends BaseBTestClass { constructor() { super(null, null) } } - + const value = getOverridableMetadata(PARAMETER_PARAMKEY, BTestClass, undefined) // pass the base class params if there is not defined inject in the new ctor // expect(value).to.undefined @@ -1997,15 +1997,15 @@ describe('annotations', () => { it("overrided class constructor no param", () => { - + class BaseParamClass { constructor( @param p1, @param p2) { } } - + class ParamClass extends BaseParamClass { constructor() { super(null, null) } } - + const value = getOverridableMetadata(PARAMETER_PARAMKEY, ParamClass, undefined) // pass the base class params if there is not defined inject in the new ctor // expect(value).to.undefined @@ -2339,4 +2339,4 @@ describe('annotations', () => { }) }) }) -}) \ No newline at end of file +}) From 361a63c32be68a0749c8eacf95b6eeb716f95bdf Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Thu, 26 Aug 2021 11:35:50 +0000 Subject: [PATCH 183/196] 0.4.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7e4ed85..1aba4e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.3.1", + "version": "0.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 616d7ae..8a7e726 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.3.1", + "version": "0.4.0", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From ad1bb761b7e487c85a4468853057d99a2973dc45 Mon Sep 17 00:00:00 2001 From: Imre Kiss Date: Mon, 12 Sep 2022 13:02:05 +0200 Subject: [PATCH 184/196] Update default node version --- README.md | 2 +- package-lock.json | 6537 +++++++++++++++-- src/annotations/classes/aws/aws.ts | 2 +- src/cli/commands/serverless.ts | 2 +- .../cloudFormation/context/resources.ts | 2 +- src/cli/utilities/aws/lambda.ts | 4 +- test/annotation.tests.ts | 8 +- 7 files changed, 5844 insertions(+), 713 deletions(-) diff --git a/README.md b/README.md index 371b0b3..864e2a5 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ Define a base class for FunctionalService to set basic Lambda settings in the AW ```js import { FunctionalService, aws } from 'functionly' -@aws({ type: 'nodejs12.x', memorySize: 512, timeout: 3 }) +@aws({ type: 'nodejs16.x', memorySize: 512, timeout: 3 }) export class TodoService extends FunctionalService { } ``` diff --git a/package-lock.json b/package-lock.json index 1aba4e3..311c4c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,5095 @@ { "name": "functionly", "version": "0.4.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "functionly", + "version": "0.4.0", + "license": "ISC", + "dependencies": { + "async": "^2.1.2", + "aws-sdk": "^2.49.0", + "body-parser": "^1.17.1", + "commander": "^2.9.0", + "config": "^1.26.1", + "cors": "^2.8.3", + "decache": "^4.3.0", + "express": "^4.15.2", + "fs-extra": "^3.0.1", + "lodash": "^4.14.1", + "mongodb": "^2.2.31", + "node-zip": "^1.1.1", + "reflect-metadata": "^0.1.10", + "request": "^2.81.0", + "webpack": "^2.5.1", + "winston": "^2.3.0", + "yamljs": "^0.2.10" + }, + "bin": { + "functionly": "lib/src/cli/cli.js" + }, + "devDependencies": { + "@types/async": "^2.0.33", + "@types/chai": "^4.0.1", + "@types/lodash": "^4.14.38", + "@types/mocha": "^2.2.41", + "@types/mongodb": "^2.2.11", + "@types/node": "^6.0.46", + "@types/winston": "0.0.30", + "chai": "^4.0.2", + "istanbul": "^0.4.5", + "mocha": "^3.4.2", + "mocha-junit-reporter": "^1.15.0", + "typescript": "^2.3.0" + } + }, + "node_modules/@types/async": { + "version": "2.0.42", + "resolved": "https://registry.npmjs.org/@types/async/-/async-2.0.42.tgz", + "integrity": "sha512-rmsnoIPcAHn9k0HtBktG0vFQIJlQQvLofo70pWly8itQzqG5c/ILSZqmXLsCdJpT1X5wpxO0F3rb3GB5xCiDAA==", + "dev": true + }, + "node_modules/@types/bson": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/bson/-/bson-1.0.4.tgz", + "integrity": "sha512-/nysVvxwup1WniGHIM31UZXM+6727h4FAa2tZpFSQBooBcl2Bh1N9oQmVVg8QYnjchN/DOGi7UvVN0jpzWL6sw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/chai": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.0.4.tgz", + "integrity": "sha512-cvU0HomQ7/aGDQJZsbtJXqBQ7w4J4TqLB0Z/h8mKrpRjfeZEvTbygkfJEb7fWdmwpIeDeFmIVwAEqS0OYuUv3Q==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.14.74", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.74.tgz", + "integrity": "sha512-BZknw3E/z3JmCLqQVANcR17okqVTPZdlxvcIz0fJiJVLUCbSH1hK3zs9r634PVSmrzAxN+n/fxlVRiYoArdOIQ==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "2.2.43", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.43.tgz", + "integrity": "sha512-xNlAmH+lRJdUMXClMTI9Y0pRqIojdxfm7DHsIxoB2iTzu3fnPmSMEN8SsSx0cdwV36d02PWCWaDUoZPDSln+xw==", + "dev": true + }, + "node_modules/@types/mongodb": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-2.2.11.tgz", + "integrity": "sha512-kksYkn34+ENpZqUvGYq0IGOOa4SOhFnMfqCepF789CP+QBgeWok9osTCiAStbc8n4Cv3QSXGmpB5T3PGzL50bQ==", + "dev": true, + "dependencies": { + "@types/bson": "*", + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "6.0.88", + "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.88.tgz", + "integrity": "sha512-bYDPZTX0/s1aihdjLuAgogUAT5M+TpoWChEMea2p0yOcfn5bu3k6cJb9cp6nw268XeSNIGGr+4+/8V5K6BGzLQ==", + "dev": true + }, + "node_modules/@types/winston": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/winston/-/winston-0.0.30.tgz", + "integrity": "sha1-eyo5hwv/iWPbWHY/HlNnV6mssTQ=", + "deprecated": "This is a stub types definition. winston provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", + "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", + "dependencies": { + "mime-types": "~2.1.16", + "negotiator": "0.6.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.2.tgz", + "integrity": "sha512-o96FZLJBPY1lvTuJylGA9Bk3t/GKPPJG8H0ydQQl01crzwJgspa4AEIq/pVTXigmK0PHVQhiAtn8WMBLL9D2WA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-dynamic-import": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", + "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", + "deprecated": "This is probably built in to whatever tool you're using. If you still need it... idk", + "dependencies": { + "acorn": "^4.0.3" + } + }, + "node_modules/acorn-dynamic-import/node_modules/acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dependencies": { + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" + } + }, + "node_modules/ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "peerDependencies": { + "ajv": ">=4.10.0" + } + }, + "node_modules/align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dependencies": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true, + "engines": { + "node": ">=0.4.2" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dependencies": { + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" + } + }, + "node_modules/argparse": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dependencies": { + "arr-flatten": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "node_modules/array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "node_modules/asn1.js": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz", + "integrity": "sha1-SLokC0WpKA6UdImQull9IWYX/UA=", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dependencies": { + "util": "0.10.3" + } + }, + "node_modules/assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assertion-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", + "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/async": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", + "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==", + "dependencies": { + "lodash": "^4.14.0" + } + }, + "node_modules/async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/aws-sdk": { + "version": "2.112.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.112.0.tgz", + "integrity": "sha1-mgSMDdWnQHoh9uhiNx0gSanPs1A=", + "dependencies": { + "buffer": "4.9.1", + "crypto-browserify": "1.0.9", + "events": "^1.1.1", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.0.1", + "xml2js": "0.4.17", + "xmlbuilder": "4.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "node_modules/base64-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", + "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==" + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/big.js": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz", + "integrity": "sha1-TK2iGTZS6zyp7I5VyQFWacmAaXg=", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.10.0.tgz", + "integrity": "sha1-muuabF6IY4qtFx4Wf1kAq+JINdA=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "node_modules/body-parser": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.0.tgz", + "integrity": "sha1-07Ik1Gf6LOjUNYnAJFBDJnwJNjQ=", + "dependencies": { + "bytes": "3.0.0", + "content-type": "~1.0.2", + "debug": "2.6.8", + "depd": "~1.1.1", + "http-errors": "~1.6.2", + "iconv-lite": "0.4.18", + "on-finished": "~2.3.0", + "qs": "6.5.0", + "raw-body": "2.3.1", + "type-is": "~1.6.15" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "dependencies": { + "hoek": "2.x.x" + }, + "engines": { + "node": ">=0.10.40" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dependencies": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "node_modules/browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "node_modules/browserify-aes": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.0.8.tgz", + "integrity": "sha512-WYCMOT/PtGTlpOKFht0YJFYcPy6pLCR98CtWfzK13zoynLlBMvAdEMSRGmgnJCw2M2j/5qxBkinZQFobieM8dQ==", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz", + "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz", + "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dependencies": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dependencies": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "node_modules/browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "dependencies": { + "pako": "~0.2.0" + } + }, + "node_modules/bson": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.0.4.tgz", + "integrity": "sha1-k8ENOeqltYQVy8QFLz5T5WKwtyw=", + "deprecated": "Fixed a critical issue with BSON serialization documented in CVE-2019-2391, see https://bit.ly/2KcpXdo for more details", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "deprecated": "This version of 'buffer' is out-of-date. You must update to v4.9.2 or newer", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/buffer-shims": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=" + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + }, + "node_modules/builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "engines": { + "node": "*" + } + }, + "node_modules/camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "node_modules/center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dependencies": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chai": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", + "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", + "dev": true, + "dependencies": { + "assertion-error": "^1.0.1", + "check-error": "^1.0.1", + "deep-eql": "^3.0.0", + "get-func-name": "^2.0.0", + "pathval": "^1.0.0", + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.", + "dependencies": { + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" + }, + "optionalDependencies": { + "fsevents": "^1.0.0" + } + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dependencies": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/config": { + "version": "1.26.2", + "resolved": "https://registry.npmjs.org/config/-/config-1.26.2.tgz", + "integrity": "sha1-JGYpEWjYr64Kroq5nqTUJy9SDK4=", + "dependencies": { + "json5": "0.4.0", + "os-homedir": "1.0.2" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dependencies": { + "date-now": "^0.1.4" + } + }, + "node_modules/constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.3.tgz", + "integrity": "sha1-2hjvL7ZMpqzJBcxyAX0/OBhbkdE=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "node_modules/cors": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", + "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/create-ecdh": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", + "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "node_modules/create-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", + "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "dependencies": { + "boom": "2.x.x" + }, + "engines": { + "node": ">=0.10.40" + } + }, + "node_modules/crypto-browserify": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-1.0.9.tgz", + "integrity": "sha1-zFRJaF37hesRyYKKzHy4erW7/MA=", + "engines": { + "node": "*" + } + }, + "node_modules/cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dashdash/node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=" + }, + "node_modules/debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decache": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/decache/-/decache-4.3.0.tgz", + "integrity": "sha1-o5XkBwlWmKyKbe8B8qaky3Y49jU=", + "dependencies": { + "callsite": "^1.0.0" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "node_modules/diff": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", + "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz", + "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/domain-browser": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", + "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", + "engines": { + "node": ">=0.4", + "npm": ">=1.2" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "dependencies": { + "jsbn": "~0.1.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "node_modules/elliptic": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", + "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", + "dependencies": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "node_modules/emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/encodeurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", + "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=", + "dependencies": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "object-assign": "^4.0.1", + "tapable": "^0.2.7" + }, + "engines": { + "node": ">=4.3.0 <5.0.0 || >=5.10" + } + }, + "node_modules/errno": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz", + "integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=", + "dependencies": { + "prr": "~0.0.0" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es6-promise": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", + "integrity": "sha1-7FYjOGgDKQkgcXDDlEjiREndH8Q=" + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "dependencies": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=0.12.0" + }, + "optionalDependencies": { + "source-map": "~0.2.0" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", + "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dependencies": { + "is-posix-bracket": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dependencies": { + "fill-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/express": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.15.4.tgz", + "integrity": "sha1-Ay4iU0ic+PzgJma+yj0R7XotrtE=", + "dependencies": { + "accepts": "~1.3.3", + "array-flatten": "1.1.1", + "content-disposition": "0.5.2", + "content-type": "~1.0.2", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.8", + "depd": "~1.1.1", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "etag": "~1.8.0", + "finalhandler": "~1.0.4", + "fresh": "0.5.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.1", + "path-to-regexp": "0.1.7", + "proxy-addr": "~1.1.5", + "qs": "6.5.0", + "range-parser": "~1.2.0", + "send": "0.15.4", + "serve-static": "1.12.4", + "setprototypeof": "1.0.3", + "statuses": "~1.3.1", + "type-is": "~1.6.15", + "utils-merge": "1.0.0", + "vary": "~1.1.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "node_modules/extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", + "engines": { + "node": "> 0.1.90" + } + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "dependencies": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^1.1.3", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.4.tgz", + "integrity": "sha512-16l/r8RgzlXKmFOhZpHBztvye+lAhC5SU7hXavnerC9UfZqZxxXl3BzL8MhffPT3kF61lj9Oav2LKEzh0ei7tg==", + "dependencies": { + "debug": "2.6.8", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.1", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dependencies": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/forwarded": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.1.tgz", + "integrity": "sha1-ik4wxkCwU5U5mjVJxzAldygEiWE=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", + "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", + "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^3.0.0", + "universalify": "^0.1.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "node_modules/fsevents": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", + "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", + "bundleDependencies": [ + "node-pre-gyp" + ], + "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "nan": "^2.3.0", + "node-pre-gyp": "^0.6.39" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fsevents/node_modules/abbrev": { + "version": "1.1.0", + "inBundle": true, + "license": "ISC", + "optional": true + }, + "node_modules/fsevents/node_modules/ajv": { + "version": "4.11.8", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" + } + }, + "node_modules/fsevents/node_modules/ansi-regex": { + "version": "2.1.1", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/aproba": { + "version": "1.1.1", + "inBundle": true, + "license": "ISC", + "optional": true + }, + "node_modules/fsevents/node_modules/are-we-there-yet": { + "version": "1.1.4", + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "node_modules/fsevents/node_modules/asn1": { + "version": "0.2.3", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/assert-plus": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/fsevents/node_modules/asynckit": { + "version": "0.4.0", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/aws-sign2": { + "version": "0.6.0", + "inBundle": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/fsevents/node_modules/aws4": { + "version": "1.6.0", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/balanced-match": { + "version": "0.4.2", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/bcrypt-pbkdf": { + "version": "1.0.1", + "inBundle": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/fsevents/node_modules/block-stream": { + "version": "0.0.9", + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "inherits": "~2.0.0" + }, + "engines": { + "node": "0.4 || >=0.5.8" + } + }, + "node_modules/fsevents/node_modules/boom": { + "version": "2.10.1", + "inBundle": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "hoek": "2.x.x" + }, + "engines": { + "node": ">=0.10.40" + } + }, + "node_modules/fsevents/node_modules/brace-expansion": { + "version": "1.1.7", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^0.4.1", + "concat-map": "0.0.1" + } + }, + "node_modules/fsevents/node_modules/buffer-shims": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/caseless": { + "version": "0.12.0", + "inBundle": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/fsevents/node_modules/co": { + "version": "4.6.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/fsevents/node_modules/code-point-at": { + "version": "1.1.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/combined-stream": { + "version": "1.0.5", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents/node_modules/concat-map": { + "version": "0.0.1", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/console-control-strings": { + "version": "1.1.0", + "inBundle": true, + "license": "ISC", + "optional": true + }, + "node_modules/fsevents/node_modules/core-util-is": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/cryptiles": { + "version": "2.0.5", + "inBundle": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boom": "2.x.x" + }, + "engines": { + "node": ">=0.10.40" + } + }, + "node_modules/fsevents/node_modules/dashdash": { + "version": "1.14.1", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/fsevents/node_modules/dashdash/node_modules/assert-plus": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/fsevents/node_modules/debug": { + "version": "2.6.8", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/fsevents/node_modules/deep-extend": { + "version": "0.4.2", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.12.0" + } + }, + "node_modules/fsevents/node_modules/delayed-stream": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/fsevents/node_modules/delegates": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/detect-libc": { + "version": "1.0.2", + "inBundle": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/fsevents/node_modules/ecc-jsbn": { + "version": "0.1.1", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "jsbn": "~0.1.0" + } + }, + "node_modules/fsevents/node_modules/extend": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/extsprintf": { + "version": "1.0.2", + "engines": [ + "node >=0.6.0" + ], + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/forever-agent": { + "version": "0.6.1", + "inBundle": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/fsevents/node_modules/form-data": { + "version": "2.1.4", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/fsevents/node_modules/fs.realpath": { + "version": "1.0.0", + "inBundle": true, + "license": "ISC", + "optional": true + }, + "node_modules/fsevents/node_modules/fstream": { + "version": "1.0.11", + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/fsevents/node_modules/fstream-ignore": { + "version": "1.0.5", + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "fstream": "^1.0.0", + "inherits": "2", + "minimatch": "^3.0.0" + } + }, + "node_modules/fsevents/node_modules/gauge": { + "version": "2.7.4", + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "node_modules/fsevents/node_modules/getpass": { + "version": "0.1.7", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/fsevents/node_modules/getpass/node_modules/assert-plus": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/fsevents/node_modules/glob": { + "version": "7.1.2", + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/fsevents/node_modules/graceful-fs": { + "version": "4.1.11", + "inBundle": true, + "license": "ISC", + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/fsevents/node_modules/har-schema": { + "version": "1.0.5", + "inBundle": true, + "license": "ISC", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/fsevents/node_modules/har-validator": { + "version": "4.2.1", + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "ajv": "^4.9.1", + "har-schema": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fsevents/node_modules/has-unicode": { + "version": "2.0.1", + "inBundle": true, + "license": "ISC", + "optional": true + }, + "node_modules/fsevents/node_modules/hawk": { + "version": "3.1.3", + "inBundle": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boom": "2.x.x", + "cryptiles": "2.x.x", + "hoek": "2.x.x", + "sntp": "1.x.x" + }, + "engines": { + "node": ">=0.10.32" + } + }, + "node_modules/fsevents/node_modules/hoek": { + "version": "2.16.3", + "inBundle": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.40" + } + }, + "node_modules/fsevents/node_modules/http-signature": { + "version": "1.1.1", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^0.2.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/fsevents/node_modules/inflight": { + "version": "1.0.6", + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/fsevents/node_modules/inherits": { + "version": "2.0.3", + "inBundle": true, + "license": "ISC", + "optional": true + }, + "node_modules/fsevents/node_modules/ini": { + "version": "1.3.4", + "inBundle": true, + "license": "ISC", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/fsevents/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/is-typedarray": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/isarray": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/isstream": { + "version": "0.1.2", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/jodid25519": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "jsbn": "~0.1.0" + } + }, + "node_modules/fsevents/node_modules/jsbn": { + "version": "0.1.1", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/json-schema": { + "version": "0.2.3", + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/json-stable-stringify": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "jsonify": "~0.0.0" + } + }, + "node_modules/fsevents/node_modules/json-stringify-safe": { + "version": "5.0.1", + "inBundle": true, + "license": "ISC", + "optional": true + }, + "node_modules/fsevents/node_modules/jsonify": { + "version": "0.0.0", + "inBundle": true, + "license": "Public Domain", + "optional": true + }, + "node_modules/fsevents/node_modules/jsprim": { + "version": "1.4.0", + "engines": [ + "node >=0.6.0" + ], + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.0.2", + "json-schema": "0.2.3", + "verror": "1.3.6" + } + }, + "node_modules/fsevents/node_modules/jsprim/node_modules/assert-plus": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/fsevents/node_modules/mime-db": { + "version": "1.27.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents/node_modules/mime-types": { + "version": "2.1.15", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "mime-db": "~1.27.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents/node_modules/minimatch": { + "version": "3.0.4", + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/fsevents/node_modules/minimist": { + "version": "0.0.8", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/mkdirp": { + "version": "0.5.1", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/fsevents/node_modules/ms": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/node-pre-gyp": { + "version": "0.6.39", + "inBundle": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.2", + "hawk": "3.1.3", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "request": "2.81.0", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^2.2.1", + "tar-pack": "^3.4.0" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/fsevents/node_modules/nopt": { + "version": "4.0.1", + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/fsevents/node_modules/npmlog": { + "version": "4.1.0", + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "node_modules/fsevents/node_modules/number-is-nan": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/oauth-sign": { + "version": "0.8.2", + "inBundle": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/fsevents/node_modules/object-assign": { + "version": "4.1.1", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/once": { + "version": "1.4.0", + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/fsevents/node_modules/os-homedir": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/os-tmpdir": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/osenv": { + "version": "0.1.4", + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "node_modules/fsevents/node_modules/path-is-absolute": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/performance-now": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/process-nextick-args": { + "version": "1.0.7", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/punycode": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/qs": { + "version": "6.4.0", + "inBundle": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/fsevents/node_modules/rc": { + "version": "1.2.1", + "inBundle": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "dependencies": { + "deep-extend": "~0.4.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "index.js" + } + }, + "node_modules/fsevents/node_modules/rc/node_modules/minimist": { + "version": "1.2.0", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/readable-stream": { + "version": "2.2.9", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer-shims": "~1.0.0", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~1.0.0", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/fsevents/node_modules/request": { + "version": "2.81.0", + "inBundle": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "aws-sign2": "~0.6.0", + "aws4": "^1.2.1", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.0", + "forever-agent": "~0.6.1", + "form-data": "~2.1.1", + "har-validator": "~4.2.1", + "hawk": "~3.1.3", + "http-signature": "~1.1.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.7", + "oauth-sign": "~0.8.1", + "performance-now": "^0.2.0", + "qs": "~6.4.0", + "safe-buffer": "^5.0.1", + "stringstream": "~0.0.4", + "tough-cookie": "~2.3.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.0.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/fsevents/node_modules/rimraf": { + "version": "2.6.1", + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.0.5" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/fsevents/node_modules/safe-buffer": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/semver": { + "version": "5.3.0", + "inBundle": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/fsevents/node_modules/set-blocking": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "optional": true + }, + "node_modules/fsevents/node_modules/signal-exit": { + "version": "3.0.2", + "inBundle": true, + "license": "ISC", + "optional": true + }, + "node_modules/fsevents/node_modules/sntp": { + "version": "1.0.9", + "inBundle": true, + "optional": true, + "dependencies": { + "hoek": "2.x.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fsevents/node_modules/sshpk": { + "version": "1.13.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "dashdash": "^1.12.0", + "getpass": "^0.1.1" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + }, + "optionalDependencies": { + "bcrypt-pbkdf": "^1.0.0", + "ecc-jsbn": "~0.1.1", + "jodid25519": "^1.0.0", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" + } + }, + "node_modules/fsevents/node_modules/sshpk/node_modules/assert-plus": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/fsevents/node_modules/string_decoder": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/fsevents/node_modules/string-width": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/stringstream": { + "version": "0.0.5", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/strip-ansi": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/strip-json-comments": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/tar": { + "version": "2.2.1", + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + }, + "node_modules/fsevents/node_modules/tar-pack": { + "version": "3.4.0", + "inBundle": true, + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "debug": "^2.2.0", + "fstream": "^1.0.10", + "fstream-ignore": "^1.0.5", + "once": "^1.3.3", + "readable-stream": "^2.1.4", + "rimraf": "^2.5.1", + "tar": "^2.2.1", + "uid-number": "^0.0.6" + } + }, + "node_modules/fsevents/node_modules/tough-cookie": { + "version": "2.3.2", + "inBundle": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "punycode": "^1.4.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/fsevents/node_modules/tunnel-agent": { + "version": "0.6.0", + "inBundle": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/fsevents/node_modules/tweetnacl": { + "version": "0.14.5", + "inBundle": true, + "license": "Unlicense", + "optional": true + }, + "node_modules/fsevents/node_modules/uid-number": { + "version": "0.0.6", + "inBundle": true, + "license": "ISC", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/fsevents/node_modules/util-deprecate": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/fsevents/node_modules/uuid": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "optional": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/fsevents/node_modules/verror": { + "version": "1.3.6", + "engines": [ + "node >=0.6.0" + ], + "inBundle": true, + "optional": true, + "dependencies": { + "extsprintf": "1.0.2" + } + }, + "node_modules/fsevents/node_modules/wide-align": { + "version": "1.1.2", + "inBundle": true, + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2" + } + }, + "node_modules/fsevents/node_modules/wrappy": { + "version": "1.0.2", + "inBundle": true, + "license": "ISC", + "optional": true + }, + "node_modules/get-caller-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=" + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/getpass/node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dependencies": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dependencies": { + "is-glob": "^2.0.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "node_modules/growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "node_modules/handlebars": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.10.tgz", + "integrity": "sha1-PTDHGLCaPZbyPqTMH0A8TTup/08=", + "dev": true, + "dependencies": { + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^2.6" + } + }, + "node_modules/handlebars/node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/har-schema": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^4.9.1", + "har-schema": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hash-base": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", + "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", + "dependencies": { + "inherits": "^2.0.1" + } + }, + "node_modules/hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "deprecated": "This module moved to @hapi/hawk. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues.", + "dependencies": { + "boom": "2.x.x", + "cryptiles": "2.x.x", + "hoek": "2.x.x", + "sntp": "1.x.x" + }, + "engines": { + "node": ">=0.10.32" + } + }, + "node_modules/he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "engines": { + "node": ">=0.10.40" + } + }, + "node_modules/hosted-git-info": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", + "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==" + }, + "node_modules/http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "dependencies": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": ">= 1.3.1 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dependencies": { + "assert-plus": "^0.2.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/https-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz", + "integrity": "sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI=" + }, + "node_modules/iconv-lite": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", + "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" + }, + "node_modules/indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "node_modules/interpret": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz", + "integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=" + }, + "node_modules/invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ipaddr.js": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.4.0.tgz", + "integrity": "sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA=", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", + "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", + "deprecated": "This version of 'is-buffer' is out-of-date. You must update to v1.1.6 or newer" + }, + "node_modules/is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dependencies": { + "builtin-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dependencies": { + "is-primitive": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "node_modules/istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "deprecated": "This module is no longer maintained, try this instead:\n npm i nyc\nVisit https://istanbul.js.org/integrations for other alternatives.", + "dev": true, + "dependencies": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "istanbul": "lib/cli.js" + } + }, + "node_modules/istanbul/node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "node_modules/istanbul/node_modules/glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/istanbul/node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "node_modules/jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/js-yaml": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", + "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js-yaml/node_modules/esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "node_modules/json-loader": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", + "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==" + }, + "node_modules/json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "node_modules/json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dependencies": { + "jsonify": "~0.0.0" + } + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "node_modules/json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "deprecated": "Please use the native JSON object instead of JSON 3", + "dev": true + }, + "node_modules/json5": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz", + "integrity": "sha1-BUNS5MTIDIbAkjh31EneF2pzLI0=", + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", + "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "engines": { + "node": "*" + } + }, + "node_modules/jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/jsprim/node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/jszip": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-2.5.0.tgz", + "integrity": "sha1-dET9hVHd8+XacZj+oMkbyDCMwnQ=", + "dependencies": { + "pako": "~0.2.5" + } + }, + "node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dependencies": { + "invert-kv": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loader-runner": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", + "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=", + "engines": { + "node": ">=4.3.0 <5.0.0 || >=5.10" + } + }, + "node_modules/loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dependencies": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" + } + }, + "node_modules/loader-utils/node_modules/json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "node_modules/lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "dependencies": { + "lodash._basecopy": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "node_modules/lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "node_modules/lodash._basecreate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "dev": true + }, + "node_modules/lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "node_modules/lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "node_modules/lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true, + "dependencies": { + "lodash._baseassign": "^3.0.0", + "lodash._basecreate": "^3.0.0", + "lodash._isiterateecall": "^3.0.0" + } + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "node_modules/lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "node_modules/lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "dependencies": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "node_modules/longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/md5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", + "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", + "dev": true, + "dependencies": { + "charenc": "~0.0.1", + "crypt": "~0.0.1", + "is-buffer": "~1.1.1" + } + }, + "node_modules/md5.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", + "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/md5.js/node_modules/hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dependencies": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dependencies": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.0.tgz", + "integrity": "sha1-SmL7HUKTPAVYOYL0xxb2+55sbT0=", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", + "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=", + "bin": { + "mime": "cli.js" + } + }, + "node_modules/mime-db": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "dependencies": { + "mime-db": "~1.30.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", + "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.2.tgz", + "integrity": "sha512-iH5zl7afRZl1GvD0pnrRlazgc9Z/o34pXWmTFi8xNIMFKXgNL1SoBTDDb9sJfbV/sJV/j8X+0gvwY1QS1He7Nw==", + "dev": true, + "dependencies": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.6.8", + "diff": "3.2.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.1", + "growl": "1.9.2", + "he": "1.1.1", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 0.10.x", + "npm": ">= 1.4.x" + } + }, + "node_modules/mocha-junit-reporter": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-1.15.0.tgz", + "integrity": "sha1-MJ9LeiD82ibQrWnJt9CAjXcjAsI=", + "dev": true, + "dependencies": { + "debug": "^2.2.0", + "md5": "^2.1.0", + "mkdirp": "~0.5.1", + "xml": "^1.0.0" + }, + "peerDependencies": { + "mocha": "^4 || ^3 || ^2.2.5" + } + }, + "node_modules/mocha/node_modules/commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "dependencies": { + "graceful-readlink": ">= 1.0.0" + }, + "engines": { + "node": ">= 0.6.x" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true, + "dependencies": { + "has-flag": "^1.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/mongodb": { + "version": "2.2.31", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.31.tgz", + "integrity": "sha1-GUBEXGYeGSF7s7+CRdmFSq71SNs=", + "dependencies": { + "es6-promise": "3.2.1", + "mongodb-core": "2.1.15", + "readable-stream": "2.2.7" + }, + "engines": { + "node": ">=0.10.3" + } + }, + "node_modules/mongodb-core": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.15.tgz", + "integrity": "sha1-hB9TuH//9MdFgYnDXIroJ+EWl2Q=", + "dependencies": { + "bson": "~1.0.4", + "require_optional": "~1.0.0" + } + }, + "node_modules/mongodb/node_modules/readable-stream": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz", + "integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE=", + "dependencies": { + "buffer-shims": "~1.0.0", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~1.0.0", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/nan": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", + "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=", + "optional": true + }, + "node_modules/negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-libs-browser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.0.0.tgz", + "integrity": "sha1-o6WeyXAkmFtG6Vg3lkb5bEthZkY=", + "dependencies": { + "assert": "^1.1.1", + "browserify-zlib": "^0.1.4", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^1.0.0", + "https-browserify": "0.0.1", + "os-browserify": "^0.2.0", + "path-browserify": "0.0.0", + "process": "^0.11.0", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.0.5", + "stream-browserify": "^2.0.1", + "stream-http": "^2.3.1", + "string_decoder": "^0.10.25", + "timers-browserify": "^2.0.2", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.10.3", + "vm-browserify": "0.0.4" + } + }, + "node_modules/node-libs-browser/node_modules/crypto-browserify": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.1.tgz", + "integrity": "sha512-Na7ZlwCOqoaW5RwUK1WpXws2kv8mNhWdTlzob0UXulk6G9BDbyiJaGTYBIX61Ozn9l1EPPJpICZb4DaOpT9NlQ==", + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/node-libs-browser/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "node_modules/node-libs-browser/node_modules/url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/node-zip": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/node-zip/-/node-zip-1.1.1.tgz", + "integrity": "sha1-lNGtZ0o81GoViN1zb0qaeMdX62I=", + "dependencies": { + "jszip": "2.5.0" + }, + "bin": { + "nodezip": "bin/nodezip" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dependencies": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dependencies": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "dependencies": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, + "node_modules/optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/optionator/node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "node_modules/os-browserify": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.2.1.tgz", + "integrity": "sha1-Y/xMzuXS13Y9Jrv4YBB45sLgBE8=" + }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dependencies": { + "lcid": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=" + }, + "node_modules/parse-asn1": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", + "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", + "dependencies": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3" + } + }, + "node_modules/parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dependencies": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=" + }, + "node_modules/path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dependencies": { + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "node_modules/path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dependencies": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/pbkdf2": { + "version": "3.0.14", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz", + "integrity": "sha512-gjsZW9O34fm0R7PaLHRJmLLVfSoesxztjPjE9o6R+qtVJij90ltg1joIovN9GKrRW3t1PzhDDG3UMEMFfZ+1wA==", + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "node_modules/proxy-addr": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz", + "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=", + "dependencies": { + "forwarded": "~0.1.0", + "ipaddr.js": "1.4.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/prr": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz", + "integrity": "sha1-GoS4WQgyVQFBGFPQCB7j+obikmo=" + }, + "node_modules/public-encrypt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", + "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, + "node_modules/qs": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.0.tgz", + "integrity": "sha512-fjVFjW9yhqMhVGwRExCXLhJKrLlkYSaxNWdyc9rmHlrVZbk35YHH312dFd7191uQeXkI3mKLZTIbSvIeFwFemg==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/randomatic": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", + "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/randomatic/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/randomatic/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/randomatic/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/randombytes": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz", + "integrity": "sha512-8T7Zn1AhMsQ/HI1SjcCfT/t4ii3eAqco3yOcSzS4mozsOz69lHLsoMXmF9nZgnFanYscnSlUSgs8uZyKzpE6kg==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.1.tgz", + "integrity": "sha512-sxkd1uqaSj41SG5Vet9sNAxBMCMsmZ3LVhRkDlK8SbCpelTUB7JiMGHG70AZS6cFiCRgfNQhU2eLnTHYRFf7LA==", + "dependencies": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dependencies": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dependencies": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.0.3", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dependencies": { + "graceful-fs": "^4.1.2", + "minimatch": "^3.0.2", + "readable-stream": "^2.0.2", + "set-immediate-shim": "^1.0.1" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.10.tgz", + "integrity": "sha1-tPg3BEFqytiZiMmxVjXUfgO5NEo=" + }, + "node_modules/regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dependencies": { + "is-equal-shallow": "^0.1.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "node_modules/repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/request": { + "version": "2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { + "aws-sign2": "~0.6.0", + "aws4": "^1.2.1", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.0", + "forever-agent": "~0.6.1", + "form-data": "~2.1.1", + "har-validator": "~4.2.1", + "hawk": "~3.1.3", + "http-signature": "~1.1.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.7", + "oauth-sign": "~0.8.1", + "performance-now": "^0.2.0", + "qs": "~6.4.0", + "safe-buffer": "^5.0.1", + "stringstream": "~0.0.4", + "tough-cookie": "~2.3.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.0.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "dependencies": { + "resolve-from": "^2.0.0", + "semver": "^5.1.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + }, + "node_modules/resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "node_modules/resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dependencies": { + "align-text": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ripemd160": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", + "dependencies": { + "hash-base": "^2.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "node_modules/sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "node_modules/semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.15.4", + "resolved": "https://registry.npmjs.org/send/-/send-0.15.4.tgz", + "integrity": "sha1-mF+qPihLAnPHkzZKNcZze9k5Bbk=", + "dependencies": { + "debug": "2.6.8", + "depd": "~1.1.1", + "destroy": "~1.0.4", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "etag": "~1.8.0", + "fresh": "0.5.0", + "http-errors": "~1.6.2", + "mime": "1.3.4", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.3.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.4.tgz", + "integrity": "sha1-m2qpjutyU8Tu3Ewfb9vKYJkBqWE=", + "dependencies": { + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "parseurl": "~1.3.1", + "send": "0.15.4" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "node_modules/set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "node_modules/setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + }, + "node_modules/sha.js": { + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.8.tgz", + "integrity": "sha1-NwaMLEdra69ALRSknGf1l5IfY08=", + "dependencies": { + "inherits": "^2.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "deprecated": "This module moved to @hapi/sntp. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues.", + "dependencies": { + "hoek": "2.x.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/source-list-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", + "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==" + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "dependencies": { + "spdx-license-ids": "^1.0.2" + } + }, + "node_modules/spdx-expression-parse": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=" + }, + "node_modules/spdx-license-ids": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=" + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "node_modules/sshpk": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "dashdash": "^1.12.0", + "getpass": "^0.1.1" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + }, + "optionalDependencies": { + "bcrypt-pbkdf": "^1.0.0", + "ecc-jsbn": "~0.1.1", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" + } + }, + "node_modules/sshpk/node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "engines": { + "node": "*" + } + }, + "node_modules/statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stream-browserify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/stream-http": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz", + "integrity": "sha512-c0yTD2rbQzXtSsFSVhtpvY/vS6u066PcXOX9kBB3mSO76RiUQzL340uJkGBWnlBg4/HZzqiUXtaVA7wcRcJgEw==", + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.2.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dependencies": { + "has-flag": "^1.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/tapable": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz", + "integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/timers-browserify": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.4.tgz", + "integrity": "sha512-uZYhyU3EX8O7HQP+J9fTVYwsq90Vr68xPEFo7yrVImIxYvHgukBEgOB/SgGoorWVTzGM/3Z+wUNnboA4M8jWrg==", + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=" + }, + "node_modules/tough-cookie": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "dependencies": { + "punycode": "^1.4.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tough-cookie/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "node_modules/tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.3.tgz", + "integrity": "sha1-Dj8mcLRAmbC0bChNE2p+9Jx0wuo=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/type-is": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.15" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.2.tgz", + "integrity": "sha1-A4qV99m7tCCxvzW6MdTFwd0//jQ=", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dependencies": { + "source-map": "~0.5.1", + "yargs": "~3.10.0" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + }, + "optionalDependencies": { + "uglify-to-browserify": "~1.0.0" + } + }, + "node_modules/uglify-js/node_modules/yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dependencies": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + }, + "node_modules/uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "optional": true + }, + "node_modules/universalify": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", + "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dependencies": { + "inherits": "2.0.1" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/util/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + }, + "node_modules/utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", + "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "dependencies": { + "spdx-correct": "~1.0.0", + "spdx-expression-parse": "~1.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", + "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/verror/node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dependencies": { + "indexof": "0.0.1" + } + }, + "node_modules/watchpack": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.4.0.tgz", + "integrity": "sha1-ShRyvLuVK9Cpu0A2gB+VTfs5+qw=", + "dependencies": { + "async": "^2.1.2", + "chokidar": "^1.7.0", + "graceful-fs": "^4.1.2" + } + }, + "node_modules/webpack": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-2.7.0.tgz", + "integrity": "sha512-MjAA0ZqO1ba7ZQJRnoCdbM56mmFpipOPUv/vQpwwfSI42p5PVDdoiuK2AL2FwFUVgT859Jr43bFZXRg/LNsqvg==", + "dependencies": { + "acorn": "^5.0.0", + "acorn-dynamic-import": "^2.0.0", + "ajv": "^4.7.0", + "ajv-keywords": "^1.1.1", + "async": "^2.1.2", + "enhanced-resolve": "^3.3.0", + "interpret": "^1.0.0", + "json-loader": "^0.5.4", + "json5": "^0.5.1", + "loader-runner": "^2.3.0", + "loader-utils": "^0.2.16", + "memory-fs": "~0.4.1", + "mkdirp": "~0.5.0", + "node-libs-browser": "^2.0.0", + "source-map": "^0.5.3", + "supports-color": "^3.1.0", + "tapable": "~0.2.5", + "uglify-js": "^2.8.27", + "watchpack": "^1.3.1", + "webpack-sources": "^1.0.1", + "yargs": "^6.0.0" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=4.3.0 <5.0.0 || >=5.10" + } + }, + "node_modules/webpack-sources": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.0.1.tgz", + "integrity": "sha512-05tMxipUCwHqYaVS8xc7sYPTly8PzXayRCB4dTxLhWTqlKUiwH6ezmEe0OSreL1c30LAuA3Zqmc+uEBUGFJDjw==", + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.5.3" + } + }, + "node_modules/webpack/node_modules/json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + }, + "node_modules/window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/winston": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.3.1.tgz", + "integrity": "sha1-C0hCDZeMAYBM8CMLZIhhWYIloRk=", + "dependencies": { + "async": "~1.0.0", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/winston/node_modules/async": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", + "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=" + }, + "node_modules/wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", + "dev": true + }, + "node_modules/xml2js": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", + "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "^4.1.0" + } + }, + "node_modules/xmlbuilder": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", + "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", + "dependencies": { + "lodash": "^4.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "node_modules/yamljs": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.2.10.tgz", + "integrity": "sha1-SBzHwlynOvWfWR8MluPOVsdXpA8=", + "dependencies": { + "argparse": "^1.0.7", + "glob": "^7.0.5" + }, + "bin": { + "json2yaml": "bin/json2yaml", + "yaml2json": "bin/yaml2json" + } + }, + "node_modules/yargs": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", + "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", + "dependencies": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^4.2.0" + } + }, + "node_modules/yargs-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", + "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", + "dependencies": { + "camelcase": "^3.0.0" + } + }, + "node_modules/yargs-parser/node_modules/camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargs/node_modules/camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargs/node_modules/cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + } + }, "dependencies": { "@types/async": { "version": "2.0.42", @@ -16,7 +5103,7 @@ "integrity": "sha512-/nysVvxwup1WniGHIM31UZXM+6727h4FAa2tZpFSQBooBcl2Bh1N9oQmVVg8QYnjchN/DOGi7UvVN0jpzWL6sw==", "dev": true, "requires": { - "@types/node": "6.0.88" + "@types/node": "*" } }, "@types/chai": { @@ -43,8 +5130,8 @@ "integrity": "sha512-kksYkn34+ENpZqUvGYq0IGOOa4SOhFnMfqCepF789CP+QBgeWok9osTCiAStbc8n4Cv3QSXGmpB5T3PGzL50bQ==", "dev": true, "requires": { - "@types/bson": "1.0.4", - "@types/node": "6.0.88" + "@types/bson": "*", + "@types/node": "*" } }, "@types/node": { @@ -59,7 +5146,7 @@ "integrity": "sha1-eyo5hwv/iWPbWHY/HlNnV6mssTQ=", "dev": true, "requires": { - "@types/node": "6.0.88" + "@types/node": "*" } }, "abbrev": { @@ -73,7 +5160,7 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", "requires": { - "mime-types": "2.1.17", + "mime-types": "~2.1.16", "negotiator": "0.6.1" } }, @@ -87,7 +5174,7 @@ "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", "requires": { - "acorn": "4.0.13" + "acorn": "^4.0.3" }, "dependencies": { "acorn": { @@ -102,23 +5189,24 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" } }, "ajv-keywords": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=" + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "requires": {} }, "align-text": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" } }, "amdefine": { @@ -137,8 +5225,8 @@ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", "requires": { - "micromatch": "2.3.11", - "normalize-path": "2.1.1" + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" } }, "argparse": { @@ -146,7 +5234,7 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", "requires": { - "sprintf-js": "1.0.3" + "sprintf-js": "~1.0.2" } }, "arr-diff": { @@ -154,7 +5242,7 @@ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "requires": { - "arr-flatten": "1.1.0" + "arr-flatten": "^1.0.1" } }, "arr-flatten": { @@ -182,9 +5270,9 @@ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.1.tgz", "integrity": "sha1-SLokC0WpKA6UdImQull9IWYX/UA=", "requires": { - "bn.js": "4.11.8", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0" + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" } }, "assert": { @@ -211,7 +5299,7 @@ "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==", "requires": { - "lodash": "4.17.4" + "lodash": "^4.14.0" } }, "async-each": { @@ -231,7 +5319,7 @@ "requires": { "buffer": "4.9.1", "crypto-browserify": "1.0.9", - "events": "1.1.1", + "events": "^1.1.1", "jmespath": "0.15.0", "querystring": "0.2.0", "sax": "1.2.1", @@ -267,7 +5355,7 @@ "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", "optional": true, "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "big.js": { @@ -291,15 +5379,15 @@ "integrity": "sha1-07Ik1Gf6LOjUNYnAJFBDJnwJNjQ=", "requires": { "bytes": "3.0.0", - "content-type": "1.0.3", + "content-type": "~1.0.2", "debug": "2.6.8", - "depd": "1.1.1", - "http-errors": "1.6.2", + "depd": "~1.1.1", + "http-errors": "~1.6.2", "iconv-lite": "0.4.18", - "on-finished": "2.3.0", + "on-finished": "~2.3.0", "qs": "6.5.0", "raw-body": "2.3.1", - "type-is": "1.6.15" + "type-is": "~1.6.15" } }, "boom": { @@ -307,7 +5395,7 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", "requires": { - "hoek": "2.16.3" + "hoek": "2.x.x" } }, "brace-expansion": { @@ -315,7 +5403,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -324,9 +5412,9 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.2" + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" } }, "brorand": { @@ -345,12 +5433,12 @@ "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.0.8.tgz", "integrity": "sha512-WYCMOT/PtGTlpOKFht0YJFYcPy6pLCR98CtWfzK13zoynLlBMvAdEMSRGmgnJCw2M2j/5qxBkinZQFobieM8dQ==", "requires": { - "buffer-xor": "1.0.3", - "cipher-base": "1.0.4", - "create-hash": "1.1.3", - "evp_bytestokey": "1.0.3", - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "browserify-cipher": { @@ -358,9 +5446,9 @@ "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz", "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=", "requires": { - "browserify-aes": "1.0.8", - "browserify-des": "1.0.0", - "evp_bytestokey": "1.0.3" + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" } }, "browserify-des": { @@ -368,9 +5456,9 @@ "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz", "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=", "requires": { - "cipher-base": "1.0.4", - "des.js": "1.0.0", - "inherits": "2.0.3" + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1" } }, "browserify-rsa": { @@ -378,8 +5466,8 @@ "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "requires": { - "bn.js": "4.11.8", - "randombytes": "2.0.5" + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" } }, "browserify-sign": { @@ -387,13 +5475,13 @@ "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "elliptic": "6.4.0", - "inherits": "2.0.3", - "parse-asn1": "5.1.0" + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" } }, "browserify-zlib": { @@ -401,7 +5489,7 @@ "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", "requires": { - "pako": "0.2.9" + "pako": "~0.2.0" } }, "bson": { @@ -414,9 +5502,9 @@ "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "requires": { - "base64-js": "1.2.1", - "ieee754": "1.1.8", - "isarray": "1.0.0" + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" } }, "buffer-shims": { @@ -464,8 +5552,8 @@ "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", "requires": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" } }, "chai": { @@ -474,12 +5562,12 @@ "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", "dev": true, "requires": { - "assertion-error": "1.0.2", - "check-error": "1.0.2", - "deep-eql": "3.0.1", - "get-func-name": "2.0.0", - "pathval": "1.1.0", - "type-detect": "4.0.3" + "assertion-error": "^1.0.1", + "check-error": "^1.0.1", + "deep-eql": "^3.0.0", + "get-func-name": "^2.0.0", + "pathval": "^1.0.0", + "type-detect": "^4.0.0" } }, "charenc": { @@ -499,15 +5587,15 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", "requires": { - "anymatch": "1.3.2", - "async-each": "1.0.1", - "fsevents": "1.1.3", - "glob-parent": "2.0.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "2.0.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0" + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "fsevents": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" } }, "cipher-base": { @@ -515,8 +5603,8 @@ "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "cliui": { @@ -524,8 +5612,8 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", + "center-align": "^0.1.1", + "right-align": "^0.1.1", "wordwrap": "0.0.2" } }, @@ -549,7 +5637,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "commander": { @@ -576,7 +5664,7 @@ "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", "requires": { - "date-now": "0.1.4" + "date-now": "^0.1.4" } }, "constants-browserify": { @@ -614,8 +5702,8 @@ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", "requires": { - "object-assign": "4.1.1", - "vary": "1.1.1" + "object-assign": "^4", + "vary": "^1" } }, "create-ecdh": { @@ -623,8 +5711,8 @@ "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=", "requires": { - "bn.js": "4.11.8", - "elliptic": "6.4.0" + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" } }, "create-hash": { @@ -632,10 +5720,10 @@ "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=", "requires": { - "cipher-base": "1.0.4", - "inherits": "2.0.3", - "ripemd160": "2.0.1", - "sha.js": "2.4.8" + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "sha.js": "^2.4.0" } }, "create-hmac": { @@ -643,12 +5731,12 @@ "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=", "requires": { - "cipher-base": "1.0.4", - "create-hash": "1.1.3", - "inherits": "2.0.3", - "ripemd160": "2.0.1", - "safe-buffer": "5.1.1", - "sha.js": "2.4.8" + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" } }, "crypt": { @@ -662,7 +5750,7 @@ "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", "requires": { - "boom": "2.10.1" + "boom": "2.x.x" } }, "crypto-browserify": { @@ -680,7 +5768,7 @@ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" }, "dependencies": { "assert-plus": { @@ -708,7 +5796,7 @@ "resolved": "https://registry.npmjs.org/decache/-/decache-4.3.0.tgz", "integrity": "sha1-o5XkBwlWmKyKbe8B8qaky3Y49jU=", "requires": { - "callsite": "1.0.0" + "callsite": "^1.0.0" } }, "decamelize": { @@ -722,7 +5810,7 @@ "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { - "type-detect": "4.0.3" + "type-detect": "^4.0.0" } }, "deep-is": { @@ -746,8 +5834,8 @@ "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0" + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" } }, "destroy": { @@ -766,9 +5854,9 @@ "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz", "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=", "requires": { - "bn.js": "4.11.8", - "miller-rabin": "4.0.0", - "randombytes": "2.0.5" + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" } }, "domain-browser": { @@ -782,7 +5870,7 @@ "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "~0.1.0" } }, "ee-first": { @@ -795,13 +5883,13 @@ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0", - "hash.js": "1.1.3", - "hmac-drbg": "1.0.1", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0", - "minimalistic-crypto-utils": "1.0.1" + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" } }, "emojis-list": { @@ -819,10 +5907,10 @@ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=", "requires": { - "graceful-fs": "4.1.11", - "memory-fs": "0.4.1", - "object-assign": "4.1.1", - "tapable": "0.2.8" + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "object-assign": "^4.0.1", + "tapable": "^0.2.7" } }, "errno": { @@ -830,7 +5918,7 @@ "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz", "integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=", "requires": { - "prr": "0.0.0" + "prr": "~0.0.0" } }, "error-ex": { @@ -838,7 +5926,7 @@ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", "requires": { - "is-arrayish": "0.2.1" + "is-arrayish": "^0.2.1" } }, "es6-promise": { @@ -863,11 +5951,11 @@ "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", "dev": true, "requires": { - "esprima": "2.7.3", - "estraverse": "1.9.3", - "esutils": "2.0.2", - "optionator": "0.8.2", - "source-map": "0.2.0" + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" }, "dependencies": { "source-map": { @@ -877,7 +5965,7 @@ "dev": true, "optional": true, "requires": { - "amdefine": "1.0.1" + "amdefine": ">=0.0.4" } } } @@ -915,8 +6003,8 @@ "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "requires": { - "md5.js": "1.3.4", - "safe-buffer": "5.1.1" + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" } }, "expand-brackets": { @@ -924,7 +6012,7 @@ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "requires": { - "is-posix-bracket": "0.1.1" + "is-posix-bracket": "^0.1.0" } }, "expand-range": { @@ -932,7 +6020,7 @@ "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "requires": { - "fill-range": "2.2.3" + "fill-range": "^2.1.0" } }, "express": { @@ -940,34 +6028,34 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.15.4.tgz", "integrity": "sha1-Ay4iU0ic+PzgJma+yj0R7XotrtE=", "requires": { - "accepts": "1.3.4", + "accepts": "~1.3.3", "array-flatten": "1.1.1", "content-disposition": "0.5.2", - "content-type": "1.0.3", + "content-type": "~1.0.2", "cookie": "0.3.1", "cookie-signature": "1.0.6", "debug": "2.6.8", - "depd": "1.1.1", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "etag": "1.8.0", - "finalhandler": "1.0.4", + "depd": "~1.1.1", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "etag": "~1.8.0", + "finalhandler": "~1.0.4", "fresh": "0.5.0", "merge-descriptors": "1.0.1", - "methods": "1.1.2", - "on-finished": "2.3.0", - "parseurl": "1.3.2", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.1", "path-to-regexp": "0.1.7", - "proxy-addr": "1.1.5", + "proxy-addr": "~1.1.5", "qs": "6.5.0", - "range-parser": "1.2.0", + "range-parser": "~1.2.0", "send": "0.15.4", "serve-static": "1.12.4", "setprototypeof": "1.0.3", - "statuses": "1.3.1", - "type-is": "1.6.15", + "statuses": "~1.3.1", + "type-is": "~1.6.15", "utils-merge": "1.0.0", - "vary": "1.1.1" + "vary": "~1.1.1" } }, "extend": { @@ -980,7 +6068,7 @@ "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "requires": { - "is-extglob": "1.0.0" + "is-extglob": "^1.0.0" } }, "extsprintf": { @@ -1009,11 +6097,11 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", "requires": { - "is-number": "2.1.0", - "isobject": "2.1.0", - "randomatic": "1.1.7", - "repeat-element": "1.1.2", - "repeat-string": "1.6.1" + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^1.1.3", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" } }, "finalhandler": { @@ -1022,12 +6110,12 @@ "integrity": "sha512-16l/r8RgzlXKmFOhZpHBztvye+lAhC5SU7hXavnerC9UfZqZxxXl3BzL8MhffPT3kF61lj9Oav2LKEzh0ei7tg==", "requires": { "debug": "2.6.8", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.3.1", - "unpipe": "1.0.0" + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.1", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" } }, "find-up": { @@ -1035,8 +6123,8 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "for-in": { @@ -1049,7 +6137,7 @@ "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", "requires": { - "for-in": "1.0.2" + "for-in": "^1.0.1" } }, "forever-agent": { @@ -1062,9 +6150,9 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.12" } }, "forwarded": { @@ -1082,9 +6170,9 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "3.0.1", - "universalify": "0.1.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^3.0.0", + "universalify": "^0.1.0" } }, "fs.realpath": { @@ -1098,8 +6186,8 @@ "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", "optional": true, "requires": { - "nan": "2.8.0", - "node-pre-gyp": "0.6.39" + "nan": "^2.3.0", + "node-pre-gyp": "^0.6.39" }, "dependencies": { "abbrev": { @@ -1112,13 +6200,14 @@ "bundled": true, "optional": true, "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" } }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.1.1", @@ -1130,8 +6219,8 @@ "bundled": true, "optional": true, "requires": { - "delegates": "1.0.0", - "readable-stream": "2.2.9" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" } }, "asn1": { @@ -1161,41 +6250,46 @@ }, "balanced-match": { "version": "0.4.2", - "bundled": true + "bundled": true, + "optional": true }, "bcrypt-pbkdf": { "version": "1.0.1", "bundled": true, "optional": true, "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "block-stream": { "version": "0.0.9", "bundled": true, + "optional": true, "requires": { - "inherits": "2.0.3" + "inherits": "~2.0.0" } }, "boom": { "version": "2.10.1", "bundled": true, + "optional": true, "requires": { - "hoek": "2.16.3" + "hoek": "2.x.x" } }, "brace-expansion": { "version": "1.1.7", "bundled": true, + "optional": true, "requires": { - "balanced-match": "0.4.2", + "balanced-match": "^0.4.1", "concat-map": "0.0.1" } }, "buffer-shims": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "caseless": { "version": "0.12.0", @@ -1209,32 +6303,38 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "combined-stream": { "version": "1.0.5", "bundled": true, + "optional": true, "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "cryptiles": { "version": "2.0.5", "bundled": true, + "optional": true, "requires": { - "boom": "2.10.1" + "boom": "2.x.x" } }, "dashdash": { @@ -1242,7 +6342,7 @@ "bundled": true, "optional": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" }, "dependencies": { "assert-plus": { @@ -1267,7 +6367,8 @@ }, "delayed-stream": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "delegates": { "version": "1.0.0", @@ -1284,7 +6385,7 @@ "bundled": true, "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "~0.1.0" } }, "extend": { @@ -1294,7 +6395,8 @@ }, "extsprintf": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "forever-agent": { "version": "0.6.1", @@ -1306,23 +6408,25 @@ "bundled": true, "optional": true, "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.15" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.12" } }, "fs.realpath": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "fstream": { "version": "1.0.11", "bundled": true, + "optional": true, "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.1" + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" } }, "fstream-ignore": { @@ -1330,9 +6434,9 @@ "bundled": true, "optional": true, "requires": { - "fstream": "1.0.11", - "inherits": "2.0.3", - "minimatch": "3.0.4" + "fstream": "^1.0.0", + "inherits": "2", + "minimatch": "^3.0.0" } }, "gauge": { @@ -1340,14 +6444,14 @@ "bundled": true, "optional": true, "requires": { - "aproba": "1.1.1", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" } }, "getpass": { @@ -1355,7 +6459,7 @@ "bundled": true, "optional": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" }, "dependencies": { "assert-plus": { @@ -1368,18 +6472,20 @@ "glob": { "version": "7.1.2", "bundled": true, + "optional": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "graceful-fs": { "version": "4.1.11", - "bundled": true + "bundled": true, + "optional": true }, "har-schema": { "version": "1.0.5", @@ -1391,8 +6497,8 @@ "bundled": true, "optional": true, "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" + "ajv": "^4.9.1", + "har-schema": "^1.0.5" } }, "has-unicode": { @@ -1403,38 +6509,42 @@ "hawk": { "version": "3.1.3", "bundled": true, + "optional": true, "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" + "boom": "2.x.x", + "cryptiles": "2.x.x", + "hoek": "2.x.x", + "sntp": "1.x.x" } }, "hoek": { "version": "2.16.3", - "bundled": true + "bundled": true, + "optional": true }, "http-signature": { "version": "1.1.1", "bundled": true, "optional": true, "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.0", - "sshpk": "1.13.0" + "assert-plus": "^0.2.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "inflight": { "version": "1.0.6", "bundled": true, + "optional": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.4", @@ -1444,8 +6554,9 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-typedarray": { @@ -1455,7 +6566,8 @@ }, "isarray": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "isstream": { "version": "0.1.2", @@ -1467,7 +6579,7 @@ "bundled": true, "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "~0.1.0" } }, "jsbn": { @@ -1485,7 +6597,7 @@ "bundled": true, "optional": true, "requires": { - "jsonify": "0.0.0" + "jsonify": "~0.0.0" } }, "json-stringify-safe": { @@ -1518,29 +6630,34 @@ }, "mime-db": { "version": "1.27.0", - "bundled": true + "bundled": true, + "optional": true }, "mime-types": { "version": "2.1.15", "bundled": true, + "optional": true, "requires": { - "mime-db": "1.27.0" + "mime-db": "~1.27.0" } }, "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { - "brace-expansion": "1.1.7" + "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -1555,17 +6672,17 @@ "bundled": true, "optional": true, "requires": { - "detect-libc": "1.0.2", + "detect-libc": "^1.0.2", "hawk": "3.1.3", - "mkdirp": "0.5.1", - "nopt": "4.0.1", - "npmlog": "4.1.0", - "rc": "1.2.1", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "npmlog": "^4.0.2", + "rc": "^1.1.7", "request": "2.81.0", - "rimraf": "2.6.1", - "semver": "5.3.0", - "tar": "2.2.1", - "tar-pack": "3.4.0" + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^2.2.1", + "tar-pack": "^3.4.0" } }, "nopt": { @@ -1573,8 +6690,8 @@ "bundled": true, "optional": true, "requires": { - "abbrev": "1.1.0", - "osenv": "0.1.4" + "abbrev": "1", + "osenv": "^0.1.4" } }, "npmlog": { @@ -1582,15 +6699,16 @@ "bundled": true, "optional": true, "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" } }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "oauth-sign": { "version": "0.8.2", @@ -1605,8 +6723,9 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "os-homedir": { @@ -1624,13 +6743,14 @@ "bundled": true, "optional": true, "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" } }, "path-is-absolute": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "performance-now": { "version": "0.2.0", @@ -1639,7 +6759,8 @@ }, "process-nextick-args": { "version": "1.0.7", - "bundled": true + "bundled": true, + "optional": true }, "punycode": { "version": "1.4.1", @@ -1656,10 +6777,10 @@ "bundled": true, "optional": true, "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.4", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" + "deep-extend": "~0.4.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, "dependencies": { "minimist": { @@ -1672,14 +6793,15 @@ "readable-stream": { "version": "2.2.9", "bundled": true, + "optional": true, "requires": { - "buffer-shims": "1.0.0", - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "1.0.1", - "util-deprecate": "1.0.2" + "buffer-shims": "~1.0.0", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~1.0.0", + "util-deprecate": "~1.0.1" } }, "request": { @@ -1687,40 +6809,42 @@ "bundled": true, "optional": true, "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.15", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.0.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.2", - "tunnel-agent": "0.6.0", - "uuid": "3.0.1" + "aws-sign2": "~0.6.0", + "aws4": "^1.2.1", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.0", + "forever-agent": "~0.6.1", + "form-data": "~2.1.1", + "har-validator": "~4.2.1", + "hawk": "~3.1.3", + "http-signature": "~1.1.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.7", + "oauth-sign": "~0.8.1", + "performance-now": "^0.2.0", + "qs": "~6.4.0", + "safe-buffer": "^5.0.1", + "stringstream": "~0.0.4", + "tough-cookie": "~2.3.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.0.0" } }, "rimraf": { "version": "2.6.1", "bundled": true, + "optional": true, "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" } }, "safe-buffer": { "version": "5.0.1", - "bundled": true + "bundled": true, + "optional": true }, "semver": { "version": "5.3.0", @@ -1740,8 +6864,9 @@ "sntp": { "version": "1.0.9", "bundled": true, + "optional": true, "requires": { - "hoek": "2.16.3" + "hoek": "2.x.x" } }, "sshpk": { @@ -1749,15 +6874,15 @@ "bundled": true, "optional": true, "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jodid25519": "1.0.2", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jodid25519": "^1.0.0", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" }, "dependencies": { "assert-plus": { @@ -1767,20 +6892,22 @@ } } }, - "string-width": { - "version": "1.0.2", + "string_decoder": { + "version": "1.0.1", "bundled": true, + "optional": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "safe-buffer": "^5.0.1" } }, - "string_decoder": { - "version": "1.0.1", + "string-width": { + "version": "1.0.2", "bundled": true, + "optional": true, "requires": { - "safe-buffer": "5.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "stringstream": { @@ -1791,8 +6918,9 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-json-comments": { @@ -1803,10 +6931,11 @@ "tar": { "version": "2.2.1", "bundled": true, + "optional": true, "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" } }, "tar-pack": { @@ -1814,14 +6943,14 @@ "bundled": true, "optional": true, "requires": { - "debug": "2.6.8", - "fstream": "1.0.11", - "fstream-ignore": "1.0.5", - "once": "1.4.0", - "readable-stream": "2.2.9", - "rimraf": "2.6.1", - "tar": "2.2.1", - "uid-number": "0.0.6" + "debug": "^2.2.0", + "fstream": "^1.0.10", + "fstream-ignore": "^1.0.5", + "once": "^1.3.3", + "readable-stream": "^2.1.4", + "rimraf": "^2.5.1", + "tar": "^2.2.1", + "uid-number": "^0.0.6" } }, "tough-cookie": { @@ -1829,7 +6958,7 @@ "bundled": true, "optional": true, "requires": { - "punycode": "1.4.1" + "punycode": "^1.4.1" } }, "tunnel-agent": { @@ -1837,7 +6966,7 @@ "bundled": true, "optional": true, "requires": { - "safe-buffer": "5.0.1" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -1852,7 +6981,8 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "uuid": { "version": "3.0.1", @@ -1872,12 +7002,13 @@ "bundled": true, "optional": true, "requires": { - "string-width": "1.0.2" + "string-width": "^1.0.2" } }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true } } }, @@ -1897,7 +7028,7 @@ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" }, "dependencies": { "assert-plus": { @@ -1912,12 +7043,12 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "glob-base": { @@ -1925,8 +7056,8 @@ "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", "requires": { - "glob-parent": "2.0.0", - "is-glob": "2.0.1" + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" } }, "glob-parent": { @@ -1934,7 +7065,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "requires": { - "is-glob": "2.0.1" + "is-glob": "^2.0.0" } }, "graceful-fs": { @@ -1960,10 +7091,10 @@ "integrity": "sha1-PTDHGLCaPZbyPqTMH0A8TTup/08=", "dev": true, "requires": { - "async": "1.5.2", - "optimist": "0.6.1", - "source-map": "0.4.4", - "uglify-js": "2.8.29" + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4", + "uglify-js": "^2.6" }, "dependencies": { "async": { @@ -1978,7 +7109,7 @@ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { - "amdefine": "1.0.1" + "amdefine": ">=0.0.4" } } } @@ -1993,8 +7124,8 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" + "ajv": "^4.9.1", + "har-schema": "^1.0.5" } }, "has-flag": { @@ -2007,7 +7138,7 @@ "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", "requires": { - "inherits": "2.0.3" + "inherits": "^2.0.1" } }, "hash.js": { @@ -2015,8 +7146,8 @@ "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0" + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" } }, "hawk": { @@ -2024,10 +7155,10 @@ "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" + "boom": "2.x.x", + "cryptiles": "2.x.x", + "hoek": "2.x.x", + "sntp": "1.x.x" } }, "he": { @@ -2041,9 +7172,9 @@ "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "requires": { - "hash.js": "1.1.3", - "minimalistic-assert": "1.0.0", - "minimalistic-crypto-utils": "1.0.1" + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" } }, "hoek": { @@ -2064,7 +7195,7 @@ "depd": "1.1.1", "inherits": "2.0.3", "setprototypeof": "1.0.3", - "statuses": "1.3.1" + "statuses": ">= 1.3.1 < 2" } }, "http-signature": { @@ -2072,9 +7203,9 @@ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.1", - "sshpk": "1.13.1" + "assert-plus": "^0.2.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "https-browserify": { @@ -2102,8 +7233,8 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -2136,7 +7267,7 @@ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "requires": { - "binary-extensions": "1.10.0" + "binary-extensions": "^1.0.0" } }, "is-buffer": { @@ -2149,7 +7280,7 @@ "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "requires": { - "builtin-modules": "1.1.1" + "builtin-modules": "^1.0.0" } }, "is-dotfile": { @@ -2162,7 +7293,7 @@ "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", "requires": { - "is-primitive": "2.0.0" + "is-primitive": "^2.0.0" } }, "is-extendable": { @@ -2180,7 +7311,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-glob": { @@ -2188,7 +7319,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "requires": { - "is-extglob": "1.0.0" + "is-extglob": "^1.0.0" } }, "is-number": { @@ -2196,7 +7327,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" } }, "is-posix-bracket": { @@ -2249,20 +7380,20 @@ "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", "dev": true, "requires": { - "abbrev": "1.0.9", - "async": "1.5.2", - "escodegen": "1.8.1", - "esprima": "2.7.3", - "glob": "5.0.15", - "handlebars": "4.0.10", - "js-yaml": "3.10.0", - "mkdirp": "0.5.1", - "nopt": "3.0.6", - "once": "1.4.0", - "resolve": "1.1.7", - "supports-color": "3.2.3", - "which": "1.3.0", - "wordwrap": "1.0.0" + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" }, "dependencies": { "async": { @@ -2277,11 +7408,11 @@ "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "wordwrap": { @@ -2303,8 +7434,8 @@ "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", "dev": true, "requires": { - "argparse": "1.0.9", - "esprima": "4.0.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, "dependencies": { "esprima": { @@ -2336,7 +7467,7 @@ "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", "requires": { - "jsonify": "0.0.0" + "jsonify": "~0.0.0" } }, "json-stringify-safe": { @@ -2360,7 +7491,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.6" } }, "jsonify": { @@ -2391,7 +7522,7 @@ "resolved": "https://registry.npmjs.org/jszip/-/jszip-2.5.0.tgz", "integrity": "sha1-dET9hVHd8+XacZj+oMkbyDCMwnQ=", "requires": { - "pako": "0.2.9" + "pako": "~0.2.5" } }, "kind-of": { @@ -2399,7 +7530,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } }, "lazy-cache": { @@ -2412,7 +7543,7 @@ "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "requires": { - "invert-kv": "1.0.0" + "invert-kv": "^1.0.0" } }, "levn": { @@ -2421,8 +7552,8 @@ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" } }, "load-json-file": { @@ -2430,11 +7561,11 @@ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" } }, "loader-runner": { @@ -2447,10 +7578,10 @@ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", "requires": { - "big.js": "3.1.3", - "emojis-list": "2.1.0", - "json5": "0.5.1", - "object-assign": "4.1.1" + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" }, "dependencies": { "json5": { @@ -2471,8 +7602,8 @@ "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", "dev": true, "requires": { - "lodash._basecopy": "3.0.1", - "lodash.keys": "3.1.2" + "lodash._basecopy": "^3.0.0", + "lodash.keys": "^3.0.0" } }, "lodash._basecopy": { @@ -2505,9 +7636,9 @@ "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", "dev": true, "requires": { - "lodash._baseassign": "3.2.0", - "lodash._basecreate": "3.0.3", - "lodash._isiterateecall": "3.0.9" + "lodash._baseassign": "^3.0.0", + "lodash._basecreate": "^3.0.0", + "lodash._isiterateecall": "^3.0.0" } }, "lodash.isarguments": { @@ -2528,9 +7659,9 @@ "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", "dev": true, "requires": { - "lodash._getnative": "3.9.1", - "lodash.isarguments": "3.1.0", - "lodash.isarray": "3.0.4" + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" } }, "longest": { @@ -2544,9 +7675,9 @@ "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", "dev": true, "requires": { - "charenc": "0.0.2", - "crypt": "0.0.2", - "is-buffer": "1.1.5" + "charenc": "~0.0.1", + "crypt": "~0.0.1", + "is-buffer": "~1.1.1" } }, "md5.js": { @@ -2554,8 +7685,8 @@ "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", "requires": { - "hash-base": "3.0.4", - "inherits": "2.0.3" + "hash-base": "^3.0.0", + "inherits": "^2.0.1" }, "dependencies": { "hash-base": { @@ -2563,8 +7694,8 @@ "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } } } @@ -2579,8 +7710,8 @@ "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", "requires": { - "errno": "0.1.4", - "readable-stream": "2.3.3" + "errno": "^0.1.3", + "readable-stream": "^2.0.1" } }, "merge-descriptors": { @@ -2598,19 +7729,19 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "requires": { - "arr-diff": "2.0.0", - "array-unique": "0.2.1", - "braces": "1.8.5", - "expand-brackets": "0.1.5", - "extglob": "0.3.2", - "filename-regex": "2.0.1", - "is-extglob": "1.0.0", - "is-glob": "2.0.1", - "kind-of": "3.2.2", - "normalize-path": "2.1.1", - "object.omit": "2.0.1", - "parse-glob": "3.0.4", - "regex-cache": "0.4.4" + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" } }, "miller-rabin": { @@ -2618,8 +7749,8 @@ "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.0.tgz", "integrity": "sha1-SmL7HUKTPAVYOYL0xxb2+55sbT0=", "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0" + "bn.js": "^4.0.0", + "brorand": "^1.0.1" } }, "mime": { @@ -2637,7 +7768,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", "requires": { - "mime-db": "1.30.0" + "mime-db": "~1.30.0" } }, "minimalistic-assert": { @@ -2655,7 +7786,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { - "brace-expansion": "1.1.8" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -2697,7 +7828,7 @@ "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", "dev": true, "requires": { - "graceful-readlink": "1.0.1" + "graceful-readlink": ">= 1.0.0" } }, "glob": { @@ -2706,12 +7837,12 @@ "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "supports-color": { @@ -2720,7 +7851,7 @@ "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", "dev": true, "requires": { - "has-flag": "1.0.0" + "has-flag": "^1.0.0" } } } @@ -2731,10 +7862,10 @@ "integrity": "sha1-MJ9LeiD82ibQrWnJt9CAjXcjAsI=", "dev": true, "requires": { - "debug": "2.6.8", - "md5": "2.2.1", - "mkdirp": "0.5.1", - "xml": "1.0.1" + "debug": "^2.2.0", + "md5": "^2.1.0", + "mkdirp": "~0.5.1", + "xml": "^1.0.0" } }, "mongodb": { @@ -2752,13 +7883,13 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz", "integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE=", "requires": { - "buffer-shims": "1.0.0", - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" + "buffer-shims": "~1.0.0", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~1.0.0", + "util-deprecate": "~1.0.1" } } } @@ -2768,8 +7899,8 @@ "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.15.tgz", "integrity": "sha1-hB9TuH//9MdFgYnDXIroJ+EWl2Q=", "requires": { - "bson": "1.0.4", - "require_optional": "1.0.1" + "bson": "~1.0.4", + "require_optional": "~1.0.0" } }, "ms": { @@ -2793,28 +7924,28 @@ "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.0.0.tgz", "integrity": "sha1-o6WeyXAkmFtG6Vg3lkb5bEthZkY=", "requires": { - "assert": "1.4.1", - "browserify-zlib": "0.1.4", - "buffer": "4.9.1", - "console-browserify": "1.1.0", - "constants-browserify": "1.0.0", - "crypto-browserify": "3.11.1", - "domain-browser": "1.1.7", - "events": "1.1.1", + "assert": "^1.1.1", + "browserify-zlib": "^0.1.4", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^1.0.0", "https-browserify": "0.0.1", - "os-browserify": "0.2.1", + "os-browserify": "^0.2.0", "path-browserify": "0.0.0", - "process": "0.11.10", - "punycode": "1.3.2", - "querystring-es3": "0.2.1", - "readable-stream": "2.3.3", - "stream-browserify": "2.0.1", - "stream-http": "2.7.2", - "string_decoder": "0.10.31", - "timers-browserify": "2.0.4", + "process": "^0.11.0", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.0.5", + "stream-browserify": "^2.0.1", + "stream-http": "^2.3.1", + "string_decoder": "^0.10.25", + "timers-browserify": "^2.0.2", "tty-browserify": "0.0.0", - "url": "0.11.0", - "util": "0.10.3", + "url": "^0.11.0", + "util": "^0.10.3", "vm-browserify": "0.0.4" }, "dependencies": { @@ -2823,16 +7954,16 @@ "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.11.1.tgz", "integrity": "sha512-Na7ZlwCOqoaW5RwUK1WpXws2kv8mNhWdTlzob0UXulk6G9BDbyiJaGTYBIX61Ozn9l1EPPJpICZb4DaOpT9NlQ==", "requires": { - "browserify-cipher": "1.0.0", - "browserify-sign": "4.0.4", - "create-ecdh": "4.0.0", - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "diffie-hellman": "5.0.2", - "inherits": "2.0.3", - "pbkdf2": "3.0.14", - "public-encrypt": "4.0.0", - "randombytes": "2.0.5" + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0" } }, "string_decoder": { @@ -2865,7 +7996,7 @@ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { - "abbrev": "1.0.9" + "abbrev": "1" } }, "normalize-package-data": { @@ -2873,10 +8004,10 @@ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "requires": { - "hosted-git-info": "2.5.0", - "is-builtin-module": "1.0.0", - "semver": "5.4.1", - "validate-npm-package-license": "3.0.1" + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, "normalize-path": { @@ -2884,7 +8015,7 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "requires": { - "remove-trailing-separator": "1.1.0" + "remove-trailing-separator": "^1.0.1" } }, "number-is-nan": { @@ -2907,8 +8038,8 @@ "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", "requires": { - "for-own": "0.1.5", - "is-extendable": "0.1.1" + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" } }, "on-finished": { @@ -2924,7 +8055,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "optimist": { @@ -2933,8 +8064,8 @@ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { - "minimist": "0.0.8", - "wordwrap": "0.0.2" + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" } }, "optionator": { @@ -2943,12 +8074,12 @@ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", "dev": true, "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" }, "dependencies": { "wordwrap": { @@ -2974,7 +8105,7 @@ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "requires": { - "lcid": "1.0.0" + "lcid": "^1.0.0" } }, "pako": { @@ -2987,11 +8118,11 @@ "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", "requires": { - "asn1.js": "4.9.1", - "browserify-aes": "1.0.8", - "create-hash": "1.1.3", - "evp_bytestokey": "1.0.3", - "pbkdf2": "3.0.14" + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3" } }, "parse-glob": { @@ -2999,10 +8130,10 @@ "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", "requires": { - "glob-base": "0.3.0", - "is-dotfile": "1.0.3", - "is-extglob": "1.0.0", - "is-glob": "2.0.1" + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" } }, "parse-json": { @@ -3010,7 +8141,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "requires": { - "error-ex": "1.3.1" + "error-ex": "^1.2.0" } }, "parseurl": { @@ -3028,7 +8159,7 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "requires": { - "pinkie-promise": "2.0.1" + "pinkie-promise": "^2.0.0" } }, "path-is-absolute": { @@ -3046,9 +8177,9 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "pathval": { @@ -3062,11 +8193,11 @@ "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz", "integrity": "sha512-gjsZW9O34fm0R7PaLHRJmLLVfSoesxztjPjE9o6R+qtVJij90ltg1joIovN9GKrRW3t1PzhDDG3UMEMFfZ+1wA==", "requires": { - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "ripemd160": "2.0.1", - "safe-buffer": "5.1.1", - "sha.js": "2.4.8" + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" } }, "performance-now": { @@ -3089,7 +8220,7 @@ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "requires": { - "pinkie": "2.0.4" + "pinkie": "^2.0.0" } }, "prelude-ls": { @@ -3118,7 +8249,7 @@ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz", "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=", "requires": { - "forwarded": "0.1.1", + "forwarded": "~0.1.0", "ipaddr.js": "1.4.0" } }, @@ -3132,11 +8263,11 @@ "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=", "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.1.3", - "parse-asn1": "5.1.0", - "randombytes": "2.0.5" + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1" } }, "punycode": { @@ -3164,8 +8295,8 @@ "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" + "is-number": "^3.0.0", + "kind-of": "^4.0.0" }, "dependencies": { "is-number": { @@ -3173,7 +8304,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -3181,7 +8312,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -3191,7 +8322,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -3201,7 +8332,7 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz", "integrity": "sha512-8T7Zn1AhMsQ/HI1SjcCfT/t4ii3eAqco3yOcSzS4mozsOz69lHLsoMXmF9nZgnFanYscnSlUSgs8uZyKzpE6kg==", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.1.0" } }, "range-parser": { @@ -3225,9 +8356,9 @@ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" } }, "read-pkg-up": { @@ -3235,8 +8366,8 @@ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" } }, "readable-stream": { @@ -3244,13 +8375,13 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.0.3", + "util-deprecate": "~1.0.1" } }, "readdirp": { @@ -3258,10 +8389,10 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", "requires": { - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "readable-stream": "2.3.3", - "set-immediate-shim": "1.0.1" + "graceful-fs": "^4.1.2", + "minimatch": "^3.0.2", + "readable-stream": "^2.0.2", + "set-immediate-shim": "^1.0.1" } }, "reflect-metadata": { @@ -3274,7 +8405,7 @@ "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", "requires": { - "is-equal-shallow": "0.1.3" + "is-equal-shallow": "^0.1.3" } }, "remove-trailing-separator": { @@ -3297,28 +8428,28 @@ "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.2", - "tunnel-agent": "0.6.0", - "uuid": "3.0.1" + "aws-sign2": "~0.6.0", + "aws4": "^1.2.1", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.0", + "forever-agent": "~0.6.1", + "form-data": "~2.1.1", + "har-validator": "~4.2.1", + "hawk": "~3.1.3", + "http-signature": "~1.1.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.7", + "oauth-sign": "~0.8.1", + "performance-now": "^0.2.0", + "qs": "~6.4.0", + "safe-buffer": "^5.0.1", + "stringstream": "~0.0.4", + "tough-cookie": "~2.3.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.0.0" }, "dependencies": { "qs": { @@ -3328,6 +8459,15 @@ } } }, + "require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "requires": { + "resolve-from": "^2.0.0", + "semver": "^5.1.0" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -3338,15 +8478,6 @@ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, - "require_optional": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", - "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", - "requires": { - "resolve-from": "2.0.0", - "semver": "5.4.1" - } - }, "resolve": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", @@ -3363,7 +8494,7 @@ "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", "requires": { - "align-text": "0.1.4" + "align-text": "^0.1.1" } }, "ripemd160": { @@ -3371,8 +8502,8 @@ "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", "requires": { - "hash-base": "2.0.2", - "inherits": "2.0.3" + "hash-base": "^2.0.0", + "inherits": "^2.0.1" } }, "safe-buffer": { @@ -3396,18 +8527,18 @@ "integrity": "sha1-mF+qPihLAnPHkzZKNcZze9k5Bbk=", "requires": { "debug": "2.6.8", - "depd": "1.1.1", - "destroy": "1.0.4", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "etag": "1.8.0", + "depd": "~1.1.1", + "destroy": "~1.0.4", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "etag": "~1.8.0", "fresh": "0.5.0", - "http-errors": "1.6.2", + "http-errors": "~1.6.2", "mime": "1.3.4", "ms": "2.0.0", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.3.1" + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.3.1" } }, "serve-static": { @@ -3415,9 +8546,9 @@ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.4.tgz", "integrity": "sha1-m2qpjutyU8Tu3Ewfb9vKYJkBqWE=", "requires": { - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "parseurl": "1.3.2", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "parseurl": "~1.3.1", "send": "0.15.4" } }, @@ -3446,7 +8577,7 @@ "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.8.tgz", "integrity": "sha1-NwaMLEdra69ALRSknGf1l5IfY08=", "requires": { - "inherits": "2.0.3" + "inherits": "^2.0.1" } }, "sntp": { @@ -3454,7 +8585,7 @@ "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", "requires": { - "hoek": "2.16.3" + "hoek": "2.x.x" } }, "source-list-map": { @@ -3472,7 +8603,7 @@ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", "requires": { - "spdx-license-ids": "1.2.2" + "spdx-license-ids": "^1.0.2" } }, "spdx-expression-parse": { @@ -3495,14 +8626,14 @@ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" }, "dependencies": { "assert-plus": { @@ -3527,8 +8658,8 @@ "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.3" + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" } }, "stream-http": { @@ -3536,21 +8667,11 @@ "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz", "integrity": "sha512-c0yTD2rbQzXtSsFSVhtpvY/vS6u066PcXOX9kBB3mSO76RiUQzL340uJkGBWnlBg4/HZzqiUXtaVA7wcRcJgEw==", "requires": { - "builtin-status-codes": "3.0.0", - "inherits": "2.0.3", - "readable-stream": "2.3.3", - "to-arraybuffer": "1.0.1", - "xtend": "4.0.1" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.2.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" } }, "string_decoder": { @@ -3558,7 +8679,17 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "stringstream": { @@ -3571,7 +8702,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-bom": { @@ -3579,7 +8710,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "requires": { - "is-utf8": "0.2.1" + "is-utf8": "^0.2.0" } }, "supports-color": { @@ -3587,7 +8718,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", "requires": { - "has-flag": "1.0.0" + "has-flag": "^1.0.0" } }, "tapable": { @@ -3600,7 +8731,7 @@ "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.4.tgz", "integrity": "sha512-uZYhyU3EX8O7HQP+J9fTVYwsq90Vr68xPEFo7yrVImIxYvHgukBEgOB/SgGoorWVTzGM/3Z+wUNnboA4M8jWrg==", "requires": { - "setimmediate": "1.0.5" + "setimmediate": "^1.0.4" } }, "to-arraybuffer": { @@ -3613,7 +8744,7 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", "requires": { - "punycode": "1.4.1" + "punycode": "^1.4.1" }, "dependencies": { "punycode": { @@ -3633,7 +8764,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -3648,7 +8779,7 @@ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { - "prelude-ls": "1.1.2" + "prelude-ls": "~1.1.2" } }, "type-detect": { @@ -3663,7 +8794,7 @@ "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", "requires": { "media-typer": "0.3.0", - "mime-types": "2.1.17" + "mime-types": "~2.1.15" } }, "typescript": { @@ -3677,9 +8808,9 @@ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", "requires": { - "source-map": "0.5.7", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" }, "dependencies": { "yargs": { @@ -3687,9 +8818,9 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", "window-size": "0.1.0" } } @@ -3755,8 +8886,8 @@ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", "requires": { - "spdx-correct": "1.0.2", - "spdx-expression-parse": "1.0.4" + "spdx-correct": "~1.0.0", + "spdx-expression-parse": "~1.0.0" } }, "vary": { @@ -3769,9 +8900,9 @@ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "requires": { - "assert-plus": "1.0.0", + "assert-plus": "^1.0.0", "core-util-is": "1.0.2", - "extsprintf": "1.3.0" + "extsprintf": "^1.2.0" }, "dependencies": { "assert-plus": { @@ -3794,9 +8925,9 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.4.0.tgz", "integrity": "sha1-ShRyvLuVK9Cpu0A2gB+VTfs5+qw=", "requires": { - "async": "2.5.0", - "chokidar": "1.7.0", - "graceful-fs": "4.1.11" + "async": "^2.1.2", + "chokidar": "^1.7.0", + "graceful-fs": "^4.1.2" } }, "webpack": { @@ -3804,27 +8935,27 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-2.7.0.tgz", "integrity": "sha512-MjAA0ZqO1ba7ZQJRnoCdbM56mmFpipOPUv/vQpwwfSI42p5PVDdoiuK2AL2FwFUVgT859Jr43bFZXRg/LNsqvg==", "requires": { - "acorn": "5.1.2", - "acorn-dynamic-import": "2.0.2", - "ajv": "4.11.8", - "ajv-keywords": "1.5.1", - "async": "2.5.0", - "enhanced-resolve": "3.4.1", - "interpret": "1.0.3", - "json-loader": "0.5.7", - "json5": "0.5.1", - "loader-runner": "2.3.0", - "loader-utils": "0.2.17", - "memory-fs": "0.4.1", - "mkdirp": "0.5.1", - "node-libs-browser": "2.0.0", - "source-map": "0.5.7", - "supports-color": "3.2.3", - "tapable": "0.2.8", - "uglify-js": "2.8.29", - "watchpack": "1.4.0", - "webpack-sources": "1.0.1", - "yargs": "6.6.0" + "acorn": "^5.0.0", + "acorn-dynamic-import": "^2.0.0", + "ajv": "^4.7.0", + "ajv-keywords": "^1.1.1", + "async": "^2.1.2", + "enhanced-resolve": "^3.3.0", + "interpret": "^1.0.0", + "json-loader": "^0.5.4", + "json5": "^0.5.1", + "loader-runner": "^2.3.0", + "loader-utils": "^0.2.16", + "memory-fs": "~0.4.1", + "mkdirp": "~0.5.0", + "node-libs-browser": "^2.0.0", + "source-map": "^0.5.3", + "supports-color": "^3.1.0", + "tapable": "~0.2.5", + "uglify-js": "^2.8.27", + "watchpack": "^1.3.1", + "webpack-sources": "^1.0.1", + "yargs": "^6.0.0" }, "dependencies": { "json5": { @@ -3839,8 +8970,8 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.0.1.tgz", "integrity": "sha512-05tMxipUCwHqYaVS8xc7sYPTly8PzXayRCB4dTxLhWTqlKUiwH6ezmEe0OSreL1c30LAuA3Zqmc+uEBUGFJDjw==", "requires": { - "source-list-map": "2.0.0", - "source-map": "0.5.7" + "source-list-map": "^2.0.0", + "source-map": "~0.5.3" } }, "which": { @@ -3849,7 +8980,7 @@ "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } }, "which-module": { @@ -3867,12 +8998,12 @@ "resolved": "https://registry.npmjs.org/winston/-/winston-2.3.1.tgz", "integrity": "sha1-C0hCDZeMAYBM8CMLZIhhWYIloRk=", "requires": { - "async": "1.0.0", - "colors": "1.0.3", - "cycle": "1.0.3", - "eyes": "0.1.8", - "isstream": "0.1.2", - "stack-trace": "0.0.10" + "async": "~1.0.0", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" }, "dependencies": { "async": { @@ -3892,8 +9023,8 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" } }, "wrappy": { @@ -3912,8 +9043,8 @@ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", "requires": { - "sax": "1.2.1", - "xmlbuilder": "4.2.1" + "sax": ">=0.6.0", + "xmlbuilder": "^4.1.0" } }, "xmlbuilder": { @@ -3921,7 +9052,7 @@ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", "requires": { - "lodash": "4.17.4" + "lodash": "^4.0.0" } }, "xtend": { @@ -3939,8 +9070,8 @@ "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.2.10.tgz", "integrity": "sha1-SBzHwlynOvWfWR8MluPOVsdXpA8=", "requires": { - "argparse": "1.0.9", - "glob": "7.1.2" + "argparse": "^1.0.7", + "glob": "^7.0.5" } }, "yargs": { @@ -3948,19 +9079,19 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", "requires": { - "camelcase": "3.0.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "os-locale": "1.4.0", - "read-pkg-up": "1.0.1", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "1.0.2", - "which-module": "1.0.0", - "y18n": "3.2.1", - "yargs-parser": "4.2.1" + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^4.2.0" }, "dependencies": { "camelcase": { @@ -3973,9 +9104,9 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" } } } @@ -3985,7 +9116,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", "requires": { - "camelcase": "3.0.0" + "camelcase": "^3.0.0" }, "dependencies": { "camelcase": { diff --git a/src/annotations/classes/aws/aws.ts b/src/annotations/classes/aws/aws.ts index 847fc11..7de5f86 100644 --- a/src/annotations/classes/aws/aws.ts +++ b/src/annotations/classes/aws/aws.ts @@ -2,7 +2,7 @@ import { CLASS_AWSMEMORYSIZEKEY, CLASS_AWSTIMEOUTKEY, CLASS_AWSRUNTIMEKEY } from import { defineMetadata } from '../../metadata' export const aws = (config: { - type?: 'nodejs8.10'|'nodejs12.x', + type?: 'nodejs12.x'|'nodejs16.x', memorySize?: number, timeout?: number }) => (target: Function) => { diff --git a/src/cli/commands/serverless.ts b/src/cli/commands/serverless.ts index 3cbbfc6..2b9bb21 100644 --- a/src/cli/commands/serverless.ts +++ b/src/cli/commands/serverless.ts @@ -93,7 +93,7 @@ export default (api) => { const def = serverless.functions[functionName] = { handler: `${nameKey}.${serviceDefinition.exportName}`, - runtime: getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs12.x" + runtime: getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs16.x" } await executor({ context, name: 'funtionEnvironments', method: funtionEnvironments }) diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index e4d4a49..4a30f78 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -284,7 +284,7 @@ export const lambdaResource = async (context) => { Handler: serviceDefinition.handler, MemorySize: serviceDefinition[CLASS_AWSMEMORYSIZEKEY] || getMetadata(CLASS_AWSMEMORYSIZEKEY, serviceDefinition.service), Role: serviceDefinition[CLASS_ROLEKEY] || getMetadata(CLASS_ROLEKEY, serviceDefinition.service), - Runtime: serviceDefinition[CLASS_AWSRUNTIMEKEY] || getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs12.x", + Runtime: serviceDefinition[CLASS_AWSRUNTIMEKEY] || getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs16.x", Timeout: serviceDefinition[CLASS_AWSTIMEOUTKEY] || getMetadata(CLASS_AWSTIMEOUTKEY, serviceDefinition.service), Environment: { Variables: serviceDefinition[CLASS_ENVIRONMENTKEY] || getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) diff --git a/src/cli/utilities/aws/lambda.ts b/src/cli/utilities/aws/lambda.ts index ddecb52..246b1b6 100644 --- a/src/cli/utilities/aws/lambda.ts +++ b/src/cli/utilities/aws/lambda.ts @@ -34,7 +34,7 @@ export const createLambdaFunction = ExecuteStep.register('CreateLambdaFunction', MemorySize: getMetadata(constants.CLASS_AWSMEMORYSIZEKEY, context.serviceDefinition.service), Publish: true, Role: getMetadata(constants.CLASS_ROLEKEY, context.serviceDefinition.service), - Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs12.x", + Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs16.x", Timeout: getMetadata(constants.CLASS_AWSTIMEOUTKEY, context.serviceDefinition.service), Environment: { Variables: getMetadata(constants.CLASS_ENVIRONMENTKEY, context.serviceDefinition.service) @@ -118,7 +118,7 @@ export const updateLambdaFunctionConfiguration = ExecuteStep.register('UpdateLam Handler: context.serviceDefinition.handler, MemorySize: getMetadata(constants.CLASS_AWSMEMORYSIZEKEY, context.serviceDefinition.service), Role: getMetadata(constants.CLASS_ROLEKEY, context.serviceDefinition.service), - Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs12.x", + Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs16.x", Timeout: getMetadata(constants.CLASS_AWSTIMEOUTKEY, context.serviceDefinition.service), VpcConfig: { } diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index a59d105..dfcd2bf 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -1269,14 +1269,14 @@ describe('annotations', () => { }) describe("aws", () => { it("type", () => { - @aws({ type: 'nodejs12.x' }) + @aws({ type: 'nodejs16.x' }) class RuntimeTestClass { } const runtimeValue = getMetadata(CLASS_AWSRUNTIMEKEY, RuntimeTestClass) const memoryValue = getMetadata(CLASS_AWSMEMORYSIZEKEY, RuntimeTestClass) const timeoutValue = getMetadata(CLASS_AWSTIMEOUTKEY, RuntimeTestClass) - expect(runtimeValue).to.equal('nodejs12.x') + expect(runtimeValue).to.equal('nodejs16.x') expect(memoryValue).to.undefined expect(timeoutValue).to.undefined }) @@ -1305,14 +1305,14 @@ describe('annotations', () => { expect(timeoutValue).to.equal(3) }) it("all", () => { - @aws({ type: 'nodejs12.x', memorySize: 100, timeout: 3 }) + @aws({ type: 'nodejs16.x', memorySize: 100, timeout: 3 }) class RuntimeTestClass { } const runtimeValue = getMetadata(CLASS_AWSRUNTIMEKEY, RuntimeTestClass) const memoryValue = getMetadata(CLASS_AWSMEMORYSIZEKEY, RuntimeTestClass) const timeoutValue = getMetadata(CLASS_AWSTIMEOUTKEY, RuntimeTestClass) - expect(runtimeValue).to.equal('nodejs12.x') + expect(runtimeValue).to.equal('nodejs16.x') expect(memoryValue).to.equal(100) expect(timeoutValue).to.equal(3) }) From ee590ad81cdd5d377102f5002e906d9197cbb081 Mon Sep 17 00:00:00 2001 From: Jaystack CI Date: Mon, 12 Sep 2022 12:30:37 +0000 Subject: [PATCH 185/196] 0.5.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 311c4c1..2b16a08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.4.0", + "version": "0.5.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 8a7e726..2a09cf8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.4.0", + "version": "0.5.0", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 9de7505fdfef7c0a290bc38ab688a4a47b37d946 Mon Sep 17 00:00:00 2001 From: Imre Kiss Date: Tue, 27 Feb 2024 16:57:52 +0100 Subject: [PATCH 186/196] feat: update the default node version --- README.md | 2 +- src/annotations/classes/aws/aws.ts | 2 +- src/cli/commands/serverless.ts | 2 +- src/cli/providers/cloudFormation/context/resources.ts | 2 +- src/cli/utilities/aws/lambda.ts | 4 ++-- test/annotation.tests.ts | 8 ++++---- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 864e2a5..9158bd6 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ Define a base class for FunctionalService to set basic Lambda settings in the AW ```js import { FunctionalService, aws } from 'functionly' -@aws({ type: 'nodejs16.x', memorySize: 512, timeout: 3 }) +@aws({ type: 'nodejs20.x', memorySize: 512, timeout: 3 }) export class TodoService extends FunctionalService { } ``` diff --git a/src/annotations/classes/aws/aws.ts b/src/annotations/classes/aws/aws.ts index 7de5f86..9241e20 100644 --- a/src/annotations/classes/aws/aws.ts +++ b/src/annotations/classes/aws/aws.ts @@ -2,7 +2,7 @@ import { CLASS_AWSMEMORYSIZEKEY, CLASS_AWSTIMEOUTKEY, CLASS_AWSRUNTIMEKEY } from import { defineMetadata } from '../../metadata' export const aws = (config: { - type?: 'nodejs12.x'|'nodejs16.x', + type?: 'nodejs16.x' | 'nodejs18.x' | 'nodejs20.x', memorySize?: number, timeout?: number }) => (target: Function) => { diff --git a/src/cli/commands/serverless.ts b/src/cli/commands/serverless.ts index 2b9bb21..13ef3af 100644 --- a/src/cli/commands/serverless.ts +++ b/src/cli/commands/serverless.ts @@ -93,7 +93,7 @@ export default (api) => { const def = serverless.functions[functionName] = { handler: `${nameKey}.${serviceDefinition.exportName}`, - runtime: getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs16.x" + runtime: getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs20.x" } await executor({ context, name: 'funtionEnvironments', method: funtionEnvironments }) diff --git a/src/cli/providers/cloudFormation/context/resources.ts b/src/cli/providers/cloudFormation/context/resources.ts index 4a30f78..1937662 100644 --- a/src/cli/providers/cloudFormation/context/resources.ts +++ b/src/cli/providers/cloudFormation/context/resources.ts @@ -284,7 +284,7 @@ export const lambdaResource = async (context) => { Handler: serviceDefinition.handler, MemorySize: serviceDefinition[CLASS_AWSMEMORYSIZEKEY] || getMetadata(CLASS_AWSMEMORYSIZEKEY, serviceDefinition.service), Role: serviceDefinition[CLASS_ROLEKEY] || getMetadata(CLASS_ROLEKEY, serviceDefinition.service), - Runtime: serviceDefinition[CLASS_AWSRUNTIMEKEY] || getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs16.x", + Runtime: serviceDefinition[CLASS_AWSRUNTIMEKEY] || getMetadata(CLASS_AWSRUNTIMEKEY, serviceDefinition.service) || "nodejs20.x", Timeout: serviceDefinition[CLASS_AWSTIMEOUTKEY] || getMetadata(CLASS_AWSTIMEOUTKEY, serviceDefinition.service), Environment: { Variables: serviceDefinition[CLASS_ENVIRONMENTKEY] || getMetadata(CLASS_ENVIRONMENTKEY, serviceDefinition.service) diff --git a/src/cli/utilities/aws/lambda.ts b/src/cli/utilities/aws/lambda.ts index 246b1b6..fe2d97d 100644 --- a/src/cli/utilities/aws/lambda.ts +++ b/src/cli/utilities/aws/lambda.ts @@ -34,7 +34,7 @@ export const createLambdaFunction = ExecuteStep.register('CreateLambdaFunction', MemorySize: getMetadata(constants.CLASS_AWSMEMORYSIZEKEY, context.serviceDefinition.service), Publish: true, Role: getMetadata(constants.CLASS_ROLEKEY, context.serviceDefinition.service), - Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs16.x", + Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs20.x", Timeout: getMetadata(constants.CLASS_AWSTIMEOUTKEY, context.serviceDefinition.service), Environment: { Variables: getMetadata(constants.CLASS_ENVIRONMENTKEY, context.serviceDefinition.service) @@ -118,7 +118,7 @@ export const updateLambdaFunctionConfiguration = ExecuteStep.register('UpdateLam Handler: context.serviceDefinition.handler, MemorySize: getMetadata(constants.CLASS_AWSMEMORYSIZEKEY, context.serviceDefinition.service), Role: getMetadata(constants.CLASS_ROLEKEY, context.serviceDefinition.service), - Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs16.x", + Runtime: getMetadata(constants.CLASS_AWSRUNTIMEKEY, context.serviceDefinition.service) || "nodejs20.x", Timeout: getMetadata(constants.CLASS_AWSTIMEOUTKEY, context.serviceDefinition.service), VpcConfig: { } diff --git a/test/annotation.tests.ts b/test/annotation.tests.ts index dfcd2bf..0680fc8 100644 --- a/test/annotation.tests.ts +++ b/test/annotation.tests.ts @@ -1269,14 +1269,14 @@ describe('annotations', () => { }) describe("aws", () => { it("type", () => { - @aws({ type: 'nodejs16.x' }) + @aws({ type: 'nodejs20.x' }) class RuntimeTestClass { } const runtimeValue = getMetadata(CLASS_AWSRUNTIMEKEY, RuntimeTestClass) const memoryValue = getMetadata(CLASS_AWSMEMORYSIZEKEY, RuntimeTestClass) const timeoutValue = getMetadata(CLASS_AWSTIMEOUTKEY, RuntimeTestClass) - expect(runtimeValue).to.equal('nodejs16.x') + expect(runtimeValue).to.equal('nodejs20.x') expect(memoryValue).to.undefined expect(timeoutValue).to.undefined }) @@ -1305,14 +1305,14 @@ describe('annotations', () => { expect(timeoutValue).to.equal(3) }) it("all", () => { - @aws({ type: 'nodejs16.x', memorySize: 100, timeout: 3 }) + @aws({ type: 'nodejs20.x', memorySize: 100, timeout: 3 }) class RuntimeTestClass { } const runtimeValue = getMetadata(CLASS_AWSRUNTIMEKEY, RuntimeTestClass) const memoryValue = getMetadata(CLASS_AWSMEMORYSIZEKEY, RuntimeTestClass) const timeoutValue = getMetadata(CLASS_AWSTIMEOUTKEY, RuntimeTestClass) - expect(runtimeValue).to.equal('nodejs16.x') + expect(runtimeValue).to.equal('nodejs20.x') expect(memoryValue).to.equal(100) expect(timeoutValue).to.equal(3) }) From 8b6ad98ced07956ac469bf597f2a9319d6e9cdf3 Mon Sep 17 00:00:00 2001 From: Tony Csernok <92568895+csernokn123@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:06:19 +0100 Subject: [PATCH 187/196] Add initial gh workflow --- .github/workflows/main.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..f875d25 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,25 @@ +name: Functionly build and publish + +on: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v3 + - name: Set up Docker + uses: docker/setup-buildx-action@v1 + - name: Build and Test with Coverage + run: | + docker run --rm \ + -e PROPERTIES="BUILD_ID:${{ github.run_number }}" \ + -w "/app" \ + -v $PWD:/app \ + node:8 bash -c "\ + npm i --no-save -q; \ + npm run build; \ + npm run coverage -- --reporter mocha-junit-reporter; \ + chown -R $(id -u):$(id -g) ." From 2a35d5899fc3392c17dadbf7cd802b0366926f21 Mon Sep 17 00:00:00 2001 From: Tony Csernok <92568895+csernokn123@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:10:11 +0100 Subject: [PATCH 188/196] Fix: Docker command format in gha workflow --- .github/workflows/main.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f875d25..5977d3f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,11 +15,8 @@ jobs: - name: Build and Test with Coverage run: | docker run --rm \ - -e PROPERTIES="BUILD_ID:${{ github.run_number }}" \ - -w "/app" \ - -v $PWD:/app \ - node:8 bash -c "\ - npm i --no-save -q; \ - npm run build; \ - npm run coverage -- --reporter mocha-junit-reporter; \ - chown -R $(id -u):$(id -g) ." + -e PROPERTIES="BUILD_ID:${{ github.run_number }}" \ + -w "/app" \ + -v $PWD:/app \ + node:8 bash -c "npm i --no-save -q; npm run build; npm run coverage -- --reporter mocha-junit-reporter; chown -R $(id -u):$(id -g) ." + From 654bfbd34c6cc0c46b73266535e2aba546e044f7 Mon Sep 17 00:00:00 2001 From: Tony Csernok <92568895+csernokn123@users.noreply.github.com> Date: Mon, 4 Mar 2024 15:04:23 +0100 Subject: [PATCH 189/196] Update main.yml Update: gha workflow with npm publish step --- .github/workflows/main.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5977d3f..15d0356 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,4 +19,17 @@ jobs: -w "/app" \ -v $PWD:/app \ node:8 bash -c "npm i --no-save -q; npm run build; npm run coverage -- --reporter mocha-junit-reporter; chown -R $(id -u):$(id -g) ." - + - name: Setup .npmrc + run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Build, Version, and Publish to npm using Docker + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_VERSION: '1.0.0' + run: | + docker run --rm \ + -w "/app" \ + -v $PWD:/app \ + -v ${{ github.workspace }}/.npmrc:/root/.npmrc \ + node:16 bash -c "npm i --no-save -q; npm run build; npm version ${NPM_VERSION} > version.txt; npm publish --tag dev; chown -R $(id -u):$(id -g) ." From 2ad8dfd8bf6cfefe01a8fa998b6f74cfcb25ead1 Mon Sep 17 00:00:00 2001 From: Tony Csernok <92568895+csernokn123@users.noreply.github.com> Date: Mon, 4 Mar 2024 15:13:50 +0100 Subject: [PATCH 190/196] Fix: docker command format in the npm publish step --- .github/workflows/main.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 15d0356..9b85be5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,13 +23,12 @@ jobs: run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Build, Version, and Publish to npm using Docker + - name: Build and Publish to npmjs env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NPM_VERSION: '1.0.0' + NPM_VERSION: '1.0.0' run: | docker run --rm \ -w "/app" \ - -v $PWD:/app \ - -v ${{ github.workspace }}/.npmrc:/root/.npmrc \ - node:16 bash -c "npm i --no-save -q; npm run build; npm version ${NPM_VERSION} > version.txt; npm publish --tag dev; chown -R $(id -u):$(id -g) ." + -v "$PWD:/app" \ + -v "${{ github.workspace }}/.npmrc:/root/.npmrc" \ + node:8 bash -c "npm i --no-save -q; npm run build; npm version ${NPM_VERSION} > version.txt; npm publish --tag dev; chown -R $(id -u):$(id -g) ." From e7df39c220c808c37268e6e133aa4de9c19c7abd Mon Sep 17 00:00:00 2001 From: Imre Kiss Date: Mon, 4 Mar 2024 16:39:23 +0100 Subject: [PATCH 191/196] fix: remove the aws-sdk extrenals config --- src/cli/context/steppes/codeCompile.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/cli/context/steppes/codeCompile.ts b/src/cli/context/steppes/codeCompile.ts index 347f261..f81d81f 100644 --- a/src/cli/context/steppes/codeCompile.ts +++ b/src/cli/context/steppes/codeCompile.ts @@ -110,9 +110,6 @@ export const bundleConfig = ExecuteStep.register('WebpackBundleConfig', async (c }) const externals = {} - if (context.deployTarget === 'aws') { - externals['aws-sdk'] = 'commonjs aws-sdk' - } let compile = {} if (projectConfig.compile) { @@ -183,4 +180,4 @@ export const watchConfig = ExecuteStep.register('WebpackWatchConfig', (context) ignored: /dist/ } } -}) \ No newline at end of file +}) From 16f0ac384160f67e58419087a026e659cebc2caf Mon Sep 17 00:00:00 2001 From: Tony Csernok <92568895+csernokn123@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:46:03 +0100 Subject: [PATCH 192/196] 0.6.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2a09cf8..5412099 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.5.0", + "version": "0.6.0", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From fff64ce6c61543f05bb96879ddfe8d0cb4e2c4fd Mon Sep 17 00:00:00 2001 From: Norbert Csernok Date: Mon, 4 Mar 2024 21:34:17 +0100 Subject: [PATCH 193/196] Add: refactor gha workflow --- .github/workflows/main.yml | 114 ++++++++++++++++++++++++++++++------- 1 file changed, 95 insertions(+), 19 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9b85be5..b90a48f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,31 +4,107 @@ on: workflow_dispatch: jobs: - build: + dev: runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v3 - - name: Set up Docker - uses: docker/setup-buildx-action@v1 - - name: Build and Test with Coverage - run: | - docker run --rm \ - -e PROPERTIES="BUILD_ID:${{ github.run_number }}" \ - -w "/app" \ - -v $PWD:/app \ - node:8 bash -c "npm i --no-save -q; npm run build; npm run coverage -- --reporter mocha-junit-reporter; chown -R $(id -u):$(id -g) ." - - name: Setup .npmrc - run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '8' + - name: Install dependencies + run: npm i --no-save -q + - name: Setup .npmrc for npmjs + run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Build and Publish to npmjs + - name: Auto Increment Version, and Publish to npmjs + run: | + git config --global user.email "jaystack-ci@jaystack.com" + git config --global user.name "jaystack-ci" + git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} + npm version patch -m "Auto-increment version: %s" --force + npm publish --tag dev + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Git operations + run: | + git tag -f dev + git push -f --tags + git push + stage: + needs: dev + runs-on: ubuntu-latest + environment: stage + permissions: + contents: write + steps: + - uses: actions/checkout@v3 + with: + ref: 'refs/tags/dev' + + - name: Configure npm + run: echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" > .npmrc + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Extract package version + id: package_version + run: echo "::set-output name=VERSION::$(jq -r .version package.json)" + shell: bash + + - name: Configure Git + run: | + git config --global user.email "jaystack-ci@jaystack.com" + git config --global user.name "jaystack-ci" + git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} + + - name: Create and push git tag + run: | + git tag -f stage + git push -f --tags + + - name: Publish to npm + run: npm dist-tag add functionly@${{ steps.package_version.outputs.VERSION }} stage + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + live: + needs: stage + runs-on: ubuntu-latest + environment: live + permissions: + contents: write + steps: + - uses: actions/checkout@v3 + with: + ref: 'refs/tags/stage' + + - name: Configure npm + run: echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" > .npmrc env: - NPM_VERSION: '1.0.0' + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Extract package version + id: package_version + run: echo "::set-output name=VERSION::$(jq -r .version package.json)" + shell: bash + + - name: Configure Git + run: | + git config --global user.email "jaystack-ci@jaystack.com" + git config --global user.name "jaystack-ci" + git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} + + - name: Create and push git tag run: | - docker run --rm \ - -w "/app" \ - -v "$PWD:/app" \ - -v "${{ github.workspace }}/.npmrc:/root/.npmrc" \ - node:8 bash -c "npm i --no-save -q; npm run build; npm version ${NPM_VERSION} > version.txt; npm publish --tag dev; chown -R $(id -u):$(id -g) ." + git tag -f latest + git push -f --tags + + - name: Publish to npm + run: npm dist-tag add functionly@${{ steps.package_version.outputs.VERSION }} latest + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + From 82bc369624aa4489c2d7159d275440d0837d667e Mon Sep 17 00:00:00 2001 From: Norbert Csernok Date: Mon, 4 Mar 2024 21:38:59 +0100 Subject: [PATCH 194/196] Chore: update package version manually in the package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5412099..b2551e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.6.0", + "version": "0.6.1", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 408f2da212d3dfe0b79c6502b2d8dd3bb9708ede Mon Sep 17 00:00:00 2001 From: jaystack-ci Date: Mon, 4 Mar 2024 20:39:31 +0000 Subject: [PATCH 195/196] Auto-increment version: 0.6.2 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2b16a08..075f6af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.5.0", + "version": "0.6.2", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index b2551e8..191042d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.6.1", + "version": "0.6.2", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts", From 498ac3c3618509e09be3c166393520de246ba113 Mon Sep 17 00:00:00 2001 From: jaystack-ci Date: Mon, 26 May 2025 11:53:11 +0000 Subject: [PATCH 196/196] Auto-increment version: 0.6.3 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 075f6af..a9a6296 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.6.2", + "version": "0.6.3", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 191042d..7f3b876 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functionly", - "version": "0.6.2", + "version": "0.6.3", "description": "", "main": "lib/src/index", "types": "lib/src/index.d.ts",