@spfn/core 0.2.0-beta.45 → 0.2.0-beta.47

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.
@@ -148,4 +148,119 @@ type InferEventPayloads<T extends EventRouterDef<any>> = T['_types'];
148
148
  */
149
149
  declare function defineEventRouter<TEvents extends Record<string, EventDef<any>>>(events: TEvents): EventRouterDef<TEvents>;
150
150
 
151
- export { type EventRouterDef as E, type InferEventNames as I, type JobQueueSender as J, type PubSubCache as P, type EventDef as a, type InferEventPayload as b, type InferEventPayloads as c, defineEventRouter as d, type EventHandler as e, type InferEventPayload$1 as f };
151
+ /**
152
+ * SSE Token Manager
153
+ *
154
+ * Auth-agnostic token issuance and verification for SSE connections.
155
+ * Issues one-time-use tokens with TTL for Token Exchange pattern.
156
+ *
157
+ * @example
158
+ * ```typescript
159
+ * const manager = new SSETokenManager({ ttl: 30000 });
160
+ *
161
+ * // Issue token for authenticated user
162
+ * const token = await manager.issue('user-123');
163
+ *
164
+ * // Verify and consume token (one-time use)
165
+ * const subject = await manager.verify(token); // 'user-123'
166
+ * const again = await manager.verify(token); // null (already consumed)
167
+ *
168
+ * // Cleanup on shutdown
169
+ * manager.destroy();
170
+ * ```
171
+ */
172
+ /**
173
+ * Minimal cache client interface (compatible with ioredis Redis | Cluster)
174
+ */
175
+ type CacheClient = {
176
+ set(key: string, value: string, ...args: any[]): Promise<any>;
177
+ getdel?(key: string): Promise<string | null>;
178
+ get(key: string): Promise<string | null>;
179
+ del(key: string | string[]): Promise<number>;
180
+ };
181
+ /**
182
+ * Stored SSE token data
183
+ */
184
+ interface SSEToken {
185
+ token: string;
186
+ subject: string;
187
+ expiresAt: number;
188
+ }
189
+ /**
190
+ * Token storage interface
191
+ *
192
+ * Implement this for custom storage backends (e.g., Redis for multi-instance).
193
+ */
194
+ interface SSETokenStore {
195
+ /** Store a token */
196
+ set(token: string, data: SSEToken): Promise<void>;
197
+ /** Get and delete a token (one-time use) */
198
+ consume(token: string): Promise<SSEToken | null>;
199
+ /** Remove expired tokens */
200
+ cleanup(): Promise<void>;
201
+ }
202
+ /**
203
+ * SSETokenManager configuration
204
+ */
205
+ interface SSETokenManagerConfig {
206
+ /**
207
+ * Token time-to-live in milliseconds
208
+ * @default 30000
209
+ */
210
+ ttl?: number;
211
+ /**
212
+ * Custom token store (default: in-memory Map)
213
+ */
214
+ store?: SSETokenStore;
215
+ /**
216
+ * Cleanup interval in milliseconds
217
+ * @default 60000
218
+ */
219
+ cleanupInterval?: number;
220
+ }
221
+ /**
222
+ * Redis/Valkey-backed token store for multi-instance deployments.
223
+ *
224
+ * Uses SET EX for automatic TTL expiry and GETDEL for atomic one-time consumption.
225
+ * No cleanup needed — Redis handles expiration automatically.
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * import { getCache } from '@spfn/core/cache';
230
+ *
231
+ * const cache = getCache();
232
+ * if (cache) {
233
+ * const store = new CacheTokenStore(cache);
234
+ * const manager = new SSETokenManager({ store });
235
+ * }
236
+ * ```
237
+ */
238
+ declare class CacheTokenStore implements SSETokenStore {
239
+ private cache;
240
+ private prefix;
241
+ constructor(cache: CacheClient);
242
+ set(token: string, data: SSEToken): Promise<void>;
243
+ consume(token: string): Promise<SSEToken | null>;
244
+ cleanup(): Promise<void>;
245
+ }
246
+ declare class SSETokenManager {
247
+ private store;
248
+ private ttl;
249
+ private cleanupTimer;
250
+ constructor(config?: SSETokenManagerConfig);
251
+ /**
252
+ * Issue a new one-time-use token for the given subject
253
+ */
254
+ issue(subject: string): Promise<string>;
255
+ /**
256
+ * Verify and consume a token
257
+ * @returns subject string if valid, null if invalid/expired/already consumed
258
+ */
259
+ verify(token: string): Promise<string | null>;
260
+ /**
261
+ * Cleanup timer and resources
262
+ */
263
+ destroy(): void;
264
+ }
265
+
266
+ export { CacheTokenStore as C, type EventRouterDef as E, type InferEventNames as I, type JobQueueSender as J, type PubSubCache as P, SSETokenManager as S, type EventDef as a, type InferEventPayload as b, type InferEventPayloads as c, defineEventRouter as d, type EventHandler as e, type InferEventPayload$1 as f, type SSEToken as g, type SSETokenStore as h, type SSETokenManagerConfig as i };
@@ -1,120 +1,5 @@
1
1
  import { Context } from 'hono';
2
- import { E as EventRouterDef, I as InferEventNames, b as InferEventPayload } from './router-Di7ENoah.js';
3
-
4
- /**
5
- * SSE Token Manager
6
- *
7
- * Auth-agnostic token issuance and verification for SSE connections.
8
- * Issues one-time-use tokens with TTL for Token Exchange pattern.
9
- *
10
- * @example
11
- * ```typescript
12
- * const manager = new SSETokenManager({ ttl: 30000 });
13
- *
14
- * // Issue token for authenticated user
15
- * const token = await manager.issue('user-123');
16
- *
17
- * // Verify and consume token (one-time use)
18
- * const subject = await manager.verify(token); // 'user-123'
19
- * const again = await manager.verify(token); // null (already consumed)
20
- *
21
- * // Cleanup on shutdown
22
- * manager.destroy();
23
- * ```
24
- */
25
- /**
26
- * Minimal cache client interface (compatible with ioredis Redis | Cluster)
27
- */
28
- type CacheClient = {
29
- set(key: string, value: string, ...args: any[]): Promise<any>;
30
- getdel?(key: string): Promise<string | null>;
31
- get(key: string): Promise<string | null>;
32
- del(key: string | string[]): Promise<number>;
33
- };
34
- /**
35
- * Stored SSE token data
36
- */
37
- interface SSEToken {
38
- token: string;
39
- subject: string;
40
- expiresAt: number;
41
- }
42
- /**
43
- * Token storage interface
44
- *
45
- * Implement this for custom storage backends (e.g., Redis for multi-instance).
46
- */
47
- interface SSETokenStore {
48
- /** Store a token */
49
- set(token: string, data: SSEToken): Promise<void>;
50
- /** Get and delete a token (one-time use) */
51
- consume(token: string): Promise<SSEToken | null>;
52
- /** Remove expired tokens */
53
- cleanup(): Promise<void>;
54
- }
55
- /**
56
- * SSETokenManager configuration
57
- */
58
- interface SSETokenManagerConfig {
59
- /**
60
- * Token time-to-live in milliseconds
61
- * @default 30000
62
- */
63
- ttl?: number;
64
- /**
65
- * Custom token store (default: in-memory Map)
66
- */
67
- store?: SSETokenStore;
68
- /**
69
- * Cleanup interval in milliseconds
70
- * @default 60000
71
- */
72
- cleanupInterval?: number;
73
- }
74
- /**
75
- * Redis/Valkey-backed token store for multi-instance deployments.
76
- *
77
- * Uses SET EX for automatic TTL expiry and GETDEL for atomic one-time consumption.
78
- * No cleanup needed — Redis handles expiration automatically.
79
- *
80
- * @example
81
- * ```typescript
82
- * import { getCache } from '@spfn/core/cache';
83
- *
84
- * const cache = getCache();
85
- * if (cache) {
86
- * const store = new CacheTokenStore(cache);
87
- * const manager = new SSETokenManager({ store });
88
- * }
89
- * ```
90
- */
91
- declare class CacheTokenStore implements SSETokenStore {
92
- private cache;
93
- private prefix;
94
- constructor(cache: CacheClient);
95
- set(token: string, data: SSEToken): Promise<void>;
96
- consume(token: string): Promise<SSEToken | null>;
97
- cleanup(): Promise<void>;
98
- }
99
- declare class SSETokenManager {
100
- private store;
101
- private ttl;
102
- private cleanupTimer;
103
- constructor(config?: SSETokenManagerConfig);
104
- /**
105
- * Issue a new one-time-use token for the given subject
106
- */
107
- issue(subject: string): Promise<string>;
108
- /**
109
- * Verify and consume a token
110
- * @returns subject string if valid, null if invalid/expired/already consumed
111
- */
112
- verify(token: string): Promise<string | null>;
113
- /**
114
- * Cleanup timer and resources
115
- */
116
- destroy(): void;
117
- }
2
+ import { h as SSETokenStore, S as SSETokenManager, E as EventRouterDef, I as InferEventNames, b as InferEventPayload } from './token-manager-DSwIDD-_.js';
118
3
 
119
4
  /**
120
5
  * SSE Types
@@ -369,4 +254,4 @@ type SSEConnectionState = 'connecting' | 'open' | 'closed' | 'error';
369
254
  */
370
255
  type SSEUnsubscribe = () => void;
371
256
 
372
- export { CacheTokenStore as C, type SSEHandlerConfig as S, type SSEAuthConfig as a, SSETokenManager as b, type SSEToken as c, type SSETokenStore as d, type SSETokenManagerConfig as e, type SSEMessage as f, type SSEHandlerAuthConfig as g, type SSEClientConfig as h, type SSEEventHandler as i, type SSEEventHandlers as j, type SSESubscribeOptions as k, type SSEConnectionState as l, type SSEUnsubscribe as m };
257
+ export type { SSEHandlerConfig as S, SSEAuthConfig as a, SSEMessage as b, SSEHandlerAuthConfig as c, SSEClientConfig as d, SSEEventHandler as e, SSEEventHandlers as f, SSESubscribeOptions as g, SSEConnectionState as h, SSEUnsubscribe as i };
@@ -0,0 +1,151 @@
1
+ import { Context } from 'hono';
2
+ import { a as EventDef, E as EventRouterDef, h as SSETokenStore, S as SSETokenManager, I as InferEventNames, b as InferEventPayload } from './token-manager-DSwIDD-_.js';
3
+
4
+ /**
5
+ * WebSocket Types
6
+ */
7
+
8
+ /**
9
+ * WebSocket Router Definition
10
+ *
11
+ * Extends EventRouterDef with client→server message handlers.
12
+ */
13
+ interface WSRouterDef<TEvents extends Record<string, EventDef<any>>, TMessages extends WSMessageHandlers = WSMessageHandlers> extends EventRouterDef<TEvents> {
14
+ messages: TMessages;
15
+ }
16
+ /**
17
+ * Low-level WS connection handle passed to message handlers
18
+ */
19
+ interface WSRawConnection {
20
+ send(type: string, payload: unknown): void;
21
+ close(code?: number, reason?: string): void;
22
+ }
23
+ /**
24
+ * Context passed to each client→server message handler
25
+ */
26
+ interface WSMessageContext<TPayload = unknown> {
27
+ payload: TPayload;
28
+ subject?: string;
29
+ ws: WSRawConnection;
30
+ }
31
+ /**
32
+ * Single message handler function
33
+ */
34
+ type WSMessageHandlerFn<TPayload = unknown> = (ctx: WSMessageContext<TPayload>) => void | Promise<void>;
35
+ /**
36
+ * Map of message type name → handler
37
+ */
38
+ type WSMessageHandlers = Record<string, WSMessageHandlerFn<any>>;
39
+ /**
40
+ * WebSocket auth configuration (internal, non-generic)
41
+ */
42
+ interface WSHandlerAuthConfig {
43
+ enabled?: boolean;
44
+ tokenTtl?: number;
45
+ store?: SSETokenStore;
46
+ tokenManager?: SSETokenManager | (() => SSETokenManager);
47
+ /**
48
+ * Extract subject from Hono context (used on token-issue endpoint)
49
+ * @default (c) => c.get('auth')?.userId ?? null
50
+ */
51
+ getSubject?: (c: Context) => string | null;
52
+ /**
53
+ * Authorize event subscriptions on connect
54
+ * Return allowed events subset. Empty array = 403 rejection.
55
+ */
56
+ authorize?: (subject: string, events: string[]) => Promise<string[]> | string[];
57
+ /**
58
+ * Per-event payload filter (called on every emission)
59
+ * Return false to skip sending the event to this client.
60
+ */
61
+ filter?: Record<string, (subject: string, payload: unknown) => boolean>;
62
+ }
63
+ /**
64
+ * WebSocket auth configuration (user-facing, generic)
65
+ */
66
+ interface WSAuthConfig<TRouter extends WSRouterDef<any, any>> {
67
+ enabled?: boolean;
68
+ tokenTtl?: number;
69
+ store?: SSETokenStore;
70
+ tokenManager?: SSETokenManager | (() => SSETokenManager);
71
+ getSubject?: (c: Context) => string | null;
72
+ authorize?: (subject: string, events: InferEventNames<TRouter>[]) => Promise<InferEventNames<TRouter>[]> | InferEventNames<TRouter>[];
73
+ filter?: {
74
+ [K in InferEventNames<TRouter>]?: (subject: string, payload: InferEventPayload<TRouter, K>) => boolean;
75
+ };
76
+ }
77
+ /**
78
+ * Configuration for the WebSocket server handler
79
+ */
80
+ interface WSHandlerConfig {
81
+ /**
82
+ * Keep-alive ping interval in ms
83
+ * @default 30000
84
+ */
85
+ pingInterval?: number;
86
+ /**
87
+ * Authentication and authorization configuration
88
+ */
89
+ auth?: WSHandlerAuthConfig;
90
+ }
91
+ /**
92
+ * WebSocket client configuration
93
+ */
94
+ interface WSClientConfig {
95
+ /**
96
+ * Backend API host URL (ws:// or wss://)
97
+ * @default derived from NEXT_PUBLIC_SPFN_API_URL
98
+ */
99
+ host?: string;
100
+ /**
101
+ * WS endpoint pathname
102
+ * @default '/ws'
103
+ */
104
+ pathname?: string;
105
+ /**
106
+ * Auto reconnect on disconnect
107
+ * @default true
108
+ */
109
+ reconnect?: boolean;
110
+ /**
111
+ * Reconnect delay in ms
112
+ * @default 3000
113
+ */
114
+ reconnectDelay?: number;
115
+ /**
116
+ * Maximum reconnect attempts (0 = infinite)
117
+ * @default 0
118
+ */
119
+ maxReconnectAttempts?: number;
120
+ /**
121
+ * Acquire a one-time token before connecting
122
+ */
123
+ acquireToken?: () => Promise<string>;
124
+ }
125
+ /**
126
+ * WebSocket connection state
127
+ */
128
+ type WSConnectionState = 'connecting' | 'open' | 'closed' | 'error';
129
+ /**
130
+ * Event handlers map for WSRouterDef
131
+ */
132
+ type WSEventHandlers<TRouter extends WSRouterDef<any, any>> = {
133
+ [K in InferEventNames<TRouter>]?: (payload: InferEventPayload<TRouter, K>) => void;
134
+ };
135
+ /**
136
+ * Subscribe options
137
+ */
138
+ interface WSSubscribeOptions<TRouter extends WSRouterDef<any, any>> {
139
+ events: InferEventNames<TRouter>[];
140
+ handlers: WSEventHandlers<TRouter>;
141
+ onOpen?: () => void;
142
+ onError?: (error: Event) => void;
143
+ onClose?: () => void;
144
+ onReconnect?: (attempt: number) => void;
145
+ }
146
+ /**
147
+ * Unsubscribe function
148
+ */
149
+ type WSUnsubscribe = () => void;
150
+
151
+ export type { WSRouterDef as W, WSHandlerConfig as a, WSMessageHandlers as b, WSAuthConfig as c, WSHandlerAuthConfig as d, WSMessageContext as e, WSMessageHandlerFn as f, WSRawConnection as g, WSClientConfig as h, WSConnectionState as i, WSEventHandlers as j, WSSubscribeOptions as k, WSUnsubscribe as l };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spfn/core",
3
- "version": "0.2.0-beta.45",
3
+ "version": "0.2.0-beta.47",
4
4
  "description": "SPFN Framework Core - File-based routing, transactions, repository pattern",
5
5
  "type": "module",
6
6
  "exports": {
@@ -98,6 +98,16 @@
98
98
  "types": "./dist/event/sse/client.d.ts",
99
99
  "import": "./dist/event/sse/client.js",
100
100
  "require": "./dist/event/sse/client.js"
101
+ },
102
+ "./event/ws": {
103
+ "types": "./dist/event/ws/index.d.ts",
104
+ "import": "./dist/event/ws/index.js",
105
+ "require": "./dist/event/ws/index.js"
106
+ },
107
+ "./event/ws/client": {
108
+ "types": "./dist/event/ws/client.d.ts",
109
+ "import": "./dist/event/ws/client.js",
110
+ "require": "./dist/event/ws/client.js"
101
111
  }
102
112
  },
103
113
  "keywords": [
@@ -156,11 +166,13 @@
156
166
  "zod": "^4.1.11"
157
167
  },
158
168
  "optionalDependencies": {
159
- "ioredis": "^5.4.1"
169
+ "ioredis": "^5.4.1",
170
+ "ws": "^8.0.0"
160
171
  },
161
172
  "devDependencies": {
162
173
  "@types/micromatch": "^4.0.9",
163
174
  "@types/node": "^20.11.0",
175
+ "@types/ws": "^8.18.1",
164
176
  "@vitest/coverage-v8": "^4.0.6",
165
177
  "drizzle-kit": "^0.31.6",
166
178
  "madge": "^8.0.0",