@spfn/core 0.2.0-beta.4 → 0.2.0-beta.42

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 (68) hide show
  1. package/README.md +260 -1175
  2. package/dist/{boss-BO8ty33K.d.ts → boss-DI1r4kTS.d.ts} +24 -7
  3. package/dist/cache/index.js +32 -29
  4. package/dist/cache/index.js.map +1 -1
  5. package/dist/codegen/index.d.ts +55 -8
  6. package/dist/codegen/index.js +179 -5
  7. package/dist/codegen/index.js.map +1 -1
  8. package/dist/config/index.d.ts +168 -6
  9. package/dist/config/index.js +29 -5
  10. package/dist/config/index.js.map +1 -1
  11. package/dist/db/index.d.ts +128 -4
  12. package/dist/db/index.js +177 -50
  13. package/dist/db/index.js.map +1 -1
  14. package/dist/env/index.d.ts +55 -1
  15. package/dist/env/index.js +71 -3
  16. package/dist/env/index.js.map +1 -1
  17. package/dist/env/loader.d.ts +27 -19
  18. package/dist/env/loader.js +33 -25
  19. package/dist/env/loader.js.map +1 -1
  20. package/dist/event/index.d.ts +27 -1
  21. package/dist/event/index.js +6 -1
  22. package/dist/event/index.js.map +1 -1
  23. package/dist/event/sse/client.d.ts +77 -2
  24. package/dist/event/sse/client.js +87 -24
  25. package/dist/event/sse/client.js.map +1 -1
  26. package/dist/event/sse/index.d.ts +10 -4
  27. package/dist/event/sse/index.js +158 -12
  28. package/dist/event/sse/index.js.map +1 -1
  29. package/dist/job/index.d.ts +23 -8
  30. package/dist/job/index.js +96 -20
  31. package/dist/job/index.js.map +1 -1
  32. package/dist/logger/index.d.ts +5 -0
  33. package/dist/logger/index.js +14 -0
  34. package/dist/logger/index.js.map +1 -1
  35. package/dist/middleware/index.d.ts +23 -1
  36. package/dist/middleware/index.js +58 -5
  37. package/dist/middleware/index.js.map +1 -1
  38. package/dist/nextjs/index.d.ts +2 -2
  39. package/dist/nextjs/index.js +77 -31
  40. package/dist/nextjs/index.js.map +1 -1
  41. package/dist/nextjs/server.d.ts +44 -23
  42. package/dist/nextjs/server.js +83 -65
  43. package/dist/nextjs/server.js.map +1 -1
  44. package/dist/route/index.d.ts +158 -4
  45. package/dist/route/index.js +253 -17
  46. package/dist/route/index.js.map +1 -1
  47. package/dist/server/index.d.ts +251 -16
  48. package/dist/server/index.js +774 -228
  49. package/dist/server/index.js.map +1 -1
  50. package/dist/{types-D_N_U-Py.d.ts → types-7Mhoxnnt.d.ts} +21 -1
  51. package/dist/types-DKQ90YL7.d.ts +372 -0
  52. package/docs/cache.md +133 -0
  53. package/docs/codegen.md +74 -0
  54. package/docs/database.md +370 -0
  55. package/docs/entity.md +539 -0
  56. package/docs/env.md +499 -0
  57. package/docs/errors.md +319 -0
  58. package/docs/event.md +443 -0
  59. package/docs/file-upload.md +717 -0
  60. package/docs/job.md +131 -0
  61. package/docs/logger.md +108 -0
  62. package/docs/middleware.md +337 -0
  63. package/docs/nextjs.md +247 -0
  64. package/docs/repository.md +496 -0
  65. package/docs/route.md +497 -0
  66. package/docs/server.md +429 -0
  67. package/package.json +3 -2
  68. package/dist/types-B-e_f2dQ.d.ts +0 -121
package/docs/server.md ADDED
@@ -0,0 +1,429 @@
1
+ # Server
2
+
3
+ HTTP server configuration with three-level progressive customization.
4
+
5
+ ## Quick Start
6
+
7
+ ```typescript
8
+ // src/server/index.ts
9
+ import { startServer } from '@spfn/core/server';
10
+
11
+ await startServer();
12
+ ```
13
+
14
+ ---
15
+
16
+ ## Configuration Levels
17
+
18
+ ### Level 1: Zero Config
19
+
20
+ No configuration needed. Uses sensible defaults:
21
+
22
+ ```typescript
23
+ import { startServer } from '@spfn/core/server';
24
+
25
+ await startServer();
26
+ // Port: 4000 (or process.env.PORT)
27
+ // Host: localhost (or process.env.HOST)
28
+ // Middleware: Logger + CORS + ErrorHandler
29
+ // Infrastructure: Auto-init from env vars
30
+ ```
31
+
32
+ ### Level 2: Partial Customization
33
+
34
+ Create `src/server/server.config.ts`:
35
+
36
+ ```typescript
37
+ import { defineServerConfig, defineRouter } from '@spfn/core/server';
38
+ import * as userRoutes from './routes/users';
39
+ import * as postRoutes from './routes/posts';
40
+ import { authMiddleware, rateLimiter } from './middlewares';
41
+
42
+ const appRouter = defineRouter({
43
+ ...userRoutes,
44
+ ...postRoutes
45
+ });
46
+
47
+ export default defineServerConfig()
48
+ .port(8790)
49
+ .host('0.0.0.0')
50
+ .routes(appRouter)
51
+ .middlewares([authMiddleware, rateLimiter])
52
+ .build();
53
+
54
+ export type AppRouter = typeof appRouter;
55
+ ```
56
+
57
+ ### Level 3: Full Control
58
+
59
+ Create `src/server/app.ts` for custom Hono setup:
60
+
61
+ ```typescript
62
+ import { Hono } from 'hono';
63
+ import { timing } from 'hono/timing';
64
+ import { compress } from 'hono/compress';
65
+ import type { AppFactory } from '@spfn/core/server';
66
+
67
+ export default (async () => {
68
+ const app = new Hono();
69
+
70
+ // Custom middleware
71
+ app.use('*', timing());
72
+ app.use('*', compress());
73
+
74
+ // Custom routes
75
+ app.get('/custom', (c) => c.json({ custom: true }));
76
+
77
+ return app;
78
+ }) satisfies AppFactory;
79
+ ```
80
+
81
+ Then in `server.config.ts`:
82
+
83
+ ```typescript
84
+ export default defineServerConfig()
85
+ .routes(appRouter) // Routes registered to your custom app
86
+ .build();
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Configuration Builder
92
+
93
+ ### Basic Options
94
+
95
+ ```typescript
96
+ defineServerConfig()
97
+ .port(8790) // Server port
98
+ .host('0.0.0.0') // Server host
99
+ .routes(appRouter) // Router
100
+ .middlewares([...]) // Global middlewares
101
+ .build();
102
+ ```
103
+
104
+ ### Infrastructure Options
105
+
106
+ ```typescript
107
+ defineServerConfig()
108
+ .database(true) // Enable database (default: auto from env)
109
+ .redis(true) // Enable Redis (default: auto from env)
110
+ .build();
111
+ ```
112
+
113
+ ### CORS Options
114
+
115
+ ```typescript
116
+ defineServerConfig()
117
+ .cors({
118
+ origin: ['https://example.com'],
119
+ methods: ['GET', 'POST', 'PUT', 'DELETE'],
120
+ credentials: true
121
+ })
122
+ .build();
123
+ ```
124
+
125
+ ### Lifecycle Hooks
126
+
127
+ ```typescript
128
+ defineServerConfig()
129
+ .beforeStart(async () => {
130
+ // Before server starts
131
+ console.log('Initializing...');
132
+ })
133
+ .afterStart(async (server) => {
134
+ // After server is running
135
+ console.log(`Server running on port ${server.port}`);
136
+ })
137
+ .beforeShutdown(async () => {
138
+ // Before graceful shutdown
139
+ console.log('Shutting down...');
140
+ })
141
+ .build();
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Router
147
+
148
+ ### Define Router
149
+
150
+ ```typescript
151
+ import { defineRouter } from '@spfn/core/route';
152
+
153
+ export const appRouter = defineRouter({
154
+ getUser,
155
+ createUser,
156
+ updateUser,
157
+ deleteUser
158
+ });
159
+ ```
160
+
161
+ ### Nested Routers
162
+
163
+ ```typescript
164
+ export const appRouter = defineRouter({
165
+ users: defineRouter({
166
+ get: getUser,
167
+ create: createUser,
168
+ list: getUsers
169
+ }),
170
+ posts: defineRouter({
171
+ get: getPost,
172
+ create: createPost
173
+ })
174
+ });
175
+ ```
176
+
177
+ ### Type Export
178
+
179
+ ```typescript
180
+ // server.config.ts
181
+ export type AppRouter = typeof appRouter;
182
+
183
+ // Use in Next.js client
184
+ import type { AppRouter } from '@/server/server.config';
185
+ import { createApi } from '@spfn/core/nextjs';
186
+
187
+ const api = createApi<AppRouter>();
188
+ ```
189
+
190
+ ---
191
+
192
+ ## Graceful Shutdown
193
+
194
+ Automatic graceful shutdown with drain behavior (AWS drain style):
195
+
196
+ ```
197
+ SIGTERM
198
+
199
+ ├─ Phase 1: server.close() ← 새 연결 거부, 진행 중 요청 대기
200
+ ├─ Phase 2: stopBoss() ← pg-boss 작업 정리
201
+ ├─ Phase 3: ShutdownManager.execute()
202
+ │ ├─ drain: tracked operations 완료 대기
203
+ │ └─ hooks: 등록된 훅 순서대로 실행
204
+ ├─ Phase 4: beforeShutdown lifecycle ← 기존 lifecycle 호환
205
+ ├─ Phase 5: closeDatabase / closeCache
206
+ └─ process.exit(0)
207
+ ```
208
+
209
+ **Signals handled:** `SIGTERM`, `SIGINT`
210
+
211
+ ### ShutdownManager
212
+
213
+ 모듈별 독립적인 shutdown 훅 등록과 장기 작업 추적:
214
+
215
+ ```typescript
216
+ import { getShutdownManager } from '@spfn/core/server';
217
+
218
+ const shutdown = getShutdownManager();
219
+
220
+ // 1. Shutdown 훅 등록 — 모듈별 독립 cleanup
221
+ shutdown.onShutdown('ai-client', async () =>
222
+ {
223
+ await openaiClient.close();
224
+ }, { timeout: 5000, order: 10 });
225
+
226
+ shutdown.onShutdown('search-index', async () =>
227
+ {
228
+ await elasticClient.close();
229
+ }, { order: 20 });
230
+
231
+ // 2. 장기 작업 추적 — shutdown 시 완료까지 대기
232
+ const result = await shutdown.trackOperation(
233
+ 'ai-generate',
234
+ aiService.generate(prompt)
235
+ );
236
+ // shutdown 중이면 자동으로 throw → 503
237
+
238
+ // 3. Shutdown 상태 확인
239
+ if (shutdown.isShuttingDown())
240
+ {
241
+ return c.json({ error: 'Server is shutting down' }, 503);
242
+ }
243
+ ```
244
+
245
+ ### Timeout Configuration
246
+
247
+ ```typescript
248
+ defineServerConfig()
249
+ .timeout({
250
+ request: 120000, // SERVER_TIMEOUT (default: 120s)
251
+ keepAlive: 65000, // SERVER_KEEPALIVE_TIMEOUT (default: 65s)
252
+ headers: 60000, // SERVER_HEADERS_TIMEOUT (default: 60s)
253
+ })
254
+ .fetchTimeout({
255
+ connect: 10000, // FETCH_CONNECT_TIMEOUT (default: 10s)
256
+ headers: 300000, // FETCH_HEADERS_TIMEOUT (default: 300s)
257
+ body: 300000, // FETCH_BODY_TIMEOUT (default: 300s)
258
+ })
259
+ .shutdown({
260
+ timeout: 280000, // SHUTDOWN_TIMEOUT (default: 280s)
261
+ })
262
+ .build();
263
+ ```
264
+
265
+ For long-running AI workloads, increase fetch timeouts:
266
+
267
+ ```bash
268
+ FETCH_HEADERS_TIMEOUT=600000 # 10 minutes
269
+ FETCH_BODY_TIMEOUT=600000 # 10 minutes
270
+ RPC_PROXY_TIMEOUT=580000 # Must be < FETCH_HEADERS_TIMEOUT
271
+ ```
272
+
273
+ AI 파이프라인 등 장기 작업이 있는 앱은 Helm chart과 함께 조정:
274
+
275
+ ```yaml
276
+ # apps/values.yaml
277
+ gracefulShutdown:
278
+ terminationGracePeriodSeconds: 660 # 11분
279
+ ```
280
+
281
+ ```bash
282
+ SHUTDOWN_TIMEOUT=640000 # 660 - 5 - 15 = 640s
283
+ ```
284
+
285
+ ---
286
+
287
+ ## Health Check
288
+
289
+ Built-in health endpoint at `/health`:
290
+
291
+ ```bash
292
+ # Normal
293
+ curl http://localhost:8790/health
294
+ # { "status": "ok", "timestamp": "2024-..." }
295
+
296
+ # During shutdown → 503
297
+ # { "status": "shutting_down", "timestamp": "2024-..." }
298
+ ```
299
+
300
+ ---
301
+
302
+ ## Environment Variables
303
+
304
+ ```bash
305
+ # Server
306
+ PORT=8790
307
+ HOST=localhost
308
+ NODE_ENV=development
309
+
310
+ # Database
311
+ DATABASE_URL=postgresql://localhost:5432/mydb
312
+ DATABASE_WRITE_URL=postgresql://primary:5432/mydb
313
+ DATABASE_READ_URL=postgresql://replica:5432/mydb
314
+
315
+ # Redis
316
+ REDIS_URL=redis://localhost:6379
317
+
318
+ # Server Timeout (inbound HTTP server)
319
+ SERVER_TIMEOUT=120000 # Request timeout (default: 120s)
320
+ SERVER_KEEPALIVE_TIMEOUT=65000 # Keep-alive timeout (default: 65s)
321
+ SERVER_HEADERS_TIMEOUT=60000 # Headers timeout, Slowloris defense (default: 60s)
322
+
323
+ # Fetch Timeout (outbound HTTP via undici global dispatcher)
324
+ FETCH_CONNECT_TIMEOUT=10000 # TCP connection timeout (default: 10s)
325
+ FETCH_HEADERS_TIMEOUT=300000 # Response headers timeout (default: 300s)
326
+ FETCH_BODY_TIMEOUT=300000 # Body chunk interval timeout (default: 300s)
327
+
328
+ # RPC Proxy Timeout (Next.js → Backend)
329
+ RPC_PROXY_TIMEOUT=120000 # AbortController timeout (default: 120s)
330
+
331
+ # Shutdown
332
+ SHUTDOWN_TIMEOUT=280000 # Graceful shutdown timeout (default: 280s)
333
+ ```
334
+
335
+ ### Timeout Architecture
336
+
337
+ ```
338
+ Request flow:
339
+ Client → Next.js API Route → fetch() → SPFN Backend
340
+
341
+ Timeout layers:
342
+ ┌──────────────────────────────────────────────────────┐
343
+ │ RPC_PROXY_TIMEOUT (AbortController) default 120s │ ← shortest, returns 504
344
+ │ FETCH_HEADERS_TIMEOUT (undici) default 300s │ ← fetch socket level
345
+ │ FETCH_BODY_TIMEOUT (undici) default 300s │ ← body chunk interval
346
+ │ FETCH_CONNECT_TIMEOUT (undici) default 10s │ ← TCP connection
347
+ ├──────────────────────────────────────────────────────┤
348
+ │ SERVER_TIMEOUT (backend server.timeout) default 120s │ ← backend HTTP server
349
+ │ SERVER_HEADERS_TIMEOUT default 60s │ ← Slowloris defense
350
+ │ SERVER_KEEPALIVE_TIMEOUT default 65s │ ← idle connection
351
+ └──────────────────────────────────────────────────────┘
352
+
353
+ Rule: RPC_PROXY_TIMEOUT < FETCH_HEADERS_TIMEOUT
354
+ ```
355
+
356
+ ---
357
+
358
+ ## File Structure
359
+
360
+ ```
361
+ src/server/
362
+ ├── server.config.ts # Configuration (Level 2)
363
+ ├── app.ts # Custom Hono app (Level 3, optional)
364
+ ├── index.ts # Entry point
365
+ ├── entities/ # Database schema
366
+ ├── repositories/ # Data access
367
+ ├── routes/ # API routes
368
+ └── middlewares/ # Custom middleware
369
+ ```
370
+
371
+ ---
372
+
373
+ ## Startup Banner
374
+
375
+ On startup, server displays:
376
+
377
+ ```
378
+ ╭──────────────────────────────────────╮
379
+ │ │
380
+ │ SPFN Server v1.0.0 │
381
+ │ │
382
+ │ http://localhost:8790 │
383
+ │ │
384
+ │ Press Ctrl+C to stop │
385
+ │ │
386
+ ╰──────────────────────────────────────╯
387
+
388
+ ✓ Database connected
389
+ ✓ Redis connected
390
+ ✓ 12 routes registered
391
+ ```
392
+
393
+ ---
394
+
395
+ ## Best Practices
396
+
397
+ ### Do
398
+
399
+ ```typescript
400
+ // 1. Export router type for client usage
401
+ export type AppRouter = typeof appRouter;
402
+
403
+ // 2. Use lifecycle hooks for initialization
404
+ .beforeStart(async () => {
405
+ await warmupCache();
406
+ })
407
+
408
+ // 3. Register global middlewares
409
+ .middlewares([authMiddleware, rateLimiter])
410
+
411
+ // 4. Use Level 2 for most projects
412
+ defineServerConfig()
413
+ .port(8790)
414
+ .routes(appRouter)
415
+ .build();
416
+ ```
417
+
418
+ ### Don't
419
+
420
+ ```typescript
421
+ // 1. Don't hardcode port in production
422
+ .port(8790) // Use process.env.PORT instead
423
+
424
+ // 2. Don't skip database auto-init unless needed
425
+ .database(false) // Usually let it auto-detect
426
+
427
+ // 3. Don't use Level 3 unless necessary
428
+ // Level 2 covers most use cases
429
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spfn/core",
3
- "version": "0.2.0-beta.4",
3
+ "version": "0.2.0-beta.42",
4
4
  "description": "SPFN Framework Core - File-based routing, transactions, repository pattern",
5
5
  "type": "module",
6
6
  "exports": {
@@ -146,12 +146,13 @@
146
146
  "dotenv": "^17.2.3",
147
147
  "drizzle-orm": "^0.45.0",
148
148
  "drizzle-typebox": "^0.1.0",
149
- "hono": "^4.10.6",
149
+ "hono": "^4.12.4",
150
150
  "jiti": "^2.6.1",
151
151
  "micromatch": "^4.0.8",
152
152
  "pg-boss": "^11.1.2",
153
153
  "postgres": "^3.4.0",
154
154
  "typescript": "^5.3.3",
155
+ "undici": "^7.24.0",
155
156
  "zod": "^4.1.11"
156
157
  },
157
158
  "optionalDependencies": {
@@ -1,121 +0,0 @@
1
- import { E as EventRouterDef, I as InferEventNames, b as InferEventPayload } from './router-Di7ENoah.js';
2
-
3
- /**
4
- * SSE Types
5
- *
6
- * Type definitions for Server-Sent Events
7
- */
8
-
9
- /**
10
- * SSE message sent from server
11
- */
12
- interface SSEMessage<TEvent extends string = string, TPayload = unknown> {
13
- /** Event name */
14
- event: TEvent;
15
- /** Event payload */
16
- data: TPayload;
17
- /** Optional message ID for reconnection */
18
- id?: string;
19
- }
20
- /**
21
- * SSE Handler configuration
22
- */
23
- interface SSEHandlerConfig {
24
- /**
25
- * Keep-alive ping interval in milliseconds
26
- * @default 30000
27
- */
28
- pingInterval?: number;
29
- /**
30
- * Custom headers for SSE response
31
- */
32
- headers?: Record<string, string>;
33
- }
34
- /**
35
- * SSE Client configuration
36
- */
37
- interface SSEClientConfig {
38
- /**
39
- * Backend API host URL
40
- * @default NEXT_PUBLIC_SPFN_API_URL || 'http://localhost:8790'
41
- * @example 'http://localhost:8790'
42
- * @example 'https://api.example.com'
43
- */
44
- host?: string;
45
- /**
46
- * SSE endpoint pathname
47
- * @default '/events/stream'
48
- */
49
- pathname?: string;
50
- /**
51
- * Full URL (overrides host + pathname)
52
- * @deprecated Use host and pathname instead
53
- * @example 'http://localhost:8790/events/stream'
54
- */
55
- url?: string;
56
- /**
57
- * Auto reconnect on disconnect
58
- * @default true
59
- */
60
- reconnect?: boolean;
61
- /**
62
- * Reconnect delay in milliseconds
63
- * @default 3000
64
- */
65
- reconnectDelay?: number;
66
- /**
67
- * Maximum reconnect attempts (0 = infinite)
68
- * @default 0
69
- */
70
- maxReconnectAttempts?: number;
71
- /**
72
- * Include credentials (cookies) in request
73
- * @default false
74
- */
75
- withCredentials?: boolean;
76
- }
77
- /**
78
- * Event handler function
79
- */
80
- type SSEEventHandler<TPayload> = (payload: TPayload) => void;
81
- /**
82
- * Event handlers map for EventRouter
83
- */
84
- type SSEEventHandlers<TRouter extends EventRouterDef<any>> = {
85
- [K in InferEventNames<TRouter>]?: SSEEventHandler<InferEventPayload<TRouter, K>>;
86
- };
87
- /**
88
- * Subscription options
89
- */
90
- interface SSESubscribeOptions<TRouter extends EventRouterDef<any>> {
91
- /**
92
- * Events to subscribe
93
- */
94
- events: InferEventNames<TRouter>[];
95
- /**
96
- * Event handlers
97
- */
98
- handlers: SSEEventHandlers<TRouter>;
99
- /**
100
- * Called when connection opens
101
- */
102
- onOpen?: () => void;
103
- /**
104
- * Called on connection error
105
- */
106
- onError?: (error: Event) => void;
107
- /**
108
- * Called when reconnecting
109
- */
110
- onReconnect?: (attempt: number) => void;
111
- }
112
- /**
113
- * SSE connection state
114
- */
115
- type SSEConnectionState = 'connecting' | 'open' | 'closed' | 'error';
116
- /**
117
- * Unsubscribe function
118
- */
119
- type SSEUnsubscribe = () => void;
120
-
121
- export type { SSEHandlerConfig as S, SSEMessage as a, SSEClientConfig as b, SSEEventHandler as c, SSEEventHandlers as d, SSESubscribeOptions as e, SSEConnectionState as f, SSEUnsubscribe as g };