@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/env.md ADDED
@@ -0,0 +1,499 @@
1
+ # Environment
2
+
3
+ Type-safe environment variable management with schema-based validation.
4
+
5
+ ## Quick Start
6
+
7
+ ```typescript
8
+ // src/config/env.ts
9
+ import {
10
+ defineEnvSchema,
11
+ envString,
12
+ envNumber,
13
+ envBoolean,
14
+ envEnum,
15
+ createEnvRegistry,
16
+ parsePostgresUrl,
17
+ } from '@spfn/core/env';
18
+
19
+ // 1. Define schema
20
+ const schema = defineEnvSchema({
21
+ DATABASE_URL: envString({
22
+ description: 'PostgreSQL connection URL',
23
+ required: true,
24
+ sensitive: true,
25
+ validator: parsePostgresUrl,
26
+ }),
27
+ PORT: envNumber({
28
+ description: 'Server port',
29
+ default: 3000,
30
+ }),
31
+ DEBUG: envBoolean({
32
+ description: 'Enable debug mode',
33
+ default: false,
34
+ }),
35
+ LOG_LEVEL: envEnum(['debug', 'info', 'warn', 'error'] as const, {
36
+ description: 'Logging level',
37
+ default: 'info',
38
+ }),
39
+ });
40
+
41
+ // 2. Create registry and validate
42
+ const registry = createEnvRegistry(schema);
43
+ export const env = registry.validate();
44
+
45
+ // 3. Use with full type safety
46
+ env.DATABASE_URL // string (required)
47
+ env.PORT // number (default: 3000)
48
+ env.DEBUG // boolean (default: false)
49
+ env.LOG_LEVEL // 'debug' | 'info' | 'warn' | 'error'
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Schema Definition
55
+
56
+ ### `defineEnvSchema(schema)`
57
+
58
+ Define environment variable schema with auto-filled keys.
59
+
60
+ ```typescript
61
+ const schema = defineEnvSchema({
62
+ API_KEY: envString({ description: 'API key', required: true }),
63
+ // Automatically adds key: 'API_KEY'
64
+ });
65
+ ```
66
+
67
+ ### Schema Type Helpers
68
+
69
+ #### `envString(options)`
70
+
71
+ ```typescript
72
+ API_KEY: envString({
73
+ description: 'API authentication key',
74
+ required: true,
75
+ sensitive: true,
76
+ minLength: 32,
77
+ })
78
+ ```
79
+
80
+ #### `envNumber(options)`
81
+
82
+ ```typescript
83
+ PORT: envNumber({
84
+ description: 'Server port',
85
+ default: 3000,
86
+ validator: createNumberParser({ min: 1, max: 65535 }),
87
+ })
88
+ ```
89
+
90
+ #### `envBoolean(options)`
91
+
92
+ ```typescript
93
+ DEBUG: envBoolean({
94
+ description: 'Enable debug mode',
95
+ default: false,
96
+ })
97
+ // Parses: 'true', '1', 'yes' → true
98
+ // Parses: 'false', '0', 'no' → false
99
+ ```
100
+
101
+ #### `envUrl(options)`
102
+
103
+ ```typescript
104
+ API_URL: envUrl({
105
+ description: 'API endpoint URL',
106
+ required: true,
107
+ })
108
+ ```
109
+
110
+ #### `envEnum(allowed, options)`
111
+
112
+ ```typescript
113
+ LOG_LEVEL: envEnum(['debug', 'info', 'warn', 'error'] as const, {
114
+ description: 'Logging level',
115
+ default: 'info',
116
+ })
117
+ ```
118
+
119
+ #### `envJson<T>(options)`
120
+
121
+ ```typescript
122
+ CONFIG: envJson<{ host: string; port: number }>({
123
+ description: 'JSON configuration',
124
+ required: true,
125
+ })
126
+ ```
127
+
128
+ ### Schema Options
129
+
130
+ | Option | Type | Description |
131
+ |--------|------|-------------|
132
+ | `description` | `string` | Variable description |
133
+ | `required` | `boolean` | Whether variable is required |
134
+ | `default` | `T` | Default value if not set |
135
+ | `validator` | `(value: string) => T` | Custom validation/transform |
136
+ | `fallbackKeys` | `string[]` | Fallback environment variable keys |
137
+ | `minLength` | `number` | Minimum string length |
138
+ | `sensitive` | `boolean` | Mark as sensitive (masked in logs) |
139
+
140
+ ---
141
+
142
+ ## EnvRegistry
143
+
144
+ ### Creating Registry
145
+
146
+ ```typescript
147
+ import { createEnvRegistry } from '@spfn/core/env';
148
+
149
+ const registry = createEnvRegistry(schema);
150
+ const env = registry.validate();
151
+ ```
152
+
153
+ ### Lazy Validation
154
+
155
+ Values are validated when accessed (Proxy-based):
156
+
157
+ ```typescript
158
+ const env = registry.validate();
159
+
160
+ // Later, when accessed:
161
+ console.log(env.DATABASE_URL); // Validates at this point
162
+ ```
163
+
164
+ ### Fallback Keys
165
+
166
+ Support legacy environment variable names:
167
+
168
+ ```typescript
169
+ DATABASE_URL: envString({
170
+ description: 'Database URL',
171
+ required: true,
172
+ fallbackKeys: ['DB_URL', 'POSTGRES_URL'],
173
+ })
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Parsers
179
+
180
+ ### String Parsers
181
+
182
+ ```typescript
183
+ import { parseString, createStringParser } from '@spfn/core/env';
184
+
185
+ parseString(' hello '); // 'hello'
186
+
187
+ const apiKeyParser = createStringParser({
188
+ minLength: 32,
189
+ maxLength: 128,
190
+ pattern: /^[A-Za-z0-9_-]+$/,
191
+ });
192
+ ```
193
+
194
+ ### Number Parsers
195
+
196
+ ```typescript
197
+ import { parseNumber, createNumberParser, parseInteger } from '@spfn/core/env';
198
+
199
+ parseNumber('42'); // 42
200
+
201
+ const portParser = createNumberParser({
202
+ min: 1,
203
+ max: 65535,
204
+ integer: true,
205
+ });
206
+ ```
207
+
208
+ ### URL Parsers
209
+
210
+ ```typescript
211
+ import { parseUrl, parsePostgresUrl, parseRedisUrl } from '@spfn/core/env';
212
+
213
+ parseUrl('https://api.example.com');
214
+ parsePostgresUrl('postgres://user:pass@localhost:5432/db');
215
+ parseRedisUrl('redis://localhost:6379');
216
+ ```
217
+
218
+ ### Enum Parser
219
+
220
+ ```typescript
221
+ import { parseEnum, createEnumParser } from '@spfn/core/env';
222
+
223
+ parseEnum('info', ['debug', 'info', 'warn', 'error']);
224
+
225
+ const logLevelParser = createEnumParser(
226
+ ['debug', 'info', 'warn', 'error'],
227
+ true // case-insensitive
228
+ );
229
+ ```
230
+
231
+ ### Array Parser
232
+
233
+ ```typescript
234
+ import { parseArray, createArrayParser } from '@spfn/core/env';
235
+
236
+ parseArray('a,b,c'); // ['a', 'b', 'c']
237
+ parseArray('a|b|c', { separator: '|' }); // ['a', 'b', 'c']
238
+
239
+ const portsParser = createArrayParser(
240
+ createNumberParser({ min: 1, max: 65535, integer: true })
241
+ );
242
+ portsParser('3000,4000,5000'); // [3000, 4000, 5000]
243
+ ```
244
+
245
+ ### Security Parsers
246
+
247
+ ```typescript
248
+ import { createSecureSecretParser, createPasswordParser } from '@spfn/core/env';
249
+
250
+ // Entropy-based secret validation
251
+ const secretParser = createSecureSecretParser({
252
+ minLength: 32,
253
+ minUniqueChars: 16,
254
+ minEntropy: 3.5,
255
+ });
256
+
257
+ // Password strength validation
258
+ const passwordParser = createPasswordParser({
259
+ minLength: 12,
260
+ requireUppercase: true,
261
+ requireLowercase: true,
262
+ requireNumber: true,
263
+ requireSpecial: true,
264
+ });
265
+ ```
266
+
267
+ ### Parser Composition
268
+
269
+ ```typescript
270
+ import { chain, withFallback, optional } from '@spfn/core/env';
271
+
272
+ // Chain parsers
273
+ const apiKeyParser = chain(
274
+ parseString,
275
+ createStringParser({ minLength: 32 })
276
+ );
277
+
278
+ // Fallback value
279
+ const configParser = withFallback(parseJson, { host: 'localhost' });
280
+
281
+ // Optional (returns undefined for empty)
282
+ const optionalRedisParser = optional(parseRedisUrl);
283
+ ```
284
+
285
+ ---
286
+
287
+ ## Environment File Loading
288
+
289
+ ### SPFN Server
290
+
291
+ ```typescript
292
+ import { loadEnv } from '@spfn/core/env/loader';
293
+
294
+ // 기본 사용 (NODE_ENV 자동 감지)
295
+ loadEnv();
296
+
297
+ // 특정 환경 지정
298
+ loadEnv({ nodeEnv: 'production' });
299
+
300
+ // 서버 레이어 제외 (Next.js 클라이언트용)
301
+ loadEnv({ server: false });
302
+ ```
303
+
304
+ ### Loading Priority (6-Layer)
305
+
306
+ `NODE_ENV`에 따라 동적으로 파일 목록이 결정됩니다 (나중 파일이 덮어씀):
307
+
308
+ 1. `.env` - 공통 기본값 (committed)
309
+ 2. `.env.{NODE_ENV}` - 환경별 오버라이드 (committed)
310
+ 3. `.env.local` - Next.js용 로컬 오버라이드 (gitignored, **test에서 스킵**)
311
+ 4. `.env.{NODE_ENV}.local` - 환경별 시크릿 (gitignored)
312
+ 5. `.env.server` - 서버 전용 기본값 (committed)
313
+ 6. `.env.server.local` - 서버 전용 시크릿 (gitignored)
314
+
315
+ > **Important:** `.env.local`은 Next.js용입니다. 서버 전용 시크릿(`DATABASE_URL` 등)은 반드시 `.env.server.local`에 넣으세요.
316
+
317
+ ### Options
318
+
319
+ ```typescript
320
+ loadEnv({
321
+ cwd: '/path/to/project', // 프로젝트 루트 (default: process.cwd())
322
+ nodeEnv: 'production', // NODE_ENV 지정 (default: process.env.NODE_ENV || 'local')
323
+ server: true, // 서버 전용 파일 포함 (default: true)
324
+ debug: true, // 로드된 파일 로깅 (default: false)
325
+ override: false, // 기존 process.env 덮어쓰기 (default: false)
326
+ });
327
+
328
+ // Load once (prevent duplicate calls)
329
+ import { loadEnvOnce } from '@spfn/core/env/loader';
330
+ loadEnvOnce();
331
+ ```
332
+
333
+ ---
334
+
335
+ ## Security Separation (Next.js + SPFN)
336
+
337
+ ### File Structure
338
+
339
+ ```
340
+ project/
341
+ ├── .env # 공통 기본값 (committed)
342
+ ├── .env.production # production 오버라이드 (committed)
343
+ ├── .env.local # Next.js용 로컬 오버라이드 (gitignored)
344
+ ├── .env.production.local # production 시크릿 (gitignored)
345
+ ├── .env.server # 서버 전용 기본값 (committed)
346
+ └── .env.server.local # 서버 전용 시크릿 (gitignored)
347
+ ```
348
+
349
+ ### Which File for What?
350
+
351
+ | 환경변수 | 파일 | 이유 |
352
+ |----------|------|------|
353
+ | `NODE_ENV`, `SPFN_LOG_LEVEL` | `.env` | 모든 환경 공통, 비민감 |
354
+ | `SPFN_API_URL` (production) | `.env.production` | 환경별 비민감 설정 |
355
+ | `NEXT_PUBLIC_*` | `.env.local` | Next.js 클라이언트용, 브라우저 노출 OK |
356
+ | `SPFN_APP_URL` | `.env.local` | Next.js에서 사용하는 로컬 설정 |
357
+ | `DB_POOL_MAX` | `.env.server` | 서버 전용, 비민감 |
358
+ | `DATABASE_URL` | `.env.server.local` | 서버 전용, **민감정보** |
359
+ | `SESSION_SECRET` | `.env.server.local` | 서버 전용, **민감정보** |
360
+
361
+ > **Rule:** `.env.local`은 Next.js용입니다. `DATABASE_URL`, `SESSION_SECRET` 등 서버 전용 시크릿은 `.env.server.local`에 넣으세요.
362
+
363
+ ### Schema with `nextjs` Option
364
+
365
+ ```typescript
366
+ DATABASE_URL: envString({
367
+ description: 'PostgreSQL connection URL',
368
+ required: true,
369
+ sensitive: true,
370
+ nextjs: false, // SPFN 서버에서만 사용 → .env.server.local
371
+ }),
372
+
373
+ SPFN_API_URL: envString({
374
+ description: 'Backend API URL',
375
+ required: true,
376
+ nextjs: true, // Next.js에서도 사용 → .env 또는 .env.local
377
+ }),
378
+ ```
379
+
380
+ ---
381
+
382
+ ## Type Inference
383
+
384
+ ```typescript
385
+ import type { InferEnvType } from '@spfn/core/env';
386
+
387
+ const schema = defineEnvSchema({
388
+ DATABASE_URL: envString({ required: true }),
389
+ PORT: envNumber({ default: 3000 }),
390
+ DEBUG: envBoolean({}),
391
+ });
392
+
393
+ type Env = InferEnvType<typeof schema>;
394
+ // {
395
+ // DATABASE_URL: string; // required
396
+ // PORT: number; // has default
397
+ // DEBUG?: boolean | undefined; // optional
398
+ // }
399
+ ```
400
+
401
+ ---
402
+
403
+ ## Complete Example
404
+
405
+ ```typescript
406
+ // src/config/env.ts
407
+ import {
408
+ defineEnvSchema,
409
+ envString,
410
+ envNumber,
411
+ envBoolean,
412
+ envEnum,
413
+ createEnvRegistry,
414
+ parsePostgresUrl,
415
+ createSecureSecretParser,
416
+ createNumberParser,
417
+ } from '@spfn/core/env';
418
+
419
+ const schema = defineEnvSchema({
420
+ // Database
421
+ DATABASE_URL: envString({
422
+ description: 'PostgreSQL connection URL',
423
+ required: true,
424
+ sensitive: true,
425
+ validator: parsePostgresUrl,
426
+ }),
427
+
428
+ // Server
429
+ PORT: envNumber({
430
+ description: 'Server port',
431
+ default: 3000,
432
+ validator: createNumberParser({ min: 1, max: 65535, integer: true }),
433
+ }),
434
+
435
+ // Security
436
+ SESSION_SECRET: envString({
437
+ description: 'Session encryption secret',
438
+ required: true,
439
+ sensitive: true,
440
+ validator: createSecureSecretParser({ minLength: 32 }),
441
+ }),
442
+
443
+ // Environment
444
+ NODE_ENV: envEnum(['local', 'development', 'staging', 'production', 'test'] as const, {
445
+ description: 'Node environment',
446
+ default: 'local',
447
+ }),
448
+
449
+ // Logging
450
+ LOG_LEVEL: envEnum(['debug', 'info', 'warn', 'error'] as const, {
451
+ description: 'Log level',
452
+ default: 'info',
453
+ }),
454
+
455
+ // Optional
456
+ REDIS_URL: envString({
457
+ description: 'Redis connection URL',
458
+ required: false,
459
+ }),
460
+
461
+ DEBUG: envBoolean({
462
+ description: 'Enable debug mode',
463
+ default: false,
464
+ }),
465
+ });
466
+
467
+ const registry = createEnvRegistry(schema);
468
+ export const env = registry.validate();
469
+ export type Env = typeof env;
470
+ ```
471
+
472
+ ---
473
+
474
+ ## Best Practices
475
+
476
+ ```typescript
477
+ // 1. Centralize in single file
478
+ // src/config/env.ts
479
+
480
+ // 2. Use descriptive descriptions
481
+ DATABASE_URL: envString({
482
+ description: 'PostgreSQL connection URL for primary database',
483
+ })
484
+
485
+ // 3. Mark sensitive variables
486
+ API_SECRET: envString({
487
+ sensitive: true,
488
+ })
489
+
490
+ // 4. Provide fallback keys for migrations
491
+ DATABASE_URL: envString({
492
+ fallbackKeys: ['DB_URL', 'POSTGRES_URL'],
493
+ })
494
+
495
+ // 5. Use strong validators for secrets
496
+ SESSION_SECRET: envString({
497
+ validator: createSecureSecretParser({ minLength: 32, minEntropy: 3.5 }),
498
+ })
499
+ ```