mirror of
https://github.com/Qortal/qapp-core.git
synced 2025-06-15 01:41:21 +00:00
226 lines
7.7 KiB
TypeScript
226 lines
7.7 KiB
TypeScript
import { create } from "zustand";
|
|
import { QortalMetadata } from "../types/interfaces/resources";
|
|
import { persist } from "zustand/middleware";
|
|
|
|
|
|
interface SearchCache {
|
|
[listName: string]: {
|
|
searches: {
|
|
[searchTerm: string]: QortalMetadata[]; // List of products for each search term
|
|
};
|
|
temporaryNewResources: QortalMetadata[],
|
|
expiry: number; // Expiry timestamp for the whole list
|
|
};
|
|
}
|
|
|
|
|
|
|
|
export const mergeUniqueItems = (array1: QortalMetadata[], array2: QortalMetadata[]) => {
|
|
const mergedArray = [...array1, ...array2];
|
|
|
|
// Use a Map to ensure uniqueness based on `identifier-name`
|
|
const uniqueMap = new Map();
|
|
|
|
mergedArray.forEach(item => {
|
|
if (item.identifier && item.name && item.service) {
|
|
const key = `${item.service}-${item.name}-${item.identifier}`;
|
|
uniqueMap.set(key, item);
|
|
}
|
|
});
|
|
|
|
return Array.from(uniqueMap.values()); // Return the unique values
|
|
};
|
|
|
|
export interface ListItem {
|
|
data?: any
|
|
qortalMetadata: QortalMetadata
|
|
}
|
|
|
|
|
|
interface resourceCache {
|
|
[id: string]: {
|
|
data: ListItem | false | null; // Cached resource data
|
|
expiry: number; // Expiry timestamp in milliseconds
|
|
};
|
|
}
|
|
|
|
interface DeletedResources {
|
|
[key: string]: { deleted: true; expiry: number }; // ✅ Added expiry field
|
|
}
|
|
|
|
interface CacheState {
|
|
resourceCache: resourceCache;
|
|
|
|
searchCache: SearchCache;
|
|
// Search cache actions
|
|
setResourceCache: (id: string, data: ListItem | false | null, customExpiry?: number) => void;
|
|
|
|
setSearchCache: (listName: string, searchTerm: string, data: QortalMetadata[], customExpiry?: number) => void;
|
|
getSearchCache: (listName: string, searchTerm: string) => QortalMetadata[] | null;
|
|
clearExpiredCache: () => void;
|
|
getResourceCache: (id: string, ignoreExpire?: boolean) => ListItem | false | null;
|
|
addTemporaryResource: (listName: string, newResources: QortalMetadata[], customExpiry?: number)=> void;
|
|
getTemporaryResources:(listName: string)=> QortalMetadata[]
|
|
deletedResources: DeletedResources;
|
|
markResourceAsDeleted: (item: QortalMetadata) => void;
|
|
filterOutDeletedResources: (items: QortalMetadata[]) => QortalMetadata[];
|
|
isListExpired: (listName: string)=> boolean
|
|
searchCacheExpiryDuration: number;
|
|
resourceCacheExpiryDuration: number;
|
|
setSearchCacheExpiryDuration: (duration: number) => void;
|
|
setResourceCacheExpiryDuration: (duration: number)=> void;
|
|
}
|
|
|
|
export const useCacheStore = create<CacheState>
|
|
((set, get) => ({
|
|
searchCacheExpiryDuration: 5 * 60 * 1000,
|
|
resourceCacheExpiryDuration: 30 * 60 * 1000,
|
|
resourceCache: {},
|
|
searchCache: {},
|
|
deletedResources: {},
|
|
setSearchCacheExpiryDuration: (duration) => set({ searchCacheExpiryDuration: duration }),
|
|
setResourceCacheExpiryDuration: (duration) => set({ resourceCacheExpiryDuration: duration }),
|
|
getResourceCache: (id, ignoreExpire) => {
|
|
const cache = get().resourceCache[id];
|
|
if (cache) {
|
|
if (cache.expiry > Date.now() || ignoreExpire) {
|
|
return cache.data; // ✅ Return data if not expired
|
|
} else {
|
|
set((state) => {
|
|
const updatedCache = { ...state.resourceCache };
|
|
delete updatedCache[id]; // ✅ Remove expired entry
|
|
return { resourceCache: updatedCache };
|
|
});
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
|
|
setResourceCache: (id, data, customExpiry) =>
|
|
set((state) => {
|
|
const expiry = Date.now() + (customExpiry || get().resourceCacheExpiryDuration);
|
|
return {
|
|
resourceCache: {
|
|
...state.resourceCache,
|
|
[id]: { data, expiry },
|
|
},
|
|
};
|
|
}),
|
|
|
|
setSearchCache: (listName, searchTerm, data, customExpiry) =>
|
|
set((state) => {
|
|
const expiry = Date.now() + (customExpiry || get().searchCacheExpiryDuration);
|
|
|
|
return {
|
|
searchCache: {
|
|
...state.searchCache,
|
|
[listName]: {
|
|
searches: {
|
|
...(state.searchCache[listName]?.searches || {}),
|
|
[searchTerm]: data,
|
|
},
|
|
temporaryNewResources: state.searchCache[listName]?.temporaryNewResources || [],
|
|
expiry,
|
|
},
|
|
},
|
|
};
|
|
}),
|
|
|
|
|
|
getSearchCache: (listName, searchTerm) => {
|
|
const cache = get().searchCache[listName];
|
|
if (cache) {
|
|
if (cache.expiry > Date.now()) {
|
|
return cache.searches[searchTerm] || null; // ✅ Return if valid
|
|
} else {
|
|
set((state) => {
|
|
const updatedCache = { ...state.searchCache };
|
|
delete updatedCache[listName]; // ✅ Remove expired list
|
|
return { searchCache: updatedCache };
|
|
});
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
|
|
addTemporaryResource: (listName, newResources, customExpiry) =>
|
|
set((state) => {
|
|
|
|
const expiry = Date.now() + (customExpiry || 5 * 60 * 1000);
|
|
|
|
const existingResources = state.searchCache[listName]?.temporaryNewResources || [];
|
|
|
|
// Merge & remove duplicates, keeping the latest by `created` timestamp
|
|
const uniqueResourcesMap = new Map<string, QortalMetadata>();
|
|
|
|
[...existingResources, ...newResources].forEach((item) => {
|
|
const key = `${item.service}-${item.name}-${item.identifier}`;
|
|
const existingItem = uniqueResourcesMap.get(key);
|
|
|
|
if (!existingItem || item.created > existingItem.created) {
|
|
uniqueResourcesMap.set(key, item);
|
|
}
|
|
});
|
|
|
|
return {
|
|
searchCache: {
|
|
...state.searchCache,
|
|
[listName]: {
|
|
...state.searchCache[listName],
|
|
temporaryNewResources: Array.from(uniqueResourcesMap.values()),
|
|
expiry,
|
|
},
|
|
},
|
|
};
|
|
}),
|
|
|
|
getTemporaryResources: (listName: string) => {
|
|
const cache = get().searchCache[listName];
|
|
if (cache && cache.expiry > Date.now()) {
|
|
return cache.temporaryNewResources || [];
|
|
}
|
|
return [];
|
|
},
|
|
|
|
markResourceAsDeleted: (item) =>
|
|
set((state) => {
|
|
const now = Date.now();
|
|
const expiry = now + 5 * 60 * 1000; // ✅ Expires in 5 minutes
|
|
|
|
// ✅ Remove expired deletions before adding a new one
|
|
const validDeletedResources = Object.fromEntries(
|
|
Object.entries(state.deletedResources).filter(([_, value]) => value.expiry > now)
|
|
);
|
|
|
|
const key = `${item.service}-${item.name}-${item.identifier}`;
|
|
return {
|
|
deletedResources: {
|
|
...validDeletedResources, // ✅ Keep only non-expired ones
|
|
[key]: { deleted: true, expiry },
|
|
},
|
|
};
|
|
}),
|
|
|
|
filterOutDeletedResources: (items) => {
|
|
const deletedResources = get().deletedResources; // ✅ Read without modifying store
|
|
return items.filter(
|
|
(item) => !deletedResources[`${item.service}-${item.name}-${item.identifier}`]
|
|
);
|
|
},
|
|
isListExpired: (listName: string): boolean => {
|
|
const cache = get().searchCache[listName];
|
|
return cache ? cache.expiry <= Date.now() : true; // ✅ Expired if expiry timestamp is in the past
|
|
},
|
|
|
|
|
|
clearExpiredCache: () =>
|
|
set((state) => {
|
|
const now = Date.now();
|
|
const validSearchCache = Object.fromEntries(
|
|
Object.entries(state.searchCache).filter(([, value]) => value.expiry > now)
|
|
);
|
|
return { searchCache: validSearchCache };
|
|
}),
|
|
}),
|
|
|
|
); |