@onchaindb/sdk 0.4.4 → 1.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.
Files changed (97) hide show
  1. package/.claude/settings.local.json +10 -2
  2. package/README.md +422 -355
  3. package/dist/batch.d.ts +1 -10
  4. package/dist/batch.d.ts.map +1 -1
  5. package/dist/batch.js +4 -26
  6. package/dist/batch.js.map +1 -1
  7. package/dist/client.d.ts +29 -43
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +199 -323
  10. package/dist/client.js.map +1 -1
  11. package/dist/database.d.ts +14 -131
  12. package/dist/database.d.ts.map +1 -1
  13. package/dist/database.js +35 -131
  14. package/dist/database.js.map +1 -1
  15. package/dist/index.d.ts +6 -9
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1 -15
  18. package/dist/index.js.map +1 -1
  19. package/dist/query-sdk/ConditionBuilder.d.ts +3 -11
  20. package/dist/query-sdk/ConditionBuilder.d.ts.map +1 -1
  21. package/dist/query-sdk/ConditionBuilder.js +10 -48
  22. package/dist/query-sdk/ConditionBuilder.js.map +1 -1
  23. package/dist/query-sdk/NestedBuilders.d.ts +33 -30
  24. package/dist/query-sdk/NestedBuilders.d.ts.map +1 -1
  25. package/dist/query-sdk/NestedBuilders.js +46 -43
  26. package/dist/query-sdk/NestedBuilders.js.map +1 -1
  27. package/dist/query-sdk/QueryBuilder.d.ts +4 -2
  28. package/dist/query-sdk/QueryBuilder.d.ts.map +1 -1
  29. package/dist/query-sdk/QueryBuilder.js +47 -169
  30. package/dist/query-sdk/QueryBuilder.js.map +1 -1
  31. package/dist/query-sdk/QueryResult.d.ts +0 -38
  32. package/dist/query-sdk/QueryResult.d.ts.map +1 -1
  33. package/dist/query-sdk/QueryResult.js +1 -227
  34. package/dist/query-sdk/QueryResult.js.map +1 -1
  35. package/dist/query-sdk/index.d.ts +1 -1
  36. package/dist/query-sdk/index.d.ts.map +1 -1
  37. package/dist/query-sdk/index.js.map +1 -1
  38. package/dist/query-sdk/operators.d.ts +32 -28
  39. package/dist/query-sdk/operators.d.ts.map +1 -1
  40. package/dist/query-sdk/operators.js +45 -155
  41. package/dist/query-sdk/operators.js.map +1 -1
  42. package/dist/types.d.ts +153 -1
  43. package/dist/types.d.ts.map +1 -1
  44. package/dist/types.js.map +1 -1
  45. package/jest.config.js +4 -0
  46. package/package.json +1 -1
  47. package/skills.md +0 -1
  48. package/src/client.ts +243 -745
  49. package/src/database.ts +70 -493
  50. package/src/index.ts +40 -193
  51. package/src/query-sdk/ConditionBuilder.ts +37 -89
  52. package/src/query-sdk/NestedBuilders.ts +90 -92
  53. package/src/query-sdk/QueryBuilder.ts +59 -218
  54. package/src/query-sdk/QueryResult.ts +4 -330
  55. package/src/query-sdk/README.md +214 -583
  56. package/src/query-sdk/index.ts +1 -1
  57. package/src/query-sdk/operators.ts +91 -200
  58. package/src/query-sdk/tests/FieldConditionBuilder.test.ts +70 -71
  59. package/src/query-sdk/tests/LogicalOperator.test.ts +43 -82
  60. package/src/query-sdk/tests/NestedBuilders.test.ts +229 -309
  61. package/src/query-sdk/tests/QueryBuilder.test.ts +5 -5
  62. package/src/query-sdk/tests/QueryResult.test.ts +41 -435
  63. package/src/query-sdk/tests/comprehensive.test.ts +4 -185
  64. package/src/tests/client-requests.test.ts +280 -0
  65. package/src/tests/client-validation.test.ts +80 -0
  66. package/src/types.ts +229 -8
  67. package/src/batch.ts +0 -257
  68. package/src/query-sdk/dist/ConditionBuilder.d.ts +0 -22
  69. package/src/query-sdk/dist/ConditionBuilder.js +0 -90
  70. package/src/query-sdk/dist/FieldConditionBuilder.d.ts +0 -1
  71. package/src/query-sdk/dist/FieldConditionBuilder.js +0 -6
  72. package/src/query-sdk/dist/NestedBuilders.d.ts +0 -43
  73. package/src/query-sdk/dist/NestedBuilders.js +0 -144
  74. package/src/query-sdk/dist/OnChainDB.d.ts +0 -19
  75. package/src/query-sdk/dist/OnChainDB.js +0 -123
  76. package/src/query-sdk/dist/QueryBuilder.d.ts +0 -70
  77. package/src/query-sdk/dist/QueryBuilder.js +0 -295
  78. package/src/query-sdk/dist/QueryResult.d.ts +0 -52
  79. package/src/query-sdk/dist/QueryResult.js +0 -293
  80. package/src/query-sdk/dist/SelectionBuilder.d.ts +0 -20
  81. package/src/query-sdk/dist/SelectionBuilder.js +0 -80
  82. package/src/query-sdk/dist/adapters/HttpClientAdapter.d.ts +0 -27
  83. package/src/query-sdk/dist/adapters/HttpClientAdapter.js +0 -170
  84. package/src/query-sdk/dist/index.d.ts +0 -36
  85. package/src/query-sdk/dist/index.js +0 -27
  86. package/src/query-sdk/dist/operators.d.ts +0 -56
  87. package/src/query-sdk/dist/operators.js +0 -289
  88. package/src/query-sdk/dist/tests/setup.d.ts +0 -15
  89. package/src/query-sdk/dist/tests/setup.js +0 -46
  90. package/src/query-sdk/jest.config.js +0 -25
  91. package/src/query-sdk/package.json +0 -46
  92. package/src/query-sdk/tests/aggregations.test.ts +0 -653
  93. package/src/query-sdk/tests/integration.test.ts +0 -608
  94. package/src/query-sdk/tests/operators.test.ts +0 -327
  95. package/src/query-sdk/tests/unit.test.ts +0 -794
  96. package/src/query-sdk/tsconfig.json +0 -26
  97. package/src/query-sdk/yarn.lock +0 -3092
package/src/client.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import axios, {AxiosError, AxiosInstance} from 'axios';
2
2
  import {EventEmitter} from 'eventemitter3';
3
3
  import {
4
- BlobMetadata,
5
4
  CreateCollectionResult,
6
5
  IndexRequest,
7
6
  IndexResponse,
@@ -13,12 +12,25 @@ import {
13
12
  RelationResponse,
14
13
  RetrieveBlobRequest,
15
14
  SimpleCollectionSchema,
15
+ SimpleCollectionSchemaWithSharding,
16
16
  SimpleFieldDefinition,
17
+ ShardingStrategy,
18
+ UpdateCollectionRequest,
19
+ SetRetentionRequest,
20
+ RetentionConfig,
21
+ RetentionCostResponse,
22
+ SqlQueryRequest,
23
+ SqlQueryResponse,
24
+ SqlInsertRequest,
25
+ CreateQueryRequest,
26
+ CreateQueryResponse,
27
+ QueryDefinition,
28
+ QueryDataResponse,
29
+ CollectionResponse,
17
30
  StoreRequest,
18
31
  StoreResponse,
19
32
  SyncCollectionResult,
20
33
  TaskInfo,
21
- TransactionError,
22
34
  TransactionEvents,
23
35
  UploadBlobRequest,
24
36
  UploadBlobResponse,
@@ -28,7 +40,7 @@ import {
28
40
  // Import query builder components
29
41
  // Import database management
30
42
  import {createDatabaseManager, DatabaseManager} from './database';
31
- import {LogicalOperator, QueryBuilder, QueryResponse} from "./query-sdk";
43
+ import {QueryBuilder} from "./query-sdk";
32
44
 
33
45
  // Import x402 utilities
34
46
  import {
@@ -53,12 +65,18 @@ import {
53
65
  *
54
66
  * @example
55
67
  * ```typescript
56
- * // Initialize with app key (for writes) and user key (for Auto-Pay)
68
+ * // Initialize with app key (for writes)
57
69
  * const db = new OnChainDBClient({
58
70
  * endpoint: 'http://localhost:9092',
59
71
  * appId: 'app_abc123',
60
- * appKey: 'app_xxx...', // Required for write operations
61
- * userKey: 'user_yyy...' // Optional: enables Auto-Pay for reads/writes
72
+ * appKey: 'app_xxx...', // Required for write operations (X-App-Key header)
73
+ * });
74
+ *
75
+ * // Agent Key: autonomous agent that pays other apps inline via USDC (EIP-3009)
76
+ * const agent = new OnChainDBClient({
77
+ * endpoint: 'http://localhost:9092',
78
+ * appId: 'app_abc123',
79
+ * agentKey: 'agent_zzz...', // X-Agent-Key header — key must have Pay permission
62
80
  * });
63
81
  *
64
82
  * // Store data (requires appKey)
@@ -67,7 +85,7 @@ import {
67
85
  * collection: 'messages'
68
86
  * });
69
87
  *
70
- * // Query data (userKey enables Auto-Pay if authz granted)
88
+ * // Query data
71
89
  * const messages = await db.query({ collection: 'messages' });
72
90
  *
73
91
  * // Backwards compatible: legacy apiKey maps to appKey
@@ -88,7 +106,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
88
106
  deletedAt: {type: 'date', index: true}
89
107
  };
90
108
  private http: AxiosInstance;
91
- private config: Required<Omit<OnChainDBConfig, 'appId'>> & { appId?: string; appKey?: string; userKey?: string };
109
+ private config: Required<Omit<OnChainDBConfig, 'appId'>> & { appId?: string; appKey?: string; userKey?: string; agentKey?: string };
92
110
  private _database?: DatabaseManager;
93
111
 
94
112
  constructor(config: OnChainDBConfig) {
@@ -97,19 +115,21 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
97
115
  // Support legacy apiKey for backwards compatibility (maps to appKey)
98
116
  const appKey = config.appKey || '';
99
117
  const userKey = config.userKey || '';
118
+ const agentKey = config.agentKey || '';
100
119
 
101
120
  this.config = {
102
121
  endpoint: config.endpoint,
103
122
  apiKey: config.apiKey || "", // Keep for backwards compat, but maps to appKey
104
123
  appKey: appKey,
105
124
  userKey: userKey,
125
+ agentKey: agentKey,
106
126
  appId: config.appId || undefined,
107
127
  timeout: config.timeout || 30000,
108
128
  retryCount: config.retryCount || 3,
109
129
  retryDelay: config.retryDelay || 1000
110
130
  };
111
131
 
112
- // Build headers with both keys if provided
132
+ // Build headers with provided keys
113
133
  const headers: Record<string, string> = {
114
134
  'Content-Type': 'application/json'
115
135
  };
@@ -122,11 +142,15 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
122
142
  headers['X-Api-Key'] = config.apiKey;
123
143
  }
124
144
 
125
-
126
145
  if (userKey) {
127
146
  headers['X-User-Key'] = userKey;
128
147
  }
129
148
 
149
+ // Agent Keys use X-Agent-Key (not X-API-Key / X-App-Key)
150
+ if (agentKey) {
151
+ headers['X-Agent-Key'] = agentKey;
152
+ }
153
+
130
154
  this.http = axios.create({
131
155
  baseURL: this.config.endpoint,
132
156
  timeout: this.config.timeout,
@@ -162,12 +186,15 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
162
186
  * });
163
187
  * ```
164
188
  */
165
- database(appId: string): DatabaseManager {
166
- if (!this._database || this._database['appId'] !== appId) {
189
+ database(appId?: string): DatabaseManager {
190
+ const resolvedAppId = appId || this.config.appId;
191
+ if (!resolvedAppId) {
192
+ throw new ValidationError('appId must be provided either in config or as an argument to database()');
193
+ }
194
+ if (!this._database || this._database['appId'] !== resolvedAppId) {
167
195
  this._database = createDatabaseManager(
168
196
  this.http,
169
- this.config.endpoint,
170
- appId,
197
+ resolvedAppId,
171
198
  this.config.apiKey
172
199
  );
173
200
  }
@@ -187,7 +214,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
187
214
  * @param waitForConfirmation - Whether to wait for blockchain confirmation
188
215
  * @returns Promise resolving to the operation result
189
216
  */
190
- async handleX402(
217
+ private async handleX402(
191
218
  response: { data: any },
192
219
  paymentCallback: ((quote: X402Quote) => Promise<X402PaymentCallbackResult>) | undefined,
193
220
  finalRequest: any,
@@ -295,29 +322,11 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
295
322
  if (serverResult.ticket_id) {
296
323
  if (waitForConfirmation) {
297
324
  const taskInfo = await this.waitForTaskCompletion(serverResult.ticket_id);
298
- // Extract result from task
299
- if (taskInfo.result && taskInfo.result.results && taskInfo.result.results.length > 0) {
300
- const firstResult = taskInfo.result.results[0];
301
- return {
302
- id: firstResult.id,
303
- block_height: firstResult.celestia_height || 0,
304
- transaction_hash: firstResult.blob_id || '',
305
- celestia_height: firstResult.celestia_height || 0,
306
- namespace: firstResult.namespace || '',
307
- confirmed: firstResult.celestia_height > 0
308
- };
325
+ if (taskInfo.result?.results?.length > 0) {
326
+ return this.buildStoreResult(taskInfo.result.results[0]);
309
327
  }
310
328
  } else {
311
- // Return ticket without waiting
312
- return {
313
- id: serverResult.ticket_id,
314
- block_height: 0,
315
- transaction_hash: '',
316
- celestia_height: 0,
317
- namespace: '',
318
- confirmed: false,
319
- ticket_id: serverResult.ticket_id
320
- } as StoreResponse;
329
+ return { id: serverResult.ticket_id, block_height: 0, transaction_hash: '', celestia_height: 0, namespace: '', confirmed: false, ticket_id: serverResult.ticket_id } as StoreResponse;
321
330
  }
322
331
  }
323
332
 
@@ -368,18 +377,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
368
377
  delete resolvedRequest.collection;
369
378
 
370
379
 
371
- // Step 1: Try to store without payment (may get 402)
372
380
  const response = await this.http.post('/store', resolvedRequest);
373
-
374
- // Step 2: Handle 402 Payment Required (x402)
375
- if (response.status === 402 && response.data) {
376
- const v = await this.handleX402(response, paymentCallback, resolvedRequest, waitForConfirmation);
377
-
378
- if (v) {
379
- return v;
380
- }
381
- }
382
-
383
381
  const serverResult = response.data;
384
382
 
385
383
  // Check if we got an async response with ticket_id (new flow)
@@ -396,20 +394,8 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
396
394
  const taskInfo = await this.waitForTaskCompletion(serverResult.ticket_id);
397
395
 
398
396
  // Extract the actual storage result from the completed task
399
- if (taskInfo.result && taskInfo.result.results && taskInfo.result.results.length > 0) {
400
- const firstResult = taskInfo.result.results[0];
401
-
402
- // Transform to SDK format
403
- const result: StoreResponse = {
404
- id: firstResult.id,
405
- block_height: firstResult.celestia_height || 0,
406
- transaction_hash: firstResult.blob_id || '',
407
- celestia_height: firstResult.celestia_height || 0,
408
- namespace: firstResult.namespace || '',
409
- confirmed: firstResult.celestia_height > 0
410
- };
411
-
412
- // Emit completion event
397
+ if (taskInfo.result?.results?.length > 0) {
398
+ const result = this.buildStoreResult(taskInfo.result.results[0]);
413
399
  this.emit('transaction:confirmed', {
414
400
  id: result.id,
415
401
  status: 'confirmed',
@@ -417,40 +403,22 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
417
403
  transaction_hash: result.transaction_hash,
418
404
  celestia_height: result.celestia_height
419
405
  });
420
-
421
406
  return result;
422
407
  } else {
423
408
  throw new OnChainDBError('Task completed but no storage results found', 'STORE_ERROR');
424
409
  }
425
410
  } else {
426
- // Return ticket info without waiting
427
- return {
428
- id: serverResult.ticket_id,
429
- block_height: 0,
430
- transaction_hash: '',
431
- celestia_height: 0,
432
- namespace: '',
433
- confirmed: false,
434
- ticket_id: serverResult.ticket_id
435
- } as StoreResponse;
411
+ return { id: serverResult.ticket_id, block_height: 0, transaction_hash: '', celestia_height: 0, namespace: '', confirmed: false, ticket_id: serverResult.ticket_id } as StoreResponse;
436
412
  }
437
413
  }
438
414
 
439
415
  // Legacy response format (if server returns old format)
440
- const firstResult = serverResult.results && serverResult.results[0];
416
+ const firstResult = serverResult.results?.[0];
441
417
  if (!firstResult) {
442
418
  throw new OnChainDBError('No results returned from server', 'STORE_ERROR');
443
419
  }
444
420
 
445
- // Transform server response to SDK format
446
- const result: StoreResponse = {
447
- id: firstResult.id,
448
- block_height: firstResult.celestia_height || 0,
449
- transaction_hash: firstResult.blob_id || '',
450
- celestia_height: firstResult.celestia_height || 0,
451
- namespace: firstResult.namespace || '',
452
- confirmed: firstResult.celestia_height > 0
453
- };
421
+ const result = this.buildStoreResult(firstResult);
454
422
 
455
423
  // Emit appropriate event based on height
456
424
  if (result.block_height === 0) {
@@ -470,10 +438,6 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
470
438
  });
471
439
  }
472
440
 
473
- if (waitForConfirmation && result.block_height === 0) {
474
- return await this.waitForConfirmation(result.transaction_hash);
475
- }
476
-
477
441
  return result;
478
442
  } catch (error: any) {
479
443
  if ((error as AxiosError)?.response) {
@@ -489,6 +453,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
489
453
  throw new OnChainDBError(JSON.stringify(err.response?.data), 'STORE_ERROR');
490
454
  }
491
455
 
456
+ console.error(error)
492
457
  const dbError = error instanceof OnChainDBError ? error :
493
458
  new OnChainDBError(error.message, 'STORE_ERROR');
494
459
  this.emit('error', dbError);
@@ -496,98 +461,6 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
496
461
  }
497
462
  }
498
463
 
499
- /**
500
- * Store data and return a promise that resolves when transaction is confirmed
501
- *
502
- * @param request - Store request
503
- * @param paymentOptions - Payment configuration for broker fees
504
- * @returns Promise resolving when transaction is confirmed on blockchain
505
- */
506
- async storeAndConfirm(
507
- request: StoreRequest
508
- ): Promise<StoreResponse> {
509
- return this.store(request, undefined, true);
510
- }
511
-
512
- /**
513
- * Wait for transaction confirmation
514
- *
515
- * @param transactionHash - Transaction hash to monitor
516
- * @param maxWaitTime - Maximum wait time in milliseconds (default: 5 minutes)
517
- * @returns Promise resolving to confirmed transaction
518
- */
519
- async waitForConfirmation(transactionHash: string, maxWaitTime: number = 300000): Promise<StoreResponse> {
520
- const startTime = Date.now();
521
- const pollInterval = 3000; // Poll every 3 seconds (same as server)
522
-
523
- console.log(`🔄 Waiting for transaction ${transactionHash} confirmation...`);
524
-
525
- while (Date.now() - startTime < maxWaitTime) {
526
- try {
527
- // Query Celestia RPC directly to check transaction status
528
- const rpcUrl = 'https://celestia-mocha-rpc.publicnode.com:443'; // Default testnet RPC
529
- const txUrl = `${rpcUrl}/tx?hash=0x${transactionHash}`;
530
-
531
- console.log(`🔍 Checking transaction status: attempt ${Math.floor((Date.now() - startTime) / pollInterval) + 1}`);
532
-
533
- const response = await axios.get(txUrl);
534
-
535
- if (response.data?.result && response.data.result !== null) {
536
- const txResult = response.data.result;
537
- const height = parseInt(txResult.height);
538
-
539
- if (height > 0) {
540
- console.log(`✅ Transaction ${transactionHash} confirmed at height ${height}`);
541
-
542
- const confirmedTx: StoreResponse = {
543
- id: transactionHash, // Use transaction hash as ID for confirmation
544
- namespace: '', // Will be filled by actual usage
545
- block_height: height,
546
- transaction_hash: transactionHash,
547
- celestia_height: height,
548
- confirmed: true
549
- };
550
-
551
- this.emit('transaction:confirmed', {
552
- id: transactionHash,
553
- status: 'confirmed',
554
- block_height: height,
555
- transaction_hash: transactionHash
556
- });
557
- return confirmedTx;
558
- }
559
- }
560
-
561
- // Still pending, wait and retry
562
- console.log(`⏳ Transaction still pending, waiting ${pollInterval}ms...`);
563
- this.emit('transaction:pending', {
564
- id: transactionHash,
565
- status: 'pending',
566
- block_height: 0,
567
- transaction_hash: transactionHash
568
- });
569
- await this.sleep(pollInterval);
570
-
571
- } catch (error) {
572
- // For 404 or other errors, the transaction might not be confirmed yet
573
- if (Date.now() - startTime >= maxWaitTime) {
574
- throw new TransactionError(
575
- `Transaction confirmation timeout after ${maxWaitTime}ms`,
576
- transactionHash
577
- );
578
- }
579
-
580
- // Wait and retry for temporary errors
581
- console.log(`⚠️ Error checking transaction (will retry): ${error}`);
582
- await this.sleep(pollInterval);
583
- }
584
- }
585
-
586
- throw new TransactionError(
587
- `Transaction confirmation timeout after ${maxWaitTime}ms`,
588
- transactionHash
589
- );
590
- }
591
464
 
592
465
  /**
593
466
  * Create an index on a collection field
@@ -751,7 +624,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
751
624
  * console.log('Removed:', result.removed); // [{ field: 'username', type: 'string' }]
752
625
  * ```
753
626
  */
754
- async syncCollection(schema: SimpleCollectionSchema): Promise<SyncCollectionResult> {
627
+ async syncCollection(schema: SimpleCollectionSchemaWithSharding): Promise<SyncCollectionResult> {
755
628
  const appId = this.config.appId;
756
629
  if (!appId) {
757
630
  throw new ValidationError('appId must be configured to sync collections');
@@ -766,22 +639,6 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
766
639
  errors: []
767
640
  };
768
641
 
769
- // Get existing indexes for this collection
770
- let existingIndexes: Array<{ field_name: string; index_type: string; name: string }> = [];
771
- try {
772
- const response = await this.http.get(`/api/apps/${appId}/collections/${schema.name}/indexes`);
773
- existingIndexes = response.data?.indexes || response.data || [];
774
- } catch (error) {
775
- // Collection might not exist yet, that's okay
776
- existingIndexes = [];
777
- }
778
-
779
- // Build map of existing indexes by field name
780
- const existingByField = new Map<string, { type: string; name: string }>();
781
- for (const idx of existingIndexes) {
782
- existingByField.set(idx.field_name, {type: idx.index_type, name: idx.name});
783
- }
784
-
785
642
  // Merge base fields if enabled (default: true)
786
643
  const allFields: Record<string, SimpleFieldDefinition> = {};
787
644
  if (schema.useBaseFields !== false) {
@@ -797,9 +654,9 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
797
654
  }
798
655
  }
799
656
 
800
- // Find indexes to create (in desired but not existing)
657
+ // Create all desired indexes (broker handles upsert)
801
658
  for (const fieldName of desiredIndexedFields) {
802
- if (!existingByField.has(fieldName)) {
659
+ {
803
660
  const fieldDef = allFields[fieldName];
804
661
  const indexType = fieldDef.indexType || this.getDefaultIndexType(fieldDef.type);
805
662
 
@@ -839,36 +696,179 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
839
696
  }
840
697
  }
841
698
 
842
- // Find indexes to remove (existing but not in desired)
843
- for (const [fieldName, existing] of existingByField) {
844
- if (!desiredIndexedFields.has(fieldName)) {
845
- try {
846
- // Index ID format: {collection}_{field_name}_index
847
- const indexId = `${schema.name}_${fieldName}_index`;
848
- await this.http.delete(`/api/apps/${appId}/indexes/${indexId}`);
849
- result.removed.push({
850
- field: fieldName,
851
- type: existing.type
852
- });
853
- } catch (error) {
854
- const errorMsg = error instanceof Error ? error.message : String(error);
855
- result.errors!.push(`Failed to remove index on ${fieldName}: ${errorMsg}`);
856
- result.success = false;
857
- }
699
+ if (schema.sharding) {
700
+ try {
701
+ await this.setupSharding(schema.name, schema.sharding);
702
+ result.sharding_configured = true;
703
+ } catch (error) {
704
+ const errorMsg = error instanceof Error ? error.message : String(error);
705
+ result.errors!.push(`Failed to configure sharding: ${errorMsg}`);
706
+ result.success = false;
858
707
  }
859
708
  }
860
709
 
861
- // Track unchanged indexes
862
- for (const [fieldName, existing] of existingByField) {
863
- if (desiredIndexedFields.has(fieldName)) {
864
- result.unchanged.push({
865
- field: fieldName,
866
- type: existing.type
867
- });
868
- }
710
+ return result;
711
+ }
712
+
713
+ async setupSharding(collection: string, sharding: ShardingStrategy): Promise<void> {
714
+ const appId = this.config.appId;
715
+ if (!appId) {
716
+ throw new ValidationError('appId must be configured to setup sharding');
869
717
  }
718
+ try {
719
+ await this.http.patch(
720
+ `/api/apps/${appId}/collections/${collection}/sharding`,
721
+ sharding
722
+ );
723
+ } catch (error) {
724
+ throw error instanceof OnChainDBError ? error :
725
+ new OnChainDBError('Failed to setup sharding', 'SHARDING_ERROR');
726
+ }
727
+ }
870
728
 
871
- return result;
729
+ async deleteCollection(collection: string): Promise<{ success: boolean }> {
730
+ const appId = this.config.appId;
731
+ if (!appId) throw new ValidationError('appId must be configured');
732
+ try {
733
+ const response = await this.http.delete(`/api/apps/${appId}/collections/${collection}`);
734
+ return response.data;
735
+ } catch (error) {
736
+ throw error instanceof OnChainDBError ? error :
737
+ new OnChainDBError('Failed to delete collection', 'COLLECTION_ERROR');
738
+ }
739
+ }
740
+
741
+ async updateCollection(collection: string, updates: UpdateCollectionRequest): Promise<any> {
742
+ const appId = this.config.appId;
743
+ if (!appId) throw new ValidationError('appId must be configured');
744
+ try {
745
+ const response = await this.http.patch(`/api/apps/${appId}/collections/${collection}`, updates);
746
+ return response.data;
747
+ } catch (error) {
748
+ throw error instanceof OnChainDBError ? error :
749
+ new OnChainDBError('Failed to update collection', 'COLLECTION_ERROR');
750
+ }
751
+ }
752
+
753
+ async getRetention(collection: string): Promise<RetentionConfig> {
754
+ const appId = this.config.appId;
755
+ if (!appId) throw new ValidationError('appId must be configured');
756
+ try {
757
+ const response = await this.http.get(`/api/apps/${appId}/collections/${collection}/retention`);
758
+ return response.data;
759
+ } catch (error) {
760
+ throw error instanceof OnChainDBError ? error :
761
+ new OnChainDBError('Failed to get retention config', 'RETENTION_ERROR');
762
+ }
763
+ }
764
+
765
+ async setRetention(collection: string, request: SetRetentionRequest): Promise<{ message: string; config: RetentionConfig }> {
766
+ const appId = this.config.appId;
767
+ if (!appId) throw new ValidationError('appId must be configured');
768
+ try {
769
+ const response = await this.http.post(`/api/apps/${appId}/collections/${collection}/retention`, request);
770
+ return response.data;
771
+ } catch (error) {
772
+ throw error instanceof OnChainDBError ? error :
773
+ new OnChainDBError('Failed to set retention config', 'RETENTION_ERROR');
774
+ }
775
+ }
776
+
777
+ async getRetentionCost(): Promise<RetentionCostResponse> {
778
+ const appId = this.config.appId;
779
+ if (!appId) throw new ValidationError('appId must be configured');
780
+ try {
781
+ const response = await this.http.get(`/api/apps/${appId}/retention/cost`);
782
+ return response.data;
783
+ } catch (error) {
784
+ throw error instanceof OnChainDBError ? error :
785
+ new OnChainDBError('Failed to get retention cost', 'RETENTION_ERROR');
786
+ }
787
+ }
788
+
789
+ async sql(query: string, options?: { includeHistory?: boolean }): Promise<SqlQueryResponse> {
790
+ try {
791
+ const body: SqlQueryRequest = { sql: query };
792
+ if (options?.includeHistory !== undefined) body.include_history = options.includeHistory;
793
+ const response = await this.http.post('/query/sql', body);
794
+ return response.data;
795
+ } catch (error) {
796
+ throw error instanceof OnChainDBError ? error :
797
+ new OnChainDBError('SQL query failed', 'SQL_ERROR');
798
+ }
799
+ }
800
+
801
+ async sqlInsert(sql: string): Promise<any> {
802
+ try {
803
+ const body: SqlInsertRequest = { sql };
804
+ const response = await this.http.post('/insert/sql', body);
805
+ return response.data;
806
+ } catch (error) {
807
+ throw error instanceof OnChainDBError ? error :
808
+ new OnChainDBError('SQL insert failed', 'SQL_ERROR');
809
+ }
810
+ }
811
+
812
+ async listQueries(): Promise<QueryDefinition[]> {
813
+ const appId = this.config.appId;
814
+ if (!appId) throw new ValidationError('appId must be configured');
815
+ try {
816
+ const response = await this.http.get(`/apps/${appId}/queries`);
817
+ return response.data.queries ?? response.data;
818
+ } catch (error) {
819
+ throw error instanceof OnChainDBError ? error :
820
+ new OnChainDBError('Failed to list predefined queries', 'QUERY_ERROR');
821
+ }
822
+ }
823
+
824
+ async createQuery(request: CreateQueryRequest): Promise<CreateQueryResponse> {
825
+ const appId = this.config.appId;
826
+ if (!appId) throw new ValidationError('appId must be configured');
827
+ try {
828
+ const response = await this.http.post(`/apps/${appId}/queries`, request);
829
+ return response.data;
830
+ } catch (error) {
831
+ throw error instanceof OnChainDBError ? error :
832
+ new OnChainDBError('Failed to create predefined query', 'QUERY_ERROR');
833
+ }
834
+ }
835
+
836
+ async getQuery(name: string): Promise<QueryDefinition> {
837
+ const appId = this.config.appId;
838
+ if (!appId) throw new ValidationError('appId must be configured');
839
+ try {
840
+ const response = await this.http.get(`/apps/${appId}/queries/${name}`);
841
+ return response.data;
842
+ } catch (error) {
843
+ throw error instanceof OnChainDBError ? error :
844
+ new OnChainDBError('Failed to get predefined query', 'QUERY_ERROR');
845
+ }
846
+ }
847
+
848
+ async deleteQuery(name: string): Promise<{ success: boolean }> {
849
+ const appId = this.config.appId;
850
+ if (!appId) throw new ValidationError('appId must be configured');
851
+ try {
852
+ const response = await this.http.delete(`/apps/${appId}/queries/${name}`);
853
+ return response.data;
854
+ } catch (error) {
855
+ throw error instanceof OnChainDBError ? error :
856
+ new OnChainDBError('Failed to delete predefined query', 'QUERY_ERROR');
857
+ }
858
+ }
859
+
860
+ async executeQuery(name: string, params?: Record<string, string>, version?: number): Promise<QueryDataResponse> {
861
+ const appId = this.config.appId;
862
+ if (!appId) throw new ValidationError('appId must be configured');
863
+ try {
864
+ const queryParams: Record<string, string | number> = { ...params };
865
+ if (version !== undefined) queryParams['v'] = version;
866
+ const response = await this.http.get(`/api/queries/${appId}/${name}/data`, { params: queryParams });
867
+ return response.data;
868
+ } catch (error) {
869
+ throw error instanceof OnChainDBError ? error :
870
+ new OnChainDBError('Failed to execute predefined query', 'QUERY_ERROR');
871
+ }
872
872
  }
873
873
 
874
874
  /**
@@ -877,14 +877,14 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
877
877
  * @param collection - Collection name
878
878
  * @returns Collection information
879
879
  */
880
- async getCollectionInfo(collection: string): Promise<{ indexes: string[]; recordCount?: number }> {
880
+ async getCollectionInfo(collectionId: string): Promise<CollectionResponse> {
881
881
  const appId = this.config.appId;
882
882
  if (!appId) {
883
883
  throw new ValidationError('appId must be configured');
884
884
  }
885
885
 
886
886
  try {
887
- const response = await this.http.get(`/api/apps/${appId}/collections/${collection}`);
887
+ const response = await this.http.get(`/api/apps/${appId}/collections/${collectionId}`);
888
888
  return response.data;
889
889
  } catch (error) {
890
890
  throw error instanceof OnChainDBError ? error :
@@ -985,62 +985,6 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
985
985
  }
986
986
  }
987
987
 
988
- /**
989
- * Execute a simple query with basic filtering
990
- *
991
- * @param request - Simple query request
992
- * @returns Query response with records
993
- *
994
- * @example
995
- * ```typescript
996
- * const tweets = await db.query({
997
- * collection: 'tweets',
998
- * filters: { author: 'alice' },
999
- * limit: 10
1000
- * });
1001
- * ```
1002
- */
1003
- async query(request: {
1004
- collection: string;
1005
- filters?: Record<string, any>;
1006
- limit?: number;
1007
- offset?: number;
1008
- sort?: string[];
1009
- }): Promise<QueryResponse> {
1010
- try {
1011
- const queryBuilder = this.queryBuilder();
1012
-
1013
- // Set collection using root building
1014
- const root = this.resolveRoot(request);
1015
-
1016
- // Add filters if provided
1017
- if (request.filters) {
1018
- queryBuilder.find(builder => {
1019
- const conditions = Object.entries(request.filters!).map(([field, value]) =>
1020
- LogicalOperator.Condition(builder.field(field).equals(value))
1021
- );
1022
- return conditions.length === 1 ? conditions[0] : LogicalOperator.And(conditions);
1023
- });
1024
- }
1025
-
1026
- // Set limit and offset
1027
- if (request.limit) {
1028
- queryBuilder.limit(request.limit);
1029
- }
1030
-
1031
- if (request.offset) {
1032
- queryBuilder.offset(request.offset);
1033
- }
1034
-
1035
- // Execute the query
1036
- return await queryBuilder.execute();
1037
-
1038
- } catch (error) {
1039
- throw error instanceof OnChainDBError ? error :
1040
- new OnChainDBError('Failed to execute query', 'QUERY_ERROR');
1041
- }
1042
- }
1043
-
1044
988
  /**
1045
989
  * Create a fluent query builder instance
1046
990
  *
@@ -1050,10 +994,10 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1050
994
  * ```typescript
1051
995
  * const results = await db.queryBuilder()
1052
996
  * .find(builder =>
1053
- * LogicalOperator.And([
1054
- * LogicalOperator.Condition(builder.field('status').equals('published')),
1055
- * LogicalOperator.Condition(builder.field('author').equals('alice'))
1056
- * ])
997
+ * builder.and(
998
+ * builder.field('status').equals('published'),
999
+ * builder.field('author').equals('alice')
1000
+ * )
1057
1001
  * )
1058
1002
  * .select(selection =>
1059
1003
  * selection.field('title').field('content')
@@ -1069,359 +1013,6 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1069
1013
  return new QueryBuilder(httpClient, this.config.endpoint, this.config.appId);
1070
1014
  }
1071
1015
 
1072
- /**
1073
- * Create a batch operations instance for this client
1074
- *
1075
- * @returns BatchOperations instance
1076
- *
1077
- * @example
1078
- * ```typescript
1079
- * const batch = db.batch();
1080
- * const results = await batch.store([...], { concurrency: 5 });
1081
- * ```
1082
- */
1083
- batch() {
1084
- const {BatchOperations} = require('./batch');
1085
- return new BatchOperations(this);
1086
- }
1087
-
1088
- /**
1089
- * Find a single document by query (Prisma-style findUnique)
1090
- * Returns the latest record by metadata (updatedAt or createdAt) if multiple matches.
1091
- *
1092
- * @param collection - Collection name to search in
1093
- * @param where - Query conditions as key-value pairs
1094
- * @returns Promise resolving to document or null if not found
1095
- *
1096
- * @example
1097
- * ```typescript
1098
- * const user = await client.findUnique('users', { email: 'alice@example.com' });
1099
- * if (user) {
1100
- * console.log('Found user:', user);
1101
- * }
1102
- * ```
1103
- */
1104
- async findUnique<T extends Record<string, any>>(
1105
- collection: string,
1106
- where: Record<string, any>
1107
- ): Promise<T | null> {
1108
- try {
1109
- let queryBuilder = this.queryBuilder().collection(collection);
1110
-
1111
- // Add each where condition
1112
- for (const [field, value] of Object.entries(where)) {
1113
- queryBuilder = queryBuilder.whereField(field).equals(value);
1114
- }
1115
-
1116
- // Execute query and return the latest record by metadata
1117
- return await queryBuilder.selectAll().executeUnique<T>();
1118
- } catch (error) {
1119
- console.error(`findUnique error for ${collection}:`, error);
1120
- return null;
1121
- }
1122
- }
1123
-
1124
- // ===== PRISMA-LIKE CRUD OPERATIONS =====
1125
-
1126
- /**
1127
- * Find multiple documents by query (Prisma-style findMany)
1128
- *
1129
- * @param collection - Collection name to search in
1130
- * @param where - Query conditions as key-value pairs
1131
- * @param options - Query options (limit, offset, sort)
1132
- * @returns Promise resolving to array of documents
1133
- *
1134
- * @example
1135
- * ```typescript
1136
- * const users = await client.findMany('users',
1137
- * { active: true },
1138
- * { limit: 10, sort: { field: 'createdAt', order: 'desc' } }
1139
- * );
1140
- * ```
1141
- */
1142
- async findMany<T>(
1143
- collection: string,
1144
- where: Record<string, any> = {},
1145
- options: {
1146
- limit?: number;
1147
- offset?: number;
1148
- sort?: { field: string; order: 'asc' | 'desc' };
1149
- } = {}
1150
- ): Promise<T[]> {
1151
- try {
1152
- let queryBuilder = this.queryBuilder().collection(collection);
1153
-
1154
- // Add where conditions
1155
- for (const [field, value] of Object.entries(where)) {
1156
- queryBuilder = queryBuilder.whereField(field).equals(value);
1157
- }
1158
-
1159
- // Add limit
1160
- if (options.limit) {
1161
- queryBuilder = queryBuilder.limit(options.limit);
1162
- }
1163
-
1164
- // Add offset
1165
- if (options.offset) {
1166
- queryBuilder = queryBuilder.offset(options.offset);
1167
- }
1168
-
1169
- const result = await queryBuilder.selectAll().execute();
1170
-
1171
- if (!result.records) {
1172
- return [];
1173
- }
1174
-
1175
- // Apply sorting if specified (client-side for now)
1176
- let records = result.records as T[];
1177
- if (options.sort) {
1178
- records = records.sort((a: any, b: any) => {
1179
- const aVal = a[options.sort!.field];
1180
- const bVal = b[options.sort!.field];
1181
-
1182
- if (options.sort!.order === 'asc') {
1183
- return aVal > bVal ? 1 : -1;
1184
- } else {
1185
- return aVal < bVal ? 1 : -1;
1186
- }
1187
- });
1188
- }
1189
-
1190
- return records;
1191
- } catch (error) {
1192
- console.error(`findMany error for ${collection}:`, error);
1193
- return [];
1194
- }
1195
- }
1196
-
1197
- /**
1198
- * Create a new document (Prisma-style create)
1199
- *
1200
- * @param collection - Collection name
1201
- * @param data - Document data (without id, createdAt, updatedAt)
1202
- * @param paymentCallback - x402 payment callback for handling payment
1203
- * @param options - Optional settings (idGenerator)
1204
- * @returns Promise resolving to created document
1205
- *
1206
- * @example
1207
- * ```typescript
1208
- * const user = await client.createDocument('users',
1209
- * { email: 'alice@example.com', name: 'Alice' },
1210
- * async (quote) => {
1211
- * const txHash = await signAndBroadcastPayment(quote);
1212
- * return { txHash, network: quote.network, sender: userAddress, chainType: quote.chainType, paymentMethod: quote.paymentMethod };
1213
- * }
1214
- * );
1215
- * ```
1216
- */
1217
- async createDocument<T extends Record<string, any>>(
1218
- collection: string,
1219
- data: Omit<T, 'id' | 'createdAt' | 'updatedAt'>,
1220
- paymentCallback?: (quote: X402Quote) => Promise<X402PaymentResult>,
1221
- options?: {
1222
- idGenerator?: () => string;
1223
- }
1224
- ): Promise<T> {
1225
- const document: any = {
1226
- id: options?.idGenerator ? options.idGenerator() : this.generateId(),
1227
- ...data,
1228
- createdAt: new Date().toISOString(),
1229
- updatedAt: new Date().toISOString(),
1230
- };
1231
-
1232
- // Use the store method with x402 payment callback
1233
- await this.store({
1234
- collection,
1235
- data: [document],
1236
- }, paymentCallback);
1237
-
1238
- return document as T;
1239
- }
1240
-
1241
- /**
1242
- * Update an existing document (Prisma-style update)
1243
- *
1244
- * @param collection - Collection name
1245
- * @param where - Query conditions to find document
1246
- * @param data - Partial data to update
1247
- * @param paymentCallback - x402 payment callback for handling payment
1248
- * @returns Promise resolving to updated document or null if not found
1249
- *
1250
- * @example
1251
- * ```typescript
1252
- * const updated = await client.updateDocument('users',
1253
- * { email: 'alice@example.com' },
1254
- * { name: 'Alice Smith' },
1255
- * async (quote) => {
1256
- * const txHash = await signAndBroadcastPayment(quote);
1257
- * return { txHash, network: quote.network, sender: userAddress, chainType: quote.chainType, paymentMethod: quote.paymentMethod };
1258
- * }
1259
- * );
1260
- * ```
1261
- */
1262
- async updateDocument<T extends Record<string, any>>(
1263
- collection: string,
1264
- where: Record<string, any>,
1265
- data: Partial<T>,
1266
- paymentCallback?: (quote: X402Quote) => Promise<X402PaymentResult>
1267
- ): Promise<T | null> {
1268
- // Fetch current document
1269
- const current = await this.findUnique<T>(collection, where);
1270
-
1271
- if (!current) {
1272
- return null;
1273
- }
1274
-
1275
- // Create updated document
1276
- const updated: any = {
1277
- ...current,
1278
- ...data,
1279
- updatedAt: new Date().toISOString(),
1280
- };
1281
-
1282
- // Store updated version with x402 payment callback
1283
- await this.store({
1284
- collection,
1285
- data: [updated],
1286
- }, paymentCallback);
1287
-
1288
- return updated as T;
1289
- }
1290
-
1291
- /**
1292
- * Upsert a document (Prisma-style upsert - create or update)
1293
- *
1294
- * @param collection - Collection name
1295
- * @param where - Query conditions to find document
1296
- * @param create - Data for creating new document
1297
- * @param update - Data for updating existing document
1298
- * @param paymentCallback - x402 payment callback for handling payment
1299
- * @param options - Optional settings (idGenerator)
1300
- * @returns Promise resolving to created/updated document
1301
- *
1302
- * @example
1303
- * ```typescript
1304
- * const user = await client.upsertDocument('users',
1305
- * { email: 'alice@example.com' },
1306
- * { email: 'alice@example.com', name: 'Alice', active: true },
1307
- * { active: true },
1308
- * async (quote) => {
1309
- * const txHash = await signAndBroadcastPayment(quote);
1310
- * return { txHash, network: quote.network, sender: userAddress, chainType: quote.chainType, paymentMethod: quote.paymentMethod };
1311
- * }
1312
- * );
1313
- * ```
1314
- */
1315
- async upsertDocument<T extends Record<string, any>>(
1316
- collection: string,
1317
- where: Record<string, any>,
1318
- create: Omit<T, 'id' | 'createdAt' | 'updatedAt'>,
1319
- update: Partial<T>,
1320
- paymentCallback?: (quote: X402Quote) => Promise<X402PaymentResult>,
1321
- options?: {
1322
- idGenerator?: () => string;
1323
- }
1324
- ): Promise<T> {
1325
- const existing = await this.findUnique<T>(collection, where);
1326
-
1327
- if (existing) {
1328
- return (await this.updateDocument<T>(collection, where, update, paymentCallback))!;
1329
- } else {
1330
- return await this.createDocument<T>(collection, create, paymentCallback, options);
1331
- }
1332
- }
1333
-
1334
- /**
1335
- * Soft delete a document by marking it as deleted
1336
- *
1337
- * @param collection - Collection name
1338
- * @param where - Query conditions to find document
1339
- * @param paymentCallback - x402 payment callback for handling payment
1340
- * @returns Promise resolving to true if deleted, false if not found
1341
- *
1342
- * @example
1343
- * ```typescript
1344
- * const deleted = await client.deleteDocument('users',
1345
- * { email: 'alice@example.com' },
1346
- * async (quote) => {
1347
- * const txHash = await signAndBroadcastPayment(quote);
1348
- * return { txHash, network: quote.network, sender: userAddress, chainType: quote.chainType, paymentMethod: quote.paymentMethod };
1349
- * }
1350
- * );
1351
- * ```
1352
- */
1353
- async deleteDocument<T extends Record<string, any>>(
1354
- collection: string,
1355
- where: Record<string, any>,
1356
- paymentCallback?: (quote: X402Quote) => Promise<X402PaymentResult>
1357
- ): Promise<boolean> {
1358
- const existing = await this.findUnique<T>(collection, where);
1359
-
1360
- if (!existing) {
1361
- return false;
1362
- }
1363
-
1364
- // Soft delete by marking
1365
- const deleted: any = {
1366
- ...existing,
1367
- deleted: true,
1368
- updatedAt: new Date().toISOString(),
1369
- };
1370
-
1371
- // Store deleted version with x402 payment callback
1372
- await this.store({
1373
- collection,
1374
- data: [deleted],
1375
- }, paymentCallback);
1376
-
1377
- return true;
1378
- }
1379
-
1380
- /**
1381
- * Count documents matching criteria
1382
- * Uses server-side aggregation via QueryBuilder for efficiency.
1383
- *
1384
- * @param collection - Collection name
1385
- * @param where - Query conditions
1386
- * @returns Promise resolving to count
1387
- *
1388
- * @example
1389
- * ```typescript
1390
- * const activeUsers = await client.countDocuments('users', { active: true });
1391
- * console.log(`Active users: ${activeUsers}`);
1392
- * ```
1393
- */
1394
- async countDocuments(collection: string, where: Record<string, any> = {}): Promise<number> {
1395
- try {
1396
- let queryBuilder = this.queryBuilder().collection(collection);
1397
-
1398
- // Add where conditions
1399
- for (const [field, value] of Object.entries(where)) {
1400
- queryBuilder = queryBuilder.whereField(field).equals(value);
1401
- }
1402
-
1403
- return await queryBuilder.count();
1404
- } catch (error) {
1405
- console.error(`countDocuments error for ${collection}:`, error);
1406
- return 0;
1407
- }
1408
- }
1409
-
1410
- /**
1411
- * Generate a unique ID for documents (simple base62 implementation)
1412
- * Override this if you want to use a different ID generation strategy
1413
- *
1414
- * @returns Unique ID string
1415
- */
1416
- generateId(): string {
1417
- const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
1418
- let id = '';
1419
- for (let i = 0; i < 24; i++) {
1420
- id += chars[Math.floor(Math.random() * chars.length)];
1421
- }
1422
- return id;
1423
- }
1424
-
1425
1016
  /**
1426
1017
  * Get task status by ticket ID
1427
1018
  * @param ticketId - The ticket ID returned from async store operation
@@ -1574,13 +1165,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1574
1165
  return await this.http.post<UploadBlobResponse>(
1575
1166
  `/api/apps/${appId}/blobs/${request.collection}`,
1576
1167
  formData,
1577
- {
1578
- headers: {
1579
- 'Content-Type': 'multipart/form-data',
1580
- 'X-App-Key': this.config.appKey,
1581
- ...headers
1582
- }
1583
- }
1168
+ { headers: { 'Content-Type': 'multipart/form-data', ...headers } }
1584
1169
  );
1585
1170
  };
1586
1171
 
@@ -1665,12 +1250,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1665
1250
  // Retrieve blob via GET endpoint
1666
1251
  const response = await this.http.get(
1667
1252
  `/api/apps/${appId}/blobs/${request.collection}/${request.blob_id}`,
1668
- {
1669
- responseType: 'arraybuffer',
1670
- headers: {
1671
- 'X-App-Key': this.config.appKey
1672
- }
1673
- }
1253
+ { responseType: 'arraybuffer' }
1674
1254
  );
1675
1255
 
1676
1256
  // Return as Blob in browser, Buffer in Node.js
@@ -1688,49 +1268,6 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1688
1268
  }
1689
1269
  }
1690
1270
 
1691
- /**
1692
- * Query blob metadata using the standard query interface
1693
- *
1694
- * Returns metadata about blobs without downloading the actual binary data.
1695
- * Useful for listing, filtering, and searching blobs by their metadata.
1696
- *
1697
- * @param collection - Blob collection name
1698
- * @param where - Query conditions for filtering blobs
1699
- * @returns Promise resolving to array of blob metadata records
1700
- *
1701
- * @example
1702
- * ```typescript
1703
- * // Query all blobs by user
1704
- * const userBlobs = await client.queryBlobMetadata('avatars', {
1705
- * user_address: 'celestia1abc...'
1706
- * });
1707
- *
1708
- * // Query blobs by content type
1709
- * const images = await client.queryBlobMetadata('uploads', {
1710
- * content_type: { $regex: 'image/' }
1711
- * });
1712
- *
1713
- * // Query recent blobs
1714
- * const recentBlobs = await client.queryBlobMetadata('files', {
1715
- * uploaded_at: { $gte: '2024-01-01T00:00:00Z' }
1716
- * });
1717
- *
1718
- * // Access blob metadata
1719
- * for (const blob of userBlobs) {
1720
- * console.log('Blob ID:', blob.blob_id);
1721
- * console.log('Size:', blob.size_bytes);
1722
- * console.log('Type:', blob.content_type);
1723
- * console.log('Custom fields:', blob);
1724
- * }
1725
- * ```
1726
- */
1727
- async queryBlobMetadata(
1728
- collection: string,
1729
- where: Record<string, any> = {}
1730
- ): Promise<BlobMetadata[]> {
1731
- return await this.findMany<BlobMetadata>(collection, where);
1732
- }
1733
-
1734
1271
  /**
1735
1272
  * Get default index type based on field type
1736
1273
  */
@@ -1749,6 +1286,17 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1749
1286
  }
1750
1287
  }
1751
1288
 
1289
+ private buildStoreResult(raw: any): StoreResponse {
1290
+ return {
1291
+ id: raw.id,
1292
+ block_height: raw.celestia_height || 0,
1293
+ transaction_hash: raw.blob_id || '',
1294
+ celestia_height: raw.celestia_height || 0,
1295
+ namespace: raw.namespace || '',
1296
+ confirmed: raw.celestia_height > 0
1297
+ };
1298
+ }
1299
+
1752
1300
  private validateStoreRequest(request: StoreRequest): void {
1753
1301
  // Validate that either root or collection is provided
1754
1302
  if (!request.root && !request.collection) {
@@ -1785,67 +1333,17 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1785
1333
  }
1786
1334
  }
1787
1335
 
1788
- private handleHttpError(error: AxiosError): OnChainDBError {
1789
- console.error(error);
1790
- if (error.response) {
1791
- const statusCode = error.response.status;
1792
- const message = (error.response.data as any)?.error || error.message;
1793
-
1794
-
1795
- if (statusCode >= 400 && statusCode < 500) {
1796
- return new ValidationError(message, error.response.data);
1797
- }
1798
-
1799
- return new OnChainDBError(
1800
- message,
1801
- 'HTTP_ERROR',
1802
- statusCode,
1803
- error.response.data
1804
- );
1805
- }
1806
-
1807
- if (error.request) {
1808
- return new OnChainDBError(
1809
- 'Network error - could not reach OnChainDB service',
1810
- 'NETWORK_ERROR'
1811
- );
1812
- }
1813
-
1814
- return new OnChainDBError(error.message, 'UNKNOWN_ERROR');
1815
- }
1816
-
1817
1336
  private sleep(ms: number): Promise<void> {
1818
1337
  return new Promise(resolve => setTimeout(resolve, ms));
1819
1338
  }
1820
1339
 
1821
- /**
1822
- * Build root string from collection name using configured appId
1823
- *
1824
- * @param collection - Collection name
1825
- * @returns Full root string in format "appId::collection" or just collection for system ops
1826
- */
1827
- private buildRoot(collection: string): string {
1828
- if (!this.config.appId) {
1829
- return collection; // System operation or no appId configured
1830
- }
1831
- return `${this.config.appId}::${collection}`;
1832
- }
1833
-
1834
- /**
1835
- * Resolve root parameter from request, building it if needed
1836
- *
1837
- * @param request - Store or Query request
1838
- * @returns Resolved root string
1839
- */
1840
1340
  private resolveRoot(request: { root?: string; collection?: string }): string {
1841
1341
  if (request.root) {
1842
- return request.root; // Explicit root takes precedence
1342
+ return request.root;
1843
1343
  }
1844
-
1845
1344
  if (request.collection) {
1846
- return this.buildRoot(request.collection); // Build from collection + appId
1345
+ return this.config.appId ? `${this.config.appId}::${request.collection}` : request.collection;
1847
1346
  }
1848
-
1849
1347
  throw new ValidationError('Either root or collection must be provided');
1850
1348
  }
1851
1349