amplifyquery 1.0.19 → 1.0.21
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/README.md +11 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.js +57 -3
- package/dist/index.d.ts +0 -62
- package/dist/index.js +10 -4
- package/dist/query.js +60 -29
- package/dist/service.js +347 -97
- package/dist/singleton.js +104 -88
- package/dist/types.d.ts +33 -1
- package/dist/utils.d.ts +0 -72
- package/dist/utils.js +20 -262
- package/package.json +14 -17
package/dist/service.js
CHANGED
|
@@ -27,6 +27,37 @@ function itemKey(modelName, id) {
|
|
|
27
27
|
function isItemKeyForModel(modelName, key) {
|
|
28
28
|
return Array.isArray(key) && key[0] === modelName && key[1] === "item";
|
|
29
29
|
}
|
|
30
|
+
function signatureForModelItem(item) {
|
|
31
|
+
const id = typeof (item === null || item === void 0 ? void 0 : item.id) === "string" ? item.id : "";
|
|
32
|
+
const updatedAt = typeof (item === null || item === void 0 ? void 0 : item.updatedAt) === "string" ? item.updatedAt : "";
|
|
33
|
+
const createdAt = typeof (item === null || item === void 0 ? void 0 : item.createdAt) === "string" ? item.createdAt : "";
|
|
34
|
+
return `${id}::${updatedAt || createdAt}`;
|
|
35
|
+
}
|
|
36
|
+
function areItemArraysEquivalentById(a, b) {
|
|
37
|
+
if (a === b)
|
|
38
|
+
return true;
|
|
39
|
+
if (!Array.isArray(a) || !Array.isArray(b))
|
|
40
|
+
return false;
|
|
41
|
+
if (a.length !== b.length)
|
|
42
|
+
return false;
|
|
43
|
+
const mapA = new Map();
|
|
44
|
+
for (const item of a) {
|
|
45
|
+
const sig = signatureForModelItem(item);
|
|
46
|
+
const [id, ts] = sig.split("::");
|
|
47
|
+
if (!id)
|
|
48
|
+
continue;
|
|
49
|
+
mapA.set(id, ts || "");
|
|
50
|
+
}
|
|
51
|
+
for (const item of b) {
|
|
52
|
+
const sig = signatureForModelItem(item);
|
|
53
|
+
const [id, ts] = sig.split("::");
|
|
54
|
+
if (!id)
|
|
55
|
+
return false;
|
|
56
|
+
if (mapA.get(id) !== (ts || ""))
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
30
61
|
/**
|
|
31
62
|
* Utility function to get owner value based on authentication mode
|
|
32
63
|
* Sets owner value only in userPool auth mode, returns empty string for other auth modes
|
|
@@ -37,11 +68,22 @@ function isItemKeyForModel(modelName, key) {
|
|
|
37
68
|
function getOwnerByAuthMode(authMode) {
|
|
38
69
|
return __awaiter(this, void 0, void 0, function* () {
|
|
39
70
|
let owner = "";
|
|
71
|
+
let ownerCandidates = [];
|
|
40
72
|
// Set owner value only in userPool auth mode
|
|
41
73
|
if (authMode === "userPool") {
|
|
42
74
|
try {
|
|
43
75
|
const { username, userId } = yield (0, auth_1.getCurrentUser)();
|
|
44
|
-
owner
|
|
76
|
+
// Canonical "owner" in Amplify is typically the Cognito user sub / userId.
|
|
77
|
+
// Some legacy codepaths used `${userId}::${username}`. We keep it as a fallback
|
|
78
|
+
// candidate so list-by-owner can still find older records.
|
|
79
|
+
const canonicalOwner = userId;
|
|
80
|
+
// Avoid useless legacy candidates like `${userId}::${userId}` (can happen depending on username config).
|
|
81
|
+
const legacyOwner = username && userId && username !== userId ? `${userId}::${username}` : "";
|
|
82
|
+
owner = canonicalOwner;
|
|
83
|
+
ownerCandidates = [canonicalOwner].filter(Boolean);
|
|
84
|
+
if (legacyOwner && legacyOwner !== canonicalOwner) {
|
|
85
|
+
ownerCandidates.push(legacyOwner);
|
|
86
|
+
}
|
|
45
87
|
}
|
|
46
88
|
catch (error) {
|
|
47
89
|
console.error("Error getting user authentication info:", error);
|
|
@@ -51,6 +93,7 @@ function getOwnerByAuthMode(authMode) {
|
|
|
51
93
|
// Return with auth mode parameters
|
|
52
94
|
return {
|
|
53
95
|
owner,
|
|
96
|
+
ownerCandidates,
|
|
54
97
|
authModeParams: { authMode },
|
|
55
98
|
};
|
|
56
99
|
});
|
|
@@ -66,13 +109,16 @@ function findRelatedQueryKeys(modelName, queryClient) {
|
|
|
66
109
|
return queryClient
|
|
67
110
|
.getQueryCache()
|
|
68
111
|
.findAll({
|
|
69
|
-
predicate: (
|
|
112
|
+
predicate: (query) => {
|
|
113
|
+
const { queryKey } = query;
|
|
70
114
|
// Find all query keys for the model, but EXCLUDE single-item keys
|
|
71
115
|
// Examples kept: [model], [model, 'filter', ...], [model, 'query', ...], [model, Relation, id, ...]
|
|
72
116
|
// Excluded: [model, 'item', id]
|
|
73
117
|
return (Array.isArray(queryKey) &&
|
|
74
118
|
queryKey[0] === modelName &&
|
|
75
|
-
!isItemKeyForModel(modelName, queryKey)
|
|
119
|
+
!isItemKeyForModel(modelName, queryKey) &&
|
|
120
|
+
// Also exclude internal singleton key(s) like ["User", "currentId"]
|
|
121
|
+
queryKey[1] !== "currentId");
|
|
76
122
|
},
|
|
77
123
|
})
|
|
78
124
|
.map((query) => query.queryKey);
|
|
@@ -197,6 +243,14 @@ function rollbackCache(queryClient, previousDataMap) {
|
|
|
197
243
|
queryClient.setQueryData(queryKey, previousData);
|
|
198
244
|
});
|
|
199
245
|
}
|
|
246
|
+
function isOwnerNotInSchemaError(err) {
|
|
247
|
+
const msg = typeof (err === null || err === void 0 ? void 0 : err.message) === "string"
|
|
248
|
+
? err.message
|
|
249
|
+
: String(err);
|
|
250
|
+
// Typical GraphQL validation/input errors mention the field and that it isn't defined.
|
|
251
|
+
return (/owner/i.test(msg) &&
|
|
252
|
+
/(not defined|does not exist|unknown|not a valid|not allowed|defined for input)/i.test(msg));
|
|
253
|
+
}
|
|
200
254
|
/**
|
|
201
255
|
* Create model-specific Amplify service
|
|
202
256
|
* @param modelName Model name
|
|
@@ -213,7 +267,7 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
213
267
|
// Set authentication mode method
|
|
214
268
|
setAuthMode: (authMode) => {
|
|
215
269
|
currentAuthMode = authMode;
|
|
216
|
-
|
|
270
|
+
(0, config_1.debugLog)(`🔐 ${modelName} service auth mode changed: ${authMode}`);
|
|
217
271
|
},
|
|
218
272
|
// Get current authentication mode
|
|
219
273
|
getAuthMode: () => {
|
|
@@ -235,7 +289,7 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
235
289
|
// Determine auth mode (use provided option if available)
|
|
236
290
|
const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode;
|
|
237
291
|
// Get owner and parameters based on auth mode
|
|
238
|
-
const { authModeParams } = yield getOwnerByAuthMode(authMode);
|
|
292
|
+
const { owner, authModeParams } = yield getOwnerByAuthMode(authMode);
|
|
239
293
|
const dataWithoutOwner = utils_1.Utils.removeOwnerField(data, "create");
|
|
240
294
|
const cleanedData = {};
|
|
241
295
|
Object.entries(dataWithoutOwner).forEach(([key, value]) => {
|
|
@@ -286,8 +340,8 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
286
340
|
});
|
|
287
341
|
}
|
|
288
342
|
}
|
|
289
|
-
else if (queryKey.length
|
|
290
|
-
// Regular list query
|
|
343
|
+
else if (queryKey.length === 1) {
|
|
344
|
+
// Regular list query (e.g. ["User"]). Avoid internal keys like ["User","currentId"].
|
|
291
345
|
const data = query_1.queryClient.getQueryData(queryKey);
|
|
292
346
|
if (data) {
|
|
293
347
|
previousDataMap.set(queryKey, data);
|
|
@@ -300,8 +354,22 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
300
354
|
});
|
|
301
355
|
try {
|
|
302
356
|
// Attempt API call - apply auth mode
|
|
303
|
-
|
|
304
|
-
|
|
357
|
+
(0, config_1.debugLog)(`🍬 ${modelName} creation attempt [Auth: ${authMode}]:`, newItem.id);
|
|
358
|
+
// Many schemas use an owner field for auth. Prefer adding owner when available,
|
|
359
|
+
// but retry without it if the model doesn't define it.
|
|
360
|
+
const createPayload = owner ? Object.assign(Object.assign({}, newItem), { owner }) : newItem;
|
|
361
|
+
let createdItem = null;
|
|
362
|
+
try {
|
|
363
|
+
({ data: createdItem } = yield (0, client_1.getClient)().models[modelName].create(createPayload, authModeParams));
|
|
364
|
+
}
|
|
365
|
+
catch (e) {
|
|
366
|
+
if (owner && isOwnerNotInSchemaError(e)) {
|
|
367
|
+
({ data: createdItem } = yield (0, client_1.getClient)().models[modelName].create(newItem, authModeParams));
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
throw e;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
305
373
|
if (createdItem) {
|
|
306
374
|
// Update cache on API success
|
|
307
375
|
query_1.queryClient.setQueryData(singleItemQueryKey, createdItem);
|
|
@@ -322,7 +390,7 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
322
390
|
queryKey: [modelName],
|
|
323
391
|
refetchType: "active",
|
|
324
392
|
});
|
|
325
|
-
|
|
393
|
+
(0, config_1.debugLog)(`🍬 ${modelName} creation successful:`, newItem.id);
|
|
326
394
|
return createdItem;
|
|
327
395
|
}
|
|
328
396
|
// Keep optimistic update data even if no API response
|
|
@@ -351,7 +419,7 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
351
419
|
// Determine auth mode (use provided option if available)
|
|
352
420
|
const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode;
|
|
353
421
|
// Get owner and parameters based on auth mode
|
|
354
|
-
const { authModeParams } = yield getOwnerByAuthMode(authMode);
|
|
422
|
+
const { owner, authModeParams } = yield getOwnerByAuthMode(authMode);
|
|
355
423
|
const preparedItems = dataList
|
|
356
424
|
.map((data) => {
|
|
357
425
|
if (!data)
|
|
@@ -378,7 +446,7 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
378
446
|
}
|
|
379
447
|
}
|
|
380
448
|
}
|
|
381
|
-
|
|
449
|
+
(0, config_1.debugLog)(`🍬 ${modelName} batch creation attempt: ${preparedItems.length} items`);
|
|
382
450
|
// Batch optimistic update - with relation filtering
|
|
383
451
|
relatedQueryKeys.forEach((queryKey) => {
|
|
384
452
|
// Check if this query key is a relational query (e.g., ["Mission", "Daily", "daily-id", ...])
|
|
@@ -403,8 +471,8 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
403
471
|
return oldItems; // No change if no items match relation ID
|
|
404
472
|
});
|
|
405
473
|
}
|
|
406
|
-
else if (queryKey.length
|
|
407
|
-
// Regular list query - add all items
|
|
474
|
+
else if (queryKey.length === 1) {
|
|
475
|
+
// Regular list query - add all items (e.g. ["User"]). Avoid internal keys like ["User","currentId"].
|
|
408
476
|
query_1.queryClient.setQueryData(queryKey, (oldData) => {
|
|
409
477
|
const oldItems = Array.isArray(oldData) ? oldData : [];
|
|
410
478
|
return [...oldItems, ...preparedItems];
|
|
@@ -422,7 +490,20 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
422
490
|
// Parallel API calls - apply auth mode
|
|
423
491
|
const createPromises = preparedItems.map((newItem) => __awaiter(this, void 0, void 0, function* () {
|
|
424
492
|
try {
|
|
425
|
-
const
|
|
493
|
+
const createPayload = owner
|
|
494
|
+
? Object.assign(Object.assign({}, newItem), { owner }) : newItem;
|
|
495
|
+
let createdItem = null;
|
|
496
|
+
try {
|
|
497
|
+
({ data: createdItem } = yield (0, client_1.getClient)().models[modelName].create(createPayload, authModeParams));
|
|
498
|
+
}
|
|
499
|
+
catch (e) {
|
|
500
|
+
if (owner && isOwnerNotInSchemaError(e)) {
|
|
501
|
+
({ data: createdItem } = yield (0, client_1.getClient)().models[modelName].create(newItem, authModeParams));
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
throw e;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
426
507
|
// Update individual item cache on API success
|
|
427
508
|
if (createdItem) {
|
|
428
509
|
const itemId = createdItem === null || createdItem === void 0 ? void 0 : createdItem.id;
|
|
@@ -442,7 +523,7 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
442
523
|
}
|
|
443
524
|
}));
|
|
444
525
|
const results = yield Promise.all(createPromises);
|
|
445
|
-
|
|
526
|
+
(0, config_1.debugLog)(`🍬 ${modelName} batch creation completed: ${results.length} items`);
|
|
446
527
|
// After creation, invalidate all related queries to ensure exact server data
|
|
447
528
|
// This is important as it prevents side effects of optimistic updates by invalidating related queries
|
|
448
529
|
for (const [field, value] of relationFields) {
|
|
@@ -549,8 +630,8 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
549
630
|
}
|
|
550
631
|
}),
|
|
551
632
|
// Batch get items
|
|
552
|
-
list: (...args_1) => __awaiter(this, [...args_1], void 0, function* (options = { filter: undefined, forceRefresh: false }) {
|
|
553
|
-
var _a, _b, _c, _d;
|
|
633
|
+
list: (...args_1) => __awaiter(this, [...args_1], void 0, function* (options = { filter: undefined, forceRefresh: false, throwOnError: false }) {
|
|
634
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
554
635
|
try {
|
|
555
636
|
// Determine query key
|
|
556
637
|
const queryKey = options.filter
|
|
@@ -563,27 +644,55 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
563
644
|
if (cachedItems &&
|
|
564
645
|
cachedItems.length > 0 &&
|
|
565
646
|
!(queryState === null || queryState === void 0 ? void 0 : queryState.isInvalidated)) {
|
|
566
|
-
|
|
647
|
+
(0, config_1.debugLog)(`🍬 ${modelName} list using cache`, queryKey);
|
|
567
648
|
return cachedItems.filter((item) => item !== null);
|
|
568
649
|
}
|
|
569
650
|
}
|
|
570
651
|
// Determine auth mode (use provided option if available)
|
|
571
652
|
const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode;
|
|
572
653
|
// Get owner and parameters based on auth mode
|
|
573
|
-
const { owner, authModeParams } = yield getOwnerByAuthMode(authMode);
|
|
654
|
+
const { owner, ownerCandidates, authModeParams } = yield getOwnerByAuthMode(authMode);
|
|
574
655
|
// Get owner-based query name from global config
|
|
575
656
|
const ownerQueryName = (0, config_1.getOwnerQueryName)(modelName);
|
|
576
657
|
// Try query call
|
|
577
658
|
try {
|
|
578
|
-
|
|
579
|
-
|
|
659
|
+
(0, config_1.debugLog)(`🍬 ${modelName} list API call`, queryKey, `by ${ownerQueryName}`, `[Auth: ${authMode}]`);
|
|
660
|
+
if ((0, config_1.isDebugEnabled)()) {
|
|
661
|
+
// Debug: Check if model and query exist
|
|
662
|
+
const client = (0, client_1.getClient)();
|
|
663
|
+
(0, config_1.debugLog)(`🍬 Debug - client.models exists:`, !!client.models);
|
|
664
|
+
(0, config_1.debugLog)(`🍬 Debug - client.models[${modelName}] exists:`, !!client.models[modelName]);
|
|
665
|
+
(0, config_1.debugLog)(`🍬 Debug - client.models[${modelName}][${ownerQueryName}] exists:`, !!((_a = client.models[modelName]) === null || _a === void 0 ? void 0 : _a[ownerQueryName]));
|
|
666
|
+
(0, config_1.debugLog)(`🍬 Debug - Available methods for ${modelName}:`, Object.keys(client.models[modelName] || {}));
|
|
667
|
+
}
|
|
580
668
|
const client = (0, client_1.getClient)();
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
669
|
+
const model = (_b = client.models) === null || _b === void 0 ? void 0 : _b[modelName];
|
|
670
|
+
// Execute owner query (try canonical owner first, then legacy owner formats)
|
|
671
|
+
const ownersToTry = Array.isArray(ownerCandidates) && ownerCandidates.length > 0
|
|
672
|
+
? ownerCandidates
|
|
673
|
+
: owner
|
|
674
|
+
? [owner]
|
|
675
|
+
: [];
|
|
676
|
+
if (!(model === null || model === void 0 ? void 0 : model[ownerQueryName]) || ownersToTry.length === 0) {
|
|
677
|
+
throw new Error(`owner query not available or owner missing: ${modelName}.${ownerQueryName}`);
|
|
678
|
+
}
|
|
679
|
+
let result = null;
|
|
680
|
+
let usedOwner = null;
|
|
681
|
+
for (const candidateOwner of ownersToTry) {
|
|
682
|
+
(0, config_1.debugLog)(`🍬 ${modelName} list owner-query attempt`, `[${ownerQueryName}]`, { owner: candidateOwner, authMode });
|
|
683
|
+
// Generated secondary index query typically expects { owner } only.
|
|
684
|
+
const { data } = yield model[ownerQueryName]({ owner: candidateOwner }, authModeParams);
|
|
685
|
+
const items = ((data === null || data === void 0 ? void 0 : data.items) || (data === null || data === void 0 ? void 0 : data.data) || data || []).filter((item) => item !== null);
|
|
686
|
+
if (items.length > 0) {
|
|
687
|
+
result = data;
|
|
688
|
+
usedOwner = candidateOwner;
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
// If empty, try next candidate (legacy owner format)
|
|
692
|
+
}
|
|
693
|
+
if (usedOwner && usedOwner !== ownersToTry[0]) {
|
|
694
|
+
(0, config_1.debugWarn)(`🍬 ${modelName} list: owner-query returned results only for legacy owner format`, { tried: ownersToTry, usedOwner });
|
|
695
|
+
}
|
|
587
696
|
// Extract result data + filter null values
|
|
588
697
|
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);
|
|
589
698
|
// Apply filter (if client-side filtering needed)
|
|
@@ -631,9 +740,11 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
631
740
|
}
|
|
632
741
|
catch (error) {
|
|
633
742
|
// Check if the error is because the owner query doesn't exist
|
|
634
|
-
if (((
|
|
635
|
-
((
|
|
636
|
-
((
|
|
743
|
+
if (((_c = error === null || error === void 0 ? void 0 : error.message) === null || _c === void 0 ? void 0 : _c.includes("not found")) ||
|
|
744
|
+
((_d = error === null || error === void 0 ? void 0 : error.message) === null || _d === void 0 ? void 0 : _d.includes("is not a function")) ||
|
|
745
|
+
((_e = error === null || error === void 0 ? void 0 : error.message) === null || _e === void 0 ? void 0 : _e.includes("is undefined")) ||
|
|
746
|
+
((_f = error === null || error === void 0 ? void 0 : error.message) === null || _f === void 0 ? void 0 : _f.includes("owner query not available")) ||
|
|
747
|
+
((_g = error === null || error === void 0 ? void 0 : error.message) === null || _g === void 0 ? void 0 : _g.includes("owner missing"))) {
|
|
637
748
|
console.warn(`🍬 ${ownerQueryName} query not found. Trying default list query...`);
|
|
638
749
|
// Try default list query if owner query not found
|
|
639
750
|
const { data: result } = yield (0, client_1.getClient)().models[modelName].list({}, authModeParams);
|
|
@@ -688,7 +799,7 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
688
799
|
}
|
|
689
800
|
}
|
|
690
801
|
catch (error) {
|
|
691
|
-
|
|
802
|
+
(0, config_1.debugLog)("🍬 error", error);
|
|
692
803
|
console.error(`🍬 ${modelName} list error:`, error);
|
|
693
804
|
// Invalidate list query cache on error
|
|
694
805
|
const queryKey = [
|
|
@@ -697,6 +808,8 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
697
808
|
JSON.stringify(options.filter),
|
|
698
809
|
];
|
|
699
810
|
query_1.queryClient.invalidateQueries({ queryKey });
|
|
811
|
+
if (options === null || options === void 0 ? void 0 : options.throwOnError)
|
|
812
|
+
throw error;
|
|
700
813
|
return [];
|
|
701
814
|
}
|
|
702
815
|
}),
|
|
@@ -726,7 +839,7 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
726
839
|
const previousDataMap = yield performOptimisticUpdate(query_1.queryClient, modelName, relatedQueryKeys, itemId, cleanedData);
|
|
727
840
|
try {
|
|
728
841
|
// Attempt API call - apply auth mode
|
|
729
|
-
|
|
842
|
+
(0, config_1.debugLog)(`🍬 ${modelName} update attempt [Auth: ${authMode}]:`, itemId);
|
|
730
843
|
const { data: updatedItem } = yield (0, client_1.getClient)().models[modelName].update(cleanedData, authModeParams);
|
|
731
844
|
if (updatedItem) {
|
|
732
845
|
// Update cache on API success
|
|
@@ -736,7 +849,7 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
736
849
|
queryKey: [modelName],
|
|
737
850
|
refetchType: "active",
|
|
738
851
|
});
|
|
739
|
-
|
|
852
|
+
(0, config_1.debugLog)(`🍬 ${modelName} update success:`, itemId);
|
|
740
853
|
return updatedItem;
|
|
741
854
|
}
|
|
742
855
|
else {
|
|
@@ -794,9 +907,9 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
794
907
|
});
|
|
795
908
|
try {
|
|
796
909
|
// API call - apply auth mode
|
|
797
|
-
|
|
910
|
+
(0, config_1.debugLog)(`🍬 ${modelName} delete attempt [Auth: ${authMode}]:`, id);
|
|
798
911
|
yield (0, client_1.getClient)().models[modelName].delete({ id }, authModeParams);
|
|
799
|
-
|
|
912
|
+
(0, config_1.debugLog)(`🍬 ${modelName} delete success:`, id);
|
|
800
913
|
// On API success, invalidate all related queries to automatically refresh
|
|
801
914
|
relatedQueryKeys.forEach((queryKey) => query_1.queryClient.invalidateQueries({
|
|
802
915
|
queryKey,
|
|
@@ -863,7 +976,7 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
863
976
|
});
|
|
864
977
|
});
|
|
865
978
|
try {
|
|
866
|
-
|
|
979
|
+
(0, config_1.debugLog)(`🍬 ${modelName} batch delete attempt [Auth: ${authMode}]: ${ids.length} items`);
|
|
867
980
|
// Parallel API calls - apply auth mode
|
|
868
981
|
const deletePromises = ids.map((id) => __awaiter(this, void 0, void 0, function* () {
|
|
869
982
|
try {
|
|
@@ -912,7 +1025,7 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
912
1025
|
// Invalidate all related queries to force refresh (safety mechanism)
|
|
913
1026
|
relatedQueryKeys.forEach((queryKey) => query_1.queryClient.invalidateQueries({ queryKey }));
|
|
914
1027
|
ids.forEach((id) => query_1.queryClient.invalidateQueries({ queryKey: itemKey(modelName, id) }));
|
|
915
|
-
|
|
1028
|
+
(0, config_1.debugLog)(`🍬 ${modelName} batch delete: ${results.success.length} items deleted, ${results.failed.length} items failed`);
|
|
916
1029
|
return results;
|
|
917
1030
|
}
|
|
918
1031
|
catch (generalError) {
|
|
@@ -959,11 +1072,11 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
959
1072
|
try {
|
|
960
1073
|
if (existingItem) {
|
|
961
1074
|
// Use update logic if item exists - apply auth mode
|
|
962
|
-
|
|
1075
|
+
(0, config_1.debugLog)(`🍬 ${modelName} upsert(update) attempt [Auth: ${authMode}]:`, data.id);
|
|
963
1076
|
const { data: updatedItem } = yield (0, client_1.getClient)().models[modelName].update(cleanedData, authModeParams);
|
|
964
1077
|
if (updatedItem) {
|
|
965
1078
|
handleCacheUpdateOnSuccess(query_1.queryClient, modelName, relatedQueryKeys, data.id, updatedItem);
|
|
966
|
-
|
|
1079
|
+
(0, config_1.debugLog)(`🍬 ${modelName} upsert(update) success:`, data.id);
|
|
967
1080
|
return updatedItem;
|
|
968
1081
|
}
|
|
969
1082
|
else {
|
|
@@ -975,11 +1088,11 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
975
1088
|
}
|
|
976
1089
|
else {
|
|
977
1090
|
// Use create logic if item doesn't exist - apply auth mode
|
|
978
|
-
|
|
1091
|
+
(0, config_1.debugLog)(`🍬 ${modelName} upsert(create) attempt [Auth: ${authMode}]:`, data.id);
|
|
979
1092
|
const { data: createdItem } = yield (0, client_1.getClient)().models[modelName].create(cleanedData, authModeParams);
|
|
980
1093
|
if (createdItem) {
|
|
981
1094
|
handleCacheUpdateOnSuccess(query_1.queryClient, modelName, relatedQueryKeys, data.id, createdItem);
|
|
982
|
-
|
|
1095
|
+
(0, config_1.debugLog)(`🍬 ${modelName} upsert(create) success:`, data.id);
|
|
983
1096
|
return createdItem;
|
|
984
1097
|
}
|
|
985
1098
|
else {
|
|
@@ -1003,7 +1116,7 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
1003
1116
|
}
|
|
1004
1117
|
}),
|
|
1005
1118
|
// Index-based query (added method)
|
|
1006
|
-
customList: (queryName_1, args_1, ...args_2) => __awaiter(this, [queryName_1, args_1, ...args_2], void 0, function* (queryName, args, options = { forceRefresh: false }) {
|
|
1119
|
+
customList: (queryName_1, args_1, ...args_2) => __awaiter(this, [queryName_1, args_1, ...args_2], void 0, function* (queryName, args, options = { forceRefresh: false, throwOnError: false }) {
|
|
1007
1120
|
var _a;
|
|
1008
1121
|
try {
|
|
1009
1122
|
// Determine auth mode (use provided options if available)
|
|
@@ -1043,11 +1156,11 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
1043
1156
|
if (!options.forceRefresh) {
|
|
1044
1157
|
const cachedItems = query_1.queryClient.getQueryData(queryKey);
|
|
1045
1158
|
if (cachedItems && cachedItems.length > 0) {
|
|
1046
|
-
|
|
1159
|
+
(0, config_1.debugLog)(`🍬 ${modelName} ${queryName} using cache`);
|
|
1047
1160
|
return cachedItems.filter((item) => item !== null);
|
|
1048
1161
|
}
|
|
1049
1162
|
}
|
|
1050
|
-
|
|
1163
|
+
(0, config_1.debugLog)(`🍬 ${modelName} customList call [Auth: ${authMode}]:`, queryName, enhancedArgs);
|
|
1051
1164
|
// Check if index query method exists
|
|
1052
1165
|
if (!((_a = (0, client_1.getClient)().models[modelName]) === null || _a === void 0 ? void 0 : _a[queryName])) {
|
|
1053
1166
|
throw new Error(`🍬 Query ${queryName} does not exist.`);
|
|
@@ -1056,7 +1169,7 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
1056
1169
|
const { data: result } = yield (0, client_1.getClient)().models[modelName][queryName](enhancedArgs, authModeParams);
|
|
1057
1170
|
// Extract result data
|
|
1058
1171
|
const items = (result === null || result === void 0 ? void 0 : result.items) || (result === null || result === void 0 ? void 0 : result.data) || result || [];
|
|
1059
|
-
|
|
1172
|
+
(0, config_1.debugLog)(`🍬 ${modelName} ${queryName} result:`, items.length, "items");
|
|
1060
1173
|
// Filter null values
|
|
1061
1174
|
const filteredItems = items.filter((item) => item !== null);
|
|
1062
1175
|
// Update cache
|
|
@@ -1104,6 +1217,8 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
1104
1217
|
query_1.queryClient.invalidateQueries({ queryKey });
|
|
1105
1218
|
}
|
|
1106
1219
|
}
|
|
1220
|
+
if (options === null || options === void 0 ? void 0 : options.throwOnError)
|
|
1221
|
+
throw error;
|
|
1107
1222
|
return [];
|
|
1108
1223
|
}
|
|
1109
1224
|
}),
|
|
@@ -1122,7 +1237,7 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
1122
1237
|
},
|
|
1123
1238
|
// React Hook returning method - Reimplemented based on TanStack Query
|
|
1124
1239
|
useHook: (options) => {
|
|
1125
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
1240
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
1126
1241
|
const hookQueryClient = (0, react_query_1.useQueryClient)();
|
|
1127
1242
|
// Determine query key
|
|
1128
1243
|
const queryKey = (0, react_1.useMemo)(() => {
|
|
@@ -1151,24 +1266,37 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
1151
1266
|
]);
|
|
1152
1267
|
// Determine query function
|
|
1153
1268
|
const queryFn = (0, react_1.useCallback)((context) => __awaiter(this, void 0, void 0, function* () {
|
|
1154
|
-
var _a;
|
|
1269
|
+
var _a, _b, _c;
|
|
1270
|
+
(0, config_1.debugLog)(`🍬 ${modelName} useHook queryFn called`, {
|
|
1271
|
+
queryKey,
|
|
1272
|
+
isRefetch: (_a = context.meta) === null || _a === void 0 ? void 0 : _a.refetch,
|
|
1273
|
+
customList: (_b = options === null || options === void 0 ? void 0 : options.customList) === null || _b === void 0 ? void 0 : _b.queryName,
|
|
1274
|
+
});
|
|
1155
1275
|
if (options === null || options === void 0 ? void 0 : options.customList) {
|
|
1156
|
-
|
|
1157
|
-
|
|
1276
|
+
(0, config_1.debugLog)(`🍬 ${modelName} useHook customList call:`, options.customList.queryName, options.customList.args, options.customList.forceRefresh);
|
|
1277
|
+
const result = yield service.customList(options.customList.queryName, options.customList.args, { forceRefresh: options.customList.forceRefresh });
|
|
1278
|
+
(0, config_1.debugLog)(`🍬 ${modelName} useHook customList result:`, (result === null || result === void 0 ? void 0 : result.length) || 0, "items");
|
|
1279
|
+
return result;
|
|
1158
1280
|
}
|
|
1159
|
-
if ((
|
|
1160
|
-
|
|
1281
|
+
if ((_c = options === null || options === void 0 ? void 0 : options.initialFetchOptions) === null || _c === void 0 ? void 0 : _c.filter) {
|
|
1282
|
+
const result = yield service.list({
|
|
1161
1283
|
filter: options.initialFetchOptions.filter,
|
|
1162
1284
|
forceRefresh: true,
|
|
1163
1285
|
});
|
|
1286
|
+
(0, config_1.debugLog)(`🍬 ${modelName} useHook list (filtered) result:`, (result === null || result === void 0 ? void 0 : result.length) || 0, "items");
|
|
1287
|
+
return result;
|
|
1164
1288
|
}
|
|
1165
|
-
|
|
1289
|
+
const result = yield service.list();
|
|
1290
|
+
(0, config_1.debugLog)(`🍬 ${modelName} useHook list result:`, (result === null || result === void 0 ? void 0 : result.length) || 0, "items");
|
|
1291
|
+
return result;
|
|
1166
1292
|
}), [
|
|
1167
1293
|
(_d = options === null || options === void 0 ? void 0 : options.initialFetchOptions) === null || _d === void 0 ? void 0 : _d.filter,
|
|
1168
1294
|
(_e = options === null || options === void 0 ? void 0 : options.customList) === null || _e === void 0 ? void 0 : _e.queryName,
|
|
1169
1295
|
(_f = options === null || options === void 0 ? void 0 : options.customList) === null || _f === void 0 ? void 0 : _f.args,
|
|
1170
1296
|
(_g = options === null || options === void 0 ? void 0 : options.customList) === null || _g === void 0 ? void 0 : _g.forceRefresh,
|
|
1171
1297
|
service,
|
|
1298
|
+
modelName,
|
|
1299
|
+
queryKey,
|
|
1172
1300
|
]);
|
|
1173
1301
|
const realtimeEnabled = ((_h = options === null || options === void 0 ? void 0 : options.realtime) === null || _h === void 0 ? void 0 : _h.enabled) === true;
|
|
1174
1302
|
const realtimeEvents = (_k = (_j = options === null || options === void 0 ? void 0 : options.realtime) === null || _j === void 0 ? void 0 : _j.events) !== null && _k !== void 0 ? _k : ["create", "update", "delete"];
|
|
@@ -1177,12 +1305,39 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
1177
1305
|
queryFn,
|
|
1178
1306
|
enabled: ((_l = options === null || options === void 0 ? void 0 : options.initialFetchOptions) === null || _l === void 0 ? void 0 : _l.fetch) !== false && !realtimeEnabled,
|
|
1179
1307
|
staleTime: 1000 * 30, // Keep fresh for 30 seconds (refresh more frequently)
|
|
1308
|
+
gcTime: 1000 * 60 * 5, // Keep in cache for 5 minutes (prevents data from being garbage collected)
|
|
1180
1309
|
refetchOnMount: true, // Refetch on component mount
|
|
1181
1310
|
refetchOnWindowFocus: false, // Don't auto-refetch on window focus
|
|
1182
1311
|
refetchOnReconnect: true, // Refetch on network reconnect
|
|
1312
|
+
// Keep previous data during refetch to prevent UI flicker
|
|
1313
|
+
// This ensures data persists during refetch operations
|
|
1314
|
+
placeholderData: (previousData) => {
|
|
1315
|
+
(0, config_1.debugLog)(`🍬 ${modelName} useHook placeholderData called - previous data:`, (previousData === null || previousData === void 0 ? void 0 : previousData.length) || 0, "items");
|
|
1316
|
+
return previousData;
|
|
1317
|
+
},
|
|
1183
1318
|
};
|
|
1184
1319
|
const [isSynced, setIsSynced] = (0, react_1.useState)(undefined);
|
|
1185
1320
|
const { data: items = [], isLoading, error, refetch, } = (0, react_query_1.useQuery)(queryOptions);
|
|
1321
|
+
// Log data changes (debug only) - avoid spamming when nothing changed
|
|
1322
|
+
const lastDebugStateRef = (0, react_1.useRef)(null);
|
|
1323
|
+
(0, react_1.useEffect)(() => {
|
|
1324
|
+
if (!(0, config_1.isDebugEnabled)())
|
|
1325
|
+
return;
|
|
1326
|
+
const nextState = {
|
|
1327
|
+
itemsCount: (items === null || items === void 0 ? void 0 : items.length) || 0,
|
|
1328
|
+
isLoading: Boolean(isLoading),
|
|
1329
|
+
hasError: Boolean(error),
|
|
1330
|
+
};
|
|
1331
|
+
const prev = lastDebugStateRef.current;
|
|
1332
|
+
if (prev &&
|
|
1333
|
+
prev.itemsCount === nextState.itemsCount &&
|
|
1334
|
+
prev.isLoading === nextState.isLoading &&
|
|
1335
|
+
prev.hasError === nextState.hasError) {
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
lastDebugStateRef.current = nextState;
|
|
1339
|
+
(0, config_1.debugLog)(`🍬 ${modelName} useHook data changed:`, Object.assign(Object.assign({}, nextState), { queryKey }));
|
|
1340
|
+
}, [items === null || items === void 0 ? void 0 : items.length, isLoading, error, modelName, queryKey]);
|
|
1186
1341
|
(0, react_1.useEffect)(() => {
|
|
1187
1342
|
var _a, _b, _c, _d, _e, _f;
|
|
1188
1343
|
if (!realtimeEnabled)
|
|
@@ -1244,6 +1399,7 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
1244
1399
|
observeOptions.filter = options.initialFetchOptions.filter;
|
|
1245
1400
|
}
|
|
1246
1401
|
let isMounted = true;
|
|
1402
|
+
let lastSynced = undefined;
|
|
1247
1403
|
const subscription = model.observeQuery(observeOptions).subscribe({
|
|
1248
1404
|
next: ({ items: nextItems, isSynced: synced }) => {
|
|
1249
1405
|
if (!isMounted)
|
|
@@ -1252,23 +1408,21 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
1252
1408
|
? nextItems.filter(Boolean)
|
|
1253
1409
|
: [];
|
|
1254
1410
|
const previousItems = hookQueryClient.getQueryData(queryKey) || [];
|
|
1255
|
-
const
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
});
|
|
1411
|
+
const nextSynced = Boolean(synced);
|
|
1412
|
+
if (lastSynced !== nextSynced) {
|
|
1413
|
+
lastSynced = nextSynced;
|
|
1414
|
+
setIsSynced(nextSynced);
|
|
1415
|
+
}
|
|
1416
|
+
// Avoid cache thrash: observeQuery can emit repeatedly even when data didn't change.
|
|
1417
|
+
if (areItemArraysEquivalentById(previousItems, safeItems)) {
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1265
1420
|
hookQueryClient.setQueryData(queryKey, safeItems);
|
|
1266
1421
|
safeItems.forEach((item) => {
|
|
1267
1422
|
if (item === null || item === void 0 ? void 0 : item.id) {
|
|
1268
1423
|
hookQueryClient.setQueryData(itemKey(modelName, item.id), item);
|
|
1269
1424
|
}
|
|
1270
1425
|
});
|
|
1271
|
-
setIsSynced(Boolean(synced));
|
|
1272
1426
|
},
|
|
1273
1427
|
error: (err) => {
|
|
1274
1428
|
console.error(`🍬 ${modelName} useHook realtime subscribe error:`, err);
|
|
@@ -1372,10 +1526,57 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
1372
1526
|
}), [service, refetch] // refetch dependency added
|
|
1373
1527
|
);
|
|
1374
1528
|
const refresh = (0, react_1.useCallback)((refreshOptions) => __awaiter(this, void 0, void 0, function* () {
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1529
|
+
var _a;
|
|
1530
|
+
(0, config_1.debugLog)(`🍬 ${modelName} useHook refresh called`, queryKey);
|
|
1531
|
+
// IMPORTANT: refresh must always fetch from server (no cache).
|
|
1532
|
+
// We do this by calling service.list/customList with forceRefresh: true.
|
|
1533
|
+
const currentData = hookQueryClient.getQueryData(queryKey);
|
|
1534
|
+
(0, config_1.debugLog)(`🍬 ${modelName} useHook refresh - current data before server refresh:`, (currentData === null || currentData === void 0 ? void 0 : currentData.length) || 0, "items");
|
|
1535
|
+
try {
|
|
1536
|
+
// If filter is provided and different from current filter, we'd need a new query key.
|
|
1537
|
+
// For now, we ignore refreshOptions.filter and refresh the current hook query only.
|
|
1538
|
+
if (refreshOptions === null || refreshOptions === void 0 ? void 0 : refreshOptions.filter) {
|
|
1539
|
+
console.warn(`🍬 ${modelName} useHook refresh: refreshOptions.filter is currently ignored (queryKey is fixed per hook instance).`);
|
|
1540
|
+
}
|
|
1541
|
+
let newData = [];
|
|
1542
|
+
if (options === null || options === void 0 ? void 0 : options.customList) {
|
|
1543
|
+
newData = yield service.customList(options.customList.queryName, options.customList.args, { forceRefresh: true, throwOnError: true });
|
|
1544
|
+
}
|
|
1545
|
+
else if ((_a = options === null || options === void 0 ? void 0 : options.initialFetchOptions) === null || _a === void 0 ? void 0 : _a.filter) {
|
|
1546
|
+
newData = yield service.list({
|
|
1547
|
+
filter: options.initialFetchOptions.filter,
|
|
1548
|
+
forceRefresh: true,
|
|
1549
|
+
throwOnError: true,
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
else {
|
|
1553
|
+
newData = yield service.list({
|
|
1554
|
+
forceRefresh: true,
|
|
1555
|
+
throwOnError: true,
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
(0, config_1.debugLog)(`🍬 ${modelName} useHook refresh - server refresh result:`, (newData === null || newData === void 0 ? void 0 : newData.length) || 0, "items");
|
|
1559
|
+
// Keep hook cache in sync with hook's queryKey.
|
|
1560
|
+
hookQueryClient.setQueryData(queryKey, newData);
|
|
1561
|
+
return newData || [];
|
|
1562
|
+
}
|
|
1563
|
+
catch (e) {
|
|
1564
|
+
console.error(`🍬 ${modelName} useHook refresh error:`, e);
|
|
1565
|
+
// On error, restore previous data if available (prevents list flashing empty)
|
|
1566
|
+
if (currentData && currentData.length > 0) {
|
|
1567
|
+
hookQueryClient.setQueryData(queryKey, currentData);
|
|
1568
|
+
return currentData;
|
|
1569
|
+
}
|
|
1570
|
+
return [];
|
|
1571
|
+
}
|
|
1572
|
+
}), [
|
|
1573
|
+
modelName,
|
|
1574
|
+
queryKey,
|
|
1575
|
+
hookQueryClient,
|
|
1576
|
+
service,
|
|
1577
|
+
options === null || options === void 0 ? void 0 : options.customList,
|
|
1578
|
+
(_p = options === null || options === void 0 ? void 0 : options.initialFetchOptions) === null || _p === void 0 ? void 0 : _p.filter,
|
|
1579
|
+
]);
|
|
1379
1580
|
const customListFn = (0, react_1.useCallback)((queryName, args, options) => __awaiter(this, void 0, void 0, function* () {
|
|
1380
1581
|
try {
|
|
1381
1582
|
const result = yield service.customList(queryName, args, options);
|
|
@@ -1403,7 +1604,10 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
1403
1604
|
useItemHook: (id, options) => {
|
|
1404
1605
|
var _a, _b;
|
|
1405
1606
|
const hookQueryClient = (0, react_query_1.useQueryClient)();
|
|
1406
|
-
|
|
1607
|
+
// Runtime safety: avoid non-string ids creating broken query keys like
|
|
1608
|
+
// ["User","item",[...]] which can cause cache thrash/flicker.
|
|
1609
|
+
const safeId = typeof id === "string" ? id : "";
|
|
1610
|
+
const singleItemQueryKey = itemKey(modelName, safeId);
|
|
1407
1611
|
const realtimeEnabled = ((_a = options === null || options === void 0 ? void 0 : options.realtime) === null || _a === void 0 ? void 0 : _a.enabled) === true;
|
|
1408
1612
|
// First check data from cache
|
|
1409
1613
|
const rawCachedData = hookQueryClient.getQueryData(singleItemQueryKey);
|
|
@@ -1411,32 +1615,27 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
1411
1615
|
let cachedData;
|
|
1412
1616
|
if (Array.isArray(rawCachedData)) {
|
|
1413
1617
|
console.warn(`🍬 ${modelName} useItemHook: Cache contains array instead of single item. Finding matching item.`);
|
|
1414
|
-
const matchingItem = rawCachedData.find((item) => (item === null || item === void 0 ? void 0 : item.id) ===
|
|
1618
|
+
const matchingItem = rawCachedData.find((item) => (item === null || item === void 0 ? void 0 : item.id) === safeId);
|
|
1415
1619
|
cachedData = matchingItem || undefined;
|
|
1416
|
-
//
|
|
1417
|
-
|
|
1418
|
-
hookQueryClient.removeQueries({
|
|
1419
|
-
queryKey: singleItemQueryKey,
|
|
1420
|
-
exact: true,
|
|
1421
|
-
});
|
|
1422
|
-
}, 0);
|
|
1620
|
+
// Normalize the cache in-place instead of deleting it (deleting causes UI flicker).
|
|
1621
|
+
hookQueryClient.setQueryData(singleItemQueryKey, matchingItem !== null && matchingItem !== void 0 ? matchingItem : null);
|
|
1423
1622
|
}
|
|
1424
1623
|
else if (rawCachedData && (rawCachedData === null || rawCachedData === void 0 ? void 0 : rawCachedData.id) === id) {
|
|
1425
1624
|
cachedData = rawCachedData;
|
|
1426
1625
|
}
|
|
1427
1626
|
else if (rawCachedData) {
|
|
1428
|
-
console.warn(`🍬 ${modelName} useItemHook: Cache ID mismatch! Requested: ${
|
|
1627
|
+
console.warn(`🍬 ${modelName} useItemHook: Cache ID mismatch! Requested: ${safeId}, Cached: ${rawCachedData === null || rawCachedData === void 0 ? void 0 : rawCachedData.id}. Ignoring cached data.`);
|
|
1429
1628
|
cachedData = undefined;
|
|
1430
1629
|
}
|
|
1431
1630
|
// Single item query
|
|
1432
1631
|
const { data: item, isLoading, error, refetch, } = (0, react_query_1.useQuery)({
|
|
1433
1632
|
queryKey: singleItemQueryKey,
|
|
1434
|
-
queryFn: () => service.get(
|
|
1633
|
+
queryFn: () => service.get(safeId),
|
|
1435
1634
|
initialData: cachedData, // Use cached data as initial value if available
|
|
1436
1635
|
staleTime: 1000 * 60, // Keep data "fresh" for 1 minute
|
|
1437
1636
|
refetchOnMount: cachedData ? false : true, // Only refetch if no cached data
|
|
1438
1637
|
refetchOnWindowFocus: false, // Disable window focus refetch to prevent loops
|
|
1439
|
-
enabled: !!
|
|
1638
|
+
enabled: !!safeId && !realtimeEnabled, // Only enable query when id exists
|
|
1440
1639
|
});
|
|
1441
1640
|
const [isSynced, setIsSynced] = (0, react_1.useState)(undefined);
|
|
1442
1641
|
(0, react_1.useEffect)(() => {
|
|
@@ -1454,6 +1653,11 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
1454
1653
|
observeOptions.filter = { id: { eq: id } };
|
|
1455
1654
|
}
|
|
1456
1655
|
let isMounted = true;
|
|
1656
|
+
let lastSynced = undefined;
|
|
1657
|
+
// observeQuery can temporarily emit empty items during sync/reconnect, even when the item
|
|
1658
|
+
// still exists on the server. If we immediately set the cache to null, UI flickers.
|
|
1659
|
+
// When observeQuery reports "missing", confirm with a server get (forceRefresh) before clearing.
|
|
1660
|
+
let lastMissingConfirmAt = 0;
|
|
1457
1661
|
const subscription = model.observeQuery(observeOptions).subscribe({
|
|
1458
1662
|
next: ({ items: nextItems, isSynced: synced }) => {
|
|
1459
1663
|
if (!isMounted)
|
|
@@ -1462,6 +1666,44 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
1462
1666
|
? nextItems.filter(Boolean)
|
|
1463
1667
|
: [];
|
|
1464
1668
|
const nextItem = safeItems.find((entry) => (entry === null || entry === void 0 ? void 0 : entry.id) === id) || null;
|
|
1669
|
+
const nextSynced = Boolean(synced);
|
|
1670
|
+
if (lastSynced !== nextSynced) {
|
|
1671
|
+
lastSynced = nextSynced;
|
|
1672
|
+
setIsSynced(nextSynced);
|
|
1673
|
+
}
|
|
1674
|
+
const prevItem = hookQueryClient.getQueryData(singleItemQueryKey);
|
|
1675
|
+
if (!nextItem) {
|
|
1676
|
+
// Never clear immediately. During sync/reconnect observeQuery can be temporarily empty.
|
|
1677
|
+
if (!nextSynced)
|
|
1678
|
+
return;
|
|
1679
|
+
const now = Date.now();
|
|
1680
|
+
// Throttle server confirmations to avoid loops.
|
|
1681
|
+
if (now - lastMissingConfirmAt < 2000)
|
|
1682
|
+
return;
|
|
1683
|
+
lastMissingConfirmAt = now;
|
|
1684
|
+
void (() => __awaiter(this, void 0, void 0, function* () {
|
|
1685
|
+
try {
|
|
1686
|
+
const latest = yield service.get(id, { forceRefresh: true });
|
|
1687
|
+
if (!isMounted)
|
|
1688
|
+
return;
|
|
1689
|
+
if (latest) {
|
|
1690
|
+
hookQueryClient.setQueryData(singleItemQueryKey, latest);
|
|
1691
|
+
}
|
|
1692
|
+
else {
|
|
1693
|
+
hookQueryClient.setQueryData(singleItemQueryKey, null);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
catch (e) {
|
|
1697
|
+
// If confirm fails, keep existing cache (avoid flicker)
|
|
1698
|
+
(0, config_1.debugWarn)(`🍬 ${modelName} useItemHook realtime missing confirm error:`, e);
|
|
1699
|
+
}
|
|
1700
|
+
}))();
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
if (signatureForModelItem(prevItem) === signatureForModelItem(nextItem)) {
|
|
1704
|
+
// Avoid thrashing caches when observeQuery emits identical item repeatedly
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1465
1707
|
const relatedQueryKeys = findRelatedQueryKeys(modelName, hookQueryClient);
|
|
1466
1708
|
if (nextItem) {
|
|
1467
1709
|
hookQueryClient.setQueryData(singleItemQueryKey, nextItem);
|
|
@@ -1482,19 +1724,6 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
1482
1724
|
});
|
|
1483
1725
|
});
|
|
1484
1726
|
}
|
|
1485
|
-
else {
|
|
1486
|
-
hookQueryClient.setQueryData(singleItemQueryKey, null);
|
|
1487
|
-
relatedQueryKeys.forEach((queryKey) => {
|
|
1488
|
-
if (isItemKeyForModel(modelName, queryKey)) {
|
|
1489
|
-
return;
|
|
1490
|
-
}
|
|
1491
|
-
hookQueryClient.setQueryData(queryKey, (oldData) => {
|
|
1492
|
-
const oldItems = Array.isArray(oldData) ? oldData : [];
|
|
1493
|
-
return oldItems.filter((entry) => (entry === null || entry === void 0 ? void 0 : entry.id) !== id);
|
|
1494
|
-
});
|
|
1495
|
-
});
|
|
1496
|
-
}
|
|
1497
|
-
setIsSynced(Boolean(synced));
|
|
1498
1727
|
},
|
|
1499
1728
|
error: (err) => {
|
|
1500
1729
|
console.error(`🍬 ${modelName} useItemHook realtime subscribe error:`, err);
|
|
@@ -1526,10 +1755,31 @@ function createAmplifyService(modelName, defaultAuthMode) {
|
|
|
1526
1755
|
});
|
|
1527
1756
|
// Interface function implementations
|
|
1528
1757
|
const refreshItem = (0, react_1.useCallback)(() => __awaiter(this, void 0, void 0, function* () {
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1758
|
+
(0, config_1.debugLog)(`🍬 ${modelName} useItemHook refresh called`, singleItemQueryKey);
|
|
1759
|
+
// IMPORTANT: Use hookQueryClient.refetchQueries() or refetch() instead of service.get()
|
|
1760
|
+
// to ensure we're updating the same QueryClient instance that useQuery uses.
|
|
1761
|
+
// This prevents data from disappearing during refetch.
|
|
1762
|
+
try {
|
|
1763
|
+
// First, fetch fresh data with forceRefresh
|
|
1764
|
+
const latest = yield service.get(id, { forceRefresh: true });
|
|
1765
|
+
// Update the hookQueryClient cache to sync with the fetched data
|
|
1766
|
+
if (latest) {
|
|
1767
|
+
hookQueryClient.setQueryData(singleItemQueryKey, latest);
|
|
1768
|
+
}
|
|
1769
|
+
else {
|
|
1770
|
+
hookQueryClient.setQueryData(singleItemQueryKey, null);
|
|
1771
|
+
}
|
|
1772
|
+
// Also trigger refetch to ensure UI updates
|
|
1773
|
+
yield refetch();
|
|
1774
|
+
return latest || null;
|
|
1775
|
+
}
|
|
1776
|
+
catch (error) {
|
|
1777
|
+
console.error(`🍬 ${modelName} useItemHook refresh error:`, error);
|
|
1778
|
+
// On error, still try to refetch to get current state
|
|
1779
|
+
const refetchResult = yield refetch();
|
|
1780
|
+
return refetchResult.data || null;
|
|
1781
|
+
}
|
|
1782
|
+
}), [id, modelName, service, singleItemQueryKey, hookQueryClient, refetch]);
|
|
1533
1783
|
const updateItem = (0, react_1.useCallback)((data) => __awaiter(this, void 0, void 0, function* () {
|
|
1534
1784
|
// No additional work needed here as cache is already updated in service.update
|
|
1535
1785
|
try {
|