amplifyquery
Version:
864 lines β’ 79.2 kB
JavaScript
"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(`π¬