@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
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.2",
3
+ "version": "0.2.0-beta.21",
4
4
  "description": "SPFN Framework Core - File-based routing, transactions, repository pattern",
5
5
  "type": "module",
6
6
  "exports": {
@@ -64,6 +64,11 @@
64
64
  "import": "./dist/env/index.js",
65
65
  "require": "./dist/env/index.js"
66
66
  },
67
+ "./env/loader": {
68
+ "types": "./dist/env/loader.d.ts",
69
+ "import": "./dist/env/loader.js",
70
+ "require": "./dist/env/loader.js"
71
+ },
67
72
  "./logger": {
68
73
  "types": "./dist/logger/index.d.ts",
69
74
  "import": "./dist/logger/index.js",
@@ -83,6 +88,16 @@
83
88
  "types": "./dist/event/index.d.ts",
84
89
  "import": "./dist/event/index.js",
85
90
  "require": "./dist/event/index.js"
91
+ },
92
+ "./event/sse": {
93
+ "types": "./dist/event/sse/index.d.ts",
94
+ "import": "./dist/event/sse/index.js",
95
+ "require": "./dist/event/sse/index.js"
96
+ },
97
+ "./event/sse/client": {
98
+ "types": "./dist/event/sse/client.d.ts",
99
+ "import": "./dist/event/sse/client.js",
100
+ "require": "./dist/event/sse/client.js"
86
101
  }
87
102
  },
88
103
  "keywords": [
@@ -137,6 +152,7 @@
137
152
  "pg-boss": "^11.1.2",
138
153
  "postgres": "^3.4.0",
139
154
  "typescript": "^5.3.3",
155
+ "undici": "^7.22.0",
140
156
  "zod": "^4.1.11"
141
157
  },
142
158
  "optionalDependencies": {
@@ -148,7 +164,7 @@
148
164
  "@vitest/coverage-v8": "^4.0.6",
149
165
  "drizzle-kit": "^0.31.6",
150
166
  "madge": "^8.0.0",
151
- "next": "16.0.7",
167
+ "next": "^16.0.0",
152
168
  "tsup": "^8.5.0",
153
169
  "vitest": "^4.0.6"
154
170
  },
@@ -168,7 +184,7 @@
168
184
  ],
169
185
  "publishConfig": {
170
186
  "access": "public",
171
- "tag": "alpha"
187
+ "tag": "beta"
172
188
  },
173
189
  "scripts": {
174
190
  "build": "pnpm check:circular && tsup",