@object-ui/data-objectstack 0.3.0 → 0.5.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/README.md +317 -1
- package/dist/index.cjs +649 -34
- package/dist/index.d.cts +267 -7
- package/dist/index.d.ts +267 -7
- package/dist/index.js +639 -33
- package/package.json +4 -4
- package/src/cache/MetadataCache.test.ts +426 -0
- package/src/cache/MetadataCache.ts +229 -0
- package/src/connection.test.ts +100 -0
- package/src/errors.test.ts +426 -0
- package/src/errors.ts +275 -0
- package/src/index.ts +463 -42
package/src/index.ts
CHANGED
|
@@ -9,6 +9,49 @@
|
|
|
9
9
|
import { ObjectStackClient, type QueryOptions as ObjectStackQueryOptions } from '@objectstack/client';
|
|
10
10
|
import type { DataSource, QueryParams, QueryResult } from '@object-ui/types';
|
|
11
11
|
import { convertFiltersToAST } from '@object-ui/core';
|
|
12
|
+
import { MetadataCache } from './cache/MetadataCache';
|
|
13
|
+
import {
|
|
14
|
+
ObjectStackError,
|
|
15
|
+
MetadataNotFoundError,
|
|
16
|
+
BulkOperationError,
|
|
17
|
+
ConnectionError,
|
|
18
|
+
createErrorFromResponse,
|
|
19
|
+
} from './errors';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Connection state for monitoring
|
|
23
|
+
*/
|
|
24
|
+
export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Connection state change event
|
|
28
|
+
*/
|
|
29
|
+
export interface ConnectionStateEvent {
|
|
30
|
+
state: ConnectionState;
|
|
31
|
+
timestamp: number;
|
|
32
|
+
error?: Error;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Batch operation progress event
|
|
37
|
+
*/
|
|
38
|
+
export interface BatchProgressEvent {
|
|
39
|
+
operation: 'create' | 'update' | 'delete';
|
|
40
|
+
total: number;
|
|
41
|
+
completed: number;
|
|
42
|
+
failed: number;
|
|
43
|
+
percentage: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Event listener type for connection state changes
|
|
48
|
+
*/
|
|
49
|
+
export type ConnectionStateListener = (event: ConnectionStateEvent) => void;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Event listener type for batch operation progress
|
|
53
|
+
*/
|
|
54
|
+
export type BatchProgressListener = (event: BatchProgressEvent) => void;
|
|
12
55
|
|
|
13
56
|
/**
|
|
14
57
|
* ObjectStack Data Source Adapter
|
|
@@ -23,7 +66,14 @@ import { convertFiltersToAST } from '@object-ui/core';
|
|
|
23
66
|
*
|
|
24
67
|
* const dataSource = new ObjectStackAdapter({
|
|
25
68
|
* baseUrl: 'https://api.example.com',
|
|
26
|
-
* token: 'your-api-token'
|
|
69
|
+
* token: 'your-api-token',
|
|
70
|
+
* autoReconnect: true,
|
|
71
|
+
* maxReconnectAttempts: 5
|
|
72
|
+
* });
|
|
73
|
+
*
|
|
74
|
+
* // Monitor connection state
|
|
75
|
+
* dataSource.onConnectionStateChange((event) => {
|
|
76
|
+
* console.log('Connection state:', event.state);
|
|
27
77
|
* });
|
|
28
78
|
*
|
|
29
79
|
* const users = await dataSource.find('users', {
|
|
@@ -32,16 +82,35 @@ import { convertFiltersToAST } from '@object-ui/core';
|
|
|
32
82
|
* });
|
|
33
83
|
* ```
|
|
34
84
|
*/
|
|
35
|
-
export class ObjectStackAdapter<T =
|
|
85
|
+
export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
|
|
36
86
|
private client: ObjectStackClient;
|
|
37
87
|
private connected: boolean = false;
|
|
88
|
+
private metadataCache: MetadataCache;
|
|
89
|
+
private connectionState: ConnectionState = 'disconnected';
|
|
90
|
+
private connectionStateListeners: ConnectionStateListener[] = [];
|
|
91
|
+
private batchProgressListeners: BatchProgressListener[] = [];
|
|
92
|
+
private autoReconnect: boolean;
|
|
93
|
+
private maxReconnectAttempts: number;
|
|
94
|
+
private reconnectDelay: number;
|
|
95
|
+
private reconnectAttempts: number = 0;
|
|
38
96
|
|
|
39
97
|
constructor(config: {
|
|
40
98
|
baseUrl: string;
|
|
41
99
|
token?: string;
|
|
42
100
|
fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
101
|
+
cache?: {
|
|
102
|
+
maxSize?: number;
|
|
103
|
+
ttl?: number;
|
|
104
|
+
};
|
|
105
|
+
autoReconnect?: boolean;
|
|
106
|
+
maxReconnectAttempts?: number;
|
|
107
|
+
reconnectDelay?: number;
|
|
43
108
|
}) {
|
|
44
109
|
this.client = new ObjectStackClient(config);
|
|
110
|
+
this.metadataCache = new MetadataCache(config.cache);
|
|
111
|
+
this.autoReconnect = config.autoReconnect ?? true;
|
|
112
|
+
this.maxReconnectAttempts = config.maxReconnectAttempts ?? 3;
|
|
113
|
+
this.reconnectDelay = config.reconnectDelay ?? 1000;
|
|
45
114
|
}
|
|
46
115
|
|
|
47
116
|
/**
|
|
@@ -50,11 +119,127 @@ export class ObjectStackAdapter<T = any> implements DataSource<T> {
|
|
|
50
119
|
*/
|
|
51
120
|
async connect(): Promise<void> {
|
|
52
121
|
if (!this.connected) {
|
|
53
|
-
|
|
54
|
-
|
|
122
|
+
this.setConnectionState('connecting');
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await this.client.connect();
|
|
126
|
+
this.connected = true;
|
|
127
|
+
this.reconnectAttempts = 0;
|
|
128
|
+
this.setConnectionState('connected');
|
|
129
|
+
} catch (error: unknown) {
|
|
130
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to connect to ObjectStack server';
|
|
131
|
+
const connectionError = new ConnectionError(
|
|
132
|
+
errorMessage,
|
|
133
|
+
undefined,
|
|
134
|
+
{ originalError: error }
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
this.setConnectionState('error', connectionError);
|
|
138
|
+
|
|
139
|
+
// Attempt auto-reconnect if enabled
|
|
140
|
+
if (this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
141
|
+
await this.attemptReconnect();
|
|
142
|
+
} else {
|
|
143
|
+
throw connectionError;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
55
146
|
}
|
|
56
147
|
}
|
|
57
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Attempt to reconnect to the server with exponential backoff
|
|
151
|
+
*/
|
|
152
|
+
private async attemptReconnect(): Promise<void> {
|
|
153
|
+
this.reconnectAttempts++;
|
|
154
|
+
this.setConnectionState('reconnecting');
|
|
155
|
+
|
|
156
|
+
// Exponential backoff: delay * 2^(attempts-1)
|
|
157
|
+
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
158
|
+
|
|
159
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
160
|
+
|
|
161
|
+
this.connected = false;
|
|
162
|
+
await this.connect();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get the current connection state
|
|
167
|
+
*/
|
|
168
|
+
getConnectionState(): ConnectionState {
|
|
169
|
+
return this.connectionState;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Check if the adapter is currently connected
|
|
174
|
+
*/
|
|
175
|
+
isConnected(): boolean {
|
|
176
|
+
return this.connected && this.connectionState === 'connected';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Register a listener for connection state changes
|
|
181
|
+
*/
|
|
182
|
+
onConnectionStateChange(listener: ConnectionStateListener): () => void {
|
|
183
|
+
this.connectionStateListeners.push(listener);
|
|
184
|
+
|
|
185
|
+
// Return unsubscribe function
|
|
186
|
+
return () => {
|
|
187
|
+
const index = this.connectionStateListeners.indexOf(listener);
|
|
188
|
+
if (index > -1) {
|
|
189
|
+
this.connectionStateListeners.splice(index, 1);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Register a listener for batch operation progress
|
|
196
|
+
*/
|
|
197
|
+
onBatchProgress(listener: BatchProgressListener): () => void {
|
|
198
|
+
this.batchProgressListeners.push(listener);
|
|
199
|
+
|
|
200
|
+
// Return unsubscribe function
|
|
201
|
+
return () => {
|
|
202
|
+
const index = this.batchProgressListeners.indexOf(listener);
|
|
203
|
+
if (index > -1) {
|
|
204
|
+
this.batchProgressListeners.splice(index, 1);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Set connection state and notify listeners
|
|
211
|
+
*/
|
|
212
|
+
private setConnectionState(state: ConnectionState, error?: Error): void {
|
|
213
|
+
this.connectionState = state;
|
|
214
|
+
|
|
215
|
+
const event: ConnectionStateEvent = {
|
|
216
|
+
state,
|
|
217
|
+
timestamp: Date.now(),
|
|
218
|
+
error,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
this.connectionStateListeners.forEach(listener => {
|
|
222
|
+
try {
|
|
223
|
+
listener(event);
|
|
224
|
+
} catch (err) {
|
|
225
|
+
console.error('Error in connection state listener:', err);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Emit batch progress event to listeners
|
|
232
|
+
*/
|
|
233
|
+
private emitBatchProgress(event: BatchProgressEvent): void {
|
|
234
|
+
this.batchProgressListeners.forEach(listener => {
|
|
235
|
+
try {
|
|
236
|
+
listener(event);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
console.error('Error in batch progress listener:', err);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
58
243
|
/**
|
|
59
244
|
* Find multiple records with query parameters.
|
|
60
245
|
* Converts OData-style params to ObjectStack query options.
|
|
@@ -63,14 +248,27 @@ export class ObjectStackAdapter<T = any> implements DataSource<T> {
|
|
|
63
248
|
await this.connect();
|
|
64
249
|
|
|
65
250
|
const queryOptions = this.convertQueryParams(params);
|
|
66
|
-
const result = await this.client.data.find<T>(resource, queryOptions);
|
|
251
|
+
const result: unknown = await this.client.data.find<T>(resource, queryOptions);
|
|
252
|
+
|
|
253
|
+
// Handle legacy/raw array response (e.g. from some mock servers or non-OData endpoints)
|
|
254
|
+
if (Array.isArray(result)) {
|
|
255
|
+
return {
|
|
256
|
+
data: result,
|
|
257
|
+
total: result.length,
|
|
258
|
+
page: 1,
|
|
259
|
+
pageSize: result.length,
|
|
260
|
+
hasMore: false,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
67
263
|
|
|
264
|
+
const resultObj = result as { value?: T[]; count?: number };
|
|
68
265
|
return {
|
|
69
|
-
data:
|
|
70
|
-
total:
|
|
71
|
-
|
|
266
|
+
data: resultObj.value || [],
|
|
267
|
+
total: resultObj.count || (resultObj.value ? resultObj.value.length : 0),
|
|
268
|
+
// Calculate page number safely
|
|
269
|
+
page: params?.$skip && params.$top ? Math.floor(params.$skip / params.$top) + 1 : 1,
|
|
72
270
|
pageSize: params?.$top,
|
|
73
|
-
hasMore:
|
|
271
|
+
hasMore: params?.$top ? (resultObj.value?.length || 0) === params.$top : false,
|
|
74
272
|
};
|
|
75
273
|
}
|
|
76
274
|
|
|
@@ -83,9 +281,9 @@ export class ObjectStackAdapter<T = any> implements DataSource<T> {
|
|
|
83
281
|
try {
|
|
84
282
|
const record = await this.client.data.get<T>(resource, String(id));
|
|
85
283
|
return record;
|
|
86
|
-
} catch (error) {
|
|
284
|
+
} catch (error: unknown) {
|
|
87
285
|
// If record not found, return null instead of throwing
|
|
88
|
-
if ((error as
|
|
286
|
+
if ((error as Record<string, unknown>)?.status === 404) {
|
|
89
287
|
return null;
|
|
90
288
|
}
|
|
91
289
|
throw error;
|
|
@@ -118,31 +316,166 @@ export class ObjectStackAdapter<T = any> implements DataSource<T> {
|
|
|
118
316
|
}
|
|
119
317
|
|
|
120
318
|
/**
|
|
121
|
-
* Bulk operations
|
|
319
|
+
* Bulk operations with optimized batch processing and error handling.
|
|
320
|
+
* Emits progress events for tracking operation status.
|
|
321
|
+
*
|
|
322
|
+
* @param resource - Resource name
|
|
323
|
+
* @param operation - Operation type (create, update, delete)
|
|
324
|
+
* @param data - Array of records to process
|
|
325
|
+
* @returns Promise resolving to array of results
|
|
122
326
|
*/
|
|
123
327
|
async bulk(resource: string, operation: 'create' | 'update' | 'delete', data: Partial<T>[]): Promise<T[]> {
|
|
124
328
|
await this.connect();
|
|
125
329
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
330
|
+
if (!data || data.length === 0) {
|
|
331
|
+
return [];
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const total = data.length;
|
|
335
|
+
let completed = 0;
|
|
336
|
+
let failed = 0;
|
|
337
|
+
|
|
338
|
+
const emitProgress = () => {
|
|
339
|
+
this.emitBatchProgress({
|
|
340
|
+
operation,
|
|
341
|
+
total,
|
|
342
|
+
completed,
|
|
343
|
+
failed,
|
|
344
|
+
percentage: total > 0 ? (completed + failed) / total * 100 : 0,
|
|
345
|
+
});
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
switch (operation) {
|
|
350
|
+
case 'create': {
|
|
351
|
+
emitProgress();
|
|
352
|
+
const created = await this.client.data.createMany<T>(resource, data);
|
|
353
|
+
completed = created.length;
|
|
354
|
+
failed = total - completed;
|
|
355
|
+
emitProgress();
|
|
356
|
+
return created;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
case 'delete': {
|
|
360
|
+
const ids = data.map(item => (item as Record<string, unknown>).id).filter(Boolean) as string[];
|
|
361
|
+
|
|
362
|
+
if (ids.length === 0) {
|
|
363
|
+
// Track which items are missing IDs
|
|
364
|
+
const errors = data.map((_, index) => ({
|
|
365
|
+
index,
|
|
366
|
+
error: `Missing ID for item at index ${index}`
|
|
367
|
+
}));
|
|
368
|
+
|
|
369
|
+
failed = data.length;
|
|
370
|
+
emitProgress();
|
|
371
|
+
|
|
372
|
+
throw new BulkOperationError('delete', 0, data.length, errors);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
emitProgress();
|
|
376
|
+
await this.client.data.deleteMany(resource, ids);
|
|
377
|
+
completed = ids.length;
|
|
378
|
+
failed = total - completed;
|
|
379
|
+
emitProgress();
|
|
380
|
+
return [] as T[];
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
case 'update': {
|
|
384
|
+
// Check if client supports updateMany
|
|
385
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
386
|
+
if (typeof (this.client.data as any).updateMany === 'function') {
|
|
387
|
+
try {
|
|
388
|
+
emitProgress();
|
|
389
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
390
|
+
const updateMany = (this.client.data as any).updateMany;
|
|
391
|
+
const updated = await updateMany(resource, data) as T[];
|
|
392
|
+
completed = updated.length;
|
|
393
|
+
failed = total - completed;
|
|
394
|
+
emitProgress();
|
|
395
|
+
return updated;
|
|
396
|
+
} catch {
|
|
397
|
+
// If updateMany is not supported, fall back to individual updates
|
|
398
|
+
// Silently fallback without logging
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Fallback: Process updates individually with detailed error tracking and progress
|
|
403
|
+
const results: T[] = [];
|
|
404
|
+
const errors: Array<{ index: number; error: unknown }> = [];
|
|
405
|
+
|
|
406
|
+
for (let i = 0; i < data.length; i++) {
|
|
407
|
+
const item = data[i];
|
|
408
|
+
const id = (item as Record<string, unknown>).id;
|
|
409
|
+
|
|
410
|
+
if (!id) {
|
|
411
|
+
errors.push({ index: i, error: 'Missing ID' });
|
|
412
|
+
failed++;
|
|
413
|
+
emitProgress();
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
try {
|
|
418
|
+
const result = await this.client.data.update<T>(resource, String(id), item);
|
|
419
|
+
results.push(result);
|
|
420
|
+
completed++;
|
|
421
|
+
emitProgress();
|
|
422
|
+
} catch (error: unknown) {
|
|
423
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
424
|
+
errors.push({ index: i, error: errorMessage });
|
|
425
|
+
failed++;
|
|
426
|
+
emitProgress();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// If there were any errors, throw BulkOperationError
|
|
431
|
+
if (errors.length > 0) {
|
|
432
|
+
throw new BulkOperationError(
|
|
433
|
+
'update',
|
|
434
|
+
results.length,
|
|
435
|
+
errors.length,
|
|
436
|
+
errors,
|
|
437
|
+
{ resource, totalRecords: data.length }
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return results;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
default:
|
|
445
|
+
throw new ObjectStackError(
|
|
446
|
+
`Unsupported bulk operation: ${operation}`,
|
|
447
|
+
'UNSUPPORTED_OPERATION',
|
|
448
|
+
400
|
|
449
|
+
);
|
|
133
450
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
451
|
+
} catch (error: unknown) {
|
|
452
|
+
// Emit final progress with failure
|
|
453
|
+
emitProgress();
|
|
454
|
+
|
|
455
|
+
// If it's already a BulkOperationError, re-throw it
|
|
456
|
+
if (error instanceof BulkOperationError) {
|
|
457
|
+
throw error;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// If it's already an ObjectStackError, re-throw it
|
|
461
|
+
if (error instanceof ObjectStackError) {
|
|
462
|
+
throw error;
|
|
143
463
|
}
|
|
144
|
-
|
|
145
|
-
|
|
464
|
+
|
|
465
|
+
// Wrap other errors in BulkOperationError with proper error tracking
|
|
466
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
467
|
+
const errors = data.map((_, index) => ({
|
|
468
|
+
index,
|
|
469
|
+
error: errorMessage
|
|
470
|
+
}));
|
|
471
|
+
|
|
472
|
+
throw new BulkOperationError(
|
|
473
|
+
operation,
|
|
474
|
+
0,
|
|
475
|
+
data.length,
|
|
476
|
+
errors,
|
|
477
|
+
{ resource, originalError: error }
|
|
478
|
+
);
|
|
146
479
|
}
|
|
147
480
|
}
|
|
148
481
|
|
|
@@ -162,15 +495,32 @@ export class ObjectStackAdapter<T = any> implements DataSource<T> {
|
|
|
162
495
|
|
|
163
496
|
// Filtering - convert to ObjectStack FilterNode AST format
|
|
164
497
|
if (params.$filter) {
|
|
165
|
-
|
|
498
|
+
if (Array.isArray(params.$filter)) {
|
|
499
|
+
// Assume active AST format if it's already an array
|
|
500
|
+
options.filters = params.$filter;
|
|
501
|
+
} else {
|
|
502
|
+
options.filters = convertFiltersToAST(params.$filter);
|
|
503
|
+
}
|
|
166
504
|
}
|
|
167
505
|
|
|
168
506
|
// Sorting - convert to ObjectStack format
|
|
169
507
|
if (params.$orderby) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
508
|
+
if (Array.isArray(params.$orderby)) {
|
|
509
|
+
// Handle array format ['name', '-age'] or [{ field: 'name', order: 'asc' }]
|
|
510
|
+
options.sort = params.$orderby.map(item => {
|
|
511
|
+
if (typeof item === 'string') return item;
|
|
512
|
+
// Handle object format { field: 'name', order: 'desc' }
|
|
513
|
+
const field = item.field;
|
|
514
|
+
const order = item.order || 'asc';
|
|
515
|
+
return order === 'desc' ? `-${field}` : field;
|
|
516
|
+
});
|
|
517
|
+
} else {
|
|
518
|
+
// Handle Record format { name: 'asc', age: 'desc' }
|
|
519
|
+
const sortArray = Object.entries(params.$orderby).map(([field, order]) => {
|
|
520
|
+
return order === 'desc' ? `-${field}` : field;
|
|
521
|
+
});
|
|
522
|
+
options.sort = sortArray;
|
|
523
|
+
}
|
|
174
524
|
}
|
|
175
525
|
|
|
176
526
|
// Pagination
|
|
@@ -187,19 +537,41 @@ export class ObjectStackAdapter<T = any> implements DataSource<T> {
|
|
|
187
537
|
|
|
188
538
|
/**
|
|
189
539
|
* Get object schema/metadata from ObjectStack.
|
|
540
|
+
* Uses caching to improve performance for repeated requests.
|
|
190
541
|
*
|
|
191
542
|
* @param objectName - Object name
|
|
192
543
|
* @returns Promise resolving to the object schema
|
|
193
544
|
*/
|
|
194
|
-
async getObjectSchema(objectName: string): Promise<
|
|
545
|
+
async getObjectSchema(objectName: string): Promise<unknown> {
|
|
195
546
|
await this.connect();
|
|
196
547
|
|
|
197
548
|
try {
|
|
198
|
-
|
|
549
|
+
// Use cache with automatic fetching
|
|
550
|
+
const schema = await this.metadataCache.get(objectName, async () => {
|
|
551
|
+
const result: any = await this.client.meta.getObject(objectName);
|
|
552
|
+
|
|
553
|
+
// Unwrap 'item' property if present (common API response wrapper)
|
|
554
|
+
if (result && result.item) {
|
|
555
|
+
return result.item;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return result;
|
|
559
|
+
});
|
|
560
|
+
|
|
199
561
|
return schema;
|
|
200
|
-
} catch (error) {
|
|
201
|
-
|
|
202
|
-
|
|
562
|
+
} catch (error: unknown) {
|
|
563
|
+
// Check if it's a 404 error
|
|
564
|
+
const errorObj = error as Record<string, unknown>;
|
|
565
|
+
if (errorObj?.status === 404 || errorObj?.statusCode === 404) {
|
|
566
|
+
throw new MetadataNotFoundError(objectName, { originalError: error });
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// For other errors, wrap in ObjectStackError if not already
|
|
570
|
+
if (error instanceof ObjectStackError) {
|
|
571
|
+
throw error;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
throw createErrorFromResponse(errorObj, `getObjectSchema(${objectName})`);
|
|
203
575
|
}
|
|
204
576
|
}
|
|
205
577
|
|
|
@@ -209,6 +581,29 @@ export class ObjectStackAdapter<T = any> implements DataSource<T> {
|
|
|
209
581
|
getClient(): ObjectStackClient {
|
|
210
582
|
return this.client;
|
|
211
583
|
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Get cache statistics for monitoring performance.
|
|
587
|
+
*/
|
|
588
|
+
getCacheStats() {
|
|
589
|
+
return this.metadataCache.getStats();
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Invalidate metadata cache entries.
|
|
594
|
+
*
|
|
595
|
+
* @param key - Optional key to invalidate. If omitted, invalidates all entries.
|
|
596
|
+
*/
|
|
597
|
+
invalidateCache(key?: string): void {
|
|
598
|
+
this.metadataCache.invalidate(key);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Clear all cache entries and statistics.
|
|
603
|
+
*/
|
|
604
|
+
clearCache(): void {
|
|
605
|
+
this.metadataCache.clear();
|
|
606
|
+
}
|
|
212
607
|
}
|
|
213
608
|
|
|
214
609
|
/**
|
|
@@ -218,14 +613,40 @@ export class ObjectStackAdapter<T = any> implements DataSource<T> {
|
|
|
218
613
|
* ```typescript
|
|
219
614
|
* const dataSource = createObjectStackAdapter({
|
|
220
615
|
* baseUrl: process.env.API_URL,
|
|
221
|
-
* token: process.env.API_TOKEN
|
|
616
|
+
* token: process.env.API_TOKEN,
|
|
617
|
+
* cache: { maxSize: 100, ttl: 300000 },
|
|
618
|
+
* autoReconnect: true,
|
|
619
|
+
* maxReconnectAttempts: 5
|
|
222
620
|
* });
|
|
223
621
|
* ```
|
|
224
622
|
*/
|
|
225
|
-
export function createObjectStackAdapter<T =
|
|
623
|
+
export function createObjectStackAdapter<T = unknown>(config: {
|
|
226
624
|
baseUrl: string;
|
|
227
625
|
token?: string;
|
|
228
626
|
fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
627
|
+
cache?: {
|
|
628
|
+
maxSize?: number;
|
|
629
|
+
ttl?: number;
|
|
630
|
+
};
|
|
631
|
+
autoReconnect?: boolean;
|
|
632
|
+
maxReconnectAttempts?: number;
|
|
633
|
+
reconnectDelay?: number;
|
|
229
634
|
}): DataSource<T> {
|
|
230
635
|
return new ObjectStackAdapter<T>(config);
|
|
231
636
|
}
|
|
637
|
+
|
|
638
|
+
// Export error classes for error handling
|
|
639
|
+
export {
|
|
640
|
+
ObjectStackError,
|
|
641
|
+
MetadataNotFoundError,
|
|
642
|
+
BulkOperationError,
|
|
643
|
+
ConnectionError,
|
|
644
|
+
AuthenticationError,
|
|
645
|
+
ValidationError,
|
|
646
|
+
createErrorFromResponse,
|
|
647
|
+
isObjectStackError,
|
|
648
|
+
isErrorType,
|
|
649
|
+
} from './errors';
|
|
650
|
+
|
|
651
|
+
// Export cache types
|
|
652
|
+
export type { CacheStats } from './cache/MetadataCache';
|