@onchaindb/sdk 0.0.6
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/README.md +475 -0
- package/dist/batch.d.ts +42 -0
- package/dist/batch.d.ts.map +1 -0
- package/dist/batch.js +124 -0
- package/dist/batch.js.map +1 -0
- package/dist/client.d.ts +93 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +679 -0
- package/dist/client.js.map +1 -0
- package/dist/database.d.ts +194 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +211 -0
- package/dist/database.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/query-sdk/ConditionBuilder.d.ts +23 -0
- package/dist/query-sdk/ConditionBuilder.d.ts.map +1 -0
- package/dist/query-sdk/ConditionBuilder.js +76 -0
- package/dist/query-sdk/ConditionBuilder.js.map +1 -0
- package/dist/query-sdk/FieldConditionBuilder.d.ts +2 -0
- package/dist/query-sdk/FieldConditionBuilder.d.ts.map +1 -0
- package/dist/query-sdk/FieldConditionBuilder.js +6 -0
- package/dist/query-sdk/FieldConditionBuilder.js.map +1 -0
- package/dist/query-sdk/NestedBuilders.d.ts +44 -0
- package/dist/query-sdk/NestedBuilders.d.ts.map +1 -0
- package/dist/query-sdk/NestedBuilders.js +131 -0
- package/dist/query-sdk/NestedBuilders.js.map +1 -0
- package/dist/query-sdk/OnChainDB.d.ts +27 -0
- package/dist/query-sdk/OnChainDB.d.ts.map +1 -0
- package/dist/query-sdk/OnChainDB.js +191 -0
- package/dist/query-sdk/OnChainDB.js.map +1 -0
- package/dist/query-sdk/PrismaLikeClient.d.ts +41 -0
- package/dist/query-sdk/PrismaLikeClient.d.ts.map +1 -0
- package/dist/query-sdk/PrismaLikeClient.js +202 -0
- package/dist/query-sdk/PrismaLikeClient.js.map +1 -0
- package/dist/query-sdk/QueryBuilder.d.ts +155 -0
- package/dist/query-sdk/QueryBuilder.d.ts.map +1 -0
- package/dist/query-sdk/QueryBuilder.js +757 -0
- package/dist/query-sdk/QueryBuilder.js.map +1 -0
- package/dist/query-sdk/QueryResult.d.ts +53 -0
- package/dist/query-sdk/QueryResult.d.ts.map +1 -0
- package/dist/query-sdk/QueryResult.js +267 -0
- package/dist/query-sdk/QueryResult.js.map +1 -0
- package/dist/query-sdk/SelectionBuilder.d.ts +21 -0
- package/dist/query-sdk/SelectionBuilder.d.ts.map +1 -0
- package/dist/query-sdk/SelectionBuilder.js +67 -0
- package/dist/query-sdk/SelectionBuilder.js.map +1 -0
- package/dist/query-sdk/adapters/HttpClientAdapter.d.ts +28 -0
- package/dist/query-sdk/adapters/HttpClientAdapter.d.ts.map +1 -0
- package/dist/query-sdk/adapters/HttpClientAdapter.js +206 -0
- package/dist/query-sdk/adapters/HttpClientAdapter.js.map +1 -0
- package/dist/query-sdk/index.d.ts +38 -0
- package/dist/query-sdk/index.d.ts.map +1 -0
- package/dist/query-sdk/index.js +28 -0
- package/dist/query-sdk/index.js.map +1 -0
- package/dist/query-sdk/operators.d.ts +57 -0
- package/dist/query-sdk/operators.d.ts.map +1 -0
- package/dist/query-sdk/operators.js +275 -0
- package/dist/query-sdk/operators.js.map +1 -0
- package/dist/types.d.ts +263 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +46 -0
- package/dist/types.js.map +1 -0
- package/package.json +47 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.OnChainDBClient = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const eventemitter3_1 = require("eventemitter3");
|
|
9
|
+
const types_1 = require("./types");
|
|
10
|
+
const database_1 = require("./database");
|
|
11
|
+
const query_sdk_1 = require("./query-sdk");
|
|
12
|
+
class OnChainDBClient extends eventemitter3_1.EventEmitter {
|
|
13
|
+
constructor(config) {
|
|
14
|
+
super();
|
|
15
|
+
const appKey = config.appKey || config.apiKey || '';
|
|
16
|
+
const userKey = config.userKey || '';
|
|
17
|
+
this.config = {
|
|
18
|
+
endpoint: config.endpoint,
|
|
19
|
+
apiKey: appKey,
|
|
20
|
+
appKey: appKey,
|
|
21
|
+
userKey: userKey,
|
|
22
|
+
appId: config.appId || undefined,
|
|
23
|
+
timeout: config.timeout || 30000,
|
|
24
|
+
retryCount: config.retryCount || 3,
|
|
25
|
+
retryDelay: config.retryDelay || 1000
|
|
26
|
+
};
|
|
27
|
+
const headers = {
|
|
28
|
+
'Content-Type': 'application/json'
|
|
29
|
+
};
|
|
30
|
+
if (appKey) {
|
|
31
|
+
headers['X-App-Key'] = appKey;
|
|
32
|
+
}
|
|
33
|
+
if (userKey) {
|
|
34
|
+
headers['X-User-Key'] = userKey;
|
|
35
|
+
}
|
|
36
|
+
this.http = axios_1.default.create({
|
|
37
|
+
baseURL: this.config.endpoint,
|
|
38
|
+
timeout: this.config.timeout,
|
|
39
|
+
headers
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
database(appId) {
|
|
43
|
+
if (!this._database || this._database['appId'] !== appId) {
|
|
44
|
+
this._database = (0, database_1.createDatabaseManager)(this.http, this.config.endpoint, appId, this.config.apiKey);
|
|
45
|
+
}
|
|
46
|
+
return this._database;
|
|
47
|
+
}
|
|
48
|
+
async handlex420(response, paymentCallback, finalRequest, waitForConfirmation) {
|
|
49
|
+
console.log('💰 Received 402 Payment Required for store');
|
|
50
|
+
const x402Quote = response.data;
|
|
51
|
+
const requirement = x402Quote.accepts?.[0];
|
|
52
|
+
if (!requirement) {
|
|
53
|
+
throw new types_1.OnChainDBError('Invalid 402 response - no payment requirement', 'PAYMENT_ERROR', 402);
|
|
54
|
+
}
|
|
55
|
+
const quote = {
|
|
56
|
+
quote_id: requirement.quoteId,
|
|
57
|
+
total_cost_tia: parseInt(requirement.maxAmountRequired) / 1000000,
|
|
58
|
+
broker_address: requirement.payTo,
|
|
59
|
+
description: requirement.description,
|
|
60
|
+
expires_at: requirement.expiresAt
|
|
61
|
+
};
|
|
62
|
+
console.log('💳 Quote:', quote);
|
|
63
|
+
if (!paymentCallback) {
|
|
64
|
+
throw new types_1.OnChainDBError('Payment required but no payment callback provided. Please provide a payment callback to handle 402.', 'PAYMENT_REQUIRED', 402, quote);
|
|
65
|
+
}
|
|
66
|
+
console.log('💰 Calling payment callback...');
|
|
67
|
+
const payment = await paymentCallback(quote);
|
|
68
|
+
console.log('✅ Payment completed, tx hash:', payment.txHash);
|
|
69
|
+
const x402Payload = {
|
|
70
|
+
x402Version: 1,
|
|
71
|
+
scheme: payment.network?.includes('ethereum') || payment.network?.includes('sepolia')
|
|
72
|
+
? 'ethereum-native' : 'celestia-native',
|
|
73
|
+
network: payment.network || 'mocha-4',
|
|
74
|
+
quoteId: requirement.quoteId,
|
|
75
|
+
payload: {
|
|
76
|
+
txHash: payment.txHash.toLowerCase(),
|
|
77
|
+
sender: 'unknown',
|
|
78
|
+
timestamp: Math.floor(Date.now() / 1000)
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const encodedPayment = Buffer.from(JSON.stringify(x402Payload)).toString('base64');
|
|
82
|
+
console.log('📤 Retrying store with X-PAYMENT header...');
|
|
83
|
+
const retryResponse = await this.http.post('/store', finalRequest, {
|
|
84
|
+
headers: {
|
|
85
|
+
'X-PAYMENT': encodedPayment
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
console.log('Server response after payment:', retryResponse.data);
|
|
89
|
+
const serverResult = retryResponse.data;
|
|
90
|
+
if (serverResult.ticket_id) {
|
|
91
|
+
if (waitForConfirmation) {
|
|
92
|
+
const taskInfo = await this.waitForTaskCompletion(serverResult.ticket_id);
|
|
93
|
+
if (taskInfo.result && taskInfo.result.results && taskInfo.result.results.length > 0) {
|
|
94
|
+
const firstResult = taskInfo.result.results[0];
|
|
95
|
+
return {
|
|
96
|
+
id: firstResult.id,
|
|
97
|
+
block_height: firstResult.celestia_height || 0,
|
|
98
|
+
transaction_hash: firstResult.blob_id || '',
|
|
99
|
+
celestia_height: firstResult.celestia_height || 0,
|
|
100
|
+
namespace: firstResult.namespace || '',
|
|
101
|
+
confirmed: firstResult.celestia_height > 0
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
return {
|
|
107
|
+
id: serverResult.ticket_id,
|
|
108
|
+
block_height: 0,
|
|
109
|
+
transaction_hash: '',
|
|
110
|
+
celestia_height: 0,
|
|
111
|
+
namespace: '',
|
|
112
|
+
confirmed: false,
|
|
113
|
+
ticket_id: serverResult.ticket_id
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
throw new types_1.OnChainDBError('No ticket_id in response after payment', 'STORE_ERROR');
|
|
118
|
+
}
|
|
119
|
+
async store(request, paymentCallback, waitForConfirmation = true) {
|
|
120
|
+
this.validateStoreRequest(request);
|
|
121
|
+
const resolvedRequest = {
|
|
122
|
+
...request,
|
|
123
|
+
root: this.resolveRoot(request)
|
|
124
|
+
};
|
|
125
|
+
try {
|
|
126
|
+
delete resolvedRequest.collection;
|
|
127
|
+
const response = await this.http.post('/store', resolvedRequest);
|
|
128
|
+
if (response.status === 402 && response.data) {
|
|
129
|
+
const v = await this.handlex420(response, paymentCallback, resolvedRequest, waitForConfirmation);
|
|
130
|
+
if (v) {
|
|
131
|
+
return v;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
console.log('Server response:', response.data);
|
|
135
|
+
const serverResult = response.data;
|
|
136
|
+
if (serverResult.ticket_id) {
|
|
137
|
+
console.log(`🎫 Got ticket ${serverResult.ticket_id}, polling for completion...`);
|
|
138
|
+
this.emit('transaction:queued', {
|
|
139
|
+
ticket_id: serverResult.ticket_id,
|
|
140
|
+
status: serverResult.status,
|
|
141
|
+
message: serverResult.message
|
|
142
|
+
});
|
|
143
|
+
if (waitForConfirmation) {
|
|
144
|
+
const taskInfo = await this.waitForTaskCompletion(serverResult.ticket_id);
|
|
145
|
+
if (taskInfo.result && taskInfo.result.results && taskInfo.result.results.length > 0) {
|
|
146
|
+
const firstResult = taskInfo.result.results[0];
|
|
147
|
+
const result = {
|
|
148
|
+
id: firstResult.id,
|
|
149
|
+
block_height: firstResult.celestia_height || 0,
|
|
150
|
+
transaction_hash: firstResult.blob_id || '',
|
|
151
|
+
celestia_height: firstResult.celestia_height || 0,
|
|
152
|
+
namespace: firstResult.namespace || '',
|
|
153
|
+
confirmed: firstResult.celestia_height > 0
|
|
154
|
+
};
|
|
155
|
+
this.emit('transaction:confirmed', {
|
|
156
|
+
id: result.id,
|
|
157
|
+
status: 'confirmed',
|
|
158
|
+
block_height: result.block_height,
|
|
159
|
+
transaction_hash: result.transaction_hash,
|
|
160
|
+
celestia_height: result.celestia_height
|
|
161
|
+
});
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
throw new types_1.OnChainDBError('Task completed but no storage results found', 'STORE_ERROR');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
return {
|
|
170
|
+
id: serverResult.ticket_id,
|
|
171
|
+
block_height: 0,
|
|
172
|
+
transaction_hash: '',
|
|
173
|
+
celestia_height: 0,
|
|
174
|
+
namespace: '',
|
|
175
|
+
confirmed: false,
|
|
176
|
+
ticket_id: serverResult.ticket_id
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const firstResult = serverResult.results && serverResult.results[0];
|
|
181
|
+
if (!firstResult) {
|
|
182
|
+
throw new types_1.OnChainDBError('No results returned from server', 'STORE_ERROR');
|
|
183
|
+
}
|
|
184
|
+
const result = {
|
|
185
|
+
id: firstResult.id,
|
|
186
|
+
block_height: firstResult.celestia_height || 0,
|
|
187
|
+
transaction_hash: firstResult.blob_id || '',
|
|
188
|
+
celestia_height: firstResult.celestia_height || 0,
|
|
189
|
+
namespace: firstResult.namespace || '',
|
|
190
|
+
confirmed: firstResult.celestia_height > 0
|
|
191
|
+
};
|
|
192
|
+
if (result.block_height === 0) {
|
|
193
|
+
this.emit('transaction:pending', {
|
|
194
|
+
id: result.id,
|
|
195
|
+
status: 'pending',
|
|
196
|
+
block_height: result.block_height,
|
|
197
|
+
transaction_hash: result.transaction_hash
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
this.emit('transaction:confirmed', {
|
|
202
|
+
id: result.id,
|
|
203
|
+
status: 'confirmed',
|
|
204
|
+
block_height: result.block_height,
|
|
205
|
+
transaction_hash: result.transaction_hash,
|
|
206
|
+
celestia_height: result.celestia_height
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
console.log('Transaction result:', result);
|
|
210
|
+
if (waitForConfirmation && result.block_height === 0) {
|
|
211
|
+
return await this.waitForConfirmation(result.transaction_hash);
|
|
212
|
+
}
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
console.log("HERE", error);
|
|
217
|
+
if (error?.response) {
|
|
218
|
+
const err = error;
|
|
219
|
+
if (err.response?.status === 402 && err?.response?.data) {
|
|
220
|
+
const v = await this.handlex420(err.response, paymentCallback, resolvedRequest, waitForConfirmation);
|
|
221
|
+
if (v) {
|
|
222
|
+
return v;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const dbError = error instanceof types_1.OnChainDBError ? error :
|
|
227
|
+
new types_1.OnChainDBError('Failed to store data', 'STORE_ERROR');
|
|
228
|
+
this.emit('error', dbError);
|
|
229
|
+
throw dbError;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async storeAndConfirm(request) {
|
|
233
|
+
return this.store(request, undefined, true);
|
|
234
|
+
}
|
|
235
|
+
async waitForConfirmation(transactionHash, maxWaitTime = 300000) {
|
|
236
|
+
const startTime = Date.now();
|
|
237
|
+
const pollInterval = 3000;
|
|
238
|
+
console.log(`🔄 Waiting for transaction ${transactionHash} confirmation...`);
|
|
239
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
240
|
+
try {
|
|
241
|
+
const rpcUrl = 'https://celestia-mocha-rpc.publicnode.com:443';
|
|
242
|
+
const txUrl = `${rpcUrl}/tx?hash=0x${transactionHash}`;
|
|
243
|
+
console.log(`🔍 Checking transaction status: attempt ${Math.floor((Date.now() - startTime) / pollInterval) + 1}`);
|
|
244
|
+
const response = await axios_1.default.get(txUrl);
|
|
245
|
+
if (response.data?.result && response.data.result !== null) {
|
|
246
|
+
const txResult = response.data.result;
|
|
247
|
+
const height = parseInt(txResult.height);
|
|
248
|
+
if (height > 0) {
|
|
249
|
+
console.log(`✅ Transaction ${transactionHash} confirmed at height ${height}`);
|
|
250
|
+
const confirmedTx = {
|
|
251
|
+
id: transactionHash,
|
|
252
|
+
namespace: '',
|
|
253
|
+
block_height: height,
|
|
254
|
+
transaction_hash: transactionHash,
|
|
255
|
+
celestia_height: height,
|
|
256
|
+
confirmed: true
|
|
257
|
+
};
|
|
258
|
+
this.emit('transaction:confirmed', {
|
|
259
|
+
id: transactionHash,
|
|
260
|
+
status: 'confirmed',
|
|
261
|
+
block_height: height,
|
|
262
|
+
transaction_hash: transactionHash
|
|
263
|
+
});
|
|
264
|
+
return confirmedTx;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
console.log(`⏳ Transaction still pending, waiting ${pollInterval}ms...`);
|
|
268
|
+
this.emit('transaction:pending', {
|
|
269
|
+
id: transactionHash,
|
|
270
|
+
status: 'pending',
|
|
271
|
+
block_height: 0,
|
|
272
|
+
transaction_hash: transactionHash
|
|
273
|
+
});
|
|
274
|
+
await this.sleep(pollInterval);
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
if (Date.now() - startTime >= maxWaitTime) {
|
|
278
|
+
throw new types_1.TransactionError(`Transaction confirmation timeout after ${maxWaitTime}ms`, transactionHash);
|
|
279
|
+
}
|
|
280
|
+
console.log(`⚠️ Error checking transaction (will retry): ${error}`);
|
|
281
|
+
await this.sleep(pollInterval);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
throw new types_1.TransactionError(`Transaction confirmation timeout after ${maxWaitTime}ms`, transactionHash);
|
|
285
|
+
}
|
|
286
|
+
async getIndexCostEstimate(request) {
|
|
287
|
+
try {
|
|
288
|
+
const response = await this.http.post('/api/indexes/cost-estimate', request);
|
|
289
|
+
return response.data;
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
throw error instanceof types_1.OnChainDBError ? error :
|
|
293
|
+
new types_1.OnChainDBError('Failed to get index cost estimate', 'COST_ESTIMATE_ERROR');
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async getStorageCostEstimate(dataSizeBytes) {
|
|
297
|
+
try {
|
|
298
|
+
const response = await this.http.post('/api/storage/cost-estimate', {
|
|
299
|
+
data_size_bytes: dataSizeBytes
|
|
300
|
+
});
|
|
301
|
+
return response.data;
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
throw error instanceof types_1.OnChainDBError ? error :
|
|
305
|
+
new types_1.OnChainDBError('Failed to get storage cost estimate', 'COST_ESTIMATE_ERROR');
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async getPricingQuote(request) {
|
|
309
|
+
try {
|
|
310
|
+
const response = await this.http.post('/api/pricing/quote', request);
|
|
311
|
+
return response.data;
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
throw error instanceof types_1.OnChainDBError ? error :
|
|
315
|
+
new types_1.OnChainDBError('Failed to get pricing quote', 'PRICING_QUOTE_ERROR');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
async createIndex(request) {
|
|
319
|
+
try {
|
|
320
|
+
const appId = this.config.appId || 'default';
|
|
321
|
+
const response = await this.http.post(`/api/apps/${appId}/indexes`, request);
|
|
322
|
+
return response.data;
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
throw error instanceof types_1.OnChainDBError ? error :
|
|
326
|
+
new types_1.OnChainDBError('Failed to create index', 'INDEX_ERROR');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async createRelation(request) {
|
|
330
|
+
try {
|
|
331
|
+
const appId = this.config.appId || 'default';
|
|
332
|
+
const response = await this.http.post(`/api/apps/${appId}/relations`, request);
|
|
333
|
+
return response.data;
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
throw error instanceof types_1.OnChainDBError ? error :
|
|
337
|
+
new types_1.OnChainDBError('Failed to create relation', 'RELATION_ERROR');
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
async health() {
|
|
341
|
+
try {
|
|
342
|
+
const response = await this.http.get('/');
|
|
343
|
+
return response.data;
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
throw new types_1.OnChainDBError('Health check failed', 'HEALTH_ERROR');
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
async query(request) {
|
|
350
|
+
try {
|
|
351
|
+
const queryBuilder = this.queryBuilder();
|
|
352
|
+
const root = this.resolveRoot(request);
|
|
353
|
+
if (request.filters) {
|
|
354
|
+
queryBuilder.find(builder => {
|
|
355
|
+
const conditions = Object.entries(request.filters).map(([field, value]) => query_sdk_1.LogicalOperator.Condition(builder.field(field).equals(value)));
|
|
356
|
+
return conditions.length === 1 ? conditions[0] : query_sdk_1.LogicalOperator.And(conditions);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
if (request.limit) {
|
|
360
|
+
queryBuilder.limit(request.limit);
|
|
361
|
+
}
|
|
362
|
+
if (request.offset) {
|
|
363
|
+
queryBuilder.offset(request.offset);
|
|
364
|
+
}
|
|
365
|
+
return await queryBuilder.execute();
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
throw error instanceof types_1.OnChainDBError ? error :
|
|
369
|
+
new types_1.OnChainDBError('Failed to execute query', 'QUERY_ERROR');
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
queryBuilder() {
|
|
373
|
+
const { AxiosHttpClient } = require('./query-sdk/adapters/HttpClientAdapter');
|
|
374
|
+
const httpClient = new AxiosHttpClient(this.http);
|
|
375
|
+
return new query_sdk_1.QueryBuilder(httpClient, this.config.endpoint, this.config.appId);
|
|
376
|
+
}
|
|
377
|
+
batch() {
|
|
378
|
+
const { BatchOperations } = require('./batch');
|
|
379
|
+
return new BatchOperations(this);
|
|
380
|
+
}
|
|
381
|
+
async findUnique(collection, where) {
|
|
382
|
+
try {
|
|
383
|
+
let queryBuilder = this.queryBuilder().collection(collection);
|
|
384
|
+
for (const [field, value] of Object.entries(where)) {
|
|
385
|
+
queryBuilder = queryBuilder.whereField(field).equals(value);
|
|
386
|
+
}
|
|
387
|
+
return await queryBuilder.selectAll().executeUnique();
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
console.error(`findUnique error for ${collection}:`, error);
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
async findMany(collection, where = {}, options = {}) {
|
|
395
|
+
try {
|
|
396
|
+
let queryBuilder = this.queryBuilder().collection(collection);
|
|
397
|
+
for (const [field, value] of Object.entries(where)) {
|
|
398
|
+
queryBuilder = queryBuilder.whereField(field).equals(value);
|
|
399
|
+
}
|
|
400
|
+
if (options.limit) {
|
|
401
|
+
queryBuilder = queryBuilder.limit(options.limit);
|
|
402
|
+
}
|
|
403
|
+
if (options.offset) {
|
|
404
|
+
queryBuilder = queryBuilder.offset(options.offset);
|
|
405
|
+
}
|
|
406
|
+
const result = await queryBuilder.selectAll().execute();
|
|
407
|
+
if (!result.records) {
|
|
408
|
+
return [];
|
|
409
|
+
}
|
|
410
|
+
let records = result.records;
|
|
411
|
+
if (options.sort) {
|
|
412
|
+
records = records.sort((a, b) => {
|
|
413
|
+
const aVal = a[options.sort.field];
|
|
414
|
+
const bVal = b[options.sort.field];
|
|
415
|
+
if (options.sort.order === 'asc') {
|
|
416
|
+
return aVal > bVal ? 1 : -1;
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
return aVal < bVal ? 1 : -1;
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
return records;
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
console.error(`findMany error for ${collection}:`, error);
|
|
427
|
+
return [];
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
async createDocument(collection, data, paymentProof, options) {
|
|
431
|
+
const document = {
|
|
432
|
+
id: options?.idGenerator ? options.idGenerator() : this.generateId(),
|
|
433
|
+
...data,
|
|
434
|
+
createdAt: new Date().toISOString(),
|
|
435
|
+
updatedAt: new Date().toISOString(),
|
|
436
|
+
};
|
|
437
|
+
await this.store({
|
|
438
|
+
collection,
|
|
439
|
+
data: [document],
|
|
440
|
+
...paymentProof
|
|
441
|
+
});
|
|
442
|
+
return document;
|
|
443
|
+
}
|
|
444
|
+
async updateDocument(collection, where, data, paymentProof) {
|
|
445
|
+
const current = await this.findUnique(collection, where);
|
|
446
|
+
if (!current) {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
const updated = {
|
|
450
|
+
...current,
|
|
451
|
+
...data,
|
|
452
|
+
updatedAt: new Date().toISOString(),
|
|
453
|
+
};
|
|
454
|
+
await this.store({
|
|
455
|
+
collection,
|
|
456
|
+
data: [updated],
|
|
457
|
+
...paymentProof
|
|
458
|
+
});
|
|
459
|
+
return updated;
|
|
460
|
+
}
|
|
461
|
+
async upsertDocument(collection, where, create, update, paymentProof, options) {
|
|
462
|
+
const existing = await this.findUnique(collection, where);
|
|
463
|
+
if (existing) {
|
|
464
|
+
return (await this.updateDocument(collection, where, update, paymentProof));
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
return await this.createDocument(collection, create, paymentProof, options);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
async deleteDocument(collection, where, paymentProof) {
|
|
471
|
+
const existing = await this.findUnique(collection, where);
|
|
472
|
+
if (!existing) {
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
const deleted = {
|
|
476
|
+
...existing,
|
|
477
|
+
deleted: true,
|
|
478
|
+
updatedAt: new Date().toISOString(),
|
|
479
|
+
};
|
|
480
|
+
await this.store({
|
|
481
|
+
collection,
|
|
482
|
+
data: [deleted],
|
|
483
|
+
...paymentProof
|
|
484
|
+
});
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
async countDocuments(collection, where = {}) {
|
|
488
|
+
try {
|
|
489
|
+
let queryBuilder = this.queryBuilder().collection(collection);
|
|
490
|
+
for (const [field, value] of Object.entries(where)) {
|
|
491
|
+
queryBuilder = queryBuilder.whereField(field).equals(value);
|
|
492
|
+
}
|
|
493
|
+
return await queryBuilder.count();
|
|
494
|
+
}
|
|
495
|
+
catch (error) {
|
|
496
|
+
console.error(`countDocuments error for ${collection}:`, error);
|
|
497
|
+
return 0;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
generateId() {
|
|
501
|
+
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
502
|
+
let id = '';
|
|
503
|
+
for (let i = 0; i < 24; i++) {
|
|
504
|
+
id += chars[Math.floor(Math.random() * chars.length)];
|
|
505
|
+
}
|
|
506
|
+
return id;
|
|
507
|
+
}
|
|
508
|
+
async getTaskStatus(ticketId) {
|
|
509
|
+
try {
|
|
510
|
+
const response = await this.http.get(`/task/${ticketId}`);
|
|
511
|
+
return response.data;
|
|
512
|
+
}
|
|
513
|
+
catch (error) {
|
|
514
|
+
throw error instanceof types_1.OnChainDBError ? error :
|
|
515
|
+
new types_1.OnChainDBError('Failed to get task status', 'TASK_STATUS_ERROR');
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
async waitForTaskCompletion(ticketId, pollInterval = 2000, maxWaitTime = 600000) {
|
|
519
|
+
const startTime = Date.now();
|
|
520
|
+
console.log(`🔄 Waiting for task ${ticketId} to complete...`);
|
|
521
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
522
|
+
try {
|
|
523
|
+
const taskInfo = await this.getTaskStatus(ticketId);
|
|
524
|
+
if (typeof taskInfo.status === 'object') {
|
|
525
|
+
console.log(`📊 Task ${ticketId} status:`, JSON.stringify(taskInfo.status));
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
console.log(`📊 Task ${ticketId} status: ${taskInfo.status}`);
|
|
529
|
+
}
|
|
530
|
+
if (taskInfo.status === 'Completed') {
|
|
531
|
+
console.log(`✅ Task ${ticketId} completed successfully`);
|
|
532
|
+
return taskInfo;
|
|
533
|
+
}
|
|
534
|
+
if (typeof taskInfo.status === 'object' && 'Failed' in taskInfo.status) {
|
|
535
|
+
const error = taskInfo.status.Failed.error;
|
|
536
|
+
console.error(`🚫 Task ${ticketId} failed: ${error}`);
|
|
537
|
+
throw new types_1.OnChainDBError(`Task failed: ${error}`, 'TASK_FAILED');
|
|
538
|
+
}
|
|
539
|
+
if (typeof taskInfo.status === 'string' && taskInfo.status.toLowerCase().includes('error')) {
|
|
540
|
+
console.error(`🚫 Task ${ticketId} has error status: ${taskInfo.status}`);
|
|
541
|
+
throw new types_1.OnChainDBError(`Task error: ${taskInfo.status}`, 'TASK_FAILED');
|
|
542
|
+
}
|
|
543
|
+
await this.sleep(pollInterval);
|
|
544
|
+
}
|
|
545
|
+
catch (error) {
|
|
546
|
+
console.error(`❌ Error polling task ${ticketId}:`, error);
|
|
547
|
+
if (error instanceof types_1.OnChainDBError) {
|
|
548
|
+
if (error.code === 'TASK_FAILED' || error.statusCode === 404 || error.statusCode === 400) {
|
|
549
|
+
console.error(`🚫 Stopping polling due to permanent error: ${error.message}`);
|
|
550
|
+
throw error;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (Date.now() - startTime >= maxWaitTime) {
|
|
554
|
+
throw new types_1.OnChainDBError(`Task completion timeout after ${maxWaitTime}ms. Last error: ${error instanceof Error ? error.message : String(error)}`, 'TASK_TIMEOUT');
|
|
555
|
+
}
|
|
556
|
+
console.warn(`⚠️ Temporary error polling task ${ticketId}, retrying in ${pollInterval}ms...`);
|
|
557
|
+
await this.sleep(pollInterval);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
throw new types_1.OnChainDBError(`Task completion timeout after ${maxWaitTime}ms`, 'TASK_TIMEOUT');
|
|
561
|
+
}
|
|
562
|
+
async uploadBlob(request) {
|
|
563
|
+
try {
|
|
564
|
+
const appId = this.config.appId;
|
|
565
|
+
if (!appId) {
|
|
566
|
+
throw new types_1.ValidationError('appId must be configured to upload blobs');
|
|
567
|
+
}
|
|
568
|
+
const formData = new FormData();
|
|
569
|
+
formData.append('blob', request.blob);
|
|
570
|
+
if (request.metadata) {
|
|
571
|
+
formData.append('metadata', JSON.stringify(request.metadata));
|
|
572
|
+
}
|
|
573
|
+
formData.append('payment_tx_hash', request.payment_tx_hash);
|
|
574
|
+
formData.append('user_address', request.user_address);
|
|
575
|
+
formData.append('broker_address', request.broker_address);
|
|
576
|
+
formData.append('amount_utia', String(request.amount_utia));
|
|
577
|
+
const response = await this.http.post(`/api/apps/${appId}/blobs/${request.collection}`, formData, {
|
|
578
|
+
headers: {
|
|
579
|
+
'Content-Type': 'multipart/form-data',
|
|
580
|
+
'X-App-Key': this.config.appKey
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
return response.data;
|
|
584
|
+
}
|
|
585
|
+
catch (error) {
|
|
586
|
+
throw error instanceof types_1.OnChainDBError ? error :
|
|
587
|
+
new types_1.OnChainDBError('Failed to upload blob', 'BLOB_UPLOAD_ERROR');
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
async retrieveBlob(request) {
|
|
591
|
+
try {
|
|
592
|
+
const appId = this.config.appId;
|
|
593
|
+
if (!appId) {
|
|
594
|
+
throw new types_1.ValidationError('appId must be configured to retrieve blobs');
|
|
595
|
+
}
|
|
596
|
+
const response = await this.http.get(`/api/apps/${appId}/blobs/${request.collection}/${request.blob_id}`, {
|
|
597
|
+
responseType: 'arraybuffer',
|
|
598
|
+
headers: {
|
|
599
|
+
'X-App-Key': this.config.appKey
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
if (typeof global.window !== 'undefined' && typeof Blob !== 'undefined') {
|
|
603
|
+
const contentType = response.headers['content-type'] || 'application/octet-stream';
|
|
604
|
+
return new Blob([response.data], { type: contentType });
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
return Buffer.from(response.data);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
throw error instanceof types_1.OnChainDBError ? error :
|
|
612
|
+
new types_1.OnChainDBError('Failed to retrieve blob', 'BLOB_RETRIEVAL_ERROR');
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
async queryBlobMetadata(collection, where = {}) {
|
|
616
|
+
return await this.findMany(collection, where);
|
|
617
|
+
}
|
|
618
|
+
validateStoreRequest(request) {
|
|
619
|
+
if (!request.root && !request.collection) {
|
|
620
|
+
throw new types_1.ValidationError('Either root or collection must be provided');
|
|
621
|
+
}
|
|
622
|
+
if (request.root && typeof request.root !== 'string') {
|
|
623
|
+
throw new types_1.ValidationError('Root must be a valid string in format "app::collection"');
|
|
624
|
+
}
|
|
625
|
+
if (request.collection && typeof request.collection !== 'string') {
|
|
626
|
+
throw new types_1.ValidationError('Collection must be a valid string');
|
|
627
|
+
}
|
|
628
|
+
if (!request.data || !Array.isArray(request.data)) {
|
|
629
|
+
throw new types_1.ValidationError('Data must be an array of objects');
|
|
630
|
+
}
|
|
631
|
+
if (request.data.length === 0) {
|
|
632
|
+
throw new types_1.ValidationError('Data array cannot be empty');
|
|
633
|
+
}
|
|
634
|
+
for (const item of request.data) {
|
|
635
|
+
if (!item || typeof item !== 'object') {
|
|
636
|
+
throw new types_1.ValidationError('Each data item must be a valid object');
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
const dataSize = JSON.stringify(request.data).length;
|
|
640
|
+
if (dataSize > 5 * 1024 * 1024) {
|
|
641
|
+
throw new types_1.ValidationError('Total data size exceeds 5MB limit');
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
handleHttpError(error) {
|
|
645
|
+
console.error(error);
|
|
646
|
+
if (error.response) {
|
|
647
|
+
const statusCode = error.response.status;
|
|
648
|
+
const message = error.response.data?.error || error.message;
|
|
649
|
+
if (statusCode >= 400 && statusCode < 500) {
|
|
650
|
+
return new types_1.ValidationError(message, error.response.data);
|
|
651
|
+
}
|
|
652
|
+
return new types_1.OnChainDBError(message, 'HTTP_ERROR', statusCode, error.response.data);
|
|
653
|
+
}
|
|
654
|
+
if (error.request) {
|
|
655
|
+
return new types_1.OnChainDBError('Network error - could not reach OnChainDB service', 'NETWORK_ERROR');
|
|
656
|
+
}
|
|
657
|
+
return new types_1.OnChainDBError(error.message, 'UNKNOWN_ERROR');
|
|
658
|
+
}
|
|
659
|
+
sleep(ms) {
|
|
660
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
661
|
+
}
|
|
662
|
+
buildRoot(collection) {
|
|
663
|
+
if (!this.config.appId) {
|
|
664
|
+
return collection;
|
|
665
|
+
}
|
|
666
|
+
return `${this.config.appId}::${collection}`;
|
|
667
|
+
}
|
|
668
|
+
resolveRoot(request) {
|
|
669
|
+
if (request.root) {
|
|
670
|
+
return request.root;
|
|
671
|
+
}
|
|
672
|
+
if (request.collection) {
|
|
673
|
+
return this.buildRoot(request.collection);
|
|
674
|
+
}
|
|
675
|
+
throw new types_1.ValidationError('Either root or collection must be provided');
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
exports.OnChainDBClient = OnChainDBClient;
|
|
679
|
+
//# sourceMappingURL=client.js.map
|