@spfn/core 0.2.0-beta.5 → 0.2.0-beta.50

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 (64) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +181 -1281
  3. package/dist/{boss-BO8ty33K.d.ts → boss-Cxqc-Oiw.d.ts} +37 -7
  4. package/dist/cache/index.js +32 -29
  5. package/dist/cache/index.js.map +1 -1
  6. package/dist/codegen/index.d.ts +55 -8
  7. package/dist/codegen/index.js +179 -5
  8. package/dist/codegen/index.js.map +1 -1
  9. package/dist/config/index.d.ts +168 -6
  10. package/dist/config/index.js +29 -5
  11. package/dist/config/index.js.map +1 -1
  12. package/dist/db/index.d.ts +218 -4
  13. package/dist/db/index.js +351 -57
  14. package/dist/db/index.js.map +1 -1
  15. package/dist/env/index.d.ts +2 -1
  16. package/dist/env/index.js +2 -1
  17. package/dist/env/index.js.map +1 -1
  18. package/dist/env/loader.d.ts +26 -19
  19. package/dist/env/loader.js +32 -25
  20. package/dist/env/loader.js.map +1 -1
  21. package/dist/errors/index.js.map +1 -1
  22. package/dist/event/index.d.ts +33 -3
  23. package/dist/event/index.js +17 -1
  24. package/dist/event/index.js.map +1 -1
  25. package/dist/event/sse/client.d.ts +42 -3
  26. package/dist/event/sse/client.js +128 -45
  27. package/dist/event/sse/client.js.map +1 -1
  28. package/dist/event/sse/index.d.ts +12 -5
  29. package/dist/event/sse/index.js +188 -20
  30. package/dist/event/sse/index.js.map +1 -1
  31. package/dist/event/ws/client.d.ts +59 -0
  32. package/dist/event/ws/client.js +273 -0
  33. package/dist/event/ws/client.js.map +1 -0
  34. package/dist/event/ws/index.d.ts +94 -0
  35. package/dist/event/ws/index.js +213 -0
  36. package/dist/event/ws/index.js.map +1 -0
  37. package/dist/job/index.d.ts +23 -8
  38. package/dist/job/index.js +154 -44
  39. package/dist/job/index.js.map +1 -1
  40. package/dist/logger/index.d.ts +5 -0
  41. package/dist/logger/index.js +14 -0
  42. package/dist/logger/index.js.map +1 -1
  43. package/dist/middleware/index.d.ts +23 -1
  44. package/dist/middleware/index.js +58 -5
  45. package/dist/middleware/index.js.map +1 -1
  46. package/dist/nextjs/index.d.ts +2 -2
  47. package/dist/nextjs/index.js +77 -31
  48. package/dist/nextjs/index.js.map +1 -1
  49. package/dist/nextjs/server.d.ts +44 -23
  50. package/dist/nextjs/server.js +83 -65
  51. package/dist/nextjs/server.js.map +1 -1
  52. package/dist/route/index.d.ts +158 -4
  53. package/dist/route/index.js +238 -22
  54. package/dist/route/index.js.map +1 -1
  55. package/dist/server/index.d.ts +308 -17
  56. package/dist/server/index.js +1128 -261
  57. package/dist/server/index.js.map +1 -1
  58. package/dist/{router-Di7ENoah.d.ts → token-manager-CyG7la3p.d.ts} +116 -1
  59. package/dist/{types-D_N_U-Py.d.ts → types-7Mhoxnnt.d.ts} +21 -1
  60. package/dist/types-C1jMLGwK.d.ts +257 -0
  61. package/dist/types-Cfj--lfr.d.ts +151 -0
  62. package/docs/file-upload.md +717 -0
  63. package/package.json +18 -5
  64. package/dist/types-B-e_f2dQ.d.ts +0 -121
@@ -1,26 +1,42 @@
1
+ export { loadEnv } from '../env/loader.js';
1
2
  import { MiddlewareHandler, Hono } 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-BO8ty33K.js';
6
- import { E as EventRouterDef } from '../router-Di7ENoah.js';
7
- import { S as SSEHandlerConfig } from '../types-B-e_f2dQ.js';
6
+ import { OnErrorContext } from '@spfn/core/middleware';
7
+ import { J as JobRouter, B as BossOptions } from '../boss-Cxqc-Oiw.js';
8
+ import { E as EventRouterDef } from '../token-manager-CyG7la3p.js';
9
+ import { S as SSEHandlerConfig, a as SSEAuthConfig } from '../types-C1jMLGwK.js';
10
+ import { W as WSRouterDef, a as WSHandlerConfig, b as WSMessageHandlers, c as WSAuthConfig } from '../types-Cfj--lfr.js';
8
11
  import '@sinclair/typebox';
9
12
  import 'pg-boss';
10
13
 
11
14
  /**
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
15
+ * @deprecated Use `loadEnv` from '@spfn/core/env/loader' instead.
16
+ * This module will be removed in the next major version.
17
+ */
18
+ /**
19
+ * @deprecated Use `loadEnv()` from '@spfn/core/env/loader' instead.
21
20
  */
22
21
  declare function loadEnvFiles(): void;
23
22
 
23
+ /**
24
+ * Workflow router interface for @spfn/core integration
25
+ *
26
+ * This is a minimal interface that avoids circular dependency with @spfn/workflow.
27
+ * The actual WorkflowRouter from @spfn/workflow implements this interface.
28
+ */
29
+ interface WorkflowRouterLike {
30
+ /**
31
+ * Initialize the workflow engine
32
+ * Called by server during infrastructure initialization
33
+ *
34
+ * @internal
35
+ */
36
+ _init: (db: any, options?: {
37
+ largeOutputThreshold?: number;
38
+ }) => void;
39
+ }
24
40
  /**
25
41
  * CORS configuration options - inferred from hono/cors
26
42
  */
@@ -60,6 +76,21 @@ interface ServerConfig {
60
76
  * Error handler (default: true)
61
77
  */
62
78
  errorHandler?: boolean;
79
+ /**
80
+ * Callback invoked when an error occurs (passed to ErrorHandler)
81
+ *
82
+ * Called asynchronously without blocking the response.
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * import { createErrorSlackNotifier } from '@spfn/notification/server';
87
+ *
88
+ * middleware: {
89
+ * onError: createErrorSlackNotifier({ minStatusCode: 500 }),
90
+ * }
91
+ * ```
92
+ */
93
+ onError?: (err: Error, context: OnErrorContext) => Promise<void> | void;
63
94
  };
64
95
  /**
65
96
  * Additional custom middleware
@@ -163,6 +194,30 @@ interface ServerConfig {
163
194
  */
164
195
  path?: string;
165
196
  };
197
+ /**
198
+ * WebSocket router for bidirectional real-time communication
199
+ *
200
+ * @example
201
+ * ```typescript
202
+ * import { defineWSRouter } from '@spfn/core/event/ws';
203
+ *
204
+ * export default defineServerConfig()
205
+ * .websockets(wsRouter) // → WS /ws
206
+ * .build();
207
+ * ```
208
+ */
209
+ websockets?: WSRouterDef<any, any>;
210
+ /**
211
+ * WebSocket configuration options
212
+ * Only used if websockets router is provided
213
+ */
214
+ websocketsConfig?: WSHandlerConfig & {
215
+ /**
216
+ * WebSocket endpoint path
217
+ * @default '/ws'
218
+ */
219
+ path?: string;
220
+ };
166
221
  /**
167
222
  * Enable debug mode (default: NODE_ENV === 'development')
168
223
  */
@@ -282,6 +337,34 @@ interface ServerConfig {
282
337
  */
283
338
  headers?: number;
284
339
  };
340
+ /**
341
+ * Fetch (outbound HTTP) timeout configuration
342
+ * Controls Node.js undici global dispatcher timeouts for fetch() calls
343
+ * Applies to all outbound HTTP requests made via fetch() in this process
344
+ */
345
+ fetchTimeout?: {
346
+ /**
347
+ * TCP connection timeout in milliseconds
348
+ * Time to establish socket connection to upstream server
349
+ * @default 10000 (10 seconds)
350
+ * @env FETCH_CONNECT_TIMEOUT
351
+ */
352
+ connect?: number;
353
+ /**
354
+ * Response headers timeout in milliseconds
355
+ * Time to receive complete response headers after request sent
356
+ * @default 300000 (5 minutes)
357
+ * @env FETCH_HEADERS_TIMEOUT
358
+ */
359
+ headers?: number;
360
+ /**
361
+ * Body data timeout in milliseconds
362
+ * Maximum time between body data chunks from upstream server
363
+ * @default 300000 (5 minutes)
364
+ * @env FETCH_BODY_TIMEOUT
365
+ */
366
+ body?: number;
367
+ };
285
368
  /**
286
369
  * Graceful shutdown configuration
287
370
  * Controls server shutdown behavior during SIGTERM/SIGINT signals
@@ -289,9 +372,13 @@ interface ServerConfig {
289
372
  shutdown?: {
290
373
  /**
291
374
  * Graceful shutdown timeout in milliseconds
292
- * Maximum time to wait for ongoing requests and resource cleanup
293
- * After timeout, forces process termination
294
- * @default 30000 (30 seconds)
375
+ * Maximum time to wait for in-flight operations to drain and resource cleanup
376
+ * After timeout, forces process.exit() before k8s SIGKILL
377
+ *
378
+ * Formula: terminationGracePeriodSeconds - preStopSleep - safetyMargin
379
+ * Default: 300s - 5s - 15s = 280s
380
+ *
381
+ * @default 280000 (280 seconds)
295
382
  * @env SHUTDOWN_TIMEOUT
296
383
  */
297
384
  timeout?: number;
@@ -338,6 +425,39 @@ interface ServerConfig {
338
425
  */
339
426
  redis?: boolean;
340
427
  };
428
+ /**
429
+ * Workflow router for workflow orchestration
430
+ *
431
+ * Automatically initializes the workflow engine after database is ready.
432
+ * Workflows are defined using @spfn/workflow package.
433
+ *
434
+ * @example
435
+ * ```typescript
436
+ * import { defineWorkflowRouter } from '@spfn/workflow';
437
+ *
438
+ * const workflowRouter = defineWorkflowRouter([
439
+ * provisionTenant,
440
+ * deprovisionTenant,
441
+ * ]);
442
+ *
443
+ * export default defineServerConfig()
444
+ * .workflows(workflowRouter)
445
+ * .build();
446
+ * ```
447
+ */
448
+ workflows?: WorkflowRouterLike;
449
+ /**
450
+ * Workflow engine configuration
451
+ * Only used if workflows router is provided
452
+ */
453
+ workflowsConfig?: {
454
+ /**
455
+ * Large output threshold in bytes
456
+ * Outputs larger than this will be stored in external storage
457
+ * @default 1024 * 1024 (1MB)
458
+ */
459
+ largeOutputThreshold?: number;
460
+ };
341
461
  /**
342
462
  * Server lifecycle hooks for custom infrastructure setup and management
343
463
  * Allows initialization of custom services and resources at different stages
@@ -517,6 +637,124 @@ declare function createServer(config?: ServerConfig): Promise<Hono>;
517
637
  */
518
638
  declare function startServer(config?: ServerConfig): Promise<ServerInstance>;
519
639
 
640
+ /**
641
+ * Shutdown Manager
642
+ *
643
+ * Manages graceful shutdown with drain behavior.
644
+ * All tracked operations must complete before shutdown proceeds.
645
+ *
646
+ * Features:
647
+ * - Hook registry: Multiple modules can register independent cleanup handlers
648
+ * - Operation tracking: Long-running tasks are awaited during shutdown (drain)
649
+ * - State management: isShuttingDown() for rejecting new work
650
+ */
651
+ interface ShutdownHookOptions {
652
+ /**
653
+ * Timeout for this hook in milliseconds
654
+ * If the hook exceeds this time, it is skipped and the next hook runs
655
+ * @default 10000 (10s)
656
+ */
657
+ timeout?: number;
658
+ /**
659
+ * Execution order (lower runs first)
660
+ * @default 100
661
+ */
662
+ order?: number;
663
+ }
664
+ declare class ShutdownManager {
665
+ private state;
666
+ private hooks;
667
+ private operations;
668
+ private operationCounter;
669
+ /**
670
+ * Register a shutdown hook
671
+ *
672
+ * Hooks run in order during shutdown, after all tracked operations drain.
673
+ * Each hook has its own timeout — failure does not block subsequent hooks.
674
+ *
675
+ * @example
676
+ * shutdown.onShutdown('ai-service', async () => {
677
+ * await aiService.cancelPending();
678
+ * }, { timeout: 30000, order: 10 });
679
+ */
680
+ onShutdown(name: string, handler: () => Promise<void>, options?: ShutdownHookOptions): void;
681
+ /**
682
+ * Track a long-running operation
683
+ *
684
+ * During shutdown (drain phase), the process waits for ALL tracked
685
+ * operations to complete before proceeding with cleanup.
686
+ *
687
+ * If shutdown has already started, the operation is rejected immediately.
688
+ *
689
+ * @returns The operation result (pass-through)
690
+ *
691
+ * @example
692
+ * const result = await shutdown.trackOperation(
693
+ * 'ai-generate',
694
+ * aiService.generate(prompt)
695
+ * );
696
+ */
697
+ trackOperation<T>(name: string, operation: Promise<T>): Promise<T>;
698
+ /**
699
+ * Whether the server is shutting down
700
+ *
701
+ * Use this to reject new work early (e.g., return 503 in route handlers).
702
+ */
703
+ isShuttingDown(): boolean;
704
+ /**
705
+ * Number of currently active tracked operations
706
+ */
707
+ getActiveOperationCount(): number;
708
+ /**
709
+ * Mark shutdown as started immediately
710
+ *
711
+ * Call this at the very beginning of the shutdown sequence so that:
712
+ * - Health check returns 503 right away
713
+ * - trackOperation() rejects new work
714
+ * - isShuttingDown() returns true
715
+ */
716
+ beginShutdown(): void;
717
+ /**
718
+ * Execute the full shutdown sequence
719
+ *
720
+ * 1. State → draining (reject new operations)
721
+ * 2. Wait for all tracked operations to complete (drain)
722
+ * 3. Run shutdown hooks in order
723
+ * 4. State → closed
724
+ *
725
+ * @param drainTimeout - Max time to wait for operations to drain (ms)
726
+ */
727
+ execute(drainTimeout: number): Promise<void>;
728
+ /**
729
+ * Wait for all tracked operations to complete, up to drainTimeout
730
+ */
731
+ private drain;
732
+ /**
733
+ * Execute registered shutdown hooks in order
734
+ */
735
+ private executeHooks;
736
+ }
737
+ /**
738
+ * Get the global ShutdownManager instance
739
+ *
740
+ * Available after server starts. Use this to register shutdown hooks
741
+ * or track long-running operations.
742
+ *
743
+ * @example
744
+ * import { getShutdownManager } from '@spfn/core/server';
745
+ *
746
+ * const shutdown = getShutdownManager();
747
+ *
748
+ * // Register cleanup
749
+ * shutdown.onShutdown('my-service', async () => {
750
+ * await myService.close();
751
+ * });
752
+ *
753
+ * // Track long operation
754
+ * await shutdown.trackOperation('ai-task', longRunningPromise);
755
+ */
756
+ declare function getShutdownManager(): ShutdownManager;
757
+
520
758
  /**
521
759
  * Server Config Builder
522
760
  *
@@ -619,8 +857,40 @@ declare class ServerConfigBuilder {
619
857
  * .events(eventRouter, { path: '/sse' })
620
858
  * ```
621
859
  */
622
- events(router: EventRouterDef<any>, config?: SSEHandlerConfig & {
860
+ events<TRouter extends EventRouterDef<any>>(router: TRouter, config?: Omit<SSEHandlerConfig, 'auth'> & {
861
+ path?: string;
862
+ auth?: SSEAuthConfig<TRouter>;
863
+ }): this;
864
+ /**
865
+ * Register WebSocket router for bidirectional real-time communication
866
+ *
867
+ * Enables type-safe WebSocket connections with:
868
+ * - Server→client event push (via defineEvent + emit)
869
+ * - Client→server message handling (via messages in defineWSRouter)
870
+ *
871
+ * @example
872
+ * ```typescript
873
+ * // src/server/ws.ts
874
+ * export const wsRouter = defineWSRouter({
875
+ * events: { userUpdated, notification },
876
+ * messages: {
877
+ * ping: ({ ws }) => ws.send('pong', {}),
878
+ * },
879
+ * });
880
+ *
881
+ * // server.config.ts
882
+ * export default defineServerConfig()
883
+ * .websockets(wsRouter) // → WS /ws
884
+ * .websockets(wsRouter, {
885
+ * path: '/realtime', // custom path
886
+ * auth: { enabled: true }, // token authentication
887
+ * })
888
+ * .build();
889
+ * ```
890
+ */
891
+ websockets<TEvents extends Record<string, any>, TMessages extends WSMessageHandlers, TRouter extends WSRouterDef<TEvents, TMessages>>(router: TRouter, config?: Omit<WSHandlerConfig, 'auth'> & {
623
892
  path?: string;
893
+ auth?: WSAuthConfig<TRouter>;
624
894
  }): this;
625
895
  /**
626
896
  * Enable/disable debug mode
@@ -646,6 +916,27 @@ declare class ServerConfigBuilder {
646
916
  * Configure infrastructure initialization
647
917
  */
648
918
  infrastructure(infrastructure: ServerConfig['infrastructure']): this;
919
+ /**
920
+ * Register workflow router for workflow orchestration
921
+ *
922
+ * Automatically initializes the workflow engine after database is ready.
923
+ *
924
+ * @example
925
+ * ```typescript
926
+ * import { defineWorkflowRouter } from '@spfn/workflow';
927
+ *
928
+ * const workflowRouter = defineWorkflowRouter([
929
+ * provisionTenant,
930
+ * deprovisionTenant,
931
+ * ]);
932
+ *
933
+ * export default defineServerConfig()
934
+ * .routes(appRouter)
935
+ * .workflows(workflowRouter)
936
+ * .build();
937
+ * ```
938
+ */
939
+ workflows(router: ServerConfig['workflows'], config?: ServerConfig['workflowsConfig']): this;
649
940
  /**
650
941
  * Configure lifecycle hooks
651
942
  * Can be called multiple times - hooks will be executed in registration order
@@ -685,4 +976,4 @@ declare class ServerConfigBuilder {
685
976
  */
686
977
  declare function defineServerConfig(): ServerConfigBuilder;
687
978
 
688
- export { type AppFactory, type ServerConfig, type ServerInstance, createServer, defineServerConfig, loadEnvFiles, startServer };
979
+ export { type AppFactory, type ServerConfig, type ServerInstance, type ShutdownHookOptions, createServer, defineServerConfig, getShutdownManager, loadEnvFiles, startServer };