@spfn/core 0.2.0-beta.3 → 0.2.0-beta.30

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 (65) hide show
  1. package/README.md +260 -1175
  2. package/dist/{boss-BO8ty33K.d.ts → boss-DI1r4kTS.d.ts} +24 -7
  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 +24 -3
  10. package/dist/db/index.js +118 -45
  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 +238 -0
  26. package/dist/event/sse/index.js.map +1 -0
  27. package/dist/job/index.d.ts +23 -8
  28. package/dist/job/index.js +108 -23
  29. package/dist/job/index.js.map +1 -1
  30. package/dist/logger/index.js +9 -0
  31. package/dist/logger/index.js.map +1 -1
  32. package/dist/middleware/index.d.ts +23 -1
  33. package/dist/middleware/index.js +58 -5
  34. package/dist/middleware/index.js.map +1 -1
  35. package/dist/nextjs/index.d.ts +2 -2
  36. package/dist/nextjs/index.js +37 -5
  37. package/dist/nextjs/index.js.map +1 -1
  38. package/dist/nextjs/server.d.ts +44 -23
  39. package/dist/nextjs/server.js +87 -66
  40. package/dist/nextjs/server.js.map +1 -1
  41. package/dist/route/index.d.ts +168 -5
  42. package/dist/route/index.js +262 -17
  43. package/dist/route/index.js.map +1 -1
  44. package/dist/router-Di7ENoah.d.ts +151 -0
  45. package/dist/server/index.d.ts +316 -5
  46. package/dist/server/index.js +892 -200
  47. package/dist/server/index.js.map +1 -1
  48. package/dist/{types-BVxUIkcU.d.ts → types-7Mhoxnnt.d.ts} +68 -2
  49. package/dist/types-DAVwA-_7.d.ts +339 -0
  50. package/docs/cache.md +133 -0
  51. package/docs/codegen.md +74 -0
  52. package/docs/database.md +346 -0
  53. package/docs/entity.md +539 -0
  54. package/docs/env.md +499 -0
  55. package/docs/errors.md +319 -0
  56. package/docs/event.md +443 -0
  57. package/docs/file-upload.md +717 -0
  58. package/docs/job.md +131 -0
  59. package/docs/logger.md +108 -0
  60. package/docs/middleware.md +337 -0
  61. package/docs/nextjs.md +247 -0
  62. package/docs/repository.md +496 -0
  63. package/docs/route.md +497 -0
  64. package/docs/server.md +429 -0
  65. package/package.json +18 -2
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Environment Variable Loader
3
+ *
4
+ * Next.js 스타일의 환경변수 파일 로딩 (환경별 분리 지원)
5
+ *
6
+ * 로딩 우선순위 (낮음 -> 높음, 나중 파일이 덮어씀):
7
+ * 1. .env - 기본값 (committed)
8
+ * 2. .env.{NODE_ENV} - 환경별 오버라이드 (committed)
9
+ * 3. .env.local - 로컬 오버라이드 (gitignored, test에서 스킵)
10
+ * 4. .env.{NODE_ENV}.local - 환경별 시크릿 (gitignored)
11
+ * 5. .env.server - 서버 전용 기본값 (committed)
12
+ * 6. .env.server.local - 서버 전용 시크릿 (gitignored)
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { loadEnv } from '@spfn/core/env/loader';
17
+ *
18
+ * // 기본 사용 (NODE_ENV 자동 감지)
19
+ * loadEnv();
20
+ *
21
+ * // 특정 환경 지정
22
+ * loadEnv({ nodeEnv: 'production' });
23
+ *
24
+ * // 서버 레이어 제외 (Next.js 클라이언트용)
25
+ * loadEnv({ server: false });
26
+ * ```
27
+ *
28
+ * @module env/loader
29
+ */
30
+ /**
31
+ * loadEnv 옵션
32
+ */
33
+ interface LoadEnvOptions {
34
+ /**
35
+ * 프로젝트 루트 경로
36
+ * @default process.cwd()
37
+ */
38
+ cwd?: string;
39
+ /**
40
+ * NODE_ENV 값 (환경별 .env 파일 결정)
41
+ * @default process.env.NODE_ENV || 'local'
42
+ */
43
+ nodeEnv?: string;
44
+ /**
45
+ * 서버 전용 파일 포함 여부 (.env.server, .env.server.local)
46
+ * @default true
47
+ */
48
+ server?: boolean;
49
+ /**
50
+ * 디버그 모드 (로드된 파일 로깅)
51
+ * @default false
52
+ */
53
+ debug?: boolean;
54
+ /**
55
+ * 기존 process.env 값 덮어쓰기 허용
56
+ * @default false
57
+ */
58
+ override?: boolean;
59
+ }
60
+ /**
61
+ * 환경변수 로드 결과
62
+ */
63
+ interface LoadEnvResult {
64
+ /**
65
+ * 로드된 파일 목록
66
+ */
67
+ loadedFiles: string[];
68
+ /**
69
+ * 로드된 환경변수 키 목록
70
+ */
71
+ loadedKeys: string[];
72
+ }
73
+ /**
74
+ * 프로젝트 루트의 환경변수 파일들을 규칙에 따라 로드
75
+ *
76
+ * 모든 파일을 파싱 후 머지한 뒤 process.env에 한번에 적용.
77
+ * 이미 process.env에 존재하는 키는 덮어쓰지 않음 (플랫폼 주입 보호).
78
+ *
79
+ * @param options - 로드 옵션
80
+ * @returns 로드 결과 (로드된 파일, 키 목록)
81
+ */
82
+ declare function loadEnv(options?: LoadEnvOptions): LoadEnvResult;
83
+ /**
84
+ * 환경변수를 한 번만 로드 (중복 호출 방지)
85
+ *
86
+ * @param options - 로드 옵션
87
+ * @returns 로드 결과 (이미 로드된 경우 빈 결과)
88
+ */
89
+ declare function loadEnvOnce(options?: LoadEnvOptions): LoadEnvResult;
90
+ /**
91
+ * 환경변수 로드 상태 리셋 (테스트용)
92
+ */
93
+ declare function resetEnvLoadState(): void;
94
+
95
+ export { type LoadEnvOptions, type LoadEnvResult, loadEnv, loadEnvOnce, resetEnvLoadState };
@@ -0,0 +1,78 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { resolve } from 'path';
3
+ import { parse } from 'dotenv';
4
+ import { logger } from '@spfn/core/logger';
5
+
6
+ // src/env/loader.ts
7
+ var envLogger = logger.child("@spfn/core:env-loader");
8
+ function getEnvFiles(nodeEnv, server) {
9
+ const files = [
10
+ ".env",
11
+ `.env.${nodeEnv}`
12
+ ];
13
+ if (nodeEnv !== "test") {
14
+ files.push(".env.local");
15
+ }
16
+ files.push(`.env.${nodeEnv}.local`);
17
+ if (server) {
18
+ files.push(".env.server");
19
+ files.push(".env.server.local");
20
+ }
21
+ return files;
22
+ }
23
+ function parseEnvFile(filePath) {
24
+ if (!existsSync(filePath)) {
25
+ return null;
26
+ }
27
+ return parse(readFileSync(filePath, "utf-8"));
28
+ }
29
+ function loadEnv(options = {}) {
30
+ const {
31
+ cwd = process.cwd(),
32
+ nodeEnv = process.env.NODE_ENV || "local",
33
+ server = true,
34
+ debug = false,
35
+ override = false
36
+ } = options;
37
+ const envFiles = getEnvFiles(nodeEnv, server);
38
+ const loadedFiles = [];
39
+ const existingKeys = new Set(Object.keys(process.env));
40
+ const merged = {};
41
+ for (const fileName of envFiles) {
42
+ const filePath = resolve(cwd, fileName);
43
+ const parsed = parseEnvFile(filePath);
44
+ if (parsed === null) {
45
+ continue;
46
+ }
47
+ loadedFiles.push(fileName);
48
+ Object.assign(merged, parsed);
49
+ }
50
+ const loadedKeys = [];
51
+ for (const [key, value] of Object.entries(merged)) {
52
+ if (!override && existingKeys.has(key)) {
53
+ continue;
54
+ }
55
+ process.env[key] = value;
56
+ loadedKeys.push(key);
57
+ }
58
+ if (debug && loadedFiles.length > 0) {
59
+ envLogger.debug(`Loaded env files: ${loadedFiles.join(", ")}`);
60
+ envLogger.debug(`Loaded ${loadedKeys.length} environment variables`);
61
+ }
62
+ return { loadedFiles, loadedKeys };
63
+ }
64
+ var isEnvLoaded = false;
65
+ function loadEnvOnce(options = {}) {
66
+ if (isEnvLoaded) {
67
+ return { loadedFiles: [], loadedKeys: [] };
68
+ }
69
+ isEnvLoaded = true;
70
+ return loadEnv(options);
71
+ }
72
+ function resetEnvLoadState() {
73
+ isEnvLoaded = false;
74
+ }
75
+
76
+ export { loadEnv, loadEnvOnce, resetEnvLoadState };
77
+ //# sourceMappingURL=loader.js.map
78
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/env/loader.ts"],"names":[],"mappings":"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","file":"loader.js","sourcesContent":["/**\n * Environment Variable Loader\n *\n * Next.js 스타일의 환경변수 파일 로딩 (환경별 분리 지원)\n *\n * 로딩 우선순위 (낮음 -> 높음, 나중 파일이 덮어씀):\n * 1. .env - 기본값 (committed)\n * 2. .env.{NODE_ENV} - 환경별 오버라이드 (committed)\n * 3. .env.local - 로컬 오버라이드 (gitignored, test에서 스킵)\n * 4. .env.{NODE_ENV}.local - 환경별 시크릿 (gitignored)\n * 5. .env.server - 서버 전용 기본값 (committed)\n * 6. .env.server.local - 서버 전용 시크릿 (gitignored)\n *\n * @example\n * ```typescript\n * import { loadEnv } from '@spfn/core/env/loader';\n *\n * // 기본 사용 (NODE_ENV 자동 감지)\n * loadEnv();\n *\n * // 특정 환경 지정\n * loadEnv({ nodeEnv: 'production' });\n *\n * // 서버 레이어 제외 (Next.js 클라이언트용)\n * loadEnv({ server: false });\n * ```\n *\n * @module env/loader\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { parse } from 'dotenv';\nimport { logger } from '@spfn/core/logger';\n\nconst envLogger = logger.child('@spfn/core:env-loader');\n\n/**\n * loadEnv 옵션\n */\nexport interface LoadEnvOptions\n{\n /**\n * 프로젝트 루트 경로\n * @default process.cwd()\n */\n cwd?: string;\n\n /**\n * NODE_ENV 값 (환경별 .env 파일 결정)\n * @default process.env.NODE_ENV || 'local'\n */\n nodeEnv?: string;\n\n /**\n * 서버 전용 파일 포함 여부 (.env.server, .env.server.local)\n * @default true\n */\n server?: boolean;\n\n /**\n * 디버그 모드 (로드된 파일 로깅)\n * @default false\n */\n debug?: boolean;\n\n /**\n * 기존 process.env 값 덮어쓰기 허용\n * @default false\n */\n override?: boolean;\n}\n\n/**\n * 환경변수 로드 결과\n */\nexport interface LoadEnvResult\n{\n /**\n * 로드된 파일 목록\n */\n loadedFiles: string[];\n\n /**\n * 로드된 환경변수 키 목록\n */\n loadedKeys: string[];\n}\n\n/**\n * NODE_ENV에 따른 .env 파일 목록 생성 (우선순위 낮음 -> 높음)\n *\n * 더 구체적인 파일이 승리:\n * - environment > base\n * - server > shared\n * - local > committed\n */\nfunction getEnvFiles(nodeEnv: string, server: boolean): string[]\n{\n const files: string[] = [\n '.env',\n `.env.${nodeEnv}`,\n ];\n\n // test 환경에서는 .env.local 스킵 (테스트 결정론성 보장)\n if (nodeEnv !== 'test')\n {\n files.push('.env.local');\n }\n\n files.push(`.env.${nodeEnv}.local`);\n\n if (server)\n {\n files.push('.env.server');\n files.push('.env.server.local');\n }\n\n return files;\n}\n\n/**\n * 단일 .env 파일 파싱\n */\nfunction parseEnvFile(filePath: string): Record<string, string> | null\n{\n if (!existsSync(filePath))\n {\n return null;\n }\n\n return parse(readFileSync(filePath, 'utf-8'));\n}\n\n/**\n * 프로젝트 루트의 환경변수 파일들을 규칙에 따라 로드\n *\n * 모든 파일을 파싱 후 머지한 뒤 process.env에 한번에 적용.\n * 이미 process.env에 존재하는 키는 덮어쓰지 않음 (플랫폼 주입 보호).\n *\n * @param options - 로드 옵션\n * @returns 로드 결과 (로드된 파일, 키 목록)\n */\nexport function loadEnv(options: LoadEnvOptions = {}): LoadEnvResult\n{\n const {\n cwd = process.cwd(),\n nodeEnv = process.env.NODE_ENV || 'local',\n server = true,\n debug = false,\n override = false,\n } = options;\n\n const envFiles = getEnvFiles(nodeEnv, server);\n const loadedFiles: string[] = [];\n\n // 1) 기존 process.env 키 스냅샷 저장\n const existingKeys = new Set(Object.keys(process.env));\n\n // 2) 모든 .env 파일 파싱 후 머지 (나중 파일이 승리)\n const merged: Record<string, string> = {};\n\n for (const fileName of envFiles)\n {\n const filePath = resolve(cwd, fileName);\n const parsed = parseEnvFile(filePath);\n\n if (parsed === null)\n {\n continue;\n }\n\n loadedFiles.push(fileName);\n Object.assign(merged, parsed);\n }\n\n // 3) 머지된 결과를 process.env에 적용\n const loadedKeys: string[] = [];\n\n for (const [key, value] of Object.entries(merged))\n {\n // 기존 process.env에 이미 있는 키는 스킵 (override가 false일 때)\n if (!override && existingKeys.has(key))\n {\n continue;\n }\n\n process.env[key] = value;\n loadedKeys.push(key);\n }\n\n if (debug && loadedFiles.length > 0)\n {\n envLogger.debug(`Loaded env files: ${loadedFiles.join(', ')}`);\n envLogger.debug(`Loaded ${loadedKeys.length} environment variables`);\n }\n\n return { loadedFiles, loadedKeys };\n}\n\n/**\n * 환경변수가 이미 로드되었는지 확인하는 플래그\n */\nlet isEnvLoaded = false;\n\n/**\n * 환경변수를 한 번만 로드 (중복 호출 방지)\n *\n * @param options - 로드 옵션\n * @returns 로드 결과 (이미 로드된 경우 빈 결과)\n */\nexport function loadEnvOnce(options: LoadEnvOptions = {}): LoadEnvResult\n{\n if (isEnvLoaded)\n {\n return { loadedFiles: [], loadedKeys: [] };\n }\n\n isEnvLoaded = true;\n return loadEnv(options);\n}\n\n/**\n * 환경변수 로드 상태 리셋 (테스트용)\n */\nexport function resetEnvLoadState(): void\n{\n isEnvLoaded = false;\n}\n"]}
@@ -1,73 +1,6 @@
1
1
  import { TSchema, Static } from '@sinclair/typebox';
2
-
3
- /**
4
- * Event System Types
5
- */
6
-
7
- /**
8
- * Pub/Sub capable cache interface for multi-instance events
9
- */
10
- interface PubSubCache {
11
- /**
12
- * Publish a message to a channel
13
- */
14
- publish(channel: string, message: unknown): Promise<void>;
15
- /**
16
- * Subscribe to a channel
17
- */
18
- subscribe(channel: string, handler: (message: unknown) => void | Promise<void>): Promise<void>;
19
- }
20
- /**
21
- * Event handler function type
22
- */
23
- type EventHandler<TPayload> = (payload: TPayload) => void | Promise<void>;
24
- /**
25
- * Job queue sender function type (used by job module)
26
- */
27
- type JobQueueSender = (queueName: string, payload: unknown) => Promise<void>;
28
- /**
29
- * Event definition interface
30
- */
31
- interface EventDef<TPayload = void> {
32
- /**
33
- * Unique event name
34
- */
35
- readonly name: string;
36
- /**
37
- * TypeBox payload schema (optional)
38
- */
39
- readonly schema?: TSchema;
40
- /**
41
- * Subscribe to this event (in-memory handler)
42
- */
43
- subscribe: (handler: EventHandler<TPayload>) => () => void;
44
- /**
45
- * Unsubscribe all handlers
46
- */
47
- unsubscribeAll: () => void;
48
- /**
49
- * Emit the event (triggers all subscribers and queued jobs)
50
- */
51
- emit: TPayload extends void ? () => Promise<void> : (payload: TPayload) => Promise<void>;
52
- /**
53
- * Enable cache-based pub/sub for multi-instance support
54
- * Must await before emitting events to ensure subscription is ready
55
- */
56
- useCache: (cache: PubSubCache) => Promise<EventDef<TPayload>>;
57
- /**
58
- * Internal: Register a job queue to receive this event
59
- * Called by job registration system
60
- */
61
- _registerJobQueue: (queueName: string, sender: JobQueueSender) => void;
62
- /**
63
- * Type inference helper
64
- */
65
- _payload: TPayload;
66
- }
67
- /**
68
- * Infer payload type from EventDef
69
- */
70
- type InferEventPayload<TEvent> = TEvent extends EventDef<infer TPayload> ? TPayload : never;
2
+ import { a as EventDef } from '../router-Di7ENoah.js';
3
+ export { e as EventHandler, E as EventRouterDef, I as InferEventNames, f as InferEventPayload, c as InferEventPayloads, b as InferRouterEventPayload, J as JobQueueSender, P as PubSubCache, d as defineEventRouter } from '../router-Di7ENoah.js';
71
4
 
72
5
  /**
73
6
  * Event System
@@ -105,4 +38,30 @@ declare function defineEvent(name: string): EventDef<void>;
105
38
  */
106
39
  declare function defineEvent<T extends TSchema>(name: string, schema: T): EventDef<Static<T>>;
107
40
 
108
- export { type EventDef, type EventHandler, type InferEventPayload, type JobQueueSender, type PubSubCache, defineEvent };
41
+ /**
42
+ * SSE Event Route Map
43
+ *
44
+ * Static route map for SSE token endpoint.
45
+ * Merge into RPC proxy routeMap so `eventsToken` resolves to `POST /events/token`.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * // app/api/rpc/[routeName]/route.ts
50
+ * import { createRpcProxy } from '@spfn/core/nextjs/server';
51
+ * import { eventRouteMap } from '@spfn/core/event';
52
+ * import { authRouteMap } from '@spfn/auth';
53
+ * import { routeMap } from '@/generated/route-map';
54
+ *
55
+ * export const { GET, POST } = createRpcProxy({
56
+ * routeMap: { ...routeMap, ...authRouteMap, ...eventRouteMap },
57
+ * });
58
+ * ```
59
+ */
60
+ declare const eventRouteMap: {
61
+ eventsToken: {
62
+ method: "POST";
63
+ path: string;
64
+ };
65
+ };
66
+
67
+ export { EventDef, defineEvent, eventRouteMap };
@@ -117,6 +117,20 @@ function defineEvent(name, schema) {
117
117
  return createEventImpl(name);
118
118
  }
119
119
 
120
- export { defineEvent };
120
+ // src/event/sse/route-map.ts
121
+ var eventRouteMap = {
122
+ eventsToken: { method: "POST", path: "/events/token" }
123
+ };
124
+
125
+ // src/event/router.ts
126
+ function defineEventRouter(events) {
127
+ return {
128
+ events,
129
+ eventNames: Object.keys(events),
130
+ _types: {}
131
+ };
132
+ }
133
+
134
+ export { defineEvent, defineEventRouter, eventRouteMap };
121
135
  //# sourceMappingURL=index.js.map
122
136
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/event/event.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Event System\n *\n * Decoupled pub/sub event system with optional cache integration for multi-instance support\n *\n * @example\n * ```typescript\n * // Define event\n * const userCreated = defineEvent('user.created', Type.Object({\n * userId: Type.String(),\n * }));\n *\n * // Subscribe (in-memory)\n * userCreated.subscribe((payload) => {\n * console.log('User created:', payload.userId);\n * });\n *\n * // Emit\n * await userCreated.emit({ userId: '123' });\n *\n * // With cache for multi-instance\n * const event = defineEvent('user.created', schema);\n * await event.useCache(cache); // Must await before emitting\n * await event.emit({ userId: '123' }); // Broadcast to all instances\n * ```\n */\n\nimport type { TSchema, Static } from '@sinclair/typebox';\nimport { logger } from '@spfn/core/logger';\nimport type { EventDef, EventHandler, JobQueueSender, PubSubCache } from './types';\n\nconst eventLogger = logger.child('@spfn/core:event');\n\n/**\n * Log handler error with consistent format\n */\nfunction logHandlerError(eventName: string, error: unknown): void\n{\n eventLogger.error(`Event handler error: ${eventName}`, {\n error: error instanceof Error ? error.message : String(error),\n });\n}\n\n/**\n * Log job queue error with consistent format\n */\nfunction logJobQueueError(queueName: string, error: unknown): void\n{\n eventLogger.error(`Failed to send event to job queue: ${queueName}`, {\n error: error instanceof Error ? error.message : String(error),\n });\n}\n\n/**\n * Create handler subscription manager\n */\nfunction createHandlerManager<TPayload>(name: string)\n{\n const handlers: Set<EventHandler<TPayload>> = new Set();\n\n return {\n add: (handler: EventHandler<TPayload>): (() => void) =>\n {\n handlers.add(handler);\n eventLogger.debug(`Subscribed to event: ${name}`, { handlerCount: handlers.size });\n\n return () =>\n {\n handlers.delete(handler);\n eventLogger.debug(`Unsubscribed from event: ${name}`, { handlerCount: handlers.size });\n };\n },\n\n clear: (): void =>\n {\n handlers.clear();\n eventLogger.debug(`Unsubscribed all from event: ${name}`);\n },\n\n trigger: async (payload: TPayload): Promise<void> =>\n {\n const results = await Promise.allSettled(\n [...handlers].map((handler) => handler(payload))\n );\n\n for (const result of results)\n {\n if (result.status === 'rejected')\n {\n logHandlerError(name, result.reason);\n }\n }\n },\n };\n}\n\n/**\n * Create job queue manager\n */\nfunction createJobQueueManager(name: string)\n{\n const jobQueues: Map<string, JobQueueSender> = new Map();\n\n return {\n register: (queueName: string, sender: JobQueueSender): void =>\n {\n jobQueues.set(queueName, sender);\n eventLogger.debug(`Registered job queue for event: ${name}`, { queueName });\n },\n\n send: async (payload: unknown): Promise<void> =>\n {\n if (jobQueues.size === 0)\n {\n return;\n }\n\n const entries = [...jobQueues.entries()];\n const results = await Promise.allSettled(\n entries.map(([queueName, sender]) => sender(queueName, payload))\n );\n\n for (const [i, result] of results.entries())\n {\n if (result.status === 'rejected')\n {\n logJobQueueError(entries[i][0], result.reason);\n }\n }\n },\n\n get size(): number\n {\n return jobQueues.size;\n },\n };\n}\n\n/**\n * Internal: Create event implementation\n */\nfunction createEventImpl<TPayload>(\n name: string,\n schema?: TSchema\n): EventDef<TPayload>\n{\n const handlerManager = createHandlerManager<TPayload>(name);\n const jobQueueManager = createJobQueueManager(name);\n let cache: PubSubCache | undefined;\n let cacheSubscribed = false;\n\n const emit = async (payload?: TPayload): Promise<void> =>\n {\n eventLogger.debug(`Emitting event: ${name}`, {\n payload,\n hasCache: !!cache,\n jobQueueCount: jobQueueManager.size,\n });\n\n if (cache)\n {\n await cache.publish(name, payload);\n }\n else\n {\n await handlerManager.trigger(payload as TPayload);\n }\n\n await jobQueueManager.send(payload);\n eventLogger.debug(`Event emitted: ${name}`);\n };\n\n const useCache = async (newCache: PubSubCache): Promise<EventDef<TPayload>> =>\n {\n if (cacheSubscribed)\n {\n eventLogger.warn(`Cache already configured for event: ${name}`);\n return self;\n }\n\n cache = newCache;\n cacheSubscribed = true;\n\n await newCache.subscribe(name, async (message: unknown) =>\n {\n eventLogger.debug(`Received event from cache: ${name}`);\n await handlerManager.trigger(message as TPayload);\n });\n\n eventLogger.debug(`Cache subscription ready for event: ${name}`);\n return self;\n };\n\n const self: EventDef<TPayload> = {\n name,\n schema,\n subscribe: handlerManager.add,\n unsubscribeAll: handlerManager.clear,\n emit: emit as EventDef<TPayload>['emit'],\n useCache,\n _registerJobQueue: jobQueueManager.register,\n _payload: undefined as unknown as TPayload,\n };\n\n return self;\n}\n\n/**\n * Define an event without payload\n */\nexport function defineEvent(name: string): EventDef<void>;\n\n/**\n * Define an event with typed payload\n */\nexport function defineEvent<T extends TSchema>(\n name: string,\n schema: T\n): EventDef<Static<T>>;\n\n/**\n * Define an event for decoupled pub/sub\n *\n * @example\n * ```typescript\n * // Define event with payload\n * export const userCreated = defineEvent('user.created', Type.Object({\n * userId: Type.String(),\n * }));\n *\n * // Subscribe to event (in-memory)\n * const unsubscribe = userCreated.subscribe((payload) => {\n * console.log('User created:', payload.userId);\n * });\n *\n * // Emit event\n * await userCreated.emit({ userId: '123' });\n *\n * // Unsubscribe when done\n * unsubscribe();\n *\n * // Multi-instance with cache\n * await userCreated.useCache(cache);\n * await userCreated.emit({ userId: '123' }); // Broadcast to all instances\n * ```\n */\nexport function defineEvent<T extends TSchema>(\n name: string,\n schema?: T\n): EventDef<Static<T>> | EventDef\n{\n if (schema)\n {\n return createEventImpl<Static<T>>(name, schema);\n }\n\n return createEventImpl<void>(name);\n}\n"]}
1
+ {"version":3,"sources":["../../src/event/event.ts","../../src/event/sse/route-map.ts","../../src/event/router.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Event System\n *\n * Decoupled pub/sub event system with optional cache integration for multi-instance support\n *\n * @example\n * ```typescript\n * // Define event\n * const userCreated = defineEvent('user.created', Type.Object({\n * userId: Type.String(),\n * }));\n *\n * // Subscribe (in-memory)\n * userCreated.subscribe((payload) => {\n * console.log('User created:', payload.userId);\n * });\n *\n * // Emit\n * await userCreated.emit({ userId: '123' });\n *\n * // With cache for multi-instance\n * const event = defineEvent('user.created', schema);\n * await event.useCache(cache); // Must await before emitting\n * await event.emit({ userId: '123' }); // Broadcast to all instances\n * ```\n */\n\nimport type { TSchema, Static } from '@sinclair/typebox';\nimport { logger } from '@spfn/core/logger';\nimport type { EventDef, EventHandler, JobQueueSender, PubSubCache } from './types';\n\nconst eventLogger = logger.child('@spfn/core:event');\n\n/**\n * Log handler error with consistent format\n */\nfunction logHandlerError(eventName: string, error: unknown): void\n{\n eventLogger.error(`Event handler error: ${eventName}`, {\n error: error instanceof Error ? error.message : String(error),\n });\n}\n\n/**\n * Log job queue error with consistent format\n */\nfunction logJobQueueError(queueName: string, error: unknown): void\n{\n eventLogger.error(`Failed to send event to job queue: ${queueName}`, {\n error: error instanceof Error ? error.message : String(error),\n });\n}\n\n/**\n * Create handler subscription manager\n */\nfunction createHandlerManager<TPayload>(name: string)\n{\n const handlers: Set<EventHandler<TPayload>> = new Set();\n\n return {\n add: (handler: EventHandler<TPayload>): (() => void) =>\n {\n handlers.add(handler);\n eventLogger.debug(`Subscribed to event: ${name}`, { handlerCount: handlers.size });\n\n return () =>\n {\n handlers.delete(handler);\n eventLogger.debug(`Unsubscribed from event: ${name}`, { handlerCount: handlers.size });\n };\n },\n\n clear: (): void =>\n {\n handlers.clear();\n eventLogger.debug(`Unsubscribed all from event: ${name}`);\n },\n\n trigger: async (payload: TPayload): Promise<void> =>\n {\n const results = await Promise.allSettled(\n [...handlers].map((handler) => handler(payload))\n );\n\n for (const result of results)\n {\n if (result.status === 'rejected')\n {\n logHandlerError(name, result.reason);\n }\n }\n },\n };\n}\n\n/**\n * Create job queue manager\n */\nfunction createJobQueueManager(name: string)\n{\n const jobQueues: Map<string, JobQueueSender> = new Map();\n\n return {\n register: (queueName: string, sender: JobQueueSender): void =>\n {\n jobQueues.set(queueName, sender);\n eventLogger.debug(`Registered job queue for event: ${name}`, { queueName });\n },\n\n send: async (payload: unknown): Promise<void> =>\n {\n if (jobQueues.size === 0)\n {\n return;\n }\n\n const entries = [...jobQueues.entries()];\n const results = await Promise.allSettled(\n entries.map(([queueName, sender]) => sender(queueName, payload))\n );\n\n for (const [i, result] of results.entries())\n {\n if (result.status === 'rejected')\n {\n logJobQueueError(entries[i][0], result.reason);\n }\n }\n },\n\n get size(): number\n {\n return jobQueues.size;\n },\n };\n}\n\n/**\n * Internal: Create event implementation\n */\nfunction createEventImpl<TPayload>(\n name: string,\n schema?: TSchema\n): EventDef<TPayload>\n{\n const handlerManager = createHandlerManager<TPayload>(name);\n const jobQueueManager = createJobQueueManager(name);\n let cache: PubSubCache | undefined;\n let cacheSubscribed = false;\n\n const emit = async (payload?: TPayload): Promise<void> =>\n {\n eventLogger.debug(`Emitting event: ${name}`, {\n payload,\n hasCache: !!cache,\n jobQueueCount: jobQueueManager.size,\n });\n\n if (cache)\n {\n await cache.publish(name, payload);\n }\n else\n {\n await handlerManager.trigger(payload as TPayload);\n }\n\n await jobQueueManager.send(payload);\n eventLogger.debug(`Event emitted: ${name}`);\n };\n\n const useCache = async (newCache: PubSubCache): Promise<EventDef<TPayload>> =>\n {\n if (cacheSubscribed)\n {\n eventLogger.warn(`Cache already configured for event: ${name}`);\n return self;\n }\n\n cache = newCache;\n cacheSubscribed = true;\n\n await newCache.subscribe(name, async (message: unknown) =>\n {\n eventLogger.debug(`Received event from cache: ${name}`);\n await handlerManager.trigger(message as TPayload);\n });\n\n eventLogger.debug(`Cache subscription ready for event: ${name}`);\n return self;\n };\n\n const self: EventDef<TPayload> = {\n name,\n schema,\n subscribe: handlerManager.add,\n unsubscribeAll: handlerManager.clear,\n emit: emit as EventDef<TPayload>['emit'],\n useCache,\n _registerJobQueue: jobQueueManager.register,\n _payload: undefined as unknown as TPayload,\n };\n\n return self;\n}\n\n/**\n * Define an event without payload\n */\nexport function defineEvent(name: string): EventDef<void>;\n\n/**\n * Define an event with typed payload\n */\nexport function defineEvent<T extends TSchema>(\n name: string,\n schema: T\n): EventDef<Static<T>>;\n\n/**\n * Define an event for decoupled pub/sub\n *\n * @example\n * ```typescript\n * // Define event with payload\n * export const userCreated = defineEvent('user.created', Type.Object({\n * userId: Type.String(),\n * }));\n *\n * // Subscribe to event (in-memory)\n * const unsubscribe = userCreated.subscribe((payload) => {\n * console.log('User created:', payload.userId);\n * });\n *\n * // Emit event\n * await userCreated.emit({ userId: '123' });\n *\n * // Unsubscribe when done\n * unsubscribe();\n *\n * // Multi-instance with cache\n * await userCreated.useCache(cache);\n * await userCreated.emit({ userId: '123' }); // Broadcast to all instances\n * ```\n */\nexport function defineEvent<T extends TSchema>(\n name: string,\n schema?: T\n): EventDef<Static<T>> | EventDef\n{\n if (schema)\n {\n return createEventImpl<Static<T>>(name, schema);\n }\n\n return createEventImpl<void>(name);\n}\n","/**\n * SSE Event Route Map\n *\n * Static route map for SSE token endpoint.\n * Merge into RPC proxy routeMap so `eventsToken` resolves to `POST /events/token`.\n *\n * @example\n * ```typescript\n * // app/api/rpc/[routeName]/route.ts\n * import { createRpcProxy } from '@spfn/core/nextjs/server';\n * import { eventRouteMap } from '@spfn/core/event';\n * import { authRouteMap } from '@spfn/auth';\n * import { routeMap } from '@/generated/route-map';\n *\n * export const { GET, POST } = createRpcProxy({\n * routeMap: { ...routeMap, ...authRouteMap, ...eventRouteMap },\n * });\n * ```\n */\nexport const eventRouteMap = {\n eventsToken: { method: 'POST' as const, path: '/events/token' },\n};\n","/**\n * Event Router\n *\n * Type-safe event router for SSE subscription\n *\n * @example\n * ```typescript\n * import { defineEvent, defineEventRouter } from '@spfn/core/event';\n * import { Type } from '@sinclair/typebox';\n *\n * const userCreated = defineEvent('user.created', Type.Object({\n * userId: Type.String(),\n * }));\n *\n * const orderPlaced = defineEvent('order.placed', Type.Object({\n * orderId: Type.String(),\n * amount: Type.Number(),\n * }));\n *\n * export const eventRouter = defineEventRouter({\n * userCreated,\n * orderPlaced,\n * });\n *\n * export type EventRouter = typeof eventRouter;\n * ```\n */\n\nimport type { EventDef } from './types';\n\n/**\n * Event Router Definition\n */\nexport interface EventRouterDef<TEvents extends Record<string, EventDef<any>>>\n{\n /**\n * Event definitions\n */\n readonly events: TEvents;\n\n /**\n * Event names as array\n */\n readonly eventNames: (keyof TEvents)[];\n\n /**\n * Type inference helper - payload types by event name\n */\n readonly _types: {\n [K in keyof TEvents]: TEvents[K]['_payload'];\n };\n}\n\n/**\n * Infer event names from EventRouter\n */\nexport type InferEventNames<T> = T extends EventRouterDef<infer E>\n ? keyof E & string\n : never;\n\n/**\n * Infer payload type for specific event\n */\nexport type InferEventPayload<\n T extends EventRouterDef<any>,\n K extends InferEventNames<T>\n> = T['_types'][K];\n\n/**\n * Infer all event payloads map\n */\nexport type InferEventPayloads<T extends EventRouterDef<any>> = T['_types'];\n\n/**\n * Define an event router for SSE subscription\n *\n * @example\n * ```typescript\n * export const eventRouter = defineEventRouter({\n * userCreated,\n * orderPlaced,\n * });\n *\n * // Type inference\n * type Names = InferEventNames<typeof eventRouter>;\n * // 'userCreated' | 'orderPlaced'\n *\n * type Payload = InferEventPayload<typeof eventRouter, 'userCreated'>;\n * // { userId: string }\n * ```\n */\nexport function defineEventRouter<\n TEvents extends Record<string, EventDef<any>>\n>(events: TEvents): EventRouterDef<TEvents>\n{\n return {\n events,\n eventNames: Object.keys(events) as (keyof TEvents)[],\n _types: {} as EventRouterDef<TEvents>['_types'],\n };\n}"]}
@@ -0,0 +1,157 @@
1
+ import { E as EventRouterDef, I as InferEventNames } from '../../router-Di7ENoah.js';
2
+ import { k as SSESubscribeOptions, m as SSEUnsubscribe, l as SSEConnectionState, h as SSEClientConfig } from '../../types-DAVwA-_7.js';
3
+ import '@sinclair/typebox';
4
+ import 'hono';
5
+
6
+ /**
7
+ * SSE Client
8
+ *
9
+ * Type-safe EventSource wrapper for event subscription
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { createSSEClient } from '@spfn/core/event/sse/client';
14
+ * import type { EventRouter } from '@/server/events';
15
+ *
16
+ * // Uses defaults: NEXT_PUBLIC_SPFN_API_URL + /events/stream
17
+ * const client = createSSEClient<EventRouter>();
18
+ *
19
+ * // Or with custom host/pathname
20
+ * const client = createSSEClient<EventRouter>({
21
+ * host: 'https://api.example.com',
22
+ * pathname: '/sse',
23
+ * });
24
+ *
25
+ * // With token authentication (recommended: use createAuthSSEClient)
26
+ * import { createAuthSSEClient } from '@spfn/core/event/sse/client';
27
+ * const client = createAuthSSEClient<EventRouter>();
28
+ *
29
+ * const unsubscribe = client.subscribe({
30
+ * events: ['userCreated', 'orderPlaced'],
31
+ * handlers: {
32
+ * userCreated: (payload) => console.log('User:', payload.userId),
33
+ * orderPlaced: (payload) => console.log('Order:', payload.orderId),
34
+ * },
35
+ * });
36
+ *
37
+ * // Later: cleanup
38
+ * unsubscribe();
39
+ * ```
40
+ */
41
+
42
+ /**
43
+ * SSE Client instance
44
+ */
45
+ interface SSEClient<TRouter extends EventRouterDef<any>> {
46
+ /**
47
+ * Subscribe to events
48
+ */
49
+ subscribe(options: SSESubscribeOptions<TRouter>): SSEUnsubscribe;
50
+ /**
51
+ * Get current connection state
52
+ */
53
+ getState(): SSEConnectionState;
54
+ /**
55
+ * Close all connections
56
+ */
57
+ close(): void;
58
+ }
59
+ /**
60
+ * Create type-safe SSE client
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * // Uses defaults (NEXT_PUBLIC_SPFN_API_URL + /events/stream)
65
+ * const client = createSSEClient<EventRouter>();
66
+ *
67
+ * // Or with custom configuration
68
+ * const client = createSSEClient<EventRouter>({
69
+ * host: 'https://api.example.com',
70
+ * pathname: '/sse',
71
+ * reconnect: true,
72
+ * reconnectDelay: 3000,
73
+ * });
74
+ *
75
+ * // Subscribe to events
76
+ * const unsubscribe = client.subscribe({
77
+ * events: ['userCreated', 'orderPlaced'],
78
+ * handlers: {
79
+ * userCreated: (payload) => {
80
+ * console.log('New user:', payload.userId);
81
+ * },
82
+ * orderPlaced: (payload) => {
83
+ * console.log('New order:', payload.orderId);
84
+ * },
85
+ * },
86
+ * onOpen: () => console.log('Connected'),
87
+ * onError: (err) => console.error('Error:', err),
88
+ * onReconnect: (attempt) => console.log('Reconnecting...', attempt),
89
+ * });
90
+ *
91
+ * // Cleanup
92
+ * unsubscribe();
93
+ * ```
94
+ */
95
+ declare function createSSEClient<TRouter extends EventRouterDef<any>>(config?: SSEClientConfig): SSEClient<TRouter>;
96
+ /**
97
+ * Simple subscribe function for one-off subscriptions
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * import { subscribeToEvents } from '@spfn/core/event/sse/client';
102
+ * import type { EventRouter } from '@/server/events';
103
+ *
104
+ * // Using defaults
105
+ * const unsubscribe = subscribeToEvents<EventRouter>(
106
+ * ['userCreated', 'orderPlaced'],
107
+ * {
108
+ * userCreated: (payload) => console.log('User:', payload),
109
+ * orderPlaced: (payload) => console.log('Order:', payload),
110
+ * }
111
+ * );
112
+ *
113
+ * // With custom host
114
+ * const unsubscribe = subscribeToEvents<EventRouter>(
115
+ * ['userCreated'],
116
+ * { userCreated: (payload) => console.log('User:', payload) },
117
+ * { host: 'https://api.example.com' }
118
+ * );
119
+ * ```
120
+ */
121
+ declare function subscribeToEvents<TRouter extends EventRouterDef<any>>(events: InferEventNames<TRouter>[], handlers: SSESubscribeOptions<TRouter>['handlers'], options?: SSEClientConfig): SSEUnsubscribe;
122
+ /**
123
+ * SSE client configuration for authenticated connections
124
+ *
125
+ * Same as SSEClientConfig but without acquireToken (auto-configured).
126
+ */
127
+ interface AuthSSEClientConfig extends Omit<SSEClientConfig, 'acquireToken'> {
128
+ /**
129
+ * RPC proxy base URL for token acquisition
130
+ * @default '/api/rpc'
131
+ */
132
+ rpcBaseUrl?: string;
133
+ }
134
+ /**
135
+ * Create SSE client with built-in token authentication
136
+ *
137
+ * Acquires one-time SSE tokens via RPC proxy automatically.
138
+ * Requires eventRouteMap to be merged into RPC proxy config.
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * import { createAuthSSEClient } from '@spfn/core/event/sse/client';
143
+ * import type { EventRouter } from '@/server/events';
144
+ *
145
+ * const client = createAuthSSEClient<EventRouter>();
146
+ *
147
+ * client.subscribe({
148
+ * events: ['userCreated'],
149
+ * handlers: {
150
+ * userCreated: (payload) => console.log(payload),
151
+ * },
152
+ * });
153
+ * ```
154
+ */
155
+ declare function createAuthSSEClient<TRouter extends EventRouterDef<any>>(config?: AuthSSEClientConfig): SSEClient<TRouter>;
156
+
157
+ export { type AuthSSEClientConfig, type SSEClient, createAuthSSEClient, createSSEClient, subscribeToEvents };