@asaidimu/utils-cache 3.1.11 → 3.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.cts +212 -0
- package/index.d.ts +120 -165
- package/index.js +1 -1
- package/index.mjs +1 -1
- package/package.json +2 -2
- package/index.d.mts +0 -209
package/index.d.cts
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
//#region src/persistence/types.d.ts
|
|
2
|
+
interface SimplePersistence<T> {
|
|
3
|
+
/**
|
|
4
|
+
* Persists data to storage.
|
|
5
|
+
*
|
|
6
|
+
* @param id The **unique identifier of the *consumer instance*** making the change. This is NOT the ID of the data (`T`) itself.
|
|
7
|
+
* Think of it as the ID of the specific browser tab, component, or module that's currently interacting with the persistence layer.
|
|
8
|
+
* It should typically be a **UUID** generated once at the consumer instance's instantiation.
|
|
9
|
+
* This `id` is crucial for the `subscribe` method, helping to differentiate updates originating from the current instance versus other instances/tabs, thereby preventing self-triggered notification loops.
|
|
10
|
+
* @param state The state (of type T) to persist. This state is generally considered the **global or shared state** that all instances interact with.
|
|
11
|
+
* @returns `true` if the operation was successful, `false` if an error occurred. For asynchronous implementations (like `IndexedDBPersistence`), this returns a `Promise<boolean>`.
|
|
12
|
+
*/
|
|
13
|
+
set(id: string, state: T): boolean | Promise<boolean>;
|
|
14
|
+
/**
|
|
15
|
+
* Retrieves the global persisted data from storage.
|
|
16
|
+
*
|
|
17
|
+
* @returns The retrieved state of type `T`, or `null` if no data is found or if an error occurs during retrieval/parsing.
|
|
18
|
+
* For asynchronous implementations, this returns a `Promise<T | null>`.
|
|
19
|
+
*/
|
|
20
|
+
get(): (T | null) | Promise<T | null>;
|
|
21
|
+
/**
|
|
22
|
+
* Subscribes to changes in the global persisted data that originate from *other* instances of your application (e.g., other tabs or independent components using the same persistence layer).
|
|
23
|
+
*
|
|
24
|
+
* @param id The **unique identifier of the *consumer instance* subscribing**. This allows the persistence implementation to filter out notifications that were initiated by the subscribing instance itself.
|
|
25
|
+
* @param callback The function to call when the global persisted data changes from *another* source. The new state (`T`) is passed as an argument to this callback.
|
|
26
|
+
* @returns A function that, when called, will unsubscribe the provided callback from future updates. Call this when your component or instance is no longer active to prevent memory leaks.
|
|
27
|
+
*/
|
|
28
|
+
subscribe(id: string, callback: (state: T) => void): () => void;
|
|
29
|
+
/**
|
|
30
|
+
* Clears (removes) the entire global persisted data from storage.
|
|
31
|
+
*
|
|
32
|
+
* @returns `true` if the operation was successful, `false` if an error occurred. For asynchronous implementations, this returns a `Promise<boolean>`.
|
|
33
|
+
*/
|
|
34
|
+
clear(): boolean | Promise<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* Returns metadata about the persistence layer.
|
|
37
|
+
*
|
|
38
|
+
* This is useful for distinguishing between multiple apps running on the same host
|
|
39
|
+
* (e.g., several apps served at `localhost:3000` that share the same storage key).
|
|
40
|
+
*
|
|
41
|
+
* @returns An object containing:
|
|
42
|
+
* - `version`: The semantic version string of the persistence schema or application.
|
|
43
|
+
* - `id`: A unique identifier for the application using this persistence instance.
|
|
44
|
+
*/
|
|
45
|
+
stats(): {
|
|
46
|
+
version: string;
|
|
47
|
+
id: string;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/cache/types.d.ts
|
|
52
|
+
interface CacheOptions {
|
|
53
|
+
staleTime?: number;
|
|
54
|
+
cacheTime?: number;
|
|
55
|
+
retryAttempts?: number;
|
|
56
|
+
retryDelay?: number;
|
|
57
|
+
maxSize?: number;
|
|
58
|
+
enableMetrics?: boolean;
|
|
59
|
+
persistence?: SimplePersistence<SerializableCacheState>;
|
|
60
|
+
persistenceId?: string;
|
|
61
|
+
serializeValue?: (value: any) => any;
|
|
62
|
+
deserializeValue?: (value: any) => any;
|
|
63
|
+
persistenceDebounceTime?: number;
|
|
64
|
+
}
|
|
65
|
+
interface CacheEntry<T = any> {
|
|
66
|
+
data: T;
|
|
67
|
+
lastUpdated: number;
|
|
68
|
+
lastAccessed: number;
|
|
69
|
+
accessCount: number;
|
|
70
|
+
error?: Error;
|
|
71
|
+
isLoading?: boolean;
|
|
72
|
+
}
|
|
73
|
+
interface QueryConfig {
|
|
74
|
+
fetchFunction: () => Promise<any>;
|
|
75
|
+
options: Required<CacheOptions>;
|
|
76
|
+
}
|
|
77
|
+
interface CacheMetrics {
|
|
78
|
+
hits: number;
|
|
79
|
+
misses: number;
|
|
80
|
+
fetches: number;
|
|
81
|
+
errors: number;
|
|
82
|
+
evictions: number;
|
|
83
|
+
staleHits: number;
|
|
84
|
+
}
|
|
85
|
+
interface SerializableCacheEntry {
|
|
86
|
+
data: any;
|
|
87
|
+
lastUpdated: number;
|
|
88
|
+
lastAccessed: number;
|
|
89
|
+
accessCount: number;
|
|
90
|
+
error?: {
|
|
91
|
+
name: string;
|
|
92
|
+
message: string;
|
|
93
|
+
stack?: string;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
type SerializableCacheState = Array<[string, SerializableCacheEntry]>;
|
|
97
|
+
type CacheEventBase<Type extends string, Payload = {}> = {
|
|
98
|
+
type: Type;
|
|
99
|
+
key: string;
|
|
100
|
+
timestamp: number;
|
|
101
|
+
} & Payload;
|
|
102
|
+
type CacheReadHitEvent<T = any> = CacheEventBase<"cache:read:hit", {
|
|
103
|
+
data: T;
|
|
104
|
+
isStale: boolean;
|
|
105
|
+
}>;
|
|
106
|
+
type CacheReadMissEvent = CacheEventBase<"cache:read:miss">;
|
|
107
|
+
type CacheFetchStartEvent = CacheEventBase<"cache:fetch:start", {
|
|
108
|
+
attempt: number;
|
|
109
|
+
}>;
|
|
110
|
+
type CacheFetchSuccessEvent<T = any> = CacheEventBase<"cache:fetch:success", {
|
|
111
|
+
data: T;
|
|
112
|
+
}>;
|
|
113
|
+
type CacheFetchErrorEvent = CacheEventBase<"cache:fetch:error", {
|
|
114
|
+
error: Error;
|
|
115
|
+
attempt: number;
|
|
116
|
+
}>;
|
|
117
|
+
type CacheDataEvictEvent = CacheEventBase<"cache:data:evict", {
|
|
118
|
+
reason?: string;
|
|
119
|
+
}>;
|
|
120
|
+
type CacheDataInvalidateEvent = CacheEventBase<"cache:data:invalidate">;
|
|
121
|
+
type CacheDataSetEvent<T = any> = CacheEventBase<"cache:data:set", {
|
|
122
|
+
newData: T;
|
|
123
|
+
oldData?: T;
|
|
124
|
+
}>;
|
|
125
|
+
type CachePersistenceLoadSuccessEvent = CacheEventBase<"cache:persistence:load:success", {
|
|
126
|
+
message?: string;
|
|
127
|
+
}>;
|
|
128
|
+
type CachePersistenceLoadErrorEvent = CacheEventBase<"cache:persistence:load:error", {
|
|
129
|
+
message?: string;
|
|
130
|
+
error?: any;
|
|
131
|
+
}>;
|
|
132
|
+
type CachePersistenceSaveSuccessEvent = CacheEventBase<"cache:persistence:save:success">;
|
|
133
|
+
type CachePersistenceSaveErrorEvent = CacheEventBase<"cache:persistence:save:error", {
|
|
134
|
+
message?: string;
|
|
135
|
+
error?: any;
|
|
136
|
+
}>;
|
|
137
|
+
type CachePersistenceClearSuccessEvent = CacheEventBase<"cache:persistence:clear:success">;
|
|
138
|
+
type CachePersistenceClearErrorEvent = CacheEventBase<"cache:persistence:clear:error", {
|
|
139
|
+
message?: string;
|
|
140
|
+
error?: any;
|
|
141
|
+
}>;
|
|
142
|
+
type CachePersistenceSyncEvent = CacheEventBase<"cache:persistence:sync", {
|
|
143
|
+
message?: string;
|
|
144
|
+
}>;
|
|
145
|
+
type CacheEvent = CacheReadHitEvent | CacheReadMissEvent | CacheFetchStartEvent | CacheFetchSuccessEvent | CacheFetchErrorEvent | CacheDataEvictEvent | CacheDataInvalidateEvent | CacheDataSetEvent | CachePersistenceLoadSuccessEvent | CachePersistenceLoadErrorEvent | CachePersistenceSaveSuccessEvent | CachePersistenceSaveErrorEvent | CachePersistenceClearSuccessEvent | CachePersistenceClearErrorEvent | CachePersistenceSyncEvent;
|
|
146
|
+
type CacheEventType = CacheEvent["type"];
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/cache/cache.d.ts
|
|
149
|
+
declare class QueryCache {
|
|
150
|
+
private cache;
|
|
151
|
+
private queries;
|
|
152
|
+
private fetching;
|
|
153
|
+
private readonly defaultOptions;
|
|
154
|
+
private metrics;
|
|
155
|
+
private eventBus;
|
|
156
|
+
private gcTimer?;
|
|
157
|
+
private readonly persistenceId;
|
|
158
|
+
private persistenceUnsubscribe?;
|
|
159
|
+
private persistenceDebounceTimer?;
|
|
160
|
+
private isHandlingRemoteUpdate;
|
|
161
|
+
constructor(defaultOptions?: CacheOptions);
|
|
162
|
+
private initializePersistence;
|
|
163
|
+
private serializeCache;
|
|
164
|
+
private deserializeAndLoadCache;
|
|
165
|
+
private schedulePersistState;
|
|
166
|
+
private handleRemoteStateChange;
|
|
167
|
+
registerQuery<T>(key: string, fetchFunction: () => Promise<T>, options?: CacheOptions): void;
|
|
168
|
+
get<T>(key: string, options?: {
|
|
169
|
+
waitForFresh?: boolean;
|
|
170
|
+
throwOnError?: boolean;
|
|
171
|
+
}): Promise<T | undefined>;
|
|
172
|
+
peek<T>(key: string): T | undefined;
|
|
173
|
+
has(key: string): boolean;
|
|
174
|
+
private fetch;
|
|
175
|
+
private fetchAndWait;
|
|
176
|
+
private performFetchWithRetry;
|
|
177
|
+
private isStale;
|
|
178
|
+
invalidate(key: string, refetch?: boolean): Promise<void>;
|
|
179
|
+
invalidatePattern(pattern: RegExp, refetch?: boolean): Promise<void>;
|
|
180
|
+
prefetch(key: string): Promise<void>;
|
|
181
|
+
refresh<T>(key: string): Promise<T | undefined>;
|
|
182
|
+
setData<T>(key: string, data: T): void;
|
|
183
|
+
remove(key: string): boolean;
|
|
184
|
+
private enforceSizeLimit;
|
|
185
|
+
private startGarbageCollection;
|
|
186
|
+
garbageCollect(): number;
|
|
187
|
+
getStats(): {
|
|
188
|
+
size: number;
|
|
189
|
+
metrics: CacheMetrics;
|
|
190
|
+
hitRate: number;
|
|
191
|
+
staleHitRate: number;
|
|
192
|
+
entries: Array<{
|
|
193
|
+
key: string;
|
|
194
|
+
lastAccessed: number;
|
|
195
|
+
lastUpdated: number;
|
|
196
|
+
accessCount: number;
|
|
197
|
+
isStale: boolean;
|
|
198
|
+
isLoading?: boolean;
|
|
199
|
+
error?: boolean;
|
|
200
|
+
}>;
|
|
201
|
+
};
|
|
202
|
+
on<EType extends CacheEventType>(event: EType, listener: (ev: Extract<CacheEvent, {
|
|
203
|
+
type: EType;
|
|
204
|
+
}>) => void): () => void;
|
|
205
|
+
private emitEvent;
|
|
206
|
+
private updateMetrics;
|
|
207
|
+
private delay;
|
|
208
|
+
clear(): Promise<void>;
|
|
209
|
+
destroy(): void;
|
|
210
|
+
}
|
|
211
|
+
//#endregion
|
|
212
|
+
export { CacheDataEvictEvent, CacheDataInvalidateEvent, CacheDataSetEvent, CacheEntry, CacheEvent, CacheEventBase, CacheEventType, CacheFetchErrorEvent, CacheFetchStartEvent, CacheFetchSuccessEvent, CacheMetrics, CacheOptions, CachePersistenceClearErrorEvent, CachePersistenceClearSuccessEvent, CachePersistenceLoadErrorEvent, CachePersistenceLoadSuccessEvent, CachePersistenceSaveErrorEvent, CachePersistenceSaveSuccessEvent, CachePersistenceSyncEvent, CacheReadHitEvent, CacheReadMissEvent, QueryCache, QueryConfig, SerializableCacheEntry, SerializableCacheState };
|
package/index.d.ts
CHANGED
|
@@ -1,209 +1,164 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Persists data to storage.
|
|
4
|
-
*
|
|
5
|
-
* @param id The **unique identifier of the *consumer instance*** making the change. This is NOT the ID of the data (`T`) itself.
|
|
6
|
-
* Think of it as the ID of the specific browser tab, component, or module that's currently interacting with the persistence layer.
|
|
7
|
-
* It should typically be a **UUID** generated once at the consumer instance's instantiation.
|
|
8
|
-
* This `id` is crucial for the `subscribe` method, helping to differentiate updates originating from the current instance versus other instances/tabs, thereby preventing self-triggered notification loops.
|
|
9
|
-
* @param state The state (of type T) to persist. This state is generally considered the **global or shared state** that all instances interact with.
|
|
10
|
-
* @returns `true` if the operation was successful, `false` if an error occurred. For asynchronous implementations (like `IndexedDBPersistence`), this returns a `Promise<boolean>`.
|
|
11
|
-
*/
|
|
12
|
-
set(id: string, state: T): boolean | Promise<boolean>;
|
|
13
|
-
/**
|
|
14
|
-
* Retrieves the global persisted data from storage.
|
|
15
|
-
*
|
|
16
|
-
* @returns The retrieved state of type `T`, or `null` if no data is found or if an error occurs during retrieval/parsing.
|
|
17
|
-
* For asynchronous implementations, this returns a `Promise<T | null>`.
|
|
18
|
-
*/
|
|
19
|
-
get(): (T | null) | Promise<T | null>;
|
|
20
|
-
/**
|
|
21
|
-
* Subscribes to changes in the global persisted data that originate from *other* instances of your application (e.g., other tabs or independent components using the same persistence layer).
|
|
22
|
-
*
|
|
23
|
-
* @param id The **unique identifier of the *consumer instance* subscribing**. This allows the persistence implementation to filter out notifications that were initiated by the subscribing instance itself.
|
|
24
|
-
* @param callback The function to call when the global persisted data changes from *another* source. The new state (`T`) is passed as an argument to this callback.
|
|
25
|
-
* @returns A function that, when called, will unsubscribe the provided callback from future updates. Call this when your component or instance is no longer active to prevent memory leaks.
|
|
26
|
-
*/
|
|
27
|
-
subscribe(id: string, callback: (state: T) => void): () => void;
|
|
28
|
-
/**
|
|
29
|
-
* Clears (removes) the entire global persisted data from storage.
|
|
30
|
-
*
|
|
31
|
-
* @returns `true` if the operation was successful, `false` if an error occurred. For asynchronous implementations, this returns a `Promise<boolean>`.
|
|
32
|
-
*/
|
|
33
|
-
clear(): boolean | Promise<boolean>;
|
|
34
|
-
/**
|
|
35
|
-
* Returns metadata about the persistence layer.
|
|
36
|
-
*
|
|
37
|
-
* This is useful for distinguishing between multiple apps running on the same host
|
|
38
|
-
* (e.g., several apps served at `localhost:3000` that share the same storage key).
|
|
39
|
-
*
|
|
40
|
-
* @returns An object containing:
|
|
41
|
-
* - `version`: The semantic version string of the persistence schema or application.
|
|
42
|
-
* - `id`: A unique identifier for the application using this persistence instance.
|
|
43
|
-
*/
|
|
44
|
-
stats(): {
|
|
45
|
-
version: string;
|
|
46
|
-
id: string;
|
|
47
|
-
};
|
|
48
|
-
}
|
|
1
|
+
import { SimplePersistence } from "@asaidimu/utils-persistence";
|
|
49
2
|
|
|
3
|
+
//#region src/cache/types.d.ts
|
|
50
4
|
interface CacheOptions {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
5
|
+
staleTime?: number;
|
|
6
|
+
cacheTime?: number;
|
|
7
|
+
retryAttempts?: number;
|
|
8
|
+
retryDelay?: number;
|
|
9
|
+
maxSize?: number;
|
|
10
|
+
enableMetrics?: boolean;
|
|
11
|
+
persistence?: SimplePersistence<SerializableCacheState>;
|
|
12
|
+
persistenceId?: string;
|
|
13
|
+
serializeValue?: (value: any) => any;
|
|
14
|
+
deserializeValue?: (value: any) => any;
|
|
15
|
+
persistenceDebounceTime?: number;
|
|
62
16
|
}
|
|
63
17
|
interface CacheEntry<T = any> {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
18
|
+
data: T;
|
|
19
|
+
lastUpdated: number;
|
|
20
|
+
lastAccessed: number;
|
|
21
|
+
accessCount: number;
|
|
22
|
+
error?: Error;
|
|
23
|
+
isLoading?: boolean;
|
|
70
24
|
}
|
|
71
25
|
interface QueryConfig {
|
|
72
|
-
|
|
73
|
-
|
|
26
|
+
fetchFunction: () => Promise<any>;
|
|
27
|
+
options: Required<CacheOptions>;
|
|
74
28
|
}
|
|
75
29
|
interface CacheMetrics {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
30
|
+
hits: number;
|
|
31
|
+
misses: number;
|
|
32
|
+
fetches: number;
|
|
33
|
+
errors: number;
|
|
34
|
+
evictions: number;
|
|
35
|
+
staleHits: number;
|
|
82
36
|
}
|
|
83
37
|
interface SerializableCacheEntry {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
38
|
+
data: any;
|
|
39
|
+
lastUpdated: number;
|
|
40
|
+
lastAccessed: number;
|
|
41
|
+
accessCount: number;
|
|
42
|
+
error?: {
|
|
43
|
+
name: string;
|
|
44
|
+
message: string;
|
|
45
|
+
stack?: string;
|
|
46
|
+
};
|
|
93
47
|
}
|
|
94
48
|
type SerializableCacheState = Array<[string, SerializableCacheEntry]>;
|
|
95
49
|
type CacheEventBase<Type extends string, Payload = {}> = {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
50
|
+
type: Type;
|
|
51
|
+
key: string;
|
|
52
|
+
timestamp: number;
|
|
99
53
|
} & Payload;
|
|
100
54
|
type CacheReadHitEvent<T = any> = CacheEventBase<"cache:read:hit", {
|
|
101
|
-
|
|
102
|
-
|
|
55
|
+
data: T;
|
|
56
|
+
isStale: boolean;
|
|
103
57
|
}>;
|
|
104
58
|
type CacheReadMissEvent = CacheEventBase<"cache:read:miss">;
|
|
105
59
|
type CacheFetchStartEvent = CacheEventBase<"cache:fetch:start", {
|
|
106
|
-
|
|
60
|
+
attempt: number;
|
|
107
61
|
}>;
|
|
108
62
|
type CacheFetchSuccessEvent<T = any> = CacheEventBase<"cache:fetch:success", {
|
|
109
|
-
|
|
63
|
+
data: T;
|
|
110
64
|
}>;
|
|
111
65
|
type CacheFetchErrorEvent = CacheEventBase<"cache:fetch:error", {
|
|
112
|
-
|
|
113
|
-
|
|
66
|
+
error: Error;
|
|
67
|
+
attempt: number;
|
|
114
68
|
}>;
|
|
115
69
|
type CacheDataEvictEvent = CacheEventBase<"cache:data:evict", {
|
|
116
|
-
|
|
70
|
+
reason?: string;
|
|
117
71
|
}>;
|
|
118
72
|
type CacheDataInvalidateEvent = CacheEventBase<"cache:data:invalidate">;
|
|
119
73
|
type CacheDataSetEvent<T = any> = CacheEventBase<"cache:data:set", {
|
|
120
|
-
|
|
121
|
-
|
|
74
|
+
newData: T;
|
|
75
|
+
oldData?: T;
|
|
122
76
|
}>;
|
|
123
77
|
type CachePersistenceLoadSuccessEvent = CacheEventBase<"cache:persistence:load:success", {
|
|
124
|
-
|
|
78
|
+
message?: string;
|
|
125
79
|
}>;
|
|
126
80
|
type CachePersistenceLoadErrorEvent = CacheEventBase<"cache:persistence:load:error", {
|
|
127
|
-
|
|
128
|
-
|
|
81
|
+
message?: string;
|
|
82
|
+
error?: any;
|
|
129
83
|
}>;
|
|
130
84
|
type CachePersistenceSaveSuccessEvent = CacheEventBase<"cache:persistence:save:success">;
|
|
131
85
|
type CachePersistenceSaveErrorEvent = CacheEventBase<"cache:persistence:save:error", {
|
|
132
|
-
|
|
133
|
-
|
|
86
|
+
message?: string;
|
|
87
|
+
error?: any;
|
|
134
88
|
}>;
|
|
135
89
|
type CachePersistenceClearSuccessEvent = CacheEventBase<"cache:persistence:clear:success">;
|
|
136
90
|
type CachePersistenceClearErrorEvent = CacheEventBase<"cache:persistence:clear:error", {
|
|
137
|
-
|
|
138
|
-
|
|
91
|
+
message?: string;
|
|
92
|
+
error?: any;
|
|
139
93
|
}>;
|
|
140
94
|
type CachePersistenceSyncEvent = CacheEventBase<"cache:persistence:sync", {
|
|
141
|
-
|
|
95
|
+
message?: string;
|
|
142
96
|
}>;
|
|
143
97
|
type CacheEvent = CacheReadHitEvent | CacheReadMissEvent | CacheFetchStartEvent | CacheFetchSuccessEvent | CacheFetchErrorEvent | CacheDataEvictEvent | CacheDataInvalidateEvent | CacheDataSetEvent | CachePersistenceLoadSuccessEvent | CachePersistenceLoadErrorEvent | CachePersistenceSaveSuccessEvent | CachePersistenceSaveErrorEvent | CachePersistenceClearSuccessEvent | CachePersistenceClearErrorEvent | CachePersistenceSyncEvent;
|
|
144
98
|
type CacheEventType = CacheEvent["type"];
|
|
145
|
-
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/cache/cache.d.ts
|
|
146
101
|
declare class QueryCache {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
102
|
+
private cache;
|
|
103
|
+
private queries;
|
|
104
|
+
private fetching;
|
|
105
|
+
private readonly defaultOptions;
|
|
106
|
+
private metrics;
|
|
107
|
+
private eventBus;
|
|
108
|
+
private gcTimer?;
|
|
109
|
+
private readonly persistenceId;
|
|
110
|
+
private persistenceUnsubscribe?;
|
|
111
|
+
private persistenceDebounceTimer?;
|
|
112
|
+
private isHandlingRemoteUpdate;
|
|
113
|
+
constructor(defaultOptions?: CacheOptions);
|
|
114
|
+
private initializePersistence;
|
|
115
|
+
private serializeCache;
|
|
116
|
+
private deserializeAndLoadCache;
|
|
117
|
+
private schedulePersistState;
|
|
118
|
+
private handleRemoteStateChange;
|
|
119
|
+
registerQuery<T>(key: string, fetchFunction: () => Promise<T>, options?: CacheOptions): void;
|
|
120
|
+
get<T>(key: string, options?: {
|
|
121
|
+
waitForFresh?: boolean;
|
|
122
|
+
throwOnError?: boolean;
|
|
123
|
+
}): Promise<T | undefined>;
|
|
124
|
+
peek<T>(key: string): T | undefined;
|
|
125
|
+
has(key: string): boolean;
|
|
126
|
+
private fetch;
|
|
127
|
+
private fetchAndWait;
|
|
128
|
+
private performFetchWithRetry;
|
|
129
|
+
private isStale;
|
|
130
|
+
invalidate(key: string, refetch?: boolean): Promise<void>;
|
|
131
|
+
invalidatePattern(pattern: RegExp, refetch?: boolean): Promise<void>;
|
|
132
|
+
prefetch(key: string): Promise<void>;
|
|
133
|
+
refresh<T>(key: string): Promise<T | undefined>;
|
|
134
|
+
setData<T>(key: string, data: T): void;
|
|
135
|
+
remove(key: string): boolean;
|
|
136
|
+
private enforceSizeLimit;
|
|
137
|
+
private startGarbageCollection;
|
|
138
|
+
garbageCollect(): number;
|
|
139
|
+
getStats(): {
|
|
140
|
+
size: number;
|
|
141
|
+
metrics: CacheMetrics;
|
|
142
|
+
hitRate: number;
|
|
143
|
+
staleHitRate: number;
|
|
144
|
+
entries: Array<{
|
|
145
|
+
key: string;
|
|
146
|
+
lastAccessed: number;
|
|
147
|
+
lastUpdated: number;
|
|
148
|
+
accessCount: number;
|
|
149
|
+
isStale: boolean;
|
|
150
|
+
isLoading?: boolean;
|
|
151
|
+
error?: boolean;
|
|
152
|
+
}>;
|
|
153
|
+
};
|
|
154
|
+
on<EType extends CacheEventType>(event: EType, listener: (ev: Extract<CacheEvent, {
|
|
155
|
+
type: EType;
|
|
156
|
+
}>) => void): () => void;
|
|
157
|
+
private emitEvent;
|
|
158
|
+
private updateMetrics;
|
|
159
|
+
private delay;
|
|
160
|
+
clear(): Promise<void>;
|
|
161
|
+
destroy(): void;
|
|
207
162
|
}
|
|
208
|
-
|
|
209
|
-
export {
|
|
163
|
+
//#endregion
|
|
164
|
+
export { CacheDataEvictEvent, CacheDataInvalidateEvent, CacheDataSetEvent, CacheEntry, CacheEvent, CacheEventBase, CacheEventType, CacheFetchErrorEvent, CacheFetchStartEvent, CacheFetchSuccessEvent, CacheMetrics, CacheOptions, CachePersistenceClearErrorEvent, CachePersistenceClearSuccessEvent, CachePersistenceLoadErrorEvent, CachePersistenceLoadSuccessEvent, CachePersistenceSaveErrorEvent, CachePersistenceSaveSuccessEvent, CachePersistenceSyncEvent, CacheReadHitEvent, CacheReadMissEvent, QueryCache, QueryConfig, SerializableCacheEntry, SerializableCacheState };
|
package/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e,t,s=require("uuid"),i=Object.create,a=Object.defineProperty,r=Object.getOwnPropertyDescriptor,c=Object.getOwnPropertyNames,n=Object.getPrototypeOf,o=Object.prototype.hasOwnProperty,h=(e={"node_modules/.bun/@asaidimu+events@1.1.2/node_modules/@asaidimu/events/index.js"(e,t){var s,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,c=Object.prototype.hasOwnProperty,n={};((e,t)=>{for(var s in t)i(e,s,{get:t[s],enumerable:!0})})(n,{createEventBus:()=>o}),t.exports=(s=n,((e,t,s,n)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let o of r(t))c.call(e,o)||o===s||i(e,o,{get:()=>t[o],enumerable:!(n=a(t,o))||n.enumerable});return e})(i({},"__esModule",{value:!0}),s));var o=(e={async:!1,batchSize:1e3,batchDelay:16,errorHandler:e=>console.error("EventBus Error:",e),crossTab:!1,channelName:"event-bus-channel"})=>{const t=new Map;let s=[],i=0,a=0;const r=new Map,c=new Map;let n=null;e.crossTab&&"undefined"!=typeof BroadcastChannel?n=new BroadcastChannel(e.channelName):e.crossTab&&console.warn("BroadcastChannel is not supported in this browser. Cross-tab notifications are disabled.");const o=(e,t)=>{i++,a+=t,r.set(e,(r.get(e)||0)+1)},h=()=>{const t=s;s=[],t.forEach(({name:t,payload:s})=>{const i=performance.now();try{(c.get(t)||[]).forEach(e=>e(s))}catch(i){e.errorHandler({...i,eventName:t,payload:s})}o(t,performance.now()-i)})},d=(()=>{let t;return()=>{clearTimeout(t),t=setTimeout(h,e.batchDelay)}})(),l=e=>{const s=t.get(e);s?c.set(e,Array.from(s)):c.delete(e)};return n&&(n.onmessage=e=>{const{name:t,payload:s}=e.data;(c.get(t)||[]).forEach(e=>e(s))}),{subscribe:(e,s)=>{t.has(e)||t.set(e,new Set);const i=t.get(e);return i.add(s),l(e),()=>{i.delete(s),0===i.size?(t.delete(e),c.delete(e)):l(e)}},emit:({name:t,payload:i})=>{if(e.async)return s.push({name:t,payload:i}),s.length>=e.batchSize?h():d(),void(n&&n.postMessage({name:t,payload:i}));const a=performance.now();try{(c.get(t)||[]).forEach(e=>e(i)),n&&n.postMessage({name:t,payload:i})}catch(s){e.errorHandler({...s,eventName:t,payload:i})}o(t,performance.now()-a)},getMetrics:()=>({totalEvents:i,activeSubscriptions:Array.from(t.values()).reduce((e,t)=>e+t.size,0),eventCounts:r,averageEmitDuration:i>0?a/i:0}),clear:()=>{t.clear(),c.clear(),s=[],i=0,a=0,r.clear(),n&&(n.close(),n=null)}}}}},function(){return t||(0,e[c(e)[0]])((t={exports:{}}).exports,t),t.exports}),d=((e,t,s)=>(s=null!=e?i(n(e)):{},((e,t,s,i)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let n of c(t))o.call(e,n)||n===s||a(e,n,{get:()=>t[n],enumerable:!(i=r(t,n))||i.enumerable});return e})(e&&e.__esModule?s:a(s,"default",{value:e,enumerable:!0}),e)))(h());exports.QueryCache=class{cache=new Map;queries=new Map;fetching=new Map;defaultOptions;metrics;eventBus;gcTimer;persistenceId;persistenceUnsubscribe;persistenceDebounceTimer;isHandlingRemoteUpdate=!1;constructor(e={}){void 0!==e.staleTime&&e.staleTime<0&&(console.warn("CacheOptions: staleTime should be non-negative. Using 0."),e.staleTime=0),void 0!==e.cacheTime&&e.cacheTime<0&&(console.warn("CacheOptions: cacheTime should be non-negative. Using 0."),e.cacheTime=0),void 0!==e.retryAttempts&&e.retryAttempts<0&&(console.warn("CacheOptions: retryAttempts should be non-negative. Using 0."),e.retryAttempts=0),void 0!==e.retryDelay&&e.retryDelay<0&&(console.warn("CacheOptions: retryDelay should be non-negative. Using 0."),e.retryDelay=0),void 0!==e.maxSize&&e.maxSize<0&&(console.warn("CacheOptions: maxSize should be non-negative. Using 0."),e.maxSize=0),this.defaultOptions={staleTime:0,cacheTime:18e5,retryAttempts:3,retryDelay:1e3,maxSize:1e3,enableMetrics:!0,persistence:void 0,persistenceId:void 0,serializeValue:e=>e,deserializeValue:e=>e,persistenceDebounceTime:500,...e},this.metrics={hits:0,misses:0,fetches:0,errors:0,evictions:0,staleHits:0},this.persistenceId=this.defaultOptions.persistenceId||s.v4(),this.eventBus=(0,d.createEventBus)(),this.startGarbageCollection(),this.initializePersistence()}async initializePersistence(){const{persistence:e}=this.defaultOptions;if(e){try{const t=await e.get();t&&(this.deserializeAndLoadCache(t),this.emitEvent({type:"cache:persistence:load:success",key:this.persistenceId,timestamp:Date.now(),message:`Cache loaded for ID: ${this.persistenceId}`}))}catch(e){console.error(`Cache (${this.persistenceId}): Failed to load state from persistence:`,e),this.emitEvent({type:"cache:persistence:load:error",key:this.persistenceId,timestamp:Date.now(),error:e,message:`Failed to load cache for ID: ${this.persistenceId}`})}if("function"==typeof e.subscribe)try{this.persistenceUnsubscribe=e.subscribe(this.persistenceId,e=>{this.handleRemoteStateChange(e)})}catch(e){console.error(`Cache (${this.persistenceId}): Failed to subscribe to persistence:`,e)}}}serializeCache(){const e=[],{serializeValue:t}=this.defaultOptions;for(const[s,i]of this.cache)i.isLoading&&void 0===i.data&&0===i.lastUpdated||e.push([s,{data:t(i.data),lastUpdated:i.lastUpdated,lastAccessed:i.lastAccessed,accessCount:i.accessCount,error:i.error?{name:i.error.name,message:i.error.message,stack:i.error.stack}:void 0}]);return e}deserializeAndLoadCache(e){this.isHandlingRemoteUpdate=!0;const t=new Map,{deserializeValue:s}=this.defaultOptions;for(const[i,a]of e){let e;a.error&&(e=new Error(a.error.message),e.name=a.error.name,e.stack=a.error.stack),t.set(i,{data:s(a.data),lastUpdated:a.lastUpdated,lastAccessed:a.lastAccessed,accessCount:a.accessCount,error:e,isLoading:!1})}this.cache=t,this.enforceSizeLimit(!1),this.isHandlingRemoteUpdate=!1}schedulePersistState(){this.defaultOptions.persistence&&!this.isHandlingRemoteUpdate&&(this.persistenceDebounceTimer&&clearTimeout(this.persistenceDebounceTimer),this.persistenceDebounceTimer=setTimeout(async()=>{try{const e=this.serializeCache();await this.defaultOptions.persistence.set(this.persistenceId,e),this.emitEvent({type:"cache:persistence:save:success",key:this.persistenceId,timestamp:Date.now()})}catch(e){console.error(`Cache (${this.persistenceId}): Failed to persist state:`,e),this.emitEvent({type:"cache:persistence:save:error",key:this.persistenceId,timestamp:Date.now(),error:e})}},this.defaultOptions.persistenceDebounceTime))}handleRemoteStateChange(e){if(this.isHandlingRemoteUpdate||!e)return;this.isHandlingRemoteUpdate=!0;const{deserializeValue:t}=this.defaultOptions,s=new Map;let i=!1;for(const[a,r]of e){let e;r.error&&(e=new Error(r.error.message),e.name=r.error.name,e.stack=r.error.stack);const c={data:t(r.data),lastUpdated:r.lastUpdated,lastAccessed:r.lastAccessed,accessCount:r.accessCount,error:e,isLoading:!1};s.set(a,c);const n=this.cache.get(a);(!n||n.lastUpdated<c.lastUpdated||JSON.stringify(n.data)!==JSON.stringify(c.data))&&(i=!0)}this.cache.size!==s.size&&(i=!0),i&&(this.cache=s,this.enforceSizeLimit(!1),this.emitEvent({type:"cache:persistence:sync",key:this.persistenceId,timestamp:Date.now(),message:"Cache updated from remote state."})),this.isHandlingRemoteUpdate=!1}registerQuery(e,t,s={}){void 0!==s.staleTime&&s.staleTime<0&&(s.staleTime=0),void 0!==s.cacheTime&&s.cacheTime<0&&(s.cacheTime=0),this.queries.set(e,{fetchFunction:t,options:{...this.defaultOptions,...s}})}async get(e,t){const s=this.queries.get(e);if(!s)throw new Error(`No query registered for key: ${e}`);let i=this.cache.get(e);const a=this.isStale(i,s.options);let r=!1;if(i)i.lastAccessed=Date.now(),i.accessCount++,this.updateMetrics("hits"),a&&this.updateMetrics("staleHits"),this.emitEvent({type:"cache:read:hit",key:e,timestamp:Date.now(),data:i.data,isStale:a});else if(this.updateMetrics("misses"),this.emitEvent({type:"cache:read:miss",key:e,timestamp:Date.now()}),!t?.waitForFresh||a){const t={data:void 0,lastUpdated:0,lastAccessed:Date.now(),accessCount:1,isLoading:!0,error:void 0};this.cache.set(e,t),i=t,r=!0}if(t?.waitForFresh&&(!i||a||i.isLoading))try{const t=await this.fetchAndWait(e,s);return r&&this.cache.get(e)===i&&this.schedulePersistState(),t}catch(s){if(t.throwOnError)throw s;return this.cache.get(e)?.data}if(!i||a||i&&!i.isLoading&&0===i.lastUpdated&&!i.error){if(i&&!i.isLoading)i.isLoading=!0;else if(!i){const t={data:void 0,lastUpdated:0,lastAccessed:Date.now(),accessCount:0,isLoading:!0,error:void 0};this.cache.set(e,t),i=t,r=!0}this.fetch(e,s).catch(()=>{})}if(r&&this.schedulePersistState(),i?.error&&t?.throwOnError)throw i.error;return i?.data}peek(e){const t=this.cache.get(e);return t&&(t.lastAccessed=Date.now(),t.accessCount++),t?.data}has(e){const t=this.cache.get(e),s=this.queries.get(e);return!(!t||!s)&&(!this.isStale(t,s.options)&&!t.isLoading)}async fetch(e,t){if(this.fetching.has(e))return this.fetching.get(e);let s=this.cache.get(e);s?s.isLoading||(s.isLoading=!0,s.error=void 0):(s={data:void 0,lastUpdated:0,lastAccessed:Date.now(),accessCount:0,isLoading:!0,error:void 0},this.cache.set(e,s),this.schedulePersistState());const i=this.performFetchWithRetry(e,t,s);this.fetching.set(e,i);try{return await i}finally{this.fetching.delete(e)}}async fetchAndWait(e,t){const s=this.fetching.get(e);if(s)return s;const i=await this.fetch(e,t);if(void 0===i){const t=this.cache.get(e);if(t?.error)throw t.error;throw new Error(`Failed to fetch data for key: ${e} after retries.`)}return i}async performFetchWithRetry(e,t,s){const{retryAttempts:i,retryDelay:a}=t.options;let r;s.isLoading=!0;for(let c=0;c<=i;c++)try{this.emitEvent({type:"cache:fetch:start",key:e,timestamp:Date.now(),attempt:c}),this.updateMetrics("fetches");const i=await t.fetchFunction();return s.data=i,s.lastUpdated=Date.now(),s.isLoading=!1,s.error=void 0,this.cache.set(e,s),this.schedulePersistState(),this.enforceSizeLimit(),this.emitEvent({type:"cache:fetch:success",key:e,timestamp:Date.now(),data:i}),i}catch(t){r=t,this.updateMetrics("errors"),this.emitEvent({type:"cache:fetch:error",key:e,timestamp:Date.now(),error:r,attempt:c}),c<i&&await this.delay(a*Math.pow(2,c))}s.error=r,s.isLoading=!1,this.cache.set(e,s),this.schedulePersistState()}isStale(e,t){if(!e||e.error)return!0;if(e.isLoading&&!e.data)return!0;const{staleTime:s}=t;return 0!==s&&s!==1/0&&Date.now()-e.lastUpdated>s}async invalidate(e,t=!0){const s=this.cache.get(e),i=this.queries.get(e);let a=!1;s&&(a=0!==s.lastUpdated||void 0!==s.error,s.lastUpdated=0,s.error=void 0,this.emitEvent({type:"cache:data:invalidate",key:e,timestamp:Date.now()}),a&&this.schedulePersistState(),t&&i&&this.fetch(e,i).catch(()=>{}))}async invalidatePattern(e,t=!0){const s=[];let i=!1;for(const t of this.cache.keys())e.test(t)&&s.push(t);s.forEach(e=>{const t=this.cache.get(e);t&&(0===t.lastUpdated&&void 0===t.error||(i=!0),t.lastUpdated=0,t.error=void 0,this.emitEvent({type:"cache:data:invalidate",key:e,timestamp:Date.now()}))}),i&&this.schedulePersistState(),t&&s.length>0&&await Promise.all(s.map(e=>{const t=this.queries.get(e);return t?this.fetch(e,t).catch(()=>{}):Promise.resolve()}))}async prefetch(e){const t=this.queries.get(e);if(!t)return void console.warn(`Cannot prefetch: No query registered for key: ${e}`);const s=this.cache.get(e);s&&!this.isStale(s,t.options)||this.fetch(e,t).catch(()=>{})}async refresh(e){const t=this.queries.get(e);if(!t)return void console.warn(`Cannot refresh: No query registered for key: ${e}`);this.fetching.delete(e);let s=this.cache.get(e);s?(s.isLoading=!0,s.error=void 0):(s={data:void 0,lastUpdated:0,lastAccessed:Date.now(),accessCount:0,isLoading:!0,error:void 0},this.cache.set(e,s),this.schedulePersistState());const i=this.performFetchWithRetry(e,t,s);this.fetching.set(e,i);try{return await i}finally{this.fetching.delete(e)}}setData(e,t){const s=this.cache.get(e),i=s?.data,a={data:t,lastUpdated:Date.now(),lastAccessed:Date.now(),accessCount:(s?.accessCount||0)+1,isLoading:!1,error:void 0};this.cache.set(e,a),this.schedulePersistState(),this.enforceSizeLimit(),this.emitEvent({type:"cache:data:set",key:e,timestamp:Date.now(),newData:t,oldData:i})}remove(e){this.fetching.delete(e);const t=this.cache.has(e),s=this.cache.delete(e);return s&&t&&this.schedulePersistState(),s}enforceSizeLimit(e=!0){const{maxSize:t}=this.defaultOptions;if(t===1/0||this.cache.size<=t)return;let s=0;if(0===t){s=this.cache.size;for(const e of this.cache.keys())this.cache.delete(e),this.emitEvent({type:"cache:data:evict",key:e,timestamp:Date.now(),reason:"size_limit_zero"}),this.updateMetrics("evictions")}else{const e=Array.from(this.cache.entries()).sort(([,e],[,t])=>e.lastAccessed-t.lastAccessed),i=this.cache.size-t;if(i>0){e.slice(0,i).forEach(([e])=>{this.cache.delete(e)&&(s++,this.emitEvent({type:"cache:data:evict",key:e,timestamp:Date.now(),reason:"size_limit_lru"}),this.updateMetrics("evictions"))})}}s>0&&e&&this.schedulePersistState()}startGarbageCollection(){const{cacheTime:e}=this.defaultOptions;if(e===1/0||e<=0)return;const t=Math.max(1e3,Math.min(e/4,3e5));this.gcTimer=setInterval(()=>this.garbageCollect(),t)}garbageCollect(){const e=Date.now();let t=0;const s=[];for(const[t,i]of this.cache){if(i.isLoading)continue;const a=this.queries.get(t),r=a?.options.cacheTime??this.defaultOptions.cacheTime;r===1/0||r<=0||e-i.lastAccessed>r&&s.push(t)}return s.length>0&&(s.forEach(e=>{this.cache.delete(e)&&(this.fetching.delete(e),this.emitEvent({type:"cache:data:evict",key:e,timestamp:Date.now(),reason:"garbage_collected_idle"}),this.updateMetrics("evictions"),t++)}),this.schedulePersistState()),t}getStats(){const e=this.metrics.hits+this.metrics.misses,t=e>0?this.metrics.hits/e:0,s=this.metrics.hits>0?this.metrics.staleHits/this.metrics.hits:0,i=Array.from(this.cache.entries()).map(([e,t])=>{const s=this.queries.get(e),i=!s||this.isStale(t,s.options);return{key:e,lastAccessed:t.lastAccessed,lastUpdated:t.lastUpdated,accessCount:t.accessCount,isStale:i,isLoading:t.isLoading,error:!!t.error}});return{size:this.cache.size,metrics:{...this.metrics},hitRate:t,staleHitRate:s,entries:i}}on(e,t){return this.eventBus.subscribe(e,t)}emitEvent(e){this.eventBus.emit({name:e.type,payload:e})}updateMetrics(e,t=1){this.defaultOptions.enableMetrics&&(this.metrics[e]=(this.metrics[e]||0)+t)}delay(e){return new Promise(t=>setTimeout(t,e))}async clear(){const e=this.cache.size>0;if(this.cache.clear(),this.fetching.clear(),this.defaultOptions.enableMetrics&&(this.metrics={hits:0,misses:0,fetches:0,errors:0,evictions:0,staleHits:0}),this.defaultOptions.persistence)try{await this.defaultOptions.persistence.clear(),this.emitEvent({type:"cache:persistence:clear:success",key:this.persistenceId,timestamp:Date.now()})}catch(e){console.error(`Cache (${this.persistenceId}): Failed to clear persisted state:`,e),this.emitEvent({type:"cache:persistence:clear:error",key:this.persistenceId,timestamp:Date.now(),error:e})}else e&&this.schedulePersistState()}destroy(){if(this.gcTimer&&(clearInterval(this.gcTimer),this.gcTimer=void 0),this.persistenceDebounceTimer&&clearTimeout(this.persistenceDebounceTimer),this.persistenceUnsubscribe)try{this.persistenceUnsubscribe()}catch(e){console.error(`Cache (${this.persistenceId}): Error unsubscribing persistence:`,e)}this.cache.clear(),this.fetching.clear(),this.queries.clear(),this.eventBus.clear(),this.metrics={hits:0,misses:0,fetches:0,errors:0,evictions:0,staleHits:0}}};
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports);let t=require("uuid");var n=e(((e,t)=>{var n=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.prototype.hasOwnProperty,o=(e,t)=>{for(var r in t)n(e,r,{get:t[r],enumerable:!0})},s=(e,t,o,s)=>{if(t&&typeof t==`object`||typeof t==`function`)for(let c of i(t))!a.call(e,c)&&c!==o&&n(e,c,{get:()=>t[c],enumerable:!(s=r(t,c))||s.enumerable});return e},c=e=>s(n({},`__esModule`,{value:!0}),e),l={};o(l,{createEventBus:()=>u}),t.exports=c(l);var u=(e={async:!1,batchSize:1e3,batchDelay:16,errorHandler:e=>console.error(`EventBus Error:`,e),crossTab:!1,channelName:`event-bus-channel`})=>{let t=new Map,n=[],r=0,i=0,a=new Map,o=new Map,s=null;e.crossTab&&typeof BroadcastChannel<`u`?s=new BroadcastChannel(e.channelName):e.crossTab&&console.warn(`BroadcastChannel is not supported in this browser. Cross-tab notifications are disabled.`);let c=(e,t)=>{r++,i+=t,a.set(e,(a.get(e)||0)+1)},l=()=>{let t=n;n=[],t.forEach(({name:t,payload:n})=>{let r=performance.now();try{(o.get(t)||[]).forEach(e=>e(n))}catch(r){e.errorHandler({...r,eventName:t,payload:n})}c(t,performance.now()-r)})},u=(()=>{let t;return()=>{clearTimeout(t),t=setTimeout(l,e.batchDelay)}})(),d=e=>{let n=t.get(e);n?o.set(e,Array.from(n)):o.delete(e)};return s&&(s.onmessage=e=>{let{name:t,payload:n}=e.data;(o.get(t)||[]).forEach(e=>e(n))}),{subscribe:(e,n)=>{t.has(e)||t.set(e,new Set);let r=t.get(e);return r.add(n),d(e),()=>{r.delete(n),r.size===0?(t.delete(e),o.delete(e)):d(e)}},emit:({name:t,payload:r})=>{if(e.async){n.push({name:t,payload:r}),n.length>=e.batchSize?l():u(),s&&s.postMessage({name:t,payload:r});return}let i=performance.now();try{(o.get(t)||[]).forEach(e=>e(r)),s&&s.postMessage({name:t,payload:r})}catch(n){e.errorHandler({...n,eventName:t,payload:r})}c(t,performance.now()-i)},getMetrics:()=>({totalEvents:r,activeSubscriptions:Array.from(t.values()).reduce((e,t)=>e+t.size,0),eventCounts:a,averageEmitDuration:r>0?i/r:0}),clear:()=>{t.clear(),o.clear(),n=[],r=0,i=0,a.clear(),s&&=(s.close(),null)}}};0&&(t.exports={createEventBus:u})}))(),r=class{cache=new Map;queries=new Map;fetching=new Map;defaultOptions;metrics;eventBus;gcTimer;persistenceId;persistenceUnsubscribe;persistenceDebounceTimer;isHandlingRemoteUpdate=!1;constructor(e={}){e.staleTime!==void 0&&e.staleTime<0&&(console.warn(`CacheOptions: staleTime should be non-negative. Using 0.`),e.staleTime=0),e.cacheTime!==void 0&&e.cacheTime<0&&(console.warn(`CacheOptions: cacheTime should be non-negative. Using 0.`),e.cacheTime=0),e.retryAttempts!==void 0&&e.retryAttempts<0&&(console.warn(`CacheOptions: retryAttempts should be non-negative. Using 0.`),e.retryAttempts=0),e.retryDelay!==void 0&&e.retryDelay<0&&(console.warn(`CacheOptions: retryDelay should be non-negative. Using 0.`),e.retryDelay=0),e.maxSize!==void 0&&e.maxSize<0&&(console.warn(`CacheOptions: maxSize should be non-negative. Using 0.`),e.maxSize=0),this.defaultOptions={staleTime:0,cacheTime:1800*1e3,retryAttempts:3,retryDelay:1e3,maxSize:1e3,enableMetrics:!0,persistence:void 0,persistenceId:void 0,serializeValue:e=>e,deserializeValue:e=>e,persistenceDebounceTime:500,...e},this.metrics={hits:0,misses:0,fetches:0,errors:0,evictions:0,staleHits:0},this.persistenceId=this.defaultOptions.persistenceId||(0,t.v4)(),this.eventBus=(0,n.createEventBus)(),this.startGarbageCollection(),this.initializePersistence()}async initializePersistence(){let{persistence:e}=this.defaultOptions;if(e){try{let t=await e.get();t&&(this.deserializeAndLoadCache(t),this.emitEvent({type:`cache:persistence:load:success`,key:this.persistenceId,timestamp:Date.now(),message:`Cache loaded for ID: ${this.persistenceId}`}))}catch(e){console.error(`Cache (${this.persistenceId}): Failed to load state from persistence:`,e),this.emitEvent({type:`cache:persistence:load:error`,key:this.persistenceId,timestamp:Date.now(),error:e,message:`Failed to load cache for ID: ${this.persistenceId}`})}if(typeof e.subscribe==`function`)try{this.persistenceUnsubscribe=e.subscribe(this.persistenceId,e=>{this.handleRemoteStateChange(e)})}catch(e){console.error(`Cache (${this.persistenceId}): Failed to subscribe to persistence:`,e)}}}serializeCache(){let e=[],{serializeValue:t}=this.defaultOptions;for(let[n,r]of this.cache)r.isLoading&&r.data===void 0&&r.lastUpdated===0||e.push([n,{data:t(r.data),lastUpdated:r.lastUpdated,lastAccessed:r.lastAccessed,accessCount:r.accessCount,error:r.error?{name:r.error.name,message:r.error.message,stack:r.error.stack}:void 0}]);return e}deserializeAndLoadCache(e){this.isHandlingRemoteUpdate=!0;let t=new Map,{deserializeValue:n}=this.defaultOptions;for(let[r,i]of e){let e;i.error&&(e=Error(i.error.message),e.name=i.error.name,e.stack=i.error.stack),t.set(r,{data:n(i.data),lastUpdated:i.lastUpdated,lastAccessed:i.lastAccessed,accessCount:i.accessCount,error:e,isLoading:!1})}this.cache=t,this.enforceSizeLimit(!1),this.isHandlingRemoteUpdate=!1}schedulePersistState(){!this.defaultOptions.persistence||this.isHandlingRemoteUpdate||(this.persistenceDebounceTimer&&clearTimeout(this.persistenceDebounceTimer),this.persistenceDebounceTimer=setTimeout(async()=>{try{let e=this.serializeCache();await this.defaultOptions.persistence.set(this.persistenceId,e),this.emitEvent({type:`cache:persistence:save:success`,key:this.persistenceId,timestamp:Date.now()})}catch(e){console.error(`Cache (${this.persistenceId}): Failed to persist state:`,e),this.emitEvent({type:`cache:persistence:save:error`,key:this.persistenceId,timestamp:Date.now(),error:e})}},this.defaultOptions.persistenceDebounceTime))}handleRemoteStateChange(e){if(this.isHandlingRemoteUpdate||!e)return;this.isHandlingRemoteUpdate=!0;let{deserializeValue:t}=this.defaultOptions,n=new Map,r=!1;for(let[i,a]of e){let e;a.error&&(e=Error(a.error.message),e.name=a.error.name,e.stack=a.error.stack);let o={data:t(a.data),lastUpdated:a.lastUpdated,lastAccessed:a.lastAccessed,accessCount:a.accessCount,error:e,isLoading:!1};n.set(i,o);let s=this.cache.get(i);(!s||s.lastUpdated<o.lastUpdated||JSON.stringify(s.data)!==JSON.stringify(o.data))&&(r=!0)}this.cache.size!==n.size&&(r=!0),r&&(this.cache=n,this.enforceSizeLimit(!1),this.emitEvent({type:`cache:persistence:sync`,key:this.persistenceId,timestamp:Date.now(),message:`Cache updated from remote state.`})),this.isHandlingRemoteUpdate=!1}registerQuery(e,t,n={}){n.staleTime!==void 0&&n.staleTime<0&&(n.staleTime=0),n.cacheTime!==void 0&&n.cacheTime<0&&(n.cacheTime=0),this.queries.set(e,{fetchFunction:t,options:{...this.defaultOptions,...n}})}async get(e,t){let n=this.queries.get(e);if(!n)throw Error(`No query registered for key: ${e}`);let r=this.cache.get(e),i=this.isStale(r,n.options),a=!1;if(r)r.lastAccessed=Date.now(),r.accessCount++,this.updateMetrics(`hits`),i&&this.updateMetrics(`staleHits`),this.emitEvent({type:`cache:read:hit`,key:e,timestamp:Date.now(),data:r.data,isStale:i});else if(this.updateMetrics(`misses`),this.emitEvent({type:`cache:read:miss`,key:e,timestamp:Date.now()}),!t?.waitForFresh||i){let t={data:void 0,lastUpdated:0,lastAccessed:Date.now(),accessCount:1,isLoading:!0,error:void 0};this.cache.set(e,t),r=t,a=!0}if(t?.waitForFresh&&(!r||i||r.isLoading))try{let t=await this.fetchAndWait(e,n);return a&&this.cache.get(e)===r&&this.schedulePersistState(),t}catch(n){if(t.throwOnError)throw n;return this.cache.get(e)?.data}if(!r||i||r&&!r.isLoading&&r.lastUpdated===0&&!r.error){if(r&&!r.isLoading)r.isLoading=!0;else if(!r){let t={data:void 0,lastUpdated:0,lastAccessed:Date.now(),accessCount:0,isLoading:!0,error:void 0};this.cache.set(e,t),r=t,a=!0}this.fetch(e,n).catch(()=>{})}if(a&&this.schedulePersistState(),r?.error&&t?.throwOnError)throw r.error;return r?.data}peek(e){let t=this.cache.get(e);return t&&(t.lastAccessed=Date.now(),t.accessCount++),t?.data}has(e){let t=this.cache.get(e),n=this.queries.get(e);return!t||!n?!1:!this.isStale(t,n.options)&&!t.isLoading}async fetch(e,t){if(this.fetching.has(e))return this.fetching.get(e);let n=this.cache.get(e);n?n.isLoading||(n.isLoading=!0,n.error=void 0):(n={data:void 0,lastUpdated:0,lastAccessed:Date.now(),accessCount:0,isLoading:!0,error:void 0},this.cache.set(e,n),this.schedulePersistState());let r=this.performFetchWithRetry(e,t,n);this.fetching.set(e,r);try{return await r}finally{this.fetching.delete(e)}}async fetchAndWait(e,t){let n=this.fetching.get(e);if(n)return n;let r=await this.fetch(e,t);if(r===void 0){let t=this.cache.get(e);throw t?.error?t.error:Error(`Failed to fetch data for key: ${e} after retries.`)}return r}async performFetchWithRetry(e,t,n){let{retryAttempts:r,retryDelay:i}=t.options,a;n.isLoading=!0;for(let o=0;o<=r;o++)try{this.emitEvent({type:`cache:fetch:start`,key:e,timestamp:Date.now(),attempt:o}),this.updateMetrics(`fetches`);let r=await t.fetchFunction();return n.data=r,n.lastUpdated=Date.now(),n.isLoading=!1,n.error=void 0,this.cache.set(e,n),this.schedulePersistState(),this.enforceSizeLimit(),this.emitEvent({type:`cache:fetch:success`,key:e,timestamp:Date.now(),data:r}),r}catch(t){a=t,this.updateMetrics(`errors`),this.emitEvent({type:`cache:fetch:error`,key:e,timestamp:Date.now(),error:a,attempt:o}),o<r&&await this.delay(i*2**o)}n.error=a,n.isLoading=!1,this.cache.set(e,n),this.schedulePersistState()}isStale(e,t){if(!e||e.error||e.isLoading&&!e.data)return!0;let{staleTime:n}=t;return n===0||n===1/0?!1:Date.now()-e.lastUpdated>n}async invalidate(e,t=!0){let n=this.cache.get(e),r=this.queries.get(e),i=!1;n&&(i=n.lastUpdated!==0||n.error!==void 0,n.lastUpdated=0,n.error=void 0,this.emitEvent({type:`cache:data:invalidate`,key:e,timestamp:Date.now()}),i&&this.schedulePersistState(),t&&r&&this.fetch(e,r).catch(()=>{}))}async invalidatePattern(e,t=!0){let n=[],r=!1;for(let t of this.cache.keys())e.test(t)&&n.push(t);n.forEach(e=>{let t=this.cache.get(e);t&&((t.lastUpdated!==0||t.error!==void 0)&&(r=!0),t.lastUpdated=0,t.error=void 0,this.emitEvent({type:`cache:data:invalidate`,key:e,timestamp:Date.now()}))}),r&&this.schedulePersistState(),t&&n.length>0&&await Promise.all(n.map(e=>{let t=this.queries.get(e);return t?this.fetch(e,t).catch(()=>{}):Promise.resolve()}))}async prefetch(e){let t=this.queries.get(e);if(!t){console.warn(`Cannot prefetch: No query registered for key: ${e}`);return}let n=this.cache.get(e);(!n||this.isStale(n,t.options))&&this.fetch(e,t).catch(()=>{})}async refresh(e){let t=this.queries.get(e);if(!t){console.warn(`Cannot refresh: No query registered for key: ${e}`);return}this.fetching.delete(e);let n=this.cache.get(e);n?(n.isLoading=!0,n.error=void 0):(n={data:void 0,lastUpdated:0,lastAccessed:Date.now(),accessCount:0,isLoading:!0,error:void 0},this.cache.set(e,n),this.schedulePersistState());let r=this.performFetchWithRetry(e,t,n);this.fetching.set(e,r);try{return await r}finally{this.fetching.delete(e)}}setData(e,t){let n=this.cache.get(e),r=n?.data,i={data:t,lastUpdated:Date.now(),lastAccessed:Date.now(),accessCount:(n?.accessCount||0)+1,isLoading:!1,error:void 0};this.cache.set(e,i),this.schedulePersistState(),this.enforceSizeLimit(),this.emitEvent({type:`cache:data:set`,key:e,timestamp:Date.now(),newData:t,oldData:r})}remove(e){this.fetching.delete(e);let t=this.cache.has(e),n=this.cache.delete(e);return n&&t&&this.schedulePersistState(),n}enforceSizeLimit(e=!0){let{maxSize:t}=this.defaultOptions;if(t===1/0||this.cache.size<=t)return;let n=0;if(t===0){n=this.cache.size;for(let e of this.cache.keys())this.cache.delete(e),this.emitEvent({type:`cache:data:evict`,key:e,timestamp:Date.now(),reason:`size_limit_zero`}),this.updateMetrics(`evictions`)}else{let e=Array.from(this.cache.entries()).sort(([,e],[,t])=>e.lastAccessed-t.lastAccessed),r=this.cache.size-t;r>0&&e.slice(0,r).forEach(([e])=>{this.cache.delete(e)&&(n++,this.emitEvent({type:`cache:data:evict`,key:e,timestamp:Date.now(),reason:`size_limit_lru`}),this.updateMetrics(`evictions`))})}n>0&&e&&this.schedulePersistState()}startGarbageCollection(){let{cacheTime:e}=this.defaultOptions;if(e===1/0||e<=0)return;let t=Math.max(1e3,Math.min(e/4,300*1e3));this.gcTimer=setInterval(()=>this.garbageCollect(),t)}garbageCollect(){let e=Date.now(),t=0,n=[];for(let[t,r]of this.cache){if(r.isLoading)continue;let i=this.queries.get(t)?.options.cacheTime??this.defaultOptions.cacheTime;i===1/0||i<=0||e-r.lastAccessed>i&&n.push(t)}return n.length>0&&(n.forEach(e=>{this.cache.delete(e)&&(this.fetching.delete(e),this.emitEvent({type:`cache:data:evict`,key:e,timestamp:Date.now(),reason:`garbage_collected_idle`}),this.updateMetrics(`evictions`),t++)}),this.schedulePersistState()),t}getStats(){let e=this.metrics.hits+this.metrics.misses,t=e>0?this.metrics.hits/e:0,n=this.metrics.hits>0?this.metrics.staleHits/this.metrics.hits:0,r=Array.from(this.cache.entries()).map(([e,t])=>{let n=this.queries.get(e),r=n?this.isStale(t,n.options):!0;return{key:e,lastAccessed:t.lastAccessed,lastUpdated:t.lastUpdated,accessCount:t.accessCount,isStale:r,isLoading:t.isLoading,error:!!t.error}});return{size:this.cache.size,metrics:{...this.metrics},hitRate:t,staleHitRate:n,entries:r}}on(e,t){return this.eventBus.subscribe(e,t)}emitEvent(e){this.eventBus.emit({name:e.type,payload:e})}updateMetrics(e,t=1){this.defaultOptions.enableMetrics&&(this.metrics[e]=(this.metrics[e]||0)+t)}delay(e){return new Promise(t=>setTimeout(t,e))}async clear(){let e=this.cache.size>0;if(this.cache.clear(),this.fetching.clear(),this.defaultOptions.enableMetrics&&(this.metrics={hits:0,misses:0,fetches:0,errors:0,evictions:0,staleHits:0}),this.defaultOptions.persistence)try{await this.defaultOptions.persistence.clear(),this.emitEvent({type:`cache:persistence:clear:success`,key:this.persistenceId,timestamp:Date.now()})}catch(e){console.error(`Cache (${this.persistenceId}): Failed to clear persisted state:`,e),this.emitEvent({type:`cache:persistence:clear:error`,key:this.persistenceId,timestamp:Date.now(),error:e})}else e&&this.schedulePersistState()}destroy(){if(this.gcTimer&&=(clearInterval(this.gcTimer),void 0),this.persistenceDebounceTimer&&clearTimeout(this.persistenceDebounceTimer),this.persistenceUnsubscribe)try{this.persistenceUnsubscribe()}catch(e){console.error(`Cache (${this.persistenceId}): Error unsubscribing persistence:`,e)}this.cache.clear(),this.fetching.clear(),this.queries.clear(),this.eventBus.clear(),this.metrics={hits:0,misses:0,fetches:0,errors:0,evictions:0,staleHits:0}}};exports.QueryCache=r;
|
package/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{v4 as e}from"uuid";var t,s,i=Object.create,a=Object.defineProperty,r=Object.getOwnPropertyDescriptor,c=Object.getOwnPropertyNames,n=Object.getPrototypeOf,o=Object.prototype.hasOwnProperty,h=(t={"node_modules/.bun/@asaidimu+events@1.1.2/node_modules/@asaidimu/events/index.js"(e,t){var s,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,c=Object.prototype.hasOwnProperty,n={};((e,t)=>{for(var s in t)i(e,s,{get:t[s],enumerable:!0})})(n,{createEventBus:()=>o}),t.exports=(s=n,((e,t,s,n)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let o of r(t))c.call(e,o)||o===s||i(e,o,{get:()=>t[o],enumerable:!(n=a(t,o))||n.enumerable});return e})(i({},"__esModule",{value:!0}),s));var o=(e={async:!1,batchSize:1e3,batchDelay:16,errorHandler:e=>console.error("EventBus Error:",e),crossTab:!1,channelName:"event-bus-channel"})=>{const t=new Map;let s=[],i=0,a=0;const r=new Map,c=new Map;let n=null;e.crossTab&&"undefined"!=typeof BroadcastChannel?n=new BroadcastChannel(e.channelName):e.crossTab&&console.warn("BroadcastChannel is not supported in this browser. Cross-tab notifications are disabled.");const o=(e,t)=>{i++,a+=t,r.set(e,(r.get(e)||0)+1)},h=()=>{const t=s;s=[],t.forEach(({name:t,payload:s})=>{const i=performance.now();try{(c.get(t)||[]).forEach(e=>e(s))}catch(i){e.errorHandler({...i,eventName:t,payload:s})}o(t,performance.now()-i)})},d=(()=>{let t;return()=>{clearTimeout(t),t=setTimeout(h,e.batchDelay)}})(),l=e=>{const s=t.get(e);s?c.set(e,Array.from(s)):c.delete(e)};return n&&(n.onmessage=e=>{const{name:t,payload:s}=e.data;(c.get(t)||[]).forEach(e=>e(s))}),{subscribe:(e,s)=>{t.has(e)||t.set(e,new Set);const i=t.get(e);return i.add(s),l(e),()=>{i.delete(s),0===i.size?(t.delete(e),c.delete(e)):l(e)}},emit:({name:t,payload:i})=>{if(e.async)return s.push({name:t,payload:i}),s.length>=e.batchSize?h():d(),void(n&&n.postMessage({name:t,payload:i}));const a=performance.now();try{(c.get(t)||[]).forEach(e=>e(i)),n&&n.postMessage({name:t,payload:i})}catch(s){e.errorHandler({...s,eventName:t,payload:i})}o(t,performance.now()-a)},getMetrics:()=>({totalEvents:i,activeSubscriptions:Array.from(t.values()).reduce((e,t)=>e+t.size,0),eventCounts:r,averageEmitDuration:i>0?a/i:0}),clear:()=>{t.clear(),c.clear(),s=[],i=0,a=0,r.clear(),n&&(n.close(),n=null)}}}}},function(){return s||(0,t[c(t)[0]])((s={exports:{}}).exports,s),s.exports}),d=((e,t,s)=>(s=null!=e?i(n(e)):{},((e,t,s,i)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let n of c(t))o.call(e,n)||n===s||a(e,n,{get:()=>t[n],enumerable:!(i=r(t,n))||i.enumerable});return e})(e&&e.__esModule?s:a(s,"default",{value:e,enumerable:!0}),e)))(h()),l=class{cache=new Map;queries=new Map;fetching=new Map;defaultOptions;metrics;eventBus;gcTimer;persistenceId;persistenceUnsubscribe;persistenceDebounceTimer;isHandlingRemoteUpdate=!1;constructor(t={}){void 0!==t.staleTime&&t.staleTime<0&&(console.warn("CacheOptions: staleTime should be non-negative. Using 0."),t.staleTime=0),void 0!==t.cacheTime&&t.cacheTime<0&&(console.warn("CacheOptions: cacheTime should be non-negative. Using 0."),t.cacheTime=0),void 0!==t.retryAttempts&&t.retryAttempts<0&&(console.warn("CacheOptions: retryAttempts should be non-negative. Using 0."),t.retryAttempts=0),void 0!==t.retryDelay&&t.retryDelay<0&&(console.warn("CacheOptions: retryDelay should be non-negative. Using 0."),t.retryDelay=0),void 0!==t.maxSize&&t.maxSize<0&&(console.warn("CacheOptions: maxSize should be non-negative. Using 0."),t.maxSize=0),this.defaultOptions={staleTime:0,cacheTime:18e5,retryAttempts:3,retryDelay:1e3,maxSize:1e3,enableMetrics:!0,persistence:void 0,persistenceId:void 0,serializeValue:e=>e,deserializeValue:e=>e,persistenceDebounceTime:500,...t},this.metrics={hits:0,misses:0,fetches:0,errors:0,evictions:0,staleHits:0},this.persistenceId=this.defaultOptions.persistenceId||e(),this.eventBus=(0,d.createEventBus)(),this.startGarbageCollection(),this.initializePersistence()}async initializePersistence(){const{persistence:e}=this.defaultOptions;if(e){try{const t=await e.get();t&&(this.deserializeAndLoadCache(t),this.emitEvent({type:"cache:persistence:load:success",key:this.persistenceId,timestamp:Date.now(),message:`Cache loaded for ID: ${this.persistenceId}`}))}catch(e){console.error(`Cache (${this.persistenceId}): Failed to load state from persistence:`,e),this.emitEvent({type:"cache:persistence:load:error",key:this.persistenceId,timestamp:Date.now(),error:e,message:`Failed to load cache for ID: ${this.persistenceId}`})}if("function"==typeof e.subscribe)try{this.persistenceUnsubscribe=e.subscribe(this.persistenceId,e=>{this.handleRemoteStateChange(e)})}catch(e){console.error(`Cache (${this.persistenceId}): Failed to subscribe to persistence:`,e)}}}serializeCache(){const e=[],{serializeValue:t}=this.defaultOptions;for(const[s,i]of this.cache)i.isLoading&&void 0===i.data&&0===i.lastUpdated||e.push([s,{data:t(i.data),lastUpdated:i.lastUpdated,lastAccessed:i.lastAccessed,accessCount:i.accessCount,error:i.error?{name:i.error.name,message:i.error.message,stack:i.error.stack}:void 0}]);return e}deserializeAndLoadCache(e){this.isHandlingRemoteUpdate=!0;const t=new Map,{deserializeValue:s}=this.defaultOptions;for(const[i,a]of e){let e;a.error&&(e=new Error(a.error.message),e.name=a.error.name,e.stack=a.error.stack),t.set(i,{data:s(a.data),lastUpdated:a.lastUpdated,lastAccessed:a.lastAccessed,accessCount:a.accessCount,error:e,isLoading:!1})}this.cache=t,this.enforceSizeLimit(!1),this.isHandlingRemoteUpdate=!1}schedulePersistState(){this.defaultOptions.persistence&&!this.isHandlingRemoteUpdate&&(this.persistenceDebounceTimer&&clearTimeout(this.persistenceDebounceTimer),this.persistenceDebounceTimer=setTimeout(async()=>{try{const e=this.serializeCache();await this.defaultOptions.persistence.set(this.persistenceId,e),this.emitEvent({type:"cache:persistence:save:success",key:this.persistenceId,timestamp:Date.now()})}catch(e){console.error(`Cache (${this.persistenceId}): Failed to persist state:`,e),this.emitEvent({type:"cache:persistence:save:error",key:this.persistenceId,timestamp:Date.now(),error:e})}},this.defaultOptions.persistenceDebounceTime))}handleRemoteStateChange(e){if(this.isHandlingRemoteUpdate||!e)return;this.isHandlingRemoteUpdate=!0;const{deserializeValue:t}=this.defaultOptions,s=new Map;let i=!1;for(const[a,r]of e){let e;r.error&&(e=new Error(r.error.message),e.name=r.error.name,e.stack=r.error.stack);const c={data:t(r.data),lastUpdated:r.lastUpdated,lastAccessed:r.lastAccessed,accessCount:r.accessCount,error:e,isLoading:!1};s.set(a,c);const n=this.cache.get(a);(!n||n.lastUpdated<c.lastUpdated||JSON.stringify(n.data)!==JSON.stringify(c.data))&&(i=!0)}this.cache.size!==s.size&&(i=!0),i&&(this.cache=s,this.enforceSizeLimit(!1),this.emitEvent({type:"cache:persistence:sync",key:this.persistenceId,timestamp:Date.now(),message:"Cache updated from remote state."})),this.isHandlingRemoteUpdate=!1}registerQuery(e,t,s={}){void 0!==s.staleTime&&s.staleTime<0&&(s.staleTime=0),void 0!==s.cacheTime&&s.cacheTime<0&&(s.cacheTime=0),this.queries.set(e,{fetchFunction:t,options:{...this.defaultOptions,...s}})}async get(e,t){const s=this.queries.get(e);if(!s)throw new Error(`No query registered for key: ${e}`);let i=this.cache.get(e);const a=this.isStale(i,s.options);let r=!1;if(i)i.lastAccessed=Date.now(),i.accessCount++,this.updateMetrics("hits"),a&&this.updateMetrics("staleHits"),this.emitEvent({type:"cache:read:hit",key:e,timestamp:Date.now(),data:i.data,isStale:a});else if(this.updateMetrics("misses"),this.emitEvent({type:"cache:read:miss",key:e,timestamp:Date.now()}),!t?.waitForFresh||a){const t={data:void 0,lastUpdated:0,lastAccessed:Date.now(),accessCount:1,isLoading:!0,error:void 0};this.cache.set(e,t),i=t,r=!0}if(t?.waitForFresh&&(!i||a||i.isLoading))try{const t=await this.fetchAndWait(e,s);return r&&this.cache.get(e)===i&&this.schedulePersistState(),t}catch(s){if(t.throwOnError)throw s;return this.cache.get(e)?.data}if(!i||a||i&&!i.isLoading&&0===i.lastUpdated&&!i.error){if(i&&!i.isLoading)i.isLoading=!0;else if(!i){const t={data:void 0,lastUpdated:0,lastAccessed:Date.now(),accessCount:0,isLoading:!0,error:void 0};this.cache.set(e,t),i=t,r=!0}this.fetch(e,s).catch(()=>{})}if(r&&this.schedulePersistState(),i?.error&&t?.throwOnError)throw i.error;return i?.data}peek(e){const t=this.cache.get(e);return t&&(t.lastAccessed=Date.now(),t.accessCount++),t?.data}has(e){const t=this.cache.get(e),s=this.queries.get(e);return!(!t||!s)&&(!this.isStale(t,s.options)&&!t.isLoading)}async fetch(e,t){if(this.fetching.has(e))return this.fetching.get(e);let s=this.cache.get(e);s?s.isLoading||(s.isLoading=!0,s.error=void 0):(s={data:void 0,lastUpdated:0,lastAccessed:Date.now(),accessCount:0,isLoading:!0,error:void 0},this.cache.set(e,s),this.schedulePersistState());const i=this.performFetchWithRetry(e,t,s);this.fetching.set(e,i);try{return await i}finally{this.fetching.delete(e)}}async fetchAndWait(e,t){const s=this.fetching.get(e);if(s)return s;const i=await this.fetch(e,t);if(void 0===i){const t=this.cache.get(e);if(t?.error)throw t.error;throw new Error(`Failed to fetch data for key: ${e} after retries.`)}return i}async performFetchWithRetry(e,t,s){const{retryAttempts:i,retryDelay:a}=t.options;let r;s.isLoading=!0;for(let c=0;c<=i;c++)try{this.emitEvent({type:"cache:fetch:start",key:e,timestamp:Date.now(),attempt:c}),this.updateMetrics("fetches");const i=await t.fetchFunction();return s.data=i,s.lastUpdated=Date.now(),s.isLoading=!1,s.error=void 0,this.cache.set(e,s),this.schedulePersistState(),this.enforceSizeLimit(),this.emitEvent({type:"cache:fetch:success",key:e,timestamp:Date.now(),data:i}),i}catch(t){r=t,this.updateMetrics("errors"),this.emitEvent({type:"cache:fetch:error",key:e,timestamp:Date.now(),error:r,attempt:c}),c<i&&await this.delay(a*Math.pow(2,c))}s.error=r,s.isLoading=!1,this.cache.set(e,s),this.schedulePersistState()}isStale(e,t){if(!e||e.error)return!0;if(e.isLoading&&!e.data)return!0;const{staleTime:s}=t;return 0!==s&&s!==1/0&&Date.now()-e.lastUpdated>s}async invalidate(e,t=!0){const s=this.cache.get(e),i=this.queries.get(e);let a=!1;s&&(a=0!==s.lastUpdated||void 0!==s.error,s.lastUpdated=0,s.error=void 0,this.emitEvent({type:"cache:data:invalidate",key:e,timestamp:Date.now()}),a&&this.schedulePersistState(),t&&i&&this.fetch(e,i).catch(()=>{}))}async invalidatePattern(e,t=!0){const s=[];let i=!1;for(const t of this.cache.keys())e.test(t)&&s.push(t);s.forEach(e=>{const t=this.cache.get(e);t&&(0===t.lastUpdated&&void 0===t.error||(i=!0),t.lastUpdated=0,t.error=void 0,this.emitEvent({type:"cache:data:invalidate",key:e,timestamp:Date.now()}))}),i&&this.schedulePersistState(),t&&s.length>0&&await Promise.all(s.map(e=>{const t=this.queries.get(e);return t?this.fetch(e,t).catch(()=>{}):Promise.resolve()}))}async prefetch(e){const t=this.queries.get(e);if(!t)return void console.warn(`Cannot prefetch: No query registered for key: ${e}`);const s=this.cache.get(e);s&&!this.isStale(s,t.options)||this.fetch(e,t).catch(()=>{})}async refresh(e){const t=this.queries.get(e);if(!t)return void console.warn(`Cannot refresh: No query registered for key: ${e}`);this.fetching.delete(e);let s=this.cache.get(e);s?(s.isLoading=!0,s.error=void 0):(s={data:void 0,lastUpdated:0,lastAccessed:Date.now(),accessCount:0,isLoading:!0,error:void 0},this.cache.set(e,s),this.schedulePersistState());const i=this.performFetchWithRetry(e,t,s);this.fetching.set(e,i);try{return await i}finally{this.fetching.delete(e)}}setData(e,t){const s=this.cache.get(e),i=s?.data,a={data:t,lastUpdated:Date.now(),lastAccessed:Date.now(),accessCount:(s?.accessCount||0)+1,isLoading:!1,error:void 0};this.cache.set(e,a),this.schedulePersistState(),this.enforceSizeLimit(),this.emitEvent({type:"cache:data:set",key:e,timestamp:Date.now(),newData:t,oldData:i})}remove(e){this.fetching.delete(e);const t=this.cache.has(e),s=this.cache.delete(e);return s&&t&&this.schedulePersistState(),s}enforceSizeLimit(e=!0){const{maxSize:t}=this.defaultOptions;if(t===1/0||this.cache.size<=t)return;let s=0;if(0===t){s=this.cache.size;for(const e of this.cache.keys())this.cache.delete(e),this.emitEvent({type:"cache:data:evict",key:e,timestamp:Date.now(),reason:"size_limit_zero"}),this.updateMetrics("evictions")}else{const e=Array.from(this.cache.entries()).sort(([,e],[,t])=>e.lastAccessed-t.lastAccessed),i=this.cache.size-t;if(i>0){e.slice(0,i).forEach(([e])=>{this.cache.delete(e)&&(s++,this.emitEvent({type:"cache:data:evict",key:e,timestamp:Date.now(),reason:"size_limit_lru"}),this.updateMetrics("evictions"))})}}s>0&&e&&this.schedulePersistState()}startGarbageCollection(){const{cacheTime:e}=this.defaultOptions;if(e===1/0||e<=0)return;const t=Math.max(1e3,Math.min(e/4,3e5));this.gcTimer=setInterval(()=>this.garbageCollect(),t)}garbageCollect(){const e=Date.now();let t=0;const s=[];for(const[t,i]of this.cache){if(i.isLoading)continue;const a=this.queries.get(t),r=a?.options.cacheTime??this.defaultOptions.cacheTime;r===1/0||r<=0||e-i.lastAccessed>r&&s.push(t)}return s.length>0&&(s.forEach(e=>{this.cache.delete(e)&&(this.fetching.delete(e),this.emitEvent({type:"cache:data:evict",key:e,timestamp:Date.now(),reason:"garbage_collected_idle"}),this.updateMetrics("evictions"),t++)}),this.schedulePersistState()),t}getStats(){const e=this.metrics.hits+this.metrics.misses,t=e>0?this.metrics.hits/e:0,s=this.metrics.hits>0?this.metrics.staleHits/this.metrics.hits:0,i=Array.from(this.cache.entries()).map(([e,t])=>{const s=this.queries.get(e),i=!s||this.isStale(t,s.options);return{key:e,lastAccessed:t.lastAccessed,lastUpdated:t.lastUpdated,accessCount:t.accessCount,isStale:i,isLoading:t.isLoading,error:!!t.error}});return{size:this.cache.size,metrics:{...this.metrics},hitRate:t,staleHitRate:s,entries:i}}on(e,t){return this.eventBus.subscribe(e,t)}emitEvent(e){this.eventBus.emit({name:e.type,payload:e})}updateMetrics(e,t=1){this.defaultOptions.enableMetrics&&(this.metrics[e]=(this.metrics[e]||0)+t)}delay(e){return new Promise(t=>setTimeout(t,e))}async clear(){const e=this.cache.size>0;if(this.cache.clear(),this.fetching.clear(),this.defaultOptions.enableMetrics&&(this.metrics={hits:0,misses:0,fetches:0,errors:0,evictions:0,staleHits:0}),this.defaultOptions.persistence)try{await this.defaultOptions.persistence.clear(),this.emitEvent({type:"cache:persistence:clear:success",key:this.persistenceId,timestamp:Date.now()})}catch(e){console.error(`Cache (${this.persistenceId}): Failed to clear persisted state:`,e),this.emitEvent({type:"cache:persistence:clear:error",key:this.persistenceId,timestamp:Date.now(),error:e})}else e&&this.schedulePersistState()}destroy(){if(this.gcTimer&&(clearInterval(this.gcTimer),this.gcTimer=void 0),this.persistenceDebounceTimer&&clearTimeout(this.persistenceDebounceTimer),this.persistenceUnsubscribe)try{this.persistenceUnsubscribe()}catch(e){console.error(`Cache (${this.persistenceId}): Error unsubscribing persistence:`,e)}this.cache.clear(),this.fetching.clear(),this.queries.clear(),this.eventBus.clear(),this.metrics={hits:0,misses:0,fetches:0,errors:0,evictions:0,staleHits:0}}};export{l as QueryCache};
|
|
1
|
+
import{v4 as e}from"uuid";var t=((e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports))(((e,t)=>{var n=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.prototype.hasOwnProperty,o=(e,t)=>{for(var r in t)n(e,r,{get:t[r],enumerable:!0})},s=(e,t,o,s)=>{if(t&&typeof t==`object`||typeof t==`function`)for(let c of i(t))!a.call(e,c)&&c!==o&&n(e,c,{get:()=>t[c],enumerable:!(s=r(t,c))||s.enumerable});return e},c=e=>s(n({},`__esModule`,{value:!0}),e),l={};o(l,{createEventBus:()=>u}),t.exports=c(l);var u=(e={async:!1,batchSize:1e3,batchDelay:16,errorHandler:e=>console.error(`EventBus Error:`,e),crossTab:!1,channelName:`event-bus-channel`})=>{let t=new Map,n=[],r=0,i=0,a=new Map,o=new Map,s=null;e.crossTab&&typeof BroadcastChannel<`u`?s=new BroadcastChannel(e.channelName):e.crossTab&&console.warn(`BroadcastChannel is not supported in this browser. Cross-tab notifications are disabled.`);let c=(e,t)=>{r++,i+=t,a.set(e,(a.get(e)||0)+1)},l=()=>{let t=n;n=[],t.forEach(({name:t,payload:n})=>{let r=performance.now();try{(o.get(t)||[]).forEach(e=>e(n))}catch(r){e.errorHandler({...r,eventName:t,payload:n})}c(t,performance.now()-r)})},u=(()=>{let t;return()=>{clearTimeout(t),t=setTimeout(l,e.batchDelay)}})(),d=e=>{let n=t.get(e);n?o.set(e,Array.from(n)):o.delete(e)};return s&&(s.onmessage=e=>{let{name:t,payload:n}=e.data;(o.get(t)||[]).forEach(e=>e(n))}),{subscribe:(e,n)=>{t.has(e)||t.set(e,new Set);let r=t.get(e);return r.add(n),d(e),()=>{r.delete(n),r.size===0?(t.delete(e),o.delete(e)):d(e)}},emit:({name:t,payload:r})=>{if(e.async){n.push({name:t,payload:r}),n.length>=e.batchSize?l():u(),s&&s.postMessage({name:t,payload:r});return}let i=performance.now();try{(o.get(t)||[]).forEach(e=>e(r)),s&&s.postMessage({name:t,payload:r})}catch(n){e.errorHandler({...n,eventName:t,payload:r})}c(t,performance.now()-i)},getMetrics:()=>({totalEvents:r,activeSubscriptions:Array.from(t.values()).reduce((e,t)=>e+t.size,0),eventCounts:a,averageEmitDuration:r>0?i/r:0}),clear:()=>{t.clear(),o.clear(),n=[],r=0,i=0,a.clear(),s&&=(s.close(),null)}}};0&&(t.exports={createEventBus:u})}))(),n=class{cache=new Map;queries=new Map;fetching=new Map;defaultOptions;metrics;eventBus;gcTimer;persistenceId;persistenceUnsubscribe;persistenceDebounceTimer;isHandlingRemoteUpdate=!1;constructor(n={}){n.staleTime!==void 0&&n.staleTime<0&&(console.warn(`CacheOptions: staleTime should be non-negative. Using 0.`),n.staleTime=0),n.cacheTime!==void 0&&n.cacheTime<0&&(console.warn(`CacheOptions: cacheTime should be non-negative. Using 0.`),n.cacheTime=0),n.retryAttempts!==void 0&&n.retryAttempts<0&&(console.warn(`CacheOptions: retryAttempts should be non-negative. Using 0.`),n.retryAttempts=0),n.retryDelay!==void 0&&n.retryDelay<0&&(console.warn(`CacheOptions: retryDelay should be non-negative. Using 0.`),n.retryDelay=0),n.maxSize!==void 0&&n.maxSize<0&&(console.warn(`CacheOptions: maxSize should be non-negative. Using 0.`),n.maxSize=0),this.defaultOptions={staleTime:0,cacheTime:1800*1e3,retryAttempts:3,retryDelay:1e3,maxSize:1e3,enableMetrics:!0,persistence:void 0,persistenceId:void 0,serializeValue:e=>e,deserializeValue:e=>e,persistenceDebounceTime:500,...n},this.metrics={hits:0,misses:0,fetches:0,errors:0,evictions:0,staleHits:0},this.persistenceId=this.defaultOptions.persistenceId||e(),this.eventBus=(0,t.createEventBus)(),this.startGarbageCollection(),this.initializePersistence()}async initializePersistence(){let{persistence:e}=this.defaultOptions;if(e){try{let t=await e.get();t&&(this.deserializeAndLoadCache(t),this.emitEvent({type:`cache:persistence:load:success`,key:this.persistenceId,timestamp:Date.now(),message:`Cache loaded for ID: ${this.persistenceId}`}))}catch(e){console.error(`Cache (${this.persistenceId}): Failed to load state from persistence:`,e),this.emitEvent({type:`cache:persistence:load:error`,key:this.persistenceId,timestamp:Date.now(),error:e,message:`Failed to load cache for ID: ${this.persistenceId}`})}if(typeof e.subscribe==`function`)try{this.persistenceUnsubscribe=e.subscribe(this.persistenceId,e=>{this.handleRemoteStateChange(e)})}catch(e){console.error(`Cache (${this.persistenceId}): Failed to subscribe to persistence:`,e)}}}serializeCache(){let e=[],{serializeValue:t}=this.defaultOptions;for(let[n,r]of this.cache)r.isLoading&&r.data===void 0&&r.lastUpdated===0||e.push([n,{data:t(r.data),lastUpdated:r.lastUpdated,lastAccessed:r.lastAccessed,accessCount:r.accessCount,error:r.error?{name:r.error.name,message:r.error.message,stack:r.error.stack}:void 0}]);return e}deserializeAndLoadCache(e){this.isHandlingRemoteUpdate=!0;let t=new Map,{deserializeValue:n}=this.defaultOptions;for(let[r,i]of e){let e;i.error&&(e=Error(i.error.message),e.name=i.error.name,e.stack=i.error.stack),t.set(r,{data:n(i.data),lastUpdated:i.lastUpdated,lastAccessed:i.lastAccessed,accessCount:i.accessCount,error:e,isLoading:!1})}this.cache=t,this.enforceSizeLimit(!1),this.isHandlingRemoteUpdate=!1}schedulePersistState(){!this.defaultOptions.persistence||this.isHandlingRemoteUpdate||(this.persistenceDebounceTimer&&clearTimeout(this.persistenceDebounceTimer),this.persistenceDebounceTimer=setTimeout(async()=>{try{let e=this.serializeCache();await this.defaultOptions.persistence.set(this.persistenceId,e),this.emitEvent({type:`cache:persistence:save:success`,key:this.persistenceId,timestamp:Date.now()})}catch(e){console.error(`Cache (${this.persistenceId}): Failed to persist state:`,e),this.emitEvent({type:`cache:persistence:save:error`,key:this.persistenceId,timestamp:Date.now(),error:e})}},this.defaultOptions.persistenceDebounceTime))}handleRemoteStateChange(e){if(this.isHandlingRemoteUpdate||!e)return;this.isHandlingRemoteUpdate=!0;let{deserializeValue:t}=this.defaultOptions,n=new Map,r=!1;for(let[i,a]of e){let e;a.error&&(e=Error(a.error.message),e.name=a.error.name,e.stack=a.error.stack);let o={data:t(a.data),lastUpdated:a.lastUpdated,lastAccessed:a.lastAccessed,accessCount:a.accessCount,error:e,isLoading:!1};n.set(i,o);let s=this.cache.get(i);(!s||s.lastUpdated<o.lastUpdated||JSON.stringify(s.data)!==JSON.stringify(o.data))&&(r=!0)}this.cache.size!==n.size&&(r=!0),r&&(this.cache=n,this.enforceSizeLimit(!1),this.emitEvent({type:`cache:persistence:sync`,key:this.persistenceId,timestamp:Date.now(),message:`Cache updated from remote state.`})),this.isHandlingRemoteUpdate=!1}registerQuery(e,t,n={}){n.staleTime!==void 0&&n.staleTime<0&&(n.staleTime=0),n.cacheTime!==void 0&&n.cacheTime<0&&(n.cacheTime=0),this.queries.set(e,{fetchFunction:t,options:{...this.defaultOptions,...n}})}async get(e,t){let n=this.queries.get(e);if(!n)throw Error(`No query registered for key: ${e}`);let r=this.cache.get(e),i=this.isStale(r,n.options),a=!1;if(r)r.lastAccessed=Date.now(),r.accessCount++,this.updateMetrics(`hits`),i&&this.updateMetrics(`staleHits`),this.emitEvent({type:`cache:read:hit`,key:e,timestamp:Date.now(),data:r.data,isStale:i});else if(this.updateMetrics(`misses`),this.emitEvent({type:`cache:read:miss`,key:e,timestamp:Date.now()}),!t?.waitForFresh||i){let t={data:void 0,lastUpdated:0,lastAccessed:Date.now(),accessCount:1,isLoading:!0,error:void 0};this.cache.set(e,t),r=t,a=!0}if(t?.waitForFresh&&(!r||i||r.isLoading))try{let t=await this.fetchAndWait(e,n);return a&&this.cache.get(e)===r&&this.schedulePersistState(),t}catch(n){if(t.throwOnError)throw n;return this.cache.get(e)?.data}if(!r||i||r&&!r.isLoading&&r.lastUpdated===0&&!r.error){if(r&&!r.isLoading)r.isLoading=!0;else if(!r){let t={data:void 0,lastUpdated:0,lastAccessed:Date.now(),accessCount:0,isLoading:!0,error:void 0};this.cache.set(e,t),r=t,a=!0}this.fetch(e,n).catch(()=>{})}if(a&&this.schedulePersistState(),r?.error&&t?.throwOnError)throw r.error;return r?.data}peek(e){let t=this.cache.get(e);return t&&(t.lastAccessed=Date.now(),t.accessCount++),t?.data}has(e){let t=this.cache.get(e),n=this.queries.get(e);return!t||!n?!1:!this.isStale(t,n.options)&&!t.isLoading}async fetch(e,t){if(this.fetching.has(e))return this.fetching.get(e);let n=this.cache.get(e);n?n.isLoading||(n.isLoading=!0,n.error=void 0):(n={data:void 0,lastUpdated:0,lastAccessed:Date.now(),accessCount:0,isLoading:!0,error:void 0},this.cache.set(e,n),this.schedulePersistState());let r=this.performFetchWithRetry(e,t,n);this.fetching.set(e,r);try{return await r}finally{this.fetching.delete(e)}}async fetchAndWait(e,t){let n=this.fetching.get(e);if(n)return n;let r=await this.fetch(e,t);if(r===void 0){let t=this.cache.get(e);throw t?.error?t.error:Error(`Failed to fetch data for key: ${e} after retries.`)}return r}async performFetchWithRetry(e,t,n){let{retryAttempts:r,retryDelay:i}=t.options,a;n.isLoading=!0;for(let o=0;o<=r;o++)try{this.emitEvent({type:`cache:fetch:start`,key:e,timestamp:Date.now(),attempt:o}),this.updateMetrics(`fetches`);let r=await t.fetchFunction();return n.data=r,n.lastUpdated=Date.now(),n.isLoading=!1,n.error=void 0,this.cache.set(e,n),this.schedulePersistState(),this.enforceSizeLimit(),this.emitEvent({type:`cache:fetch:success`,key:e,timestamp:Date.now(),data:r}),r}catch(t){a=t,this.updateMetrics(`errors`),this.emitEvent({type:`cache:fetch:error`,key:e,timestamp:Date.now(),error:a,attempt:o}),o<r&&await this.delay(i*2**o)}n.error=a,n.isLoading=!1,this.cache.set(e,n),this.schedulePersistState()}isStale(e,t){if(!e||e.error||e.isLoading&&!e.data)return!0;let{staleTime:n}=t;return n===0||n===1/0?!1:Date.now()-e.lastUpdated>n}async invalidate(e,t=!0){let n=this.cache.get(e),r=this.queries.get(e),i=!1;n&&(i=n.lastUpdated!==0||n.error!==void 0,n.lastUpdated=0,n.error=void 0,this.emitEvent({type:`cache:data:invalidate`,key:e,timestamp:Date.now()}),i&&this.schedulePersistState(),t&&r&&this.fetch(e,r).catch(()=>{}))}async invalidatePattern(e,t=!0){let n=[],r=!1;for(let t of this.cache.keys())e.test(t)&&n.push(t);n.forEach(e=>{let t=this.cache.get(e);t&&((t.lastUpdated!==0||t.error!==void 0)&&(r=!0),t.lastUpdated=0,t.error=void 0,this.emitEvent({type:`cache:data:invalidate`,key:e,timestamp:Date.now()}))}),r&&this.schedulePersistState(),t&&n.length>0&&await Promise.all(n.map(e=>{let t=this.queries.get(e);return t?this.fetch(e,t).catch(()=>{}):Promise.resolve()}))}async prefetch(e){let t=this.queries.get(e);if(!t){console.warn(`Cannot prefetch: No query registered for key: ${e}`);return}let n=this.cache.get(e);(!n||this.isStale(n,t.options))&&this.fetch(e,t).catch(()=>{})}async refresh(e){let t=this.queries.get(e);if(!t){console.warn(`Cannot refresh: No query registered for key: ${e}`);return}this.fetching.delete(e);let n=this.cache.get(e);n?(n.isLoading=!0,n.error=void 0):(n={data:void 0,lastUpdated:0,lastAccessed:Date.now(),accessCount:0,isLoading:!0,error:void 0},this.cache.set(e,n),this.schedulePersistState());let r=this.performFetchWithRetry(e,t,n);this.fetching.set(e,r);try{return await r}finally{this.fetching.delete(e)}}setData(e,t){let n=this.cache.get(e),r=n?.data,i={data:t,lastUpdated:Date.now(),lastAccessed:Date.now(),accessCount:(n?.accessCount||0)+1,isLoading:!1,error:void 0};this.cache.set(e,i),this.schedulePersistState(),this.enforceSizeLimit(),this.emitEvent({type:`cache:data:set`,key:e,timestamp:Date.now(),newData:t,oldData:r})}remove(e){this.fetching.delete(e);let t=this.cache.has(e),n=this.cache.delete(e);return n&&t&&this.schedulePersistState(),n}enforceSizeLimit(e=!0){let{maxSize:t}=this.defaultOptions;if(t===1/0||this.cache.size<=t)return;let n=0;if(t===0){n=this.cache.size;for(let e of this.cache.keys())this.cache.delete(e),this.emitEvent({type:`cache:data:evict`,key:e,timestamp:Date.now(),reason:`size_limit_zero`}),this.updateMetrics(`evictions`)}else{let e=Array.from(this.cache.entries()).sort(([,e],[,t])=>e.lastAccessed-t.lastAccessed),r=this.cache.size-t;r>0&&e.slice(0,r).forEach(([e])=>{this.cache.delete(e)&&(n++,this.emitEvent({type:`cache:data:evict`,key:e,timestamp:Date.now(),reason:`size_limit_lru`}),this.updateMetrics(`evictions`))})}n>0&&e&&this.schedulePersistState()}startGarbageCollection(){let{cacheTime:e}=this.defaultOptions;if(e===1/0||e<=0)return;let t=Math.max(1e3,Math.min(e/4,300*1e3));this.gcTimer=setInterval(()=>this.garbageCollect(),t)}garbageCollect(){let e=Date.now(),t=0,n=[];for(let[t,r]of this.cache){if(r.isLoading)continue;let i=this.queries.get(t)?.options.cacheTime??this.defaultOptions.cacheTime;i===1/0||i<=0||e-r.lastAccessed>i&&n.push(t)}return n.length>0&&(n.forEach(e=>{this.cache.delete(e)&&(this.fetching.delete(e),this.emitEvent({type:`cache:data:evict`,key:e,timestamp:Date.now(),reason:`garbage_collected_idle`}),this.updateMetrics(`evictions`),t++)}),this.schedulePersistState()),t}getStats(){let e=this.metrics.hits+this.metrics.misses,t=e>0?this.metrics.hits/e:0,n=this.metrics.hits>0?this.metrics.staleHits/this.metrics.hits:0,r=Array.from(this.cache.entries()).map(([e,t])=>{let n=this.queries.get(e),r=n?this.isStale(t,n.options):!0;return{key:e,lastAccessed:t.lastAccessed,lastUpdated:t.lastUpdated,accessCount:t.accessCount,isStale:r,isLoading:t.isLoading,error:!!t.error}});return{size:this.cache.size,metrics:{...this.metrics},hitRate:t,staleHitRate:n,entries:r}}on(e,t){return this.eventBus.subscribe(e,t)}emitEvent(e){this.eventBus.emit({name:e.type,payload:e})}updateMetrics(e,t=1){this.defaultOptions.enableMetrics&&(this.metrics[e]=(this.metrics[e]||0)+t)}delay(e){return new Promise(t=>setTimeout(t,e))}async clear(){let e=this.cache.size>0;if(this.cache.clear(),this.fetching.clear(),this.defaultOptions.enableMetrics&&(this.metrics={hits:0,misses:0,fetches:0,errors:0,evictions:0,staleHits:0}),this.defaultOptions.persistence)try{await this.defaultOptions.persistence.clear(),this.emitEvent({type:`cache:persistence:clear:success`,key:this.persistenceId,timestamp:Date.now()})}catch(e){console.error(`Cache (${this.persistenceId}): Failed to clear persisted state:`,e),this.emitEvent({type:`cache:persistence:clear:error`,key:this.persistenceId,timestamp:Date.now(),error:e})}else e&&this.schedulePersistState()}destroy(){if(this.gcTimer&&=(clearInterval(this.gcTimer),void 0),this.persistenceDebounceTimer&&clearTimeout(this.persistenceDebounceTimer),this.persistenceUnsubscribe)try{this.persistenceUnsubscribe()}catch(e){console.error(`Cache (${this.persistenceId}): Error unsubscribing persistence:`,e)}this.cache.clear(),this.fetching.clear(),this.queries.clear(),this.eventBus.clear(),this.metrics={hits:0,misses:0,fetches:0,errors:0,evictions:0,staleHits:0}}};export{n as QueryCache};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asaidimu/utils-cache",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.13",
|
|
4
4
|
"description": "Resource and cache management utilities for @asaidimu applications.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.mjs",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@asaidimu/utils-persistence": "^6.1.
|
|
33
|
+
"@asaidimu/utils-persistence": "^6.1.12",
|
|
34
34
|
"uuid": "^11.1.0",
|
|
35
35
|
"@asaidimu/events": "^1.1.1"
|
|
36
36
|
},
|
package/index.d.mts
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
interface SimplePersistence<T> {
|
|
2
|
-
/**
|
|
3
|
-
* Persists data to storage.
|
|
4
|
-
*
|
|
5
|
-
* @param id The **unique identifier of the *consumer instance*** making the change. This is NOT the ID of the data (`T`) itself.
|
|
6
|
-
* Think of it as the ID of the specific browser tab, component, or module that's currently interacting with the persistence layer.
|
|
7
|
-
* It should typically be a **UUID** generated once at the consumer instance's instantiation.
|
|
8
|
-
* This `id` is crucial for the `subscribe` method, helping to differentiate updates originating from the current instance versus other instances/tabs, thereby preventing self-triggered notification loops.
|
|
9
|
-
* @param state The state (of type T) to persist. This state is generally considered the **global or shared state** that all instances interact with.
|
|
10
|
-
* @returns `true` if the operation was successful, `false` if an error occurred. For asynchronous implementations (like `IndexedDBPersistence`), this returns a `Promise<boolean>`.
|
|
11
|
-
*/
|
|
12
|
-
set(id: string, state: T): boolean | Promise<boolean>;
|
|
13
|
-
/**
|
|
14
|
-
* Retrieves the global persisted data from storage.
|
|
15
|
-
*
|
|
16
|
-
* @returns The retrieved state of type `T`, or `null` if no data is found or if an error occurs during retrieval/parsing.
|
|
17
|
-
* For asynchronous implementations, this returns a `Promise<T | null>`.
|
|
18
|
-
*/
|
|
19
|
-
get(): (T | null) | Promise<T | null>;
|
|
20
|
-
/**
|
|
21
|
-
* Subscribes to changes in the global persisted data that originate from *other* instances of your application (e.g., other tabs or independent components using the same persistence layer).
|
|
22
|
-
*
|
|
23
|
-
* @param id The **unique identifier of the *consumer instance* subscribing**. This allows the persistence implementation to filter out notifications that were initiated by the subscribing instance itself.
|
|
24
|
-
* @param callback The function to call when the global persisted data changes from *another* source. The new state (`T`) is passed as an argument to this callback.
|
|
25
|
-
* @returns A function that, when called, will unsubscribe the provided callback from future updates. Call this when your component or instance is no longer active to prevent memory leaks.
|
|
26
|
-
*/
|
|
27
|
-
subscribe(id: string, callback: (state: T) => void): () => void;
|
|
28
|
-
/**
|
|
29
|
-
* Clears (removes) the entire global persisted data from storage.
|
|
30
|
-
*
|
|
31
|
-
* @returns `true` if the operation was successful, `false` if an error occurred. For asynchronous implementations, this returns a `Promise<boolean>`.
|
|
32
|
-
*/
|
|
33
|
-
clear(): boolean | Promise<boolean>;
|
|
34
|
-
/**
|
|
35
|
-
* Returns metadata about the persistence layer.
|
|
36
|
-
*
|
|
37
|
-
* This is useful for distinguishing between multiple apps running on the same host
|
|
38
|
-
* (e.g., several apps served at `localhost:3000` that share the same storage key).
|
|
39
|
-
*
|
|
40
|
-
* @returns An object containing:
|
|
41
|
-
* - `version`: The semantic version string of the persistence schema or application.
|
|
42
|
-
* - `id`: A unique identifier for the application using this persistence instance.
|
|
43
|
-
*/
|
|
44
|
-
stats(): {
|
|
45
|
-
version: string;
|
|
46
|
-
id: string;
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
interface CacheOptions {
|
|
51
|
-
staleTime?: number;
|
|
52
|
-
cacheTime?: number;
|
|
53
|
-
retryAttempts?: number;
|
|
54
|
-
retryDelay?: number;
|
|
55
|
-
maxSize?: number;
|
|
56
|
-
enableMetrics?: boolean;
|
|
57
|
-
persistence?: SimplePersistence<SerializableCacheState>;
|
|
58
|
-
persistenceId?: string;
|
|
59
|
-
serializeValue?: (value: any) => any;
|
|
60
|
-
deserializeValue?: (value: any) => any;
|
|
61
|
-
persistenceDebounceTime?: number;
|
|
62
|
-
}
|
|
63
|
-
interface CacheEntry<T = any> {
|
|
64
|
-
data: T;
|
|
65
|
-
lastUpdated: number;
|
|
66
|
-
lastAccessed: number;
|
|
67
|
-
accessCount: number;
|
|
68
|
-
error?: Error;
|
|
69
|
-
isLoading?: boolean;
|
|
70
|
-
}
|
|
71
|
-
interface QueryConfig {
|
|
72
|
-
fetchFunction: () => Promise<any>;
|
|
73
|
-
options: Required<CacheOptions>;
|
|
74
|
-
}
|
|
75
|
-
interface CacheMetrics {
|
|
76
|
-
hits: number;
|
|
77
|
-
misses: number;
|
|
78
|
-
fetches: number;
|
|
79
|
-
errors: number;
|
|
80
|
-
evictions: number;
|
|
81
|
-
staleHits: number;
|
|
82
|
-
}
|
|
83
|
-
interface SerializableCacheEntry {
|
|
84
|
-
data: any;
|
|
85
|
-
lastUpdated: number;
|
|
86
|
-
lastAccessed: number;
|
|
87
|
-
accessCount: number;
|
|
88
|
-
error?: {
|
|
89
|
-
name: string;
|
|
90
|
-
message: string;
|
|
91
|
-
stack?: string;
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
type SerializableCacheState = Array<[string, SerializableCacheEntry]>;
|
|
95
|
-
type CacheEventBase<Type extends string, Payload = {}> = {
|
|
96
|
-
type: Type;
|
|
97
|
-
key: string;
|
|
98
|
-
timestamp: number;
|
|
99
|
-
} & Payload;
|
|
100
|
-
type CacheReadHitEvent<T = any> = CacheEventBase<"cache:read:hit", {
|
|
101
|
-
data: T;
|
|
102
|
-
isStale: boolean;
|
|
103
|
-
}>;
|
|
104
|
-
type CacheReadMissEvent = CacheEventBase<"cache:read:miss">;
|
|
105
|
-
type CacheFetchStartEvent = CacheEventBase<"cache:fetch:start", {
|
|
106
|
-
attempt: number;
|
|
107
|
-
}>;
|
|
108
|
-
type CacheFetchSuccessEvent<T = any> = CacheEventBase<"cache:fetch:success", {
|
|
109
|
-
data: T;
|
|
110
|
-
}>;
|
|
111
|
-
type CacheFetchErrorEvent = CacheEventBase<"cache:fetch:error", {
|
|
112
|
-
error: Error;
|
|
113
|
-
attempt: number;
|
|
114
|
-
}>;
|
|
115
|
-
type CacheDataEvictEvent = CacheEventBase<"cache:data:evict", {
|
|
116
|
-
reason?: string;
|
|
117
|
-
}>;
|
|
118
|
-
type CacheDataInvalidateEvent = CacheEventBase<"cache:data:invalidate">;
|
|
119
|
-
type CacheDataSetEvent<T = any> = CacheEventBase<"cache:data:set", {
|
|
120
|
-
newData: T;
|
|
121
|
-
oldData?: T;
|
|
122
|
-
}>;
|
|
123
|
-
type CachePersistenceLoadSuccessEvent = CacheEventBase<"cache:persistence:load:success", {
|
|
124
|
-
message?: string;
|
|
125
|
-
}>;
|
|
126
|
-
type CachePersistenceLoadErrorEvent = CacheEventBase<"cache:persistence:load:error", {
|
|
127
|
-
message?: string;
|
|
128
|
-
error?: any;
|
|
129
|
-
}>;
|
|
130
|
-
type CachePersistenceSaveSuccessEvent = CacheEventBase<"cache:persistence:save:success">;
|
|
131
|
-
type CachePersistenceSaveErrorEvent = CacheEventBase<"cache:persistence:save:error", {
|
|
132
|
-
message?: string;
|
|
133
|
-
error?: any;
|
|
134
|
-
}>;
|
|
135
|
-
type CachePersistenceClearSuccessEvent = CacheEventBase<"cache:persistence:clear:success">;
|
|
136
|
-
type CachePersistenceClearErrorEvent = CacheEventBase<"cache:persistence:clear:error", {
|
|
137
|
-
message?: string;
|
|
138
|
-
error?: any;
|
|
139
|
-
}>;
|
|
140
|
-
type CachePersistenceSyncEvent = CacheEventBase<"cache:persistence:sync", {
|
|
141
|
-
message?: string;
|
|
142
|
-
}>;
|
|
143
|
-
type CacheEvent = CacheReadHitEvent | CacheReadMissEvent | CacheFetchStartEvent | CacheFetchSuccessEvent | CacheFetchErrorEvent | CacheDataEvictEvent | CacheDataInvalidateEvent | CacheDataSetEvent | CachePersistenceLoadSuccessEvent | CachePersistenceLoadErrorEvent | CachePersistenceSaveSuccessEvent | CachePersistenceSaveErrorEvent | CachePersistenceClearSuccessEvent | CachePersistenceClearErrorEvent | CachePersistenceSyncEvent;
|
|
144
|
-
type CacheEventType = CacheEvent["type"];
|
|
145
|
-
|
|
146
|
-
declare class QueryCache {
|
|
147
|
-
private cache;
|
|
148
|
-
private queries;
|
|
149
|
-
private fetching;
|
|
150
|
-
private readonly defaultOptions;
|
|
151
|
-
private metrics;
|
|
152
|
-
private eventBus;
|
|
153
|
-
private gcTimer?;
|
|
154
|
-
private readonly persistenceId;
|
|
155
|
-
private persistenceUnsubscribe?;
|
|
156
|
-
private persistenceDebounceTimer?;
|
|
157
|
-
private isHandlingRemoteUpdate;
|
|
158
|
-
constructor(defaultOptions?: CacheOptions);
|
|
159
|
-
private initializePersistence;
|
|
160
|
-
private serializeCache;
|
|
161
|
-
private deserializeAndLoadCache;
|
|
162
|
-
private schedulePersistState;
|
|
163
|
-
private handleRemoteStateChange;
|
|
164
|
-
registerQuery<T>(key: string, fetchFunction: () => Promise<T>, options?: CacheOptions): void;
|
|
165
|
-
get<T>(key: string, options?: {
|
|
166
|
-
waitForFresh?: boolean;
|
|
167
|
-
throwOnError?: boolean;
|
|
168
|
-
}): Promise<T | undefined>;
|
|
169
|
-
peek<T>(key: string): T | undefined;
|
|
170
|
-
has(key: string): boolean;
|
|
171
|
-
private fetch;
|
|
172
|
-
private fetchAndWait;
|
|
173
|
-
private performFetchWithRetry;
|
|
174
|
-
private isStale;
|
|
175
|
-
invalidate(key: string, refetch?: boolean): Promise<void>;
|
|
176
|
-
invalidatePattern(pattern: RegExp, refetch?: boolean): Promise<void>;
|
|
177
|
-
prefetch(key: string): Promise<void>;
|
|
178
|
-
refresh<T>(key: string): Promise<T | undefined>;
|
|
179
|
-
setData<T>(key: string, data: T): void;
|
|
180
|
-
remove(key: string): boolean;
|
|
181
|
-
private enforceSizeLimit;
|
|
182
|
-
private startGarbageCollection;
|
|
183
|
-
garbageCollect(): number;
|
|
184
|
-
getStats(): {
|
|
185
|
-
size: number;
|
|
186
|
-
metrics: CacheMetrics;
|
|
187
|
-
hitRate: number;
|
|
188
|
-
staleHitRate: number;
|
|
189
|
-
entries: Array<{
|
|
190
|
-
key: string;
|
|
191
|
-
lastAccessed: number;
|
|
192
|
-
lastUpdated: number;
|
|
193
|
-
accessCount: number;
|
|
194
|
-
isStale: boolean;
|
|
195
|
-
isLoading?: boolean;
|
|
196
|
-
error?: boolean;
|
|
197
|
-
}>;
|
|
198
|
-
};
|
|
199
|
-
on<EType extends CacheEventType>(event: EType, listener: (ev: Extract<CacheEvent, {
|
|
200
|
-
type: EType;
|
|
201
|
-
}>) => void): () => void;
|
|
202
|
-
private emitEvent;
|
|
203
|
-
private updateMetrics;
|
|
204
|
-
private delay;
|
|
205
|
-
clear(): Promise<void>;
|
|
206
|
-
destroy(): void;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export { type CacheDataEvictEvent, type CacheDataInvalidateEvent, type CacheDataSetEvent, type CacheEntry, type CacheEvent, type CacheEventBase, type CacheEventType, type CacheFetchErrorEvent, type CacheFetchStartEvent, type CacheFetchSuccessEvent, type CacheMetrics, type CacheOptions, type CachePersistenceClearErrorEvent, type CachePersistenceClearSuccessEvent, type CachePersistenceLoadErrorEvent, type CachePersistenceLoadSuccessEvent, type CachePersistenceSaveErrorEvent, type CachePersistenceSaveSuccessEvent, type CachePersistenceSyncEvent, type CacheReadHitEvent, type CacheReadMissEvent, QueryCache, type QueryConfig, type SerializableCacheEntry, type SerializableCacheState };
|