@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/README.md CHANGED
@@ -171,8 +171,9 @@ This produces `dist/app.js` (configurable via `--out-name`) that default-exports
171
171
  the Express app and, when `--init` is used, also exports `init` for the `start`
172
172
  command to run before listening.
173
173
 
174
- > `express` is always external; additional externals can be added via
175
- > `--external`.
174
+ > `express` and `@web-ts-toolkit/express-runtime` are always external;
175
+ > additional externals can be added via `--external`. Deploy the bundle with
176
+ > both packages installed (`express` is a peer dependency).
176
177
 
177
178
  ### CLI — start (run a bundled app locally)
178
179
 
@@ -202,8 +203,10 @@ npx wtt-express-runtime build-serverless ./src/app.ts --init ./src/init.ts --out
202
203
  ```
203
204
 
204
205
  This produces `netlify/functions/handler.js` (configurable via `--out-name`)
205
- that exports a `handler` function using `serverless-http`. Choose provider
206
- options in `createServerlessHandler()` for the deployment platform you run on.
206
+ that exports a `handler` function using `serverless-http`. Configure the
207
+ supported `serverless-http` provider options via `serverlessOptions`
208
+ (`provider: 'aws' | 'azure'`); other platform shapes are not emulated locally
209
+ (see the `start-serverless` adapter contract below).
207
210
 
208
211
  ### CLI — start-serverless (run a bundled handler locally)
209
212
 
@@ -232,6 +235,13 @@ npx wtt-express-runtime start-serverless ./dist/handler.js --port 9000 --env .en
232
235
  > IP.
233
236
  >
234
237
  > The incoming URL query is split from the path before the handler is invoked.
238
+ > Origin-form paths are preserved verbatim — never dot-segment-resolved,
239
+ > slash-collapsed, or percent-decoded — so `//admin/users`, `/a/../private`,
240
+ > and `/%2E%2E/private` reach wrapped routing exactly as sent. Absolute-form
241
+ > targets (`scheme://authority/path?query`, as sent to proxies) are supported
242
+ > by stripping the scheme and authority; asterisk-form (`*`) yields path `*`;
243
+ > the empty string maps to `/`. Any other target shape is rejected with a 500
244
+ > before the handler runs.
235
245
  > Query keys and values are decoded once from percent-encoding, duplicate keys are
236
246
  > preserved in `multiValueQueryStringParameters`, empty values are preserved as
237
247
  > `''`, literal `+` signs remain `+`, and encoded delimiters such as `%26` and
@@ -253,8 +263,12 @@ npx wtt-express-runtime start-serverless ./dist/handler.js --port 9000 --env .en
253
263
  > chunked bodies are checked incrementally and stop retaining chunks after the
254
264
  > limit — the request is drained and a `413 Payload Too Large` is returned without
255
265
  > invoking the handler. Client aborts and stream errors release listeners and do
256
- > not produce an unhandled rejection. Memory retained is at most the limit plus
257
- > one incoming chunk.
266
+ > not produce an unhandled rejection. Chunk retention is `O(limit)` appending
267
+ > stops once the running total would exceed the limit, so at most one chunk over
268
+ > the limit is observed. `Buffer.concat` then holds the chunks plus one output
269
+ > Buffer, and event translation adds a transient base64 copy (~4/3 of the body),
270
+ > so peak transient memory is a small multiple of the limit rather than an exact
271
+ > limit-plus-chunk ceiling.
258
272
  >
259
273
  > Override the limit intentionally:
260
274
  >
@@ -374,19 +388,27 @@ runtime calls, which pass `(request, event, context)` before Express and
374
388
  `Record<string, unknown>` for both arguments; provide provider-specific event and
375
389
  context types when you need typed access in hooks.
376
390
 
377
- #### Netlify example
391
+ #### Serverless deployment example
378
392
 
379
393
  ```ts
380
394
  import { createExpressApp, createServerlessHandler } from '@web-ts-toolkit/express-runtime';
381
- import { Handler } from '@netlify/functions';
382
395
 
383
396
  const app = createExpressApp({
384
397
  routers: [{ path: () => '/.netlify/functions/main', handler: myRouter }],
385
398
  });
386
399
 
387
- export const handler: Handler = createServerlessHandler(app, { init: startDB });
400
+ export const handler = createServerlessHandler(app, { init: startDB });
388
401
  ```
389
402
 
403
+ Export the `ServerlessHandler` as-is with its inferred type. Do not annotate
404
+ it with the platform's `Handler` type (e.g. from `@netlify/functions`): the
405
+ handler resolves `Promise<object>` for the event shapes `serverless-http`
406
+ supports (`aws`/`azure` providers), which is not assignable to Netlify's
407
+ `HandlerResponse` (`statusCode` is required there), so such an annotation fails
408
+ strict compilation. If the platform requires its own handler type, add an
409
+ explicit adapter in the app. No platform-specific adapter is shipped; the local
410
+ `start-serverless` command emulates AWS API Gateway REST API v1 only.
411
+
390
412
  ### `startLocalServer(app, options?): LocalServer`
391
413
 
392
414
  Binds an Express app to a TCP port (or named pipe) via `http.createServer`,
@@ -398,18 +420,18 @@ Shutdown order and timeout policy: on `shutdown()`, the server first stops accep
398
420
 
399
421
  Port `0` logs the actual bound port (e.g. `Server running at http://127.0.0.1:54321/ (port 54321)`).
400
422
 
401
- | Option | Type | Default | Description |
402
- | ------------------- | ----------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------- |
403
- | `port` | `number \| string` | `process.env.PORT ?? 8080` | Port number or named-pipe path (use `0` for an ephemeral port; actual port is logged) |
404
- | `host` | `string` | `process.env.HOST ?? 0.0.0.0` | Hostname (ignored for named pipes) |
405
- | `init` | `() => Promise<void>` | — | Called once before listening; rejection rejects `ready` and skips listening |
406
- | `onShutdown` | `() => Promise<void> \| void` | — | Called **after** draining; rejection is logged and fails shutdown |
407
- | `onListening` | `() => void` | — | Called when listening (after actual-port log) |
408
- | `onError` | `(error) => void` | logs + exits | Called on listen errors and init failures (init failures are not `listen` syscall errors) |
409
- | `signals` | `boolean \| NodeJS.Signals[]` | `true` (`SIGINT`, `SIGTERM`) | Signal handlers to register (owned handlers removed on shutdown/terminal failure) |
410
- | `shutdownTimeout` | `number` | `5000` | Max ms to wait for in-flight requests before force-closing (covers draining only) |
411
- | `exitAfterShutdown` | `boolean` | `false` | Call `process.exit(0)` after successful shutdown or `process.exit(1)` after cleanup failure |
412
- | `logger` | `Logger` | `console` | Logger used internally |
423
+ | Option | Type | Default | Description |
424
+ | ------------------- | ----------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
425
+ | `port` | `number \| string` | `process.env.PORT ?? 8080` | Port number or named-pipe path (use `0` for an ephemeral port; actual port is logged) |
426
+ | `host` | `string` | `process.env.HOST ?? 0.0.0.0` | Hostname (ignored for named pipes) |
427
+ | `init` | `() => Promise<void>` | — | Called once before listening; rejection rejects `ready` and skips listening |
428
+ | `onShutdown` | `() => Promise<void> \| void` | — | Called **after** draining; rejection is logged and fails shutdown |
429
+ | `onListening` | `() => void` | — | Called when listening (after actual-port log) |
430
+ | `onError` | `(error) => void` | logs + exits | Called on listen errors and init failures (init failures are not `listen` syscall errors) |
431
+ | `signals` | `boolean \| NodeJS.Signals[]` | `true` (`SIGINT`, `SIGTERM`) | Signal handlers to register (owned handlers removed on shutdown/terminal failure) |
432
+ | `shutdownTimeout` | `number` | `5000` | Max ms to wait for in-flight requests before force-closing (covers draining only; `0..2147483647`, `0` force-closes immediately) |
433
+ | `exitAfterShutdown` | `boolean` | `false` | Call `process.exit(0)` after successful shutdown or `process.exit(1)` after cleanup failure |
434
+ | `logger` | `Logger` | `console` | Logger used internally |
413
435
 
414
436
  #### `LocalServer`
415
437
 
@@ -509,25 +531,25 @@ Omitting `<command>` defaults to `dev` for backward compatibility.
509
531
  | `--port <number>` | Port or named pipe (default: `process.env.PORT` or `8080`) |
510
532
  | `--host <hostname>` | Hostname to bind (default: `process.env.HOST` or `0.0.0.0`) |
511
533
  | `--no-signals` | Disable `SIGINT` / `SIGTERM` handler registration |
512
- | `--shutdown-timeout <ms>` | Max ms to wait for in-flight requests (default: `5000`) |
534
+ | `--shutdown-timeout <ms>` | Max ms to wait for in-flight requests (default: `5000`; `0..2147483647`) |
513
535
  | `--require <module>` | Module(s) to preload before app load (repeatable; comma-separated values supported) |
514
536
  | `--env <path>` | Env file(s) to load before app load (repeatable; existing env vars are not overridden) |
515
537
  | `--watch <paths>` | Comma-separated paths to watch for restart (repeatable; forks a child process) |
516
538
  | `--ext <extensions>` | Comma-separated extensions to watch (default: `ts,js,mjs,cjs,json`) |
517
- | `--delay <ms>` | Debounce ms before restarting on change (default: `500`) |
539
+ | `--delay <ms>` | Debounce ms before restarting on change (default: `500`; `0..2147483647`) |
518
540
 
519
541
  #### build options
520
542
 
521
- | Option | Description |
522
- | --------------------- | -------------------------------------------------------------------------------- |
523
- | `<app-module>` | Module path whose **default export** is an Express app (sync, not async factory) |
524
- | `--init <path>` | Init hook module (default export, async function) called once per cold start |
525
- | `--out-dir <path>` | Output directory (default: `dist`) |
526
- | `--out-name <name>` | Output filename without extension (default: `app`) |
527
- | `--format <cjs\|esm>` | Output format (default: `cjs`) |
528
- | `--target <target>` | Compilation target (default: `node22`) |
529
- | `--external <pkg>` | Mark package as external (repeatable; `express` is always external) |
530
- | `--no-clean` | Don't clean the output directory before building |
543
+ | Option | Description |
544
+ | --------------------- | ---------------------------------------------------------------------------------------------------------- |
545
+ | `<app-module>` | Module path whose **default export** is an Express app (sync, not async factory) |
546
+ | `--init <path>` | Init hook module (default export, async function) called once per cold start |
547
+ | `--out-dir <path>` | Output directory (default: `dist`) |
548
+ | `--out-name <name>` | Output filename without extension (default: `app`) |
549
+ | `--format <cjs\|esm>` | Output format (default: `cjs`) |
550
+ | `--target <target>` | Compilation target (default: `node22`) |
551
+ | `--external <pkg>` | Mark package as external (repeatable; `express` and `@web-ts-toolkit/express-runtime` are always external) |
552
+ | `--no-clean` | Don't clean the output directory before building |
531
553
 
532
554
  #### start options
533
555
 
@@ -537,22 +559,22 @@ Omitting `<command>` defaults to `dev` for backward compatibility.
537
559
  | `--port <number>` | Port or named pipe (default: `process.env.PORT` or `8080`) |
538
560
  | `--host <hostname>` | Hostname to bind (default: `process.env.HOST` or `0.0.0.0`) |
539
561
  | `--no-signals` | Disable `SIGINT` / `SIGTERM` handler registration |
540
- | `--shutdown-timeout <ms>` | Max ms to wait for in-flight requests (default: `5000`) |
562
+ | `--shutdown-timeout <ms>` | Max ms to wait for in-flight requests (default: `5000`; `0..2147483647`) |
541
563
  | `--require <module>` | Module(s) to preload before app load (repeatable; comma-separated values supported) |
542
564
  | `--env <path>` | Env file(s) to load before app load (repeatable; existing env vars are not overridden) |
543
565
 
544
566
  #### build-serverless options
545
567
 
546
- | Option | Description |
547
- | --------------------- | -------------------------------------------------------------------------------- |
548
- | `<app-module>` | Module path whose **default export** is an Express app (sync, not async factory) |
549
- | `--init <path>` | Init hook module (default export, async function) called once per cold start |
550
- | `--out-dir <path>` | Output directory (default: `dist`) |
551
- | `--out-name <name>` | Output filename without extension (default: `handler`) |
552
- | `--format <cjs\|esm>` | Output format (default: `cjs`) |
553
- | `--target <target>` | Compilation target (default: `node22`) |
554
- | `--external <pkg>` | Mark package as external (repeatable; `express` is always external) |
555
- | `--no-clean` | Don't clean the output directory before building |
568
+ | Option | Description |
569
+ | --------------------- | ---------------------------------------------------------------------------------------------------------- |
570
+ | `<app-module>` | Module path whose **default export** is an Express app (sync, not async factory) |
571
+ | `--init <path>` | Init hook module (default export, async function) called once per cold start |
572
+ | `--out-dir <path>` | Output directory (default: `dist`) |
573
+ | `--out-name <name>` | Output filename without extension (default: `handler`) |
574
+ | `--format <cjs\|esm>` | Output format (default: `cjs`) |
575
+ | `--target <target>` | Compilation target (default: `node22`) |
576
+ | `--external <pkg>` | Mark package as external (repeatable; `express` and `@web-ts-toolkit/express-runtime` are always external) |
577
+ | `--no-clean` | Don't clean the output directory before building |
556
578
 
557
579
  #### start-serverless options
558
580
 
@@ -562,7 +584,7 @@ Omitting `<command>` defaults to `dev` for backward compatibility.
562
584
  | `--port <number>` | Port or named pipe (default: `process.env.PORT` or `8080`) |
563
585
  | `--host <hostname>` | Hostname to bind (default: `process.env.HOST` or `0.0.0.0`) |
564
586
  | `--no-signals` | Disable `SIGINT` / `SIGTERM` handler registration |
565
- | `--shutdown-timeout <ms>` | Max ms to wait for in-flight requests (default: `5000`) |
587
+ | `--shutdown-timeout <ms>` | Max ms to wait for in-flight requests (default: `5000`; `0..2147483647`) |
566
588
  | `--max-body-bytes <bytes>` | Max request body bytes for adapter (default: `1048576`; `0` allows empty bodies only) |
567
589
  | `--require <module>` | Module(s) to preload before handler load (repeatable; comma-separated values supported) |
568
590
  | `--env <path>` | Env file(s) to load before handler load (repeatable; existing env vars are not overridden) |
@@ -578,8 +600,11 @@ Use `--` to stop option parsing when a positional module path starts with a
578
600
  dash, for example `wtt-express-runtime dev -- --app.js`. Numeric CLI values are
579
601
  validated before env files, preload modules, app modules, watchers, or servers
580
602
  are opened. Ports must be canonical decimal integers in `0..65535` or explicit
581
- nonnumeric named-pipe paths; timeout, delay, and adapter body-limit values must
582
- be finite integers in `0..9007199254740991`.
603
+ nonnumeric named-pipe paths; timer durations (`--shutdown-timeout`, `--delay`)
604
+ must be finite integers in `0..2147483647` (Node's `setTimeout` limit — larger
605
+ values are rejected instead of overflowing into near-immediate timers; `0`
606
+ means no wait and `2147483647` is the largest safe delay). Adapter body-limit
607
+ values (`--max-body-bytes`) keep the wider `0..9007199254740991` range.
583
608
 
584
609
  The `dev` command sets `exitAfterShutdown: true` so `SIGINT` / `SIGTERM` cleanly
585
610
  exit the process after the server drains. TypeScript app modules require a TS
@@ -594,9 +619,14 @@ starts.
594
619
  The `build` command generates a temporary entry file that re-exports the app
595
620
  module and optional `init` hook, then produces a local runtime bundle. The
596
621
  `build-serverless` command instead wraps the app with `createServerlessHandler`
597
- and bundles the serverless runtime. `express` is always external; all other
598
- dependencies are bundled into the output unless marked external via
599
- `--external`.
622
+ and bundles the serverless runtime. `express` and
623
+ `@web-ts-toolkit/express-runtime` (imported by the generated entry) are always
624
+ external; all other dependencies are bundled into the output unless marked
625
+ external via `--external`. Deploy the bundle with both mandatory externals
626
+ installed — `pnpm add express @web-ts-toolkit/express-runtime`
627
+ (`serverless-http` ships with the runtime package, so no extra install is
628
+ needed for it). A bundle executed without the runtime package installed fails
629
+ to load with a missing-module error for `@web-ts-toolkit/express-runtime`.
600
630
 
601
631
  ## License
602
632