@web-ts-toolkit/express-runtime 0.43.0 → 0.44.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/index.d.mts CHANGED
@@ -84,9 +84,13 @@ interface ExpressAppOptions {
84
84
  }
85
85
  declare function createExpressApp(options?: ExpressAppOptions): Express;
86
86
  /**
87
- * A platform-agnostic serverless handler. Works with Netlify, Vercel, AWS
88
- * Lambda, and any platform that calls `(event, context)` and expects a
89
- * response.
87
+ * A serverless handler generic over provider event/context shapes, backed by
88
+ * `serverless-http` 4. The handler accepts the event shapes the configured
89
+ * `serverlessOptions.provider` supports (`'aws'`/`'azure'`, with default
90
+ * detection); it is not tested against Netlify, Vercel, HTTP API v2, or ALB
91
+ * shapes. The local `start-serverless` adapter supplies AWS API Gateway REST
92
+ * API v1 events only. Provide provider-specific `TEvent`/`TContext` when hooks
93
+ * need typed access; the default for both is `Record<string, unknown>`.
90
94
  */
91
95
  type ServerlessHandler<TEvent extends object = Record<string, unknown>, TContext extends object = Record<string, unknown>> = ((event: TEvent, context: TContext) => Promise<object>) & {
92
96
  /**
@@ -124,8 +128,8 @@ interface ServerlessHandlerOptions<TEvent extends object = Record<string, unknow
124
128
  /** Hook called after Express finishes processing as `(response, event, context)`. */
125
129
  response?: ServerlessResponseHook<TEvent, TContext>;
126
130
  /**
127
- * Additional options forwarded to `serverless-http` (e.g. `provider`,
128
- * `binary`, `basePath`). `request` and `response` are controlled by the
131
+ * Additional options forwarded to `serverless-http` (e.g. `provider`
132
+ * (`'aws'`/`'azure'`), `binary`, `basePath`). `request` and `response` are controlled by the
129
133
  * dedicated hooks above.
130
134
  */
131
135
  serverlessOptions?: Omit<ServerlessHttpOptions, 'request' | 'response'>;
@@ -146,6 +150,12 @@ interface ServerlessHandlerOptions<TEvent extends object = Record<string, unknow
146
150
  * strings. Malformed JSON is treated as client input and left unchanged without
147
151
  * logging an internal error.
148
152
  *
153
+ * String conversion is deferred until the media-type/readability decision
154
+ * needs it: readable JSON inputs return before any `toString('utf8')`, so no
155
+ * unused UTF-8 decode is performed for the streamed path. Plain-object
156
+ * conversion behavior, media-type matching, the `maxBodyBytes` threshold, and
157
+ * Express parser ownership are unchanged.
158
+ *
149
159
  * Public extension seam used by the default `createServerlessHandler()` request
150
160
  * hook and by consumers that want the same conservative body conversion policy
151
161
  * in a custom hook.
@@ -178,7 +188,7 @@ interface LocalServerOptions {
178
188
  * `shutdown()`. Pass an explicit array to choose different signals.
179
189
  */
180
190
  signals?: boolean | ReadonlyArray<NodeJS.Signals>;
181
- /** Max ms to wait for in-flight requests on shutdown. Default: `5000`. */
191
+ /** Max ms to wait for in-flight requests on shutdown. Default: `5000`. Must be a finite integer in `0..2147483647` (Node timer limit); `0` force-closes immediately, `2147483647` is the largest safe delay. */
182
192
  shutdownTimeout?: number;
183
193
  /**
184
194
  * Call `process.exit(0)` after graceful shutdown completes. Default: `false`
package/index.d.ts CHANGED
@@ -84,9 +84,13 @@ interface ExpressAppOptions {
84
84
  }
85
85
  declare function createExpressApp(options?: ExpressAppOptions): Express;
86
86
  /**
87
- * A platform-agnostic serverless handler. Works with Netlify, Vercel, AWS
88
- * Lambda, and any platform that calls `(event, context)` and expects a
89
- * response.
87
+ * A serverless handler generic over provider event/context shapes, backed by
88
+ * `serverless-http` 4. The handler accepts the event shapes the configured
89
+ * `serverlessOptions.provider` supports (`'aws'`/`'azure'`, with default
90
+ * detection); it is not tested against Netlify, Vercel, HTTP API v2, or ALB
91
+ * shapes. The local `start-serverless` adapter supplies AWS API Gateway REST
92
+ * API v1 events only. Provide provider-specific `TEvent`/`TContext` when hooks
93
+ * need typed access; the default for both is `Record<string, unknown>`.
90
94
  */
91
95
  type ServerlessHandler<TEvent extends object = Record<string, unknown>, TContext extends object = Record<string, unknown>> = ((event: TEvent, context: TContext) => Promise<object>) & {
92
96
  /**
@@ -124,8 +128,8 @@ interface ServerlessHandlerOptions<TEvent extends object = Record<string, unknow
124
128
  /** Hook called after Express finishes processing as `(response, event, context)`. */
125
129
  response?: ServerlessResponseHook<TEvent, TContext>;
126
130
  /**
127
- * Additional options forwarded to `serverless-http` (e.g. `provider`,
128
- * `binary`, `basePath`). `request` and `response` are controlled by the
131
+ * Additional options forwarded to `serverless-http` (e.g. `provider`
132
+ * (`'aws'`/`'azure'`), `binary`, `basePath`). `request` and `response` are controlled by the
129
133
  * dedicated hooks above.
130
134
  */
131
135
  serverlessOptions?: Omit<ServerlessHttpOptions, 'request' | 'response'>;
@@ -146,6 +150,12 @@ interface ServerlessHandlerOptions<TEvent extends object = Record<string, unknow
146
150
  * strings. Malformed JSON is treated as client input and left unchanged without
147
151
  * logging an internal error.
148
152
  *
153
+ * String conversion is deferred until the media-type/readability decision
154
+ * needs it: readable JSON inputs return before any `toString('utf8')`, so no
155
+ * unused UTF-8 decode is performed for the streamed path. Plain-object
156
+ * conversion behavior, media-type matching, the `maxBodyBytes` threshold, and
157
+ * Express parser ownership are unchanged.
158
+ *
149
159
  * Public extension seam used by the default `createServerlessHandler()` request
150
160
  * hook and by consumers that want the same conservative body conversion policy
151
161
  * in a custom hook.
@@ -178,7 +188,7 @@ interface LocalServerOptions {
178
188
  * `shutdown()`. Pass an explicit array to choose different signals.
179
189
  */
180
190
  signals?: boolean | ReadonlyArray<NodeJS.Signals>;
181
- /** Max ms to wait for in-flight requests on shutdown. Default: `5000`. */
191
+ /** Max ms to wait for in-flight requests on shutdown. Default: `5000`. Must be a finite integer in `0..2147483647` (Node timer limit); `0` force-closes immediately, `2147483647` is the largest safe delay. */
182
192
  shutdownTimeout?: number;
183
193
  /**
184
194
  * Call `process.exit(0)` after graceful shutdown completes. Default: `false`
package/index.js CHANGED
@@ -45,6 +45,7 @@ var import_serverless_http = __toESM(require("serverless-http"));
45
45
 
46
46
  // src/numeric-validation.ts
47
47
  var MAX_INTEGER_OPTION_VALUE = Number.MAX_SAFE_INTEGER;
48
+ var MAX_TIMER_DURATION_MS = 2147483647;
48
49
  function validateFiniteInteger(value, options) {
49
50
  const min = options.min ?? Number.MIN_SAFE_INTEGER;
50
51
  const max = options.max ?? MAX_INTEGER_OPTION_VALUE;
@@ -75,6 +76,9 @@ function parsePortValue(value, name) {
75
76
  }
76
77
  return value;
77
78
  }
79
+ function validateTimerDuration(value, name) {
80
+ return validateFiniteInteger(value, { name, min: 0, max: MAX_TIMER_DURATION_MS });
81
+ }
78
82
 
79
83
  // src/index.ts
80
84
  var defaultLogger = {
@@ -143,20 +147,19 @@ function defaultRequestHook(req, maxBodyBytes = 1024 * 1024, logger = defaultLog
143
147
  logger.debug?.(" Skipping oversized serverless body for content-type parsing");
144
148
  return;
145
149
  }
146
- const bodyStr = req.body.toString("utf8");
147
150
  const contentType = getHeaderValue(req.headers, "content-type");
148
151
  if (isJsonMediaType(contentType)) {
149
152
  if (isReadableRequest(req)) {
150
153
  return;
151
154
  }
152
155
  try {
153
- req.body = JSON.parse(bodyStr);
156
+ req.body = JSON.parse(req.body.toString("utf8"));
154
157
  } catch (_error) {
155
158
  void _error;
156
159
  }
157
160
  return;
158
161
  }
159
- req.body = bodyStr;
162
+ req.body = req.body.toString("utf8");
160
163
  }
161
164
  function getHeaderValue(headers, name) {
162
165
  if (!headers) return "";
@@ -256,11 +259,7 @@ function startLocalServer(app, options = {}) {
256
259
  const logger = options.logger ?? defaultLogger;
257
260
  const port = normalizePort(options.port);
258
261
  const host = options.host ?? process.env.HOST ?? "0.0.0.0";
259
- const shutdownTimeout = validateFiniteInteger(options.shutdownTimeout ?? DEFAULT_SHUTDOWN_TIMEOUT, {
260
- name: "shutdownTimeout",
261
- min: 0,
262
- max: MAX_INTEGER_OPTION_VALUE
263
- });
262
+ const shutdownTimeout = validateTimerDuration(options.shutdownTimeout ?? DEFAULT_SHUTDOWN_TIMEOUT, "shutdownTimeout");
264
263
  const server = import_node_http.default.createServer(app);
265
264
  app.set("port", port);
266
265
  let state = "initializing";
@@ -432,6 +431,7 @@ function startLocalServer(app, options = {}) {
432
431
  done();
433
432
  }
434
433
  });
434
+ let shutdownFailed = false;
435
435
  let shutdownError;
436
436
  try {
437
437
  if (options.onShutdown) {
@@ -439,9 +439,10 @@ function startLocalServer(app, options = {}) {
439
439
  }
440
440
  } catch (err) {
441
441
  logger.error("onShutdown hook failed:", err);
442
+ shutdownFailed = true;
442
443
  shutdownError = err;
443
444
  }
444
- if (shutdownError) {
445
+ if (shutdownFailed) {
445
446
  state = "failed";
446
447
  if (options.exitAfterShutdown) {
447
448
  process.exit(1);
@@ -460,7 +461,7 @@ function startLocalServer(app, options = {}) {
460
461
  };
461
462
  if (options.signals !== false) {
462
463
  const list = options.signals === void 0 || options.signals === true ? DEFAULT_SIGNALS : options.signals;
463
- for (const sig of list) {
464
+ for (const sig of new Set(list)) {
464
465
  const handler = () => {
465
466
  void shutdown().catch(() => {
466
467
  });
package/index.mjs CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  parsePortValue,
7
7
  startLocalServer,
8
8
  validateFiniteInteger
9
- } from "./chunk-VPFBKM2K.mjs";
9
+ } from "./chunk-QNRHWPTO.mjs";
10
10
  export {
11
11
  createExpressApp,
12
12
  createServerlessHandler,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@web-ts-toolkit/express-runtime",
3
3
  "description": "Express app factory plus serverless handler and local dev server helpers",
4
4
  "homepage": "https://web-ts-toolkit.pages.dev/docs/packages/express-runtime",
5
- "version": "0.43.0",
5
+ "version": "0.44.0",
6
6
  "sideEffects": false,
7
7
  "keywords": [
8
8
  "express",