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 +2 -2
- package/dist/index.js +3 -1
- package/dist/query.d.ts +16 -0
- package/dist/query.js +74 -34
- package/dist/service.js +102 -34
- package/package.json +1 -1
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
179
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
417
|
-
console.
|
|
418
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
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
|
|
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
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
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
|
|
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
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
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
|
|
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 : "";
|