refactor: consolidate core runtime state helpers

This commit is contained in:
Peter Steinberger
2026-03-22 17:57:24 +00:00
parent ca986d05aa
commit 9428b38452
11 changed files with 173 additions and 105 deletions

21
src/shared/listeners.ts Normal file
View File

@@ -0,0 +1,21 @@
export function notifyListeners<T>(
listeners: Iterable<(event: T) => void>,
event: T,
onError?: (error: unknown) => void,
): void {
for (const listener of listeners) {
try {
listener(event);
} catch (error) {
onError?.(error);
}
}
}
export function registerListener<T>(
listeners: Set<(event: T) => void>,
listener: (event: T) => void,
): () => void {
listeners.add(listener);
return () => listeners.delete(listener);
}

View File

@@ -0,0 +1,57 @@
export type ScopedExpiringIdCache<TScope extends string | number, TId extends string | number> = {
record: (scope: TScope, id: TId, now?: number) => void;
has: (scope: TScope, id: TId, now?: number) => boolean;
clear: () => void;
};
export function createScopedExpiringIdCache<
TScope extends string | number,
TId extends string | number,
>(options: {
store: Map<string, Map<string, number>>;
ttlMs: number;
cleanupThreshold: number;
}): ScopedExpiringIdCache<TScope, TId> {
const ttlMs = Math.max(0, options.ttlMs);
const cleanupThreshold = Math.max(1, Math.floor(options.cleanupThreshold));
function cleanupExpired(scopeKey: string, entry: Map<string, number>, now: number): void {
for (const [id, timestamp] of entry) {
if (now - timestamp > ttlMs) {
entry.delete(id);
}
}
if (entry.size === 0) {
options.store.delete(scopeKey);
}
}
return {
record: (scope, id, now = Date.now()) => {
const scopeKey = String(scope);
const idKey = String(id);
let entry = options.store.get(scopeKey);
if (!entry) {
entry = new Map<string, number>();
options.store.set(scopeKey, entry);
}
entry.set(idKey, now);
if (entry.size > cleanupThreshold) {
cleanupExpired(scopeKey, entry, now);
}
},
has: (scope, id, now = Date.now()) => {
const scopeKey = String(scope);
const idKey = String(id);
const entry = options.store.get(scopeKey);
if (!entry) {
return false;
}
cleanupExpired(scopeKey, entry, now);
return entry.has(idKey);
},
clear: () => {
options.store.clear();
},
};
}