/** * @file In-memory store that keeps the most recent ProtoPedia snapshot and exposes * efficient lookup helpers (O(1) by id via index, stale detection, etc.). * * The store sits above upstream fetch logic, allowing server actions to reuse * canonical data without repeated API calls while still respecting TTL limits. */ import type { NormalizedPrototype } from '../types/index.js'; import type { PrototypeInMemoryStoreConfig } from './types/config.types.js'; import type { Snapshot } from './types/snapshot.types.js'; import type { PrototypeInMemoryStats } from './types/stats.types.js'; /** * Hard limit for data size in bytes for storing snapshots. * Attempting to configure a store with a size larger than this will throw an error. */ export declare const LIMIT_DATA_SIZE_BYTES: number; type RefreshTask = () => Promise; /** * In-memory store that keeps the full set of normalized prototypes with an ID-based index. * * The store accepts full snapshots (`setAll`) and exposes O(1) lookups by prototype id * via an internal index. When TTL expires, data remains readable while callers kick off * background refresh tasks using {@link runExclusive} to avoid redundant upstream calls. */ export declare class PrototypeInMemoryStore { private readonly logger; private readonly logLevel; private readonly ttlMs; private readonly maxDataSizeBytes; private prototypeIdIndex; private prototypes; private cachedAt; private dataSizeBytes; private refreshPromise; /** * Create a new PrototypeInMemoryStore instance. * * Initializes an in-memory cache for normalized prototypes with configurable * TTL and size limits. The store manages snapshot expiration, refresh state, * and provides type-safe read-only access to cached data. * * @param config - Configuration options for the store * @param config.ttlMs - Time-to-live in milliseconds for cached snapshots. * Defaults to 30 minutes (1,800,000ms). After this duration, the snapshot * is considered expired and should be refreshed. * @param config.maxDataSizeBytes - Maximum allowed size for cached data in bytes. * Defaults to 10 MiB (10,485,760 bytes). Must not exceed 30 MiB. * If a snapshot exceeds this limit, setAll() will reject it. * @param config.logger - Optional custom logger instance. If not provided, * a default ConsoleLogger is created. * @param config.logLevel - Log level for the logger. When `logger` is not provided, * creates a ConsoleLogger with this level. When `logger` is provided and mutable, * updates the logger's level property. * * @throws {ConfigurationError} When maxDataSizeBytes exceeds LIMIT_DATA_SIZE_BYTES (30 MiB) */ constructor({ ttlMs, maxDataSizeBytes, logger, logLevel, }?: PrototypeInMemoryStoreConfig); /** * Retrieve the configuration used to initialize this store. * * Returns the resolved configuration values (TTL and max payload size) that were * set during instantiation. These values are immutable after construction. */ getConfig(): Omit, 'logger'>; /** Count of prototypes currently kept in the in-memory store. */ get size(): number; /** Timestamp representing when the snapshot was last refreshed. */ getCachedAt(): Date | null; /** * Calculate elapsed time in milliseconds since the snapshot was cached. * Returns 0 if no data is cached. */ private getElapsedTime; /** * Calculate remaining time in milliseconds until expiration. * Returns 0 if already expired or no data is cached. */ private getRemainingTtl; /** Determine whether the snapshot is stale based on the configured TTL. */ isExpired(): boolean; /** Report whether a background refresh is currently in flight. */ isRefreshInFlight(): boolean; /** * Provide statistics describing cache health and runtime state. * * Returns metadata about the current snapshot including size, expiration status, * and refresh state. For configuration values like TTL, use {@link getConfig}. */ getStats(): PrototypeInMemoryStats; /** * Estimates the JSON payload size of an array of NormalizedPrototypes in bytes. * * This method calculates the size by iteratively serializing each item and summing their byte lengths, * along with the overhead for array brackets and commas. This approach minimizes memory usage * by avoiding the creation of a single large JSON string for the entire array, thus reducing * the risk of out-of-memory errors, especially with large datasets. * * @param data - The array of NormalizedPrototypes to estimate the size for. * @returns The estimated size in bytes of the JSON-serialized data. * @throws {SizeEstimationError} When JSON serialization fails (e.g., circular references). */ private estimateSize; /** * Execute a refresh task while preventing concurrent execution. * * @returns Promise resolved when the task completes; callers may ignore it for background refreshes. */ runExclusive(task: RefreshTask): Promise; /** Reset the store to an empty state and clear all metadata. */ clear(): void; /** * Store the provided snapshot if it fits within the configured payload limit. * Creates a shallow copy of the input array to prevent external mutations. * * @param prototypes - Array of normalized prototypes to store (array will be copied) * @returns Metadata about the stored snapshot including the exact data size in bytes * @throws {SizeEstimationError} When data size estimation fails * @throws {DataSizeExceededError} When the payload exceeds the configured maximum size limit * * @remarks * **Error Handling**: When an error is thrown, the store is NOT modified. * Any previously stored snapshot remains intact and accessible. This ensures * that applications can continue serving data from the last successful snapshot * even when new data cannot be stored. * * The method creates a shallow copy of the input array to ensure the store's * internal state cannot be corrupted by external mutations of the array. * However, the prototype objects themselves are not cloned. Callers must not * mutate the prototype objects after passing them to this method. * * Size calculation is performed AFTER deduplication to ensure accurate size checking. * If duplicate IDs are present in the input array, only the last occurrence is kept. */ setAll(prototypes: readonly NormalizedPrototype[]): { dataSizeBytes: number; }; /** * Retrieve the latest fetched prototypes in their original order. * * Returns type-level readonly reference to the internal prototypes array. * The readonly type provides compile-time safety but not runtime protection. * * @returns Type-level readonly array of prototypes. Returns an empty array if no data is stored. * * @remarks * **Type Safety**: This method returns a readonly-typed reference without * runtime immutability enforcement (no Object.freeze or defensive copying). * Callers must honor the readonly contract and not cast it away. * * **Performance**: Direct reference with zero overhead - suitable for * high-frequency reads of large datasets. */ getAll(): readonly NormalizedPrototype[]; /** * Return a lightweight structure containing the cached data and metadata. * * Useful for callers that want to inspect expiry state without mutating the store. */ getSnapshot(): Snapshot; /** * Retrieve a single prototype by its numeric identifier. * * Uses the internal prototypeIdIndex for O(1) constant-time lookup, providing * exceptional performance even with thousands of cached prototypes. This is * significantly faster than linear search alternatives (approximately 12,500x * faster for 5,000 items). * * The index-based implementation adds minimal memory overhead (~230KB for 5,000 * items, or ~0.8% of total cache size) while delivering constant-time access * regardless of cache size. * * @param prototypeId - The numeric ID of the prototype to retrieve * @returns The prototype with type-level immutability, or null if not found * * @example * ```typescript * const proto = store.getByPrototypeId(123); * if (proto) { * console.log(proto.prototypeNm); * } * ``` * * @performance * - Time complexity: O(1) - constant time regardless of cache size * - Measured: ~0.0002ms per lookup (10,000 items) * - Memory overhead: ~40 bytes per entry (including index metadata and hash table) */ getByPrototypeId(prototypeId: number): NormalizedPrototype | null; /** * Return an array of all cached prototype IDs. * * This method provides efficient access to prototype IDs without copying the * entire prototype objects. Useful for operations that only need IDs, such as * ID-based filtering, statistics, or exporting ID lists. * * @returns Read-only array of prototype IDs in insertion order. Returns an empty array if no data is stored. * * @performance * - Time complexity: O(n) - must iterate through all Map keys * - Memory: Creates a new array of numbers (~40 bytes per ID) * - Lighter than getAll() which copies full objects (~300+ bytes each) * * @example * ```typescript * // ✅ Good: Call once and reuse * const ids = store.getPrototypeIds(); * const count = ids.length; * const maxId = Math.max(...ids); * * // ✅ Good: Single-use cases * return { availableIds: store.getPrototypeIds() }; * * // ❌ Bad: Repeated calls in loops * for (let i = 0; i < 1000; i++) { * const ids = store.getPrototypeIds(); // O(n) × 1000 = very slow! * const id = ids[Math.floor(Math.random() * ids.length)]; * } * * // ✅ Better: Use getAll() once for repeated access * const all = store.getAll(); * for (let i = 0; i < 1000; i++) { * const item = all[Math.floor(Math.random() * all.length)]; * } * ``` * * @remarks * **Performance Warning**: This method creates a new array on every call. * For high-frequency operations (loops, repeated random access), prefer * calling {@link getAll} once and reusing the result. The O(n) cost per * call makes this unsuitable for tight loops. */ getPrototypeIds(): readonly number[]; } export {}; //# sourceMappingURL=store.d.ts.map