@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/README.md
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
# OnChainDB TypeScript SDK
|
|
2
|
+
|
|
3
|
+
A TypeScript SDK for OnChainDB - the decentralized database built on Celestia blockchain with x402 payment protocol integration.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @onchaindb/sdk
|
|
9
|
+
# or
|
|
10
|
+
yarn add @onchaindb/sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { createClient } from '@onchaindb/sdk';
|
|
17
|
+
|
|
18
|
+
const db = createClient({
|
|
19
|
+
endpoint: 'https://api.onchaindb.io',
|
|
20
|
+
appId: 'my_app',
|
|
21
|
+
apiKey: 'your_api_key'
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Store data
|
|
25
|
+
await db.store({
|
|
26
|
+
collection: 'messages',
|
|
27
|
+
data: [{ message: 'Hello OnChainDB!', author: 'alice' }]
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Query data
|
|
31
|
+
const result = await db.queryBuilder()
|
|
32
|
+
.collection('messages')
|
|
33
|
+
.whereField('author').equals('alice')
|
|
34
|
+
.selectAll()
|
|
35
|
+
.limit(10)
|
|
36
|
+
.execute();
|
|
37
|
+
|
|
38
|
+
console.log(result.records);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Configuration
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
interface OnChainDBConfig {
|
|
45
|
+
endpoint: string; // OnChainDB server endpoint
|
|
46
|
+
appId?: string; // Application ID
|
|
47
|
+
apiKey?: string; // API key for authentication
|
|
48
|
+
timeout?: number; // Request timeout (default: 30000ms)
|
|
49
|
+
retryCount?: number; // Retry attempts (default: 3)
|
|
50
|
+
retryDelay?: number; // Retry delay (default: 1000ms)
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Storing Data
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// Basic store
|
|
58
|
+
const result = await db.store({
|
|
59
|
+
collection: 'tweets',
|
|
60
|
+
data: [
|
|
61
|
+
{ id: 'tweet_1', content: 'Hello!', author: 'alice' },
|
|
62
|
+
{ id: 'tweet_2', content: 'World!', author: 'bob' }
|
|
63
|
+
]
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Store with payment (x402 protocol)
|
|
67
|
+
const result = await db.store(
|
|
68
|
+
{ collection: 'tweets', data: [{ content: 'Paid tweet' }] },
|
|
69
|
+
async (quote) => {
|
|
70
|
+
// Pay via wallet (e.g., Keplr)
|
|
71
|
+
const txHash = await wallet.signAndBroadcast(
|
|
72
|
+
quote.broker_address,
|
|
73
|
+
`${quote.total_cost_utia}utia`,
|
|
74
|
+
'OnChainDB storage'
|
|
75
|
+
);
|
|
76
|
+
return { txHash, network: 'mocha-4' };
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Query Builder
|
|
82
|
+
|
|
83
|
+
The SDK provides a fluent query builder for constructing queries. For comprehensive query documentation including all operators, nested queries, and advanced patterns, see the [Query SDK Reference](./src/query-sdk/README.md).
|
|
84
|
+
|
|
85
|
+
### Basic Queries
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const result = await db.queryBuilder()
|
|
89
|
+
.collection('users')
|
|
90
|
+
.whereField('status').equals('active')
|
|
91
|
+
.selectFields(['id', 'name', 'email'])
|
|
92
|
+
.limit(50)
|
|
93
|
+
.execute();
|
|
94
|
+
|
|
95
|
+
// Access results - records have flat structure
|
|
96
|
+
result.records.forEach(user => {
|
|
97
|
+
console.log(user.name, user.email);
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Query Operators
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
.equals(value)
|
|
105
|
+
.notEquals(value)
|
|
106
|
+
.greaterThan(value)
|
|
107
|
+
.lessThan(value)
|
|
108
|
+
.contains(value)
|
|
109
|
+
.startsWith(value)
|
|
110
|
+
.isNull()
|
|
111
|
+
.isNotNull()
|
|
112
|
+
.in(values)
|
|
113
|
+
.notIn(values)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Complex Queries with Logical Operators
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { LogicalOperator } from '@onchaindb/sdk';
|
|
120
|
+
|
|
121
|
+
const result = await db.queryBuilder()
|
|
122
|
+
.collection('posts')
|
|
123
|
+
.find(builder =>
|
|
124
|
+
LogicalOperator.And([
|
|
125
|
+
LogicalOperator.Condition(builder.field('published').equals(true)),
|
|
126
|
+
LogicalOperator.Or([
|
|
127
|
+
LogicalOperator.Condition(builder.field('category').equals('tech')),
|
|
128
|
+
LogicalOperator.Condition(builder.field('views').greaterThan(1000))
|
|
129
|
+
])
|
|
130
|
+
])
|
|
131
|
+
)
|
|
132
|
+
.selectFields(['title', 'content', 'author'])
|
|
133
|
+
.limit(20)
|
|
134
|
+
.execute();
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Server-Side JOINs
|
|
138
|
+
|
|
139
|
+
Server-side JOINs execute on the backend for optimal performance. Use `$data.fieldname` to reference parent record fields.
|
|
140
|
+
|
|
141
|
+
### One-to-One JOIN (joinOne)
|
|
142
|
+
|
|
143
|
+
Returns a single object or null.
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// Get tweets with author profile
|
|
147
|
+
const result = await db.queryBuilder()
|
|
148
|
+
.collection('tweets')
|
|
149
|
+
.joinOne('author_info', 'users')
|
|
150
|
+
.onField('address').equals('$data.author')
|
|
151
|
+
.selectFields(['address', 'display_name', 'avatar_url', 'verified'])
|
|
152
|
+
.build()
|
|
153
|
+
.selectAll()
|
|
154
|
+
.limit(20)
|
|
155
|
+
.execute();
|
|
156
|
+
|
|
157
|
+
// Result structure:
|
|
158
|
+
// {
|
|
159
|
+
// id: 'tweet_123',
|
|
160
|
+
// content: 'Hello world!',
|
|
161
|
+
// author: 'celestia1abc...',
|
|
162
|
+
// author_info: {
|
|
163
|
+
// address: 'celestia1abc...',
|
|
164
|
+
// display_name: 'Alice',
|
|
165
|
+
// avatar_url: 'https://...',
|
|
166
|
+
// verified: true
|
|
167
|
+
// }
|
|
168
|
+
// }
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### One-to-Many JOIN (joinMany)
|
|
172
|
+
|
|
173
|
+
Returns an array of related records.
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// Get users with their tweets
|
|
177
|
+
const result = await db.queryBuilder()
|
|
178
|
+
.collection('users')
|
|
179
|
+
.whereField('address').equals(userAddress)
|
|
180
|
+
.joinMany('user_tweets', 'tweets')
|
|
181
|
+
.onField('author').equals('$data.address')
|
|
182
|
+
.selectFields(['id', 'content', 'created_at'])
|
|
183
|
+
.build()
|
|
184
|
+
.selectAll()
|
|
185
|
+
.execute();
|
|
186
|
+
|
|
187
|
+
// Result structure:
|
|
188
|
+
// {
|
|
189
|
+
// address: 'celestia1abc...',
|
|
190
|
+
// display_name: 'Alice',
|
|
191
|
+
// user_tweets: [
|
|
192
|
+
// { id: 'tweet_1', content: 'Hello!', created_at: '...' },
|
|
193
|
+
// { id: 'tweet_2', content: 'World!', created_at: '...' }
|
|
194
|
+
// ]
|
|
195
|
+
// }
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Multiple JOINs
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
// Timeline with likes, replies, and author info
|
|
202
|
+
const result = await db.queryBuilder()
|
|
203
|
+
.collection('tweets')
|
|
204
|
+
.whereField('reply_to_id').isNull()
|
|
205
|
+
.joinOne('author_info', 'users')
|
|
206
|
+
.onField('address').equals('$data.author')
|
|
207
|
+
.selectFields(['display_name', 'avatar_url', 'verified'])
|
|
208
|
+
.build()
|
|
209
|
+
.joinMany('likes', 'likes')
|
|
210
|
+
.onField('tweet_id').equals('$data.id')
|
|
211
|
+
.selectFields(['user', 'created_at'])
|
|
212
|
+
.build()
|
|
213
|
+
.joinMany('replies', 'tweets')
|
|
214
|
+
.onField('reply_to_id').equals('$data.id')
|
|
215
|
+
.selectFields(['id', 'author', 'content'])
|
|
216
|
+
.build()
|
|
217
|
+
.selectAll()
|
|
218
|
+
.offset(offset)
|
|
219
|
+
.limit(limit)
|
|
220
|
+
.execute();
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Nested JOINs
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// Get tweet with replies, each reply includes author info
|
|
227
|
+
const result = await db.queryBuilder()
|
|
228
|
+
.collection('tweets')
|
|
229
|
+
.whereField('id').equals(tweetId)
|
|
230
|
+
.joinMany('replies', 'tweets')
|
|
231
|
+
.onField('reply_to_id').equals('$data.id')
|
|
232
|
+
.selectAll()
|
|
233
|
+
// Nested: get author for each reply
|
|
234
|
+
.joinOne('author_info', 'users')
|
|
235
|
+
.onField('address').equals('$data.author')
|
|
236
|
+
.selectFields(['display_name', 'avatar_url', 'verified'])
|
|
237
|
+
.build()
|
|
238
|
+
.build()
|
|
239
|
+
.selectAll()
|
|
240
|
+
.limit(1)
|
|
241
|
+
.execute();
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Self-Referential JOINs
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
// Get tweets with quoted tweet info
|
|
248
|
+
const result = await db.queryBuilder()
|
|
249
|
+
.collection('tweets')
|
|
250
|
+
.whereField('quote_tweet_id').isNotNull()
|
|
251
|
+
.joinOne('quote_tweet', 'tweets')
|
|
252
|
+
.onField('id').equals('$data.quote_tweet_id')
|
|
253
|
+
.selectFields(['id', 'content', 'author', 'created_at'])
|
|
254
|
+
// Nested: get quoted tweet's author
|
|
255
|
+
.joinOne('author_info', 'users')
|
|
256
|
+
.onField('address').equals('$data.author')
|
|
257
|
+
.selectFields(['display_name', 'avatar_url'])
|
|
258
|
+
.build()
|
|
259
|
+
.build()
|
|
260
|
+
.selectAll()
|
|
261
|
+
.execute();
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Blob Storage
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// Get pricing quote
|
|
268
|
+
const quote = await db.getPricingQuote({
|
|
269
|
+
app_id: 'my_app',
|
|
270
|
+
operation_type: 'write',
|
|
271
|
+
size_kb: Math.ceil(file.size / 1024),
|
|
272
|
+
collection: 'images'
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const costInUtia = Math.ceil(quote.total_cost * 1_000_000);
|
|
276
|
+
|
|
277
|
+
// Pay for storage
|
|
278
|
+
const txHash = await wallet.signAndBroadcast(
|
|
279
|
+
brokerAddress,
|
|
280
|
+
`${costInUtia}utia`,
|
|
281
|
+
'Image upload'
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
// Upload blob
|
|
285
|
+
const upload = await db.uploadBlob({
|
|
286
|
+
collection: 'images',
|
|
287
|
+
blob: file,
|
|
288
|
+
metadata: { user_address: userAddress },
|
|
289
|
+
payment_tx_hash: txHash,
|
|
290
|
+
user_address: userAddress,
|
|
291
|
+
broker_address: brokerAddress,
|
|
292
|
+
amount_utia: costInUtia
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Wait for completion
|
|
296
|
+
const task = await db.waitForTaskCompletion(upload.ticket_id);
|
|
297
|
+
|
|
298
|
+
if (task.status === 'Completed') {
|
|
299
|
+
console.log('Blob ID:', upload.blob_id);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Retrieve blob
|
|
303
|
+
const blob = await db.retrieveBlob({
|
|
304
|
+
collection: 'images',
|
|
305
|
+
blob_id: upload.blob_id
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Task Tracking
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
// Check task status
|
|
313
|
+
const task = await db.getTaskStatus(ticket_id);
|
|
314
|
+
console.log('Status:', task.status);
|
|
315
|
+
// Pending, PaymentBroadcast, PaymentConfirming, Completed, Failed
|
|
316
|
+
|
|
317
|
+
// Wait with polling
|
|
318
|
+
const completed = await db.waitForTaskCompletion(
|
|
319
|
+
ticket_id,
|
|
320
|
+
2000, // Poll interval (ms)
|
|
321
|
+
600000 // Max wait (ms)
|
|
322
|
+
);
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Cost Estimation
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
const quote = await db.getPricingQuote({
|
|
329
|
+
app_id: 'my_app',
|
|
330
|
+
operation_type: 'write',
|
|
331
|
+
size_kb: 50,
|
|
332
|
+
collection: 'tweets'
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
console.log('Total cost:', quote.total_cost, 'TIA');
|
|
336
|
+
console.log('In utia:', Math.ceil(quote.total_cost * 1_000_000));
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Real-World Example: Social App
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
import { createClient, OnChainDBClient } from '@onchaindb/sdk';
|
|
343
|
+
|
|
344
|
+
class SocialService {
|
|
345
|
+
private client: OnChainDBClient;
|
|
346
|
+
|
|
347
|
+
constructor(endpoint: string, appId: string, apiKey?: string) {
|
|
348
|
+
this.client = createClient({ endpoint, appId, apiKey });
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async getTimeline(limit = 20, offset = 0) {
|
|
352
|
+
const result = await this.client.queryBuilder()
|
|
353
|
+
.collection('tweets')
|
|
354
|
+
.whereField('reply_to_id').isNull()
|
|
355
|
+
.joinOne('author_info', 'users')
|
|
356
|
+
.onField('address').equals('$data.author')
|
|
357
|
+
.selectFields(['display_name', 'avatar_url', 'verified'])
|
|
358
|
+
.build()
|
|
359
|
+
.joinMany('likes', 'likes')
|
|
360
|
+
.onField('tweet_id').equals('$data.id')
|
|
361
|
+
.selectFields(['user'])
|
|
362
|
+
.build()
|
|
363
|
+
.joinMany('replies', 'tweets')
|
|
364
|
+
.onField('reply_to_id').equals('$data.id')
|
|
365
|
+
.selectFields(['id'])
|
|
366
|
+
.build()
|
|
367
|
+
.selectAll()
|
|
368
|
+
.offset(offset)
|
|
369
|
+
.limit(limit)
|
|
370
|
+
.execute();
|
|
371
|
+
|
|
372
|
+
return result.records.map(tweet => ({
|
|
373
|
+
...tweet,
|
|
374
|
+
like_count: tweet.likes?.length || 0,
|
|
375
|
+
reply_count: tweet.replies?.length || 0
|
|
376
|
+
}));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async createTweet(content: string, author: string, paymentCallback: Function) {
|
|
380
|
+
const tweet = {
|
|
381
|
+
id: `tweet_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
382
|
+
content,
|
|
383
|
+
author,
|
|
384
|
+
created_at: new Date().toISOString()
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
await this.client.store(
|
|
388
|
+
{ collection: 'tweets', data: [tweet] },
|
|
389
|
+
paymentCallback
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
return tweet;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async getUserProfile(address: string) {
|
|
396
|
+
const result = await this.client.queryBuilder()
|
|
397
|
+
.collection('users')
|
|
398
|
+
.whereField('address').equals(address)
|
|
399
|
+
.joinMany('tweets', 'tweets')
|
|
400
|
+
.onField('author').equals('$data.address')
|
|
401
|
+
.selectAll()
|
|
402
|
+
.build()
|
|
403
|
+
.joinMany('followers', 'follows')
|
|
404
|
+
.onField('following').equals('$data.address')
|
|
405
|
+
.selectFields(['follower'])
|
|
406
|
+
.build()
|
|
407
|
+
.joinMany('following', 'follows')
|
|
408
|
+
.onField('follower').equals('$data.address')
|
|
409
|
+
.selectFields(['following'])
|
|
410
|
+
.build()
|
|
411
|
+
.selectAll()
|
|
412
|
+
.limit(1)
|
|
413
|
+
.execute();
|
|
414
|
+
|
|
415
|
+
if (!result.records.length) return null;
|
|
416
|
+
|
|
417
|
+
const user = result.records[0];
|
|
418
|
+
return {
|
|
419
|
+
...user,
|
|
420
|
+
follower_count: user.followers?.length || 0,
|
|
421
|
+
following_count: user.following?.length || 0
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
## Error Handling
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
import { OnChainDBError, ValidationError } from '@onchaindb/sdk';
|
|
431
|
+
|
|
432
|
+
try {
|
|
433
|
+
await db.store({ collection: 'test', data: [{ test: 'data' }] });
|
|
434
|
+
} catch (error) {
|
|
435
|
+
if (error instanceof ValidationError) {
|
|
436
|
+
console.log('Validation failed:', error.message);
|
|
437
|
+
} else if (error instanceof OnChainDBError) {
|
|
438
|
+
console.log('Error:', error.code, error.message);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
## API Reference
|
|
444
|
+
|
|
445
|
+
### OnChainDBClient
|
|
446
|
+
|
|
447
|
+
| Method | Description |
|
|
448
|
+
|--------|-------------|
|
|
449
|
+
| `store(request, paymentCallback?, wait?)` | Store data |
|
|
450
|
+
| `queryBuilder()` | Create query builder |
|
|
451
|
+
| `uploadBlob(request)` | Upload binary file |
|
|
452
|
+
| `retrieveBlob(request)` | Download binary file |
|
|
453
|
+
| `getTaskStatus(ticketId)` | Get task status |
|
|
454
|
+
| `waitForTaskCompletion(ticketId)` | Poll until complete |
|
|
455
|
+
| `getPricingQuote(request)` | Get pricing quote |
|
|
456
|
+
| `health()` | Health check |
|
|
457
|
+
|
|
458
|
+
### QueryBuilder
|
|
459
|
+
|
|
460
|
+
| Method | Description |
|
|
461
|
+
|--------|-------------|
|
|
462
|
+
| `collection(name)` | Set collection |
|
|
463
|
+
| `whereField(name)` | Start field condition |
|
|
464
|
+
| `find(builderFn)` | Complex conditions |
|
|
465
|
+
| `selectFields(fields)` | Select specific fields |
|
|
466
|
+
| `selectAll()` | Select all fields |
|
|
467
|
+
| `joinOne(alias, model)` | One-to-one JOIN |
|
|
468
|
+
| `joinMany(alias, model)` | One-to-many JOIN |
|
|
469
|
+
| `limit(n)` | Limit results |
|
|
470
|
+
| `offset(n)` | Skip results |
|
|
471
|
+
| `execute()` | Execute query |
|
|
472
|
+
|
|
473
|
+
## License
|
|
474
|
+
|
|
475
|
+
MIT License
|
package/dist/batch.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { OnChainDBClient } from './client';
|
|
2
|
+
import { StoreRequest, StoreResponse, TransactionStatus } from './types';
|
|
3
|
+
import { EventEmitter } from 'eventemitter3';
|
|
4
|
+
export declare class BatchOperations extends EventEmitter {
|
|
5
|
+
private client;
|
|
6
|
+
constructor(client: OnChainDBClient);
|
|
7
|
+
store(requests: StoreRequest[], options?: {
|
|
8
|
+
concurrency?: number;
|
|
9
|
+
waitForConfirmation?: boolean;
|
|
10
|
+
onProgress?: (completed: number, total: number) => void;
|
|
11
|
+
paymentOptions?: {
|
|
12
|
+
userWallet?: any;
|
|
13
|
+
brokerAddress?: string;
|
|
14
|
+
};
|
|
15
|
+
}): Promise<StoreResponse[]>;
|
|
16
|
+
storeWithRetry(requests: StoreRequest[], options?: {
|
|
17
|
+
concurrency?: number;
|
|
18
|
+
waitForConfirmation?: boolean;
|
|
19
|
+
maxRetries?: number;
|
|
20
|
+
retryDelay?: number;
|
|
21
|
+
onProgress?: (completed: number, total: number) => void;
|
|
22
|
+
paymentOptions?: {
|
|
23
|
+
userWallet?: any;
|
|
24
|
+
brokerAddress?: string;
|
|
25
|
+
};
|
|
26
|
+
}): Promise<StoreResponse[]>;
|
|
27
|
+
waitForBatchConfirmation(transactionIds: string[], maxWaitTime?: number): Promise<TransactionStatus[]>;
|
|
28
|
+
private sleep;
|
|
29
|
+
}
|
|
30
|
+
export declare class BulkBuilder {
|
|
31
|
+
private requests;
|
|
32
|
+
private defaultCollection?;
|
|
33
|
+
private defaultApp?;
|
|
34
|
+
collection(name: string): this;
|
|
35
|
+
app(name: string): this;
|
|
36
|
+
add(data: Record<string, any>, collection?: string, app?: string): this;
|
|
37
|
+
addMany(records: Record<string, any>[], collection?: string, app?: string): this;
|
|
38
|
+
build(): StoreRequest[];
|
|
39
|
+
count(): number;
|
|
40
|
+
clear(): this;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=batch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAQ7C,qBAAa,eAAgB,SAAQ,YAAY;IACnC,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,eAAe;IA6BrC,KAAK,CACT,QAAQ,EAAE,YAAY,EAAE,EACxB,OAAO,GAAE;QACP,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,mBAAmB,CAAC,EAAE,OAAO,CAAC;QAC9B,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;QACxD,cAAc,CAAC,EAAE;YACf,UAAU,CAAC,EAAE,GAAG,CAAC;YACjB,aAAa,CAAC,EAAE,MAAM,CAAC;SACxB,CAAC;KACE,GACL,OAAO,CAAC,aAAa,EAAE,CAAC;IAqDrB,cAAc,CAClB,QAAQ,EAAE,YAAY,EAAE,EACxB,OAAO,GAAE;QACP,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,mBAAmB,CAAC,EAAE,OAAO,CAAC;QAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;QACxD,cAAc,CAAC,EAAE;YACf,UAAU,CAAC,EAAE,GAAG,CAAC;YACjB,aAAa,CAAC,EAAE,MAAM,CAAC;SACxB,CAAC;KACE,GACL,OAAO,CAAC,aAAa,EAAE,CAAC;IAkCrB,wBAAwB,CAC5B,cAAc,EAAE,MAAM,EAAE,EACxB,WAAW,GAAE,MAAe,GAC3B,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAyB/B,OAAO,CAAC,KAAK;CAGd;AAgBD,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,iBAAiB,CAAC,CAAS;IACnC,OAAO,CAAC,UAAU,CAAC,CAAS;IAK5B,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAQ9B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAQvB,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI;IAQvE,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI;IAQhF,KAAK,IAAI,YAAY,EAAE;IAOvB,KAAK,IAAI,MAAM;IAOf,KAAK,IAAI,IAAI;CAId"}
|
package/dist/batch.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BulkBuilder = exports.BatchOperations = void 0;
|
|
4
|
+
const eventemitter3_1 = require("eventemitter3");
|
|
5
|
+
class BatchOperations extends eventemitter3_1.EventEmitter {
|
|
6
|
+
constructor(client) {
|
|
7
|
+
super();
|
|
8
|
+
this.client = client;
|
|
9
|
+
}
|
|
10
|
+
async store(requests, options = {}) {
|
|
11
|
+
const { concurrency = 10, waitForConfirmation = false, onProgress, paymentOptions } = options;
|
|
12
|
+
const results = [];
|
|
13
|
+
const errors = [];
|
|
14
|
+
let completed = 0;
|
|
15
|
+
for (let i = 0; i < requests.length; i += concurrency) {
|
|
16
|
+
const batch = requests.slice(i, i + concurrency);
|
|
17
|
+
const batchPromises = batch.map(async (request, batchIndex) => {
|
|
18
|
+
const globalIndex = i + batchIndex;
|
|
19
|
+
try {
|
|
20
|
+
const result = await this.client.store(request, undefined, waitForConfirmation);
|
|
21
|
+
results[globalIndex] = result;
|
|
22
|
+
completed++;
|
|
23
|
+
onProgress?.(completed, requests.length);
|
|
24
|
+
this.emit('progress', { completed, total: requests.length, success: true });
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
errors.push({ index: globalIndex, error: error });
|
|
29
|
+
completed++;
|
|
30
|
+
onProgress?.(completed, requests.length);
|
|
31
|
+
this.emit('progress', { completed, total: requests.length, success: false, error });
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
await Promise.allSettled(batchPromises);
|
|
36
|
+
}
|
|
37
|
+
if (errors.length > 0) {
|
|
38
|
+
this.emit('batchComplete', { results, errors });
|
|
39
|
+
throw new Error(`Batch operation failed: ${errors.length}/${requests.length} requests failed`);
|
|
40
|
+
}
|
|
41
|
+
this.emit('batchComplete', { results, errors: [] });
|
|
42
|
+
return results;
|
|
43
|
+
}
|
|
44
|
+
async storeWithRetry(requests, options = {}) {
|
|
45
|
+
const { maxRetries = 3, retryDelay = 1000, ...batchOptions } = options;
|
|
46
|
+
let attempt = 0;
|
|
47
|
+
let lastError;
|
|
48
|
+
while (attempt <= maxRetries) {
|
|
49
|
+
try {
|
|
50
|
+
return await this.store(requests, batchOptions);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
lastError = error;
|
|
54
|
+
attempt++;
|
|
55
|
+
if (attempt <= maxRetries) {
|
|
56
|
+
this.emit('retryAttempt', { attempt, maxRetries, error });
|
|
57
|
+
await this.sleep(retryDelay * attempt);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
throw lastError;
|
|
62
|
+
}
|
|
63
|
+
async waitForBatchConfirmation(transactionIds, maxWaitTime = 300000) {
|
|
64
|
+
const confirmationPromises = transactionIds.map(async (id, index) => {
|
|
65
|
+
try {
|
|
66
|
+
const result = await this.client.waitForConfirmation(id, maxWaitTime);
|
|
67
|
+
this.emit('transactionConfirmed', { id, index, result });
|
|
68
|
+
return {
|
|
69
|
+
id,
|
|
70
|
+
status: 'confirmed',
|
|
71
|
+
block_height: result.block_height,
|
|
72
|
+
transaction_hash: result.transaction_hash,
|
|
73
|
+
celestia_height: result.celestia_height
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
this.emit('transactionFailed', { id, index, error });
|
|
78
|
+
return {
|
|
79
|
+
id,
|
|
80
|
+
status: 'failed',
|
|
81
|
+
error: error.message
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
return Promise.all(confirmationPromises);
|
|
86
|
+
}
|
|
87
|
+
sleep(ms) {
|
|
88
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.BatchOperations = BatchOperations;
|
|
92
|
+
class BulkBuilder {
|
|
93
|
+
constructor() {
|
|
94
|
+
this.requests = [];
|
|
95
|
+
}
|
|
96
|
+
collection(name) {
|
|
97
|
+
this.defaultCollection = name;
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
app(name) {
|
|
101
|
+
this.defaultApp = name;
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
add(data, collection, app) {
|
|
105
|
+
this.requests.push({ data: [data], root: `${app}::${collection}` });
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
addMany(records, collection, app) {
|
|
109
|
+
records.forEach(data => this.add(data, collection, app));
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
build() {
|
|
113
|
+
return [...this.requests];
|
|
114
|
+
}
|
|
115
|
+
count() {
|
|
116
|
+
return this.requests.length;
|
|
117
|
+
}
|
|
118
|
+
clear() {
|
|
119
|
+
this.requests = [];
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
exports.BulkBuilder = BulkBuilder;
|
|
124
|
+
//# sourceMappingURL=batch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch.js","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"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"}
|