UNPKG

amplifyquery

Version:
438 lines (437 loc) 16.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); 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()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AuthService = exports.StorageService = exports.Utils = void 0; exports.setAppUrl = setAppUrl; exports.getAppUrl = getAppUrl; exports.createRelationalHook = createRelationalHook; const react_query_1 = require("@tanstack/react-query"); const auth_1 = require("aws-amplify/auth"); const storage_1 = require("aws-amplify/storage"); const expo_crypto_1 = require("expo-crypto"); const FileSystem = __importStar(require("expo-file-system")); const react_1 = require("react"); const react_native_mmkv_1 = require("react-native-mmkv"); // Create MMKV storage instance const storage = new react_native_mmkv_1.MMKV({ id: "mmkv.amplify-query" }); // Key for managing app URL const APP_URL_KEY = "amplify_query_app_url"; /** * Set the base URL for the app * @param url Base URL for the app */ function setAppUrl(url) { storage.set(APP_URL_KEY, url); } /** * Get the base URL for the app * @returns Configured app URL or empty string */ function getAppUrl() { return storage.getString(APP_URL_KEY) || ""; } /** * Utility functions */ exports.Utils = { /** * Get the ID of the currently logged-in user. */ getUserId: () => __awaiter(void 0, void 0, void 0, function* () { try { const user = yield (0, auth_1.getCurrentUser)(); return user.userId; } catch (e) { console.error("Failed to get user ID:", e); throw new Error("Could not retrieve user authentication information."); } }), /** * Timestamp formatting function */ formatTimestamp: (date = new Date()) => { return date.toISOString(); }, /** * Extract YYYY-MM-DD format from date string */ getDateString: (dateStr) => { if (!dateStr) return ""; return dateStr.split("T")[0]; }, /** * Utility function to remove the owner field and print a warning * @param data Data object to process * @param operation Operation being performed (create or update) * @returns Data object with owner field removed */ removeOwnerField: (data, operation) => { const { owner } = data, dataWithoutOwner = __rest(data, ["owner"]); if (owner) { console.warn(`The owner field exists. This field is added automatically and will be excluded from the ${operation} operation.`); } return dataWithoutOwner; }, }; /** * Storage related utilities */ exports.StorageService = { // URL cache internal type definition _types: { // URL cache interface (used internally only) Item: {}, // Cache data structure (stored as key-value) CacheData: {}, }, // Memory cache (for fast access) _urlCache: new Map(), // MMKV cache storage key _CACHE_KEY: "storage_url_cache", // Initialization status _initialized: false, /** * Initialize the memory cache. * Loads URL cache from MMKV storage. */ _initCache: () => { if (exports.StorageService._initialized) return; try { // Retrieve URL cache data from MMKV const cachedData = storage.getString(exports.StorageService._CACHE_KEY); // Restore to memory cache if (cachedData && cachedData.length > 0) { const parsedData = JSON.parse(cachedData); const now = Date.now(); // Add only non-expired items to memory cache Object.entries(parsedData).forEach(([key, item]) => { if (item.expiresAt > now) { exports.StorageService._urlCache.set(key, { url: item.url, expiresAt: item.expiresAt, }); } }); } exports.StorageService._initialized = true; } catch (error) { console.error("URL cache initialization error:", error); // Clear cache on error exports.StorageService.clearUrlCache(); exports.StorageService._initialized = true; } }, /** * Save cache to MMKV storage. */ _saveCache: () => { try { const cacheData = {}; // Convert memory cache to persistent storage format exports.StorageService._urlCache.forEach((item, key) => { cacheData[key] = { url: item.url, expiresAt: item.expiresAt, }; }); // Save to MMKV storage.set(exports.StorageService._CACHE_KEY, JSON.stringify(cacheData)); } catch (error) { console.error("URL cache save error:", error); } }, /** * Upload an image file to Storage. * @param file File to upload (Blob or File object) * @param key Path and filename to save as (auto-generated if not specified) * @returns Key of the uploaded file */ uploadImage: (file, key) => __awaiter(void 0, void 0, void 0, function* () { try { const fileKey = key || `images/${(0, expo_crypto_1.randomUUID)()}.${file.type.split("/")[1] || "jpg"}`; const result = yield (0, storage_1.uploadData)({ path: fileKey, data: file, options: { contentType: file.type, }, }).result; return result.path; } catch (error) { console.error("File upload failed:", error); throw error; } }), /** * Get the URL of a stored file. (Auto-caching) * @param key File key * @param options Caching options (forceRefresh: ignore cache and fetch new URL) * @returns File URL */ getFileUrl: (key, options) => __awaiter(void 0, void 0, void 0, function* () { try { // Initialize cache (only runs on first call) if (!exports.StorageService._initialized) { exports.StorageService._initCache(); } // If not ignoring cache and URL is in cache, return from cache const cachedItem = exports.StorageService._urlCache.get(key); const now = Date.now(); // If cached URL exists, is not expired, and not forced refresh if (cachedItem && cachedItem.expiresAt > now && !(options === null || options === void 0 ? void 0 : options.forceRefresh)) { return cachedItem.url; } // If not in cache, expired, or forced refresh, get new URL // Number of seconds till the URL expires. // The expiration time of the presigned url is dependent on the session and will max out at 1 hour. const result = yield (0, storage_1.getUrl)({ path: key, options: { // Default 1 hour expiration, adjustable if needed expiresIn: 60 * 60, // 1 hour }, }); const url = result.url.toString(); // Convert expiration time to timestamp const expiresAt = result.expiresAt.getTime(); // Save to cache exports.StorageService._urlCache.set(key, { url, expiresAt, }); // Also save to persistent storage exports.StorageService._saveCache(); return url; } catch (error) { console.error("Failed to get file URL:", error); throw error; } }), /** * Delete a stored file. * @param key Key of the file to delete */ deleteFile: (key) => __awaiter(void 0, void 0, void 0, function* () { try { yield (0, storage_1.remove)({ path: key }); // Remove from cache on file deletion exports.StorageService._urlCache.delete(key); // Update persistent storage exports.StorageService._saveCache(); } catch (error) { console.error("File deletion failed:", error); throw error; } }), /** * Clear the URL cache. */ clearUrlCache: () => { exports.StorageService._urlCache.clear(); storage.delete(exports.StorageService._CACHE_KEY); }, /** * Remove a specific key's URL cache. * @param key Key of the URL to remove */ clearUrlCacheForKey: (key) => { exports.StorageService._urlCache.delete(key); // Update persistent storage exports.StorageService._saveCache(); }, /** * Remove only expired URL caches. */ clearExpiredUrlCache: () => { const now = Date.now(); for (const [key, item] of exports.StorageService._urlCache.entries()) { if (item.expiresAt <= now) { exports.StorageService._urlCache.delete(key); } } // Update persistent storage exports.StorageService._saveCache(); }, /** * Download an audio file. * @param audioKey Key of the audio file to download * @returns Local file system path of the downloaded file */ downloadAudioFile: (audioKey) => __awaiter(void 0, void 0, void 0, function* () { try { // Create directory path where the file will be saved const audioDir = `${FileSystem.cacheDirectory}sounds/`; const localFilePath = `${audioDir}${audioKey}`; // Check if directory exists, create if not const dirInfo = yield FileSystem.getInfoAsync(audioDir); if (!dirInfo.exists) { yield FileSystem.makeDirectoryAsync(audioDir, { intermediates: true }); } // Check if file already exists const fileInfo = yield FileSystem.getInfoAsync(localFilePath); if (fileInfo.exists) { console.log("Audio file already exists locally:", localFilePath); return localFilePath; } // Get file URL from S3 const s3Url = yield (0, storage_1.getUrl)({ path: `public/sound/${audioKey}` }); // Download file const downloadResult = yield FileSystem.downloadAsync(s3Url.url.toString(), localFilePath); console.log("Audio file downloaded successfully:", downloadResult.uri); return downloadResult.uri; } catch (error) { console.error("Audio file download failed:", error); throw error; } }), }; /** * Authentication related utilities */ exports.AuthService = { /** * Get information about the currently logged-in user. */ getCurrentUserInfo: () => __awaiter(void 0, void 0, void 0, function* () { try { const user = yield (0, auth_1.getCurrentUser)(); return { userId: user.userId, username: user.username, }; } catch (e) { console.error("Error getting current user info:", e); throw new Error("Could not retrieve user authentication information."); } }), }; /** * Utility to create a relational query hook. * Creates a hook to query related items based on a specific foreign key. * * @param service Base service object * @param relationName Name of the relation (e.g., Daily, User) * @param queryName API query name (e.g., listMissionsByDaily) * @param idParamName ID parameter name (e.g., dailyId, userId) * @returns Relational query hook function */ function createRelationalHook(service, relationName, queryName, idParamName = `${relationName.toLowerCase()}Id`) { return (id) => { // Create query key - set dedicated cache key for specific relation ID const queryKey = [ service.modelName, relationName, id, // Explicitly include relation ID in query key for cache separation "query", queryName, JSON.stringify({ [idParamName]: id }), ]; // Get only CRUD methods from existing hook const baseHook = service.useHook({ initialFetchOptions: { fetch: false }, }); // Execute query to fetch actual data const { data = [], isLoading, error, refetch, } = (0, react_query_1.useQuery)({ queryKey, queryFn: () => service.customList(queryName, { [idParamName]: id }), enabled: !!id, // Enable query only when ID exists // Configure caching for optimized data per relation ID staleTime: 1000 * 30, // Keep fresh for 30 seconds gcTime: 1000 * 60 * 5, // Keep in cache for 5 minutes }); // Custom CRUD methods specific to the ID const createItem = (0, react_1.useCallback)((data) => __awaiter(this, void 0, void 0, function* () { try { // Add relation ID field const dataWithRelation = Object.assign(Object.assign({}, data), { [idParamName]: id }); // Auto-update cache per relation ID after creation const result = yield service.create(dataWithRelation); // Force refresh list per relation ID yield refetch(); return result; } catch (error) { console.error(`🍬 ${service.modelName} relational create error:`, error); throw error; } }), [service, id, refetch]); // Integrate data and loading state from useQuery, methods from baseHook return { items: data, isLoading, error, getItem: baseHook.getItem, refresh: () => __awaiter(this, void 0, void 0, function* () { console.log(`🍬 ${service.modelName} relational refresh called`, id); const { data } = yield refetch({ throwOnError: true }); return data || []; }), create: createItem, // ID-specific method update: baseHook.update, delete: baseHook.delete, customList: baseHook.customList, }; }; }