@spfn/core 0.2.0-beta.20 → 0.2.0-beta.22
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/index.d.ts +27 -1
- package/dist/event/index.js +6 -1
- package/dist/event/index.js.map +1 -1
- package/dist/event/sse/client.d.ts +39 -13
- package/dist/event/sse/client.js +20 -1
- package/dist/event/sse/client.js.map +1 -1
- package/dist/event/sse/index.d.ts +2 -2
- package/dist/event/sse/index.js +34 -1
- package/dist/event/sse/index.js.map +1 -1
- package/dist/nextjs/server.d.ts +19 -51
- package/dist/nextjs/server.js +7 -57
- package/dist/nextjs/server.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +50 -3
- package/dist/server/index.js.map +1 -1
- package/dist/{types-B-lVqv6b.d.ts → types-DAVwA-_7.d.ts} +43 -2
- package/docs/event.md +31 -11
- package/docs/nextjs.md +11 -5
- package/docs/server.md +54 -2
- package/package.json +1 -1
|
@@ -22,6 +22,15 @@ import { E as EventRouterDef, I as InferEventNames, b as InferEventPayload } fro
|
|
|
22
22
|
* manager.destroy();
|
|
23
23
|
* ```
|
|
24
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
|
+
};
|
|
25
34
|
/**
|
|
26
35
|
* Stored SSE token data
|
|
27
36
|
*/
|
|
@@ -62,6 +71,31 @@ interface SSETokenManagerConfig {
|
|
|
62
71
|
*/
|
|
63
72
|
cleanupInterval?: number;
|
|
64
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
|
+
}
|
|
65
99
|
declare class SSETokenManager {
|
|
66
100
|
private store;
|
|
67
101
|
private ttl;
|
|
@@ -237,10 +271,17 @@ interface SSEClientConfig {
|
|
|
237
271
|
* Called on every (re)connect. The returned token is appended
|
|
238
272
|
* to the SSE URL as `?token=...`.
|
|
239
273
|
*
|
|
274
|
+
* For automatic token acquisition via RPC proxy, use `createAuthSSEClient` instead.
|
|
275
|
+
*
|
|
240
276
|
* @example
|
|
241
277
|
* ```typescript
|
|
278
|
+
* // Recommended: use createAuthSSEClient for automatic token handling
|
|
279
|
+
* import { createAuthSSEClient } from '@spfn/core/event/sse/client';
|
|
280
|
+
* const client = createAuthSSEClient<EventRouter>();
|
|
281
|
+
*
|
|
282
|
+
* // Manual: provide acquireToken directly
|
|
242
283
|
* acquireToken: async () => {
|
|
243
|
-
* const res = await fetch('/api/
|
|
284
|
+
* const res = await fetch('/api/rpc/eventsToken', {
|
|
244
285
|
* method: 'POST',
|
|
245
286
|
* credentials: 'include',
|
|
246
287
|
* });
|
|
@@ -295,4 +336,4 @@ type SSEConnectionState = 'connecting' | 'open' | 'closed' | 'error';
|
|
|
295
336
|
*/
|
|
296
337
|
type SSEUnsubscribe = () => void;
|
|
297
338
|
|
|
298
|
-
export { 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 };
|
|
339
|
+
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 };
|
package/docs/event.md
CHANGED
|
@@ -195,21 +195,30 @@ unsubscribe();
|
|
|
195
195
|
|
|
196
196
|
### With Authentication
|
|
197
197
|
|
|
198
|
+
Use `createAuthSSEClient` which handles token acquisition automatically via the RPC proxy:
|
|
199
|
+
|
|
198
200
|
```typescript
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
201
|
+
import { createAuthSSEClient } from '@spfn/core/event/sse/client';
|
|
202
|
+
import type { EventRouter } from '@/server/events/router';
|
|
203
|
+
|
|
204
|
+
const client = createAuthSSEClient<EventRouter>();
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
This requires `eventRouteMap` to be merged into your RPC proxy (one-time setup):
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
// app/api/rpc/[routeName]/route.ts
|
|
211
|
+
import '@spfn/auth/nextjs/api';
|
|
212
|
+
import { createRpcProxy } from '@spfn/core/nextjs/server';
|
|
213
|
+
import { eventRouteMap } from '@spfn/core/event';
|
|
214
|
+
import { routeMap } from '@/generated/route-map';
|
|
215
|
+
|
|
216
|
+
export const { GET, POST } = createRpcProxy({
|
|
217
|
+
routeMap: { ...routeMap, ...eventRouteMap },
|
|
209
218
|
});
|
|
210
219
|
```
|
|
211
220
|
|
|
212
|
-
|
|
221
|
+
Tokens are acquired on every (re)connect — one-time tokens are handled automatically.
|
|
213
222
|
|
|
214
223
|
## Simple Subscribe Helper
|
|
215
224
|
|
|
@@ -298,6 +307,17 @@ if (cache)
|
|
|
298
307
|
await userCreated.emit({ userId: '123' });
|
|
299
308
|
```
|
|
300
309
|
|
|
310
|
+
### SSE Token Store
|
|
311
|
+
|
|
312
|
+
SSE 토큰 저장소도 멀티 인스턴스에서 공유되어야 합니다.
|
|
313
|
+
캐시가 연결되면 `CacheTokenStore`가 **자동 사용**됩니다.
|
|
314
|
+
|
|
315
|
+
| 환경 | 토큰 저장소 | 설정 |
|
|
316
|
+
|------|------------|------|
|
|
317
|
+
| `CACHE_URL` 없음 | `InMemoryTokenStore` | 자동 |
|
|
318
|
+
| `CACHE_URL` 설정됨 | `CacheTokenStore` | 자동 감지 |
|
|
319
|
+
| 커스텀 | `SSETokenStore` 구현 | `auth.store` 옵션 |
|
|
320
|
+
|
|
301
321
|
## API Reference
|
|
302
322
|
|
|
303
323
|
### defineEvent(name)
|
package/docs/nextjs.md
CHANGED
|
@@ -8,12 +8,14 @@ RPC proxy and type-safe API client for Next.js.
|
|
|
8
8
|
|
|
9
9
|
```typescript
|
|
10
10
|
// app/api/rpc/[routeName]/route.ts
|
|
11
|
-
import
|
|
11
|
+
import '@spfn/auth/nextjs/api';
|
|
12
12
|
import { createRpcProxy } from '@spfn/core/nextjs/server';
|
|
13
|
+
import { authRouteMap } from '@spfn/auth';
|
|
14
|
+
import { eventRouteMap } from '@spfn/core/event';
|
|
15
|
+
import { routeMap } from '@/generated/route-map';
|
|
13
16
|
|
|
14
|
-
export const { GET, POST
|
|
15
|
-
|
|
16
|
-
apiUrl: process.env.SPFN_API_URL || 'http://localhost:8790'
|
|
17
|
+
export const { GET, POST } = createRpcProxy({
|
|
18
|
+
routeMap: { ...routeMap, ...authRouteMap, ...eventRouteMap },
|
|
17
19
|
});
|
|
18
20
|
```
|
|
19
21
|
|
|
@@ -135,7 +137,7 @@ const updated = await api.updateUser.call({
|
|
|
135
137
|
|
|
136
138
|
```typescript
|
|
137
139
|
export const { GET, POST } = createRpcProxy({
|
|
138
|
-
|
|
140
|
+
routeMap: { ...routeMap, ...authRouteMap },
|
|
139
141
|
apiUrl: process.env.SPFN_API_URL,
|
|
140
142
|
interceptors: {
|
|
141
143
|
request: async (request, context) => {
|
|
@@ -208,6 +210,10 @@ SPFN_API_URL=http://localhost:8790
|
|
|
208
210
|
|
|
209
211
|
# For production
|
|
210
212
|
SPFN_API_URL=https://api.example.com
|
|
213
|
+
|
|
214
|
+
# RPC proxy timeout (AbortController, default: 120s)
|
|
215
|
+
# Should be shorter than FETCH_HEADERS_TIMEOUT for meaningful 504 responses
|
|
216
|
+
RPC_PROXY_TIMEOUT=120000
|
|
211
217
|
```
|
|
212
218
|
|
|
213
219
|
## Best Practices
|
package/docs/server.md
CHANGED
|
@@ -246,12 +246,30 @@ if (shutdown.isShuttingDown())
|
|
|
246
246
|
|
|
247
247
|
```typescript
|
|
248
248
|
defineServerConfig()
|
|
249
|
+
.timeout({
|
|
250
|
+
request: 120000, // SERVER_TIMEOUT (default: 120s)
|
|
251
|
+
keepAlive: 65000, // SERVER_KEEPALIVE_TIMEOUT (default: 65s)
|
|
252
|
+
headers: 60000, // SERVER_HEADERS_TIMEOUT (default: 60s)
|
|
253
|
+
})
|
|
254
|
+
.fetchTimeout({
|
|
255
|
+
connect: 10000, // FETCH_CONNECT_TIMEOUT (default: 10s)
|
|
256
|
+
headers: 300000, // FETCH_HEADERS_TIMEOUT (default: 300s)
|
|
257
|
+
body: 300000, // FETCH_BODY_TIMEOUT (default: 300s)
|
|
258
|
+
})
|
|
249
259
|
.shutdown({
|
|
250
|
-
timeout: 280000,
|
|
260
|
+
timeout: 280000, // SHUTDOWN_TIMEOUT (default: 280s)
|
|
251
261
|
})
|
|
252
262
|
.build();
|
|
253
263
|
```
|
|
254
264
|
|
|
265
|
+
For long-running AI workloads, increase fetch timeouts:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
FETCH_HEADERS_TIMEOUT=600000 # 10 minutes
|
|
269
|
+
FETCH_BODY_TIMEOUT=600000 # 10 minutes
|
|
270
|
+
RPC_PROXY_TIMEOUT=580000 # Must be < FETCH_HEADERS_TIMEOUT
|
|
271
|
+
```
|
|
272
|
+
|
|
255
273
|
AI 파이프라인 등 장기 작업이 있는 앱은 Helm chart과 함께 조정:
|
|
256
274
|
|
|
257
275
|
```yaml
|
|
@@ -297,8 +315,42 @@ DATABASE_READ_URL=postgresql://replica:5432/mydb
|
|
|
297
315
|
# Redis
|
|
298
316
|
REDIS_URL=redis://localhost:6379
|
|
299
317
|
|
|
318
|
+
# Server Timeout (inbound HTTP server)
|
|
319
|
+
SERVER_TIMEOUT=120000 # Request timeout (default: 120s)
|
|
320
|
+
SERVER_KEEPALIVE_TIMEOUT=65000 # Keep-alive timeout (default: 65s)
|
|
321
|
+
SERVER_HEADERS_TIMEOUT=60000 # Headers timeout, Slowloris defense (default: 60s)
|
|
322
|
+
|
|
323
|
+
# Fetch Timeout (outbound HTTP via undici global dispatcher)
|
|
324
|
+
FETCH_CONNECT_TIMEOUT=10000 # TCP connection timeout (default: 10s)
|
|
325
|
+
FETCH_HEADERS_TIMEOUT=300000 # Response headers timeout (default: 300s)
|
|
326
|
+
FETCH_BODY_TIMEOUT=300000 # Body chunk interval timeout (default: 300s)
|
|
327
|
+
|
|
328
|
+
# RPC Proxy Timeout (Next.js → Backend)
|
|
329
|
+
RPC_PROXY_TIMEOUT=120000 # AbortController timeout (default: 120s)
|
|
330
|
+
|
|
300
331
|
# Shutdown
|
|
301
|
-
SHUTDOWN_TIMEOUT=280000
|
|
332
|
+
SHUTDOWN_TIMEOUT=280000 # Graceful shutdown timeout (default: 280s)
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Timeout Architecture
|
|
336
|
+
|
|
337
|
+
```
|
|
338
|
+
Request flow:
|
|
339
|
+
Client → Next.js API Route → fetch() → SPFN Backend
|
|
340
|
+
|
|
341
|
+
Timeout layers:
|
|
342
|
+
┌──────────────────────────────────────────────────────┐
|
|
343
|
+
│ RPC_PROXY_TIMEOUT (AbortController) default 120s │ ← shortest, returns 504
|
|
344
|
+
│ FETCH_HEADERS_TIMEOUT (undici) default 300s │ ← fetch socket level
|
|
345
|
+
│ FETCH_BODY_TIMEOUT (undici) default 300s │ ← body chunk interval
|
|
346
|
+
│ FETCH_CONNECT_TIMEOUT (undici) default 10s │ ← TCP connection
|
|
347
|
+
├──────────────────────────────────────────────────────┤
|
|
348
|
+
│ SERVER_TIMEOUT (backend server.timeout) default 120s │ ← backend HTTP server
|
|
349
|
+
│ SERVER_HEADERS_TIMEOUT default 60s │ ← Slowloris defense
|
|
350
|
+
│ SERVER_KEEPALIVE_TIMEOUT default 65s │ ← idle connection
|
|
351
|
+
└──────────────────────────────────────────────────────┘
|
|
352
|
+
|
|
353
|
+
Rule: RPC_PROXY_TIMEOUT < FETCH_HEADERS_TIMEOUT
|
|
302
354
|
```
|
|
303
355
|
|
|
304
356
|
---
|