@spfn/core 0.2.0-beta.9 → 0.3.0-beta.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 (95) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +467 -305
  3. package/dist/authz/index.d.ts +34 -0
  4. package/dist/authz/index.js +810 -0
  5. package/dist/authz/index.js.map +1 -0
  6. package/dist/{boss-DI1r4kTS.d.ts → boss-D16fO2oG.d.ts} +41 -1
  7. package/dist/cache/index.js +42 -30
  8. package/dist/cache/index.js.map +1 -1
  9. package/dist/codegen/index.d.ts +121 -13
  10. package/dist/codegen/index.js +212 -15
  11. package/dist/codegen/index.js.map +1 -1
  12. package/dist/config/index.d.ts +615 -6
  13. package/dist/config/index.js +124 -5
  14. package/dist/config/index.js.map +1 -1
  15. package/dist/contract/index.d.ts +220 -0
  16. package/dist/contract/index.js +558 -0
  17. package/dist/contract/index.js.map +1 -0
  18. package/dist/db/index.d.ts +528 -85
  19. package/dist/db/index.js +831 -122
  20. package/dist/db/index.js.map +1 -1
  21. package/dist/define-middleware-DfDP39Nq.d.ts +167 -0
  22. package/dist/env/index.d.ts +26 -2
  23. package/dist/env/index.js +15 -5
  24. package/dist/env/index.js.map +1 -1
  25. package/dist/env/loader.d.ts +26 -19
  26. package/dist/env/loader.js +32 -25
  27. package/dist/env/loader.js.map +1 -1
  28. package/dist/errors/index.d.ts +10 -0
  29. package/dist/errors/index.js +418 -5
  30. package/dist/errors/index.js.map +1 -1
  31. package/dist/event/index.d.ts +33 -3
  32. package/dist/event/index.js +24 -3
  33. package/dist/event/index.js.map +1 -1
  34. package/dist/event/sse/client.d.ts +42 -3
  35. package/dist/event/sse/client.js +128 -45
  36. package/dist/event/sse/client.js.map +1 -1
  37. package/dist/event/sse/index.d.ts +12 -5
  38. package/dist/event/sse/index.js +280 -32
  39. package/dist/event/sse/index.js.map +1 -1
  40. package/dist/event/ws/client.d.ts +59 -0
  41. package/dist/event/ws/client.js +273 -0
  42. package/dist/event/ws/client.js.map +1 -0
  43. package/dist/event/ws/index.d.ts +94 -0
  44. package/dist/event/ws/index.js +272 -0
  45. package/dist/event/ws/index.js.map +1 -0
  46. package/dist/job/index.d.ts +2 -2
  47. package/dist/job/index.js +155 -42
  48. package/dist/job/index.js.map +1 -1
  49. package/dist/logger/index.d.ts +5 -0
  50. package/dist/logger/index.js +14 -0
  51. package/dist/logger/index.js.map +1 -1
  52. package/dist/middleware/index.d.ts +347 -9
  53. package/dist/middleware/index.js +1462 -15
  54. package/dist/middleware/index.js.map +1 -1
  55. package/dist/nextjs/index.d.ts +2 -2
  56. package/dist/nextjs/index.js +42 -28
  57. package/dist/nextjs/index.js.map +1 -1
  58. package/dist/nextjs/server.d.ts +35 -51
  59. package/dist/nextjs/server.js +126 -60
  60. package/dist/nextjs/server.js.map +1 -1
  61. package/dist/ops/index.d.ts +152 -0
  62. package/dist/ops/index.js +500 -0
  63. package/dist/ops/index.js.map +1 -0
  64. package/dist/route/index.d.ts +8 -694
  65. package/dist/route/index.js +111 -22
  66. package/dist/route/index.js.map +1 -1
  67. package/dist/router-Qbssr11H.d.ts +676 -0
  68. package/dist/security/index.d.ts +83 -0
  69. package/dist/security/index.js +173 -0
  70. package/dist/security/index.js.map +1 -0
  71. package/dist/server/index.d.ts +491 -22
  72. package/dist/server/index.js +1887 -308
  73. package/dist/server/index.js.map +1 -1
  74. package/dist/token-manager-BT5EnUAR.d.ts +278 -0
  75. package/dist/types-2AbaW4Ie.d.ts +205 -0
  76. package/dist/{types-BOPTApC2.d.ts → types-9oszaJqp.d.ts} +7 -2
  77. package/dist/types-D1c57Ko-.d.ts +115 -0
  78. package/dist/types-ZQODsBft.d.ts +282 -0
  79. package/package.json +244 -208
  80. package/dist/router-Di7ENoah.d.ts +0 -151
  81. package/dist/types-B-e_f2dQ.d.ts +0 -121
  82. package/docs/cache.md +0 -133
  83. package/docs/codegen.md +0 -74
  84. package/docs/database.md +0 -346
  85. package/docs/entity.md +0 -539
  86. package/docs/env.md +0 -477
  87. package/docs/errors.md +0 -319
  88. package/docs/event.md +0 -116
  89. package/docs/job.md +0 -131
  90. package/docs/logger.md +0 -108
  91. package/docs/middleware.md +0 -337
  92. package/docs/nextjs.md +0 -241
  93. package/docs/repository.md +0 -496
  94. package/docs/route.md +0 -497
  95. package/docs/server.md +0 -307
@@ -1,23 +1,24 @@
1
- import { MiddlewareHandler, Hono } from 'hono';
1
+ export { loadEnv } from '../env/loader.js';
2
+ import { Hono, MiddlewareHandler } from 'hono';
2
3
  import { cors } from 'hono/cors';
3
4
  import { serve } from '@hono/node-server';
4
5
  import { NamedMiddleware, Router } from '@spfn/core/route';
5
- import { J as JobRouter, B as BossOptions } from '../boss-DI1r4kTS.js';
6
- import { E as EventRouterDef } from '../router-Di7ENoah.js';
7
- import { S as SSEHandlerConfig } from '../types-B-e_f2dQ.js';
6
+ import { OnErrorContext, ProxyGuardConfig, RateLimitOptions } from '@spfn/core/middleware';
7
+ import { SafeFetchPolicy } from '@spfn/core/security';
8
+ import { c as JobRouter, e as BossOptions } from '../boss-D16fO2oG.js';
9
+ import { b as EventRouterDef, E as EventDef } from '../token-manager-BT5EnUAR.js';
10
+ import { d as SSEHandlerConfig, e as SSEAuthConfig } from '../types-ZQODsBft.js';
11
+ import { W as WSRouterDef, f as WSHandlerConfig, e as WSMessageHandlers, g as WSAuthConfig } from '../types-2AbaW4Ie.js';
12
+ import { DatabaseProvider, MigrationStatus, MigrationStatusDb } from '@spfn/core/db';
8
13
  import '@sinclair/typebox';
9
14
  import 'pg-boss';
10
15
 
11
16
  /**
12
- * Load environment files for SPFN server
13
- *
14
- * Priority (high → low, later files don't override):
15
- * 1. .env.server.local - Server-only secrets (gitignored)
16
- * 2. .env.server - Server-only defaults
17
- * 3. .env.{NODE_ENV}.local
18
- * 4. .env.local - Local overrides (gitignored)
19
- * 5. .env.{NODE_ENV}
20
- * 6. .env - Defaults
17
+ * @deprecated Use `loadEnv` from '@spfn/core/env/loader' instead.
18
+ * This module will be removed in the next major version.
19
+ */
20
+ /**
21
+ * @deprecated Use `loadEnv()` from '@spfn/core/env/loader' instead.
21
22
  */
22
23
  declare function loadEnvFiles(): void;
23
24
 
@@ -77,11 +78,86 @@ interface ServerConfig {
77
78
  * Error handler (default: true)
78
79
  */
79
80
  errorHandler?: boolean;
81
+ /**
82
+ * Callback invoked when an error occurs (passed to ErrorHandler)
83
+ *
84
+ * Called asynchronously without blocking the response.
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * import { createErrorSlackNotifier } from '@spfn/notification/server';
89
+ *
90
+ * middleware: {
91
+ * onError: createErrorSlackNotifier({ minStatusCode: 500 }),
92
+ * }
93
+ * ```
94
+ */
95
+ onError?: (err: Error, context: OnErrorContext) => Promise<void> | void;
80
96
  };
81
97
  /**
82
98
  * Additional custom middleware
83
99
  */
84
100
  use?: MiddlewareHandler[];
101
+ /**
102
+ * Proxy-guard: verify requests came through the trusted Next.js RPC proxy
103
+ * (HMAC signature) and/or an allowed browser origin, then tag `clientType`.
104
+ * Lets the backend reject direct-to-backend calls that bypass the proxy.
105
+ *
106
+ * Disabled by default (`mode: 'off'`). Requires the same `SPFN_PROXY_SECRET`
107
+ * on the proxy and the backend. See PROXY-BACKEND-AUTH-SPEC.md.
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * .proxyGuard({ mode: 'strict', allowedOrigins: ['https://app.example.com'] })
112
+ * ```
113
+ */
114
+ proxyGuard?: Omit<ProxyGuardConfig, 'nonceStore'> & {
115
+ /**
116
+ * Enable hard replay rejection via a Redis nonce store. Evaluated in BOTH modes
117
+ * (tag observes replays, strict rejects). Requires a cache (CACHE_URL); without
118
+ * one, falls back to the timestamp window. Degrades to the window if the store
119
+ * is briefly unavailable. @default false
120
+ */
121
+ nonce?: boolean;
122
+ };
123
+ /**
124
+ * Rate limiting: an optional global default limiter plus named policies.
125
+ *
126
+ * `mode: 'on'` applies `default` to every named-middleware route (opt out
127
+ * with `.skip(['rateLimit'])`); `policies` lets packages tag sensitive routes
128
+ * via `rateLimitPolicy(name, fallback)` while this app tunes the numbers in
129
+ * one place. Backed by the shared cache (CACHE_URL); without a cache it fails
130
+ * open unless `default.failClosed` is set. Disabled by default (`mode: 'off'`).
131
+ *
132
+ * @example
133
+ * ```typescript
134
+ * .rateLimit({
135
+ * mode: 'on',
136
+ * default: { limit: 100, windowMs: 60_000 },
137
+ * policies: { 'auth-login': { limit: 5, windowMs: 60_000 } },
138
+ * })
139
+ * ```
140
+ */
141
+ rateLimit?: {
142
+ /** 'on' applies the default limiter to every route. @default 'off' */
143
+ mode?: 'off' | 'on';
144
+ /** Default policy applied to all routes when `mode` is 'on'. */
145
+ default?: RateLimitOptions;
146
+ /** Named policies referenced by `rateLimitPolicy(name, fallback)` tags. */
147
+ policies?: Record<string, RateLimitOptions>;
148
+ };
149
+ /**
150
+ * SSRF policy for outbound requests made via `safeFetch` (`@spfn/core/security`).
151
+ * Sets the process-wide default used by webhook/callback senders. Private and
152
+ * reserved IPs are blocked by default; set `allowHosts` to restrict to a known
153
+ * set of upstreams, or `blockPrivateIps: false` for trusted internal calls.
154
+ *
155
+ * @example
156
+ * ```typescript
157
+ * .outboundFetch({ allowHosts: ['hooks.slack.com'] })
158
+ * ```
159
+ */
160
+ outboundFetch?: SafeFetchPolicy;
85
161
  /**
86
162
  * Global middlewares with names for route-level skip control
87
163
  * Use defineMiddleware() for type-safe middleware definitions
@@ -180,6 +256,30 @@ interface ServerConfig {
180
256
  */
181
257
  path?: string;
182
258
  };
259
+ /**
260
+ * WebSocket router for bidirectional real-time communication
261
+ *
262
+ * @example
263
+ * ```typescript
264
+ * import { defineWSRouter } from '@spfn/core/event/ws';
265
+ *
266
+ * export default defineServerConfig()
267
+ * .websockets(wsRouter) // → WS /ws
268
+ * .build();
269
+ * ```
270
+ */
271
+ websockets?: WSRouterDef<any, any>;
272
+ /**
273
+ * WebSocket configuration options
274
+ * Only used if websockets router is provided
275
+ */
276
+ websocketsConfig?: WSHandlerConfig & {
277
+ /**
278
+ * WebSocket endpoint path
279
+ * @default '/ws'
280
+ */
281
+ path?: string;
282
+ };
183
283
  /**
184
284
  * Enable debug mode (default: NODE_ENV === 'development')
185
285
  */
@@ -188,6 +288,13 @@ interface ServerConfig {
188
288
  * Database configuration
189
289
  */
190
290
  database?: {
291
+ /**
292
+ * Externally owned PostgreSQL Drizzle provider.
293
+ *
294
+ * When supplied, SPFN skips DATABASE_URL/postgres.js initialization
295
+ * and closes the provider during graceful shutdown.
296
+ */
297
+ provider?: DatabaseProvider;
191
298
  /**
192
299
  * Connection pool configuration
193
300
  * Overrides environment variables and defaults
@@ -299,6 +406,34 @@ interface ServerConfig {
299
406
  */
300
407
  headers?: number;
301
408
  };
409
+ /**
410
+ * Fetch (outbound HTTP) timeout configuration
411
+ * Controls Node.js undici global dispatcher timeouts for fetch() calls
412
+ * Applies to all outbound HTTP requests made via fetch() in this process
413
+ */
414
+ fetchTimeout?: {
415
+ /**
416
+ * TCP connection timeout in milliseconds
417
+ * Time to establish socket connection to upstream server
418
+ * @default 10000 (10 seconds)
419
+ * @env FETCH_CONNECT_TIMEOUT
420
+ */
421
+ connect?: number;
422
+ /**
423
+ * Response headers timeout in milliseconds
424
+ * Time to receive complete response headers after request sent
425
+ * @default 300000 (5 minutes)
426
+ * @env FETCH_HEADERS_TIMEOUT
427
+ */
428
+ headers?: number;
429
+ /**
430
+ * Body data timeout in milliseconds
431
+ * Maximum time between body data chunks from upstream server
432
+ * @default 300000 (5 minutes)
433
+ * @env FETCH_BODY_TIMEOUT
434
+ */
435
+ body?: number;
436
+ };
302
437
  /**
303
438
  * Graceful shutdown configuration
304
439
  * Controls server shutdown behavior during SIGTERM/SIGINT signals
@@ -306,9 +441,13 @@ interface ServerConfig {
306
441
  shutdown?: {
307
442
  /**
308
443
  * Graceful shutdown timeout in milliseconds
309
- * Maximum time to wait for ongoing requests and resource cleanup
310
- * After timeout, forces process termination
311
- * @default 30000 (30 seconds)
444
+ * Maximum time to wait for in-flight operations to drain and resource cleanup
445
+ * After timeout, forces process.exit() before k8s SIGKILL
446
+ *
447
+ * Formula: terminationGracePeriodSeconds - preStopSleep - safetyMargin
448
+ * Default: 300s - 5s - 15s = 280s
449
+ *
450
+ * @default 280000 (280 seconds)
312
451
  * @env SHUTDOWN_TIMEOUT
313
452
  */
314
453
  timeout?: number;
@@ -338,6 +477,27 @@ interface ServerConfig {
338
477
  */
339
478
  detailed?: boolean;
340
479
  };
480
+ /**
481
+ * Migration boot gate
482
+ *
483
+ * Before serving, the server compares the migrations shipped by installed
484
+ * function packages (and by `src/server/drizzle`) against what the database
485
+ * records as applied, and refuses to start when any are still pending —
486
+ * otherwise the mismatch surfaces only at request time, as an opaque 500.
487
+ *
488
+ * Skipped when the app initializes no database or ships no migrations.
489
+ */
490
+ migrations?: {
491
+ /**
492
+ * Start anyway when migrations are pending, logging a warning that lists
493
+ * them. The environment equivalent is `SPFN_ALLOW_PENDING_MIGRATIONS=true`;
494
+ * this field wins over it.
495
+ *
496
+ * @default false
497
+ * @env SPFN_ALLOW_PENDING_MIGRATIONS
498
+ */
499
+ allowPending?: boolean;
500
+ };
341
501
  /**
342
502
  * Infrastructure initialization control
343
503
  * Controls automatic initialization of database and Redis
@@ -567,6 +727,236 @@ declare function createServer(config?: ServerConfig): Promise<Hono>;
567
727
  */
568
728
  declare function startServer(config?: ServerConfig): Promise<ServerInstance>;
569
729
 
730
+ /**
731
+ * Shutdown Manager
732
+ *
733
+ * Manages graceful shutdown with drain behavior.
734
+ * All tracked operations must complete before shutdown proceeds.
735
+ *
736
+ * Features:
737
+ * - Hook registry: Multiple modules can register independent cleanup handlers
738
+ * - Operation tracking: Long-running tasks are awaited during shutdown (drain)
739
+ * - State management: isShuttingDown() for rejecting new work
740
+ */
741
+ interface ShutdownHookOptions {
742
+ /**
743
+ * Timeout for this hook in milliseconds
744
+ * If the hook exceeds this time, it is skipped and the next hook runs
745
+ * @default 10000 (10s)
746
+ */
747
+ timeout?: number;
748
+ /**
749
+ * Execution order (lower runs first)
750
+ * @default 100
751
+ */
752
+ order?: number;
753
+ }
754
+ declare class ShutdownManager {
755
+ private state;
756
+ private hooks;
757
+ private operations;
758
+ private operationCounter;
759
+ /**
760
+ * Register a shutdown hook
761
+ *
762
+ * Hooks run in order during shutdown, after all tracked operations drain.
763
+ * Each hook has its own timeout — failure does not block subsequent hooks.
764
+ *
765
+ * @example
766
+ * shutdown.onShutdown('ai-service', async () => {
767
+ * await aiService.cancelPending();
768
+ * }, { timeout: 30000, order: 10 });
769
+ */
770
+ onShutdown(name: string, handler: () => Promise<void>, options?: ShutdownHookOptions): void;
771
+ /**
772
+ * Track a long-running operation
773
+ *
774
+ * During shutdown (drain phase), the process waits for ALL tracked
775
+ * operations to complete before proceeding with cleanup.
776
+ *
777
+ * If shutdown has already started, the operation is rejected immediately.
778
+ *
779
+ * @returns The operation result (pass-through)
780
+ *
781
+ * @example
782
+ * const result = await shutdown.trackOperation(
783
+ * 'ai-generate',
784
+ * aiService.generate(prompt)
785
+ * );
786
+ */
787
+ trackOperation<T>(name: string, operation: Promise<T>): Promise<T>;
788
+ /**
789
+ * Whether the server is shutting down
790
+ *
791
+ * Use this to reject new work early (e.g., return 503 in route handlers).
792
+ */
793
+ isShuttingDown(): boolean;
794
+ /**
795
+ * Number of currently active tracked operations
796
+ */
797
+ getActiveOperationCount(): number;
798
+ /**
799
+ * Mark shutdown as started immediately
800
+ *
801
+ * Call this at the very beginning of the shutdown sequence so that:
802
+ * - Health check returns 503 right away
803
+ * - trackOperation() rejects new work
804
+ * - isShuttingDown() returns true
805
+ */
806
+ beginShutdown(): void;
807
+ /**
808
+ * Execute the full shutdown sequence
809
+ *
810
+ * 1. State → draining (reject new operations)
811
+ * 2. Wait for all tracked operations to complete (drain)
812
+ * 3. Run shutdown hooks in order
813
+ * 4. State → closed
814
+ *
815
+ * @param drainTimeout - Max time to wait for operations to drain (ms)
816
+ */
817
+ execute(drainTimeout: number): Promise<void>;
818
+ /**
819
+ * Wait for all tracked operations to complete, up to drainTimeout
820
+ */
821
+ private drain;
822
+ /**
823
+ * Execute registered shutdown hooks in order
824
+ */
825
+ private executeHooks;
826
+ }
827
+ /**
828
+ * Get the global ShutdownManager instance
829
+ *
830
+ * Available after server starts. Use this to register shutdown hooks
831
+ * or track long-running operations.
832
+ *
833
+ * @example
834
+ * import { getShutdownManager } from '@spfn/core/server';
835
+ *
836
+ * const shutdown = getShutdownManager();
837
+ *
838
+ * // Register cleanup
839
+ * shutdown.onShutdown('my-service', async () => {
840
+ * await myService.close();
841
+ * });
842
+ *
843
+ * // Track long operation
844
+ * await shutdown.trackOperation('ai-task', longRunningPromise);
845
+ */
846
+ declare function getShutdownManager(): ShutdownManager;
847
+
848
+ /**
849
+ * Serverless target for SPFN.
850
+ *
851
+ * Produces a listen-free, initialized Hono app for serverless platforms (Vercel,
852
+ * AWS Lambda, Cloudflare) — wrap the result with a hono platform adapter, e.g.
853
+ * `handle(app)` from `hono/vercel`.
854
+ *
855
+ * Unlike {@link startServer} (which serve()s a long-lived process and is the
856
+ * always-on / container path), this:
857
+ * - initializes the database in-handler (startServer welds DB init to serve()),
858
+ * - runs at most once per warm container (memoized),
859
+ * - disables the periodic DB health-check (pointless — and a timer leak — on
860
+ * frozen invocations),
861
+ * - does NOT start the in-process pg-boss worker (it cannot run on a serverless
862
+ * platform); enqueue still works, but nothing drains the queue here,
863
+ * - does NOT run seed/RBAC provisioning per cold start — that moves to a
864
+ * deploy-time step, see {@link provisionInfrastructure}.
865
+ *
866
+ * `startServer()` / `spfn start` (the always-on path) is unchanged by this module.
867
+ */
868
+
869
+ /**
870
+ * Build — once per warm container — the initialized, listen-free Hono app for a
871
+ * serverless platform. Wrap the result with the platform adapter:
872
+ *
873
+ * ```ts
874
+ * import { handle } from 'hono/vercel';
875
+ * import { createServerlessApp } from '@spfn/core/server';
876
+ * import serverConfig from '@/server/server.config';
877
+ *
878
+ * const handler = async (req: Request) => handle(await createServerlessApp(serverConfig))(req);
879
+ * export const GET = handler;
880
+ * export const POST = handler;
881
+ * ```
882
+ */
883
+ declare function createServerlessApp(config?: ServerConfig): Promise<Hono>;
884
+ /**
885
+ * Reset the memoized serverless app. Tests only.
886
+ */
887
+ declare function resetServerlessApp(): void;
888
+ /**
889
+ * Deploy-time provisioning — run ONCE per deploy, not per request.
890
+ *
891
+ * Initializes the database and runs the config's provisioning lifecycle hooks
892
+ * (`beforeInfrastructure` / `afterInfrastructure`, e.g. admin seeding + RBAC init).
893
+ * Intended for a build/deploy step (`spfn provision`), keeping per-cold-start work
894
+ * out of the serverless handler. On always-on targets this is equally useful: it
895
+ * avoids re-seeding on every pod restart / replica.
896
+ *
897
+ * Does NOT start the HTTP server, jobs worker, or health-check.
898
+ */
899
+ declare function provisionInfrastructure(config?: ServerConfig): Promise<void>;
900
+
901
+ /**
902
+ * Migration Boot Gate
903
+ *
904
+ * A server that boots with pending migrations passes its health check and then
905
+ * fails every request that touches a missing column, as an opaque 500. The gate
906
+ * moves that failure to boot, where it is one line to read and one command to
907
+ * fix.
908
+ *
909
+ * The check runs on the database the server already connected to. When no
910
+ * database was initialized — an app that uses none — there is nothing to check
911
+ * and boot proceeds. When the database is configured but unreachable,
912
+ * `initDatabase()` has already failed before the gate runs, so the gate never
913
+ * turns a database outage into a migration message.
914
+ */
915
+
916
+ type MigrationSnapshot =
917
+ /** Checked successfully — `status` carries per-target applied/pending counts. */
918
+ {
919
+ state: 'ok';
920
+ checkedAt: string;
921
+ status: MigrationStatus;
922
+ pending: number;
923
+ }
924
+ /** Nothing to check: no database in use, or no migrations shipped. */
925
+ | {
926
+ state: 'skipped';
927
+ checkedAt: string;
928
+ reason: string;
929
+ }
930
+ /** Could not check — distinct from "checked and pending". */
931
+ | {
932
+ state: 'unavailable';
933
+ checkedAt: string;
934
+ reason: string;
935
+ };
936
+ /**
937
+ * Thrown when the gate refuses a boot. Not an HTTP error — it never reaches a
938
+ * request.
939
+ */
940
+ declare class PendingMigrationsError extends Error {
941
+ readonly targets: string[];
942
+ constructor(message: string, targets: string[]);
943
+ }
944
+ /**
945
+ * Forget the cached snapshot — used by tests and after a manual migration run.
946
+ */
947
+ declare function resetMigrationSnapshot(): void;
948
+ /**
949
+ * Current migration snapshot, recomputed at most once per TTL.
950
+ *
951
+ * The boot gate seeds it, so the first health probe after startup costs nothing.
952
+ */
953
+ declare function getMigrationSnapshot(options?: {
954
+ cwd?: string;
955
+ db?: MigrationStatusDb;
956
+ force?: boolean;
957
+ ttlMs?: number;
958
+ }): Promise<MigrationSnapshot>;
959
+
570
960
  /**
571
961
  * Server Config Builder
572
962
  *
@@ -600,12 +990,47 @@ declare class ServerConfigBuilder {
600
990
  * Add named middlewares for route-level skip control
601
991
  */
602
992
  middlewares(middlewares: ServerConfig['middlewares']): this;
993
+ /**
994
+ * Configure proxy-guard (verify trusted-proxy signature + origin → clientType)
995
+ */
996
+ proxyGuard(proxyGuard: ServerConfig['proxyGuard']): this;
997
+ /**
998
+ * Configure rate limiting: an optional global default limiter plus the named
999
+ * policies that `rateLimitPolicy(name, fallback)` tags resolve against.
1000
+ *
1001
+ * @example
1002
+ * ```typescript
1003
+ * .rateLimit({
1004
+ * mode: 'on',
1005
+ * default: { limit: 100, windowMs: 60_000 },
1006
+ * policies: { 'auth-login': { limit: 5, windowMs: 60_000 } },
1007
+ * })
1008
+ * ```
1009
+ */
1010
+ rateLimit(rateLimit: ServerConfig['rateLimit']): this;
1011
+ /**
1012
+ * Configure the SSRF policy for outbound `safeFetch` calls (webhooks,
1013
+ * callbacks). Private/reserved IPs are blocked by default.
1014
+ *
1015
+ * @example
1016
+ * ```typescript
1017
+ * .outboundFetch({ allowHosts: ['hooks.slack.com'] })
1018
+ * ```
1019
+ */
1020
+ outboundFetch(outboundFetch: ServerConfig['outboundFetch']): this;
603
1021
  /**
604
1022
  * Register define-route based router
605
1023
  *
606
- * Automatically applies:
607
- * - Global middlewares from router._globalMiddlewares (via .use())
608
- * - Package routers from router._packageRouters (via .packages())
1024
+ * Router-level middleware (`.use()`) and package routers (`.packages()`) travel
1025
+ * with the router itself and are applied by `registerRoutes` when the routes are
1026
+ * mounted this method only records which router to mount.
1027
+ *
1028
+ * It deliberately does **not** copy `router._globalMiddlewares` into
1029
+ * `config.middlewares`: that copy used to make `registerRoutes` see the same
1030
+ * middleware twice (once from the config list, once from the router it was
1031
+ * handed) and attach both to every route. Middleware that verifies a JWT survives
1032
+ * running twice; middleware that consumes one-shot state — a nonce replay ledger —
1033
+ * rejects its own request the second time round.
609
1034
  *
610
1035
  * @example
611
1036
  * ```typescript
@@ -616,7 +1041,7 @@ declare class ServerConfigBuilder {
616
1041
  * .use([authMiddleware]);
617
1042
  *
618
1043
  * export default defineServerConfig()
619
- * .routes(appRouter) // middlewares auto-applied
1044
+ * .routes(appRouter) // .use() middleware applied once, at registration
620
1045
  * .build();
621
1046
  * ```
622
1047
  */
@@ -669,8 +1094,40 @@ declare class ServerConfigBuilder {
669
1094
  * .events(eventRouter, { path: '/sse' })
670
1095
  * ```
671
1096
  */
672
- events(router: EventRouterDef<any>, config?: SSEHandlerConfig & {
1097
+ events<TRouter extends EventRouterDef<any>>(router: TRouter, config?: Omit<SSEHandlerConfig, 'auth'> & {
1098
+ path?: string;
1099
+ auth?: SSEAuthConfig<TRouter>;
1100
+ }): this;
1101
+ /**
1102
+ * Register WebSocket router for bidirectional real-time communication
1103
+ *
1104
+ * Enables type-safe WebSocket connections with:
1105
+ * - Server→client event push (via defineEvent + emit)
1106
+ * - Client→server message handling (via messages in defineWSRouter)
1107
+ *
1108
+ * @example
1109
+ * ```typescript
1110
+ * // src/server/ws.ts
1111
+ * export const wsRouter = defineWSRouter({
1112
+ * events: { userUpdated, notification },
1113
+ * messages: {
1114
+ * ping: ({ ws }) => ws.send('pong', {}),
1115
+ * },
1116
+ * });
1117
+ *
1118
+ * // server.config.ts
1119
+ * export default defineServerConfig()
1120
+ * .websockets(wsRouter) // → WS /ws
1121
+ * .websockets(wsRouter, {
1122
+ * path: '/realtime', // custom path
1123
+ * auth: { enabled: true }, // token authentication
1124
+ * })
1125
+ * .build();
1126
+ * ```
1127
+ */
1128
+ websockets<TEvents extends Record<string, EventDef<any>>, TMessages extends WSMessageHandlers>(router: WSRouterDef<TEvents, TMessages>, config?: Omit<WSHandlerConfig, 'auth'> & {
673
1129
  path?: string;
1130
+ auth?: WSAuthConfig<WSRouterDef<TEvents, TMessages>>;
674
1131
  }): this;
675
1132
  /**
676
1133
  * Enable/disable debug mode
@@ -696,6 +1153,18 @@ declare class ServerConfigBuilder {
696
1153
  * Configure infrastructure initialization
697
1154
  */
698
1155
  infrastructure(infrastructure: ServerConfig['infrastructure']): this;
1156
+ /**
1157
+ * Configure the migration boot gate
1158
+ *
1159
+ * @example
1160
+ * ```typescript
1161
+ * // A harness that applies migrations itself, after the server is up
1162
+ * export default defineServerConfig()
1163
+ * .migrations({ allowPending: true })
1164
+ * .build();
1165
+ * ```
1166
+ */
1167
+ migrations(migrations: ServerConfig['migrations']): this;
699
1168
  /**
700
1169
  * Register workflow router for workflow orchestration
701
1170
  *
@@ -756,4 +1225,4 @@ declare class ServerConfigBuilder {
756
1225
  */
757
1226
  declare function defineServerConfig(): ServerConfigBuilder;
758
1227
 
759
- export { type AppFactory, type ServerConfig, type ServerInstance, createServer, defineServerConfig, loadEnvFiles, startServer };
1228
+ export { type AppFactory, type MigrationSnapshot, PendingMigrationsError, type ServerConfig, type ServerInstance, type ShutdownHookOptions, createServer, createServerlessApp, defineServerConfig, getMigrationSnapshot, getShutdownManager, loadEnvFiles, provisionInfrastructure, resetMigrationSnapshot, resetServerlessApp, startServer };