@molecule/api-server-default-express 1.0.0 → 1.0.1
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 +273 -0
- package/package.json +19 -13
package/README.md
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AUTO-GENERATED — DO NOT EDIT THIS FILE.
|
|
3
|
+
Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
|
|
4
|
+
Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
|
|
5
|
+
To change this document, edit the module-level JSDoc in src/index.ts.
|
|
6
|
+
Generated: 2026-08-04T01:49:30.307Z
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
# @molecule/api-server-default-express
|
|
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
|
+
`@molecule/api-server-default-express` — drop-in Express server
|
|
16
|
+
factory used by the molecule fleet's `api/src/server.ts`.
|
|
17
|
+
|
|
18
|
+
`createServerFactory({ setupBonds, runMigrations, getRouter })`
|
|
19
|
+
returns a `(port?) => Promise<server>` function that runs
|
|
20
|
+
migrations, wires bonds, mounts router + middleware, and starts
|
|
21
|
+
an HTTP (or self-signed HTTPS for local dev) listener.
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { createServerFactory } from '@molecule/api-server-default-express'
|
|
27
|
+
|
|
28
|
+
// In your app's api/src/server.ts these come from the scaffolded files:
|
|
29
|
+
// import { setupBonds } from './bonds/index.js'
|
|
30
|
+
// import { runMigrations } from './scripts/migrate.js'
|
|
31
|
+
const create = createServerFactory({
|
|
32
|
+
setupBonds,
|
|
33
|
+
runMigrations,
|
|
34
|
+
// Router loads lazily AFTER setupBonds() so bond-conditional route
|
|
35
|
+
// maps see fully-registered providers. The module must export
|
|
36
|
+
// `router`. In your app: getRouter: () => import('./App/router.js')
|
|
37
|
+
getRouter: async () => ({ router }),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// Runs migrations → wires bonds → mounts middleware + router at /api →
|
|
41
|
+
// listens on PORT (default 4000). The scaffolded server.ts exports
|
|
42
|
+
// `create` and invokes it when the file is run directly.
|
|
43
|
+
await create()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Type
|
|
47
|
+
|
|
48
|
+
`feature`
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```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
|
|
54
|
+
npm install -D @types/express
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## API
|
|
58
|
+
|
|
59
|
+
### Interfaces
|
|
60
|
+
|
|
61
|
+
#### `CreateServerOptions`
|
|
62
|
+
|
|
63
|
+
Options for `createServerFactory`.
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
interface CreateServerOptions {
|
|
67
|
+
/** App-specific bond wiring (resolves secrets + wires providers). */
|
|
68
|
+
setupBonds: () => Promise<void>
|
|
69
|
+
/** DB migration runner (typically the `createMigrator()`-bound function). */
|
|
70
|
+
runMigrations: () => Promise<void>
|
|
71
|
+
/**
|
|
72
|
+
* Lazy router import. Loaded AFTER `setupBonds()` so bond-conditional
|
|
73
|
+
* route maps see fully-registered providers at module-evaluation time.
|
|
74
|
+
*/
|
|
75
|
+
getRouter: () => Promise<{ router: express.Router }>
|
|
76
|
+
/**
|
|
77
|
+
* Optional hook to mount middleware AFTER cors+cookieParser but
|
|
78
|
+
* BEFORE the body parser. Use this for routes that need their own
|
|
79
|
+
* multipart streaming (file uploads via busboy) — the body parser's
|
|
80
|
+
* `files: 0` config would silently consume the multipart stream.
|
|
81
|
+
*/
|
|
82
|
+
preBodyParser?: (app: express.Express) => Promise<void> | void
|
|
83
|
+
/**
|
|
84
|
+
* Optional hook called after `setupBonds()` but before the router
|
|
85
|
+
* import. Use for additional one-shot setup (e.g. entitlements
|
|
86
|
+
* tier-registry registration that runs after the bonds are wired).
|
|
87
|
+
*/
|
|
88
|
+
postBondsSetup?: () => Promise<void> | void
|
|
89
|
+
/**
|
|
90
|
+
* Optional hook to mount middleware on `/api` BEFORE the canonical
|
|
91
|
+
* `app.use('/api', router)` mount. Use for app-specific authed
|
|
92
|
+
* content handlers (`/api`-prefixed) that need to run before the
|
|
93
|
+
* resource router.
|
|
94
|
+
*/
|
|
95
|
+
preApiRouter?: (app: express.Express) => Promise<void> | void
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### `TaggedError`
|
|
100
|
+
|
|
101
|
+
A deliberately-tagged molecule error mapped to a real HTTP status by the API.
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
interface TaggedError {
|
|
105
|
+
/** HTTP status to return (e.g. 503 for a missing provider config). */
|
|
106
|
+
statusCode: number
|
|
107
|
+
/** Machine-readable key the app/IDE maps to a friendly message. */
|
|
108
|
+
errorKey: string
|
|
109
|
+
/** Human-readable message. */
|
|
110
|
+
message: string
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Functions
|
|
115
|
+
|
|
116
|
+
#### `classifyTaggedError(error)`
|
|
117
|
+
|
|
118
|
+
Classify a thrown value for the API error middleware. Returns a {@link TaggedError}
|
|
119
|
+
ONLY for values deliberately tagged by molecule with BOTH a numeric `statusCode`
|
|
120
|
+
AND a string `errorKey` — e.g. a provider's config-missing throw (`statusCode: 503`,
|
|
121
|
+
`errorKey: 'config.notConfigured'`). These are expected, actionable conditions a
|
|
122
|
+
user must resolve (a missing `STRIPE_SECRET_KEY` is theirs to set, not a server bug),
|
|
123
|
+
so the middleware surfaces the real status + errorKey instead of an opaque 500 — the
|
|
124
|
+
app/IDE can then show "configure X to enable this feature".
|
|
125
|
+
|
|
126
|
+
Requiring BOTH fields is deliberate: it keeps arbitrary library errors that merely
|
|
127
|
+
carry a `.statusCode` (e.g. an AWS SDK error) from being silently surfaced with a
|
|
128
|
+
status molecule never chose. Returns `null` for everything else (→ default 500 path).
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
function classifyTaggedError(error: unknown): TaggedError | null
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
- `error` — The thrown value caught by the error middleware.
|
|
135
|
+
|
|
136
|
+
**Returns:** The classified tagged error, or `null` if it isn't a molecule-tagged error.
|
|
137
|
+
|
|
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
|
+
#### `errorMiddleware(error, req, res, _next)`
|
|
163
|
+
|
|
164
|
+
Terminal Express error middleware for the canonical molecule fleet server.
|
|
165
|
+
|
|
166
|
+
Resolves a thrown value to exactly one of three sanitized responses and NEVER
|
|
167
|
+
delegates to Express's built-in `finalhandler`:
|
|
168
|
+
|
|
169
|
+
1. Bare-string `Unauthorized` / `Unauthorized.` → `401` with the string body
|
|
170
|
+
(so authSelf-style middleware routes to 401 instead of a 500 page).
|
|
171
|
+
2. A deliberately-tagged molecule error ({@link classifyTaggedError}) → its real
|
|
172
|
+
`statusCode` + `{ error, errorKey }` JSON (expected, user-actionable config
|
|
173
|
+
conditions, e.g. a missing `STRIPE_SECRET_KEY` → 503 `config.notConfigured`).
|
|
174
|
+
3. EVERYTHING else (untagged library throws, null derefs, driver errors) → a
|
|
175
|
+
generic `500 { error: 'Internal Server Error' }`, logged server-side AND
|
|
176
|
+
reported to the bonded error tracker (`@molecule/api-error-tracking`'s
|
|
177
|
+
`captureException`, a documented no-op when no tracker is bonded).
|
|
178
|
+
|
|
179
|
+
Only case 3 is captured: cases 1–2 (401s, tagged config-missing 503s, and any
|
|
180
|
+
other tagged 4xx/5xx) are expected, user-actionable conditions — not defects —
|
|
181
|
+
so reporting them would drown real faults in noise.
|
|
182
|
+
|
|
183
|
+
Case 3 is the security-critical branch: it is safe-by-construction and does NOT
|
|
184
|
+
depend on `NODE_ENV`. Calling `next(error)` here would fall through to Express's
|
|
185
|
+
`finalhandler`, which embeds `err.stack` in the HTTP response body whenever
|
|
186
|
+
`app.get('env') !== 'production'` (the default when `NODE_ENV` is unset or
|
|
187
|
+
`development`), disclosing absolute server paths, module layout, dependency
|
|
188
|
+
versions, and query/data fragments. Returning the opaque 500 unconditionally
|
|
189
|
+
removes that leak for every flagship app regardless of how it is deployed.
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
function errorMiddleware(
|
|
193
|
+
error: any,
|
|
194
|
+
req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>,
|
|
195
|
+
res: Response<any, Record<string, any>, number>,
|
|
196
|
+
_next: NextFunction,
|
|
197
|
+
): void
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
- `error` — The thrown value caught by Express.
|
|
201
|
+
- `req` — The request (used only as capture context for error tracking).
|
|
202
|
+
- `res` — The response to write the sanitized error to.
|
|
203
|
+
- `_next` — The next function (intentionally never called for untagged errors).
|
|
204
|
+
|
|
205
|
+
#### `registerServerCreatedHook(hook)`
|
|
206
|
+
|
|
207
|
+
Register a hook to run with the HTTP(S) server right before it listens.
|
|
208
|
+
Typically called from a bond's setup (e.g. `setupRealtimeSocketio`) during
|
|
209
|
+
`setupBonds()`, which runs earlier in `create()` than server construction.
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
function registerServerCreatedHook(
|
|
213
|
+
hook: (server: http.Server | https.Server) => void | Promise<void>,
|
|
214
|
+
): void
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
- `hook` — Receives the real `http.Server`/`https.Server`.
|
|
218
|
+
|
|
219
|
+
#### `securityHeadersMiddleware(_req, res, next)`
|
|
220
|
+
|
|
221
|
+
Global browser-security headers applied to EVERY response (mounted before the
|
|
222
|
+
routers in `createServerFactory`, mirroring the molecule.dev platform server).
|
|
223
|
+
|
|
224
|
+
Defaults are conservative and framework-agnostic — no app-specific CSP source
|
|
225
|
+
lists, just the clickjacking / MIME-sniffing / referrer baseline a JSON API
|
|
226
|
+
should always ship:
|
|
227
|
+
|
|
228
|
+
- `X-Content-Type-Options: nosniff` — stop MIME-type sniffing.
|
|
229
|
+
- `X-Frame-Options: DENY` + `Content-Security-Policy: frame-ancestors 'none'`
|
|
230
|
+
— anti-clickjacking. A generated app that intends to be embedded (iframe)
|
|
231
|
+
can override these in its own middleware.
|
|
232
|
+
- `X-XSS-Protection: 0` — disable the legacy, buggy XSS auditor (modern
|
|
233
|
+
correct value; CSP is the real defense).
|
|
234
|
+
- `Referrer-Policy: strict-origin-when-cross-origin` — don't leak full URLs
|
|
235
|
+
cross-origin.
|
|
236
|
+
- `Strict-Transport-Security` — production only (mirrors the platform server's
|
|
237
|
+
`NODE_ENV` check) so local plain-HTTP dev isn't force-upgraded to HTTPS.
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
function securityHeadersMiddleware(
|
|
241
|
+
_req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>,
|
|
242
|
+
res: Response<any, Record<string, any>, number>,
|
|
243
|
+
next: NextFunction,
|
|
244
|
+
): void
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
- `_req` — The request (unused).
|
|
248
|
+
- `res` — The response to set headers on.
|
|
249
|
+
- `next` — Express next.
|
|
250
|
+
|
|
251
|
+
## Injection Notes
|
|
252
|
+
|
|
253
|
+
### Requirements
|
|
254
|
+
|
|
255
|
+
Peer dependencies:
|
|
256
|
+
|
|
257
|
+
- `@molecule/api-error-tracking` ^1.0.1
|
|
258
|
+
- `@molecule/api-logger` ^1.0.1
|
|
259
|
+
- `@molecule/api-middleware-body-parser` ^1.0.1
|
|
260
|
+
- `@molecule/api-middleware-cookie-parser` ^1.0.1
|
|
261
|
+
- `@molecule/api-middleware-cors` ^1.0.1
|
|
262
|
+
- `@molecule/api-secrets` ^1.0.1
|
|
263
|
+
- `express` ^4.0.0 || ^5.0.0
|
|
264
|
+
|
|
265
|
+
### Runtime Dependencies
|
|
266
|
+
|
|
267
|
+
- `@molecule/api-error-tracking`
|
|
268
|
+
- `@molecule/api-logger`
|
|
269
|
+
- `@molecule/api-middleware-body-parser`
|
|
270
|
+
- `@molecule/api-middleware-cookie-parser`
|
|
271
|
+
- `@molecule/api-middleware-cors`
|
|
272
|
+
- `@molecule/api-secrets`
|
|
273
|
+
- `express`
|
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.0.1",
|
|
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
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"dist"
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md"
|
|
21
22
|
],
|
|
22
23
|
"keywords": [
|
|
23
24
|
"molecule",
|
|
@@ -26,25 +27,30 @@
|
|
|
26
27
|
"express"
|
|
27
28
|
],
|
|
28
29
|
"license": "Apache-2.0",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/molecule-dev/molecule.git",
|
|
33
|
+
"directory": "packages/api/infrastructure/server-default-express"
|
|
34
|
+
},
|
|
29
35
|
"devDependencies": {
|
|
30
36
|
"@types/node": "26.1.2",
|
|
31
37
|
"typescript": "6.0.3",
|
|
32
38
|
"vitest": "4.1.10",
|
|
33
|
-
"@molecule/api-error-tracking": "1.0.
|
|
34
|
-
"@molecule/api-logger": "1.0.
|
|
35
|
-
"@molecule/api-middleware-body-parser": "1.0.
|
|
36
|
-
"@molecule/api-middleware-cookie-parser": "1.0.
|
|
37
|
-
"@molecule/api-middleware-cors": "1.0.
|
|
39
|
+
"@molecule/api-error-tracking": "1.0.1",
|
|
40
|
+
"@molecule/api-logger": "1.0.1",
|
|
41
|
+
"@molecule/api-middleware-body-parser": "1.0.1",
|
|
42
|
+
"@molecule/api-middleware-cookie-parser": "1.0.1",
|
|
43
|
+
"@molecule/api-middleware-cors": "1.0.1",
|
|
38
44
|
"express": "5.2.1",
|
|
39
45
|
"@types/express": "5.0.6"
|
|
40
46
|
},
|
|
41
47
|
"peerDependencies": {
|
|
42
|
-
"@molecule/api-error-tracking": "^1.0.
|
|
43
|
-
"@molecule/api-logger": "^1.0.
|
|
44
|
-
"@molecule/api-middleware-body-parser": "^1.0.
|
|
45
|
-
"@molecule/api-middleware-cookie-parser": "^1.0.
|
|
46
|
-
"@molecule/api-middleware-cors": "^1.0.
|
|
47
|
-
"@molecule/api-secrets": "^1.0.
|
|
48
|
+
"@molecule/api-error-tracking": "^1.0.1",
|
|
49
|
+
"@molecule/api-logger": "^1.0.1",
|
|
50
|
+
"@molecule/api-middleware-body-parser": "^1.0.1",
|
|
51
|
+
"@molecule/api-middleware-cookie-parser": "^1.0.1",
|
|
52
|
+
"@molecule/api-middleware-cors": "^1.0.1",
|
|
53
|
+
"@molecule/api-secrets": "^1.0.1",
|
|
48
54
|
"express": "^4.0.0 || ^5.0.0"
|
|
49
55
|
}
|
|
50
56
|
}
|