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