135 lines
3.9 KiB
JavaScript
135 lines
3.9 KiB
JavaScript
// Shared hook composition runtime.
|
|
// The function stays local to each generated probe while the registry state is
|
|
// shared through window, allowing independently generated probes to compose.
|
|
function getHookRegistry(root) {
|
|
const registryKey = "__JSRA_HOOK_REGISTRY__";
|
|
const existing = root[registryKey];
|
|
if (existing && existing.version === 1) return existing;
|
|
|
|
const targets = new WeakMap();
|
|
|
|
function targetEntries(target, create = false) {
|
|
let entries = targets.get(target);
|
|
if (!entries && create) {
|
|
entries = new Map();
|
|
targets.set(target, entries);
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
function entryFor(target, key, create = false) {
|
|
const entries = targetEntries(target, create);
|
|
if (!entries) return null;
|
|
let entry = entries.get(key);
|
|
if (!entry && create) {
|
|
if (!target || typeof target[key] !== "function") return null;
|
|
entry = {
|
|
target,
|
|
key,
|
|
base: target[key],
|
|
current: target[key],
|
|
layers: []
|
|
};
|
|
entries.set(key, entry);
|
|
}
|
|
return entry || null;
|
|
}
|
|
|
|
function assign(entry, value) {
|
|
try {
|
|
entry.target[entry.key] = value;
|
|
return entry.target[entry.key] === value;
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function rebuild(entry) {
|
|
let current = entry.base;
|
|
for (const layer of entry.layers) {
|
|
current = layer.factory(current);
|
|
if (typeof current !== "function")
|
|
throw new TypeError(`Hook factory did not return a function: ${layer.id}`);
|
|
}
|
|
entry.current = current;
|
|
if (!assign(entry, current)) throw new TypeError(`Hook assignment failed: ${entry.key}`);
|
|
return current;
|
|
}
|
|
|
|
function adoptExternal(entry) {
|
|
const current = entry.target[entry.key];
|
|
if (current !== entry.current && typeof current === "function") {
|
|
entry.base = current;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function add(target, key, id, factory) {
|
|
if (!target || typeof target[key] !== "function") return null;
|
|
const entry = entryFor(target, key, true);
|
|
const duplicate = entry.layers.find(layer => layer.id === id);
|
|
if (duplicate) {
|
|
adoptExternal(entry);
|
|
try { rebuild(entry); } catch (_) {}
|
|
return { entry, id, duplicate: true };
|
|
}
|
|
adoptExternal(entry);
|
|
const layer = { id, factory };
|
|
entry.layers.push(layer);
|
|
try {
|
|
rebuild(entry);
|
|
return { entry, id, duplicate: false };
|
|
} catch (error) {
|
|
entry.layers = entry.layers.filter(item => item !== layer);
|
|
try { rebuild(entry); } catch (_) {}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
function reconcile(target, key) {
|
|
const entry = entryFor(target, key, false);
|
|
if (!entry) return { status: "none", changed: false };
|
|
const changed = adoptExternal(entry);
|
|
if (!changed) return { status: "ok", changed: false };
|
|
try {
|
|
rebuild(entry);
|
|
return { status: "rebuilt", changed: true };
|
|
} catch (error) {
|
|
return { status: "error", changed: true, error: String(error) };
|
|
}
|
|
}
|
|
|
|
function remove(target, key, id) {
|
|
const entry = entryFor(target, key, false);
|
|
if (!entry) return false;
|
|
entry.layers = entry.layers.filter(layer => layer.id !== id);
|
|
if (entry.layers.length === 0) {
|
|
assign(entry, entry.base);
|
|
const entries = targetEntries(target, false);
|
|
if (entries) entries.delete(key);
|
|
return true;
|
|
}
|
|
rebuild(entry);
|
|
return true;
|
|
}
|
|
|
|
const registry = {
|
|
version: 1,
|
|
add,
|
|
reconcile,
|
|
remove,
|
|
native(target, key) {
|
|
const entry = entryFor(target, key, false);
|
|
return entry ? entry.base : target && target[key];
|
|
},
|
|
internalDepth: 0,
|
|
runInternal(callback) {
|
|
this.internalDepth += 1;
|
|
try { return callback(); } finally { this.internalDepth -= 1; }
|
|
}
|
|
};
|
|
root[registryKey] = registry;
|
|
return registry;
|
|
}
|