@squidcloud/cli 1.0.422 → 1.0.423

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/dist/index.js CHANGED
@@ -11423,7 +11423,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
11423
11423
  exports.CONNECTOR_METADATA_JSON_FILE = exports.CONNECTOR_IDS = void 0;
11424
11424
  /**
11425
11425
  * List of all connector package names.
11426
- * To prevent automatic discovery && latest version sync in 'prod' add you connector package name to
11426
+ * To prevent automatic discovery && latest version sync in 'prod' add your connector package name to
11427
11427
  * 'CONNECTOR_PACKAGES_TO_EXCLUDE_FROM_AUTO_SYNC' in ConnectorsService (console-backend).
11428
11428
  */
11429
11429
  exports.CONNECTOR_IDS = [
@@ -11431,6 +11431,7 @@ exports.CONNECTOR_IDS = [
11431
11431
  'cotomi',
11432
11432
  'essentials',
11433
11433
  'google_calendar',
11434
+ 'google_drive',
11434
11435
  'hubspot',
11435
11436
  'jira',
11436
11437
  'mail',
@@ -30623,7 +30624,7 @@ function exitWithError(...messages) {
30623
30624
  /***/ ((module) => {
30624
30625
 
30625
30626
  "use strict";
30626
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@squidcloud/cli","version":"1.0.422","description":"The Squid CLI","main":"dist/index.js","scripts":{"start":"node dist/index.js","start-ts":"ts-node -r tsconfig-paths/register src/index.ts","prebuild":"rimraf dist","build":"webpack --mode=production","build:dev":"webpack --mode=development","lint":"eslint","link":"npm run build && chmod 755 dist/index.js && npm link","watch":"webpack --watch","deploy":"npm run build && npm pack --silent | xargs -I {} mv {} package.tgz && npm install -g package.tgz && rm -rf package.tgz","publish:public":"npm run build && npm publish --access public"},"files":["dist/**/*"],"bin":{"squid":"dist/index.js"},"keywords":[],"author":"","license":"ISC","engines":{"node":">=18.0.0"},"dependencies":{"@squidcloud/local-backend":"^1.0.422","adm-zip":"^0.5.16","copy-webpack-plugin":"^12.0.2","decompress":"^4.2.1","nodemon":"^3.1.9","terser-webpack-plugin":"^5.3.10","ts-loader":"^9.5.1","ts-node":"^10.9.2","tsconfig-paths":"^4.2.0","tsconfig-paths-webpack-plugin":"^4.1.0","webpack":"^5.101.3","zip-webpack-plugin":"^4.0.1"},"devDependencies":{"@types/adm-zip":"^0.5.7","@types/decompress":"^4.2.7","@types/node":"^20.19.9","terminal-link":"^3.0.0"}}');
30627
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@squidcloud/cli","version":"1.0.423","description":"The Squid CLI","main":"dist/index.js","scripts":{"start":"node dist/index.js","start-ts":"ts-node -r tsconfig-paths/register src/index.ts","prebuild":"rimraf dist","build":"webpack --mode=production","build:dev":"webpack --mode=development","lint":"eslint","link":"npm run build && chmod 755 dist/index.js && npm link","watch":"webpack --watch","deploy":"npm run build && npm pack --silent | xargs -I {} mv {} package.tgz && npm install -g package.tgz && rm -rf package.tgz","publish:public":"npm run build && npm publish --access public"},"files":["dist/**/*"],"bin":{"squid":"dist/index.js"},"keywords":[],"author":"","license":"ISC","engines":{"node":">=18.0.0"},"dependencies":{"@squidcloud/local-backend":"^1.0.423","adm-zip":"^0.5.16","copy-webpack-plugin":"^12.0.2","decompress":"^4.2.1","nodemon":"^3.1.9","terser-webpack-plugin":"^5.3.10","ts-loader":"^9.5.1","ts-node":"^10.9.2","tsconfig-paths":"^4.2.0","tsconfig-paths-webpack-plugin":"^4.1.0","webpack":"^5.101.3","zip-webpack-plugin":"^4.0.1"},"devDependencies":{"@types/adm-zip":"^0.5.7","@types/decompress":"^4.2.7","@types/node":"^20.19.9","terminal-link":"^3.0.0"}}');
30627
30628
 
30628
30629
  /***/ }),
30629
30630
 
@@ -0,0 +1,1244 @@
1
+ ---
2
+ name: Squid React Development
3
+ description: Provides comprehensive, code-verified knowledge about developing React applications with Squid using @squidcloud/react. Use this skill proactively before writing, editing, or creating any React code that uses Squid's React SDK hooks to ensure compliance with Squid's React APIs and best practices.
4
+ ---
5
+
6
+ # Squid React Development Skill (Verified)
7
+
8
+ This skill provides comprehensive, code-verified knowledge about developing React applications with Squid using the `@squidcloud/react` SDK.
9
+
10
+ ## Overview
11
+
12
+ `@squidcloud/react` is a React wrapper for the Squid Client SDK (`@squidcloud/client`) that provides:
13
+ - React Context provider for Squid instance management
14
+ - Custom hooks for state management with Squid backend services
15
+ - Support for databases, APIs, AI agents, and real-time data
16
+ - Full TypeScript support with strict type safety
17
+
18
+ **Key Point:** `@squidcloud/react` provides partial hooks for common operations. For any functionality not covered by a dedicated hook, use `useSquid()` to access the full Squid client SDK.
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @squidcloud/react @squidcloud/client rxjs
24
+ ```
25
+
26
+ **Peer Dependencies:**
27
+ - `@squidcloud/client` (^1.0.415+)
28
+ - `react` (16.11+, 17+, 18+, 19+)
29
+ - `rxjs` (>=7.5.7 <8.0.0)
30
+
31
+ ## Setup: SquidContextProvider
32
+
33
+ Wrap your application with `SquidContextProvider` to make the Squid instance available to all child components.
34
+
35
+ ```typescript
36
+ import { SquidContextProvider } from '@squidcloud/react';
37
+ import { createRoot } from 'react-dom/client';
38
+ import App from './App';
39
+
40
+ createRoot(document.getElementById('root')!).render(
41
+ <SquidContextProvider
42
+ options={{
43
+ appId: import.meta.env.VITE_SQUID_APP_ID,
44
+ region: import.meta.env.VITE_SQUID_REGION,
45
+ environmentId: import.meta.env.VITE_SQUID_ENVIRONMENT_ID,
46
+ squidDeveloperId: import.meta.env.VITE_SQUID_DEVELOPER_ID,
47
+ // Optional: API key for AI queries and admin operations
48
+ apiKey: import.meta.env.VITE_SQUID_API_KEY,
49
+ }}
50
+ >
51
+ <App />
52
+ </SquidContextProvider>
53
+ );
54
+ ```
55
+
56
+ ### SquidContextProvider Props
57
+
58
+ ```typescript
59
+ interface SquidContextProps {
60
+ children: React.ReactNode;
61
+ options: SquidOptions; // Same options as Squid client SDK
62
+ }
63
+ ```
64
+
65
+ The `options` prop accepts all standard Squid client options including:
66
+ - `appId` - Your Squid application ID
67
+ - `region` - Squid region (e.g., 'us-east-1.aws')
68
+ - `environmentId` - Environment ID (e.g., 'dev', 'prod')
69
+ - `squidDeveloperId` - Your Squid developer ID
70
+ - `apiKey` - Optional API key for elevated permissions
71
+ - `authProvider` - Optional authentication provider
72
+
73
+ ## Core Hooks
74
+
75
+ ### useSquid
76
+
77
+ Access the Squid client instance directly. Use this for any operations not covered by other hooks.
78
+
79
+ ```typescript
80
+ import { useSquid } from '@squidcloud/react';
81
+
82
+ function MyComponent() {
83
+ const squid = useSquid();
84
+
85
+ // Now you can use any Squid client SDK method
86
+ const handleClick = async () => {
87
+ // Execute backend function
88
+ await squid.executeFunction('myFunction', { param: 'value' });
89
+
90
+ // Access AI
91
+ const agent = squid.ai().agent('my-agent');
92
+
93
+ // Access storage
94
+ const storage = squid.storage();
95
+
96
+ // Access queues
97
+ const queue = squid.queue('my-queue');
98
+
99
+ // Any other Squid client SDK operation
100
+ };
101
+
102
+ return <button onClick={handleClick}>Do Something</button>;
103
+ }
104
+ ```
105
+
106
+ **Important:** `useSquid()` must be used within a `SquidContextProvider`. It throws an error if used outside.
107
+
108
+ ### useCollection
109
+
110
+ Get a reference to a Squid collection for querying and mutations.
111
+
112
+ ```typescript
113
+ import { useCollection } from '@squidcloud/react';
114
+
115
+ interface User {
116
+ id: string;
117
+ name: string;
118
+ email: string;
119
+ active: boolean;
120
+ }
121
+
122
+ function UserList() {
123
+ const usersCollection = useCollection<User>('users');
124
+
125
+ // Use collection for queries
126
+ const query = usersCollection.query().where('active', '==', true);
127
+
128
+ // Use collection for mutations
129
+ const addUser = async (user: User) => {
130
+ await usersCollection.doc({ id: user.id }).insert(user);
131
+ };
132
+
133
+ return <div>...</div>;
134
+ }
135
+ ```
136
+
137
+ **Signature:**
138
+ ```typescript
139
+ function useCollection<T extends DocumentData>(
140
+ collectionName: CollectionName,
141
+ integrationId?: IntegrationId,
142
+ ): CollectionReference<T>
143
+ ```
144
+
145
+ ### useQuery
146
+
147
+ Subscribe to real-time query updates or fetch a single snapshot.
148
+
149
+ ```typescript
150
+ import { useQuery, useCollection } from '@squidcloud/react';
151
+
152
+ interface Task {
153
+ id: string;
154
+ title: string;
155
+ completed: boolean;
156
+ userId: string;
157
+ }
158
+
159
+ function TaskList({ userId }: { userId: string }) {
160
+ const tasksCollection = useCollection<Task>('tasks');
161
+ const query = tasksCollection.query().where('userId', '==', userId);
162
+
163
+ const { loading, data, error } = useQuery(query, {
164
+ enabled: !!userId, // Only run when userId is set
165
+ subscribe: true, // Real-time updates (default)
166
+ initialData: [], // Initial data before first load
167
+ }, [userId]); // Re-subscribe when userId changes
168
+
169
+ if (loading) return <div>Loading...</div>;
170
+ if (error) return <div>Error: {error.message}</div>;
171
+
172
+ return (
173
+ <ul>
174
+ {data.map(task => (
175
+ <li key={task.id}>{task.title}</li>
176
+ ))}
177
+ </ul>
178
+ );
179
+ }
180
+ ```
181
+
182
+ **Signature:**
183
+ ```typescript
184
+ function useQuery<T>(
185
+ query: SnapshotEmitter<T>,
186
+ options?: QueryOptions<T>,
187
+ deps?: ReadonlyArray<unknown>,
188
+ ): QueryType<T>
189
+
190
+ interface QueryOptions<T> {
191
+ enabled?: boolean; // Default: true
192
+ subscribe?: boolean; // Default: true (continuous updates)
193
+ initialData?: Array<T>; // Default: []
194
+ }
195
+
196
+ interface QueryType<T> {
197
+ loading: boolean;
198
+ data: Array<T>;
199
+ error: any;
200
+ }
201
+ ```
202
+
203
+ **Single Snapshot vs Real-time:**
204
+ ```typescript
205
+ // Real-time updates (subscribe: true) - default
206
+ const { data } = useQuery(query, { subscribe: true });
207
+
208
+ // Single snapshot (subscribe: false) - fetches once
209
+ const { data } = useQuery(query, { subscribe: false });
210
+ ```
211
+
212
+ ### useDoc
213
+
214
+ Get a single document with real-time updates.
215
+
216
+ ```typescript
217
+ import { useDoc, useCollection } from '@squidcloud/react';
218
+
219
+ interface User {
220
+ id: string;
221
+ name: string;
222
+ email: string;
223
+ }
224
+
225
+ function UserProfile({ userId }: { userId: string }) {
226
+ const usersCollection = useCollection<User>('users');
227
+ const docRef = usersCollection.doc({ id: userId });
228
+
229
+ const { loading, data, error } = useDoc(docRef, {
230
+ enabled: !!userId,
231
+ subscribe: true, // Default: true
232
+ });
233
+
234
+ if (loading) return <div>Loading...</div>;
235
+ if (error) return <div>Error: {error.message}</div>;
236
+ if (!data) return <div>User not found</div>;
237
+
238
+ return <div>{data.name}</div>;
239
+ }
240
+ ```
241
+
242
+ **Signature:**
243
+ ```typescript
244
+ function useDoc<T extends DocumentData>(
245
+ doc: DocumentReference<T>,
246
+ options?: DocOptions,
247
+ ): DocType<T>
248
+
249
+ interface DocOptions {
250
+ enabled?: boolean; // Default: true
251
+ subscribe?: boolean; // Default: true
252
+ }
253
+
254
+ interface DocType<T> {
255
+ loading: boolean;
256
+ data: T | undefined;
257
+ error: any;
258
+ }
259
+ ```
260
+
261
+ ### useDocs
262
+
263
+ Get multiple documents in parallel with real-time updates.
264
+
265
+ ```typescript
266
+ import { useDocs, useCollection } from '@squidcloud/react';
267
+
268
+ interface User {
269
+ id: string;
270
+ name: string;
271
+ }
272
+
273
+ function UserAvatars({ userIds }: { userIds: Array<string> }) {
274
+ const usersCollection = useCollection<User>('users');
275
+ const docRefs = userIds.map(id => usersCollection.doc({ id }));
276
+
277
+ const { loading, data, error } = useDocs(docRefs, {
278
+ enabled: userIds.length > 0,
279
+ subscribe: true,
280
+ });
281
+
282
+ if (loading) return <div>Loading...</div>;
283
+ if (error) return <div>Error: {error.message}</div>;
284
+
285
+ return (
286
+ <div>
287
+ {data.map((user, i) => (
288
+ <span key={userIds[i]}>{user?.name ?? 'Unknown'}</span>
289
+ ))}
290
+ </div>
291
+ );
292
+ }
293
+ ```
294
+
295
+ **Signature:**
296
+ ```typescript
297
+ function useDocs<T extends DocumentData>(
298
+ docs: Array<DocumentReference<T>>,
299
+ options?: DocOptions,
300
+ ): DocsType<T>
301
+
302
+ interface DocsType<T> {
303
+ loading: boolean;
304
+ data: Array<T | undefined>; // Matches order of input refs
305
+ error: any;
306
+ }
307
+ ```
308
+
309
+ ### usePagination
310
+
311
+ Paginate query results with navigation controls.
312
+
313
+ ```typescript
314
+ import { usePagination, useCollection } from '@squidcloud/react';
315
+
316
+ interface Post {
317
+ id: string;
318
+ title: string;
319
+ createdAt: number;
320
+ }
321
+
322
+ function PostList() {
323
+ const postsCollection = useCollection<Post>('posts');
324
+ const query = postsCollection.query().sortBy('createdAt', false);
325
+
326
+ const { loading, data, hasNext, hasPrev, next, prev } = usePagination(
327
+ query,
328
+ {
329
+ pageSize: 10,
330
+ enabled: true,
331
+ },
332
+ [], // Dependencies - pagination resets when these change
333
+ );
334
+
335
+ return (
336
+ <div>
337
+ {loading && <div>Loading...</div>}
338
+
339
+ <ul>
340
+ {data.map(post => (
341
+ <li key={post.id}>{post.title}</li>
342
+ ))}
343
+ </ul>
344
+
345
+ <div>
346
+ <button onClick={prev} disabled={!hasPrev}>Previous</button>
347
+ <button onClick={next} disabled={!hasNext}>Next</button>
348
+ </div>
349
+ </div>
350
+ );
351
+ }
352
+ ```
353
+
354
+ **Signature:**
355
+ ```typescript
356
+ function usePagination<T>(
357
+ query: Pick<SnapshotEmitter<T>, 'paginate'>,
358
+ options: PaginationOptions,
359
+ deps?: ReadonlyArray<unknown>,
360
+ ): PaginationType<T>
361
+
362
+ interface PaginationOptions {
363
+ pageSize: number; // Required: items per page
364
+ enabled?: boolean; // Default: true
365
+ // Other Squid pagination options...
366
+ }
367
+
368
+ interface PaginationType<T> {
369
+ loading: boolean;
370
+ data: Array<T>;
371
+ hasNext: boolean;
372
+ hasPrev: boolean;
373
+ next: () => void;
374
+ prev: () => void;
375
+ }
376
+ ```
377
+
378
+ ## AI Hooks
379
+
380
+ ### useAiAgent
381
+
382
+ Main hook for interacting with AI agents.
383
+
384
+ ```typescript
385
+ import { useAiAgent } from '@squidcloud/react';
386
+
387
+ function ChatBot() {
388
+ const {
389
+ chat,
390
+ transcribeAndChat,
391
+ chatWithVoiceResponse,
392
+ transcribeAndChatWithVoiceResponse,
393
+ history,
394
+ statusUpdates,
395
+ data,
396
+ loading,
397
+ error,
398
+ complete,
399
+ } = useAiAgent('support-agent', {
400
+ // Optional default chat options
401
+ smoothTyping: true,
402
+ }, {
403
+ // Optional client options
404
+ apiKey: import.meta.env.VITE_SQUID_AGENT_API_KEY,
405
+ });
406
+
407
+ const [input, setInput] = useState('');
408
+
409
+ const handleSend = () => {
410
+ chat(input, {
411
+ // Per-message options (override defaults)
412
+ memoryOptions: { memoryId: 'session-123' },
413
+ });
414
+ setInput('');
415
+ };
416
+
417
+ return (
418
+ <div>
419
+ {/* Display chat history */}
420
+ <div>
421
+ {history.map((msg) => (
422
+ <div key={msg.id} className={msg.type}>
423
+ {msg.message}
424
+ </div>
425
+ ))}
426
+ {loading && <div>AI is typing...</div>}
427
+ </div>
428
+
429
+ {/* Input */}
430
+ <input
431
+ value={input}
432
+ onChange={(e) => setInput(e.target.value)}
433
+ disabled={loading}
434
+ />
435
+ <button onClick={handleSend} disabled={loading}>Send</button>
436
+ </div>
437
+ );
438
+ }
439
+ ```
440
+
441
+ **Signature:**
442
+ ```typescript
443
+ function useAiAgent(
444
+ agentId: AiAgentId,
445
+ chatOptions?: AiChatOptions,
446
+ clientOptions?: AiAgentClientOptions,
447
+ ): AiHookResponse
448
+
449
+ interface AiHookResponse {
450
+ // Methods
451
+ chat: (prompt: string, options?: AiChatOptions, jobId?: JobId) => void;
452
+ transcribeAndChat: (file: File, options?: AiChatOptions, jobId?: JobId) => void;
453
+ chatWithVoiceResponse: (prompt: string, options?: Omit<AiChatOptions, 'smoothTyping'>, jobId?: JobId) => void;
454
+ transcribeAndChatWithVoiceResponse: (file: File, options?: Omit<AiChatOptions, 'smoothTyping'>, jobId?: JobId) => void;
455
+
456
+ // State
457
+ history: Array<ChatMessage>;
458
+ statusUpdates: Record<JobId, Array<AiStatusMessage>>;
459
+ data: string; // Latest response text
460
+ loading: boolean;
461
+ error: any;
462
+ complete: boolean;
463
+ }
464
+
465
+ interface ChatMessage {
466
+ id: string;
467
+ type: 'ai' | 'user';
468
+ message: string;
469
+ jobId: JobId | undefined;
470
+ voiceFile?: File; // Only on AI messages with voice
471
+ }
472
+ ```
473
+
474
+ **Chat with Memory:**
475
+ ```typescript
476
+ const { chat, history } = useAiAgent('my-agent');
477
+
478
+ // Messages are persisted and loaded automatically
479
+ chat('Hello!', {
480
+ memoryOptions: {
481
+ memoryId: 'user-123-session', // Unique ID for conversation
482
+ },
483
+ });
484
+ ```
485
+
486
+ **Voice Interactions:**
487
+ ```typescript
488
+ const { transcribeAndChat, chatWithVoiceResponse } = useAiAgent('voice-agent');
489
+
490
+ // Transcribe audio and get text response
491
+ const handleAudioInput = (audioFile: File) => {
492
+ transcribeAndChat(audioFile);
493
+ };
494
+
495
+ // Send text and get voice response
496
+ const handleTextWithVoice = (text: string) => {
497
+ chatWithVoiceResponse(text, {
498
+ voiceOptions: {
499
+ voiceId: 'alloy',
500
+ model: 'tts-1',
501
+ },
502
+ });
503
+ };
504
+ ```
505
+
506
+ ### useAiChat
507
+
508
+ Simplified wrapper for `useAiAgent` when you only need basic chat functionality.
509
+
510
+ ```typescript
511
+ import { useAiChat } from '@squidcloud/react';
512
+
513
+ function SimpleChat() {
514
+ const { chat, history, loading } = useAiChat('my-agent');
515
+
516
+ return (
517
+ <div>
518
+ {history.map(msg => (
519
+ <div key={msg.id}>{msg.type}: {msg.message}</div>
520
+ ))}
521
+ <button onClick={() => chat('Hello!')}>Say Hello</button>
522
+ </div>
523
+ );
524
+ }
525
+ ```
526
+
527
+ ### useAiQuery
528
+
529
+ AI-powered database queries. Requires Squid API key.
530
+
531
+ ```typescript
532
+ import { useAiQuery } from '@squidcloud/react';
533
+
534
+ function DataExplorer() {
535
+ const { chat, data, loading, error } = useAiQuery('my-database', {
536
+ // Optional AI query options
537
+ });
538
+
539
+ const [question, setQuestion] = useState('');
540
+
541
+ const handleAsk = () => {
542
+ chat(question); // e.g., "Show me all users who signed up last month"
543
+ };
544
+
545
+ return (
546
+ <div>
547
+ <input
548
+ value={question}
549
+ onChange={(e) => setQuestion(e.target.value)}
550
+ placeholder="Ask about your data..."
551
+ />
552
+ <button onClick={handleAsk}>Ask</button>
553
+
554
+ {loading && <div>Thinking...</div>}
555
+ {data && <div dangerouslySetInnerHTML={{ __html: data }} />}
556
+ </div>
557
+ );
558
+ }
559
+ ```
560
+
561
+ ### useAiOnApi
562
+
563
+ AI-powered API querying.
564
+
565
+ ```typescript
566
+ import { useAiOnApi } from '@squidcloud/react';
567
+
568
+ function ApiExplorer() {
569
+ const { chat, data, loading } = useAiOnApi(
570
+ 'my-api-integration',
571
+ ['GET /users', 'GET /orders'], // Optional: restrict endpoints
572
+ true, // Include explanation
573
+ );
574
+
575
+ return (
576
+ <div>
577
+ <button onClick={() => chat('Get all active users')}>
578
+ Query API
579
+ </button>
580
+ {loading && <div>Loading...</div>}
581
+ {data && <pre>{data}</pre>}
582
+ </div>
583
+ );
584
+ }
585
+ ```
586
+
587
+ ### useAskWithApi
588
+
589
+ Query a custom API endpoint with AI.
590
+
591
+ ```typescript
592
+ import { useAskWithApi } from '@squidcloud/react';
593
+
594
+ function CustomApiChat() {
595
+ const { chat, data, loading, error } = useAskWithApi({
596
+ customApiUrl: 'https://my-api.com/ai/chat',
597
+ customApiHeaders: {
598
+ 'X-Custom-Header': 'value',
599
+ },
600
+ agentId: 'optional-agent-id',
601
+ });
602
+
603
+ return (
604
+ <div>
605
+ <button onClick={() => chat('Hello custom API')}>Send</button>
606
+ {loading && <div>Loading...</div>}
607
+ {data && <div>{data}</div>}
608
+ </div>
609
+ );
610
+ }
611
+ ```
612
+
613
+ ## Utility Hooks
614
+
615
+ ### useQueue
616
+
617
+ Subscribe to queue messages and produce new ones.
618
+
619
+ ```typescript
620
+ import { useQueue, useSquid } from '@squidcloud/react';
621
+
622
+ interface Message {
623
+ id: string;
624
+ content: string;
625
+ timestamp: number;
626
+ }
627
+
628
+ function MessageQueue() {
629
+ const squid = useSquid();
630
+ const queue = squid.queue<Message>('notifications');
631
+
632
+ const { data, error, produce } = useQueue(queue, {
633
+ enabled: true,
634
+ });
635
+
636
+ const sendMessage = async () => {
637
+ await produce([{
638
+ id: crypto.randomUUID(),
639
+ content: 'Hello!',
640
+ timestamp: Date.now(),
641
+ }]);
642
+ };
643
+
644
+ return (
645
+ <div>
646
+ <div>Latest message: {data?.content}</div>
647
+ <button onClick={sendMessage}>Send Message</button>
648
+ </div>
649
+ );
650
+ }
651
+ ```
652
+
653
+ **Signature:**
654
+ ```typescript
655
+ function useQueue<T>(
656
+ queue: QueueManager<T>,
657
+ options?: QueueOptions,
658
+ deps?: ReadonlyArray<unknown>,
659
+ ): QueueType<T>
660
+
661
+ interface QueueOptions {
662
+ enabled?: boolean; // Default: true
663
+ }
664
+
665
+ interface QueueType<T> {
666
+ data: T | null;
667
+ error: any;
668
+ produce: (messages: Array<T>) => Promise<void>;
669
+ }
670
+ ```
671
+
672
+ ### useObservable
673
+
674
+ Subscribe to any RxJS Observable. Base hook used by other hooks.
675
+
676
+ ```typescript
677
+ import { useObservable, useSquid } from '@squidcloud/react';
678
+
679
+ function CustomSubscription() {
680
+ const squid = useSquid();
681
+
682
+ const { loading, data, error, complete } = useObservable(
683
+ () => squid.collection('users').query().snapshots(),
684
+ {
685
+ enabled: true,
686
+ initialData: [],
687
+ },
688
+ [], // Dependencies
689
+ );
690
+
691
+ return <div>{data.length} users</div>;
692
+ }
693
+ ```
694
+
695
+ **Signature:**
696
+ ```typescript
697
+ function useObservable<T>(
698
+ observable: () => Observable<T>,
699
+ options?: ObservableOptions<T>,
700
+ deps?: ReadonlyArray<unknown>,
701
+ ): ObservableType<T>
702
+
703
+ interface ObservableOptions<T> {
704
+ enabled?: boolean; // Default: true
705
+ initialData?: T; // Default: null
706
+ }
707
+
708
+ interface ObservableType<T> {
709
+ loading: boolean;
710
+ data: T;
711
+ error: any;
712
+ complete: boolean;
713
+ }
714
+ ```
715
+
716
+ ### usePromise
717
+
718
+ Handle promise-based async operations.
719
+
720
+ ```typescript
721
+ import { usePromise, useSquid } from '@squidcloud/react';
722
+
723
+ function AsyncData({ userId }: { userId: string }) {
724
+ const squid = useSquid();
725
+
726
+ const { loading, data, error } = usePromise(
727
+ () => squid.executeFunction('getUserDetails', { userId }),
728
+ {
729
+ enabled: !!userId,
730
+ initialData: null,
731
+ },
732
+ [userId],
733
+ );
734
+
735
+ if (loading) return <div>Loading...</div>;
736
+ if (error) return <div>Error: {error.message}</div>;
737
+
738
+ return <div>{data?.name}</div>;
739
+ }
740
+ ```
741
+
742
+ **Signature:**
743
+ ```typescript
744
+ function usePromise<T>(
745
+ promiseFn: () => Promise<T>,
746
+ options?: PromiseOptions<T>,
747
+ deps?: ReadonlyArray<unknown>,
748
+ ): PromiseType<T>
749
+
750
+ interface PromiseOptions<T> {
751
+ enabled?: boolean; // Default: true
752
+ initialData?: T; // Default: null
753
+ }
754
+
755
+ interface PromiseType<T> {
756
+ loading: boolean;
757
+ data: T;
758
+ error: any;
759
+ }
760
+ ```
761
+
762
+ ## Server-Side Rendering (SSR)
763
+
764
+ ### withServerQuery HOC
765
+
766
+ For Next.js and other SSR frameworks, use `withServerQuery` to fetch initial data on the server.
767
+
768
+ ```typescript
769
+ import { withServerQuery, WithQueryProps } from '@squidcloud/react';
770
+ import { Squid } from '@squidcloud/client';
771
+
772
+ interface Post {
773
+ id: string;
774
+ title: string;
775
+ content: string;
776
+ }
777
+
778
+ // Component receives data prop from HOC
779
+ function PostListComponent({ data }: WithQueryProps<Post>) {
780
+ return (
781
+ <ul>
782
+ {data.map(post => (
783
+ <li key={post.id}>{post.title}</li>
784
+ ))}
785
+ </ul>
786
+ );
787
+ }
788
+
789
+ // Create HOC with query
790
+ const squid = new Squid({ /* options */ });
791
+ const query = squid.collection<Post>('posts').query();
792
+
793
+ export const PostList = withServerQuery(
794
+ PostListComponent,
795
+ query,
796
+ { subscribe: true }, // Continue real-time updates on client
797
+ );
798
+ ```
799
+
800
+ **Signature:**
801
+ ```typescript
802
+ function withServerQuery<C extends React.ComponentType<any>, T>(
803
+ Component: C,
804
+ query: SnapshotEmitter<T>,
805
+ options?: WithQueryOptions,
806
+ ): React.FC<Omit<React.ComponentProps<C>, keyof WithQueryProps<T>>>
807
+
808
+ interface WithQueryProps<T> {
809
+ data: Array<T>; // Injected by HOC
810
+ }
811
+
812
+ interface WithQueryOptions {
813
+ subscribe?: boolean; // Default: true - continue updates on client
814
+ }
815
+ ```
816
+
817
+ ## Common Patterns
818
+
819
+ ### Pattern 1: Conditional Queries
820
+
821
+ ```typescript
822
+ function UserTasks({ userId }: { userId: string | null }) {
823
+ const tasksCollection = useCollection<Task>('tasks');
824
+ const query = userId
825
+ ? tasksCollection.query().where('userId', '==', userId)
826
+ : tasksCollection.query().limit(0);
827
+
828
+ const { loading, data } = useQuery(query, {
829
+ enabled: !!userId, // Only runs when userId is set
830
+ }, [userId]);
831
+
832
+ return <div>{data.length} tasks</div>;
833
+ }
834
+ ```
835
+
836
+ ### Pattern 2: Dependent Queries
837
+
838
+ ```typescript
839
+ function UserWithPosts({ userId }: { userId: string }) {
840
+ const usersCollection = useCollection<User>('users');
841
+ const postsCollection = useCollection<Post>('posts');
842
+
843
+ // First query: get user
844
+ const { data: user, loading: userLoading } = useDoc(
845
+ usersCollection.doc({ id: userId })
846
+ );
847
+
848
+ // Second query: depends on user
849
+ const postsQuery = postsCollection.query().where('authorId', '==', userId);
850
+ const { data: posts, loading: postsLoading } = useQuery(
851
+ postsQuery,
852
+ { enabled: !!user }, // Only run when user is loaded
853
+ [userId],
854
+ );
855
+
856
+ if (userLoading || postsLoading) return <div>Loading...</div>;
857
+
858
+ return (
859
+ <div>
860
+ <h1>{user?.name}</h1>
861
+ <p>{posts.length} posts</p>
862
+ </div>
863
+ );
864
+ }
865
+ ```
866
+
867
+ ### Pattern 3: Mutations with useSquid
868
+
869
+ Hooks are for reading data. Use `useSquid()` for mutations:
870
+
871
+ ```typescript
872
+ function TodoApp() {
873
+ const squid = useSquid();
874
+ const todosCollection = useCollection<Todo>('todos');
875
+
876
+ // Read with hook
877
+ const { data: todos } = useQuery(todosCollection.query());
878
+
879
+ // Mutations with squid client
880
+ const addTodo = async (title: string) => {
881
+ await todosCollection.doc({ id: crypto.randomUUID() }).insert({
882
+ id: crypto.randomUUID(),
883
+ title,
884
+ completed: false,
885
+ });
886
+ };
887
+
888
+ const toggleTodo = async (id: string, completed: boolean) => {
889
+ await todosCollection.doc({ id }).update({ completed });
890
+ };
891
+
892
+ const deleteTodo = async (id: string) => {
893
+ await todosCollection.doc({ id }).delete();
894
+ };
895
+
896
+ return (
897
+ <div>
898
+ <button onClick={() => addTodo('New Todo')}>Add</button>
899
+ {todos.map(todo => (
900
+ <div key={todo.id}>
901
+ <input
902
+ type="checkbox"
903
+ checked={todo.completed}
904
+ onChange={() => toggleTodo(todo.id, !todo.completed)}
905
+ />
906
+ {todo.title}
907
+ <button onClick={() => deleteTodo(todo.id)}>Delete</button>
908
+ </div>
909
+ ))}
910
+ </div>
911
+ );
912
+ }
913
+ ```
914
+
915
+ ### Pattern 4: Using Squid Client for Non-Hook Operations
916
+
917
+ For any Squid functionality without a dedicated hook, use `useSquid()`:
918
+
919
+ ```typescript
920
+ function AdvancedFeatures() {
921
+ const squid = useSquid();
922
+
923
+ // Execute backend functions
924
+ const processPayment = async (amount: number) => {
925
+ return await squid.executeFunction('processPayment', { amount });
926
+ };
927
+
928
+ // File uploads
929
+ const uploadFile = async (file: File) => {
930
+ const storage = squid.storage();
931
+ return await storage.uploadFile(file, 'uploads/' + file.name);
932
+ };
933
+
934
+ // AI agent management
935
+ const createAgent = async () => {
936
+ const agent = squid.ai().agent('new-agent');
937
+ await agent.upsert({
938
+ description: 'My new agent',
939
+ options: { model: 'gpt-4o' },
940
+ });
941
+ };
942
+
943
+ // Transactions
944
+ const transferFunds = async (from: string, to: string, amount: number) => {
945
+ await squid.runInTransaction(async (txId) => {
946
+ const accounts = squid.collection('accounts');
947
+
948
+ const fromAccount = await accounts.doc({ id: from }).snapshot();
949
+ const toAccount = await accounts.doc({ id: to }).snapshot();
950
+
951
+ await accounts.doc({ id: from }).update(
952
+ { balance: fromAccount!.balance - amount },
953
+ txId,
954
+ );
955
+ await accounts.doc({ id: to }).update(
956
+ { balance: toAccount!.balance + amount },
957
+ txId,
958
+ );
959
+ });
960
+ };
961
+
962
+ return <div>...</div>;
963
+ }
964
+ ```
965
+
966
+ ### Pattern 5: Error Handling
967
+
968
+ ```typescript
969
+ function SafeDataFetch() {
970
+ const { loading, data, error } = useQuery(/* ... */);
971
+
972
+ if (error) {
973
+ // Log error for debugging
974
+ console.error('Query failed:', error);
975
+
976
+ // Show user-friendly message
977
+ return (
978
+ <div className="error">
979
+ <p>Failed to load data. Please try again.</p>
980
+ <button onClick={() => window.location.reload()}>Retry</button>
981
+ </div>
982
+ );
983
+ }
984
+
985
+ if (loading) {
986
+ return <LoadingSpinner />;
987
+ }
988
+
989
+ return <DataDisplay data={data} />;
990
+ }
991
+ ```
992
+
993
+ ### Pattern 6: Optimistic Updates
994
+
995
+ ```typescript
996
+ function OptimisticTodo() {
997
+ const squid = useSquid();
998
+ const todosCollection = useCollection<Todo>('todos');
999
+ const { data: todos } = useQuery(todosCollection.query());
1000
+
1001
+ const [optimisticTodos, setOptimisticTodos] = useState<Array<Todo>>([]);
1002
+
1003
+ const displayTodos = [...todos, ...optimisticTodos];
1004
+
1005
+ const addTodo = async (title: string) => {
1006
+ const newTodo = {
1007
+ id: crypto.randomUUID(),
1008
+ title,
1009
+ completed: false,
1010
+ };
1011
+
1012
+ // Optimistic update
1013
+ setOptimisticTodos(prev => [...prev, newTodo]);
1014
+
1015
+ try {
1016
+ await todosCollection.doc({ id: newTodo.id }).insert(newTodo);
1017
+ // Remove from optimistic list (real data will appear via subscription)
1018
+ setOptimisticTodos(prev => prev.filter(t => t.id !== newTodo.id));
1019
+ } catch (error) {
1020
+ // Revert optimistic update on error
1021
+ setOptimisticTodos(prev => prev.filter(t => t.id !== newTodo.id));
1022
+ // Show error to user
1023
+ }
1024
+ };
1025
+
1026
+ return <div>{displayTodos.map(/* ... */)}</div>;
1027
+ }
1028
+ ```
1029
+
1030
+ ## Best Practices
1031
+
1032
+ ### 1. Always Use Dependencies Correctly
1033
+
1034
+ ```typescript
1035
+ // GOOD: Include all values that affect the query
1036
+ const { data } = useQuery(
1037
+ collection.query().where('userId', '==', userId).where('status', '==', status),
1038
+ {},
1039
+ [userId, status], // Re-subscribe when these change
1040
+ );
1041
+
1042
+ // BAD: Missing dependencies can cause stale data
1043
+ const { data } = useQuery(
1044
+ collection.query().where('userId', '==', userId),
1045
+ {},
1046
+ [], // Missing userId - won't re-fetch when userId changes!
1047
+ );
1048
+ ```
1049
+
1050
+ ### 2. Use `enabled` for Conditional Fetching
1051
+
1052
+ ```typescript
1053
+ // GOOD: Prevent unnecessary queries
1054
+ const { data } = useQuery(query, { enabled: !!userId }, [userId]);
1055
+
1056
+ // BAD: Query runs even when userId is null
1057
+ const { data } = useQuery(query, {}, [userId]);
1058
+ ```
1059
+
1060
+ ### 3. Provide Type Parameters
1061
+
1062
+ ```typescript
1063
+ // GOOD: Full type safety
1064
+ interface User {
1065
+ id: string;
1066
+ name: string;
1067
+ email: string;
1068
+ }
1069
+
1070
+ const collection = useCollection<User>('users');
1071
+ const { data } = useQuery(collection.query()); // data: Array<User>
1072
+
1073
+ // BAD: Loses type information
1074
+ const collection = useCollection('users'); // collection: CollectionReference<DocumentData>
1075
+ ```
1076
+
1077
+ ### 4. Handle Loading and Error States
1078
+
1079
+ ```typescript
1080
+ // GOOD: Always handle all states
1081
+ const { loading, data, error } = useQuery(query);
1082
+
1083
+ if (loading) return <Spinner />;
1084
+ if (error) return <ErrorMessage error={error} />;
1085
+ if (!data.length) return <EmptyState />;
1086
+
1087
+ return <DataList data={data} />;
1088
+
1089
+ // BAD: Can cause runtime errors or poor UX
1090
+ const { data } = useQuery(query);
1091
+ return <DataList data={data} />; // data might be empty array during loading!
1092
+ ```
1093
+
1094
+ ### 5. Separate Read and Write Concerns
1095
+
1096
+ ```typescript
1097
+ // GOOD: Hooks for reads, squid client for writes
1098
+ function Component() {
1099
+ const squid = useSquid();
1100
+ const collection = useCollection<Item>('items');
1101
+
1102
+ // Read with hook (reactive)
1103
+ const { data: items } = useQuery(collection.query());
1104
+
1105
+ // Write with squid client (imperative)
1106
+ const addItem = () => squid.collection<Item>('items').doc({ id: '...' }).insert({ ... });
1107
+ }
1108
+ ```
1109
+
1110
+ ### 6. Memoize Query Objects When Needed
1111
+
1112
+ ```typescript
1113
+ // If building complex queries, memoize to prevent unnecessary re-renders
1114
+ function FilteredList({ filters }: { filters: Filters }) {
1115
+ const collection = useCollection<Item>('items');
1116
+
1117
+ const query = useMemo(() => {
1118
+ let q = collection.query();
1119
+ if (filters.status) q = q.where('status', '==', filters.status);
1120
+ if (filters.category) q = q.where('category', '==', filters.category);
1121
+ return q;
1122
+ }, [collection, filters.status, filters.category]);
1123
+
1124
+ const { data } = useQuery(query, {}, [filters.status, filters.category]);
1125
+
1126
+ return <div>{data.length} items</div>;
1127
+ }
1128
+ ```
1129
+
1130
+ ## TypeScript Types
1131
+
1132
+ All hooks and components are fully typed. Key types exported from `@squidcloud/react`:
1133
+
1134
+ ```typescript
1135
+ // Context
1136
+ export type { SquidContextType, SquidContextProps };
1137
+ export { SquidContext, SquidContextProvider };
1138
+
1139
+ // Hooks
1140
+ export { useSquid };
1141
+ export { useCollection };
1142
+ export { useQuery, QueryType, QueryOptions };
1143
+ export { useDoc, DocType, DocOptions };
1144
+ export { useDocs, DocsType };
1145
+ export { usePagination, PaginationType, PaginationOptions };
1146
+ export { useObservable, ObservableType, ObservableOptions };
1147
+ export { usePromise, PromiseType, PromiseOptions };
1148
+ export { useQueue, QueueType, QueueOptions };
1149
+
1150
+ // AI Hooks
1151
+ export { useAiAgent, useAiChat, useAiQuery, useAiOnApi, useAskWithApi };
1152
+ export type { AiHookResponse, ChatMessage, AiChatMessage, UserChatMessage, CustomApiOptions };
1153
+
1154
+ // HOC
1155
+ export { withServerQuery };
1156
+ export type { WithQueryProps, WithQueryOptions };
1157
+ ```
1158
+
1159
+ ## Relationship with Squid Client SDK
1160
+
1161
+ `@squidcloud/react` is a thin wrapper around `@squidcloud/client`. Here's how they relate:
1162
+
1163
+ | React Hook | Client SDK Equivalent |
1164
+ |------------|----------------------|
1165
+ | `useSquid()` | `Squid.getInstance(options)` |
1166
+ | `useCollection(name)` | `squid.collection(name)` |
1167
+ | `useQuery(query)` | `query.snapshots()` / `query.snapshot()` |
1168
+ | `useDoc(docRef)` | `docRef.snapshots()` / `docRef.snapshot()` |
1169
+ | `usePagination(query)` | `query.paginate(options)` |
1170
+ | `useQueue(queue)` | `queue.consume()` / `queue.produce()` |
1171
+ | `useAiAgent(id)` | `squid.ai().agent(id)` |
1172
+ | `useObservable(obs)` | Direct RxJS subscription |
1173
+
1174
+ **When to use hooks vs. client SDK directly:**
1175
+
1176
+ - **Use hooks** for: Reading/subscribing to data in components
1177
+ - **Use client SDK** (via `useSquid()`) for: Mutations, backend functions, file uploads, transactions, and any operation that doesn't need reactive updates
1178
+
1179
+ ## Common Mistakes to Avoid
1180
+
1181
+ ### 1. Using Hooks Outside Provider
1182
+
1183
+ ```typescript
1184
+ // ERROR: Will throw
1185
+ function App() {
1186
+ const squid = useSquid(); // Error: must be within SquidContextProvider
1187
+ return <div>...</div>;
1188
+ }
1189
+
1190
+ // CORRECT
1191
+ function App() {
1192
+ return (
1193
+ <SquidContextProvider options={...}>
1194
+ <MyComponent />
1195
+ </SquidContextProvider>
1196
+ );
1197
+ }
1198
+
1199
+ function MyComponent() {
1200
+ const squid = useSquid(); // Works!
1201
+ return <div>...</div>;
1202
+ }
1203
+ ```
1204
+
1205
+ ### 2. Forgetting to Handle Empty/Undefined Data
1206
+
1207
+ ```typescript
1208
+ // BAD: data.map will fail during loading
1209
+ const { data } = useQuery(query);
1210
+ return data.map(item => ...);
1211
+
1212
+ // GOOD: Check loading or provide initialData
1213
+ const { loading, data } = useQuery(query, { initialData: [] });
1214
+ if (loading) return <Loading />;
1215
+ return data.map(item => ...);
1216
+ ```
1217
+
1218
+ ### 3. Creating Queries in Render
1219
+
1220
+ ```typescript
1221
+ // BAD: Creates new query every render, causing infinite re-subscriptions
1222
+ function List({ userId }) {
1223
+ const collection = useCollection('items');
1224
+ const { data } = useQuery(
1225
+ collection.query().where('userId', '==', userId), // New object every render!
1226
+ {},
1227
+ [], // Empty deps won't help
1228
+ );
1229
+ }
1230
+
1231
+ // GOOD: Use deps to control re-subscription
1232
+ function List({ userId }) {
1233
+ const collection = useCollection('items');
1234
+ const query = collection.query().where('userId', '==', userId);
1235
+ const { data } = useQuery(query, {}, [userId]); // Re-subscribe when userId changes
1236
+ }
1237
+ ```
1238
+
1239
+ ### 4. Missing RxJS Peer Dependency
1240
+
1241
+ ```bash
1242
+ # If you see errors about Observable, install rxjs:
1243
+ npm install rxjs
1244
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squidcloud/cli",
3
- "version": "1.0.422",
3
+ "version": "1.0.423",
4
4
  "description": "The Squid CLI",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -28,7 +28,7 @@
28
28
  "node": ">=18.0.0"
29
29
  },
30
30
  "dependencies": {
31
- "@squidcloud/local-backend": "^1.0.422",
31
+ "@squidcloud/local-backend": "^1.0.423",
32
32
  "adm-zip": "^0.5.16",
33
33
  "copy-webpack-plugin": "^12.0.2",
34
34
  "decompress": "^4.2.1",