@onchaindb/sdk 0.4.5 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) 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 +31 -46
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +222 -357
  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 +10 -13
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +4 -18
  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/{src/query-sdk/dist/OnChainDB.d.ts → dist/query-sdk/OnDB.d.ts} +10 -2
  28. package/dist/query-sdk/OnDB.d.ts.map +1 -0
  29. package/{src/query-sdk/dist/OnChainDB.js → dist/query-sdk/OnDB.js} +86 -18
  30. package/dist/query-sdk/OnDB.js.map +1 -0
  31. package/dist/query-sdk/QueryBuilder.d.ts +4 -2
  32. package/dist/query-sdk/QueryBuilder.d.ts.map +1 -1
  33. package/dist/query-sdk/QueryBuilder.js +47 -169
  34. package/dist/query-sdk/QueryBuilder.js.map +1 -1
  35. package/dist/query-sdk/QueryResult.d.ts +0 -38
  36. package/dist/query-sdk/QueryResult.d.ts.map +1 -1
  37. package/dist/query-sdk/QueryResult.js +1 -227
  38. package/dist/query-sdk/QueryResult.js.map +1 -1
  39. package/dist/query-sdk/index.d.ts +2 -2
  40. package/dist/query-sdk/index.d.ts.map +1 -1
  41. package/dist/query-sdk/index.js +3 -3
  42. package/dist/query-sdk/index.js.map +1 -1
  43. package/dist/query-sdk/operators.d.ts +32 -28
  44. package/dist/query-sdk/operators.d.ts.map +1 -1
  45. package/dist/query-sdk/operators.js +45 -155
  46. package/dist/query-sdk/operators.js.map +1 -1
  47. package/dist/types.d.ts +159 -36
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/types.js +8 -8
  50. package/dist/types.js.map +1 -1
  51. package/dist/x402/types.d.ts +1 -1
  52. package/dist/x402/types.d.ts.map +1 -1
  53. package/dist/x402/utils.js +2 -2
  54. package/dist/x402/utils.js.map +1 -1
  55. package/jest.config.js +4 -0
  56. package/package.json +1 -1
  57. package/skills.md +0 -1
  58. package/src/batch.d.ts +3 -3
  59. package/src/batch.js +1 -1
  60. package/src/client.ts +287 -823
  61. package/src/database.d.ts +1 -1
  62. package/src/database.js +4 -4
  63. package/src/database.ts +71 -494
  64. package/src/index.d.ts +18 -18
  65. package/src/index.js +16 -16
  66. package/src/index.ts +44 -198
  67. package/src/query-sdk/ConditionBuilder.ts +37 -89
  68. package/src/query-sdk/NestedBuilders.ts +90 -92
  69. package/src/query-sdk/{OnChainDB.ts → OnDB.ts} +1 -1
  70. package/src/query-sdk/QueryBuilder.ts +59 -218
  71. package/src/query-sdk/QueryResult.ts +4 -330
  72. package/src/query-sdk/README.md +218 -587
  73. package/src/query-sdk/index.ts +2 -2
  74. package/src/query-sdk/operators.ts +91 -200
  75. package/src/query-sdk/tests/FieldConditionBuilder.test.ts +70 -71
  76. package/src/query-sdk/tests/LogicalOperator.test.ts +43 -82
  77. package/src/query-sdk/tests/NestedBuilders.test.ts +229 -309
  78. package/src/query-sdk/tests/QueryBuilder.test.ts +5 -5
  79. package/src/query-sdk/tests/QueryResult.test.ts +41 -435
  80. package/src/query-sdk/tests/comprehensive.test.ts +4 -185
  81. package/src/tests/client-requests.test.ts +280 -0
  82. package/src/tests/client-validation.test.ts +80 -0
  83. package/src/types.d.ts +6 -6
  84. package/src/types.js +8 -8
  85. package/src/types.ts +239 -54
  86. package/src/x402/types.ts +3 -3
  87. package/src/x402/utils.ts +3 -3
  88. package/examples/blob-upload-example.ts +0 -140
  89. package/src/batch.ts +0 -257
  90. package/src/query-sdk/dist/ConditionBuilder.d.ts +0 -22
  91. package/src/query-sdk/dist/ConditionBuilder.js +0 -90
  92. package/src/query-sdk/dist/FieldConditionBuilder.d.ts +0 -1
  93. package/src/query-sdk/dist/FieldConditionBuilder.js +0 -6
  94. package/src/query-sdk/dist/NestedBuilders.d.ts +0 -43
  95. package/src/query-sdk/dist/NestedBuilders.js +0 -144
  96. package/src/query-sdk/dist/QueryBuilder.d.ts +0 -70
  97. package/src/query-sdk/dist/QueryBuilder.js +0 -295
  98. package/src/query-sdk/dist/QueryResult.d.ts +0 -52
  99. package/src/query-sdk/dist/QueryResult.js +0 -293
  100. package/src/query-sdk/dist/SelectionBuilder.d.ts +0 -20
  101. package/src/query-sdk/dist/SelectionBuilder.js +0 -80
  102. package/src/query-sdk/dist/adapters/HttpClientAdapter.d.ts +0 -27
  103. package/src/query-sdk/dist/adapters/HttpClientAdapter.js +0 -170
  104. package/src/query-sdk/dist/index.d.ts +0 -36
  105. package/src/query-sdk/dist/index.js +0 -27
  106. package/src/query-sdk/dist/operators.d.ts +0 -56
  107. package/src/query-sdk/dist/operators.js +0 -289
  108. package/src/query-sdk/dist/tests/setup.d.ts +0 -15
  109. package/src/query-sdk/dist/tests/setup.js +0 -46
  110. package/src/query-sdk/jest.config.js +0 -25
  111. package/src/query-sdk/package.json +0 -46
  112. package/src/query-sdk/tests/aggregations.test.ts +0 -653
  113. package/src/query-sdk/tests/integration.test.ts +0 -608
  114. package/src/query-sdk/tests/operators.test.ts +0 -327
  115. package/src/query-sdk/tests/unit.test.ts +0 -794
  116. package/src/query-sdk/tsconfig.json +0 -26
  117. package/src/query-sdk/yarn.lock +0 -3092
package/src/client.ts CHANGED
@@ -1,24 +1,35 @@
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,
8
- OnChainDBConfig,
9
- OnChainDBError,
7
+ OnDBConfig,
8
+ OnDBError,
10
9
  PricingQuoteRequest,
11
- PricingQuoteResponse,
12
10
  RelationRequest,
13
11
  RelationResponse,
14
12
  RetrieveBlobRequest,
15
13
  SimpleCollectionSchema,
14
+ SimpleCollectionSchemaWithSharding,
16
15
  SimpleFieldDefinition,
16
+ ShardingStrategy,
17
+ UpdateCollectionRequest,
18
+ SetRetentionRequest,
19
+ RetentionConfig,
20
+ RetentionCostResponse,
21
+ SqlQueryRequest,
22
+ SqlQueryResponse,
23
+ SqlInsertRequest,
24
+ CreateQueryRequest,
25
+ CreateQueryResponse,
26
+ QueryDefinition,
27
+ QueryDataResponse,
28
+ CollectionResponse,
17
29
  StoreRequest,
18
30
  StoreResponse,
19
31
  SyncCollectionResult,
20
32
  TaskInfo,
21
- TransactionError,
22
33
  TransactionEvents,
23
34
  UploadBlobRequest,
24
35
  UploadBlobResponse,
@@ -28,7 +39,7 @@ import {
28
39
  // Import query builder components
29
40
  // Import database management
30
41
  import {createDatabaseManager, DatabaseManager} from './database';
31
- import {LogicalOperator, QueryBuilder, QueryResponse} from "./query-sdk";
42
+ import {QueryBuilder} from "./query-sdk";
32
43
 
33
44
  // Import x402 utilities
34
45
  import {
@@ -46,38 +57,44 @@ import {
46
57
  } from './x402';
47
58
 
48
59
  /**
49
- * OnChainDB TypeScript SDK
60
+ * OnDB TypeScript SDK
50
61
  *
51
- * Provides a complete interface for storing and querying data on OnChainDB,
62
+ * Provides a complete interface for storing and querying data on OnDB,
52
63
  * built on Celestia blockchain with automatic transaction management.
53
64
  *
54
65
  * @example
55
66
  * ```typescript
56
- * // Initialize with app key (for writes) and user key (for Auto-Pay)
57
- * const db = new OnChainDBClient({
67
+ * // Initialize with app key (for writes)
68
+ * const db = new OnDBClient({
58
69
  * endpoint: 'http://localhost:9092',
59
70
  * appId: 'app_abc123',
60
- * appKey: 'app_xxx...', // Required for write operations
61
- * userKey: 'user_yyy...' // Optional: enables Auto-Pay for reads/writes
71
+ * appKey: 'app_xxx...', // Required for write operations (X-App-Key header)
72
+ * });
73
+ *
74
+ * // Agent Key: autonomous agent that pays other apps inline via USDC (EIP-3009)
75
+ * const agent = new OnDBClient({
76
+ * endpoint: 'http://localhost:9092',
77
+ * appId: 'app_abc123',
78
+ * agentKey: 'agent_zzz...', // X-Agent-Key header — key must have Pay permission
62
79
  * });
63
80
  *
64
81
  * // Store data (requires appKey)
65
82
  * const result = await db.store({
66
- * data: [{ message: 'Hello OnChainDB!', user: 'alice' }],
83
+ * data: [{ message: 'Hello OnDB!', user: 'alice' }],
67
84
  * collection: 'messages'
68
85
  * });
69
86
  *
70
- * // Query data (userKey enables Auto-Pay if authz granted)
87
+ * // Query data
71
88
  * const messages = await db.query({ collection: 'messages' });
72
89
  *
73
90
  * // Backwards compatible: legacy apiKey maps to appKey
74
- * const legacyDb = new OnChainDBClient({
91
+ * const legacyDb = new OnDBClient({
75
92
  * endpoint: 'http://localhost:9092',
76
93
  * apiKey: 'app_xxx...' // Still works, maps to appKey
77
94
  * });
78
95
  * ```
79
96
  */
80
- export class OnChainDBClient extends EventEmitter<TransactionEvents> {
97
+ export class OnDBClient extends EventEmitter<TransactionEvents> {
81
98
  /**
82
99
  * Base fields that are automatically indexed when useBaseFields is true
83
100
  */
@@ -88,28 +105,30 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
88
105
  deletedAt: {type: 'date', index: true}
89
106
  };
90
107
  private http: AxiosInstance;
91
- private config: Required<Omit<OnChainDBConfig, 'appId'>> & { appId?: string; appKey?: string; userKey?: string };
108
+ private config: Required<Omit<OnDBConfig, 'appId'>> & { appId?: string; appKey?: string; userKey?: string; agentKey?: string };
92
109
  private _database?: DatabaseManager;
93
110
 
94
- constructor(config: OnChainDBConfig) {
111
+ constructor(config: OnDBConfig) {
95
112
  super();
96
113
 
97
114
  // Support legacy apiKey for backwards compatibility (maps to appKey)
98
115
  const appKey = config.appKey || '';
99
116
  const userKey = config.userKey || '';
117
+ const agentKey = config.agentKey || '';
100
118
 
101
119
  this.config = {
102
120
  endpoint: config.endpoint,
103
121
  apiKey: config.apiKey || "", // Keep for backwards compat, but maps to appKey
104
122
  appKey: appKey,
105
123
  userKey: userKey,
124
+ agentKey: agentKey,
106
125
  appId: config.appId || undefined,
107
126
  timeout: config.timeout || 30000,
108
127
  retryCount: config.retryCount || 3,
109
128
  retryDelay: config.retryDelay || 1000
110
129
  };
111
130
 
112
- // Build headers with both keys if provided
131
+ // Build headers with provided keys
113
132
  const headers: Record<string, string> = {
114
133
  'Content-Type': 'application/json'
115
134
  };
@@ -122,11 +141,15 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
122
141
  headers['X-Api-Key'] = config.apiKey;
123
142
  }
124
143
 
125
-
126
144
  if (userKey) {
127
145
  headers['X-User-Key'] = userKey;
128
146
  }
129
147
 
148
+ // Agent Keys use X-Agent-Key (not X-API-Key / X-App-Key)
149
+ if (agentKey) {
150
+ headers['X-Agent-Key'] = agentKey;
151
+ }
152
+
130
153
  this.http = axios.create({
131
154
  baseURL: this.config.endpoint,
132
155
  timeout: this.config.timeout,
@@ -162,12 +185,15 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
162
185
  * });
163
186
  * ```
164
187
  */
165
- database(appId: string): DatabaseManager {
166
- if (!this._database || this._database['appId'] !== appId) {
188
+ database(appId?: string): DatabaseManager {
189
+ const resolvedAppId = appId || this.config.appId;
190
+ if (!resolvedAppId) {
191
+ throw new ValidationError('appId must be provided either in config or as an argument to database()');
192
+ }
193
+ if (!this._database || this._database['appId'] !== resolvedAppId) {
167
194
  this._database = createDatabaseManager(
168
195
  this.http,
169
- this.config.endpoint,
170
- appId,
196
+ resolvedAppId,
171
197
  this.config.apiKey
172
198
  );
173
199
  }
@@ -187,7 +213,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
187
213
  * @param waitForConfirmation - Whether to wait for blockchain confirmation
188
214
  * @returns Promise resolving to the operation result
189
215
  */
190
- async handleX402(
216
+ private async handleX402(
191
217
  response: { data: any },
192
218
  paymentCallback: ((quote: X402Quote) => Promise<X402PaymentCallbackResult>) | undefined,
193
219
  finalRequest: any,
@@ -200,7 +226,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
200
226
  try {
201
227
  x402Response = parseX402Response(response.data);
202
228
  } catch (e) {
203
- throw new OnChainDBError(
229
+ throw new OnDBError(
204
230
  `Invalid x402 response: ${e instanceof Error ? e.message : String(e)}`,
205
231
  'PAYMENT_ERROR',
206
232
  402
@@ -224,7 +250,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
224
250
 
225
251
  // Call payment callback if provided
226
252
  if (!paymentCallback) {
227
- throw new OnChainDBError(
253
+ throw new OnDBError(
228
254
  'Payment required but no payment callback provided. Please provide a payment callback to handle x402.',
229
255
  'PAYMENT_REQUIRED',
230
256
  402,
@@ -248,7 +274,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
248
274
  );
249
275
 
250
276
  if (!selectedRequirement) {
251
- throw new OnChainDBError(
277
+ throw new OnDBError(
252
278
  `Payment callback returned network '${facilitatorPayment.network}' but no matching payment option found`,
253
279
  'PAYMENT_ERROR'
254
280
  );
@@ -268,7 +294,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
268
294
  transaction: facilitatorPayment.solanaAuthorization.transaction,
269
295
  });
270
296
  } else {
271
- throw new OnChainDBError(
297
+ throw new OnDBError(
272
298
  'Facilitator payment missing authorization data',
273
299
  'PAYMENT_ERROR'
274
300
  );
@@ -295,37 +321,19 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
295
321
  if (serverResult.ticket_id) {
296
322
  if (waitForConfirmation) {
297
323
  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
- };
324
+ if (taskInfo.result?.results?.length > 0) {
325
+ return this.buildStoreResult(taskInfo.result.results[0]);
309
326
  }
310
327
  } 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;
328
+ return { id: serverResult.ticket_id, block_height: 0, transaction_hash: '', celestia_height: 0, namespace: '', confirmed: false, ticket_id: serverResult.ticket_id } as StoreResponse;
321
329
  }
322
330
  }
323
331
 
324
- throw new OnChainDBError('No ticket_id in response after payment', 'STORE_ERROR');
332
+ throw new OnDBError('No ticket_id in response after payment', 'STORE_ERROR');
325
333
  }
326
334
 
327
335
  /**
328
- * Store data on OnChainDB using the new root-based API with broker payment
336
+ * Store data on OnDB using the new root-based API with broker payment
329
337
  *
330
338
  * @param request - Store request with root (app::collection) and data array
331
339
  * @param paymentOptions - Payment configuration for broker fees
@@ -368,18 +376,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
368
376
  delete resolvedRequest.collection;
369
377
 
370
378
 
371
- // Step 1: Try to store without payment (may get 402)
372
379
  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
380
  const serverResult = response.data;
384
381
 
385
382
  // Check if we got an async response with ticket_id (new flow)
@@ -396,20 +393,8 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
396
393
  const taskInfo = await this.waitForTaskCompletion(serverResult.ticket_id);
397
394
 
398
395
  // 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
396
+ if (taskInfo.result?.results?.length > 0) {
397
+ const result = this.buildStoreResult(taskInfo.result.results[0]);
413
398
  this.emit('transaction:confirmed', {
414
399
  id: result.id,
415
400
  status: 'confirmed',
@@ -417,40 +402,22 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
417
402
  transaction_hash: result.transaction_hash,
418
403
  celestia_height: result.celestia_height
419
404
  });
420
-
421
405
  return result;
422
406
  } else {
423
- throw new OnChainDBError('Task completed but no storage results found', 'STORE_ERROR');
407
+ throw new OnDBError('Task completed but no storage results found', 'STORE_ERROR');
424
408
  }
425
409
  } 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;
410
+ return { id: serverResult.ticket_id, block_height: 0, transaction_hash: '', celestia_height: 0, namespace: '', confirmed: false, ticket_id: serverResult.ticket_id } as StoreResponse;
436
411
  }
437
412
  }
438
413
 
439
414
  // Legacy response format (if server returns old format)
440
- const firstResult = serverResult.results && serverResult.results[0];
415
+ const firstResult = serverResult.results?.[0];
441
416
  if (!firstResult) {
442
- throw new OnChainDBError('No results returned from server', 'STORE_ERROR');
417
+ throw new OnDBError('No results returned from server', 'STORE_ERROR');
443
418
  }
444
419
 
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
- };
420
+ const result = this.buildStoreResult(firstResult);
454
421
 
455
422
  // Emit appropriate event based on height
456
423
  if (result.block_height === 0) {
@@ -470,10 +437,6 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
470
437
  });
471
438
  }
472
439
 
473
- if (waitForConfirmation && result.block_height === 0) {
474
- return await this.waitForConfirmation(result.transaction_hash);
475
- }
476
-
477
440
  return result;
478
441
  } catch (error: any) {
479
442
  if ((error as AxiosError)?.response) {
@@ -486,109 +449,17 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
486
449
  }
487
450
  }
488
451
 
489
- throw new OnChainDBError(JSON.stringify(err.response?.data), 'STORE_ERROR');
452
+ throw new OnDBError(JSON.stringify(err.response?.data), 'STORE_ERROR');
490
453
  }
491
454
 
492
455
  console.error(error)
493
- const dbError = error instanceof OnChainDBError ? error :
494
- new OnChainDBError(error.message, 'STORE_ERROR');
456
+ const dbError = error instanceof OnDBError ? error :
457
+ new OnDBError(error.message, 'STORE_ERROR');
495
458
  this.emit('error', dbError);
496
459
  throw dbError;
497
460
  }
498
461
  }
499
462
 
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
463
 
593
464
  /**
594
465
  * Create an index on a collection field
@@ -603,8 +474,8 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
603
474
  const response = await this.http.post<IndexResponse>(`/api/apps/${appId}/indexes`, request);
604
475
  return response.data;
605
476
  } catch (error) {
606
- throw error instanceof OnChainDBError ? error :
607
- new OnChainDBError('Failed to create index', 'INDEX_ERROR');
477
+ throw error instanceof OnDBError ? error :
478
+ new OnDBError('Failed to create index', 'INDEX_ERROR');
608
479
  }
609
480
  }
610
481
 
@@ -652,7 +523,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
652
523
  const allFields: Record<string, SimpleFieldDefinition> = {};
653
524
 
654
525
  if (schema.useBaseFields !== false) {
655
- Object.assign(allFields, OnChainDBClient.BASE_FIELDS);
526
+ Object.assign(allFields, OnDBClient.BASE_FIELDS);
656
527
  }
657
528
 
658
529
  Object.assign(allFields, schema.fields);
@@ -752,7 +623,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
752
623
  * console.log('Removed:', result.removed); // [{ field: 'username', type: 'string' }]
753
624
  * ```
754
625
  */
755
- async syncCollection(schema: SimpleCollectionSchema): Promise<SyncCollectionResult> {
626
+ async syncCollection(schema: SimpleCollectionSchemaWithSharding): Promise<SyncCollectionResult> {
756
627
  const appId = this.config.appId;
757
628
  if (!appId) {
758
629
  throw new ValidationError('appId must be configured to sync collections');
@@ -767,26 +638,10 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
767
638
  errors: []
768
639
  };
769
640
 
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
641
  // Merge base fields if enabled (default: true)
787
642
  const allFields: Record<string, SimpleFieldDefinition> = {};
788
643
  if (schema.useBaseFields !== false) {
789
- Object.assign(allFields, OnChainDBClient.BASE_FIELDS);
644
+ Object.assign(allFields, OnDBClient.BASE_FIELDS);
790
645
  }
791
646
  Object.assign(allFields, schema.fields);
792
647
 
@@ -798,9 +653,9 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
798
653
  }
799
654
  }
800
655
 
801
- // Find indexes to create (in desired but not existing)
656
+ // Create all desired indexes (broker handles upsert)
802
657
  for (const fieldName of desiredIndexedFields) {
803
- if (!existingByField.has(fieldName)) {
658
+ {
804
659
  const fieldDef = allFields[fieldName];
805
660
  const indexType = fieldDef.indexType || this.getDefaultIndexType(fieldDef.type);
806
661
 
@@ -840,36 +695,179 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
840
695
  }
841
696
  }
842
697
 
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
- }
698
+ if (schema.sharding) {
699
+ try {
700
+ await this.setupSharding(schema.name, schema.sharding);
701
+ result.sharding_configured = true;
702
+ } catch (error) {
703
+ const errorMsg = error instanceof Error ? error.message : String(error);
704
+ result.errors!.push(`Failed to configure sharding: ${errorMsg}`);
705
+ result.success = false;
859
706
  }
860
707
  }
861
708
 
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
- }
709
+ return result;
710
+ }
711
+
712
+ async setupSharding(collection: string, sharding: ShardingStrategy): Promise<void> {
713
+ const appId = this.config.appId;
714
+ if (!appId) {
715
+ throw new ValidationError('appId must be configured to setup sharding');
870
716
  }
717
+ try {
718
+ await this.http.patch(
719
+ `/api/apps/${appId}/collections/${collection}/sharding`,
720
+ sharding
721
+ );
722
+ } catch (error) {
723
+ throw error instanceof OnDBError ? error :
724
+ new OnDBError('Failed to setup sharding', 'SHARDING_ERROR');
725
+ }
726
+ }
871
727
 
872
- return result;
728
+ async deleteCollection(collection: string): Promise<{ success: boolean }> {
729
+ const appId = this.config.appId;
730
+ if (!appId) throw new ValidationError('appId must be configured');
731
+ try {
732
+ const response = await this.http.delete(`/api/apps/${appId}/collections/${collection}`);
733
+ return response.data;
734
+ } catch (error) {
735
+ throw error instanceof OnDBError ? error :
736
+ new OnDBError('Failed to delete collection', 'COLLECTION_ERROR');
737
+ }
738
+ }
739
+
740
+ async updateCollection(collection: string, updates: UpdateCollectionRequest): Promise<any> {
741
+ const appId = this.config.appId;
742
+ if (!appId) throw new ValidationError('appId must be configured');
743
+ try {
744
+ const response = await this.http.patch(`/api/apps/${appId}/collections/${collection}`, updates);
745
+ return response.data;
746
+ } catch (error) {
747
+ throw error instanceof OnDBError ? error :
748
+ new OnDBError('Failed to update collection', 'COLLECTION_ERROR');
749
+ }
750
+ }
751
+
752
+ async getRetention(collection: string): Promise<RetentionConfig> {
753
+ const appId = this.config.appId;
754
+ if (!appId) throw new ValidationError('appId must be configured');
755
+ try {
756
+ const response = await this.http.get(`/api/apps/${appId}/collections/${collection}/retention`);
757
+ return response.data;
758
+ } catch (error) {
759
+ throw error instanceof OnDBError ? error :
760
+ new OnDBError('Failed to get retention config', 'RETENTION_ERROR');
761
+ }
762
+ }
763
+
764
+ async setRetention(collection: string, request: SetRetentionRequest): Promise<{ message: string; config: RetentionConfig }> {
765
+ const appId = this.config.appId;
766
+ if (!appId) throw new ValidationError('appId must be configured');
767
+ try {
768
+ const response = await this.http.post(`/api/apps/${appId}/collections/${collection}/retention`, request);
769
+ return response.data;
770
+ } catch (error) {
771
+ throw error instanceof OnDBError ? error :
772
+ new OnDBError('Failed to set retention config', 'RETENTION_ERROR');
773
+ }
774
+ }
775
+
776
+ async getRetentionCost(): Promise<RetentionCostResponse> {
777
+ const appId = this.config.appId;
778
+ if (!appId) throw new ValidationError('appId must be configured');
779
+ try {
780
+ const response = await this.http.get(`/api/apps/${appId}/retention/cost`);
781
+ return response.data;
782
+ } catch (error) {
783
+ throw error instanceof OnDBError ? error :
784
+ new OnDBError('Failed to get retention cost', 'RETENTION_ERROR');
785
+ }
786
+ }
787
+
788
+ async sql(query: string, options?: { includeHistory?: boolean }): Promise<SqlQueryResponse> {
789
+ try {
790
+ const body: SqlQueryRequest = { sql: query };
791
+ if (options?.includeHistory !== undefined) body.include_history = options.includeHistory;
792
+ const response = await this.http.post('/query/sql', body);
793
+ return response.data;
794
+ } catch (error) {
795
+ throw error instanceof OnDBError ? error :
796
+ new OnDBError('SQL query failed', 'SQL_ERROR');
797
+ }
798
+ }
799
+
800
+ async sqlInsert(sql: string): Promise<any> {
801
+ try {
802
+ const body: SqlInsertRequest = { sql };
803
+ const response = await this.http.post('/insert/sql', body);
804
+ return response.data;
805
+ } catch (error) {
806
+ throw error instanceof OnDBError ? error :
807
+ new OnDBError('SQL insert failed', 'SQL_ERROR');
808
+ }
809
+ }
810
+
811
+ async listQueries(): Promise<QueryDefinition[]> {
812
+ const appId = this.config.appId;
813
+ if (!appId) throw new ValidationError('appId must be configured');
814
+ try {
815
+ const response = await this.http.get(`/apps/${appId}/queries`);
816
+ return response.data.queries ?? response.data;
817
+ } catch (error) {
818
+ throw error instanceof OnDBError ? error :
819
+ new OnDBError('Failed to list predefined queries', 'QUERY_ERROR');
820
+ }
821
+ }
822
+
823
+ async createQuery(request: CreateQueryRequest): Promise<CreateQueryResponse> {
824
+ const appId = this.config.appId;
825
+ if (!appId) throw new ValidationError('appId must be configured');
826
+ try {
827
+ const response = await this.http.post(`/apps/${appId}/queries`, request);
828
+ return response.data;
829
+ } catch (error) {
830
+ throw error instanceof OnDBError ? error :
831
+ new OnDBError('Failed to create predefined query', 'QUERY_ERROR');
832
+ }
833
+ }
834
+
835
+ async getQuery(name: string): Promise<QueryDefinition> {
836
+ const appId = this.config.appId;
837
+ if (!appId) throw new ValidationError('appId must be configured');
838
+ try {
839
+ const response = await this.http.get(`/apps/${appId}/queries/${name}`);
840
+ return response.data;
841
+ } catch (error) {
842
+ throw error instanceof OnDBError ? error :
843
+ new OnDBError('Failed to get predefined query', 'QUERY_ERROR');
844
+ }
845
+ }
846
+
847
+ async deleteQuery(name: string): Promise<{ success: boolean }> {
848
+ const appId = this.config.appId;
849
+ if (!appId) throw new ValidationError('appId must be configured');
850
+ try {
851
+ const response = await this.http.delete(`/apps/${appId}/queries/${name}`);
852
+ return response.data;
853
+ } catch (error) {
854
+ throw error instanceof OnDBError ? error :
855
+ new OnDBError('Failed to delete predefined query', 'QUERY_ERROR');
856
+ }
857
+ }
858
+
859
+ async executeQuery(name: string, params?: Record<string, string>, version?: number): Promise<QueryDataResponse> {
860
+ const appId = this.config.appId;
861
+ if (!appId) throw new ValidationError('appId must be configured');
862
+ try {
863
+ const queryParams: Record<string, string | number> = { ...params };
864
+ if (version !== undefined) queryParams['v'] = version;
865
+ const response = await this.http.get(`/api/queries/${appId}/${name}/data`, { params: queryParams });
866
+ return response.data;
867
+ } catch (error) {
868
+ throw error instanceof OnDBError ? error :
869
+ new OnDBError('Failed to execute predefined query', 'QUERY_ERROR');
870
+ }
873
871
  }
874
872
 
875
873
  /**
@@ -878,18 +876,18 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
878
876
  * @param collection - Collection name
879
877
  * @returns Collection information
880
878
  */
881
- async getCollectionInfo(collection: string): Promise<{ indexes: string[]; recordCount?: number }> {
879
+ async getCollectionInfo(collectionId: string): Promise<CollectionResponse> {
882
880
  const appId = this.config.appId;
883
881
  if (!appId) {
884
882
  throw new ValidationError('appId must be configured');
885
883
  }
886
884
 
887
885
  try {
888
- const response = await this.http.get(`/api/apps/${appId}/collections/${collection}`);
886
+ const response = await this.http.get(`/api/apps/${appId}/collections/${collectionId}`);
889
887
  return response.data;
890
888
  } catch (error) {
891
- throw error instanceof OnChainDBError ? error :
892
- new OnChainDBError('Failed to get collection info', 'COLLECTION_ERROR');
889
+ throw error instanceof OnDBError ? error :
890
+ new OnDBError('Failed to get collection info', 'COLLECTION_ERROR');
893
891
  }
894
892
  }
895
893
 
@@ -934,13 +932,13 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
934
932
  const response = await this.http.post<RelationResponse>(`/api/apps/${appId}/relations`, request);
935
933
  return response.data;
936
934
  } catch (error) {
937
- throw error instanceof OnChainDBError ? error :
938
- new OnChainDBError('Failed to create relation', 'RELATION_ERROR');
935
+ throw error instanceof OnDBError ? error :
936
+ new OnDBError('Failed to create relation', 'RELATION_ERROR');
939
937
  }
940
938
  }
941
939
 
942
940
  /**
943
- * Get health status of OnChainDB service
941
+ * Get health status of OnDB service
944
942
  *
945
943
  * @returns Health check response
946
944
  */
@@ -949,98 +947,10 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
949
947
  const response = await this.http.get('/');
950
948
  return response.data;
951
949
  } catch (error) {
952
- throw new OnChainDBError('Health check failed', 'HEALTH_ERROR');
950
+ throw new OnDBError('Health check failed', 'HEALTH_ERROR');
953
951
  }
954
952
  }
955
953
 
956
- /**
957
- * Get pricing quote for an operation before executing it
958
- *
959
- * Use this to estimate costs before committing to a store operation,
960
- * especially useful for large files or high-volume scenarios.
961
- *
962
- * @param request - Pricing quote request with app_id, operation type, size, and collection
963
- * @returns Pricing quote with detailed cost breakdown
964
- *
965
- * @example
966
- * ```typescript
967
- * const quote = await db.getPricingQuote({
968
- * app_id: 'my_app',
969
- * operation_type: 'write',
970
- * size_kb: 50,
971
- * collection: 'users',
972
- * monthly_volume_kb: 1000
973
- * });
974
- *
975
- * console.log(`Total cost: ${quote.total_cost_utia} utia`);
976
- * console.log(`Indexing costs:`, quote.indexing_costs_utia);
977
- * ```
978
- */
979
- async getPricingQuote(request: PricingQuoteRequest): Promise<PricingQuoteResponse> {
980
- try {
981
- const response = await this.http.post('/api/pricing/quote', request);
982
- return response.data;
983
- } catch (error) {
984
- throw error instanceof OnChainDBError ? error :
985
- new OnChainDBError('Failed to get pricing quote', 'PRICING_QUOTE_ERROR');
986
- }
987
- }
988
-
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
954
 
1045
955
  /**
1046
956
  * Create a fluent query builder instance
@@ -1051,10 +961,10 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1051
961
  * ```typescript
1052
962
  * const results = await db.queryBuilder()
1053
963
  * .find(builder =>
1054
- * LogicalOperator.And([
1055
- * LogicalOperator.Condition(builder.field('status').equals('published')),
1056
- * LogicalOperator.Condition(builder.field('author').equals('alice'))
1057
- * ])
964
+ * builder.and(
965
+ * builder.field('status').equals('published'),
966
+ * builder.field('author').equals('alice')
967
+ * )
1058
968
  * )
1059
969
  * .select(selection =>
1060
970
  * selection.field('title').field('content')
@@ -1070,359 +980,6 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1070
980
  return new QueryBuilder(httpClient, this.config.endpoint, this.config.appId);
1071
981
  }
1072
982
 
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
983
  /**
1427
984
  * Get task status by ticket ID
1428
985
  * @param ticketId - The ticket ID returned from async store operation
@@ -1433,8 +990,8 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1433
990
  const response = await this.http.get(`/task/${ticketId}`);
1434
991
  return response.data;
1435
992
  } catch (error) {
1436
- throw error instanceof OnChainDBError ? error :
1437
- new OnChainDBError('Failed to get task status', 'TASK_STATUS_ERROR');
993
+ throw error instanceof OnDBError ? error :
994
+ new OnDBError('Failed to get task status', 'TASK_STATUS_ERROR');
1438
995
  }
1439
996
  }
1440
997
 
@@ -1471,13 +1028,13 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1471
1028
  if (typeof taskInfo.status === 'object' && 'Failed' in taskInfo.status) {
1472
1029
  const error = (taskInfo.status as { Failed: { error: string } }).Failed.error;
1473
1030
  console.error(`🚫 Task ${ticketId} failed: ${error}`);
1474
- throw new OnChainDBError(`Task failed: ${error}`, 'TASK_FAILED');
1031
+ throw new OnDBError(`Task failed: ${error}`, 'TASK_FAILED');
1475
1032
  }
1476
1033
 
1477
1034
  // Check for any other error-like statuses
1478
1035
  if (typeof taskInfo.status === 'string' && taskInfo.status.toLowerCase().includes('error')) {
1479
1036
  console.error(`🚫 Task ${ticketId} has error status: ${taskInfo.status}`);
1480
- throw new OnChainDBError(`Task error: ${taskInfo.status}`, 'TASK_FAILED');
1037
+ throw new OnDBError(`Task error: ${taskInfo.status}`, 'TASK_FAILED');
1481
1038
  }
1482
1039
 
1483
1040
  // Task still in progress, wait and check again
@@ -1487,8 +1044,8 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1487
1044
  console.error(`❌ Error polling task ${ticketId}:`, error);
1488
1045
 
1489
1046
  // Check if this is a permanent error (like 404, 400, etc.) that shouldn't be retried
1490
- if (error instanceof OnChainDBError) {
1491
- // OnChainDB errors with specific codes should stop polling
1047
+ if (error instanceof OnDBError) {
1048
+ // OnDB errors with specific codes should stop polling
1492
1049
  if (error.code === 'TASK_FAILED' || error.statusCode === 404 || error.statusCode === 400) {
1493
1050
  console.error(`🚫 Stopping polling due to permanent error: ${error.message}`);
1494
1051
  throw error;
@@ -1497,7 +1054,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1497
1054
 
1498
1055
  // For network/temporary errors, check if we've exceeded max wait time
1499
1056
  if (Date.now() - startTime >= maxWaitTime) {
1500
- throw new OnChainDBError(
1057
+ throw new OnDBError(
1501
1058
  `Task completion timeout after ${maxWaitTime}ms. Last error: ${error instanceof Error ? error.message : String(error)}`,
1502
1059
  'TASK_TIMEOUT'
1503
1060
  );
@@ -1509,14 +1066,14 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1509
1066
  }
1510
1067
  }
1511
1068
 
1512
- throw new OnChainDBError(
1069
+ throw new OnDBError(
1513
1070
  `Task completion timeout after ${maxWaitTime}ms`,
1514
1071
  'TASK_TIMEOUT'
1515
1072
  );
1516
1073
  }
1517
1074
 
1518
1075
  /**
1519
- * Upload a blob (binary file) to OnChainDB with optional custom metadata fields
1076
+ * Upload a blob (binary file) to OnDB with optional custom metadata fields
1520
1077
  *
1521
1078
  * Supports images, videos, documents, and any binary data up to 2MB.
1522
1079
  * Automatically handles multipart/form-data upload and returns a ticket for tracking.
@@ -1575,13 +1132,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1575
1132
  return await this.http.post<UploadBlobResponse>(
1576
1133
  `/api/apps/${appId}/blobs/${request.collection}`,
1577
1134
  formData,
1578
- {
1579
- headers: {
1580
- 'Content-Type': 'multipart/form-data',
1581
- 'X-App-Key': this.config.appKey,
1582
- ...headers
1583
- }
1584
- }
1135
+ { headers: { 'Content-Type': 'multipart/form-data', ...headers } }
1585
1136
  );
1586
1137
  };
1587
1138
 
@@ -1599,7 +1150,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1599
1150
  const quote = requirementToQuote(requirement, x402Response.accepts);
1600
1151
 
1601
1152
  if (!paymentCallback) {
1602
- throw new OnChainDBError(
1153
+ throw new OnDBError(
1603
1154
  'Payment required but no payment callback provided',
1604
1155
  'PAYMENT_REQUIRED',
1605
1156
  402,
@@ -1621,13 +1172,13 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1621
1172
  throw error;
1622
1173
  }
1623
1174
  } catch (error) {
1624
- throw error instanceof OnChainDBError ? error :
1625
- new OnChainDBError('Failed to upload blob', 'BLOB_UPLOAD_ERROR');
1175
+ throw error instanceof OnDBError ? error :
1176
+ new OnDBError('Failed to upload blob', 'BLOB_UPLOAD_ERROR');
1626
1177
  }
1627
1178
  }
1628
1179
 
1629
1180
  /**
1630
- * Retrieve a blob (binary file) from OnChainDB by blob_id
1181
+ * Retrieve a blob (binary file) from OnDB by blob_id
1631
1182
  *
1632
1183
  * Returns the raw blob data with proper Content-Type headers.
1633
1184
  * Can be used to serve images, videos, documents, etc.
@@ -1666,12 +1217,7 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1666
1217
  // Retrieve blob via GET endpoint
1667
1218
  const response = await this.http.get(
1668
1219
  `/api/apps/${appId}/blobs/${request.collection}/${request.blob_id}`,
1669
- {
1670
- responseType: 'arraybuffer',
1671
- headers: {
1672
- 'X-App-Key': this.config.appKey
1673
- }
1674
- }
1220
+ { responseType: 'arraybuffer' }
1675
1221
  );
1676
1222
 
1677
1223
  // Return as Blob in browser, Buffer in Node.js
@@ -1684,54 +1230,11 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1684
1230
  return Buffer.from(response.data);
1685
1231
  }
1686
1232
  } catch (error) {
1687
- throw error instanceof OnChainDBError ? error :
1688
- new OnChainDBError('Failed to retrieve blob', 'BLOB_RETRIEVAL_ERROR');
1233
+ throw error instanceof OnDBError ? error :
1234
+ new OnDBError('Failed to retrieve blob', 'BLOB_RETRIEVAL_ERROR');
1689
1235
  }
1690
1236
  }
1691
1237
 
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
1238
  /**
1736
1239
  * Get default index type based on field type
1737
1240
  */
@@ -1750,6 +1253,17 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1750
1253
  }
1751
1254
  }
1752
1255
 
1256
+ private buildStoreResult(raw: any): StoreResponse {
1257
+ return {
1258
+ id: raw.id,
1259
+ block_height: raw.celestia_height || 0,
1260
+ transaction_hash: raw.blob_id || '',
1261
+ celestia_height: raw.celestia_height || 0,
1262
+ namespace: raw.namespace || '',
1263
+ confirmed: raw.celestia_height > 0
1264
+ };
1265
+ }
1266
+
1753
1267
  private validateStoreRequest(request: StoreRequest): void {
1754
1268
  // Validate that either root or collection is provided
1755
1269
  if (!request.root && !request.collection) {
@@ -1786,67 +1300,17 @@ export class OnChainDBClient extends EventEmitter<TransactionEvents> {
1786
1300
  }
1787
1301
  }
1788
1302
 
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
1303
  private sleep(ms: number): Promise<void> {
1819
1304
  return new Promise(resolve => setTimeout(resolve, ms));
1820
1305
  }
1821
1306
 
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
1307
  private resolveRoot(request: { root?: string; collection?: string }): string {
1842
1308
  if (request.root) {
1843
- return request.root; // Explicit root takes precedence
1309
+ return request.root;
1844
1310
  }
1845
-
1846
1311
  if (request.collection) {
1847
- return this.buildRoot(request.collection); // Build from collection + appId
1312
+ return this.config.appId ? `${this.config.appId}::${request.collection}` : request.collection;
1848
1313
  }
1849
-
1850
1314
  throw new ValidationError('Either root or collection must be provided');
1851
1315
  }
1852
1316