@molecule/api-server-default-express 1.0.3 → 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 +77 -41
- 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 +55 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -3,15 +3,11 @@ 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
|
|
10
10
|
|
|
11
|
-
> **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
|
|
12
|
-
> It is written to be read by coding agents as much as by people, and is generated from this
|
|
13
|
-
> package's source — edit `src/index.ts` JSDoc, not this file.
|
|
14
|
-
|
|
15
11
|
`@molecule/api-server-default-express` — drop-in Express server
|
|
16
12
|
factory used by the molecule fleet's `api/src/server.ts`.
|
|
17
13
|
|
|
@@ -20,6 +16,16 @@ returns a `(port?) => Promise<server>` function that runs
|
|
|
20
16
|
migrations, wires bonds, mounts router + middleware, and starts
|
|
21
17
|
an HTTP (or self-signed HTTPS for local dev) listener.
|
|
22
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
|
+
|
|
25
|
+
> **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
|
|
26
|
+
> It is written to be read by coding agents as much as by people, and is generated from this
|
|
27
|
+
> package's source — edit `src/index.ts` JSDoc, not this file.
|
|
28
|
+
|
|
23
29
|
## Quick Start
|
|
24
30
|
|
|
25
31
|
```ts
|
|
@@ -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
|
|
|
@@ -58,6 +64,21 @@ npm install -D @types/express
|
|
|
58
64
|
|
|
59
65
|
### Interfaces
|
|
60
66
|
|
|
67
|
+
#### `TaggedError`
|
|
68
|
+
|
|
69
|
+
A deliberately-tagged molecule error mapped to a real HTTP status by the API.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
interface TaggedError {
|
|
73
|
+
/** HTTP status to return (e.g. 503 for a missing provider config). */
|
|
74
|
+
statusCode: number
|
|
75
|
+
/** Machine-readable key the app/IDE maps to a friendly message. */
|
|
76
|
+
errorKey: string
|
|
77
|
+
/** Human-readable message. */
|
|
78
|
+
message: string
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
61
82
|
#### `CreateServerOptions`
|
|
62
83
|
|
|
63
84
|
Options for `createServerFactory`.
|
|
@@ -93,21 +114,34 @@ interface CreateServerOptions {
|
|
|
93
114
|
* resource router.
|
|
94
115
|
*/
|
|
95
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
|
|
96
126
|
}
|
|
97
127
|
```
|
|
98
128
|
|
|
99
|
-
#### `
|
|
129
|
+
#### `ServerLifecycleOptions`
|
|
100
130
|
|
|
101
|
-
|
|
131
|
+
Options for the process-lifecycle wiring inside {@link createServerFactory}.
|
|
102
132
|
|
|
103
133
|
```typescript
|
|
104
|
-
interface
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
|
|
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
|
|
|
@@ -135,30 +169,6 @@ function classifyTaggedError(error: unknown): TaggedError | null
|
|
|
135
169
|
|
|
136
170
|
**Returns:** The classified tagged error, or `null` if it isn't a molecule-tagged error.
|
|
137
171
|
|
|
138
|
-
#### `createServerFactory(opts)`
|
|
139
|
-
|
|
140
|
-
Returns an Express server-creation function bound to the given
|
|
141
|
-
setupBonds / runMigrations / router loaders. The returned `create`
|
|
142
|
-
builds the canonical molecule fleet server:
|
|
143
|
-
|
|
144
|
-
- Migrations run first, then bonds, then router import.
|
|
145
|
-
- Global browser-security headers ({@link securityHeadersMiddleware})
|
|
146
|
-
applied before any router (anti-clickjacking + nosniff + referrer baseline).
|
|
147
|
-
- `bodyParser` / `cookieParser` / `cors` middleware applied.
|
|
148
|
-
- Router mounted at `/api`.
|
|
149
|
-
- `/health` endpoint with `{ status: 'ok', timestamp }`.
|
|
150
|
-
- Bare-string `Unauthorized` / `Unauthorized.` errors normalized to 401.
|
|
151
|
-
- HTTPS in dev if `process.env.HTTPS` is set, using self-signed
|
|
152
|
-
certs from optional dependency `pem`.
|
|
153
|
-
- `process.on('uncaughtException')` + `unhandledRejection` registered
|
|
154
|
-
on first call (idempotent across multiple `create()` invocations).
|
|
155
|
-
|
|
156
|
-
```typescript
|
|
157
|
-
function createServerFactory(
|
|
158
|
-
opts: CreateServerOptions,
|
|
159
|
-
): (port?: number) => Promise<express.Express | https.Server>
|
|
160
|
-
```
|
|
161
|
-
|
|
162
172
|
#### `errorMiddleware(error, req, res, _next)`
|
|
163
173
|
|
|
164
174
|
Terminal Express error middleware for the canonical molecule fleet server.
|
|
@@ -185,8 +195,8 @@ depend on `NODE_ENV`. Calling `next(error)` here would fall through to Express's
|
|
|
185
195
|
`finalhandler`, which embeds `err.stack` in the HTTP response body whenever
|
|
186
196
|
`app.get('env') !== 'production'` (the default when `NODE_ENV` is unset or
|
|
187
197
|
`development`), disclosing absolute server paths, module layout, dependency
|
|
188
|
-
|
|
189
|
-
|
|
198
|
+
|
|
199
|
+
_(description truncated — read the full text in `node_modules/@molecule/api-server-default-express/dist/server.d.ts`.)_
|
|
190
200
|
|
|
191
201
|
```typescript
|
|
192
202
|
function errorMiddleware(
|
|
@@ -248,6 +258,30 @@ function securityHeadersMiddleware(
|
|
|
248
258
|
- `res` — The response to set headers on.
|
|
249
259
|
- `next` — Express next.
|
|
250
260
|
|
|
261
|
+
#### `createServerFactory(opts)`
|
|
262
|
+
|
|
263
|
+
Returns an Express server-creation function bound to the given
|
|
264
|
+
setupBonds / runMigrations / router loaders. The returned `create`
|
|
265
|
+
builds the canonical molecule fleet server:
|
|
266
|
+
|
|
267
|
+
- Migrations run first, then bonds, then router import.
|
|
268
|
+
- Global browser-security headers ({@link securityHeadersMiddleware})
|
|
269
|
+
applied before any router (anti-clickjacking + nosniff + referrer baseline).
|
|
270
|
+
- `bodyParser` / `cookieParser` / `cors` middleware applied.
|
|
271
|
+
- Router mounted at `/api`.
|
|
272
|
+
- `/health` endpoint with `{ status: 'ok', timestamp }`.
|
|
273
|
+
- Bare-string `Unauthorized` / `Unauthorized.` errors normalized to 401.
|
|
274
|
+
- HTTPS in dev if `process.env.HTTPS` is set, using self-signed
|
|
275
|
+
certs from optional dependency `pem`.
|
|
276
|
+
- `process.on('uncaughtException')` + `unhandledRejection` registered
|
|
277
|
+
on first call (idempotent across multiple `create()` invocations).
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
function createServerFactory(
|
|
281
|
+
opts: CreateServerOptions,
|
|
282
|
+
): (port?: number) => Promise<express.Express | https.Server>
|
|
283
|
+
```
|
|
284
|
+
|
|
251
285
|
## Injection Notes
|
|
252
286
|
|
|
253
287
|
### Requirements
|
|
@@ -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
|
|
@@ -288,8 +324,27 @@ export function createServerFactory(opts) {
|
|
|
288
324
|
const httpServer = process.env.HTTPS
|
|
289
325
|
? server
|
|
290
326
|
: http.createServer(app);
|
|
327
|
+
// Outlive every connection pool that sits in front of this server. Node's
|
|
328
|
+
// default keepAliveTimeout is 5000 ms — exactly what Node's global http
|
|
329
|
+
// agent (what a proxy's `http.request` uses with no agent given) keeps an
|
|
330
|
+
// idle socket for. With both sides on the same 5 s timer, an idle
|
|
331
|
+
// connection is closed by the server at the very moment a proxy reuses it,
|
|
332
|
+
// and that one request is lost (measured against the platform front: a
|
|
333
|
+
// dropped reuse answers it a fast ECONNRESET, which costs a retry). A
|
|
334
|
+
// server-side timeout well above the client's makes the CLIENT always let
|
|
335
|
+
// go first, so a reuse can never land on a socket the server is closing.
|
|
336
|
+
// `headersTimeout` must stay greater than `keepAliveTimeout` (Node throws
|
|
337
|
+
// on boot otherwise).
|
|
338
|
+
httpServer.keepAliveTimeout = 65_000;
|
|
339
|
+
httpServer.headersTimeout = 66_000;
|
|
291
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);
|
|
292
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();
|
|
293
348
|
logger.info(`Started ${process.env.NODE_ENV} ${process.env.HTTPS ? 'HTTPS' : 'HTTP'} server on port ${port}.`);
|
|
294
349
|
// Boot-time configuration report: bonds self-register their secret
|
|
295
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",
|