@spfn/core 0.2.0-beta.2 → 0.2.0-beta.21

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/README.md +262 -1092
  2. package/dist/{boss-D-fGtVgM.d.ts → boss-DI1r4kTS.d.ts} +68 -11
  3. package/dist/codegen/index.d.ts +55 -8
  4. package/dist/codegen/index.js +179 -5
  5. package/dist/codegen/index.js.map +1 -1
  6. package/dist/config/index.d.ts +204 -6
  7. package/dist/config/index.js +44 -11
  8. package/dist/config/index.js.map +1 -1
  9. package/dist/db/index.d.ts +13 -0
  10. package/dist/db/index.js +92 -33
  11. package/dist/db/index.js.map +1 -1
  12. package/dist/env/index.d.ts +83 -3
  13. package/dist/env/index.js +83 -15
  14. package/dist/env/index.js.map +1 -1
  15. package/dist/env/loader.d.ts +95 -0
  16. package/dist/env/loader.js +78 -0
  17. package/dist/env/loader.js.map +1 -0
  18. package/dist/event/index.d.ts +29 -70
  19. package/dist/event/index.js +15 -1
  20. package/dist/event/index.js.map +1 -1
  21. package/dist/event/sse/client.d.ts +157 -0
  22. package/dist/event/sse/client.js +169 -0
  23. package/dist/event/sse/client.js.map +1 -0
  24. package/dist/event/sse/index.d.ts +46 -0
  25. package/dist/event/sse/index.js +205 -0
  26. package/dist/event/sse/index.js.map +1 -0
  27. package/dist/job/index.d.ts +54 -8
  28. package/dist/job/index.js +61 -12
  29. package/dist/job/index.js.map +1 -1
  30. package/dist/middleware/index.d.ts +124 -11
  31. package/dist/middleware/index.js +41 -7
  32. package/dist/middleware/index.js.map +1 -1
  33. package/dist/nextjs/index.d.ts +2 -2
  34. package/dist/nextjs/index.js +37 -5
  35. package/dist/nextjs/index.js.map +1 -1
  36. package/dist/nextjs/server.d.ts +45 -24
  37. package/dist/nextjs/server.js +87 -66
  38. package/dist/nextjs/server.js.map +1 -1
  39. package/dist/route/index.d.ts +207 -14
  40. package/dist/route/index.js +304 -31
  41. package/dist/route/index.js.map +1 -1
  42. package/dist/route/types.d.ts +2 -31
  43. package/dist/router-Di7ENoah.d.ts +151 -0
  44. package/dist/server/index.d.ts +321 -10
  45. package/dist/server/index.js +798 -189
  46. package/dist/server/index.js.map +1 -1
  47. package/dist/{types-DRG2XMTR.d.ts → types-7Mhoxnnt.d.ts} +97 -4
  48. package/dist/types-DHQMQlcb.d.ts +305 -0
  49. package/docs/cache.md +133 -0
  50. package/docs/codegen.md +74 -0
  51. package/docs/database.md +346 -0
  52. package/docs/entity.md +539 -0
  53. package/docs/env.md +499 -0
  54. package/docs/errors.md +319 -0
  55. package/docs/event.md +432 -0
  56. package/docs/file-upload.md +717 -0
  57. package/docs/job.md +131 -0
  58. package/docs/logger.md +108 -0
  59. package/docs/middleware.md +337 -0
  60. package/docs/nextjs.md +247 -0
  61. package/docs/repository.md +496 -0
  62. package/docs/route.md +497 -0
  63. package/docs/server.md +429 -0
  64. package/package.json +19 -3
@@ -1,13 +1,41 @@
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 BossConfig } from '../boss-D-fGtVgM.js';
6
+ import { OnErrorContext } from '@spfn/core/middleware';
7
+ import { J as JobRouter, B as BossOptions } from '../boss-DI1r4kTS.js';
8
+ import { E as EventRouterDef } from '../router-Di7ENoah.js';
9
+ import { S as SSEHandlerConfig, a as SSEAuthConfig } from '../types-DHQMQlcb.js';
6
10
  import '@sinclair/typebox';
7
11
  import 'pg-boss';
8
12
 
13
+ /**
14
+ * @deprecated Use `loadEnv` from '@spfn/core/env/loader' instead.
15
+ * This module will be removed in the next major version.
16
+ */
17
+ /**
18
+ * @deprecated Use `loadEnv()` from '@spfn/core/env/loader' instead.
19
+ */
9
20
  declare function loadEnvFiles(): void;
10
21
 
22
+ /**
23
+ * Workflow router interface for @spfn/core integration
24
+ *
25
+ * This is a minimal interface that avoids circular dependency with @spfn/workflow.
26
+ * The actual WorkflowRouter from @spfn/workflow implements this interface.
27
+ */
28
+ interface WorkflowRouterLike {
29
+ /**
30
+ * Initialize the workflow engine
31
+ * Called by server during infrastructure initialization
32
+ *
33
+ * @internal
34
+ */
35
+ _init: (db: any, options?: {
36
+ largeOutputThreshold?: number;
37
+ }) => void;
38
+ }
11
39
  /**
12
40
  * CORS configuration options - inferred from hono/cors
13
41
  */
@@ -47,6 +75,21 @@ interface ServerConfig {
47
75
  * Error handler (default: true)
48
76
  */
49
77
  errorHandler?: boolean;
78
+ /**
79
+ * Callback invoked when an error occurs (passed to ErrorHandler)
80
+ *
81
+ * Called asynchronously without blocking the response.
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * import { createErrorSlackNotifier } from '@spfn/notification/server';
86
+ *
87
+ * middleware: {
88
+ * onError: createErrorSlackNotifier({ minStatusCode: 500 }),
89
+ * }
90
+ * ```
91
+ */
92
+ onError?: (err: Error, context: OnErrorContext) => Promise<void> | void;
50
93
  };
51
94
  /**
52
95
  * Additional custom middleware
@@ -117,7 +160,39 @@ interface ServerConfig {
117
160
  * pg-boss configuration options
118
161
  * Only used if jobs router is provided
119
162
  */
120
- jobsConfig?: Omit<BossConfig, 'connectionString'>;
163
+ jobsConfig?: Omit<BossOptions, 'connectionString'>;
164
+ /**
165
+ * Event router for SSE (Server-Sent Events) subscription
166
+ * Enables real-time event streaming to frontend clients
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * import { defineEvent, defineEventRouter } from '@spfn/core/event';
171
+ *
172
+ * const userCreated = defineEvent('user.created', Type.Object({
173
+ * userId: Type.String(),
174
+ * }));
175
+ *
176
+ * const eventRouter = defineEventRouter({ userCreated });
177
+ *
178
+ * export default defineServerConfig()
179
+ * .routes(appRouter)
180
+ * .events(eventRouter) // → GET /events/stream
181
+ * .build();
182
+ * ```
183
+ */
184
+ events?: EventRouterDef<any>;
185
+ /**
186
+ * SSE configuration options
187
+ * Only used if events router is provided
188
+ */
189
+ eventsConfig?: SSEHandlerConfig & {
190
+ /**
191
+ * SSE endpoint path
192
+ * @default '/events/stream'
193
+ */
194
+ path?: string;
195
+ };
121
196
  /**
122
197
  * Enable debug mode (default: NODE_ENV === 'development')
123
198
  */
@@ -237,6 +312,34 @@ interface ServerConfig {
237
312
  */
238
313
  headers?: number;
239
314
  };
315
+ /**
316
+ * Fetch (outbound HTTP) timeout configuration
317
+ * Controls Node.js undici global dispatcher timeouts for fetch() calls
318
+ * Applies to all outbound HTTP requests made via fetch() in this process
319
+ */
320
+ fetchTimeout?: {
321
+ /**
322
+ * TCP connection timeout in milliseconds
323
+ * Time to establish socket connection to upstream server
324
+ * @default 10000 (10 seconds)
325
+ * @env FETCH_CONNECT_TIMEOUT
326
+ */
327
+ connect?: number;
328
+ /**
329
+ * Response headers timeout in milliseconds
330
+ * Time to receive complete response headers after request sent
331
+ * @default 300000 (5 minutes)
332
+ * @env FETCH_HEADERS_TIMEOUT
333
+ */
334
+ headers?: number;
335
+ /**
336
+ * Body data timeout in milliseconds
337
+ * Maximum time between body data chunks from upstream server
338
+ * @default 300000 (5 minutes)
339
+ * @env FETCH_BODY_TIMEOUT
340
+ */
341
+ body?: number;
342
+ };
240
343
  /**
241
344
  * Graceful shutdown configuration
242
345
  * Controls server shutdown behavior during SIGTERM/SIGINT signals
@@ -244,9 +347,13 @@ interface ServerConfig {
244
347
  shutdown?: {
245
348
  /**
246
349
  * Graceful shutdown timeout in milliseconds
247
- * Maximum time to wait for ongoing requests and resource cleanup
248
- * After timeout, forces process termination
249
- * @default 30000 (30 seconds)
350
+ * Maximum time to wait for in-flight operations to drain and resource cleanup
351
+ * After timeout, forces process.exit() before k8s SIGKILL
352
+ *
353
+ * Formula: terminationGracePeriodSeconds - preStopSleep - safetyMargin
354
+ * Default: 300s - 5s - 15s = 280s
355
+ *
356
+ * @default 280000 (280 seconds)
250
357
  * @env SHUTDOWN_TIMEOUT
251
358
  */
252
359
  timeout?: number;
@@ -293,6 +400,39 @@ interface ServerConfig {
293
400
  */
294
401
  redis?: boolean;
295
402
  };
403
+ /**
404
+ * Workflow router for workflow orchestration
405
+ *
406
+ * Automatically initializes the workflow engine after database is ready.
407
+ * Workflows are defined using @spfn/workflow package.
408
+ *
409
+ * @example
410
+ * ```typescript
411
+ * import { defineWorkflowRouter } from '@spfn/workflow';
412
+ *
413
+ * const workflowRouter = defineWorkflowRouter([
414
+ * provisionTenant,
415
+ * deprovisionTenant,
416
+ * ]);
417
+ *
418
+ * export default defineServerConfig()
419
+ * .workflows(workflowRouter)
420
+ * .build();
421
+ * ```
422
+ */
423
+ workflows?: WorkflowRouterLike;
424
+ /**
425
+ * Workflow engine configuration
426
+ * Only used if workflows router is provided
427
+ */
428
+ workflowsConfig?: {
429
+ /**
430
+ * Large output threshold in bytes
431
+ * Outputs larger than this will be stored in external storage
432
+ * @default 1024 * 1024 (1MB)
433
+ */
434
+ largeOutputThreshold?: number;
435
+ };
296
436
  /**
297
437
  * Server lifecycle hooks for custom infrastructure setup and management
298
438
  * Allows initialization of custom services and resources at different stages
@@ -472,6 +612,124 @@ declare function createServer(config?: ServerConfig): Promise<Hono>;
472
612
  */
473
613
  declare function startServer(config?: ServerConfig): Promise<ServerInstance>;
474
614
 
615
+ /**
616
+ * Shutdown Manager
617
+ *
618
+ * Manages graceful shutdown with drain behavior.
619
+ * All tracked operations must complete before shutdown proceeds.
620
+ *
621
+ * Features:
622
+ * - Hook registry: Multiple modules can register independent cleanup handlers
623
+ * - Operation tracking: Long-running tasks are awaited during shutdown (drain)
624
+ * - State management: isShuttingDown() for rejecting new work
625
+ */
626
+ interface ShutdownHookOptions {
627
+ /**
628
+ * Timeout for this hook in milliseconds
629
+ * If the hook exceeds this time, it is skipped and the next hook runs
630
+ * @default 10000 (10s)
631
+ */
632
+ timeout?: number;
633
+ /**
634
+ * Execution order (lower runs first)
635
+ * @default 100
636
+ */
637
+ order?: number;
638
+ }
639
+ declare class ShutdownManager {
640
+ private state;
641
+ private hooks;
642
+ private operations;
643
+ private operationCounter;
644
+ /**
645
+ * Register a shutdown hook
646
+ *
647
+ * Hooks run in order during shutdown, after all tracked operations drain.
648
+ * Each hook has its own timeout — failure does not block subsequent hooks.
649
+ *
650
+ * @example
651
+ * shutdown.onShutdown('ai-service', async () => {
652
+ * await aiService.cancelPending();
653
+ * }, { timeout: 30000, order: 10 });
654
+ */
655
+ onShutdown(name: string, handler: () => Promise<void>, options?: ShutdownHookOptions): void;
656
+ /**
657
+ * Track a long-running operation
658
+ *
659
+ * During shutdown (drain phase), the process waits for ALL tracked
660
+ * operations to complete before proceeding with cleanup.
661
+ *
662
+ * If shutdown has already started, the operation is rejected immediately.
663
+ *
664
+ * @returns The operation result (pass-through)
665
+ *
666
+ * @example
667
+ * const result = await shutdown.trackOperation(
668
+ * 'ai-generate',
669
+ * aiService.generate(prompt)
670
+ * );
671
+ */
672
+ trackOperation<T>(name: string, operation: Promise<T>): Promise<T>;
673
+ /**
674
+ * Whether the server is shutting down
675
+ *
676
+ * Use this to reject new work early (e.g., return 503 in route handlers).
677
+ */
678
+ isShuttingDown(): boolean;
679
+ /**
680
+ * Number of currently active tracked operations
681
+ */
682
+ getActiveOperationCount(): number;
683
+ /**
684
+ * Mark shutdown as started immediately
685
+ *
686
+ * Call this at the very beginning of the shutdown sequence so that:
687
+ * - Health check returns 503 right away
688
+ * - trackOperation() rejects new work
689
+ * - isShuttingDown() returns true
690
+ */
691
+ beginShutdown(): void;
692
+ /**
693
+ * Execute the full shutdown sequence
694
+ *
695
+ * 1. State → draining (reject new operations)
696
+ * 2. Wait for all tracked operations to complete (drain)
697
+ * 3. Run shutdown hooks in order
698
+ * 4. State → closed
699
+ *
700
+ * @param drainTimeout - Max time to wait for operations to drain (ms)
701
+ */
702
+ execute(drainTimeout: number): Promise<void>;
703
+ /**
704
+ * Wait for all tracked operations to complete, up to drainTimeout
705
+ */
706
+ private drain;
707
+ /**
708
+ * Execute registered shutdown hooks in order
709
+ */
710
+ private executeHooks;
711
+ }
712
+ /**
713
+ * Get the global ShutdownManager instance
714
+ *
715
+ * Available after server starts. Use this to register shutdown hooks
716
+ * or track long-running operations.
717
+ *
718
+ * @example
719
+ * import { getShutdownManager } from '@spfn/core/server';
720
+ *
721
+ * const shutdown = getShutdownManager();
722
+ *
723
+ * // Register cleanup
724
+ * shutdown.onShutdown('my-service', async () => {
725
+ * await myService.close();
726
+ * });
727
+ *
728
+ * // Track long operation
729
+ * await shutdown.trackOperation('ai-task', longRunningPromise);
730
+ */
731
+ declare function getShutdownManager(): ShutdownManager;
732
+
475
733
  /**
476
734
  * Server Config Builder
477
735
  *
@@ -545,7 +803,39 @@ declare class ServerConfigBuilder {
545
803
  * .build();
546
804
  * ```
547
805
  */
548
- jobs(router: JobRouter<any>, config?: Omit<BossConfig, 'connectionString'>): this;
806
+ jobs(router: JobRouter<any>, config?: Omit<BossOptions, 'connectionString'>): this;
807
+ /**
808
+ * Register event router for SSE (Server-Sent Events)
809
+ *
810
+ * Enables real-time event streaming to frontend clients.
811
+ * Events defined with defineEvent() can be subscribed by:
812
+ * - Backend: .subscribe() for internal handlers
813
+ * - Jobs: .on(event) for background processing
814
+ * - Frontend: SSE stream for real-time updates
815
+ *
816
+ * @example
817
+ * ```typescript
818
+ * import { defineEvent, defineEventRouter } from '@spfn/core/event';
819
+ *
820
+ * const userCreated = defineEvent('user.created', Type.Object({
821
+ * userId: Type.String(),
822
+ * }));
823
+ *
824
+ * const eventRouter = defineEventRouter({ userCreated });
825
+ *
826
+ * export default defineServerConfig()
827
+ * .routes(appRouter)
828
+ * .events(eventRouter) // → GET /events/stream
829
+ * .build();
830
+ *
831
+ * // Custom path
832
+ * .events(eventRouter, { path: '/sse' })
833
+ * ```
834
+ */
835
+ events<TRouter extends EventRouterDef<any>>(router: TRouter, config?: Omit<SSEHandlerConfig, 'auth'> & {
836
+ path?: string;
837
+ auth?: SSEAuthConfig<TRouter>;
838
+ }): this;
549
839
  /**
550
840
  * Enable/disable debug mode
551
841
  */
@@ -570,6 +860,27 @@ declare class ServerConfigBuilder {
570
860
  * Configure infrastructure initialization
571
861
  */
572
862
  infrastructure(infrastructure: ServerConfig['infrastructure']): this;
863
+ /**
864
+ * Register workflow router for workflow orchestration
865
+ *
866
+ * Automatically initializes the workflow engine after database is ready.
867
+ *
868
+ * @example
869
+ * ```typescript
870
+ * import { defineWorkflowRouter } from '@spfn/workflow';
871
+ *
872
+ * const workflowRouter = defineWorkflowRouter([
873
+ * provisionTenant,
874
+ * deprovisionTenant,
875
+ * ]);
876
+ *
877
+ * export default defineServerConfig()
878
+ * .routes(appRouter)
879
+ * .workflows(workflowRouter)
880
+ * .build();
881
+ * ```
882
+ */
883
+ workflows(router: ServerConfig['workflows'], config?: ServerConfig['workflowsConfig']): this;
573
884
  /**
574
885
  * Configure lifecycle hooks
575
886
  * Can be called multiple times - hooks will be executed in registration order
@@ -592,10 +903,10 @@ declare class ServerConfigBuilder {
592
903
  *
593
904
  * const appRouter = defineRouter({
594
905
  * getUser: route.get('/users/:id')
595
- * .input(Type.Object({ id: Type.String() }))
906
+ * .input({ params: Type.Object({ id: Type.String() }) })
596
907
  * .handler(async (c) => {
597
- * const { id } = await c.data();
598
- * return c.success({ id, name: 'John' });
908
+ * const { params } = await c.data();
909
+ * return { id: params.id, name: 'John' };
599
910
  * }),
600
911
  * });
601
912
  *
@@ -609,4 +920,4 @@ declare class ServerConfigBuilder {
609
920
  */
610
921
  declare function defineServerConfig(): ServerConfigBuilder;
611
922
 
612
- export { type AppFactory, type ServerConfig, type ServerInstance, createServer, defineServerConfig, loadEnvFiles, startServer };
923
+ export { type AppFactory, type ServerConfig, type ServerInstance, type ShutdownHookOptions, createServer, defineServerConfig, getShutdownManager, loadEnvFiles, startServer };