@middy/core 7.0.0-alpha.2 → 7.0.0-alpha.3

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.
@@ -0,0 +1,48 @@
1
+ import { withDurableExecution } from "@aws/durable-execution-sdk-js";
2
+ import { executionContextKeys, lambdaContextKeys } from "@middy/util";
3
+
4
+ export const executionModeDurableContext = (
5
+ { middyRequest, runRequest },
6
+ beforeMiddlewares,
7
+ lambdaHandler,
8
+ afterMiddlewares,
9
+ onErrorMiddlewares,
10
+ plugin,
11
+ ) => {
12
+ const middy = withDurableExecution(async (event, context) => {
13
+ const request = middyRequest(event, context);
14
+ plugin.requestStart?.(request);
15
+
16
+ // normalize context with executionModeStandard
17
+ // https://docs.aws.amazon.com/lambda/latest/dg/typescript-context.html
18
+ // Idea: Use Proxy instead of copying. Faster for common use case?
19
+ copyKeys(
20
+ request.context,
21
+ request.context.executionContext,
22
+ executionContextKeys,
23
+ );
24
+ copyKeys(request.context, request.context.lambdaContext, lambdaContextKeys);
25
+
26
+ const response = await runRequest(
27
+ request,
28
+ beforeMiddlewares,
29
+ lambdaHandler,
30
+ afterMiddlewares,
31
+ onErrorMiddlewares,
32
+ plugin,
33
+ );
34
+ await plugin.requestEnd?.(request);
35
+ return response;
36
+ });
37
+ middy.handler = (replaceLambdaHandler) => {
38
+ lambdaHandler = replaceLambdaHandler;
39
+ return middy;
40
+ };
41
+ return middy;
42
+ };
43
+
44
+ const copyKeys = (to, from, keys) => {
45
+ keys.forEach((key) => {
46
+ to[key] = from[key];
47
+ });
48
+ };
@@ -0,0 +1,29 @@
1
+ export const executionModeStandard = (
2
+ { middyRequest, runRequest },
3
+ beforeMiddlewares,
4
+ lambdaHandler,
5
+ afterMiddlewares,
6
+ onErrorMiddlewares,
7
+ plugin,
8
+ ) => {
9
+ const middy = async (event, context) => {
10
+ const request = middyRequest(event, context);
11
+ plugin.requestStart?.(request);
12
+
13
+ const response = await runRequest(
14
+ request,
15
+ beforeMiddlewares,
16
+ lambdaHandler,
17
+ afterMiddlewares,
18
+ onErrorMiddlewares,
19
+ plugin,
20
+ );
21
+ await plugin.requestEnd?.(request);
22
+ return response;
23
+ };
24
+ middy.handler = (replaceLambdaHandler) => {
25
+ lambdaHandler = replaceLambdaHandler;
26
+ return middy;
27
+ };
28
+ return middy;
29
+ };
@@ -0,0 +1,75 @@
1
+ /* global awslambda */
2
+ import { Readable } from "node:stream";
3
+ import { pipeline } from "node:stream/promises";
4
+ import { ReadableStream } from "node:stream/web";
5
+
6
+ export const executionModeStreamifyResponse = (
7
+ { middyRequest, runRequest },
8
+ beforeMiddlewares,
9
+ lambdaHandler,
10
+ afterMiddlewares,
11
+ onErrorMiddlewares,
12
+ plugin,
13
+ ) => {
14
+ const middy = awslambda.streamifyResponse(
15
+ async (event, lambdaResponseStream, context) => {
16
+ const request = middyRequest(event, context);
17
+ plugin.requestStart?.(request);
18
+ const handlerResponse = await runRequest(
19
+ request,
20
+ beforeMiddlewares,
21
+ lambdaHandler,
22
+ afterMiddlewares,
23
+ onErrorMiddlewares,
24
+ plugin,
25
+ );
26
+ let responseStream = lambdaResponseStream;
27
+ let handlerBody = handlerResponse;
28
+ if (handlerResponse.statusCode) {
29
+ const { body, ...restResponse } = handlerResponse;
30
+ handlerBody = body ?? ""; // #1137
31
+ responseStream = awslambda.HttpResponseStream.from(
32
+ responseStream,
33
+ restResponse,
34
+ );
35
+ }
36
+
37
+ let handlerStream;
38
+ if (handlerBody._readableState || handlerBody instanceof ReadableStream) {
39
+ handlerStream = handlerBody;
40
+ } else if (typeof handlerBody === "string") {
41
+ // #1189
42
+ handlerStream = Readable.from(
43
+ handlerBody.length < stringIteratorSize
44
+ ? handlerBody
45
+ : stringIterator(handlerBody),
46
+ );
47
+ }
48
+
49
+ if (!handlerStream) {
50
+ throw new Error("handler response not a Readable or ReadableStream", {
51
+ cause: { package: "@middy/core" },
52
+ });
53
+ }
54
+
55
+ await pipeline(handlerStream, responseStream);
56
+ await plugin.requestEnd?.(request);
57
+ },
58
+ );
59
+
60
+ middy.handler = (replaceLambdaHandler) => {
61
+ lambdaHandler = replaceLambdaHandler;
62
+ return middy;
63
+ };
64
+ return middy;
65
+ };
66
+
67
+ const stringIteratorSize = 16384; // 16 * 1024 // Node.js default
68
+ function* stringIterator(input) {
69
+ let position = 0;
70
+ const length = input.length;
71
+ while (position < length) {
72
+ yield input.substring(position, position + stringIteratorSize);
73
+ position += stringIteratorSize;
74
+ }
75
+ }
package/index.d.ts CHANGED
@@ -2,12 +2,17 @@ import type {
2
2
  Context as LambdaContext,
3
3
  Handler as LambdaHandler,
4
4
  } from "aws-lambda";
5
+ import type { DurableContext as LambdaContextDurable } from "@aws/durable-execution-sdk-js";
5
6
 
6
7
  declare type PluginHook = () => void;
7
8
  declare type PluginHookWithMiddlewareName = (middlewareName: string) => void;
8
9
  declare type PluginHookPromise = (
9
10
  request: Request,
10
11
  ) => Promise<unknown> | unknown;
12
+ export type PluginExecutionMode = () => void;
13
+ export declare const executionModeStandard: PluginExecutionMode;
14
+ export declare const executionModeDurableContext: PluginExecutionMode;
15
+ export declare const executionModeStreamifyResponse: PluginExecutionMode;
11
16
 
12
17
  interface PluginObject {
13
18
  internal?: any;
@@ -20,14 +25,14 @@ interface PluginObject {
20
25
  timeoutEarlyResponse?: PluginHook;
21
26
  afterHandler?: PluginHook;
22
27
  requestEnd?: PluginHookPromise;
23
- streamifyResponse?: boolean;
28
+ executionMode?: PluginExecutionMode;
24
29
  }
25
30
 
26
31
  export interface Request<
27
32
  TEvent = any,
28
33
  TResult = any,
29
34
  TErr = Error,
30
- TContext extends LambdaContext = LambdaContext,
35
+ TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
31
36
  TInternal extends Record<string, unknown> = {},
32
37
  > {
33
38
  event: TEvent;
@@ -42,7 +47,7 @@ declare type MiddlewareFn<
42
47
  TEvent = any,
43
48
  TResult = any,
44
49
  TErr = Error,
45
- TContext extends LambdaContext = LambdaContext,
50
+ TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
46
51
  TInternal extends Record<string, unknown> = {},
47
52
  > = (request: Request<TEvent, TResult, TErr, TContext, TInternal>) => any;
48
53
 
@@ -50,7 +55,7 @@ export interface MiddlewareObj<
50
55
  TEvent = unknown,
51
56
  TResult = any,
52
57
  TErr = Error,
53
- TContext extends LambdaContext = LambdaContext,
58
+ TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
54
59
  TInternal extends Record<string, unknown> = {},
55
60
  > {
56
61
  before?: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>;
@@ -71,7 +76,7 @@ export interface MiddyHandlerObject {
71
76
  type MiddyInputHandler<
72
77
  TEvent,
73
78
  TResult,
74
- TContext extends LambdaContext = LambdaContext,
79
+ TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
75
80
  > = (
76
81
  event: TEvent,
77
82
  context: TContext,
@@ -80,14 +85,14 @@ type MiddyInputHandler<
80
85
  type MiddyInputPromiseHandler<
81
86
  TEvent,
82
87
  TResult,
83
- TContext extends LambdaContext = LambdaContext,
88
+ TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
84
89
  > = (event: TEvent, context: TContext) => Promise<TResult>;
85
90
 
86
91
  export interface MiddyfiedHandler<
87
92
  TEvent = any,
88
93
  TResult = any,
89
94
  TErr = Error,
90
- TContext extends LambdaContext = LambdaContext,
95
+ TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
91
96
  TInternal extends Record<string, unknown> = {},
92
97
  > extends MiddyInputHandler<TEvent, TResult, TContext>,
93
98
  MiddyInputPromiseHandler<TEvent, TResult, TContext> {
@@ -118,7 +123,7 @@ declare type AttachMiddlewareFn<
118
123
  TEvent = any,
119
124
  TResult = any,
120
125
  TErr = Error,
121
- TContext extends LambdaContext = LambdaContext,
126
+ TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
122
127
  TInternal extends Record<string, unknown> = {},
123
128
  > = (
124
129
  middleware: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>,
@@ -128,7 +133,7 @@ declare type AttachMiddlewareObj<
128
133
  TEvent = any,
129
134
  TResult = any,
130
135
  TErr = Error,
131
- TContext extends LambdaContext = LambdaContext,
136
+ TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
132
137
  TInternal extends Record<string, unknown> = {},
133
138
  > = (
134
139
  middleware: MiddlewareObj<TEvent, TResult, TErr, TContext, TInternal>,
@@ -138,7 +143,7 @@ declare type UseFn<
138
143
  TEvent = any,
139
144
  TResult = any,
140
145
  TErr = Error,
141
- TContext extends LambdaContext = LambdaContext,
146
+ TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
142
147
  TInternal extends Record<string, unknown> = {},
143
148
  > = <
144
149
  TMiddlewares extends
@@ -178,7 +183,7 @@ declare type UseFn<
178
183
 
179
184
  declare type MiddlewareHandler<
180
185
  THandler extends LambdaHandler<any, any>,
181
- TContext extends LambdaContext = LambdaContext,
186
+ TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
182
187
  TResult = any,
183
188
  TEvent = any,
184
189
  > = THandler extends LambdaHandler<TEvent, TResult> // always true
@@ -194,7 +199,7 @@ declare function middy<
194
199
  TEvent = unknown,
195
200
  TResult = any,
196
201
  TErr = Error,
197
- TContext extends LambdaContext = LambdaContext,
202
+ TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
198
203
  TInternal extends Record<string, unknown> = {},
199
204
  >(
200
205
  handler?:
package/index.js CHANGED
@@ -1,8 +1,9 @@
1
- /* global awslambda */
2
- import { Readable } from "node:stream";
3
- import { pipeline } from "node:stream/promises";
4
- import { ReadableStream } from "node:stream/web";
5
1
  import { setTimeout } from "node:timers";
2
+ import { executionModeStandard } from "./executionModeStandard.js";
3
+
4
+ export { executionModeDurableContext } from "./executionModeDurableContext.js";
5
+ export { executionModeStandard } from "./executionModeStandard.js";
6
+ export { executionModeStreamifyResponse } from "./executionModeStreamifyResponse.js";
6
7
 
7
8
  const defaultLambdaHandler = () => {};
8
9
  const defaultPluginConfig = {
@@ -14,10 +15,10 @@ const defaultPluginConfig = {
14
15
  err.name = "TimeoutError";
15
16
  throw err;
16
17
  },
17
- streamifyResponse: false, // Deprecate need for this when AWS provides a flag for when it's looking for it
18
+ executionMode: executionModeStandard,
18
19
  };
19
20
 
20
- const middy = (setupLambdaHandler, pluginConfig) => {
21
+ export const middy = (setupLambdaHandler, pluginConfig) => {
21
22
  let lambdaHandler;
22
23
  let plugin;
23
24
  // Allow base handler to be set using .handler()
@@ -44,73 +45,15 @@ const middy = (setupLambdaHandler, pluginConfig) => {
44
45
  internal: plugin.internal ?? {},
45
46
  };
46
47
  };
47
- const middy = plugin.streamifyResponse
48
- ? awslambda.streamifyResponse(
49
- async (event, lambdaResponseStream, context) => {
50
- const request = middyRequest(event, context);
51
- plugin.requestStart?.(request);
52
- const handlerResponse = await runRequest(
53
- request,
54
- beforeMiddlewares,
55
- lambdaHandler,
56
- afterMiddlewares,
57
- onErrorMiddlewares,
58
- plugin,
59
- );
60
- let responseStream = lambdaResponseStream;
61
- let handlerBody = handlerResponse;
62
- if (handlerResponse.statusCode) {
63
- const { body, ...restResponse } = handlerResponse;
64
- handlerBody = body ?? ""; // #1137
65
- responseStream = awslambda.HttpResponseStream.from(
66
- responseStream,
67
- restResponse,
68
- );
69
- }
70
-
71
- let handlerStream;
72
- if (
73
- handlerBody._readableState ||
74
- handlerBody instanceof ReadableStream
75
- ) {
76
- handlerStream = handlerBody;
77
- } else if (typeof handlerBody === "string") {
78
- // #1189
79
- handlerStream = Readable.from(
80
- handlerBody.length < stringIteratorSize
81
- ? handlerBody
82
- : stringIterator(handlerBody),
83
- );
84
- }
85
-
86
- if (!handlerStream) {
87
- throw new Error(
88
- "handler response not a Readable or ReadableStream",
89
- {
90
- cause: { package: "@middy/core" },
91
- },
92
- );
93
- }
94
48
 
95
- await pipeline(handlerStream, responseStream);
96
- await plugin.requestEnd?.(request);
97
- },
98
- )
99
- : async (event, context) => {
100
- const request = middyRequest(event, context);
101
- plugin.requestStart?.(request);
102
-
103
- const response = await runRequest(
104
- request,
105
- beforeMiddlewares,
106
- lambdaHandler,
107
- afterMiddlewares,
108
- onErrorMiddlewares,
109
- plugin,
110
- );
111
- await plugin.requestEnd?.(request);
112
- return response;
113
- };
49
+ const middy = plugin.executionMode(
50
+ { middyRequest, runRequest },
51
+ beforeMiddlewares,
52
+ lambdaHandler,
53
+ afterMiddlewares,
54
+ onErrorMiddlewares,
55
+ plugin,
56
+ );
114
57
 
115
58
  middy.use = (inputMiddleware) => {
116
59
  const middlewares = Array.isArray(inputMiddleware)
@@ -148,24 +91,10 @@ const middy = (setupLambdaHandler, pluginConfig) => {
148
91
  onErrorMiddlewares.unshift(onErrorMiddleware);
149
92
  return middy;
150
93
  };
151
- middy.handler = (replaceLambdaHandler) => {
152
- lambdaHandler = replaceLambdaHandler;
153
- return middy;
154
- };
155
94
 
156
95
  return middy;
157
96
  };
158
97
 
159
- const stringIteratorSize = 16384; // 16 * 1024 // Node.js default
160
- function* stringIterator(input) {
161
- let position = 0;
162
- const length = input.length;
163
- while (position < length) {
164
- yield input.substring(position, position + stringIteratorSize);
165
- position += stringIteratorSize;
166
- }
167
- }
168
-
169
98
  // shared AbortController, because it's slow
170
99
  let handlerAbort = new AbortController();
171
100
  const runRequest = async (
@@ -178,8 +107,10 @@ const runRequest = async (
178
107
  ) => {
179
108
  let timeoutID;
180
109
  // context.getRemainingTimeInMillis checked for when AWS context missing (tests, containers)
181
- const timeoutEarly =
182
- plugin.timeoutEarly && request.context.getRemainingTimeInMillis;
110
+ const getRemainingTimeInMillis =
111
+ request.context.getRemainingTimeInMillis ||
112
+ request.context.lambdaContext?.getRemainingTimeInMillis;
113
+ const timeoutEarly = plugin.timeoutEarly && getRemainingTimeInMillis;
183
114
 
184
115
  try {
185
116
  await runMiddlewares(request, beforeMiddlewares, plugin);
@@ -201,6 +132,7 @@ const runRequest = async (
201
132
 
202
133
  // clearTimeout pattern is 10x faster than using AbortController
203
134
  // Note: signal.abort is slow ~6000ns
135
+ // Required --test-force-exit to ignore unresolved timeoutPromise
204
136
  if (timeoutEarly) {
205
137
  let timeoutResolve;
206
138
  const timeoutPromise = new Promise((resolve, reject) => {
@@ -215,15 +147,30 @@ const runRequest = async (
215
147
  });
216
148
  timeoutID = setTimeout(
217
149
  timeoutResolve,
218
- request.context.getRemainingTimeInMillis() -
219
- plugin.timeoutEarlyInMillis,
150
+ getRemainingTimeInMillis() - plugin.timeoutEarlyInMillis,
220
151
  );
221
152
  promises.push(timeoutPromise);
222
153
  }
223
154
  request.response = await Promise.race(promises);
155
+
224
156
  if (timeoutID) {
225
157
  clearTimeout(timeoutID);
226
- }
158
+ } // alt varient for when abort() is faster
159
+ // if (timeoutEarly) {
160
+ // const timeoutPromise = setTimeoutPromise(
161
+ // () => {
162
+ // handlerAbort.abort();
163
+ // plugin.timeoutEarlyResponse()
164
+ // },
165
+ // getRemainingTimeInMillis() - plugin.timeoutEarlyInMillis,
166
+ // {
167
+ // signal: handlerAbort.signal,
168
+ // }
169
+ // );
170
+ // promises.push(timeoutPromise);
171
+ // }
172
+ // request.response = await Promise.race(promises);
173
+ // handlerAbort.abort(); // abort timeout
227
174
 
228
175
  plugin.afterHandler?.();
229
176
  await runMiddlewares(request, afterMiddlewares, plugin);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@middy/core",
3
- "version": "7.0.0-alpha.2",
3
+ "version": "7.0.0-alpha.3",
4
4
  "description": "🛵 The stylish Node.js middleware engine for AWS Lambda (core package)",
5
5
  "type": "module",
6
6
  "engines": {
@@ -11,6 +11,7 @@
11
11
  "access": "public"
12
12
  },
13
13
  "module": "./index.js",
14
+ "sideEffects": false,
14
15
  "exports": {
15
16
  ".": {
16
17
  "import": {
@@ -25,11 +26,14 @@
25
26
  "types": "index.d.ts",
26
27
  "files": [
27
28
  "index.js",
29
+ "executionModeStandard.js",
30
+ "executionModeDurableContext.js",
31
+ "executionModeStreamifyResponse.js",
28
32
  "index.d.ts"
29
33
  ],
30
34
  "scripts": {
31
35
  "test": "npm run test:unit && npm run test:fuzz",
32
- "test:unit": "node --test",
36
+ "test:unit": "node --test --test-force-exit",
33
37
  "test:fuzz": "node --test index.fuzz.js",
34
38
  "test:perf": "node --test index.perf.js",
35
39
  "test:profile": "node --prof __benchmarks__/index.js && node --prof-process --preprocess -j isolate*.log | speedscope -"
@@ -41,7 +45,9 @@
41
45
  "Serverless",
42
46
  "Framework",
43
47
  "AWS",
44
- "AWS Lambda"
48
+ "AWS Lambda",
49
+ "Function URL",
50
+ "Durable function"
45
51
  ],
46
52
  "author": {
47
53
  "name": "Middy contributors",
@@ -61,7 +67,9 @@
61
67
  "url": "https://github.com/sponsors/willfarrell"
62
68
  },
63
69
  "devDependencies": {
64
- "@datastream/core": "0.0.41",
70
+ "@aws/durable-execution-sdk-js": "^1.0.0",
71
+ "@aws/durable-execution-sdk-js-testing": "^1.0.0",
72
+ "@datastream/core": "0.0.42",
65
73
  "@types/aws-lambda": "^8.10.76",
66
74
  "@types/node": "^22.0.0"
67
75
  },