@onchaindb/sdk 0.4.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +10 -2
- package/README.md +422 -355
- package/dist/batch.d.ts +1 -10
- package/dist/batch.d.ts.map +1 -1
- package/dist/batch.js +4 -26
- package/dist/batch.js.map +1 -1
- package/dist/client.d.ts +29 -43
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +199 -323
- package/dist/client.js.map +1 -1
- package/dist/database.d.ts +14 -131
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +35 -131
- package/dist/database.js.map +1 -1
- package/dist/index.d.ts +6 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -15
- package/dist/index.js.map +1 -1
- package/dist/query-sdk/ConditionBuilder.d.ts +3 -11
- package/dist/query-sdk/ConditionBuilder.d.ts.map +1 -1
- package/dist/query-sdk/ConditionBuilder.js +10 -48
- package/dist/query-sdk/ConditionBuilder.js.map +1 -1
- package/dist/query-sdk/NestedBuilders.d.ts +33 -30
- package/dist/query-sdk/NestedBuilders.d.ts.map +1 -1
- package/dist/query-sdk/NestedBuilders.js +46 -43
- package/dist/query-sdk/NestedBuilders.js.map +1 -1
- package/dist/query-sdk/QueryBuilder.d.ts +4 -2
- package/dist/query-sdk/QueryBuilder.d.ts.map +1 -1
- package/dist/query-sdk/QueryBuilder.js +47 -169
- package/dist/query-sdk/QueryBuilder.js.map +1 -1
- package/dist/query-sdk/QueryResult.d.ts +0 -38
- package/dist/query-sdk/QueryResult.d.ts.map +1 -1
- package/dist/query-sdk/QueryResult.js +1 -227
- package/dist/query-sdk/QueryResult.js.map +1 -1
- package/dist/query-sdk/index.d.ts +1 -1
- package/dist/query-sdk/index.d.ts.map +1 -1
- package/dist/query-sdk/index.js.map +1 -1
- package/dist/query-sdk/operators.d.ts +32 -28
- package/dist/query-sdk/operators.d.ts.map +1 -1
- package/dist/query-sdk/operators.js +45 -155
- package/dist/query-sdk/operators.js.map +1 -1
- package/dist/types.d.ts +153 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/jest.config.js +4 -0
- package/package.json +1 -1
- package/skills.md +0 -1
- package/src/client.ts +243 -745
- package/src/database.ts +70 -493
- package/src/index.ts +40 -193
- package/src/query-sdk/ConditionBuilder.ts +37 -89
- package/src/query-sdk/NestedBuilders.ts +90 -92
- package/src/query-sdk/QueryBuilder.ts +59 -218
- package/src/query-sdk/QueryResult.ts +4 -330
- package/src/query-sdk/README.md +214 -583
- package/src/query-sdk/index.ts +1 -1
- package/src/query-sdk/operators.ts +91 -200
- package/src/query-sdk/tests/FieldConditionBuilder.test.ts +70 -71
- package/src/query-sdk/tests/LogicalOperator.test.ts +43 -82
- package/src/query-sdk/tests/NestedBuilders.test.ts +229 -309
- package/src/query-sdk/tests/QueryBuilder.test.ts +5 -5
- package/src/query-sdk/tests/QueryResult.test.ts +41 -435
- package/src/query-sdk/tests/comprehensive.test.ts +4 -185
- package/src/tests/client-requests.test.ts +280 -0
- package/src/tests/client-validation.test.ts +80 -0
- package/src/types.ts +229 -8
- package/src/batch.ts +0 -257
- package/src/query-sdk/dist/ConditionBuilder.d.ts +0 -22
- package/src/query-sdk/dist/ConditionBuilder.js +0 -90
- package/src/query-sdk/dist/FieldConditionBuilder.d.ts +0 -1
- package/src/query-sdk/dist/FieldConditionBuilder.js +0 -6
- package/src/query-sdk/dist/NestedBuilders.d.ts +0 -43
- package/src/query-sdk/dist/NestedBuilders.js +0 -144
- package/src/query-sdk/dist/OnChainDB.d.ts +0 -19
- package/src/query-sdk/dist/OnChainDB.js +0 -123
- package/src/query-sdk/dist/QueryBuilder.d.ts +0 -70
- package/src/query-sdk/dist/QueryBuilder.js +0 -295
- package/src/query-sdk/dist/QueryResult.d.ts +0 -52
- package/src/query-sdk/dist/QueryResult.js +0 -293
- package/src/query-sdk/dist/SelectionBuilder.d.ts +0 -20
- package/src/query-sdk/dist/SelectionBuilder.js +0 -80
- package/src/query-sdk/dist/adapters/HttpClientAdapter.d.ts +0 -27
- package/src/query-sdk/dist/adapters/HttpClientAdapter.js +0 -170
- package/src/query-sdk/dist/index.d.ts +0 -36
- package/src/query-sdk/dist/index.js +0 -27
- package/src/query-sdk/dist/operators.d.ts +0 -56
- package/src/query-sdk/dist/operators.js +0 -289
- package/src/query-sdk/dist/tests/setup.d.ts +0 -15
- package/src/query-sdk/dist/tests/setup.js +0 -46
- package/src/query-sdk/jest.config.js +0 -25
- package/src/query-sdk/package.json +0 -46
- package/src/query-sdk/tests/aggregations.test.ts +0 -653
- package/src/query-sdk/tests/integration.test.ts +0 -608
- package/src/query-sdk/tests/operators.test.ts +0 -327
- package/src/query-sdk/tests/unit.test.ts +0 -794
- package/src/query-sdk/tsconfig.json +0 -26
- package/src/query-sdk/yarn.lock +0 -3092
package/README.md
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
# OnChainDB TypeScript SDK
|
|
2
2
|
|
|
3
|
-
A TypeScript SDK for OnChainDB
|
|
3
|
+
A TypeScript SDK for OnChainDB — the decentralized database built on Celestia with x402 payment protocol integration.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install @onchaindb/sdk
|
|
9
|
-
# or
|
|
10
|
-
yarn add @onchaindb/sdk
|
|
11
9
|
```
|
|
12
10
|
|
|
13
11
|
## Quick Start
|
|
@@ -18,7 +16,7 @@ import { createClient } from '@onchaindb/sdk';
|
|
|
18
16
|
const db = createClient({
|
|
19
17
|
endpoint: 'https://api.onchaindb.io',
|
|
20
18
|
appId: 'my_app',
|
|
21
|
-
|
|
19
|
+
appKey: 'your_app_key'
|
|
22
20
|
});
|
|
23
21
|
|
|
24
22
|
// Store data
|
|
@@ -42,20 +40,34 @@ console.log(result.records);
|
|
|
42
40
|
|
|
43
41
|
```typescript
|
|
44
42
|
interface OnChainDBConfig {
|
|
45
|
-
endpoint: string;
|
|
46
|
-
appId?: string;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
endpoint: string; // OnChainDB server endpoint
|
|
44
|
+
appId?: string; // Application ID
|
|
45
|
+
appKey?: string; // App key for write operations (X-App-Key header)
|
|
46
|
+
userKey?: string; // User key for Auto-Pay (X-User-Key header)
|
|
47
|
+
agentKey?: string; // Agent key with Pay permission (X-Agent-Key header)
|
|
48
|
+
timeout?: number; // Request timeout in ms (default: 30000)
|
|
49
|
+
retryCount?: number; // Retry attempts (default: 3)
|
|
50
|
+
retryDelay?: number; // Retry delay in ms (default: 1000)
|
|
51
51
|
}
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
### Agent Keys
|
|
55
|
+
|
|
56
|
+
Keys with the `Pay` permission are Agent Keys — they can pay other apps inline using USDC via EIP-3009.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
const db = createClient({
|
|
60
|
+
endpoint: 'https://api.onchaindb.io',
|
|
61
|
+
appId: 'my_app',
|
|
62
|
+
agentKey: 'agent_key_with_pay_permission'
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
54
66
|
## Storing Data
|
|
55
67
|
|
|
56
68
|
```typescript
|
|
57
69
|
// Basic store
|
|
58
|
-
|
|
70
|
+
await db.store({
|
|
59
71
|
collection: 'tweets',
|
|
60
72
|
data: [
|
|
61
73
|
{ id: 'tweet_1', content: 'Hello!', author: 'alice' },
|
|
@@ -63,24 +75,29 @@ const result = await db.store({
|
|
|
63
75
|
]
|
|
64
76
|
});
|
|
65
77
|
|
|
66
|
-
// Store with payment
|
|
67
|
-
|
|
78
|
+
// Store with x402 payment callback
|
|
79
|
+
await db.store(
|
|
68
80
|
{ collection: 'tweets', data: [{ content: 'Paid tweet' }] },
|
|
69
81
|
async (quote) => {
|
|
70
|
-
//
|
|
71
|
-
const txHash = await wallet.
|
|
72
|
-
quote.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
// quote is an X402Quote — sign and broadcast payment via your wallet
|
|
83
|
+
const txHash = await wallet.sendTransaction({
|
|
84
|
+
to: quote.payTo,
|
|
85
|
+
value: quote.maxAmountRequired
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
txHash,
|
|
89
|
+
network: quote.network,
|
|
90
|
+
sender: wallet.address,
|
|
91
|
+
chainType: quote.chainType,
|
|
92
|
+
paymentMethod: quote.paymentMethod
|
|
93
|
+
};
|
|
77
94
|
}
|
|
78
95
|
);
|
|
79
96
|
```
|
|
80
97
|
|
|
81
98
|
## Query Builder
|
|
82
99
|
|
|
83
|
-
The SDK provides a fluent query builder for constructing queries.
|
|
100
|
+
The SDK provides a fluent query builder for constructing queries. See the [Query SDK Reference](./src/query-sdk/README.md) for the full operator and aggregation reference.
|
|
84
101
|
|
|
85
102
|
### Basic Queries
|
|
86
103
|
|
|
@@ -92,25 +109,35 @@ const result = await db.queryBuilder()
|
|
|
92
109
|
.limit(50)
|
|
93
110
|
.execute();
|
|
94
111
|
|
|
95
|
-
|
|
96
|
-
result.records.forEach(user => {
|
|
97
|
-
console.log(user.name, user.email);
|
|
98
|
-
});
|
|
112
|
+
result.records.forEach(user => console.log(user.name, user.email));
|
|
99
113
|
```
|
|
100
114
|
|
|
101
|
-
###
|
|
115
|
+
### All Field Operators
|
|
102
116
|
|
|
103
117
|
```typescript
|
|
104
|
-
|
|
105
|
-
.notEquals(value)
|
|
106
|
-
.greaterThan(value)
|
|
107
|
-
.lessThan(value)
|
|
108
|
-
.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
.
|
|
112
|
-
.
|
|
113
|
-
.
|
|
118
|
+
// Comparison
|
|
119
|
+
.equals(value) .notEquals(value)
|
|
120
|
+
.greaterThan(value) .greaterThanOrEqual(value)
|
|
121
|
+
.lessThan(value) .lessThanOrEqual(value)
|
|
122
|
+
.between(min, max)
|
|
123
|
+
|
|
124
|
+
// String
|
|
125
|
+
.contains(value) .startsWith(value) .endsWith(value)
|
|
126
|
+
.regExpMatches(pattern)
|
|
127
|
+
.includesCaseInsensitive(value)
|
|
128
|
+
.startsWithCaseInsensitive(value)
|
|
129
|
+
.endsWithCaseInsensitive(value)
|
|
130
|
+
|
|
131
|
+
// Array
|
|
132
|
+
.in(values) .notIn(values)
|
|
133
|
+
|
|
134
|
+
// Existence
|
|
135
|
+
.exists() .notExists() .isNull() .isNotNull()
|
|
136
|
+
.isTrue() .isFalse()
|
|
137
|
+
|
|
138
|
+
// Network / security
|
|
139
|
+
.isLocalIp() .isExternalIp() .inCountry(code) .cidr(range)
|
|
140
|
+
.b64(value) .inDataset(name)
|
|
114
141
|
```
|
|
115
142
|
|
|
116
143
|
### Complex Queries with Logical Operators
|
|
@@ -122,10 +149,13 @@ const result = await db.queryBuilder()
|
|
|
122
149
|
.collection('posts')
|
|
123
150
|
.find(builder =>
|
|
124
151
|
LogicalOperator.And([
|
|
125
|
-
|
|
152
|
+
builder.field('published').isTrue(),
|
|
126
153
|
LogicalOperator.Or([
|
|
127
|
-
|
|
128
|
-
|
|
154
|
+
builder.field('category').equals('tech'),
|
|
155
|
+
builder.field('views').greaterThan(1000)
|
|
156
|
+
]),
|
|
157
|
+
LogicalOperator.Not([
|
|
158
|
+
builder.field('archived').isTrue()
|
|
129
159
|
])
|
|
130
160
|
])
|
|
131
161
|
)
|
|
@@ -134,124 +164,84 @@ const result = await db.queryBuilder()
|
|
|
134
164
|
.execute();
|
|
135
165
|
```
|
|
136
166
|
|
|
137
|
-
|
|
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.
|
|
167
|
+
### Sorting & Pagination
|
|
144
168
|
|
|
145
169
|
```typescript
|
|
146
|
-
// Get tweets with author profile
|
|
147
170
|
const result = await db.queryBuilder()
|
|
148
|
-
.collection('
|
|
149
|
-
.
|
|
150
|
-
|
|
151
|
-
.selectFields(['address', 'display_name', 'avatar_url', 'verified'])
|
|
152
|
-
.build()
|
|
153
|
-
.selectAll()
|
|
171
|
+
.collection('posts')
|
|
172
|
+
.whereField('published').isTrue()
|
|
173
|
+
.orderBy('created_at', 'DESC')
|
|
154
174
|
.limit(20)
|
|
175
|
+
.offset(40)
|
|
176
|
+
.selectAll()
|
|
155
177
|
.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
178
|
```
|
|
170
179
|
|
|
171
|
-
###
|
|
172
|
-
|
|
173
|
-
Returns an array of related records.
|
|
180
|
+
### Server-Side Aggregations
|
|
174
181
|
|
|
175
182
|
```typescript
|
|
176
|
-
//
|
|
177
|
-
const
|
|
183
|
+
// Count
|
|
184
|
+
const total = await db.queryBuilder()
|
|
178
185
|
.collection('users')
|
|
179
|
-
.whereField('
|
|
180
|
-
.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
.execute();
|
|
186
|
+
.whereField('active').isTrue()
|
|
187
|
+
.count();
|
|
188
|
+
|
|
189
|
+
// Sum / average
|
|
190
|
+
const revenue = await db.queryBuilder().collection('orders').sumBy('amount');
|
|
191
|
+
const avg = await db.queryBuilder().collection('orders').avgBy('amount');
|
|
186
192
|
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
//
|
|
193
|
-
// { id: 'tweet_2', content: 'World!', created_at: '...' }
|
|
194
|
-
// ]
|
|
195
|
-
// }
|
|
193
|
+
// Group by
|
|
194
|
+
const byCountry = await db.queryBuilder()
|
|
195
|
+
.collection('users')
|
|
196
|
+
.groupBy('country')
|
|
197
|
+
.count();
|
|
198
|
+
// { "USA": 150, "UK": 75, ... }
|
|
196
199
|
```
|
|
197
200
|
|
|
198
|
-
|
|
201
|
+
## Server-Side JOINs
|
|
202
|
+
|
|
203
|
+
JOINs execute on the backend in a single request. Use `$data.fieldname` to reference parent record fields.
|
|
204
|
+
|
|
205
|
+
### joinOne (one-to-one)
|
|
199
206
|
|
|
200
207
|
```typescript
|
|
201
|
-
// Timeline with likes, replies, and author info
|
|
202
208
|
const result = await db.queryBuilder()
|
|
203
209
|
.collection('tweets')
|
|
204
|
-
.whereField('reply_to_id').isNull()
|
|
205
210
|
.joinOne('author_info', 'users')
|
|
206
211
|
.onField('address').equals('$data.author')
|
|
207
212
|
.selectFields(['display_name', 'avatar_url', 'verified'])
|
|
208
213
|
.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
214
|
.selectAll()
|
|
218
|
-
.
|
|
219
|
-
.limit(limit)
|
|
215
|
+
.limit(20)
|
|
220
216
|
.execute();
|
|
217
|
+
|
|
218
|
+
// result.records[0].author_info => { display_name, avatar_url, verified } | null
|
|
221
219
|
```
|
|
222
220
|
|
|
223
|
-
###
|
|
221
|
+
### joinMany (one-to-many)
|
|
224
222
|
|
|
225
223
|
```typescript
|
|
226
|
-
// Get tweet with replies, each reply includes author info
|
|
227
224
|
const result = await db.queryBuilder()
|
|
228
|
-
.collection('
|
|
229
|
-
.whereField('
|
|
230
|
-
.joinMany('
|
|
231
|
-
.onField('
|
|
232
|
-
.
|
|
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()
|
|
225
|
+
.collection('users')
|
|
226
|
+
.whereField('address').equals(userAddress)
|
|
227
|
+
.joinMany('tweets', 'tweets')
|
|
228
|
+
.onField('author').equals('$data.address')
|
|
229
|
+
.selectFields(['id', 'content', 'created_at'])
|
|
238
230
|
.build()
|
|
239
231
|
.selectAll()
|
|
240
|
-
.limit(1)
|
|
241
232
|
.execute();
|
|
233
|
+
|
|
234
|
+
// result.records[0].tweets => [{ id, content, created_at }, ...]
|
|
242
235
|
```
|
|
243
236
|
|
|
244
|
-
###
|
|
237
|
+
### Nested JOINs
|
|
245
238
|
|
|
246
239
|
```typescript
|
|
247
|
-
// Get tweets with quoted tweet info
|
|
248
240
|
const result = await db.queryBuilder()
|
|
249
241
|
.collection('tweets')
|
|
250
|
-
.
|
|
251
|
-
|
|
252
|
-
.
|
|
253
|
-
.selectFields(['id', 'content', 'author', 'created_at'])
|
|
254
|
-
// Nested: get quoted tweet's author
|
|
242
|
+
.joinMany('replies', 'tweets')
|
|
243
|
+
.onField('reply_to_id').equals('$data.id')
|
|
244
|
+
.selectAll()
|
|
255
245
|
.joinOne('author_info', 'users')
|
|
256
246
|
.onField('address').equals('$data.author')
|
|
257
247
|
.selectFields(['display_name', 'avatar_url'])
|
|
@@ -261,299 +251,331 @@ const result = await db.queryBuilder()
|
|
|
261
251
|
.execute();
|
|
262
252
|
```
|
|
263
253
|
|
|
264
|
-
##
|
|
254
|
+
## Collection Management
|
|
255
|
+
|
|
256
|
+
### Create & Sync Collections
|
|
265
257
|
|
|
266
258
|
```typescript
|
|
267
|
-
|
|
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
|
-
});
|
|
259
|
+
import { SimpleCollectionSchema } from '@onchaindb/sdk';
|
|
274
260
|
|
|
275
|
-
const
|
|
261
|
+
const schema: SimpleCollectionSchema = {
|
|
262
|
+
name: 'users',
|
|
263
|
+
fields: {
|
|
264
|
+
email: { type: 'string', index: true },
|
|
265
|
+
age: { type: 'number', index: true },
|
|
266
|
+
status: { type: 'string', index: true, indexType: 'hash' },
|
|
267
|
+
bio: { type: 'string', index: true, indexType: 'fulltext' },
|
|
268
|
+
'address.city': { type: 'string', index: true },
|
|
269
|
+
},
|
|
270
|
+
useBaseFields: true, // Auto-index id, createdAt, updatedAt, deletedAt
|
|
271
|
+
};
|
|
276
272
|
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
brokerAddress,
|
|
280
|
-
`${costInUtia}utia`,
|
|
281
|
-
'Image upload'
|
|
282
|
-
);
|
|
273
|
+
// Create collection with indexes
|
|
274
|
+
await db.createCollection(schema);
|
|
283
275
|
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
});
|
|
276
|
+
// Sync: creates new indexes, removes none (broker handles upsert)
|
|
277
|
+
await db.syncCollection(schema);
|
|
278
|
+
```
|
|
294
279
|
|
|
295
|
-
|
|
296
|
-
const task = await db.waitForTaskCompletion(upload.ticket_id);
|
|
280
|
+
### Sharding
|
|
297
281
|
|
|
298
|
-
|
|
299
|
-
console.log('Blob ID:', upload.blob_id);
|
|
300
|
-
}
|
|
282
|
+
Partition large collections across multiple files for massive datasets.
|
|
301
283
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
collection: 'images',
|
|
305
|
-
blob_id: upload.blob_id
|
|
306
|
-
});
|
|
307
|
-
```
|
|
284
|
+
```typescript
|
|
285
|
+
import { SimpleCollectionSchemaWithSharding, ShardingStrategy } from '@onchaindb/sdk';
|
|
308
286
|
|
|
309
|
-
|
|
287
|
+
const schema: SimpleCollectionSchemaWithSharding = {
|
|
288
|
+
name: 'orderbook_snapshots',
|
|
289
|
+
fields: {
|
|
290
|
+
market: { type: 'string', index: true },
|
|
291
|
+
timestamp: { type: 'number', index: true },
|
|
292
|
+
mid_price: { type: 'number' }
|
|
293
|
+
},
|
|
294
|
+
sharding: {
|
|
295
|
+
keys: [
|
|
296
|
+
{ field: 'market', type: 'discrete' },
|
|
297
|
+
{ field: 'timestamp', type: 'time_range', granularity: 'hour' }
|
|
298
|
+
],
|
|
299
|
+
enforce_in_queries: true
|
|
300
|
+
}
|
|
301
|
+
};
|
|
310
302
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
);
|
|
303
|
+
// syncCollection applies sharding automatically if present
|
|
304
|
+
await db.syncCollection(schema);
|
|
305
|
+
|
|
306
|
+
// Or configure sharding separately
|
|
307
|
+
await db.setupSharding('orderbook_snapshots', schema.sharding!);
|
|
323
308
|
```
|
|
324
309
|
|
|
325
|
-
|
|
310
|
+
### Update & Delete Collections
|
|
326
311
|
|
|
327
312
|
```typescript
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
313
|
+
// Update collection properties
|
|
314
|
+
await db.updateCollection('users', {
|
|
315
|
+
public: true,
|
|
316
|
+
description: 'User profiles',
|
|
317
|
+
default_sort_column: 'created_at',
|
|
318
|
+
collection_type: 'local' // 'local' | 'offline' | 'api' | 'hybrid'
|
|
333
319
|
});
|
|
334
320
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
```
|
|
321
|
+
// Delete a collection
|
|
322
|
+
await db.deleteCollection('old_collection');
|
|
338
323
|
|
|
339
|
-
|
|
324
|
+
// Get collection info by ID (ID format: "{appId}_{index}", from listCollections)
|
|
325
|
+
const info = await db.getCollectionInfo('myapp_0');
|
|
326
|
+
// { id, name, namespace, primary_column, sort_column, status, collection_type,
|
|
327
|
+
// document_count, size_kb, created_at, last_modified }
|
|
328
|
+
```
|
|
340
329
|
|
|
341
|
-
|
|
330
|
+
## Data Retention
|
|
342
331
|
|
|
343
|
-
|
|
332
|
+
Configure per-collection retention policies. First 30 days are always free.
|
|
344
333
|
|
|
345
334
|
```typescript
|
|
346
|
-
|
|
335
|
+
// Get current retention config
|
|
336
|
+
const config = await db.getRetention('orders');
|
|
337
|
+
// { collection, retention_days, monthly_cost_per_kb, last_cleanup, status }
|
|
347
338
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
email: { type: 'string', index: true },
|
|
352
|
-
age: { type: 'number', index: true },
|
|
353
|
-
status: { type: 'string', index: true, indexType: 'hash' },
|
|
354
|
-
bio: { type: 'string', index: true, indexType: 'fulltext' },
|
|
355
|
-
'address.city': { type: 'string', index: true }, // Nested field
|
|
356
|
-
},
|
|
357
|
-
useBaseFields: true, // Auto-index id, createdAt, updatedAt, deletedAt
|
|
358
|
-
};
|
|
339
|
+
// Set retention (null = permanent storage)
|
|
340
|
+
const result = await db.setRetention('orders', { retention_days: 90 });
|
|
341
|
+
// { message, config: RetentionConfig }
|
|
359
342
|
|
|
360
|
-
|
|
361
|
-
|
|
343
|
+
// Get cost estimate for all collections
|
|
344
|
+
const costs = await db.getRetentionCost();
|
|
345
|
+
// { app_id, collections: CollectionRetentionCost[], total_monthly_cost_tia, projected_next_month_cost_tia }
|
|
362
346
|
```
|
|
363
347
|
|
|
364
|
-
|
|
348
|
+
## SQL Interface
|
|
365
349
|
|
|
366
|
-
|
|
350
|
+
Query and insert data using SQL syntax. Table references use `app::collection` format.
|
|
367
351
|
|
|
368
352
|
```typescript
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
// 'age' index removed
|
|
375
|
-
},
|
|
376
|
-
};
|
|
353
|
+
// SQL query
|
|
354
|
+
const result = await db.sql(
|
|
355
|
+
'SELECT * FROM myapp::orders WHERE amount > 100 ORDER BY created_at DESC LIMIT 50'
|
|
356
|
+
);
|
|
357
|
+
// { data: any[], count, query, app_id, collection }
|
|
377
358
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
359
|
+
// SQL query with history (includes all versions of records)
|
|
360
|
+
const withHistory = await db.sql(
|
|
361
|
+
'SELECT * FROM myapp::orders',
|
|
362
|
+
{ includeHistory: true }
|
|
363
|
+
);
|
|
381
364
|
|
|
382
|
-
|
|
365
|
+
// SQL insert
|
|
366
|
+
await db.sqlInsert(
|
|
367
|
+
"INSERT INTO myapp::orders (id, amount, status) VALUES ('ord_1', 250, 'pending')"
|
|
368
|
+
);
|
|
369
|
+
```
|
|
383
370
|
|
|
384
|
-
|
|
385
|
-
|------------|-------------|
|
|
386
|
-
| `string` | `string` (default), `hash`, `fulltext` |
|
|
387
|
-
| `number` | `number` (default) |
|
|
388
|
-
| `boolean` | `boolean` (default) |
|
|
389
|
-
| `date` | `date` (default) |
|
|
390
|
-
| `object` | `string` |
|
|
391
|
-
| `array` | `string` |
|
|
371
|
+
## Predefined Queries
|
|
392
372
|
|
|
393
|
-
|
|
373
|
+
Named, parameterized queries that can be executed publicly without authentication.
|
|
394
374
|
|
|
395
375
|
```typescript
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
376
|
+
import { CreateQueryRequest } from '@onchaindb/sdk';
|
|
377
|
+
|
|
378
|
+
// Create a predefined query
|
|
379
|
+
const created = await db.createQuery({
|
|
380
|
+
name: 'active_markets',
|
|
381
|
+
source_collection: 'orderbook_snapshots',
|
|
382
|
+
base_query: { find: { status: { $eq: 'active' } } },
|
|
383
|
+
parameters: [
|
|
384
|
+
{
|
|
385
|
+
name: 'market',
|
|
386
|
+
field_path: 'market', // maps to find.market.$eq
|
|
387
|
+
required: true,
|
|
388
|
+
description: 'Market symbol (e.g. BTC, ETH)'
|
|
406
389
|
},
|
|
407
|
-
|
|
408
|
-
|
|
390
|
+
{
|
|
391
|
+
name: 'min_price',
|
|
392
|
+
field_path: 'price.$gte',
|
|
393
|
+
default: 0,
|
|
394
|
+
description: 'Minimum price filter'
|
|
395
|
+
}
|
|
396
|
+
],
|
|
397
|
+
description: 'Query active orderbook snapshots by market'
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// List all queries
|
|
401
|
+
const queries = await db.listQueries();
|
|
402
|
+
// QueryDefinition[]
|
|
403
|
+
|
|
404
|
+
// Get a specific query definition
|
|
405
|
+
const query = await db.getQuery('active_markets');
|
|
406
|
+
|
|
407
|
+
// Execute a predefined query (public endpoint — no auth required)
|
|
408
|
+
const data = await db.executeQuery(
|
|
409
|
+
'active_markets',
|
|
410
|
+
{ market: 'BTC', min_price: '1000' } // params as Record<string, string>
|
|
411
|
+
);
|
|
412
|
+
// { success, query_name, data, count, query_time_ms }
|
|
413
|
+
|
|
414
|
+
// Execute a specific version
|
|
415
|
+
const v2data = await db.executeQuery('active_markets', { market: 'ETH' }, 2);
|
|
416
|
+
|
|
417
|
+
// Delete a query
|
|
418
|
+
await db.deleteQuery('active_markets');
|
|
409
419
|
```
|
|
410
420
|
|
|
411
421
|
## Materialized Views
|
|
412
422
|
|
|
413
|
-
Create
|
|
414
|
-
|
|
415
|
-
### Create View
|
|
423
|
+
### Create via JSON Query
|
|
416
424
|
|
|
417
425
|
```typescript
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
groupBy: 'region',
|
|
425
|
-
aggregate: { totalOrders: { $count: 'orders' } },
|
|
426
|
-
}
|
|
426
|
+
const db_manager = db.database();
|
|
427
|
+
|
|
428
|
+
await db_manager.createView(
|
|
429
|
+
'top_sellers',
|
|
430
|
+
['products', 'orders'],
|
|
431
|
+
{ find: { status: { $eq: 'completed' } }, sort_by: ['-total'], limit: 100 }
|
|
427
432
|
);
|
|
428
433
|
```
|
|
429
434
|
|
|
430
|
-
###
|
|
435
|
+
### Create via SQL
|
|
431
436
|
|
|
432
437
|
```typescript
|
|
433
|
-
|
|
434
|
-
|
|
438
|
+
await db_manager.createViewSql(
|
|
439
|
+
'SELECT market, AVG(price) as avg_price FROM myapp::trades GROUP BY market',
|
|
440
|
+
'live' // 'live' | 'lazy'
|
|
441
|
+
);
|
|
435
442
|
```
|
|
436
443
|
|
|
437
|
-
###
|
|
444
|
+
### Query & Manage Views
|
|
438
445
|
|
|
439
446
|
```typescript
|
|
440
|
-
|
|
441
|
-
|
|
447
|
+
// Query view data
|
|
448
|
+
const result = await db_manager.queryView<TradeStats>('top_sellers', {
|
|
449
|
+
find: { avg_price: { $gte: 1000 } },
|
|
450
|
+
sort_by: ['-avg_price'],
|
|
451
|
+
limit: 20
|
|
452
|
+
});
|
|
453
|
+
// { success, view_name, data: T[], count, query_time_ms }
|
|
454
|
+
|
|
455
|
+
// Count records in a view
|
|
456
|
+
const count = await db_manager.countView('top_sellers', { market: 'BTC' });
|
|
457
|
+
|
|
458
|
+
await db_manager.listViews();
|
|
459
|
+
await db_manager.getView('top_sellers');
|
|
460
|
+
await db_manager.refreshView('top_sellers');
|
|
461
|
+
await db_manager.deleteView('top_sellers');
|
|
442
462
|
```
|
|
443
463
|
|
|
444
|
-
|
|
464
|
+
## API Collections
|
|
465
|
+
|
|
466
|
+
Collections backed by external APIs instead of on-chain data.
|
|
445
467
|
|
|
446
468
|
```typescript
|
|
447
|
-
|
|
469
|
+
const db_manager = db.database();
|
|
470
|
+
|
|
471
|
+
await db_manager.createApiCollection({
|
|
472
|
+
name: 'live_prices',
|
|
473
|
+
description: 'Real-time price feed from external API',
|
|
474
|
+
api_config: {
|
|
475
|
+
base_url: 'https://api.example.com',
|
|
476
|
+
authentication: { type: 'bearer', token: 'xxx' },
|
|
477
|
+
query_mapping: { path_template: '/v1/prices/{market}' },
|
|
478
|
+
response_mapping: { records_path: 'data.prices' }
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
const collections = await db_manager.listApiCollections();
|
|
483
|
+
const collection = await db_manager.getApiCollection('live_prices');
|
|
484
|
+
await db_manager.deleteApiCollection('live_prices');
|
|
448
485
|
```
|
|
449
486
|
|
|
450
|
-
|
|
487
|
+
## Index Management
|
|
488
|
+
|
|
489
|
+
Access the `DatabaseManager` via `db.database()` for direct index control.
|
|
451
490
|
|
|
452
491
|
```typescript
|
|
453
|
-
|
|
492
|
+
const dbm = db.database();
|
|
493
|
+
|
|
494
|
+
// List all indexes
|
|
495
|
+
const indexes = await dbm.listIndexes();
|
|
496
|
+
|
|
497
|
+
// Create an index
|
|
498
|
+
await dbm.createIndex({
|
|
499
|
+
name: 'users_email_idx',
|
|
500
|
+
collection: 'users',
|
|
501
|
+
field_name: 'email',
|
|
502
|
+
index_type: 'btree'
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// Create multiple indexes at once
|
|
506
|
+
await dbm.createIndexes('users', [
|
|
507
|
+
{ name: 'email_idx', field_name: 'email', index_type: 'btree' },
|
|
508
|
+
{ name: 'status_idx', field_name: 'status', index_type: 'hash' },
|
|
509
|
+
{ name: 'bio_idx', field_name: 'bio', index_type: 'fulltext' },
|
|
510
|
+
]);
|
|
511
|
+
|
|
512
|
+
// Create a price index (enables payment splits on write)
|
|
513
|
+
await dbm.createPriceIndex('orders', 'total_price', {
|
|
514
|
+
app_owner_percentage: 0.80,
|
|
515
|
+
platform_percentage: 0.20,
|
|
516
|
+
app_owner_address: 'celestia1abc...'
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
// Drop an index by ID
|
|
520
|
+
await dbm.dropIndex('users_email_idx');
|
|
454
521
|
```
|
|
455
522
|
|
|
456
|
-
##
|
|
523
|
+
## Blob Storage
|
|
457
524
|
|
|
458
525
|
```typescript
|
|
459
|
-
|
|
526
|
+
// Upload
|
|
527
|
+
const upload = await db.uploadBlob({
|
|
528
|
+
collection: 'images',
|
|
529
|
+
blob: file,
|
|
530
|
+
metadata: { user_address: userAddress }
|
|
531
|
+
});
|
|
460
532
|
|
|
461
|
-
|
|
462
|
-
|
|
533
|
+
// Wait for on-chain confirmation
|
|
534
|
+
const task = await db.waitForTaskCompletion(upload.ticket_id);
|
|
535
|
+
console.log('Blob ID:', upload.blob_id);
|
|
463
536
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
537
|
+
// Retrieve
|
|
538
|
+
const blob = await db.retrieveBlob({
|
|
539
|
+
collection: 'images',
|
|
540
|
+
blob_id: upload.blob_id
|
|
541
|
+
});
|
|
542
|
+
```
|
|
467
543
|
|
|
468
|
-
|
|
469
|
-
const result = await this.client.queryBuilder()
|
|
470
|
-
.collection('tweets')
|
|
471
|
-
.whereField('reply_to_id').isNull()
|
|
472
|
-
.joinOne('author_info', 'users')
|
|
473
|
-
.onField('address').equals('$data.author')
|
|
474
|
-
.selectFields(['display_name', 'avatar_url', 'verified'])
|
|
475
|
-
.build()
|
|
476
|
-
.joinMany('likes', 'likes')
|
|
477
|
-
.onField('tweet_id').equals('$data.id')
|
|
478
|
-
.selectFields(['user'])
|
|
479
|
-
.build()
|
|
480
|
-
.joinMany('replies', 'tweets')
|
|
481
|
-
.onField('reply_to_id').equals('$data.id')
|
|
482
|
-
.selectFields(['id'])
|
|
483
|
-
.build()
|
|
484
|
-
.selectAll()
|
|
485
|
-
.offset(offset)
|
|
486
|
-
.limit(limit)
|
|
487
|
-
.execute();
|
|
488
|
-
|
|
489
|
-
return result.records.map(tweet => ({
|
|
490
|
-
...tweet,
|
|
491
|
-
like_count: tweet.likes?.length || 0,
|
|
492
|
-
reply_count: tweet.replies?.length || 0
|
|
493
|
-
}));
|
|
494
|
-
}
|
|
544
|
+
## Task Tracking
|
|
495
545
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
author,
|
|
501
|
-
created_at: new Date().toISOString()
|
|
502
|
-
};
|
|
546
|
+
```typescript
|
|
547
|
+
const task = await db.getTaskStatus(ticketId);
|
|
548
|
+
// status: 'Pending' | 'PaymentBroadcast' | 'PaymentConfirming' |
|
|
549
|
+
// 'PaymentConfirmed' | 'StoringData' | 'Completed' | { Failed: { error } }
|
|
503
550
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
);
|
|
551
|
+
const completed = await db.waitForTaskCompletion(ticketId, 2000, 600000);
|
|
552
|
+
// Polls every 2s, times out after 600s
|
|
553
|
+
```
|
|
508
554
|
|
|
509
|
-
|
|
510
|
-
}
|
|
555
|
+
## Pricing Quotes
|
|
511
556
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
.onField('following').equals('$data.address')
|
|
522
|
-
.selectFields(['follower'])
|
|
523
|
-
.build()
|
|
524
|
-
.joinMany('following', 'follows')
|
|
525
|
-
.onField('follower').equals('$data.address')
|
|
526
|
-
.selectFields(['following'])
|
|
527
|
-
.build()
|
|
528
|
-
.selectAll()
|
|
529
|
-
.limit(1)
|
|
530
|
-
.execute();
|
|
531
|
-
|
|
532
|
-
if (!result.records.length) return null;
|
|
533
|
-
|
|
534
|
-
const user = result.records[0];
|
|
535
|
-
return {
|
|
536
|
-
...user,
|
|
537
|
-
follower_count: user.followers?.length || 0,
|
|
538
|
-
following_count: user.following?.length || 0
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
}
|
|
557
|
+
```typescript
|
|
558
|
+
const quote = await db.getPricingQuote({
|
|
559
|
+
app_id: 'my_app',
|
|
560
|
+
operation_type: 'write',
|
|
561
|
+
size_kb: 10,
|
|
562
|
+
collection: 'orders',
|
|
563
|
+
data: { amount: 150 } // for price index calculation
|
|
564
|
+
});
|
|
565
|
+
// { total_cost, total_cost_utia, indexing_costs, ... }
|
|
542
566
|
```
|
|
543
567
|
|
|
544
568
|
## Error Handling
|
|
545
569
|
|
|
546
570
|
```typescript
|
|
547
|
-
import { OnChainDBError, ValidationError } from '@onchaindb/sdk';
|
|
571
|
+
import { OnChainDBError, ValidationError, PaymentRequiredError } from '@onchaindb/sdk';
|
|
548
572
|
|
|
549
573
|
try {
|
|
550
|
-
await db.store({ collection: 'test', data: [{
|
|
574
|
+
await db.store({ collection: 'test', data: [{ value: 1 }] });
|
|
551
575
|
} catch (error) {
|
|
552
|
-
if (error instanceof ValidationError)
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
console.log('Error:', error.code, error.message);
|
|
556
|
-
}
|
|
576
|
+
if (error instanceof ValidationError) console.log('Validation:', error.message);
|
|
577
|
+
if (error instanceof PaymentRequiredError) console.log('Quote:', error.quote);
|
|
578
|
+
if (error instanceof OnChainDBError) console.log(error.code, error.message);
|
|
557
579
|
}
|
|
558
580
|
```
|
|
559
581
|
|
|
@@ -563,37 +585,82 @@ try {
|
|
|
563
585
|
|
|
564
586
|
| Method | Description |
|
|
565
587
|
|--------|-------------|
|
|
566
|
-
| `store(request, paymentCallback
|
|
567
|
-
| `queryBuilder()` |
|
|
568
|
-
| `uploadBlob(request)` | Upload binary file |
|
|
569
|
-
| `retrieveBlob(request)` | Download binary file |
|
|
570
|
-
| `getTaskStatus(ticketId)` | Get task status |
|
|
571
|
-
| `waitForTaskCompletion(ticketId)` | Poll until complete |
|
|
572
|
-
| `getPricingQuote(request)` | Get pricing quote |
|
|
588
|
+
| `store(request, paymentCallback?)` | Store data on-chain |
|
|
589
|
+
| `queryBuilder()` | Fluent query builder |
|
|
573
590
|
| `createCollection(schema)` | Create collection with indexes |
|
|
574
|
-
| `syncCollection(schema)` | Sync indexes to
|
|
591
|
+
| `syncCollection(schema)` | Sync indexes to schema (supports sharding) |
|
|
592
|
+
| `setupSharding(collection, sharding)` | Configure sharding on an existing collection |
|
|
593
|
+
| `deleteCollection(collection)` | Delete a collection |
|
|
594
|
+
| `updateCollection(collection, updates)` | Update collection properties |
|
|
595
|
+
| `getCollectionInfo(collectionId)` | Get collection details by ID |
|
|
596
|
+
| `getRetention(collection)` | Get retention config |
|
|
597
|
+
| `setRetention(collection, request)` | Set retention period |
|
|
598
|
+
| `getRetentionCost()` | Get retention cost estimate for all collections |
|
|
599
|
+
| `sql(query, options?)` | Execute SQL query |
|
|
600
|
+
| `sqlInsert(sql)` | Execute SQL insert |
|
|
601
|
+
| `listQueries()` | List predefined queries |
|
|
602
|
+
| `createQuery(request)` | Create a predefined query |
|
|
603
|
+
| `getQuery(name)` | Get query definition |
|
|
604
|
+
| `deleteQuery(name)` | Delete a predefined query |
|
|
605
|
+
| `executeQuery(name, params?, version?)` | Execute a predefined query |
|
|
606
|
+
| `createRelation(request)` | Create a collection relation |
|
|
607
|
+
| `createIndex(request)` | Create a single index |
|
|
608
|
+
| `getPricingQuote(request)` | Estimate storage cost |
|
|
609
|
+
| `uploadBlob(request, paymentCallback?)` | Upload binary file |
|
|
610
|
+
| `retrieveBlob(request)` | Download binary file |
|
|
611
|
+
| `getTaskStatus(ticketId)` | Get async task status |
|
|
612
|
+
| `waitForTaskCompletion(ticketId)` | Poll until task completes |
|
|
613
|
+
| `health()` | Server health check |
|
|
614
|
+
| `database(appId?)` | Access `DatabaseManager` |
|
|
615
|
+
|
|
616
|
+
### DatabaseManager (`db.database()`)
|
|
617
|
+
|
|
618
|
+
| Method | Description |
|
|
619
|
+
|--------|-------------|
|
|
620
|
+
| `listIndexes()` | List all indexes for the app |
|
|
621
|
+
| `createIndex(definition)` | Create an index |
|
|
622
|
+
| `createIndexes(collection, indexes)` | Bulk create indexes |
|
|
623
|
+
| `createPriceIndex(collection, field, config?)` | Create payment-split price index |
|
|
624
|
+
| `dropIndex(indexId)` | Delete an index by ID |
|
|
575
625
|
| `createView(name, sources, query)` | Create materialized view |
|
|
626
|
+
| `createViewSql(sql, refreshMode?)` | Create view from SQL |
|
|
627
|
+
| `queryView(viewName, query?)` | Query view data |
|
|
628
|
+
| `countView(viewName, filter?)` | Count view records |
|
|
576
629
|
| `listViews()` | List all views |
|
|
577
|
-
| `getView(name)` | Get view
|
|
630
|
+
| `getView(name)` | Get view definition |
|
|
631
|
+
| `refreshView(name)` | Rebuild view |
|
|
578
632
|
| `deleteView(name)` | Delete a view |
|
|
579
|
-
| `
|
|
580
|
-
| `
|
|
633
|
+
| `createApiCollection(request)` | Create API-backed collection |
|
|
634
|
+
| `listApiCollections()` | List API collections |
|
|
635
|
+
| `getApiCollection(name)` | Get API collection details |
|
|
636
|
+
| `deleteApiCollection(name)` | Delete API collection |
|
|
581
637
|
|
|
582
638
|
### QueryBuilder
|
|
583
639
|
|
|
584
640
|
| Method | Description |
|
|
585
641
|
|--------|-------------|
|
|
586
|
-
| `collection(name)` | Set collection |
|
|
587
|
-
| `whereField(name)` | Start field condition |
|
|
588
|
-
| `find(builderFn)` |
|
|
642
|
+
| `collection(name)` | Set target collection |
|
|
643
|
+
| `whereField(name)` | Start a field condition |
|
|
644
|
+
| `find(builderFn)` | Set complex logical conditions |
|
|
589
645
|
| `selectFields(fields)` | Select specific fields |
|
|
590
646
|
| `selectAll()` | Select all fields |
|
|
591
|
-
| `joinOne(alias, model)` | One-to-one JOIN |
|
|
592
|
-
| `joinMany(alias, model)` | One-to-many JOIN |
|
|
593
|
-
| `
|
|
647
|
+
| `joinOne(alias, model)` | One-to-one server JOIN |
|
|
648
|
+
| `joinMany(alias, model)` | One-to-many server JOIN |
|
|
649
|
+
| `orderBy(field, dir?)` | Sort results |
|
|
650
|
+
| `limit(n)` | Limit result count |
|
|
594
651
|
| `offset(n)` | Skip results |
|
|
595
|
-
| `execute()` | Execute
|
|
652
|
+
| `execute()` | Execute and return records |
|
|
653
|
+
| `executeUnique()` | Return latest single record |
|
|
654
|
+
| `count()` | Count matching records |
|
|
655
|
+
| `sumBy(field)` | Sum numeric field |
|
|
656
|
+
| `avgBy(field)` | Average numeric field |
|
|
657
|
+
| `maxBy(field)` | Maximum field value |
|
|
658
|
+
| `minBy(field)` | Minimum field value |
|
|
659
|
+
| `distinctBy(field)` | Distinct field values |
|
|
660
|
+
| `groupBy(field)` | Start grouped aggregation |
|
|
661
|
+
| `buildRawQuery()` | Inspect raw query object |
|
|
662
|
+
| `clone()` | Clone builder |
|
|
596
663
|
|
|
597
664
|
## License
|
|
598
665
|
|
|
599
|
-
MIT
|
|
666
|
+
MIT
|