@onchaindb/sdk 0.4.0 → 0.4.2
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/.DS_Store +0 -0
- package/.claude/settings.local.json +8 -0
- package/.gitignore +5 -0
- package/.idea/.gitignore +5 -0
- package/.idea/compiler.xml +6 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/jsLinters/eslint.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/prettier.xml +7 -0
- package/.idea/sdk.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/.idea/workspace.xml +257 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +11 -3
- package/dist/client.js.map +1 -1
- package/dist/database.d.ts +0 -20
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +0 -40
- package/dist/database.js.map +1 -1
- package/dist/query-sdk/tests/setup.d.ts +16 -0
- package/dist/query-sdk/tests/setup.d.ts.map +1 -0
- package/dist/query-sdk/tests/setup.js +49 -0
- package/dist/query-sdk/tests/setup.js.map +1 -0
- package/examples/basic-usage.ts +136 -0
- package/examples/blob-upload-example.ts +140 -0
- package/examples/collection-schema-example.ts +304 -0
- package/examples/server-side-joins.ts +201 -0
- package/examples/tweet-self-joins-example.ts +352 -0
- package/package-lock.json +3823 -0
- package/package.json +1 -1
- package/skills.md +1096 -0
- package/src/.env +1 -0
- package/src/batch.d.ts +121 -0
- package/src/batch.js +205 -0
- package/src/batch.ts +257 -0
- package/src/client.ts +1856 -0
- package/src/database.d.ts +268 -0
- package/src/database.js +294 -0
- package/src/database.ts +695 -0
- package/src/index.d.ts +160 -0
- package/src/index.js +186 -0
- package/src/index.ts +253 -0
- package/src/query-sdk/ConditionBuilder.ts +103 -0
- package/src/query-sdk/FieldConditionBuilder.ts +2 -0
- package/src/query-sdk/NestedBuilders.ts +186 -0
- package/src/query-sdk/OnChainDB.ts +294 -0
- package/src/query-sdk/QueryBuilder.ts +1191 -0
- package/src/query-sdk/QueryResult.ts +375 -0
- package/src/query-sdk/README.md +866 -0
- package/src/query-sdk/SelectionBuilder.ts +94 -0
- package/src/query-sdk/adapters/HttpClientAdapter.ts +249 -0
- package/src/query-sdk/dist/ConditionBuilder.d.ts +22 -0
- package/src/query-sdk/dist/ConditionBuilder.js +90 -0
- package/src/query-sdk/dist/FieldConditionBuilder.d.ts +1 -0
- package/src/query-sdk/dist/FieldConditionBuilder.js +6 -0
- package/src/query-sdk/dist/NestedBuilders.d.ts +43 -0
- package/src/query-sdk/dist/NestedBuilders.js +144 -0
- package/src/query-sdk/dist/OnChainDB.d.ts +19 -0
- package/src/query-sdk/dist/OnChainDB.js +123 -0
- package/src/query-sdk/dist/QueryBuilder.d.ts +70 -0
- package/src/query-sdk/dist/QueryBuilder.js +295 -0
- package/src/query-sdk/dist/QueryResult.d.ts +52 -0
- package/src/query-sdk/dist/QueryResult.js +293 -0
- package/src/query-sdk/dist/SelectionBuilder.d.ts +20 -0
- package/src/query-sdk/dist/SelectionBuilder.js +80 -0
- package/src/query-sdk/dist/adapters/HttpClientAdapter.d.ts +27 -0
- package/src/query-sdk/dist/adapters/HttpClientAdapter.js +170 -0
- package/src/query-sdk/dist/index.d.ts +36 -0
- package/src/query-sdk/dist/index.js +27 -0
- package/src/query-sdk/dist/operators.d.ts +56 -0
- package/src/query-sdk/dist/operators.js +289 -0
- package/src/query-sdk/dist/tests/setup.d.ts +15 -0
- package/src/query-sdk/dist/tests/setup.js +46 -0
- package/src/query-sdk/index.ts +59 -0
- package/src/query-sdk/jest.config.js +25 -0
- package/src/query-sdk/operators.ts +335 -0
- package/src/query-sdk/package.json +46 -0
- package/src/query-sdk/tests/FieldConditionBuilder.test.ts +84 -0
- package/src/query-sdk/tests/LogicalOperator.test.ts +85 -0
- package/src/query-sdk/tests/NestedBuilders.test.ts +321 -0
- package/src/query-sdk/tests/QueryBuilder.test.ts +348 -0
- package/src/query-sdk/tests/QueryResult.test.ts +464 -0
- package/src/query-sdk/tests/aggregations.test.ts +653 -0
- package/src/query-sdk/tests/comprehensive.test.ts +279 -0
- package/src/query-sdk/tests/integration.test.ts +608 -0
- package/src/query-sdk/tests/operators.test.ts +327 -0
- package/src/query-sdk/tests/setup.ts +59 -0
- package/src/query-sdk/tests/unit.test.ts +794 -0
- package/src/query-sdk/tsconfig.json +26 -0
- package/src/query-sdk/yarn.lock +3092 -0
- package/src/types.d.ts +131 -0
- package/src/types.js +46 -0
- package/src/types.ts +534 -0
- package/src/x402/index.ts +12 -0
- package/src/x402/types.ts +250 -0
- package/src/x402/utils.ts +332 -0
- package/tsconfig.json +20 -0
- package/yarn.lock +2309 -0
|
@@ -0,0 +1,866 @@
|
|
|
1
|
+
# OnChainDB Query SDK
|
|
2
|
+
|
|
3
|
+
The Query SDK provides a powerful fluent API for building queries with server-side JOINs, complex logical operators, field conditions, and result processing utilities.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Quick Start](#quick-start)
|
|
8
|
+
- [QueryBuilder](#querybuilder)
|
|
9
|
+
- [Aggregations](#aggregations)
|
|
10
|
+
- [Server-Side JOINs](#server-side-joins)
|
|
11
|
+
- [Logical Operators](#logical-operators)
|
|
12
|
+
- [Field Operators](#field-operators)
|
|
13
|
+
- [Selection](#selection)
|
|
14
|
+
- [QueryResult Utilities](#queryresult-utilities)
|
|
15
|
+
- [Nested Field Queries](#nested-field-queries)
|
|
16
|
+
- [OnChainDB Static Class](#onchaindb-static-class)
|
|
17
|
+
- [HTTP Adapters](#http-adapters)
|
|
18
|
+
- [API Reference](#api-reference)
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { createClient } from '@onchaindb/sdk';
|
|
24
|
+
|
|
25
|
+
const db = createClient({
|
|
26
|
+
endpoint: 'http://localhost:9092',
|
|
27
|
+
appId: 'my_app'
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Simple query
|
|
31
|
+
const result = await db.queryBuilder()
|
|
32
|
+
.collection('users')
|
|
33
|
+
.whereField('status').equals('active')
|
|
34
|
+
.selectFields(['id', 'name', 'email'])
|
|
35
|
+
.limit(10)
|
|
36
|
+
.execute();
|
|
37
|
+
|
|
38
|
+
console.log(result.records);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## QueryBuilder
|
|
42
|
+
|
|
43
|
+
The QueryBuilder provides a fluent interface for constructing queries.
|
|
44
|
+
|
|
45
|
+
### Basic Methods
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
const query = db.queryBuilder()
|
|
49
|
+
.collection('users') // Set target collection
|
|
50
|
+
.whereField('status').equals('active') // Add filter condition
|
|
51
|
+
.selectFields(['id', 'name']) // Select specific fields
|
|
52
|
+
.selectAll() // Or select all fields
|
|
53
|
+
.limit(50) // Limit results
|
|
54
|
+
.offset(100) // Skip results (pagination)
|
|
55
|
+
.orderBy('created_at') // Sort results
|
|
56
|
+
.includeHistory(true) // Include historical versions
|
|
57
|
+
.execute(); // Execute the query
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### executeUnique()
|
|
61
|
+
|
|
62
|
+
Returns the latest record by metadata timestamp (`updatedAt` or `createdAt`). Useful for finding a single record when multiple versions may exist.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// Find the latest user record by email
|
|
66
|
+
const user = await db.queryBuilder()
|
|
67
|
+
.collection('users')
|
|
68
|
+
.whereField('email').equals('alice@example.com')
|
|
69
|
+
.executeUnique<User>();
|
|
70
|
+
|
|
71
|
+
// Returns: User | null (latest version by timestamp)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### WhereField Operators
|
|
75
|
+
|
|
76
|
+
All operators available on `whereField()`:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// Comparison
|
|
80
|
+
.equals(value)
|
|
81
|
+
.notEquals(value)
|
|
82
|
+
.greaterThan(value)
|
|
83
|
+
.greaterThanOrEqual(value)
|
|
84
|
+
.lessThan(value)
|
|
85
|
+
.lessThanOrEqual(value)
|
|
86
|
+
.between(min, max)
|
|
87
|
+
|
|
88
|
+
// String
|
|
89
|
+
.contains(value)
|
|
90
|
+
.startsWith(value)
|
|
91
|
+
.endsWith(value)
|
|
92
|
+
.regExpMatches(pattern)
|
|
93
|
+
.includesCaseInsensitive(value)
|
|
94
|
+
.startsWithCaseInsensitive(value)
|
|
95
|
+
.endsWithCaseInsensitive(value)
|
|
96
|
+
|
|
97
|
+
// Array
|
|
98
|
+
.in(values)
|
|
99
|
+
.notIn(values)
|
|
100
|
+
|
|
101
|
+
// Existence
|
|
102
|
+
.exists()
|
|
103
|
+
.notExists()
|
|
104
|
+
.isNull()
|
|
105
|
+
.isNotNull()
|
|
106
|
+
.isTrue()
|
|
107
|
+
.isFalse()
|
|
108
|
+
|
|
109
|
+
// IP Address
|
|
110
|
+
.isLocalIp()
|
|
111
|
+
.isExternalIp()
|
|
112
|
+
.inCountry(countryCode)
|
|
113
|
+
.cidr(cidrRange)
|
|
114
|
+
|
|
115
|
+
// Special
|
|
116
|
+
.b64(value)
|
|
117
|
+
.inDataset(dataset)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Debugging
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// Get raw query object for debugging
|
|
124
|
+
const rawQuery = db.queryBuilder()
|
|
125
|
+
.collection('tweets')
|
|
126
|
+
.whereField('author').equals('alice')
|
|
127
|
+
.selectAll()
|
|
128
|
+
.buildRawQuery();
|
|
129
|
+
|
|
130
|
+
console.log(JSON.stringify(rawQuery, null, 2));
|
|
131
|
+
|
|
132
|
+
// Clone a query builder
|
|
133
|
+
const baseQuery = db.queryBuilder().collection('users').selectAll();
|
|
134
|
+
const activeUsers = baseQuery.clone().whereField('active').isTrue();
|
|
135
|
+
const inactiveUsers = baseQuery.clone().whereField('active').isFalse();
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Aggregations
|
|
139
|
+
|
|
140
|
+
QueryBuilder provides built-in aggregation methods for counting, summing, averaging, and grouping data.
|
|
141
|
+
|
|
142
|
+
### Basic Aggregations
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// Count records
|
|
146
|
+
const activeUsers = await db.queryBuilder()
|
|
147
|
+
.collection('users')
|
|
148
|
+
.whereField('active').equals(true)
|
|
149
|
+
.count();
|
|
150
|
+
// Returns: number
|
|
151
|
+
|
|
152
|
+
// Sum a numeric field
|
|
153
|
+
const totalRevenue = await db.queryBuilder()
|
|
154
|
+
.collection('orders')
|
|
155
|
+
.whereField('status').equals('completed')
|
|
156
|
+
.sumBy('amount');
|
|
157
|
+
// Returns: number
|
|
158
|
+
|
|
159
|
+
// Calculate average
|
|
160
|
+
const avgPrice = await db.queryBuilder()
|
|
161
|
+
.collection('products')
|
|
162
|
+
.whereField('category').equals('electronics')
|
|
163
|
+
.avgBy('price');
|
|
164
|
+
// Returns: number
|
|
165
|
+
|
|
166
|
+
// Find maximum value
|
|
167
|
+
const highestPrice = await db.queryBuilder()
|
|
168
|
+
.collection('products')
|
|
169
|
+
.maxBy('price');
|
|
170
|
+
// Returns: T | null
|
|
171
|
+
|
|
172
|
+
// Find minimum value
|
|
173
|
+
const lowestPrice = await db.queryBuilder()
|
|
174
|
+
.collection('products')
|
|
175
|
+
.minBy('price');
|
|
176
|
+
// Returns: T | null
|
|
177
|
+
|
|
178
|
+
// Get distinct values
|
|
179
|
+
const categories = await db.queryBuilder()
|
|
180
|
+
.collection('products')
|
|
181
|
+
.distinctBy('category');
|
|
182
|
+
// Returns: string[]
|
|
183
|
+
|
|
184
|
+
// Count distinct values
|
|
185
|
+
const uniqueCategories = await db.queryBuilder()
|
|
186
|
+
.collection('products')
|
|
187
|
+
.countDistinct('category');
|
|
188
|
+
// Returns: number
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Grouped Aggregations
|
|
192
|
+
|
|
193
|
+
Use `groupBy()` to perform aggregations on groups of records:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// Count users by country
|
|
197
|
+
const usersByCountry = await db.queryBuilder()
|
|
198
|
+
.collection('users')
|
|
199
|
+
.groupBy('country')
|
|
200
|
+
.count();
|
|
201
|
+
// Returns: { "USA": 150, "UK": 75, "Germany": 50 }
|
|
202
|
+
|
|
203
|
+
// Sum order amounts by category
|
|
204
|
+
const salesByCategory = await db.queryBuilder()
|
|
205
|
+
.collection('orders')
|
|
206
|
+
.whereField('status').equals('completed')
|
|
207
|
+
.groupBy('category')
|
|
208
|
+
.sumBy('amount');
|
|
209
|
+
// Returns: { "electronics": 50000, "clothing": 25000 }
|
|
210
|
+
|
|
211
|
+
// Average rating by product
|
|
212
|
+
const avgRatingByProduct = await db.queryBuilder()
|
|
213
|
+
.collection('reviews')
|
|
214
|
+
.groupBy('productId')
|
|
215
|
+
.avgBy('rating');
|
|
216
|
+
// Returns: { "prod_1": 4.5, "prod_2": 3.8 }
|
|
217
|
+
|
|
218
|
+
// Max/Min by group
|
|
219
|
+
const maxPriceByCategory = await db.queryBuilder()
|
|
220
|
+
.collection('products')
|
|
221
|
+
.groupBy('category')
|
|
222
|
+
.maxBy('price');
|
|
223
|
+
// Returns: { "electronics": 999, "books": 49 }
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Nested Field Grouping
|
|
227
|
+
|
|
228
|
+
GroupBy supports nested field paths:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
// Group by nested field
|
|
232
|
+
const ordersByRegion = await db.queryBuilder()
|
|
233
|
+
.collection('orders')
|
|
234
|
+
.groupBy('customer.address.region')
|
|
235
|
+
.sumBy('total');
|
|
236
|
+
// Returns: { "West": 10000, "East": 8500 }
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Server-Side JOINs
|
|
240
|
+
|
|
241
|
+
Server-side JOINs execute on the backend in a single request. Use `$data.fieldname` to reference parent record fields.
|
|
242
|
+
|
|
243
|
+
### joinOne (One-to-One)
|
|
244
|
+
|
|
245
|
+
Returns a single object or null. Use when you expect at most one related record.
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
const result = await db.queryBuilder()
|
|
249
|
+
.collection('tweets')
|
|
250
|
+
.joinOne('author_info', 'users')
|
|
251
|
+
.onField('address').equals('$data.author')
|
|
252
|
+
.selectFields(['display_name', 'avatar_url', 'verified'])
|
|
253
|
+
.build()
|
|
254
|
+
.selectAll()
|
|
255
|
+
.execute();
|
|
256
|
+
|
|
257
|
+
// Result: { id, content, author, author_info: { display_name, ... } | null }
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### joinMany (One-to-Many)
|
|
261
|
+
|
|
262
|
+
Returns an array of related records. Use when you expect multiple related records.
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
const result = await db.queryBuilder()
|
|
266
|
+
.collection('users')
|
|
267
|
+
.joinMany('tweets', 'tweets')
|
|
268
|
+
.onField('author').equals('$data.address')
|
|
269
|
+
.selectFields(['id', 'content', 'created_at'])
|
|
270
|
+
.build()
|
|
271
|
+
.selectAll()
|
|
272
|
+
.execute();
|
|
273
|
+
|
|
274
|
+
// Result: { address, name, tweets: [{ id, content, ... }, ...] }
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### joinWith (Default Behavior)
|
|
278
|
+
|
|
279
|
+
Returns an array (same as joinMany but without explicit many flag).
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
const result = await db.queryBuilder()
|
|
283
|
+
.collection('posts')
|
|
284
|
+
.joinWith('comments', 'comments')
|
|
285
|
+
.onField('post_id').equals('$data.id')
|
|
286
|
+
.selectAll()
|
|
287
|
+
.build()
|
|
288
|
+
.selectAll()
|
|
289
|
+
.execute();
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Multiple JOINs
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
const result = await db.queryBuilder()
|
|
296
|
+
.collection('tweets')
|
|
297
|
+
.whereField('reply_to_id').isNull()
|
|
298
|
+
// Author profile (one-to-one)
|
|
299
|
+
.joinOne('author_info', 'users')
|
|
300
|
+
.onField('address').equals('$data.author')
|
|
301
|
+
.selectFields(['display_name', 'avatar_url', 'verified'])
|
|
302
|
+
.build()
|
|
303
|
+
// All likes (one-to-many)
|
|
304
|
+
.joinMany('likes', 'likes')
|
|
305
|
+
.onField('tweet_id').equals('$data.id')
|
|
306
|
+
.selectFields(['user', 'created_at'])
|
|
307
|
+
.build()
|
|
308
|
+
// All replies (one-to-many)
|
|
309
|
+
.joinMany('replies', 'tweets')
|
|
310
|
+
.onField('reply_to_id').equals('$data.id')
|
|
311
|
+
.selectFields(['id', 'author', 'content'])
|
|
312
|
+
.build()
|
|
313
|
+
.selectAll()
|
|
314
|
+
.limit(20)
|
|
315
|
+
.execute();
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Nested JOINs
|
|
319
|
+
|
|
320
|
+
Chain JOINs before calling `.build()` to create nested relationships:
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
const result = await db.queryBuilder()
|
|
324
|
+
.collection('tweets')
|
|
325
|
+
.whereField('id').equals(tweetId)
|
|
326
|
+
// Get replies with their authors
|
|
327
|
+
.joinMany('replies', 'tweets')
|
|
328
|
+
.onField('reply_to_id').equals('$data.id')
|
|
329
|
+
.selectAll()
|
|
330
|
+
// Nested: get author for each reply
|
|
331
|
+
.joinOne('author_info', 'users')
|
|
332
|
+
.onField('address').equals('$data.author')
|
|
333
|
+
.selectFields(['display_name', 'avatar_url'])
|
|
334
|
+
.build()
|
|
335
|
+
.build()
|
|
336
|
+
.selectAll()
|
|
337
|
+
.execute();
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Self-Referential JOINs
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
// Get tweets with quoted tweet info
|
|
344
|
+
const result = await db.queryBuilder()
|
|
345
|
+
.collection('tweets')
|
|
346
|
+
.whereField('quote_tweet_id').isNotNull()
|
|
347
|
+
.joinOne('quote_tweet', 'tweets')
|
|
348
|
+
.onField('id').equals('$data.quote_tweet_id')
|
|
349
|
+
.selectFields(['id', 'content', 'author', 'created_at'])
|
|
350
|
+
.joinOne('author_info', 'users')
|
|
351
|
+
.onField('address').equals('$data.author')
|
|
352
|
+
.selectFields(['display_name', 'avatar_url'])
|
|
353
|
+
.build()
|
|
354
|
+
.build()
|
|
355
|
+
.selectAll()
|
|
356
|
+
.execute();
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### JoinBuilder Operators
|
|
360
|
+
|
|
361
|
+
Available on `onField()`:
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
.equals(value)
|
|
365
|
+
.in(values)
|
|
366
|
+
.greaterThan(value)
|
|
367
|
+
.lessThan(value)
|
|
368
|
+
.isNull()
|
|
369
|
+
.isNotNull()
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Logical Operators
|
|
373
|
+
|
|
374
|
+
Build complex conditions with AND, OR, and NOT:
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
import { LogicalOperator } from '@onchaindb/sdk';
|
|
378
|
+
|
|
379
|
+
const result = await db.queryBuilder()
|
|
380
|
+
.collection('posts')
|
|
381
|
+
.find(builder =>
|
|
382
|
+
LogicalOperator.And([
|
|
383
|
+
LogicalOperator.Condition(builder.field('published').equals(true)),
|
|
384
|
+
LogicalOperator.Or([
|
|
385
|
+
LogicalOperator.Condition(builder.field('category').equals('tech')),
|
|
386
|
+
LogicalOperator.Condition(builder.field('views').greaterThan(1000))
|
|
387
|
+
]),
|
|
388
|
+
LogicalOperator.Not([
|
|
389
|
+
LogicalOperator.Condition(builder.field('archived').equals(true))
|
|
390
|
+
])
|
|
391
|
+
])
|
|
392
|
+
)
|
|
393
|
+
.selectAll()
|
|
394
|
+
.execute();
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### ConditionBuilder Methods
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
import { ConditionBuilder, LogicalOperator } from '@onchaindb/sdk';
|
|
401
|
+
|
|
402
|
+
const builder = new ConditionBuilder();
|
|
403
|
+
|
|
404
|
+
// Create field conditions
|
|
405
|
+
builder.field('name').equals('John')
|
|
406
|
+
builder.field('age').greaterThan(18)
|
|
407
|
+
|
|
408
|
+
// Group conditions
|
|
409
|
+
builder.andGroup(() => [...conditions])
|
|
410
|
+
builder.orGroup(() => [...conditions])
|
|
411
|
+
builder.notGroup(() => [...conditions])
|
|
412
|
+
|
|
413
|
+
// Nested field conditions (ORM-like)
|
|
414
|
+
builder.nested('user', nested =>
|
|
415
|
+
nested.andGroup(() => [
|
|
416
|
+
LogicalOperator.Condition(nested.field('profile').field('name').equals('John')),
|
|
417
|
+
LogicalOperator.Condition(nested.field('settings').field('theme').equals('dark'))
|
|
418
|
+
])
|
|
419
|
+
)
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## Field Operators
|
|
423
|
+
|
|
424
|
+
### FieldConditionBuilder
|
|
425
|
+
|
|
426
|
+
All available operators:
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
import { FieldConditionBuilder } from '@onchaindb/sdk';
|
|
430
|
+
|
|
431
|
+
const field = new FieldConditionBuilder('fieldName');
|
|
432
|
+
|
|
433
|
+
// Base operators
|
|
434
|
+
field.equals(value) // is
|
|
435
|
+
field.notEquals(value) // isNot
|
|
436
|
+
field.in(values) // in
|
|
437
|
+
field.notIn(values) // notIn
|
|
438
|
+
field.isNull() // isNull: true
|
|
439
|
+
field.isNotNull() // isNull: false
|
|
440
|
+
field.exists() // exists: true
|
|
441
|
+
field.notExists() // exists: false
|
|
442
|
+
|
|
443
|
+
// String operators
|
|
444
|
+
field.startsWith(value)
|
|
445
|
+
field.endsWith(value)
|
|
446
|
+
field.contains(value) // includes
|
|
447
|
+
field.regExpMatches(pattern)
|
|
448
|
+
field.includesCaseInsensitive(value)
|
|
449
|
+
field.startsWithCaseInsensitive(value)
|
|
450
|
+
field.endsWithCaseInsensitive(value)
|
|
451
|
+
|
|
452
|
+
// Number operators
|
|
453
|
+
field.greaterThan(value)
|
|
454
|
+
field.lessThan(value)
|
|
455
|
+
field.greaterThanOrEqual(value)
|
|
456
|
+
field.lessThanOrEqual(value)
|
|
457
|
+
field.between(min, max) // betweenOp: { from, to }
|
|
458
|
+
|
|
459
|
+
// IP operators
|
|
460
|
+
field.isLocalIp()
|
|
461
|
+
field.isExternalIp()
|
|
462
|
+
field.inCountry(countryCode)
|
|
463
|
+
field.cidr(cidrRange)
|
|
464
|
+
|
|
465
|
+
// Misc operators
|
|
466
|
+
field.b64(value) // Base64 comparison
|
|
467
|
+
field.inDataset(dataset) // Dataset membership
|
|
468
|
+
|
|
469
|
+
// Convenience
|
|
470
|
+
field.isTrue() // equals(true)
|
|
471
|
+
field.isFalse() // equals(false)
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
## Selection
|
|
475
|
+
|
|
476
|
+
### SelectionBuilder
|
|
477
|
+
|
|
478
|
+
```typescript
|
|
479
|
+
import { SelectionBuilder } from '@onchaindb/sdk';
|
|
480
|
+
|
|
481
|
+
// Select specific fields
|
|
482
|
+
const selection = new SelectionBuilder()
|
|
483
|
+
.field('id')
|
|
484
|
+
.field('name')
|
|
485
|
+
.field('email')
|
|
486
|
+
.build();
|
|
487
|
+
|
|
488
|
+
// Select multiple fields at once
|
|
489
|
+
new SelectionBuilder()
|
|
490
|
+
.fields(['id', 'name', 'email', 'created_at'])
|
|
491
|
+
.build();
|
|
492
|
+
|
|
493
|
+
// Nested field selection
|
|
494
|
+
new SelectionBuilder()
|
|
495
|
+
.field('id')
|
|
496
|
+
.nested('profile', nested => nested.field('bio').field('avatar'))
|
|
497
|
+
.build();
|
|
498
|
+
|
|
499
|
+
// Exclude fields
|
|
500
|
+
new SelectionBuilder()
|
|
501
|
+
.excludeFields(['password', 'secret_key'])
|
|
502
|
+
.build();
|
|
503
|
+
|
|
504
|
+
// Select all fields
|
|
505
|
+
SelectionBuilder.all(); // Returns {}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Using with QueryBuilder
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
// Method 1: selectFields()
|
|
512
|
+
db.queryBuilder()
|
|
513
|
+
.collection('users')
|
|
514
|
+
.selectFields(['id', 'name', 'email'])
|
|
515
|
+
.execute();
|
|
516
|
+
|
|
517
|
+
// Method 2: selectAll()
|
|
518
|
+
db.queryBuilder()
|
|
519
|
+
.collection('users')
|
|
520
|
+
.selectAll()
|
|
521
|
+
.execute();
|
|
522
|
+
|
|
523
|
+
// Method 3: select() with builder
|
|
524
|
+
db.queryBuilder()
|
|
525
|
+
.collection('users')
|
|
526
|
+
.select(s => s.field('id').field('name').nested('profile', n => n.field('bio')))
|
|
527
|
+
.execute();
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
## QueryResult Utilities
|
|
531
|
+
|
|
532
|
+
Process query results with built-in utilities:
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
import { createQueryResult, QueryResult } from '@onchaindb/sdk';
|
|
536
|
+
|
|
537
|
+
const result = createQueryResult(response.records);
|
|
538
|
+
|
|
539
|
+
// Basic utilities
|
|
540
|
+
result.len() // Count of records
|
|
541
|
+
result.isEmpty() // Check if empty
|
|
542
|
+
result.first() // First record or undefined
|
|
543
|
+
result.last() // Last record or undefined
|
|
544
|
+
result.get(5) // Get record at index
|
|
545
|
+
|
|
546
|
+
// Iteration
|
|
547
|
+
result.any(r => r.active) // Any match predicate?
|
|
548
|
+
result.all(r => r.verified) // All match predicate?
|
|
549
|
+
result.find(r => r.id === '123') // Find first matching
|
|
550
|
+
result.filter(r => r.status === 'active')
|
|
551
|
+
result.map(r => r.name)
|
|
552
|
+
result.forEach((r, i) => console.log(i, r))
|
|
553
|
+
result.reduce((acc, r) => acc + r.amount, 0)
|
|
554
|
+
|
|
555
|
+
// Aggregation
|
|
556
|
+
result.groupBy('department') // { 'sales': [...], 'engineering': [...] }
|
|
557
|
+
result.pluck('name') // ['Alice', 'Bob', ...]
|
|
558
|
+
result.pluckStrings('name') // Type-safe string pluck
|
|
559
|
+
result.pluckNumbers('age') // Type-safe number pluck
|
|
560
|
+
result.countBy('status') // { 'active': 10, 'inactive': 3 }
|
|
561
|
+
|
|
562
|
+
// Numeric summary
|
|
563
|
+
const stats = result.summarizeNumeric('salary');
|
|
564
|
+
// { count, sum, mean, median, min, max, standardDeviation, variance }
|
|
565
|
+
|
|
566
|
+
// Sorting
|
|
567
|
+
result.sortBy('name') // Ascending
|
|
568
|
+
result.sortBy('created_at', false) // Descending
|
|
569
|
+
result.sortByMultiple([
|
|
570
|
+
{ field: 'department', ascending: true },
|
|
571
|
+
{ field: 'salary', ascending: false }
|
|
572
|
+
])
|
|
573
|
+
|
|
574
|
+
// Pagination
|
|
575
|
+
result.paginate(2, 20) // Page 2, 20 items per page
|
|
576
|
+
result.chunk(10) // Split into chunks of 10
|
|
577
|
+
|
|
578
|
+
// Distinct
|
|
579
|
+
result.distinctBy('email') // Unique by field
|
|
580
|
+
result.unique() // Unique records
|
|
581
|
+
|
|
582
|
+
// Joining (client-side)
|
|
583
|
+
const departments = createQueryResult(deptData);
|
|
584
|
+
result.innerJoin(departments, 'dept_id', 'id')
|
|
585
|
+
result.leftJoin(departments, 'dept_id', 'id')
|
|
586
|
+
|
|
587
|
+
// Export
|
|
588
|
+
result.toCsv() // CSV string
|
|
589
|
+
result.toCsv(';') // Custom delimiter
|
|
590
|
+
result.toJSON() // JSON string
|
|
591
|
+
result.toArray() // Plain array
|
|
592
|
+
|
|
593
|
+
// Debugging
|
|
594
|
+
result.inspect() // Log to console
|
|
595
|
+
result.inspect(5) // Log first 5 records
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
## Nested Field Queries
|
|
599
|
+
|
|
600
|
+
Query nested objects with dot notation or ORM-like builders:
|
|
601
|
+
|
|
602
|
+
### Dot Notation
|
|
603
|
+
|
|
604
|
+
```typescript
|
|
605
|
+
// Query nested fields with dot notation
|
|
606
|
+
const result = await db.queryBuilder()
|
|
607
|
+
.collection('users')
|
|
608
|
+
.whereField('profile.settings.theme').equals('dark')
|
|
609
|
+
.selectAll()
|
|
610
|
+
.execute();
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### ORM-like Nested Builder
|
|
614
|
+
|
|
615
|
+
```typescript
|
|
616
|
+
import { NestedConditionBuilder, LogicalOperator } from '@onchaindb/sdk';
|
|
617
|
+
|
|
618
|
+
const result = await db.queryBuilder()
|
|
619
|
+
.collection('users')
|
|
620
|
+
.find(builder =>
|
|
621
|
+
builder.nested('user', nested =>
|
|
622
|
+
nested.andGroup(() => [
|
|
623
|
+
LogicalOperator.Condition(
|
|
624
|
+
nested.field('profile').field('name').equals('John')
|
|
625
|
+
),
|
|
626
|
+
LogicalOperator.Condition(
|
|
627
|
+
nested.field('settings').field('notifications').isTrue()
|
|
628
|
+
)
|
|
629
|
+
])
|
|
630
|
+
)
|
|
631
|
+
)
|
|
632
|
+
.execute();
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
## OnChainDB Static Class
|
|
636
|
+
|
|
637
|
+
For standalone query building without client initialization:
|
|
638
|
+
|
|
639
|
+
```typescript
|
|
640
|
+
import { OnChainDB, FetchHttpClient } from '@onchaindb/sdk';
|
|
641
|
+
|
|
642
|
+
// Configure globally
|
|
643
|
+
OnChainDB.configure(new FetchHttpClient(), 'http://localhost:9092', 'api_key');
|
|
644
|
+
|
|
645
|
+
// Build queries
|
|
646
|
+
const query = OnChainDB.query()
|
|
647
|
+
.collection('users')
|
|
648
|
+
.selectAll();
|
|
649
|
+
|
|
650
|
+
// Execute raw queries
|
|
651
|
+
const result = await OnChainDB.rawQuery('{"find": {}, "select": {}}');
|
|
652
|
+
|
|
653
|
+
// Query from JSON object
|
|
654
|
+
const result = await OnChainDB.queryFromJson({ find: {}, select: {} });
|
|
655
|
+
|
|
656
|
+
// Health check
|
|
657
|
+
const isHealthy = await OnChainDB.healthCheck();
|
|
658
|
+
|
|
659
|
+
// Create indexes
|
|
660
|
+
await OnChainDB.createIndex('my_app', 'users', 'email');
|
|
661
|
+
await OnChainDB.createUniqueIndex('my_app', 'users', 'id');
|
|
662
|
+
|
|
663
|
+
// Reset configuration
|
|
664
|
+
OnChainDB.reset();
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
## HTTP Adapters
|
|
668
|
+
|
|
669
|
+
### Built-in Adapters
|
|
670
|
+
|
|
671
|
+
```typescript
|
|
672
|
+
import {
|
|
673
|
+
FetchHttpClient,
|
|
674
|
+
AxiosHttpClient,
|
|
675
|
+
NodeHttpClient,
|
|
676
|
+
createHttpClient
|
|
677
|
+
} from '@onchaindb/sdk';
|
|
678
|
+
|
|
679
|
+
// Browser (Fetch API)
|
|
680
|
+
const fetchClient = new FetchHttpClient();
|
|
681
|
+
|
|
682
|
+
// Axios (browser or Node.js)
|
|
683
|
+
const axiosClient = new AxiosHttpClient();
|
|
684
|
+
|
|
685
|
+
// Node.js native HTTP
|
|
686
|
+
const nodeClient = new NodeHttpClient();
|
|
687
|
+
|
|
688
|
+
// Auto-detect best client for environment
|
|
689
|
+
const client = createHttpClient();
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
### Custom HTTP Client
|
|
693
|
+
|
|
694
|
+
```typescript
|
|
695
|
+
import { HttpClient } from '@onchaindb/sdk';
|
|
696
|
+
|
|
697
|
+
class CustomHttpClient implements HttpClient {
|
|
698
|
+
async post(url: string, data: any, headers?: Record<string, string>): Promise<any> {
|
|
699
|
+
// Your implementation
|
|
700
|
+
const response = await fetch(url, {
|
|
701
|
+
method: 'POST',
|
|
702
|
+
headers: { 'Content-Type': 'application/json', ...headers },
|
|
703
|
+
body: JSON.stringify(data)
|
|
704
|
+
});
|
|
705
|
+
return response.json();
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
## API Reference
|
|
711
|
+
|
|
712
|
+
### QueryBuilder
|
|
713
|
+
|
|
714
|
+
| Method | Description |
|
|
715
|
+
|--------|-------------|
|
|
716
|
+
| `collection(name)` | Set target collection |
|
|
717
|
+
| `whereField(name)` | Start a field condition |
|
|
718
|
+
| `find(builderFn)` | Set complex logical conditions |
|
|
719
|
+
| `select(builderFn)` | Configure selection with builder |
|
|
720
|
+
| `selectFields(fields)` | Select specific fields |
|
|
721
|
+
| `selectAll()` | Select all fields |
|
|
722
|
+
| `joinOne(alias, model)` | One-to-one JOIN (returns object/null) |
|
|
723
|
+
| `joinMany(alias, model)` | One-to-many JOIN (returns array) |
|
|
724
|
+
| `joinWith(alias, model)` | JOIN with default behavior |
|
|
725
|
+
| `serverJoin(alias, model, resolve)` | Low-level server JOIN |
|
|
726
|
+
| `limit(n)` | Limit result count |
|
|
727
|
+
| `offset(n)` | Skip results (pagination) |
|
|
728
|
+
| `orderBy(field)` | Sort results |
|
|
729
|
+
| `includeHistory(bool)` | Include historical versions |
|
|
730
|
+
| `withFieldMap(map)` | Set field mapping |
|
|
731
|
+
| `execute()` | Execute query |
|
|
732
|
+
| `executeUnique()` | Execute and return latest record by metadata |
|
|
733
|
+
| `executeWithPayment(quoteId, txHash, network?)` | Execute with x402 payment |
|
|
734
|
+
| `count()` | Count matching records |
|
|
735
|
+
| `sumBy(field)` | Sum numeric field values |
|
|
736
|
+
| `avgBy(field)` | Calculate average of numeric field |
|
|
737
|
+
| `maxBy(field)` | Find maximum value |
|
|
738
|
+
| `minBy(field)` | Find minimum value |
|
|
739
|
+
| `distinctBy(field)` | Get distinct field values |
|
|
740
|
+
| `countDistinct(field)` | Count distinct values |
|
|
741
|
+
| `groupBy(field)` | Start grouped aggregation |
|
|
742
|
+
| `buildRawQuery()` | Get raw query object |
|
|
743
|
+
| `clone()` | Clone the builder |
|
|
744
|
+
| `isValid()` | Check if query has conditions |
|
|
745
|
+
|
|
746
|
+
### JoinBuilder
|
|
747
|
+
|
|
748
|
+
| Method | Description |
|
|
749
|
+
|--------|-------------|
|
|
750
|
+
| `onField(name)` | Start JOIN condition |
|
|
751
|
+
| `on(builderFn)` | Complex JOIN condition |
|
|
752
|
+
| `selectFields(fields)` | Select fields from joined collection |
|
|
753
|
+
| `selectAll()` | Select all fields |
|
|
754
|
+
| `selecting(builderFn)` | Configure selection with builder |
|
|
755
|
+
| `joinOne(alias, model)` | Nested one-to-one JOIN |
|
|
756
|
+
| `joinMany(alias, model)` | Nested one-to-many JOIN |
|
|
757
|
+
| `build()` | Complete JOIN and return to parent |
|
|
758
|
+
|
|
759
|
+
### GroupByQueryBuilder
|
|
760
|
+
|
|
761
|
+
Returned by `queryBuilder.groupBy(field)`:
|
|
762
|
+
|
|
763
|
+
| Method | Description |
|
|
764
|
+
|--------|-------------|
|
|
765
|
+
| `count()` | Count records per group |
|
|
766
|
+
| `sumBy(field)` | Sum field values per group |
|
|
767
|
+
| `avgBy(field)` | Average field values per group |
|
|
768
|
+
| `maxBy(field)` | Max field value per group |
|
|
769
|
+
| `minBy(field)` | Min field value per group |
|
|
770
|
+
|
|
771
|
+
### LogicalOperator
|
|
772
|
+
|
|
773
|
+
| Static Method | Description |
|
|
774
|
+
|---------------|-------------|
|
|
775
|
+
| `And(conditions)` | Combine with AND logic |
|
|
776
|
+
| `Or(conditions)` | Combine with OR logic |
|
|
777
|
+
| `Not(conditions)` | Negate conditions |
|
|
778
|
+
| `Condition(cond)` | Wrap a field condition |
|
|
779
|
+
|
|
780
|
+
### SelectionBuilder
|
|
781
|
+
|
|
782
|
+
| Method | Description |
|
|
783
|
+
|--------|-------------|
|
|
784
|
+
| `field(name)` | Include a field |
|
|
785
|
+
| `fields(names)` | Include multiple fields |
|
|
786
|
+
| `nested(name, builderFn)` | Nested selection |
|
|
787
|
+
| `exclude(name)` | Exclude a field |
|
|
788
|
+
| `excludeFields(names)` | Exclude multiple fields |
|
|
789
|
+
| `build()` | Get selection map |
|
|
790
|
+
| `clear()` | Reset builder |
|
|
791
|
+
| `static all()` | Select all fields |
|
|
792
|
+
|
|
793
|
+
### QueryResult
|
|
794
|
+
|
|
795
|
+
| Method | Description |
|
|
796
|
+
|--------|-------------|
|
|
797
|
+
| `len()` / `length()` | Record count |
|
|
798
|
+
| `isEmpty()` | Check if empty |
|
|
799
|
+
| `first()` / `last()` / `get(i)` | Access records |
|
|
800
|
+
| `any(predicate)` | Any match? |
|
|
801
|
+
| `all(predicate)` | All match? |
|
|
802
|
+
| `find(predicate)` | Find first matching |
|
|
803
|
+
| `filter(predicate)` | Filter records |
|
|
804
|
+
| `map(transform)` | Transform records |
|
|
805
|
+
| `forEach(callback)` | Iterate records |
|
|
806
|
+
| `reduce(callback, initial)` | Reduce records |
|
|
807
|
+
| `groupBy(field)` | Group by field value |
|
|
808
|
+
| `pluck(field)` | Extract field values |
|
|
809
|
+
| `summarizeNumeric(field)` | Statistical summary |
|
|
810
|
+
| `sortBy(field, asc?)` | Sort by field |
|
|
811
|
+
| `sortByMultiple(specs)` | Multi-field sort |
|
|
812
|
+
| `paginate(page, size)` | Paginate results |
|
|
813
|
+
| `chunk(size)` | Split into chunks |
|
|
814
|
+
| `distinctBy(field)` | Unique by field |
|
|
815
|
+
| `count(predicate?)` | Count records |
|
|
816
|
+
| `countBy(field)` | Count by field value |
|
|
817
|
+
| `innerJoin(other, thisKey, otherKey)` | Inner join |
|
|
818
|
+
| `leftJoin(other, thisKey, otherKey)` | Left join |
|
|
819
|
+
| `toCsv(delimiter?)` | Export to CSV |
|
|
820
|
+
| `toJSON()` | Export to JSON |
|
|
821
|
+
| `toArray()` | Get plain array |
|
|
822
|
+
| `inspect(limit?)` | Debug output |
|
|
823
|
+
|
|
824
|
+
## Types
|
|
825
|
+
|
|
826
|
+
```typescript
|
|
827
|
+
// Query types
|
|
828
|
+
interface QueryRequest {
|
|
829
|
+
find: any;
|
|
830
|
+
select: SelectionMap;
|
|
831
|
+
include_history?: boolean;
|
|
832
|
+
limit?: number;
|
|
833
|
+
offset?: number;
|
|
834
|
+
sort?: string[];
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
interface QueryResponse<T = any> {
|
|
838
|
+
records: T[];
|
|
839
|
+
total?: number;
|
|
840
|
+
error?: string;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Value types
|
|
844
|
+
type Val = string | number | boolean | null | Val[] | Date | RegExp;
|
|
845
|
+
|
|
846
|
+
interface BetweenValue {
|
|
847
|
+
min: Val;
|
|
848
|
+
max: Val;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Numeric summary
|
|
852
|
+
interface NumericSummary {
|
|
853
|
+
count: number;
|
|
854
|
+
sum: number;
|
|
855
|
+
mean: number;
|
|
856
|
+
median: number;
|
|
857
|
+
min: number;
|
|
858
|
+
max: number;
|
|
859
|
+
standardDeviation: number;
|
|
860
|
+
variance: number;
|
|
861
|
+
}
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
---
|
|
865
|
+
|
|
866
|
+
For complete SDK documentation, see the [main README](../../README.md).
|