@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.
- package/LICENSE +1 -1
- package/README.md +467 -305
- package/dist/authz/index.d.ts +34 -0
- package/dist/authz/index.js +810 -0
- package/dist/authz/index.js.map +1 -0
- package/dist/{boss-DI1r4kTS.d.ts → boss-D16fO2oG.d.ts} +41 -1
- package/dist/cache/index.js +42 -30
- package/dist/cache/index.js.map +1 -1
- package/dist/codegen/index.d.ts +121 -13
- package/dist/codegen/index.js +212 -15
- package/dist/codegen/index.js.map +1 -1
- package/dist/config/index.d.ts +615 -6
- package/dist/config/index.js +124 -5
- package/dist/config/index.js.map +1 -1
- package/dist/contract/index.d.ts +220 -0
- package/dist/contract/index.js +558 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/db/index.d.ts +528 -85
- package/dist/db/index.js +831 -122
- package/dist/db/index.js.map +1 -1
- package/dist/define-middleware-DfDP39Nq.d.ts +167 -0
- package/dist/env/index.d.ts +26 -2
- package/dist/env/index.js +15 -5
- package/dist/env/index.js.map +1 -1
- package/dist/env/loader.d.ts +26 -19
- package/dist/env/loader.js +32 -25
- package/dist/env/loader.js.map +1 -1
- package/dist/errors/index.d.ts +10 -0
- package/dist/errors/index.js +418 -5
- package/dist/errors/index.js.map +1 -1
- package/dist/event/index.d.ts +33 -3
- package/dist/event/index.js +24 -3
- package/dist/event/index.js.map +1 -1
- package/dist/event/sse/client.d.ts +42 -3
- package/dist/event/sse/client.js +128 -45
- package/dist/event/sse/client.js.map +1 -1
- package/dist/event/sse/index.d.ts +12 -5
- package/dist/event/sse/index.js +280 -32
- package/dist/event/sse/index.js.map +1 -1
- package/dist/event/ws/client.d.ts +59 -0
- package/dist/event/ws/client.js +273 -0
- package/dist/event/ws/client.js.map +1 -0
- package/dist/event/ws/index.d.ts +94 -0
- package/dist/event/ws/index.js +272 -0
- package/dist/event/ws/index.js.map +1 -0
- package/dist/job/index.d.ts +2 -2
- package/dist/job/index.js +155 -42
- package/dist/job/index.js.map +1 -1
- package/dist/logger/index.d.ts +5 -0
- package/dist/logger/index.js +14 -0
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/index.d.ts +347 -9
- package/dist/middleware/index.js +1462 -15
- package/dist/middleware/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +2 -2
- package/dist/nextjs/index.js +42 -28
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/server.d.ts +35 -51
- package/dist/nextjs/server.js +126 -60
- package/dist/nextjs/server.js.map +1 -1
- package/dist/ops/index.d.ts +152 -0
- package/dist/ops/index.js +500 -0
- package/dist/ops/index.js.map +1 -0
- package/dist/route/index.d.ts +8 -694
- package/dist/route/index.js +111 -22
- package/dist/route/index.js.map +1 -1
- package/dist/router-Qbssr11H.d.ts +676 -0
- package/dist/security/index.d.ts +83 -0
- package/dist/security/index.js +173 -0
- package/dist/security/index.js.map +1 -0
- package/dist/server/index.d.ts +491 -22
- package/dist/server/index.js +1887 -308
- package/dist/server/index.js.map +1 -1
- package/dist/token-manager-BT5EnUAR.d.ts +278 -0
- package/dist/types-2AbaW4Ie.d.ts +205 -0
- package/dist/{types-BOPTApC2.d.ts → types-9oszaJqp.d.ts} +7 -2
- package/dist/types-D1c57Ko-.d.ts +115 -0
- package/dist/types-ZQODsBft.d.ts +282 -0
- package/package.json +244 -208
- package/dist/router-Di7ENoah.d.ts +0 -151
- package/dist/types-B-e_f2dQ.d.ts +0 -121
- package/docs/cache.md +0 -133
- package/docs/codegen.md +0 -74
- package/docs/database.md +0 -346
- package/docs/entity.md +0 -539
- package/docs/env.md +0 -477
- package/docs/errors.md +0 -319
- package/docs/event.md +0 -116
- package/docs/job.md +0 -131
- package/docs/logger.md +0 -108
- package/docs/middleware.md +0 -337
- package/docs/nextjs.md +0 -241
- package/docs/repository.md +0 -496
- package/docs/route.md +0 -497
- package/docs/server.md +0 -307
package/dist/server/index.d.ts
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
|
|
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 {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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
|
-
*
|
|
13
|
-
*
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
*
|
|
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
|
|
310
|
-
* After timeout, forces process
|
|
311
|
-
*
|
|
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
|
-
*
|
|
607
|
-
*
|
|
608
|
-
*
|
|
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) //
|
|
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
|
|
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 };
|