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 +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 +143 -35
- 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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
377
|
-
console.
|
|
378
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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
|
|
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
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
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
|
|
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
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
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
|
|
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 : "";
|