@robosystems/client 0.2.0 → 0.2.1
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/GraphClient.d.ts +58 -0
- package/extensions/GraphClient.js +163 -0
- package/extensions/GraphClient.test.ts +285 -0
- package/extensions/GraphClient.ts +267 -0
- package/extensions/hooks.d.ts +1 -1
- package/extensions/index.d.ts +5 -1
- package/extensions/index.js +14 -1
- package/extensions/index.test.ts +22 -0
- package/extensions/index.ts +15 -1
- package/package.json +1 -1
- package/sdk-extensions/GraphClient.d.ts +58 -0
- package/sdk-extensions/GraphClient.js +163 -0
- package/sdk-extensions/GraphClient.test.ts +285 -0
- package/sdk-extensions/GraphClient.ts +267 -0
- package/sdk-extensions/hooks.d.ts +1 -1
- package/sdk-extensions/index.d.ts +5 -1
- package/sdk-extensions/index.js +14 -1
- package/sdk-extensions/index.test.ts +22 -0
- package/sdk-extensions/index.ts +15 -1
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.GraphClient = void 0;
|
|
5
|
+
/**
|
|
6
|
+
* Graph Management Client
|
|
7
|
+
* Provides high-level graph management operations with automatic operation monitoring
|
|
8
|
+
*/
|
|
9
|
+
const sdk_gen_1 = require("../sdk/sdk.gen");
|
|
10
|
+
const OperationClient_1 = require("./OperationClient");
|
|
11
|
+
class GraphClient {
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.operationClient = new OperationClient_1.OperationClient(config);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Create a graph and wait for completion
|
|
18
|
+
*/
|
|
19
|
+
async createGraphAndWait(metadata, initialEntity, options = {}) {
|
|
20
|
+
const { timeout = 60000, pollInterval = 2000, onProgress } = options;
|
|
21
|
+
if (!this.config.token) {
|
|
22
|
+
throw new Error('No API key provided. Set token in config.');
|
|
23
|
+
}
|
|
24
|
+
// Build initial entity if provided
|
|
25
|
+
let initialEntityData;
|
|
26
|
+
if (initialEntity) {
|
|
27
|
+
initialEntityData = {
|
|
28
|
+
name: initialEntity.name,
|
|
29
|
+
uri: initialEntity.uri,
|
|
30
|
+
category: initialEntity.category,
|
|
31
|
+
sic: initialEntity.sic,
|
|
32
|
+
sic_description: initialEntity.sicDescription,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// Build API metadata
|
|
36
|
+
const apiMetadata = {
|
|
37
|
+
graph_name: metadata.graphName,
|
|
38
|
+
description: metadata.description,
|
|
39
|
+
schema_extensions: metadata.schemaExtensions || [],
|
|
40
|
+
tags: metadata.tags || [],
|
|
41
|
+
};
|
|
42
|
+
if (onProgress) {
|
|
43
|
+
onProgress(`Creating graph: ${metadata.graphName}`);
|
|
44
|
+
}
|
|
45
|
+
// Create graph request - SDK expects options format, not data format
|
|
46
|
+
const response = await (0, sdk_gen_1.createGraph)({
|
|
47
|
+
body: {
|
|
48
|
+
metadata: apiMetadata,
|
|
49
|
+
initial_entity: initialEntityData || null,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
// Check if we got immediate graph_id
|
|
53
|
+
const responseData = response.data;
|
|
54
|
+
if (responseData?.graph_id) {
|
|
55
|
+
if (onProgress) {
|
|
56
|
+
onProgress(`Graph created: ${responseData.graph_id}`);
|
|
57
|
+
}
|
|
58
|
+
return responseData.graph_id;
|
|
59
|
+
}
|
|
60
|
+
// Otherwise, we have an operation_id to monitor
|
|
61
|
+
if (responseData?.operation_id) {
|
|
62
|
+
const operationId = responseData.operation_id;
|
|
63
|
+
if (onProgress) {
|
|
64
|
+
onProgress(`Graph creation queued (operation: ${operationId})`);
|
|
65
|
+
}
|
|
66
|
+
// Poll operation status
|
|
67
|
+
const maxAttempts = Math.floor(timeout / pollInterval);
|
|
68
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
69
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
70
|
+
const statusResponse = await (0, sdk_gen_1.getOperationStatus)({
|
|
71
|
+
path: { operation_id: operationId },
|
|
72
|
+
});
|
|
73
|
+
const statusData = statusResponse.data;
|
|
74
|
+
const status = statusData?.status;
|
|
75
|
+
if (onProgress) {
|
|
76
|
+
onProgress(`Status: ${status} (attempt ${attempt + 1}/${maxAttempts})`);
|
|
77
|
+
}
|
|
78
|
+
if (status === 'completed') {
|
|
79
|
+
const result = statusData?.result;
|
|
80
|
+
const graphId = result?.graph_id;
|
|
81
|
+
if (graphId) {
|
|
82
|
+
if (onProgress) {
|
|
83
|
+
onProgress(`Graph created: ${graphId}`);
|
|
84
|
+
}
|
|
85
|
+
return graphId;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
throw new Error('Operation completed but no graph_id in result');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else if (status === 'failed') {
|
|
92
|
+
const error = statusData?.error || statusData?.message || 'Unknown error';
|
|
93
|
+
throw new Error(`Graph creation failed: ${error}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
throw new Error(`Graph creation timed out after ${timeout}ms`);
|
|
97
|
+
}
|
|
98
|
+
throw new Error('No graph_id or operation_id in response');
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get information about a graph
|
|
102
|
+
*/
|
|
103
|
+
async getGraphInfo(graphId) {
|
|
104
|
+
if (!this.config.token) {
|
|
105
|
+
throw new Error('No API key provided. Set token in config.');
|
|
106
|
+
}
|
|
107
|
+
// Use getGraphs to list all graphs and find the one we want
|
|
108
|
+
// Note: This is a workaround since there's no dedicated getGraph endpoint yet
|
|
109
|
+
const response = await (0, sdk_gen_1.getGraphs)();
|
|
110
|
+
const graphs = response.data?.graphs || [];
|
|
111
|
+
const graph = graphs.find((g) => g.graph_id === graphId || g.id === graphId);
|
|
112
|
+
if (!graph) {
|
|
113
|
+
throw new Error(`Graph not found: ${graphId}`);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
graphId: graph.graph_id || graph.id,
|
|
117
|
+
graphName: graph.graph_name || graph.name,
|
|
118
|
+
description: graph.description,
|
|
119
|
+
schemaExtensions: graph.schema_extensions,
|
|
120
|
+
tags: graph.tags,
|
|
121
|
+
createdAt: graph.created_at,
|
|
122
|
+
status: graph.status,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* List all graphs
|
|
127
|
+
*/
|
|
128
|
+
async listGraphs() {
|
|
129
|
+
if (!this.config.token) {
|
|
130
|
+
throw new Error('No API key provided. Set token in config.');
|
|
131
|
+
}
|
|
132
|
+
const response = await (0, sdk_gen_1.getGraphs)();
|
|
133
|
+
const graphs = response.data?.graphs || [];
|
|
134
|
+
return graphs.map((graph) => ({
|
|
135
|
+
graphId: graph.graph_id || graph.id,
|
|
136
|
+
graphName: graph.graph_name || graph.name,
|
|
137
|
+
description: graph.description,
|
|
138
|
+
schemaExtensions: graph.schema_extensions,
|
|
139
|
+
tags: graph.tags,
|
|
140
|
+
createdAt: graph.created_at,
|
|
141
|
+
status: graph.status,
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Delete a graph
|
|
146
|
+
* Note: This will be implemented when the deleteGraph endpoint is available in the SDK
|
|
147
|
+
*/
|
|
148
|
+
async deleteGraph(graphId) {
|
|
149
|
+
throw new Error('deleteGraph is not yet implemented - waiting for SDK endpoint to be generated');
|
|
150
|
+
// TODO: Implement when deleteGraph endpoint is available
|
|
151
|
+
// const response = await deleteGraph({ path: { graph_id: graphId } })
|
|
152
|
+
// if (response.status !== 200 && response.status !== 204) {
|
|
153
|
+
// throw new Error(`Failed to delete graph: ${response.status}`)
|
|
154
|
+
// }
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Clean up resources
|
|
158
|
+
*/
|
|
159
|
+
close() {
|
|
160
|
+
this.operationClient.closeAll();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
exports.GraphClient = GraphClient;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import type { GraphMetadataInput, InitialEntityInput } from './GraphClient'
|
|
3
|
+
import { GraphClient } from './GraphClient'
|
|
4
|
+
|
|
5
|
+
// Helper to create proper mock Response objects
|
|
6
|
+
function createMockResponse(data: any, options: { ok?: boolean; status?: number } = {}) {
|
|
7
|
+
return {
|
|
8
|
+
ok: options.ok ?? true,
|
|
9
|
+
status: options.status ?? 200,
|
|
10
|
+
statusText: options.status === 200 ? 'OK' : 'Error',
|
|
11
|
+
headers: new Headers({ 'content-type': 'application/json' }),
|
|
12
|
+
json: async () => data,
|
|
13
|
+
text: async () => JSON.stringify(data),
|
|
14
|
+
blob: async () => new Blob([JSON.stringify(data)]),
|
|
15
|
+
arrayBuffer: async () => new TextEncoder().encode(JSON.stringify(data)).buffer,
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('GraphClient', () => {
|
|
20
|
+
let graphClient: GraphClient
|
|
21
|
+
let mockFetch: any
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
graphClient = new GraphClient({
|
|
25
|
+
baseUrl: 'http://localhost:8000',
|
|
26
|
+
token: 'test-api-key',
|
|
27
|
+
headers: { 'X-API-Key': 'test-api-key' },
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// Mock global fetch
|
|
31
|
+
mockFetch = vi.fn()
|
|
32
|
+
global.fetch = mockFetch
|
|
33
|
+
|
|
34
|
+
// Reset all mocks
|
|
35
|
+
vi.clearAllMocks()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('createGraphAndWait', () => {
|
|
39
|
+
const mockMetadata: GraphMetadataInput = {
|
|
40
|
+
graphName: 'Test Graph',
|
|
41
|
+
description: 'A test graph',
|
|
42
|
+
schemaExtensions: ['custom_prop'],
|
|
43
|
+
tags: ['test'],
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
it('should create graph with immediate response', async () => {
|
|
47
|
+
mockFetch.mockResolvedValueOnce(
|
|
48
|
+
createMockResponse({
|
|
49
|
+
graph_id: 'graph_123',
|
|
50
|
+
})
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const graphId = await graphClient.createGraphAndWait(mockMetadata)
|
|
54
|
+
|
|
55
|
+
expect(graphId).toBe('graph_123')
|
|
56
|
+
expect(mockFetch).toHaveBeenCalled()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should create graph with initial entity', async () => {
|
|
60
|
+
const mockInitialEntity: InitialEntityInput = {
|
|
61
|
+
name: 'ACME Corp',
|
|
62
|
+
uri: 'https://example.com/acme',
|
|
63
|
+
category: 'Technology',
|
|
64
|
+
sic: '7372',
|
|
65
|
+
sicDescription: 'Prepackaged Software',
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
mockFetch.mockResolvedValueOnce(
|
|
69
|
+
createMockResponse({
|
|
70
|
+
graph_id: 'graph_456',
|
|
71
|
+
})
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const graphId = await graphClient.createGraphAndWait(mockMetadata, mockInitialEntity)
|
|
75
|
+
|
|
76
|
+
expect(graphId).toBe('graph_456')
|
|
77
|
+
expect(mockFetch).toHaveBeenCalled()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should poll operation status when queued', async () => {
|
|
81
|
+
// First call: createGraph returns operation_id
|
|
82
|
+
mockFetch.mockResolvedValueOnce(
|
|
83
|
+
createMockResponse({
|
|
84
|
+
operation_id: 'op_789',
|
|
85
|
+
})
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
// Second call: getOperationStatus returns pending
|
|
89
|
+
mockFetch.mockResolvedValueOnce(
|
|
90
|
+
createMockResponse({
|
|
91
|
+
status: 'pending',
|
|
92
|
+
})
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
// Third call: getOperationStatus returns completed
|
|
96
|
+
mockFetch.mockResolvedValueOnce(
|
|
97
|
+
createMockResponse({
|
|
98
|
+
status: 'completed',
|
|
99
|
+
result: {
|
|
100
|
+
graph_id: 'graph_completed',
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
const graphId = await graphClient.createGraphAndWait(mockMetadata, undefined, {
|
|
106
|
+
pollInterval: 100,
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
expect(graphId).toBe('graph_completed')
|
|
110
|
+
expect(mockFetch).toHaveBeenCalledTimes(3)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('should handle operation failure', async () => {
|
|
114
|
+
mockFetch
|
|
115
|
+
.mockResolvedValueOnce(
|
|
116
|
+
createMockResponse({
|
|
117
|
+
operation_id: 'op_fail',
|
|
118
|
+
})
|
|
119
|
+
)
|
|
120
|
+
.mockResolvedValueOnce(
|
|
121
|
+
createMockResponse({
|
|
122
|
+
status: 'failed',
|
|
123
|
+
error: 'Graph creation failed',
|
|
124
|
+
})
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
await expect(
|
|
128
|
+
graphClient.createGraphAndWait(mockMetadata, undefined, { pollInterval: 100 })
|
|
129
|
+
).rejects.toThrow('Graph creation failed')
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('should timeout if operation takes too long', async () => {
|
|
133
|
+
mockFetch
|
|
134
|
+
.mockResolvedValueOnce(
|
|
135
|
+
createMockResponse({
|
|
136
|
+
operation_id: 'op_timeout',
|
|
137
|
+
})
|
|
138
|
+
)
|
|
139
|
+
.mockResolvedValue(
|
|
140
|
+
createMockResponse({
|
|
141
|
+
status: 'pending',
|
|
142
|
+
})
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
await expect(
|
|
146
|
+
graphClient.createGraphAndWait(mockMetadata, undefined, {
|
|
147
|
+
timeout: 500,
|
|
148
|
+
pollInterval: 100,
|
|
149
|
+
})
|
|
150
|
+
).rejects.toThrow('timed out')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('should call onProgress callback', async () => {
|
|
154
|
+
mockFetch.mockResolvedValueOnce(
|
|
155
|
+
createMockResponse({
|
|
156
|
+
graph_id: 'graph_progress',
|
|
157
|
+
})
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
const onProgress = vi.fn()
|
|
161
|
+
|
|
162
|
+
await graphClient.createGraphAndWait(mockMetadata, undefined, { onProgress })
|
|
163
|
+
|
|
164
|
+
expect(onProgress).toHaveBeenCalledWith(expect.stringContaining('Creating graph'))
|
|
165
|
+
expect(onProgress).toHaveBeenCalledWith(expect.stringContaining('Graph created'))
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('should throw error if no token provided', async () => {
|
|
169
|
+
const clientNoToken = new GraphClient({
|
|
170
|
+
baseUrl: 'http://localhost:8000',
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
await expect(clientNoToken.createGraphAndWait(mockMetadata)).rejects.toThrow(
|
|
174
|
+
'No API key provided'
|
|
175
|
+
)
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
describe('getGraphInfo', () => {
|
|
180
|
+
it('should get graph info by ID', async () => {
|
|
181
|
+
mockFetch.mockResolvedValueOnce(
|
|
182
|
+
createMockResponse({
|
|
183
|
+
graphs: [
|
|
184
|
+
{
|
|
185
|
+
graph_id: 'graph_123',
|
|
186
|
+
graph_name: 'Test Graph',
|
|
187
|
+
description: 'Test description',
|
|
188
|
+
schema_extensions: ['prop1'],
|
|
189
|
+
tags: ['test'],
|
|
190
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
191
|
+
status: 'active',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
graph_id: 'graph_456',
|
|
195
|
+
graph_name: 'Other Graph',
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
})
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
const info = await graphClient.getGraphInfo('graph_123')
|
|
202
|
+
|
|
203
|
+
expect(info.graphId).toBe('graph_123')
|
|
204
|
+
expect(info.graphName).toBe('Test Graph')
|
|
205
|
+
expect(info.description).toBe('Test description')
|
|
206
|
+
expect(info.tags).toEqual(['test'])
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('should throw error if graph not found', async () => {
|
|
210
|
+
mockFetch.mockResolvedValueOnce(
|
|
211
|
+
createMockResponse({
|
|
212
|
+
graphs: [
|
|
213
|
+
{
|
|
214
|
+
graph_id: 'graph_other',
|
|
215
|
+
graph_name: 'Other Graph',
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
})
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
await expect(graphClient.getGraphInfo('graph_notfound')).rejects.toThrow('Graph not found')
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('should throw error if no token provided', async () => {
|
|
225
|
+
const clientNoToken = new GraphClient({
|
|
226
|
+
baseUrl: 'http://localhost:8000',
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
await expect(clientNoToken.getGraphInfo('graph_123')).rejects.toThrow('No API key provided')
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
describe('listGraphs', () => {
|
|
234
|
+
it('should list all graphs', async () => {
|
|
235
|
+
mockFetch.mockResolvedValueOnce(
|
|
236
|
+
createMockResponse({
|
|
237
|
+
graphs: [
|
|
238
|
+
{
|
|
239
|
+
graph_id: 'graph_1',
|
|
240
|
+
graph_name: 'Graph 1',
|
|
241
|
+
description: 'First graph',
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
graph_id: 'graph_2',
|
|
245
|
+
graph_name: 'Graph 2',
|
|
246
|
+
tags: ['production'],
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
})
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
const graphs = await graphClient.listGraphs()
|
|
253
|
+
|
|
254
|
+
expect(graphs).toHaveLength(2)
|
|
255
|
+
expect(graphs[0].graphId).toBe('graph_1')
|
|
256
|
+
expect(graphs[0].graphName).toBe('Graph 1')
|
|
257
|
+
expect(graphs[1].graphId).toBe('graph_2')
|
|
258
|
+
expect(graphs[1].tags).toEqual(['production'])
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it('should return empty array if no graphs', async () => {
|
|
262
|
+
mockFetch.mockResolvedValueOnce(
|
|
263
|
+
createMockResponse({
|
|
264
|
+
graphs: [],
|
|
265
|
+
})
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
const graphs = await graphClient.listGraphs()
|
|
269
|
+
|
|
270
|
+
expect(graphs).toEqual([])
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
describe('deleteGraph', () => {
|
|
275
|
+
it('should throw not implemented error', async () => {
|
|
276
|
+
await expect(graphClient.deleteGraph('graph_123')).rejects.toThrow('not yet implemented')
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
describe('close', () => {
|
|
281
|
+
it('should close without errors', () => {
|
|
282
|
+
expect(() => graphClient.close()).not.toThrow()
|
|
283
|
+
})
|
|
284
|
+
})
|
|
285
|
+
})
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Graph Management Client
|
|
5
|
+
* Provides high-level graph management operations with automatic operation monitoring
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createGraph, getGraphs, getOperationStatus } from '../sdk/sdk.gen'
|
|
9
|
+
import type { GraphMetadata, InitialEntityData } from '../sdk/types.gen'
|
|
10
|
+
import { OperationClient } from './OperationClient'
|
|
11
|
+
|
|
12
|
+
// API Response Types
|
|
13
|
+
interface GraphCreateResponse {
|
|
14
|
+
graph_id?: string
|
|
15
|
+
operation_id?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface OperationStatusResponse {
|
|
19
|
+
status?: 'pending' | 'completed' | 'failed' | string
|
|
20
|
+
result?: {
|
|
21
|
+
graph_id?: string
|
|
22
|
+
}
|
|
23
|
+
error?: string
|
|
24
|
+
message?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface GraphApiData {
|
|
28
|
+
graph_id?: string
|
|
29
|
+
id?: string
|
|
30
|
+
graph_name?: string
|
|
31
|
+
name?: string
|
|
32
|
+
description?: string
|
|
33
|
+
schema_extensions?: string[]
|
|
34
|
+
tags?: string[]
|
|
35
|
+
created_at?: string
|
|
36
|
+
status?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface GetGraphsResponse {
|
|
40
|
+
graphs?: GraphApiData[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface GraphMetadataInput {
|
|
44
|
+
graphName: string
|
|
45
|
+
description?: string
|
|
46
|
+
schemaExtensions?: string[]
|
|
47
|
+
tags?: string[]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface InitialEntityInput {
|
|
51
|
+
name: string
|
|
52
|
+
uri: string
|
|
53
|
+
category?: string
|
|
54
|
+
sic?: string
|
|
55
|
+
sicDescription?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface GraphInfo {
|
|
59
|
+
graphId: string
|
|
60
|
+
graphName: string
|
|
61
|
+
description?: string
|
|
62
|
+
schemaExtensions?: string[]
|
|
63
|
+
tags?: string[]
|
|
64
|
+
createdAt?: string
|
|
65
|
+
status?: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface CreateGraphOptions {
|
|
69
|
+
timeout?: number
|
|
70
|
+
pollInterval?: number
|
|
71
|
+
onProgress?: (message: string) => void
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export class GraphClient {
|
|
75
|
+
private operationClient: OperationClient
|
|
76
|
+
private config: {
|
|
77
|
+
baseUrl: string
|
|
78
|
+
credentials?: 'include' | 'same-origin' | 'omit'
|
|
79
|
+
headers?: Record<string, string>
|
|
80
|
+
token?: string
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
constructor(config: {
|
|
84
|
+
baseUrl: string
|
|
85
|
+
credentials?: 'include' | 'same-origin' | 'omit'
|
|
86
|
+
headers?: Record<string, string>
|
|
87
|
+
token?: string
|
|
88
|
+
}) {
|
|
89
|
+
this.config = config
|
|
90
|
+
this.operationClient = new OperationClient(config)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Create a graph and wait for completion
|
|
95
|
+
*/
|
|
96
|
+
async createGraphAndWait(
|
|
97
|
+
metadata: GraphMetadataInput,
|
|
98
|
+
initialEntity?: InitialEntityInput,
|
|
99
|
+
options: CreateGraphOptions = {}
|
|
100
|
+
): Promise<string> {
|
|
101
|
+
const { timeout = 60000, pollInterval = 2000, onProgress } = options
|
|
102
|
+
|
|
103
|
+
if (!this.config.token) {
|
|
104
|
+
throw new Error('No API key provided. Set token in config.')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Build initial entity if provided
|
|
108
|
+
let initialEntityData: InitialEntityData | undefined
|
|
109
|
+
if (initialEntity) {
|
|
110
|
+
initialEntityData = {
|
|
111
|
+
name: initialEntity.name,
|
|
112
|
+
uri: initialEntity.uri,
|
|
113
|
+
category: initialEntity.category,
|
|
114
|
+
sic: initialEntity.sic,
|
|
115
|
+
sic_description: initialEntity.sicDescription,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Build API metadata
|
|
120
|
+
const apiMetadata: GraphMetadata = {
|
|
121
|
+
graph_name: metadata.graphName,
|
|
122
|
+
description: metadata.description,
|
|
123
|
+
schema_extensions: metadata.schemaExtensions || [],
|
|
124
|
+
tags: metadata.tags || [],
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (onProgress) {
|
|
128
|
+
onProgress(`Creating graph: ${metadata.graphName}`)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Create graph request - SDK expects options format, not data format
|
|
132
|
+
const response = await createGraph({
|
|
133
|
+
body: {
|
|
134
|
+
metadata: apiMetadata,
|
|
135
|
+
initial_entity: initialEntityData || null,
|
|
136
|
+
},
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
// Check if we got immediate graph_id
|
|
140
|
+
const responseData = response.data as GraphCreateResponse
|
|
141
|
+
if (responseData?.graph_id) {
|
|
142
|
+
if (onProgress) {
|
|
143
|
+
onProgress(`Graph created: ${responseData.graph_id}`)
|
|
144
|
+
}
|
|
145
|
+
return responseData.graph_id
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Otherwise, we have an operation_id to monitor
|
|
149
|
+
if (responseData?.operation_id) {
|
|
150
|
+
const operationId = responseData.operation_id
|
|
151
|
+
|
|
152
|
+
if (onProgress) {
|
|
153
|
+
onProgress(`Graph creation queued (operation: ${operationId})`)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Poll operation status
|
|
157
|
+
const maxAttempts = Math.floor(timeout / pollInterval)
|
|
158
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
159
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval))
|
|
160
|
+
|
|
161
|
+
const statusResponse = await getOperationStatus({
|
|
162
|
+
path: { operation_id: operationId },
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
const statusData = statusResponse.data as OperationStatusResponse
|
|
166
|
+
const status = statusData?.status
|
|
167
|
+
|
|
168
|
+
if (onProgress) {
|
|
169
|
+
onProgress(`Status: ${status} (attempt ${attempt + 1}/${maxAttempts})`)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (status === 'completed') {
|
|
173
|
+
const result = statusData?.result
|
|
174
|
+
const graphId = result?.graph_id
|
|
175
|
+
|
|
176
|
+
if (graphId) {
|
|
177
|
+
if (onProgress) {
|
|
178
|
+
onProgress(`Graph created: ${graphId}`)
|
|
179
|
+
}
|
|
180
|
+
return graphId
|
|
181
|
+
} else {
|
|
182
|
+
throw new Error('Operation completed but no graph_id in result')
|
|
183
|
+
}
|
|
184
|
+
} else if (status === 'failed') {
|
|
185
|
+
const error = statusData?.error || statusData?.message || 'Unknown error'
|
|
186
|
+
throw new Error(`Graph creation failed: ${error}`)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
throw new Error(`Graph creation timed out after ${timeout}ms`)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
throw new Error('No graph_id or operation_id in response')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get information about a graph
|
|
198
|
+
*/
|
|
199
|
+
async getGraphInfo(graphId: string): Promise<GraphInfo> {
|
|
200
|
+
if (!this.config.token) {
|
|
201
|
+
throw new Error('No API key provided. Set token in config.')
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Use getGraphs to list all graphs and find the one we want
|
|
205
|
+
// Note: This is a workaround since there's no dedicated getGraph endpoint yet
|
|
206
|
+
const response = await getGraphs()
|
|
207
|
+
const graphs = (response.data as GetGraphsResponse)?.graphs || []
|
|
208
|
+
|
|
209
|
+
const graph = graphs.find((g) => g.graph_id === graphId || g.id === graphId)
|
|
210
|
+
|
|
211
|
+
if (!graph) {
|
|
212
|
+
throw new Error(`Graph not found: ${graphId}`)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
graphId: graph.graph_id || graph.id,
|
|
217
|
+
graphName: graph.graph_name || graph.name,
|
|
218
|
+
description: graph.description,
|
|
219
|
+
schemaExtensions: graph.schema_extensions,
|
|
220
|
+
tags: graph.tags,
|
|
221
|
+
createdAt: graph.created_at,
|
|
222
|
+
status: graph.status,
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* List all graphs
|
|
228
|
+
*/
|
|
229
|
+
async listGraphs(): Promise<GraphInfo[]> {
|
|
230
|
+
if (!this.config.token) {
|
|
231
|
+
throw new Error('No API key provided. Set token in config.')
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const response = await getGraphs()
|
|
235
|
+
const graphs = (response.data as GetGraphsResponse)?.graphs || []
|
|
236
|
+
|
|
237
|
+
return graphs.map((graph) => ({
|
|
238
|
+
graphId: graph.graph_id || graph.id,
|
|
239
|
+
graphName: graph.graph_name || graph.name,
|
|
240
|
+
description: graph.description,
|
|
241
|
+
schemaExtensions: graph.schema_extensions,
|
|
242
|
+
tags: graph.tags,
|
|
243
|
+
createdAt: graph.created_at,
|
|
244
|
+
status: graph.status,
|
|
245
|
+
}))
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Delete a graph
|
|
250
|
+
* Note: This will be implemented when the deleteGraph endpoint is available in the SDK
|
|
251
|
+
*/
|
|
252
|
+
async deleteGraph(graphId: string): Promise<void> {
|
|
253
|
+
throw new Error('deleteGraph is not yet implemented - waiting for SDK endpoint to be generated')
|
|
254
|
+
// TODO: Implement when deleteGraph endpoint is available
|
|
255
|
+
// const response = await deleteGraph({ path: { graph_id: graphId } })
|
|
256
|
+
// if (response.status !== 200 && response.status !== 204) {
|
|
257
|
+
// throw new Error(`Failed to delete graph: ${response.status}`)
|
|
258
|
+
// }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Clean up resources
|
|
263
|
+
*/
|
|
264
|
+
close(): void {
|
|
265
|
+
this.operationClient.closeAll()
|
|
266
|
+
}
|
|
267
|
+
}
|