@molecule/api-bonds-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.
Files changed (2) hide show
  1. package/README.md +1038 -0
  2. package/package.json +197 -191
package/README.md ADDED
@@ -0,0 +1,1038 @@
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:47:47.108Z
7
+ -->
8
+
9
+ # @molecule/api-bonds-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-bonds-default-express` — default API bond wirings and
16
+ shared route/handler plumbing for Express-based apps.
17
+
18
+ Two halves:
19
+
20
+ 1. **`setup<Name>()` bond wirings** (40+): one function per default
21
+ provider (`setupConfigEnv`, `setupDatabasePostgresql`,
22
+ `setupJwtJsonwebtoken`, `setupEmailsMailgun`, `setupUploadsS3`,
23
+ `setupRealtimeSocketio`, `setupAiAnthropic`, …) so per-app
24
+ `api/src/bonds/<name>.ts` files are 1-line re-exports and
25
+ `bonds/index.ts` just calls them in order.
26
+ 2. **Shared Express plumbing**: `createBillingRouter` (the fleet's
27
+ Stripe billing endpoints), the `mountDefaultUserAuthRoutes` /
28
+ `mountDefaultDeviceRoutes` / other `mountDefault*Routes` helpers,
29
+ handler guards (`requireAuth`, `requireUser`, `requireOwnership`,
30
+ `getUserId`, `validationError`, `internalError`), zod param schemas,
31
+ `trackAuthEvent`, and a `createMigrator` re-export.
32
+
33
+ ## Quick Start
34
+
35
+ ```typescript
36
+ // api/src/bonds/index.ts — wire defaults at startup, then validate:
37
+ import { validateBonds } from '@molecule/api-bond'
38
+ import {
39
+ setupConfigEnv,
40
+ setupDatabasePostgresql,
41
+ setupEmailsMailgun,
42
+ setupJwtJsonwebtoken,
43
+ setupSecretsEnv,
44
+ } from '@molecule/api-bonds-default-express'
45
+
46
+ async function setupBonds(): Promise<void> {
47
+ setupConfigEnv()
48
+ setupSecretsEnv()
49
+ setupDatabasePostgresql()
50
+ setupJwtJsonwebtoken()
51
+ setupEmailsMailgun()
52
+ validateBonds()
53
+ }
54
+ ```
55
+
56
+ ```typescript
57
+ // api/src/routes/billing.ts — the fleet-standard billing endpoints
58
+ // (POST /checkout, POST /cancel, GET /status, GET /tiers), mounted by
59
+ // the app router at /billing (the real file default-exports the router):
60
+ import { createBillingRouter } from '@molecule/api-bonds-default-express'
61
+
62
+ // Your app owns these (typically in api/src/tiers.ts):
63
+ interface AppLimits {
64
+ seats: number
65
+ }
66
+ const getPricingTiers = () => [] // your tiers, each with a stripePriceId + limits
67
+ const appPlanKeys = { free: 'free', pro: 'pro' }
68
+
69
+ const billingRouter = createBillingRouter<AppLimits>({
70
+ getPricingTiers,
71
+ planKeys: appPlanKeys,
72
+ })
73
+ ```
74
+
75
+ ## Type
76
+
77
+ `feature`
78
+
79
+ ## Installation
80
+
81
+ ```bash
82
+ npm install @molecule/api-bonds-default-express @molecule/api-ai-anthropic @molecule/api-ai-embeddings @molecule/api-ai-embeddings-openai @molecule/api-ai-openai @molecule/api-ai-speech @molecule/api-ai-speech-openai @molecule/api-ai-vector-store @molecule/api-ai-vector-store-pgvector @molecule/api-analytics @molecule/api-audit @molecule/api-audit-database @molecule/api-bond @molecule/api-cache @molecule/api-cache-memory @molecule/api-cache-redis @molecule/api-config @molecule/api-config-env @molecule/api-cron @molecule/api-cron-node-cron @molecule/api-database @molecule/api-database-postgresql @molecule/api-emails @molecule/api-emails-capture @molecule/api-emails-mailgun @molecule/api-encryption @molecule/api-encryption-aes @molecule/api-entitlements @molecule/api-error-tracking @molecule/api-error-tracking-console @molecule/api-error-tracking-sentry @molecule/api-geolocation @molecule/api-geolocation-google @molecule/api-geolocation-mapbox @molecule/api-geolocation-nominatim @molecule/api-http @molecule/api-http-fetch @molecule/api-i18n @molecule/api-image @molecule/api-image-sharp @molecule/api-import-export @molecule/api-import-export-csv @molecule/api-jwt @molecule/api-jwt-jsonwebtoken @molecule/api-logger @molecule/api-media-streaming @molecule/api-media-streaming-hls @molecule/api-middleware-body-parser @molecule/api-middleware-body-parser-express @molecule/api-middleware-cookie-parser @molecule/api-middleware-cookie-parser-express @molecule/api-middleware-cors @molecule/api-middleware-cors-express @molecule/api-middleware-validation @molecule/api-notifications-webhook @molecule/api-password @molecule/api-password-bcrypt @molecule/api-payments @molecule/api-payments-stripe @molecule/api-pdf @molecule/api-pdf-pdfkit @molecule/api-permissions @molecule/api-permissions-custom @molecule/api-push-capture @molecule/api-push-notifications @molecule/api-push-notifications-web-push @molecule/api-queue @molecule/api-queue-memory @molecule/api-queue-redis @molecule/api-rate-limit @molecule/api-rate-limit-memory @molecule/api-realtime @molecule/api-realtime-socketio @molecule/api-realtime-sse @molecule/api-realtime-ws @molecule/api-reporting @molecule/api-reporting-database @molecule/api-resource @molecule/api-resource-device @molecule/api-resource-payment @molecule/api-resource-user @molecule/api-search @molecule/api-search-meilisearch @molecule/api-search-postgres @molecule/api-secrets @molecule/api-secrets-env @molecule/api-two-factor @molecule/api-two-factor-otplib @molecule/api-uploads @molecule/api-uploads-filesystem @molecule/api-uploads-s3 @molecule/api-webhook @molecule/api-webhook-http @molecule/api-workflow @molecule/api-workflow-database
83
+ ```
84
+
85
+ ## API
86
+
87
+ ### Types
88
+
89
+ #### `AuthzResult`
90
+
91
+ Result of an ownership check. `ok: true` carries the resolved row;
92
+ `ok: false` carries the HTTP status the handler should return —
93
+ always 404 to avoid leaking row existence to non-owners (the
94
+ "no IDOR" rule).
95
+
96
+ ```typescript
97
+ type AuthzResult<T> = { ok: true; row: T } | { ok: false; status: 404 }
98
+ ```
99
+
100
+ ### Functions
101
+
102
+ #### `createBillingRouter(opts)`
103
+
104
+ Factory for the default billing router. The router exposes four
105
+ endpoints: `POST /checkout`, `POST /cancel`, `GET /status`, `GET /tiers`.
106
+
107
+ ```typescript
108
+ function createBillingRouter(opts: {
109
+ getPricingTiers: () => ReadonlyArray<PricingTier>
110
+ planKeys: { free: string } & Record<string, string>
111
+ }): Router
112
+ ```
113
+
114
+ #### `createMigrator(migrationsDir)`
115
+
116
+ Returns a `runMigrations()` function bound to the given directory.
117
+
118
+ ```typescript
119
+ function createMigrator(migrationsDir: string): () => Promise<void>
120
+ ```
121
+
122
+ - `migrationsDir` — Absolute path to the directory containing ordered `*.sql` migration files. Resolve via `join(new URL('.', import.meta.url).pathname, '../../migrations')` from the app's `scripts/migrate.ts`.
123
+
124
+ **Returns:** A no-arg `runMigrations()` that creates the database (if missing) and applies every migration file in lexical order.
125
+
126
+ #### `getParamId(req, name?)`
127
+
128
+ Read a route param as a string, defending against the Express
129
+ type union `string | string[]` (multi-value when the same param
130
+ key appears more than once). Defaults to `'id'`.
131
+
132
+ ```typescript
133
+ function getParamId(
134
+ req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>,
135
+ name?: string,
136
+ ): string
137
+ ```
138
+
139
+ #### `getUserId(res)`
140
+
141
+ Read the JWT session userId off `res.locals.session`. Returns null
142
+ when there is no session (the auth middleware never ran, or the
143
+ request is unauthenticated).
144
+
145
+ ```typescript
146
+ function getUserId(res: Response<any, Record<string, any>>): string | null
147
+ ```
148
+
149
+ #### `internalError(res, error?)`
150
+
151
+ Standard 500 response that logs the underlying cause before responding
152
+ with a generic message. Always pass the original error so silent
153
+ catches don't ship to prod under green tests.
154
+
155
+ ```typescript
156
+ function internalError(res: Response<any, Record<string, any>>, error?: unknown): void
157
+ ```
158
+
159
+ #### `mountDefaultDeviceRoutes(router, device)`
160
+
161
+ Mounts the standard device routes:
162
+
163
+ - `GET /devices/push/public-key` (public — the VAPID public key browsers
164
+ need for `pushManager.subscribe({ applicationServerKey })`; bond-gated
165
+ 404/503 when no push provider is bonded/configured)
166
+ - `GET /devices` (auth+query)
167
+ - `GET /devices/:id` (authUser+read)
168
+ - `PATCH /devices/:id` (authUser+update)
169
+ - `DELETE /devices/:id` (authUser+del)
170
+
171
+ ```typescript
172
+ function mountDefaultDeviceRoutes(router: Router, device: DeviceRequestHandlerMap): void
173
+ ```
174
+
175
+ #### `mountDefaultUserAuthRoutes(router, user)`
176
+
177
+ Mounts the public auth endpoints:
178
+
179
+ - `POST /users` (create)
180
+ - `POST /users/log-in` (rateLimitAuth + logIn)
181
+ - `POST /users/forgot-password` (rateLimitAuth + forgotPassword)
182
+
183
+ The credential-bearing routes are fronted by `user.rateLimitAuth` — the
184
+ default IP+account brute-force throttle from `@molecule/api-resource-user` —
185
+ so generated apps are not left with unthrottled password / TOTP-via-login
186
+ guessing. The limiter degrades open (logs a warning) when no rate-limit
187
+ provider is bonded, so apps that opt out still boot.
188
+
189
+ ```typescript
190
+ function mountDefaultUserAuthRoutes(router: Router, user: UserRequestHandlerMap): void
191
+ ```
192
+
193
+ #### `mountDefaultUserBillingRoutes(router, user)`
194
+
195
+ Mounts plan/billing routes:
196
+
197
+ - `PATCH /users/:id/plan` (authSelf+updatePlan)
198
+ - `POST /users/payment-notification/:provider` (requireWebhookAuthenticity+handlePaymentNotification)
199
+
200
+ The notification route is public (providers POST to it), so it is gated by
201
+ `requireWebhookAuthenticity`: signature-verifying webhook providers (Stripe)
202
+ pass through, while unsigned server-to-server providers (Apple/Google) require
203
+ a shared secret — the endpoint is not open by default.
204
+
205
+ ```typescript
206
+ function mountDefaultUserBillingRoutes(router: Router, user: UserRequestHandlerMap): void
207
+ ```
208
+
209
+ #### `mountDefaultUserCrudRoutes(router, user)`
210
+
211
+ Mounts the authed-self user CRUD routes:
212
+
213
+ - `GET /users/me` (auth+readSelf) — session restore; MUST precede `/users/:id`
214
+ - `GET /users/:id` (authSelf+read)
215
+ - `PATCH /users/:id` (authSelf+update)
216
+ - `DELETE /users/:id` (authSelf+del)
217
+
218
+ ```typescript
219
+ function mountDefaultUserCrudRoutes(router: Router, user: UserRequestHandlerMap): void
220
+ ```
221
+
222
+ #### `mountDefaultUserOAuthLoginRoute(router, user)`
223
+
224
+ Optional OAuth routes — BOTH halves of the flow:
225
+
226
+ - `GET /users/oauth/:provider` (rateLimitAuth + oauthAuthorize) —
227
+ initiation: sets the CSRF `oauth_state` + PKCE `oauth_verifier` httpOnly
228
+ cookies and 302-redirects to the bonded provider's authorization URL.
229
+ Without this half the state cookie `logInOAuth` validates is never set,
230
+ so every callback fails 403 (this is exactly how the generated-app fleet
231
+ shipped an exchange endpoint with no way to start the dance). The GET
232
+ carries the same `rateLimitAuth` throttle as the POST: it has no body, so
233
+ only the generous per-IP bucket applies — an abuse ceiling on cookie-mint/
234
+ redirect flooding that a legitimate login (one GET + one POST) never
235
+ approaches. A trip is a 429 JSON on a top-level navigation, which is
236
+ acceptable for that ceiling.
237
+ - `POST /users/log-in/oauth` (rateLimitAuth + logInOAuth) — callback
238
+ exchange: verifies state + code with the bonded provider and logs the
239
+ user in.
240
+
241
+ Only mount when the app wires an oauth bond. Handlers check the bond
242
+ registry at request time, so an unbonded provider yields a clean 404.
243
+
244
+ ```typescript
245
+ function mountDefaultUserOAuthLoginRoute(router: Router, user: UserRequestHandlerMap): void
246
+ ```
247
+
248
+ #### `mountDefaultUserResetPasswordRoute(router, user)`
249
+
250
+ Optional reset-password route: `POST /users/reset-password` (rateLimitAuth +
251
+ resetPassword). Only mount when the app uses the pkg's resetPassword handler
252
+ rather than a custom local handler.
253
+
254
+ ```typescript
255
+ function mountDefaultUserResetPasswordRoute(router: Router, user: UserRequestHandlerMap): void
256
+ ```
257
+
258
+ #### `mountDefaultUserSecurityRoutes(router, user)`
259
+
260
+ Mounts password + 2FA security routes:
261
+
262
+ - `PATCH /users/:id/password` (authSelf+updatePassword)
263
+ - `POST /users/:id/verify-two-factor` (authSelf + rateLimitTwoFactor + verifyTwoFactor)
264
+
265
+ The 2FA verification route carries a stricter limiter (`user.rateLimitTwoFactor`)
266
+ that temp-locks the second factor per account after consecutive misses.
267
+
268
+ ```typescript
269
+ function mountDefaultUserSecurityRoutes(router: Router, user: UserRequestHandlerMap): void
270
+ ```
271
+
272
+ #### `mountDefaultUserVerifyPaymentRoutes(router, user)`
273
+
274
+ Optional payment-verification routes for apps that support
275
+ client-driven payment confirmation (Apple/Google receipt verify).
276
+
277
+ Both verbs require `authSelf` ([M3-1]): the handler mutates and returns the
278
+ `:id` user, so an unauthenticated / cross-user call must not reach it. The
279
+ permissive global `verifyMiddleware()` never blocks, so per-route `authSelf`
280
+ is the gate. `authSelf` does NOT break the Stripe Checkout `success_url`
281
+ callback — that is a top-level browser navigation which carries the
282
+ `sameSite:'lax'` session cookie — and in-handler customer/checkout-session
283
+ binding remains as defense-in-depth. This mirrors the hardened declarative
284
+ route table (`resources/user/src/routes.ts`) and molecule-dev's live router;
285
+ the fix had not been propagated to this mounter, which the generated-app
286
+ fleet uses.
287
+
288
+ - `GET /users/:id/verify-payment/:provider` (authSelf+verifyPayment)
289
+ - `POST /users/:id/verify-payment/:provider` (authSelf+verifyPayment)
290
+
291
+ ```typescript
292
+ function mountDefaultUserVerifyPaymentRoutes(router: Router, user: UserRequestHandlerMap): void
293
+ ```
294
+
295
+ #### `requireAuth(_req, res, next)`
296
+
297
+ Express middleware that 401s any request lacking `res.locals.session.userId`.
298
+ Drop-in for the fleet's 51 inline `requireAuth` copies.
299
+
300
+ ```typescript
301
+ function requireAuth(
302
+ _req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>,
303
+ res: Response<any, Record<string, any>>,
304
+ next: NextFunction,
305
+ ): void
306
+ ```
307
+
308
+ #### `requireOwnership(table, id, userId)`
309
+
310
+ Look up a row by id and verify the caller owns it via `owner_id`.
311
+ Returns the row on success, 404 when missing OR owned by a different
312
+ user (so attackers can't probe row existence).
313
+
314
+ ```typescript
315
+ function requireOwnership(table: string, id: string, userId: string): Promise<AuthzResult<T>>
316
+ ```
317
+
318
+ #### `requireUser(res)`
319
+
320
+ Like `getUserId` but writes a 401 + returns null when there's no
321
+ session. Use at the top of handler bodies to bail early:
322
+
323
+ ```ts
324
+ const userId = requireUser(res)
325
+ if (!userId) return
326
+ ```
327
+
328
+ ```typescript
329
+ function requireUser(res: Response<any, Record<string, any>>): string | null
330
+ ```
331
+
332
+ #### `requireUserOwnership(table, id, userId)`
333
+
334
+ Variant of `requireOwnership` for tables that scope by `user_id`
335
+ instead of `owner_id` (notifications, user-bound preferences, etc.).
336
+
337
+ ```typescript
338
+ function requireUserOwnership(table: string, id: string, userId: string): Promise<AuthzResult<T>>
339
+ ```
340
+
341
+ #### `setupAiAnthropic()`
342
+
343
+ Registers `@molecule/api-ai-anthropic` as a named `'anthropic'` AI provider.
344
+
345
+ ```typescript
346
+ function setupAiAnthropic(): Promise<void>
347
+ ```
348
+
349
+ #### `setupAiEmbeddingsOpenai()`
350
+
351
+ Wires `@molecule/api-ai-embeddings-openai` to `@molecule/api-ai-embeddings`.
352
+
353
+ ```typescript
354
+ function setupAiEmbeddingsOpenai(): Promise<void>
355
+ ```
356
+
357
+ #### `setupAiOpenai()`
358
+
359
+ Registers `@molecule/api-ai-openai` as a named `'openai'` AI provider.
360
+
361
+ ```typescript
362
+ function setupAiOpenai(): Promise<void>
363
+ ```
364
+
365
+ #### `setupAiSpeechOpenai()`
366
+
367
+ Wires `@molecule/api-ai-speech-openai` to `@molecule/api-ai-speech`.
368
+
369
+ ```typescript
370
+ function setupAiSpeechOpenai(): Promise<void>
371
+ ```
372
+
373
+ #### `setupAiVectorStorePgvector()`
374
+
375
+ Wires `@molecule/api-ai-vector-store-pgvector` to `@molecule/api-ai-vector-store`.
376
+
377
+ ```typescript
378
+ function setupAiVectorStorePgvector(): Promise<void>
379
+ ```
380
+
381
+ #### `setupApiAnalyticsDefault()`
382
+
383
+ Wires a no-op default analytics provider so `@molecule/api-analytics` calls succeed.
384
+
385
+ ```typescript
386
+ function setupApiAnalyticsDefault(): Promise<void>
387
+ ```
388
+
389
+ #### `setupAuditDatabase()`
390
+
391
+ Wires `@molecule/api-audit-database` to `@molecule/api-audit`.
392
+
393
+ ```typescript
394
+ function setupAuditDatabase(): Promise<void>
395
+ ```
396
+
397
+ #### `setupCacheRedis()`
398
+
399
+ Wires `@molecule/api-cache-redis` to `@molecule/api-cache`.
400
+
401
+ ```typescript
402
+ function setupCacheRedis(): Promise<void>
403
+ ```
404
+
405
+ #### `setupConfigEnv()`
406
+
407
+ Wires `@molecule/api-config-env` to `@molecule/api-config`.
408
+
409
+ ```typescript
410
+ function setupConfigEnv(): void
411
+ ```
412
+
413
+ #### `setupCronNodeCron()`
414
+
415
+ Wires `@molecule/api-cron-node-cron` to `@molecule/api-cron`.
416
+
417
+ ```typescript
418
+ function setupCronNodeCron(): Promise<void>
419
+ ```
420
+
421
+ #### `setupDatabasePostgresql()`
422
+
423
+ Wires `@molecule/api-database-postgresql` to `@molecule/api-database`.
424
+
425
+ ```typescript
426
+ function setupDatabasePostgresql(): void
427
+ ```
428
+
429
+ #### `setupEmailsMailgun()`
430
+
431
+ Wires `@molecule/api-emails-mailgun` to `@molecule/api-emails`.
432
+
433
+ ```typescript
434
+ function setupEmailsMailgun(): void
435
+ ```
436
+
437
+ #### `setupEncryptionAes()`
438
+
439
+ Wires `@molecule/api-encryption-aes` to `@molecule/api-encryption`.
440
+
441
+ ```typescript
442
+ function setupEncryptionAes(): Promise<void>
443
+ ```
444
+
445
+ #### `setupErrorTrackingConsole()`
446
+
447
+ Wires `@molecule/api-error-tracking-console` to `@molecule/api-error-tracking`.
448
+
449
+ Zero-credential development default: captures are logged as structured
450
+ lines through the bonded logger instead of being sent to a remote service.
451
+
452
+ ```typescript
453
+ function setupErrorTrackingConsole(): Promise<void>
454
+ ```
455
+
456
+ #### `setupErrorTrackingSentry()`
457
+
458
+ Wires `@molecule/api-error-tracking-sentry` to `@molecule/api-error-tracking`.
459
+
460
+ Safe to wire unconditionally: without `SENTRY_DSN` the Sentry provider is a
461
+ documented no-op (the boot config report flags the missing key), so an app
462
+ that hasn't configured Sentry yet boots and runs untouched.
463
+
464
+ ```typescript
465
+ function setupErrorTrackingSentry(): Promise<void>
466
+ ```
467
+
468
+ #### `setupGeolocationGoogle()`
469
+
470
+ Wires `@molecule/api-geolocation-google` to `@molecule/api-geolocation`.
471
+
472
+ ```typescript
473
+ function setupGeolocationGoogle(): Promise<void>
474
+ ```
475
+
476
+ #### `setupGeolocationMapbox()`
477
+
478
+ Wires `@molecule/api-geolocation-mapbox` to `@molecule/api-geolocation`.
479
+
480
+ ```typescript
481
+ function setupGeolocationMapbox(): Promise<void>
482
+ ```
483
+
484
+ #### `setupHttpFetch()`
485
+
486
+ Wires `@molecule/api-http-fetch` to `@molecule/api-http`.
487
+
488
+ ```typescript
489
+ function setupHttpFetch(): Promise<void>
490
+ ```
491
+
492
+ #### `setupImageSharp()`
493
+
494
+ Wires `@molecule/api-image-sharp` to `@molecule/api-image`.
495
+
496
+ ```typescript
497
+ function setupImageSharp(): Promise<void>
498
+ ```
499
+
500
+ #### `setupImportExportCsv()`
501
+
502
+ Wires `@molecule/api-import-export-csv` to `@molecule/api-import-export`.
503
+
504
+ ```typescript
505
+ function setupImportExportCsv(): Promise<void>
506
+ ```
507
+
508
+ #### `setupJwtJsonwebtoken()`
509
+
510
+ Wires `@molecule/api-jwt-jsonwebtoken` to `@molecule/api-jwt`.
511
+
512
+ ```typescript
513
+ function setupJwtJsonwebtoken(): void
514
+ ```
515
+
516
+ #### `setupMediaStreamingHls()`
517
+
518
+ Wires `@molecule/api-media-streaming-hls` to `@molecule/api-media-streaming`.
519
+
520
+ ```typescript
521
+ function setupMediaStreamingHls(): Promise<void>
522
+ ```
523
+
524
+ #### `setupMiddlewareBodyParserExpress()`
525
+
526
+ Wires `@molecule/api-middleware-body-parser-express` to `@molecule/api-middleware-body-parser`.
527
+
528
+ ```typescript
529
+ function setupMiddlewareBodyParserExpress(): void
530
+ ```
531
+
532
+ #### `setupMiddlewareCookieParserExpress()`
533
+
534
+ Wires `@molecule/api-middleware-cookie-parser-express` to `@molecule/api-middleware-cookie-parser`.
535
+
536
+ ```typescript
537
+ function setupMiddlewareCookieParserExpress(): void
538
+ ```
539
+
540
+ #### `setupMiddlewareCorsExpress()`
541
+
542
+ Wires `@molecule/api-middleware-cors-express` to `@molecule/api-middleware-cors`.
543
+
544
+ ```typescript
545
+ function setupMiddlewareCorsExpress(): void
546
+ ```
547
+
548
+ #### `setupNotificationsWebhook()`
549
+
550
+ Registers `@molecule/api-notifications-webhook` as a named `'webhook'` notifications provider.
551
+
552
+ ```typescript
553
+ function setupNotificationsWebhook(): Promise<void>
554
+ ```
555
+
556
+ #### `setupPasswordBcrypt()`
557
+
558
+ Wires `@molecule/api-password-bcrypt` to `@molecule/api-password`.
559
+
560
+ ```typescript
561
+ function setupPasswordBcrypt(): void
562
+ ```
563
+
564
+ #### `setupPaymentsStripe()`
565
+
566
+ Registers `@molecule/api-payments-stripe` as a named `'stripe'` payments provider.
567
+
568
+ ```typescript
569
+ function setupPaymentsStripe(): void
570
+ ```
571
+
572
+ #### `setupPdfPdfkit()`
573
+
574
+ Wires `@molecule/api-pdf-pdfkit` to `@molecule/api-pdf`.
575
+
576
+ ```typescript
577
+ function setupPdfPdfkit(): Promise<void>
578
+ ```
579
+
580
+ #### `setupPermissionsCustom()`
581
+
582
+ Wires `@molecule/api-permissions-custom` to `@molecule/api-permissions`.
583
+
584
+ ```typescript
585
+ function setupPermissionsCustom(): Promise<void>
586
+ ```
587
+
588
+ #### `setupPushNotificationsWebPush()`
589
+
590
+ Wires `@molecule/api-push-notifications-web-push` to `@molecule/api-push-notifications`.
591
+
592
+ ```typescript
593
+ function setupPushNotificationsWebPush(): Promise<void>
594
+ ```
595
+
596
+ #### `setupQueueMemory()`
597
+
598
+ Wires `@molecule/api-queue-memory` to `@molecule/api-queue` — the
599
+ zero-credential in-process queue (single-process/dev; swap to
600
+ redis/rabbitmq/sqs for multi-instance production).
601
+
602
+ ```typescript
603
+ function setupQueueMemory(): Promise<void>
604
+ ```
605
+
606
+ #### `setupQueueRedis()`
607
+
608
+ Wires `@molecule/api-queue-redis` to `@molecule/api-queue`. Outside
609
+ production, when `REDIS_URL` is absent, falls back to
610
+ `@molecule/api-queue-memory` — the zero-credential in-process queue — so
611
+ queue-backed features (background jobs, async delivery workers) run out of
612
+ the box, mirroring `setupCacheRedis`.
613
+
614
+ ```typescript
615
+ function setupQueueRedis(): Promise<void>
616
+ ```
617
+
618
+ #### `setupRateLimitMemory()`
619
+
620
+ Wires `@molecule/api-rate-limit-memory` to `@molecule/api-rate-limit`.
621
+
622
+ This is the default brute-force-protection backend for `mlcl`-generated apps
623
+ (single-instance). Multi-instance deployments should swap in
624
+ `@molecule/api-rate-limit-redis` so the throttle is shared across replicas.
625
+
626
+ ```typescript
627
+ function setupRateLimitMemory(): Promise<void>
628
+ ```
629
+
630
+ #### `setupRealtimeSocketio()`
631
+
632
+ Wires `@molecule/api-realtime-socketio` to `@molecule/api-realtime`.
633
+
634
+ ```typescript
635
+ function setupRealtimeSocketio(): Promise<void>
636
+ ```
637
+
638
+ #### `setupRealtimeSse()`
639
+
640
+ Wires `@molecule/api-realtime-sse` to `@molecule/api-realtime`.
641
+
642
+ ```typescript
643
+ function setupRealtimeSse(): Promise<void>
644
+ ```
645
+
646
+ #### `setupRealtimeWs()`
647
+
648
+ Wires `@molecule/api-realtime-ws` to `@molecule/api-realtime`.
649
+
650
+ ```typescript
651
+ function setupRealtimeWs(): Promise<void>
652
+ ```
653
+
654
+ #### `setupReportingDatabase()`
655
+
656
+ Wires `@molecule/api-reporting-database` to `@molecule/api-reporting`.
657
+
658
+ ```typescript
659
+ function setupReportingDatabase(): Promise<void>
660
+ ```
661
+
662
+ #### `setupSearchMeilisearch()`
663
+
664
+ Wires `@molecule/api-search-meilisearch` to `@molecule/api-search`.
665
+
666
+ ```typescript
667
+ function setupSearchMeilisearch(): void
668
+ ```
669
+
670
+ #### `setupSecretsEnv()`
671
+
672
+ Wires `@molecule/api-secrets-env` to `@molecule/api-secrets`.
673
+
674
+ ```typescript
675
+ function setupSecretsEnv(): void
676
+ ```
677
+
678
+ #### `setupServiceDevice()`
679
+
680
+ Registers the device service from `@molecule/api-resource-device` on the bond system.
681
+
682
+ ```typescript
683
+ function setupServiceDevice(): void
684
+ ```
685
+
686
+ #### `setupServicePayment()`
687
+
688
+ Registers the plan + paymentRecord services from `@molecule/api-resource-payment`.
689
+
690
+ ```typescript
691
+ function setupServicePayment(): void
692
+ ```
693
+
694
+ #### `setupTwoFactorOtplib()`
695
+
696
+ Wires `@molecule/api-two-factor-otplib` to `@molecule/api-two-factor`.
697
+
698
+ ```typescript
699
+ function setupTwoFactorOtplib(): void
700
+ ```
701
+
702
+ #### `setupUploadsS3()`
703
+
704
+ Wires `@molecule/api-uploads-s3` to `@molecule/api-uploads`.
705
+
706
+ ```typescript
707
+ function setupUploadsS3(): void
708
+ ```
709
+
710
+ #### `setupWebhookHttp()`
711
+
712
+ Wires `@molecule/api-webhook-http` to `@molecule/api-webhook`.
713
+
714
+ ```typescript
715
+ function setupWebhookHttp(): Promise<void>
716
+ ```
717
+
718
+ #### `setupWorkflowDatabase()`
719
+
720
+ Wires `@molecule/api-workflow-database` to `@molecule/api-workflow`.
721
+
722
+ ```typescript
723
+ function setupWorkflowDatabase(): Promise<void>
724
+ ```
725
+
726
+ #### `trackAuthEvent(eventName)`
727
+
728
+ Emits an analytics event AND a log entry for an auth-related mutation
729
+ (signup, login, password reset, plan change, etc.). Logs at info on
730
+ success and warn on auth failure (4xx) so security signal is captured.
731
+
732
+ Replaces the per-app `api/src/middleware/auth-analytics.ts` shipped
733
+ by 10 fleet apps.
734
+
735
+ ```typescript
736
+ function trackAuthEvent(
737
+ eventName: string,
738
+ ): RequestHandler<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
739
+ ```
740
+
741
+ #### `validationError(res, issues)`
742
+
743
+ Standard 400 response for zod / schema validation failures.
744
+ Used by ~21 fleet apps' `api/src/lib/authz.ts` files.
745
+
746
+ ```typescript
747
+ function validationError(res: Response<any, Record<string, any>>, issues: unknown): void
748
+ ```
749
+
750
+ ### Constants
751
+
752
+ #### `deviceRequestHandlerMap`
753
+
754
+ Pre-wired request handler map for `@molecule/api-resource-device`.
755
+
756
+ ```typescript
757
+ const deviceRequestHandlerMap: DeviceRequestHandlerMap
758
+ ```
759
+
760
+ #### `deviceService`
761
+
762
+ DeviceService implementation for the bond system.
763
+
764
+ Provides device CRUD operations that other resources
765
+ can use through `get('device')` / `require('device')`.
766
+
767
+ ```typescript
768
+ const deviceService: DeviceService
769
+ ```
770
+
771
+ #### `idParamSchema`
772
+
773
+ Standard route-param schema for `:id`. Accepts any non-empty string.
774
+ Pair with `validateParams(idParamSchema)`.
775
+
776
+ ```typescript
777
+ const idParamSchema: z.ZodObject<{ id: z.ZodString }, z.core.$strip>
778
+ ```
779
+
780
+ #### `userRequestHandlerMap`
781
+
782
+ Pre-wired request handler map for `@molecule/api-resource-user`.
783
+
784
+ ```typescript
785
+ const userRequestHandlerMap: UserRequestHandlerMap
786
+ ```
787
+
788
+ #### `uuidParamSchema`
789
+
790
+ Strict variant of `idParamSchema` that requires a UUID. Use when the
791
+ underlying column is a uuid.
792
+
793
+ ```typescript
794
+ const uuidParamSchema: z.ZodObject<{ id: z.ZodString }, z.core.$strip>
795
+ ```
796
+
797
+ ### Namespaces
798
+
799
+ #### `userAuthorization`
800
+
801
+ Members:
802
+
803
+ - `userAuthorization.getAuthCookieName` — const: Resolve the actual cookie name for an auth cookie.
804
+ - `userAuthorization.getAuthCookieOptions` — const: Base cookie attributes shared by EVERY auth cookie this resource sets and
805
+ - `userAuthorization.invalidateDeviceExistsCache` — const: Evict a single device's positive entry from the device-exists cache so the
806
+ - `userAuthorization.invalidateAllDeviceExistsCache` — const: Evict ALL positive entries from the device-exists cache.
807
+ - `userAuthorization.set` — const: Set authorization headers and cookie for a session.
808
+ - `userAuthorization.verifyMiddleware` — const: Middleware that verifies the JWT token from the `Authorization` header and sets `res.locals.session`.
809
+
810
+ ## Injection Notes
811
+
812
+ ### Requirements
813
+
814
+ Peer dependencies:
815
+
816
+ - `@molecule/api-ai-anthropic` ^1.0.1
817
+ - `@molecule/api-ai-embeddings` ^1.0.1
818
+ - `@molecule/api-ai-embeddings-openai` ^1.0.1
819
+ - `@molecule/api-ai-openai` ^1.0.1
820
+ - `@molecule/api-ai-speech` ^1.0.1
821
+ - `@molecule/api-ai-speech-openai` ^1.0.1
822
+ - `@molecule/api-ai-vector-store` ^1.0.1
823
+ - `@molecule/api-ai-vector-store-pgvector` ^1.0.1
824
+ - `@molecule/api-analytics` ^1.0.1
825
+ - `@molecule/api-audit` ^1.0.1
826
+ - `@molecule/api-audit-database` ^1.0.1
827
+ - `@molecule/api-bond` ^1.0.1
828
+ - `@molecule/api-cache` ^1.0.1
829
+ - `@molecule/api-cache-memory` ^1.0.1
830
+ - `@molecule/api-cache-redis` ^1.0.1
831
+ - `@molecule/api-config` ^1.0.1
832
+ - `@molecule/api-config-env` ^1.0.1
833
+ - `@molecule/api-cron` ^1.0.1
834
+ - `@molecule/api-cron-node-cron` ^1.0.1
835
+ - `@molecule/api-database` ^1.0.1
836
+ - `@molecule/api-database-postgresql` ^1.0.1
837
+ - `@molecule/api-emails` ^1.0.1
838
+ - `@molecule/api-emails-capture` ^1.0.1
839
+ - `@molecule/api-emails-mailgun` ^1.0.1
840
+ - `@molecule/api-encryption` ^1.0.1
841
+ - `@molecule/api-encryption-aes` ^1.0.1
842
+ - `@molecule/api-entitlements` ^1.0.1
843
+ - `@molecule/api-error-tracking` ^1.0.1
844
+ - `@molecule/api-error-tracking-console` ^1.0.1
845
+ - `@molecule/api-error-tracking-sentry` ^1.0.1
846
+ - `@molecule/api-geolocation` ^1.0.1
847
+ - `@molecule/api-geolocation-google` ^1.0.1
848
+ - `@molecule/api-geolocation-mapbox` ^1.0.1
849
+ - `@molecule/api-geolocation-nominatim` ^1.0.1
850
+ - `@molecule/api-http` ^1.0.1
851
+ - `@molecule/api-http-fetch` ^1.0.1
852
+ - `@molecule/api-i18n` ^1.0.1
853
+ - `@molecule/api-image` ^1.0.1
854
+ - `@molecule/api-image-sharp` ^1.0.1
855
+ - `@molecule/api-import-export` ^1.0.1
856
+ - `@molecule/api-import-export-csv` ^1.0.1
857
+ - `@molecule/api-jwt` ^1.0.1
858
+ - `@molecule/api-jwt-jsonwebtoken` ^1.0.1
859
+ - `@molecule/api-logger` ^1.0.1
860
+ - `@molecule/api-media-streaming` ^1.0.1
861
+ - `@molecule/api-media-streaming-hls` ^1.0.1
862
+ - `@molecule/api-middleware-body-parser` ^1.0.1
863
+ - `@molecule/api-middleware-body-parser-express` ^1.0.1
864
+ - `@molecule/api-middleware-cookie-parser` ^1.0.1
865
+ - `@molecule/api-middleware-cookie-parser-express` ^1.0.1
866
+ - `@molecule/api-middleware-cors` ^1.0.1
867
+ - `@molecule/api-middleware-cors-express` ^1.0.1
868
+ - `@molecule/api-middleware-validation` ^1.0.1
869
+ - `@molecule/api-notifications-webhook` ^1.0.1
870
+ - `@molecule/api-password` ^1.0.1
871
+ - `@molecule/api-password-bcrypt` ^1.0.1
872
+ - `@molecule/api-payments` ^1.0.1
873
+ - `@molecule/api-payments-stripe` ^1.0.1
874
+ - `@molecule/api-pdf` ^1.0.1
875
+ - `@molecule/api-pdf-pdfkit` ^1.0.1
876
+ - `@molecule/api-permissions` ^1.0.1
877
+ - `@molecule/api-permissions-custom` ^1.0.1
878
+ - `@molecule/api-push-capture` ^1.0.1
879
+ - `@molecule/api-push-notifications` ^1.0.1
880
+ - `@molecule/api-push-notifications-web-push` ^1.0.1
881
+ - `@molecule/api-queue` ^1.0.1
882
+ - `@molecule/api-queue-memory` ^1.0.1
883
+ - `@molecule/api-queue-redis` ^1.0.1
884
+ - `@molecule/api-rate-limit` ^1.0.1
885
+ - `@molecule/api-rate-limit-memory` ^1.0.1
886
+ - `@molecule/api-realtime` ^1.0.1
887
+ - `@molecule/api-realtime-socketio` ^1.0.1
888
+ - `@molecule/api-realtime-sse` ^1.0.1
889
+ - `@molecule/api-realtime-ws` ^1.0.1
890
+ - `@molecule/api-reporting` ^1.0.1
891
+ - `@molecule/api-reporting-database` ^1.0.1
892
+ - `@molecule/api-resource` ^1.0.1
893
+ - `@molecule/api-resource-device` ^1.0.1
894
+ - `@molecule/api-resource-payment` ^1.0.1
895
+ - `@molecule/api-resource-user` ^1.0.1
896
+ - `@molecule/api-search` ^1.0.1
897
+ - `@molecule/api-search-meilisearch` ^1.0.1
898
+ - `@molecule/api-search-postgres` ^1.0.1
899
+ - `@molecule/api-secrets` ^1.0.1
900
+ - `@molecule/api-secrets-env` ^1.0.1
901
+ - `@molecule/api-two-factor` ^1.0.1
902
+ - `@molecule/api-two-factor-otplib` ^1.0.1
903
+ - `@molecule/api-uploads` ^1.0.1
904
+ - `@molecule/api-uploads-filesystem` ^1.0.1
905
+ - `@molecule/api-uploads-s3` ^1.0.1
906
+ - `@molecule/api-webhook` ^1.0.1
907
+ - `@molecule/api-webhook-http` ^1.0.1
908
+ - `@molecule/api-workflow` ^1.0.1
909
+ - `@molecule/api-workflow-database` ^1.0.1
910
+
911
+ ### Runtime Dependencies
912
+
913
+ - `@molecule/api-ai-anthropic`
914
+ - `@molecule/api-ai-embeddings`
915
+ - `@molecule/api-ai-embeddings-openai`
916
+ - `@molecule/api-ai-openai`
917
+ - `@molecule/api-ai-speech`
918
+ - `@molecule/api-ai-speech-openai`
919
+ - `@molecule/api-ai-vector-store`
920
+ - `@molecule/api-ai-vector-store-pgvector`
921
+ - `@molecule/api-analytics`
922
+ - `@molecule/api-audit`
923
+ - `@molecule/api-audit-database`
924
+ - `@molecule/api-bond`
925
+ - `@molecule/api-cache`
926
+ - `@molecule/api-cache-memory`
927
+ - `@molecule/api-cache-redis`
928
+ - `@molecule/api-config`
929
+ - `@molecule/api-config-env`
930
+ - `@molecule/api-cron`
931
+ - `@molecule/api-cron-node-cron`
932
+ - `@molecule/api-database`
933
+ - `@molecule/api-database-postgresql`
934
+ - `@molecule/api-emails`
935
+ - `@molecule/api-emails-capture`
936
+ - `@molecule/api-emails-mailgun`
937
+ - `@molecule/api-encryption`
938
+ - `@molecule/api-encryption-aes`
939
+ - `@molecule/api-entitlements`
940
+ - `@molecule/api-error-tracking`
941
+ - `@molecule/api-error-tracking-console`
942
+ - `@molecule/api-error-tracking-sentry`
943
+ - `@molecule/api-geolocation`
944
+ - `@molecule/api-geolocation-google`
945
+ - `@molecule/api-geolocation-mapbox`
946
+ - `@molecule/api-geolocation-nominatim`
947
+ - `@molecule/api-http`
948
+ - `@molecule/api-http-fetch`
949
+ - `@molecule/api-i18n`
950
+ - `@molecule/api-image`
951
+ - `@molecule/api-image-sharp`
952
+ - `@molecule/api-import-export`
953
+ - `@molecule/api-import-export-csv`
954
+ - `@molecule/api-jwt`
955
+ - `@molecule/api-jwt-jsonwebtoken`
956
+ - `@molecule/api-logger`
957
+ - `@molecule/api-media-streaming`
958
+ - `@molecule/api-media-streaming-hls`
959
+ - `@molecule/api-middleware-body-parser`
960
+ - `@molecule/api-middleware-body-parser-express`
961
+ - `@molecule/api-middleware-cookie-parser`
962
+ - `@molecule/api-middleware-cookie-parser-express`
963
+ - `@molecule/api-middleware-cors`
964
+ - `@molecule/api-middleware-cors-express`
965
+ - `@molecule/api-middleware-validation`
966
+ - `@molecule/api-notifications-webhook`
967
+ - `@molecule/api-password`
968
+ - `@molecule/api-password-bcrypt`
969
+ - `@molecule/api-payments`
970
+ - `@molecule/api-payments-stripe`
971
+ - `@molecule/api-pdf`
972
+ - `@molecule/api-pdf-pdfkit`
973
+ - `@molecule/api-permissions`
974
+ - `@molecule/api-permissions-custom`
975
+ - `@molecule/api-push-capture`
976
+ - `@molecule/api-push-notifications`
977
+ - `@molecule/api-push-notifications-web-push`
978
+ - `@molecule/api-queue`
979
+ - `@molecule/api-queue-memory`
980
+ - `@molecule/api-queue-redis`
981
+ - `@molecule/api-rate-limit`
982
+ - `@molecule/api-rate-limit-memory`
983
+ - `@molecule/api-realtime`
984
+ - `@molecule/api-realtime-socketio`
985
+ - `@molecule/api-realtime-sse`
986
+ - `@molecule/api-realtime-ws`
987
+ - `@molecule/api-reporting`
988
+ - `@molecule/api-reporting-database`
989
+ - `@molecule/api-resource`
990
+ - `@molecule/api-resource-device`
991
+ - `@molecule/api-resource-payment`
992
+ - `@molecule/api-resource-user`
993
+ - `@molecule/api-search`
994
+ - `@molecule/api-search-meilisearch`
995
+ - `@molecule/api-search-postgres`
996
+ - `@molecule/api-secrets`
997
+ - `@molecule/api-secrets-env`
998
+ - `@molecule/api-two-factor`
999
+ - `@molecule/api-two-factor-otplib`
1000
+ - `@molecule/api-uploads`
1001
+ - `@molecule/api-uploads-filesystem`
1002
+ - `@molecule/api-uploads-s3`
1003
+ - `@molecule/api-webhook`
1004
+ - `@molecule/api-webhook-http`
1005
+ - `@molecule/api-workflow`
1006
+ - `@molecule/api-workflow-database`
1007
+
1008
+ - **Development falls back to zero-credential providers; production never
1009
+ does.** When `NODE_ENV !== 'production'` and a provider's required env
1010
+ is missing, the setup wires the capture/local sibling instead and logs
1011
+ the swap: mailgun→emails-capture (`MAILGUN_API_KEY`/`MAILGUN_DOMAIN`),
1012
+ uploads-s3→uploads-filesystem (`AWS_*`), search-meilisearch→
1013
+ search-postgres (`MEILISEARCH_URL`), web-push→push-capture
1014
+ (`VAPID_*`), geolocation-mapbox→nominatim (`MAPBOX_ACCESS_TOKEN`),
1015
+ cache-redis→cache-memory (`REDIS_URL`). In production the credentialed
1016
+ provider is wired regardless — missing env surfaces as loud,
1017
+ actionable 503s and boot-report entries, never a silent provider swap.
1018
+ So "emails don't arrive in dev" usually means they were CAPTURED (read
1019
+ them via the activity/capture tooling), not lost.
1020
+ - **Realtime setups (`setupRealtimeSocketio`, `setupRealtimeWs`,
1021
+ `setupRealtimeSse`) all defer-attach.** Each dynamic-imports its
1022
+ provider's `createProvider({ deferAttach: true })`, calls
1023
+ `setProvider()`, then `registerServerCreatedHook((server) =>
1024
+ provider.attachHttpServer?.(server))` from
1025
+ `@molecule/api-server-default-express` — so the realtime transport
1026
+ shares the API's HTTP server/port once it exists, instead of a
1027
+ standalone port a containerized sandbox / proxied deploy may not
1028
+ expose. Add new realtime bonds by mirroring this pattern exactly.
1029
+ - `createBillingRouter` registers the app's Stripe plan catalogue with
1030
+ `@molecule/api-resource-payment` at construction AND re-registers per
1031
+ checkout (price-id env vars may resolve after startup); webhook
1032
+ handling stays with `@molecule/api-resource-user`'s
1033
+ `handlePaymentNotification`. A paid price whose `planKeys` entry is
1034
+ missing is skipped WITH a warning — that plan could never be granted.
1035
+ - Only wire the setups whose packages your app actually installed —
1036
+ each one imports its provider package (several lazily via dynamic
1037
+ import), so calling a setup for an uninstalled bond fails at that
1038
+ import.