@spfn/core 0.1.0-alpha.83 → 0.1.0-alpha.84

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.
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Event handler function type
3
+ */
4
+ type EventHandler<T = any> = (data: T) => Promise<void> | void;
5
+ /**
6
+ * EventEmitter interface
7
+ *
8
+ * All event emitter adapters must implement this interface
9
+ */
10
+ interface EventEmitter {
11
+ /**
12
+ * Subscribe to an event
13
+ *
14
+ * @param event - Event name
15
+ * @param handler - Event handler function
16
+ */
17
+ on(event: string, handler: EventHandler): void;
18
+ /**
19
+ * Emit an event
20
+ *
21
+ * @param event - Event name
22
+ * @param data - Event data
23
+ */
24
+ emit(event: string, data?: any): Promise<void>;
25
+ /**
26
+ * Unsubscribe from an event
27
+ *
28
+ * @param event - Event name
29
+ */
30
+ off(event: string): void;
31
+ /**
32
+ * Clear all event subscriptions
33
+ */
34
+ clear(): void;
35
+ }
36
+
37
+ /**
38
+ * Event Emitter
39
+ *
40
+ * Adapter-based event emitter for decoupled communication between packages.
41
+ *
42
+ * Default adapter: InMemoryEventEmitter (single-instance)
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * // Subscribe to events
47
+ * import { on } from '@spfn/core/events';
48
+ *
49
+ * on('user:created', async (data) => {
50
+ * console.log('User created:', data.email);
51
+ * });
52
+ *
53
+ * // Emit events
54
+ * import { emit } from '@spfn/core/events';
55
+ *
56
+ * await emit('user:created', {
57
+ * userId: '123',
58
+ * email: 'user@example.com'
59
+ * });
60
+ *
61
+ * // Switch to Redis adapter (multi-instance)
62
+ * import { setEventEmitter } from '@spfn/core/events';
63
+ * import { RedisEventEmitter } from '@spfn/core/events/adapters';
64
+ *
65
+ * setEventEmitter(new RedisEventEmitter({
66
+ * host: 'localhost',
67
+ * port: 6379
68
+ * }));
69
+ * ```
70
+ */
71
+
72
+ /**
73
+ * Set the event emitter adapter
74
+ *
75
+ * @param adapter - EventEmitter adapter implementation
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * import { setEventEmitter } from '@spfn/core/events';
80
+ * import { InMemoryEventEmitter } from '@spfn/core/events/adapters';
81
+ *
82
+ * setEventEmitter(new InMemoryEventEmitter());
83
+ * ```
84
+ */
85
+ declare function setEventEmitter(adapter: EventEmitter): void;
86
+ /**
87
+ * Get the current event emitter adapter
88
+ *
89
+ * @returns Current EventEmitter instance
90
+ */
91
+ declare function getEventEmitter(): EventEmitter;
92
+ /**
93
+ * Subscribe to an event
94
+ *
95
+ * @param event - Event name
96
+ * @param handler - Event handler function
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * on('user:created', async (data) => {
101
+ * console.log('User created:', data.email);
102
+ * });
103
+ * ```
104
+ */
105
+ declare function on(event: string, handler: EventHandler): void;
106
+ /**
107
+ * Emit an event
108
+ *
109
+ * @param event - Event name
110
+ * @param data - Event data
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * await emit('user:created', {
115
+ * userId: '123',
116
+ * email: 'user@example.com'
117
+ * });
118
+ * ```
119
+ */
120
+ declare function emit(event: string, data?: any): Promise<void>;
121
+ /**
122
+ * Unsubscribe from an event
123
+ *
124
+ * @param event - Event name
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * off('user:created');
129
+ * ```
130
+ */
131
+ declare function off(event: string): void;
132
+ /**
133
+ * Clear all event subscriptions
134
+ *
135
+ * Useful for testing or cleanup
136
+ *
137
+ * @example
138
+ * ```typescript
139
+ * // In tests
140
+ * beforeEach(() => {
141
+ * clear();
142
+ * });
143
+ * ```
144
+ */
145
+ declare function clear(): void;
146
+
147
+ /**
148
+ * In-Memory Event Emitter
149
+ *
150
+ * Simple in-process event emitter for single-instance deployments.
151
+ * Events are not shared across multiple server instances.
152
+ *
153
+ * Use this adapter for:
154
+ * - Development
155
+ * - Single-instance production deployments
156
+ * - When you don't need distributed events
157
+ *
158
+ * @example
159
+ * ```typescript
160
+ * import { setEventEmitter } from '@spfn/core/events';
161
+ * import { InMemoryEventEmitter } from '@spfn/core/events/adapters';
162
+ *
163
+ * setEventEmitter(new InMemoryEventEmitter());
164
+ * ```
165
+ */
166
+
167
+ declare class InMemoryEventEmitter implements EventEmitter {
168
+ private listeners;
169
+ on(event: string, handler: EventHandler): void;
170
+ emit(event: string, data?: any): Promise<void>;
171
+ off(event: string): void;
172
+ clear(): void;
173
+ /**
174
+ * Get list of registered events (for debugging)
175
+ */
176
+ getEvents(): string[];
177
+ /**
178
+ * Get number of handlers for an event (for debugging)
179
+ */
180
+ getHandlerCount(event: string): number;
181
+ }
182
+
183
+ export { type EventEmitter, type EventHandler, InMemoryEventEmitter, clear, emit, getEventEmitter, off, on, setEventEmitter };
@@ -0,0 +1,77 @@
1
+ // src/events/adapters/memory.ts
2
+ var InMemoryEventEmitter = class {
3
+ listeners = /* @__PURE__ */ new Map();
4
+ on(event, handler) {
5
+ if (!this.listeners.has(event)) {
6
+ this.listeners.set(event, []);
7
+ }
8
+ this.listeners.get(event).push(handler);
9
+ }
10
+ async emit(event, data) {
11
+ const handlers = this.listeners.get(event) || [];
12
+ if (handlers.length === 0) {
13
+ return;
14
+ }
15
+ const results = await Promise.allSettled(
16
+ handlers.map(async (handler) => {
17
+ try {
18
+ return await handler(data);
19
+ } catch (error) {
20
+ throw error;
21
+ }
22
+ })
23
+ );
24
+ const failed = results.filter((r) => r.status === "rejected");
25
+ if (failed.length > 0) {
26
+ console.error(
27
+ `[Events] ${failed.length}/${handlers.length} handlers failed for event "${event}"`,
28
+ {
29
+ errors: failed.map((r) => r.reason)
30
+ }
31
+ );
32
+ }
33
+ }
34
+ off(event) {
35
+ this.listeners.delete(event);
36
+ }
37
+ clear() {
38
+ this.listeners.clear();
39
+ }
40
+ /**
41
+ * Get list of registered events (for debugging)
42
+ */
43
+ getEvents() {
44
+ return Array.from(this.listeners.keys());
45
+ }
46
+ /**
47
+ * Get number of handlers for an event (for debugging)
48
+ */
49
+ getHandlerCount(event) {
50
+ return this.listeners.get(event)?.length || 0;
51
+ }
52
+ };
53
+
54
+ // src/events/emitter.ts
55
+ var emitter = new InMemoryEventEmitter();
56
+ function setEventEmitter(adapter) {
57
+ emitter = adapter;
58
+ }
59
+ function getEventEmitter() {
60
+ return emitter;
61
+ }
62
+ function on(event, handler) {
63
+ emitter.on(event, handler);
64
+ }
65
+ async function emit(event, data) {
66
+ await emitter.emit(event, data);
67
+ }
68
+ function off(event) {
69
+ emitter.off(event);
70
+ }
71
+ function clear() {
72
+ emitter.clear();
73
+ }
74
+
75
+ export { InMemoryEventEmitter, clear, emit, getEventEmitter, off, on, setEventEmitter };
76
+ //# sourceMappingURL=index.js.map
77
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/events/adapters/memory.ts","../../src/events/emitter.ts"],"names":[],"mappings":";AAsBO,IAAM,uBAAN,MACP;AAAA,EACY,SAAA,uBAAgB,GAAA,EAA4B;AAAA,EAEpD,EAAA,CAAG,OAAe,OAAA,EAClB;AACI,IAAA,IAAI,CAAC,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,EAC7B;AACI,MAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAA,EAAO,EAAE,CAAA;AAAA,IAChC;AACA,IAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,CAAG,KAAK,OAAO,CAAA;AAAA,EAC3C;AAAA,EAEA,MAAM,IAAA,CAAK,KAAA,EAAe,IAAA,EAC1B;AACI,IAAA,MAAM,WAAW,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,KAAK,EAAC;AAE/C,IAAA,IAAI,QAAA,CAAS,WAAW,CAAA,EACxB;AACI,MAAA;AAAA,IACJ;AAIA,IAAA,MAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,UAAA;AAAA,MAC1B,QAAA,CAAS,GAAA,CAAI,OAAO,OAAA,KACpB;AACI,QAAA,IACA;AACI,UAAA,OAAO,MAAM,QAAQ,IAAI,CAAA;AAAA,QAC7B,SACO,KAAA,EACP;AAEI,UAAA,MAAM,KAAA;AAAA,QACV;AAAA,MACJ,CAAC;AAAA,KACL;AAGA,IAAA,MAAM,SAAS,OAAA,CAAQ,MAAA,CAAO,CAAA,CAAA,KAAK,CAAA,CAAE,WAAW,UAAU,CAAA;AAC1D,IAAA,IAAI,MAAA,CAAO,SAAS,CAAA,EACpB;AACI,MAAA,OAAA,CAAQ,KAAA;AAAA,QACJ,YAAY,MAAA,CAAO,MAAM,IAAI,QAAA,CAAS,MAAM,+BAA+B,KAAK,CAAA,CAAA,CAAA;AAAA,QAChF;AAAA,UACI,MAAA,EAAQ,MAAA,CAAO,GAAA,CAAI,CAAA,CAAA,KAAM,EAA4B,MAAM;AAAA;AAC/D,OACJ;AAAA,IACJ;AAAA,EACJ;AAAA,EAEA,IAAI,KAAA,EACJ;AACI,IAAA,IAAA,CAAK,SAAA,CAAU,OAAO,KAAK,CAAA;AAAA,EAC/B;AAAA,EAEA,KAAA,GACA;AACI,IAAA,IAAA,CAAK,UAAU,KAAA,EAAM;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,SAAA,GACA;AACI,IAAA,OAAO,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,SAAA,CAAU,MAAM,CAAA;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,KAAA,EAChB;AACI,IAAA,OAAO,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,GAAG,MAAA,IAAU,CAAA;AAAA,EAChD;AACJ;;;AC7DA,IAAI,OAAA,GAAwB,IAAI,oBAAA,EAAqB;AAe9C,SAAS,gBAAgB,OAAA,EAChC;AACI,EAAA,OAAA,GAAU,OAAA;AACd;AAOO,SAAS,eAAA,GAChB;AACI,EAAA,OAAO,OAAA;AACX;AAeO,SAAS,EAAA,CAAG,OAAe,OAAA,EAClC;AACI,EAAA,OAAA,CAAQ,EAAA,CAAG,OAAO,OAAO,CAAA;AAC7B;AAgBA,eAAsB,IAAA,CAAK,OAAe,IAAA,EAC1C;AACI,EAAA,MAAM,OAAA,CAAQ,IAAA,CAAK,KAAA,EAAO,IAAI,CAAA;AAClC;AAYO,SAAS,IAAI,KAAA,EACpB;AACI,EAAA,OAAA,CAAQ,IAAI,KAAK,CAAA;AACrB;AAeO,SAAS,KAAA,GAChB;AACI,EAAA,OAAA,CAAQ,KAAA,EAAM;AAClB","file":"index.js","sourcesContent":["/**\n * In-Memory Event Emitter\n *\n * Simple in-process event emitter for single-instance deployments.\n * Events are not shared across multiple server instances.\n *\n * Use this adapter for:\n * - Development\n * - Single-instance production deployments\n * - When you don't need distributed events\n *\n * @example\n * ```typescript\n * import { setEventEmitter } from '@spfn/core/events';\n * import { InMemoryEventEmitter } from '@spfn/core/events/adapters';\n *\n * setEventEmitter(new InMemoryEventEmitter());\n * ```\n */\n\nimport type { EventEmitter, EventHandler } from '../types';\n\nexport class InMemoryEventEmitter implements EventEmitter\n{\n private listeners = new Map<string, EventHandler[]>();\n\n on(event: string, handler: EventHandler): void\n {\n if (!this.listeners.has(event))\n {\n this.listeners.set(event, []);\n }\n this.listeners.get(event)!.push(handler);\n }\n\n async emit(event: string, data?: any): Promise<void>\n {\n const handlers = this.listeners.get(event) || [];\n\n if (handlers.length === 0)\n {\n return;\n }\n\n // Execute all handlers in parallel\n // Failures in individual handlers don't affect others\n const results = await Promise.allSettled(\n handlers.map(async (handler) =>\n {\n try\n {\n return await handler(data);\n }\n catch (error)\n {\n // Catch and re-throw for Promise.allSettled\n throw error;\n }\n })\n );\n\n // Log failed handlers for debugging\n const failed = results.filter(r => r.status === 'rejected');\n if (failed.length > 0)\n {\n console.error(\n `[Events] ${failed.length}/${handlers.length} handlers failed for event \"${event}\"`,\n {\n errors: failed.map(r => (r as PromiseRejectedResult).reason),\n }\n );\n }\n }\n\n off(event: string): void\n {\n this.listeners.delete(event);\n }\n\n clear(): void\n {\n this.listeners.clear();\n }\n\n /**\n * Get list of registered events (for debugging)\n */\n getEvents(): string[]\n {\n return Array.from(this.listeners.keys());\n }\n\n /**\n * Get number of handlers for an event (for debugging)\n */\n getHandlerCount(event: string): number\n {\n return this.listeners.get(event)?.length || 0;\n }\n}","/**\n * Event Emitter\n *\n * Adapter-based event emitter for decoupled communication between packages.\n *\n * Default adapter: InMemoryEventEmitter (single-instance)\n *\n * @example\n * ```typescript\n * // Subscribe to events\n * import { on } from '@spfn/core/events';\n *\n * on('user:created', async (data) => {\n * console.log('User created:', data.email);\n * });\n *\n * // Emit events\n * import { emit } from '@spfn/core/events';\n *\n * await emit('user:created', {\n * userId: '123',\n * email: 'user@example.com'\n * });\n *\n * // Switch to Redis adapter (multi-instance)\n * import { setEventEmitter } from '@spfn/core/events';\n * import { RedisEventEmitter } from '@spfn/core/events/adapters';\n *\n * setEventEmitter(new RedisEventEmitter({\n * host: 'localhost',\n * port: 6379\n * }));\n * ```\n */\n\nimport type { EventEmitter, EventHandler } from './types';\nimport { InMemoryEventEmitter } from './adapters/memory';\n\nlet emitter: EventEmitter = new InMemoryEventEmitter();\n\n/**\n * Set the event emitter adapter\n *\n * @param adapter - EventEmitter adapter implementation\n *\n * @example\n * ```typescript\n * import { setEventEmitter } from '@spfn/core/events';\n * import { InMemoryEventEmitter } from '@spfn/core/events/adapters';\n *\n * setEventEmitter(new InMemoryEventEmitter());\n * ```\n */\nexport function setEventEmitter(adapter: EventEmitter): void\n{\n emitter = adapter;\n}\n\n/**\n * Get the current event emitter adapter\n *\n * @returns Current EventEmitter instance\n */\nexport function getEventEmitter(): EventEmitter\n{\n return emitter;\n}\n\n/**\n * Subscribe to an event\n *\n * @param event - Event name\n * @param handler - Event handler function\n *\n * @example\n * ```typescript\n * on('user:created', async (data) => {\n * console.log('User created:', data.email);\n * });\n * ```\n */\nexport function on(event: string, handler: EventHandler): void\n{\n emitter.on(event, handler);\n}\n\n/**\n * Emit an event\n *\n * @param event - Event name\n * @param data - Event data\n *\n * @example\n * ```typescript\n * await emit('user:created', {\n * userId: '123',\n * email: 'user@example.com'\n * });\n * ```\n */\nexport async function emit(event: string, data?: any): Promise<void>\n{\n await emitter.emit(event, data);\n}\n\n/**\n * Unsubscribe from an event\n *\n * @param event - Event name\n *\n * @example\n * ```typescript\n * off('user:created');\n * ```\n */\nexport function off(event: string): void\n{\n emitter.off(event);\n}\n\n/**\n * Clear all event subscriptions\n *\n * Useful for testing or cleanup\n *\n * @example\n * ```typescript\n * // In tests\n * beforeEach(() => {\n * clear();\n * });\n * ```\n */\nexport function clear(): void\n{\n emitter.clear();\n}"]}
package/dist/index.js CHANGED
@@ -3410,11 +3410,12 @@ async function initializeInfrastructure(config) {
3410
3410
  }
3411
3411
  function startHttpServer(app, host, port) {
3412
3412
  serverLogger2.debug(`Starting server on ${host}:${port}...`);
3413
- return serve({
3413
+ const server = serve({
3414
3414
  fetch: app.fetch,
3415
3415
  port,
3416
3416
  hostname: host
3417
3417
  });
3418
+ return server;
3418
3419
  }
3419
3420
  function logServerTimeouts(timeouts) {
3420
3421
  serverLogger2.info("Server timeouts configured", {
@@ -3494,7 +3495,18 @@ function registerShutdownHandlers(shutdown) {
3494
3495
  process.on("SIGTERM", () => shutdown("SIGTERM"));
3495
3496
  process.on("SIGINT", () => shutdown("SIGINT"));
3496
3497
  process.on("uncaughtException", (error) => {
3497
- serverLogger2.error("Uncaught exception", error);
3498
+ if (error.message?.includes("EADDRINUSE")) {
3499
+ serverLogger2.error("Port conflict detected - detailed trace:", {
3500
+ error: error.message,
3501
+ stack: error.stack,
3502
+ code: error.code,
3503
+ port: error.port,
3504
+ address: error.address,
3505
+ syscall: error.syscall
3506
+ });
3507
+ } else {
3508
+ serverLogger2.error("Uncaught exception", error);
3509
+ }
3498
3510
  shutdown("UNCAUGHT_EXCEPTION");
3499
3511
  });
3500
3512
  process.on("unhandledRejection", (reason, promise) => {