@jaypie/express 0.1.0

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.
package/.eslintignore ADDED
@@ -0,0 +1 @@
1
+ dist/
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # Jaypie Express 🐦‍⬛🧋
2
+
3
+ Express Jaypie handler for AWS Lambda
4
+
5
+ ## 📋 Usage
6
+
7
+ ### Installation
8
+
9
+ ```bash
10
+ npm install @jaypie/express
11
+ ```
12
+
13
+ See [Jaypie documentation](https://github.com/finlaysonstudio/jaypie) for usage.
14
+
15
+ ## 📝 Changelog
16
+
17
+ | Date | Version | Summary |
18
+ | ---------- | ------- | -------------- |
19
+ | 5/19/2024 | 0.1.0 | Initial deploy |
20
+ | 5/12/2024 | 0.0.1 | Initial commit |
21
+
22
+ ## 📜 License
23
+
24
+ Published by Finlayson Studio. All rights reserved
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ //
4
+ //
5
+ // Export
6
+ //
7
+
8
+ var index = {};
9
+
10
+ module.exports = index;
@@ -0,0 +1,8 @@
1
+ //
2
+ //
3
+ // Export
4
+ //
5
+
6
+ var index = {};
7
+
8
+ export { index as default };
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@jaypie/express",
3
+ "version": "0.1.0",
4
+ "author": "Finlayson Studio",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "default": {
9
+ "require": "./dist/module.cjs.js",
10
+ "default": "./src/index.js"
11
+ }
12
+ }
13
+ },
14
+ "main": "src/index.js",
15
+ "scripts": {
16
+ "build": "rollup --config",
17
+ "format": "npm run format:package && npm run format:lint",
18
+ "format:lint": "eslint --fix .",
19
+ "format:package": "sort-package-json ./package.json",
20
+ "init:deploy": "hygen jaypie workflow-npm",
21
+ "lint": "eslint .",
22
+ "new": "hygen jaypie vite",
23
+ "new:command": "hygen jaypie command",
24
+ "new:constants": "hygen jaypie constants",
25
+ "new:hygen": "hygen jaypie hygen",
26
+ "new:index": "hygen jaypie index",
27
+ "new:model": "hygen jaypie model",
28
+ "new:test": "hygen jaypie vitest",
29
+ "prepublish": "npm run build",
30
+ "test": "vitest",
31
+ "test:spec:decorateResponse.helper": "vitest run ./src/__tests__/decorateResponse.helper.spec.js",
32
+ "test:spec:expressHandler": "vitest run ./src/__tests__/expressHandler.spec.js",
33
+ "test:spec:getCurrentInvokeUuid.adapter": "vitest run ./src/__tests__/getCurrentInvokeUuid.adapter.spec.js",
34
+ "test:spec:index": "vitest run ./src/__tests__/index.spec.js",
35
+ "test:spec:summarizeRequest.helper": "vitest run ./src/__tests__/summarizeRequest.helper.spec.js",
36
+ "test:spec:summarizeResponse.helper": "vitest run ./src/__tests__/summarizeResponse.helper.spec.js",
37
+ "test:spec:supertest": "vitest run ./src/__tests__/supertest.spec.js"
38
+ },
39
+ "dependencies": {
40
+ "@codegenie/serverless-express": "^4.14.1",
41
+ "@jaypie/core": "^1.0.34",
42
+ "@jaypie/testkit": "^1.0.19",
43
+ "mock-express-response": "^0.3.0"
44
+ },
45
+ "devDependencies": {
46
+ "@rollup/plugin-commonjs": "^25.0.7",
47
+ "@rollup/plugin-node-resolve": "^15.2.3",
48
+ "eslint": "^8.57.0",
49
+ "eslint-config-prettier": "^9.1.0",
50
+ "eslint-plugin-import": "^2.29.1",
51
+ "eslint-plugin-prettier": "^5.1.3",
52
+ "eslint-plugin-vitest": "^0.5.4",
53
+ "express": "^4.19.2",
54
+ "hygen": "^6.2.11",
55
+ "jest-extended": "^4.0.2",
56
+ "prettier": "^3.2.5",
57
+ "rollup": "^4.17.2",
58
+ "rollup-plugin-auto-external": "^2.0.0",
59
+ "sort-package-json": "^2.10.0",
60
+ "supertest": "^7.0.0",
61
+ "vitest": "^1.6.0"
62
+ }
63
+ }
@@ -0,0 +1,22 @@
1
+ import autoExternal from "rollup-plugin-auto-external";
2
+ import commonjs from "@rollup/plugin-commonjs";
3
+ import resolve from "@rollup/plugin-node-resolve";
4
+
5
+ export default {
6
+ input: "src/index.js", // Path to your main JavaScript file
7
+ output: [
8
+ {
9
+ file: "dist/module.cjs.js", // Output file for CommonJS
10
+ format: "cjs", // CommonJS format
11
+ },
12
+ {
13
+ file: "dist/module.esm.js", // Output file for ES Module
14
+ format: "es", // ES Module format
15
+ },
16
+ ],
17
+ plugins: [
18
+ autoExternal(), // Automatically exclude dependencies from the bundle
19
+ resolve(), // Tells Rollup how to find node modules
20
+ commonjs(), // Converts CommonJS modules to ES6
21
+ ],
22
+ };
@@ -0,0 +1,90 @@
1
+ import { HTTP, JAYPIE, log as publicLogger } from "@jaypie/core";
2
+ import getCurrentInvokeUuid from "./getCurrentInvokeUuid.adapter.js";
3
+
4
+ //
5
+ //
6
+ // Main
7
+ //
8
+
9
+ const decorateResponse = (
10
+ res,
11
+ { handler = "", version = process.env.PROJECT_VERSION } = {},
12
+ ) => {
13
+ const log = publicLogger.lib({
14
+ lib: JAYPIE.LIB.EXPRESS,
15
+ });
16
+
17
+ //
18
+ //
19
+ // Validate
20
+ //
21
+ if (typeof res !== "object" || res === null) {
22
+ log.warn("decorateResponse called but response is not an object");
23
+ return; // eslint-disable-line no-useless-return
24
+ }
25
+
26
+ try {
27
+ //
28
+ //
29
+ // Decorate Headers
30
+ //
31
+
32
+ // X-Powered-By, override "Express" but nothing else
33
+ if (
34
+ !res.get(HTTP.HEADER.POWERED_BY) ||
35
+ res.get(HTTP.HEADER.POWERED_BY) === "Express"
36
+ ) {
37
+ res.set(HTTP.HEADER.POWERED_BY, JAYPIE.LIB.EXPRESS);
38
+ }
39
+
40
+ // X-Project-Environment
41
+ if (process.env.PROJECT_ENV) {
42
+ res.setHeader(HTTP.HEADER.PROJECT.ENVIRONMENT, process.env.PROJECT_ENV);
43
+ }
44
+
45
+ // X-Project-Handler
46
+ if (handler) {
47
+ res.setHeader(HTTP.HEADER.PROJECT.HANDLER, handler);
48
+ }
49
+
50
+ // X-Project-Invocation
51
+ const currentInvoke = getCurrentInvokeUuid();
52
+ if (currentInvoke) {
53
+ res.setHeader(HTTP.HEADER.PROJECT.INVOCATION, currentInvoke);
54
+ }
55
+
56
+ // X-Project-Key
57
+ if (process.env.PROJECT_KEY) {
58
+ res.setHeader(HTTP.HEADER.PROJECT.KEY, process.env.PROJECT_KEY);
59
+ }
60
+
61
+ // X-Project-Version
62
+ if (version) {
63
+ res.setHeader(HTTP.HEADER.PROJECT.VERSION, version);
64
+ }
65
+
66
+ //
67
+ //
68
+ // Error Handling
69
+ //
70
+ } catch (error) {
71
+ log.warn("decorateResponse caught an internal error");
72
+ log.var({ error });
73
+ }
74
+ };
75
+
76
+ //
77
+ //
78
+ // Export
79
+ //
80
+
81
+ export default decorateResponse;
82
+
83
+ //
84
+ //
85
+ // Footnotes
86
+ //
87
+
88
+ // This is a "utility" function but it needs a lot of "context"
89
+ // about the environment's secret parameters, the special adapter,
90
+ // HTTP, etc. There must be a better way to organize this
@@ -0,0 +1,258 @@
1
+ import {
2
+ force,
3
+ HTTP,
4
+ JAYPIE,
5
+ jaypieHandler,
6
+ log as publicLogger,
7
+ UnhandledError,
8
+ validate as validateIs,
9
+ } from "@jaypie/core";
10
+
11
+ import getCurrentInvokeUuid from "./getCurrentInvokeUuid.adapter.js";
12
+ import decorateResponse from "./decorateResponse.helper.js";
13
+ import summarizeRequest from "./summarizeRequest.helper.js";
14
+ import summarizeResponse from "./summarizeResponse.helper.js";
15
+
16
+ //
17
+ //
18
+ // Main
19
+ //
20
+
21
+ const expressHandler = (
22
+ handler,
23
+ { locals, name, setup = [], teardown = [], unavailable, validate } = {},
24
+ ) => {
25
+ //
26
+ //
27
+ // Validate
28
+ //
29
+ validateIs.function(handler);
30
+ validateIs.optional.object(locals);
31
+ setup = force.array(setup); // allows a single item
32
+ teardown = force.array(teardown); // allows a single item
33
+
34
+ //
35
+ //
36
+ // Setup
37
+ //
38
+
39
+ let jaypieFunction;
40
+
41
+ return async (req, res, ...params) => {
42
+ // * This is the first line of code that runs when a request is received
43
+
44
+ // Update the public logger with the request ID
45
+ publicLogger.tag({ invoke: getCurrentInvokeUuid() });
46
+
47
+ // Very low-level, internal sub-trace details
48
+ const libLogger = publicLogger.lib({
49
+ lib: JAYPIE.LIB.EXPRESS,
50
+ });
51
+ libLogger.trace("[jaypie] Express init");
52
+
53
+ // Top-level, important details that run at the same level as the main logger
54
+ const log = publicLogger.lib({
55
+ level: publicLogger.level,
56
+ lib: JAYPIE.LIB.EXPRESS,
57
+ });
58
+
59
+ // Set req.locals if it doesn't exist
60
+ if (!req.locals) req.locals = {};
61
+ if (!req.locals._jaypie) req.locals._jaypie = {};
62
+
63
+ // Set res.locals if it doesn't exist
64
+ if (!res.locals) res.locals = {};
65
+ if (!res.locals._jaypie) res.locals._jaypie = {};
66
+
67
+ const originalRes = {
68
+ attemptedCall: undefined,
69
+ attemptedParams: undefined,
70
+ end: res.end,
71
+ json: res.json,
72
+ send: res.send,
73
+ status: res.status,
74
+ statusSent: false,
75
+ };
76
+ res.end = (...params) => {
77
+ originalRes.attemptedCall = originalRes.end;
78
+ originalRes.attemptedParams = params;
79
+ log.warn(
80
+ "[jaypie] Illegal call to res.end(); prefer Jaypie response conventions",
81
+ );
82
+ };
83
+ res.json = (...params) => {
84
+ originalRes.attemptedCall = originalRes.json;
85
+ originalRes.attemptedParams = params;
86
+ log.warn(
87
+ "[jaypie] Illegal call to res.json(); prefer Jaypie response conventions",
88
+ );
89
+ };
90
+ res.send = (...params) => {
91
+ originalRes.attemptedCall = originalRes.send;
92
+ originalRes.attemptedParams = params;
93
+ log.warn(
94
+ "[jaypie] Illegal call to res.send(); prefer Jaypie response conventions",
95
+ );
96
+ };
97
+ res.status = (...params) => {
98
+ originalRes.statusSent = true;
99
+ return originalRes.status(...params);
100
+ };
101
+
102
+ //
103
+ //
104
+ // Preprocess
105
+ //
106
+
107
+ if (locals) {
108
+ // Locals
109
+ const keys = Object.keys(locals);
110
+ if (keys.length > 0) {
111
+ const localsSetup = async () => {
112
+ libLogger.trace("[jaypie] Locals");
113
+ for (let i = 0; i < keys.length; i += 1) {
114
+ const key = keys[i];
115
+ if (typeof locals[key] === "function") {
116
+ // eslint-disable-next-line no-await-in-loop
117
+ req.locals[key] = await locals[key](req, res);
118
+ } else {
119
+ req.locals[key] = locals[key];
120
+ }
121
+ }
122
+ };
123
+ setup.push(localsSetup);
124
+ }
125
+ }
126
+
127
+ if (!jaypieFunction) {
128
+ // Initialize after logging is set up
129
+ jaypieFunction = jaypieHandler(handler, {
130
+ name,
131
+ setup,
132
+ teardown,
133
+ unavailable,
134
+ validate,
135
+ });
136
+ }
137
+
138
+ let response;
139
+ let status;
140
+
141
+ try {
142
+ libLogger.trace("[jaypie] Lambda execution");
143
+ log.info.var({ req: summarizeRequest(req) });
144
+
145
+ //
146
+ //
147
+ // Process
148
+ //
149
+
150
+ response = await jaypieFunction(req, res, ...params);
151
+
152
+ //
153
+ //
154
+ // Error Handling
155
+ //
156
+ } catch (error) {
157
+ // In theory jaypieFunction has handled all errors
158
+ if (error.status) {
159
+ status = error.status;
160
+ }
161
+ if (typeof error.json === "function") {
162
+ response = error.json();
163
+ } else {
164
+ // This should never happen
165
+ const unhandledError = new UnhandledError();
166
+ response = unhandledError.json();
167
+ status = unhandledError.status;
168
+ }
169
+ }
170
+
171
+ //
172
+ //
173
+ // Postprocess
174
+ //
175
+
176
+ // Restore original res functions
177
+ res.end = originalRes.end;
178
+ res.json = originalRes.json;
179
+ res.send = originalRes.send;
180
+ res.status = originalRes.status;
181
+
182
+ // Decorate response
183
+ decorateResponse(res, { handler: name });
184
+
185
+ // Send response
186
+ try {
187
+ // Status
188
+ if (status && !originalRes.statusSent) {
189
+ res.status(status);
190
+ }
191
+
192
+ if (!originalRes.attemptedCall) {
193
+ // Body
194
+ if (response) {
195
+ if (typeof response === "object") {
196
+ if (typeof response.json === "function") {
197
+ res.json(response.json());
198
+ } else {
199
+ res.json(response);
200
+ }
201
+ } else if (typeof response === "string") {
202
+ try {
203
+ res.json(JSON.parse(response));
204
+ } catch (error) {
205
+ res.send(response);
206
+ }
207
+ } else if (response === true) {
208
+ res.status(HTTP.CODE.CREATED);
209
+ res.send();
210
+ } else {
211
+ res.send(response);
212
+ }
213
+ } else {
214
+ // No response
215
+ res.status(HTTP.CODE.NO_CONTENT).send();
216
+ }
217
+ } else {
218
+ // Resolve illegal call to res.end(), res.json(), or res.send()
219
+ log.debug("[jaypie] Resolving illegal call to res");
220
+ log.var({
221
+ attemptedCall: {
222
+ name: originalRes.attemptedCall.name,
223
+ params: originalRes.attemptedParams,
224
+ },
225
+ });
226
+ // Call the original function with the original parameters and the original `this` (res)
227
+ originalRes.attemptedCall.call(res, ...originalRes.attemptedParams);
228
+ }
229
+ } catch (error) {
230
+ log.fatal("Express encountered an error while sending the response");
231
+ log.var({ responseError: error });
232
+ }
233
+
234
+ // Log response
235
+ const extras = {};
236
+ if (response) extras.body = response;
237
+ log.info.var({
238
+ res: summarizeResponse(res, extras),
239
+ });
240
+
241
+ // Clean up the public logger
242
+ publicLogger.untag("handler");
243
+
244
+ //
245
+ //
246
+ // Return
247
+ //
248
+
249
+ return response;
250
+ };
251
+ };
252
+
253
+ //
254
+ //
255
+ // Export
256
+ //
257
+
258
+ export default expressHandler;
@@ -0,0 +1,33 @@
1
+ import { getCurrentInvoke } from "@codegenie/serverless-express";
2
+
3
+ //
4
+ //
5
+ // Helper Functions
6
+ //
7
+
8
+ // Adapter for the "@codegenie/serverless-express" uuid
9
+ function getServerlessExpressUuid() {
10
+ const currentInvoke = getCurrentInvoke();
11
+ if (
12
+ currentInvoke &&
13
+ currentInvoke.context &&
14
+ currentInvoke.context.awsRequestId
15
+ ) {
16
+ return currentInvoke.context.awsRequestId;
17
+ }
18
+ return undefined;
19
+ }
20
+
21
+ //
22
+ //
23
+ // Main
24
+ //
25
+
26
+ const getCurrentInvokeUuid = () => getServerlessExpressUuid();
27
+
28
+ //
29
+ //
30
+ // Export
31
+ //
32
+
33
+ export default getCurrentInvokeUuid;
package/src/index.js ADDED
@@ -0,0 +1,6 @@
1
+ //
2
+ //
3
+ // Export
4
+ //
5
+
6
+ export default {};
@@ -0,0 +1,28 @@
1
+ //
2
+ //
3
+ // Function Definition
4
+ //
5
+
6
+ function summarizeRequest(req) {
7
+ // If body is a buffer, convert it to a string
8
+ let { body } = req;
9
+ if (Buffer.isBuffer(body)) {
10
+ body = body.toString();
11
+ }
12
+
13
+ return {
14
+ baseUrl: req.baseUrl,
15
+ body,
16
+ headers: req.headers,
17
+ method: req.method,
18
+ query: req.query,
19
+ url: req.url,
20
+ };
21
+ }
22
+
23
+ //
24
+ //
25
+ // Export
26
+ //
27
+
28
+ export default summarizeRequest;
@@ -0,0 +1,25 @@
1
+ //
2
+ //
3
+ // Function Definition
4
+ //
5
+
6
+ function summarizeResponse(res, extras) {
7
+ const response = {
8
+ statusCode: res.statusCode,
9
+ statusMessage: res.statusMessage,
10
+ };
11
+ if (typeof res.getHeaders === "function") {
12
+ response.headers = res.getHeaders();
13
+ }
14
+ if (typeof extras === "object" && extras !== null) {
15
+ Object.assign(response, extras);
16
+ }
17
+ return response;
18
+ }
19
+
20
+ //
21
+ //
22
+ // Export
23
+ //
24
+
25
+ export default summarizeResponse;