openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
228 lines (227 loc) • 8.25 kB
JavaScript
import { u as toErrorObject } from "./error-coercion-1P2One0X.mjs";
import { a as resolveGlobalSingleton } from "./global-singleton-0m-JziJW.mjs";
import { t as pruneMapToMaxSize } from "./map-size-CNcWiFKu.mjs";
import { t as runGitWorkerOperation } from "./git-worker-umTf8mnl.mjs";
//#region src/infra/retained-cache.ts
const UNWATCHED_CACHE_LIMIT = 100;
/** Signal-scoped watchers retain one canonical entry beyond the unobserved bound. */
function createRetainedCache() {
const entries = /* @__PURE__ */ new Map();
const retained = /* @__PURE__ */ new Map();
const pins = /* @__PURE__ */ new WeakMap();
const release = (signal) => {
if (signal) pins.get(signal)?.release();
};
const retain = (key, entry, signal) => {
if (!signal || signal.aborted || pins.get(signal)?.key === key) return;
release(signal);
const current = retained.get(key) ?? {
entry,
watchers: /* @__PURE__ */ new Set()
};
retained.set(key, current);
current.watchers.add(signal);
const unpin = () => {
signal.removeEventListener("abort", unpin);
pins.delete(signal);
current.watchers.delete(signal);
if (current.watchers.size === 0) retained.delete(key);
};
pins.set(signal, {
key,
release: unpin
});
signal.addEventListener("abort", unpin, { once: true });
};
return {
get(key, signal) {
if (signal && pins.get(signal)?.key !== key) release(signal);
const entry = retained.get(key)?.entry ?? entries.get(key);
if (entry !== void 0) retain(key, entry, signal);
return entry;
},
set(key, entry, signal) {
const current = retained.get(key);
if (current) current.entry = entry;
entries.delete(key);
entries.set(key, entry);
pruneMapToMaxSize(entries, UNWATCHED_CACHE_LIMIT);
retain(key, entry, signal);
},
delete(key, expected) {
if (entries.get(key) === expected) entries.delete(key);
const current = retained.get(key);
if (current && current.entry === expected) current.entry = void 0;
},
clear() {
for (const current of retained.values()) for (const signal of current.watchers) release(signal);
entries.clear();
},
release
};
}
//#endregion
//#region src/infra/git-read-cache.ts
function subscribe(entry, clone, signal) {
entry.subscribers += 1;
return new Promise((resolve, reject) => {
let settled = false;
const finish = (complete) => {
if (settled) return;
settled = true;
signal?.removeEventListener("abort", abort);
entry.subscribers -= 1;
if (entry.pending && entry.subscribers === 0) {
entry.expiresAt = 0;
entry.controller.abort();
entry.promise.then(complete, complete);
return;
}
complete();
};
const abort = () => finish(() => reject(toErrorObject(signal?.reason, "Git read aborted")));
signal?.addEventListener("abort", abort, { once: true });
entry.promise.then((value) => finish(() => resolve(clone(value))), (error) => finish(() => reject(toErrorObject(error, "Git read failed"))));
if (signal?.aborted) abort();
});
}
function createReadCache(load, freshnessMs, clone = structuredClone, revision) {
const entries = createRetainedCache();
const pending = /* @__PURE__ */ new Set();
const revisions = /* @__PURE__ */ new Map();
let closed = false;
return {
async read(input, options = {}) {
options.signal?.throwIfAborted();
const prepared = structuredClone(input);
const key = JSON.stringify(prepared);
let currentRevision;
if (revision) {
const controller = new AbortController();
const check = revision(prepared, options.signal ? AbortSignal.any([controller.signal, options.signal]) : controller.signal);
revisions.set(controller, check);
try {
currentRevision = await check;
} finally {
revisions.delete(controller);
}
}
options.signal?.throwIfAborted();
if (closed) throw new Error("Git reads are unavailable while the Gateway is restarting");
let entry = entries.get(key, options.cacheSignal);
if (options.refresh === true || options.refresh === "unversioned" && currentRevision === null || !entry || entry.revision !== currentRevision || entry.expiresAt <= Date.now()) {
const controller = new AbortController();
const next = {
revision: currentRevision,
expiresAt: freshnessMs === 0 ? Number.POSITIVE_INFINITY : Date.now() + (currentRevision === null ? Math.min(freshnessMs, 75e3) : freshnessMs),
controller,
subscribers: 0,
pending: true,
promise: Promise.resolve().then(() => load(prepared, controller.signal))
};
pending.add(next);
next.promise = next.promise.then((value) => {
next.pending = false;
pending.delete(next);
controller.signal.throwIfAborted();
if (freshnessMs === 0) entries.delete(key, next);
return value;
}, (error) => {
next.pending = false;
pending.delete(next);
next.expiresAt = 0;
entries.delete(key, next);
throw error;
});
entries.set(key, next, options.cacheSignal);
entry = next;
}
return subscribe(entry, clone, options.signal);
},
async close() {
closed = true;
for (const controller of revisions.keys()) controller.abort();
const retiring = [...pending];
for (const entry of retiring) {
entry.expiresAt = 0;
entry.controller.abort();
}
entries.clear();
await Promise.allSettled([...revisions.values(), ...retiring.map((entry) => entry.promise)]);
},
release: entries.release
};
}
function createReadCaches() {
return {
context: createReadCache((input, signal) => runGitWorkerOperation({
type: "checkout.context",
input
}, { signal }), Number.POSITIVE_INFINITY, structuredClone, (input, signal) => runGitWorkerOperation({
type: "checkout.revision",
input: {
root: input.root,
includeIndex: false
}
}, { signal })),
branchFacts: createReadCache((input, signal) => runGitWorkerOperation({
type: "pull-request.branch-facts",
input
}, { signal }), 3e5, structuredClone, (input, signal) => runGitWorkerOperation({
type: "checkout.revision",
input: {
root: input.root,
includeIndex: true
}
}, { signal })),
diff: createReadCache((input, signal) => runGitWorkerOperation({
type: "checkout.diff",
input
}, { signal }), 0, (diff) => ({
...diff,
files: diff.files.map((file) => ({ ...file })),
...diff.commits ? { commits: diff.commits.map((commit) => ({ ...commit })) } : {},
...diff.mergeBase ? { mergeBase: { ...diff.mergeBase } } : {}
})),
branches: createReadCache((input, signal) => runGitWorkerOperation({
type: "repository.branches",
input
}, { signal }), 0),
baseline: createReadCache((input, signal) => runGitWorkerOperation({
type: "checkout.baseline",
input
}, { signal }), 0)
};
}
function runtime() {
return resolveGlobalSingleton(Symbol.for("openclaw.gitReadCache"), () => ({}), (state) => {
state.closing ??= Promise.resolve().then(async () => {
const caches = state.caches;
state.caches = void 0;
if (caches) await Promise.all(Object.values(caches).map((cache) => cache.close()));
}).finally(() => {
state.closing = void 0;
});
return state.closing;
});
}
function releaseGitReadCache(type, signal) {
const caches = runtime().caches;
if (caches) (type === "checkout.context" ? caches.context : caches.branchFacts).release(signal);
}
function runGitReadOperation(operation, options) {
const state = runtime();
if (state.closing) return Promise.reject(/* @__PURE__ */ new Error("Git reads are unavailable while the Gateway is restarting"));
const { context, branchFacts, diff, branches, baseline } = state.caches ??= createReadCaches();
switch (operation.type) {
case "checkout.revision": return runGitWorkerOperation(operation, options);
case "checkout.context": return context.read(operation.input, options);
case "pull-request.branch-facts": return branchFacts.read(operation.input, options);
case "checkout.diff": return diff.read(operation.input, options);
case "repository.branches": return branches.read(operation.input, options);
case "checkout.baseline": return baseline.read(operation.input, options);
}
throw new Error("Unsupported Git read operation");
}
//#endregion
export { runGitReadOperation as n, createRetainedCache as r, releaseGitReadCache as t };