@bardioc/app-sdk 0.4.0
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/LICENSE +5 -0
- package/README.md +368 -0
- package/assets/fonts/README.md +11 -0
- package/assets/fonts/bardioc-fonts.css +55 -0
- package/assets/fonts/v1/geist-mono-latin-wght-normal.woff2 +0 -0
- package/assets/fonts/v1/nunito-sans-latin-wght-italic.woff2 +0 -0
- package/assets/fonts/v1/nunito-sans-latin-wght-normal.woff2 +0 -0
- package/dist/contract-matrix.d.ts +130 -0
- package/dist/contract-matrix.js +132 -0
- package/dist/dev-auth-proxy-core.d.ts +24 -0
- package/dist/dev-auth-proxy-core.js +59 -0
- package/dist/dev-auth-vite.d.ts +34 -0
- package/dist/dev-auth-vite.js +221 -0
- package/dist/dev-proxy.d.ts +8 -0
- package/dist/dev-proxy.js +40 -0
- package/dist/dev-session-cli.d.ts +34 -0
- package/dist/dev-session-cli.js +125 -0
- package/dist/dev.d.ts +33 -0
- package/dist/dev.js +223 -0
- package/dist/dot-env.d.ts +2 -0
- package/dist/dot-env.js +22 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +57 -0
- package/dist/host-bridge.d.ts +3 -0
- package/dist/host-bridge.js +260 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +6 -0
- package/dist/manifest.d.ts +78 -0
- package/dist/manifest.js +169 -0
- package/dist/protocol.d.ts +26 -0
- package/dist/protocol.js +28 -0
- package/dist/react.d.ts +26 -0
- package/dist/react.js +208 -0
- package/dist/transports/graph-transport.d.ts +224 -0
- package/dist/transports/graph-transport.js +584 -0
- package/dist/transports/os-transport.d.ts +189 -0
- package/dist/transports/os-transport.js +444 -0
- package/dist/types.d.ts +343 -0
- package/dist/vite.d.ts +9 -0
- package/dist/vite.js +262 -0
- package/package.json +101 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
import { EntityNotFoundError, NetworkError, PermissionError, SdkError, TimeoutError, ValidationError, } from '../errors.js';
|
|
2
|
+
const GRAPH_BASE = '/bardioc/api/graph/7.4';
|
|
3
|
+
function buildQueryString(params) {
|
|
4
|
+
const searchParams = new URLSearchParams();
|
|
5
|
+
for (const [key, value] of Object.entries(params)) {
|
|
6
|
+
if (value !== undefined && value !== '') {
|
|
7
|
+
searchParams.set(key, String(value));
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
const qs = searchParams.toString();
|
|
11
|
+
return qs ? `?${qs}` : '';
|
|
12
|
+
}
|
|
13
|
+
// Maps backend errors to typed SDK errors
|
|
14
|
+
function parseError(error, operation, context) {
|
|
15
|
+
let statusCode;
|
|
16
|
+
let errorMessage = 'Unknown error';
|
|
17
|
+
// Check for direct message on error object (e.g. from fetch/network errors)
|
|
18
|
+
if (error.message && typeof error.message === 'string') {
|
|
19
|
+
errorMessage = error.message;
|
|
20
|
+
}
|
|
21
|
+
// Backend may return status in responsePayload
|
|
22
|
+
if (error.responsePayload && typeof error.responsePayload === 'object') {
|
|
23
|
+
statusCode = error.responsePayload.status || error.responsePayload.statusCode;
|
|
24
|
+
if (error.responsePayload.message) {
|
|
25
|
+
errorMessage = error.responsePayload.message;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Check for timeout errors
|
|
29
|
+
if (errorMessage.includes('timed out') || errorMessage.includes('timeout')) {
|
|
30
|
+
return new TimeoutError(operation, 10000);
|
|
31
|
+
}
|
|
32
|
+
// Map by status code
|
|
33
|
+
if (statusCode) {
|
|
34
|
+
const fullContext = {
|
|
35
|
+
...context,
|
|
36
|
+
operation,
|
|
37
|
+
timestamp: Date.now(),
|
|
38
|
+
statusCode,
|
|
39
|
+
};
|
|
40
|
+
switch (statusCode) {
|
|
41
|
+
case 404:
|
|
42
|
+
return new EntityNotFoundError(context?.id || context?.xid || 'unknown');
|
|
43
|
+
case 400:
|
|
44
|
+
return new ValidationError(errorMessage, context?.field);
|
|
45
|
+
case 403:
|
|
46
|
+
return new PermissionError(errorMessage);
|
|
47
|
+
case 408:
|
|
48
|
+
return new TimeoutError(operation, 10000);
|
|
49
|
+
case 500:
|
|
50
|
+
case 502:
|
|
51
|
+
case 503:
|
|
52
|
+
return new NetworkError(errorMessage, statusCode);
|
|
53
|
+
default:
|
|
54
|
+
if (statusCode >= 400) {
|
|
55
|
+
return new NetworkError(errorMessage, statusCode);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Default to generic SDK error
|
|
60
|
+
return new SdkError(errorMessage, 'UNKNOWN_ERROR', statusCode, {
|
|
61
|
+
...context,
|
|
62
|
+
operation,
|
|
63
|
+
timestamp: Date.now(),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** Graph transport class providing graph database operations */
|
|
67
|
+
export class GraphTransport {
|
|
68
|
+
_request;
|
|
69
|
+
_debug;
|
|
70
|
+
constructor(_request, options) {
|
|
71
|
+
this._request = _request;
|
|
72
|
+
this._debug = options?.debug ?? false;
|
|
73
|
+
}
|
|
74
|
+
log(...args) {
|
|
75
|
+
if (this._debug)
|
|
76
|
+
console.log('[graph-transport]', ...args);
|
|
77
|
+
}
|
|
78
|
+
withScope(payload, options) {
|
|
79
|
+
if (!options?.scopeId) {
|
|
80
|
+
return payload;
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
...payload,
|
|
84
|
+
scopeId: options.scopeId,
|
|
85
|
+
scopePolicy: 'required',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get single node by ID
|
|
90
|
+
* Returns raw backend format (ogit/* fields)
|
|
91
|
+
*
|
|
92
|
+
* @throws {EntityNotFoundError} if node doesn't exist
|
|
93
|
+
* @throws {NetworkError} on HTTP errors
|
|
94
|
+
* @throws {TimeoutError} on request timeout
|
|
95
|
+
*/
|
|
96
|
+
async get(id, options) {
|
|
97
|
+
try {
|
|
98
|
+
const qs = buildQueryString({
|
|
99
|
+
fields: options?.fields,
|
|
100
|
+
includeDeleted: options?.includeDeleted,
|
|
101
|
+
listMeta: options?.listMeta,
|
|
102
|
+
vid: options?.vid,
|
|
103
|
+
});
|
|
104
|
+
return await this._request(this.withScope({
|
|
105
|
+
path: `${GRAPH_BASE}/${encodeURIComponent(id)}${qs}`,
|
|
106
|
+
method: 'GET',
|
|
107
|
+
}, options));
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
throw parseError(error, 'graph.get', { id, ...options });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get multiple nodes by IDs (batched request)
|
|
115
|
+
* Uses /query/ids endpoint
|
|
116
|
+
*
|
|
117
|
+
* @returns Array in same order as input IDs (missing nodes = undefined)
|
|
118
|
+
* @throws {ValidationError} if ids array is empty or invalid
|
|
119
|
+
* @throws {NetworkError} on HTTP errors
|
|
120
|
+
* @throws {TimeoutError} on request timeout
|
|
121
|
+
*/
|
|
122
|
+
async getMany(ids, options) {
|
|
123
|
+
try {
|
|
124
|
+
const qs = buildQueryString({
|
|
125
|
+
query: ids.join(','),
|
|
126
|
+
fields: options?.fields,
|
|
127
|
+
includeDeleted: options?.includeDeleted,
|
|
128
|
+
listMeta: options?.listMeta,
|
|
129
|
+
});
|
|
130
|
+
this.log('getMany', { path: `${GRAPH_BASE}/query/ids${qs}`, count: ids.length });
|
|
131
|
+
return await this._request(this.withScope({
|
|
132
|
+
path: `${GRAPH_BASE}/query/ids${qs}`,
|
|
133
|
+
method: 'GET',
|
|
134
|
+
}, options));
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
throw parseError(error, 'graph.getMany', { ids: ids.length, ...options });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get node by external ID
|
|
142
|
+
*
|
|
143
|
+
* @throws {EntityNotFoundError} if XID doesn't exist
|
|
144
|
+
* @throws {NetworkError} on HTTP errors
|
|
145
|
+
* @throws {TimeoutError} on request timeout
|
|
146
|
+
*/
|
|
147
|
+
async getByXid(xid, options) {
|
|
148
|
+
try {
|
|
149
|
+
const qs = buildQueryString({
|
|
150
|
+
fields: options?.fields,
|
|
151
|
+
includeDeleted: options?.includeDeleted,
|
|
152
|
+
listMeta: options?.listMeta,
|
|
153
|
+
});
|
|
154
|
+
return await this._request(this.withScope({
|
|
155
|
+
path: `${GRAPH_BASE}/xid/${encodeURIComponent(xid)}${qs}`,
|
|
156
|
+
method: 'GET',
|
|
157
|
+
}, options));
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
throw parseError(error, 'graph.getByXid', { xid, ...options });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Query nodes using Lucene syntax
|
|
165
|
+
* Returns raw backend format (ogit/* fields)
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* const people = await graph.query<GraphNodeRaw>(
|
|
169
|
+
* 'ogit/_type:ogit/Person',
|
|
170
|
+
* { limit: 50, offset: 0 }
|
|
171
|
+
* );
|
|
172
|
+
*
|
|
173
|
+
* @throws {ValidationError} if query syntax is invalid
|
|
174
|
+
* @throws {NetworkError} on HTTP errors
|
|
175
|
+
* @throws {TimeoutError} on request timeout
|
|
176
|
+
*/
|
|
177
|
+
async query(lucene, options) {
|
|
178
|
+
try {
|
|
179
|
+
const qs = buildQueryString({
|
|
180
|
+
query: lucene,
|
|
181
|
+
limit: options?.limit,
|
|
182
|
+
offset: options?.offset,
|
|
183
|
+
fields: options?.fields,
|
|
184
|
+
includeDeleted: options?.includeDeleted,
|
|
185
|
+
listMeta: options?.listMeta,
|
|
186
|
+
order: options?.order,
|
|
187
|
+
});
|
|
188
|
+
this.log('query', {
|
|
189
|
+
path: `${GRAPH_BASE}/query/vertices${qs}`,
|
|
190
|
+
lucene,
|
|
191
|
+
limit: options?.limit,
|
|
192
|
+
offset: options?.offset,
|
|
193
|
+
});
|
|
194
|
+
return await this._request(this.withScope({
|
|
195
|
+
path: `${GRAPH_BASE}/query/vertices${qs}`,
|
|
196
|
+
method: 'GET',
|
|
197
|
+
}, options));
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
throw parseError(error, 'graph.query', { lucene, ...options });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Execute Gremlin traversal query
|
|
205
|
+
* Returns raw results (structure varies by query)
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* const edges = await graph.gremlin(rootId, "outE('ogit/relates')");
|
|
209
|
+
*
|
|
210
|
+
* @throws {EntityNotFoundError} if root node doesn't exist
|
|
211
|
+
* @throws {ValidationError} if query syntax is invalid
|
|
212
|
+
* @throws {NetworkError} on HTTP errors
|
|
213
|
+
* @throws {TimeoutError} on request timeout
|
|
214
|
+
*/
|
|
215
|
+
async gremlin(rootId, query, options) {
|
|
216
|
+
try {
|
|
217
|
+
return await this._request(this.withScope({
|
|
218
|
+
path: `${GRAPH_BASE}/query/gremlin`,
|
|
219
|
+
method: 'POST',
|
|
220
|
+
body: {
|
|
221
|
+
root: rootId,
|
|
222
|
+
query,
|
|
223
|
+
fields: options?.fields,
|
|
224
|
+
includeDeleted: options?.includeDeleted,
|
|
225
|
+
listMeta: options?.listMeta,
|
|
226
|
+
},
|
|
227
|
+
contentType: 'application/json',
|
|
228
|
+
}, options));
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
throw parseError(error, 'graph.gremlin', { rootId, query, ...options });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Execute combined query (Lucene + Gremlin in single call)
|
|
236
|
+
* Returns raw results (structure varies by query)
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* const results = await graph.combined(
|
|
240
|
+
* 'ogit/_type:ogit/Person AND name:Test',
|
|
241
|
+
* { limit: 50 }
|
|
242
|
+
* );
|
|
243
|
+
*
|
|
244
|
+
* @throws {ValidationError} if query syntax is invalid
|
|
245
|
+
* @throws {NetworkError} on HTTP errors
|
|
246
|
+
* @throws {TimeoutError} on request timeout
|
|
247
|
+
*/
|
|
248
|
+
async combined(query, options) {
|
|
249
|
+
try {
|
|
250
|
+
const qs = buildQueryString({
|
|
251
|
+
query,
|
|
252
|
+
limit: options?.limit,
|
|
253
|
+
offset: options?.offset,
|
|
254
|
+
fields: options?.fields,
|
|
255
|
+
includeDeleted: options?.includeDeleted,
|
|
256
|
+
listMeta: options?.listMeta,
|
|
257
|
+
order: options?.order,
|
|
258
|
+
});
|
|
259
|
+
this.log('combined', { path: `${GRAPH_BASE}/query/combined${qs}`, query });
|
|
260
|
+
return await this._request(this.withScope({
|
|
261
|
+
path: `${GRAPH_BASE}/query/combined${qs}`,
|
|
262
|
+
method: 'GET',
|
|
263
|
+
}, options));
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
throw parseError(error, 'graph.combined', { query, ...options });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Create new graph node
|
|
271
|
+
*
|
|
272
|
+
* @param type - OGIT type (e.g., 'ogit/Person')
|
|
273
|
+
* @param data - Node attributes (ogit/* format)
|
|
274
|
+
* @returns Created node (raw backend format)
|
|
275
|
+
*
|
|
276
|
+
* @throws {ValidationError} if data is invalid or missing required fields
|
|
277
|
+
* @throws {PermissionError} if user lacks permission to create this type
|
|
278
|
+
* @throws {NetworkError} on HTTP errors
|
|
279
|
+
* @throws {TimeoutError} on request timeout
|
|
280
|
+
*/
|
|
281
|
+
async create(type, data, options) {
|
|
282
|
+
try {
|
|
283
|
+
const qs = buildQueryString({
|
|
284
|
+
fullResponse: options?.fullResponse,
|
|
285
|
+
listMeta: options?.listMeta,
|
|
286
|
+
});
|
|
287
|
+
return await this._request(this.withScope({
|
|
288
|
+
path: `${GRAPH_BASE}/new/${encodeURIComponent(type)}${qs}`,
|
|
289
|
+
method: 'POST',
|
|
290
|
+
body: data,
|
|
291
|
+
contentType: 'application/json',
|
|
292
|
+
}, options));
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
throw parseError(error, 'graph.create', { type, ...options });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Update existing node
|
|
300
|
+
*
|
|
301
|
+
* @param id - Node ID
|
|
302
|
+
* @param data - Fields to update (ogit/* format)
|
|
303
|
+
* @returns Updated node (raw backend format)
|
|
304
|
+
*
|
|
305
|
+
* @throws {EntityNotFoundError} if node doesn't exist
|
|
306
|
+
* @throws {ValidationError} if data is invalid
|
|
307
|
+
* @throws {PermissionError} if user lacks permission to update this node
|
|
308
|
+
* @throws {NetworkError} on HTTP errors
|
|
309
|
+
* @throws {TimeoutError} on request timeout
|
|
310
|
+
*/
|
|
311
|
+
async update(id, data, options) {
|
|
312
|
+
try {
|
|
313
|
+
const qs = buildQueryString({
|
|
314
|
+
fullResponse: options?.fullResponse,
|
|
315
|
+
listMeta: options?.listMeta,
|
|
316
|
+
});
|
|
317
|
+
return await this._request(this.withScope({
|
|
318
|
+
path: `${GRAPH_BASE}/${encodeURIComponent(id)}${qs}`,
|
|
319
|
+
method: 'POST',
|
|
320
|
+
body: data,
|
|
321
|
+
contentType: 'application/json',
|
|
322
|
+
}, options));
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
throw parseError(error, 'graph.update', { id, ...options });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Delete node (soft delete by default)
|
|
330
|
+
*
|
|
331
|
+
* @throws {EntityNotFoundError} if node doesn't exist
|
|
332
|
+
* @throws {PermissionError} if user lacks permission to delete this node
|
|
333
|
+
* @throws {NetworkError} on HTTP errors
|
|
334
|
+
* @throws {TimeoutError} on request timeout
|
|
335
|
+
*/
|
|
336
|
+
async delete(id, options) {
|
|
337
|
+
try {
|
|
338
|
+
const qs = buildQueryString({
|
|
339
|
+
hard: options?.hard,
|
|
340
|
+
});
|
|
341
|
+
await this._request(this.withScope({
|
|
342
|
+
path: `${GRAPH_BASE}/${encodeURIComponent(id)}${qs}`,
|
|
343
|
+
method: 'DELETE',
|
|
344
|
+
}, options));
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
throw parseError(error, 'graph.delete', { id, ...options });
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Create edge between two nodes
|
|
352
|
+
*
|
|
353
|
+
* @param from - Source node ID
|
|
354
|
+
* @param to - Target node ID
|
|
355
|
+
* @param type - Edge type (e.g., 'ogit/relates')
|
|
356
|
+
* @returns Created edge (raw backend format)
|
|
357
|
+
*
|
|
358
|
+
* @throws {EntityNotFoundError} if either node doesn't exist
|
|
359
|
+
* @throws {ValidationError} if edge type is invalid or not allowed
|
|
360
|
+
* @throws {PermissionError} if user lacks permission to create this edge
|
|
361
|
+
* @throws {NetworkError} on HTTP errors
|
|
362
|
+
* @throws {TimeoutError} on request timeout
|
|
363
|
+
*/
|
|
364
|
+
async connect(from, to, type, options) {
|
|
365
|
+
try {
|
|
366
|
+
const qs = buildQueryString({
|
|
367
|
+
fullResponse: options?.fullResponse,
|
|
368
|
+
listMeta: options?.listMeta,
|
|
369
|
+
});
|
|
370
|
+
return await this._request(this.withScope({
|
|
371
|
+
path: `${GRAPH_BASE}/connect${qs}`,
|
|
372
|
+
method: 'POST',
|
|
373
|
+
body: { fromId: from, toId: to, edgeType: type },
|
|
374
|
+
contentType: 'application/json',
|
|
375
|
+
}, options));
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
throw parseError(error, 'graph.connect', { from, to, type, ...options });
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Get node history
|
|
383
|
+
*
|
|
384
|
+
* @param nodeId - Node ID
|
|
385
|
+
* @param options - Time range, limit, etc.
|
|
386
|
+
* @returns Array of historical versions
|
|
387
|
+
*
|
|
388
|
+
* @throws {EntityNotFoundError} if node doesn't exist
|
|
389
|
+
* @throws {NetworkError} on HTTP errors
|
|
390
|
+
* @throws {TimeoutError} on request timeout
|
|
391
|
+
*/
|
|
392
|
+
async history(nodeId, options) {
|
|
393
|
+
try {
|
|
394
|
+
const qs = buildQueryString({
|
|
395
|
+
from: options?.from,
|
|
396
|
+
to: options?.to,
|
|
397
|
+
limit: options?.limit,
|
|
398
|
+
offset: options?.offset,
|
|
399
|
+
includeDeleted: options?.includeDeleted,
|
|
400
|
+
});
|
|
401
|
+
return await this._request(this.withScope({
|
|
402
|
+
path: `${GRAPH_BASE}/${encodeURIComponent(nodeId)}/history${qs}`,
|
|
403
|
+
method: 'GET',
|
|
404
|
+
}, options));
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
throw parseError(error, 'graph.history', { nodeId, ...options });
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Batch operations
|
|
412
|
+
*/
|
|
413
|
+
batch = {
|
|
414
|
+
/**
|
|
415
|
+
* Delete multiple nodes in parallel
|
|
416
|
+
* Continues deleting even if some fail
|
|
417
|
+
*
|
|
418
|
+
* @param ids - Array of node IDs to delete
|
|
419
|
+
* @param options - Delete options (hard delete, etc.)
|
|
420
|
+
* @returns BatchResult with succeeded and failed IDs
|
|
421
|
+
*
|
|
422
|
+
* @example
|
|
423
|
+
* const result = await graph.batch.delete(['id1', 'id2', 'id3'], { hard: false });
|
|
424
|
+
* console.log(`Deleted: ${result.succeeded.length}, Failed: ${result.failed.length}`);
|
|
425
|
+
* result.failed.forEach(f => console.error(`Failed ${f.id}: ${f.error}`));
|
|
426
|
+
*/
|
|
427
|
+
delete: async (ids, options) => {
|
|
428
|
+
const results = await Promise.allSettled(ids.map(id => this.delete(id, options)));
|
|
429
|
+
const succeeded = [];
|
|
430
|
+
const failed = [];
|
|
431
|
+
results.forEach((result, index) => {
|
|
432
|
+
const id = ids[index];
|
|
433
|
+
if (!id)
|
|
434
|
+
return;
|
|
435
|
+
if (result.status === 'fulfilled') {
|
|
436
|
+
succeeded.push(id);
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
failed.push({
|
|
440
|
+
id,
|
|
441
|
+
error: result.reason?.message || 'Unknown error',
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
return { succeeded, failed };
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
/**
|
|
449
|
+
* Time series operations
|
|
450
|
+
*/
|
|
451
|
+
timeseries = {
|
|
452
|
+
/**
|
|
453
|
+
* Get time series values
|
|
454
|
+
*
|
|
455
|
+
* @throws {EntityNotFoundError} if time series doesn't exist
|
|
456
|
+
* @throws {NetworkError} on HTTP errors
|
|
457
|
+
* @throws {TimeoutError} on request timeout
|
|
458
|
+
*/
|
|
459
|
+
get: async (id, options) => {
|
|
460
|
+
try {
|
|
461
|
+
const qs = buildQueryString({
|
|
462
|
+
from: options?.from,
|
|
463
|
+
to: options?.to,
|
|
464
|
+
limit: options?.limit,
|
|
465
|
+
order: options?.order,
|
|
466
|
+
});
|
|
467
|
+
return await this._request(this.withScope({
|
|
468
|
+
path: `${GRAPH_BASE}/${encodeURIComponent(id)}/values${qs}`,
|
|
469
|
+
method: 'GET',
|
|
470
|
+
}, options));
|
|
471
|
+
}
|
|
472
|
+
catch (error) {
|
|
473
|
+
throw parseError(error, 'graph.timeseries.get', { id, ...options });
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
/**
|
|
477
|
+
* Add time series values
|
|
478
|
+
*
|
|
479
|
+
* @throws {EntityNotFoundError} if time series doesn't exist
|
|
480
|
+
* @throws {ValidationError} if values format is invalid
|
|
481
|
+
* @throws {NetworkError} on HTTP errors
|
|
482
|
+
* @throws {TimeoutError} on request timeout
|
|
483
|
+
*/
|
|
484
|
+
add: async (id, values, options) => {
|
|
485
|
+
try {
|
|
486
|
+
await this._request(this.withScope({
|
|
487
|
+
path: `${GRAPH_BASE}/${encodeURIComponent(id)}/values`,
|
|
488
|
+
method: 'POST',
|
|
489
|
+
body: { values },
|
|
490
|
+
contentType: 'application/json',
|
|
491
|
+
}, options));
|
|
492
|
+
}
|
|
493
|
+
catch (error) {
|
|
494
|
+
throw parseError(error, 'graph.timeseries.add', { id, count: values.length, ...options });
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
/**
|
|
498
|
+
* Query time series with filters
|
|
499
|
+
*
|
|
500
|
+
* @throws {ValidationError} if query options are invalid
|
|
501
|
+
* @throws {NetworkError} on HTTP errors
|
|
502
|
+
* @throws {TimeoutError} on request timeout
|
|
503
|
+
*/
|
|
504
|
+
query: async (options) => {
|
|
505
|
+
try {
|
|
506
|
+
const qs = buildQueryString({
|
|
507
|
+
ids: options.ids?.join(','),
|
|
508
|
+
from: options.from,
|
|
509
|
+
to: options.to,
|
|
510
|
+
limit: options.limit,
|
|
511
|
+
order: options.order,
|
|
512
|
+
aggregate: options.aggregate,
|
|
513
|
+
});
|
|
514
|
+
return await this._request(this.withScope({
|
|
515
|
+
path: `${GRAPH_BASE}/query/values${qs}`,
|
|
516
|
+
method: 'GET',
|
|
517
|
+
}, options));
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
throw parseError(error, 'graph.timeseries.query', { ...options });
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
};
|
|
524
|
+
/**
|
|
525
|
+
* Content/blob operations
|
|
526
|
+
*/
|
|
527
|
+
content = {
|
|
528
|
+
/**
|
|
529
|
+
* Upload binary content to a node
|
|
530
|
+
*
|
|
531
|
+
* @param nodeId - Node ID to attach content to
|
|
532
|
+
* @param data - Binary content as Blob
|
|
533
|
+
* @param contentType - MIME type (e.g., 'image/png', 'application/pdf')
|
|
534
|
+
* @returns Content ID for later retrieval
|
|
535
|
+
*
|
|
536
|
+
* @throws {EntityNotFoundError} if node doesn't exist
|
|
537
|
+
* @throws {ValidationError} if content is invalid
|
|
538
|
+
* @throws {NetworkError} on HTTP errors
|
|
539
|
+
* @throws {TimeoutError} on request timeout
|
|
540
|
+
*/
|
|
541
|
+
set: async (nodeId, data, contentType, options) => {
|
|
542
|
+
try {
|
|
543
|
+
const result = await this._request(this.withScope({
|
|
544
|
+
path: `${GRAPH_BASE}/${encodeURIComponent(nodeId)}/content`,
|
|
545
|
+
method: 'POST',
|
|
546
|
+
body: data,
|
|
547
|
+
contentType,
|
|
548
|
+
}, options));
|
|
549
|
+
return result.contentId;
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
throw parseError(error, 'graph.content.set', {
|
|
553
|
+
nodeId,
|
|
554
|
+
contentType,
|
|
555
|
+
size: data.size,
|
|
556
|
+
...options,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
/**
|
|
561
|
+
* Download binary content from a node
|
|
562
|
+
*
|
|
563
|
+
* @param nodeId - Node ID
|
|
564
|
+
* @param contentId - Content ID returned from set operation
|
|
565
|
+
* @returns Binary content as Blob
|
|
566
|
+
*
|
|
567
|
+
* @throws {EntityNotFoundError} if node or content doesn't exist
|
|
568
|
+
* @throws {NetworkError} on HTTP errors
|
|
569
|
+
* @throws {TimeoutError} on request timeout
|
|
570
|
+
*/
|
|
571
|
+
get: async (nodeId, contentId, options) => {
|
|
572
|
+
try {
|
|
573
|
+
const qs = buildQueryString({ contentId });
|
|
574
|
+
return await this._request(this.withScope({
|
|
575
|
+
path: `${GRAPH_BASE}/${encodeURIComponent(nodeId)}/content${qs}`,
|
|
576
|
+
method: 'GET',
|
|
577
|
+
}, options));
|
|
578
|
+
}
|
|
579
|
+
catch (error) {
|
|
580
|
+
throw parseError(error, 'graph.content.get', { nodeId, contentId, ...options });
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
};
|
|
584
|
+
}
|