UNPKG

amplifyquery

Version:
864 lines β€’ 79.2 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createAmplifyService = createAmplifyService; const client_1 = require("./client"); const config_1 = require("./config"); const query_1 = require("./query"); const utils_1 = require("./utils"); const react_query_1 = require("@tanstack/react-query"); const auth_1 = require("aws-amplify/auth"); const expo_crypto_1 = require("expo-crypto"); const react_1 = require("react"); /** * Utility function to get owner value based on authentication mode * Sets owner value only in userPool auth mode, returns empty string for other auth modes * * @param authMode Current authentication mode * @returns Owner value and API call parameters based on auth mode */ function getOwnerByAuthMode(authMode) { return __awaiter(this, void 0, void 0, function* () { let owner = ""; // Set owner value only in userPool auth mode if (authMode === "userPool") { try { const { username, userId } = yield (0, auth_1.getCurrentUser)(); owner = userId + "::" + username; } catch (error) { console.error("Error getting user authentication info:", error); // Continue even if error occurs (API call will fail) } } // Return with auth mode parameters return { owner, authModeParams: { authMode }, }; }); } /** * Find all related query keys * @param modelName Model name * @param queryClient TanStack QueryClient instance * @returns Array of related query keys (readonly unknown[] type) */ function findRelatedQueryKeys(modelName, queryClient) { // Extract only query keys from Query objects array return queryClient .getQueryCache() .findAll({ predicate: ({ queryKey }) => { // Find all query keys starting with model name // Example: ["Mission"], ["Mission", "filter", ...], ["Daily", dailyId, "Mission"] return Array.isArray(queryKey) && queryKey[0] === modelName; }, }) .map((query) => query.queryKey); } /** * Helper function to handle optimistic updates and cache updates for a single item * @param queryClient TanStack QueryClient instance * @param modelName Model name * @param relatedQueryKeys Array of related query keys to update * @param itemId ID of item to update * @param updateData Data to use for optimistic update (includes id) * @returns Previous cache data (for rollback) - using QueryKey as key */ function performOptimisticUpdate(queryClient, modelName, relatedQueryKeys, itemId, updateData) { return __awaiter(this, void 0, void 0, function* () { const previousDataMap = new Map(); // πŸ”§ 버그 μˆ˜μ •: updateData의 ID와 μš”μ²­ν•œ IDκ°€ μΌμΉ˜ν•˜λŠ”μ§€ 검증 const updateDataId = updateData === null || updateData === void 0 ? void 0 : updateData.id; if (!updateDataId || typeof updateDataId !== "string" || updateDataId !== itemId) { console.warn(`🍬 ${modelName} performOptimisticUpdate: ID mismatch! Expected: ${itemId}, UpdateData ID: ${updateDataId}. Skipping optimistic update.`); return previousDataMap; // 빈 λ§΅ λ°˜ν™˜μœΌλ‘œ rollback μ‹œ 영ν–₯ μ—†μŒ } // 1. Update individual item cache const singleItemQueryKey = [modelName, itemId]; const previousItemSingle = queryClient.getQueryData(singleItemQueryKey); previousDataMap.set(singleItemQueryKey, previousItemSingle); // Merge with existing data if available, otherwise use updateData as optimistic data const optimisticData = previousItemSingle ? Object.assign(Object.assign({}, previousItemSingle), updateData) : updateData; // updateData includes at least id here queryClient.setQueryData(singleItemQueryKey, optimisticData); // 2. Update list queries relatedQueryKeys.forEach((queryKey) => { // Process only list query keys since single item key is handled above if (queryKey.length > 1 && queryKey[1] !== itemId) { const previousItems = queryClient.getQueryData(queryKey); previousDataMap.set(queryKey, previousItems); // Backup previous data queryClient.setQueryData(queryKey, (oldData) => { // Safely handle if oldData is null, undefined or not an array const oldItems = Array.isArray(oldData) ? oldData : []; const hasItem = oldItems.some((item) => item && item.id === itemId); if (hasItem) { // Update if existing item found return oldItems.map((item) => item && item.id === itemId ? Object.assign(Object.assign({}, item), updateData) : item); } else if (optimisticData && queryKey.length < 3) { // Only consider adding created item for top-level list queries // Add if no existing item and optimistic update data available (for create/upsert) return [...oldItems, optimisticData]; } return oldItems; // No changes }); } }); return previousDataMap; }); } /** * Handle cache updates after API call success * @param queryClient TanStack QueryClient instance * @param modelName Model name * @param relatedQueryKeys Array of related query keys to update * @param itemId ID of updated item * @param updatedItem Latest item data from API response */ function handleCacheUpdateOnSuccess(queryClient, modelName, relatedQueryKeys, itemId, updatedItem) { // 1. Update individual item cache const actualItemId = updatedItem === null || updatedItem === void 0 ? void 0 : updatedItem.id; // πŸ”§ 버그 μˆ˜μ •: μ‹€μ œ μ•„μ΄ν…œ ID와 μš”μ²­ν•œ IDκ°€ μΌμΉ˜ν•˜λŠ”μ§€ 검증 if (actualItemId && typeof actualItemId === "string" && actualItemId === itemId) { queryClient.setQueryData([modelName, itemId], updatedItem); } else { console.warn(`🍬 ${modelName} handleCacheUpdateOnSuccess: ID mismatch! Expected: ${itemId}, Actual: ${actualItemId}. Skipping cache update.`); return; // IDκ°€ μΌμΉ˜ν•˜μ§€ μ•ŠμœΌλ©΄ μΊμ‹œ μ—…λ°μ΄νŠΈ 쀑단 } // 2. Update list query cache (with relational filtering applied) relatedQueryKeys.forEach((queryKey) => { // Check if relational query - e.g. ["Mission", "Daily", "daily-id", ...] const isRelationalQuery = queryKey.length > 3 && typeof queryKey[1] === "string" && typeof queryKey[2] === "string"; // Check if relational query - e.g. ["Mission", "Daily", "daily-id", ...] if (isRelationalQuery) { const relationName = queryKey[1]; // "Daily", "User" λ“± const relationId = queryKey[2]; // μ‹€μ œ 관계 ID const relationField = `${relationName.toLowerCase()}Id`; // "dailyId", "userId" λ“± // Check if updated item belongs to this relation ID const belongsToRelation = updatedItem[relationField] === relationId; if (!belongsToRelation) { // Skip cache update if item does not belong to this relation ID return; } } // Update query cache queryClient.setQueryData(queryKey, (oldData) => { const oldItems = Array.isArray(oldData) ? oldData : []; const hasItem = oldItems.some((item) => item && item.id === itemId); if (hasItem) { // Update if existing item found return oldItems.map((item) => item && item.id === itemId ? updatedItem : item); } else { // Add if successfully created (already filtered in relational queries) return [...oldItems, updatedItem]; } }); }); } /** * Rollback cache on error * @param queryClient TanStack QueryClient instance * @param previousDataMap Map containing previous cache data */ function rollbackCache(queryClient, previousDataMap) { previousDataMap.forEach((previousData, queryKey) => { queryClient.setQueryData(queryKey, previousData); }); } /** * Create model-specific Amplify service * @param modelName Model name * @param defaultAuthMode Default authentication mode (optional, uses global config if not provided) * @returns AmplifyDataService instance for the model */ function createAmplifyService(modelName, defaultAuthMode) { // Track current authentication mode state - use global config if not provided let currentAuthMode = defaultAuthMode || (0, config_1.getDefaultAuthMode)(); // Create service object const service = { // Add model name (needed for singleton services) modelName, // Set authentication mode method setAuthMode: (authMode) => { currentAuthMode = authMode; console.log(`πŸ” ${modelName} service auth mode changed: ${authMode}`); }, // Get current authentication mode getAuthMode: () => { return currentAuthMode; }, // Set authentication mode chaining method withAuthMode: (authMode) => { const clonedService = Object.assign({}, service); clonedService.setAuthMode(authMode); return clonedService; }, // Create item create: (data, options) => __awaiter(this, void 0, void 0, function* () { try { if (!data) { console.error(`🍬 ${modelName} creation error: data is null`); return null; } // Determine auth mode (use provided option if available) const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode; // Get owner and parameters based on auth mode const { authModeParams } = yield getOwnerByAuthMode(authMode); const dataWithoutOwner = utils_1.Utils.removeOwnerField(data, "create"); const cleanedData = {}; Object.entries(dataWithoutOwner).forEach(([key, value]) => { if (value !== undefined) { cleanedData[key] = value; } }); const newItem = Object.assign(Object.assign({}, cleanedData), { id: cleanedData.id || (0, expo_crypto_1.randomUUID)() }); // Extract relation fields (e.g., dailyId, userId) const relationFields = new Map(); for (const [key, value] of Object.entries(newItem)) { if (key.endsWith("Id") && typeof value === "string" && value) { // Relation field name and its value relationFields.set(key, value); } } // Find related query keys (all keys starting with model name) const relatedQueryKeys = findRelatedQueryKeys(modelName, query_1.queryClient); // Backup previous data for optimistic update const previousDataMap = new Map(); // Update individual item cache const singleItemQueryKey = [modelName, newItem.id]; const previousItemSingle = query_1.queryClient.getQueryData(singleItemQueryKey); previousDataMap.set(singleItemQueryKey, previousItemSingle); query_1.queryClient.setQueryData(singleItemQueryKey, newItem); // Update list queries (with relation filtering) relatedQueryKeys.forEach((queryKey) => { // Check if it's a relational query (e.g., ["Mission", "Daily", "daily-id", ...]) const isRelationalQuery = queryKey.length > 3 && typeof queryKey[1] === "string" && typeof queryKey[2] === "string"; if (isRelationalQuery) { // For relational queries, only add to cache if it belongs to the relation const relationName = queryKey[1]; // "Daily", "User" etc. const relationId = queryKey[2]; // Actual relation ID value const relationField = `${relationName.toLowerCase()}Id`; // "dailyId", "userId" etc. // Check if new item belongs to this relation ID const belongsToRelation = newItem[relationField] === relationId; if (belongsToRelation) { // Only update cache if it belongs to this relation const data = query_1.queryClient.getQueryData(queryKey); if (data) { previousDataMap.set(queryKey, data); } query_1.queryClient.setQueryData(queryKey, (oldData) => { const oldItems = Array.isArray(oldData) ? oldData : []; return [...oldItems, newItem]; }); } } else if (queryKey.length < 3) { // Regular list query const data = query_1.queryClient.getQueryData(queryKey); if (data) { previousDataMap.set(queryKey, data); } query_1.queryClient.setQueryData(queryKey, (oldData) => { const oldItems = Array.isArray(oldData) ? oldData : []; return [...oldItems, newItem]; }); } }); try { // Attempt API call - apply auth mode console.log(`🍬 ${modelName} creation attempt [Auth: ${authMode}]:`, newItem.id); const { data: createdItem } = yield (0, client_1.getClient)().models[modelName].create(newItem, authModeParams); if (createdItem) { // Update cache on API success query_1.queryClient.setQueryData(singleItemQueryKey, createdItem); // Invalidate queries based on relation fields for (const [field, value] of relationFields) { // Extract relation name from field name (e.g., "dailyId" -> "Daily") const relationName = field .replace(/Id$/, "") .replace(/^./, (c) => c.toUpperCase()); // Invalidate queries for this relation query_1.queryClient.invalidateQueries({ queryKey: [modelName, relationName, value], refetchType: "active", }); } // Invalidate all related queries query_1.queryClient.invalidateQueries({ queryKey: [modelName], refetchType: "active", }); console.log(`🍬 ${modelName} creation successful:`, newItem.id); return createdItem; } // Keep optimistic update data even if no API response console.warn(`🍬 ${modelName} creation API no response. Keeping optimistic update data.`); return newItem; } catch (apiError) { // Rollback and log error on API failure console.error(`🍬 ${modelName} creation error, performing rollback:`, apiError); rollbackCache(query_1.queryClient, previousDataMap); // Re-throw error for caller to handle throw apiError; } } catch (error) { console.error(`🍬 ${modelName} final creation error:`, error); return null; } }), // Batch create items createList: (dataList, options) => __awaiter(this, void 0, void 0, function* () { try { if (!dataList || dataList.length === 0) { return []; } // Determine auth mode (use provided option if available) const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode; // Get owner and parameters based on auth mode const { authModeParams } = yield getOwnerByAuthMode(authMode); const preparedItems = dataList .map((data) => { if (!data) return null; const dataWithoutOwner = utils_1.Utils.removeOwnerField(data, "create"); const cleanedData = {}; Object.entries(dataWithoutOwner).forEach(([key, value]) => { if (value !== undefined) { cleanedData[key] = value; } }); return Object.assign(Object.assign({}, cleanedData), { id: cleanedData.id || (0, expo_crypto_1.randomUUID)() }); }) .filter(Boolean); const relatedQueryKeys = findRelatedQueryKeys(modelName, query_1.queryClient); const previousDataMap = new Map(); // Extract relation properties (e.g., dailyId) const relationFields = new Map(); for (const item of preparedItems) { for (const [key, value] of Object.entries(item)) { if (key.endsWith("Id") && typeof value === "string" && value) { // Relation field name and its value relationFields.set(key, value); } } } console.log(`🍬 ${modelName} batch creation attempt: ${preparedItems.length} items`); // Batch optimistic update - with relation filtering relatedQueryKeys.forEach((queryKey) => { // Check if this query key is a relational query (e.g., ["Mission", "Daily", "daily-id", ...]) const isRelationalQuery = queryKey.length > 3 && typeof queryKey[1] === "string" && typeof queryKey[2] === "string"; const previousItems = query_1.queryClient.getQueryData(queryKey); previousDataMap.set(queryKey, previousItems); // Backup previous data if (isRelationalQuery) { // For relational queries, only add items that belong to the relation const relationName = queryKey[1]; // "Daily", "User" etc. const relationId = queryKey[2]; // Actual relation ID value const relationField = `${relationName.toLowerCase()}Id`; // "dailyId", "userId" etc. query_1.queryClient.setQueryData(queryKey, (oldData) => { const oldItems = Array.isArray(oldData) ? oldData : []; // Filter new items that belong to this relation ID const itemsToAdd = preparedItems.filter((newItem) => newItem[relationField] === relationId); // Merge existing items with filtered new items if (itemsToAdd.length > 0) { return [...oldItems, ...itemsToAdd]; } return oldItems; // No change if no items match relation ID }); } else if (queryKey.length < 3) { // Regular list query - add all items query_1.queryClient.setQueryData(queryKey, (oldData) => { const oldItems = Array.isArray(oldData) ? oldData : []; return [...oldItems, ...preparedItems]; }); } }); // Update individual item caches preparedItems.forEach((item) => { const singleItemQueryKey = [modelName, item.id]; const previousItemSingle = query_1.queryClient.getQueryData(singleItemQueryKey); previousDataMap.set(singleItemQueryKey, previousItemSingle); query_1.queryClient.setQueryData(singleItemQueryKey, item); }); try { // Parallel API calls - apply auth mode const createPromises = preparedItems.map((newItem) => __awaiter(this, void 0, void 0, function* () { try { const { data: createdItem } = yield (0, client_1.getClient)().models[modelName].create(newItem, authModeParams); // Update individual item cache on API success if (createdItem) { const itemId = createdItem === null || createdItem === void 0 ? void 0 : createdItem.id; // πŸ”§ 버그 μˆ˜μ •: IDκ°€ μœ νš¨ν•œ κ²½μš°μ—λ§Œ κ°œλ³„ μΊμ‹œμ— μ €μž₯ if (itemId && typeof itemId === "string") { query_1.queryClient.setQueryData([modelName, itemId], createdItem); } else { console.warn(`🍬 ${modelName} createList: Invalid createdItem ID found, skipping cache update:`, itemId, createdItem); } } return createdItem || newItem; } catch (error) { console.error(`🍬 ${modelName} batch creation failed for ID ${newItem.id}:`, error); return newItem; } })); const results = yield Promise.all(createPromises); console.log(`🍬 ${modelName} batch creation completed: ${results.length} items`); // After creation, invalidate all related queries to ensure exact server data // This is important as it prevents side effects of optimistic updates by invalidating related queries for (const [field, value] of relationFields) { // Extract relation name from field name (e.g., "dailyId" -> "Daily") const relationName = field .replace(/Id$/, "") .replace(/^./, (c) => c.toUpperCase()); // Invalidate query for this relation query_1.queryClient.invalidateQueries({ queryKey: [modelName, relationName, value], refetchType: "active", }); // Also invalidate general queries query_1.queryClient.invalidateQueries({ queryKey: [modelName], refetchType: "active", }); } return results; } catch (apiError) { console.error(`🍬 ${modelName} batch creation API error, performing rollback:`, apiError); rollbackCache(query_1.queryClient, previousDataMap); throw apiError; } } catch (error) { console.error(`🍬 ${modelName} final batch creation error:`, error); return []; } }), // Get item get: (id_1, ...args_1) => __awaiter(this, [id_1, ...args_1], void 0, function* (id, options = { forceRefresh: false }) { try { const singleItemQueryKey = [modelName, id]; // Check cache first (if forceRefresh is false) if (!options.forceRefresh) { const cachedItem = query_1.queryClient.getQueryData(singleItemQueryKey); if (cachedItem) { // πŸ”§ 버그 μˆ˜μ •: μΊμ‹œλœ μ•„μ΄ν…œμ˜ μ‹€μ œ IDκ°€ μš”μ²­ν•œ ID와 μΌμΉ˜ν•˜λŠ”μ§€ 검증 const itemId = cachedItem === null || cachedItem === void 0 ? void 0 : cachedItem.id; if (itemId === id) { return cachedItem; } else { // IDκ°€ μΌμΉ˜ν•˜μ§€ μ•ŠμœΌλ©΄ μΊμ‹œμ—μ„œ μ œκ±°ν•˜κ³  API 호좜 console.warn(`🍬 ${modelName} get: Cache ID mismatch! Requested: ${id}, Cached: ${itemId}. Removing invalid cache and fetching from API.`); query_1.queryClient.removeQueries({ queryKey: singleItemQueryKey }); } } } // Determine auth mode (use provided option if available) const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode; // Get parameters based on auth mode const { authModeParams } = yield getOwnerByAuthMode(authMode); // API call - apply auth mode const { data: apiResponse } = yield (0, client_1.getClient)().models[modelName].get({ id }, authModeParams); // Handle case where API returns array instead of single item let item = apiResponse; if (Array.isArray(apiResponse)) { console.warn(`🍬 ${modelName} get: API returned array instead of single item. Taking first item.`); item = apiResponse.find((i) => (i === null || i === void 0 ? void 0 : i.id) === id) || apiResponse[0] || null; } // Update cache if (item) { const itemId = item === null || item === void 0 ? void 0 : item.id; // πŸ”§ 버그 μˆ˜μ •: API 응닡 μ•„μ΄ν…œμ˜ IDκ°€ μš”μ²­ν•œ ID와 μΌμΉ˜ν•˜λŠ”μ§€ 검증 if (itemId && typeof itemId === "string" && itemId === id) { query_1.queryClient.setQueryData(singleItemQueryKey, item); // Update related list queries (lists that might contain this item) const relatedQueryKeys = findRelatedQueryKeys(modelName, query_1.queryClient); relatedQueryKeys.forEach((queryKey) => { // Exclude single item keys, only process list queries if (queryKey.length > 1 && queryKey[1] !== id) { // Query keys with id as second element are not single item get query key format, so treat as list query_1.queryClient.setQueryData(queryKey, (oldData) => { const oldItems = Array.isArray(oldData) ? oldData : []; const exists = oldItems.some((oldItem) => oldItem && oldItem.id === id); if (exists) { return oldItems.map((oldItem) => oldItem && oldItem.id === id ? item : oldItem); } else { // Need to check if item matches list query filter conditions (not checking here) // If checking is difficult, invalidateQueries might be safer // Currently implemented to add item to all related lists that might contain it on API get success return [...oldItems, item]; } }); } }); } else { console.warn(`🍬 ${modelName} get: API response ID mismatch! Requested: ${id}, API Response ID: ${itemId}. Skipping cache update.`); } } return item || null; } catch (error) { console.error(`🍬 ${modelName} get error:`, error); return null; } }), // Batch get items list: (...args_1) => __awaiter(this, [...args_1], void 0, function* (options = { filter: undefined, forceRefresh: false }) { var _a, _b, _c, _d; try { // Determine query key const queryKey = options.filter ? [modelName, "filter", JSON.stringify(options.filter)] : [modelName]; // Check cache first (if forceRefresh is false) if (!options.forceRefresh) { const cachedItems = query_1.queryClient.getQueryData(queryKey); if (cachedItems && cachedItems.length > 0) { console.log(`🍬 ${modelName} list using cache`, queryKey); return cachedItems.filter((item) => item !== null); } } // Determine auth mode (use provided option if available) const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode; // Get owner and parameters based on auth mode const { owner, authModeParams } = yield getOwnerByAuthMode(authMode); // Get owner-based query name from global config const ownerQueryName = (0, config_1.getOwnerQueryName)(modelName); // Try query call try { console.log(`🍬 ${modelName} list API call`, queryKey, `by ${ownerQueryName}`, `[Auth: ${authMode}]`); // Debug: Check if model and query exist const client = (0, client_1.getClient)(); console.log(`🍬 Debug - client.models exists:`, !!client.models); console.log(`🍬 Debug - client.models[${modelName}] exists:`, !!client.models[modelName]); console.log(`🍬 Debug - client.models[${modelName}][${ownerQueryName}] exists:`, !!((_a = client.models[modelName]) === null || _a === void 0 ? void 0 : _a[ownerQueryName])); console.log(`🍬 Debug - Available methods for ${modelName}:`, Object.keys(client.models[modelName] || {})); // Execute owner query const { data: result } = yield (0, client_1.getClient)().models[modelName][ownerQueryName]({ owner, authMode }, authModeParams); // Extract result data + filter null values 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); // Apply filter (if client-side filtering needed) let filteredItems = items; if (options.filter) { filteredItems = items.filter((item) => { return Object.entries(options.filter).every(([key, value]) => { if (typeof value === "object" && value !== null) { // Ensure type safety when accessing item with key const itemValue = item[key]; if ("eq" in value) return itemValue === value.eq; if ("ne" in value) return itemValue !== value.ne; if ("gt" in value) return itemValue > value.gt; if ("lt" in value) return itemValue < value.lt; if ("contains" in value) return String(itemValue).includes(value.contains); if ("between" in value && Array.isArray(value.between)) return (itemValue >= value.between[0] && itemValue <= value.between[1]); } // Ensure type safety when accessing item with key return item[key] === value; }); }); } // Update cache query_1.queryClient.setQueryData(queryKey, filteredItems); // Update individual item cache filteredItems.forEach((item) => { const itemId = item === null || item === void 0 ? void 0 : item.id; // πŸ”§ 버그 μˆ˜μ •: IDκ°€ μœ νš¨ν•œ κ²½μš°μ—λ§Œ κ°œλ³„ μΊμ‹œμ— μ €μž₯ if (itemId && typeof itemId === "string") { query_1.queryClient.setQueryData([modelName, itemId], item); } else { console.warn(`🍬 ${modelName} list: Invalid item ID found, skipping cache update:`, itemId, item); } }); return filteredItems; } catch (error) { // Check if the error is because the owner query doesn't exist if (((_b = error === null || error === void 0 ? void 0 : error.message) === null || _b === void 0 ? void 0 : _b.includes("not found")) || ((_c = error === null || error === void 0 ? void 0 : error.message) === null || _c === void 0 ? void 0 : _c.includes("is not a function")) || ((_d = error === null || error === void 0 ? void 0 : error.message) === null || _d === void 0 ? void 0 : _d.includes("is undefined"))) { console.warn(`🍬 ${ownerQueryName} query not found. Trying default list query...`); // Try default list query if owner query not found const { data: result } = yield (0, client_1.getClient)().models[modelName].list({}, authModeParams); // Extract and process result data 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); // Filter, cache update etc. remaining logic same let filteredItems = items; if (options.filter) { // Filtering logic (same as before) filteredItems = items.filter((item) => { return Object.entries(options.filter).every(([key, value]) => { if (typeof value === "object" && value !== null) { // Ensure type safety when accessing item with key const itemValue = item[key]; if ("eq" in value) return itemValue === value.eq; if ("ne" in value) return itemValue !== value.ne; if ("gt" in value) return itemValue > value.gt; if ("lt" in value) return itemValue < value.lt; if ("contains" in value) return String(itemValue).includes(value.contains); if ("between" in value && Array.isArray(value.between)) return (itemValue >= value.between[0] && itemValue <= value.between[1]); } // Ensure type safety when accessing item with key return item[key] === value; }); }); } query_1.queryClient.setQueryData(queryKey, filteredItems); filteredItems.forEach((item) => { const itemId = item === null || item === void 0 ? void 0 : item.id; // πŸ”§ 버그 μˆ˜μ •: IDκ°€ μœ νš¨ν•œ κ²½μš°μ—λ§Œ κ°œλ³„ μΊμ‹œμ— μ €μž₯ if (itemId && typeof itemId === "string") { query_1.queryClient.setQueryData([modelName, itemId], item); } else { console.warn(`🍬 ${modelName} list fallback: Invalid item ID found, skipping cache update:`, itemId, item); } }); return filteredItems; } throw error; // Pass other errors to upper catch } } catch (error) { console.log("🍬 error", error); console.error(`🍬 ${modelName} list error:`, error); // Invalidate list query cache on error const queryKey = [ modelName, "filter", JSON.stringify(options.filter), ]; query_1.queryClient.invalidateQueries({ queryKey }); return []; } }), // Update item (modified to use helper functions) update: (data, options) => __awaiter(this, void 0, void 0, function* () { try { if (!(data === null || data === void 0 ? void 0 : data.id)) { console.error(`🍬 ${modelName} update error: No valid data or id provided.`); return null; } // Determine auth mode (use provided option if available) const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode; // Get parameters based on auth mode const { authModeParams } = yield getOwnerByAuthMode(authMode); const dataWithoutOwner = utils_1.Utils.removeOwnerField(data, "update"); const cleanedData = {}; // Always include id, and only include other fields that are actually passed (not undefined) Object.entries(dataWithoutOwner).forEach(([key, value]) => { if (key === "id" || value !== undefined) { cleanedData[key] = value; } }); const { id: itemId } = cleanedData; // Get id from cleanedData // Find related query keys const relatedQueryKeys = findRelatedQueryKeys(modelName, query_1.queryClient); // Perform optimistic update const previousDataMap = yield performOptimisticUpdate(query_1.queryClient, modelName, relatedQueryKeys, itemId, cleanedData); try { // Attempt API call - apply auth mode console.log(`🍬 ${modelName} update attempt [Auth: ${authMode}]:`, itemId); const { data: updatedItem } = yield (0, client_1.getClient)().models[modelName].update(cleanedData, authModeParams); if (updatedItem) { // Update cache on API success handleCacheUpdateOnSuccess(query_1.queryClient, modelName, relatedQueryKeys, itemId, updatedItem); // Invalidate all related queries to automatically fetch new data query_1.queryClient.invalidateQueries({ queryKey: [modelName], refetchType: "active", }); console.log(`🍬 ${modelName} update success:`, itemId); return updatedItem; } else { console.warn(`🍬 ${modelName} update API response missing. Maintaining optimistic update data.`); // If no API response, return the data saved during optimistic update const singleItemQueryKey = [modelName, itemId]; return (previousDataMap.get(singleItemQueryKey) || null); } } catch (apiError) { // Rollback and log error on API failure console.error(`🍬 ${modelName} update error, performing rollback:`, apiError); rollbackCache(query_1.queryClient, previousDataMap); throw apiError; } } catch (error) { console.error(`🍬 ${modelName} final update error:`, error); return null; } }), // Delete item (modified to use helper functions) delete: (id, options) => __awaiter(this, void 0, void 0, function* () { try { if (!id) { console.error(`🍬 ${modelName} delete error: No valid id provided.`); return false; } // Determine auth mode (use provided option if available) const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode; // Get parameters based on auth mode const { authModeParams } = yield getOwnerByAuthMode(authMode); // Find related query keys const relatedQueryKeys = findRelatedQueryKeys(modelName, query_1.queryClient); // Backup item data before deletion (for rollback) const previousDataMap = new Map(); // Backup previous data and perform optimistic update for individual item (set to null) const singleItemQueryKey = [modelName, id]; const previousItemSingle = query_1.queryClient.getQueryData(singleItemQueryKey); previousDataMap.set(singleItemQueryKey, previousItemSingle); query_1.queryClient.setQueryData(singleItemQueryKey, null); // Backup and perform optimistic update for list queries (remove item) relatedQueryKeys.forEach((queryKey) => { if (queryKey.length > 1 && queryKey[1] !== id) { // Exclude single item keys const data = query_1.queryClient.getQueryData(queryKey); if (data) { previousDataMap.set(queryKey, data); } query_1.queryClient.setQueryData(queryKey, (oldData) => { const oldItems = Array.isArray(oldData) ? oldData : []; return oldItems.filter((item) => (item === null || item === void 0 ? void 0 : item.id) !== id); }); } }); try { // API call - apply auth mode console.log(`🍬 ${modelName} delete attempt [Auth: ${authMode}]:`, id); yield (0, client_1.getClient)().models[modelName].delete({ id }, authModeParams); console.log(`🍬 ${modelName} delete success:`, id); // On API success, invalidate all related queries to automatically refresh query_1.queryClient.invalidateQueries({ queryKey: [modelName], refetchType: "active", }); return true; } catch (error) { // Rollback on API error console.error(`🍬 ${modelName} delete API error, performing rollback:`, error); rollbackCache(query_1.queryClient, previousDataMap); throw error; } } catch (error) { console.error(`🍬 ${modelName} final delete error:`, error); return false; } }), // Batch delete multiple items (consider applying helper functions) deleteList: (ids, options) => __awaiter(this, void 0, void 0, function* () { try { const results = { success: [], failed: [], }; if (!ids || ids.length === 0) { console.warn(`🍬 ${modelName} batch delete: Empty ID array provided.`); return results; } // Determine auth mode (use provided option if available) const authMode = (options === null || options === void 0 ? void 0 : options.authMode) || currentAuthMode; // Get parameters based on auth mode const { authModeParams } = yield getOwnerByAuthMode(authMode); // Prepare related query keys and previous data map const relatedQueryKeys = findRelatedQueryKeys(modelName, query_1.queryClient); const previousDataMap = new Map(); const previousItemsCache = new Map(); // For individual item rollback // Optimistic update - remove items from all caches ids.forEach((id) => { // Backup previous data and perform optimistic update for individual item (null) const singleItemQueryKey = [modelName, id]; const previousItemSingle = query_1.queryClient.getQueryData(singleItemQueryKey); previousItemsCache.set(id, previousItemSingle || null); previousDataMap.set(singleItemQueryKey, previousItemSingle); // Backup to map query_1.queryClient.setQueryData(singleItemQueryKey, null); }); // Update all list query caches (remove items included in id list) relatedQueryKeys.forEach((queryKey) => { if (queryKey.length > 1 && !ids.includes(queryKey[1])) { // Exclude single item keys and list keys where id is second element (handled individually) const data = query_1.queryClient.getQueryData(queryKey); if (data) { previousDataMap.set(queryKey, data); } query_1.queryClient.setQueryData(queryKey, (oldData) => { const oldItems = Array.isArray(oldData) ? oldData : []; return oldItems.filter((item) => item && !ids.includes(item.id)); }); } }); try { console.log(`🍬 ${modelName} batch delete attempt [Auth: ${authMode}]: ${ids.length} items`); // Parallel API calls - apply auth mode const deletePromises = ids.map((id) => __awaiter(this, void 0, void 0, function* () { try { yield (0, client_1.getClient)().models[modelName].delete({ id }, authModeParams); results.success.push(id); return { id, success: true }; } catch (error) { console.error(`🍬 ${modelName} batch delete failed for ID ${id}:`, error); results.failed.push(id); return { id, success: false, error }; } })); yield Promise.all(deletePromises); // If there are failed items, rollback only those items if (results.failed.length > 0) { console.warn(`🍬