refactor: Query emits Filters
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
import debug from "debug";
|
||||
import { removeUndefined, unixNowMs, unwrap } from "./utils";
|
||||
import { removeUndefined, unixNowMs } from "./utils";
|
||||
import { DexieTableLike } from "./dexie-like";
|
||||
import EventEmitter from "eventemitter3";
|
||||
|
||||
type HookFn = () => void;
|
||||
|
||||
@ -9,14 +10,17 @@ export interface KeyedHookFilter {
|
||||
fn: HookFn;
|
||||
}
|
||||
|
||||
export interface FeedCacheEvents {
|
||||
change: (keys: Array<string>) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dexie backed generic hookable store
|
||||
*/
|
||||
export abstract class FeedCache<TCached> {
|
||||
#name: string;
|
||||
#hooks: Array<KeyedHookFilter> = [];
|
||||
export abstract class FeedCache<TCached> extends EventEmitter<FeedCacheEvents> {
|
||||
readonly name: string;
|
||||
#snapshot: Array<TCached> = [];
|
||||
#changed = true;
|
||||
#log: ReturnType<typeof debug>;
|
||||
#hits = 0;
|
||||
#miss = 0;
|
||||
protected table?: DexieTableLike<TCached>;
|
||||
@ -24,21 +28,22 @@ export abstract class FeedCache<TCached> {
|
||||
protected cache: Map<string, TCached> = new Map();
|
||||
|
||||
constructor(name: string, table?: DexieTableLike<TCached>) {
|
||||
this.#name = name;
|
||||
super();
|
||||
this.name = name;
|
||||
this.table = table;
|
||||
this.#log = debug(name);
|
||||
setInterval(() => {
|
||||
debug(this.#name)(
|
||||
this.#log(
|
||||
"%d loaded, %d on-disk, %d hooks, %d% hit",
|
||||
this.cache.size,
|
||||
this.onTable.size,
|
||||
this.#hooks.length,
|
||||
this.listenerCount("change"),
|
||||
((this.#hits / (this.#hits + this.#miss)) * 100).toFixed(1),
|
||||
);
|
||||
}, 30_000);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.#name;
|
||||
this.on("change", () => {
|
||||
this.#snapshot = this.takeSnapshot();
|
||||
});
|
||||
}
|
||||
|
||||
async preload() {
|
||||
@ -49,28 +54,25 @@ export abstract class FeedCache<TCached> {
|
||||
}
|
||||
}
|
||||
|
||||
keysOnTable() {
|
||||
return [...this.onTable];
|
||||
}
|
||||
|
||||
hook(fn: HookFn, key: string | undefined) {
|
||||
if (!key) {
|
||||
return () => {
|
||||
//noop
|
||||
if (key) {
|
||||
const handle = (keys: Array<string>) => {
|
||||
if (keys.includes(key)) {
|
||||
fn();
|
||||
}
|
||||
};
|
||||
this.on("change", handle);
|
||||
return () => this.off("change", handle);
|
||||
}
|
||||
|
||||
this.#hooks.push({
|
||||
key,
|
||||
fn,
|
||||
});
|
||||
return () => {
|
||||
const idx = this.#hooks.findIndex(a => a.fn === fn);
|
||||
if (idx >= 0) {
|
||||
this.#hooks.splice(idx, 1);
|
||||
}
|
||||
// noop
|
||||
};
|
||||
}
|
||||
|
||||
keysOnTable() {
|
||||
return [...this.onTable];
|
||||
}
|
||||
|
||||
getFromCache(key?: string) {
|
||||
if (key) {
|
||||
@ -89,7 +91,7 @@ export abstract class FeedCache<TCached> {
|
||||
const cached = await this.table.get(key);
|
||||
if (cached) {
|
||||
this.cache.set(this.key(cached), cached);
|
||||
this.notifyChange([key]);
|
||||
this.emit("change", [key]);
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
@ -120,7 +122,7 @@ export abstract class FeedCache<TCached> {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
this.notifyChange([k]);
|
||||
this.emit("change", [k]);
|
||||
}
|
||||
|
||||
async bulkSet(obj: Array<TCached> | Readonly<Array<TCached>>) {
|
||||
@ -133,7 +135,10 @@ export abstract class FeedCache<TCached> {
|
||||
}
|
||||
}
|
||||
obj.forEach(v => this.cache.set(this.key(v), v));
|
||||
this.notifyChange(obj.map(a => this.key(a)));
|
||||
this.emit(
|
||||
"change",
|
||||
obj.map(a => this.key(a)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -156,7 +161,7 @@ export abstract class FeedCache<TCached> {
|
||||
}
|
||||
return "no_change";
|
||||
})();
|
||||
debug(this.#name)("Updating %s %s %o", k, updateType, m);
|
||||
this.#log("Updating %s %s %o", k, updateType, m);
|
||||
if (updateType !== "no_change") {
|
||||
const updated = {
|
||||
...existing,
|
||||
@ -184,8 +189,11 @@ export abstract class FeedCache<TCached> {
|
||||
fromCache.forEach(a => {
|
||||
this.cache.set(this.key(a), a);
|
||||
});
|
||||
this.notifyChange(fromCache.map(a => this.key(a)));
|
||||
debug(this.#name)(`Loaded %d/%d in %d ms`, fromCache.length, keys.length, (unixNowMs() - start).toLocaleString());
|
||||
this.emit(
|
||||
"change",
|
||||
fromCache.map(a => this.key(a)),
|
||||
);
|
||||
this.#log(`Loaded %d/%d in %d ms`, fromCache.length, keys.length, (unixNowMs() - start).toLocaleString());
|
||||
return mapped.filter(a => !a.has).map(a => a.key);
|
||||
}
|
||||
|
||||
@ -197,23 +205,12 @@ export abstract class FeedCache<TCached> {
|
||||
await this.table?.clear();
|
||||
this.cache.clear();
|
||||
this.onTable.clear();
|
||||
this.#changed = true;
|
||||
this.#hooks.forEach(h => h.fn());
|
||||
}
|
||||
|
||||
snapshot() {
|
||||
if (this.#changed) {
|
||||
this.#snapshot = this.takeSnapshot();
|
||||
this.#changed = false;
|
||||
}
|
||||
return this.#snapshot;
|
||||
}
|
||||
|
||||
protected notifyChange(keys: Array<string>) {
|
||||
this.#changed = true;
|
||||
this.#hooks.filter(a => keys.includes(a.key) || a.key === "*").forEach(h => h.fn());
|
||||
}
|
||||
|
||||
abstract key(of: TCached): string;
|
||||
abstract takeSnapshot(): Array<TCached>;
|
||||
}
|
||||
|
Reference in New Issue
Block a user