@onchaindb/sdk 0.4.5 → 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 +198 -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 +242 -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) {
@@ -497,98 +461,6 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
497
461
  }
498
462
  }
499
463
 
500
- /**
501
- * Store data and return a promise that resolves when transaction is confirmed
502
- *
503
- * @param request - Store request
504
- * @param paymentOptions - Payment configuration for broker fees
505
- * @returns Promise resolving when transaction is confirmed on blockchain
506
- */
507
- async storeAndConfirm(
508
- request: StoreRequest
509
- ): Promise<StoreResponse> {
510
- return this.store(request, undefined, true);
511
- }
512
-
513
- /**
514
- * Wait for transaction confirmation
515
- *
516
- * @param transactionHash - Transaction hash to monitor
517
- * @param maxWaitTime - Maximum wait time in milliseconds (default: 5 minutes)
518
- * @returns Promise resolving to confirmed transaction
519
- */
520
- async waitForConfirmation(transactionHash: string, maxWaitTime: number = 300000): Promise<StoreResponse> {
521
- const startTime = Date.now();
522
- const pollInterval = 3000; // Poll every 3 seconds (same as server)
523
-
524
- console.log(`🔄 Waiting for transaction ${transactionHash} confirmation...`);
525
-
526
- while (Date.now() - startTime < maxWaitTime) {
527
- try {
528
- // Query Celestia RPC directly to check transaction status
529
- const rpcUrl = 'https://celestia-mocha-rpc.publicnode.com:443'; // Default testnet RPC
530
- const txUrl = `${rpcUrl}/tx?hash=0x${transactionHash}`;
531
-
532
- console.log(`🔍 Checking transaction status: attempt ${Math.floor((Date.now() - startTime) / pollInterval) + 1}`);
533
-
534
- const response = await axios.get(txUrl);
535
-
536
- if (response.data?.result && response.data.result !== null) {
537
- const txResult = response.data.result;
538
- const height = parseInt(txResult.height);
539
-
540
- if (height > 0) {
541
- console.log(`✅ Transaction ${transactionHash} confirmed at height ${height}`);
542
-
543
- const confirmedTx: StoreResponse = {
544
- id: transactionHash, // Use transaction hash as ID for confirmation
545
- namespace: '', // Will be filled by actual usage
546
- block_height: height,
547
- transaction_hash: transactionHash,
548
- celestia_height: height,
549
- confirmed: true
550
- };
551
-
552
- this.emit('transaction:confirmed', {
553
- id: transactionHash,
554
- status: 'confirmed',
555
- block_height: height,
556
- transaction_hash: transactionHash
557
- });
558
- return confirmedTx;
559
- }
560
- }
561
-
562
- // Still pending, wait and retry
563
- console.log(`⏳ Transaction still pending, waiting ${pollInterval}ms...`);
564
- this.emit('transaction:pending', {
565
- id: transactionHash,
566
- status: 'pending',
567
- block_height: 0,
568
- transaction_hash: transactionHash
569
- });
570
- await this.sleep(pollInterval);
571
-
572
- } catch (error) {
573
- // For 404 or other errors, the transaction might not be confirmed yet
574
- if (Date.now() - startTime >= maxWaitTime) {
575
- throw new TransactionError(
576
- `Transaction confirmation timeout after ${maxWaitTime}ms`,
577
- transactionHash
578
- );
579
- }
580
-
581
- // Wait and retry for temporary errors
582
- console.log(`⚠️ Error checking transaction (will retry): ${error}`);
583
- await this.sleep(pollInterval);
584
- }
585
- }
586
-
587
- throw new TransactionError(
588
- `Transaction confirmation timeout after ${maxWaitTime}ms`,
589
- transactionHash
590
- );
591
- }
592
464
 
593
465
  /**
594
466
  * Create an index on a collection field
@@ -752,7 +624,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
752
624
  * console.log('Removed:', result.removed); // [{ field: 'username', type: 'string' }]
753
625
  * ```
754
626
  */
755
- async syncCollection(schema: SimpleCollectionSchema): Promise<SyncCollectionResult> {
627
+ async syncCollection(schema: SimpleCollectionSchemaWithSharding): Promise<SyncCollectionResult> {
756
628
  const appId = this.config.appId;
757
629
  if (!appId) {
758
630
  throw new ValidationError('appId must be configured to sync collections');
@@ -767,22 +639,6 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
767
639
  errors: []
768
640
  };
769
641
 
770
- // Get existing indexes for this collection
771
- let existingIndexes: Array<{ field_name: string; index_type: string; name: string }> = [];
772
- try {
773
- const response = await this.http.get(`/api/apps/${appId}/collections/${schema.name}/indexes`);
774
- existingIndexes = response.data?.indexes || response.data || [];
775
- } catch (error) {
776
- // Collection might not exist yet, that's okay
777
- existingIndexes = [];
778
- }
779
-
780
- // Build map of existing indexes by field name
781
- const existingByField = new Map<string, { type: string; name: string }>();
782
- for (const idx of existingIndexes) {
783
- existingByField.set(idx.field_name, {type: idx.index_type, name: idx.name});
784
- }
785
-
786
642
  // Merge base fields if enabled (default: true)
787
643
  const allFields: Record<string, SimpleFieldDefinition> = {};
788
644
  if (schema.useBaseFields !== false) {
@@ -798,9 +654,9 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
798
654
  }
799
655
  }
800
656
 
801
- // Find indexes to create (in desired but not existing)
657
+ // Create all desired indexes (broker handles upsert)
802
658
  for (const fieldName of desiredIndexedFields) {
803
- if (!existingByField.has(fieldName)) {
659
+ {
804
660
  const fieldDef = allFields[fieldName];
805
661
  const indexType = fieldDef.indexType || this.getDefaultIndexType(fieldDef.type);
806
662
 
@@ -840,36 +696,179 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
840
696
  }
841
697
  }
842
698
 
843
- // Find indexes to remove (existing but not in desired)
844
- for (const [fieldName, existing] of existingByField) {
845
- if (!desiredIndexedFields.has(fieldName)) {
846
- try {
847
- // Index ID format: {collection}_{field_name}_index
848
- const indexId = `${schema.name}_${fieldName}_index`;
849
- await this.http.delete(`/api/apps/${appId}/indexes/${indexId}`);
850
- result.removed.push({
851
- field: fieldName,
852
- type: existing.type
853
- });
854
- } catch (error) {
855
- const errorMsg = error instanceof Error ? error.message : String(error);
856
- result.errors!.push(`Failed to remove index on ${fieldName}: ${errorMsg}`);
857
- result.success = false;
858
- }
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;
859
707
  }
860
708
  }
861
709
 
862
- // Track unchanged indexes
863
- for (const [fieldName, existing] of existingByField) {
864
- if (desiredIndexedFields.has(fieldName)) {
865
- result.unchanged.push({
866
- field: fieldName,
867
- type: existing.type
868
- });
869
- }
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');
870
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
+ }
871
728
 
872
- 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
+ }
873
872
  }
874
873
 
875
874
  /**
@@ -878,14 +877,14 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
878
877
  * @param collection - Collection name
879
878
  * @returns Collection information
880
879
  */
881
- async getCollectionInfo(collection: string): Promise<{ indexes: string[]; recordCount?: number }> {
880
+ async getCollectionInfo(collectionId: string): Promise<CollectionResponse> {
882
881
  const appId = this.config.appId;
883
882
  if (!appId) {
884
883
  throw new ValidationError('appId must be configured');
885
884
  }
886
885
 
887
886
  try {
888
- const response = await this.http.get(`/api/apps/${appId}/collections/${collection}`);
887
+ const response = await this.http.get(`/api/apps/${appId}/collections/${collectionId}`);
889
888
  return response.data;
890
889
  } catch (error) {
891
890
  throw error instanceof OnChainDBError ? error :
@@ -986,62 +985,6 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
986
985
  }
987
986
  }
988
987
 
989
- /**
990
- * Execute a simple query with basic filtering
991
- *
992
- * @param request - Simple query request
993
- * @returns Query response with records
994
- *
995
- * @example
996
- * ```typescript
997
- * const tweets = await db.query({
998
- * collection: 'tweets',
999
- * filters: { author: 'alice' },
1000
- * limit: 10
1001
- * });
1002
- * ```
1003
- */
1004
- async query(request: {
1005
- collection: string;
1006
- filters?: Record<string, any>;
1007
- limit?: number;
1008
- offset?: number;
1009
- sort?: string[];
1010
- }): Promise<QueryResponse> {
1011
- try {
1012
- const queryBuilder = this.queryBuilder();
1013
-
1014
- // Set collection using root building
1015
- const root = this.resolveRoot(request);
1016
-
1017
- // Add filters if provided
1018
- if (request.filters) {
1019
- queryBuilder.find(builder => {
1020
- const conditions = Object.entries(request.filters!).map(([field, value]) =>
1021
- LogicalOperator.Condition(builder.field(field).equals(value))
1022
- );
1023
- return conditions.length === 1 ? conditions[0] : LogicalOperator.And(conditions);
1024
- });
1025
- }
1026
-
1027
- // Set limit and offset
1028
- if (request.limit) {
1029
- queryBuilder.limit(request.limit);
1030
- }
1031
-
1032
- if (request.offset) {
1033
- queryBuilder.offset(request.offset);
1034
- }
1035
-
1036
- // Execute the query
1037
- return await queryBuilder.execute();
1038
-
1039
- } catch (error) {
1040
- throw error instanceof OnChainDBError ? error :
1041
- new OnChainDBError('Failed to execute query', 'QUERY_ERROR');
1042
- }
1043
- }
1044
-
1045
988
  /**
1046
989
  * Create a fluent query builder instance
1047
990
  *
@@ -1051,10 +994,10 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1051
994
  * ```typescript
1052
995
  * const results = await db.queryBuilder()
1053
996
  * .find(builder =>
1054
- * LogicalOperator.And([
1055
- * LogicalOperator.Condition(builder.field('status').equals('published')),
1056
- * LogicalOperator.Condition(builder.field('author').equals('alice'))
1057
- * ])
997
+ * builder.and(
998
+ * builder.field('status').equals('published'),
999
+ * builder.field('author').equals('alice')
1000
+ * )
1058
1001
  * )
1059
1002
  * .select(selection =>
1060
1003
  * selection.field('title').field('content')
@@ -1070,359 +1013,6 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1070
1013
  return new QueryBuilder(httpClient, this.config.endpoint, this.config.appId);
1071
1014
  }
1072
1015
 
1073
- /**
1074
- * Create a batch operations instance for this client
1075
- *
1076
- * @returns BatchOperations instance
1077
- *
1078
- * @example
1079
- * ```typescript
1080
- * const batch = db.batch();
1081
- * const results = await batch.store([...], { concurrency: 5 });
1082
- * ```
1083
- */
1084
- batch() {
1085
- const {BatchOperations} = require('./batch');
1086
- return new BatchOperations(this);
1087
- }
1088
-
1089
- /**
1090
- * Find a single document by query (Prisma-style findUnique)
1091
- * Returns the latest record by metadata (updatedAt or createdAt) if multiple matches.
1092
- *
1093
- * @param collection - Collection name to search in
1094
- * @param where - Query conditions as key-value pairs
1095
- * @returns Promise resolving to document or null if not found
1096
- *
1097
- * @example
1098
- * ```typescript
1099
- * const user = await client.findUnique('users', { email: 'alice@example.com' });
1100
- * if (user) {
1101
- * console.log('Found user:', user);
1102
- * }
1103
- * ```
1104
- */
1105
- async findUnique<T extends Record<string, any>>(
1106
- collection: string,
1107
- where: Record<string, any>
1108
- ): Promise<T | null> {
1109
- try {
1110
- let queryBuilder = this.queryBuilder().collection(collection);
1111
-
1112
- // Add each where condition
1113
- for (const [field, value] of Object.entries(where)) {
1114
- queryBuilder = queryBuilder.whereField(field).equals(value);
1115
- }
1116
-
1117
- // Execute query and return the latest record by metadata
1118
- return await queryBuilder.selectAll().executeUnique<T>();
1119
- } catch (error) {
1120
- console.error(`findUnique error for ${collection}:`, error);
1121
- return null;
1122
- }
1123
- }
1124
-
1125
- // ===== PRISMA-LIKE CRUD OPERATIONS =====
1126
-
1127
- /**
1128
- * Find multiple documents by query (Prisma-style findMany)
1129
- *
1130
- * @param collection - Collection name to search in
1131
- * @param where - Query conditions as key-value pairs
1132
- * @param options - Query options (limit, offset, sort)
1133
- * @returns Promise resolving to array of documents
1134
- *
1135
- * @example
1136
- * ```typescript
1137
- * const users = await client.findMany('users',
1138
- * { active: true },
1139
- * { limit: 10, sort: { field: 'createdAt', order: 'desc' } }
1140
- * );
1141
- * ```
1142
- */
1143
- async findMany<T>(
1144
- collection: string,
1145
- where: Record<string, any> = {},
1146
- options: {
1147
- limit?: number;
1148
- offset?: number;
1149
- sort?: { field: string; order: 'asc' | 'desc' };
1150
- } = {}
1151
- ): Promise<T[]> {
1152
- try {
1153
- let queryBuilder = this.queryBuilder().collection(collection);
1154
-
1155
- // Add where conditions
1156
- for (const [field, value] of Object.entries(where)) {
1157
- queryBuilder = queryBuilder.whereField(field).equals(value);
1158
- }
1159
-
1160
- // Add limit
1161
- if (options.limit) {
1162
- queryBuilder = queryBuilder.limit(options.limit);
1163
- }
1164
-
1165
- // Add offset
1166
- if (options.offset) {
1167
- queryBuilder = queryBuilder.offset(options.offset);
1168
- }
1169
-
1170
- const result = await queryBuilder.selectAll().execute();
1171
-
1172
- if (!result.records) {
1173
- return [];
1174
- }
1175
-
1176
- // Apply sorting if specified (client-side for now)
1177
- let records = result.records as T[];
1178
- if (options.sort) {
1179
- records = records.sort((a: any, b: any) => {
1180
- const aVal = a[options.sort!.field];
1181
- const bVal = b[options.sort!.field];
1182
-
1183
- if (options.sort!.order === 'asc') {
1184
- return aVal > bVal ? 1 : -1;
1185
- } else {
1186
- return aVal < bVal ? 1 : -1;
1187
- }
1188
- });
1189
- }
1190
-
1191
- return records;
1192
- } catch (error) {
1193
- console.error(`findMany error for ${collection}:`, error);
1194
- return [];
1195
- }
1196
- }
1197
-
1198
- /**
1199
- * Create a new document (Prisma-style create)
1200
- *
1201
- * @param collection - Collection name
1202
- * @param data - Document data (without id, createdAt, updatedAt)
1203
- * @param paymentCallback - x402 payment callback for handling payment
1204
- * @param options - Optional settings (idGenerator)
1205
- * @returns Promise resolving to created document
1206
- *
1207
- * @example
1208
- * ```typescript
1209
- * const user = await client.createDocument('users',
1210
- * { email: 'alice@example.com', name: 'Alice' },
1211
- * async (quote) => {
1212
- * const txHash = await signAndBroadcastPayment(quote);
1213
- * return { txHash, network: quote.network, sender: userAddress, chainType: quote.chainType, paymentMethod: quote.paymentMethod };
1214
- * }
1215
- * );
1216
- * ```
1217
- */
1218
- async createDocument<T extends Record<string, any>>(
1219
- collection: string,
1220
- data: Omit<T, 'id' | 'createdAt' | 'updatedAt'>,
1221
- paymentCallback?: (quote: X402Quote) => Promise<X402PaymentResult>,
1222
- options?: {
1223
- idGenerator?: () => string;
1224
- }
1225
- ): Promise<T> {
1226
- const document: any = {
1227
- id: options?.idGenerator ? options.idGenerator() : this.generateId(),
1228
- ...data,
1229
- createdAt: new Date().toISOString(),
1230
- updatedAt: new Date().toISOString(),
1231
- };
1232
-
1233
- // Use the store method with x402 payment callback
1234
- await this.store({
1235
- collection,
1236
- data: [document],
1237
- }, paymentCallback);
1238
-
1239
- return document as T;
1240
- }
1241
-
1242
- /**
1243
- * Update an existing document (Prisma-style update)
1244
- *
1245
- * @param collection - Collection name
1246
- * @param where - Query conditions to find document
1247
- * @param data - Partial data to update
1248
- * @param paymentCallback - x402 payment callback for handling payment
1249
- * @returns Promise resolving to updated document or null if not found
1250
- *
1251
- * @example
1252
- * ```typescript
1253
- * const updated = await client.updateDocument('users',
1254
- * { email: 'alice@example.com' },
1255
- * { name: 'Alice Smith' },
1256
- * async (quote) => {
1257
- * const txHash = await signAndBroadcastPayment(quote);
1258
- * return { txHash, network: quote.network, sender: userAddress, chainType: quote.chainType, paymentMethod: quote.paymentMethod };
1259
- * }
1260
- * );
1261
- * ```
1262
- */
1263
- async updateDocument<T extends Record<string, any>>(
1264
- collection: string,
1265
- where: Record<string, any>,
1266
- data: Partial<T>,
1267
- paymentCallback?: (quote: X402Quote) => Promise<X402PaymentResult>
1268
- ): Promise<T | null> {
1269
- // Fetch current document
1270
- const current = await this.findUnique<T>(collection, where);
1271
-
1272
- if (!current) {
1273
- return null;
1274
- }
1275
-
1276
- // Create updated document
1277
- const updated: any = {
1278
- ...current,
1279
- ...data,
1280
- updatedAt: new Date().toISOString(),
1281
- };
1282
-
1283
- // Store updated version with x402 payment callback
1284
- await this.store({
1285
- collection,
1286
- data: [updated],
1287
- }, paymentCallback);
1288
-
1289
- return updated as T;
1290
- }
1291
-
1292
- /**
1293
- * Upsert a document (Prisma-style upsert - create or update)
1294
- *
1295
- * @param collection - Collection name
1296
- * @param where - Query conditions to find document
1297
- * @param create - Data for creating new document
1298
- * @param update - Data for updating existing document
1299
- * @param paymentCallback - x402 payment callback for handling payment
1300
- * @param options - Optional settings (idGenerator)
1301
- * @returns Promise resolving to created/updated document
1302
- *
1303
- * @example
1304
- * ```typescript
1305
- * const user = await client.upsertDocument('users',
1306
- * { email: 'alice@example.com' },
1307
- * { email: 'alice@example.com', name: 'Alice', active: true },
1308
- * { active: true },
1309
- * async (quote) => {
1310
- * const txHash = await signAndBroadcastPayment(quote);
1311
- * return { txHash, network: quote.network, sender: userAddress, chainType: quote.chainType, paymentMethod: quote.paymentMethod };
1312
- * }
1313
- * );
1314
- * ```
1315
- */
1316
- async upsertDocument<T extends Record<string, any>>(
1317
- collection: string,
1318
- where: Record<string, any>,
1319
- create: Omit<T, 'id' | 'createdAt' | 'updatedAt'>,
1320
- update: Partial<T>,
1321
- paymentCallback?: (quote: X402Quote) => Promise<X402PaymentResult>,
1322
- options?: {
1323
- idGenerator?: () => string;
1324
- }
1325
- ): Promise<T> {
1326
- const existing = await this.findUnique<T>(collection, where);
1327
-
1328
- if (existing) {
1329
- return (await this.updateDocument<T>(collection, where, update, paymentCallback))!;
1330
- } else {
1331
- return await this.createDocument<T>(collection, create, paymentCallback, options);
1332
- }
1333
- }
1334
-
1335
- /**
1336
- * Soft delete a document by marking it as deleted
1337
- *
1338
- * @param collection - Collection name
1339
- * @param where - Query conditions to find document
1340
- * @param paymentCallback - x402 payment callback for handling payment
1341
- * @returns Promise resolving to true if deleted, false if not found
1342
- *
1343
- * @example
1344
- * ```typescript
1345
- * const deleted = await client.deleteDocument('users',
1346
- * { email: 'alice@example.com' },
1347
- * async (quote) => {
1348
- * const txHash = await signAndBroadcastPayment(quote);
1349
- * return { txHash, network: quote.network, sender: userAddress, chainType: quote.chainType, paymentMethod: quote.paymentMethod };
1350
- * }
1351
- * );
1352
- * ```
1353
- */
1354
- async deleteDocument<T extends Record<string, any>>(
1355
- collection: string,
1356
- where: Record<string, any>,
1357
- paymentCallback?: (quote: X402Quote) => Promise<X402PaymentResult>
1358
- ): Promise<boolean> {
1359
- const existing = await this.findUnique<T>(collection, where);
1360
-
1361
- if (!existing) {
1362
- return false;
1363
- }
1364
-
1365
- // Soft delete by marking
1366
- const deleted: any = {
1367
- ...existing,
1368
- deleted: true,
1369
- updatedAt: new Date().toISOString(),
1370
- };
1371
-
1372
- // Store deleted version with x402 payment callback
1373
- await this.store({
1374
- collection,
1375
- data: [deleted],
1376
- }, paymentCallback);
1377
-
1378
- return true;
1379
- }
1380
-
1381
- /**
1382
- * Count documents matching criteria
1383
- * Uses server-side aggregation via QueryBuilder for efficiency.
1384
- *
1385
- * @param collection - Collection name
1386
- * @param where - Query conditions
1387
- * @returns Promise resolving to count
1388
- *
1389
- * @example
1390
- * ```typescript
1391
- * const activeUsers = await client.countDocuments('users', { active: true });
1392
- * console.log(`Active users: ${activeUsers}`);
1393
- * ```
1394
- */
1395
- async countDocuments(collection: string, where: Record<string, any> = {}): Promise<number> {
1396
- try {
1397
- let queryBuilder = this.queryBuilder().collection(collection);
1398
-
1399
- // Add where conditions
1400
- for (const [field, value] of Object.entries(where)) {
1401
- queryBuilder = queryBuilder.whereField(field).equals(value);
1402
- }
1403
-
1404
- return await queryBuilder.count();
1405
- } catch (error) {
1406
- console.error(`countDocuments error for ${collection}:`, error);
1407
- return 0;
1408
- }
1409
- }
1410
-
1411
- /**
1412
- * Generate a unique ID for documents (simple base62 implementation)
1413
- * Override this if you want to use a different ID generation strategy
1414
- *
1415
- * @returns Unique ID string
1416
- */
1417
- generateId(): string {
1418
- const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
1419
- let id = '';
1420
- for (let i = 0; i < 24; i++) {
1421
- id += chars[Math.floor(Math.random() * chars.length)];
1422
- }
1423
- return id;
1424
- }
1425
-
1426
1016
  /**
1427
1017
  * Get task status by ticket ID
1428
1018
  * @param ticketId - The ticket ID returned from async store operation
@@ -1575,13 +1165,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1575
1165
  return await this.http.post<UploadBlobResponse>(
1576
1166
  `/api/apps/${appId}/blobs/${request.collection}`,
1577
1167
  formData,
1578
- {
1579
- headers: {
1580
- 'Content-Type': 'multipart/form-data',
1581
- 'X-App-Key': this.config.appKey,
1582
- ...headers
1583
- }
1584
- }
1168
+ { headers: { 'Content-Type': 'multipart/form-data', ...headers } }
1585
1169
  );
1586
1170
  };
1587
1171
 
@@ -1666,12 +1250,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1666
1250
  // Retrieve blob via GET endpoint
1667
1251
  const response = await this.http.get(
1668
1252
  `/api/apps/${appId}/blobs/${request.collection}/${request.blob_id}`,
1669
- {
1670
- responseType: 'arraybuffer',
1671
- headers: {
1672
- 'X-App-Key': this.config.appKey
1673
- }
1674
- }
1253
+ { responseType: 'arraybuffer' }
1675
1254
  );
1676
1255
 
1677
1256
  // Return as Blob in browser, Buffer in Node.js
@@ -1689,49 +1268,6 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1689
1268
  }
1690
1269
  }
1691
1270
 
1692
- /**
1693
- * Query blob metadata using the standard query interface
1694
- *
1695
- * Returns metadata about blobs without downloading the actual binary data.
1696
- * Useful for listing, filtering, and searching blobs by their metadata.
1697
- *
1698
- * @param collection - Blob collection name
1699
- * @param where - Query conditions for filtering blobs
1700
- * @returns Promise resolving to array of blob metadata records
1701
- *
1702
- * @example
1703
- * ```typescript
1704
- * // Query all blobs by user
1705
- * const userBlobs = await client.queryBlobMetadata('avatars', {
1706
- * user_address: 'celestia1abc...'
1707
- * });
1708
- *
1709
- * // Query blobs by content type
1710
- * const images = await client.queryBlobMetadata('uploads', {
1711
- * content_type: { $regex: 'image/' }
1712
- * });
1713
- *
1714
- * // Query recent blobs
1715
- * const recentBlobs = await client.queryBlobMetadata('files', {
1716
- * uploaded_at: { $gte: '2024-01-01T00:00:00Z' }
1717
- * });
1718
- *
1719
- * // Access blob metadata
1720
- * for (const blob of userBlobs) {
1721
- * console.log('Blob ID:', blob.blob_id);
1722
- * console.log('Size:', blob.size_bytes);
1723
- * console.log('Type:', blob.content_type);
1724
- * console.log('Custom fields:', blob);
1725
- * }
1726
- * ```
1727
- */
1728
- async queryBlobMetadata(
1729
- collection: string,
1730
- where: Record<string, any> = {}
1731
- ): Promise<BlobMetadata[]> {
1732
- return await this.findMany<BlobMetadata>(collection, where);
1733
- }
1734
-
1735
1271
  /**
1736
1272
  * Get default index type based on field type
1737
1273
  */
@@ -1750,6 +1286,17 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1750
1286
  }
1751
1287
  }
1752
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
+
1753
1300
  private validateStoreRequest(request: StoreRequest): void {
1754
1301
  // Validate that either root or collection is provided
1755
1302
  if (!request.root && !request.collection) {
@@ -1786,67 +1333,17 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1786
1333
  }
1787
1334
  }
1788
1335
 
1789
- private handleHttpError(error: AxiosError): OnChainDBError {
1790
- console.error(error);
1791
- if (error.response) {
1792
- const statusCode = error.response.status;
1793
- const message = (error.response.data as any)?.error || error.message;
1794
-
1795
-
1796
- if (statusCode >= 400 && statusCode < 500) {
1797
- return new ValidationError(message, error.response.data);
1798
- }
1799
-
1800
- return new OnChainDBError(
1801
- message,
1802
- 'HTTP_ERROR',
1803
- statusCode,
1804
- error.response.data
1805
- );
1806
- }
1807
-
1808
- if (error.request) {
1809
- return new OnChainDBError(
1810
- 'Network error - could not reach OnChainDB service',
1811
- 'NETWORK_ERROR'
1812
- );
1813
- }
1814
-
1815
- return new OnChainDBError(error.message, 'UNKNOWN_ERROR');
1816
- }
1817
-
1818
1336
  private sleep(ms: number): Promise<void> {
1819
1337
  return new Promise(resolve => setTimeout(resolve, ms));
1820
1338
  }
1821
1339
 
1822
- /**
1823
- * Build root string from collection name using configured appId
1824
- *
1825
- * @param collection - Collection name
1826
- * @returns Full root string in format "appId::collection" or just collection for system ops
1827
- */
1828
- private buildRoot(collection: string): string {
1829
- if (!this.config.appId) {
1830
- return collection; // System operation or no appId configured
1831
- }
1832
- return `${this.config.appId}::${collection}`;
1833
- }
1834
-
1835
- /**
1836
- * Resolve root parameter from request, building it if needed
1837
- *
1838
- * @param request - Store or Query request
1839
- * @returns Resolved root string
1840
- */
1841
1340
  private resolveRoot(request: { root?: string; collection?: string }): string {
1842
1341
  if (request.root) {
1843
- return request.root; // Explicit root takes precedence
1342
+ return request.root;
1844
1343
  }
1845
-
1846
1344
  if (request.collection) {
1847
- return this.buildRoot(request.collection); // Build from collection + appId
1345
+ return this.config.appId ? `${this.config.appId}::${request.collection}` : request.collection;
1848
1346
  }
1849
-
1850
1347
  throw new ValidationError('Either root or collection must be provided');
1851
1348
  }
1852
1349