@squidcloud/cli 1.0.488 → 1.0.489

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
@@ -32174,7 +32174,8 @@ module.exports = function toBuffer(data, encoding) {
32174
32174
  var __webpack_unused_export__;
32175
32175
 
32176
32176
  __webpack_unused_export__ = ({ value: true });
32177
- __webpack_unused_export__ = __webpack_unused_export__ = __webpack_unused_export__ = __webpack_unused_export__ = __webpack_unused_export__ = exports.h3 = exports.VF = __webpack_unused_export__ = exports.F3 = exports.eS = exports.Eh = exports.dH = exports.mz = exports.Pq = exports.lZ = exports.I1 = exports.xV = exports.Kq = exports.oL = exports.hp = exports.AI_PROVIDER_TYPES = __webpack_unused_export__ = void 0;
32177
+ __webpack_unused_export__ = __webpack_unused_export__ = __webpack_unused_export__ = __webpack_unused_export__ = __webpack_unused_export__ = exports.h3 = exports.VF = __webpack_unused_export__ = exports.F3 = exports.eS = exports.Eh = exports.dH = exports.mz = exports.Pq = exports.lZ = exports.I1 = exports.xV = exports.Kq = exports.oL = exports.hp = exports.MD = exports.AI_PROVIDER_TYPES = __webpack_unused_export__ = void 0;
32178
+ __webpack_unused_export__ = isFileCapableProviderType;
32178
32179
  __webpack_unused_export__ = isVendorAiChatModelName;
32179
32180
  __webpack_unused_export__ = isAiEmbeddingsModelName;
32180
32181
  __webpack_unused_export__ = isIntegrationEmbeddingModelSpec;
@@ -32204,29 +32205,40 @@ exports.AI_PROVIDER_TYPES = [
32204
32205
  'vertex',
32205
32206
  'external', // This is a technicality, referring to user-defined providers.
32206
32207
  ];
32208
+ /**
32209
+ * The AI providers that support file upload operations, and so are the only ones that resolve a
32210
+ * pre-uploaded file id. Declared as the list, with the type derived from it, so the two cannot drift:
32211
+ * a provider added to one is added to both.
32212
+ */
32213
+ exports.MD = [
32214
+ 'openai',
32215
+ 'gemini',
32216
+ 'anthropic',
32217
+ 'grok',
32218
+ ];
32219
+ /**
32220
+ * True when the provider resolves pre-uploaded file ids. Every other provider — the integration-backed
32221
+ * and user-defined ones, which report no vendor identity of their own — ignores `fileIds` outright, so
32222
+ * for them the ids are inert rather than wrong.
32223
+ */
32224
+ function isFileCapableProviderType(type) {
32225
+ return exports.MD.includes(type);
32226
+ }
32207
32227
  /**
32208
32228
  * Public OpenAI chat model names (active models only).
32209
32229
  * @category AI
32210
32230
  */
32211
- exports.hp = [
32212
- 'gpt-5.4-mini',
32213
- 'gpt-5.4-nano',
32214
- 'gpt-5.5',
32215
- 'gpt-5.5-pro',
32216
- 'gpt-5.6-sol',
32217
- 'gpt-5.6-terra',
32218
- 'gpt-5.6-luna',
32219
- ];
32231
+ exports.hp = ['gpt-5.5-pro', 'gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna'];
32220
32232
  /**
32221
32233
  * Public Gemini chat model names (active models only).
32222
32234
  * @category AI
32223
32235
  */
32224
- exports.oL = ['gemini-3.1-pro', 'gemini-3.6-flash', 'gemini-3.5-flash-lite'];
32236
+ exports.oL = ['gemini-3.1-pro', 'gemini-3.7-flash', 'gemini-3.5-flash-lite'];
32225
32237
  /**
32226
32238
  * Public Grok chat model names (active models only).
32227
32239
  * @category AI
32228
32240
  */
32229
- exports.Kq = ['grok-4.5', 'grok-4-1-fast-reasoning', 'grok-4-1-fast-non-reasoning'];
32241
+ exports.Kq = ['grok-4.6', 'grok-4-1-fast-reasoning', 'grok-4-1-fast-non-reasoning'];
32230
32242
  /**
32231
32243
  * Public Anthropic chat model names (active models only).
32232
32244
  * @category AI
@@ -34735,7 +34747,24 @@ onStagingStarted) {
34735
34747
  activeJobIds.add(jobId);
34736
34748
  const { finalStatus, timedOut } = await waitForJobCompletion(client, jobId, batchLabel, timeoutMillis, isInterrupted);
34737
34749
  activeJobIds.delete(jobId);
34738
- return { batchIndex, batchCount, jobId, uploadFailures, duplicates, finalStatus, timedOut };
34750
+ // A duplicate found by the deferred extraction pass (a presigned object's bytes are first hashed
34751
+ // there) reports as a failed file with `errorCode: 'DUPLICATE_CONTENT'` — the same skip a
34752
+ // staging-time rejection is, so fold it into the duplicates the summary reconciles by name.
34753
+ const deferredDuplicates = (finalStatus?.files ?? [])
34754
+ .filter(file => file.errorCode === 'DUPLICATE_CONTENT')
34755
+ .map(file => file.title);
34756
+ if (deferredDuplicates.length > 0) {
34757
+ console.log(`${batchLabel}: ${deferredDuplicates.length} file(s) skipped as content this knowledge base already holds.`);
34758
+ }
34759
+ return {
34760
+ batchIndex,
34761
+ batchCount,
34762
+ jobId,
34763
+ uploadFailures,
34764
+ duplicates: [...duplicates, ...deferredDuplicates],
34765
+ finalStatus,
34766
+ timedOut,
34767
+ };
34739
34768
  }
34740
34769
  /** Mints presigned upload URLs for a batch, chunking requests to the server's per-call cap. */
34741
34770
  async function createUploadUrlsForBatch(client, fileNames) {
@@ -34832,7 +34861,13 @@ async function cancelActiveJobs(client, activeJobIds) {
34832
34861
  }
34833
34862
  function printProgress(batchLabel, jobId, status) {
34834
34863
  const { counts, state } = status;
34835
- const line = `${batchLabel} job ${jobId}: ${counts.finalized}/${counts.files} finalized, ${counts.failed} failed, state=${state}`;
34864
+ // Deferred duplicates sit inside counts.failed but are skips to the caller (see printSummary) — shown
34865
+ // raw, the live line says "1 failed" on a job the summary then reports as 0 failed, 1 skipped.
34866
+ const duplicateSkips = status.files.filter(file => file.errorCode === 'DUPLICATE_CONTENT').length;
34867
+ const failed = Math.max(0, counts.failed - duplicateSkips);
34868
+ const skippedPart = duplicateSkips > 0 ? `, ${duplicateSkips} skipped` : '';
34869
+ const line = `${batchLabel} job ${jobId}: ${counts.finalized}/${counts.files} finalized, ` +
34870
+ `${failed} failed${skippedPart}, state=${state}`;
34836
34871
  process.stdout.write(`\r${line}${' '.repeat(Math.max(0, PROGRESS_LINE_WIDTH - line.length))}`);
34837
34872
  }
34838
34873
  /** Narrows a `FileUploadResult` to one that uploaded successfully (has a `stagedObjectKey`, no `error`). */
@@ -34855,7 +34890,13 @@ async function printDryRun(dir, files, batchSize) {
34855
34890
  }
34856
34891
  function printSummary(totalFiles, results) {
34857
34892
  const totalFinalized = results.reduce((sum, result) => sum + (result.finalStatus?.counts.finalized ?? 0), 0);
34858
- const totalServerFailed = results.reduce((sum, result) => sum + (result.finalStatus?.counts.failed ?? 0), 0);
34893
+ // Deferred duplicates sit inside counts.failed but are reported on the "Skipped as duplicate" line
34894
+ // (runBatch folds them into `duplicates`), so subtract them here or the two lines double-count.
34895
+ const totalServerFailed = results.reduce((sum, result) => {
34896
+ const failed = result.finalStatus?.counts.failed ?? 0;
34897
+ const duplicateFailures = (result.finalStatus?.files ?? []).filter(file => file.errorCode === 'DUPLICATE_CONTENT').length;
34898
+ return sum + Math.max(0, failed - duplicateFailures);
34899
+ }, 0);
34859
34900
  const totalUploadFailed = results.reduce((sum, result) => sum + result.uploadFailures.length, 0);
34860
34901
  const totalDuplicates = results.reduce((sum, result) => sum + result.duplicates.length, 0);
34861
34902
  const totalStagedButFailed = results.reduce((sum, result) => sum + (result.stagingError ? (result.stagedCount ?? 0) : 0), 0);
@@ -34890,9 +34931,15 @@ function printSummary(totalFiles, results) {
34890
34931
  const state = result.timedOut
34891
34932
  ? `${result.finalStatus?.state ?? 'unknown'} (timed out waiting)`
34892
34933
  : result.finalStatus?.state;
34893
- console.log(` ${label}: job ${result.jobId} ${result.finalStatus?.counts.finalized ?? 0}/${result.finalStatus?.counts.files ?? 0} finalized, ${result.finalStatus?.counts.failed ?? 0} failed [${state}]`);
34934
+ // Deferred duplicates are excluded from BOTH the failed count and the per-file failure lines: they
34935
+ // are already named on the "skipped" lines below, and a clean idempotent re-run must not read as
34936
+ // "0/1 finalized, 1 failed" while exiting 0 — the CLI tells one story about a skip.
34937
+ const deferredDuplicateCount = result.finalStatus?.files.filter(file => file.errorCode === 'DUPLICATE_CONTENT').length ?? 0;
34938
+ const failedCount = Math.max(0, (result.finalStatus?.counts.failed ?? 0) - deferredDuplicateCount);
34939
+ console.log(` ${label}: job ${result.jobId} — ${result.finalStatus?.counts.finalized ?? 0}/${result.finalStatus?.counts.files ?? 0} finalized, ${failedCount} failed [${state}]`);
34894
34940
  // Per-file failure reasons, straight from the job-status response (BulkIngestionFileStatus).
34895
- const failedFiles = result.finalStatus?.files.filter(file => file.status === 'failed') ?? [];
34941
+ const failedFiles = result.finalStatus?.files.filter(file => file.status === 'failed' && file.errorCode !== 'DUPLICATE_CONTENT') ??
34942
+ [];
34896
34943
  for (const failedFile of failedFiles) {
34897
34944
  console.log(` - ${failedFile.title}: ${failedFile.errorMessage ?? 'unknown error'}`);
34898
34945
  }
@@ -34908,7 +34955,10 @@ function hasBatchFailed(result) {
34908
34955
  return true;
34909
34956
  if (!result.finalStatus)
34910
34957
  return true;
34911
- return result.finalStatus.state !== 'completed' || result.finalStatus.counts.failed > 0;
34958
+ // Deferred duplicates are skips, not failures: an idempotent re-run over an already-ingested
34959
+ // directory must exit 0, exactly as it does when staging rejects the duplicates synchronously.
34960
+ const duplicateFailures = result.finalStatus.files.filter(file => file.errorCode === 'DUPLICATE_CONTENT').length;
34961
+ return result.finalStatus.state !== 'completed' || result.finalStatus.counts.failed > duplicateFailures;
34912
34962
  }
34913
34963
  /** Recursively walks `dir`, skipping symlinks and dotfiles/dot-directories, filtered to `extensions`. */
34914
34964
  async function collectFiles(dir, extensions) {
@@ -40729,7 +40779,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"name":"seek-bzip","version":"1.0.6",
40729
40779
  (module) {
40730
40780
 
40731
40781
  "use strict";
40732
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@squidcloud/cli","version":"1.0.488","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.488","adm-zip":"^0.5.16","copy-webpack-plugin":"^14.0.0","decompress":"^4.2.1","logpipes":"^1.11.0","nodemon":"^3.1.9","terser-webpack-plugin":"^5.5.0","ts-loader":"^9.5.1","ts-node":"^10.9.2","tsconfig-paths":"^4.2.0","tsconfig-paths-webpack-plugin":"^4.1.0","webpack":"^5.106.2","zip-webpack-plugin":"^4.0.1"},"devDependencies":{"@types/adm-zip":"^0.5.7","@types/decompress":"^4.2.7","terminal-link":"^3.0.0"}}');
40782
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@squidcloud/cli","version":"1.0.489","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.489","adm-zip":"^0.5.16","copy-webpack-plugin":"^14.0.0","decompress":"^4.2.1","logpipes":"^1.11.0","nodemon":"^3.1.9","terser-webpack-plugin":"^5.5.0","ts-loader":"^9.5.1","ts-node":"^10.9.2","tsconfig-paths":"^4.2.0","tsconfig-paths-webpack-plugin":"^4.1.0","webpack":"^5.106.2","zip-webpack-plugin":"^4.0.1"},"devDependencies":{"@types/adm-zip":"^0.5.7","@types/decompress":"^4.2.7","terminal-link":"^3.0.0"}}');
40733
40783
 
40734
40784
  /***/ }
40735
40785
 
@@ -59,10 +59,10 @@ import {
59
59
 
60
60
  | Provider | `*_CHAT_MODEL_NAMES` |
61
61
  |---|---|
62
- | OpenAI | `gpt-5.4-mini`, `gpt-5.4-nano`, `gpt-5.5`, `gpt-5.5-pro`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna` |
62
+ | OpenAI | `gpt-5.5-pro`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna` |
63
63
  | Anthropic | `claude-fable-5`, `claude-haiku-4-5-20251001`, `claude-opus-5`, `claude-sonnet-5` |
64
- | Gemini | `gemini-3.1-pro`, `gemini-3.6-flash`, `gemini-3.5-flash-lite` |
65
- | Grok | `grok-4.5`, `grok-4-1-fast-reasoning`, `grok-4-1-fast-non-reasoning` |
64
+ | Gemini | `gemini-3.1-pro`, `gemini-3.7-flash`, `gemini-3.5-flash-lite` |
65
+ | Grok | `grok-4.6`, `grok-4-1-fast-reasoning`, `grok-4-1-fast-non-reasoning` |
66
66
 
67
67
  This list moves. **Don't hardcode a model name from this table into user code** — call
68
68
  `squid.ai().listChatModels()`, which also returns the app's custom integration models
@@ -72,7 +72,7 @@ This list moves. **Don't hardcode a model name from this table into user code**
72
72
  **Model Categories:**
73
73
  - **Chat Models**: Used for AI Agents, AI Query, etc. (OpenAI, Anthropic, Gemini, Grok)
74
74
  - **Embedding Models**: Used for Knowledge Bases (OpenAI, Voyage)
75
- - **Image Generation Models**: Used for image creation (DALL-E, Stable Diffusion, Flux)
75
+ - **Image Generation Models**: Used for image creation (OpenAI `gpt-image-*`, Stable Diffusion, Flux)
76
76
  - **Audio Models**: Transcription (Whisper, GPT-4o) and Text-to-Speech (TTS-1, GPT-4o-mini-tts)
77
77
 
78
78
  ## AI Agents
@@ -115,7 +115,7 @@ await myAgent.upsert({
115
115
  isPublic: false, // Whether the agent is publicly accessible
116
116
  auditLog: true, // Enable audit logging for compliance
117
117
  options: {
118
- model: 'gpt-5.4-mini', // or 'claude-sonnet-5', 'gemini-3.6-flash' - see Supported Models
118
+ model: 'gpt-5.6-terra', // or 'claude-sonnet-5', 'gemini-3.7-flash' - see Supported Models
119
119
  instructions: 'You are a helpful customer support assistant. Be concise and professional.',
120
120
  temperature: 0.7
121
121
  }
@@ -386,7 +386,7 @@ const chatObs = agent.chat('What is your return policy?', {
386
386
  connectedKnowledgeBases: [{ knowledgeBaseId: 'kb1', description: 'When to use this KB' }],
387
387
 
388
388
  // Model & generation
389
- model: 'gpt-5.4-mini', // Override agent's default model
389
+ model: 'gpt-5.6-terra', // Override agent's default model
390
390
  temperature: 0.7,
391
391
  maxTokens: 4000,
392
392
  maxOutputTokens: 2000,
@@ -433,7 +433,7 @@ const chatObs = agent.chat('What is your return policy?', {
433
433
  useCodeInterpreter: 'llm', // 'none' | 'llm' (OpenAI/Gemini only)
434
434
  executionPlanOptions: {
435
435
  enabled: true,
436
- model: 'gpt-5.4-mini',
436
+ model: 'gpt-5.6-terra',
437
437
  reasoningEffort: 'high',
438
438
  allowClarificationQuestions: false
439
439
  },
@@ -818,10 +818,10 @@ REST API: [AI Image API](https://docs.getsquid.ai/reference-docs/api/#tag/AI-Ima
818
818
  ### Image Generation
819
819
 
820
820
  ```typescript
821
- // Generate image
821
+ // Generate image - `modelName` is required
822
822
  const imageUrl = await squid.ai().image().generate(
823
823
  'A futuristic city',
824
- { size: '1024x1024', quality: 'hd' }
824
+ { modelName: 'gpt-image-1', size: '1024x1024', quality: 'high' }
825
825
  );
826
826
 
827
827
  // Remove background
@@ -894,7 +894,7 @@ const settings = await aiClient.getApplicationAiSettings();
894
894
 
895
895
  // Set application AI settings
896
896
  await aiClient.setApplicationAiSettings({
897
- defaultModel: 'gpt-5.4-mini',
897
+ defaultModel: 'gpt-5.6-terra',
898
898
  // ... other settings
899
899
  });
900
900
 
@@ -763,7 +763,7 @@ declare namespace JSX {
763
763
  squid-ai-instructions="Be concise and professional."
764
764
  squid-ai-functions="getOrderStatus,lookupAccount"
765
765
  squid-ai-connected-agents='[{"agentId":"billing-agent","description":"Handles billing questions"}]'
766
- squid-ai-override-model="gpt-5.4-mini"
766
+ squid-ai-override-model="gpt-5.6-terra"
767
767
  squid-ai-temperature="0.7"
768
768
  squid-ai-max-tokens="2000"
769
769
  squid-ai-agent-chat-options='{
@@ -77,7 +77,7 @@ Every Squid application includes the **Essentials Connector** - a built-in integ
77
77
  | **Web Utilities** | `squid.web()` | AI-powered web search, URL content extraction, short URLs | [Web section](#web) |
78
78
  | **AI Agents** | `squid.ai().agent()` | Chat with built-in or custom AI agents | [ai.md](ai.md) |
79
79
  | **Knowledge Bases** | `squid.ai().knowledgeBase()` | RAG with semantic, keyword, and knowledge-graph search plus reranking | [ai.md](ai.md) |
80
- | **Image Generation** | `squid.ai().image()` | Generate images with DALL-E | [ai.md](ai.md) |
80
+ | **Image Generation** | `squid.ai().image()` | Generate images with `gpt-image-*`, Stable Diffusion, or Flux | [ai.md](ai.md) |
81
81
  | **Audio** | `squid.ai().audio()` | Transcription and text-to-speech | [ai.md](ai.md) |
82
82
  | **PDF/Extraction** | `squid.extraction()` | Create PDFs, extract data from documents | [Extraction section](#extraction) |
83
83
  | **Observability** | `squid.observability` | Report and query custom metrics | [Observability section](#observability--metrics) |
@@ -156,7 +156,7 @@ const agent = squid.ai().agent('my-agent');
156
156
  await agent.upsert({
157
157
  description: 'Customer support agent',
158
158
  options: {
159
- model: 'gpt-4o',
159
+ model: 'gpt-5.6-terra',
160
160
  instructions: 'You are a helpful support agent...'
161
161
  }
162
162
  });
@@ -7,7 +7,7 @@ OpenAI-specific features in Squid. For general AI usage, see [ai.md](ai.md).
7
7
  ```typescript
8
8
  import {
9
9
  OPENAI_CHAT_MODEL_NAMES, // Chat models
10
- OPENAI_IMAGE_MODEL_NAMES, // DALL-E
10
+ OPENAI_IMAGE_MODEL_NAMES, // gpt-image-*
11
11
  OPENAI_AUDIO_TRANSCRIPTION_MODEL_NAMES, // Whisper
12
12
  OPENAI_AUDIO_CREATE_SPEECH_MODEL_NAMES, // TTS
13
13
  OPENAI_EMBEDDINGS_MODEL_NAMES, // Embeddings
@@ -12,7 +12,7 @@ Each Squid SaaS connector provides built-in AI functions that AI agents can call
12
12
  await squid.ai().agent('my-agent').upsert({
13
13
  description: 'Support agent',
14
14
  options: {
15
- model: 'gpt-5.4-mini',
15
+ model: 'gpt-5.6-terra',
16
16
  instructions: 'You are a support agent that can search tickets and send messages.'
17
17
  }
18
18
  });
@@ -164,12 +164,13 @@ function TaskList({ userId }: { userId: string }) {
164
164
  const tasksCollection = useCollection<Task>('tasks');
165
165
  const query = tasksCollection.query().where('userId', '==', userId);
166
166
 
167
- const { loading, data, error } = useQuery(query, {
167
+ const { loading, data, error, enabled } = useQuery(query, {
168
168
  enabled: !!userId, // Only run when userId is set
169
169
  subscribe: true, // Real-time updates (default)
170
170
  initialData: [], // Initial data before first load
171
171
  }, [userId]); // Re-subscribe when userId changes
172
172
 
173
+ if (!enabled) return <div>No user selected</div>; // Check before `loading`
173
174
  if (loading) return <div>Loading...</div>;
174
175
  if (error) return <div>Error: {error.message}</div>;
175
176
 
@@ -201,9 +202,20 @@ interface QueryType<T> {
201
202
  loading: boolean;
202
203
  data: Array<T>;
203
204
  error: any;
205
+ enabled: boolean; // Mirrors options.enabled
204
206
  }
205
207
  ```
206
208
 
209
+ **`loading` stays `true` while the query is disabled.** A disabled query never subscribes, so it
210
+ never resolves — gate on `enabled` first, or `if (loading)` renders a spinner forever:
211
+
212
+ ```typescript
213
+ const { loading, data, error, enabled } = useQuery(query, { enabled: !!userId }, [userId]);
214
+
215
+ if (!enabled) return <SelectUserPrompt />; // Not "loading" - just not running
216
+ if (loading) return <Spinner />;
217
+ ```
218
+
207
219
  **Single Snapshot vs Real-time:**
208
220
  ```typescript
209
221
  // Real-time updates (subscribe: true) - default
@@ -940,7 +952,7 @@ function AdvancedFeatures() {
940
952
  const agent = squid.ai().agent('new-agent');
941
953
  await agent.upsert({
942
954
  description: 'My new agent',
943
- options: { model: 'gpt-4o' },
955
+ options: { model: 'gpt-5.6-terra' },
944
956
  });
945
957
  };
946
958
 
@@ -1054,13 +1066,18 @@ const { data } = useQuery(
1054
1066
  ### 2. Use `enabled` for Conditional Fetching
1055
1067
 
1056
1068
  ```typescript
1057
- // GOOD: Prevent unnecessary queries
1058
- const { data } = useQuery(query, { enabled: !!userId }, [userId]);
1069
+ // GOOD: Prevent unnecessary queries, and distinguish "disabled" from "loading"
1070
+ const { data, loading, enabled } = useQuery(query, { enabled: !!userId }, [userId]);
1071
+ if (!enabled) return <EmptyState />; // `loading` is still true here
1072
+ if (loading) return <Spinner />;
1059
1073
 
1060
1074
  // BAD: Query runs even when userId is null
1061
1075
  const { data } = useQuery(query, {}, [userId]);
1062
1076
  ```
1063
1077
 
1078
+ Only `useQuery` returns `enabled`. For the other hooks, track the same condition yourself rather
1079
+ than relying on `loading` to go false while they are disabled.
1080
+
1064
1081
  ### 3. Provide Type Parameters
1065
1082
 
1066
1083
  ```typescript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squidcloud/cli",
3
- "version": "1.0.488",
3
+ "version": "1.0.489",
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.488",
31
+ "@squidcloud/local-backend": "^1.0.489",
32
32
  "adm-zip": "^0.5.16",
33
33
  "copy-webpack-plugin": "^14.0.0",
34
34
  "decompress": "^4.2.1",