@spfn/core 0.2.0-beta.11 → 0.2.0-beta.13
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.
- package/dist/event/sse/client.d.ts +50 -1
- package/dist/event/sse/client.js +57 -22
- package/dist/event/sse/client.js.map +1 -1
- package/dist/event/sse/index.d.ts +10 -4
- package/dist/event/sse/index.js +125 -12
- package/dist/event/sse/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +2 -2
- package/dist/nextjs/server.d.ts +5 -1
- package/dist/nextjs/server.js +4 -2
- package/dist/nextjs/server.js.map +1 -1
- package/dist/server/index.d.ts +3 -2
- package/dist/server/index.js +151 -15
- package/dist/server/index.js.map +1 -1
- package/dist/types-B-lVqv6b.d.ts +298 -0
- package/dist/{types-BOPTApC2.d.ts → types-DP8nzQcY.d.ts} +5 -0
- package/docs/event.md +374 -67
- package/package.json +1 -1
- package/dist/types-B-e_f2dQ.d.ts +0 -121
package/docs/event.md
CHANGED
|
@@ -1,116 +1,423 @@
|
|
|
1
1
|
# Event
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Decoupled pub/sub event system with SSE support for real-time frontend updates.
|
|
4
4
|
|
|
5
5
|
## Define Events
|
|
6
6
|
|
|
7
7
|
```typescript
|
|
8
8
|
// src/server/events/index.ts
|
|
9
|
-
import { defineEvent
|
|
9
|
+
import { defineEvent } from '@spfn/core/event';
|
|
10
|
+
import { Type } from '@sinclair/typebox';
|
|
10
11
|
|
|
11
|
-
//
|
|
12
|
-
export const userCreated = defineEvent
|
|
13
|
-
userId:
|
|
14
|
-
email:
|
|
15
|
-
}
|
|
12
|
+
// Event with typed payload
|
|
13
|
+
export const userCreated = defineEvent('user.created', Type.Object({
|
|
14
|
+
userId: Type.String(),
|
|
15
|
+
email: Type.String(),
|
|
16
|
+
}));
|
|
16
17
|
|
|
17
|
-
export const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
18
|
+
export const orderPlaced = defineEvent('order.placed', Type.Object({
|
|
19
|
+
orderId: Type.String(),
|
|
20
|
+
amount: Type.Number(),
|
|
21
|
+
}));
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}>('user.deleted');
|
|
23
|
+
// Event without payload
|
|
24
|
+
export const serverStarted = defineEvent('server.started');
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
## Emit
|
|
27
|
+
## Subscribe & Emit
|
|
28
28
|
|
|
29
29
|
```typescript
|
|
30
|
-
import {
|
|
31
|
-
import { userCreated } from './events';
|
|
30
|
+
import { userCreated, serverStarted } from './events';
|
|
32
31
|
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
// Subscribe to event
|
|
33
|
+
const unsubscribe = userCreated.subscribe((payload) => {
|
|
34
|
+
console.log('User created:', payload.userId);
|
|
35
|
+
});
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
email: user.email
|
|
41
|
-
});
|
|
37
|
+
// Emit event
|
|
38
|
+
await userCreated.emit({ userId: '123', email: 'user@example.com' });
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
// Emit event without payload
|
|
41
|
+
await serverStarted.emit();
|
|
42
|
+
|
|
43
|
+
// Unsubscribe when done
|
|
44
|
+
unsubscribe();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Multiple Subscribers
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// Multiple independent handlers
|
|
51
|
+
userCreated.subscribe(async (payload) => {
|
|
52
|
+
await sendWelcomeEmail(payload.email);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
userCreated.subscribe(async (payload) => {
|
|
56
|
+
await createDefaultSettings(payload.userId);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
userCreated.subscribe(async (payload) => {
|
|
60
|
+
await notifyAdmins(payload.userId);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// All handlers execute when event is emitted
|
|
64
|
+
await userCreated.emit({ userId: '123', email: 'user@example.com' });
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Event Router for SSE
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// src/server/events/router.ts
|
|
71
|
+
import { defineEventRouter } from '@spfn/core/event';
|
|
72
|
+
import { userCreated, orderPlaced } from './index';
|
|
73
|
+
|
|
74
|
+
export const eventRouter = defineEventRouter({
|
|
75
|
+
userCreated,
|
|
76
|
+
orderPlaced,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export type EventRouter = typeof eventRouter;
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Server Setup
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// server.config.ts
|
|
86
|
+
import { defineServerConfig } from '@spfn/core/server';
|
|
87
|
+
import { eventRouter } from './events/router';
|
|
88
|
+
|
|
89
|
+
export default defineServerConfig()
|
|
90
|
+
.routes(appRouter)
|
|
91
|
+
.jobs(jobRouter)
|
|
92
|
+
.events(eventRouter) // → GET /events/stream
|
|
93
|
+
.build();
|
|
94
|
+
|
|
95
|
+
// Custom path and options
|
|
96
|
+
.events(eventRouter, {
|
|
97
|
+
path: '/sse', // Custom endpoint path
|
|
98
|
+
pingInterval: 30000, // Keep-alive interval (default: 30s)
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## SSE Authentication
|
|
103
|
+
|
|
104
|
+
Browser `EventSource` API does not support custom headers. SPFN uses a **Token Exchange** pattern:
|
|
105
|
+
|
|
106
|
+
1. Client sends `POST /events/token` with Bearer JWT
|
|
107
|
+
2. Server returns a one-time token (30s TTL)
|
|
108
|
+
3. Client connects to `GET /events/stream?token=...&events=...`
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// server.config.ts
|
|
112
|
+
import { defineServerConfig } from '@spfn/core/server';
|
|
113
|
+
import { authenticate } from '@spfn/auth/server';
|
|
114
|
+
|
|
115
|
+
export default defineServerConfig()
|
|
116
|
+
.middlewares([authenticate])
|
|
117
|
+
.routes(appRouter)
|
|
118
|
+
.events(eventRouter, {
|
|
119
|
+
auth: { enabled: true }, // This is all you need
|
|
120
|
+
})
|
|
121
|
+
.build();
|
|
122
|
+
// → POST /events/token (protected by authenticate middleware)
|
|
123
|
+
// → GET /events/stream?token=...&events=... (token verified)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Authorization Hooks
|
|
127
|
+
|
|
128
|
+
#### `authorize` — Subscription Authorization (once on connect)
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
.events(eventRouter, {
|
|
132
|
+
auth: {
|
|
133
|
+
enabled: true,
|
|
134
|
+
authorize: async (subject, events) =>
|
|
135
|
+
{
|
|
136
|
+
// events: ('userCreated' | 'orderPlaced')[] — type inferred
|
|
137
|
+
const user = await usersRepository.findById(subject);
|
|
138
|
+
if (user.role === 'admin') return events;
|
|
139
|
+
return events.filter(e => !e.startsWith('admin.'));
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
})
|
|
45
143
|
```
|
|
46
144
|
|
|
47
|
-
|
|
145
|
+
#### `filter` — Payload Filtering (on every event emission)
|
|
48
146
|
|
|
49
147
|
```typescript
|
|
50
|
-
|
|
51
|
-
|
|
148
|
+
.events(eventRouter, {
|
|
149
|
+
auth: {
|
|
150
|
+
enabled: true,
|
|
151
|
+
filter: {
|
|
152
|
+
// payload type inferred per-event — no casting needed
|
|
153
|
+
orderPlaced: (subject, payload) => payload.userId === subject,
|
|
154
|
+
// userCreated: no filter → sent to all authenticated users
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
})
|
|
158
|
+
```
|
|
52
159
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
160
|
+
## Browser Client
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { createSSEClient } from '@spfn/core/event/sse/client';
|
|
164
|
+
import type { EventRouter } from '@/server/events/router';
|
|
165
|
+
|
|
166
|
+
// Create client (uses defaults: NEXT_PUBLIC_SPFN_API_URL + /events/stream)
|
|
167
|
+
const client = createSSEClient<EventRouter>();
|
|
168
|
+
|
|
169
|
+
// Or with custom configuration
|
|
170
|
+
const client = createSSEClient<EventRouter>({
|
|
171
|
+
host: 'https://api.example.com',
|
|
172
|
+
pathname: '/sse',
|
|
173
|
+
reconnect: true,
|
|
174
|
+
reconnectDelay: 3000,
|
|
57
175
|
});
|
|
58
176
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
177
|
+
// Subscribe to events
|
|
178
|
+
const unsubscribe = client.subscribe({
|
|
179
|
+
events: ['userCreated', 'orderPlaced'],
|
|
180
|
+
handlers: {
|
|
181
|
+
userCreated: (payload) => {
|
|
182
|
+
console.log('New user:', payload.userId);
|
|
183
|
+
},
|
|
184
|
+
orderPlaced: (payload) => {
|
|
185
|
+
console.log('New order:', payload.orderId);
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
onOpen: () => console.log('SSE connected'),
|
|
189
|
+
onError: (err) => console.error('SSE error:', err),
|
|
62
190
|
});
|
|
63
191
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
192
|
+
// Cleanup
|
|
193
|
+
unsubscribe();
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### With Authentication
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
const client = createSSEClient<EventRouter>({
|
|
200
|
+
acquireToken: async () =>
|
|
201
|
+
{
|
|
202
|
+
const res = await fetch('/api/events/token', {
|
|
203
|
+
method: 'POST',
|
|
204
|
+
credentials: 'include',
|
|
205
|
+
});
|
|
206
|
+
const data = await res.json();
|
|
207
|
+
return data.token;
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
`acquireToken` is called on every (re)connect — one-time tokens are handled automatically.
|
|
213
|
+
|
|
214
|
+
## Simple Subscribe Helper
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import { subscribeToEvents } from '@spfn/core/event/sse/client';
|
|
218
|
+
import type { EventRouter } from '@/server/events/router';
|
|
219
|
+
|
|
220
|
+
// One-liner subscription
|
|
221
|
+
const unsubscribe = subscribeToEvents<EventRouter>(
|
|
222
|
+
['userCreated'],
|
|
223
|
+
{
|
|
224
|
+
userCreated: (payload) => console.log('User:', payload),
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Job Integration
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import { defineEvent } from '@spfn/core/event';
|
|
233
|
+
import { job, defineJobRouter } from '@spfn/core/job';
|
|
234
|
+
|
|
235
|
+
// Define event
|
|
236
|
+
export const orderPlaced = defineEvent('order.placed', Type.Object({
|
|
237
|
+
orderId: Type.String(),
|
|
238
|
+
userId: Type.String(),
|
|
239
|
+
}));
|
|
240
|
+
|
|
241
|
+
// Jobs subscribe to event
|
|
242
|
+
export const sendOrderConfirmation = job('send-order-confirmation')
|
|
243
|
+
.on(orderPlaced)
|
|
244
|
+
.handler(async (payload) => {
|
|
245
|
+
await emailService.sendOrderConfirmation(payload.orderId);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
export const updateInventory = job('update-inventory')
|
|
249
|
+
.on(orderPlaced)
|
|
250
|
+
.handler(async (payload) => {
|
|
251
|
+
await inventoryService.reserve(payload.orderId);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Register jobs
|
|
255
|
+
export const jobRouter = defineJobRouter({
|
|
256
|
+
sendOrderConfirmation,
|
|
257
|
+
updateInventory,
|
|
67
258
|
});
|
|
259
|
+
|
|
260
|
+
// Emit event - all subscribed jobs execute
|
|
261
|
+
await orderPlaced.emit({ orderId: 'ord-123', userId: 'user-456' });
|
|
68
262
|
```
|
|
69
263
|
|
|
70
|
-
##
|
|
264
|
+
## Multi-Instance Support
|
|
265
|
+
|
|
266
|
+
For applications running multiple instances, use cache-based pub/sub.
|
|
71
267
|
|
|
72
268
|
```typescript
|
|
73
|
-
|
|
74
|
-
import {
|
|
75
|
-
import { userCreated, userUpdated, userDeleted } from './index';
|
|
269
|
+
import { defineEvent } from '@spfn/core/event';
|
|
270
|
+
import { getCache } from '@spfn/core/cache';
|
|
76
271
|
|
|
77
|
-
|
|
78
|
-
|
|
272
|
+
const userCreated = defineEvent('user.created', Type.Object({
|
|
273
|
+
userId: Type.String(),
|
|
274
|
+
}));
|
|
275
|
+
|
|
276
|
+
// Enable cache-based pub/sub
|
|
277
|
+
const cache = getCache();
|
|
278
|
+
if (cache)
|
|
79
279
|
{
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
280
|
+
await userCreated.useCache({
|
|
281
|
+
publish: async (channel, message) => {
|
|
282
|
+
await cache.publish(channel, JSON.stringify(message));
|
|
283
|
+
},
|
|
284
|
+
subscribe: async (channel, handler) => {
|
|
285
|
+
const subscriber = cache.duplicate();
|
|
286
|
+
await subscriber.subscribe(channel);
|
|
287
|
+
subscriber.on('message', (ch, msg) => {
|
|
288
|
+
if (ch === channel)
|
|
289
|
+
{
|
|
290
|
+
handler(JSON.parse(msg));
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
},
|
|
294
|
+
});
|
|
83
295
|
}
|
|
84
296
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
registerEventHandlers();
|
|
297
|
+
// Events now broadcast to all instances
|
|
298
|
+
await userCreated.emit({ userId: '123' });
|
|
88
299
|
```
|
|
89
300
|
|
|
90
|
-
##
|
|
301
|
+
## API Reference
|
|
302
|
+
|
|
303
|
+
### defineEvent(name)
|
|
304
|
+
|
|
305
|
+
Define an event without payload.
|
|
91
306
|
|
|
92
307
|
```typescript
|
|
93
|
-
|
|
94
|
-
|
|
308
|
+
export const serverStarted = defineEvent('server.started');
|
|
309
|
+
|
|
310
|
+
serverStarted.subscribe(() => { ... });
|
|
311
|
+
await serverStarted.emit();
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### defineEvent(name, schema)
|
|
95
315
|
|
|
96
|
-
|
|
316
|
+
Define an event with typed payload.
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
export const userCreated = defineEvent('user.created', Type.Object({
|
|
320
|
+
userId: Type.String(),
|
|
321
|
+
}));
|
|
322
|
+
|
|
323
|
+
userCreated.subscribe((payload) => { ... });
|
|
324
|
+
await userCreated.emit({ userId: '123' });
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### EventDef Methods
|
|
328
|
+
|
|
329
|
+
| Method | Description |
|
|
330
|
+
|--------|-------------|
|
|
331
|
+
| `subscribe(handler)` | Subscribe to event. Returns unsubscribe function |
|
|
332
|
+
| `unsubscribeAll()` | Remove all subscribers |
|
|
333
|
+
| `emit(payload?)` | Emit event to all subscribers |
|
|
334
|
+
| `useCache(cache)` | Enable cache-based pub/sub for multi-instance |
|
|
335
|
+
|
|
336
|
+
### SSE Client Options
|
|
337
|
+
|
|
338
|
+
| Option | Type | Default | Description |
|
|
339
|
+
|--------|------|---------|-------------|
|
|
340
|
+
| `host` | string | `NEXT_PUBLIC_SPFN_API_URL` | Backend API host URL |
|
|
341
|
+
| `pathname` | string | `/events/stream` | SSE endpoint pathname |
|
|
342
|
+
| `reconnect` | boolean | `true` | Auto reconnect on disconnect |
|
|
343
|
+
| `reconnectDelay` | number | `3000` | Reconnect delay (ms) |
|
|
344
|
+
| `maxReconnectAttempts` | number | `0` | Max attempts (0 = infinite) |
|
|
345
|
+
| `withCredentials` | boolean | `false` | Include cookies |
|
|
346
|
+
| `acquireToken` | () => Promise\<string\> | - | Acquire one-time SSE token before connecting |
|
|
347
|
+
|
|
348
|
+
### SSE Auth Options
|
|
349
|
+
|
|
350
|
+
| Option | Type | Default | Description |
|
|
351
|
+
|--------|------|---------|-------------|
|
|
352
|
+
| `enabled` | boolean | `false` | Enable token authentication |
|
|
353
|
+
| `tokenTtl` | number | `30000` | Token TTL in milliseconds |
|
|
354
|
+
| `store` | SSETokenStore | InMemory | Custom token store (e.g., Redis) |
|
|
355
|
+
| `getSubject` | (c) => string \| null | `c.get('auth')?.userId` | Extract subject from context |
|
|
356
|
+
| `authorize` | (subject, events) => events[] | - | Subscription authorization hook |
|
|
357
|
+
| `filter` | { [event]: (subject, payload) => boolean } | - | Per-event payload filter |
|
|
358
|
+
|
|
359
|
+
## Event Flow Architecture
|
|
360
|
+
|
|
361
|
+
```
|
|
362
|
+
userCreated.emit({ ... })
|
|
363
|
+
│
|
|
364
|
+
┌───────────────────┼───────────────────┐
|
|
365
|
+
▼ ▼ ▼
|
|
366
|
+
┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
367
|
+
│ Backend │ │ Job │ │ SSE │
|
|
368
|
+
│ Handler │ │ Queue │ │ Stream │
|
|
369
|
+
└──────────┘ └──────────┘ └──────────┘
|
|
370
|
+
.subscribe() .on(event) ↓
|
|
371
|
+
│ │ ┌──────────┐
|
|
372
|
+
▼ ▼ │ Browser │
|
|
373
|
+
[Logging, [Background │ Client │
|
|
374
|
+
Analytics] Processing] └──────────┘
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Best Practices
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
// 1. Use descriptive event names
|
|
97
381
|
defineEvent('user.created')
|
|
98
382
|
defineEvent('order.completed')
|
|
99
383
|
defineEvent('payment.failed')
|
|
100
384
|
|
|
101
|
-
//
|
|
102
|
-
defineEvent
|
|
385
|
+
// 2. Keep payloads minimal - just IDs, not full objects
|
|
386
|
+
defineEvent('user.deleted', Type.Object({
|
|
387
|
+
userId: Type.String(),
|
|
388
|
+
}));
|
|
103
389
|
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
await sendEmail(payload.email);
|
|
108
|
-
} catch (error) {
|
|
109
|
-
logger.error('Failed to send email', { error });
|
|
110
|
-
}
|
|
390
|
+
// 3. Handler errors are isolated - one failing handler doesn't affect others
|
|
391
|
+
userCreated.subscribe(async (payload) => {
|
|
392
|
+
throw new Error('This fails');
|
|
111
393
|
});
|
|
112
394
|
|
|
113
|
-
|
|
395
|
+
userCreated.subscribe(async (payload) => {
|
|
396
|
+
// This still executes
|
|
397
|
+
console.log('Handler 2 runs');
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// 4. Use events for side effects, not core logic
|
|
114
401
|
// Core: await userRepo.create(data);
|
|
115
|
-
// Side effect: emit(
|
|
402
|
+
// Side effect: await userCreated.emit({ ... });
|
|
403
|
+
|
|
404
|
+
// 5. Await useCache() before emitting for multi-instance
|
|
405
|
+
await userCreated.useCache(cache);
|
|
406
|
+
await userCreated.emit({ userId: '123' });
|
|
116
407
|
```
|
|
408
|
+
|
|
409
|
+
## Event vs Direct Job
|
|
410
|
+
|
|
411
|
+
| Aspect | Event + Job | Direct Job |
|
|
412
|
+
|--------|-------------|------------|
|
|
413
|
+
| Coupling | Loose (producer doesn't know consumers) | Tight (producer calls specific job) |
|
|
414
|
+
| Multiple consumers | Easy (multiple jobs subscribe) | Manual (call each job) |
|
|
415
|
+
| Extensibility | Add consumers without modifying producer | Modify producer for each consumer |
|
|
416
|
+
|
|
417
|
+
**Use Event when:**
|
|
418
|
+
- Multiple systems need to react to the same occurrence
|
|
419
|
+
- You want to decouple producers from consumers
|
|
420
|
+
|
|
421
|
+
**Use Direct Job when:**
|
|
422
|
+
- Single, known consumer
|
|
423
|
+
- Simpler mental model preferred
|
package/package.json
CHANGED
package/dist/types-B-e_f2dQ.d.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { E as EventRouterDef, I as InferEventNames, b as InferEventPayload } from './router-Di7ENoah.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SSE Types
|
|
5
|
-
*
|
|
6
|
-
* Type definitions for Server-Sent Events
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* SSE message sent from server
|
|
11
|
-
*/
|
|
12
|
-
interface SSEMessage<TEvent extends string = string, TPayload = unknown> {
|
|
13
|
-
/** Event name */
|
|
14
|
-
event: TEvent;
|
|
15
|
-
/** Event payload */
|
|
16
|
-
data: TPayload;
|
|
17
|
-
/** Optional message ID for reconnection */
|
|
18
|
-
id?: string;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* SSE Handler configuration
|
|
22
|
-
*/
|
|
23
|
-
interface SSEHandlerConfig {
|
|
24
|
-
/**
|
|
25
|
-
* Keep-alive ping interval in milliseconds
|
|
26
|
-
* @default 30000
|
|
27
|
-
*/
|
|
28
|
-
pingInterval?: number;
|
|
29
|
-
/**
|
|
30
|
-
* Custom headers for SSE response
|
|
31
|
-
*/
|
|
32
|
-
headers?: Record<string, string>;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* SSE Client configuration
|
|
36
|
-
*/
|
|
37
|
-
interface SSEClientConfig {
|
|
38
|
-
/**
|
|
39
|
-
* Backend API host URL
|
|
40
|
-
* @default NEXT_PUBLIC_SPFN_API_URL || 'http://localhost:8790'
|
|
41
|
-
* @example 'http://localhost:8790'
|
|
42
|
-
* @example 'https://api.example.com'
|
|
43
|
-
*/
|
|
44
|
-
host?: string;
|
|
45
|
-
/**
|
|
46
|
-
* SSE endpoint pathname
|
|
47
|
-
* @default '/events/stream'
|
|
48
|
-
*/
|
|
49
|
-
pathname?: string;
|
|
50
|
-
/**
|
|
51
|
-
* Full URL (overrides host + pathname)
|
|
52
|
-
* @deprecated Use host and pathname instead
|
|
53
|
-
* @example 'http://localhost:8790/events/stream'
|
|
54
|
-
*/
|
|
55
|
-
url?: string;
|
|
56
|
-
/**
|
|
57
|
-
* Auto reconnect on disconnect
|
|
58
|
-
* @default true
|
|
59
|
-
*/
|
|
60
|
-
reconnect?: boolean;
|
|
61
|
-
/**
|
|
62
|
-
* Reconnect delay in milliseconds
|
|
63
|
-
* @default 3000
|
|
64
|
-
*/
|
|
65
|
-
reconnectDelay?: number;
|
|
66
|
-
/**
|
|
67
|
-
* Maximum reconnect attempts (0 = infinite)
|
|
68
|
-
* @default 0
|
|
69
|
-
*/
|
|
70
|
-
maxReconnectAttempts?: number;
|
|
71
|
-
/**
|
|
72
|
-
* Include credentials (cookies) in request
|
|
73
|
-
* @default false
|
|
74
|
-
*/
|
|
75
|
-
withCredentials?: boolean;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Event handler function
|
|
79
|
-
*/
|
|
80
|
-
type SSEEventHandler<TPayload> = (payload: TPayload) => void;
|
|
81
|
-
/**
|
|
82
|
-
* Event handlers map for EventRouter
|
|
83
|
-
*/
|
|
84
|
-
type SSEEventHandlers<TRouter extends EventRouterDef<any>> = {
|
|
85
|
-
[K in InferEventNames<TRouter>]?: SSEEventHandler<InferEventPayload<TRouter, K>>;
|
|
86
|
-
};
|
|
87
|
-
/**
|
|
88
|
-
* Subscription options
|
|
89
|
-
*/
|
|
90
|
-
interface SSESubscribeOptions<TRouter extends EventRouterDef<any>> {
|
|
91
|
-
/**
|
|
92
|
-
* Events to subscribe
|
|
93
|
-
*/
|
|
94
|
-
events: InferEventNames<TRouter>[];
|
|
95
|
-
/**
|
|
96
|
-
* Event handlers
|
|
97
|
-
*/
|
|
98
|
-
handlers: SSEEventHandlers<TRouter>;
|
|
99
|
-
/**
|
|
100
|
-
* Called when connection opens
|
|
101
|
-
*/
|
|
102
|
-
onOpen?: () => void;
|
|
103
|
-
/**
|
|
104
|
-
* Called on connection error
|
|
105
|
-
*/
|
|
106
|
-
onError?: (error: Event) => void;
|
|
107
|
-
/**
|
|
108
|
-
* Called when reconnecting
|
|
109
|
-
*/
|
|
110
|
-
onReconnect?: (attempt: number) => void;
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* SSE connection state
|
|
114
|
-
*/
|
|
115
|
-
type SSEConnectionState = 'connecting' | 'open' | 'closed' | 'error';
|
|
116
|
-
/**
|
|
117
|
-
* Unsubscribe function
|
|
118
|
-
*/
|
|
119
|
-
type SSEUnsubscribe = () => void;
|
|
120
|
-
|
|
121
|
-
export type { SSEHandlerConfig as S, SSEMessage as a, SSEClientConfig as b, SSEEventHandler as c, SSEEventHandlers as d, SSESubscribeOptions as e, SSEConnectionState as f, SSEUnsubscribe as g };
|