@robosystems/client 0.1.10 → 0.1.12
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/client/client.gen.d.ts +2 -0
- package/client/client.gen.js +153 -0
- package/client/index.d.ts +7 -0
- package/client/index.js +15 -0
- package/client/types.gen.d.ts +122 -0
- package/client/types.gen.js +4 -0
- package/client/utils.gen.d.ts +45 -0
- package/client/utils.gen.js +296 -0
- package/client.gen.d.ts +12 -0
- package/client.gen.js +8 -0
- package/core/auth.gen.d.ts +18 -0
- package/core/auth.gen.js +18 -0
- package/core/bodySerializer.gen.d.ts +17 -0
- package/core/bodySerializer.gen.js +57 -0
- package/core/params.gen.d.ts +33 -0
- package/core/params.gen.js +92 -0
- package/core/pathSerializer.gen.d.ts +33 -0
- package/core/pathSerializer.gen.js +123 -0
- package/core/types.gen.d.ts +78 -0
- package/core/types.gen.js +4 -0
- package/extensions/OperationClient.d.js +45 -0
- package/extensions/OperationClient.d.ts +64 -0
- package/extensions/OperationClient.js +297 -0
- package/extensions/OperationClient.ts +322 -0
- package/extensions/QueryClient.d.js +22 -0
- package/extensions/QueryClient.d.ts +50 -0
- package/extensions/QueryClient.js +245 -0
- package/extensions/QueryClient.ts +283 -0
- package/extensions/SSEClient.d.js +35 -0
- package/extensions/SSEClient.d.ts +48 -0
- package/extensions/SSEClient.js +166 -0
- package/extensions/SSEClient.ts +189 -0
- package/extensions/config.d.js +25 -0
- package/extensions/config.d.ts +32 -0
- package/extensions/config.js +66 -0
- package/extensions/config.ts +91 -0
- package/extensions/hooks.d.js +80 -0
- package/extensions/hooks.d.ts +110 -0
- package/extensions/hooks.js +435 -0
- package/extensions/hooks.ts +438 -0
- package/extensions/index.d.js +35 -0
- package/extensions/index.d.ts +46 -0
- package/extensions/index.js +118 -0
- package/extensions/index.ts +123 -0
- package/package.json +3 -2
- package/prepare.js +10 -0
- package/sdk.gen.d.ts +1145 -0
- package/sdk.gen.js +2436 -0
- package/types.gen.d.ts +5712 -0
- package/types.gen.js +3 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* General Operations Client for monitoring async operations
|
|
5
|
+
* Handles graph creation, backups, imports, and other long-running tasks
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { cancelOperation, getOperationStatus } from '../sdk.gen'
|
|
9
|
+
import { EventType, SSEClient } from './SSEClient'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export class OperationClient {
|
|
18
|
+
private sseClients = new Map()
|
|
19
|
+
private cleanupTimeouts> = new Map()
|
|
20
|
+
private config
|
|
21
|
+
private cleanupIntervalMs = 300000 // 5 minutes
|
|
22
|
+
private cleanupInterval?
|
|
23
|
+
|
|
24
|
+
constructor(config) {
|
|
25
|
+
this.config = config
|
|
26
|
+
|
|
27
|
+
// Start periodic cleanup check every 5 minutes
|
|
28
|
+
this.cleanupInterval = setInterval(() => {
|
|
29
|
+
this.performPeriodicCleanup()
|
|
30
|
+
}, this.cleanupIntervalMs)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async monitorOperation(
|
|
34
|
+
operationId,
|
|
35
|
+
options = {}
|
|
36
|
+
)> {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const sseClient = new SSEClient(this.config)
|
|
39
|
+
this.sseClients.set(operationId, sseClient)
|
|
40
|
+
|
|
41
|
+
const timeoutHandle = options.timeout
|
|
42
|
+
? setTimeout(() => {
|
|
43
|
+
this.cleanupClient(operationId)
|
|
44
|
+
reject(new Error(`Operation timeout after ${options.timeout}ms`))
|
|
45
|
+
}, options.timeout)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
sseClient
|
|
49
|
+
.connect(operationId)
|
|
50
|
+
.then(() => {
|
|
51
|
+
let result = { success }
|
|
52
|
+
|
|
53
|
+
// Track queue updates
|
|
54
|
+
if (options.onQueueUpdate) {
|
|
55
|
+
sseClient.on(EventType.QUEUE_UPDATE, (data) => {
|
|
56
|
+
options.onQueueUpdate!(
|
|
57
|
+
data.position || data.queue_position,
|
|
58
|
+
data.estimated_wait_seconds || 0
|
|
59
|
+
)
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Track progress
|
|
64
|
+
if (options.onProgress) {
|
|
65
|
+
sseClient.on(EventType.OPERATION_PROGRESS, (data) => {
|
|
66
|
+
options.onProgress!({
|
|
67
|
+
message.message || data.status || 'Processing...',
|
|
68
|
+
progressPercent.progress_percent || data.progress,
|
|
69
|
+
details,
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Handle completion
|
|
75
|
+
sseClient.on(EventType.OPERATION_COMPLETED, (data) => {
|
|
76
|
+
if (timeoutHandle) clearTimeout(timeoutHandle)
|
|
77
|
+
result = {
|
|
78
|
+
success,
|
|
79
|
+
result.result || data,
|
|
80
|
+
metadata.metadata,
|
|
81
|
+
}
|
|
82
|
+
// Schedule cleanup after a short delay to ensure all data is received
|
|
83
|
+
this.scheduleCleanup(operationId, 5000)
|
|
84
|
+
resolve(result)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// Handle errors
|
|
88
|
+
sseClient.on(EventType.OPERATION_ERROR, (error) => {
|
|
89
|
+
if (timeoutHandle) clearTimeout(timeoutHandle)
|
|
90
|
+
result = {
|
|
91
|
+
success,
|
|
92
|
+
error.message || error.error || 'Operation failed',
|
|
93
|
+
metadata.metadata,
|
|
94
|
+
}
|
|
95
|
+
// Schedule cleanup after a short delay
|
|
96
|
+
this.scheduleCleanup(operationId, 5000)
|
|
97
|
+
resolve(result) // Resolve with error result, not reject
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// Handle cancellation
|
|
101
|
+
sseClient.on(EventType.OPERATION_CANCELLED, (data) => {
|
|
102
|
+
if (timeoutHandle) clearTimeout(timeoutHandle)
|
|
103
|
+
result = {
|
|
104
|
+
success,
|
|
105
|
+
error: 'Operation cancelled',
|
|
106
|
+
metadata,
|
|
107
|
+
}
|
|
108
|
+
// Schedule cleanup after a short delay
|
|
109
|
+
this.scheduleCleanup(operationId, 5000)
|
|
110
|
+
resolve(result)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
.catch((error) => {
|
|
114
|
+
if (timeoutHandle) clearTimeout(timeoutHandle)
|
|
115
|
+
this.cleanupClient(operationId)
|
|
116
|
+
reject(error)
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Monitor multiple operations concurrently
|
|
123
|
+
*/
|
|
124
|
+
async monitorMultiple(
|
|
125
|
+
operationIds,
|
|
126
|
+
options = {}
|
|
127
|
+
)>> {
|
|
128
|
+
const results = await Promise.all(
|
|
129
|
+
operationIds.map(async (id) => {
|
|
130
|
+
const result = await this.monitorOperation(id, options)
|
|
131
|
+
return [id, result] as [string, OperationResult]
|
|
132
|
+
})
|
|
133
|
+
)
|
|
134
|
+
return new Map(results)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get the current status of an operation (point-in-time check)
|
|
139
|
+
*/
|
|
140
|
+
async getStatus(operationId) {
|
|
141
|
+
const response = await getOperationStatus({
|
|
142
|
+
path,
|
|
143
|
+
})
|
|
144
|
+
return response.data
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Cancel a pending or running operation
|
|
149
|
+
*/
|
|
150
|
+
async cancelOperation(operationId) {
|
|
151
|
+
// First close any active SSE connection
|
|
152
|
+
this.cleanupClient(operationId)
|
|
153
|
+
|
|
154
|
+
// Then cancel the operation
|
|
155
|
+
await cancelOperationSDK({
|
|
156
|
+
path,
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Wait for an operation with a simple promise interface
|
|
162
|
+
*/
|
|
163
|
+
async waitForOperation(operationId, timeoutMs?) {
|
|
164
|
+
const result = await this.monitorOperation(operationId, {
|
|
165
|
+
timeout,
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
if (!result.success) {
|
|
169
|
+
throw new Error(result.error || 'Operation failed')
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return result.result
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Monitor operation with async iterator for progress updates
|
|
177
|
+
*/
|
|
178
|
+
async *monitorWithProgress(
|
|
179
|
+
operationId
|
|
180
|
+
)> {
|
|
181
|
+
const progressQueue: (OperationProgress | OperationResult)[] = []
|
|
182
|
+
let completed = false
|
|
183
|
+
let finalResult | null = null
|
|
184
|
+
|
|
185
|
+
// Start monitoring in background
|
|
186
|
+
const monitorPromise = this.monitorOperation(operationId, {
|
|
187
|
+
onProgress: (progress) => {
|
|
188
|
+
progressQueue.push(progress)
|
|
189
|
+
},
|
|
190
|
+
onQueueUpdate: (position, estimatedWait) => {
|
|
191
|
+
progressQueue.push({
|
|
192
|
+
message: `Queue position: ${position}`,
|
|
193
|
+
progressPercent,
|
|
194
|
+
details,
|
|
195
|
+
})
|
|
196
|
+
},
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// Handle completion
|
|
200
|
+
monitorPromise
|
|
201
|
+
.then((result) => {
|
|
202
|
+
finalResult = result
|
|
203
|
+
completed = true
|
|
204
|
+
})
|
|
205
|
+
.catch((error) => {
|
|
206
|
+
finalResult = {
|
|
207
|
+
success,
|
|
208
|
+
error.message,
|
|
209
|
+
}
|
|
210
|
+
completed = true
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
// Yield progress updates come
|
|
214
|
+
while (!completed || progressQueue.length > 0) {
|
|
215
|
+
if (progressQueue.length > 0) {
|
|
216
|
+
yield progressQueue.shift()!
|
|
217
|
+
} else if (!completed) {
|
|
218
|
+
// Wait for more progress
|
|
219
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Yield final result
|
|
224
|
+
if (finalResult) {
|
|
225
|
+
yield finalResult
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private cleanupClient(operationId) {
|
|
230
|
+
const client = this.sseClients.get(operationId)
|
|
231
|
+
if (client) {
|
|
232
|
+
client.close()
|
|
233
|
+
this.sseClients.delete(operationId)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Clear any cleanup timeout for this operation
|
|
237
|
+
const timeout = this.cleanupTimeouts.get(operationId)
|
|
238
|
+
if (timeout) {
|
|
239
|
+
clearTimeout(timeout)
|
|
240
|
+
this.cleanupTimeouts.delete(operationId)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Schedule automatic cleanup of SSE client after a delay
|
|
246
|
+
*/
|
|
247
|
+
private scheduleCleanup(operationId, delayMs = 60000) {
|
|
248
|
+
// Clear any existing timeout
|
|
249
|
+
const existingTimeout = this.cleanupTimeouts.get(operationId)
|
|
250
|
+
if (existingTimeout) {
|
|
251
|
+
clearTimeout(existingTimeout)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Schedule new cleanup
|
|
255
|
+
const timeout = setTimeout(() => {
|
|
256
|
+
this.cleanupClient(operationId)
|
|
257
|
+
this.cleanupTimeouts.delete(operationId)
|
|
258
|
+
}, delayMs)
|
|
259
|
+
|
|
260
|
+
this.cleanupTimeouts.set(operationId, timeout)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Perform periodic cleanup of stale SSE connections
|
|
265
|
+
*/
|
|
266
|
+
private performPeriodicCleanup() {
|
|
267
|
+
// Check each SSE client for staleness
|
|
268
|
+
this.sseClients.forEach((client, operationId) => {
|
|
269
|
+
if (!client.isConnected()) {
|
|
270
|
+
// Clean up disconnected clients immediately
|
|
271
|
+
this.cleanupClient(operationId)
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Close all active SSE connections and clean up resources
|
|
278
|
+
*/
|
|
279
|
+
closeAll() {
|
|
280
|
+
// Clear periodic cleanup interval
|
|
281
|
+
if (this.cleanupInterval) {
|
|
282
|
+
clearInterval(this.cleanupInterval)
|
|
283
|
+
this.cleanupInterval = undefined
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Clear all cleanup timeouts
|
|
287
|
+
this.cleanupTimeouts.forEach((timeout) => {
|
|
288
|
+
clearTimeout(timeout)
|
|
289
|
+
})
|
|
290
|
+
this.cleanupTimeouts.clear()
|
|
291
|
+
|
|
292
|
+
// Close all SSE clients
|
|
293
|
+
this.sseClients.forEach((client, operationId) => {
|
|
294
|
+
this.cleanupClient(operationId)
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* General Operations Client for monitoring async operations
|
|
5
|
+
* Handles graph creation, backups, imports, and other long-running tasks
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { cancelOperation as cancelOperationSDK, getOperationStatus } from '../sdk.gen'
|
|
9
|
+
import { EventType, SSEClient } from './SSEClient'
|
|
10
|
+
|
|
11
|
+
export interface OperationProgress {
|
|
12
|
+
message: string
|
|
13
|
+
progressPercent?: number
|
|
14
|
+
details?: Record<string, any>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface OperationResult<T = any> {
|
|
18
|
+
success: boolean
|
|
19
|
+
result?: T
|
|
20
|
+
error?: string
|
|
21
|
+
metadata?: Record<string, any>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface OperationMonitorOptions {
|
|
25
|
+
onProgress?: (progress: OperationProgress) => void
|
|
26
|
+
onQueueUpdate?: (position: number, estimatedWait: number) => void
|
|
27
|
+
timeout?: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class OperationClient {
|
|
31
|
+
private sseClients: Map<string, SSEClient> = new Map()
|
|
32
|
+
private cleanupTimeouts: Map<string, ReturnType<typeof setTimeout>> = new Map()
|
|
33
|
+
private config: {
|
|
34
|
+
baseUrl: string
|
|
35
|
+
credentials?: 'include' | 'same-origin' | 'omit'
|
|
36
|
+
headers?: Record<string, string>
|
|
37
|
+
maxRetries?: number
|
|
38
|
+
retryDelay?: number
|
|
39
|
+
}
|
|
40
|
+
private cleanupIntervalMs = 300000 // 5 minutes
|
|
41
|
+
private cleanupInterval?: ReturnType<typeof setInterval>
|
|
42
|
+
|
|
43
|
+
constructor(config: {
|
|
44
|
+
baseUrl: string
|
|
45
|
+
credentials?: 'include' | 'same-origin' | 'omit'
|
|
46
|
+
headers?: Record<string, string>
|
|
47
|
+
maxRetries?: number
|
|
48
|
+
retryDelay?: number
|
|
49
|
+
}) {
|
|
50
|
+
this.config = config
|
|
51
|
+
|
|
52
|
+
// Start periodic cleanup check every 5 minutes
|
|
53
|
+
this.cleanupInterval = setInterval(() => {
|
|
54
|
+
this.performPeriodicCleanup()
|
|
55
|
+
}, this.cleanupIntervalMs)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async monitorOperation<T = any>(
|
|
59
|
+
operationId: string,
|
|
60
|
+
options: OperationMonitorOptions = {}
|
|
61
|
+
): Promise<OperationResult<T>> {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const sseClient = new SSEClient(this.config)
|
|
64
|
+
this.sseClients.set(operationId, sseClient)
|
|
65
|
+
|
|
66
|
+
const timeoutHandle = options.timeout
|
|
67
|
+
? setTimeout(() => {
|
|
68
|
+
this.cleanupClient(operationId)
|
|
69
|
+
reject(new Error(`Operation timeout after ${options.timeout}ms`))
|
|
70
|
+
}, options.timeout)
|
|
71
|
+
: undefined
|
|
72
|
+
|
|
73
|
+
sseClient
|
|
74
|
+
.connect(operationId)
|
|
75
|
+
.then(() => {
|
|
76
|
+
let result: OperationResult<T> = { success: false }
|
|
77
|
+
|
|
78
|
+
// Track queue updates
|
|
79
|
+
if (options.onQueueUpdate) {
|
|
80
|
+
sseClient.on(EventType.QUEUE_UPDATE, (data) => {
|
|
81
|
+
options.onQueueUpdate!(
|
|
82
|
+
data.position || data.queue_position,
|
|
83
|
+
data.estimated_wait_seconds || 0
|
|
84
|
+
)
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Track progress
|
|
89
|
+
if (options.onProgress) {
|
|
90
|
+
sseClient.on(EventType.OPERATION_PROGRESS, (data) => {
|
|
91
|
+
options.onProgress!({
|
|
92
|
+
message: data.message || data.status || 'Processing...',
|
|
93
|
+
progressPercent: data.progress_percent || data.progress,
|
|
94
|
+
details: data,
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Handle completion
|
|
100
|
+
sseClient.on(EventType.OPERATION_COMPLETED, (data) => {
|
|
101
|
+
if (timeoutHandle) clearTimeout(timeoutHandle)
|
|
102
|
+
result = {
|
|
103
|
+
success: true,
|
|
104
|
+
result: data.result || data,
|
|
105
|
+
metadata: data.metadata,
|
|
106
|
+
}
|
|
107
|
+
// Schedule cleanup after a short delay to ensure all data is received
|
|
108
|
+
this.scheduleCleanup(operationId, 5000)
|
|
109
|
+
resolve(result)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// Handle errors
|
|
113
|
+
sseClient.on(EventType.OPERATION_ERROR, (error) => {
|
|
114
|
+
if (timeoutHandle) clearTimeout(timeoutHandle)
|
|
115
|
+
result = {
|
|
116
|
+
success: false,
|
|
117
|
+
error: error.message || error.error || 'Operation failed',
|
|
118
|
+
metadata: error.metadata,
|
|
119
|
+
}
|
|
120
|
+
// Schedule cleanup after a short delay
|
|
121
|
+
this.scheduleCleanup(operationId, 5000)
|
|
122
|
+
resolve(result) // Resolve with error result, not reject
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// Handle cancellation
|
|
126
|
+
sseClient.on(EventType.OPERATION_CANCELLED, (data) => {
|
|
127
|
+
if (timeoutHandle) clearTimeout(timeoutHandle)
|
|
128
|
+
result = {
|
|
129
|
+
success: false,
|
|
130
|
+
error: 'Operation cancelled',
|
|
131
|
+
metadata: data,
|
|
132
|
+
}
|
|
133
|
+
// Schedule cleanup after a short delay
|
|
134
|
+
this.scheduleCleanup(operationId, 5000)
|
|
135
|
+
resolve(result)
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
.catch((error) => {
|
|
139
|
+
if (timeoutHandle) clearTimeout(timeoutHandle)
|
|
140
|
+
this.cleanupClient(operationId)
|
|
141
|
+
reject(error)
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Monitor multiple operations concurrently
|
|
148
|
+
*/
|
|
149
|
+
async monitorMultiple<T = any>(
|
|
150
|
+
operationIds: string[],
|
|
151
|
+
options: OperationMonitorOptions = {}
|
|
152
|
+
): Promise<Map<string, OperationResult<T>>> {
|
|
153
|
+
const results = await Promise.all(
|
|
154
|
+
operationIds.map(async (id) => {
|
|
155
|
+
const result = await this.monitorOperation<T>(id, options)
|
|
156
|
+
return [id, result] as [string, OperationResult<T>]
|
|
157
|
+
})
|
|
158
|
+
)
|
|
159
|
+
return new Map(results)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get the current status of an operation (point-in-time check)
|
|
164
|
+
*/
|
|
165
|
+
async getStatus(operationId: string): Promise<any> {
|
|
166
|
+
const response = await getOperationStatus({
|
|
167
|
+
path: { operation_id: operationId },
|
|
168
|
+
})
|
|
169
|
+
return response.data
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Cancel a pending or running operation
|
|
174
|
+
*/
|
|
175
|
+
async cancelOperation(operationId: string): Promise<void> {
|
|
176
|
+
// First close any active SSE connection
|
|
177
|
+
this.cleanupClient(operationId)
|
|
178
|
+
|
|
179
|
+
// Then cancel the operation
|
|
180
|
+
await cancelOperationSDK({
|
|
181
|
+
path: { operation_id: operationId },
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Wait for an operation with a simple promise interface
|
|
187
|
+
*/
|
|
188
|
+
async waitForOperation<T = any>(operationId: string, timeoutMs?: number): Promise<T> {
|
|
189
|
+
const result = await this.monitorOperation<T>(operationId, {
|
|
190
|
+
timeout: timeoutMs,
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
if (!result.success) {
|
|
194
|
+
throw new Error(result.error || 'Operation failed')
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return result.result as T
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Monitor operation with async iterator for progress updates
|
|
202
|
+
*/
|
|
203
|
+
async *monitorWithProgress<T = any>(
|
|
204
|
+
operationId: string
|
|
205
|
+
): AsyncIterableIterator<OperationProgress | OperationResult<T>> {
|
|
206
|
+
const progressQueue: (OperationProgress | OperationResult<T>)[] = []
|
|
207
|
+
let completed = false
|
|
208
|
+
let finalResult: OperationResult<T> | null = null
|
|
209
|
+
|
|
210
|
+
// Start monitoring in background
|
|
211
|
+
const monitorPromise = this.monitorOperation<T>(operationId, {
|
|
212
|
+
onProgress: (progress) => {
|
|
213
|
+
progressQueue.push(progress)
|
|
214
|
+
},
|
|
215
|
+
onQueueUpdate: (position, estimatedWait) => {
|
|
216
|
+
progressQueue.push({
|
|
217
|
+
message: `Queue position: ${position}`,
|
|
218
|
+
progressPercent: 0,
|
|
219
|
+
details: { position, estimatedWait },
|
|
220
|
+
})
|
|
221
|
+
},
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
// Handle completion
|
|
225
|
+
monitorPromise
|
|
226
|
+
.then((result) => {
|
|
227
|
+
finalResult = result
|
|
228
|
+
completed = true
|
|
229
|
+
})
|
|
230
|
+
.catch((error) => {
|
|
231
|
+
finalResult = {
|
|
232
|
+
success: false,
|
|
233
|
+
error: error.message,
|
|
234
|
+
}
|
|
235
|
+
completed = true
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
// Yield progress updates as they come
|
|
239
|
+
while (!completed || progressQueue.length > 0) {
|
|
240
|
+
if (progressQueue.length > 0) {
|
|
241
|
+
yield progressQueue.shift()!
|
|
242
|
+
} else if (!completed) {
|
|
243
|
+
// Wait for more progress
|
|
244
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Yield final result
|
|
249
|
+
if (finalResult) {
|
|
250
|
+
yield finalResult
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private cleanupClient(operationId: string): void {
|
|
255
|
+
const client = this.sseClients.get(operationId)
|
|
256
|
+
if (client) {
|
|
257
|
+
client.close()
|
|
258
|
+
this.sseClients.delete(operationId)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Clear any cleanup timeout for this operation
|
|
262
|
+
const timeout = this.cleanupTimeouts.get(operationId)
|
|
263
|
+
if (timeout) {
|
|
264
|
+
clearTimeout(timeout)
|
|
265
|
+
this.cleanupTimeouts.delete(operationId)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Schedule automatic cleanup of SSE client after a delay
|
|
271
|
+
*/
|
|
272
|
+
private scheduleCleanup(operationId: string, delayMs: number = 60000): void {
|
|
273
|
+
// Clear any existing timeout
|
|
274
|
+
const existingTimeout = this.cleanupTimeouts.get(operationId)
|
|
275
|
+
if (existingTimeout) {
|
|
276
|
+
clearTimeout(existingTimeout)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Schedule new cleanup
|
|
280
|
+
const timeout = setTimeout(() => {
|
|
281
|
+
this.cleanupClient(operationId)
|
|
282
|
+
this.cleanupTimeouts.delete(operationId)
|
|
283
|
+
}, delayMs)
|
|
284
|
+
|
|
285
|
+
this.cleanupTimeouts.set(operationId, timeout)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Perform periodic cleanup of stale SSE connections
|
|
290
|
+
*/
|
|
291
|
+
private performPeriodicCleanup(): void {
|
|
292
|
+
// Check each SSE client for staleness
|
|
293
|
+
this.sseClients.forEach((client, operationId) => {
|
|
294
|
+
if (!client.isConnected()) {
|
|
295
|
+
// Clean up disconnected clients immediately
|
|
296
|
+
this.cleanupClient(operationId)
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Close all active SSE connections and clean up resources
|
|
303
|
+
*/
|
|
304
|
+
closeAll(): void {
|
|
305
|
+
// Clear periodic cleanup interval
|
|
306
|
+
if (this.cleanupInterval) {
|
|
307
|
+
clearInterval(this.cleanupInterval)
|
|
308
|
+
this.cleanupInterval = undefined
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Clear all cleanup timeouts
|
|
312
|
+
this.cleanupTimeouts.forEach((timeout) => {
|
|
313
|
+
clearTimeout(timeout)
|
|
314
|
+
})
|
|
315
|
+
this.cleanupTimeouts.clear()
|
|
316
|
+
|
|
317
|
+
// Close all SSE clients
|
|
318
|
+
this.sseClients.forEach((client, operationId) => {
|
|
319
|
+
this.cleanupClient(operationId)
|
|
320
|
+
})
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export declare class QueryClient {
|
|
6
|
+
private sseClient?;
|
|
7
|
+
private config;
|
|
8
|
+
constructor(config);
|
|
9
|
+
executeQuery(graphId, request, options?)>;
|
|
10
|
+
private streamQueryResults;
|
|
11
|
+
private waitForQueryCompletion;
|
|
12
|
+
query(graphId, cypher, parameters?);
|
|
13
|
+
streamQuery(graphId, cypher, parameters?, chunkSize?);
|
|
14
|
+
close();
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Error thrown when query is queued and maxWait is 0
|
|
18
|
+
*/
|
|
19
|
+
export declare class QueuedQueryError extends Error {
|
|
20
|
+
queueInfo;
|
|
21
|
+
constructor(queueInfo);
|
|
22
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface QueryRequest {
|
|
2
|
+
query: string;
|
|
3
|
+
parameters?: Record<string, any>;
|
|
4
|
+
timeout?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface QueryOptions {
|
|
7
|
+
mode?: 'auto' | 'sync' | 'async' | 'stream';
|
|
8
|
+
chunkSize?: number;
|
|
9
|
+
testMode?: boolean;
|
|
10
|
+
maxWait?: number;
|
|
11
|
+
onQueueUpdate?: (position: number, estimatedWait: number) => void;
|
|
12
|
+
onProgress?: (message: string) => void;
|
|
13
|
+
}
|
|
14
|
+
export interface QueryResult {
|
|
15
|
+
data: any[];
|
|
16
|
+
columns: string[];
|
|
17
|
+
row_count: number;
|
|
18
|
+
execution_time_ms: number;
|
|
19
|
+
graph_id?: string;
|
|
20
|
+
timestamp?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface QueuedQueryResponse {
|
|
23
|
+
status: 'queued';
|
|
24
|
+
operation_id: string;
|
|
25
|
+
queue_position: number;
|
|
26
|
+
estimated_wait_seconds: number;
|
|
27
|
+
message: string;
|
|
28
|
+
}
|
|
29
|
+
export declare class QueryClient {
|
|
30
|
+
private sseClient?;
|
|
31
|
+
private config;
|
|
32
|
+
constructor(config: {
|
|
33
|
+
baseUrl: string;
|
|
34
|
+
credentials?: 'include' | 'same-origin' | 'omit';
|
|
35
|
+
headers?: Record<string, string>;
|
|
36
|
+
});
|
|
37
|
+
executeQuery(graphId: string, request: QueryRequest, options?: QueryOptions): Promise<QueryResult | AsyncIterableIterator<any>>;
|
|
38
|
+
private streamQueryResults;
|
|
39
|
+
private waitForQueryCompletion;
|
|
40
|
+
query(graphId: string, cypher: string, parameters?: Record<string, any>): Promise<QueryResult>;
|
|
41
|
+
streamQuery(graphId: string, cypher: string, parameters?: Record<string, any>, chunkSize?: number): AsyncIterableIterator<any>;
|
|
42
|
+
close(): void;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Error thrown when query is queued and maxWait is 0
|
|
46
|
+
*/
|
|
47
|
+
export declare class QueuedQueryError extends Error {
|
|
48
|
+
queueInfo: QueuedQueryResponse;
|
|
49
|
+
constructor(queueInfo: QueuedQueryResponse);
|
|
50
|
+
}
|