@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,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OnChainDB SDK - Collection Schema Example
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates how to create collections with schemas and indexes
|
|
5
|
+
* using the simplified createCollection API.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createClient, OnChainDBError, SimpleCollectionSchema } from '../src';
|
|
9
|
+
|
|
10
|
+
async function createCollectionExample() {
|
|
11
|
+
console.log('OnChainDB SDK - Collection Schema Example\n');
|
|
12
|
+
|
|
13
|
+
const db = createClient({
|
|
14
|
+
endpoint: 'http://localhost:9092',
|
|
15
|
+
appKey: 'your_app_key',
|
|
16
|
+
appId: 'my_app'
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// 1. Create a users collection with schema
|
|
21
|
+
console.log('1. Creating users collection...');
|
|
22
|
+
|
|
23
|
+
const usersSchema: SimpleCollectionSchema = {
|
|
24
|
+
name: 'users',
|
|
25
|
+
fields: {
|
|
26
|
+
email: { type: 'string', index: true },
|
|
27
|
+
username: { type: 'string', index: true },
|
|
28
|
+
age: { type: 'number' }, // not indexed
|
|
29
|
+
isActive: { type: 'boolean', index: true },
|
|
30
|
+
joinedAt: { type: 'date', index: true }
|
|
31
|
+
},
|
|
32
|
+
useBaseFields: true // adds id, createdAt, updatedAt, deletedAt indexes
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const usersResult = await db.createCollection(usersSchema);
|
|
36
|
+
|
|
37
|
+
console.log(` Collection: ${usersResult.collection}`);
|
|
38
|
+
console.log(` Success: ${usersResult.success}`);
|
|
39
|
+
console.log(` Indexes created:`);
|
|
40
|
+
usersResult.indexes.forEach(idx => {
|
|
41
|
+
console.log(` - ${idx.field} (${idx.type}): ${idx.status}`);
|
|
42
|
+
});
|
|
43
|
+
if (usersResult.warnings?.length) {
|
|
44
|
+
console.log(` Warnings: ${usersResult.warnings.join(', ')}`);
|
|
45
|
+
}
|
|
46
|
+
console.log();
|
|
47
|
+
|
|
48
|
+
// 2. Create a posts collection with monetization
|
|
49
|
+
console.log('2. Creating posts collection with read pricing...');
|
|
50
|
+
|
|
51
|
+
const postsSchema: SimpleCollectionSchema = {
|
|
52
|
+
name: 'posts',
|
|
53
|
+
fields: {
|
|
54
|
+
title: { type: 'string', index: true },
|
|
55
|
+
content: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
index: true,
|
|
58
|
+
readPricing: {
|
|
59
|
+
pricePerAccess: 0.001 // 0.001 TIA per read
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
authorId: { type: 'string', index: true },
|
|
63
|
+
likes: { type: 'number', index: true },
|
|
64
|
+
publishedAt: { type: 'date', index: true },
|
|
65
|
+
tags: { type: 'string', index: true } // for tag-based queries
|
|
66
|
+
},
|
|
67
|
+
useBaseFields: true
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const postsResult = await db.createCollection(postsSchema);
|
|
71
|
+
|
|
72
|
+
console.log(` Collection: ${postsResult.collection}`);
|
|
73
|
+
console.log(` Success: ${postsResult.success}`);
|
|
74
|
+
console.log(` Indexes: ${postsResult.indexes.length} created`);
|
|
75
|
+
console.log();
|
|
76
|
+
|
|
77
|
+
// 3. Create a minimal collection without base fields
|
|
78
|
+
console.log('3. Creating events collection (no base fields)...');
|
|
79
|
+
|
|
80
|
+
const eventsSchema: SimpleCollectionSchema = {
|
|
81
|
+
name: 'events',
|
|
82
|
+
fields: {
|
|
83
|
+
eventType: { type: 'string', index: true },
|
|
84
|
+
timestamp: { type: 'date', index: true },
|
|
85
|
+
data: { type: 'string' } // not indexed
|
|
86
|
+
},
|
|
87
|
+
useBaseFields: false // only custom fields
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const eventsResult = await db.createCollection(eventsSchema);
|
|
91
|
+
|
|
92
|
+
console.log(` Collection: ${eventsResult.collection}`);
|
|
93
|
+
console.log(` Indexes: ${eventsResult.indexes.length} (no base fields)`);
|
|
94
|
+
console.log();
|
|
95
|
+
|
|
96
|
+
// 4. Now store some data
|
|
97
|
+
console.log('4. Storing a user...');
|
|
98
|
+
|
|
99
|
+
const user = await db.createDocument('users', {
|
|
100
|
+
email: 'alice@example.com',
|
|
101
|
+
username: 'alice',
|
|
102
|
+
age: 28,
|
|
103
|
+
isActive: true,
|
|
104
|
+
joinedAt: new Date().toISOString()
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
console.log(` Created user: ${user.id}`);
|
|
108
|
+
console.log(` Email: ${user.email}`);
|
|
109
|
+
console.log(` CreatedAt: ${user.createdAt}`);
|
|
110
|
+
console.log();
|
|
111
|
+
|
|
112
|
+
// 5. Query users efficiently (uses indexes)
|
|
113
|
+
console.log('5. Querying active users...');
|
|
114
|
+
|
|
115
|
+
const activeUsers = await db.findMany('users', { isActive: true });
|
|
116
|
+
console.log(` Found ${activeUsers.length} active users`);
|
|
117
|
+
console.log();
|
|
118
|
+
|
|
119
|
+
// 6. Sync collection - add/remove indexes based on schema changes
|
|
120
|
+
console.log('6. Syncing users collection with updated schema...');
|
|
121
|
+
|
|
122
|
+
const updatedUsersSchema: SimpleCollectionSchema = {
|
|
123
|
+
name: 'users',
|
|
124
|
+
fields: {
|
|
125
|
+
email: { type: 'string', index: true },
|
|
126
|
+
username: { type: 'string', index: true },
|
|
127
|
+
age: { type: 'number', index: true }, // NEW: now indexed
|
|
128
|
+
isActive: { type: 'boolean', index: true },
|
|
129
|
+
// joinedAt removed from indexed fields
|
|
130
|
+
lastLogin: { type: 'date', index: true } // NEW field with index
|
|
131
|
+
},
|
|
132
|
+
useBaseFields: true
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const syncResult = await db.syncCollection(updatedUsersSchema);
|
|
136
|
+
|
|
137
|
+
console.log(` Collection: ${syncResult.collection}`);
|
|
138
|
+
console.log(` Success: ${syncResult.success}`);
|
|
139
|
+
console.log(` Created: ${syncResult.created.map(i => i.field).join(', ') || 'none'}`);
|
|
140
|
+
console.log(` Removed: ${syncResult.removed.map(i => i.field).join(', ') || 'none'}`);
|
|
141
|
+
console.log(` Unchanged: ${syncResult.unchanged.length} indexes`);
|
|
142
|
+
if (syncResult.errors?.length) {
|
|
143
|
+
console.log(` Errors: ${syncResult.errors.join(', ')}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
console.log('\nAll collection operations completed!');
|
|
147
|
+
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if (error instanceof OnChainDBError) {
|
|
150
|
+
console.error('OnChainDB Error:', error.code, error.message);
|
|
151
|
+
} else {
|
|
152
|
+
console.error('Error:', error);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Example: E-commerce schema
|
|
158
|
+
async function ecommerceSchemaExample() {
|
|
159
|
+
console.log('\nOnChainDB SDK - E-commerce Schema Example\n');
|
|
160
|
+
|
|
161
|
+
const db = createClient({
|
|
162
|
+
endpoint: 'http://localhost:9092',
|
|
163
|
+
appKey: 'your_app_key',
|
|
164
|
+
appId: 'ecommerce_app'
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Products collection
|
|
168
|
+
const productsSchema: SimpleCollectionSchema = {
|
|
169
|
+
name: 'products',
|
|
170
|
+
fields: {
|
|
171
|
+
sku: { type: 'string', index: true },
|
|
172
|
+
name: { type: 'string', index: true },
|
|
173
|
+
price: { type: 'number', index: true },
|
|
174
|
+
category: { type: 'string', index: true },
|
|
175
|
+
inStock: { type: 'boolean', index: true }
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Orders collection with price index for payment splits
|
|
180
|
+
const ordersSchema: SimpleCollectionSchema = {
|
|
181
|
+
name: 'orders',
|
|
182
|
+
fields: {
|
|
183
|
+
orderNumber: { type: 'string', index: true },
|
|
184
|
+
customerId: { type: 'string', index: true },
|
|
185
|
+
status: { type: 'string', index: true },
|
|
186
|
+
total: {
|
|
187
|
+
type: 'number',
|
|
188
|
+
index: true,
|
|
189
|
+
indexType: 'price' // Price index for automatic payment splits (percentages auto-configured)
|
|
190
|
+
},
|
|
191
|
+
orderDate: { type: 'date', index: true }
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Customers collection with nested field indexes
|
|
196
|
+
const customersSchema: SimpleCollectionSchema = {
|
|
197
|
+
name: 'customers',
|
|
198
|
+
fields: {
|
|
199
|
+
email: { type: 'string', index: true },
|
|
200
|
+
name: { type: 'string', index: true },
|
|
201
|
+
tier: { type: 'string', index: true }, // bronze, silver, gold
|
|
202
|
+
// Nested field indexes using dot notation
|
|
203
|
+
'address.city': { type: 'string', index: true },
|
|
204
|
+
'address.country': { type: 'string', index: true },
|
|
205
|
+
'address.zipCode': { type: 'string', index: true }
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
console.log('Creating e-commerce collections...');
|
|
210
|
+
|
|
211
|
+
const results = await Promise.all([
|
|
212
|
+
db.createCollection(productsSchema),
|
|
213
|
+
db.createCollection(ordersSchema),
|
|
214
|
+
db.createCollection(customersSchema)
|
|
215
|
+
]);
|
|
216
|
+
|
|
217
|
+
results.forEach(result => {
|
|
218
|
+
const status = result.success ? '✓' : '✗';
|
|
219
|
+
console.log(`${status} ${result.collection}: ${result.indexes.length} indexes`);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
console.log('\nE-commerce schema ready!');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Example: Social media schema
|
|
226
|
+
async function socialMediaSchemaExample() {
|
|
227
|
+
console.log('\nOnChainDB SDK - Social Media Schema Example\n');
|
|
228
|
+
|
|
229
|
+
const db = createClient({
|
|
230
|
+
endpoint: 'http://localhost:9092',
|
|
231
|
+
appKey: 'your_app_key',
|
|
232
|
+
appId: 'social_app'
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Users collection
|
|
236
|
+
const usersSchema: SimpleCollectionSchema = {
|
|
237
|
+
name: 'users',
|
|
238
|
+
fields: {
|
|
239
|
+
handle: { type: 'string', index: true },
|
|
240
|
+
displayName: { type: 'string', index: true },
|
|
241
|
+
verified: { type: 'boolean', index: true },
|
|
242
|
+
followerCount: { type: 'number', index: true }
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// Posts with content monetization
|
|
247
|
+
const postsSchema: SimpleCollectionSchema = {
|
|
248
|
+
name: 'posts',
|
|
249
|
+
fields: {
|
|
250
|
+
authorHandle: { type: 'string', index: true },
|
|
251
|
+
content: {
|
|
252
|
+
type: 'string',
|
|
253
|
+
index: true,
|
|
254
|
+
readPricing: { pricePerAccess: 0.0001 } // micro-payment per view
|
|
255
|
+
},
|
|
256
|
+
replyToId: { type: 'string', index: true }, // for threading
|
|
257
|
+
likeCount: { type: 'number', index: true },
|
|
258
|
+
repostCount: { type: 'number', index: true }
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// Follows (lightweight, no base fields)
|
|
263
|
+
const followsSchema: SimpleCollectionSchema = {
|
|
264
|
+
name: 'follows',
|
|
265
|
+
fields: {
|
|
266
|
+
followerHandle: { type: 'string', index: true },
|
|
267
|
+
followingHandle: { type: 'string', index: true }
|
|
268
|
+
},
|
|
269
|
+
useBaseFields: false // lightweight, just the relation
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
console.log('Creating social media collections...');
|
|
273
|
+
|
|
274
|
+
const results = await Promise.all([
|
|
275
|
+
db.createCollection(usersSchema),
|
|
276
|
+
db.createCollection(postsSchema),
|
|
277
|
+
db.createCollection(followsSchema)
|
|
278
|
+
]);
|
|
279
|
+
|
|
280
|
+
results.forEach(result => {
|
|
281
|
+
const status = result.success ? '✓' : '✗';
|
|
282
|
+
console.log(`${status} ${result.collection}: ${result.indexes.length} indexes`);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
console.log('\nSocial media schema ready!');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Run examples
|
|
289
|
+
async function main() {
|
|
290
|
+
await createCollectionExample();
|
|
291
|
+
// Uncomment to run additional examples:
|
|
292
|
+
// await ecommerceSchemaExample();
|
|
293
|
+
// await socialMediaSchemaExample();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (require.main === module) {
|
|
297
|
+
main().catch(console.error);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export {
|
|
301
|
+
createCollectionExample,
|
|
302
|
+
ecommerceSchemaExample,
|
|
303
|
+
socialMediaSchemaExample
|
|
304
|
+
};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
/**
|
|
3
|
+
* OnChainDB SDK - Server-Side JOINs Examples
|
|
4
|
+
*
|
|
5
|
+
* Server-side JOINs are executed by the backend (Scepter) and support:
|
|
6
|
+
* - joinOne: One-to-one relationships (returns object or null)
|
|
7
|
+
* - joinMany: One-to-many relationships (returns array)
|
|
8
|
+
* - Nested JOINs for deep relationships
|
|
9
|
+
* - Self-referential JOINs (e.g., tweets referencing tweets)
|
|
10
|
+
* - Single HTTP request (no N+1 queries)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createClient } from '../src';
|
|
14
|
+
|
|
15
|
+
async function demonstrateServerSideJoins() {
|
|
16
|
+
console.log('OnChainDB Server-Side JOIN Examples\n');
|
|
17
|
+
console.log('='.repeat(60));
|
|
18
|
+
|
|
19
|
+
// Initialize client
|
|
20
|
+
const client = createClient({
|
|
21
|
+
endpoint: 'http://localhost:9092',
|
|
22
|
+
appId: 'twitter_app'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// ========================================
|
|
26
|
+
// Example 1: Simple One-to-One JOIN
|
|
27
|
+
// ========================================
|
|
28
|
+
console.log('\n1. One-to-One JOIN: Tweets with Author Profile\n');
|
|
29
|
+
|
|
30
|
+
const tweetsWithAuthor = client.queryBuilder()
|
|
31
|
+
.collection('tweets')
|
|
32
|
+
.whereField('reply_to_id').isNull()
|
|
33
|
+
.joinOne('author_info', 'users')
|
|
34
|
+
.onField('address').equals('$data.author')
|
|
35
|
+
.selectFields(['address', 'display_name', 'avatar_url', 'verified'])
|
|
36
|
+
.build()
|
|
37
|
+
.selectFields(['id', 'content', 'author', 'created_at', 'author_info'])
|
|
38
|
+
.limit(10);
|
|
39
|
+
|
|
40
|
+
console.log('Query:');
|
|
41
|
+
console.log(JSON.stringify(tweetsWithAuthor.buildRawQuery(), null, 2));
|
|
42
|
+
console.log();
|
|
43
|
+
|
|
44
|
+
// ========================================
|
|
45
|
+
// Example 2: One-to-Many JOIN
|
|
46
|
+
// ========================================
|
|
47
|
+
console.log('2. One-to-Many JOIN: Users with Their Tweets\n');
|
|
48
|
+
|
|
49
|
+
const usersWithTweets = client.queryBuilder()
|
|
50
|
+
.collection('users')
|
|
51
|
+
.joinMany('user_tweets', 'tweets')
|
|
52
|
+
.onField('author').equals('$data.address')
|
|
53
|
+
.selectFields(['id', 'content', 'created_at', 'like_count'])
|
|
54
|
+
.build()
|
|
55
|
+
.selectAll()
|
|
56
|
+
.limit(5);
|
|
57
|
+
|
|
58
|
+
console.log('Query:');
|
|
59
|
+
console.log(JSON.stringify(usersWithTweets.buildRawQuery(), null, 2));
|
|
60
|
+
console.log();
|
|
61
|
+
|
|
62
|
+
// ========================================
|
|
63
|
+
// Example 3: Multiple JOINs
|
|
64
|
+
// ========================================
|
|
65
|
+
console.log('3. Multiple JOINs: Timeline with Likes, Replies, Author\n');
|
|
66
|
+
|
|
67
|
+
const timeline = client.queryBuilder()
|
|
68
|
+
.collection('tweets')
|
|
69
|
+
.whereField('reply_to_id').isNull()
|
|
70
|
+
// JOIN 1: Author profile
|
|
71
|
+
.joinOne('author_info', 'users')
|
|
72
|
+
.onField('address').equals('$data.author')
|
|
73
|
+
.selectFields(['display_name', 'avatar_url', 'verified'])
|
|
74
|
+
.build()
|
|
75
|
+
// JOIN 2: All likes
|
|
76
|
+
.joinMany('likes', 'likes')
|
|
77
|
+
.onField('tweet_id').equals('$data.id')
|
|
78
|
+
.selectFields(['user', 'created_at'])
|
|
79
|
+
.build()
|
|
80
|
+
// JOIN 3: All replies
|
|
81
|
+
.joinMany('replies', 'tweets')
|
|
82
|
+
.onField('reply_to_id').equals('$data.id')
|
|
83
|
+
.selectFields(['id', 'author', 'content'])
|
|
84
|
+
.build()
|
|
85
|
+
.selectAll()
|
|
86
|
+
.limit(20);
|
|
87
|
+
|
|
88
|
+
console.log('Query:');
|
|
89
|
+
console.log(JSON.stringify(timeline.buildRawQuery(), null, 2));
|
|
90
|
+
console.log();
|
|
91
|
+
|
|
92
|
+
// ========================================
|
|
93
|
+
// Example 4: Nested JOINs
|
|
94
|
+
// ========================================
|
|
95
|
+
console.log('4. Nested JOINs: Replies with Their Authors\n');
|
|
96
|
+
|
|
97
|
+
const tweetWithReplies = client.queryBuilder()
|
|
98
|
+
.collection('tweets')
|
|
99
|
+
.whereField('id').equals('tweet_123')
|
|
100
|
+
// JOIN with nested JOIN inside
|
|
101
|
+
.joinMany('replies', 'tweets')
|
|
102
|
+
.onField('reply_to_id').equals('$data.id')
|
|
103
|
+
.selectAll()
|
|
104
|
+
// Nested: get author for each reply
|
|
105
|
+
.joinOne('author_info', 'users')
|
|
106
|
+
.onField('address').equals('$data.author')
|
|
107
|
+
.selectFields(['display_name', 'avatar_url', 'verified'])
|
|
108
|
+
.build()
|
|
109
|
+
.build()
|
|
110
|
+
.selectAll()
|
|
111
|
+
.limit(1);
|
|
112
|
+
|
|
113
|
+
console.log('Query:');
|
|
114
|
+
console.log(JSON.stringify(tweetWithReplies.buildRawQuery(), null, 2));
|
|
115
|
+
console.log();
|
|
116
|
+
|
|
117
|
+
// ========================================
|
|
118
|
+
// Example 5: Self-Referential JOIN (Quote Tweets)
|
|
119
|
+
// ========================================
|
|
120
|
+
console.log('5. Self-Referential JOIN: Tweets with Quoted Tweet\n');
|
|
121
|
+
|
|
122
|
+
const quoteTweets = client.queryBuilder()
|
|
123
|
+
.collection('tweets')
|
|
124
|
+
.whereField('quote_tweet_id').isNotNull()
|
|
125
|
+
// JOIN tweet to itself via quote_tweet_id
|
|
126
|
+
.joinOne('quote_tweet', 'tweets')
|
|
127
|
+
.onField('id').equals('$data.quote_tweet_id')
|
|
128
|
+
.selectFields(['id', 'content', 'author', 'created_at'])
|
|
129
|
+
// Nested: get quoted tweet's author
|
|
130
|
+
.joinOne('author_info', 'users')
|
|
131
|
+
.onField('address').equals('$data.author')
|
|
132
|
+
.selectFields(['display_name', 'avatar_url'])
|
|
133
|
+
.build()
|
|
134
|
+
.build()
|
|
135
|
+
.selectAll()
|
|
136
|
+
.limit(10);
|
|
137
|
+
|
|
138
|
+
console.log('Query:');
|
|
139
|
+
console.log(JSON.stringify(quoteTweets.buildRawQuery(), null, 2));
|
|
140
|
+
console.log();
|
|
141
|
+
|
|
142
|
+
// ========================================
|
|
143
|
+
// Example 6: User Profile with Everything
|
|
144
|
+
// ========================================
|
|
145
|
+
console.log('6. Complex Query: User Profile with Tweets, Followers, Following\n');
|
|
146
|
+
|
|
147
|
+
const userProfile = client.queryBuilder()
|
|
148
|
+
.collection('users')
|
|
149
|
+
.whereField('address').equals('celestia1abc...')
|
|
150
|
+
// All user's tweets
|
|
151
|
+
.joinMany('tweets', 'tweets')
|
|
152
|
+
.onField('author').equals('$data.address')
|
|
153
|
+
.selectAll()
|
|
154
|
+
.build()
|
|
155
|
+
// All followers
|
|
156
|
+
.joinMany('followers', 'follows')
|
|
157
|
+
.onField('following').equals('$data.address')
|
|
158
|
+
.selectFields(['follower', 'created_at'])
|
|
159
|
+
.build()
|
|
160
|
+
// All following
|
|
161
|
+
.joinMany('following', 'follows')
|
|
162
|
+
.onField('follower').equals('$data.address')
|
|
163
|
+
.selectFields(['following', 'created_at'])
|
|
164
|
+
.build()
|
|
165
|
+
.selectAll()
|
|
166
|
+
.limit(1);
|
|
167
|
+
|
|
168
|
+
console.log('Query:');
|
|
169
|
+
console.log(JSON.stringify(userProfile.buildRawQuery(), null, 2));
|
|
170
|
+
console.log();
|
|
171
|
+
|
|
172
|
+
// ========================================
|
|
173
|
+
// Key Points
|
|
174
|
+
// ========================================
|
|
175
|
+
console.log('='.repeat(60));
|
|
176
|
+
console.log('\nKey Points:\n');
|
|
177
|
+
console.log('- Use $data.fieldname to reference parent record fields');
|
|
178
|
+
console.log('- joinOne returns object or null (one-to-one)');
|
|
179
|
+
console.log('- joinMany returns array (one-to-many)');
|
|
180
|
+
console.log('- Nested JOINs chain with .joinOne()/.joinMany() before .build()');
|
|
181
|
+
console.log('- All JOINs execute server-side in a single request');
|
|
182
|
+
console.log('- Call .build() to close each JOIN definition');
|
|
183
|
+
console.log();
|
|
184
|
+
|
|
185
|
+
// ========================================
|
|
186
|
+
// Execute a query (uncomment to run)
|
|
187
|
+
// ========================================
|
|
188
|
+
// try {
|
|
189
|
+
// console.log('Executing timeline query...');
|
|
190
|
+
// const result = await timeline.execute();
|
|
191
|
+
// console.log(`Found ${result.records?.length || 0} tweets`);
|
|
192
|
+
// if (result.records?.length > 0) {
|
|
193
|
+
// console.log('First tweet:', JSON.stringify(result.records[0], null, 2));
|
|
194
|
+
// }
|
|
195
|
+
// } catch (error) {
|
|
196
|
+
// console.error('Query failed:', error);
|
|
197
|
+
// }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Run demonstration
|
|
201
|
+
demonstrateServerSideJoins().catch(console.error);
|