@object-ui/data-objectstack 0.3.1 → 2.0.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/dist/index.cjs CHANGED
@@ -22,17 +22,370 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
22
22
  // src/index.ts
23
23
  var index_exports = {};
24
24
  __export(index_exports, {
25
+ AuthenticationError: () => AuthenticationError,
26
+ BulkOperationError: () => BulkOperationError,
27
+ ConnectionError: () => ConnectionError,
28
+ MetadataNotFoundError: () => MetadataNotFoundError,
25
29
  ObjectStackAdapter: () => ObjectStackAdapter,
26
- createObjectStackAdapter: () => createObjectStackAdapter
30
+ ObjectStackError: () => ObjectStackError,
31
+ ValidationError: () => ValidationError,
32
+ createErrorFromResponse: () => createErrorFromResponse,
33
+ createObjectStackAdapter: () => createObjectStackAdapter,
34
+ isErrorType: () => isErrorType,
35
+ isObjectStackError: () => isObjectStackError
27
36
  });
28
37
  module.exports = __toCommonJS(index_exports);
29
38
  var import_client = require("@objectstack/client");
30
39
  var import_core = require("@object-ui/core");
40
+
41
+ // src/cache/MetadataCache.ts
42
+ var MetadataCache = class {
43
+ /**
44
+ * Create a new MetadataCache instance
45
+ *
46
+ * @param options - Configuration options
47
+ * @param options.maxSize - Maximum number of entries (default: 100)
48
+ * @param options.ttl - Time to live in milliseconds (default: 5 minutes)
49
+ */
50
+ constructor(options = {}) {
51
+ __publicField(this, "cache");
52
+ __publicField(this, "maxSize");
53
+ __publicField(this, "ttl");
54
+ __publicField(this, "stats");
55
+ this.cache = /* @__PURE__ */ new Map();
56
+ this.maxSize = options.maxSize || 100;
57
+ this.ttl = options.ttl || 5 * 60 * 1e3;
58
+ this.stats = {
59
+ hits: 0,
60
+ misses: 0,
61
+ evictions: 0
62
+ };
63
+ }
64
+ /**
65
+ * Get a value from cache or fetch it using the provided fetcher function
66
+ *
67
+ * @param key - Cache key
68
+ * @param fetcher - Async function to fetch data if not in cache
69
+ * @returns Promise resolving to the cached or fetched data
70
+ */
71
+ async get(key, fetcher) {
72
+ const now = Date.now();
73
+ const cached = this.cache.get(key);
74
+ if (cached) {
75
+ const age = now - cached.timestamp;
76
+ if (age < this.ttl) {
77
+ cached.accessCount++;
78
+ cached.lastAccessed = now;
79
+ this.stats.hits++;
80
+ this.cache.delete(key);
81
+ this.cache.set(key, cached);
82
+ return cached.data;
83
+ } else {
84
+ this.cache.delete(key);
85
+ }
86
+ }
87
+ this.stats.misses++;
88
+ const data = await fetcher();
89
+ this.set(key, data);
90
+ return data;
91
+ }
92
+ /**
93
+ * Set a value in the cache
94
+ *
95
+ * @param key - Cache key
96
+ * @param data - Data to cache
97
+ */
98
+ set(key, data) {
99
+ const now = Date.now();
100
+ if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
101
+ this.evictLRU();
102
+ }
103
+ this.cache.set(key, {
104
+ data,
105
+ timestamp: now,
106
+ accessCount: 1,
107
+ lastAccessed: now
108
+ });
109
+ }
110
+ /**
111
+ * Evict the least recently used entry
112
+ */
113
+ evictLRU() {
114
+ const firstKey = this.cache.keys().next().value;
115
+ if (firstKey !== void 0) {
116
+ this.cache.delete(firstKey);
117
+ this.stats.evictions++;
118
+ }
119
+ }
120
+ /**
121
+ * Invalidate a specific cache entry or all entries
122
+ *
123
+ * @param key - Optional key to invalidate. If omitted, invalidates all entries
124
+ */
125
+ invalidate(key) {
126
+ if (key) {
127
+ this.cache.delete(key);
128
+ } else {
129
+ this.cache.clear();
130
+ }
131
+ }
132
+ /**
133
+ * Clear all cache entries and reset statistics
134
+ */
135
+ clear() {
136
+ this.cache.clear();
137
+ this.stats = {
138
+ hits: 0,
139
+ misses: 0,
140
+ evictions: 0
141
+ };
142
+ }
143
+ /**
144
+ * Get cache statistics
145
+ *
146
+ * @returns Cache statistics including hit rate
147
+ */
148
+ getStats() {
149
+ const total = this.stats.hits + this.stats.misses;
150
+ const hitRate = total > 0 ? this.stats.hits / total : 0;
151
+ return {
152
+ size: this.cache.size,
153
+ maxSize: this.maxSize,
154
+ hits: this.stats.hits,
155
+ misses: this.stats.misses,
156
+ evictions: this.stats.evictions,
157
+ hitRate
158
+ };
159
+ }
160
+ /**
161
+ * Check if a key exists in the cache (and is not expired)
162
+ *
163
+ * @param key - Cache key to check
164
+ * @returns true if the key exists and is not expired
165
+ */
166
+ has(key) {
167
+ const cached = this.cache.get(key);
168
+ if (!cached) {
169
+ return false;
170
+ }
171
+ const age = Date.now() - cached.timestamp;
172
+ if (age >= this.ttl) {
173
+ this.cache.delete(key);
174
+ return false;
175
+ }
176
+ return true;
177
+ }
178
+ };
179
+
180
+ // src/errors.ts
181
+ var ObjectStackError = class extends Error {
182
+ /**
183
+ * Create a new ObjectStackError
184
+ *
185
+ * @param message - Human-readable error message
186
+ * @param code - Unique error code for programmatic handling
187
+ * @param statusCode - Optional HTTP status code
188
+ * @param details - Optional additional error details for debugging
189
+ */
190
+ constructor(message, code, statusCode, details) {
191
+ super(message);
192
+ this.code = code;
193
+ this.statusCode = statusCode;
194
+ this.details = details;
195
+ this.name = "ObjectStackError";
196
+ if (Error.captureStackTrace) {
197
+ Error.captureStackTrace(this, this.constructor);
198
+ }
199
+ }
200
+ /**
201
+ * Convert error to JSON for logging/debugging
202
+ */
203
+ toJSON() {
204
+ return {
205
+ name: this.name,
206
+ message: this.message,
207
+ code: this.code,
208
+ statusCode: this.statusCode,
209
+ details: this.details,
210
+ stack: this.stack
211
+ };
212
+ }
213
+ };
214
+ var MetadataNotFoundError = class extends ObjectStackError {
215
+ constructor(objectName, details) {
216
+ super(
217
+ `Metadata not found for object: ${objectName}`,
218
+ "METADATA_NOT_FOUND",
219
+ 404,
220
+ { objectName, ...details }
221
+ );
222
+ this.name = "MetadataNotFoundError";
223
+ }
224
+ };
225
+ var BulkOperationError = class extends ObjectStackError {
226
+ /**
227
+ * Create a new BulkOperationError
228
+ *
229
+ * @param operation - The bulk operation that failed (create, update, delete)
230
+ * @param successCount - Number of successful operations
231
+ * @param failureCount - Number of failed operations
232
+ * @param errors - Array of individual errors
233
+ * @param details - Additional error details
234
+ */
235
+ constructor(operation, successCount, failureCount, errors, details) {
236
+ super(
237
+ `Bulk ${operation} operation failed: ${successCount} succeeded, ${failureCount} failed`,
238
+ "BULK_OPERATION_ERROR",
239
+ 500,
240
+ {
241
+ operation,
242
+ successCount,
243
+ failureCount,
244
+ errors,
245
+ ...details
246
+ }
247
+ );
248
+ this.successCount = successCount;
249
+ this.failureCount = failureCount;
250
+ this.errors = errors;
251
+ this.name = "BulkOperationError";
252
+ }
253
+ /**
254
+ * Get a summary of the bulk operation failure
255
+ */
256
+ getSummary() {
257
+ const total = this.successCount + this.failureCount;
258
+ const failureRate = total > 0 ? this.failureCount / total : 0;
259
+ return {
260
+ operation: this.details?.operation,
261
+ total,
262
+ successful: this.successCount,
263
+ failed: this.failureCount,
264
+ failureRate,
265
+ errors: this.errors
266
+ };
267
+ }
268
+ };
269
+ var ConnectionError = class extends ObjectStackError {
270
+ constructor(message, url, details, statusCode) {
271
+ super(
272
+ `Connection error: ${message}`,
273
+ "CONNECTION_ERROR",
274
+ statusCode || 503,
275
+ { url, ...details }
276
+ );
277
+ this.url = url;
278
+ this.name = "ConnectionError";
279
+ }
280
+ };
281
+ var AuthenticationError = class extends ObjectStackError {
282
+ constructor(message = "Authentication failed", details, statusCode) {
283
+ super(
284
+ message,
285
+ "AUTHENTICATION_ERROR",
286
+ statusCode || 401,
287
+ details
288
+ );
289
+ this.name = "AuthenticationError";
290
+ }
291
+ };
292
+ var ValidationError = class extends ObjectStackError {
293
+ /**
294
+ * Create a new ValidationError
295
+ *
296
+ * @param message - Human-readable error message
297
+ * @param field - The field that failed validation (optional)
298
+ * @param validationErrors - Array of validation error details
299
+ * @param details - Additional error details
300
+ */
301
+ constructor(message, field, validationErrors, details) {
302
+ super(
303
+ message,
304
+ "VALIDATION_ERROR",
305
+ 400,
306
+ {
307
+ field,
308
+ validationErrors,
309
+ ...details
310
+ }
311
+ );
312
+ this.field = field;
313
+ this.validationErrors = validationErrors;
314
+ this.name = "ValidationError";
315
+ }
316
+ /**
317
+ * Get all validation errors as a formatted list
318
+ */
319
+ getValidationErrors() {
320
+ return this.validationErrors || [];
321
+ }
322
+ };
323
+ function createErrorFromResponse(response, context) {
324
+ const status = response?.status || response?.statusCode || 500;
325
+ const message = response?.message || response?.statusText || "Unknown error";
326
+ const details = {
327
+ context,
328
+ response: {
329
+ status,
330
+ data: response?.data,
331
+ headers: response?.headers
332
+ }
333
+ };
334
+ switch (status) {
335
+ case 401:
336
+ return new AuthenticationError(message, details, 401);
337
+ case 403:
338
+ return new AuthenticationError(message, details, 403);
339
+ case 404:
340
+ if (context?.includes("metadata") || context?.includes("schema") || context?.includes("getObjectSchema")) {
341
+ const objectName = extractObjectName(context);
342
+ return new MetadataNotFoundError(objectName, details);
343
+ }
344
+ return new ObjectStackError(message, "NOT_FOUND", 404, details);
345
+ case 400:
346
+ return new ValidationError(message, void 0, response?.data?.errors, details);
347
+ case 503:
348
+ return new ConnectionError(message, response?.config?.url, details, 503);
349
+ case 504:
350
+ return new ConnectionError(message, response?.config?.url, details, 504);
351
+ default:
352
+ return new ObjectStackError(message, "UNKNOWN_ERROR", status, details);
353
+ }
354
+ }
355
+ function extractObjectName(context) {
356
+ if (!context) return "unknown";
357
+ const match = context.match(/\(([^)]+)\)/);
358
+ return match ? match[1] : "unknown";
359
+ }
360
+ function isObjectStackError(error) {
361
+ return error instanceof ObjectStackError;
362
+ }
363
+ function isErrorType(error, errorClass) {
364
+ return error instanceof errorClass;
365
+ }
366
+
367
+ // src/index.ts
31
368
  var ObjectStackAdapter = class {
32
369
  constructor(config) {
33
370
  __publicField(this, "client");
34
371
  __publicField(this, "connected", false);
372
+ __publicField(this, "metadataCache");
373
+ __publicField(this, "connectionState", "disconnected");
374
+ __publicField(this, "connectionStateListeners", []);
375
+ __publicField(this, "batchProgressListeners", []);
376
+ __publicField(this, "autoReconnect");
377
+ __publicField(this, "maxReconnectAttempts");
378
+ __publicField(this, "reconnectDelay");
379
+ __publicField(this, "reconnectAttempts", 0);
380
+ __publicField(this, "baseUrl");
381
+ __publicField(this, "token");
35
382
  this.client = new import_client.ObjectStackClient(config);
383
+ this.metadataCache = new MetadataCache(config.cache);
384
+ this.autoReconnect = config.autoReconnect ?? true;
385
+ this.maxReconnectAttempts = config.maxReconnectAttempts ?? 3;
386
+ this.reconnectDelay = config.reconnectDelay ?? 1e3;
387
+ this.baseUrl = config.baseUrl;
388
+ this.token = config.token;
36
389
  }
37
390
  /**
38
391
  * Ensure the client is connected to the server.
@@ -40,10 +393,105 @@ var ObjectStackAdapter = class {
40
393
  */
41
394
  async connect() {
42
395
  if (!this.connected) {
43
- await this.client.connect();
44
- this.connected = true;
396
+ this.setConnectionState("connecting");
397
+ try {
398
+ await this.client.connect();
399
+ this.connected = true;
400
+ this.reconnectAttempts = 0;
401
+ this.setConnectionState("connected");
402
+ } catch (error) {
403
+ const errorMessage = error instanceof Error ? error.message : "Failed to connect to ObjectStack server";
404
+ const connectionError = new ConnectionError(
405
+ errorMessage,
406
+ void 0,
407
+ { originalError: error }
408
+ );
409
+ this.setConnectionState("error", connectionError);
410
+ if (this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
411
+ await this.attemptReconnect();
412
+ } else {
413
+ throw connectionError;
414
+ }
415
+ }
45
416
  }
46
417
  }
418
+ /**
419
+ * Attempt to reconnect to the server with exponential backoff
420
+ */
421
+ async attemptReconnect() {
422
+ this.reconnectAttempts++;
423
+ this.setConnectionState("reconnecting");
424
+ const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
425
+ await new Promise((resolve) => setTimeout(resolve, delay));
426
+ this.connected = false;
427
+ await this.connect();
428
+ }
429
+ /**
430
+ * Get the current connection state
431
+ */
432
+ getConnectionState() {
433
+ return this.connectionState;
434
+ }
435
+ /**
436
+ * Check if the adapter is currently connected
437
+ */
438
+ isConnected() {
439
+ return this.connected && this.connectionState === "connected";
440
+ }
441
+ /**
442
+ * Register a listener for connection state changes
443
+ */
444
+ onConnectionStateChange(listener) {
445
+ this.connectionStateListeners.push(listener);
446
+ return () => {
447
+ const index = this.connectionStateListeners.indexOf(listener);
448
+ if (index > -1) {
449
+ this.connectionStateListeners.splice(index, 1);
450
+ }
451
+ };
452
+ }
453
+ /**
454
+ * Register a listener for batch operation progress
455
+ */
456
+ onBatchProgress(listener) {
457
+ this.batchProgressListeners.push(listener);
458
+ return () => {
459
+ const index = this.batchProgressListeners.indexOf(listener);
460
+ if (index > -1) {
461
+ this.batchProgressListeners.splice(index, 1);
462
+ }
463
+ };
464
+ }
465
+ /**
466
+ * Set connection state and notify listeners
467
+ */
468
+ setConnectionState(state, error) {
469
+ this.connectionState = state;
470
+ const event = {
471
+ state,
472
+ timestamp: Date.now(),
473
+ error
474
+ };
475
+ this.connectionStateListeners.forEach((listener) => {
476
+ try {
477
+ listener(event);
478
+ } catch (err) {
479
+ console.error("Error in connection state listener:", err);
480
+ }
481
+ });
482
+ }
483
+ /**
484
+ * Emit batch progress event to listeners
485
+ */
486
+ emitBatchProgress(event) {
487
+ this.batchProgressListeners.forEach((listener) => {
488
+ try {
489
+ listener(event);
490
+ } catch (err) {
491
+ console.error("Error in batch progress listener:", err);
492
+ }
493
+ });
494
+ }
47
495
  /**
48
496
  * Find multiple records with query parameters.
49
497
  * Converts OData-style params to ObjectStack query options.
@@ -52,12 +500,23 @@ var ObjectStackAdapter = class {
52
500
  await this.connect();
53
501
  const queryOptions = this.convertQueryParams(params);
54
502
  const result = await this.client.data.find(resource, queryOptions);
503
+ if (Array.isArray(result)) {
504
+ return {
505
+ data: result,
506
+ total: result.length,
507
+ page: 1,
508
+ pageSize: result.length,
509
+ hasMore: false
510
+ };
511
+ }
512
+ const resultObj = result;
55
513
  return {
56
- data: result.value,
57
- total: result.count,
58
- page: params?.$skip ? Math.floor(params.$skip / (params.$top || 20)) + 1 : 1,
514
+ data: resultObj.value || [],
515
+ total: resultObj.count || (resultObj.value ? resultObj.value.length : 0),
516
+ // Calculate page number safely
517
+ page: params?.$skip && params.$top ? Math.floor(params.$skip / params.$top) + 1 : 1,
59
518
  pageSize: params?.$top,
60
- hasMore: result.value.length === params?.$top
519
+ hasMore: params?.$top ? (resultObj.value?.length || 0) === params.$top : false
61
520
  };
62
521
  }
63
522
  /**
@@ -66,8 +525,8 @@ var ObjectStackAdapter = class {
66
525
  async findOne(resource, id, _params) {
67
526
  await this.connect();
68
527
  try {
69
- const record = await this.client.data.get(resource, String(id));
70
- return record;
528
+ const result = await this.client.data.get(resource, String(id));
529
+ return result.record;
71
530
  } catch (error) {
72
531
  if (error?.status === 404) {
73
532
  return null;
@@ -80,14 +539,16 @@ var ObjectStackAdapter = class {
80
539
  */
81
540
  async create(resource, data) {
82
541
  await this.connect();
83
- return this.client.data.create(resource, data);
542
+ const result = await this.client.data.create(resource, data);
543
+ return result.record;
84
544
  }
85
545
  /**
86
546
  * Update an existing record.
87
547
  */
88
548
  async update(resource, id, data) {
89
549
  await this.connect();
90
- return this.client.data.update(resource, String(id), data);
550
+ const result = await this.client.data.update(resource, String(id), data);
551
+ return result.record;
91
552
  }
92
553
  /**
93
554
  * Delete a record.
@@ -95,31 +556,136 @@ var ObjectStackAdapter = class {
95
556
  async delete(resource, id) {
96
557
  await this.connect();
97
558
  const result = await this.client.data.delete(resource, String(id));
98
- return result.success;
559
+ return result.deleted;
99
560
  }
100
561
  /**
101
- * Bulk operations (optional implementation).
562
+ * Bulk operations with optimized batch processing and error handling.
563
+ * Emits progress events for tracking operation status.
564
+ *
565
+ * @param resource - Resource name
566
+ * @param operation - Operation type (create, update, delete)
567
+ * @param data - Array of records to process
568
+ * @returns Promise resolving to array of results
102
569
  */
103
570
  async bulk(resource, operation, data) {
104
571
  await this.connect();
105
- switch (operation) {
106
- case "create":
107
- return this.client.data.createMany(resource, data);
108
- case "delete": {
109
- const ids = data.map((item) => item.id).filter(Boolean);
110
- await this.client.data.deleteMany(resource, ids);
111
- return [];
112
- }
113
- case "update": {
114
- const results = await Promise.all(
115
- data.map(
116
- (item) => this.client.data.update(resource, String(item.id), item)
117
- )
118
- );
119
- return results;
572
+ if (!data || data.length === 0) {
573
+ return [];
574
+ }
575
+ const total = data.length;
576
+ let completed = 0;
577
+ let failed = 0;
578
+ const emitProgress = () => {
579
+ this.emitBatchProgress({
580
+ operation,
581
+ total,
582
+ completed,
583
+ failed,
584
+ percentage: total > 0 ? (completed + failed) / total * 100 : 0
585
+ });
586
+ };
587
+ try {
588
+ switch (operation) {
589
+ case "create": {
590
+ emitProgress();
591
+ const created = await this.client.data.createMany(resource, data);
592
+ completed = created.length;
593
+ failed = total - completed;
594
+ emitProgress();
595
+ return created;
596
+ }
597
+ case "delete": {
598
+ const ids = data.map((item) => item.id).filter(Boolean);
599
+ if (ids.length === 0) {
600
+ const errors = data.map((_, index) => ({
601
+ index,
602
+ error: `Missing ID for item at index ${index}`
603
+ }));
604
+ failed = data.length;
605
+ emitProgress();
606
+ throw new BulkOperationError("delete", 0, data.length, errors);
607
+ }
608
+ emitProgress();
609
+ await this.client.data.deleteMany(resource, ids);
610
+ completed = ids.length;
611
+ failed = total - completed;
612
+ emitProgress();
613
+ return [];
614
+ }
615
+ case "update": {
616
+ if (typeof this.client.data.updateMany === "function") {
617
+ try {
618
+ emitProgress();
619
+ const updateMany = this.client.data.updateMany;
620
+ const updated = await updateMany(resource, data);
621
+ completed = updated.length;
622
+ failed = total - completed;
623
+ emitProgress();
624
+ return updated;
625
+ } catch {
626
+ }
627
+ }
628
+ const results = [];
629
+ const errors = [];
630
+ for (let i = 0; i < data.length; i++) {
631
+ const item = data[i];
632
+ const id = item.id;
633
+ if (!id) {
634
+ errors.push({ index: i, error: "Missing ID" });
635
+ failed++;
636
+ emitProgress();
637
+ continue;
638
+ }
639
+ try {
640
+ const result = await this.client.data.update(resource, String(id), item);
641
+ results.push(result.record);
642
+ completed++;
643
+ emitProgress();
644
+ } catch (error) {
645
+ const errorMessage = error instanceof Error ? error.message : String(error);
646
+ errors.push({ index: i, error: errorMessage });
647
+ failed++;
648
+ emitProgress();
649
+ }
650
+ }
651
+ if (errors.length > 0) {
652
+ throw new BulkOperationError(
653
+ "update",
654
+ results.length,
655
+ errors.length,
656
+ errors,
657
+ { resource, totalRecords: data.length }
658
+ );
659
+ }
660
+ return results;
661
+ }
662
+ default:
663
+ throw new ObjectStackError(
664
+ `Unsupported bulk operation: ${operation}`,
665
+ "UNSUPPORTED_OPERATION",
666
+ 400
667
+ );
120
668
  }
121
- default:
122
- throw new Error(`Unsupported bulk operation: ${operation}`);
669
+ } catch (error) {
670
+ emitProgress();
671
+ if (error instanceof BulkOperationError) {
672
+ throw error;
673
+ }
674
+ if (error instanceof ObjectStackError) {
675
+ throw error;
676
+ }
677
+ const errorMessage = error instanceof Error ? error.message : String(error);
678
+ const errors = data.map((_, index) => ({
679
+ index,
680
+ error: errorMessage
681
+ }));
682
+ throw new BulkOperationError(
683
+ operation,
684
+ 0,
685
+ data.length,
686
+ errors,
687
+ { resource, originalError: error }
688
+ );
123
689
  }
124
690
  }
125
691
  /**
@@ -133,13 +699,26 @@ var ObjectStackAdapter = class {
133
699
  options.select = params.$select;
134
700
  }
135
701
  if (params.$filter) {
136
- options.filters = (0, import_core.convertFiltersToAST)(params.$filter);
702
+ if (Array.isArray(params.$filter)) {
703
+ options.filters = params.$filter;
704
+ } else {
705
+ options.filters = (0, import_core.convertFiltersToAST)(params.$filter);
706
+ }
137
707
  }
138
708
  if (params.$orderby) {
139
- const sortArray = Object.entries(params.$orderby).map(([field, order]) => {
140
- return order === "desc" ? `-${field}` : field;
141
- });
142
- options.sort = sortArray;
709
+ if (Array.isArray(params.$orderby)) {
710
+ options.sort = params.$orderby.map((item) => {
711
+ if (typeof item === "string") return item;
712
+ const field = item.field;
713
+ const order = item.order || "asc";
714
+ return order === "desc" ? `-${field}` : field;
715
+ });
716
+ } else {
717
+ const sortArray = Object.entries(params.$orderby).map(([field, order]) => {
718
+ return order === "desc" ? `-${field}` : field;
719
+ });
720
+ options.sort = sortArray;
721
+ }
143
722
  }
144
723
  if (params.$skip !== void 0) {
145
724
  options.skip = params.$skip;
@@ -151,6 +730,7 @@ var ObjectStackAdapter = class {
151
730
  }
152
731
  /**
153
732
  * Get object schema/metadata from ObjectStack.
733
+ * Uses caching to improve performance for repeated requests.
154
734
  *
155
735
  * @param objectName - Object name
156
736
  * @returns Promise resolving to the object schema
@@ -158,11 +738,23 @@ var ObjectStackAdapter = class {
158
738
  async getObjectSchema(objectName) {
159
739
  await this.connect();
160
740
  try {
161
- const schema = await this.client.meta.getObject(objectName);
741
+ const schema = await this.metadataCache.get(objectName, async () => {
742
+ const result = await this.client.meta.getObject(objectName);
743
+ if (result && result.item) {
744
+ return result.item;
745
+ }
746
+ return result;
747
+ });
162
748
  return schema;
163
749
  } catch (error) {
164
- console.error(`Failed to fetch schema for ${objectName}:`, error);
165
- throw error;
750
+ const errorObj = error;
751
+ if (errorObj?.status === 404 || errorObj?.statusCode === 404) {
752
+ throw new MetadataNotFoundError(objectName, { originalError: error });
753
+ }
754
+ if (error instanceof ObjectStackError) {
755
+ throw error;
756
+ }
757
+ throw createErrorFromResponse(errorObj, `getObjectSchema(${objectName})`);
166
758
  }
167
759
  }
168
760
  /**
@@ -171,12 +763,195 @@ var ObjectStackAdapter = class {
171
763
  getClient() {
172
764
  return this.client;
173
765
  }
766
+ /**
767
+ * Get the discovery information from the connected server.
768
+ * Returns the capabilities and service status of the ObjectStack server.
769
+ *
770
+ * Note: This accesses an internal property of the ObjectStackClient.
771
+ * The discovery data is populated during client.connect() and cached.
772
+ *
773
+ * @returns Promise resolving to discovery data, or null if not connected
774
+ */
775
+ async getDiscovery() {
776
+ try {
777
+ await this.connect();
778
+ return this.client.discoveryInfo || null;
779
+ } catch {
780
+ return null;
781
+ }
782
+ }
783
+ /**
784
+ * Get a view definition for an object.
785
+ * Attempts to fetch from the server metadata API.
786
+ * Falls back to null if the server doesn't provide view definitions,
787
+ * allowing the consumer to use static config.
788
+ *
789
+ * @param objectName - Object name
790
+ * @param viewId - View identifier
791
+ * @returns Promise resolving to the view definition or null
792
+ */
793
+ async getView(objectName, viewId) {
794
+ await this.connect();
795
+ try {
796
+ const cacheKey = `view:${objectName}:${viewId}`;
797
+ return await this.metadataCache.get(cacheKey, async () => {
798
+ const result = await this.client.meta.getItem(objectName, `views/${viewId}`);
799
+ if (result && result.item) return result.item;
800
+ return result ?? null;
801
+ });
802
+ } catch {
803
+ return null;
804
+ }
805
+ }
806
+ /**
807
+ * Get an application definition by name or ID.
808
+ * Attempts to fetch from the server metadata API.
809
+ * Falls back to null if the server doesn't provide app definitions,
810
+ * allowing the consumer to use static config.
811
+ *
812
+ * @param appId - Application identifier
813
+ * @returns Promise resolving to the app definition or null
814
+ */
815
+ async getApp(appId) {
816
+ await this.connect();
817
+ try {
818
+ const cacheKey = `app:${appId}`;
819
+ return await this.metadataCache.get(cacheKey, async () => {
820
+ const result = await this.client.meta.getItem("apps", appId);
821
+ if (result && result.item) return result.item;
822
+ return result ?? null;
823
+ });
824
+ } catch {
825
+ return null;
826
+ }
827
+ }
828
+ /**
829
+ * Get cache statistics for monitoring performance.
830
+ */
831
+ getCacheStats() {
832
+ return this.metadataCache.getStats();
833
+ }
834
+ /**
835
+ * Invalidate metadata cache entries.
836
+ *
837
+ * @param key - Optional key to invalidate. If omitted, invalidates all entries.
838
+ */
839
+ invalidateCache(key) {
840
+ this.metadataCache.invalidate(key);
841
+ }
842
+ /**
843
+ * Clear all cache entries and statistics.
844
+ */
845
+ clearCache() {
846
+ this.metadataCache.clear();
847
+ }
848
+ /**
849
+ * Upload a single file to a resource.
850
+ * Posts the file as multipart/form-data to the ObjectStack server.
851
+ *
852
+ * @param resource - The resource/object name to attach the file to
853
+ * @param file - File object or Blob to upload
854
+ * @param options - Additional upload options (recordId, fieldName, metadata)
855
+ * @returns Promise resolving to the upload result (file URL, metadata)
856
+ */
857
+ async uploadFile(resource, file, options) {
858
+ await this.connect();
859
+ const formData = new FormData();
860
+ formData.append("file", file);
861
+ if (options?.recordId) {
862
+ formData.append("recordId", options.recordId);
863
+ }
864
+ if (options?.fieldName) {
865
+ formData.append("fieldName", options.fieldName);
866
+ }
867
+ if (options?.metadata) {
868
+ formData.append("metadata", JSON.stringify(options.metadata));
869
+ }
870
+ const url = `${this.baseUrl}/api/data/${encodeURIComponent(resource)}/upload`;
871
+ const response = await fetch(url, {
872
+ method: "POST",
873
+ body: formData,
874
+ headers: {
875
+ ...this.getAuthHeaders()
876
+ }
877
+ });
878
+ if (!response.ok) {
879
+ const error = await response.json().catch(() => ({ message: response.statusText }));
880
+ throw new ObjectStackError(
881
+ error.message || `Upload failed with status ${response.status}`,
882
+ "UPLOAD_ERROR",
883
+ response.status
884
+ );
885
+ }
886
+ return response.json();
887
+ }
888
+ /**
889
+ * Upload multiple files to a resource.
890
+ * Posts all files as a single multipart/form-data request.
891
+ *
892
+ * @param resource - The resource/object name to attach the files to
893
+ * @param files - Array of File objects or Blobs to upload
894
+ * @param options - Additional upload options
895
+ * @returns Promise resolving to array of upload results
896
+ */
897
+ async uploadFiles(resource, files, options) {
898
+ await this.connect();
899
+ const formData = new FormData();
900
+ files.forEach((file, idx) => {
901
+ formData.append(`files`, file, file.name || `file-${idx}`);
902
+ });
903
+ if (options?.recordId) {
904
+ formData.append("recordId", options.recordId);
905
+ }
906
+ if (options?.fieldName) {
907
+ formData.append("fieldName", options.fieldName);
908
+ }
909
+ if (options?.metadata) {
910
+ formData.append("metadata", JSON.stringify(options.metadata));
911
+ }
912
+ const url = `${this.baseUrl}/api/data/${encodeURIComponent(resource)}/upload`;
913
+ const response = await fetch(url, {
914
+ method: "POST",
915
+ body: formData,
916
+ headers: {
917
+ ...this.getAuthHeaders()
918
+ }
919
+ });
920
+ if (!response.ok) {
921
+ const error = await response.json().catch(() => ({ message: response.statusText }));
922
+ throw new ObjectStackError(
923
+ error.message || `Upload failed with status ${response.status}`,
924
+ "UPLOAD_ERROR",
925
+ response.status
926
+ );
927
+ }
928
+ return response.json();
929
+ }
930
+ /**
931
+ * Get authorization headers from the adapter config.
932
+ */
933
+ getAuthHeaders() {
934
+ const headers = {};
935
+ if (this.token) {
936
+ headers["Authorization"] = `Bearer ${this.token}`;
937
+ }
938
+ return headers;
939
+ }
174
940
  };
175
941
  function createObjectStackAdapter(config) {
176
942
  return new ObjectStackAdapter(config);
177
943
  }
178
944
  // Annotate the CommonJS export names for ESM import in node:
179
945
  0 && (module.exports = {
946
+ AuthenticationError,
947
+ BulkOperationError,
948
+ ConnectionError,
949
+ MetadataNotFoundError,
180
950
  ObjectStackAdapter,
181
- createObjectStackAdapter
951
+ ObjectStackError,
952
+ ValidationError,
953
+ createErrorFromResponse,
954
+ createObjectStackAdapter,
955
+ isErrorType,
956
+ isObjectStackError
182
957
  });