release: WRNexusJS 0.8.0
This commit is contained in:
Vendored
+100
@@ -0,0 +1,100 @@
|
||||
import { TagCache, type CacheEvent, type CacheSetOptions, type TagCacheOptions } from "./memory.ts";
|
||||
|
||||
export type CacheLayerName = "data" | "component" | "page";
|
||||
|
||||
export interface CacheInspection {
|
||||
layers: Record<CacheLayerName, ReturnType<TagCache<unknown>["snapshot"]>>;
|
||||
recentEvents: Array<CacheEvent & { layer: CacheLayerName }>;
|
||||
}
|
||||
|
||||
export interface CacheCoordinatorOptions extends Omit<TagCacheOptions, "onEvent"> {
|
||||
eventLimit?: number;
|
||||
onEvent?: (event: CacheEvent & { layer: CacheLayerName }) => void;
|
||||
}
|
||||
|
||||
/** A request-lifetime cache: deduplicates work without leaking values between requests. */
|
||||
export class RequestCache {
|
||||
private pending = new Map<string, Promise<unknown>>();
|
||||
|
||||
getOrLoad<V>(key: string, loader: () => V | Promise<V>): Promise<V> {
|
||||
const existing = this.pending.get(key);
|
||||
if (existing) return existing as Promise<V>;
|
||||
const value = Promise.resolve().then(loader);
|
||||
this.pending.set(key, value);
|
||||
return value as Promise<V>;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.pending.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/** Owns the three cross-request cache layers and creates isolated request caches. */
|
||||
export class CacheCoordinator {
|
||||
readonly data: TagCache<unknown>;
|
||||
readonly component: TagCache<unknown>;
|
||||
readonly page: TagCache<unknown>;
|
||||
private readonly events: Array<CacheEvent & { layer: CacheLayerName }> = [];
|
||||
private readonly eventLimit: number;
|
||||
|
||||
constructor(options: CacheCoordinatorOptions = {}) {
|
||||
const { eventLimit = 200, onEvent, ...cacheOptions } = options;
|
||||
this.eventLimit = Math.max(1, eventLimit);
|
||||
const create = (layer: CacheLayerName) =>
|
||||
new TagCache<unknown>({
|
||||
...cacheOptions,
|
||||
onEvent: (event) => {
|
||||
const item = { ...event, layer };
|
||||
this.events.push(item);
|
||||
if (this.events.length > this.eventLimit)
|
||||
this.events.splice(0, this.events.length - this.eventLimit);
|
||||
onEvent?.(item);
|
||||
},
|
||||
});
|
||||
this.data = create("data");
|
||||
this.component = create("component");
|
||||
this.page = create("page");
|
||||
}
|
||||
|
||||
request(): RequestCache {
|
||||
return new RequestCache();
|
||||
}
|
||||
|
||||
layer(name: CacheLayerName): TagCache<unknown> {
|
||||
return this[name];
|
||||
}
|
||||
|
||||
getOrLoad<V>(
|
||||
layer: CacheLayerName,
|
||||
key: string,
|
||||
loader: () => V | Promise<V>,
|
||||
options?: CacheSetOptions,
|
||||
): Promise<V> {
|
||||
return this.layer(layer).getOrLoad(key, loader, options) as Promise<V>;
|
||||
}
|
||||
|
||||
invalidateTags(tags: Iterable<string>): number {
|
||||
return (
|
||||
this.data.invalidateTags(tags) +
|
||||
this.component.invalidateTags(tags) +
|
||||
this.page.invalidateTags(tags)
|
||||
);
|
||||
}
|
||||
|
||||
inspect(): CacheInspection {
|
||||
return {
|
||||
layers: {
|
||||
data: this.data.snapshot(),
|
||||
component: this.component.snapshot(),
|
||||
page: this.page.snapshot(),
|
||||
},
|
||||
recentEvents: this.events.map((event) => ({ ...event, tags: event.tags && [...event.tags] })),
|
||||
};
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.data.clear();
|
||||
this.component.clear();
|
||||
this.page.clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user