@molecule/api-server-default-express 1.0.4 → 1.2.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
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
3
  Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
4
  Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
5
  To change this document, edit the module-level JSDoc in src/index.ts.
6
- Generated: 2026-10-16T23:31:50.537Z
6
+ Generated: 2026-10-19T14:30:49.145Z
7
7
  -->
8
8
 
9
9
  # @molecule/api-server-default-express
@@ -16,6 +16,12 @@ returns a `(port?) => Promise<server>` function that runs
16
16
  migrations, wires bonds, mounts router + middleware, and starts
17
17
  an HTTP (or self-signed HTTPS for local dev) listener.
18
18
 
19
+ It also wires the process lifecycle by default: `GET /health/live` and
20
+ `GET /health/ready`, in-flight request tracking with `Connection: close`
21
+ while draining, and SIGTERM/SIGINT graceful shutdown (drain bound
22
+ `SHUTDOWN_TIMEOUT_MS`, default 25 s) that runs ordered shutdown hooks and
23
+ exits 0. Pass `lifecycle: { disabled: true }` to opt out.
24
+
19
25
  > **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
20
26
  > It is written to be read by coding agents as much as by people, and is generated from this
21
27
  > package's source — edit `src/index.ts` JSDoc, not this file.
@@ -50,7 +56,7 @@ await create()
50
56
  ## Installation
51
57
 
52
58
  ```bash
53
- npm install @molecule/api-server-default-express @molecule/api-error-tracking @molecule/api-logger @molecule/api-middleware-body-parser @molecule/api-middleware-cookie-parser @molecule/api-middleware-cors @molecule/api-secrets express pem
59
+ npm install @molecule/api-server-default-express @molecule/api-error-tracking @molecule/api-logger @molecule/api-middleware-body-parser @molecule/api-middleware-cookie-parser @molecule/api-middleware-cors @molecule/api-process-lifecycle @molecule/api-process-lifecycle-express @molecule/api-secrets express pem
54
60
  npm install -D @types/express
55
61
  ```
56
62
 
@@ -108,6 +114,34 @@ interface CreateServerOptions {
108
114
  * resource router.
109
115
  */
110
116
  preApiRouter?: (app: express.Express) => Promise<void> | void
117
+ /**
118
+ * Process-lifecycle tuning. Omitted, the canonical defaults apply: health
119
+ * routes at `/health/live` and `/health/ready`, a 25 s drain bound
120
+ * (`SHUTDOWN_TIMEOUT_MS`), a 5 s load-balancer flip wait in production
121
+ * (`SHUTDOWN_LB_FLIP_WAIT_MS`), and SIGTERM/SIGINT handlers that exit 0.
122
+ * Set `{ disabled: true }` to keep a legacy process with no lifecycle
123
+ * wiring (health routes and signal handlers are not installed).
124
+ */
125
+ lifecycle?: ServerLifecycleOptions
126
+ }
127
+ ```
128
+
129
+ #### `ServerLifecycleOptions`
130
+
131
+ Options for the process-lifecycle wiring inside {@link createServerFactory}.
132
+
133
+ ```typescript
134
+ interface ServerLifecycleOptions {
135
+ /** Skip the lifecycle wiring entirely. Defaults to `false`. */
136
+ disabled?: boolean
137
+ /** Maximum time to drain in-flight requests. Defaults to `SHUTDOWN_TIMEOUT_MS` / 25 s. */
138
+ drainTimeoutMs?: number
139
+ /** Time to keep listening after draining starts. Defaults to `SHUTDOWN_LB_FLIP_WAIT_MS` / 5 s in production. */
140
+ flipWaitMs?: number
141
+ /** Exit function override (tests). Defaults to `process.exit`. */
142
+ exit?: (code: number) => void
143
+ /** Signals to handle. Defaults to `['SIGTERM', 'SIGINT']`. */
144
+ signals?: NodeJS.Signals[]
111
145
  }
112
146
  ```
113
147
 
@@ -270,6 +304,8 @@ Peer dependencies:
270
304
  - `@molecule/api-middleware-body-parser`
271
305
  - `@molecule/api-middleware-cookie-parser`
272
306
  - `@molecule/api-middleware-cors`
307
+ - `@molecule/api-process-lifecycle`
308
+ - `@molecule/api-process-lifecycle-express`
273
309
  - `@molecule/api-secrets`
274
310
  - `express`
275
311
  - `pem`
package/dist/index.d.ts CHANGED
@@ -7,6 +7,12 @@
7
7
  * migrations, wires bonds, mounts router + middleware, and starts
8
8
  * an HTTP (or self-signed HTTPS for local dev) listener.
9
9
  *
10
+ * It also wires the process lifecycle by default: `GET /health/live` and
11
+ * `GET /health/ready`, in-flight request tracking with `Connection: close`
12
+ * while draining, and SIGTERM/SIGINT graceful shutdown (drain bound
13
+ * `SHUTDOWN_TIMEOUT_MS`, default 25 s) that runs ordered shutdown hooks and
14
+ * exits 0. Pass `lifecycle: { disabled: true }` to opt out.
15
+ *
10
16
  * @example
11
17
  * ```ts
12
18
  * import { createServerFactory } from '@molecule/api-server-default-express'
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,cAAc,oBAAoB,CAAA;AAClC,cAAc,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,cAAc,oBAAoB,CAAA;AAClC,cAAc,aAAa,CAAA"}
package/dist/index.js CHANGED
@@ -7,6 +7,12 @@
7
7
  * migrations, wires bonds, mounts router + middleware, and starts
8
8
  * an HTTP (or self-signed HTTPS for local dev) listener.
9
9
  *
10
+ * It also wires the process lifecycle by default: `GET /health/live` and
11
+ * `GET /health/ready`, in-flight request tracking with `Connection: close`
12
+ * while draining, and SIGTERM/SIGINT graceful shutdown (drain bound
13
+ * `SHUTDOWN_TIMEOUT_MS`, default 25 s) that runs ordered shutdown hooks and
14
+ * exits 0. Pass `lifecycle: { disabled: true }` to opt out.
15
+ *
10
16
  * @example
11
17
  * ```ts
12
18
  * import { createServerFactory } from '@molecule/api-server-default-express'
package/dist/server.d.ts CHANGED
@@ -94,6 +94,30 @@ export interface CreateServerOptions {
94
94
  * resource router.
95
95
  */
96
96
  preApiRouter?: (app: express.Express) => Promise<void> | void;
97
+ /**
98
+ * Process-lifecycle tuning. Omitted, the canonical defaults apply: health
99
+ * routes at `/health/live` and `/health/ready`, a 25 s drain bound
100
+ * (`SHUTDOWN_TIMEOUT_MS`), a 5 s load-balancer flip wait in production
101
+ * (`SHUTDOWN_LB_FLIP_WAIT_MS`), and SIGTERM/SIGINT handlers that exit 0.
102
+ * Set `{ disabled: true }` to keep a legacy process with no lifecycle
103
+ * wiring (health routes and signal handlers are not installed).
104
+ */
105
+ lifecycle?: ServerLifecycleOptions;
106
+ }
107
+ /**
108
+ * Options for the process-lifecycle wiring inside {@link createServerFactory}.
109
+ */
110
+ export interface ServerLifecycleOptions {
111
+ /** Skip the lifecycle wiring entirely. Defaults to `false`. */
112
+ disabled?: boolean;
113
+ /** Maximum time to drain in-flight requests. Defaults to `SHUTDOWN_TIMEOUT_MS` / 25 s. */
114
+ drainTimeoutMs?: number;
115
+ /** Time to keep listening after draining starts. Defaults to `SHUTDOWN_LB_FLIP_WAIT_MS` / 5 s in production. */
116
+ flipWaitMs?: number;
117
+ /** Exit function override (tests). Defaults to `process.exit`. */
118
+ exit?: (code: number) => void;
119
+ /** Signals to handle. Defaults to `['SIGTERM', 'SIGINT']`. */
120
+ signals?: NodeJS.Signals[];
97
121
  }
98
122
  /**
99
123
  * Register a hook to run with the HTTP(S) server right before it listens.
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,KAAK,MAAM,YAAY,CAAA;AAI9B,OAAO,OAAO,MAAM,SAAS,CAAA;AAS7B,oFAAoF;AACpF,MAAM,WAAW,WAAW;IAC1B,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAA;IAClB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAA;IAChB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CAetE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,eAAe,EAAE,OAAO,CAAC,mBA6CrC,CAAA;AAED,yCAAyC;AACzC,MAAM,WAAW,mBAAmB;IAClC,qEAAqE;IACrE,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/B,6EAA6E;IAC7E,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC;;;OAGG;IACH,SAAS,EAAE,MAAM,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAA;KAAE,CAAC,CAAA;IACpD;;;;;OAKG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC9D;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC3C;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAC9D;AAYD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACjE,IAAI,CAEN;AAYD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,yBAAyB,EAAE,OAAO,CAAC,cAU/C,CAAA;AA6BD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,mBAAmB,GACxB,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAqH5D"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,KAAK,MAAM,YAAY,CAAA;AAI9B,OAAO,OAAO,MAAM,SAAS,CAAA;AAgB7B,oFAAoF;AACpF,MAAM,WAAW,WAAW;IAC1B,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAA;IAClB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAA;IAChB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CAetE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,eAAe,EAAE,OAAO,CAAC,mBA6CrC,CAAA;AAED,yCAAyC;AACzC,MAAM,WAAW,mBAAmB;IAClC,qEAAqE;IACrE,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/B,6EAA6E;IAC7E,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC;;;OAGG;IACH,SAAS,EAAE,MAAM,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAA;KAAE,CAAC,CAAA;IACpD;;;;;OAKG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC9D;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC3C;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC7D;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,sBAAsB,CAAA;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,gHAAgH;IAChH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,kEAAkE;IAClE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7B,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC,OAAO,EAAE,CAAA;CAC3B;AAmBD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACjE,IAAI,CAEN;AAYD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,yBAAyB,EAAE,OAAO,CAAC,cAU/C,CAAA;AA6BD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,mBAAmB,GACxB,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAyJ5D"}
package/dist/server.js CHANGED
@@ -8,6 +8,8 @@ import { logger } from '@molecule/api-logger';
8
8
  import { bodyParser as bodyParserMiddleware } from '@molecule/api-middleware-body-parser';
9
9
  import { cookieParser as cookieParserMiddleware } from '@molecule/api-middleware-cookie-parser';
10
10
  import { cors as corsMiddleware } from '@molecule/api-middleware-cors';
11
+ import { setProvider as setLifecycleProvider } from '@molecule/api-process-lifecycle';
12
+ import { installExpressLifecycle, provider as sharedLifecycleProvider, } from '@molecule/api-process-lifecycle-express';
11
13
  import { buildConfigReport, logConfigReport } from '@molecule/api-secrets';
12
14
  /**
13
15
  * Classify a thrown value for the API error middleware. Returns a {@link TaggedError}
@@ -118,6 +120,12 @@ export const errorMiddleware = (error, req, res, _next) => {
118
120
  res.status(500).json({ error: 'Internal Server Error' });
119
121
  }
120
122
  };
123
+ /**
124
+ * The lifecycle handle of the most recently created server in this process.
125
+ * Repeated `create()` calls (tests, embedded servers) replace it so signal
126
+ * handlers never stack.
127
+ */
128
+ let activeLifecycle = null;
121
129
  /**
122
130
  * Generic server-lifecycle hooks invoked with the real HTTP(S) server once it
123
131
  * has been created in `createServerFactory`, before it starts listening. Lets a
@@ -228,6 +236,21 @@ export function createServerFactory(opts) {
228
236
  captureException(error, { tags: { source: 'unhandledRejection' } });
229
237
  });
230
238
  }
239
+ // Bond the lifecycle provider BEFORE the app's own setup runs, so a bond's
240
+ // setup (database, Redis, queue) can register readiness checks and shutdown
241
+ // hooks at the moment it knows what it owns.
242
+ const lifecycleOptions = opts.lifecycle;
243
+ let lifecycleProvider = null;
244
+ if (!lifecycleOptions?.disabled) {
245
+ activeLifecycle?.uninstall();
246
+ activeLifecycle = null;
247
+ // The provider is the bond package's process-wide singleton, so a
248
+ // generated `bonds/process-lifecycle-express.ts` (which calls
249
+ // `setProvider(provider)` during setupBonds) wires the SAME instance the
250
+ // health routes read and app code registers checks on.
251
+ lifecycleProvider = sharedLifecycleProvider;
252
+ setLifecycleProvider(lifecycleProvider);
253
+ }
231
254
  await opts.runMigrations();
232
255
  await opts.setupBonds();
233
256
  if (opts.postBondsSetup)
@@ -242,6 +265,19 @@ export function createServerFactory(opts) {
242
265
  // /api, /health and error responses all carry them (mirrors the platform
243
266
  // server). Anti-clickjacking + nosniff + referrer baseline; HSTS in prod only.
244
267
  app.use(securityHeadersMiddleware);
268
+ // Process lifecycle: count every request (before any route), mount
269
+ // GET /health/live + /health/ready, and carry the drain state. The signal
270
+ // handlers are installed once the real HTTP server exists (below).
271
+ let lifecycle = null;
272
+ if (lifecycleProvider) {
273
+ lifecycle = installExpressLifecycle({
274
+ app,
275
+ provider: lifecycleProvider,
276
+ wireProvider: false,
277
+ ...(lifecycleOptions ?? {}),
278
+ });
279
+ activeLifecycle = lifecycle;
280
+ }
245
281
  if (opts.preBodyParser) {
246
282
  // Custom-ordering path: mount cors+cookie before custom hooks
247
283
  // (e.g. multipart uploads) which run BEFORE the body parser
@@ -302,7 +338,13 @@ export function createServerFactory(opts) {
302
338
  httpServer.keepAliveTimeout = 65_000;
303
339
  httpServer.headersTimeout = 66_000;
304
340
  await runServerCreatedHooks(httpServer);
341
+ // Signal handlers are installed before the listener starts: a SIGTERM that
342
+ // arrives during boot must still drain rather than kill the process mid-boot.
343
+ lifecycle?.attachServer(httpServer);
305
344
  httpServer.listen(port, () => {
345
+ // The boot sequence is complete and the listener owns the port: from here
346
+ // /health/ready answers 200 (unless a critical check fails).
347
+ lifecycle?.markReady();
306
348
  logger.info(`Started ${process.env.NODE_ENV} ${process.env.HTTPS ? 'HTTPS' : 'HTTP'} server on port ${port}.`);
307
349
  // Boot-time configuration report: bonds self-register their secret
308
350
  // definitions at import; log which are configured vs missing (each
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/api-server-default-express",
3
- "version": "1.0.4",
3
+ "version": "1.2.0",
4
4
  "description": "Default Express server factory: bonds setup, DB migrations, body/cookie/cors middleware, /api router mount, /health endpoint, 401 normalization, optional HTTPS via self-signed pem certs. Extracts 80-line server.ts shipped by 79 fleet apps.",
5
5
  "homepage": "https://www.molecule.dev/packages/api-server-default-express",
6
6
  "type": "module",
@@ -28,6 +28,10 @@
28
28
  "express"
29
29
  ],
30
30
  "license": "Apache-2.0",
31
+ "dependencies": {
32
+ "@molecule/api-process-lifecycle": "1.2.0",
33
+ "@molecule/api-process-lifecycle-express": "1.2.0"
34
+ },
31
35
  "repository": {
32
36
  "type": "git",
33
37
  "url": "https://github.com/molecule-dev/molecule.git",