amplifyquery 2.0.5 → 2.0.7

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.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { getClient } from "./client";
2
- import { queryClient, getQueryClient, createQueryKeys, invalidateModel, invalidateModelItem, invalidateModelByField, invalidateAll, ensureMutationsFlushed } from "./query";
2
+ import { queryClient, getQueryClient, attachQueryClient, detachQueryClient, createQueryKeys, invalidateModel, invalidateModelItem, invalidateModelByField, invalidateAll, ensureMutationsFlushed } from "./query";
3
3
  import { createAmplifyService } from "./service";
4
4
  import { createSingletonService } from "./singleton";
5
5
  import { AmplifyQueryConfig } from "./types";
@@ -30,7 +30,7 @@ import { setModelOwnerQueryMap, setDefaultAuthMode, getModelOwnerQueryMap, getDe
30
30
  */
31
31
  export declare function configure(config: AmplifyQueryConfig): void;
32
32
  export * from "./types";
33
- export { queryClient, getQueryClient, invalidateModel, invalidateModelItem, invalidateModelByField, invalidateAll, ensureMutationsFlushed, createQueryKeys, };
33
+ export { queryClient, getQueryClient, attachQueryClient, detachQueryClient, invalidateModel, invalidateModelItem, invalidateModelByField, invalidateAll, ensureMutationsFlushed, createQueryKeys, };
34
34
  export { getClient };
35
35
  export * from "./provider";
36
36
  export declare const Utils: {
package/dist/index.js CHANGED
@@ -14,13 +14,15 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.AmplifyQuery = exports.getAppUrl = exports.setAppUrl = exports.Auth = exports.Utils = exports.getClient = exports.createQueryKeys = exports.ensureMutationsFlushed = exports.invalidateAll = exports.invalidateModelByField = exports.invalidateModelItem = exports.invalidateModel = exports.getQueryClient = exports.queryClient = void 0;
17
+ exports.AmplifyQuery = exports.getAppUrl = exports.setAppUrl = exports.Auth = exports.Utils = exports.getClient = exports.createQueryKeys = exports.ensureMutationsFlushed = exports.invalidateAll = exports.invalidateModelByField = exports.invalidateModelItem = exports.invalidateModel = exports.detachQueryClient = exports.attachQueryClient = exports.getQueryClient = exports.queryClient = void 0;
18
18
  exports.configure = configure;
19
19
  const client_1 = require("./client");
20
20
  Object.defineProperty(exports, "getClient", { enumerable: true, get: function () { return client_1.getClient; } });
21
21
  const query_1 = require("./query");
22
22
  Object.defineProperty(exports, "queryClient", { enumerable: true, get: function () { return query_1.queryClient; } });
23
23
  Object.defineProperty(exports, "getQueryClient", { enumerable: true, get: function () { return query_1.getQueryClient; } });
24
+ Object.defineProperty(exports, "attachQueryClient", { enumerable: true, get: function () { return query_1.attachQueryClient; } });
25
+ Object.defineProperty(exports, "detachQueryClient", { enumerable: true, get: function () { return query_1.detachQueryClient; } });
24
26
  Object.defineProperty(exports, "createQueryKeys", { enumerable: true, get: function () { return query_1.createQueryKeys; } });
25
27
  Object.defineProperty(exports, "invalidateModel", { enumerable: true, get: function () { return query_1.invalidateModel; } });
26
28
  Object.defineProperty(exports, "invalidateModelItem", { enumerable: true, get: function () { return query_1.invalidateModelItem; } });
package/dist/query.d.ts CHANGED
@@ -17,6 +17,22 @@ export declare let queryClient: QueryClient;
17
17
  * @returns The QueryClient instance
18
18
  */
19
19
  export declare function getQueryClient(): QueryClient;
20
+ /**
21
+ * Attach an external QueryClient (e.g. app-level QueryClientProvider client).
22
+ *
23
+ * Why:
24
+ * - `useHook` uses the app's QueryClient from context.
25
+ * - Direct service calls (e.g. `AmplifyService.Model.create()`) historically updated ONLY the internal singleton client,
26
+ * so hook caches would not update.
27
+ *
28
+ * Attaching lets BOTH direct service calls and hooks operate on the same QueryClient.
29
+ */
30
+ export declare function attachQueryClient(externalClient: QueryClient): void;
31
+ /**
32
+ * Detach and revert to the internal QueryClient.
33
+ * (Mostly useful for tests or advanced setups.)
34
+ */
35
+ export declare function detachQueryClient(): void;
20
36
  /**
21
37
  * AmplifyQuery configuration
22
38
  * @param options Configuration options
package/dist/query.js CHANGED
@@ -11,6 +11,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.queryClient = void 0;
13
13
  exports.getQueryClient = getQueryClient;
14
+ exports.attachQueryClient = attachQueryClient;
15
+ exports.detachQueryClient = detachQueryClient;
14
16
  exports.configure = configure;
15
17
  exports.createQueryKeys = createQueryKeys;
16
18
  exports.invalidateModel = invalidateModel;
@@ -118,10 +120,46 @@ function createMmkvPersister() {
118
120
  },
119
121
  };
120
122
  }
123
+ function setupPersistenceFor(client) {
124
+ var _a;
125
+ if (!config.isCachingEnabled) {
126
+ (0, config_1.debugLog)("🏃‍♀️ React Query offline cache is disabled via flag.");
127
+ return;
128
+ }
129
+ (0, config_1.debugLog)("🏃‍♀️ React Query offline cache is enabled with MMKV.");
130
+ // Create new persister if config changed
131
+ const mmkvPersister = createMmkvPersister();
132
+ try {
133
+ // Lazy-load persistQueryClient to avoid hard dependency resolution at build time.
134
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
135
+ const { persistQueryClient } = require("@tanstack/react-query-persist-client");
136
+ persistQueryClient({
137
+ queryClient: client,
138
+ persister: mmkvPersister,
139
+ // Additional options
140
+ maxAge: ((_a = config.storage) === null || _a === void 0 ? void 0 : _a.maxAge) || 1000 * 60 * 60 * 24 * 7, // Default 7 days
141
+ dehydrateOptions: {
142
+ shouldDehydrateQuery: (query) => {
143
+ var _a;
144
+ // Only persist successful queries to reduce hydration cancellation noise
145
+ if (((_a = query.state) === null || _a === void 0 ? void 0 : _a.status) !== "success")
146
+ return false;
147
+ // Avoid persisting mutation-like or transient keys if needed later
148
+ return true;
149
+ },
150
+ },
151
+ });
152
+ }
153
+ catch (e) {
154
+ console.error("Error setting up React Query persistence:", e);
155
+ }
156
+ }
121
157
  /**
122
158
  * TanStack Query client
123
159
  */
124
160
  exports.queryClient = new react_query_1.QueryClient(config.queryClientConfig);
161
+ let internalQueryClient = exports.queryClient;
162
+ let isExternalClientAttached = false;
125
163
  /**
126
164
  * Get the current query client instance
127
165
  * @returns The QueryClient instance
@@ -129,12 +167,41 @@ exports.queryClient = new react_query_1.QueryClient(config.queryClientConfig);
129
167
  function getQueryClient() {
130
168
  return exports.queryClient;
131
169
  }
170
+ /**
171
+ * Attach an external QueryClient (e.g. app-level QueryClientProvider client).
172
+ *
173
+ * Why:
174
+ * - `useHook` uses the app's QueryClient from context.
175
+ * - Direct service calls (e.g. `AmplifyService.Model.create()`) historically updated ONLY the internal singleton client,
176
+ * so hook caches would not update.
177
+ *
178
+ * Attaching lets BOTH direct service calls and hooks operate on the same QueryClient.
179
+ */
180
+ function attachQueryClient(externalClient) {
181
+ if (!externalClient)
182
+ return;
183
+ if (exports.queryClient === externalClient)
184
+ return;
185
+ exports.queryClient = externalClient;
186
+ isExternalClientAttached = true;
187
+ // Best-effort: also enable persistence on the external client if configured.
188
+ setupPersistenceFor(exports.queryClient);
189
+ }
190
+ /**
191
+ * Detach and revert to the internal QueryClient.
192
+ * (Mostly useful for tests or advanced setups.)
193
+ */
194
+ function detachQueryClient() {
195
+ if (!isExternalClientAttached)
196
+ return;
197
+ exports.queryClient = internalQueryClient;
198
+ isExternalClientAttached = false;
199
+ }
132
200
  /**
133
201
  * AmplifyQuery configuration
134
202
  * @param options Configuration options
135
203
  */
136
204
  function configure(options = {}) {
137
- var _a;
138
205
  // Backup previous config
139
206
  const prevConfig = Object.assign({}, config);
140
207
  // Apply new config
@@ -143,41 +210,14 @@ function configure(options = {}) {
143
210
  if (options.queryClientConfig &&
144
211
  JSON.stringify(options.queryClientConfig) !==
145
212
  JSON.stringify(prevConfig.queryClientConfig)) {
146
- exports.queryClient = new react_query_1.QueryClient(config.queryClientConfig);
147
- }
148
- // Apply caching config
149
- if (config.isCachingEnabled) {
150
- (0, config_1.debugLog)("🏃‍♀️ React Query offline cache is enabled with MMKV.");
151
- // Create new persister if config changed
152
- const mmkvPersister = createMmkvPersister();
153
- try {
154
- // Lazy-load persistQueryClient to avoid hard dependency resolution at build time.
155
- // eslint-disable-next-line @typescript-eslint/no-var-requires
156
- const { persistQueryClient } = require("@tanstack/react-query-persist-client");
157
- persistQueryClient({
158
- queryClient: exports.queryClient,
159
- persister: mmkvPersister,
160
- // Additional options
161
- maxAge: ((_a = config.storage) === null || _a === void 0 ? void 0 : _a.maxAge) || 1000 * 60 * 60 * 24 * 7, // Default 7 days
162
- dehydrateOptions: {
163
- shouldDehydrateQuery: (query) => {
164
- var _a;
165
- // Only persist successful queries to reduce hydration cancellation noise
166
- if (((_a = query.state) === null || _a === void 0 ? void 0 : _a.status) !== "success")
167
- return false;
168
- // Avoid persisting mutation-like or transient keys if needed later
169
- return true;
170
- },
171
- },
172
- });
173
- }
174
- catch (e) {
175
- console.error("Error setting up React Query persistence:", e);
213
+ internalQueryClient = new react_query_1.QueryClient(config.queryClientConfig);
214
+ // Only swap the exported client if we aren't bound to an external one.
215
+ if (!isExternalClientAttached) {
216
+ exports.queryClient = internalQueryClient;
176
217
  }
177
218
  }
178
- else {
179
- (0, config_1.debugLog)("🏃‍♀️ React Query offline cache is disabled via flag.");
180
- }
219
+ // Apply caching config
220
+ setupPersistenceFor(exports.queryClient);
181
221
  }
182
222
  /**
183
223
  * Create query keys from model names
package/dist/service.js CHANGED
@@ -31,7 +31,47 @@ function signatureForModelItem(item) {
31
31
  const id = typeof (item === null || item === void 0 ? void 0 : item.id) === "string" ? item.id : "";
32
32
  const updatedAt = typeof (item === null || item === void 0 ? void 0 : item.updatedAt) === "string" ? item.updatedAt : "";
33
33
  const createdAt = typeof (item === null || item === void 0 ? void 0 : item.createdAt) === "string" ? item.createdAt : "";
34
- return `${id}::${updatedAt || createdAt}`;
34
+ // Some list/customList queries may not include `updatedAt` depending on the selection set.
35
+ // If we only compare by (id, updatedAt|createdAt), we can incorrectly treat real updates
36
+ // (e.g. status changes) as "no-op" and skip cache updates.
37
+ //
38
+ // Use a cheap "version-ish" marker when available, otherwise include a shallow signature.
39
+ const versionMarker = typeof (item === null || item === void 0 ? void 0 : item._lastChangedAt) === "number"
40
+ ? String(item._lastChangedAt)
41
+ : typeof (item === null || item === void 0 ? void 0 : item._version) === "number"
42
+ ? String(item._version)
43
+ : typeof (item === null || item === void 0 ? void 0 : item.version) === "number"
44
+ ? String(item.version)
45
+ : "";
46
+ const shallowSig = (() => {
47
+ if (!item || typeof item !== "object")
48
+ return "";
49
+ const keys = Object.keys(item).sort();
50
+ const parts = [];
51
+ for (const key of keys) {
52
+ if (key === "__typename")
53
+ continue;
54
+ const v = item[key];
55
+ if (v === null || v === undefined) {
56
+ parts.push(`${key}=null`);
57
+ continue;
58
+ }
59
+ const t = typeof v;
60
+ if (t === "string" || t === "number" || t === "boolean") {
61
+ parts.push(`${key}=${String(v)}`);
62
+ continue;
63
+ }
64
+ if (Array.isArray(v)) {
65
+ parts.push(`${key}=[${v.length}]`);
66
+ continue;
67
+ }
68
+ // Object (nested) - avoid deep/large stringify, just mark presence
69
+ parts.push(`${key}={}`);
70
+ }
71
+ // Keep it bounded to avoid huge signatures
72
+ return parts.join("|").slice(0, 500);
73
+ })();
74
+ return `${id}::${updatedAt || ""}::${createdAt || ""}::${versionMarker}::${shallowSig}`;
35
75
  }
36
76
  function areItemArraysEquivalentById(a, b) {
37
77
  if (a === b)
@@ -349,7 +389,11 @@ function createAmplifyService(modelName, defaultAuthMode) {
349
389
  try {
350
390
  // Attempt API call - apply auth mode
351
391
  (0, config_1.debugLog)(`🍬 ${modelName} creation attempt [Auth: ${authMode}]:`, newItem.id);
352
- const { data: createdItem } = yield (0, client_1.getClient)().models[modelName].create(newItem, authModeParams);
392
+ // Some Amplify clients return `{ data, errors }` without throwing on errors.
393
+ // If `data` is null/undefined, treat it as a failure and rollback.
394
+ const res = yield (0, client_1.getClient)().models[modelName].create(newItem, authModeParams);
395
+ const createdItem = res === null || res === void 0 ? void 0 : res.data;
396
+ const errors = res === null || res === void 0 ? void 0 : res.errors;
353
397
  if (createdItem) {
354
398
  // Update cache on API success
355
399
  query_1.queryClient.setQueryData(singleItemQueryKey, createdItem);
@@ -373,9 +417,10 @@ function createAmplifyService(modelName, defaultAuthMode) {
373
417
  (0, config_1.debugLog)(`🍬 ${modelName} creation successful:`, newItem.id);
374
418
  return createdItem;
375
419
  }
376
- // Keep optimistic update data even if no API response
377
- console.warn(`🍬 ${modelName} creation API no response. Keeping optimistic update data.`);
378
- return newItem;
420
+ // No data returned -> treat as error (don't silently keep optimistic item)
421
+ console.error(`🍬 ${modelName} creation failed: empty response`, errors ? { errors } : undefined);
422
+ rollbackCache(query_1.queryClient, previousDataMap);
423
+ throw new Error(`🍬 ${modelName} create returned no data${errors ? " (has errors)" : ""}`);
379
424
  }
380
425
  catch (apiError) {
381
426
  // Rollback and log error on API failure
@@ -471,7 +516,9 @@ function createAmplifyService(modelName, defaultAuthMode) {
471
516
  // Parallel API calls - apply auth mode
472
517
  const createPromises = preparedItems.map((newItem) => __awaiter(this, void 0, void 0, function* () {
473
518
  try {
474
- const { data: createdItem } = yield (0, client_1.getClient)().models[modelName].create(newItem, authModeParams);
519
+ const res = yield (0, client_1.getClient)().models[modelName].create(newItem, authModeParams);
520
+ const createdItem = res === null || res === void 0 ? void 0 : res.data;
521
+ const errors = res === null || res === void 0 ? void 0 : res.errors;
475
522
  // Update individual item cache on API success
476
523
  if (createdItem) {
477
524
  const itemId = createdItem === null || createdItem === void 0 ? void 0 : createdItem.id;
@@ -483,11 +530,21 @@ function createAmplifyService(modelName, defaultAuthMode) {
483
530
  console.warn(`🍬 ${modelName} createList: Invalid createdItem ID found, skipping cache update:`, itemId, createdItem);
484
531
  }
485
532
  }
486
- return createdItem || newItem;
533
+ if (!createdItem) {
534
+ // Treat empty data as failure (Amplify can return { data: null, errors } without throwing)
535
+ if (errors === null || errors === void 0 ? void 0 : errors.length) {
536
+ console.error(`🍬 ${modelName} createList: create returned no data (has errors)`, { errors });
537
+ }
538
+ else {
539
+ console.error(`🍬 ${modelName} createList: create returned no data`);
540
+ }
541
+ return null;
542
+ }
543
+ return createdItem;
487
544
  }
488
545
  catch (error) {
489
546
  console.error(`🍬 ${modelName} batch creation failed for ID ${newItem.id}:`, error);
490
- return newItem;
547
+ return null;
491
548
  }
492
549
  }));
493
550
  const results = yield Promise.all(createPromises);
@@ -548,7 +605,12 @@ function createAmplifyService(modelName, defaultAuthMode) {
548
605
  // Get parameters based on auth mode
549
606
  const { authModeParams } = yield getOwnerByAuthMode(authMode);
550
607
  // API call - apply auth mode
551
- const { data: apiResponse } = yield (0, client_1.getClient)().models[modelName].get({ id }, authModeParams);
608
+ const res = yield (0, client_1.getClient)().models[modelName].get({ id }, authModeParams);
609
+ const apiResponse = res === null || res === void 0 ? void 0 : res.data;
610
+ const errors = res === null || res === void 0 ? void 0 : res.errors;
611
+ if (!apiResponse && (errors === null || errors === void 0 ? void 0 : errors.length)) {
612
+ throw new Error(`🍬 ${modelName} get returned no data (has errors)`);
613
+ }
552
614
  // Handle case where API returns array instead of single item
553
615
  let item = apiResponse;
554
616
  if (Array.isArray(apiResponse)) {
@@ -633,7 +695,12 @@ function createAmplifyService(modelName, defaultAuthMode) {
633
695
  (0, config_1.debugLog)(`🍬 Debug - Available methods for ${modelName}:`, Object.keys(client.models[modelName] || {}));
634
696
  }
635
697
  // Execute owner query (old-AmplifyQuery behavior)
636
- const { data: result } = yield (0, client_1.getClient)().models[modelName][ownerQueryName]({ owner, authMode }, authModeParams);
698
+ const res = yield (0, client_1.getClient)().models[modelName][ownerQueryName]({ owner, authMode }, authModeParams);
699
+ const result = res === null || res === void 0 ? void 0 : res.data;
700
+ const errors = res === null || res === void 0 ? void 0 : res.errors;
701
+ if (!result && (errors === null || errors === void 0 ? void 0 : errors.length)) {
702
+ throw new Error(`🍬 ${modelName} ${ownerQueryName} returned no data (has errors)`);
703
+ }
637
704
  // Extract result data + filter null values
638
705
  const items = ((result === null || result === void 0 ? void 0 : result.items) || (result === null || result === void 0 ? void 0 : result.data) || result || []).filter((item) => item !== null);
639
706
  // Apply filter (if client-side filtering needed)
@@ -686,7 +753,12 @@ function createAmplifyService(modelName, defaultAuthMode) {
686
753
  ((_d = error === null || error === void 0 ? void 0 : error.message) === null || _d === void 0 ? void 0 : _d.includes("is undefined"))) {
687
754
  console.warn(`🍬 ${ownerQueryName} query not found. Trying default list query...`);
688
755
  // Try default list query if owner query not found
689
- const { data: result } = yield (0, client_1.getClient)().models[modelName].list({}, authModeParams);
756
+ const res = yield (0, client_1.getClient)().models[modelName].list({}, authModeParams);
757
+ const result = res === null || res === void 0 ? void 0 : res.data;
758
+ const errors = res === null || res === void 0 ? void 0 : res.errors;
759
+ if (!result && (errors === null || errors === void 0 ? void 0 : errors.length)) {
760
+ throw new Error(`🍬 ${modelName} list fallback returned no data (has errors)`);
761
+ }
690
762
  // Extract and process result data
691
763
  const items = ((result === null || result === void 0 ? void 0 : result.items) ||
692
764
  (result === null || result === void 0 ? void 0 : result.data) ||
@@ -779,7 +851,9 @@ function createAmplifyService(modelName, defaultAuthMode) {
779
851
  try {
780
852
  // Attempt API call - apply auth mode
781
853
  (0, config_1.debugLog)(`🍬 ${modelName} update attempt [Auth: ${authMode}]:`, itemId);
782
- const { data: updatedItem } = yield (0, client_1.getClient)().models[modelName].update(cleanedData, authModeParams);
854
+ const res = yield (0, client_1.getClient)().models[modelName].update(cleanedData, authModeParams);
855
+ const updatedItem = res === null || res === void 0 ? void 0 : res.data;
856
+ const errors = res === null || res === void 0 ? void 0 : res.errors;
783
857
  if (updatedItem) {
784
858
  // Update cache on API success
785
859
  handleCacheUpdateOnSuccess(query_1.queryClient, modelName, relatedQueryKeys, itemId, updatedItem);
@@ -791,12 +865,10 @@ function createAmplifyService(modelName, defaultAuthMode) {
791
865
  (0, config_1.debugLog)(`🍬 ${modelName} update success:`, itemId);
792
866
  return updatedItem;
793
867
  }
794
- else {
795
- console.warn(`🍬 ${modelName} update API response missing. Maintaining optimistic update data.`);
796
- // If no API response, return the data saved during optimistic update
797
- const singleItemQueryKey = itemKey(modelName, itemId);
798
- return (previousDataMap.get(singleItemQueryKey) || null);
799
- }
868
+ // No data returned -> treat as error and rollback
869
+ console.error(`🍬 ${modelName} update failed: empty response`, errors ? { errors } : undefined);
870
+ rollbackCache(query_1.queryClient, previousDataMap);
871
+ throw new Error(`🍬 ${modelName} update returned no data${errors ? " (has errors)" : ""}`);
800
872
  }
801
873
  catch (apiError) {
802
874
  // Rollback and log error on API failure
@@ -847,7 +919,12 @@ function createAmplifyService(modelName, defaultAuthMode) {
847
919
  try {
848
920
  // API call - apply auth mode
849
921
  (0, config_1.debugLog)(`🍬 ${modelName} delete attempt [Auth: ${authMode}]:`, id);
850
- yield (0, client_1.getClient)().models[modelName].delete({ id }, authModeParams);
922
+ const res = yield (0, client_1.getClient)().models[modelName].delete({ id }, authModeParams);
923
+ const errors = res === null || res === void 0 ? void 0 : res.errors;
924
+ // Delete can return no data on success; only treat explicit errors as failure.
925
+ if (errors === null || errors === void 0 ? void 0 : errors.length) {
926
+ throw new Error(`🍬 ${modelName} delete returned errors without throwing`);
927
+ }
851
928
  (0, config_1.debugLog)(`🍬 ${modelName} delete success:`, id);
852
929
  // On API success, invalidate all related queries to automatically refresh
853
930
  relatedQueryKeys.forEach((queryKey) => query_1.queryClient.invalidateQueries({
@@ -919,7 +996,11 @@ function createAmplifyService(modelName, defaultAuthMode) {
919
996
  // Parallel API calls - apply auth mode
920
997
  const deletePromises = ids.map((id) => __awaiter(this, void 0, void 0, function* () {
921
998
  try {
922
- yield (0, client_1.getClient)().models[modelName].delete({ id }, authModeParams);
999
+ const res = yield (0, client_1.getClient)().models[modelName].delete({ id }, authModeParams);
1000
+ const errors = res === null || res === void 0 ? void 0 : res.errors;
1001
+ if (errors === null || errors === void 0 ? void 0 : errors.length) {
1002
+ throw new Error(`🍬 ${modelName} delete returned errors without throwing`);
1003
+ }
923
1004
  results.success.push(id);
924
1005
  return { id, success: true };
925
1006
  }
@@ -1012,34 +1093,34 @@ function createAmplifyService(modelName, defaultAuthMode) {
1012
1093
  if (existingItem) {
1013
1094
  // Use update logic if item exists - apply auth mode
1014
1095
  (0, config_1.debugLog)(`🍬 ${modelName} upsert(update) attempt [Auth: ${authMode}]:`, data.id);
1015
- const { data: updatedItem } = yield (0, client_1.getClient)().models[modelName].update(cleanedData, authModeParams);
1096
+ const res = yield (0, client_1.getClient)().models[modelName].update(cleanedData, authModeParams);
1097
+ const updatedItem = res === null || res === void 0 ? void 0 : res.data;
1098
+ const errors = res === null || res === void 0 ? void 0 : res.errors;
1016
1099
  if (updatedItem) {
1017
1100
  handleCacheUpdateOnSuccess(query_1.queryClient, modelName, relatedQueryKeys, data.id, updatedItem);
1018
1101
  (0, config_1.debugLog)(`🍬 ${modelName} upsert(update) success:`, data.id);
1019
1102
  return updatedItem;
1020
1103
  }
1021
- else {
1022
- console.warn(`🍬 ${modelName} upsert(update) no API response. Keeping optimistic update data.`);
1023
- const singleItemQueryKey = itemKey(modelName, data.id);
1024
- return (previousDataMap.get(singleItemQueryKey) ||
1025
- null);
1026
- }
1104
+ // No data -> treat as error and rollback
1105
+ console.error(`🍬 ${modelName} upsert(update) failed: empty response`, errors ? { errors } : undefined);
1106
+ rollbackCache(query_1.queryClient, previousDataMap);
1107
+ throw new Error(`🍬 ${modelName} upsert(update) returned no data${errors ? " (has errors)" : ""}`);
1027
1108
  }
1028
1109
  else {
1029
1110
  // Use create logic if item doesn't exist - apply auth mode
1030
1111
  (0, config_1.debugLog)(`🍬 ${modelName} upsert(create) attempt [Auth: ${authMode}]:`, data.id);
1031
- const { data: createdItem } = yield (0, client_1.getClient)().models[modelName].create(cleanedData, authModeParams);
1112
+ const res = yield (0, client_1.getClient)().models[modelName].create(cleanedData, authModeParams);
1113
+ const createdItem = res === null || res === void 0 ? void 0 : res.data;
1114
+ const errors = res === null || res === void 0 ? void 0 : res.errors;
1032
1115
  if (createdItem) {
1033
1116
  handleCacheUpdateOnSuccess(query_1.queryClient, modelName, relatedQueryKeys, data.id, createdItem);
1034
1117
  (0, config_1.debugLog)(`🍬 ${modelName} upsert(create) success:`, data.id);
1035
1118
  return createdItem;
1036
1119
  }
1037
- else {
1038
- console.warn(`🍬 ${modelName} upsert(create) no API response. Keeping optimistic update data.`);
1039
- const singleItemQueryKey = itemKey(modelName, data.id);
1040
- return (previousDataMap.get(singleItemQueryKey) ||
1041
- null);
1042
- }
1120
+ // No data -> treat as error and rollback
1121
+ console.error(`🍬 ${modelName} upsert(create) failed: empty response`, errors ? { errors } : undefined);
1122
+ rollbackCache(query_1.queryClient, previousDataMap);
1123
+ throw new Error(`🍬 ${modelName} upsert(create) returned no data${errors ? " (has errors)" : ""}`);
1043
1124
  }
1044
1125
  }
1045
1126
  catch (apiError) {
@@ -1105,7 +1186,12 @@ function createAmplifyService(modelName, defaultAuthMode) {
1105
1186
  throw new Error(`🍬 Query ${queryName} does not exist.`);
1106
1187
  }
1107
1188
  // Execute index query - apply auth mode
1108
- const { data: result } = yield (0, client_1.getClient)().models[modelName][queryName](enhancedArgs, authModeParams);
1189
+ const res = yield (0, client_1.getClient)().models[modelName][queryName](enhancedArgs, authModeParams);
1190
+ const result = res === null || res === void 0 ? void 0 : res.data;
1191
+ const errors = res === null || res === void 0 ? void 0 : res.errors;
1192
+ if (!result && (errors === null || errors === void 0 ? void 0 : errors.length)) {
1193
+ throw new Error(`🍬 ${modelName} ${queryName} returned no data (has errors)`);
1194
+ }
1109
1195
  // Extract result data
1110
1196
  const items = (result === null || result === void 0 ? void 0 : result.items) || (result === null || result === void 0 ? void 0 : result.data) || result || [];
1111
1197
  (0, config_1.debugLog)(`🍬 ${modelName} ${queryName} result:`, items.length, "items");
@@ -1178,6 +1264,11 @@ function createAmplifyService(modelName, defaultAuthMode) {
1178
1264
  useHook: (options) => {
1179
1265
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
1180
1266
  const hookQueryClient = (0, react_query_1.useQueryClient)();
1267
+ // Ensure direct service calls (create/update/delete) use the same QueryClient
1268
+ // as hooks, so caches stay consistent.
1269
+ (0, react_1.useEffect)(() => {
1270
+ (0, query_1.attachQueryClient)(hookQueryClient);
1271
+ }, [hookQueryClient]);
1181
1272
  // Determine query key
1182
1273
  const queryKey = (0, react_1.useMemo)(() => {
1183
1274
  var _a;
@@ -1339,6 +1430,10 @@ function createAmplifyService(modelName, defaultAuthMode) {
1339
1430
  }
1340
1431
  let isMounted = true;
1341
1432
  let lastSynced = undefined;
1433
+ // observeQuery can temporarily emit an empty list during initial sync/reconnect.
1434
+ // If we immediately overwrite a non-empty cache with [], list UIs "flicker" (items disappear then reappear).
1435
+ // Keep the last known non-empty list until isSynced is true.
1436
+ const lastNonEmptyItemsRef = { current: [] };
1342
1437
  const subscription = model.observeQuery(observeOptions).subscribe({
1343
1438
  next: ({ items: nextItems, isSynced: synced }) => {
1344
1439
  if (!isMounted)
@@ -1352,6 +1447,14 @@ function createAmplifyService(modelName, defaultAuthMode) {
1352
1447
  lastSynced = nextSynced;
1353
1448
  setIsSynced(nextSynced);
1354
1449
  }
1450
+ if (safeItems.length > 0) {
1451
+ lastNonEmptyItemsRef.current = safeItems;
1452
+ }
1453
+ // ✅ Guard: if we're not synced yet and observeQuery emits an empty list,
1454
+ // do NOT clobber a previously non-empty cache.
1455
+ if (!nextSynced && safeItems.length === 0 && previousItems.length > 0) {
1456
+ return;
1457
+ }
1355
1458
  // Avoid cache thrash: observeQuery can emit repeatedly even when data didn't change.
1356
1459
  if (areItemArraysEquivalentById(previousItems, safeItems)) {
1357
1460
  return;
@@ -1498,6 +1601,11 @@ function createAmplifyService(modelName, defaultAuthMode) {
1498
1601
  useItemHook: (id, options) => {
1499
1602
  var _a, _b;
1500
1603
  const hookQueryClient = (0, react_query_1.useQueryClient)();
1604
+ // Ensure direct service calls (create/update/delete) use the same QueryClient
1605
+ // as hooks, so caches stay consistent.
1606
+ (0, react_1.useEffect)(() => {
1607
+ (0, query_1.attachQueryClient)(hookQueryClient);
1608
+ }, [hookQueryClient]);
1501
1609
  // Runtime safety: avoid non-string ids creating broken query keys like
1502
1610
  // ["User","item",[...]] which can cause cache thrash/flicker.
1503
1611
  const safeId = typeof id === "string" ? id : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amplifyquery",
3
- "version": "2.0.5",
3
+ "version": "2.0.7",
4
4
  "description": "Amplify+Query",
5
5
  "keywords": [
6
6
  "Amplify",