amplifyquery 1.0.0
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 +203 -0
- package/dist/client.d.ts +11 -0
- package/dist/client.js +26 -0
- package/dist/index.d.ts +133 -0
- package/dist/index.js +89 -0
- package/dist/query.d.ts +52 -0
- package/dist/query.js +197 -0
- package/dist/service.d.ts +9 -0
- package/dist/service.js +1236 -0
- package/dist/singleton.d.ts +14 -0
- package/dist/singleton.js +144 -0
- package/dist/store.d.ts +5 -0
- package/dist/store.js +67 -0
- package/dist/types.d.ts +138 -0
- package/dist/types.js +2 -0
- package/dist/utils.d.ts +130 -0
- package/dist/utils.js +437 -0
- package/package.json +53 -0
package/dist/service.js
ADDED
|
@@ -0,0 +1,1236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.createAmplifyService = createAmplifyService;
|
|
13
|
+
const client_1 = require("./client");
|
|
14
|
+
const query_1 = require("./query");
|
|
15
|
+
const utils_1 = require("./utils");
|
|
16
|
+
const react_query_1 = require("@tanstack/react-query");
|
|
17
|
+
const auth_1 = require("aws-amplify/auth");
|
|
18
|
+
const expo_crypto_1 = require("expo-crypto");
|
|
19
|
+
const react_1 = require("react");
|
|
20
|
+
/**
|
|
21
|
+
* Utility function to get owner value based on authentication mode
|
|
22
|
+
* Sets owner value only in userPool auth mode, returns empty string for other auth modes
|
|
23
|
+
*
|
|
24
|
+
* @param authMode Current authentication mode
|
|
25
|
+
* @returns Owner value and API call parameters based on auth mode
|
|
26
|
+
*/
|
|
27
|
+
function getOwnerByAuthMode(authMode) {
|
|
28
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
29
|
+
let owner = "";
|
|
30
|
+
// Set owner value only in userPool auth mode
|
|
31
|
+
if (authMode === "userPool") {
|
|
32
|
+
try {
|
|
33
|
+
const { username, userId } = yield (0, auth_1.getCurrentUser)();
|
|
34
|
+
owner = userId + "::" + username;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
console.error("Error getting user authentication info:", error);
|
|
38
|
+
// Continue even if error occurs (API call will fail)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Return with auth mode parameters
|
|
42
|
+
return {
|
|
43
|
+
owner,
|
|
44
|
+
authModeParams: { authMode },
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Find all related query keys
|
|
50
|
+
* @param modelName Model name
|
|
51
|
+
* @param queryClient TanStack QueryClient instance
|
|
52
|
+
* @returns Array of related query keys (readonly unknown[] type)
|
|
53
|
+
*/
|
|
54
|
+
function findRelatedQueryKeys(modelName, queryClient) {
|
|
55
|
+
// Extract only query keys from Query objects array
|
|
56
|
+
return queryClient
|
|
57
|
+
.getQueryCache()
|
|
58
|
+
.findAll({
|
|
59
|
+
predicate: ({ queryKey }) => {
|
|
60
|
+
// Find all query keys starting with model name
|
|
61
|
+
// Example: ["Mission"], ["Mission", "filter", ...], ["Daily", dailyId, "Mission"]
|
|
62
|
+
return Array.isArray(queryKey) && queryKey[0] === modelName;
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
.map((query) => query.queryKey);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Helper function to handle optimistic updates and cache updates for a single item
|
|
69
|
+
* @param queryClient TanStack QueryClient instance
|
|
70
|
+
* @param modelName Model name
|
|
71
|
+
* @param relatedQueryKeys Array of related query keys to update
|
|
72
|
+
* @param itemId ID of item to update
|
|
73
|
+
* @param updateData Data to use for optimistic update (includes id)
|
|
74
|
+
* @returns Previous cache data (for rollback) - using QueryKey as key
|
|
75
|
+
*/
|
|
76
|
+
function performOptimisticUpdate(queryClient, modelName, relatedQueryKeys, itemId, updateData) {
|
|
77
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
78
|
+
const previousDataMap = new Map();
|
|
79
|
+
// 1. Update individual item cache
|
|
80
|
+
const singleItemQueryKey = [modelName, itemId];
|
|
81
|
+
const previousItemSingle = queryClient.getQueryData(singleItemQueryKey);
|
|
82
|
+
previousDataMap.set(singleItemQueryKey, previousItemSingle);
|
|
83
|
+
// Merge with existing data if available, otherwise use updateData as optimistic data
|
|
84
|
+
const optimisticData = previousItemSingle
|
|
85
|
+
? Object.assign(Object.assign({}, previousItemSingle), updateData)
|
|
86
|
+
: updateData; // updateData includes at least id here
|
|
87
|
+
queryClient.setQueryData(singleItemQueryKey, optimisticData);
|
|
88
|
+
// 2. Update list queries
|
|
89
|
+
relatedQueryKeys.forEach((queryKey) => {
|
|
90
|
+
// Process only list query keys since single item key is handled above
|
|
91
|
+
if (queryKey.length > 1 && queryKey[1] !== itemId) {
|
|
92
|
+
const previousItems = queryClient.getQueryData(queryKey);
|
|
93
|
+
previousDataMap.set(queryKey, previousItems); // Backup previous data
|
|
94
|
+
queryClient.setQueryData(queryKey, (oldData) => {
|
|
95
|
+
// Safely handle if oldData is null, undefined or not an array
|
|
96
|
+
const oldItems = Array.isArray(oldData) ? oldData : [];
|
|
97
|
+
const hasItem = oldItems.some((item) => item && item.id === itemId);
|
|
98
|
+
if (hasItem) {
|
|
99
|
+
// Update if existing item found
|
|
100
|
+
return oldItems.map((item) => item && item.id === itemId
|
|
101
|
+
? Object.assign(Object.assign({}, item), updateData) : item);
|
|
102
|
+
}
|
|
103
|
+
else if (optimisticData && queryKey.length < 3) {
|
|
104
|
+
// Only consider adding created item for top-level list queries
|
|
105
|
+
// Add if no existing item and optimistic update data available (for create/upsert)
|
|
106
|
+
return [...oldItems, optimisticData];
|
|
107
|
+
}
|
|
108
|
+
return oldItems; // No changes
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
return previousDataMap;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Handle cache updates after API call success
|
|
117
|
+
* @param queryClient TanStack QueryClient instance
|
|
118
|
+
* @param modelName Model name
|
|
119
|
+
* @param relatedQueryKeys Array of related query keys to update
|
|
120
|
+
* @param itemId ID of updated item
|
|
121
|
+
* @param updatedItem Latest item data from API response
|
|
122
|
+
*/
|
|
123
|
+
function handleCacheUpdateOnSuccess(queryClient, modelName, relatedQueryKeys, itemId, updatedItem) {
|
|
124
|
+
// 1. Update individual item cache
|
|
125
|
+
queryClient.setQueryData([modelName, itemId], updatedItem);
|
|
126
|
+
// 2. Update list query cache (with relational filtering applied)
|
|
127
|
+
relatedQueryKeys.forEach((queryKey) => {
|
|
128
|
+
// Check if relational query - e.g. ["Mission", "Daily", "daily-id", ...]
|
|
129
|
+
const isRelationalQuery = queryKey.length > 3 &&
|
|
130
|
+
typeof queryKey[1] === "string" &&
|
|
131
|
+
typeof queryKey[2] === "string";
|
|
132
|
+
// Check if relational query - e.g. ["Mission", "Daily", "daily-id", ...]
|
|
133
|
+
if (isRelationalQuery) {
|
|
134
|
+
const relationName = queryKey[1]; // "Daily", "User" 등
|
|
135
|
+
const relationId = queryKey[2]; // 실제 관계 ID
|
|
136
|
+
const relationField = `${relationName.toLowerCase()}Id`; // "dailyId", "userId" 등
|
|
137
|
+
// Check if updated item belongs to this relation ID
|
|
138
|
+
const belongsToRelation = updatedItem[relationField] === relationId;
|
|
139
|
+
if (!belongsToRelation) {
|
|
140
|
+
// Skip cache update if item does not belong to this relation ID
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Update query cache
|
|
145
|
+
queryClient.setQueryData(queryKey, (oldData) => {
|
|
146
|
+
const oldItems = Array.isArray(oldData) ? oldData : [];
|
|
147
|
+
const hasItem = oldItems.some((item) => item && item.id === itemId);
|
|
148
|
+
if (hasItem) {
|
|
149
|
+
// Update if existing item found
|
|
150
|
+
return oldItems.map((item) => item && item.id === itemId ? updatedItem : item);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// Add if successfully created (already filtered in relational queries)
|
|
154
|
+
return [...oldItems, updatedItem];
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Rollback cache on error
|
|
161
|
+
* @param queryClient TanStack QueryClient instance
|
|
162
|
+
* @param previousDataMap Map containing previous cache data
|
|
163
|
+
*/
|
|
164
|
+
function rollbackCache(queryClient, previousDataMap) {
|
|
165
|
+
previousDataMap.forEach((previousData, queryKey) => {
|
|
166
|
+
queryClient.setQueryData(queryKey, previousData);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Create model-specific Amplify service
|
|
171
|
+
* @param modelName Model name
|
|
172
|
+
* @param queryMap Model-specific query name mapping (optional)
|
|
173
|
+
* @param defaultAuthMode Default authentication mode (default: 'userPool')
|
|
174
|
+
* @returns AmplifyDataService instance for the model
|
|
175
|
+
*/
|
|
176
|
+
function createAmplifyService(modelName, queryMap, defaultAuthMode = "userPool") {
|
|
177
|
+
// Track current authentication mode state
|
|
178
|
+
let currentAuthMode = defaultAuthMode;
|
|
179
|
+
// Create service object
|
|
180
|
+
const service = {
|
|
181
|
+
// Add model name (needed for singleton services)
|
|
182
|
+
modelName,
|
|
183
|
+
// Set authentication mode method
|
|
184
|
+
setAuthMode: (authMode) => {
|
|
185
|
+
currentAuthMode = authMode;
|
|
186
|
+
console.log(`🔐 ${modelName} service auth mode changed: ${authMode}`);
|
|
187
|
+
},
|
|
188
|
+
// Get current authentication mode
|
|
189
|
+
getAuthMode: () => {
|
|
190
|
+
return currentAuthMode;
|
|
191
|
+
},
|
|
192
|
+
// Set authentication mode chaining method
|
|
193
|
+
withAuthMode: (authMode) => {
|
|
194
|
+
const clonedService = Object.assign({}, service);
|
|
195
|
+
clonedService.setAuthMode(authMode);
|
|
196
|
+
return clonedService;
|
|
197
|
+
},
|
|
198
|
+
// Create item
|
|
199
|
+
create: (data, options) => __awaiter(this, void 0, void 0, function* () {
|
|
200
|
+
try {
|
|
201
|
+
if (!data) {
|
|
202
|
+
console.error(`🍬 ${modelName} creation error: data is null`);
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
// Determine auth mode (use provided option if available)
|
|
206
|
+
const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode;
|
|
207
|
+
// Get owner and parameters based on auth mode
|
|
208
|
+
const { authModeParams } = yield getOwnerByAuthMode(authMode);
|
|
209
|
+
const dataWithoutOwner = utils_1.Utils.removeOwnerField(data, "create");
|
|
210
|
+
const cleanedData = {};
|
|
211
|
+
Object.entries(dataWithoutOwner).forEach(([key, value]) => {
|
|
212
|
+
if (value !== undefined) {
|
|
213
|
+
cleanedData[key] = value;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
const newItem = Object.assign(Object.assign({}, cleanedData), { id: (0, expo_crypto_1.randomUUID)() });
|
|
217
|
+
// Extract relation fields (e.g., dailyId, userId)
|
|
218
|
+
const relationFields = new Map();
|
|
219
|
+
for (const [key, value] of Object.entries(newItem)) {
|
|
220
|
+
if (key.endsWith("Id") && typeof value === "string" && value) {
|
|
221
|
+
// Relation field name and its value
|
|
222
|
+
relationFields.set(key, value);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Find related query keys (all keys starting with model name)
|
|
226
|
+
const relatedQueryKeys = findRelatedQueryKeys(modelName, query_1.queryClient);
|
|
227
|
+
// Backup previous data for optimistic update
|
|
228
|
+
const previousDataMap = new Map();
|
|
229
|
+
// Update individual item cache
|
|
230
|
+
const singleItemQueryKey = [modelName, newItem.id];
|
|
231
|
+
const previousItemSingle = query_1.queryClient.getQueryData(singleItemQueryKey);
|
|
232
|
+
previousDataMap.set(singleItemQueryKey, previousItemSingle);
|
|
233
|
+
query_1.queryClient.setQueryData(singleItemQueryKey, newItem);
|
|
234
|
+
// Update list queries (with relation filtering)
|
|
235
|
+
relatedQueryKeys.forEach((queryKey) => {
|
|
236
|
+
// Check if it's a relational query (e.g., ["Mission", "Daily", "daily-id", ...])
|
|
237
|
+
const isRelationalQuery = queryKey.length > 3 &&
|
|
238
|
+
typeof queryKey[1] === "string" &&
|
|
239
|
+
typeof queryKey[2] === "string";
|
|
240
|
+
if (isRelationalQuery) {
|
|
241
|
+
// For relational queries, only add to cache if it belongs to the relation
|
|
242
|
+
const relationName = queryKey[1]; // "Daily", "User" etc.
|
|
243
|
+
const relationId = queryKey[2]; // Actual relation ID value
|
|
244
|
+
const relationField = `${relationName.toLowerCase()}Id`; // "dailyId", "userId" etc.
|
|
245
|
+
// Check if new item belongs to this relation ID
|
|
246
|
+
const belongsToRelation = newItem[relationField] === relationId;
|
|
247
|
+
if (belongsToRelation) {
|
|
248
|
+
// Only update cache if it belongs to this relation
|
|
249
|
+
const data = query_1.queryClient.getQueryData(queryKey);
|
|
250
|
+
if (data) {
|
|
251
|
+
previousDataMap.set(queryKey, data);
|
|
252
|
+
}
|
|
253
|
+
query_1.queryClient.setQueryData(queryKey, (oldData) => {
|
|
254
|
+
const oldItems = Array.isArray(oldData) ? oldData : [];
|
|
255
|
+
return [...oldItems, newItem];
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
else if (queryKey.length < 3) {
|
|
260
|
+
// Regular list query
|
|
261
|
+
const data = query_1.queryClient.getQueryData(queryKey);
|
|
262
|
+
if (data) {
|
|
263
|
+
previousDataMap.set(queryKey, data);
|
|
264
|
+
}
|
|
265
|
+
query_1.queryClient.setQueryData(queryKey, (oldData) => {
|
|
266
|
+
const oldItems = Array.isArray(oldData) ? oldData : [];
|
|
267
|
+
return [...oldItems, newItem];
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
try {
|
|
272
|
+
// Attempt API call - apply auth mode
|
|
273
|
+
console.log(`🍬 ${modelName} creation attempt [Auth: ${authMode}]:`, newItem.id);
|
|
274
|
+
const { data: createdItem } = yield (0, client_1.getClient)().models[modelName].create(newItem, authModeParams);
|
|
275
|
+
if (createdItem) {
|
|
276
|
+
// Update cache on API success
|
|
277
|
+
query_1.queryClient.setQueryData(singleItemQueryKey, createdItem);
|
|
278
|
+
// Invalidate queries based on relation fields
|
|
279
|
+
for (const [field, value] of relationFields) {
|
|
280
|
+
// Extract relation name from field name (e.g., "dailyId" -> "Daily")
|
|
281
|
+
const relationName = field
|
|
282
|
+
.replace(/Id$/, "")
|
|
283
|
+
.replace(/^./, (c) => c.toUpperCase());
|
|
284
|
+
// Invalidate queries for this relation
|
|
285
|
+
query_1.queryClient.invalidateQueries({
|
|
286
|
+
queryKey: [modelName, relationName, value],
|
|
287
|
+
refetchType: "active",
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
// Invalidate all related queries
|
|
291
|
+
query_1.queryClient.invalidateQueries({
|
|
292
|
+
queryKey: [modelName],
|
|
293
|
+
refetchType: "active",
|
|
294
|
+
});
|
|
295
|
+
console.log(`🍬 ${modelName} creation successful:`, newItem.id);
|
|
296
|
+
return createdItem;
|
|
297
|
+
}
|
|
298
|
+
// Keep optimistic update data even if no API response
|
|
299
|
+
console.warn(`🍬 ${modelName} creation API no response. Keeping optimistic update data.`);
|
|
300
|
+
return newItem;
|
|
301
|
+
}
|
|
302
|
+
catch (apiError) {
|
|
303
|
+
// Rollback and log error on API failure
|
|
304
|
+
console.error(`🍬 ${modelName} creation error, performing rollback:`, apiError);
|
|
305
|
+
rollbackCache(query_1.queryClient, previousDataMap);
|
|
306
|
+
// Re-throw error for caller to handle
|
|
307
|
+
throw apiError;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
console.error(`🍬 ${modelName} final creation error:`, error);
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
}),
|
|
315
|
+
// Batch create items
|
|
316
|
+
createList: (dataList, options) => __awaiter(this, void 0, void 0, function* () {
|
|
317
|
+
try {
|
|
318
|
+
if (!dataList || dataList.length === 0) {
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
// Determine auth mode (use provided option if available)
|
|
322
|
+
const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode;
|
|
323
|
+
// Get owner and parameters based on auth mode
|
|
324
|
+
const { authModeParams } = yield getOwnerByAuthMode(authMode);
|
|
325
|
+
const preparedItems = dataList
|
|
326
|
+
.map((data) => {
|
|
327
|
+
if (!data)
|
|
328
|
+
return null;
|
|
329
|
+
const dataWithoutOwner = utils_1.Utils.removeOwnerField(data, "create");
|
|
330
|
+
const cleanedData = {};
|
|
331
|
+
Object.entries(dataWithoutOwner).forEach(([key, value]) => {
|
|
332
|
+
if (value !== undefined) {
|
|
333
|
+
cleanedData[key] = value;
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
return Object.assign(Object.assign({}, cleanedData), { id: (0, expo_crypto_1.randomUUID)() });
|
|
337
|
+
})
|
|
338
|
+
.filter(Boolean);
|
|
339
|
+
const relatedQueryKeys = findRelatedQueryKeys(modelName, query_1.queryClient);
|
|
340
|
+
const previousDataMap = new Map();
|
|
341
|
+
// Extract relation properties (e.g., dailyId)
|
|
342
|
+
const relationFields = new Map();
|
|
343
|
+
for (const item of preparedItems) {
|
|
344
|
+
for (const [key, value] of Object.entries(item)) {
|
|
345
|
+
if (key.endsWith("Id") && typeof value === "string" && value) {
|
|
346
|
+
// Relation field name and its value
|
|
347
|
+
relationFields.set(key, value);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
console.log(`🍬 ${modelName} batch creation attempt: ${preparedItems.length} items`);
|
|
352
|
+
// Batch optimistic update - with relation filtering
|
|
353
|
+
relatedQueryKeys.forEach((queryKey) => {
|
|
354
|
+
// Check if this query key is a relational query (e.g., ["Mission", "Daily", "daily-id", ...])
|
|
355
|
+
const isRelationalQuery = queryKey.length > 3 &&
|
|
356
|
+
typeof queryKey[1] === "string" &&
|
|
357
|
+
typeof queryKey[2] === "string";
|
|
358
|
+
const previousItems = query_1.queryClient.getQueryData(queryKey);
|
|
359
|
+
previousDataMap.set(queryKey, previousItems); // Backup previous data
|
|
360
|
+
if (isRelationalQuery) {
|
|
361
|
+
// For relational queries, only add items that belong to the relation
|
|
362
|
+
const relationName = queryKey[1]; // "Daily", "User" etc.
|
|
363
|
+
const relationId = queryKey[2]; // Actual relation ID value
|
|
364
|
+
const relationField = `${relationName.toLowerCase()}Id`; // "dailyId", "userId" etc.
|
|
365
|
+
query_1.queryClient.setQueryData(queryKey, (oldData) => {
|
|
366
|
+
const oldItems = Array.isArray(oldData) ? oldData : [];
|
|
367
|
+
// Filter new items that belong to this relation ID
|
|
368
|
+
const itemsToAdd = preparedItems.filter((newItem) => newItem[relationField] === relationId);
|
|
369
|
+
// Merge existing items with filtered new items
|
|
370
|
+
if (itemsToAdd.length > 0) {
|
|
371
|
+
return [...oldItems, ...itemsToAdd];
|
|
372
|
+
}
|
|
373
|
+
return oldItems; // No change if no items match relation ID
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
else if (queryKey.length < 3) {
|
|
377
|
+
// Regular list query - add all items
|
|
378
|
+
query_1.queryClient.setQueryData(queryKey, (oldData) => {
|
|
379
|
+
const oldItems = Array.isArray(oldData) ? oldData : [];
|
|
380
|
+
return [...oldItems, ...preparedItems];
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
// Update individual item caches
|
|
385
|
+
preparedItems.forEach((item) => {
|
|
386
|
+
const singleItemQueryKey = [modelName, item.id];
|
|
387
|
+
const previousItemSingle = query_1.queryClient.getQueryData(singleItemQueryKey);
|
|
388
|
+
previousDataMap.set(singleItemQueryKey, previousItemSingle);
|
|
389
|
+
query_1.queryClient.setQueryData(singleItemQueryKey, item);
|
|
390
|
+
});
|
|
391
|
+
try {
|
|
392
|
+
// Parallel API calls - apply auth mode
|
|
393
|
+
const createPromises = preparedItems.map((newItem) => __awaiter(this, void 0, void 0, function* () {
|
|
394
|
+
try {
|
|
395
|
+
const { data: createdItem } = yield (0, client_1.getClient)().models[modelName].create(newItem, authModeParams);
|
|
396
|
+
// Update individual item cache on API success
|
|
397
|
+
if (createdItem) {
|
|
398
|
+
query_1.queryClient.setQueryData([modelName, createdItem.id], createdItem);
|
|
399
|
+
}
|
|
400
|
+
return createdItem || newItem;
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
console.error(`🍬 ${modelName} batch creation failed for ID ${newItem.id}:`, error);
|
|
404
|
+
return newItem;
|
|
405
|
+
}
|
|
406
|
+
}));
|
|
407
|
+
const results = yield Promise.all(createPromises);
|
|
408
|
+
console.log(`🍬 ${modelName} batch creation completed: ${results.length} items`);
|
|
409
|
+
// After creation, invalidate all related queries to ensure exact server data
|
|
410
|
+
// This is important as it prevents side effects of optimistic updates by invalidating related queries
|
|
411
|
+
for (const [field, value] of relationFields) {
|
|
412
|
+
// Extract relation name from field name (e.g., "dailyId" -> "Daily")
|
|
413
|
+
const relationName = field
|
|
414
|
+
.replace(/Id$/, "")
|
|
415
|
+
.replace(/^./, (c) => c.toUpperCase());
|
|
416
|
+
// Invalidate query for this relation
|
|
417
|
+
query_1.queryClient.invalidateQueries({
|
|
418
|
+
queryKey: [modelName, relationName, value],
|
|
419
|
+
refetchType: "active",
|
|
420
|
+
});
|
|
421
|
+
// Also invalidate general queries
|
|
422
|
+
query_1.queryClient.invalidateQueries({
|
|
423
|
+
queryKey: [modelName],
|
|
424
|
+
refetchType: "active",
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
return results;
|
|
428
|
+
}
|
|
429
|
+
catch (apiError) {
|
|
430
|
+
console.error(`🍬 ${modelName} batch creation API error, performing rollback:`, apiError);
|
|
431
|
+
rollbackCache(query_1.queryClient, previousDataMap);
|
|
432
|
+
throw apiError;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
console.error(`🍬 ${modelName} final batch creation error:`, error);
|
|
437
|
+
return [];
|
|
438
|
+
}
|
|
439
|
+
}),
|
|
440
|
+
// Get item
|
|
441
|
+
get: (id_1, ...args_1) => __awaiter(this, [id_1, ...args_1], void 0, function* (id, options = { forceRefresh: false }) {
|
|
442
|
+
try {
|
|
443
|
+
const singleItemQueryKey = [modelName, id];
|
|
444
|
+
// Check cache first (if forceRefresh is false)
|
|
445
|
+
if (!options.forceRefresh) {
|
|
446
|
+
const cachedItem = query_1.queryClient.getQueryData(singleItemQueryKey);
|
|
447
|
+
if (cachedItem) {
|
|
448
|
+
return cachedItem;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
// Determine auth mode (use provided option if available)
|
|
452
|
+
const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode;
|
|
453
|
+
// Get parameters based on auth mode
|
|
454
|
+
const { authModeParams } = yield getOwnerByAuthMode(authMode);
|
|
455
|
+
// API call - apply auth mode
|
|
456
|
+
const { data: item } = yield (0, client_1.getClient)().models[modelName].get({ id }, authModeParams);
|
|
457
|
+
// Update cache
|
|
458
|
+
if (item) {
|
|
459
|
+
query_1.queryClient.setQueryData(singleItemQueryKey, item);
|
|
460
|
+
// Update related list queries (lists that might contain this item)
|
|
461
|
+
const relatedQueryKeys = findRelatedQueryKeys(modelName, query_1.queryClient);
|
|
462
|
+
relatedQueryKeys.forEach((queryKey) => {
|
|
463
|
+
// Exclude single item keys, only process list queries
|
|
464
|
+
if (queryKey.length > 1 && queryKey[1] !== id) {
|
|
465
|
+
// Query keys with id as second element are not single item get query key format, so treat as list
|
|
466
|
+
query_1.queryClient.setQueryData(queryKey, (oldData) => {
|
|
467
|
+
const oldItems = Array.isArray(oldData) ? oldData : [];
|
|
468
|
+
const exists = oldItems.some((oldItem) => oldItem && oldItem.id === id);
|
|
469
|
+
if (exists) {
|
|
470
|
+
return oldItems.map((oldItem) => oldItem && oldItem.id === id ? item : oldItem);
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
// Need to check if item matches list query filter conditions (not checking here)
|
|
474
|
+
// If checking is difficult, invalidateQueries might be safer
|
|
475
|
+
// Currently implemented to add item to all related lists that might contain it on API get success
|
|
476
|
+
return [...oldItems, item];
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
return item || null;
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
console.error(`🍬 ${modelName} get error:`, error);
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
}),
|
|
489
|
+
// Batch get items
|
|
490
|
+
list: (...args_1) => __awaiter(this, [...args_1], void 0, function* (options = { filter: undefined, forceRefresh: false }) {
|
|
491
|
+
var _a;
|
|
492
|
+
try {
|
|
493
|
+
// Determine query key
|
|
494
|
+
const queryKey = options.filter
|
|
495
|
+
? [modelName, "filter", JSON.stringify(options.filter)]
|
|
496
|
+
: [modelName];
|
|
497
|
+
// Check cache first (if forceRefresh is false)
|
|
498
|
+
if (!options.forceRefresh) {
|
|
499
|
+
const cachedItems = query_1.queryClient.getQueryData(queryKey);
|
|
500
|
+
if (cachedItems && cachedItems.length > 0) {
|
|
501
|
+
console.log(`🍬 ${modelName} list using cache`, queryKey);
|
|
502
|
+
return cachedItems.filter((item) => item !== null);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
// Determine auth mode (use provided option if available)
|
|
506
|
+
const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode;
|
|
507
|
+
// Get owner and parameters based on auth mode
|
|
508
|
+
const { owner, authModeParams } = yield getOwnerByAuthMode(authMode);
|
|
509
|
+
// Get owner-based query name, use default format if not found
|
|
510
|
+
const ownerQueryName = (queryMap === null || queryMap === void 0 ? void 0 : queryMap[modelName]) || `list${modelName}sByOwner`;
|
|
511
|
+
// Try query call
|
|
512
|
+
try {
|
|
513
|
+
console.log(`🍬 ${modelName} list API call`, queryKey, `by ${ownerQueryName}`, `[Auth: ${authMode}]`);
|
|
514
|
+
// Execute owner query
|
|
515
|
+
const { data: result } = yield (0, client_1.getClient)().models[modelName][ownerQueryName]({ owner, authMode }, authModeParams);
|
|
516
|
+
// Extract result data + filter null values
|
|
517
|
+
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);
|
|
518
|
+
// Apply filter (if client-side filtering needed)
|
|
519
|
+
let filteredItems = items;
|
|
520
|
+
if (options.filter) {
|
|
521
|
+
filteredItems = items.filter((item) => {
|
|
522
|
+
return Object.entries(options.filter).every(([key, value]) => {
|
|
523
|
+
if (typeof value === "object" && value !== null) {
|
|
524
|
+
// Ensure type safety when accessing item with key
|
|
525
|
+
const itemValue = item[key];
|
|
526
|
+
if ("eq" in value)
|
|
527
|
+
return itemValue === value.eq;
|
|
528
|
+
if ("ne" in value)
|
|
529
|
+
return itemValue !== value.ne;
|
|
530
|
+
if ("gt" in value)
|
|
531
|
+
return itemValue > value.gt;
|
|
532
|
+
if ("lt" in value)
|
|
533
|
+
return itemValue < value.lt;
|
|
534
|
+
if ("contains" in value)
|
|
535
|
+
return String(itemValue).includes(value.contains);
|
|
536
|
+
if ("between" in value &&
|
|
537
|
+
Array.isArray(value.between))
|
|
538
|
+
return (itemValue >= value.between[0] &&
|
|
539
|
+
itemValue <= value.between[1]);
|
|
540
|
+
}
|
|
541
|
+
// Ensure type safety when accessing item with key
|
|
542
|
+
return item[key] === value;
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
// Update cache
|
|
547
|
+
query_1.queryClient.setQueryData(queryKey, filteredItems);
|
|
548
|
+
// Update individual item cache
|
|
549
|
+
filteredItems.forEach((item) => {
|
|
550
|
+
query_1.queryClient.setQueryData([modelName, item.id], item);
|
|
551
|
+
});
|
|
552
|
+
return filteredItems;
|
|
553
|
+
}
|
|
554
|
+
catch (error) {
|
|
555
|
+
if ((_a = error === null || error === void 0 ? void 0 : error.message) === null || _a === void 0 ? void 0 : _a.includes("not found")) {
|
|
556
|
+
console.warn(`🍬 ${ownerQueryName} query not found. Trying default list query...`);
|
|
557
|
+
// Try default list query if owner query not found
|
|
558
|
+
const { data: result } = yield (0, client_1.getClient)().models[modelName].list({}, authModeParams);
|
|
559
|
+
// Extract and process result data
|
|
560
|
+
const items = ((result === null || result === void 0 ? void 0 : result.items) ||
|
|
561
|
+
(result === null || result === void 0 ? void 0 : result.data) ||
|
|
562
|
+
result ||
|
|
563
|
+
[]).filter((item) => item !== null);
|
|
564
|
+
// Filter, cache update etc. remaining logic same
|
|
565
|
+
let filteredItems = items;
|
|
566
|
+
if (options.filter) {
|
|
567
|
+
// Filtering logic (same as before)
|
|
568
|
+
filteredItems = items.filter((item) => {
|
|
569
|
+
return Object.entries(options.filter).every(([key, value]) => {
|
|
570
|
+
if (typeof value === "object" && value !== null) {
|
|
571
|
+
// Ensure type safety when accessing item with key
|
|
572
|
+
const itemValue = item[key];
|
|
573
|
+
if ("eq" in value)
|
|
574
|
+
return itemValue === value.eq;
|
|
575
|
+
if ("ne" in value)
|
|
576
|
+
return itemValue !== value.ne;
|
|
577
|
+
if ("gt" in value)
|
|
578
|
+
return itemValue > value.gt;
|
|
579
|
+
if ("lt" in value)
|
|
580
|
+
return itemValue < value.lt;
|
|
581
|
+
if ("contains" in value)
|
|
582
|
+
return String(itemValue).includes(value.contains);
|
|
583
|
+
if ("between" in value &&
|
|
584
|
+
Array.isArray(value.between))
|
|
585
|
+
return (itemValue >= value.between[0] &&
|
|
586
|
+
itemValue <= value.between[1]);
|
|
587
|
+
}
|
|
588
|
+
// Ensure type safety when accessing item with key
|
|
589
|
+
return item[key] === value;
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
query_1.queryClient.setQueryData(queryKey, filteredItems);
|
|
594
|
+
filteredItems.forEach((item) => {
|
|
595
|
+
query_1.queryClient.setQueryData([modelName, item.id], item);
|
|
596
|
+
});
|
|
597
|
+
return filteredItems;
|
|
598
|
+
}
|
|
599
|
+
throw error; // Pass other errors to upper catch
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
catch (error) {
|
|
603
|
+
console.log("🍬 error", error);
|
|
604
|
+
console.error(`🍬 ${modelName} list error:`, error);
|
|
605
|
+
// Invalidate list query cache on error
|
|
606
|
+
const queryKey = [
|
|
607
|
+
modelName,
|
|
608
|
+
"filter",
|
|
609
|
+
JSON.stringify(options.filter),
|
|
610
|
+
];
|
|
611
|
+
query_1.queryClient.invalidateQueries({ queryKey });
|
|
612
|
+
return [];
|
|
613
|
+
}
|
|
614
|
+
}),
|
|
615
|
+
// Update item (modified to use helper functions)
|
|
616
|
+
update: (data, options) => __awaiter(this, void 0, void 0, function* () {
|
|
617
|
+
try {
|
|
618
|
+
if (!(data === null || data === void 0 ? void 0 : data.id)) {
|
|
619
|
+
console.error(`🍬 ${modelName} update error: No valid data or id provided.`);
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
// Determine auth mode (use provided option if available)
|
|
623
|
+
const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode;
|
|
624
|
+
// Get parameters based on auth mode
|
|
625
|
+
const { authModeParams } = yield getOwnerByAuthMode(authMode);
|
|
626
|
+
const dataWithoutOwner = utils_1.Utils.removeOwnerField(data, "update");
|
|
627
|
+
const cleanedData = {};
|
|
628
|
+
// Always include id, and only include other fields that are actually passed (not undefined)
|
|
629
|
+
Object.entries(dataWithoutOwner).forEach(([key, value]) => {
|
|
630
|
+
if (key === "id" || value !== undefined) {
|
|
631
|
+
cleanedData[key] = value;
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
const { id: itemId } = cleanedData; // Get id from cleanedData
|
|
635
|
+
// Find related query keys
|
|
636
|
+
const relatedQueryKeys = findRelatedQueryKeys(modelName, query_1.queryClient);
|
|
637
|
+
// Perform optimistic update
|
|
638
|
+
const previousDataMap = yield performOptimisticUpdate(query_1.queryClient, modelName, relatedQueryKeys, itemId, cleanedData);
|
|
639
|
+
try {
|
|
640
|
+
// Attempt API call - apply auth mode
|
|
641
|
+
console.log(`🍬 ${modelName} update attempt [Auth: ${authMode}]:`, itemId);
|
|
642
|
+
const { data: updatedItem } = yield (0, client_1.getClient)().models[modelName].update(cleanedData, authModeParams);
|
|
643
|
+
if (updatedItem) {
|
|
644
|
+
// Update cache on API success
|
|
645
|
+
handleCacheUpdateOnSuccess(query_1.queryClient, modelName, relatedQueryKeys, itemId, updatedItem);
|
|
646
|
+
// Invalidate all related queries to automatically fetch new data
|
|
647
|
+
query_1.queryClient.invalidateQueries({
|
|
648
|
+
queryKey: [modelName],
|
|
649
|
+
refetchType: "active",
|
|
650
|
+
});
|
|
651
|
+
console.log(`🍬 ${modelName} update success:`, itemId);
|
|
652
|
+
return updatedItem;
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
console.warn(`🍬 ${modelName} update API response missing. Maintaining optimistic update data.`);
|
|
656
|
+
// If no API response, return the data saved during optimistic update
|
|
657
|
+
const singleItemQueryKey = [modelName, itemId];
|
|
658
|
+
return (previousDataMap.get(singleItemQueryKey) || null);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
catch (apiError) {
|
|
662
|
+
// Rollback and log error on API failure
|
|
663
|
+
console.error(`🍬 ${modelName} update error, performing rollback:`, apiError);
|
|
664
|
+
rollbackCache(query_1.queryClient, previousDataMap);
|
|
665
|
+
throw apiError;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
catch (error) {
|
|
669
|
+
console.error(`🍬 ${modelName} final update error:`, error);
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
}),
|
|
673
|
+
// Delete item (modified to use helper functions)
|
|
674
|
+
delete: (id, options) => __awaiter(this, void 0, void 0, function* () {
|
|
675
|
+
try {
|
|
676
|
+
if (!id) {
|
|
677
|
+
console.error(`🍬 ${modelName} delete error: No valid id provided.`);
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
// Determine auth mode (use provided option if available)
|
|
681
|
+
const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode;
|
|
682
|
+
// Get parameters based on auth mode
|
|
683
|
+
const { authModeParams } = yield getOwnerByAuthMode(authMode);
|
|
684
|
+
// Find related query keys
|
|
685
|
+
const relatedQueryKeys = findRelatedQueryKeys(modelName, query_1.queryClient);
|
|
686
|
+
// Backup item data before deletion (for rollback)
|
|
687
|
+
const previousDataMap = new Map();
|
|
688
|
+
// Backup previous data and perform optimistic update for individual item (set to null)
|
|
689
|
+
const singleItemQueryKey = [modelName, id];
|
|
690
|
+
const previousItemSingle = query_1.queryClient.getQueryData(singleItemQueryKey);
|
|
691
|
+
previousDataMap.set(singleItemQueryKey, previousItemSingle);
|
|
692
|
+
query_1.queryClient.setQueryData(singleItemQueryKey, null);
|
|
693
|
+
// Backup and perform optimistic update for list queries (remove item)
|
|
694
|
+
relatedQueryKeys.forEach((queryKey) => {
|
|
695
|
+
if (queryKey.length > 1 && queryKey[1] !== id) {
|
|
696
|
+
// Exclude single item keys
|
|
697
|
+
const data = query_1.queryClient.getQueryData(queryKey);
|
|
698
|
+
if (data) {
|
|
699
|
+
previousDataMap.set(queryKey, data);
|
|
700
|
+
}
|
|
701
|
+
query_1.queryClient.setQueryData(queryKey, (oldData) => {
|
|
702
|
+
const oldItems = Array.isArray(oldData) ? oldData : [];
|
|
703
|
+
return oldItems.filter((item) => (item === null || item === void 0 ? void 0 : item.id) !== id);
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
try {
|
|
708
|
+
// API call - apply auth mode
|
|
709
|
+
console.log(`🍬 ${modelName} delete attempt [Auth: ${authMode}]:`, id);
|
|
710
|
+
yield (0, client_1.getClient)().models[modelName].delete({ id }, authModeParams);
|
|
711
|
+
console.log(`🍬 ${modelName} delete success:`, id);
|
|
712
|
+
// On API success, invalidate all related queries to automatically refresh
|
|
713
|
+
query_1.queryClient.invalidateQueries({
|
|
714
|
+
queryKey: [modelName],
|
|
715
|
+
refetchType: "active",
|
|
716
|
+
});
|
|
717
|
+
return true;
|
|
718
|
+
}
|
|
719
|
+
catch (error) {
|
|
720
|
+
// Rollback on API error
|
|
721
|
+
console.error(`🍬 ${modelName} delete API error, performing rollback:`, error);
|
|
722
|
+
rollbackCache(query_1.queryClient, previousDataMap);
|
|
723
|
+
throw error;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
catch (error) {
|
|
727
|
+
console.error(`🍬 ${modelName} final delete error:`, error);
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
}),
|
|
731
|
+
// Batch delete multiple items (consider applying helper functions)
|
|
732
|
+
deleteList: (ids, options) => __awaiter(this, void 0, void 0, function* () {
|
|
733
|
+
try {
|
|
734
|
+
const results = {
|
|
735
|
+
success: [],
|
|
736
|
+
failed: [],
|
|
737
|
+
};
|
|
738
|
+
if (!ids || ids.length === 0) {
|
|
739
|
+
console.warn(`🍬 ${modelName} batch delete: Empty ID array provided.`);
|
|
740
|
+
return results;
|
|
741
|
+
}
|
|
742
|
+
// Determine auth mode (use provided option if available)
|
|
743
|
+
const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode;
|
|
744
|
+
// Get parameters based on auth mode
|
|
745
|
+
const { authModeParams } = yield getOwnerByAuthMode(authMode);
|
|
746
|
+
// Prepare related query keys and previous data map
|
|
747
|
+
const relatedQueryKeys = findRelatedQueryKeys(modelName, query_1.queryClient);
|
|
748
|
+
const previousDataMap = new Map();
|
|
749
|
+
const previousItemsCache = new Map(); // For individual item rollback
|
|
750
|
+
// Optimistic update - remove items from all caches
|
|
751
|
+
ids.forEach((id) => {
|
|
752
|
+
// Backup previous data and perform optimistic update for individual item (null)
|
|
753
|
+
const singleItemQueryKey = [modelName, id];
|
|
754
|
+
const previousItemSingle = query_1.queryClient.getQueryData(singleItemQueryKey);
|
|
755
|
+
previousItemsCache.set(id, previousItemSingle || null);
|
|
756
|
+
previousDataMap.set(singleItemQueryKey, previousItemSingle); // Backup to map
|
|
757
|
+
query_1.queryClient.setQueryData(singleItemQueryKey, null);
|
|
758
|
+
});
|
|
759
|
+
// Update all list query caches (remove items included in id list)
|
|
760
|
+
relatedQueryKeys.forEach((queryKey) => {
|
|
761
|
+
if (queryKey.length > 1 && !ids.includes(queryKey[1])) {
|
|
762
|
+
// Exclude single item keys and list keys where id is second element (handled individually)
|
|
763
|
+
const data = query_1.queryClient.getQueryData(queryKey);
|
|
764
|
+
if (data) {
|
|
765
|
+
previousDataMap.set(queryKey, data);
|
|
766
|
+
}
|
|
767
|
+
query_1.queryClient.setQueryData(queryKey, (oldData) => {
|
|
768
|
+
const oldItems = Array.isArray(oldData) ? oldData : [];
|
|
769
|
+
return oldItems.filter((item) => item && !ids.includes(item.id));
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
try {
|
|
774
|
+
console.log(`🍬 ${modelName} batch delete attempt [Auth: ${authMode}]: ${ids.length} items`);
|
|
775
|
+
// Parallel API calls - apply auth mode
|
|
776
|
+
const deletePromises = ids.map((id) => __awaiter(this, void 0, void 0, function* () {
|
|
777
|
+
try {
|
|
778
|
+
yield (0, client_1.getClient)().models[modelName].delete({ id }, authModeParams);
|
|
779
|
+
results.success.push(id);
|
|
780
|
+
return { id, success: true };
|
|
781
|
+
}
|
|
782
|
+
catch (error) {
|
|
783
|
+
console.error(`🍬 ${modelName} batch delete failed for ID ${id}:`, error);
|
|
784
|
+
results.failed.push(id);
|
|
785
|
+
return { id, success: false, error };
|
|
786
|
+
}
|
|
787
|
+
}));
|
|
788
|
+
yield Promise.all(deletePromises);
|
|
789
|
+
// If there are failed items, rollback only those items
|
|
790
|
+
if (results.failed.length > 0) {
|
|
791
|
+
console.warn(`🍬 ${modelName} batch delete: ${results.failed.length} items failed, performing partial rollback`);
|
|
792
|
+
for (const failedId of results.failed) {
|
|
793
|
+
// Rollback individual item
|
|
794
|
+
const previousItem = previousItemsCache.get(failedId);
|
|
795
|
+
if (previousItem !== undefined) {
|
|
796
|
+
// Restore if not undefined (was in cache)
|
|
797
|
+
const singleItemQueryKey = [modelName, failedId];
|
|
798
|
+
query_1.queryClient.setQueryData(singleItemQueryKey, previousItem);
|
|
799
|
+
}
|
|
800
|
+
// Restore failed item to list queries
|
|
801
|
+
relatedQueryKeys.forEach((queryKey) => {
|
|
802
|
+
if (queryKey.length > 1 &&
|
|
803
|
+
!ids.includes(queryKey[1])) {
|
|
804
|
+
// Restore failed item to list queries
|
|
805
|
+
query_1.queryClient.setQueryData(queryKey, (oldData) => {
|
|
806
|
+
const oldItems = Array.isArray(oldData) ? oldData : [];
|
|
807
|
+
const previousItem = previousItemsCache.get(failedId);
|
|
808
|
+
if (!previousItem)
|
|
809
|
+
return oldItems;
|
|
810
|
+
// Do not add duplicate item if already in list
|
|
811
|
+
if (oldItems.some((item) => item && item.id === failedId)) {
|
|
812
|
+
return oldItems;
|
|
813
|
+
}
|
|
814
|
+
return [...oldItems, previousItem];
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
// Invalidate all related queries to force refresh (safety mechanism)
|
|
821
|
+
relatedQueryKeys.forEach((queryKey) => query_1.queryClient.invalidateQueries({ queryKey }));
|
|
822
|
+
ids.forEach((id) => query_1.queryClient.invalidateQueries({ queryKey: [modelName, id] }));
|
|
823
|
+
console.log(`🍬 ${modelName} batch delete: ${results.success.length} items deleted, ${results.failed.length} items failed`);
|
|
824
|
+
return results;
|
|
825
|
+
}
|
|
826
|
+
catch (generalError) {
|
|
827
|
+
// General error occurred, performing full rollback
|
|
828
|
+
console.error(`🍬 ${modelName} batch delete: General error occurred, performing full rollback:`, generalError);
|
|
829
|
+
rollbackCache(query_1.queryClient, previousDataMap);
|
|
830
|
+
// Invalidate all queries when a broad error occurs
|
|
831
|
+
query_1.queryClient.invalidateQueries({ queryKey: [modelName] });
|
|
832
|
+
results.failed = [...ids]; // Mark all IDs as failed
|
|
833
|
+
throw generalError; // Re-throw the error
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
catch (error) {
|
|
837
|
+
console.error(`🍬 ${modelName} final batch delete error:`, error);
|
|
838
|
+
return { success: [], failed: ids }; // Return all IDs as failed
|
|
839
|
+
}
|
|
840
|
+
}),
|
|
841
|
+
// Create or update (modified to use helper functions)
|
|
842
|
+
upsert: (data, options) => __awaiter(this, void 0, void 0, function* () {
|
|
843
|
+
try {
|
|
844
|
+
if (!(data === null || data === void 0 ? void 0 : data.id)) {
|
|
845
|
+
console.error(`🍬 ${modelName} upsert error: No valid data or id provided.`);
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
// Determine auth mode (use provided options if available)
|
|
849
|
+
const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode;
|
|
850
|
+
// Get parameters based on auth mode
|
|
851
|
+
const { authModeParams } = yield getOwnerByAuthMode(authMode);
|
|
852
|
+
// Check existing item in cache (without API call)
|
|
853
|
+
// Call get first to ensure latest state. service.get checks cache and calls API if needed.
|
|
854
|
+
const existingItem = yield service.get(data.id);
|
|
855
|
+
const dataWithoutOwner = utils_1.Utils.removeOwnerField(data, "upsert");
|
|
856
|
+
const cleanedData = {};
|
|
857
|
+
Object.entries(dataWithoutOwner).forEach(([key, value]) => {
|
|
858
|
+
if (key === "id" || value !== undefined) {
|
|
859
|
+
cleanedData[key] = value;
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
cleanedData.id = data.id; // Preserve ID
|
|
863
|
+
// Find related query keys
|
|
864
|
+
const relatedQueryKeys = findRelatedQueryKeys(modelName, query_1.queryClient);
|
|
865
|
+
// Perform optimistic update
|
|
866
|
+
const previousDataMap = yield performOptimisticUpdate(query_1.queryClient, modelName, relatedQueryKeys, data.id, cleanedData);
|
|
867
|
+
try {
|
|
868
|
+
if (existingItem) {
|
|
869
|
+
// Use update logic if item exists - apply auth mode
|
|
870
|
+
console.log(`🍬 ${modelName} upsert(update) attempt [Auth: ${authMode}]:`, data.id);
|
|
871
|
+
const { data: updatedItem } = yield (0, client_1.getClient)().models[modelName].update(cleanedData, authModeParams);
|
|
872
|
+
if (updatedItem) {
|
|
873
|
+
handleCacheUpdateOnSuccess(query_1.queryClient, modelName, relatedQueryKeys, data.id, updatedItem);
|
|
874
|
+
console.log(`🍬 ${modelName} upsert(update) success:`, data.id);
|
|
875
|
+
return updatedItem;
|
|
876
|
+
}
|
|
877
|
+
else {
|
|
878
|
+
console.warn(`🍬 ${modelName} upsert(update) no API response. Keeping optimistic update data.`);
|
|
879
|
+
const singleItemQueryKey = [modelName, data.id];
|
|
880
|
+
return (previousDataMap.get(singleItemQueryKey) ||
|
|
881
|
+
null);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
else {
|
|
885
|
+
// Use create logic if item doesn't exist - apply auth mode
|
|
886
|
+
console.log(`🍬 ${modelName} upsert(create) attempt [Auth: ${authMode}]:`, data.id);
|
|
887
|
+
const { data: createdItem } = yield (0, client_1.getClient)().models[modelName].create(cleanedData, authModeParams);
|
|
888
|
+
if (createdItem) {
|
|
889
|
+
handleCacheUpdateOnSuccess(query_1.queryClient, modelName, relatedQueryKeys, data.id, createdItem);
|
|
890
|
+
console.log(`🍬 ${modelName} upsert(create) success:`, data.id);
|
|
891
|
+
return createdItem;
|
|
892
|
+
}
|
|
893
|
+
else {
|
|
894
|
+
console.warn(`🍬 ${modelName} upsert(create) no API response. Keeping optimistic update data.`);
|
|
895
|
+
const singleItemQueryKey = [modelName, data.id];
|
|
896
|
+
return (previousDataMap.get(singleItemQueryKey) ||
|
|
897
|
+
null);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
catch (apiError) {
|
|
902
|
+
// Rollback and log error on API error
|
|
903
|
+
console.error(`🍬 ${modelName} upsert error, performing rollback:`, apiError);
|
|
904
|
+
rollbackCache(query_1.queryClient, previousDataMap);
|
|
905
|
+
throw apiError;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
catch (error) {
|
|
909
|
+
console.error(`🍬 ${modelName} final upsert error:`, error);
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
}),
|
|
913
|
+
// Index-based query (added method)
|
|
914
|
+
customList: (queryName_1, args_1, ...args_2) => __awaiter(this, [queryName_1, args_1, ...args_2], void 0, function* (queryName, args, options = { forceRefresh: false }) {
|
|
915
|
+
var _a;
|
|
916
|
+
try {
|
|
917
|
+
// Determine auth mode (use provided options if available)
|
|
918
|
+
const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode;
|
|
919
|
+
// Get owner and parameters based on auth mode
|
|
920
|
+
const { owner, authModeParams } = yield getOwnerByAuthMode(authMode);
|
|
921
|
+
// Add owner value to queries requiring owner field when userPool auth
|
|
922
|
+
const enhancedArgs = Object.assign({}, args);
|
|
923
|
+
if (owner &&
|
|
924
|
+
authMode === "userPool" &&
|
|
925
|
+
queryName.toLowerCase().includes("owner")) {
|
|
926
|
+
enhancedArgs.owner = owner;
|
|
927
|
+
}
|
|
928
|
+
// Detect relational query (if fields like dailyId, userId exist)
|
|
929
|
+
const relationField = Object.keys(enhancedArgs).find((key) => key.endsWith("Id"));
|
|
930
|
+
const isRelationalQuery = !!relationField;
|
|
931
|
+
const relationId = isRelationalQuery
|
|
932
|
+
? enhancedArgs[relationField]
|
|
933
|
+
: null;
|
|
934
|
+
const relationName = isRelationalQuery
|
|
935
|
+
? relationField
|
|
936
|
+
.replace(/Id$/, "") // 'dailyId' → 'daily'
|
|
937
|
+
.replace(/^./, (c) => c.toUpperCase()) // 'daily' → 'Daily'
|
|
938
|
+
: null;
|
|
939
|
+
// Create query key (include relation info for relational queries)
|
|
940
|
+
const queryKey = isRelationalQuery
|
|
941
|
+
? [
|
|
942
|
+
modelName,
|
|
943
|
+
relationName,
|
|
944
|
+
relationId,
|
|
945
|
+
"query",
|
|
946
|
+
queryName,
|
|
947
|
+
JSON.stringify(enhancedArgs),
|
|
948
|
+
]
|
|
949
|
+
: [modelName, "query", queryName, JSON.stringify(enhancedArgs)];
|
|
950
|
+
// Check cache first (if forceRefresh is false)
|
|
951
|
+
if (!options.forceRefresh) {
|
|
952
|
+
const cachedItems = query_1.queryClient.getQueryData(queryKey);
|
|
953
|
+
if (cachedItems && cachedItems.length > 0) {
|
|
954
|
+
console.log(`🍬 ${modelName} ${queryName} using cache`);
|
|
955
|
+
return cachedItems.filter((item) => item !== null);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
console.log(`🍬 ${modelName} customList call [Auth: ${authMode}]:`, queryName, enhancedArgs);
|
|
959
|
+
// Check if index query method exists
|
|
960
|
+
if (!((_a = (0, client_1.getClient)().models[modelName]) === null || _a === void 0 ? void 0 : _a[queryName])) {
|
|
961
|
+
throw new Error(`🍬 Query ${queryName} does not exist.`);
|
|
962
|
+
}
|
|
963
|
+
// Execute index query - apply auth mode
|
|
964
|
+
const { data: result } = yield (0, client_1.getClient)().models[modelName][queryName](enhancedArgs, authModeParams);
|
|
965
|
+
// Extract result data
|
|
966
|
+
const items = (result === null || result === void 0 ? void 0 : result.items) || (result === null || result === void 0 ? void 0 : result.data) || result || [];
|
|
967
|
+
console.log(`🍬 ${modelName} ${queryName} result:`, items.length, "items");
|
|
968
|
+
// Filter null values
|
|
969
|
+
const filteredItems = items.filter((item) => item !== null);
|
|
970
|
+
// Update cache
|
|
971
|
+
query_1.queryClient.setQueryData(queryKey, filteredItems);
|
|
972
|
+
// Update individual item cache
|
|
973
|
+
filteredItems.forEach((item) => {
|
|
974
|
+
query_1.queryClient.setQueryData([modelName, item.id], item);
|
|
975
|
+
});
|
|
976
|
+
return filteredItems;
|
|
977
|
+
}
|
|
978
|
+
catch (error) {
|
|
979
|
+
console.error(`🍬 ${modelName} ${queryName} error:`, error);
|
|
980
|
+
// Invalidate customList query cache on error
|
|
981
|
+
// For relational queries, only invalidate related query
|
|
982
|
+
if (typeof args === "object") {
|
|
983
|
+
const relationField = Object.keys(args).find((key) => key.endsWith("Id"));
|
|
984
|
+
if (relationField) {
|
|
985
|
+
const relationName = relationField
|
|
986
|
+
.replace(/Id$/, "")
|
|
987
|
+
.replace(/^./, (c) => c.toUpperCase());
|
|
988
|
+
const queryKey = [
|
|
989
|
+
modelName,
|
|
990
|
+
relationName,
|
|
991
|
+
args[relationField],
|
|
992
|
+
"query",
|
|
993
|
+
queryName,
|
|
994
|
+
JSON.stringify(args),
|
|
995
|
+
];
|
|
996
|
+
query_1.queryClient.invalidateQueries({ queryKey });
|
|
997
|
+
}
|
|
998
|
+
else {
|
|
999
|
+
const queryKey = [
|
|
1000
|
+
modelName,
|
|
1001
|
+
"query",
|
|
1002
|
+
queryName,
|
|
1003
|
+
JSON.stringify(args),
|
|
1004
|
+
];
|
|
1005
|
+
query_1.queryClient.invalidateQueries({ queryKey });
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
return [];
|
|
1009
|
+
}
|
|
1010
|
+
}),
|
|
1011
|
+
// Load from cache - Not directly implemented in TanStack Query
|
|
1012
|
+
loadFromCache: () => {
|
|
1013
|
+
// Note: This function is not directly supported in TanStack Query
|
|
1014
|
+
// If needed, connect to Zustand store implementation here
|
|
1015
|
+
console.warn(`🍬 ${modelName}.getStore() is not directly supported in TanStack Query.`);
|
|
1016
|
+
// Return empty object temporarily or throw error
|
|
1017
|
+
return {}; // or throw new Error("getStore is not supported in TanStack Query implementation");
|
|
1018
|
+
},
|
|
1019
|
+
// Cache reset - Using invalidateQueries in TanStack Query
|
|
1020
|
+
resetCache: () => {
|
|
1021
|
+
// Invalidate all queries related to this model
|
|
1022
|
+
query_1.queryClient.invalidateQueries({ queryKey: [modelName] });
|
|
1023
|
+
},
|
|
1024
|
+
// React Hook returning method - Reimplemented based on TanStack Query
|
|
1025
|
+
useHook: (options) => {
|
|
1026
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1027
|
+
const hookQueryClient = (0, react_query_1.useQueryClient)();
|
|
1028
|
+
// Determine query key
|
|
1029
|
+
const queryKey = (0, react_1.useMemo)(() => {
|
|
1030
|
+
var _a;
|
|
1031
|
+
if (options === null || options === void 0 ? void 0 : options.customList) {
|
|
1032
|
+
return [
|
|
1033
|
+
modelName,
|
|
1034
|
+
"query",
|
|
1035
|
+
options.customList.queryName,
|
|
1036
|
+
JSON.stringify(options.customList.args),
|
|
1037
|
+
];
|
|
1038
|
+
}
|
|
1039
|
+
if ((_a = options === null || options === void 0 ? void 0 : options.initialFetchOptions) === null || _a === void 0 ? void 0 : _a.filter) {
|
|
1040
|
+
return [
|
|
1041
|
+
modelName,
|
|
1042
|
+
"filter",
|
|
1043
|
+
JSON.stringify(options.initialFetchOptions.filter),
|
|
1044
|
+
];
|
|
1045
|
+
}
|
|
1046
|
+
return [modelName];
|
|
1047
|
+
}, [
|
|
1048
|
+
modelName,
|
|
1049
|
+
(_a = options === null || options === void 0 ? void 0 : options.initialFetchOptions) === null || _a === void 0 ? void 0 : _a.filter,
|
|
1050
|
+
(_b = options === null || options === void 0 ? void 0 : options.customList) === null || _b === void 0 ? void 0 : _b.queryName,
|
|
1051
|
+
(_c = options === null || options === void 0 ? void 0 : options.customList) === null || _c === void 0 ? void 0 : _c.args,
|
|
1052
|
+
]);
|
|
1053
|
+
// Determine query function
|
|
1054
|
+
const queryFn = (0, react_1.useCallback)((context) => __awaiter(this, void 0, void 0, function* () {
|
|
1055
|
+
var _a;
|
|
1056
|
+
if (options === null || options === void 0 ? void 0 : options.customList) {
|
|
1057
|
+
console.log(`🍬 ${modelName} useHook customList call:`, options.customList.queryName, options.customList.args, options.customList.forceRefresh);
|
|
1058
|
+
return service.customList(options.customList.queryName, options.customList.args, { forceRefresh: options.customList.forceRefresh });
|
|
1059
|
+
}
|
|
1060
|
+
if ((_a = options === null || options === void 0 ? void 0 : options.initialFetchOptions) === null || _a === void 0 ? void 0 : _a.filter) {
|
|
1061
|
+
return service.list({
|
|
1062
|
+
filter: options.initialFetchOptions.filter,
|
|
1063
|
+
forceRefresh: true,
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
return service.list();
|
|
1067
|
+
}), [
|
|
1068
|
+
(_d = options === null || options === void 0 ? void 0 : options.initialFetchOptions) === null || _d === void 0 ? void 0 : _d.filter,
|
|
1069
|
+
(_e = options === null || options === void 0 ? void 0 : options.customList) === null || _e === void 0 ? void 0 : _e.queryName,
|
|
1070
|
+
(_f = options === null || options === void 0 ? void 0 : options.customList) === null || _f === void 0 ? void 0 : _f.args,
|
|
1071
|
+
(_g = options === null || options === void 0 ? void 0 : options.customList) === null || _g === void 0 ? void 0 : _g.forceRefresh,
|
|
1072
|
+
service,
|
|
1073
|
+
]);
|
|
1074
|
+
const queryOptions = {
|
|
1075
|
+
queryKey,
|
|
1076
|
+
queryFn,
|
|
1077
|
+
enabled: ((_h = options === null || options === void 0 ? void 0 : options.initialFetchOptions) === null || _h === void 0 ? void 0 : _h.fetch) !== false,
|
|
1078
|
+
staleTime: 1000 * 30, // Keep fresh for 30 seconds (refresh more frequently)
|
|
1079
|
+
refetchOnMount: true, // Refetch on component mount
|
|
1080
|
+
refetchOnWindowFocus: false, // Don't auto-refetch on window focus
|
|
1081
|
+
refetchOnReconnect: true, // Refetch on network reconnect
|
|
1082
|
+
};
|
|
1083
|
+
const { data: items = [], isLoading, error, refetch, } = (0, react_query_1.useQuery)(queryOptions);
|
|
1084
|
+
// Interface functions implementation
|
|
1085
|
+
const getItem = (0, react_1.useCallback)((id) => {
|
|
1086
|
+
// Use useQueryData to get latest single item from current cache
|
|
1087
|
+
return hookQueryClient.getQueryData([modelName, id]);
|
|
1088
|
+
}, [hookQueryClient, modelName]);
|
|
1089
|
+
const createItem = (0, react_1.useCallback)((data) => __awaiter(this, void 0, void 0, function* () {
|
|
1090
|
+
try {
|
|
1091
|
+
const result = yield service.create(data);
|
|
1092
|
+
// Automatically refresh list after successful create
|
|
1093
|
+
yield refetch();
|
|
1094
|
+
return result;
|
|
1095
|
+
}
|
|
1096
|
+
catch (_error) {
|
|
1097
|
+
console.error(`🍬 ${modelName} useHook create error:`, _error);
|
|
1098
|
+
throw _error; // Re-throw error
|
|
1099
|
+
}
|
|
1100
|
+
}), [service, refetch] // Add refetch dependency
|
|
1101
|
+
);
|
|
1102
|
+
const updateItem = (0, react_1.useCallback)((data) => __awaiter(this, void 0, void 0, function* () {
|
|
1103
|
+
try {
|
|
1104
|
+
const result = yield service.update(data);
|
|
1105
|
+
// Automatically refresh list after successful update
|
|
1106
|
+
yield refetch();
|
|
1107
|
+
return result;
|
|
1108
|
+
}
|
|
1109
|
+
catch (_error) {
|
|
1110
|
+
console.error(`🍬 ${modelName} useHook update error:`, _error);
|
|
1111
|
+
throw _error;
|
|
1112
|
+
}
|
|
1113
|
+
}), [service, refetch] // Add refetch dependency
|
|
1114
|
+
);
|
|
1115
|
+
const deleteItem = (0, react_1.useCallback)((id) => __awaiter(this, void 0, void 0, function* () {
|
|
1116
|
+
try {
|
|
1117
|
+
const result = yield service.delete(id);
|
|
1118
|
+
// Automatically refresh list after successful delete
|
|
1119
|
+
yield refetch();
|
|
1120
|
+
return result;
|
|
1121
|
+
}
|
|
1122
|
+
catch (error) {
|
|
1123
|
+
console.error(`🍬 ${modelName} useHook delete error:`, error);
|
|
1124
|
+
throw error;
|
|
1125
|
+
}
|
|
1126
|
+
}), [service, refetch] // refetch dependency added
|
|
1127
|
+
);
|
|
1128
|
+
const refresh = (0, react_1.useCallback)((refreshOptions) => __awaiter(this, void 0, void 0, function* () {
|
|
1129
|
+
console.log(`🍬 ${modelName} useHook refresh called`, queryKey);
|
|
1130
|
+
const { data } = yield refetch({ throwOnError: true }); // Throw on error
|
|
1131
|
+
return data || [];
|
|
1132
|
+
}), [refetch, queryKey]);
|
|
1133
|
+
const customListFn = (0, react_1.useCallback)((queryName, args, options) => __awaiter(this, void 0, void 0, function* () {
|
|
1134
|
+
try {
|
|
1135
|
+
const result = yield service.customList(queryName, args, options);
|
|
1136
|
+
return result;
|
|
1137
|
+
}
|
|
1138
|
+
catch (error) {
|
|
1139
|
+
console.error(`🍬 ${modelName} useHook customList error:`, error);
|
|
1140
|
+
throw error;
|
|
1141
|
+
}
|
|
1142
|
+
}), [service]);
|
|
1143
|
+
return {
|
|
1144
|
+
items,
|
|
1145
|
+
isLoading,
|
|
1146
|
+
error: error,
|
|
1147
|
+
getItem,
|
|
1148
|
+
refresh,
|
|
1149
|
+
create: createItem,
|
|
1150
|
+
update: updateItem,
|
|
1151
|
+
delete: deleteItem,
|
|
1152
|
+
customList: customListFn,
|
|
1153
|
+
};
|
|
1154
|
+
},
|
|
1155
|
+
// Hook for managing single item - Reimplemented based on TanStack Query
|
|
1156
|
+
useItemHook: (id) => {
|
|
1157
|
+
const hookQueryClient = (0, react_query_1.useQueryClient)();
|
|
1158
|
+
const singleItemQueryKey = [modelName, id];
|
|
1159
|
+
// First check data from cache
|
|
1160
|
+
const cachedData = hookQueryClient.getQueryData(singleItemQueryKey);
|
|
1161
|
+
// Single item query
|
|
1162
|
+
const { data: item, isLoading, error, refetch, } = (0, react_query_1.useQuery)({
|
|
1163
|
+
queryKey: singleItemQueryKey,
|
|
1164
|
+
queryFn: () => service.get(id),
|
|
1165
|
+
initialData: cachedData, // Use cached data as initial value if available
|
|
1166
|
+
staleTime: 1000 * 60, // Keep data "fresh" for 1 minute
|
|
1167
|
+
refetchOnMount: "always", // Always attempt to refetch on mount
|
|
1168
|
+
enabled: !!id, // Only enable query when id exists
|
|
1169
|
+
});
|
|
1170
|
+
// useMutation hooks call service methods,
|
|
1171
|
+
// Service methods handle optimistic updates and cache updates/rollbacks internally.
|
|
1172
|
+
// Mutations inside useHook only serve to call service methods.
|
|
1173
|
+
const updateMutation = (0, react_query_1.useMutation)({
|
|
1174
|
+
mutationFn: (data) => service.update(data),
|
|
1175
|
+
// onSuccess, onMutate, onError logic moved to service method
|
|
1176
|
+
});
|
|
1177
|
+
const deleteMutation = (0, react_query_1.useMutation)({
|
|
1178
|
+
mutationFn: () => service.delete(id),
|
|
1179
|
+
// onSuccess, onMutate, onError logic moved to service method
|
|
1180
|
+
});
|
|
1181
|
+
// Interface function implementations
|
|
1182
|
+
const refreshItem = (0, react_1.useCallback)(() => __awaiter(this, void 0, void 0, function* () {
|
|
1183
|
+
console.log(`🍬 ${modelName} useItemHook refresh called`, singleItemQueryKey);
|
|
1184
|
+
const { data } = yield refetch({ throwOnError: true }); // Throw on error
|
|
1185
|
+
return data || null;
|
|
1186
|
+
}), [refetch, singleItemQueryKey]); // Added queryKey dependency
|
|
1187
|
+
const updateItem = (0, react_1.useCallback)((data) => __awaiter(this, void 0, void 0, function* () {
|
|
1188
|
+
// No additional work needed here as cache is already updated in service.update
|
|
1189
|
+
try {
|
|
1190
|
+
// Explicitly add id to resolve type error
|
|
1191
|
+
const updateData = Object.assign(Object.assign({}, data), { id });
|
|
1192
|
+
const result = yield updateMutation.mutateAsync(updateData);
|
|
1193
|
+
return result;
|
|
1194
|
+
}
|
|
1195
|
+
catch (error) {
|
|
1196
|
+
console.error(`🍬 ${modelName} useItemHook update error:`, error);
|
|
1197
|
+
throw error;
|
|
1198
|
+
}
|
|
1199
|
+
}), [updateMutation, id]);
|
|
1200
|
+
const deleteItem = (0, react_1.useCallback)(() => __awaiter(this, void 0, void 0, function* () {
|
|
1201
|
+
try {
|
|
1202
|
+
const result = yield deleteMutation.mutateAsync();
|
|
1203
|
+
return result;
|
|
1204
|
+
}
|
|
1205
|
+
catch (error) {
|
|
1206
|
+
console.error(`🍬 ${modelName} useItemHook delete error:`, error);
|
|
1207
|
+
throw error;
|
|
1208
|
+
}
|
|
1209
|
+
}), [deleteMutation]);
|
|
1210
|
+
// Change loading state to false when isLoading is true and cached data exists
|
|
1211
|
+
const effectiveLoading = isLoading && !cachedData;
|
|
1212
|
+
return {
|
|
1213
|
+
item: item || null,
|
|
1214
|
+
isLoading: effectiveLoading, // Not loading if cached data exists
|
|
1215
|
+
error: error, // Explicitly specify error type
|
|
1216
|
+
refresh: refreshItem,
|
|
1217
|
+
update: updateItem,
|
|
1218
|
+
delete: deleteItem,
|
|
1219
|
+
};
|
|
1220
|
+
},
|
|
1221
|
+
// Method to add model-specific extension features
|
|
1222
|
+
withExtensions: (extensions) => {
|
|
1223
|
+
// Add extension features to existing service object
|
|
1224
|
+
return Object.assign(Object.assign({}, service), extensions);
|
|
1225
|
+
},
|
|
1226
|
+
// Add TanStack Query Store access method (use when needed)
|
|
1227
|
+
getStore: () => {
|
|
1228
|
+
// TanStack Query doesn't directly expose Zustand store
|
|
1229
|
+
// Need to connect Zustand store implementation here if needed
|
|
1230
|
+
console.warn(`🍬 ${modelName}.getStore() is not directly supported in TanStack Query.`);
|
|
1231
|
+
// Temporarily return empty object or throw error
|
|
1232
|
+
return {}; // Or throw new Error("getStore is not supported in TanStack Query implementation");
|
|
1233
|
+
},
|
|
1234
|
+
};
|
|
1235
|
+
return service;
|
|
1236
|
+
}
|