amplifyquery 2.0.6 → 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
@@ -389,7 +389,11 @@ function createAmplifyService(modelName, defaultAuthMode) {
389
389
  try {
390
390
  // Attempt API call - apply auth mode
391
391
  (0, config_1.debugLog)(`🍬 ${modelName} creation attempt [Auth: ${authMode}]:`, newItem.id);
392
- 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;
393
397
  if (createdItem) {
394
398
  // Update cache on API success
395
399
  query_1.queryClient.setQueryData(singleItemQueryKey, createdItem);
@@ -413,9 +417,10 @@ function createAmplifyService(modelName, defaultAuthMode) {
413
417
  (0, config_1.debugLog)(`🍬 ${modelName} creation successful:`, newItem.id);
414
418
  return createdItem;
415
419
  }
416
- // Keep optimistic update data even if no API response
417
- console.warn(`🍬 ${modelName} creation API no response. Keeping optimistic update data.`);
418
- 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)" : ""}`);
419
424
  }
420
425
  catch (apiError) {
421
426
  // Rollback and log error on API failure
@@ -511,7 +516,9 @@ function createAmplifyService(modelName, defaultAuthMode) {
511
516
  // Parallel API calls - apply auth mode
512
517
  const createPromises = preparedItems.map((newItem) => __awaiter(this, void 0, void 0, function* () {
513
518
  try {
514
- 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;
515
522
  // Update individual item cache on API success
516
523
  if (createdItem) {
517
524
  const itemId = createdItem === null || createdItem === void 0 ? void 0 : createdItem.id;
@@ -523,11 +530,21 @@ function createAmplifyService(modelName, defaultAuthMode) {
523
530
  console.warn(`🍬 ${modelName} createList: Invalid createdItem ID found, skipping cache update:`, itemId, createdItem);
524
531
  }
525
532
  }
526
- 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;
527
544
  }
528
545
  catch (error) {
529
546
  console.error(`🍬 ${modelName} batch creation failed for ID ${newItem.id}:`, error);
530
- return newItem;
547
+ return null;
531
548
  }
532
549
  }));
533
550
  const results = yield Promise.all(createPromises);
@@ -588,7 +605,12 @@ function createAmplifyService(modelName, defaultAuthMode) {
588
605
  // Get parameters based on auth mode
589
606
  const { authModeParams } = yield getOwnerByAuthMode(authMode);
590
607
  // API call - apply auth mode
591
- 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
+ }
592
614
  // Handle case where API returns array instead of single item
593
615
  let item = apiResponse;
594
616
  if (Array.isArray(apiResponse)) {
@@ -673,7 +695,12 @@ function createAmplifyService(modelName, defaultAuthMode) {
673
695
  (0, config_1.debugLog)(`🍬 Debug - Available methods for ${modelName}:`, Object.keys(client.models[modelName] || {}));
674
696
  }
675
697
  // Execute owner query (old-AmplifyQuery behavior)
676
- 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
+ }
677
704
  // Extract result data + filter null values
678
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);
679
706
  // Apply filter (if client-side filtering needed)
@@ -726,7 +753,12 @@ function createAmplifyService(modelName, defaultAuthMode) {
726
753
  ((_d = error === null || error === void 0 ? void 0 : error.message) === null || _d === void 0 ? void 0 : _d.includes("is undefined"))) {
727
754
  console.warn(`🍬 ${ownerQueryName} query not found. Trying default list query...`);
728
755
  // Try default list query if owner query not found
729
- 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
+ }
730
762
  // Extract and process result data
731
763
  const items = ((result === null || result === void 0 ? void 0 : result.items) ||
732
764
  (result === null || result === void 0 ? void 0 : result.data) ||
@@ -819,7 +851,9 @@ function createAmplifyService(modelName, defaultAuthMode) {
819
851
  try {
820
852
  // Attempt API call - apply auth mode
821
853
  (0, config_1.debugLog)(`🍬 ${modelName} update attempt [Auth: ${authMode}]:`, itemId);
822
- 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;
823
857
  if (updatedItem) {
824
858
  // Update cache on API success
825
859
  handleCacheUpdateOnSuccess(query_1.queryClient, modelName, relatedQueryKeys, itemId, updatedItem);
@@ -831,12 +865,10 @@ function createAmplifyService(modelName, defaultAuthMode) {
831
865
  (0, config_1.debugLog)(`🍬 ${modelName} update success:`, itemId);
832
866
  return updatedItem;
833
867
  }
834
- else {
835
- console.warn(`🍬 ${modelName} update API response missing. Maintaining optimistic update data.`);
836
- // If no API response, return the data saved during optimistic update
837
- const singleItemQueryKey = itemKey(modelName, itemId);
838
- return (previousDataMap.get(singleItemQueryKey) || null);
839
- }
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)" : ""}`);
840
872
  }
841
873
  catch (apiError) {
842
874
  // Rollback and log error on API failure
@@ -887,7 +919,12 @@ function createAmplifyService(modelName, defaultAuthMode) {
887
919
  try {
888
920
  // API call - apply auth mode
889
921
  (0, config_1.debugLog)(`🍬 ${modelName} delete attempt [Auth: ${authMode}]:`, id);
890
- 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
+ }
891
928
  (0, config_1.debugLog)(`🍬 ${modelName} delete success:`, id);
892
929
  // On API success, invalidate all related queries to automatically refresh
893
930
  relatedQueryKeys.forEach((queryKey) => query_1.queryClient.invalidateQueries({
@@ -959,7 +996,11 @@ function createAmplifyService(modelName, defaultAuthMode) {
959
996
  // Parallel API calls - apply auth mode
960
997
  const deletePromises = ids.map((id) => __awaiter(this, void 0, void 0, function* () {
961
998
  try {
962
- 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
+ }
963
1004
  results.success.push(id);
964
1005
  return { id, success: true };
965
1006
  }
@@ -1052,34 +1093,34 @@ function createAmplifyService(modelName, defaultAuthMode) {
1052
1093
  if (existingItem) {
1053
1094
  // Use update logic if item exists - apply auth mode
1054
1095
  (0, config_1.debugLog)(`🍬 ${modelName} upsert(update) attempt [Auth: ${authMode}]:`, data.id);
1055
- 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;
1056
1099
  if (updatedItem) {
1057
1100
  handleCacheUpdateOnSuccess(query_1.queryClient, modelName, relatedQueryKeys, data.id, updatedItem);
1058
1101
  (0, config_1.debugLog)(`🍬 ${modelName} upsert(update) success:`, data.id);
1059
1102
  return updatedItem;
1060
1103
  }
1061
- else {
1062
- console.warn(`🍬 ${modelName} upsert(update) no API response. Keeping optimistic update data.`);
1063
- const singleItemQueryKey = itemKey(modelName, data.id);
1064
- return (previousDataMap.get(singleItemQueryKey) ||
1065
- null);
1066
- }
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)" : ""}`);
1067
1108
  }
1068
1109
  else {
1069
1110
  // Use create logic if item doesn't exist - apply auth mode
1070
1111
  (0, config_1.debugLog)(`🍬 ${modelName} upsert(create) attempt [Auth: ${authMode}]:`, data.id);
1071
- 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;
1072
1115
  if (createdItem) {
1073
1116
  handleCacheUpdateOnSuccess(query_1.queryClient, modelName, relatedQueryKeys, data.id, createdItem);
1074
1117
  (0, config_1.debugLog)(`🍬 ${modelName} upsert(create) success:`, data.id);
1075
1118
  return createdItem;
1076
1119
  }
1077
- else {
1078
- console.warn(`🍬 ${modelName} upsert(create) no API response. Keeping optimistic update data.`);
1079
- const singleItemQueryKey = itemKey(modelName, data.id);
1080
- return (previousDataMap.get(singleItemQueryKey) ||
1081
- null);
1082
- }
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)" : ""}`);
1083
1124
  }
1084
1125
  }
1085
1126
  catch (apiError) {
@@ -1145,7 +1186,12 @@ function createAmplifyService(modelName, defaultAuthMode) {
1145
1186
  throw new Error(`🍬 Query ${queryName} does not exist.`);
1146
1187
  }
1147
1188
  // Execute index query - apply auth mode
1148
- 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
+ }
1149
1195
  // Extract result data
1150
1196
  const items = (result === null || result === void 0 ? void 0 : result.items) || (result === null || result === void 0 ? void 0 : result.data) || result || [];
1151
1197
  (0, config_1.debugLog)(`🍬 ${modelName} ${queryName} result:`, items.length, "items");
@@ -1218,6 +1264,11 @@ function createAmplifyService(modelName, defaultAuthMode) {
1218
1264
  useHook: (options) => {
1219
1265
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
1220
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]);
1221
1272
  // Determine query key
1222
1273
  const queryKey = (0, react_1.useMemo)(() => {
1223
1274
  var _a;
@@ -1379,6 +1430,10 @@ function createAmplifyService(modelName, defaultAuthMode) {
1379
1430
  }
1380
1431
  let isMounted = true;
1381
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: [] };
1382
1437
  const subscription = model.observeQuery(observeOptions).subscribe({
1383
1438
  next: ({ items: nextItems, isSynced: synced }) => {
1384
1439
  if (!isMounted)
@@ -1392,6 +1447,14 @@ function createAmplifyService(modelName, defaultAuthMode) {
1392
1447
  lastSynced = nextSynced;
1393
1448
  setIsSynced(nextSynced);
1394
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
+ }
1395
1458
  // Avoid cache thrash: observeQuery can emit repeatedly even when data didn't change.
1396
1459
  if (areItemArraysEquivalentById(previousItems, safeItems)) {
1397
1460
  return;
@@ -1538,6 +1601,11 @@ function createAmplifyService(modelName, defaultAuthMode) {
1538
1601
  useItemHook: (id, options) => {
1539
1602
  var _a, _b;
1540
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]);
1541
1609
  // Runtime safety: avoid non-string ids creating broken query keys like
1542
1610
  // ["User","item",[...]] which can cause cache thrash/flicker.
1543
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.6",
3
+ "version": "2.0.7",
4
4
  "description": "Amplify+Query",
5
5
  "keywords": [
6
6
  "Amplify",