@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.
- package/.claude/settings.local.json +10 -2
- package/README.md +422 -355
- package/dist/batch.d.ts +1 -10
- package/dist/batch.d.ts.map +1 -1
- package/dist/batch.js +4 -26
- package/dist/batch.js.map +1 -1
- package/dist/client.d.ts +31 -46
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +222 -357
- package/dist/client.js.map +1 -1
- package/dist/database.d.ts +14 -131
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +35 -131
- package/dist/database.js.map +1 -1
- package/dist/index.d.ts +10 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -18
- package/dist/index.js.map +1 -1
- package/dist/query-sdk/ConditionBuilder.d.ts +3 -11
- package/dist/query-sdk/ConditionBuilder.d.ts.map +1 -1
- package/dist/query-sdk/ConditionBuilder.js +10 -48
- package/dist/query-sdk/ConditionBuilder.js.map +1 -1
- package/dist/query-sdk/NestedBuilders.d.ts +33 -30
- package/dist/query-sdk/NestedBuilders.d.ts.map +1 -1
- package/dist/query-sdk/NestedBuilders.js +46 -43
- package/dist/query-sdk/NestedBuilders.js.map +1 -1
- package/{src/query-sdk/dist/OnChainDB.d.ts → dist/query-sdk/OnDB.d.ts} +10 -2
- package/dist/query-sdk/OnDB.d.ts.map +1 -0
- package/{src/query-sdk/dist/OnChainDB.js → dist/query-sdk/OnDB.js} +86 -18
- package/dist/query-sdk/OnDB.js.map +1 -0
- package/dist/query-sdk/QueryBuilder.d.ts +4 -2
- package/dist/query-sdk/QueryBuilder.d.ts.map +1 -1
- package/dist/query-sdk/QueryBuilder.js +47 -169
- package/dist/query-sdk/QueryBuilder.js.map +1 -1
- package/dist/query-sdk/QueryResult.d.ts +0 -38
- package/dist/query-sdk/QueryResult.d.ts.map +1 -1
- package/dist/query-sdk/QueryResult.js +1 -227
- package/dist/query-sdk/QueryResult.js.map +1 -1
- package/dist/query-sdk/index.d.ts +2 -2
- package/dist/query-sdk/index.d.ts.map +1 -1
- package/dist/query-sdk/index.js +3 -3
- package/dist/query-sdk/index.js.map +1 -1
- package/dist/query-sdk/operators.d.ts +32 -28
- package/dist/query-sdk/operators.d.ts.map +1 -1
- package/dist/query-sdk/operators.js +45 -155
- package/dist/query-sdk/operators.js.map +1 -1
- package/dist/types.d.ts +159 -36
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -8
- package/dist/types.js.map +1 -1
- package/dist/x402/types.d.ts +1 -1
- package/dist/x402/types.d.ts.map +1 -1
- package/dist/x402/utils.js +2 -2
- package/dist/x402/utils.js.map +1 -1
- package/jest.config.js +4 -0
- package/package.json +1 -1
- package/skills.md +0 -1
- package/src/batch.d.ts +3 -3
- package/src/batch.js +1 -1
- package/src/client.ts +287 -823
- package/src/database.d.ts +1 -1
- package/src/database.js +4 -4
- package/src/database.ts +71 -494
- package/src/index.d.ts +18 -18
- package/src/index.js +16 -16
- package/src/index.ts +44 -198
- package/src/query-sdk/ConditionBuilder.ts +37 -89
- package/src/query-sdk/NestedBuilders.ts +90 -92
- package/src/query-sdk/{OnChainDB.ts → OnDB.ts} +1 -1
- package/src/query-sdk/QueryBuilder.ts +59 -218
- package/src/query-sdk/QueryResult.ts +4 -330
- package/src/query-sdk/README.md +218 -587
- package/src/query-sdk/index.ts +2 -2
- package/src/query-sdk/operators.ts +91 -200
- package/src/query-sdk/tests/FieldConditionBuilder.test.ts +70 -71
- package/src/query-sdk/tests/LogicalOperator.test.ts +43 -82
- package/src/query-sdk/tests/NestedBuilders.test.ts +229 -309
- package/src/query-sdk/tests/QueryBuilder.test.ts +5 -5
- package/src/query-sdk/tests/QueryResult.test.ts +41 -435
- package/src/query-sdk/tests/comprehensive.test.ts +4 -185
- package/src/tests/client-requests.test.ts +280 -0
- package/src/tests/client-validation.test.ts +80 -0
- package/src/types.d.ts +6 -6
- package/src/types.js +8 -8
- package/src/types.ts +239 -54
- package/src/x402/types.ts +3 -3
- package/src/x402/utils.ts +3 -3
- package/examples/blob-upload-example.ts +0 -140
- package/src/batch.ts +0 -257
- package/src/query-sdk/dist/ConditionBuilder.d.ts +0 -22
- package/src/query-sdk/dist/ConditionBuilder.js +0 -90
- package/src/query-sdk/dist/FieldConditionBuilder.d.ts +0 -1
- package/src/query-sdk/dist/FieldConditionBuilder.js +0 -6
- package/src/query-sdk/dist/NestedBuilders.d.ts +0 -43
- package/src/query-sdk/dist/NestedBuilders.js +0 -144
- package/src/query-sdk/dist/QueryBuilder.d.ts +0 -70
- package/src/query-sdk/dist/QueryBuilder.js +0 -295
- package/src/query-sdk/dist/QueryResult.d.ts +0 -52
- package/src/query-sdk/dist/QueryResult.js +0 -293
- package/src/query-sdk/dist/SelectionBuilder.d.ts +0 -20
- package/src/query-sdk/dist/SelectionBuilder.js +0 -80
- package/src/query-sdk/dist/adapters/HttpClientAdapter.d.ts +0 -27
- package/src/query-sdk/dist/adapters/HttpClientAdapter.js +0 -170
- package/src/query-sdk/dist/index.d.ts +0 -36
- package/src/query-sdk/dist/index.js +0 -27
- package/src/query-sdk/dist/operators.d.ts +0 -56
- package/src/query-sdk/dist/operators.js +0 -289
- package/src/query-sdk/dist/tests/setup.d.ts +0 -15
- package/src/query-sdk/dist/tests/setup.js +0 -46
- package/src/query-sdk/jest.config.js +0 -25
- package/src/query-sdk/package.json +0 -46
- package/src/query-sdk/tests/aggregations.test.ts +0 -653
- package/src/query-sdk/tests/integration.test.ts +0 -608
- package/src/query-sdk/tests/operators.test.ts +0 -327
- package/src/query-sdk/tests/unit.test.ts +0 -794
- package/src/query-sdk/tsconfig.json +0 -26
- 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
|
-
|
|
9
|
-
|
|
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 {
|
|
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
|
-
*
|
|
60
|
+
* OnDB TypeScript SDK
|
|
50
61
|
*
|
|
51
|
-
* Provides a complete interface for storing and querying data on
|
|
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)
|
|
57
|
-
* const db = new
|
|
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
|
-
*
|
|
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
|
|
83
|
+
* data: [{ message: 'Hello OnDB!', user: 'alice' }],
|
|
67
84
|
* collection: 'messages'
|
|
68
85
|
* });
|
|
69
86
|
*
|
|
70
|
-
* // Query data
|
|
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
|
|
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
|
|
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<
|
|
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:
|
|
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
|
|
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
|
|
166
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
299
|
-
|
|
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
|
-
|
|
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
|
|
332
|
+
throw new OnDBError('No ticket_id in response after payment', 'STORE_ERROR');
|
|
325
333
|
}
|
|
326
334
|
|
|
327
335
|
/**
|
|
328
|
-
* Store data on
|
|
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
|
|
400
|
-
const
|
|
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
|
|
407
|
+
throw new OnDBError('Task completed but no storage results found', 'STORE_ERROR');
|
|
424
408
|
}
|
|
425
409
|
} else {
|
|
426
|
-
|
|
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
|
|
415
|
+
const firstResult = serverResult.results?.[0];
|
|
441
416
|
if (!firstResult) {
|
|
442
|
-
throw new
|
|
417
|
+
throw new OnDBError('No results returned from server', 'STORE_ERROR');
|
|
443
418
|
}
|
|
444
419
|
|
|
445
|
-
|
|
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
|
|
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
|
|
494
|
-
new
|
|
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
|
|
607
|
-
new
|
|
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,
|
|
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:
|
|
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,
|
|
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
|
-
//
|
|
656
|
+
// Create all desired indexes (broker handles upsert)
|
|
802
657
|
for (const fieldName of desiredIndexedFields) {
|
|
803
|
-
|
|
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
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
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
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
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
|
-
|
|
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(
|
|
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/${
|
|
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
|
|
892
|
-
new
|
|
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
|
|
938
|
-
new
|
|
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
|
|
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
|
|
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
|
-
*
|
|
1055
|
-
*
|
|
1056
|
-
*
|
|
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
|
|
1437
|
-
new
|
|
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
|
|
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
|
|
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
|
|
1491
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1625
|
-
new
|
|
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
|
|
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
|
|
1688
|
-
new
|
|
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;
|
|
1309
|
+
return request.root;
|
|
1844
1310
|
}
|
|
1845
|
-
|
|
1846
1311
|
if (request.collection) {
|
|
1847
|
-
return this.
|
|
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
|
|