@robosystems/client 0.1.15 → 0.1.16
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/package.json +48 -6
- package/sdk/client/client.gen.d.ts +2 -0
- package/sdk/client/client.gen.js +153 -0
- package/sdk/client/client.gen.ts +200 -0
- package/sdk/client/index.d.ts +7 -0
- package/sdk/client/index.js +15 -0
- package/sdk/client/index.ts +25 -0
- package/sdk/client/types.gen.d.ts +122 -0
- package/sdk/client/types.gen.js +4 -0
- package/sdk/client/types.gen.ts +233 -0
- package/sdk/client/utils.gen.d.ts +45 -0
- package/sdk/client/utils.gen.js +296 -0
- package/sdk/client/utils.gen.ts +419 -0
- package/sdk/client.gen.d.ts +12 -0
- package/sdk/client.gen.js +8 -0
- package/sdk/client.gen.ts +18 -0
- package/sdk/core/auth.gen.d.ts +18 -0
- package/sdk/core/auth.gen.js +18 -0
- package/sdk/core/auth.gen.ts +42 -0
- package/sdk/core/bodySerializer.gen.d.ts +17 -0
- package/sdk/core/bodySerializer.gen.js +57 -0
- package/sdk/core/bodySerializer.gen.ts +90 -0
- package/sdk/core/params.gen.d.ts +33 -0
- package/sdk/core/params.gen.js +92 -0
- package/sdk/core/params.gen.ts +153 -0
- package/sdk/core/pathSerializer.gen.d.ts +33 -0
- package/sdk/core/pathSerializer.gen.js +123 -0
- package/sdk/core/pathSerializer.gen.ts +181 -0
- package/sdk/core/types.gen.d.ts +78 -0
- package/sdk/core/types.gen.js +4 -0
- package/sdk/core/types.gen.ts +121 -0
- package/sdk/index.d.ts +2 -0
- package/sdk/index.js +19 -0
- package/sdk/index.ts +3 -0
- package/sdk/sdk.gen.d.ts +1269 -0
- package/sdk/sdk.gen.js +2664 -0
- package/sdk/sdk.gen.ts +2677 -0
- package/sdk/types.gen.d.ts +6265 -0
- package/sdk/types.gen.js +3 -0
- package/sdk/types.gen.ts +6784 -0
- package/sdk-extensions/OperationClient.d.ts +64 -0
- package/sdk-extensions/OperationClient.js +251 -0
- package/sdk-extensions/OperationClient.ts +322 -0
- package/sdk-extensions/QueryClient.d.ts +50 -0
- package/sdk-extensions/QueryClient.js +190 -0
- package/sdk-extensions/QueryClient.ts +283 -0
- package/sdk-extensions/README.md +672 -0
- package/sdk-extensions/SSEClient.d.ts +48 -0
- package/sdk-extensions/SSEClient.js +148 -0
- package/sdk-extensions/SSEClient.ts +189 -0
- package/sdk-extensions/config.d.ts +32 -0
- package/sdk-extensions/config.js +74 -0
- package/sdk-extensions/config.ts +91 -0
- package/sdk-extensions/hooks.d.ts +110 -0
- package/sdk-extensions/hooks.js +371 -0
- package/sdk-extensions/hooks.ts +438 -0
- package/sdk-extensions/index.d.ts +46 -0
- package/sdk-extensions/index.js +110 -0
- package/sdk-extensions/index.ts +123 -0
- package/sdk.gen.d.ts +128 -2
- package/sdk.gen.js +216 -2
- package/sdk.gen.ts +216 -2
- package/types.gen.d.ts +573 -4
- package/types.gen.ts +606 -4
- package/openapi-ts.config.js +0 -9
- package/prepare.js +0 -220
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
# RoboSystems Typescript Client Extensions
|
|
2
|
+
|
|
3
|
+
🚀 **Enhanced SSE and Real-time Features** for the RoboSystems Typescript Client
|
|
4
|
+
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
The RoboSystems Typescript Client Extensions provide production-ready enhancements for real-time operations:
|
|
11
|
+
|
|
12
|
+
- **Server-Sent Events (SSE)** with automatic reconnection and event replay
|
|
13
|
+
- **Smart Query Execution** with automatic queueing and progress monitoring
|
|
14
|
+
- **Operation Monitoring** for long-running tasks with real-time updates
|
|
15
|
+
- **Connection Management** with rate limiting and circuit breaker patterns
|
|
16
|
+
- **React Hooks** for seamless UI integration
|
|
17
|
+
- **Full TypeScript Support** with comprehensive type definitions
|
|
18
|
+
|
|
19
|
+
## 🚀 Quick Start
|
|
20
|
+
|
|
21
|
+
### Installation
|
|
22
|
+
|
|
23
|
+
The extensions are included with the main SDK:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @robosystems/client
|
|
27
|
+
# or
|
|
28
|
+
yarn add @robosystems/client
|
|
29
|
+
# or
|
|
30
|
+
pnpm add @robosystems/client
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Basic SSE Usage
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { SSEClient, EventType } from '@robosystems/client/extensions'
|
|
37
|
+
|
|
38
|
+
// Initialize SSE client
|
|
39
|
+
const sseClient = new SSEClient({
|
|
40
|
+
baseUrl: 'https://api.robosystems.ai',
|
|
41
|
+
credentials: 'include', // For cookie auth
|
|
42
|
+
maxRetries: 5,
|
|
43
|
+
retryDelay: 1000,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// Connect to operation stream
|
|
47
|
+
await sseClient.connect('operation-id-123')
|
|
48
|
+
|
|
49
|
+
// Listen for events
|
|
50
|
+
sseClient.on(EventType.OPERATION_PROGRESS, (data) => {
|
|
51
|
+
console.log(`Progress: ${data.message} (${data.percentage}%)`)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
sseClient.on(EventType.DATA_CHUNK, (data) => {
|
|
55
|
+
console.log(`Received ${data.rows.length} rows`)
|
|
56
|
+
processRows(data.rows)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
sseClient.on(EventType.OPERATION_COMPLETED, (data) => {
|
|
60
|
+
console.log('Operation completed:', data.result)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// Clean up when done
|
|
64
|
+
sseClient.close()
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Query Execution with Progress Monitoring
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { QueryClient } from '@robosystems/client/extensions'
|
|
71
|
+
|
|
72
|
+
const queryClient = new QueryClient({
|
|
73
|
+
baseUrl: 'https://api.robosystems.ai',
|
|
74
|
+
apiKey: 'your-api-key',
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// Execute query with automatic SSE monitoring
|
|
78
|
+
const result = await queryClient.executeWithProgress(
|
|
79
|
+
'your-graph-id',
|
|
80
|
+
'MATCH (c:Company) RETURN c.name, c.revenue ORDER BY c.revenue DESC',
|
|
81
|
+
{
|
|
82
|
+
onProgress: (progress) => {
|
|
83
|
+
console.log(`${progress.current}/${progress.total} rows processed`)
|
|
84
|
+
},
|
|
85
|
+
onQueueUpdate: (position, estimatedWait) => {
|
|
86
|
+
console.log(`Queue position: ${position}, ETA: ${estimatedWait}s`)
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
console.log(`Query completed with ${result.rowCount} results`)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 📊 SSE Event Types
|
|
95
|
+
|
|
96
|
+
The SDK supports all RoboSystems SSE event types:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
enum EventType {
|
|
100
|
+
// Operation lifecycle
|
|
101
|
+
OPERATION_STARTED = 'operation_started',
|
|
102
|
+
OPERATION_PROGRESS = 'operation_progress',
|
|
103
|
+
OPERATION_COMPLETED = 'operation_completed',
|
|
104
|
+
OPERATION_ERROR = 'operation_error',
|
|
105
|
+
OPERATION_CANCELLED = 'operation_cancelled',
|
|
106
|
+
|
|
107
|
+
// Data streaming
|
|
108
|
+
DATA_CHUNK = 'data_chunk',
|
|
109
|
+
METADATA = 'metadata',
|
|
110
|
+
|
|
111
|
+
// Queue management
|
|
112
|
+
QUEUE_UPDATE = 'queue_update',
|
|
113
|
+
|
|
114
|
+
// Connection health
|
|
115
|
+
HEARTBEAT = 'heartbeat',
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## 🔄 Advanced SSE Features
|
|
120
|
+
|
|
121
|
+
### Automatic Reconnection
|
|
122
|
+
|
|
123
|
+
The SSE client automatically reconnects on connection loss with exponential backoff:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
const sseClient = new SSEClient({
|
|
127
|
+
baseUrl: 'https://api.robosystems.ai',
|
|
128
|
+
maxRetries: 5, // Maximum reconnection attempts
|
|
129
|
+
retryDelay: 1000, // Initial retry delay (ms)
|
|
130
|
+
heartbeatInterval: 30000, // Heartbeat check interval
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// Monitor reconnection attempts
|
|
134
|
+
sseClient.on('reconnecting', ({ attempt, delay, lastEventId }) => {
|
|
135
|
+
console.log(`Reconnecting (attempt ${attempt}) in ${delay}ms...`)
|
|
136
|
+
console.log(`Resuming from event ${lastEventId}`)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
sseClient.on('max_retries_exceeded', (error) => {
|
|
140
|
+
console.error('Failed to reconnect after maximum attempts')
|
|
141
|
+
// Fallback to polling or show error to user
|
|
142
|
+
})
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Event Replay
|
|
146
|
+
|
|
147
|
+
SSE automatically resumes from the last received event after reconnection:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// Connect with specific starting sequence
|
|
151
|
+
await sseClient.connect('operation-id', fromSequence)
|
|
152
|
+
|
|
153
|
+
// The client tracks lastEventId automatically
|
|
154
|
+
sseClient.on('event', (event) => {
|
|
155
|
+
console.log(`Event ${event.id}: ${event.event}`)
|
|
156
|
+
// Events are guaranteed to be in sequence
|
|
157
|
+
})
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Rate Limiting & Connection Management
|
|
161
|
+
|
|
162
|
+
The SDK respects server-side rate limits:
|
|
163
|
+
|
|
164
|
+
- **Maximum 5 concurrent SSE connections per user**
|
|
165
|
+
- **10 new connections per minute rate limit**
|
|
166
|
+
- **Automatic circuit breaker for Redis failures**
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// Handle rate limiting gracefully
|
|
170
|
+
try {
|
|
171
|
+
await sseClient.connect('operation-id')
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (error.status === 429) {
|
|
174
|
+
console.log('Rate limit exceeded - falling back to polling')
|
|
175
|
+
// Use polling fallback
|
|
176
|
+
const result = await pollOperation('operation-id')
|
|
177
|
+
} else if (error.status === 503) {
|
|
178
|
+
console.log('SSE temporarily unavailable - circuit breaker open')
|
|
179
|
+
// SSE system is degraded, use alternative method
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## 🎯 Operation Monitoring
|
|
185
|
+
|
|
186
|
+
### OperationClient for Long-Running Tasks
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { OperationClient, OperationStatus } from '@robosystems/client/extensions'
|
|
190
|
+
|
|
191
|
+
const operationClient = new OperationClient({
|
|
192
|
+
baseUrl: 'https://api.robosystems.ai',
|
|
193
|
+
apiKey: 'your-api-key',
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
// Monitor any long-running operation
|
|
197
|
+
const result = await operationClient.monitor('operation-id', {
|
|
198
|
+
onProgress: (progress) => {
|
|
199
|
+
console.log(`Step ${progress.currentStep}/${progress.totalSteps}`)
|
|
200
|
+
console.log(`${progress.message} (${progress.percentage}%)`)
|
|
201
|
+
updateProgressBar(progress.percentage)
|
|
202
|
+
},
|
|
203
|
+
onStatusChange: (status) => {
|
|
204
|
+
switch (status) {
|
|
205
|
+
case OperationStatus.QUEUED:
|
|
206
|
+
showMessage('Operation queued...')
|
|
207
|
+
break
|
|
208
|
+
case OperationStatus.RUNNING:
|
|
209
|
+
showMessage('Processing...')
|
|
210
|
+
break
|
|
211
|
+
case OperationStatus.COMPLETED:
|
|
212
|
+
showMessage('Success!')
|
|
213
|
+
break
|
|
214
|
+
case OperationStatus.FAILED:
|
|
215
|
+
showMessage('Operation failed')
|
|
216
|
+
break
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
maxWaitTime: 300000, // 5 minutes max wait
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
if (result.status === OperationStatus.COMPLETED) {
|
|
223
|
+
processResults(result.data)
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Progress Tracking Patterns
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
// Create a reusable progress tracker
|
|
231
|
+
class ProgressTracker {
|
|
232
|
+
private startTime = Date.now()
|
|
233
|
+
private lastUpdate = Date.now()
|
|
234
|
+
|
|
235
|
+
onProgress = (progress: OperationProgress) => {
|
|
236
|
+
const elapsed = Date.now() - this.startTime
|
|
237
|
+
const rate = progress.rowsProcessed / (elapsed / 1000)
|
|
238
|
+
const eta = (progress.totalRows - progress.rowsProcessed) / rate
|
|
239
|
+
|
|
240
|
+
console.log(`Processing: ${progress.rowsProcessed}/${progress.totalRows} rows`)
|
|
241
|
+
console.log(`Rate: ${rate.toFixed(0)} rows/sec`)
|
|
242
|
+
console.log(`ETA: ${this.formatDuration(eta * 1000)}`)
|
|
243
|
+
|
|
244
|
+
// Update UI
|
|
245
|
+
this.updateUI(progress)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private formatDuration(ms: number): string {
|
|
249
|
+
const seconds = Math.floor(ms / 1000)
|
|
250
|
+
const minutes = Math.floor(seconds / 60)
|
|
251
|
+
const hours = Math.floor(minutes / 60)
|
|
252
|
+
|
|
253
|
+
if (hours > 0) return `${hours}h ${minutes % 60}m`
|
|
254
|
+
if (minutes > 0) return `${minutes}m ${seconds % 60}s`
|
|
255
|
+
return `${seconds}s`
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private updateUI(progress: OperationProgress) {
|
|
259
|
+
// Update your UI components
|
|
260
|
+
document.querySelector('#progress-bar')?.setAttribute('value', progress.percentage.toString())
|
|
261
|
+
document.querySelector('#progress-text')?.textContent =
|
|
262
|
+
`${progress.message} (${progress.percentage}%)`
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Use the tracker
|
|
267
|
+
const tracker = new ProgressTracker()
|
|
268
|
+
await operationClient.monitor('operation-id', {
|
|
269
|
+
onProgress: tracker.onProgress,
|
|
270
|
+
})
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## ⚛️ React Integration
|
|
274
|
+
|
|
275
|
+
### useSSE Hook
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
import { useSSE } from '@robosystems/client/extensions/hooks'
|
|
279
|
+
|
|
280
|
+
function OperationMonitor({ operationId }: { operationId: string }) {
|
|
281
|
+
const {
|
|
282
|
+
data,
|
|
283
|
+
progress,
|
|
284
|
+
status,
|
|
285
|
+
error,
|
|
286
|
+
isConnected
|
|
287
|
+
} = useSSE(operationId, {
|
|
288
|
+
onProgress: (p) => console.log('Progress:', p),
|
|
289
|
+
onDataChunk: (chunk) => console.log('Chunk:', chunk),
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
if (error) return <div>Error: {error.message}</div>
|
|
293
|
+
if (!isConnected) return <div>Connecting...</div>
|
|
294
|
+
|
|
295
|
+
return (
|
|
296
|
+
<div>
|
|
297
|
+
<h3>Operation Status: {status}</h3>
|
|
298
|
+
{progress && (
|
|
299
|
+
<div>
|
|
300
|
+
<progress value={progress.percentage} max="100" />
|
|
301
|
+
<p>{progress.message}</p>
|
|
302
|
+
</div>
|
|
303
|
+
)}
|
|
304
|
+
{data && (
|
|
305
|
+
<div>
|
|
306
|
+
<h4>Results:</h4>
|
|
307
|
+
<pre>{JSON.stringify(data, null, 2)}</pre>
|
|
308
|
+
</div>
|
|
309
|
+
)}
|
|
310
|
+
</div>
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### useQueryWithSSE Hook
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import { useQueryWithSSE } from '@robosystems/client/extensions/hooks'
|
|
319
|
+
|
|
320
|
+
function QueryRunner() {
|
|
321
|
+
const {
|
|
322
|
+
execute,
|
|
323
|
+
loading,
|
|
324
|
+
data,
|
|
325
|
+
error,
|
|
326
|
+
progress,
|
|
327
|
+
queuePosition
|
|
328
|
+
} = useQueryWithSSE('your-graph-id')
|
|
329
|
+
|
|
330
|
+
const runQuery = async () => {
|
|
331
|
+
const result = await execute(
|
|
332
|
+
'MATCH (c:Company) RETURN c.name, c.revenue LIMIT 100'
|
|
333
|
+
)
|
|
334
|
+
console.log('Query completed:', result)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return (
|
|
338
|
+
<div>
|
|
339
|
+
<button onClick={runQuery} disabled={loading}>
|
|
340
|
+
Run Query
|
|
341
|
+
</button>
|
|
342
|
+
|
|
343
|
+
{loading && (
|
|
344
|
+
<div>
|
|
345
|
+
{queuePosition > 0 && <p>Queue position: {queuePosition}</p>}
|
|
346
|
+
{progress && <p>Progress: {progress.percentage}%</p>}
|
|
347
|
+
</div>
|
|
348
|
+
)}
|
|
349
|
+
|
|
350
|
+
{error && <div className="error">{error.message}</div>}
|
|
351
|
+
|
|
352
|
+
{data && (
|
|
353
|
+
<table>
|
|
354
|
+
<tbody>
|
|
355
|
+
{data.rows.map((row, i) => (
|
|
356
|
+
<tr key={i}>
|
|
357
|
+
<td>{row['c.name']}</td>
|
|
358
|
+
<td>${row['c.revenue'].toLocaleString()}</td>
|
|
359
|
+
</tr>
|
|
360
|
+
))}
|
|
361
|
+
</tbody>
|
|
362
|
+
</table>
|
|
363
|
+
)}
|
|
364
|
+
</div>
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## 🛡️ Error Handling & Resilience
|
|
370
|
+
|
|
371
|
+
### Circuit Breaker Pattern
|
|
372
|
+
|
|
373
|
+
The SSE system includes automatic circuit breaker protection:
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
// The circuit breaker automatically opens after 3 Redis failures
|
|
377
|
+
// It will close again after a cooldown period
|
|
378
|
+
|
|
379
|
+
sseClient.on('circuit_breaker_open', () => {
|
|
380
|
+
console.log('SSE circuit breaker opened - falling back to polling')
|
|
381
|
+
// Automatically degrades to polling
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
sseClient.on('circuit_breaker_closed', () => {
|
|
385
|
+
console.log('SSE circuit breaker closed - resuming streaming')
|
|
386
|
+
// Automatically resumes SSE when healthy
|
|
387
|
+
})
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Graceful Degradation
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import { QueryClient, FallbackStrategy } from '@robosystems/client/extensions'
|
|
394
|
+
|
|
395
|
+
const queryClient = new QueryClient({
|
|
396
|
+
baseUrl: 'https://api.robosystems.ai',
|
|
397
|
+
fallbackStrategy: FallbackStrategy.AUTO, // Automatically choose best strategy
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
// Automatically uses best available method:
|
|
401
|
+
// 1. SSE streaming (preferred)
|
|
402
|
+
// 2. NDJSON streaming (if SSE unavailable)
|
|
403
|
+
// 3. Polling (if streaming unavailable)
|
|
404
|
+
// 4. Direct response (for small queries)
|
|
405
|
+
|
|
406
|
+
const result = await queryClient.execute('graph-id', 'MATCH (n) RETURN n', {
|
|
407
|
+
preferStreaming: true, // Hint to prefer streaming
|
|
408
|
+
onFallback: (from, to) => {
|
|
409
|
+
console.log(`Falling back from ${from} to ${to}`)
|
|
410
|
+
},
|
|
411
|
+
})
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Connection Pool Management
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
// SDK automatically manages SSE connection pooling
|
|
418
|
+
const config = {
|
|
419
|
+
maxConnections: 5, // Per-user limit enforced by server
|
|
420
|
+
connectionTimeout: 10000, // 10 second timeout
|
|
421
|
+
poolStrategy: 'FIFO', // First-in-first-out recycling
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Connections are automatically recycled when limits are reached
|
|
425
|
+
const operations = [
|
|
426
|
+
'operation-1',
|
|
427
|
+
'operation-2',
|
|
428
|
+
'operation-3',
|
|
429
|
+
'operation-4',
|
|
430
|
+
'operation-5',
|
|
431
|
+
'operation-6', // Will recycle oldest connection
|
|
432
|
+
]
|
|
433
|
+
|
|
434
|
+
// Monitor connection pool status
|
|
435
|
+
sseClient.on('connection_recycled', ({ old, new }) => {
|
|
436
|
+
console.log(`Recycled connection from ${old} to ${new}`)
|
|
437
|
+
})
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
## 🔧 Configuration
|
|
441
|
+
|
|
442
|
+
### Environment Variables
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
# API Configuration
|
|
446
|
+
NEXT_PUBLIC_ROBOSYSTEMS_API_URL=https://api.robosystems.ai
|
|
447
|
+
ROBOSYSTEMS_API_KEY=your-api-key
|
|
448
|
+
|
|
449
|
+
# SSE Configuration
|
|
450
|
+
NEXT_PUBLIC_SSE_MAX_RETRIES=5
|
|
451
|
+
NEXT_PUBLIC_SSE_RETRY_DELAY=1000
|
|
452
|
+
NEXT_PUBLIC_SSE_HEARTBEAT_INTERVAL=30000
|
|
453
|
+
|
|
454
|
+
# Feature Flags
|
|
455
|
+
NEXT_PUBLIC_ENABLE_SSE=true
|
|
456
|
+
NEXT_PUBLIC_PREFER_STREAMING=true
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Custom Configuration
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
import { createSSEClient } from '@robosystems/client/extensions'
|
|
463
|
+
|
|
464
|
+
const sseClient = createSSEClient({
|
|
465
|
+
// API Configuration
|
|
466
|
+
baseUrl: process.env.NEXT_PUBLIC_ROBOSYSTEMS_API_URL,
|
|
467
|
+
|
|
468
|
+
// Authentication
|
|
469
|
+
credentials: 'include', // For cookies
|
|
470
|
+
headers: {
|
|
471
|
+
'X-API-Key': process.env.ROBOSYSTEMS_API_KEY,
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
// Connection Settings
|
|
475
|
+
maxRetries: 5,
|
|
476
|
+
retryDelay: 1000,
|
|
477
|
+
heartbeatInterval: 30000,
|
|
478
|
+
|
|
479
|
+
// Advanced Options
|
|
480
|
+
eventSourceOptions: {
|
|
481
|
+
withCredentials: true,
|
|
482
|
+
},
|
|
483
|
+
})
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
## 📊 Performance Optimization
|
|
487
|
+
|
|
488
|
+
### Stream Processing for Large Datasets
|
|
489
|
+
|
|
490
|
+
```typescript
|
|
491
|
+
import { StreamProcessor } from '@robosystems/client/extensions'
|
|
492
|
+
|
|
493
|
+
const processor = new StreamProcessor({
|
|
494
|
+
batchSize: 1000,
|
|
495
|
+
concurrency: 3,
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
// Process large query results efficiently
|
|
499
|
+
await processor.processStream('your-graph-id', 'MATCH (t:Transaction) RETURN t', {
|
|
500
|
+
onBatch: async (batch) => {
|
|
501
|
+
// Process batch of 1000 rows
|
|
502
|
+
await saveToDB(batch)
|
|
503
|
+
console.log(`Processed ${batch.length} transactions`)
|
|
504
|
+
},
|
|
505
|
+
onProgress: (processed, total) => {
|
|
506
|
+
const percentage = (processed / total) * 100
|
|
507
|
+
console.log(`Progress: ${percentage.toFixed(2)}%`)
|
|
508
|
+
},
|
|
509
|
+
})
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### Caching with SSE Updates
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
import { CachedQueryClient } from '@robosystems/client/extensions'
|
|
516
|
+
|
|
517
|
+
const cachedClient = new CachedQueryClient({
|
|
518
|
+
ttl: 300000, // 5 minute cache
|
|
519
|
+
maxSize: 100, // Cache up to 100 queries
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
// First call hits the API
|
|
523
|
+
const result1 = await cachedClient.execute('graph-id', 'MATCH (n) RETURN COUNT(n)')
|
|
524
|
+
|
|
525
|
+
// Second call returns from cache
|
|
526
|
+
const result2 = await cachedClient.execute('graph-id', 'MATCH (n) RETURN COUNT(n)')
|
|
527
|
+
|
|
528
|
+
// SSE updates automatically invalidate relevant cache entries
|
|
529
|
+
cachedClient.on('cache_invalidated', (query) => {
|
|
530
|
+
console.log('Cache invalidated for:', query)
|
|
531
|
+
})
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
## 🧪 Testing
|
|
535
|
+
|
|
536
|
+
### Mock SSE for Testing
|
|
537
|
+
|
|
538
|
+
```typescript
|
|
539
|
+
import { MockSSEClient } from '@robosystems/client/extensions/testing'
|
|
540
|
+
|
|
541
|
+
describe('SSE Integration', () => {
|
|
542
|
+
it('should handle progress events', async () => {
|
|
543
|
+
const mockClient = new MockSSEClient()
|
|
544
|
+
|
|
545
|
+
// Set up mock events
|
|
546
|
+
mockClient.simulateEvents([
|
|
547
|
+
{ event: EventType.OPERATION_STARTED, data: { message: 'Starting' } },
|
|
548
|
+
{ event: EventType.OPERATION_PROGRESS, data: { percentage: 50 } },
|
|
549
|
+
{ event: EventType.OPERATION_COMPLETED, data: { result: 'Success' } },
|
|
550
|
+
])
|
|
551
|
+
|
|
552
|
+
// Test your component
|
|
553
|
+
const { getByText } = render(
|
|
554
|
+
<OperationMonitor
|
|
555
|
+
operationId="test-123"
|
|
556
|
+
sseClient={mockClient}
|
|
557
|
+
/>
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
await waitFor(() => {
|
|
561
|
+
expect(getByText('Starting')).toBeInTheDocument()
|
|
562
|
+
expect(getByText('50%')).toBeInTheDocument()
|
|
563
|
+
expect(getByText('Success')).toBeInTheDocument()
|
|
564
|
+
})
|
|
565
|
+
})
|
|
566
|
+
})
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
## 📚 API Reference
|
|
570
|
+
|
|
571
|
+
### Core Classes
|
|
572
|
+
|
|
573
|
+
- **`SSEClient`** - Server-Sent Events client with auto-reconnection
|
|
574
|
+
- **`QueryClient`** - Enhanced query execution with SSE support
|
|
575
|
+
- **`OperationClient`** - Long-running operation monitoring
|
|
576
|
+
- **`StreamProcessor`** - Efficient stream processing for large datasets
|
|
577
|
+
|
|
578
|
+
### Event Types
|
|
579
|
+
|
|
580
|
+
- **`EventType`** - Enum of all supported SSE event types
|
|
581
|
+
- **`SSEEvent`** - Typed SSE event structure
|
|
582
|
+
- **`OperationProgress`** - Progress update structure
|
|
583
|
+
- **`OperationStatus`** - Operation status enum
|
|
584
|
+
|
|
585
|
+
### React Hooks
|
|
586
|
+
|
|
587
|
+
- **`useSSE`** - Hook for SSE connection management
|
|
588
|
+
- **`useQueryWithSSE`** - Hook for queries with progress
|
|
589
|
+
- **`useOperation`** - Hook for operation monitoring
|
|
590
|
+
|
|
591
|
+
### Utilities
|
|
592
|
+
|
|
593
|
+
- **`createSSEClient`** - Factory for configured SSE clients
|
|
594
|
+
- **`formatDuration`** - Human-readable duration formatting
|
|
595
|
+
- **`parseSSEEvent`** - SSE event parsing utility
|
|
596
|
+
|
|
597
|
+
## 🐛 Troubleshooting
|
|
598
|
+
|
|
599
|
+
### Common Issues
|
|
600
|
+
|
|
601
|
+
**Rate Limit Errors (429)**
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
// Handle rate limiting gracefully
|
|
605
|
+
if (error.status === 429) {
|
|
606
|
+
const retryAfter = error.headers['retry-after'] || 60
|
|
607
|
+
console.log(`Rate limited. Retry after ${retryAfter} seconds`)
|
|
608
|
+
|
|
609
|
+
// Use exponential backoff
|
|
610
|
+
await sleep(retryAfter * 1000)
|
|
611
|
+
await retry()
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
**Connection Drops**
|
|
616
|
+
|
|
617
|
+
```typescript
|
|
618
|
+
// SSE automatically reconnects, but you can handle it manually
|
|
619
|
+
sseClient.on('disconnected', () => {
|
|
620
|
+
showNotification('Connection lost. Reconnecting...')
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
sseClient.on('connected', () => {
|
|
624
|
+
showNotification('Connection restored')
|
|
625
|
+
})
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
**Circuit Breaker Open (503)**
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
// SSE system temporarily disabled
|
|
632
|
+
if (error.status === 503) {
|
|
633
|
+
console.log('SSE system unavailable - using fallback')
|
|
634
|
+
// Automatically falls back to polling
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### Debug Mode
|
|
639
|
+
|
|
640
|
+
```typescript
|
|
641
|
+
// Enable debug logging
|
|
642
|
+
const sseClient = new SSEClient({
|
|
643
|
+
baseUrl: 'https://api.robosystems.ai',
|
|
644
|
+
debug: true, // Enables detailed logging
|
|
645
|
+
})
|
|
646
|
+
|
|
647
|
+
// Monitor all events
|
|
648
|
+
sseClient.on('*', (event, data) => {
|
|
649
|
+
console.log(`[SSE Debug] ${event}:`, data)
|
|
650
|
+
})
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
## 📄 License
|
|
654
|
+
|
|
655
|
+
MIT License - see [LICENSE](../../LICENSE) file for details.
|
|
656
|
+
|
|
657
|
+
## 🤝 Contributing
|
|
658
|
+
|
|
659
|
+
See the [Contributing Guide](../../CONTRIBUTING.md) for development setup and guidelines.
|
|
660
|
+
|
|
661
|
+
## 📞 Support
|
|
662
|
+
|
|
663
|
+
- **Documentation**: [docs.robosystems.ai](https://docs.robosystems.ai)
|
|
664
|
+
- **API Reference**: [api.robosystems.ai/docs](https://api.robosystems.ai/docs)
|
|
665
|
+
- **Discord**: [Join our community](https://discord.gg/robosystems)
|
|
666
|
+
- **Issues**: [GitHub Issues](https://github.com/robosystems/sdk/issues)
|
|
667
|
+
|
|
668
|
+
---
|
|
669
|
+
|
|
670
|
+
**RoboSystems Typescript Client Extensions** - Production-ready SSE streaming and real-time monitoring for financial knowledge graphs.
|
|
671
|
+
|
|
672
|
+
_Built with ❤️ by the RoboSystems team_
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core SSE (Server-Sent Events) client for RoboSystems API
|
|
3
|
+
* Provides automatic reconnection, event replay, and type-safe event handling
|
|
4
|
+
*/
|
|
5
|
+
export interface SSEConfig {
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
credentials?: 'include' | 'same-origin' | 'omit';
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
maxRetries?: number;
|
|
10
|
+
retryDelay?: number;
|
|
11
|
+
heartbeatInterval?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface SSEEvent {
|
|
14
|
+
event: string;
|
|
15
|
+
data: any;
|
|
16
|
+
id?: string;
|
|
17
|
+
retry?: number;
|
|
18
|
+
timestamp: Date;
|
|
19
|
+
}
|
|
20
|
+
export declare enum EventType {
|
|
21
|
+
OPERATION_STARTED = "operation_started",
|
|
22
|
+
OPERATION_PROGRESS = "operation_progress",
|
|
23
|
+
OPERATION_COMPLETED = "operation_completed",
|
|
24
|
+
OPERATION_ERROR = "operation_error",
|
|
25
|
+
OPERATION_CANCELLED = "operation_cancelled",
|
|
26
|
+
DATA_CHUNK = "data_chunk",
|
|
27
|
+
METADATA = "metadata",
|
|
28
|
+
HEARTBEAT = "heartbeat",
|
|
29
|
+
QUEUE_UPDATE = "queue_update"
|
|
30
|
+
}
|
|
31
|
+
export declare class SSEClient {
|
|
32
|
+
private config;
|
|
33
|
+
private eventSource?;
|
|
34
|
+
private reconnectAttempts;
|
|
35
|
+
private lastEventId?;
|
|
36
|
+
private closed;
|
|
37
|
+
private listeners;
|
|
38
|
+
constructor(config: SSEConfig);
|
|
39
|
+
connect(operationId: string, fromSequence?: number): Promise<void>;
|
|
40
|
+
private handleMessage;
|
|
41
|
+
private handleTypedEvent;
|
|
42
|
+
private handleError;
|
|
43
|
+
on(event: string, listener: (data: any) => void): void;
|
|
44
|
+
off(event: string, listener: (data: any) => void): void;
|
|
45
|
+
private emit;
|
|
46
|
+
close(): void;
|
|
47
|
+
isConnected(): boolean;
|
|
48
|
+
}
|