@phren/cli
Version:
Knowledge layer for AI agents - CLI, MCP server, and data layer
80 lines (79 loc) • 2.66 kB
JavaScript
import { AsyncLocalStorage } from "node:async_hooks";
/** Keep only the handles each in-flight reader actually obtained. */
export function indexReaders(current, close) {
const calls = new AsyncLocalStorage();
const readers = new Map();
const retired = new Set();
return {
get() {
const handle = current();
const call = calls.getStore();
if (call?.active && !call.handles.has(handle)) {
call.handles.add(handle);
readers.set(handle, (readers.get(handle) ?? 0) + 1);
}
return handle;
},
retire(handle) {
if (readers.has(handle))
retired.add(handle);
else
close(handle);
},
run(fn) {
const call = { active: true, handles: new Set() };
return calls.run(call, async () => {
try {
return await fn();
}
finally {
call.active = false;
for (const handle of call.handles) {
const count = readers.get(handle) - 1;
if (count)
readers.set(handle, count);
else {
readers.delete(handle);
if (retired.delete(handle))
close(handle);
}
}
}
});
},
};
}
/** Coalesce local freshness checks without a timer or any remote polling. */
export function localIndexRefresh(options) {
const now = options.now ?? Date.now;
const interval = options.intervalMs ?? 1000;
let checkedAt = -Infinity;
let pending;
let failure;
return {
async ensureFresh() {
if (pending)
return pending;
if (now() - checkedAt < interval) {
if (failure)
throw failure;
return;
}
checkedAt = now();
try {
if (options.isFresh()) {
failure = undefined;
return;
}
}
catch (error) {
failure = error;
throw error;
}
pending = options.runExclusive(options.refresh).then(() => { failure = undefined; })
.catch((error) => { failure = error; throw error; })
.finally(() => { checkedAt = now(); pending = undefined; });
return pending;
},
};
}