@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/index.d.mts CHANGED
@@ -4,6 +4,14 @@ export { ErrorRequestHandler, Express, RequestHandler } from 'express';
4
4
  import serverless from 'serverless-http';
5
5
  export { Options as RawServerlessHttpOptions } from 'serverless-http';
6
6
 
7
+ interface FiniteIntegerValidationOptions {
8
+ name: string;
9
+ min?: number;
10
+ max?: number;
11
+ }
12
+ declare function validateFiniteInteger(value: unknown, options: FiniteIntegerValidationOptions): number;
13
+ declare function parsePortValue(value: number | string, name: string): number | string;
14
+
7
15
  interface Logger {
8
16
  log: (...args: unknown[]) => void;
9
17
  error: (...args: unknown[]) => void;
@@ -21,16 +29,20 @@ interface RouterMount {
21
29
  interface ExpressAppOptions {
22
30
  /**
23
31
  * Middleware registered before the built-in body parsers. Use this for
24
- * logging, helmet, request-id, etc.
32
+ * logging, helmet, request-id, etc. Express error handlers in this slot only
33
+ * catch errors from earlier middleware, not later router errors.
25
34
  */
26
35
  preMiddleware?: ReadonlyArray<RequestHandler | ErrorRequestHandler>;
27
36
  /**
28
37
  * Middleware registered after body parsers, before routers. Default location
29
- * for cookies, sessions, auth, CORS, etc.
38
+ * for cookies, sessions, auth, CORS, etc. Error-handler entries here only
39
+ * catch errors from earlier slots.
30
40
  */
31
41
  middleware?: ReadonlyArray<RequestHandler | ErrorRequestHandler>;
32
42
  /**
33
- * Middleware registered after all routers (e.g. a 404 catch-all).
43
+ * Middleware registered after all routers (e.g. a 404 catch-all). Error
44
+ * handlers here catch router errors only if Express reaches this slot before
45
+ * a later handler; prefer `errorHandler` for the final app-wide error handler.
34
46
  */
35
47
  postMiddleware?: ReadonlyArray<RequestHandler | ErrorRequestHandler>;
36
48
  /**
@@ -76,11 +88,12 @@ declare function createExpressApp(options?: ExpressAppOptions): Express;
76
88
  * Lambda, and any platform that calls `(event, context)` and expects a
77
89
  * response.
78
90
  */
79
- type ServerlessHandler = ((event: unknown, context: unknown) => Promise<unknown>) & {
91
+ type ServerlessHandler<TEvent extends object = Record<string, unknown>, TContext extends object = Record<string, unknown>> = ((event: TEvent, context: TContext) => Promise<object>) & {
80
92
  /**
81
- * Reset the memoized init promise. Call to retry a failed cold-start
82
- * (`init()` rejection is memoized; without `reset()` every subsequent
83
- * invocation re-throws).
93
+ * Reset the memoized init promise after it settles. Call to retry a failed
94
+ * cold-start (`init()` rejection is memoized; without `reset()` every
95
+ * subsequent invocation re-throws). Calls while init is still pending are
96
+ * ignored so they cannot start concurrent initialization.
84
97
  */
85
98
  reset: () => void;
86
99
  };
@@ -88,24 +101,28 @@ type ServerlessHandler = ((event: unknown, context: unknown) => Promise<unknown>
88
101
  type ServerlessHttpOptions = NonNullable<Parameters<typeof serverless>[1]>;
89
102
  interface ServerlessRequest {
90
103
  body?: unknown;
91
- headers?: Record<string, string>;
104
+ headers?: Record<string, string | string[] | undefined>;
92
105
  }
93
- interface ServerlessHandlerOptions {
106
+ type ServerlessResponse = http.ServerResponse;
107
+ type ServerlessRequestHook<TEvent extends object = Record<string, unknown>, TContext extends object = Record<string, unknown>> = (req: ServerlessRequest, event: TEvent, context: TContext) => void | Promise<void>;
108
+ type ServerlessResponseHook<TEvent extends object = Record<string, unknown>, TContext extends object = Record<string, unknown>> = (res: ServerlessResponse, event: TEvent, context: TContext) => void | Promise<void>;
109
+ interface ServerlessHandlerOptions<TEvent extends object = Record<string, unknown>, TContext extends object = Record<string, unknown>> {
94
110
  /**
95
111
  * Called once per cold start before delegating to the handler. The resulting
96
112
  * promise is memoized so subsequent warm invocations skip re-initialization.
97
- * A rejected promise is **also** memoized — call `handler.reset()` to retry.
98
- * Use this for DB connections, cache warmup, etc.
113
+ * Synchronous throws and rejected promises are **also** memoized — call
114
+ * `handler.reset()` after settlement to retry. Use this for DB connections,
115
+ * cache warmup, etc.
99
116
  */
100
- init?: () => Promise<void>;
117
+ init?: () => void | Promise<void>;
101
118
  /**
102
- * Hook called for each request before Express processes it. The default
103
- * implementation works around serverless-http issue #305 by parsing Buffer
104
- * bodies into JSON or strings. Pass a function to override this behavior.
119
+ * Hook called for each request before Express processes it. serverless-http
120
+ * passes `(request, event, context)` to the hook; the event/context generic
121
+ * parameters should match the configured provider.
105
122
  */
106
- request?: (req: ServerlessRequest) => void | Promise<void>;
107
- /** Hook called after Express finishes processing. */
108
- response?: (res: unknown) => void | Promise<void>;
123
+ request?: ServerlessRequestHook<TEvent, TContext>;
124
+ /** Hook called after Express finishes processing as `(response, event, context)`. */
125
+ response?: ServerlessResponseHook<TEvent, TContext>;
109
126
  /**
110
127
  * Additional options forwarded to `serverless-http` (e.g. `provider`,
111
128
  * `binary`, `basePath`). `request` and `response` are controlled by the
@@ -113,22 +130,28 @@ interface ServerlessHandlerOptions {
113
130
  */
114
131
  serverlessOptions?: Omit<ServerlessHttpOptions, 'request' | 'response'>;
115
132
  /**
116
- * Skip parsing bodies larger than this in the default request hook.
117
- * Default: `1mb`. Platform events bypass Express body-parser limits, so the
118
- * default hook applies its own size guard.
133
+ * Conversion threshold for the default request hook. Bodies larger than this
134
+ * remain unchanged for Express/serverless-http to handle. Default: `1mb`.
135
+ * This is not an end-to-end request rejection limit.
119
136
  */
120
137
  maxBodyBytes?: number;
121
138
  logger?: Logger;
122
139
  }
123
140
  /**
124
- * The default serverless request hook. Parses Buffer bodies into JSON (when
125
- * `content-type` starts with `application/json`, allowing charset variations)
126
- * or UTF-8 strings a workaround for serverless-http issue #305.
141
+ * The default serverless request hook. With serverless-http 4, AWS-style event
142
+ * bodies are already replayed through the request stream, so JSON Buffers are
143
+ * left for Express parsers to consume once. For plain hook-unit inputs without a
144
+ * readable stream, JSON is parsed for exact `application/json` and structured
145
+ * `application/*+json` media types. Other Buffers are converted to UTF-8
146
+ * strings. Malformed JSON is treated as client input and left unchanged without
147
+ * logging an internal error.
127
148
  *
128
- * Exported for direct unit testing.
149
+ * Public extension seam used by the default `createServerlessHandler()` request
150
+ * hook and by consumers that want the same conservative body conversion policy
151
+ * in a custom hook.
129
152
  */
130
153
  declare function defaultRequestHook(req: ServerlessRequest, maxBodyBytes?: number, logger?: Logger): void;
131
- declare function createServerlessHandler(app: Express, options?: ServerlessHandlerOptions): ServerlessHandler;
154
+ declare function createServerlessHandler<TEvent extends object = Record<string, unknown>, TContext extends object = Record<string, unknown>>(app: Express, options?: ServerlessHandlerOptions<TEvent, TContext>): ServerlessHandler<TEvent, TContext>;
132
155
  interface LocalServerOptions {
133
156
  /**
134
157
  * Port number or named pipe. Defaults to `process.env.PORT` or `8080`.
@@ -169,21 +192,39 @@ interface LocalServer {
169
192
  /** The underlying `http.Server`. */
170
193
  server: http.Server;
171
194
  /**
172
- * Trigger graceful shutdown. Resolves after `onShutdown` and `server.close`
173
- * complete (or after `shutdownTimeout` ms, force-closing idle connections).
174
- * If `exitAfterShutdown` is `true`, the process exits before the promise
175
- * resolves.
195
+ * Trigger graceful shutdown. Stops accepting new connections first, drains
196
+ * in-flight requests up to `shutdownTimeout`, then runs `onShutdown`.
197
+ * `shutdownTimeout` covers only request draining; `onShutdown` errors are
198
+ * logged and reject. Memoized so concurrent calls/signals execute at most
199
+ * once. If `exitAfterShutdown` is `true`, the process exits before the
200
+ * promise resolves.
176
201
  */
177
202
  shutdown: () => Promise<void>;
203
+ /**
204
+ * Awaitable readiness promise. Resolves when the server is listening,
205
+ * rejects on init or listen failure. Rejects if shutdown is requested
206
+ * before listening (e.g. shutdown during pending init).
207
+ */
208
+ ready: Promise<void>;
178
209
  }
210
+ /**
211
+ * Lifecycle states for the local server.
212
+ * - initializing: init running or pending listen
213
+ * - listening: server is accepting connections
214
+ * - stopping: shutdown has been requested, draining
215
+ * - stopped: shutdown completed or server closed externally
216
+ * - failed: init or listen failed
217
+ */
218
+ type LocalServerState = 'initializing' | 'listening' | 'stopping' | 'stopped' | 'failed';
179
219
  /**
180
220
  * Normalize a port value to a number or a named-pipe string. Throws on invalid
181
221
  * values (negative, out of 16-bit range). Empty/undefined falls back to
182
222
  * `process.env.PORT` then `8080`.
183
223
  *
184
- * Exported for direct unit testing.
224
+ * Public helper used by `startLocalServer()` and CLI integrations to normalize
225
+ * `PORT`-style configuration into an HTTP port number or named pipe.
185
226
  */
186
- declare function normalizePort(val: number | string | undefined): number | string;
227
+ declare function normalizePort(val: number | string | undefined, name?: string): number | string;
187
228
  declare function startLocalServer(app: Express, options?: LocalServerOptions): LocalServer;
188
229
 
189
- export { type ExpressAppOptions, type LocalServer, type LocalServerOptions, type Logger, type RouterMount, type ServerlessHandler, type ServerlessHandlerOptions, type ServerlessHttpOptions, type ServerlessRequest, createExpressApp, createServerlessHandler, defaultRequestHook, normalizePort, startLocalServer };
230
+ export { type ExpressAppOptions, type LocalServer, type LocalServerOptions, type LocalServerState, type Logger, type RouterMount, type ServerlessHandler, type ServerlessHandlerOptions, type ServerlessHttpOptions, type ServerlessRequest, type ServerlessRequestHook, type ServerlessResponse, type ServerlessResponseHook, createExpressApp, createServerlessHandler, defaultRequestHook, normalizePort, parsePortValue, startLocalServer, validateFiniteInteger };
package/index.d.ts CHANGED
@@ -4,6 +4,14 @@ export { ErrorRequestHandler, Express, RequestHandler } from 'express';
4
4
  import serverless from 'serverless-http';
5
5
  export { Options as RawServerlessHttpOptions } from 'serverless-http';
6
6
 
7
+ interface FiniteIntegerValidationOptions {
8
+ name: string;
9
+ min?: number;
10
+ max?: number;
11
+ }
12
+ declare function validateFiniteInteger(value: unknown, options: FiniteIntegerValidationOptions): number;
13
+ declare function parsePortValue(value: number | string, name: string): number | string;
14
+
7
15
  interface Logger {
8
16
  log: (...args: unknown[]) => void;
9
17
  error: (...args: unknown[]) => void;
@@ -21,16 +29,20 @@ interface RouterMount {
21
29
  interface ExpressAppOptions {
22
30
  /**
23
31
  * Middleware registered before the built-in body parsers. Use this for
24
- * logging, helmet, request-id, etc.
32
+ * logging, helmet, request-id, etc. Express error handlers in this slot only
33
+ * catch errors from earlier middleware, not later router errors.
25
34
  */
26
35
  preMiddleware?: ReadonlyArray<RequestHandler | ErrorRequestHandler>;
27
36
  /**
28
37
  * Middleware registered after body parsers, before routers. Default location
29
- * for cookies, sessions, auth, CORS, etc.
38
+ * for cookies, sessions, auth, CORS, etc. Error-handler entries here only
39
+ * catch errors from earlier slots.
30
40
  */
31
41
  middleware?: ReadonlyArray<RequestHandler | ErrorRequestHandler>;
32
42
  /**
33
- * Middleware registered after all routers (e.g. a 404 catch-all).
43
+ * Middleware registered after all routers (e.g. a 404 catch-all). Error
44
+ * handlers here catch router errors only if Express reaches this slot before
45
+ * a later handler; prefer `errorHandler` for the final app-wide error handler.
34
46
  */
35
47
  postMiddleware?: ReadonlyArray<RequestHandler | ErrorRequestHandler>;
36
48
  /**
@@ -76,11 +88,12 @@ declare function createExpressApp(options?: ExpressAppOptions): Express;
76
88
  * Lambda, and any platform that calls `(event, context)` and expects a
77
89
  * response.
78
90
  */
79
- type ServerlessHandler = ((event: unknown, context: unknown) => Promise<unknown>) & {
91
+ type ServerlessHandler<TEvent extends object = Record<string, unknown>, TContext extends object = Record<string, unknown>> = ((event: TEvent, context: TContext) => Promise<object>) & {
80
92
  /**
81
- * Reset the memoized init promise. Call to retry a failed cold-start
82
- * (`init()` rejection is memoized; without `reset()` every subsequent
83
- * invocation re-throws).
93
+ * Reset the memoized init promise after it settles. Call to retry a failed
94
+ * cold-start (`init()` rejection is memoized; without `reset()` every
95
+ * subsequent invocation re-throws). Calls while init is still pending are
96
+ * ignored so they cannot start concurrent initialization.
84
97
  */
85
98
  reset: () => void;
86
99
  };
@@ -88,24 +101,28 @@ type ServerlessHandler = ((event: unknown, context: unknown) => Promise<unknown>
88
101
  type ServerlessHttpOptions = NonNullable<Parameters<typeof serverless>[1]>;
89
102
  interface ServerlessRequest {
90
103
  body?: unknown;
91
- headers?: Record<string, string>;
104
+ headers?: Record<string, string | string[] | undefined>;
92
105
  }
93
- interface ServerlessHandlerOptions {
106
+ type ServerlessResponse = http.ServerResponse;
107
+ type ServerlessRequestHook<TEvent extends object = Record<string, unknown>, TContext extends object = Record<string, unknown>> = (req: ServerlessRequest, event: TEvent, context: TContext) => void | Promise<void>;
108
+ type ServerlessResponseHook<TEvent extends object = Record<string, unknown>, TContext extends object = Record<string, unknown>> = (res: ServerlessResponse, event: TEvent, context: TContext) => void | Promise<void>;
109
+ interface ServerlessHandlerOptions<TEvent extends object = Record<string, unknown>, TContext extends object = Record<string, unknown>> {
94
110
  /**
95
111
  * Called once per cold start before delegating to the handler. The resulting
96
112
  * promise is memoized so subsequent warm invocations skip re-initialization.
97
- * A rejected promise is **also** memoized — call `handler.reset()` to retry.
98
- * Use this for DB connections, cache warmup, etc.
113
+ * Synchronous throws and rejected promises are **also** memoized — call
114
+ * `handler.reset()` after settlement to retry. Use this for DB connections,
115
+ * cache warmup, etc.
99
116
  */
100
- init?: () => Promise<void>;
117
+ init?: () => void | Promise<void>;
101
118
  /**
102
- * Hook called for each request before Express processes it. The default
103
- * implementation works around serverless-http issue #305 by parsing Buffer
104
- * bodies into JSON or strings. Pass a function to override this behavior.
119
+ * Hook called for each request before Express processes it. serverless-http
120
+ * passes `(request, event, context)` to the hook; the event/context generic
121
+ * parameters should match the configured provider.
105
122
  */
106
- request?: (req: ServerlessRequest) => void | Promise<void>;
107
- /** Hook called after Express finishes processing. */
108
- response?: (res: unknown) => void | Promise<void>;
123
+ request?: ServerlessRequestHook<TEvent, TContext>;
124
+ /** Hook called after Express finishes processing as `(response, event, context)`. */
125
+ response?: ServerlessResponseHook<TEvent, TContext>;
109
126
  /**
110
127
  * Additional options forwarded to `serverless-http` (e.g. `provider`,
111
128
  * `binary`, `basePath`). `request` and `response` are controlled by the
@@ -113,22 +130,28 @@ interface ServerlessHandlerOptions {
113
130
  */
114
131
  serverlessOptions?: Omit<ServerlessHttpOptions, 'request' | 'response'>;
115
132
  /**
116
- * Skip parsing bodies larger than this in the default request hook.
117
- * Default: `1mb`. Platform events bypass Express body-parser limits, so the
118
- * default hook applies its own size guard.
133
+ * Conversion threshold for the default request hook. Bodies larger than this
134
+ * remain unchanged for Express/serverless-http to handle. Default: `1mb`.
135
+ * This is not an end-to-end request rejection limit.
119
136
  */
120
137
  maxBodyBytes?: number;
121
138
  logger?: Logger;
122
139
  }
123
140
  /**
124
- * The default serverless request hook. Parses Buffer bodies into JSON (when
125
- * `content-type` starts with `application/json`, allowing charset variations)
126
- * or UTF-8 strings a workaround for serverless-http issue #305.
141
+ * The default serverless request hook. With serverless-http 4, AWS-style event
142
+ * bodies are already replayed through the request stream, so JSON Buffers are
143
+ * left for Express parsers to consume once. For plain hook-unit inputs without a
144
+ * readable stream, JSON is parsed for exact `application/json` and structured
145
+ * `application/*+json` media types. Other Buffers are converted to UTF-8
146
+ * strings. Malformed JSON is treated as client input and left unchanged without
147
+ * logging an internal error.
127
148
  *
128
- * Exported for direct unit testing.
149
+ * Public extension seam used by the default `createServerlessHandler()` request
150
+ * hook and by consumers that want the same conservative body conversion policy
151
+ * in a custom hook.
129
152
  */
130
153
  declare function defaultRequestHook(req: ServerlessRequest, maxBodyBytes?: number, logger?: Logger): void;
131
- declare function createServerlessHandler(app: Express, options?: ServerlessHandlerOptions): ServerlessHandler;
154
+ declare function createServerlessHandler<TEvent extends object = Record<string, unknown>, TContext extends object = Record<string, unknown>>(app: Express, options?: ServerlessHandlerOptions<TEvent, TContext>): ServerlessHandler<TEvent, TContext>;
132
155
  interface LocalServerOptions {
133
156
  /**
134
157
  * Port number or named pipe. Defaults to `process.env.PORT` or `8080`.
@@ -169,21 +192,39 @@ interface LocalServer {
169
192
  /** The underlying `http.Server`. */
170
193
  server: http.Server;
171
194
  /**
172
- * Trigger graceful shutdown. Resolves after `onShutdown` and `server.close`
173
- * complete (or after `shutdownTimeout` ms, force-closing idle connections).
174
- * If `exitAfterShutdown` is `true`, the process exits before the promise
175
- * resolves.
195
+ * Trigger graceful shutdown. Stops accepting new connections first, drains
196
+ * in-flight requests up to `shutdownTimeout`, then runs `onShutdown`.
197
+ * `shutdownTimeout` covers only request draining; `onShutdown` errors are
198
+ * logged and reject. Memoized so concurrent calls/signals execute at most
199
+ * once. If `exitAfterShutdown` is `true`, the process exits before the
200
+ * promise resolves.
176
201
  */
177
202
  shutdown: () => Promise<void>;
203
+ /**
204
+ * Awaitable readiness promise. Resolves when the server is listening,
205
+ * rejects on init or listen failure. Rejects if shutdown is requested
206
+ * before listening (e.g. shutdown during pending init).
207
+ */
208
+ ready: Promise<void>;
178
209
  }
210
+ /**
211
+ * Lifecycle states for the local server.
212
+ * - initializing: init running or pending listen
213
+ * - listening: server is accepting connections
214
+ * - stopping: shutdown has been requested, draining
215
+ * - stopped: shutdown completed or server closed externally
216
+ * - failed: init or listen failed
217
+ */
218
+ type LocalServerState = 'initializing' | 'listening' | 'stopping' | 'stopped' | 'failed';
179
219
  /**
180
220
  * Normalize a port value to a number or a named-pipe string. Throws on invalid
181
221
  * values (negative, out of 16-bit range). Empty/undefined falls back to
182
222
  * `process.env.PORT` then `8080`.
183
223
  *
184
- * Exported for direct unit testing.
224
+ * Public helper used by `startLocalServer()` and CLI integrations to normalize
225
+ * `PORT`-style configuration into an HTTP port number or named pipe.
185
226
  */
186
- declare function normalizePort(val: number | string | undefined): number | string;
227
+ declare function normalizePort(val: number | string | undefined, name?: string): number | string;
187
228
  declare function startLocalServer(app: Express, options?: LocalServerOptions): LocalServer;
188
229
 
189
- export { type ExpressAppOptions, type LocalServer, type LocalServerOptions, type Logger, type RouterMount, type ServerlessHandler, type ServerlessHandlerOptions, type ServerlessHttpOptions, type ServerlessRequest, createExpressApp, createServerlessHandler, defaultRequestHook, normalizePort, startLocalServer };
230
+ export { type ExpressAppOptions, type LocalServer, type LocalServerOptions, type LocalServerState, type Logger, type RouterMount, type ServerlessHandler, type ServerlessHandlerOptions, type ServerlessHttpOptions, type ServerlessRequest, type ServerlessRequestHook, type ServerlessResponse, type ServerlessResponseHook, createExpressApp, createServerlessHandler, defaultRequestHook, normalizePort, parsePortValue, startLocalServer, validateFiniteInteger };