type ContextStore = Record; const contextStack: ContextStore[] = []; const defaultContext: ContextStore = {}; const debug = JSON.parse(localStorage.getItem("debug") || "false"); export function setDefaultContext(context: ContextStore) { Object.assign(defaultContext, context); } export function withContext(context: ContextStore, fn: () => T): T { contextStack.push(context); try { return fn(); } finally { contextStack.pop(); } } export const ctx = new Proxy( {}, { get(_, prop: string) { for (let i = contextStack.length - 1; i >= 0; i--) { if (prop in contextStack[i]) return contextStack[i][prop]; } if (prop in defaultContext) return defaultContext[prop]; throw new Error(`Context variable '${prop}' is not defined.`); }, }, ) as Record; export function getContext() { return ctx; } export function getContextItem(prop: string): T { return ctx[prop] as T; } export function setContextItem(prop: string, value: T) { Object.assign(contextStack[contextStack.length - 1] ?? defaultContext, { [prop]: value, }); } if (debug) { setInterval(() => { let ctxEl = document.getElementById("context"); if (!ctxEl) { ctxEl = document.createElement("div"); ctxEl.id = "context"; document.body.append(ctxEl); } ctxEl.innerHTML = ""; const div = document.createElement("div"); const pre = document.createElement("pre"); const h3 = document.createElement("h3"); h3.textContent = "Default"; div.append(h3); pre.textContent = safeStringify(defaultContext); div.append(pre); ctxEl.append(div); for (const [idx, ctx] of contextStack.entries()) { const div = document.createElement("div"); const pre = document.createElement("pre"); const h3 = document.createElement("h3"); h3.textContent = "CTX " + idx; div.append(h3); pre.textContent = safeStringify(ctx); div.append(pre); ctxEl.append(div); } }, 1000); } function safeStringify(obj: any) { const seen = new WeakSet(); return JSON.stringify(obj, (key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { return "[Circular]"; // Replace circular references } seen.add(value); } return value; }, 2); }