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.
@@ -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
+ }