@creator.co/wapi 1.1.8 → 1.2.0-alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/.eslintrc.cjs +22 -22
  2. package/dist/index.d.ts +2 -1
  3. package/dist/index.js +3 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/package.json +48 -0
  6. package/dist/src/API/Request.d.ts +4 -5
  7. package/dist/src/API/Request.js +3 -4
  8. package/dist/src/API/Request.js.map +1 -1
  9. package/dist/src/API/Response.d.ts +1 -1
  10. package/dist/src/API/Response.js.map +1 -1
  11. package/dist/src/BaseEvent/EventProcessor.js +0 -1
  12. package/dist/src/BaseEvent/EventProcessor.js.map +1 -1
  13. package/dist/src/BaseEvent/Process.d.ts +0 -1
  14. package/dist/src/BaseEvent/Process.js +1 -2
  15. package/dist/src/BaseEvent/Process.js.map +1 -1
  16. package/dist/src/BaseEvent/Transaction.d.ts +6 -6
  17. package/dist/src/BaseEvent/Transaction.js +2 -4
  18. package/dist/src/BaseEvent/Transaction.js.map +1 -1
  19. package/dist/src/Config/EnvironmentVar.d.ts +17 -0
  20. package/dist/src/Config/EnvironmentVar.js +155 -0
  21. package/dist/src/Config/EnvironmentVar.js.map +1 -0
  22. package/dist/src/Globals.d.ts +11 -0
  23. package/dist/src/Globals.js +12 -0
  24. package/dist/src/Globals.js.map +1 -1
  25. package/dist/src/Logger/Logger.js +42 -11
  26. package/dist/src/Logger/Logger.js.map +1 -1
  27. package/dist/src/Mailer/Mailer.js +50 -24
  28. package/dist/src/Mailer/Mailer.js.map +1 -1
  29. package/dist/src/Server/Router.d.ts +30 -0
  30. package/dist/src/Server/Router.js +21 -0
  31. package/dist/src/Server/Router.js.map +1 -0
  32. package/dist/src/Server/lib/ContainerServer.d.ts +11 -0
  33. package/dist/src/Server/lib/ContainerServer.js +100 -0
  34. package/dist/src/Server/lib/ContainerServer.js.map +1 -0
  35. package/dist/src/Server/lib/Server.d.ts +9 -0
  36. package/dist/src/Server/lib/Server.js +137 -0
  37. package/dist/src/Server/lib/Server.js.map +1 -0
  38. package/dist/src/Server/lib/container/GenericHandler.d.ts +4 -0
  39. package/dist/src/Server/lib/container/GenericHandler.js +138 -0
  40. package/dist/src/Server/lib/container/GenericHandler.js.map +1 -0
  41. package/dist/src/Server/lib/container/GenericHandlerEvent.d.ts +14 -0
  42. package/dist/src/Server/lib/container/GenericHandlerEvent.js +164 -0
  43. package/dist/src/Server/lib/container/GenericHandlerEvent.js.map +1 -0
  44. package/dist/src/Server/lib/container/HealthHandler.d.ts +3 -0
  45. package/dist/src/Server/lib/container/HealthHandler.js +44 -0
  46. package/dist/src/Server/lib/container/HealthHandler.js.map +1 -0
  47. package/dist/src/Server/lib/container/Proxy.d.ts +15 -0
  48. package/dist/src/Server/lib/container/Proxy.js +153 -0
  49. package/dist/src/Server/lib/container/Proxy.js.map +1 -0
  50. package/dist/src/Server/lib/container/Utils.d.ts +6 -0
  51. package/dist/src/Server/lib/container/Utils.js +109 -0
  52. package/dist/src/Server/lib/container/Utils.js.map +1 -0
  53. package/dist/src/Validation/Validator.d.ts +5 -0
  54. package/dist/src/Validation/Validator.js +39 -0
  55. package/dist/src/Validation/Validator.js.map +1 -0
  56. package/index.ts +2 -0
  57. package/package.json +12 -3
  58. package/src/API/Request.ts +6 -12
  59. package/src/API/Response.ts +1 -1
  60. package/src/BaseEvent/EventProcessor.ts +3 -2
  61. package/src/BaseEvent/Process.ts +4 -4
  62. package/src/BaseEvent/Transaction.ts +24 -16
  63. package/src/Config/EnvironmentVar.ts +94 -0
  64. package/src/Globals.ts +15 -0
  65. package/src/Server/Router.ts +51 -0
  66. package/src/Server/lib/ContainerServer.ts +27 -0
  67. package/src/Server/lib/Server.ts +66 -0
  68. package/src/Server/lib/container/GenericHandler.ts +63 -0
  69. package/src/Server/lib/container/GenericHandlerEvent.ts +133 -0
  70. package/src/Server/lib/container/HealthHandler.ts +5 -0
  71. package/src/Server/lib/container/Proxy.ts +107 -0
  72. package/src/Server/lib/container/Utils.ts +45 -0
  73. package/src/Validation/Validator.ts +35 -0
  74. package/tsconfig.json +2 -0
@@ -0,0 +1,66 @@
1
+ import { APIGatewayProxyEvent, Context } from "aws-lambda"
2
+ import { pathToRegexp } from "path-to-regexp"
3
+
4
+ import Response from "../../API/Response"
5
+ import Transaction from "../../BaseEvent/Transaction"
6
+ import Validator from "../../Validation/Validator"
7
+ import { RouterConfig } from "../Router"
8
+
9
+ export default class Server {
10
+ protected readonly config: RouterConfig
11
+ constructor(config: RouterConfig) {
12
+ this.config = config
13
+ }
14
+
15
+ public getExport(): CallableFunction {
16
+ return this.handleServerlessEvent
17
+ }
18
+
19
+ public async handleServerlessEvent(
20
+ event: APIGatewayProxyEvent,
21
+ context: Context,
22
+ ) {
23
+ // init transaction
24
+ await new Transaction(event, context, this.config).execute(
25
+ async (transaction) => {
26
+ for (const route of this.config.routes) {
27
+ // Check for possible paths
28
+ const handler = this.routeMatches(route.path, route.method, event)
29
+ if (handler) {
30
+ transaction.logger.log("Router accepted route:", route)
31
+ // Validate input?
32
+ if (route.inputValidation) {
33
+ const validationResp = Validator.validateSchema(
34
+ transaction.request.getBody(),
35
+ route.inputValidation,
36
+ )
37
+ if (validationResp && validationResp instanceof Response)
38
+ return validationResp
39
+ }
40
+ // Continue to route handler
41
+ return await route.handler(transaction)
42
+ }
43
+ }
44
+ //No route found :/
45
+ return new Response(404, { err: "Route not found!" })
46
+ },
47
+ )
48
+ }
49
+ // Helper function to match route
50
+ private routeMatches(
51
+ routePath: string,
52
+ routeMethod: string,
53
+ event: APIGatewayProxyEvent,
54
+ ): boolean {
55
+ const path = event.path
56
+ const keys = []
57
+ const result = pathToRegexp(routePath, keys).exec(path)
58
+ if (result) {
59
+ const reqMethod = event.httpMethod
60
+ if (reqMethod.toLowerCase() == routeMethod.toLowerCase()) {
61
+ return true
62
+ }
63
+ }
64
+ return false
65
+ }
66
+ }
@@ -0,0 +1,63 @@
1
+ import { Request, Response } from "express"
2
+
3
+ import GenericHandlerEvent, {
4
+ GenericHandlerEventResponse,
5
+ } from "./GenericHandlerEvent"
6
+ import Globals from "../../../Globals"
7
+ import Server from "../Server"
8
+
9
+ export default (serverlessHandler: Server["handleServerlessEvent"]) => {
10
+ return async (request: Request, res: Response) => {
11
+ const startTime = Date.now()
12
+ let resp = null
13
+ try {
14
+ // Generate event with request stuff (http to serverless translation)
15
+ const event = new GenericHandlerEvent(request, serverlessHandler)
16
+ // Invoke
17
+ const invokationResp = await event.invoke()
18
+ // Respond
19
+ resp = processServerlessResponse(invokationResp, res)
20
+ } catch (e) {
21
+ console.error("[Proxy] - Exception during execution!", e)
22
+ resp = res
23
+ .json({
24
+ ...e,
25
+ err: Globals.Resp_MSG_EXCEPTION,
26
+ errCode: Globals.Resp_CODE_EXCEPTION,
27
+ })
28
+ .status(Globals.Resp_STATUSCODE_EXCEPTION) //bad gateway
29
+ }
30
+ console.debug(`[Proxy] - Request took ${Date.now() - startTime}ms`)
31
+ return resp
32
+ }
33
+ }
34
+ const processServerlessResponse = (
35
+ invokation: GenericHandlerEventResponse,
36
+ res: Response,
37
+ ) => {
38
+ // translate answer to http layer
39
+ if (invokation && invokation.err) {
40
+ // err came
41
+ return res.json({ err: invokation.err }).status(400)
42
+ } else if (!invokation || !invokation.data) {
43
+ // invalid response came
44
+ return res
45
+ .json({
46
+ err: Globals.Resp_MSG_INVALIDRESP,
47
+ errCode: Globals.Resp_CODE_INVALIDRESP,
48
+ })
49
+ .status(Globals.Resp_STATUSCODE_INVALIDRESP)
50
+ } else {
51
+ // valid
52
+ const response = res
53
+ .json(invokation.data.body || {})
54
+ .status(invokation.data.statusCode)
55
+ // Check for headers
56
+ if (invokation?.data?.headers) {
57
+ for (const hKey of Object.keys(invokation.data.headers)) {
58
+ response.header(hKey, invokation.data.headers[hKey])
59
+ }
60
+ }
61
+ return response
62
+ }
63
+ }
@@ -0,0 +1,133 @@
1
+ import type { APIGatewayProxyEvent, Context } from "aws-lambda"
2
+ import * as cuid from "cuid"
3
+ import { Request } from "express"
4
+ import unflatten from "unflatten"
5
+
6
+ import {
7
+ parseMultiValueHeaders,
8
+ parseMultiValueQueryStringParameters,
9
+ parseQueryStringParameters,
10
+ nullIfEmpty,
11
+ } from "./Utils"
12
+ import Globals from "../../../Globals"
13
+ import Server from "../Server"
14
+ //
15
+ export type GenericHandlerEventResponse = { err?: Error | string; data?: any }
16
+ //
17
+ export default class GenericHandlerEvent {
18
+ public request: Request
19
+ public serverlessHandler: Server["handleServerlessEvent"]
20
+ constructor(
21
+ request: Request,
22
+ serverlessHandler: Server["handleServerlessEvent"],
23
+ ) {
24
+ this.request = request
25
+ this.serverlessHandler = serverlessHandler
26
+ }
27
+ public async invoke(): Promise<GenericHandlerEventResponse> {
28
+ // eslint-disable-next-line no-async-promise-executor
29
+ return new Promise(async (resolve, reject) => {
30
+ try {
31
+ // Build event and context
32
+ const event = this.buildEvent()
33
+ const context = this.buildContext(
34
+ event,
35
+ (err?: Error | string, data?: any) => {
36
+ resolve({ err, data })
37
+ },
38
+ )
39
+ // Invoke
40
+ await this.serverlessHandler(event, context)
41
+ } catch (e) {
42
+ reject(e) // forward rejection
43
+ }
44
+ })
45
+ }
46
+ /* private */
47
+ private buildEvent(): APIGatewayProxyEvent {
48
+ return {
49
+ body: this.request.body || null, //enforce key
50
+ headers: this.request.headers ? unflatten(this.request.headers, 2) : [],
51
+ httpMethod: this.request.method
52
+ ? this.request.method.toUpperCase()
53
+ : null,
54
+ isBase64Encoded: false,
55
+ multiValueHeaders: parseMultiValueHeaders(
56
+ this.request.headers ? this.request.headers || [] : [],
57
+ ),
58
+ multiValueQueryStringParameters: parseMultiValueQueryStringParameters(
59
+ this.request.url,
60
+ ),
61
+ path: this.request.path,
62
+ pathParameters: this.request.params
63
+ ? nullIfEmpty(this.request.params)
64
+ : null,
65
+ queryStringParameters: this.request.query
66
+ ? parseQueryStringParameters(this.request.query)
67
+ : null,
68
+ requestContext: {
69
+ accountId: process.env.AWS_ACCOUNT_ID || null,
70
+ apiId: null,
71
+ authorizer: null,
72
+ domainName: null,
73
+ domainPrefix: null,
74
+ extendedRequestId: cuid(),
75
+ httpMethod: this.request.method
76
+ ? this.request.method.toUpperCase()
77
+ : null,
78
+ identity: {
79
+ accessKey: null,
80
+ accountId: process.env.AWS_ACCOUNT_ID || null,
81
+ caller: null,
82
+ apiKey: null,
83
+ apiKeyId: null,
84
+ clientCert: null,
85
+ cognitoAuthenticationProvider: null,
86
+ cognitoAuthenticationType: null,
87
+ cognitoIdentityId: null,
88
+ cognitoIdentityPoolId: null,
89
+ principalOrgId: null,
90
+ sourceIp:
91
+ <string>this.request.headers?.["x-forwarded-for"] ||
92
+ this.request.socket.remoteAddress,
93
+ user: null,
94
+ userAgent: this.request.headers
95
+ ? this.request.headers["user-agent"]
96
+ : null,
97
+ userArn: null,
98
+ },
99
+ path: this.request.path,
100
+ protocol: "HTTP/1.1",
101
+ requestId: `${cuid()}-${cuid()}`,
102
+ requestTime: new Date().toISOString(),
103
+ requestTimeEpoch: Date.now(),
104
+ resourceId: null,
105
+ resourcePath: Globals.Listener_HTTP_ProxyRoute,
106
+ stage: process.env.STAGE,
107
+ },
108
+ resource: Globals.Listener_HTTP_ProxyRoute,
109
+ stageVariables: null,
110
+ }
111
+ }
112
+ private buildContext(
113
+ event: APIGatewayProxyEvent,
114
+ callback: (err?: Error | string, data?: any) => void,
115
+ ): Context {
116
+ return {
117
+ awsRequestId: event.requestContext.requestId,
118
+ callbackWaitsForEmptyEventLoop: true,
119
+ getRemainingTimeInMillis: () => {
120
+ return 0
121
+ },
122
+ done: (err, data) => callback(err, data),
123
+ fail: (err) => callback(err),
124
+ succeed: (res) => callback(null, res),
125
+ functionName: null,
126
+ functionVersion: "LATEST",
127
+ invokedFunctionArn: null,
128
+ memoryLimitInMB: "-1",
129
+ logGroupName: null,
130
+ logStreamName: null,
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,5 @@
1
+ import { Request, Response } from "express"
2
+
3
+ export default async (_request: Request, res: Response) => {
4
+ return res.send("Healthy!")
5
+ }
@@ -0,0 +1,107 @@
1
+ import { Server as HTTPServer, createServer } from "http"
2
+
3
+ import cors from "cors"
4
+ import * as express from "express"
5
+
6
+ import Server from "./../Server"
7
+ import GenericHandler from "./GenericHandler"
8
+ import HealthHandler from "./HealthHandler"
9
+ import { version as appVersion } from "../../../../package.json"
10
+ import Globals from "../../../Globals"
11
+ import { RouterConfig } from "../../Router"
12
+
13
+ export default class Proxy {
14
+ private stopping: boolean
15
+ private readonly config: RouterConfig
16
+ private readonly app: express.Express
17
+ private readonly serverlessHandler: Server["handleServerlessEvent"]
18
+ private listener: HTTPServer
19
+ constructor(
20
+ config: RouterConfig,
21
+ serverlessHandler: Server["handleServerlessEvent"],
22
+ ) {
23
+ this.stopping = false
24
+ this.config = config
25
+ this.serverlessHandler = serverlessHandler
26
+ this.app = express()
27
+ this.app.use(
28
+ cors(
29
+ this.config.cors
30
+ ? {
31
+ options: {
32
+ origin: this.config?.cors?.origin,
33
+ allowedHeaders: this.config?.cors?.headers,
34
+ credentials: !!this.config?.cors?.allowCredentials,
35
+ },
36
+ }
37
+ : {},
38
+ ),
39
+ )
40
+
41
+ // //This supposedly fix some 502 codes where nodejs socket would hang during
42
+ // //a request and if behind ALB, it would cause 502 codes. Had experiencied this
43
+ // //and 502 codes reduced dramastically, but still some appearances. Maybe this
44
+ // //is just a palliative work-around for the real issue; TODO: need to investigate
45
+ // //in the future.
46
+ // this.listener.listener.keepAliveTimeout = 120e3
47
+ // this.listener.listener.headersTimeout = 120e3
48
+ }
49
+ async load() {
50
+ await this.startListeners()
51
+ this.installRoutes()
52
+ }
53
+ async unload(err) {
54
+ await this.stopListeners(err)
55
+ }
56
+ /* lifecycle */
57
+ private async startListeners(): Promise<void> {
58
+ return new Promise((resolve) => {
59
+ const port = this.config.port || Globals.Listener_HTTP_DefaultPort
60
+ console.log(`[Proxy] - [STARTING] - v.${appVersion} - :${port}`)
61
+ // Create Server
62
+ this.listener = createServer(this.app)
63
+ // Set defaults
64
+ this.listener.setTimeout(
65
+ this.config.timeout || Globals.Listener_HTTP_DefaultTimeout,
66
+ )
67
+ // Start Server
68
+ this.listener.listen(port, () => {
69
+ console.log(`[Proxy] - [STARTED]`)
70
+ resolve()
71
+ })
72
+ })
73
+ }
74
+ private async stopListeners(err) {
75
+ if (this.stopping) return
76
+ this.stopping = true
77
+ console.debug("[Proxy] - [STOPPING]")
78
+ this.listener.close((_err) => {
79
+ if (err || _err) console.log("[Proxy] - exit output:", err || _err)
80
+ console.log("[Proxy] - [STOPPED]")
81
+ process.exit(err || _err ? 1 : 0)
82
+ })
83
+ }
84
+ /* routing */
85
+ private installRoutes() {
86
+ //Health check route -- This is a bypass route to only check if
87
+ //runtime proxy is working and responding to calls.
88
+ console.log(
89
+ `[Proxy] - [HEALTH-ROUTE] - ${
90
+ this.config.healthCheckRoute ||
91
+ Globals.Listener_HTTP_DefaultHealthCheckRoute
92
+ }`,
93
+ )
94
+ this.app
95
+ .route(
96
+ this.config.healthCheckRoute ||
97
+ Globals.Listener_HTTP_DefaultHealthCheckRoute,
98
+ )
99
+ .get(HealthHandler)
100
+ //Main route -- We use a wildcard route because is not the job of the runtime and neither
101
+ //the task to deny/constrain routes that invoked this task; all the job is done by the
102
+ //load balancer and we just foward everything we have to the function.
103
+ this.app
104
+ .route(Globals.Listener_HTTP_ProxyRoute)
105
+ .all(GenericHandler(this.serverlessHandler))
106
+ }
107
+ }
@@ -0,0 +1,45 @@
1
+ import unflatten from "unflatten"
2
+ // https://aws.amazon.com/blogs/compute/support-for-multi-value-parameters-in-amazon-api-gateway/
3
+ // (rawHeaders: Array<string>): { [string]: Array<string> }
4
+ export function parseMultiValueHeaders(rawHeaders) {
5
+ if (rawHeaders.length === 0) return null
6
+ //
7
+ const map = new Map()
8
+ const unflattened = unflatten(rawHeaders, 2)
9
+ // eslint-disable-next-line no-restricted-syntax
10
+ for (const key of Object.keys(unflattened)) {
11
+ const item = map.get(key)
12
+ if (item) item.push(unflattened[key])
13
+ else map.set(key, [unflattened[key]])
14
+ }
15
+ return Object.fromEntries(map)
16
+ }
17
+ // https://aws.amazon.com/blogs/compute/support-for-multi-value-parameters-in-amazon-api-gateway/
18
+ // [ [ 'petType', 'dog' ], [ 'petType', 'fish' ] ]
19
+ // => { petType: [ 'dog', 'fish' ] },
20
+ export function parseMultiValueQueryStringParameters(url) {
21
+ // dummy placeholder url for the WHATWG URL constructor
22
+ // https://github.com/nodejs/node/issues/12682
23
+ const { searchParams } = new URL(url, "http://example")
24
+ //
25
+ if (Array.from(searchParams).length === 0) return null
26
+ const map = new Map()
27
+ // eslint-disable-next-line no-restricted-syntax
28
+ for (const [key, value] of searchParams) {
29
+ const item = map.get(key)
30
+ if (item) item.push(value)
31
+ else map.set(key, [value])
32
+ }
33
+ return Object.fromEntries(map)
34
+ }
35
+ export function parseQueryStringParameters(url) {
36
+ // dummy placeholder url for the WHATWG URL constructor
37
+ // https://github.com/nodejs/node/issues/12682
38
+ const { searchParams } = new URL(url, "http://example")
39
+ if (Array.from(searchParams).length === 0) return null
40
+ return Object.fromEntries(searchParams)
41
+ }
42
+ //
43
+ export function nullIfEmpty(obj) {
44
+ return obj && (Object.keys(obj).length > 0 ? obj : null)
45
+ }
@@ -0,0 +1,35 @@
1
+ import { z } from "zod"
2
+
3
+ import Response from "../API/Response"
4
+ import Globals from "../Globals"
5
+
6
+ export default class Validator {
7
+ public static validateSchema(
8
+ data: any,
9
+ schema: z.ZodObject<any>,
10
+ ): boolean | Response {
11
+ let error, validatedInput
12
+ // Validate body against known zod schema
13
+ try {
14
+ validatedInput = schema.parse(data) as typeof schema
15
+ } catch (err: z.ZodError | any) {
16
+ if (err instanceof z.ZodError) {
17
+ error = err.issues
18
+ .map((v) => {
19
+ return `${v.code} - ${v.message} ${v.path}`
20
+ })
21
+ .join(", ")
22
+ } else if (err.message) error = err.message
23
+ else error = "Unknown validation error!"
24
+ }
25
+ // Error validation
26
+ if (!validatedInput || error) {
27
+ return Response.BadRequestResponse(
28
+ Globals.ErrorResponseValidationFail,
29
+ Globals.ErrorCode_InvalidInput,
30
+ )
31
+ } else {
32
+ return true
33
+ }
34
+ }
35
+ }
package/tsconfig.json CHANGED
@@ -3,6 +3,8 @@
3
3
  "noImplicitAny": false,
4
4
  "noEmitOnError": true,
5
5
  "removeComments": false,
6
+ "downlevelIteration": true,
7
+ "resolveJsonModule": true,
6
8
  "sourceMap": true,
7
9
  "target": "es5",
8
10
  "outDir": "dist",