@robosystems/client 0.1.16 → 0.1.18
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/extensions/CopyClient.d.ts +97 -0
- package/extensions/CopyClient.js +287 -0
- package/extensions/CopyClient.ts +438 -0
- package/extensions/hooks.d.ts +36 -1
- package/extensions/hooks.js +123 -0
- package/extensions/hooks.ts +139 -0
- package/extensions/index.d.ts +7 -2
- package/extensions/index.js +15 -1
- package/extensions/index.ts +23 -1
- package/package.json +1 -1
- package/sdk/sdk.gen.d.ts +182 -167
- package/sdk/sdk.gen.js +298 -355
- package/sdk/sdk.gen.ts +292 -349
- package/sdk/types.gen.d.ts +635 -512
- package/sdk/types.gen.ts +697 -588
- package/sdk-extensions/CopyClient.d.ts +97 -0
- package/sdk-extensions/CopyClient.js +287 -0
- package/sdk-extensions/CopyClient.ts +438 -0
- package/sdk-extensions/README.md +219 -0
- package/sdk-extensions/hooks.d.ts +36 -1
- package/sdk-extensions/hooks.js +123 -0
- package/sdk-extensions/hooks.ts +139 -0
- package/sdk-extensions/index.d.ts +7 -2
- package/sdk-extensions/index.js +15 -1
- package/sdk-extensions/index.ts +23 -1
- package/sdk.gen.d.ts +182 -167
- package/sdk.gen.js +298 -355
- package/sdk.gen.ts +292 -349
- package/types.gen.d.ts +635 -512
- package/types.gen.ts +697 -588
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Enhanced Copy Client with SSE support
|
|
5
|
+
* Provides intelligent data copy operations with progress monitoring
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { copyDataToGraph } from '../sdk.gen'
|
|
9
|
+
import type {
|
|
10
|
+
CopyDataToGraphData,
|
|
11
|
+
CopyResponse,
|
|
12
|
+
DataFrameCopyRequest,
|
|
13
|
+
S3CopyRequest,
|
|
14
|
+
UrlCopyRequest,
|
|
15
|
+
} from '../types.gen'
|
|
16
|
+
import { EventType, SSEClient } from './SSEClient'
|
|
17
|
+
|
|
18
|
+
export type CopySourceType = 's3' | 'url' | 'dataframe'
|
|
19
|
+
|
|
20
|
+
export interface CopyOptions {
|
|
21
|
+
onProgress?: (message: string, progressPercent?: number) => void
|
|
22
|
+
onQueueUpdate?: (position: number, estimatedWait: number) => void
|
|
23
|
+
onWarning?: (warning: string) => void
|
|
24
|
+
timeout?: number
|
|
25
|
+
testMode?: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface CopyResult {
|
|
29
|
+
status: 'completed' | 'failed' | 'partial' | 'accepted'
|
|
30
|
+
rowsImported?: number
|
|
31
|
+
rowsSkipped?: number
|
|
32
|
+
bytesProcessed?: number
|
|
33
|
+
executionTimeMs?: number
|
|
34
|
+
warnings?: string[]
|
|
35
|
+
error?: string
|
|
36
|
+
operationId?: string
|
|
37
|
+
sseUrl?: string
|
|
38
|
+
message?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface CopyStatistics {
|
|
42
|
+
totalRows: number
|
|
43
|
+
importedRows: number
|
|
44
|
+
skippedRows: number
|
|
45
|
+
bytesProcessed: number
|
|
46
|
+
duration: number
|
|
47
|
+
throughput: number // rows per second
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class CopyClient {
|
|
51
|
+
private sseClient?: SSEClient
|
|
52
|
+
private config: {
|
|
53
|
+
baseUrl: string
|
|
54
|
+
credentials?: 'include' | 'same-origin' | 'omit'
|
|
55
|
+
headers?: Record<string, string>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
constructor(config: {
|
|
59
|
+
baseUrl: string
|
|
60
|
+
credentials?: 'include' | 'same-origin' | 'omit'
|
|
61
|
+
headers?: Record<string, string>
|
|
62
|
+
}) {
|
|
63
|
+
this.config = config
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Copy data from S3 to graph database
|
|
68
|
+
*/
|
|
69
|
+
async copyFromS3(
|
|
70
|
+
graphId: string,
|
|
71
|
+
request: S3CopyRequest,
|
|
72
|
+
options: CopyOptions = {}
|
|
73
|
+
): Promise<CopyResult> {
|
|
74
|
+
return this.executeCopy(graphId, request, 's3', options)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Copy data from URL to graph database (when available)
|
|
79
|
+
*/
|
|
80
|
+
async copyFromUrl(
|
|
81
|
+
graphId: string,
|
|
82
|
+
request: UrlCopyRequest,
|
|
83
|
+
options: CopyOptions = {}
|
|
84
|
+
): Promise<CopyResult> {
|
|
85
|
+
return this.executeCopy(graphId, request, 'url', options)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Copy data from DataFrame to graph database (when available)
|
|
90
|
+
*/
|
|
91
|
+
async copyFromDataFrame(
|
|
92
|
+
graphId: string,
|
|
93
|
+
request: DataFrameCopyRequest,
|
|
94
|
+
options: CopyOptions = {}
|
|
95
|
+
): Promise<CopyResult> {
|
|
96
|
+
return this.executeCopy(graphId, request, 'dataframe', options)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Execute copy operation with automatic SSE monitoring for long-running operations
|
|
101
|
+
*/
|
|
102
|
+
private async executeCopy(
|
|
103
|
+
graphId: string,
|
|
104
|
+
request: S3CopyRequest | UrlCopyRequest | DataFrameCopyRequest,
|
|
105
|
+
_sourceType: CopySourceType,
|
|
106
|
+
options: CopyOptions = {}
|
|
107
|
+
): Promise<CopyResult> {
|
|
108
|
+
const startTime = Date.now()
|
|
109
|
+
|
|
110
|
+
const data: CopyDataToGraphData = {
|
|
111
|
+
url: '/v1/{graph_id}/copy' as const,
|
|
112
|
+
path: { graph_id: graphId },
|
|
113
|
+
body: request,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
// Execute the copy request
|
|
118
|
+
const response = await copyDataToGraph(data)
|
|
119
|
+
const responseData = response.data as CopyResponse
|
|
120
|
+
|
|
121
|
+
// Check if this is an accepted (async) operation
|
|
122
|
+
if (responseData.status === 'accepted' && responseData.operation_id) {
|
|
123
|
+
// This is a long-running operation with SSE monitoring
|
|
124
|
+
options.onProgress?.(`Copy operation started. Monitoring progress...`)
|
|
125
|
+
|
|
126
|
+
// If SSE URL is provided, use it for monitoring
|
|
127
|
+
if (responseData.sse_url) {
|
|
128
|
+
return this.monitorCopyOperation(responseData.operation_id, options, startTime)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Otherwise return the accepted response
|
|
132
|
+
return {
|
|
133
|
+
status: 'accepted',
|
|
134
|
+
operationId: responseData.operation_id,
|
|
135
|
+
sseUrl: responseData.sse_url,
|
|
136
|
+
message: responseData.message,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// This is a synchronous response - operation completed immediately
|
|
141
|
+
return this.buildCopyResult(responseData, Date.now() - startTime)
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return {
|
|
144
|
+
status: 'failed',
|
|
145
|
+
error: error instanceof Error ? error.message : String(error),
|
|
146
|
+
executionTimeMs: Date.now() - startTime,
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Monitor a copy operation using SSE
|
|
153
|
+
*/
|
|
154
|
+
private async monitorCopyOperation(
|
|
155
|
+
operationId: string,
|
|
156
|
+
options: CopyOptions,
|
|
157
|
+
startTime: number
|
|
158
|
+
): Promise<CopyResult> {
|
|
159
|
+
return new Promise((resolve, reject) => {
|
|
160
|
+
const sseClient = new SSEClient(this.config)
|
|
161
|
+
const timeoutMs = options.timeout || 3600000 // Default 1 hour for copy operations
|
|
162
|
+
|
|
163
|
+
const timeoutHandle = setTimeout(() => {
|
|
164
|
+
sseClient.close()
|
|
165
|
+
reject(new Error(`Copy operation timeout after ${timeoutMs}ms`))
|
|
166
|
+
}, timeoutMs)
|
|
167
|
+
|
|
168
|
+
sseClient
|
|
169
|
+
.connect(operationId)
|
|
170
|
+
.then(() => {
|
|
171
|
+
let result: CopyResult = { status: 'failed' }
|
|
172
|
+
const warnings: string[] = []
|
|
173
|
+
|
|
174
|
+
// Listen for queue updates
|
|
175
|
+
sseClient.on(EventType.QUEUE_UPDATE, (data) => {
|
|
176
|
+
options.onQueueUpdate?.(
|
|
177
|
+
data.position || data.queue_position,
|
|
178
|
+
data.estimated_wait_seconds || 0
|
|
179
|
+
)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
// Listen for progress updates
|
|
183
|
+
sseClient.on(EventType.OPERATION_PROGRESS, (data) => {
|
|
184
|
+
const message = data.message || data.status || 'Processing...'
|
|
185
|
+
const progressPercent = data.progress_percent || data.progress
|
|
186
|
+
|
|
187
|
+
options.onProgress?.(message, progressPercent)
|
|
188
|
+
|
|
189
|
+
// Check for warnings in progress updates
|
|
190
|
+
if (data.warnings) {
|
|
191
|
+
warnings.push(...data.warnings)
|
|
192
|
+
data.warnings.forEach((warning: string) => {
|
|
193
|
+
options.onWarning?.(warning)
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
// Listen for completion
|
|
199
|
+
sseClient.on(EventType.OPERATION_COMPLETED, (data) => {
|
|
200
|
+
clearTimeout(timeoutHandle)
|
|
201
|
+
|
|
202
|
+
const completionData = data.result || data
|
|
203
|
+
result = {
|
|
204
|
+
status: completionData.status || 'completed',
|
|
205
|
+
rowsImported: completionData.rows_imported,
|
|
206
|
+
rowsSkipped: completionData.rows_skipped,
|
|
207
|
+
bytesProcessed: completionData.bytes_processed,
|
|
208
|
+
executionTimeMs: Date.now() - startTime,
|
|
209
|
+
warnings: warnings.length > 0 ? warnings : completionData.warnings,
|
|
210
|
+
message: completionData.message,
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
sseClient.close()
|
|
214
|
+
resolve(result)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
// Listen for errors
|
|
218
|
+
sseClient.on(EventType.OPERATION_ERROR, (error) => {
|
|
219
|
+
clearTimeout(timeoutHandle)
|
|
220
|
+
|
|
221
|
+
result = {
|
|
222
|
+
status: 'failed',
|
|
223
|
+
error: error.message || error.error || 'Copy operation failed',
|
|
224
|
+
executionTimeMs: Date.now() - startTime,
|
|
225
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
sseClient.close()
|
|
229
|
+
resolve(result) // Resolve with error result, not reject
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
// Listen for cancellation
|
|
233
|
+
sseClient.on(EventType.OPERATION_CANCELLED, () => {
|
|
234
|
+
clearTimeout(timeoutHandle)
|
|
235
|
+
|
|
236
|
+
result = {
|
|
237
|
+
status: 'failed',
|
|
238
|
+
error: 'Copy operation cancelled',
|
|
239
|
+
executionTimeMs: Date.now() - startTime,
|
|
240
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
sseClient.close()
|
|
244
|
+
resolve(result)
|
|
245
|
+
})
|
|
246
|
+
})
|
|
247
|
+
.catch((error) => {
|
|
248
|
+
clearTimeout(timeoutHandle)
|
|
249
|
+
reject(error)
|
|
250
|
+
})
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Build copy result from response data
|
|
256
|
+
*/
|
|
257
|
+
private buildCopyResult(responseData: CopyResponse, executionTimeMs: number): CopyResult {
|
|
258
|
+
return {
|
|
259
|
+
status: responseData.status,
|
|
260
|
+
rowsImported: responseData.rows_imported || undefined,
|
|
261
|
+
rowsSkipped: responseData.rows_skipped || undefined,
|
|
262
|
+
bytesProcessed: responseData.bytes_processed || undefined,
|
|
263
|
+
executionTimeMs: responseData.execution_time_ms || executionTimeMs,
|
|
264
|
+
warnings: responseData.warnings || undefined,
|
|
265
|
+
message: responseData.message,
|
|
266
|
+
error: responseData.error_details ? String(responseData.error_details) : undefined,
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Calculate copy statistics from result
|
|
272
|
+
*/
|
|
273
|
+
calculateStatistics(result: CopyResult): CopyStatistics | null {
|
|
274
|
+
if (result.status === 'failed' || !result.rowsImported) {
|
|
275
|
+
return null
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const totalRows = (result.rowsImported || 0) + (result.rowsSkipped || 0)
|
|
279
|
+
const duration = (result.executionTimeMs || 0) / 1000 // Convert to seconds
|
|
280
|
+
const throughput = duration > 0 ? (result.rowsImported || 0) / duration : 0
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
totalRows,
|
|
284
|
+
importedRows: result.rowsImported || 0,
|
|
285
|
+
skippedRows: result.rowsSkipped || 0,
|
|
286
|
+
bytesProcessed: result.bytesProcessed || 0,
|
|
287
|
+
duration,
|
|
288
|
+
throughput,
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Convenience method for simple S3 copy with default options
|
|
294
|
+
*/
|
|
295
|
+
async copyS3(
|
|
296
|
+
graphId: string,
|
|
297
|
+
tableName: string,
|
|
298
|
+
s3Uri: string,
|
|
299
|
+
accessKeyId: string,
|
|
300
|
+
secretAccessKey: string,
|
|
301
|
+
options?: {
|
|
302
|
+
region?: string
|
|
303
|
+
fileFormat?: 'csv' | 'parquet' | 'json' | 'delta' | 'iceberg'
|
|
304
|
+
ignoreErrors?: boolean
|
|
305
|
+
}
|
|
306
|
+
): Promise<CopyResult> {
|
|
307
|
+
const request: S3CopyRequest = {
|
|
308
|
+
table_name: tableName,
|
|
309
|
+
source_type: 's3',
|
|
310
|
+
s3_path: s3Uri,
|
|
311
|
+
s3_access_key_id: accessKeyId,
|
|
312
|
+
s3_secret_access_key: secretAccessKey,
|
|
313
|
+
s3_region: options?.region || 'us-east-1',
|
|
314
|
+
file_format: options?.fileFormat,
|
|
315
|
+
ignore_errors: options?.ignoreErrors || false,
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return this.copyFromS3(graphId, request)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Monitor multiple copy operations concurrently
|
|
323
|
+
*/
|
|
324
|
+
async monitorMultipleCopies(
|
|
325
|
+
operationIds: string[],
|
|
326
|
+
options: CopyOptions = {}
|
|
327
|
+
): Promise<Map<string, CopyResult>> {
|
|
328
|
+
const results = await Promise.all(
|
|
329
|
+
operationIds.map(async (id) => {
|
|
330
|
+
const result = await this.monitorCopyOperation(id, options, Date.now())
|
|
331
|
+
return [id, result] as [string, CopyResult]
|
|
332
|
+
})
|
|
333
|
+
)
|
|
334
|
+
return new Map(results)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Batch copy multiple tables from S3
|
|
339
|
+
*/
|
|
340
|
+
async batchCopyFromS3(
|
|
341
|
+
graphId: string,
|
|
342
|
+
copies: Array<{
|
|
343
|
+
request: S3CopyRequest
|
|
344
|
+
options?: CopyOptions
|
|
345
|
+
}>
|
|
346
|
+
): Promise<CopyResult[]> {
|
|
347
|
+
return Promise.all(
|
|
348
|
+
copies.map(({ request, options }) => this.copyFromS3(graphId, request, options || {}))
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Copy with retry logic for transient failures
|
|
354
|
+
*/
|
|
355
|
+
async copyWithRetry(
|
|
356
|
+
graphId: string,
|
|
357
|
+
request: S3CopyRequest | UrlCopyRequest | DataFrameCopyRequest,
|
|
358
|
+
sourceType: CopySourceType,
|
|
359
|
+
maxRetries: number = 3,
|
|
360
|
+
options: CopyOptions = {}
|
|
361
|
+
): Promise<CopyResult> {
|
|
362
|
+
let lastError: Error | undefined
|
|
363
|
+
let attempt = 0
|
|
364
|
+
|
|
365
|
+
while (attempt < maxRetries) {
|
|
366
|
+
attempt++
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
const result = await this.executeCopy(graphId, request, sourceType, options)
|
|
370
|
+
|
|
371
|
+
// If successful or partially successful, return
|
|
372
|
+
if (result.status === 'completed' || result.status === 'partial') {
|
|
373
|
+
return result
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// If failed, check if it's retryable
|
|
377
|
+
if (result.status === 'failed') {
|
|
378
|
+
const isRetryable = this.isRetryableError(result.error)
|
|
379
|
+
if (!isRetryable || attempt === maxRetries) {
|
|
380
|
+
return result
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Wait before retry with exponential backoff
|
|
384
|
+
const waitTime = Math.min(1000 * Math.pow(2, attempt - 1), 30000)
|
|
385
|
+
options.onProgress?.(
|
|
386
|
+
`Retrying copy operation (attempt ${attempt}/${maxRetries}) in ${waitTime}ms...`
|
|
387
|
+
)
|
|
388
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime))
|
|
389
|
+
}
|
|
390
|
+
} catch (error) {
|
|
391
|
+
lastError = error instanceof Error ? error : new Error(String(error))
|
|
392
|
+
|
|
393
|
+
if (attempt === maxRetries) {
|
|
394
|
+
throw lastError
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Wait before retry
|
|
398
|
+
const waitTime = Math.min(1000 * Math.pow(2, attempt - 1), 30000)
|
|
399
|
+
options.onProgress?.(
|
|
400
|
+
`Retrying after error (attempt ${attempt}/${maxRetries}) in ${waitTime}ms...`
|
|
401
|
+
)
|
|
402
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime))
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
throw lastError || new Error('Copy operation failed after all retries')
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Check if an error is retryable
|
|
411
|
+
*/
|
|
412
|
+
private isRetryableError(error?: string): boolean {
|
|
413
|
+
if (!error) return false
|
|
414
|
+
|
|
415
|
+
const retryablePatterns = [
|
|
416
|
+
'timeout',
|
|
417
|
+
'network',
|
|
418
|
+
'connection',
|
|
419
|
+
'temporary',
|
|
420
|
+
'unavailable',
|
|
421
|
+
'rate limit',
|
|
422
|
+
'throttl',
|
|
423
|
+
]
|
|
424
|
+
|
|
425
|
+
const lowerError = error.toLowerCase()
|
|
426
|
+
return retryablePatterns.some((pattern) => lowerError.includes(pattern))
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Cancel any active SSE connections
|
|
431
|
+
*/
|
|
432
|
+
close(): void {
|
|
433
|
+
if (this.sseClient) {
|
|
434
|
+
this.sseClient.close()
|
|
435
|
+
this.sseClient = undefined
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
package/extensions/hooks.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { S3CopyRequest } from '../types.gen';
|
|
2
|
+
import type { CopyOptions, CopyResult } from './CopyClient';
|
|
3
|
+
import { CopyClient } from './CopyClient';
|
|
1
4
|
import type { OperationProgress, OperationResult } from './OperationClient';
|
|
2
5
|
import { OperationClient } from './OperationClient';
|
|
3
6
|
import type { QueryOptions, QueryResult } from './QueryClient';
|
|
@@ -62,7 +65,7 @@ export declare function useStreamingQuery(graphId: string): {
|
|
|
62
65
|
export declare function useOperation<T = any>(operationId?: string): {
|
|
63
66
|
monitor: (id: string, timeout?: number) => Promise<OperationResult<T> | null>;
|
|
64
67
|
cancel: (id: string) => Promise<void>;
|
|
65
|
-
status: "
|
|
68
|
+
status: "completed" | "error" | "idle" | "running";
|
|
66
69
|
progress: OperationProgress;
|
|
67
70
|
error: Error;
|
|
68
71
|
result: OperationResult<T>;
|
|
@@ -105,6 +108,38 @@ export declare function useMultipleOperations<T = any>(): {
|
|
|
105
108
|
* ```
|
|
106
109
|
*/
|
|
107
110
|
export declare function useSDKClients(): {
|
|
111
|
+
copy: CopyClient | null;
|
|
108
112
|
query: QueryClient | null;
|
|
109
113
|
operations: OperationClient | null;
|
|
110
114
|
};
|
|
115
|
+
/**
|
|
116
|
+
* Hook for copying data from S3 to graph database with progress monitoring
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```tsx
|
|
120
|
+
* const { copyFromS3, loading, progress, error, result } = useCopy('graph_123')
|
|
121
|
+
*
|
|
122
|
+
* const handleImport = async () => {
|
|
123
|
+
* const result = await copyFromS3({
|
|
124
|
+
* table_name: 'companies',
|
|
125
|
+
* source_type: 's3',
|
|
126
|
+
* s3_uri: 's3://my-bucket/data.csv',
|
|
127
|
+
* aws_access_key_id: 'KEY',
|
|
128
|
+
* aws_secret_access_key: 'SECRET',
|
|
129
|
+
* })
|
|
130
|
+
* }
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
export declare function useCopy(graphId: string): {
|
|
134
|
+
copyFromS3: (request: S3CopyRequest, options?: CopyOptions) => Promise<CopyResult | null>;
|
|
135
|
+
copyWithRetry: (request: S3CopyRequest, maxRetries?: number) => Promise<CopyResult | null>;
|
|
136
|
+
getStatistics: () => import("./CopyClient").CopyStatistics;
|
|
137
|
+
loading: boolean;
|
|
138
|
+
error: Error;
|
|
139
|
+
result: CopyResult;
|
|
140
|
+
progress: {
|
|
141
|
+
message: string;
|
|
142
|
+
percent?: number;
|
|
143
|
+
};
|
|
144
|
+
queuePosition: number;
|
|
145
|
+
};
|
package/extensions/hooks.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.useStreamingQuery = useStreamingQuery;
|
|
|
6
6
|
exports.useOperation = useOperation;
|
|
7
7
|
exports.useMultipleOperations = useMultipleOperations;
|
|
8
8
|
exports.useSDKClients = useSDKClients;
|
|
9
|
+
exports.useCopy = useCopy;
|
|
9
10
|
/**
|
|
10
11
|
* React hooks for SDK extensions
|
|
11
12
|
* Provides easy-to-use hooks for Next.js/React applications
|
|
@@ -13,6 +14,7 @@ exports.useSDKClients = useSDKClients;
|
|
|
13
14
|
const react_1 = require("react");
|
|
14
15
|
const client_gen_1 = require("../client.gen");
|
|
15
16
|
const config_1 = require("./config");
|
|
17
|
+
const CopyClient_1 = require("./CopyClient");
|
|
16
18
|
const OperationClient_1 = require("./OperationClient");
|
|
17
19
|
const QueryClient_1 = require("./QueryClient");
|
|
18
20
|
/**
|
|
@@ -345,6 +347,7 @@ function useMultipleOperations() {
|
|
|
345
347
|
*/
|
|
346
348
|
function useSDKClients() {
|
|
347
349
|
const [clients, setClients] = (0, react_1.useState)({
|
|
350
|
+
copy: null,
|
|
348
351
|
query: null,
|
|
349
352
|
operations: null,
|
|
350
353
|
});
|
|
@@ -356,16 +359,136 @@ function useSDKClients() {
|
|
|
356
359
|
credentials: sdkConfig.credentials,
|
|
357
360
|
headers: sdkConfig.headers,
|
|
358
361
|
};
|
|
362
|
+
const copyClient = new CopyClient_1.CopyClient(baseConfig);
|
|
359
363
|
const queryClient = new QueryClient_1.QueryClient(baseConfig);
|
|
360
364
|
const operationsClient = new OperationClient_1.OperationClient(baseConfig);
|
|
361
365
|
setClients({
|
|
366
|
+
copy: copyClient,
|
|
362
367
|
query: queryClient,
|
|
363
368
|
operations: operationsClient,
|
|
364
369
|
});
|
|
365
370
|
return () => {
|
|
371
|
+
copyClient.close();
|
|
366
372
|
queryClient.close();
|
|
367
373
|
operationsClient.closeAll();
|
|
368
374
|
};
|
|
369
375
|
}, []);
|
|
370
376
|
return clients;
|
|
371
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* Hook for copying data from S3 to graph database with progress monitoring
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* ```tsx
|
|
383
|
+
* const { copyFromS3, loading, progress, error, result } = useCopy('graph_123')
|
|
384
|
+
*
|
|
385
|
+
* const handleImport = async () => {
|
|
386
|
+
* const result = await copyFromS3({
|
|
387
|
+
* table_name: 'companies',
|
|
388
|
+
* source_type: 's3',
|
|
389
|
+
* s3_uri: 's3://my-bucket/data.csv',
|
|
390
|
+
* aws_access_key_id: 'KEY',
|
|
391
|
+
* aws_secret_access_key: 'SECRET',
|
|
392
|
+
* })
|
|
393
|
+
* }
|
|
394
|
+
* ```
|
|
395
|
+
*/
|
|
396
|
+
function useCopy(graphId) {
|
|
397
|
+
const [loading, setLoading] = (0, react_1.useState)(false);
|
|
398
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
399
|
+
const [result, setResult] = (0, react_1.useState)(null);
|
|
400
|
+
const [progress, setProgress] = (0, react_1.useState)(null);
|
|
401
|
+
const [queuePosition, setQueuePosition] = (0, react_1.useState)(null);
|
|
402
|
+
const clientRef = (0, react_1.useRef)(null);
|
|
403
|
+
// Initialize client
|
|
404
|
+
(0, react_1.useEffect)(() => {
|
|
405
|
+
const sdkConfig = (0, config_1.getSDKExtensionsConfig)();
|
|
406
|
+
const clientConfig = client_gen_1.client.getConfig();
|
|
407
|
+
clientRef.current = new CopyClient_1.CopyClient({
|
|
408
|
+
baseUrl: sdkConfig.baseUrl || clientConfig.baseUrl || 'http://localhost:8000',
|
|
409
|
+
credentials: sdkConfig.credentials,
|
|
410
|
+
headers: sdkConfig.headers,
|
|
411
|
+
});
|
|
412
|
+
return () => {
|
|
413
|
+
clientRef.current?.close();
|
|
414
|
+
};
|
|
415
|
+
}, []);
|
|
416
|
+
const copyFromS3 = (0, react_1.useCallback)(async (request, options) => {
|
|
417
|
+
if (!clientRef.current)
|
|
418
|
+
return null;
|
|
419
|
+
setLoading(true);
|
|
420
|
+
setError(null);
|
|
421
|
+
setResult(null);
|
|
422
|
+
setProgress(null);
|
|
423
|
+
setQueuePosition(null);
|
|
424
|
+
try {
|
|
425
|
+
const copyResult = await clientRef.current.copyFromS3(graphId, request, {
|
|
426
|
+
...options,
|
|
427
|
+
onProgress: (message, progressPercent) => {
|
|
428
|
+
setProgress({ message, percent: progressPercent });
|
|
429
|
+
setQueuePosition(null); // Clear queue position when executing
|
|
430
|
+
},
|
|
431
|
+
onQueueUpdate: (position, estimatedWait) => {
|
|
432
|
+
setQueuePosition(position);
|
|
433
|
+
setProgress({
|
|
434
|
+
message: `Queue position: ${position} (est. ${estimatedWait}s)`,
|
|
435
|
+
});
|
|
436
|
+
},
|
|
437
|
+
onWarning: (warning) => {
|
|
438
|
+
console.warn('Copy warning:', warning);
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
setResult(copyResult);
|
|
442
|
+
return copyResult;
|
|
443
|
+
}
|
|
444
|
+
catch (err) {
|
|
445
|
+
const error = err;
|
|
446
|
+
setError(error);
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
finally {
|
|
450
|
+
setLoading(false);
|
|
451
|
+
setQueuePosition(null);
|
|
452
|
+
}
|
|
453
|
+
}, [graphId]);
|
|
454
|
+
// Simple copy method with retry logic
|
|
455
|
+
const copyWithRetry = (0, react_1.useCallback)(async (request, maxRetries = 3) => {
|
|
456
|
+
if (!clientRef.current)
|
|
457
|
+
return null;
|
|
458
|
+
setLoading(true);
|
|
459
|
+
setError(null);
|
|
460
|
+
try {
|
|
461
|
+
const result = await clientRef.current.copyWithRetry(graphId, request, 's3', maxRetries, {
|
|
462
|
+
onProgress: (message, progressPercent) => {
|
|
463
|
+
setProgress({ message, percent: progressPercent });
|
|
464
|
+
},
|
|
465
|
+
});
|
|
466
|
+
setResult(result);
|
|
467
|
+
return result;
|
|
468
|
+
}
|
|
469
|
+
catch (err) {
|
|
470
|
+
const error = err;
|
|
471
|
+
setError(error);
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
finally {
|
|
475
|
+
setLoading(false);
|
|
476
|
+
}
|
|
477
|
+
}, [graphId]);
|
|
478
|
+
// Get statistics from the last copy operation
|
|
479
|
+
const getStatistics = (0, react_1.useCallback)(() => {
|
|
480
|
+
if (!clientRef.current || !result)
|
|
481
|
+
return null;
|
|
482
|
+
return clientRef.current.calculateStatistics(result);
|
|
483
|
+
}, [result]);
|
|
484
|
+
return {
|
|
485
|
+
copyFromS3,
|
|
486
|
+
copyWithRetry,
|
|
487
|
+
getStatistics,
|
|
488
|
+
loading,
|
|
489
|
+
error,
|
|
490
|
+
result,
|
|
491
|
+
progress,
|
|
492
|
+
queuePosition,
|
|
493
|
+
};
|
|
494
|
+
}
|