@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.
Files changed (98) hide show
  1. package/.DS_Store +0 -0
  2. package/.claude/settings.local.json +8 -0
  3. package/.gitignore +5 -0
  4. package/.idea/.gitignore +5 -0
  5. package/.idea/compiler.xml +6 -0
  6. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  7. package/.idea/jsLinters/eslint.xml +6 -0
  8. package/.idea/modules.xml +8 -0
  9. package/.idea/prettier.xml +7 -0
  10. package/.idea/sdk.iml +12 -0
  11. package/.idea/vcs.xml +6 -0
  12. package/.idea/workspace.xml +257 -0
  13. package/dist/client.d.ts.map +1 -1
  14. package/dist/client.js +11 -3
  15. package/dist/client.js.map +1 -1
  16. package/dist/database.d.ts +0 -20
  17. package/dist/database.d.ts.map +1 -1
  18. package/dist/database.js +0 -40
  19. package/dist/database.js.map +1 -1
  20. package/dist/query-sdk/tests/setup.d.ts +16 -0
  21. package/dist/query-sdk/tests/setup.d.ts.map +1 -0
  22. package/dist/query-sdk/tests/setup.js +49 -0
  23. package/dist/query-sdk/tests/setup.js.map +1 -0
  24. package/examples/basic-usage.ts +136 -0
  25. package/examples/blob-upload-example.ts +140 -0
  26. package/examples/collection-schema-example.ts +304 -0
  27. package/examples/server-side-joins.ts +201 -0
  28. package/examples/tweet-self-joins-example.ts +352 -0
  29. package/package-lock.json +3823 -0
  30. package/package.json +1 -1
  31. package/skills.md +1096 -0
  32. package/src/.env +1 -0
  33. package/src/batch.d.ts +121 -0
  34. package/src/batch.js +205 -0
  35. package/src/batch.ts +257 -0
  36. package/src/client.ts +1856 -0
  37. package/src/database.d.ts +268 -0
  38. package/src/database.js +294 -0
  39. package/src/database.ts +695 -0
  40. package/src/index.d.ts +160 -0
  41. package/src/index.js +186 -0
  42. package/src/index.ts +253 -0
  43. package/src/query-sdk/ConditionBuilder.ts +103 -0
  44. package/src/query-sdk/FieldConditionBuilder.ts +2 -0
  45. package/src/query-sdk/NestedBuilders.ts +186 -0
  46. package/src/query-sdk/OnChainDB.ts +294 -0
  47. package/src/query-sdk/QueryBuilder.ts +1191 -0
  48. package/src/query-sdk/QueryResult.ts +375 -0
  49. package/src/query-sdk/README.md +866 -0
  50. package/src/query-sdk/SelectionBuilder.ts +94 -0
  51. package/src/query-sdk/adapters/HttpClientAdapter.ts +249 -0
  52. package/src/query-sdk/dist/ConditionBuilder.d.ts +22 -0
  53. package/src/query-sdk/dist/ConditionBuilder.js +90 -0
  54. package/src/query-sdk/dist/FieldConditionBuilder.d.ts +1 -0
  55. package/src/query-sdk/dist/FieldConditionBuilder.js +6 -0
  56. package/src/query-sdk/dist/NestedBuilders.d.ts +43 -0
  57. package/src/query-sdk/dist/NestedBuilders.js +144 -0
  58. package/src/query-sdk/dist/OnChainDB.d.ts +19 -0
  59. package/src/query-sdk/dist/OnChainDB.js +123 -0
  60. package/src/query-sdk/dist/QueryBuilder.d.ts +70 -0
  61. package/src/query-sdk/dist/QueryBuilder.js +295 -0
  62. package/src/query-sdk/dist/QueryResult.d.ts +52 -0
  63. package/src/query-sdk/dist/QueryResult.js +293 -0
  64. package/src/query-sdk/dist/SelectionBuilder.d.ts +20 -0
  65. package/src/query-sdk/dist/SelectionBuilder.js +80 -0
  66. package/src/query-sdk/dist/adapters/HttpClientAdapter.d.ts +27 -0
  67. package/src/query-sdk/dist/adapters/HttpClientAdapter.js +170 -0
  68. package/src/query-sdk/dist/index.d.ts +36 -0
  69. package/src/query-sdk/dist/index.js +27 -0
  70. package/src/query-sdk/dist/operators.d.ts +56 -0
  71. package/src/query-sdk/dist/operators.js +289 -0
  72. package/src/query-sdk/dist/tests/setup.d.ts +15 -0
  73. package/src/query-sdk/dist/tests/setup.js +46 -0
  74. package/src/query-sdk/index.ts +59 -0
  75. package/src/query-sdk/jest.config.js +25 -0
  76. package/src/query-sdk/operators.ts +335 -0
  77. package/src/query-sdk/package.json +46 -0
  78. package/src/query-sdk/tests/FieldConditionBuilder.test.ts +84 -0
  79. package/src/query-sdk/tests/LogicalOperator.test.ts +85 -0
  80. package/src/query-sdk/tests/NestedBuilders.test.ts +321 -0
  81. package/src/query-sdk/tests/QueryBuilder.test.ts +348 -0
  82. package/src/query-sdk/tests/QueryResult.test.ts +464 -0
  83. package/src/query-sdk/tests/aggregations.test.ts +653 -0
  84. package/src/query-sdk/tests/comprehensive.test.ts +279 -0
  85. package/src/query-sdk/tests/integration.test.ts +608 -0
  86. package/src/query-sdk/tests/operators.test.ts +327 -0
  87. package/src/query-sdk/tests/setup.ts +59 -0
  88. package/src/query-sdk/tests/unit.test.ts +794 -0
  89. package/src/query-sdk/tsconfig.json +26 -0
  90. package/src/query-sdk/yarn.lock +3092 -0
  91. package/src/types.d.ts +131 -0
  92. package/src/types.js +46 -0
  93. package/src/types.ts +534 -0
  94. package/src/x402/index.ts +12 -0
  95. package/src/x402/types.ts +250 -0
  96. package/src/x402/utils.ts +332 -0
  97. package/tsconfig.json +20 -0
  98. 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);