amplifyquery
Version:
438 lines (437 loc) • 16.9 kB
JavaScript
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,
};
};
}
;