@web-ts-toolkit/express-runtime 0.40.1 → 0.41.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/cli-api.d.mts CHANGED
@@ -1,15 +1,14 @@
1
- import { Response, Express } from 'express';
2
- import { LocalServerOptions } from './index.mjs';
1
+ import { LocalServerOptions, LocalServer } from './index.mjs';
2
+ import { watch, existsSync } from 'node:fs';
3
+ import { ChildProcess, fork } from 'node:child_process';
4
+ import { Response, Request, Express } from 'express';
3
5
  import 'node:http';
4
6
  import 'serverless-http';
5
7
 
8
+ declare const CLI_VERSION: string;
6
9
  /**
7
- * Version placeholder rewritten at publish time by `@repo-toolkit/publish-package`.
8
- */
9
- declare const CLI_VERSION = "0.0.0-PLACEHOLDER";
10
- /**
11
- * Read the next argv value after a flag, throwing if it is missing or looks
12
- * like another flag.
10
+ * Read the next argv value after a flag, throwing if it is missing, empty, or
11
+ * looks like another flag.
13
12
  */
14
13
  declare function readValue(argv: string[], index: number, name: string): string;
15
14
  type Subcommand = 'dev' | 'build' | 'start' | 'build-serverless' | 'start-serverless';
@@ -42,7 +41,6 @@ interface BuildArgs {
42
41
  }
43
42
  interface BuildEntryContentArgs {
44
43
  entryContent: string;
45
- tempEntryFilename: string;
46
44
  tsconfigPath?: string;
47
45
  outDir: string;
48
46
  outName: string;
@@ -62,6 +60,8 @@ interface StartArgs {
62
60
  interface StartServerlessArgs {
63
61
  handlerPath: string;
64
62
  options: Omit<LocalServerOptions, 'init' | 'onShutdown'>;
63
+ /** Maximum bytes to buffer for request bodies via the local adapter. Default: 1048576. */
64
+ maxBodyBytes?: number;
65
65
  /** Modules to preload before loading the handler (repeatable `--require`). */
66
66
  require: string[];
67
67
  /** Env files to load before loading the handler (repeatable `--env`). */
@@ -107,7 +107,8 @@ declare function loadApp(appPath: string): Promise<Express>;
107
107
  * Parse env file content as KEY=VALUE lines. Supports `export` prefix,
108
108
  * single/double-quoted values, and `#` comments. Returns parsed entries.
109
109
  *
110
- * Exported for direct unit testing.
110
+ * Public helper for packages that reuse the CLI's env-file parsing without
111
+ * shelling out to the binary.
111
112
  */
112
113
  declare function parseEnvFile(content: string): Record<string, string>;
113
114
  /**
@@ -115,7 +116,8 @@ declare function parseEnvFile(content: string): Record<string, string>;
115
116
  * **not** overridden (consistent with dotenv's default behavior). Missing
116
117
  * files throw with a friendly message.
117
118
  *
118
- * Exported for direct unit testing.
119
+ * Public helper for programmatic CLI integrations. Mutates `process.env` by
120
+ * design and never overwrites existing environment variables.
119
121
  */
120
122
  declare function loadEnvFiles(paths: string[]): void;
121
123
  /**
@@ -123,38 +125,90 @@ declare function loadEnvFiles(paths: string[]): void;
123
125
  * loading the app module. Each module is `require()`-ed, running its
124
126
  * side effects (registering hooks, loading configs, etc.).
125
127
  *
126
- * Exported for direct unit testing.
128
+ * Public helper for programmatic CLI integrations that need the same preload
129
+ * behavior as the binary before loading an app or handler module.
127
130
  */
128
131
  declare function preloadModules(modules: string[]): Promise<void>;
132
+ /**
133
+ * Dependencies for watch supervision, injectable for tests.
134
+ * Not part of the documented public API; exposed for deterministic testing
135
+ * without expanding the supported consumer contract.
136
+ */
137
+ interface WatchSupervisorDeps {
138
+ fork?: typeof fork;
139
+ watch?: typeof watch;
140
+ existsSync?: typeof existsSync;
141
+ logger?: Pick<Console, 'error'>;
142
+ killTimeoutMs?: number;
143
+ setTimeout?: typeof setTimeout;
144
+ clearTimeout?: typeof clearTimeout;
145
+ exit?: (code: number) => void;
146
+ installSignalHandlers?: boolean;
147
+ }
148
+ /**
149
+ * Controller returned by the injectable supervisor factory.
150
+ * Allows tests to observe and deterministically shut down watchers/children.
151
+ */
152
+ interface WatchSupervisorController {
153
+ /** Stop watching and terminate child, idempotent. */
154
+ shutdown: () => Promise<void>;
155
+ /** Currently tracked child, if any. */
156
+ getChild: () => ChildProcess | null;
157
+ /** Active watchers (FSWatcher handles). */
158
+ getWatchers: () => ReturnType<typeof watch>[];
159
+ /** Whether shutdown has been initiated. */
160
+ isShuttingDown: () => boolean;
161
+ }
129
162
  /**
130
163
  * Reconstruct the argv for the child process, stripping --watch/--ext/--delay
131
164
  * flags (the child runs without watch mode).
132
165
  *
133
- * Exported for direct unit testing.
166
+ * Public helper for CLI wrappers that supervise watch mode themselves and need
167
+ * the same child argv reconstruction as `runWithWatch`.
134
168
  */
135
169
  declare function buildChildArgs(args: DevArgs): string[];
136
170
  /**
137
171
  * Run the CLI in watch mode. Forks a child process running the same CLI
138
172
  * without --watch, watches the specified paths for file changes, and
139
- * restarts the child (SIGTERM respawn) on changes matching the given
140
- * extensions. Uses Node 20+'s `fs.watch` with `{ recursive: true }`.
173
+ * restarts the child (SIGTERM, then SIGKILL after 5 seconds) on changes
174
+ * matching the given extensions. Uses Node 20+'s `fs.watch` with
175
+ * `{ recursive: true }`.
176
+ *
177
+ * Production entry point that delegates to `createWatchSupervisor` with real
178
+ * dependencies and installs signal handlers that exit the process.
141
179
  */
142
- declare function runWithWatch(args: DevArgs): void;
180
+ declare function runWithWatch(args: DevArgs, deps?: WatchSupervisorDeps): WatchSupervisorController;
181
+ /** Legacy fixed staging filenames — no longer written, but retained for regression tests that verify they are not overwritten. */
182
+ declare const TEMP_BUILD_ENTRY_FILENAME = ".express-runtime-build-entry.ts";
183
+ declare const TEMP_SERVERLESS_ENTRY_FILENAME = ".express-runtime-build-serverless-entry.ts";
143
184
  type RuntimeModuleInit = () => Promise<void> | void;
185
+ type RuntimeModuleShutdown = () => Promise<void> | void;
144
186
  /**
145
187
  * Generate the temporary entry file content that wires the user's app and
146
188
  * optional init hook into a serverless handler.
147
189
  *
148
- * Exported for direct unit testing.
190
+ * Public build-entry generator used by programmatic CLI integrations.
149
191
  */
150
192
  declare function generateServerlessEntry(appPath: string, initPath?: string): string;
151
193
  /**
152
194
  * Generate the temporary entry file content that wires the user's app and
153
195
  * optional init hook into a local runtime bundle.
154
196
  *
155
- * Exported for direct unit testing.
197
+ * Public build-entry generator used by programmatic CLI integrations.
156
198
  */
157
199
  declare function generateRuntimeEntry(appPath: string, initPath?: string): string;
200
+ /**
201
+ * Validate that `outDir` is safe to clean before invoking tsup.
202
+ * Prevents destructive `clean: true` combinations:
203
+ * - filesystem root
204
+ * - project cwd itself (repository root)
205
+ * - symlinked output directories
206
+ * - output that contains input files (appPath/initPath)
207
+ *
208
+ * Public safety check for programmatic build integrations before invoking
209
+ * `buildBundleFromEntryContent()` with `clean: true`.
210
+ */
211
+ declare function validateOutDirForClean(outDir: string, clean: boolean, appPath?: string, initPath?: string): void;
158
212
  declare function buildBundleFromEntryContent(args: BuildEntryContentArgs): Promise<void>;
159
213
  /**
160
214
  * Bundle an Express app as a local runtime module. The output default-exports
@@ -175,25 +229,70 @@ declare function buildServerless(args: BuildArgs): Promise<void>;
175
229
  * `build-serverless`).
176
230
  */
177
231
  type GenericHandler = (event: unknown, context: unknown) => Promise<unknown>;
232
+ /** AWS API Gateway REST API v1 / Lambda proxy event shape emitted by the local adapter. */
233
+ interface ApiGatewayRestEvent {
234
+ httpMethod: string;
235
+ path: string;
236
+ headers: Record<string, string>;
237
+ multiValueHeaders: Record<string, string[]>;
238
+ queryStringParameters: Record<string, string> | null;
239
+ multiValueQueryStringParameters: Record<string, string[]> | null;
240
+ body: string;
241
+ isBase64Encoded: boolean;
242
+ requestContext: {
243
+ identity: {
244
+ sourceIp: string;
245
+ };
246
+ };
247
+ }
178
248
  /**
179
- * The result shape returned by `serverless-http` (and the `build` output).
249
+ * AWS API Gateway REST API v1 / Lambda proxy result shape returned by `serverless-http`.
180
250
  */
181
251
  interface ServerlessResult {
182
252
  statusCode?: number;
183
- headers?: Record<string, string | string[] | undefined>;
253
+ headers?: Record<string, string | undefined>;
254
+ multiValueHeaders?: Record<string, string[] | undefined>;
184
255
  body?: string;
185
256
  isBase64Encoded?: boolean;
186
257
  }
258
+ declare const DEFAULT_ADAPTER_MAX_BODY_BYTES: number;
259
+ interface ServerlessAdapterOptions {
260
+ /**
261
+ * Maximum bytes to buffer for a single request body.
262
+ * Default: 1048576 (1 MiB). Must be a finite non-negative integer.
263
+ * When `0`, no body is allowed — any non-empty body receives `413`.
264
+ * The adapter never retains more than this limit plus at most one incoming chunk.
265
+ */
266
+ maxBodyBytes?: number;
267
+ }
268
+ /**
269
+ * Validate `maxBodyBytes` — finite non-negative integer. Zero means no body allowed (empty bodies only).
270
+ * Public validator shared by CLI parsing and programmatic adapter callers.
271
+ */
272
+ declare function validateMaxBodyBytes(value: unknown): number;
273
+ /**
274
+ * Read the raw request body into a Buffer with bounded memory.
275
+ * Since `createExpressApp` is called with `json: false, urlencoded: false` in the adapter,
276
+ * no body parser has consumed the stream yet. Rejects oversized declared or incremental
277
+ * bodies with a `LIMIT_EXCEEDED` error (413), stops retaining chunks after the limit,
278
+ * removes owned listeners, and drains the request.
279
+ * Distinguishes client aborts (`CLIENT_ABORT`) and stream errors from oversize.
280
+ */
281
+ declare function collectBody(req: Request, maxBytes: number): Promise<Buffer>;
187
282
  /**
188
- * Build a serverless event from HTTP request components.
283
+ * Build an AWS API Gateway REST API v1 / Lambda proxy event from HTTP request components.
189
284
  *
190
- * Exported for direct unit testing.
285
+ * Public helper for adapters that need the same AWS REST API v1 event shape as
286
+ * the `start-serverless` command.
191
287
  */
192
- declare function toServerlessEvent(method: string, url: string, headers: Record<string, string | string[] | undefined>, body: Buffer): Record<string, unknown>;
288
+ declare function toServerlessEvent(method: string, url: string, headers: Record<string, string | string[] | undefined>, body: Buffer): ApiGatewayRestEvent;
193
289
  /**
194
290
  * Write a serverless handler result to an Express response.
291
+ * Validates the complete AWS API Gateway REST API v1 / Lambda proxy result before writing anything.
292
+ * `multiValueHeaders` wins over `headers` when the same header appears in both maps.
195
293
  *
196
- * Exported for direct unit testing.
294
+ * Public helper for adapters that need the same AWS REST API v1 result-to-HTTP
295
+ * translation as the `start-serverless` command.
197
296
  */
198
297
  declare function applyServerlessResult(result: unknown, res: Response): void;
199
298
  /**
@@ -204,14 +303,18 @@ declare function applyServerlessResult(result: unknown, res: Response): void;
204
303
  * Express body parsers are disabled; the raw request body is read directly
205
304
  * from the stream and passed as a Buffer (so the serverless handler's request
206
305
  * hook — including the #305 workaround — works identically to production).
306
+ * Bodies exceeding `maxBodyBytes` (default 1 MiB, 0 = empty bodies only) receive
307
+ * `413 Payload Too Large` without invoking the handler; the request is drained
308
+ * and retained memory is bounded to the limit plus at most one chunk.
207
309
  */
208
- declare function createServerlessAdapterApp(handler: GenericHandler): Express;
310
+ declare function createServerlessAdapterApp(handler: GenericHandler, options?: ServerlessAdapterOptions): Express;
209
311
  /**
210
312
  * Load a bundled app module from the `build` output.
211
313
  */
212
314
  declare function loadBuiltApp(appPath: string): Promise<{
213
315
  app: Express;
214
316
  init?: RuntimeModuleInit;
317
+ shutdown?: RuntimeModuleShutdown;
215
318
  }>;
216
319
  /**
217
320
  * Load a bundled serverless handler from a JS/CJS module. The module must
@@ -224,12 +327,13 @@ interface DevCommandRunner<TLoaded> {
224
327
  load: (appPath: string) => Promise<TLoaded> | TLoaded;
225
328
  start: (loaded: TLoaded, options: DevArgs['options'] & {
226
329
  exitAfterShutdown: true;
227
- }) => void;
228
- watch?: (args: DevArgs) => void;
330
+ }) => LocalServer | void;
331
+ watch?: (args: DevArgs) => void | WatchSupervisorController;
229
332
  }
230
333
  interface BuildEntryCommandOptions {
231
334
  generateEntry: (appPath: string, initPath?: string) => string;
232
- tempEntryFilename: string;
335
+ /** @deprecated staging is now uniquely created; this is ignored if provided */
336
+ tempEntryFilename?: string;
233
337
  allowInit?: boolean;
234
338
  initErrorMessage?: string;
235
339
  }
@@ -238,4 +342,4 @@ declare function runExpressDevCommand(args: DevArgs): Promise<void>;
238
342
  declare function runBuildEntryCommand(args: BuildArgs, options: BuildEntryCommandOptions): Promise<void>;
239
343
  declare function runCliCommand(parsedArgs: RuntimeCliCommand): Promise<void>;
240
344
 
241
- export { type BuildArgs, type BuildEntryCommandOptions, type BuildEntryContentArgs, CLI_VERSION, type DevArgs, type DevCommandRunner, type GenericHandler, type ParsedArgs, type RuntimeCliCommand, type RuntimeModuleInit, type ServerlessResult, type StartArgs, type StartServerlessArgs, type Subcommand, applyServerlessResult, buildBundleFromEntryContent, buildChildArgs, buildRuntime, buildServerless, createServerlessAdapterApp, extractExport, generateRuntimeEntry, generateServerlessEntry, isExpressApp, loadApp, loadBuiltApp, loadEnvFiles, loadHandler, parseArgs, parseEnvFile, preloadModules, printHelp, readValue, resolveExport, runBuildEntryCommand, runCliCommand, runDevCommand, runExpressDevCommand, runWithWatch, toServerlessEvent };
345
+ export { type ApiGatewayRestEvent, type BuildArgs, type BuildEntryCommandOptions, type BuildEntryContentArgs, CLI_VERSION, DEFAULT_ADAPTER_MAX_BODY_BYTES, type DevArgs, type DevCommandRunner, type GenericHandler, type ParsedArgs, type RuntimeCliCommand, type RuntimeModuleInit, type RuntimeModuleShutdown, type ServerlessAdapterOptions, type ServerlessResult, type StartArgs, type StartServerlessArgs, type Subcommand, TEMP_BUILD_ENTRY_FILENAME, TEMP_SERVERLESS_ENTRY_FILENAME, applyServerlessResult, buildBundleFromEntryContent, buildChildArgs, buildRuntime, buildServerless, collectBody, createServerlessAdapterApp, extractExport, generateRuntimeEntry, generateServerlessEntry, isExpressApp, loadApp, loadBuiltApp, loadEnvFiles, loadHandler, parseArgs, parseEnvFile, preloadModules, printHelp, readValue, resolveExport, runBuildEntryCommand, runCliCommand, runDevCommand, runExpressDevCommand, runWithWatch, toServerlessEvent, validateMaxBodyBytes, validateOutDirForClean };
package/cli-api.d.ts CHANGED
@@ -1,15 +1,14 @@
1
- import { Response, Express } from 'express';
2
- import { LocalServerOptions } from './index.js';
1
+ import { LocalServerOptions, LocalServer } from './index.js';
2
+ import { watch, existsSync } from 'node:fs';
3
+ import { ChildProcess, fork } from 'node:child_process';
4
+ import { Response, Request, Express } from 'express';
3
5
  import 'node:http';
4
6
  import 'serverless-http';
5
7
 
8
+ declare const CLI_VERSION: string;
6
9
  /**
7
- * Version placeholder rewritten at publish time by `@repo-toolkit/publish-package`.
8
- */
9
- declare const CLI_VERSION = "0.0.0-PLACEHOLDER";
10
- /**
11
- * Read the next argv value after a flag, throwing if it is missing or looks
12
- * like another flag.
10
+ * Read the next argv value after a flag, throwing if it is missing, empty, or
11
+ * looks like another flag.
13
12
  */
14
13
  declare function readValue(argv: string[], index: number, name: string): string;
15
14
  type Subcommand = 'dev' | 'build' | 'start' | 'build-serverless' | 'start-serverless';
@@ -42,7 +41,6 @@ interface BuildArgs {
42
41
  }
43
42
  interface BuildEntryContentArgs {
44
43
  entryContent: string;
45
- tempEntryFilename: string;
46
44
  tsconfigPath?: string;
47
45
  outDir: string;
48
46
  outName: string;
@@ -62,6 +60,8 @@ interface StartArgs {
62
60
  interface StartServerlessArgs {
63
61
  handlerPath: string;
64
62
  options: Omit<LocalServerOptions, 'init' | 'onShutdown'>;
63
+ /** Maximum bytes to buffer for request bodies via the local adapter. Default: 1048576. */
64
+ maxBodyBytes?: number;
65
65
  /** Modules to preload before loading the handler (repeatable `--require`). */
66
66
  require: string[];
67
67
  /** Env files to load before loading the handler (repeatable `--env`). */
@@ -107,7 +107,8 @@ declare function loadApp(appPath: string): Promise<Express>;
107
107
  * Parse env file content as KEY=VALUE lines. Supports `export` prefix,
108
108
  * single/double-quoted values, and `#` comments. Returns parsed entries.
109
109
  *
110
- * Exported for direct unit testing.
110
+ * Public helper for packages that reuse the CLI's env-file parsing without
111
+ * shelling out to the binary.
111
112
  */
112
113
  declare function parseEnvFile(content: string): Record<string, string>;
113
114
  /**
@@ -115,7 +116,8 @@ declare function parseEnvFile(content: string): Record<string, string>;
115
116
  * **not** overridden (consistent with dotenv's default behavior). Missing
116
117
  * files throw with a friendly message.
117
118
  *
118
- * Exported for direct unit testing.
119
+ * Public helper for programmatic CLI integrations. Mutates `process.env` by
120
+ * design and never overwrites existing environment variables.
119
121
  */
120
122
  declare function loadEnvFiles(paths: string[]): void;
121
123
  /**
@@ -123,38 +125,90 @@ declare function loadEnvFiles(paths: string[]): void;
123
125
  * loading the app module. Each module is `require()`-ed, running its
124
126
  * side effects (registering hooks, loading configs, etc.).
125
127
  *
126
- * Exported for direct unit testing.
128
+ * Public helper for programmatic CLI integrations that need the same preload
129
+ * behavior as the binary before loading an app or handler module.
127
130
  */
128
131
  declare function preloadModules(modules: string[]): Promise<void>;
132
+ /**
133
+ * Dependencies for watch supervision, injectable for tests.
134
+ * Not part of the documented public API; exposed for deterministic testing
135
+ * without expanding the supported consumer contract.
136
+ */
137
+ interface WatchSupervisorDeps {
138
+ fork?: typeof fork;
139
+ watch?: typeof watch;
140
+ existsSync?: typeof existsSync;
141
+ logger?: Pick<Console, 'error'>;
142
+ killTimeoutMs?: number;
143
+ setTimeout?: typeof setTimeout;
144
+ clearTimeout?: typeof clearTimeout;
145
+ exit?: (code: number) => void;
146
+ installSignalHandlers?: boolean;
147
+ }
148
+ /**
149
+ * Controller returned by the injectable supervisor factory.
150
+ * Allows tests to observe and deterministically shut down watchers/children.
151
+ */
152
+ interface WatchSupervisorController {
153
+ /** Stop watching and terminate child, idempotent. */
154
+ shutdown: () => Promise<void>;
155
+ /** Currently tracked child, if any. */
156
+ getChild: () => ChildProcess | null;
157
+ /** Active watchers (FSWatcher handles). */
158
+ getWatchers: () => ReturnType<typeof watch>[];
159
+ /** Whether shutdown has been initiated. */
160
+ isShuttingDown: () => boolean;
161
+ }
129
162
  /**
130
163
  * Reconstruct the argv for the child process, stripping --watch/--ext/--delay
131
164
  * flags (the child runs without watch mode).
132
165
  *
133
- * Exported for direct unit testing.
166
+ * Public helper for CLI wrappers that supervise watch mode themselves and need
167
+ * the same child argv reconstruction as `runWithWatch`.
134
168
  */
135
169
  declare function buildChildArgs(args: DevArgs): string[];
136
170
  /**
137
171
  * Run the CLI in watch mode. Forks a child process running the same CLI
138
172
  * without --watch, watches the specified paths for file changes, and
139
- * restarts the child (SIGTERM respawn) on changes matching the given
140
- * extensions. Uses Node 20+'s `fs.watch` with `{ recursive: true }`.
173
+ * restarts the child (SIGTERM, then SIGKILL after 5 seconds) on changes
174
+ * matching the given extensions. Uses Node 20+'s `fs.watch` with
175
+ * `{ recursive: true }`.
176
+ *
177
+ * Production entry point that delegates to `createWatchSupervisor` with real
178
+ * dependencies and installs signal handlers that exit the process.
141
179
  */
142
- declare function runWithWatch(args: DevArgs): void;
180
+ declare function runWithWatch(args: DevArgs, deps?: WatchSupervisorDeps): WatchSupervisorController;
181
+ /** Legacy fixed staging filenames — no longer written, but retained for regression tests that verify they are not overwritten. */
182
+ declare const TEMP_BUILD_ENTRY_FILENAME = ".express-runtime-build-entry.ts";
183
+ declare const TEMP_SERVERLESS_ENTRY_FILENAME = ".express-runtime-build-serverless-entry.ts";
143
184
  type RuntimeModuleInit = () => Promise<void> | void;
185
+ type RuntimeModuleShutdown = () => Promise<void> | void;
144
186
  /**
145
187
  * Generate the temporary entry file content that wires the user's app and
146
188
  * optional init hook into a serverless handler.
147
189
  *
148
- * Exported for direct unit testing.
190
+ * Public build-entry generator used by programmatic CLI integrations.
149
191
  */
150
192
  declare function generateServerlessEntry(appPath: string, initPath?: string): string;
151
193
  /**
152
194
  * Generate the temporary entry file content that wires the user's app and
153
195
  * optional init hook into a local runtime bundle.
154
196
  *
155
- * Exported for direct unit testing.
197
+ * Public build-entry generator used by programmatic CLI integrations.
156
198
  */
157
199
  declare function generateRuntimeEntry(appPath: string, initPath?: string): string;
200
+ /**
201
+ * Validate that `outDir` is safe to clean before invoking tsup.
202
+ * Prevents destructive `clean: true` combinations:
203
+ * - filesystem root
204
+ * - project cwd itself (repository root)
205
+ * - symlinked output directories
206
+ * - output that contains input files (appPath/initPath)
207
+ *
208
+ * Public safety check for programmatic build integrations before invoking
209
+ * `buildBundleFromEntryContent()` with `clean: true`.
210
+ */
211
+ declare function validateOutDirForClean(outDir: string, clean: boolean, appPath?: string, initPath?: string): void;
158
212
  declare function buildBundleFromEntryContent(args: BuildEntryContentArgs): Promise<void>;
159
213
  /**
160
214
  * Bundle an Express app as a local runtime module. The output default-exports
@@ -175,25 +229,70 @@ declare function buildServerless(args: BuildArgs): Promise<void>;
175
229
  * `build-serverless`).
176
230
  */
177
231
  type GenericHandler = (event: unknown, context: unknown) => Promise<unknown>;
232
+ /** AWS API Gateway REST API v1 / Lambda proxy event shape emitted by the local adapter. */
233
+ interface ApiGatewayRestEvent {
234
+ httpMethod: string;
235
+ path: string;
236
+ headers: Record<string, string>;
237
+ multiValueHeaders: Record<string, string[]>;
238
+ queryStringParameters: Record<string, string> | null;
239
+ multiValueQueryStringParameters: Record<string, string[]> | null;
240
+ body: string;
241
+ isBase64Encoded: boolean;
242
+ requestContext: {
243
+ identity: {
244
+ sourceIp: string;
245
+ };
246
+ };
247
+ }
178
248
  /**
179
- * The result shape returned by `serverless-http` (and the `build` output).
249
+ * AWS API Gateway REST API v1 / Lambda proxy result shape returned by `serverless-http`.
180
250
  */
181
251
  interface ServerlessResult {
182
252
  statusCode?: number;
183
- headers?: Record<string, string | string[] | undefined>;
253
+ headers?: Record<string, string | undefined>;
254
+ multiValueHeaders?: Record<string, string[] | undefined>;
184
255
  body?: string;
185
256
  isBase64Encoded?: boolean;
186
257
  }
258
+ declare const DEFAULT_ADAPTER_MAX_BODY_BYTES: number;
259
+ interface ServerlessAdapterOptions {
260
+ /**
261
+ * Maximum bytes to buffer for a single request body.
262
+ * Default: 1048576 (1 MiB). Must be a finite non-negative integer.
263
+ * When `0`, no body is allowed — any non-empty body receives `413`.
264
+ * The adapter never retains more than this limit plus at most one incoming chunk.
265
+ */
266
+ maxBodyBytes?: number;
267
+ }
268
+ /**
269
+ * Validate `maxBodyBytes` — finite non-negative integer. Zero means no body allowed (empty bodies only).
270
+ * Public validator shared by CLI parsing and programmatic adapter callers.
271
+ */
272
+ declare function validateMaxBodyBytes(value: unknown): number;
273
+ /**
274
+ * Read the raw request body into a Buffer with bounded memory.
275
+ * Since `createExpressApp` is called with `json: false, urlencoded: false` in the adapter,
276
+ * no body parser has consumed the stream yet. Rejects oversized declared or incremental
277
+ * bodies with a `LIMIT_EXCEEDED` error (413), stops retaining chunks after the limit,
278
+ * removes owned listeners, and drains the request.
279
+ * Distinguishes client aborts (`CLIENT_ABORT`) and stream errors from oversize.
280
+ */
281
+ declare function collectBody(req: Request, maxBytes: number): Promise<Buffer>;
187
282
  /**
188
- * Build a serverless event from HTTP request components.
283
+ * Build an AWS API Gateway REST API v1 / Lambda proxy event from HTTP request components.
189
284
  *
190
- * Exported for direct unit testing.
285
+ * Public helper for adapters that need the same AWS REST API v1 event shape as
286
+ * the `start-serverless` command.
191
287
  */
192
- declare function toServerlessEvent(method: string, url: string, headers: Record<string, string | string[] | undefined>, body: Buffer): Record<string, unknown>;
288
+ declare function toServerlessEvent(method: string, url: string, headers: Record<string, string | string[] | undefined>, body: Buffer): ApiGatewayRestEvent;
193
289
  /**
194
290
  * Write a serverless handler result to an Express response.
291
+ * Validates the complete AWS API Gateway REST API v1 / Lambda proxy result before writing anything.
292
+ * `multiValueHeaders` wins over `headers` when the same header appears in both maps.
195
293
  *
196
- * Exported for direct unit testing.
294
+ * Public helper for adapters that need the same AWS REST API v1 result-to-HTTP
295
+ * translation as the `start-serverless` command.
197
296
  */
198
297
  declare function applyServerlessResult(result: unknown, res: Response): void;
199
298
  /**
@@ -204,14 +303,18 @@ declare function applyServerlessResult(result: unknown, res: Response): void;
204
303
  * Express body parsers are disabled; the raw request body is read directly
205
304
  * from the stream and passed as a Buffer (so the serverless handler's request
206
305
  * hook — including the #305 workaround — works identically to production).
306
+ * Bodies exceeding `maxBodyBytes` (default 1 MiB, 0 = empty bodies only) receive
307
+ * `413 Payload Too Large` without invoking the handler; the request is drained
308
+ * and retained memory is bounded to the limit plus at most one chunk.
207
309
  */
208
- declare function createServerlessAdapterApp(handler: GenericHandler): Express;
310
+ declare function createServerlessAdapterApp(handler: GenericHandler, options?: ServerlessAdapterOptions): Express;
209
311
  /**
210
312
  * Load a bundled app module from the `build` output.
211
313
  */
212
314
  declare function loadBuiltApp(appPath: string): Promise<{
213
315
  app: Express;
214
316
  init?: RuntimeModuleInit;
317
+ shutdown?: RuntimeModuleShutdown;
215
318
  }>;
216
319
  /**
217
320
  * Load a bundled serverless handler from a JS/CJS module. The module must
@@ -224,12 +327,13 @@ interface DevCommandRunner<TLoaded> {
224
327
  load: (appPath: string) => Promise<TLoaded> | TLoaded;
225
328
  start: (loaded: TLoaded, options: DevArgs['options'] & {
226
329
  exitAfterShutdown: true;
227
- }) => void;
228
- watch?: (args: DevArgs) => void;
330
+ }) => LocalServer | void;
331
+ watch?: (args: DevArgs) => void | WatchSupervisorController;
229
332
  }
230
333
  interface BuildEntryCommandOptions {
231
334
  generateEntry: (appPath: string, initPath?: string) => string;
232
- tempEntryFilename: string;
335
+ /** @deprecated staging is now uniquely created; this is ignored if provided */
336
+ tempEntryFilename?: string;
233
337
  allowInit?: boolean;
234
338
  initErrorMessage?: string;
235
339
  }
@@ -238,4 +342,4 @@ declare function runExpressDevCommand(args: DevArgs): Promise<void>;
238
342
  declare function runBuildEntryCommand(args: BuildArgs, options: BuildEntryCommandOptions): Promise<void>;
239
343
  declare function runCliCommand(parsedArgs: RuntimeCliCommand): Promise<void>;
240
344
 
241
- export { type BuildArgs, type BuildEntryCommandOptions, type BuildEntryContentArgs, CLI_VERSION, type DevArgs, type DevCommandRunner, type GenericHandler, type ParsedArgs, type RuntimeCliCommand, type RuntimeModuleInit, type ServerlessResult, type StartArgs, type StartServerlessArgs, type Subcommand, applyServerlessResult, buildBundleFromEntryContent, buildChildArgs, buildRuntime, buildServerless, createServerlessAdapterApp, extractExport, generateRuntimeEntry, generateServerlessEntry, isExpressApp, loadApp, loadBuiltApp, loadEnvFiles, loadHandler, parseArgs, parseEnvFile, preloadModules, printHelp, readValue, resolveExport, runBuildEntryCommand, runCliCommand, runDevCommand, runExpressDevCommand, runWithWatch, toServerlessEvent };
345
+ export { type ApiGatewayRestEvent, type BuildArgs, type BuildEntryCommandOptions, type BuildEntryContentArgs, CLI_VERSION, DEFAULT_ADAPTER_MAX_BODY_BYTES, type DevArgs, type DevCommandRunner, type GenericHandler, type ParsedArgs, type RuntimeCliCommand, type RuntimeModuleInit, type RuntimeModuleShutdown, type ServerlessAdapterOptions, type ServerlessResult, type StartArgs, type StartServerlessArgs, type Subcommand, TEMP_BUILD_ENTRY_FILENAME, TEMP_SERVERLESS_ENTRY_FILENAME, applyServerlessResult, buildBundleFromEntryContent, buildChildArgs, buildRuntime, buildServerless, collectBody, createServerlessAdapterApp, extractExport, generateRuntimeEntry, generateServerlessEntry, isExpressApp, loadApp, loadBuiltApp, loadEnvFiles, loadHandler, parseArgs, parseEnvFile, preloadModules, printHelp, readValue, resolveExport, runBuildEntryCommand, runCliCommand, runDevCommand, runExpressDevCommand, runWithWatch, toServerlessEvent, validateMaxBodyBytes, validateOutDirForClean };