@molecule/api-server-default-express 1.0.4 → 1.1.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 +38 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/server.d.ts +24 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +42 -0
- package/package.json +5 -1
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-
|
|
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'
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
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.
|
package/dist/server.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
|
3
|
+
"version": "1.1.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.1.0",
|
|
33
|
+
"@molecule/api-process-lifecycle-express": "1.1.0"
|
|
34
|
+
},
|
|
31
35
|
"repository": {
|
|
32
36
|
"type": "git",
|
|
33
37
|
"url": "https://github.com/molecule-dev/molecule.git",
|