@squidcloud/cli 1.0.445 → 1.0.447

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.
@@ -0,0 +1,472 @@
1
+ # Database & Data Management
2
+
3
+ Squid provides database functionality similar to Firestore but more powerful, with collections, document references, and real-time capabilities. Squid supports multiple database types including NoSQL databases (like MongoDB) and relational databases (like PostgreSQL, MySQL).
4
+
5
+ ## Contents
6
+ - Collection Access
7
+ - Document References
8
+ - CRUD Operations
9
+ - Real-time Subscriptions
10
+ - Query Operators
11
+ - OR Queries
12
+ - Join Queries
13
+ - Dereference
14
+ - Pagination
15
+ - Watch Changes
16
+ - Transactions
17
+ - Numeric Operations
18
+ - Native Queries
19
+ - Backend Decorator: @trigger
20
+ - Schema Discovery
21
+
22
+ ## Collection Access
23
+
24
+ A collection represents a set of documents (similar to a table in relational databases or a collection in NoSQL databases).
25
+
26
+ ```typescript
27
+ // Get collection reference in the built-in database
28
+ const users = squid.collection<User>('users');
29
+
30
+ // Get collection reference in a specific integration
31
+ const orders = squid.collection<Order>('orders', 'postgres-db');
32
+ ```
33
+
34
+ **Type parameter**: The generic type `<T>` defines the structure of documents in the collection.
35
+
36
+ ## Document References
37
+
38
+ The `doc()` method has different behaviors:
39
+
40
+ ### No parameters - `doc()`
41
+ Generates a new document ID (useful for creating new documents):
42
+ ```typescript
43
+ // For built_in_db without schema - generates a random ID
44
+ const newDocRef = collection.doc();
45
+ await newDocRef.insert({ name: 'John', age: 30 });
46
+
47
+ // For other integrations - creates a placeholder that gets resolved on insert
48
+ const newDocRef = collection.doc();
49
+ ```
50
+
51
+ ### String parameter - `doc(stringId)`
52
+ **Only supported for `built_in_db` integration without a defined schema:**
53
+ ```typescript
54
+ // Valid only for built_in_db collections without schema
55
+ const docRef = collection.doc('my-doc-id');
56
+ const docRef = collection.doc('user-12345');
57
+ ```
58
+ **Important**: For collections with defined schemas or non-built_in_db integrations, you must use object format.
59
+
60
+ ### Object parameter - `doc({id: value})`
61
+ Used for collections with defined primary keys. The object maps primary key field names to their values:
62
+ ```typescript
63
+ // Single primary key field "id"
64
+ const docRef = collection.doc({ id: 'user-123' });
65
+
66
+ // Single primary key field "userId"
67
+ const docRef = collection.doc({ userId: 42 });
68
+
69
+ // Composite primary key (id1, id2)
70
+ const docRef = collection.doc({ id1: 'part1', id2: 'part2' });
71
+
72
+ // Partial primary key - missing fields generated on server if supported
73
+ const docRef = collection.doc({ id1: 'part1' }); // id2 generated on insert
74
+ ```
75
+
76
+ **Key points about document IDs:**
77
+ - For `built_in_db` without schema: can use string IDs or object format
78
+ - For `built_in_db` with schema: must use object format matching primary key fields
79
+ - For other integrations: must use object format matching primary key fields
80
+ - Partial primary keys: missing fields may be auto-generated on insert (if integration supports it)
81
+ - Empty `doc()`: generates a placeholder ID that gets resolved when document is created
82
+
83
+ ## CRUD Operations
84
+
85
+ ```typescript
86
+ // Insert
87
+ await userDoc.insert({ name: 'John', email: 'john@example.com' });
88
+
89
+ // Update (partial)
90
+ await userDoc.update({ name: 'Jane' });
91
+
92
+ // Update specific path
93
+ await userDoc.setInPath('address.street', 'Main St');
94
+
95
+ // Delete specific path
96
+ await userDoc.deleteInPath('tempData');
97
+
98
+ // Delete document
99
+ await userDoc.delete();
100
+
101
+ // Get data (promise)
102
+ const userData = await userDoc.snapshot();
103
+ console.log(userData);
104
+
105
+ // Get data (observable - realtime)
106
+ userDoc.snapshots().subscribe(data => {
107
+ console.log(data);
108
+ });
109
+
110
+ // Get cached data (no server fetch)
111
+ const cached = userDoc.peek();
112
+
113
+ // Check if document has data populated
114
+ if (userDoc.hasData) {
115
+ console.log('Document is loaded');
116
+ // Access data directly (with defensive copy)
117
+ console.log(userDoc.data);
118
+ }
119
+
120
+ // Bulk operations
121
+ await users.insertMany([
122
+ { id: 'user-1', data: { name: 'Alice' } },
123
+ { id: 'user-2', data: { name: 'Bob' } }
124
+ ]);
125
+
126
+ await users.deleteMany(['user-1', 'user-2']);
127
+ ```
128
+
129
+ ## Real-time Subscriptions
130
+
131
+ Squid provides real-time data synchronization. Subscribe to document or query changes and receive updates automatically.
132
+
133
+ ### Document Subscriptions
134
+ ```typescript
135
+ // Subscribe to document changes
136
+ const subscription = docRef.snapshots().subscribe((userData) => {
137
+ if (userData) {
138
+ console.log('Document updated:', userData);
139
+ } else {
140
+ console.log('Document deleted or does not exist');
141
+ }
142
+ });
143
+
144
+ // Unsubscribe when done
145
+ subscription.unsubscribe();
146
+ ```
147
+
148
+ ### Query Subscriptions
149
+ ```typescript
150
+ // Subscribe to query results - ALWAYS use dereference()
151
+ const subscription = collection
152
+ .query()
153
+ .eq('status', 'active')
154
+ .gte('age', 18)
155
+ .dereference() // Important: Converts DocumentReferences to actual data
156
+ .snapshots()
157
+ .subscribe((users) => {
158
+ console.log('Active users updated:', users);
159
+ // users is Array<User> with actual data
160
+ });
161
+
162
+ // Unsubscribe when done
163
+ subscription.unsubscribe();
164
+ ```
165
+
166
+ ### Real-time Updates
167
+ When data changes on the server (from any client or backend operation):
168
+ - Document subscriptions receive the updated document data
169
+ - Query subscriptions receive the updated query results
170
+ - Updates are pushed to clients via WebSocket connections
171
+ - Changes are automatically reflected in the local data
172
+
173
+ **Important**: Always unsubscribe from subscriptions when they're no longer needed to prevent memory leaks.
174
+
175
+ ## Query Operators
176
+
177
+ **ALL Available Operators:**
178
+ - Comparison: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`
179
+ - Arrays: `in`, `nin`, `arrayIncludesSome`, `arrayIncludesAll`, `arrayNotIncludes`
180
+ - Patterns: `like`, `notLike` (% = any chars, _ = one char)
181
+
182
+ ```typescript
183
+ // Basic query
184
+ const activeUsers = await users.query()
185
+ .eq('status', 'active')
186
+ .gt('age', 18)
187
+ .sortBy('name', true) // true = ascending
188
+ .limit(100) // max 20000, default 1000
189
+ .snapshot();
190
+
191
+ activeUsers.data.forEach(doc => {
192
+ console.log(doc.data); // Document data
193
+ });
194
+
195
+ // Realtime query
196
+ users.query()
197
+ .eq('status', 'active')
198
+ .snapshots().subscribe(snapshot => {
199
+ snapshot.data.forEach(doc => console.log(doc.data));
200
+ });
201
+
202
+ // Pattern matching (CASE-INSENSITIVE by default)
203
+ const results = await users.query()
204
+ .like('email', '%.com') // case-insensitive by default
205
+ .snapshot();
206
+
207
+ // Case-sensitive pattern matching
208
+ const caseSensitive = await users.query()
209
+ .like('name', 'John%', true) // third parameter: caseSensitive = true
210
+ .snapshot();
211
+
212
+ // Array operators
213
+ const tagged = await posts.query()
214
+ .in('category', ['tech', 'news'])
215
+ .arrayIncludesSome('tags', ['urgent', 'important'])
216
+ .snapshot();
217
+
218
+ // Using where() method (all operators above are shortcuts for where)
219
+ const results1 = await users.query().eq('status', 'active').snapshot();
220
+ const results2 = await users.query().where('status', '==', 'active').snapshot();
221
+ // These are equivalent
222
+
223
+ // Multiple sort
224
+ const sorted = await users.query()
225
+ .sortBy('lastName', true)
226
+ .sortBy('firstName', true)
227
+ .limit(50)
228
+ .snapshot();
229
+ ```
230
+
231
+ **Note:** `offset()` does NOT exist - use `paginate()` for pagination.
232
+
233
+ ## OR Queries
234
+
235
+ Combine multiple queries with OR logic:
236
+
237
+ ```typescript
238
+ const query1 = users.query().eq('status', 'active');
239
+ const query2 = users.query().eq('status', 'pending');
240
+ const orResults = await users.or(query1, query2)
241
+ .dereference()
242
+ .snapshot();
243
+ // Returns documents matching either query
244
+ // Note: Results are merged and deduplicated
245
+ ```
246
+
247
+ ## Join Queries
248
+
249
+ Combine data from multiple collections:
250
+
251
+ ```typescript
252
+ // Start with joinQuery() and alias
253
+ const results = await teachers
254
+ .joinQuery('teacher') // Alias for teachers collection
255
+ .join(
256
+ students.query(),
257
+ 'students', // Alias for students collection
258
+ { left: 'id', right: 'teacherId' } // Join condition
259
+ )
260
+ .dereference() // Important: converts refs to actual data
261
+ .snapshot();
262
+ // Results: Array<{ teacher: Teacher, students?: Student }>
263
+
264
+ // Inner join (only matching records)
265
+ const innerResults = await teachers
266
+ .joinQuery('teacher')
267
+ .join(
268
+ students.query(),
269
+ 'students',
270
+ { left: 'id', right: 'teacherId' },
271
+ { isInner: true } // Inner join option
272
+ )
273
+ .dereference()
274
+ .snapshot();
275
+
276
+ // Multi-level joins
277
+ const threeWay = await collection1
278
+ .joinQuery('a')
279
+ .join(collection2.query(), 'b', { left: 'id', right: 'parentId' })
280
+ .join(collection3.query(), 'c', { left: 'id', right: 'grandParentId' })
281
+ .dereference()
282
+ .snapshot();
283
+
284
+ // Grouped joins (nests one-to-many as arrays)
285
+ const grouped = await teachers
286
+ .joinQuery('teacher')
287
+ .join(students.query(), 'students', { left: 'id', right: 'teacherId' })
288
+ .grouped()
289
+ .dereference()
290
+ .snapshot();
291
+ // Results: Array<{ teacher: Teacher, students: Student[] }>
292
+ ```
293
+
294
+ ## Dereference
295
+
296
+ Converts DocumentReferences to actual data:
297
+
298
+ ```typescript
299
+ // WITHOUT dereference: returns Array<DocumentReference<User>>
300
+ const refs = await users.query().eq('active', true).snapshot();
301
+ // refs.data[0].data to access actual data
302
+
303
+ // WITH dereference: returns Array<User> directly
304
+ const userData = await users.query()
305
+ .eq('active', true)
306
+ .dereference()
307
+ .snapshot();
308
+ // userData[0] is the actual user object
309
+
310
+ // ALWAYS use dereference() for:
311
+ // - Real-time subscriptions (makes working with data easier)
312
+ // - When you need document data directly
313
+ // DON'T use dereference() if:
314
+ // - You need DocumentReference methods like .update() or .delete()
315
+ ```
316
+
317
+ ## Pagination
318
+
319
+ ```typescript
320
+ const pagination = users.query()
321
+ .sortBy('createdAt', false)
322
+ .dereference()
323
+ .paginate({
324
+ pageSize: 50, // Number of items per page (default: 100)
325
+ subscribe: true // Subscribe to real-time updates (default: true)
326
+ });
327
+
328
+ const firstPage = await pagination.first(); // Jump to first page
329
+ const nextPage = await pagination.next(); // Go to next page
330
+ const prevPage = await pagination.prev(); // Go to previous page
331
+
332
+ // Check pagination state
333
+ console.log(firstPage.hasNext); // boolean
334
+ console.log(firstPage.hasPrev); // boolean
335
+ ```
336
+
337
+ ## Watch Changes
338
+
339
+ ```typescript
340
+ users.query()
341
+ .eq('status', 'active')
342
+ .changes()
343
+ .subscribe(changes => {
344
+ console.log('Inserts:', changes.inserts);
345
+ console.log('Updates:', changes.updates);
346
+ console.log('Deletes:', changes.deletes);
347
+ });
348
+ ```
349
+
350
+ ## Transactions
351
+
352
+ ```typescript
353
+ await squid.runInTransaction(async (txId) => {
354
+ const userRef = squid.collection('users').doc('user-1');
355
+ const accountRef = squid.collection('accounts').doc('acc-1');
356
+
357
+ // Pass txId as last parameter to each operation
358
+ // Use incrementInPath/decrementInPath for numeric operations
359
+ await userRef.decrementInPath('balance', 100, txId);
360
+ await accountRef.incrementInPath('balance', 100, txId);
361
+
362
+ // Both commit together or rollback together
363
+ });
364
+ ```
365
+
366
+ ## Numeric Operations
367
+
368
+ ```typescript
369
+ // Increment/decrement
370
+ await userDoc.incrementInPath('loginCount', 1);
371
+ await userDoc.decrementInPath('credits', 50);
372
+
373
+ // For arrays/objects, use update() with full new value
374
+ const currentData = await userDoc.snapshot();
375
+ await userDoc.update({
376
+ tags: [...currentData.tags, 'newTag'],
377
+ notifications: [...currentData.notifications, { msg: 'Hi' }]
378
+ });
379
+ ```
380
+
381
+ ## Native Queries
382
+
383
+ For advanced queries that require database-specific syntax:
384
+
385
+ ### SQL (PostgreSQL, MySQL, etc.)
386
+ Uses `${param}` syntax for parameters:
387
+ ```typescript
388
+ const users = await squid.executeNativeRelationalQuery<User[]>(
389
+ 'postgres-db',
390
+ 'SELECT * FROM users WHERE age > ${minAge}',
391
+ { minAge: 18 }
392
+ );
393
+ ```
394
+
395
+ ### MongoDB Aggregation
396
+ ```typescript
397
+ const orders = await squid.executeNativeMongoQuery<Order[]>(
398
+ 'mongo-db',
399
+ 'orders',
400
+ [
401
+ { $match: { status: 'completed' } },
402
+ { $group: { _id: '$userId', total: { $sum: '$amount' } } }
403
+ ]
404
+ );
405
+ ```
406
+
407
+ ### Elasticsearch
408
+ ```typescript
409
+ const products = await squid.executeNativeElasticQuery(
410
+ 'elastic-db',
411
+ 'products',
412
+ { query: { match: { name: 'laptop' } } },
413
+ '_search', // endpoint (optional)
414
+ 'GET' // method (optional)
415
+ );
416
+ ```
417
+
418
+ ### Pure (Finos Legend Pure Language)
419
+ Uses `${param}` syntax:
420
+ ```typescript
421
+ const items = await squid.executeNativePureQuery(
422
+ 'my-db',
423
+ 'from products->filter(p | $p.price < ${maxPrice})',
424
+ { maxPrice: 1000 }
425
+ );
426
+ ```
427
+
428
+ ## Backend Decorator: @trigger
429
+
430
+ Responds to database collection changes:
431
+
432
+ ```typescript
433
+ import { SquidService, trigger, TriggerRequest } from '@squidcloud/backend';
434
+
435
+ export class MyService extends SquidService {
436
+ @trigger({ id: 'user-created', collection: 'users', mutationTypes: ['insert'] })
437
+ async onUserCreated(request: TriggerRequest<User>): Promise<void> {
438
+ console.log(request.mutationType); // 'insert' | 'update' | 'delete'
439
+ console.log(request.docBefore);
440
+ console.log(request.docAfter);
441
+ }
442
+ }
443
+ ```
444
+
445
+ ## Schema Discovery
446
+
447
+ AI-powered schema analysis for database integrations:
448
+
449
+ ```typescript
450
+ const integrations = squid.admin().integrations();
451
+
452
+ // Discover schema from a database connection
453
+ const discovered = await integrations.discoverDataConnectionSchema('postgres-db');
454
+ console.log(discovered.schema); // Discovered tables, columns, relationships
455
+ console.log(discovered.collectionReadiness); // Permission and replication status
456
+
457
+ // Generate AI descriptions for data schema
458
+ const schemaResult = await integrations.generateAiDescriptionsForDataSchema('postgres-db', {
459
+ schema: currentSchema,
460
+ collections: ['users', 'orders'],
461
+ instructions: 'Use business terminology'
462
+ });
463
+ ```
464
+
465
+ For security rules on databases, see [security.md](security.md).
466
+
467
+ ## Best Practices
468
+
469
+ 1. **Use transactions for multi-doc updates** - Atomic operations prevent partial updates
470
+ 2. **Limit query results** - Default 1000, max 20000
471
+ 3. **Use snapshots for one-time data** - Use subscriptions only for real-time needs
472
+ 4. **Batch operations when possible** - Use insertMany, deleteMany for efficiency
@@ -0,0 +1,98 @@
1
+ # OpenAI Provider Features
2
+
3
+ OpenAI-specific features in Squid. For general AI usage, see [ai.md](ai.md).
4
+
5
+ ## Model Constants
6
+
7
+ ```typescript
8
+ import {
9
+ OPENAI_CHAT_MODEL_NAMES, // Chat models
10
+ OPENAI_IMAGE_MODEL_NAMES, // DALL-E
11
+ OPENAI_AUDIO_TRANSCRIPTION_MODEL_NAMES, // Whisper
12
+ OPENAI_AUDIO_CREATE_SPEECH_MODEL_NAMES, // TTS
13
+ OPENAI_EMBEDDINGS_MODEL_NAMES, // Embeddings
14
+ } from '@squidcloud/client';
15
+ ```
16
+
17
+ **Source of truth:** `node_modules/@squidcloud/client/dist/internal-common/src/public-types/ai-common.public-types.d.ts`
18
+
19
+ ## OpenAI-Specific Parameters
20
+
21
+ ### Verbosity Control
22
+
23
+ Controls response length. OpenAI plain text responses only (ignored for other providers).
24
+
25
+ ```typescript
26
+ const response = await agent.ask('Explain quantum computing', {
27
+ verbosity: 'low' // 'low' | 'medium' | 'high'
28
+ });
29
+ ```
30
+
31
+ ### Reasoning Models
32
+
33
+ o-series and gpt-5 series models support `reasoningEffort` (no temperature support):
34
+
35
+ ```typescript
36
+ const response = await agent.ask('Complex problem...', {
37
+ reasoningEffort: 'high' // 'minimal' | 'low' | 'medium' | 'high'
38
+ });
39
+ ```
40
+
41
+ ## Code Interpreter
42
+
43
+ Execute Python code with file access. Supported by **OpenAI, Gemini, and Claude** models.
44
+
45
+ ```typescript
46
+ const response = await agent.ask('Analyze this data', {
47
+ useCodeInterpreter: 'llm',
48
+ fileIds: [fileId] // from squid.ai().files('openai').uploadFile()
49
+ });
50
+ ```
51
+
52
+ ### Squid's Built-in Code Interpreter
53
+
54
+ Squid also provides its own code interpreter via the `executePythonCode` AI function, which executes prepared Python code independently of the LLM provider.
55
+
56
+ ## File Upload
57
+
58
+ ```typescript
59
+ const aiFiles = squid.ai().files('openai');
60
+ const fileId = await aiFiles.uploadFile({ file: myFile, purpose: 'assistants' });
61
+ await aiFiles.deleteFile(fileId);
62
+ ```
63
+
64
+ ## Voice (TTS)
65
+
66
+ ```typescript
67
+ const voiceResult = await agent.askWithVoiceResponse('Hello', {
68
+ voiceOptions: {
69
+ modelName: 'tts-1', // see OPENAI_AUDIO_CREATE_SPEECH_MODEL_NAMES
70
+ voice: 'alloy', // alloy, ash, ballad, coral, echo, fable, onyx, nova, sage, shimmer, verse
71
+ speed: 1.0 // 0.25 to 4.0
72
+ }
73
+ });
74
+ ```
75
+
76
+ ## Image Generation (DALL-E)
77
+
78
+ ```typescript
79
+ const imageUrl = await squid.ai().image().generate('A futuristic city', {
80
+ modelName: 'dall-e-3',
81
+ quality: 'hd', // 'hd' | 'standard'
82
+ size: '1024x1024' // '1024x1024' | '1792x1024' | '1024x1792'
83
+ });
84
+ ```
85
+
86
+ ## Audio Transcription (Whisper)
87
+
88
+ ```typescript
89
+ const text = await squid.ai().audio().transcribe(audioFile, {
90
+ modelName: 'whisper-1',
91
+ language: 'en',
92
+ responseFormat: 'text' // 'json' | 'text' | 'srt' | 'verbose_json' | 'vtt'
93
+ });
94
+ ```
95
+
96
+ ## OpenAI-Compatible Providers
97
+
98
+ Squid supports any OpenAI-compatible API via `openai_compatible` integration type. Configure with custom `baseUrl`, API key, and model list. Supports chat and function calling (not code interpreter).