@molecule/api-server-default-express 1.0.0 → 1.0.2

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.
Files changed (2) hide show
  1. package/README.md +273 -0
  2. package/package.json +21 -14
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,7 +1,8 @@
1
1
  {
2
2
  "name": "@molecule/api-server-default-express",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
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
+ "homepage": "https://www.molecule.dev/packages/api-server-default-express",
5
6
  "type": "module",
6
7
  "main": "dist/index.js",
7
8
  "types": "dist/index.d.ts",
@@ -17,7 +18,8 @@
17
18
  }
18
19
  },
19
20
  "files": [
20
- "dist"
21
+ "dist",
22
+ "README.md"
21
23
  ],
22
24
  "keywords": [
23
25
  "molecule",
@@ -26,25 +28,30 @@
26
28
  "express"
27
29
  ],
28
30
  "license": "Apache-2.0",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/molecule-dev/molecule.git",
34
+ "directory": "packages/api/infrastructure/server-default-express"
35
+ },
29
36
  "devDependencies": {
30
37
  "@types/node": "26.1.2",
31
38
  "typescript": "6.0.3",
32
- "vitest": "4.1.10",
33
- "@molecule/api-error-tracking": "1.0.0",
34
- "@molecule/api-logger": "1.0.0",
35
- "@molecule/api-middleware-body-parser": "1.0.0",
36
- "@molecule/api-middleware-cookie-parser": "1.0.0",
37
- "@molecule/api-middleware-cors": "1.0.0",
39
+ "vitest": "4.1.11",
40
+ "@molecule/api-error-tracking": "1.0.2",
41
+ "@molecule/api-logger": "1.0.2",
42
+ "@molecule/api-middleware-body-parser": "1.0.2",
43
+ "@molecule/api-middleware-cookie-parser": "1.0.2",
44
+ "@molecule/api-middleware-cors": "1.0.2",
38
45
  "express": "5.2.1",
39
46
  "@types/express": "5.0.6"
40
47
  },
41
48
  "peerDependencies": {
42
- "@molecule/api-error-tracking": "^1.0.0",
43
- "@molecule/api-logger": "^1.0.0",
44
- "@molecule/api-middleware-body-parser": "^1.0.0",
45
- "@molecule/api-middleware-cookie-parser": "^1.0.0",
46
- "@molecule/api-middleware-cors": "^1.0.0",
47
- "@molecule/api-secrets": "^1.0.0",
49
+ "@molecule/api-error-tracking": "^1.0.1",
50
+ "@molecule/api-logger": "^1.0.1",
51
+ "@molecule/api-middleware-body-parser": "^1.0.1",
52
+ "@molecule/api-middleware-cookie-parser": "^1.0.1",
53
+ "@molecule/api-middleware-cors": "^1.0.1",
54
+ "@molecule/api-secrets": "^1.0.1",
48
55
  "express": "^4.0.0 || ^5.0.0"
49
56
  }
50
57
  }