@shadow-js/devtools 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"agent.js","names":["now","isObject","value","toNumber","describeSide","html","preview","createAgent","clock","options","previewLength","maxPending","model","InspectorModel","events","RingBuffer","stats","ids","ignored","signals","components","stores","nextId","seq","paused","quietDepth","inspectionHook","pending","scheduled","queuedFlush","flushOpen","listeners","detachers","finalizer","id","emit","record","deliver","batch","listener","at","event","openFlush","held","lookup","register","existing","registerLeaf","isIgnored","parent","mapIds","out","values","i","nodesIn","push","candidate","child","entry","receive","kind","EVENT","a","b","name","d","c","e","refs","code","errorMessage","handle","readStoreHandle","snapshot","set","signalValue","node","fail","request","read","toEditableText","parsed","parseEditableText","write","store","clear","readInspections","error","agent","hook","detach","fn","snapshotOf","own","ref"],"sources":["../../src/agent.ts"],"sourcesContent":["import { readInspections } from \"./inspection\";\nimport { type GraphSnapshot, snapshotOf } from \"./inspector\";\nimport { InspectorModel, type ModelLimits, type ModelState, type NodeRecord } from \"./model\";\nimport { errorMessage, parseEditableText, preview, toEditableText } from \"./preview\";\nimport {\n type DevtoolsHook,\n EVENT,\n type HookSink,\n NO_ID,\n type StoreHandle,\n type WireEvent,\n} from \"./protocol\";\nimport { RingBuffer } from \"./ring\";\n\n/**\n * Options of {@link createAgent}. Every field is optional.\n *\n * @since 1.0.0\n */\nexport interface AgentOptions {\n /** Capacities of the model's buffers. The values left out take the defaults of `DEFAULT_LIMITS`. */\n limits?: Partial<ModelLimits>;\n /**\n * Raw events kept for the events log. The newest are kept; older ones are overwritten and counted\n * in `dropped`.\n *\n * @default 10000\n */\n events?: number;\n /**\n * Most events waiting for subscribers. When this many are pending they are delivered at once,\n * instead of at the end of the current microtask.\n *\n * @default 10000\n */\n maxPending?: number;\n /**\n * Maximum length of a value preview, in characters.\n *\n * @default 80\n */\n previewLength?: number;\n /**\n * The clock, in milliseconds. Defaults to `performance.now` (or `Date.now` where it is missing).\n */\n now?: () => number;\n /**\n * Writes a signal node for the value editor. Pass `writeSignal` from `@shadow-js/core/extend`.\n * Without it signals are shown but not editable: a write is refused with \"Editing is not enabled.\".\n */\n writeSignal?: (node: never, value: never) => unknown;\n}\n\n/**\n * Counters of what the agent saw. They keep growing while the agent runs; {@link Agent.clear} does not\n * reset them.\n *\n * @since 1.0.0\n */\nexport interface AgentStats {\n /** Events the agent was offered while it was not paused. */\n received: number;\n /** Events turned into wire events. */\n recorded: number;\n /**\n * Events the agent skipped on purpose: objects created inside {@link Agent.quiet} (the tools' own\n * UI), dispose and unmount events for objects it never named, malformed store handles, and event\n * kinds from a newer runtime.\n */\n ignored: number;\n /**\n * Writes, runs and mounts about objects the agent never saw created (reported before it attached,\n * when the hook's replay buffer had been released).\n */\n unknown: number;\n}\n\n/**\n * A command a panel sends to the agent, answered by {@link Agent.handle}. All fields are plain data,\n * so a command can cross a message channel:\n *\n * - `snapshot`: the model's state.\n * - `value` and `write`: read a signal as editable text, or write it.\n * - `store` and `store-set`: read a store as editable text, or write one path of it.\n * - `highlight`: the agent acknowledges it; the page draws the highlight (see `createLocalBackend`).\n * - `pause`, `resume`, `clear`: control recording. `stats`: the counters.\n * - `inspect`: every inspection source registered with `registerInspection`, read now.\n *\n * @since 1.0.0\n */\nexport type AgentRequest =\n | { type: \"snapshot\" }\n | { type: \"value\"; id: number }\n | { type: \"write\"; id: number; text: string }\n | { type: \"store\"; id: number }\n | { type: \"store-set\"; id: number; path: Array<string | number>; text: string }\n | { type: \"highlight\"; id: number | null }\n | { type: \"pause\" }\n | { type: \"resume\" }\n | { type: \"clear\" }\n | { type: \"stats\" }\n | { type: \"inspect\" };\n\n/**\n * The answer to an {@link AgentRequest}: the result, or an error message to show the user.\n *\n * @since 1.0.0\n */\nexport type AgentResponse = { ok: true; result: unknown } | { ok: false; error: string };\n\n/**\n * The value of a node, as text, for the value editor.\n *\n * @since 1.0.0\n */\nexport interface ValueResult {\n /** The node that was asked for. */\n id: number;\n /** `signal` for a signal, `store` for a store. */\n kind: \"signal\" | \"store\";\n /** The value as editable text (see {@link toEditableText}). */\n text: string;\n /** True when the panel may write it: a store always, a signal only when `writeSignal` is set. */\n editable: boolean;\n}\n\n/**\n * The agent. It receives the runtime's own objects from the hook, turns them into id-based\n * {@link WireEvent}s and folds them into an {@link InspectorModel}. Recording is O(1) per event and\n * every buffer is bounded: the raw event ring, the model's run, write and flush rings, the disposed-node\n * graveyard and the live node count.\n *\n * A development tool: the hook it attaches to is installed only in development builds (see\n * `installHook`).\n *\n * @since 1.0.0\n */\nexport interface Agent extends HookSink {\n /** The model the events are folded into. */\n readonly model: InspectorModel;\n /** The newest raw events, as many as the `events` option holds (default 10000). */\n readonly events: RingBuffer<WireEvent>;\n /** Counters of what the agent saw. */\n readonly stats: Readonly<AgentStats>;\n /** True while the agent drops the events the runtime reports (see {@link Agent.pause}). */\n readonly paused: boolean;\n /**\n * Attaches the agent to `hook`, so that the hook delivers its events to the agent. Pass the hook\n * from `installHook` or `getHook`.\n *\n * @param hook The hook to listen to.\n * @returns A function that detaches the agent from the hook.\n */\n attach(hook: DevtoolsHook): () => void;\n /**\n * Calls `listener` with batches of new events: once per microtask, or sooner when `maxPending`\n * events are waiting. Only events recorded after the call are delivered.\n *\n * @param listener Receives each batch, oldest first.\n * @returns A function that unsubscribes the listener.\n */\n subscribe(listener: (events: readonly WireEvent[]) => void): () => void;\n /** Delivers the pending events to the subscribers now, instead of at the end of the microtask. */\n flush(): void;\n /**\n * Runs `fn` while the agent ignores everything the runtime creates, and everything created under\n * those nodes. Use it to mount the tools' own UI, so that the tools never inspect themselves. An error\n * thrown by `fn` propagates.\n *\n * @param fn The work to run.\n * @returns What `fn` returns.\n */\n quiet<T>(fn: () => T): T;\n /** Stops recording: events the runtime reports are dropped until {@link Agent.resume}. */\n pause(): void;\n /** Resumes recording after {@link Agent.pause}. */\n resume(): void;\n /**\n * Forgets everything recorded so far: the model's nodes and records, the event ring, and the\n * references to live nodes. Nodes created later are tracked; live ones are not, and later events about\n * them count as unknown. {@link AgentStats} are not reset.\n */\n clear(): void;\n /** The graph snapshot of the model (see `snapshotOf`). Each call builds a new object. */\n snapshot(): GraphSnapshot;\n /** The model state, for sending to a remote panel. */\n state(): ModelState;\n /**\n * Answers a panel command. Never throws: a failure, such as an error from writing user state, comes\n * back as `ok: false`, and so does an unknown command.\n *\n * @param request The command.\n * @returns The answer.\n */\n handle(request: AgentRequest): AgentResponse;\n /**\n * The connected DOM nodes that the component `id` rendered, for highlighting. Empty when the\n * component is unknown or its nodes left the page. The agent holds the nodes weakly.\n *\n * @param id The component's node id.\n * @returns The nodes.\n */\n nodesOf(id: number): Node[];\n /**\n * Detaches from every hook it was attached to, and drops the listeners and the references to live\n * signals, components and stores. The model and the event ring keep what they recorded.\n */\n dispose(): void;\n}\n\n/**\n * Store handles by node id, held weakly: the handle lives as long as the store it reads, because the\n * runtime keeps it reachable from the store's root (see `createDeepStore` in `@shadow-js/core`).\n */\ntype Registry = Map<number, WeakRef<StoreHandle>>;\n\nfunction now(): number {\n return typeof performance !== \"undefined\" ? performance.now() : Date.now();\n}\n\nfunction isObject(value: unknown): value is object {\n return (typeof value === \"object\" && value !== null) || typeof value === \"function\";\n}\n\nfunction toNumber(value: unknown): number {\n return typeof value === \"number\" && Number.isFinite(value) ? value : 0;\n}\n\nfunction describeSide(value: unknown): string {\n if (typeof value === \"string\") return value.length > 200 ? `${value.slice(0, 199)}…` : value;\n if (typeof Node !== \"undefined\" && value instanceof Node) {\n if (value instanceof Element) {\n const html = value.outerHTML;\n return html.length > 200 ? `${html.slice(0, 199)}…` : html;\n }\n return `#${value.nodeName}`;\n }\n return preview(value, 200);\n}\n\n/**\n * Creates an agent that records the runtime's events. Attach it to a hook with `agent.attach(hook)`.\n * A development tool: the events come only from development builds, where the hook is installed.\n *\n * @param options Buffer sizes, clock and the signal writer. Every field is optional.\n * @returns The agent.\n * @example\n * import { createAgent, installHook } from \"@shadow-js/devtools\";\n * import { writeSignal } from \"@shadow-js/core/extend\";\n *\n * const hook = installHook();\n * const agent = createAgent({ writeSignal });\n * if (hook !== undefined) agent.attach(hook);\n * agent.snapshot().counts.nodes;\n * @since 1.0.0\n */\nexport function createAgent(options: AgentOptions = {}): Agent {\n const clock = options.now ?? now;\n const previewLength = options.previewLength ?? 80;\n const maxPending = options.maxPending ?? 10_000;\n const model = new InspectorModel(options.limits);\n const events = new RingBuffer<WireEvent>(options.events ?? 10_000);\n const stats: AgentStats = { received: 0, recorded: 0, ignored: 0, unknown: 0 };\n\n // Objects are named by id through weak maps: the agent never keeps the runtime's nodes alive.\n const ids = new WeakMap<object, number>();\n const ignored = new WeakSet<object>();\n const signals = new Map<number, WeakRef<object>>();\n const components = new Map<number, WeakRef<Node>[]>();\n const stores: Registry = new Map();\n let nextId = 1;\n let seq = 0;\n let paused = false;\n let quietDepth = 0;\n /** The hook the agent listens to: its registry of inspection sources answers `inspect`. */\n let inspectionHook: DevtoolsHook | undefined;\n let pending: WireEvent[] = [];\n let scheduled = false;\n /** A flush that started but has not recorded any run yet: empty flushes are not worth a record. */\n let queuedFlush: { render: number; user: number; t: number } | null = null;\n let flushOpen = false;\n const listeners = new Set<(events: readonly WireEvent[]) => void>();\n const detachers = new Set<() => void>();\n // Signals, stores and resources have no dispose event of their own when nothing owns them (or their\n // owner never disposes): the finalizer retires their node once the runtime has collected them. It is\n // registered once per object, so a resource that reports many states is not registered many times.\n const finalizer: FinalizationRegistry<number> | undefined =\n typeof FinalizationRegistry === \"function\"\n ? new FinalizationRegistry<number>((id) => {\n signals.delete(id);\n stores.delete(id);\n if (model.nodes.has(id)) emit({ k: \"owner-\", seq: 0, t: 0, id });\n })\n : undefined;\n\n model.onBury = (record: NodeRecord) => {\n signals.delete(record.id);\n components.delete(record.id);\n stores.delete(record.id);\n };\n\n const deliver = (): void => {\n scheduled = false;\n if (pending.length === 0) return;\n const batch = pending;\n pending = [];\n for (const listener of listeners) listener(batch);\n };\n\n /** Stamps, records and fans out one event. */\n function emit(event: WireEvent, at?: number): void {\n seq += 1;\n event.seq = seq;\n event.t = at ?? clock();\n stats.recorded += 1;\n events.push(event);\n model.apply(event);\n if (listeners.size === 0) return;\n pending.push(event);\n if (pending.length >= maxPending) {\n deliver();\n } else if (!scheduled) {\n scheduled = true;\n queueMicrotask(deliver);\n }\n }\n\n /** Emits the held-back flush start before the first event that belongs to it. */\n const openFlush = (): void => {\n if (queuedFlush === null) return;\n const held = queuedFlush;\n queuedFlush = null;\n flushOpen = true;\n emit({ k: \"flush+\", seq: 0, t: 0, render: held.render, user: held.user }, held.t);\n };\n\n const lookup = (value: unknown): number => {\n if (!isObject(value)) return NO_ID;\n return ids.get(value) ?? NO_ID;\n };\n\n const register = (value: object): number => {\n const existing = ids.get(value);\n if (existing !== undefined) return existing;\n const id = nextId;\n nextId += 1;\n ids.set(value, id);\n return id;\n };\n\n /**\n * Like `register`, for a leaf node (signal, store, resource): the finalizer retires it once the runtime\n * has collected the object. Owners and computations are not registered: an owner can be unreachable\n * while its signals are still in use, and retiring it would bury them too.\n */\n const registerLeaf = (value: object): number => {\n const existing = ids.get(value);\n if (existing !== undefined) return existing;\n const id = register(value);\n finalizer?.register(value, id);\n return id;\n };\n\n /** True (and remembered) when the object belongs to the tools' own UI. */\n const isIgnored = (value: unknown, parent: unknown): boolean => {\n if (!isObject(value)) return false;\n if (quietDepth > 0 || (isObject(parent) && ignored.has(parent))) {\n ignored.add(value);\n return true;\n }\n return ignored.has(value);\n };\n\n const mapIds = (values: unknown): number[] => {\n const out: number[] = [];\n if (!Array.isArray(values)) return out;\n for (let i = 0; i < values.length; i += 1) {\n const id = lookup(values[i]);\n if (id !== NO_ID) out.push(id);\n }\n return out;\n };\n\n const nodesIn = (value: unknown): WeakRef<Node>[] => {\n const out: WeakRef<Node>[] = [];\n if (typeof Node === \"undefined\") return out;\n const push = (candidate: unknown): void => {\n if (candidate instanceof DocumentFragment) {\n for (const child of Array.from(candidate.childNodes)) out.push(new WeakRef(child));\n } else if (candidate instanceof Node) {\n out.push(new WeakRef(candidate));\n }\n };\n if (Array.isArray(value)) for (const entry of value) push(entry);\n else push(value);\n return out;\n };\n\n function receive(kind: number, a: unknown, b: unknown, c: unknown, d: unknown, e: unknown): void {\n if (paused) return;\n stats.received += 1;\n switch (kind) {\n case EVENT.OWNER_CREATED: {\n if (!isObject(a) || isIgnored(a, b)) {\n stats.ignored += 1;\n return;\n }\n const name = typeof d === \"string\" ? d : undefined;\n emit({\n k: \"owner+\",\n seq: 0,\n t: 0,\n id: register(a),\n parent: lookup(b),\n label: typeof c === \"string\" ? c : \"owner\",\n ...(name === undefined ? {} : { name }),\n });\n return;\n }\n case EVENT.OWNER_DISPOSED: {\n const id = lookup(a);\n if (id === NO_ID) {\n stats.ignored += 1;\n return;\n }\n emit({ k: \"owner-\", seq: 0, t: 0, id });\n return;\n }\n case EVENT.SIGNAL_CREATED: {\n if (!isObject(a) || isIgnored(a, b)) {\n stats.ignored += 1;\n return;\n }\n const id = registerLeaf(a);\n signals.set(id, new WeakRef(a));\n const name = typeof c === \"string\" ? c : undefined;\n emit({\n k: \"signal+\",\n seq: 0,\n t: 0,\n id,\n owner: lookup(b),\n preview: preview(d, previewLength),\n ...(name === undefined ? {} : { name }),\n });\n return;\n }\n case EVENT.SIGNAL_WRITTEN: {\n const id = lookup(a);\n if (id === NO_ID) {\n if (isObject(a) && ignored.has(a)) stats.ignored += 1;\n else stats.unknown += 1;\n return;\n }\n emit({\n k: \"write\",\n seq: 0,\n t: 0,\n id,\n preview: preview(b, previewLength),\n writer: lookup(c),\n observers: mapIds(d),\n });\n return;\n }\n case EVENT.COMPUTATION_CREATED: {\n if (!isObject(a) || isIgnored(a, b)) {\n stats.ignored += 1;\n return;\n }\n const name = typeof d === \"string\" ? d : undefined;\n emit({\n k: \"comp+\",\n seq: 0,\n t: 0,\n id: register(a),\n owner: lookup(b),\n kind: c === \"memo\" ? \"memo\" : \"effect\",\n render: e === true,\n ...(name === undefined ? {} : { name }),\n });\n return;\n }\n case EVENT.COMPUTATION_RUN: {\n const id = lookup(a);\n if (id === NO_ID) {\n if (isObject(a) && ignored.has(a)) stats.ignored += 1;\n else stats.unknown += 1;\n return;\n }\n openFlush();\n emit({\n k: \"run\",\n seq: 0,\n t: 0,\n id,\n start: toNumber(b),\n duration: toNumber(c),\n changed: d === true,\n sources: mapIds(e),\n });\n return;\n }\n case EVENT.COMPUTATION_DISPOSED: {\n const id = lookup(a);\n if (id === NO_ID) {\n stats.ignored += 1;\n return;\n }\n emit({ k: \"comp-\", seq: 0, t: 0, id });\n return;\n }\n case EVENT.COMPONENT_MOUNTED: {\n const id = lookup(a);\n if (id === NO_ID) {\n if (isObject(a) && ignored.has(a)) stats.ignored += 1;\n else stats.unknown += 1;\n return;\n }\n const refs = nodesIn(c);\n if (refs.length > 0) components.set(id, refs);\n emit({ k: \"mount\", seq: 0, t: 0, id, name: typeof b === \"string\" ? b : \"Component\" });\n // A node beyond maxNodes is never buried, so its entry would never be removed: forget it.\n if (!model.nodes.has(id)) components.delete(id);\n return;\n }\n case EVENT.COMPONENT_UNMOUNTED: {\n const id = lookup(a);\n if (id === NO_ID) {\n stats.ignored += 1;\n return;\n }\n components.delete(id);\n emit({ k: \"unmount\", seq: 0, t: 0, id });\n return;\n }\n case EVENT.RESOURCE_STATE: {\n if (!isObject(a) || isIgnored(a, d)) {\n stats.ignored += 1;\n return;\n }\n const name = typeof c === \"string\" ? c : undefined;\n emit({\n k: \"resource\",\n seq: 0,\n t: 0,\n id: registerLeaf(a),\n owner: lookup(d),\n state: typeof b === \"string\" ? b : String(b),\n ...(name === undefined ? {} : { name }),\n });\n return;\n }\n case EVENT.ERROR: {\n const code = typeof c === \"string\" ? c : undefined;\n emit({\n k: \"error\",\n seq: 0,\n t: 0,\n id: lookup(a),\n message: errorMessage(b),\n ...(code === undefined ? {} : { code }),\n });\n return;\n }\n case EVENT.HYDRATION_MISMATCH:\n emit({\n k: \"hydration\",\n seq: 0,\n t: 0,\n message: typeof a === \"string\" ? a : errorMessage(a),\n expected: describeSide(b),\n actual: describeSide(c),\n });\n return;\n case EVENT.FLUSH_START:\n queuedFlush = { render: toNumber(a), user: toNumber(b), t: clock() };\n return;\n case EVENT.FLUSH_END:\n queuedFlush = null;\n if (flushOpen) {\n flushOpen = false;\n emit({ k: \"flush-\", seq: 0, t: 0, runs: toNumber(a) });\n }\n return;\n case EVENT.STORE_CREATED: {\n if (!isObject(a) || isIgnored(a, b)) {\n stats.ignored += 1;\n return;\n }\n const handle = readStoreHandle(a);\n if (handle === null) {\n stats.ignored += 1;\n return;\n }\n const id = registerLeaf(a);\n stores.set(id, new WeakRef(handle));\n emit({ k: \"store+\", seq: 0, t: 0, id, owner: lookup(b), name: handle.name });\n // A store beyond maxNodes is never buried: nothing else would forget its handle.\n if (!model.nodes.has(id)) stores.delete(id);\n return;\n }\n default:\n // An event kind from a newer runtime: ignore it.\n stats.ignored += 1;\n }\n }\n\n const readStoreHandle = (value: object): StoreHandle | null => {\n const name: unknown = Reflect.get(value, \"name\");\n const snapshot: unknown = Reflect.get(value, \"snapshot\");\n const set: unknown = Reflect.get(value, \"set\");\n if (typeof name !== \"string\" || typeof snapshot !== \"function\" || typeof set !== \"function\") {\n return null;\n }\n return value as StoreHandle;\n };\n\n const signalValue = (id: number): { found: true; value: unknown } | { found: false } => {\n const node = signals.get(id)?.deref();\n if (node === undefined) return { found: false };\n return { found: true, value: Reflect.get(node, \"value\") };\n };\n\n const fail = (error: string): AgentResponse => ({ ok: false, error });\n\n function handle(request: AgentRequest): AgentResponse {\n try {\n switch (request.type) {\n case \"snapshot\":\n return { ok: true, result: model.exportState() };\n case \"stats\":\n return { ok: true, result: { ...stats, events: events.size, dropped: events.dropped } };\n case \"value\": {\n const read = signalValue(request.id);\n if (!read.found) return fail(\"The signal is gone.\");\n const result: ValueResult = {\n id: request.id,\n kind: \"signal\",\n text: toEditableText(read.value),\n editable: options.writeSignal !== undefined,\n };\n return { ok: true, result };\n }\n case \"write\": {\n const node = signals.get(request.id)?.deref();\n if (node === undefined) return fail(\"The signal is gone.\");\n if (options.writeSignal === undefined) return fail(\"Editing is not enabled.\");\n const parsed = parseEditableText(request.text);\n if (!parsed.ok) return fail(parsed.error);\n const write = options.writeSignal as (node: object, value: unknown) => unknown;\n write(node, parsed.value);\n return { ok: true, result: null };\n }\n case \"store\": {\n const store = stores.get(request.id)?.deref();\n if (store === undefined) return fail(\"The store is gone.\");\n const result: ValueResult = {\n id: request.id,\n kind: \"store\",\n text: toEditableText(store.snapshot()),\n editable: true,\n };\n return { ok: true, result };\n }\n case \"store-set\": {\n const store = stores.get(request.id)?.deref();\n if (store === undefined) return fail(\"The store is gone.\");\n const parsed = parseEditableText(request.text);\n if (!parsed.ok) return fail(parsed.error);\n store.set(request.path, parsed.value);\n return { ok: true, result: null };\n }\n case \"pause\":\n paused = true;\n return { ok: true, result: null };\n case \"resume\":\n paused = false;\n return { ok: true, result: null };\n case \"clear\":\n clear();\n return { ok: true, result: null };\n case \"highlight\":\n // Drawing needs the page; the page side draws it (see createLocalBackend).\n return { ok: true, result: null };\n case \"inspect\":\n return {\n ok: true,\n result: { sources: readInspections(inspectionHook?.inspections?.() ?? []) },\n };\n default:\n return fail(\"Unknown command.\");\n }\n } catch (error) {\n // Writing user state can throw (a validation in the app): report it to the panel.\n return fail(errorMessage(error));\n }\n }\n\n function clear(): void {\n model.importState({\n lastSeq: model.lastSeq,\n overflow: 0,\n unknown: 0,\n nodes: [],\n graveyard: [],\n runs: [],\n writes: [],\n flushes: [],\n errors: [],\n hydration: [],\n });\n events.clear();\n pending = [];\n queuedFlush = null;\n flushOpen = false;\n signals.clear();\n components.clear();\n stores.clear();\n }\n\n const agent: Agent = {\n model,\n events,\n stats,\n get paused(): boolean {\n return paused;\n },\n receive,\n attach(hook): () => void {\n inspectionHook = hook;\n const detach = hook.attach(agent);\n detachers.add(detach);\n return () => {\n detachers.delete(detach);\n detach();\n };\n },\n subscribe(listener): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n flush: deliver,\n quiet<T>(fn: () => T): T {\n quietDepth += 1;\n try {\n return fn();\n } finally {\n quietDepth -= 1;\n }\n },\n pause(): void {\n paused = true;\n },\n resume(): void {\n paused = false;\n },\n clear,\n snapshot: () => snapshotOf(model),\n state: () => model.exportState(),\n handle,\n nodesOf(id): Node[] {\n const out: Node[] = [];\n const own = components.get(id);\n if (own !== undefined) {\n for (const ref of own) {\n const node = ref.deref();\n if (node?.isConnected === true) out.push(node);\n }\n }\n return out;\n },\n dispose(): void {\n for (const detach of detachers) detach();\n detachers.clear();\n inspectionHook = undefined;\n listeners.clear();\n pending = [];\n signals.clear();\n components.clear();\n stores.clear();\n model.onBury = undefined;\n },\n };\n return agent;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"agent.js","names":["now","isObject","value","toNumber","describeSide","describeElement","preview","createAgent","clock","options","previewLength","maxPending","model","InspectorModel","events","RingBuffer","stats","ids","ignored","signals","components","stores","nextId","seq","paused","quietDepth","inspectionHook","pending","scheduled","queuedFlush","flushOpen","listeners","detachers","finalizer","id","emit","record","deliver","batch","failure","listener","error","at","event","openFlush","held","lookup","register","existing","registerLeaf","isIgnored","parent","mapIds","out","values","i","nodesIn","push","candidate","child","entry","receive","kind","EVENT","a","b","name","d","c","e","refs","code","errorMessage","handle","readStoreHandle","snapshot","set","signalValue","node","fail","request","read","toEditableText","parsed","parseEditableText","write","store","clear","readInspections","agent","hook","detach","fn","snapshotOf","own","ref"],"sources":["../../src/agent.ts"],"sourcesContent":["import { readInspections } from \"./inspection\";\nimport { type GraphSnapshot, snapshotOf } from \"./inspector\";\nimport { describeElement } from \"./internal/describe-element\";\nimport { InspectorModel, type ModelLimits, type ModelState, type NodeRecord } from \"./model\";\nimport { errorMessage, parseEditableText, preview, toEditableText } from \"./preview\";\nimport {\n type DevtoolsHook,\n EVENT,\n type HookSink,\n NO_ID,\n type StoreHandle,\n type WireEvent,\n} from \"./protocol\";\nimport { RingBuffer } from \"./ring\";\n\n/**\n * Options of {@link createAgent}. Every field is optional.\n *\n * @since 1.0.0\n */\nexport interface AgentOptions {\n /** Capacities of the model's buffers. The values left out take the defaults of `DEFAULT_LIMITS`. */\n limits?: Partial<ModelLimits>;\n /**\n * Raw events kept for the events log. The newest are kept; older ones are overwritten and counted\n * in `dropped`.\n *\n * @default 10000\n */\n events?: number;\n /**\n * Most events waiting for subscribers. When this many are pending they are delivered at once,\n * instead of at the end of the current microtask.\n *\n * @default 10000\n */\n maxPending?: number;\n /**\n * Maximum length of a value preview, in characters.\n *\n * @default 80\n */\n previewLength?: number;\n /**\n * The clock, in milliseconds. Defaults to `performance.now` (or `Date.now` where it is missing).\n */\n now?: () => number;\n /**\n * Writes a signal node for the value editor. Pass `writeSignal` from `@shadow-js/core/extend`.\n * Without it signals are shown but not editable: a write is refused with \"Editing is not enabled.\".\n */\n writeSignal?: (node: never, value: never) => unknown;\n}\n\n/**\n * Counters of what the agent saw. They keep growing while the agent runs; {@link Agent.clear} does not\n * reset them.\n *\n * @since 1.0.0\n */\nexport interface AgentStats {\n /** Events the agent was offered while it was not paused. */\n received: number;\n /** Events turned into wire events. */\n recorded: number;\n /**\n * Events the agent skipped on purpose: objects created inside {@link Agent.quiet} (the tools' own\n * UI), dispose and unmount events for objects it never named, malformed store handles, and event\n * kinds from a newer runtime.\n */\n ignored: number;\n /**\n * Writes, runs and mounts about objects the agent never saw created (reported before it attached,\n * when the hook's replay buffer had been released).\n */\n unknown: number;\n}\n\n/**\n * A command a panel sends to the agent, answered by {@link Agent.handle}. All fields are plain data,\n * so a command can cross a message channel:\n *\n * - `snapshot`: the model's state.\n * - `value` and `write`: read a signal as editable text, or write it.\n * - `store` and `store-set`: read a store as editable text, or write one path of it.\n * - `highlight`: the agent acknowledges it; the page draws the highlight (see `createLocalBackend`).\n * - `pause`, `resume`, `clear`: control recording. `stats`: the counters.\n * - `inspect`: every inspection source registered with `registerInspection`, read now.\n *\n * @since 1.0.0\n */\nexport type AgentRequest =\n | { type: \"snapshot\" }\n | { type: \"value\"; id: number }\n | { type: \"write\"; id: number; text: string }\n | { type: \"store\"; id: number }\n | { type: \"store-set\"; id: number; path: Array<string | number>; text: string }\n | { type: \"highlight\"; id: number | null }\n | { type: \"pause\" }\n | { type: \"resume\" }\n | { type: \"clear\" }\n | { type: \"stats\" }\n | { type: \"inspect\" };\n\n/**\n * The answer to an {@link AgentRequest}: the result, or an error message to show the user.\n *\n * @since 1.0.0\n */\nexport type AgentResponse = { ok: true; result: unknown } | { ok: false; error: string };\n\n/**\n * The value of a node, as text, for the value editor.\n *\n * @since 1.0.0\n */\nexport interface ValueResult {\n /** The node that was asked for. */\n id: number;\n /** `signal` for a signal, `store` for a store. */\n kind: \"signal\" | \"store\";\n /** The value as editable text (see {@link toEditableText}). */\n text: string;\n /** True when the panel may write it: a store always, a signal only when `writeSignal` is set. */\n editable: boolean;\n}\n\n/**\n * The agent. It receives the runtime's own objects from the hook, turns them into id-based\n * {@link WireEvent}s and folds them into an {@link InspectorModel}. Recording is O(1) per event and\n * every buffer is bounded: the raw event ring, the model's run, write and flush rings, the disposed-node\n * graveyard and the live node count.\n *\n * A development tool: the hook it attaches to is installed only in development builds (see\n * `installHook`).\n *\n * @since 1.0.0\n */\nexport interface Agent extends HookSink {\n /** The model the events are folded into. */\n readonly model: InspectorModel;\n /** The newest raw events, as many as the `events` option holds (default 10000). */\n readonly events: RingBuffer<WireEvent>;\n /** Counters of what the agent saw. */\n readonly stats: Readonly<AgentStats>;\n /** True while the agent drops the events the runtime reports (see {@link Agent.pause}). */\n readonly paused: boolean;\n /**\n * Attaches the agent to `hook`, so that the hook delivers its events to the agent. Pass the hook\n * from `installHook` or `getHook`.\n *\n * @param hook The hook to listen to.\n * @returns A function that detaches the agent from the hook.\n */\n attach(hook: DevtoolsHook): () => void;\n /**\n * Calls `listener` with batches of new events: once per microtask, or sooner when `maxPending`\n * events are waiting. Only events recorded after the call are delivered.\n *\n * @param listener Receives each batch, oldest first.\n * @returns A function that unsubscribes the listener.\n */\n subscribe(listener: (events: readonly WireEvent[]) => void): () => void;\n /** Delivers the pending events to the subscribers now, instead of at the end of the microtask. */\n flush(): void;\n /**\n * Runs `fn` while the agent ignores everything the runtime creates, and everything created under\n * those nodes. Use it to mount the tools' own UI, so that the tools never inspect themselves. An error\n * thrown by `fn` propagates.\n *\n * @param fn The work to run.\n * @returns What `fn` returns.\n */\n quiet<T>(fn: () => T): T;\n /** Stops recording: events the runtime reports are dropped until {@link Agent.resume}. */\n pause(): void;\n /** Resumes recording after {@link Agent.pause}. */\n resume(): void;\n /**\n * Forgets everything recorded so far: the model's nodes and records, the event ring, and the\n * references to live nodes. Nodes created later are tracked; live ones are not, and later events about\n * them count as unknown. {@link AgentStats} are not reset.\n */\n clear(): void;\n /** The graph snapshot of the model (see `snapshotOf`). Each call builds a new object. */\n snapshot(): GraphSnapshot;\n /** The model state, for sending to a remote panel. */\n state(): ModelState;\n /**\n * Answers a panel command. Never throws: a failure, such as an error from writing user state, comes\n * back as `ok: false`, and so does an unknown command.\n *\n * @param request The command.\n * @returns The answer.\n */\n handle(request: AgentRequest): AgentResponse;\n /**\n * The connected DOM nodes that the component `id` rendered, for highlighting. Empty when the\n * component is unknown or its nodes left the page. The agent holds the nodes weakly.\n *\n * @param id The component's node id.\n * @returns The nodes.\n */\n nodesOf(id: number): Node[];\n /**\n * Detaches from every hook it was attached to, and drops the listeners and the references to live\n * signals, components and stores. The model and the event ring keep what they recorded.\n */\n dispose(): void;\n}\n\n/**\n * Store handles by node id, held weakly: the handle lives as long as the store it reads, because the\n * runtime keeps it reachable from the store's root (see `createDeepStore` in `@shadow-js/core`).\n */\ntype Registry = Map<number, WeakRef<StoreHandle>>;\n\nfunction now(): number {\n return typeof performance !== \"undefined\" ? performance.now() : Date.now();\n}\n\nfunction isObject(value: unknown): value is object {\n return (typeof value === \"object\" && value !== null) || typeof value === \"function\";\n}\n\nfunction toNumber(value: unknown): number {\n return typeof value === \"number\" && Number.isFinite(value) ? value : 0;\n}\n\nfunction describeSide(value: unknown): string {\n if (typeof value === \"string\") return value.length > 200 ? `${value.slice(0, 199)}…` : value;\n if (typeof Node !== \"undefined\" && value instanceof Node) {\n // Built from the first nodes only: `outerHTML` would serialize the whole subtree to keep 200 characters.\n if (value instanceof Element) return describeElement(value, 200);\n return `#${value.nodeName}`;\n }\n return preview(value, 200);\n}\n\n/**\n * Creates an agent that records the runtime's events. Attach it to a hook with `agent.attach(hook)`.\n * A development tool: the events come only from development builds, where the hook is installed.\n *\n * @param options Buffer sizes, clock and the signal writer. Every field is optional.\n * @returns The agent.\n * @example\n * import { createAgent, installHook } from \"@shadow-js/devtools\";\n * import { writeSignal } from \"@shadow-js/core/extend\";\n *\n * const hook = installHook();\n * const agent = createAgent({ writeSignal });\n * if (hook !== undefined) agent.attach(hook);\n * agent.snapshot().counts.nodes;\n * @since 1.0.0\n */\nexport function createAgent(options: AgentOptions = {}): Agent {\n const clock = options.now ?? now;\n const previewLength = options.previewLength ?? 80;\n const maxPending = options.maxPending ?? 10_000;\n const model = new InspectorModel(options.limits);\n const events = new RingBuffer<WireEvent>(options.events ?? 10_000);\n const stats: AgentStats = { received: 0, recorded: 0, ignored: 0, unknown: 0 };\n\n // Objects are named by id through weak maps: the agent never keeps the runtime's nodes alive.\n const ids = new WeakMap<object, number>();\n const ignored = new WeakSet<object>();\n const signals = new Map<number, WeakRef<object>>();\n const components = new Map<number, WeakRef<Node>[]>();\n const stores: Registry = new Map();\n let nextId = 1;\n let seq = 0;\n let paused = false;\n let quietDepth = 0;\n /** The hook the agent listens to: its registry of inspection sources answers `inspect`. */\n let inspectionHook: DevtoolsHook | undefined;\n let pending: WireEvent[] = [];\n let scheduled = false;\n /** A flush that started but has not recorded any run yet: empty flushes are not worth a record. */\n let queuedFlush: { render: number; user: number; t: number } | null = null;\n let flushOpen = false;\n const listeners = new Set<(events: readonly WireEvent[]) => void>();\n const detachers = new Set<() => void>();\n // Signals, stores and resources have no dispose event of their own when nothing owns them (or their\n // owner never disposes): the finalizer retires their node once the runtime has collected them. It is\n // registered once per object, so a resource that reports many states is not registered many times.\n const finalizer: FinalizationRegistry<number> | undefined =\n typeof FinalizationRegistry === \"function\"\n ? new FinalizationRegistry<number>((id) => {\n signals.delete(id);\n stores.delete(id);\n if (model.nodes.has(id)) emit({ k: \"owner-\", seq: 0, t: 0, id });\n })\n : undefined;\n\n model.onBury = (record: NodeRecord) => {\n signals.delete(record.id);\n components.delete(record.id);\n stores.delete(record.id);\n };\n\n const deliver = (): void => {\n scheduled = false;\n if (pending.length === 0) return;\n const batch = pending;\n pending = [];\n // Every subscriber gets the batch, even when an earlier one throws (a closed port, a view that\n // failed): the first error is rethrown afterward, so it is still reported.\n let failure: { error: unknown } | undefined;\n for (const listener of [...listeners]) {\n try {\n listener(batch);\n } catch (error) {\n failure ??= { error };\n }\n }\n if (failure !== undefined) throw failure.error;\n };\n\n /** Stamps, records and fans out one event. */\n function emit(event: WireEvent, at?: number): void {\n seq += 1;\n event.seq = seq;\n event.t = at ?? clock();\n stats.recorded += 1;\n events.push(event);\n model.apply(event);\n if (listeners.size === 0) return;\n pending.push(event);\n if (pending.length >= maxPending) {\n deliver();\n } else if (!scheduled) {\n scheduled = true;\n queueMicrotask(deliver);\n }\n }\n\n /** Emits the held-back flush start before the first event that belongs to it. */\n const openFlush = (): void => {\n if (queuedFlush === null) return;\n const held = queuedFlush;\n queuedFlush = null;\n flushOpen = true;\n emit({ k: \"flush+\", seq: 0, t: 0, render: held.render, user: held.user }, held.t);\n };\n\n const lookup = (value: unknown): number => {\n if (!isObject(value)) return NO_ID;\n return ids.get(value) ?? NO_ID;\n };\n\n const register = (value: object): number => {\n const existing = ids.get(value);\n if (existing !== undefined) return existing;\n const id = nextId;\n nextId += 1;\n ids.set(value, id);\n return id;\n };\n\n /**\n * Like `register`, for a leaf node (signal, store, resource): the finalizer retires it once the runtime\n * has collected the object. Owners and computations are not registered: an owner can be unreachable\n * while its signals are still in use, and retiring it would bury them too.\n */\n const registerLeaf = (value: object): number => {\n const existing = ids.get(value);\n if (existing !== undefined) return existing;\n const id = register(value);\n finalizer?.register(value, id);\n return id;\n };\n\n /** True (and remembered) when the object belongs to the tools' own UI. */\n const isIgnored = (value: unknown, parent: unknown): boolean => {\n if (!isObject(value)) return false;\n if (quietDepth > 0 || (isObject(parent) && ignored.has(parent))) {\n ignored.add(value);\n return true;\n }\n return ignored.has(value);\n };\n\n const mapIds = (values: unknown): number[] => {\n const out: number[] = [];\n if (!Array.isArray(values)) return out;\n for (let i = 0; i < values.length; i += 1) {\n const id = lookup(values[i]);\n if (id !== NO_ID) out.push(id);\n }\n return out;\n };\n\n const nodesIn = (value: unknown): WeakRef<Node>[] => {\n const out: WeakRef<Node>[] = [];\n if (typeof Node === \"undefined\") return out;\n const push = (candidate: unknown): void => {\n if (candidate instanceof DocumentFragment) {\n for (const child of Array.from(candidate.childNodes)) out.push(new WeakRef(child));\n } else if (candidate instanceof Node) {\n out.push(new WeakRef(candidate));\n }\n };\n if (Array.isArray(value)) for (const entry of value) push(entry);\n else push(value);\n return out;\n };\n\n function receive(kind: number, a: unknown, b: unknown, c: unknown, d: unknown, e: unknown): void {\n if (paused) return;\n stats.received += 1;\n switch (kind) {\n case EVENT.OWNER_CREATED: {\n if (!isObject(a) || isIgnored(a, b)) {\n stats.ignored += 1;\n return;\n }\n const name = typeof d === \"string\" ? d : undefined;\n emit({\n k: \"owner+\",\n seq: 0,\n t: 0,\n id: register(a),\n parent: lookup(b),\n label: typeof c === \"string\" ? c : \"owner\",\n ...(name === undefined ? {} : { name }),\n });\n return;\n }\n case EVENT.OWNER_DISPOSED: {\n const id = lookup(a);\n if (id === NO_ID) {\n stats.ignored += 1;\n return;\n }\n emit({ k: \"owner-\", seq: 0, t: 0, id });\n return;\n }\n case EVENT.SIGNAL_CREATED: {\n if (!isObject(a) || isIgnored(a, b)) {\n stats.ignored += 1;\n return;\n }\n const id = registerLeaf(a);\n signals.set(id, new WeakRef(a));\n const name = typeof c === \"string\" ? c : undefined;\n emit({\n k: \"signal+\",\n seq: 0,\n t: 0,\n id,\n owner: lookup(b),\n preview: preview(d, previewLength),\n ...(name === undefined ? {} : { name }),\n });\n return;\n }\n case EVENT.SIGNAL_WRITTEN: {\n const id = lookup(a);\n if (id === NO_ID) {\n if (isObject(a) && ignored.has(a)) stats.ignored += 1;\n else stats.unknown += 1;\n return;\n }\n emit({\n k: \"write\",\n seq: 0,\n t: 0,\n id,\n preview: preview(b, previewLength),\n writer: lookup(c),\n observers: mapIds(d),\n });\n return;\n }\n case EVENT.COMPUTATION_CREATED: {\n if (!isObject(a) || isIgnored(a, b)) {\n stats.ignored += 1;\n return;\n }\n const name = typeof d === \"string\" ? d : undefined;\n emit({\n k: \"comp+\",\n seq: 0,\n t: 0,\n id: register(a),\n owner: lookup(b),\n kind: c === \"memo\" ? \"memo\" : \"effect\",\n render: e === true,\n ...(name === undefined ? {} : { name }),\n });\n return;\n }\n case EVENT.COMPUTATION_RUN: {\n const id = lookup(a);\n if (id === NO_ID) {\n if (isObject(a) && ignored.has(a)) stats.ignored += 1;\n else stats.unknown += 1;\n return;\n }\n openFlush();\n emit({\n k: \"run\",\n seq: 0,\n t: 0,\n id,\n start: toNumber(b),\n duration: toNumber(c),\n changed: d === true,\n sources: mapIds(e),\n });\n return;\n }\n case EVENT.COMPUTATION_DISPOSED: {\n const id = lookup(a);\n if (id === NO_ID) {\n stats.ignored += 1;\n return;\n }\n emit({ k: \"comp-\", seq: 0, t: 0, id });\n return;\n }\n case EVENT.COMPONENT_MOUNTED: {\n const id = lookup(a);\n if (id === NO_ID) {\n if (isObject(a) && ignored.has(a)) stats.ignored += 1;\n else stats.unknown += 1;\n return;\n }\n const refs = nodesIn(c);\n if (refs.length > 0) components.set(id, refs);\n emit({ k: \"mount\", seq: 0, t: 0, id, name: typeof b === \"string\" ? b : \"Component\" });\n // A node beyond maxNodes is never buried, so its entry would never be removed: forget it.\n if (!model.nodes.has(id)) components.delete(id);\n return;\n }\n case EVENT.COMPONENT_UNMOUNTED: {\n const id = lookup(a);\n if (id === NO_ID) {\n stats.ignored += 1;\n return;\n }\n components.delete(id);\n emit({ k: \"unmount\", seq: 0, t: 0, id });\n return;\n }\n case EVENT.RESOURCE_STATE: {\n if (!isObject(a) || isIgnored(a, d)) {\n stats.ignored += 1;\n return;\n }\n const name = typeof c === \"string\" ? c : undefined;\n emit({\n k: \"resource\",\n seq: 0,\n t: 0,\n id: registerLeaf(a),\n owner: lookup(d),\n state: typeof b === \"string\" ? b : String(b),\n ...(name === undefined ? {} : { name }),\n });\n return;\n }\n case EVENT.ERROR: {\n const code = typeof c === \"string\" ? c : undefined;\n emit({\n k: \"error\",\n seq: 0,\n t: 0,\n id: lookup(a),\n message: errorMessage(b),\n ...(code === undefined ? {} : { code }),\n });\n return;\n }\n case EVENT.HYDRATION_MISMATCH:\n emit({\n k: \"hydration\",\n seq: 0,\n t: 0,\n message: typeof a === \"string\" ? a : errorMessage(a),\n expected: describeSide(b),\n actual: describeSide(c),\n });\n return;\n case EVENT.FLUSH_START:\n queuedFlush = { render: toNumber(a), user: toNumber(b), t: clock() };\n return;\n case EVENT.FLUSH_END:\n queuedFlush = null;\n if (flushOpen) {\n flushOpen = false;\n emit({ k: \"flush-\", seq: 0, t: 0, runs: toNumber(a) });\n }\n return;\n case EVENT.STORE_CREATED: {\n if (!isObject(a) || isIgnored(a, b)) {\n stats.ignored += 1;\n return;\n }\n const handle = readStoreHandle(a);\n if (handle === null) {\n stats.ignored += 1;\n return;\n }\n const id = registerLeaf(a);\n stores.set(id, new WeakRef(handle));\n emit({ k: \"store+\", seq: 0, t: 0, id, owner: lookup(b), name: handle.name });\n // A store beyond maxNodes is never buried: nothing else would forget its handle.\n if (!model.nodes.has(id)) stores.delete(id);\n return;\n }\n default:\n // An event kind from a newer runtime: ignore it.\n stats.ignored += 1;\n }\n }\n\n const readStoreHandle = (value: object): StoreHandle | null => {\n const name: unknown = Reflect.get(value, \"name\");\n const snapshot: unknown = Reflect.get(value, \"snapshot\");\n const set: unknown = Reflect.get(value, \"set\");\n if (typeof name !== \"string\" || typeof snapshot !== \"function\" || typeof set !== \"function\") {\n return null;\n }\n return value as StoreHandle;\n };\n\n const signalValue = (id: number): { found: true; value: unknown } | { found: false } => {\n const node = signals.get(id)?.deref();\n if (node === undefined) return { found: false };\n return { found: true, value: Reflect.get(node, \"value\") };\n };\n\n const fail = (error: string): AgentResponse => ({ ok: false, error });\n\n function handle(request: AgentRequest): AgentResponse {\n try {\n switch (request.type) {\n case \"snapshot\":\n return { ok: true, result: model.exportState() };\n case \"stats\":\n return { ok: true, result: { ...stats, events: events.size, dropped: events.dropped } };\n case \"value\": {\n const read = signalValue(request.id);\n if (!read.found) return fail(\"The signal is gone.\");\n const result: ValueResult = {\n id: request.id,\n kind: \"signal\",\n text: toEditableText(read.value),\n editable: options.writeSignal !== undefined,\n };\n return { ok: true, result };\n }\n case \"write\": {\n const node = signals.get(request.id)?.deref();\n if (node === undefined) return fail(\"The signal is gone.\");\n if (options.writeSignal === undefined) return fail(\"Editing is not enabled.\");\n const parsed = parseEditableText(request.text);\n if (!parsed.ok) return fail(parsed.error);\n const write = options.writeSignal as (node: object, value: unknown) => unknown;\n write(node, parsed.value);\n return { ok: true, result: null };\n }\n case \"store\": {\n const store = stores.get(request.id)?.deref();\n if (store === undefined) return fail(\"The store is gone.\");\n const result: ValueResult = {\n id: request.id,\n kind: \"store\",\n text: toEditableText(store.snapshot()),\n editable: true,\n };\n return { ok: true, result };\n }\n case \"store-set\": {\n const store = stores.get(request.id)?.deref();\n if (store === undefined) return fail(\"The store is gone.\");\n const parsed = parseEditableText(request.text);\n if (!parsed.ok) return fail(parsed.error);\n store.set(request.path, parsed.value);\n return { ok: true, result: null };\n }\n case \"pause\":\n paused = true;\n return { ok: true, result: null };\n case \"resume\":\n paused = false;\n return { ok: true, result: null };\n case \"clear\":\n clear();\n return { ok: true, result: null };\n case \"highlight\":\n // Drawing needs the page; the page side draws it (see createLocalBackend).\n return { ok: true, result: null };\n case \"inspect\":\n return {\n ok: true,\n result: { sources: readInspections(inspectionHook?.inspections?.() ?? []) },\n };\n default:\n return fail(\"Unknown command.\");\n }\n } catch (error) {\n // Writing user state can throw (a validation in the app): report it to the panel.\n return fail(errorMessage(error));\n }\n }\n\n function clear(): void {\n model.importState({\n lastSeq: model.lastSeq,\n overflow: 0,\n unknown: 0,\n nodes: [],\n graveyard: [],\n runs: [],\n writes: [],\n flushes: [],\n errors: [],\n hydration: [],\n });\n events.clear();\n pending = [];\n queuedFlush = null;\n flushOpen = false;\n signals.clear();\n components.clear();\n stores.clear();\n }\n\n const agent: Agent = {\n model,\n events,\n stats,\n get paused(): boolean {\n return paused;\n },\n receive,\n attach(hook): () => void {\n inspectionHook = hook;\n const detach = hook.attach(agent);\n detachers.add(detach);\n return () => {\n detachers.delete(detach);\n detach();\n };\n },\n subscribe(listener): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n flush: deliver,\n quiet<T>(fn: () => T): T {\n quietDepth += 1;\n try {\n return fn();\n } finally {\n quietDepth -= 1;\n }\n },\n pause(): void {\n paused = true;\n },\n resume(): void {\n paused = false;\n },\n clear,\n snapshot: () => snapshotOf(model),\n state: () => model.exportState(),\n handle,\n nodesOf(id): Node[] {\n const out: Node[] = [];\n const own = components.get(id);\n if (own !== undefined) {\n for (const ref of own) {\n const node = ref.deref();\n if (node?.isConnected === true) out.push(node);\n }\n }\n return out;\n },\n dispose(): void {\n for (const detach of detachers) detach();\n detachers.clear();\n inspectionHook = undefined;\n listeners.clear();\n pending = [];\n signals.clear();\n components.clear();\n stores.clear();\n model.onBury = undefined;\n },\n };\n return agent;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"backend.js","names":["createLocalBackend","unsubscribers","disposed","agent","off","listener","request","page"],"sources":["../../src/backend.ts"],"sourcesContent":["import type { Agent, AgentRequest, AgentResponse } from \"./agent\";\nimport type { InspectorModel } from \"./model\";\n\n/**\n * What a panel talks to: a model that stays up to date, and requests. The panel never knows\n * whether the agent is in the same page ({@link createLocalBackend}) or behind a transport\n * (`connectRemote`), so the same UI serves the floating overlay and a browser-extension panel.\n *\n * @since 1.0.0\n */\nexport interface Backend {\n /** The inspector model, kept current. Read it; do not apply events to it yourself. */\n readonly model: InspectorModel;\n /** Resolves once the first complete state is available. */\n readonly ready: Promise<void>;\n /** Calls `listener` after the model changed (batched). Returns the unsubscribe function. */\n subscribe(listener: () => void): () => void;\n /** Sends a command to the agent. Resolves with its answer; rejects only when the backend is disposed. */\n request(request: AgentRequest): Promise<AgentResponse>;\n /** Stops listening. */\n dispose(): void;\n}\n\n/**\n * Page-side behavior that a backend cannot do through the agent alone.\n *\n * @since 1.0.0\n */\nexport interface PageHooks {\n /**\n * Highlights a component on the page. `null` clears the highlight. Without this hook a highlight\n * request is acknowledged and nothing is drawn.\n */\n highlight?(id: number | null): void;\n}\n\n/**\n * A backend for an agent in the same page: the model is the agent's own model, and the requests go to\n * the agent. A development tool: the page-side panel (`mountDevtools`) uses it in development builds.\n *\n * @param agent The agent.\n * @param page Page-side behavior such as the highlight overlay.\n * @returns The backend.\n * @example\n * import { createAgent, createLocalBackend } from \"@shadow-js/devtools\";\n *\n * const agent = createAgent();\n * const backend = createLocalBackend(agent, {\n * highlight: (id) => console.info(\"highlight\", id),\n * });\n * @since 1.0.0\n */\nexport function createLocalBackend(agent: Agent, page: PageHooks = {}): Backend {\n const unsubscribers = new Set<() => void>();\n let disposed = false;\n return {\n model: agent.model,\n ready: Promise.resolve(),\n subscribe(listener): () => void {\n const off = agent.subscribe(() => listener());\n unsubscribers.add(off);\n return () => {\n unsubscribers.delete(off);\n off();\n };\n },\n request(request): Promise<AgentResponse> {\n if (disposed) return Promise.reject(new Error(\"The devtools backend was disposed.\"));\n if (request.type === \"highlight\") {\n page.highlight?.(request.id);\n return Promise.resolve({ ok: true, result: null });\n }\n return Promise.resolve(agent.handle(request));\n },\n dispose(): void {\n disposed = true;\n for (const off of unsubscribers) off();\n unsubscribers.clear();\n },\n };\n}\n"],"mappings":"AAoDA,SAAgBA,EAAmB,EAAc,EAAkB,CAAC,EAAY,CAC9E,IAAMC,kBAAgB,IAAI,IACtBC,EAAW,GACf,MAAO,CACL,MAAOC,EAAM,MACb,MAAO,QAAQ,QAAQ,EACvB,UAAU,EAAsB,CAC9B,IAAMC,EAAMD,EAAM,cAAgBE,EAAS,CAAC,EAE5C,OADAJ,EAAc,IAAIG,CAAG,MACR,CACXH,EAAc,OAAOG,CAAG,EACxBA,EAAI,CACN,CACF,EACA,QAAQ,EAAiC,CAMvC,OALIF,EAAiB,QAAQ,OAAO,gBAAI,MAAM,oCAAoC,CAAC,EAC/EI,EAAQ,OAAS,aACnBC,EAAK,YAAYD,EAAQ,EAAE,EACpB,QAAQ,QAAQ,CAAE,GAAI,GAAM,OAAQ,IAAK,CAAC,GAE5C,QAAQ,QAAQH,EAAM,OAAOG,CAAO,CAAC,CAC9C,EACA,SAAgB,CACd,EAAW,GACX,IAAK,IAAMF,KAAOH,EAAeG,EAAI,EACrCH,EAAc,MAAM,CACtB,CACF,CACF"}
1
+ {"version":3,"file":"backend.js","names":["createLocalBackend","unsubscribers","disposed","agent","off","listener","request","page"],"sources":["../../src/backend.ts"],"sourcesContent":["import type { Agent, AgentRequest, AgentResponse } from \"./agent\";\nimport type { InspectorModel } from \"./model\";\n\n/**\n * What a panel talks to: a model that stays up to date, and requests. The panel never knows\n * whether the agent is in the same page ({@link createLocalBackend}) or behind a transport\n * (`connectRemote`), so the same UI serves the floating overlay and a browser-extension panel.\n *\n * @since 1.0.0\n */\nexport interface Backend {\n /** The inspector model, kept current. Read it; do not apply events to it yourself. */\n readonly model: InspectorModel;\n /** Resolves once the first complete state is available. */\n readonly ready: Promise<void>;\n /**\n * Why the first complete state could not be loaded, or `undefined`. A remote backend sets it when the\n * agent refuses or cannot send its state (for example an answer over the bridge's size cap) and notifies\n * its subscribers; `ready` then stays pending. It clears when a state arrives. A local backend never fails.\n */\n readonly error?: () => string | undefined;\n /** Calls `listener` after the model changed (batched). Returns the unsubscribe function. */\n subscribe(listener: () => void): () => void;\n /** Sends a command to the agent. Resolves with its answer; rejects only when the backend is disposed. */\n request(request: AgentRequest): Promise<AgentResponse>;\n /** Stops listening. */\n dispose(): void;\n}\n\n/**\n * Page-side behavior that a backend cannot do through the agent alone.\n *\n * @since 1.0.0\n */\nexport interface PageHooks {\n /**\n * Highlights a component on the page. `null` clears the highlight. Without this hook a highlight\n * request is acknowledged and nothing is drawn.\n */\n highlight?(id: number | null): void;\n}\n\n/**\n * A backend for an agent in the same page: the model is the agent's own model, and the requests go to\n * the agent. A development tool: the page-side panel (`mountDevtools`) uses it in development builds.\n *\n * @param agent The agent.\n * @param page Page-side behavior such as the highlight overlay.\n * @returns The backend.\n * @example\n * import { createAgent, createLocalBackend } from \"@shadow-js/devtools\";\n *\n * const agent = createAgent();\n * const backend = createLocalBackend(agent, {\n * highlight: (id) => console.info(\"highlight\", id),\n * });\n * @since 1.0.0\n */\nexport function createLocalBackend(agent: Agent, page: PageHooks = {}): Backend {\n const unsubscribers = new Set<() => void>();\n let disposed = false;\n return {\n model: agent.model,\n ready: Promise.resolve(),\n subscribe(listener): () => void {\n const off = agent.subscribe(() => listener());\n unsubscribers.add(off);\n return () => {\n unsubscribers.delete(off);\n off();\n };\n },\n request(request): Promise<AgentResponse> {\n if (disposed) return Promise.reject(new Error(\"The devtools backend was disposed.\"));\n if (request.type === \"highlight\") {\n page.highlight?.(request.id);\n return Promise.resolve({ ok: true, result: null });\n }\n return Promise.resolve(agent.handle(request));\n },\n dispose(): void {\n disposed = true;\n for (const off of unsubscribers) off();\n unsubscribers.clear();\n },\n };\n}\n"],"mappings":"AA0DA,SAAgBA,EAAmB,EAAc,EAAkB,CAAC,EAAY,CAC9E,IAAMC,kBAAgB,IAAI,IACtBC,EAAW,GACf,MAAO,CACL,MAAOC,EAAM,MACb,MAAO,QAAQ,QAAQ,EACvB,UAAU,EAAsB,CAC9B,IAAMC,EAAMD,EAAM,cAAgBE,EAAS,CAAC,EAE5C,OADAJ,EAAc,IAAIG,CAAG,MACR,CACXH,EAAc,OAAOG,CAAG,EACxBA,EAAI,CACN,CACF,EACA,QAAQ,EAAiC,CAMvC,OALIF,EAAiB,QAAQ,OAAO,gBAAI,MAAM,oCAAoC,CAAC,EAC/EI,EAAQ,OAAS,aACnBC,EAAK,YAAYD,EAAQ,EAAE,EACpB,QAAQ,QAAQ,CAAE,GAAI,GAAM,OAAQ,IAAK,CAAC,GAE5C,QAAQ,QAAQH,EAAM,OAAOG,CAAO,CAAC,CAC9C,EACA,SAAgB,CACd,EAAW,GACX,IAAK,IAAMF,KAAOH,EAAeG,EAAI,EACrCH,EAAc,MAAM,CACtB,CACF,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"hook.js","names":["lookup","target","HOOK_KEY","isHook","value","installHook","getHook","existing","uninstallHook"],"sources":["../../src/hook.ts"],"sourcesContent":["import type { InspectionEntry, InspectionSource } from \"./inspection\";\nimport {\n type DevtoolsHook,\n HOOK_KEY,\n type HookSink,\n MAX_INSPECTIONS,\n PROTOCOL_VERSION,\n} from \"./protocol\";\n\n/**\n * Options of {@link installHook}. Every field is optional.\n *\n * @since 1.0.0\n */\nexport interface InstallHookOptions {\n /**\n * The object to install the hook on.\n *\n * @default globalThis\n */\n target?: object;\n /**\n * How many events to keep for a sink that attaches later. The first events reported are kept and\n * later ones are dropped. The buffer is released when the first sink attaches, so a hook that nobody\n * listens to cannot grow without bound.\n *\n * @default 5000\n */\n buffer?: number;\n}\n\n/**\n * The hook that {@link installHook} installs: a {@link DevtoolsHook} with the counters of its sinks.\n *\n * @since 1.0.0\n */\nexport interface InstalledHook extends DevtoolsHook {\n /** Number of times a sink threw while receiving an event. The hook swallows each error so the app keeps running. */\n readonly faults: number;\n /** The last error a sink threw, or `undefined` when none did. */\n readonly lastFault: unknown;\n}\n\ntype Buffered = readonly [number, unknown, unknown, unknown, unknown, unknown];\n\nfunction lookup(target: object): unknown {\n return Reflect.get(target, HOOK_KEY);\n}\n\nfunction isHook(value: unknown): value is InstalledHook {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof Reflect.get(value, \"report\") === \"function\" &&\n typeof Reflect.get(value, \"attach\") === \"function\" &&\n typeof Reflect.get(value, \"version\") === \"number\"\n );\n}\n\n/**\n * Installs the dev hook on `globalThis.__SHADOWJS_DEVTOOLS_HOOK__` (or on `options.target`), once, and\n * returns it. A development tool: in production builds (`__DEV__` false) it installs nothing and returns\n * `undefined`. The runtime looks the global up on each event, so call this before the application code\n * runs: events reported earlier are lost, and those reported before a sink attaches are buffered.\n *\n * When a hook with the same protocol version is already installed, it is returned. When the global holds\n * a hook of another version (or anything that is not a hook), nothing is installed and `undefined` is\n * returned.\n *\n * @param options Target object and replay buffer size. Every field is optional.\n * @returns The installed hook, or `undefined`.\n * @example\n * import { createAgent, installHook } from \"@shadow-js/devtools\";\n *\n * const hook = installHook();\n * const agent = createAgent();\n * if (hook !== undefined) agent.attach(hook);\n * @since 1.0.0\n */\nexport function installHook(options: InstallHookOptions = {}): InstalledHook | undefined {\n if (!__DEV__) return undefined;\n const target = options.target ?? globalThis;\n const existing = lookup(target);\n if (existing !== undefined) {\n return isHook(existing) && existing.version === PROTOCOL_VERSION ? existing : undefined;\n }\n const limit = options.buffer ?? 5000;\n const sinks: HookSink[] = [];\n const registry = new Map<string, InspectionSource>();\n let buffer: Buffered[] | null = [];\n let faults = 0;\n let lastFault: unknown;\n\n const deliver = (\n sink: HookSink,\n kind: number,\n a: unknown,\n b: unknown,\n c: unknown,\n d: unknown,\n e: unknown,\n ): void => {\n try {\n sink.receive(kind, a, b, c, d, e);\n } catch (error) {\n // A broken tool must never break the application: count the fault and keep the error.\n faults += 1;\n lastFault = error;\n }\n };\n\n const hook: InstalledHook = {\n version: PROTOCOL_VERSION,\n get sinks(): number {\n return sinks.length;\n },\n get faults(): number {\n return faults;\n },\n get lastFault(): unknown {\n return lastFault;\n },\n report(kind, a, b, c, d, e): void {\n if (sinks.length === 0) {\n if (buffer !== null && buffer.length < limit) buffer.push([kind, a, b, c, d, e]);\n return;\n }\n for (let i = 0; i < sinks.length; i += 1) {\n const sink = sinks[i];\n if (sink !== undefined) deliver(sink, kind, a, b, c, d, e);\n }\n },\n inspect(name, source): boolean {\n if (!registry.has(name) && registry.size >= MAX_INSPECTIONS) return false;\n registry.set(name, source);\n return true;\n },\n inspections(): readonly InspectionEntry[] {\n return [...registry].map(([name, source]) => ({ name, source }));\n },\n attach(sink): () => void {\n sinks.push(sink);\n if (buffer !== null) {\n const replay = buffer;\n buffer = null;\n for (const entry of replay)\n deliver(sink, entry[0], entry[1], entry[2], entry[3], entry[4], entry[5]);\n }\n return () => {\n const index = sinks.indexOf(sink);\n if (index !== -1) sinks.splice(index, 1);\n };\n },\n };\n Reflect.set(target, HOOK_KEY, hook);\n return hook;\n}\n\n/**\n * The hook that {@link installHook} installed, or `undefined` when none is installed or its protocol\n * version differs. A development tool: production builds never install a hook.\n *\n * @param target Object to look at (default `globalThis`).\n * @returns The installed hook, or `undefined`.\n * @example\n * import { getHook } from \"@shadow-js/devtools\";\n *\n * const sinks = getHook()?.sinks ?? 0;\n * @since 1.0.0\n */\nexport function getHook(target: object = globalThis): InstalledHook | undefined {\n const existing = lookup(target);\n return isHook(existing) && existing.version === PROTOCOL_VERSION ? existing : undefined;\n}\n\n/**\n * Removes the installed hook, so that a later {@link installHook} installs a new one. For tests and for\n * hot reloading the tools; a development tool, and a no-op in production builds. Sinks stay attached to\n * the old hook object.\n *\n * @param target Object to remove it from (default `globalThis`).\n * @example\n * import { uninstallHook } from \"@shadow-js/devtools\";\n *\n * uninstallHook();\n * @since 1.0.0\n */\nexport function uninstallHook(target: object = globalThis): void {\n Reflect.deleteProperty(target, HOOK_KEY);\n}\n"],"mappings":"yCA6CA,SAASA,EAAO,EAAyB,CACvC,OAAO,QAAQ,IAAIC,EAAQC,CAAQ,CACrC,CAEA,SAASC,EAAO,EAAwC,CACtD,OACE,OAAOC,GAAU,YACjBA,GACA,OAAO,QAAQ,IAAIA,EAAO,QAAQ,GAAM,YACxC,OAAO,QAAQ,IAAIA,EAAO,QAAQ,GAAM,YACxC,OAAO,QAAQ,IAAIA,EAAO,SAAS,GAAM,QAE7C,CAsBA,SAAgBC,EAAY,EAA8B,CAAC,EAA8B,CA6EzF,CAcA,SAAgBC,EAAQ,EAAiB,WAAuC,CAC9E,IAAMC,EAAWP,EAAOC,CAAM,EAC9B,OAAOE,EAAOI,CAAQ,GAAKA,EAAS,UAAA,EAA+BA,EAAW,IAAA,EAChF,CAcA,SAAgBC,EAAc,EAAiB,WAAkB,CAC/D,QAAQ,eAAeP,EAAQC,CAAQ,CACzC"}
1
+ {"version":3,"file":"hook.js","names":["lookup","target","HOOK_KEY","isHook","value","installHook","getHook","existing","uninstallHook"],"sources":["../../src/hook.ts"],"sourcesContent":["import type { InspectionEntry, InspectionSource } from \"./inspection\";\nimport {\n type DevtoolsHook,\n HOOK_KEY,\n type HookSink,\n MAX_INSPECTIONS,\n PROTOCOL_VERSION,\n} from \"./protocol\";\n\n/**\n * Options of {@link installHook}. Every field is optional.\n *\n * @since 1.0.0\n */\nexport interface InstallHookOptions {\n /**\n * The object to install the hook on.\n *\n * @default globalThis\n */\n target?: object;\n /**\n * How many events to keep for a sink that attaches later. The first events reported are kept and\n * later ones are dropped. The buffer is released when the first sink attaches, so a hook that nobody\n * listens to cannot grow without bound.\n *\n * @default 5000\n */\n buffer?: number;\n}\n\n/**\n * The hook that {@link installHook} installs: a {@link DevtoolsHook} with the counters of its sinks.\n *\n * @since 1.0.0\n */\nexport interface InstalledHook extends DevtoolsHook {\n /** Number of times a sink threw while receiving an event. The hook swallows each error so the app keeps running. */\n readonly faults: number;\n /** The last error a sink threw, or `undefined` when none did. */\n readonly lastFault: unknown;\n}\n\ntype Buffered = readonly [number, unknown, unknown, unknown, unknown, unknown];\n\nfunction lookup(target: object): unknown {\n return Reflect.get(target, HOOK_KEY);\n}\n\nfunction isHook(value: unknown): value is InstalledHook {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof Reflect.get(value, \"report\") === \"function\" &&\n typeof Reflect.get(value, \"attach\") === \"function\" &&\n typeof Reflect.get(value, \"version\") === \"number\"\n );\n}\n\n/**\n * Installs the dev hook on `globalThis.__SHADOWJS_DEVTOOLS_HOOK__` (or on `options.target`), once, and\n * returns it. A development tool: in production builds (`__DEV__` false) it installs nothing and returns\n * `undefined`. The runtime looks the global up on each event, so call this before the application code\n * runs: events reported earlier are lost, and those reported before a sink attaches are buffered.\n *\n * When a hook with the same protocol version is already installed, it is returned. When the global holds\n * a hook of another version (or anything that is not a hook), nothing is installed and `undefined` is\n * returned.\n *\n * @param options Target object and replay buffer size. Every field is optional.\n * @returns The installed hook, or `undefined`.\n * @example\n * import { createAgent, installHook } from \"@shadow-js/devtools\";\n *\n * const hook = installHook();\n * const agent = createAgent();\n * if (hook !== undefined) agent.attach(hook);\n * @since 1.0.0\n */\nexport function installHook(options: InstallHookOptions = {}): InstalledHook | undefined {\n if (!__DEV__) return undefined;\n const target = options.target ?? globalThis;\n const existing = lookup(target);\n if (existing !== undefined) {\n return isHook(existing) && existing.version === PROTOCOL_VERSION ? existing : undefined;\n }\n const limit = options.buffer ?? 5000;\n // Replaced, never changed in place: a sink that detaches while an event is delivered must not make\n // the loop skip the sink after it.\n let sinks: readonly HookSink[] = [];\n const registry = new Map<string, InspectionSource>();\n let buffer: Buffered[] | null = [];\n let faults = 0;\n let lastFault: unknown;\n\n const deliver = (\n sink: HookSink,\n kind: number,\n a: unknown,\n b: unknown,\n c: unknown,\n d: unknown,\n e: unknown,\n ): void => {\n try {\n sink.receive(kind, a, b, c, d, e);\n } catch (error) {\n // A broken tool must never break the application: count the fault and keep the error.\n faults += 1;\n lastFault = error;\n }\n };\n\n const hook: InstalledHook = {\n version: PROTOCOL_VERSION,\n get sinks(): number {\n return sinks.length;\n },\n get faults(): number {\n return faults;\n },\n get lastFault(): unknown {\n return lastFault;\n },\n report(kind, a, b, c, d, e): void {\n if (sinks.length === 0) {\n if (buffer !== null && buffer.length < limit) buffer.push([kind, a, b, c, d, e]);\n return;\n }\n const current = sinks;\n for (let i = 0; i < current.length; i += 1) {\n const sink = current[i];\n if (sink !== undefined) deliver(sink, kind, a, b, c, d, e);\n }\n },\n inspect(name, source): boolean {\n if (!registry.has(name) && registry.size >= MAX_INSPECTIONS) return false;\n registry.set(name, source);\n return true;\n },\n inspections(): readonly InspectionEntry[] {\n return [...registry].map(([name, source]) => ({ name, source }));\n },\n attach(sink): () => void {\n sinks = [...sinks, sink];\n if (buffer !== null) {\n const replay = buffer;\n buffer = null;\n for (const entry of replay)\n deliver(sink, entry[0], entry[1], entry[2], entry[3], entry[4], entry[5]);\n }\n return () => {\n const index = sinks.indexOf(sink);\n if (index !== -1) sinks = [...sinks.slice(0, index), ...sinks.slice(index + 1)];\n };\n },\n };\n Reflect.set(target, HOOK_KEY, hook);\n return hook;\n}\n\n/**\n * The hook that {@link installHook} installed, or `undefined` when none is installed or its protocol\n * version differs. A development tool: production builds never install a hook.\n *\n * @param target Object to look at (default `globalThis`).\n * @returns The installed hook, or `undefined`.\n * @example\n * import { getHook } from \"@shadow-js/devtools\";\n *\n * const sinks = getHook()?.sinks ?? 0;\n * @since 1.0.0\n */\nexport function getHook(target: object = globalThis): InstalledHook | undefined {\n const existing = lookup(target);\n return isHook(existing) && existing.version === PROTOCOL_VERSION ? existing : undefined;\n}\n\n/**\n * Removes the installed hook, so that a later {@link installHook} installs a new one. For tests and for\n * hot reloading the tools; a development tool, and a no-op in production builds. Sinks stay attached to\n * the old hook object.\n *\n * @param target Object to remove it from (default `globalThis`).\n * @example\n * import { uninstallHook } from \"@shadow-js/devtools\";\n *\n * uninstallHook();\n * @since 1.0.0\n */\nexport function uninstallHook(target: object = globalThis): void {\n Reflect.deleteProperty(target, HOOK_KEY);\n}\n"],"mappings":"yCA6CA,SAASA,EAAO,EAAyB,CACvC,OAAO,QAAQ,IAAIC,EAAQC,CAAQ,CACrC,CAEA,SAASC,EAAO,EAAwC,CACtD,OACE,OAAOC,GAAU,YACjBA,GACA,OAAO,QAAQ,IAAIA,EAAO,QAAQ,GAAM,YACxC,OAAO,QAAQ,IAAIA,EAAO,QAAQ,GAAM,YACxC,OAAO,QAAQ,IAAIA,EAAO,SAAS,GAAM,QAE7C,CAsBA,SAAgBC,EAAY,EAA8B,CAAC,EAA8B,CAgFzF,CAcA,SAAgBC,EAAQ,EAAiB,WAAuC,CAC9E,IAAMC,EAAWP,EAAOC,CAAM,EAC9B,OAAOE,EAAOI,CAAQ,GAAKA,EAAS,UAAA,EAA+BA,EAAW,IAAA,EAChF,CAcA,SAAgBC,EAAc,EAAiB,WAAkB,CAC/D,QAAQ,eAAeP,EAAQC,CAAQ,CACzC"}
@@ -0,0 +1,2 @@
1
+ function e(e,o){let s=``,c=()=>o+1-s.length,l=()=>s.length>o,u=[],d=e=>{let n=e.prefix===null?e.localName:`${e.prefix}:${e.localName}`;s+=`<${n}`;let i=e.attributes;for(let e=0;e<i.length&&!l();e+=1){let t=i.item(e);t!==null&&(s+=` ${t.name}="${a(t.value.slice(0,c()))}"`)}if(s+=`>`,t.has(n))return;let o=n===`template`&&`content`in e&&r(e.content)?e.content.firstChild:e.firstChild;u.push({next:o,close:`</${n}>`})};for(d(e);u.length>0&&!l();){let e=u[u.length-1];if(e===void 0)break;let t=e.next;if(t===null)u.pop(),s+=e.close;else switch(e.next=t.nextSibling,t.nodeType){case 1:d(t);break;case 3:case 4:{let e=t.data.slice(0,c());s+=n.has(t.parentNode?.localName??``)?e:i(e);break}case 8:s+=`<!--${t.data.slice(0,c())}-->`;break;case 7:s+=`<?${t.target} ${t.data.slice(0,c())}>`}}return s.length>o?`${s.slice(0,o-1)}…`:s}const t=/* @__PURE__ */ new Set([`area`,`base`,`basefont`,`bgsound`,`br`,`col`,`embed`,`frame`,`hr`,`img`,`input`,`keygen`,`link`,`meta`,`param`,`source`,`track`,`wbr`]),n=/* @__PURE__ */ new Set([`style`,`script`,`xmp`,`iframe`,`noembed`,`noframes`,`plaintext`]);function r(e){return typeof e==`object`&&!!e&&`firstChild`in e}function i(e){return e.replace(/[& <>]/g,e=>e===`&`?`&amp;`:e===`<`?`&lt;`:e===`>`?`&gt;`:`&nbsp;`)}function a(e){return e.replace(/[& "]/g,e=>e===`&`?`&amp;`:e===`"`?`&quot;`:`&nbsp;`)}export{e as describeElement};
2
+ //# sourceMappingURL=describe-element.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"describe-element.js","names":["describeElement","out","room","limit","full","stack","open","name","element","attributes","index","attribute","escapeAttribute","VOID","first","isNode","root","frame","node","text","RAW_TEXT","escapeText","value","char"],"sources":["../../../src/internal/describe-element.ts"],"sourcesContent":["/**\n * Markup of an element for a message, cut after `limit` characters.\n *\n * `outerHTML` serializes the whole subtree before anything can be cut, so describing the root of a big\n * page cost time and memory proportional to the page. This walk produces the same text as `outerHTML`\n * for small elements (tags, attributes, text, comments, void elements, raw-text elements) and stops as\n * soon as more than `limit` characters exist, so it visits only the nodes at the start of the subtree.\n * It is iterative (a deep tree cannot overflow the stack) and slices every attribute value and text node\n * to the room that is left before it escapes it.\n *\n * @param root The element to describe.\n * @param limit The most characters the result may have; a longer markup ends with `…` at this length.\n * @returns The markup, or its first `limit - 1` characters and `…`.\n */\nexport function describeElement(root: Element, limit: number): string {\n let out = \"\";\n const room = (): number => limit + 1 - out.length;\n const full = (): boolean => out.length > limit;\n const stack: Array<{ next: Node | null; close: string }> = [];\n\n const open = (element: Element): void => {\n const name =\n element.prefix === null ? element.localName : `${element.prefix}:${element.localName}`;\n out += `<${name}`;\n const attributes = element.attributes;\n for (let index = 0; index < attributes.length && !full(); index += 1) {\n const attribute = attributes.item(index);\n if (attribute === null) continue;\n out += ` ${attribute.name}=\"${escapeAttribute(attribute.value.slice(0, room()))}\"`;\n }\n out += \">\";\n if (VOID.has(name)) return;\n const first =\n name === \"template\" && \"content\" in element && isNode(element.content)\n ? element.content.firstChild\n : element.firstChild;\n stack.push({ next: first, close: `</${name}>` });\n };\n\n open(root);\n while (stack.length > 0 && !full()) {\n const frame = stack[stack.length - 1];\n if (frame === undefined) break;\n const node = frame.next;\n if (node === null) {\n stack.pop();\n out += frame.close;\n continue;\n }\n frame.next = node.nextSibling;\n switch (node.nodeType) {\n case 1:\n open(node as Element);\n break;\n case 3:\n case 4: {\n const text = (node as CharacterData).data.slice(0, room());\n out += RAW_TEXT.has((node.parentNode as Element | null)?.localName ?? \"\")\n ? text\n : escapeText(text);\n break;\n }\n case 8:\n out += `<!--${(node as CharacterData).data.slice(0, room())}-->`;\n break;\n case 7:\n out += `<?${(node as ProcessingInstruction).target} ${(node as CharacterData).data.slice(0, room())}>`;\n break;\n default:\n break;\n }\n }\n return out.length > limit ? `${out.slice(0, limit - 1)}…` : out;\n}\n\nconst VOID = new Set([\n \"area\",\n \"base\",\n \"basefont\",\n \"bgsound\",\n \"br\",\n \"col\",\n \"embed\",\n \"frame\",\n \"hr\",\n \"img\",\n \"input\",\n \"keygen\",\n \"link\",\n \"meta\",\n \"param\",\n \"source\",\n \"track\",\n \"wbr\",\n]);\n\n/** Elements whose text the HTML serializer writes without escaping. */\nconst RAW_TEXT = new Set([\"style\", \"script\", \"xmp\", \"iframe\", \"noembed\", \"noframes\", \"plaintext\"]);\n\nfunction isNode(value: unknown): value is Node {\n return typeof value === \"object\" && value !== null && \"firstChild\" in value;\n}\n\nfunction escapeText(text: string): string {\n return text.replace(/[& <>]/g, (char) =>\n char === \"&\" ? \"&amp;\" : char === \"<\" ? \"&lt;\" : char === \">\" ? \"&gt;\" : \"&nbsp;\",\n );\n}\n\nfunction escapeAttribute(text: string): string {\n return text.replace(/[& \"]/g, (char) =>\n char === \"&\" ? \"&amp;\" : char === '\"' ? \"&quot;\" : \"&nbsp;\",\n );\n}\n"],"mappings":"AAcA,SAAgBA,EAAgB,EAAe,EAAuB,CACpE,IAAIC,EAAM,GACJC,MAAqBC,EAAQ,EAAIF,EAAI,OACrCG,MAAsBH,EAAI,OAASE,EACnCE,EAAqD,CAAC,EAEtDC,EAAQ,GAA2B,CACvC,IAAMC,EACJC,EAAQ,SAAW,KAAOA,EAAQ,UAAY,GAAGA,EAAQ,OAAO,GAAGA,EAAQ,YAC7E,GAAO,IAAID,IACX,IAAME,EAAaD,EAAQ,WAC3B,IAAK,IAAIE,EAAQ,EAAGA,EAAQD,EAAW,QAAU,CAACL,EAAK,EAAG,GAAS,EAAG,CACpE,IAAMO,EAAYF,EAAW,KAAKC,CAAK,EACnCC,IAAc,OAClB,GAAO,IAAIA,EAAU,KAAK,IAAIC,EAAgBD,EAAU,MAAM,MAAM,EAAGT,EAAK,CAAC,CAAC,EAAE,GAClF,CAEA,GADA,GAAO,IACHW,EAAK,IAAIN,CAAI,EAAG,OACpB,IAAMO,EACJP,IAAS,YAAc,YAAaC,GAAWO,EAAOP,EAAQ,OAAO,EACjEA,EAAQ,QAAQ,WAChBA,EAAQ,WACdH,EAAM,KAAK,CAAE,KAAMS,EAAO,MAAO,KAAKP,EAAK,EAAG,CAAC,CACjD,EAGA,IADAD,EAAKU,CAAI,EACFX,EAAM,OAAS,GAAK,CAACD,EAAK,GAAG,CAClC,IAAMa,EAAQZ,EAAMA,EAAM,OAAS,GACnC,GAAIY,IAAU,IAAA,GAAW,MACzB,IAAMC,EAAOD,EAAM,KACnB,GAAIC,IAAS,KACXb,EAAM,IAAI,EACV,GAAOY,EAAM,WAIf,OADA,EAAM,KAAOC,EAAK,YACVA,EAAK,SAAb,CACE,IAAK,GACHZ,EAAKY,CAAe,EACpB,MACF,IAAK,GACL,IAAK,GAAG,CACN,IAAMC,EAAQD,EAAuB,KAAK,MAAM,EAAGhB,EAAK,CAAC,EACzD,GAAOkB,EAAS,IAAKF,EAAK,YAA+B,WAAa,EAAE,EACpEC,EACAE,EAAWF,CAAI,EACnB,KACF,CACA,IAAK,GACH,GAAO,OAAQD,EAAuB,KAAK,MAAM,EAAGhB,EAAK,CAAC,EAAE,KAC5D,MACF,IAAK,GACH,GAAO,KAAMgB,EAA+B,OAAO,GAAIA,EAAuB,KAAK,MAAM,EAAGhB,EAAK,CAAC,EAAE,EAIxG,CACF,CACA,OAAOD,EAAI,OAASE,EAAQ,GAAGF,EAAI,MAAM,EAAGE,EAAQ,CAAC,EAAE,GAAKF,CAC9D,CAEA,MAAMY,kBAAO,IAAI,IAAI,CACnB,OACA,OACA,WACA,UACA,KACA,MACA,QACA,QACA,KACA,MACA,QACA,SACA,OACA,OACA,QACA,SACA,QACA,KACF,CAAC,EAGKO,kBAAW,IAAI,IAAI,CAAC,QAAS,SAAU,MAAO,SAAU,UAAW,WAAY,WAAW,CAAC,EAEjG,SAASL,EAAO,EAA+B,CAC7C,OAAO,OAAOO,GAAU,YAAYA,GAAkB,eAAgBA,CACxE,CAEA,SAASD,EAAW,EAAsB,CACxC,OAAOF,EAAK,QAAQ,UAAY,GAC9BI,IAAS,IAAM,QAAUA,IAAS,IAAM,OAASA,IAAS,IAAM,OAAS,QAC3E,CACF,CAEA,SAASX,EAAgB,EAAsB,CAC7C,OAAOO,EAAK,QAAQ,SAAW,GAC7BI,IAAS,IAAM,QAAUA,IAAS,IAAM,SAAW,QACrD,CACF"}
@@ -0,0 +1,2 @@
1
+ function e(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function t(e){return typeof e==`number`&&Number.isFinite(e)}function n(e){return e===void 0||typeof e==`string`}function r(e){if(!Array.isArray(e))return!1;for(let n=0;n<e.length;n+=1)if(!t(e[n]))return!1;return!0}function i(e,t){return t.every(t=>typeof e[t]==`string`)}function a(e,n){return n.every(n=>t(e[n]))}function o(o){if(!e(o)||!t(o.seq)||!t(o.t))return!1;switch(o.k){case`owner+`:return a(o,[`id`,`parent`])&&typeof o.label==`string`&&n(o.name);case`owner-`:case`comp-`:case`unmount`:return t(o.id);case`signal+`:return a(o,[`id`,`owner`])&&typeof o.preview==`string`&&n(o.name);case`write`:return a(o,[`id`,`writer`])&&typeof o.preview==`string`&&r(o.observers);case`comp+`:return a(o,[`id`,`owner`])&&(o.kind===`memo`||o.kind===`effect`)&&typeof o.render==`boolean`&&n(o.name);case`run`:return a(o,[`id`,`start`,`duration`])&&typeof o.changed==`boolean`&&r(o.sources);case`mount`:return t(o.id)&&typeof o.name==`string`;case`resource`:return a(o,[`id`,`owner`])&&typeof o.state==`string`&&n(o.name);case`error`:return t(o.id)&&typeof o.message==`string`&&n(o.code);case`hydration`:return i(o,[`message`,`expected`,`actual`]);case`flush+`:return a(o,[`render`,`user`]);case`flush-`:return t(o.runs);case`store+`:return a(o,[`id`,`owner`])&&typeof o.name==`string`;default:return!1}}function s(t){return e(t)&&a(t,[`id`,`parent`])&&i(t,[`type`,`label`,`preview`,`state`])&&n(t.name)&&r(t.sources)&&r(t.observers)}function c(t){if(!e(t)||!a(t,[`seq`,`t`,`id`,`start`,`duration`,`flush`]))return!1;let n=t.causes;return Array.isArray(n)&&n.every(t=>e(t)&&typeof t.kind==`string`&&a(t,[`id`,`seq`]))}function l(t){return e(t)&&a(t,[`seq`,`t`,`id`,`writer`])&&typeof t.preview==`string`&&r(t.observers)}function u(t){return e(t)&&a(t,[`seq`,`startSeq`,`start`,`end`,`render`,`user`,`runs`])&&typeof t.open==`boolean`}function d(t){return e(t)&&a(t,[`seq`,`t`,`id`])&&typeof t.message==`string`&&n(t.code)}function f(t){return e(t)&&a(t,[`seq`,`t`])&&i(t,[`message`,`expected`,`actual`])}function p(e,t){if(!Array.isArray(e))return!1;for(let n=0;n<e.length;n+=1)if(!t(e[n]))return!1;return!0}function m(t){return e(t)&&a(t,[`lastSeq`,`overflow`,`unknown`])&&p(t.nodes,s)&&p(t.graveyard,s)&&p(t.runs,c)&&p(t.writes,l)&&p(t.flushes,u)&&p(t.errors,d)&&p(t.hydration,f)}export{m as isModelState,o as isWireEvent};
2
+ //# sourceMappingURL=wire-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wire-guard.js","names":["isRecord","value","isNumber","isOptionalText","areNumbers","index","areTexts","keys","key","areNumbersAt","isWireEvent","\"seq\"","\"t\"","\"k\"","\"label\"","\"name\"","\"id\"","\"preview\"","\"observers\"","\"kind\"","\"render\"","\"changed\"","\"sources\"","\"state\"","\"message\"","\"code\"","\"runs\"","isNodeState","isRunState","causes","\"causes\"","cause","isWriteState","isFlushState","\"open\"","isErrorState","isHydrationState","every","check","isModelState","\"nodes\"","\"graveyard\"","\"writes\"","\"flushes\"","\"errors\"","\"hydration\""],"sources":["../../../src/internal/wire-guard.ts"],"sourcesContent":["import type { ModelState } from \"../model\";\nimport type { WireEvent } from \"../protocol\";\n\n/**\n * Shape checks for what a panel receives from the inspected page. The page is not trusted by the tools\n * that inspect it: a page script (or a compromised dependency of the app) can post anything on the\n * channel, so the panel's model never sees a value it has not checked. The checks look at the types the\n * model and the views read (numbers where arithmetic happens, arrays where they are iterated); they do\n * not try to prove the content is true.\n */\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value);\n}\n\nfunction isOptionalText(value: unknown): boolean {\n return value === undefined || typeof value === \"string\";\n}\n\nfunction areNumbers(value: unknown): value is number[] {\n if (!Array.isArray(value)) return false;\n for (let index = 0; index < value.length; index += 1) {\n if (!isNumber(value[index])) return false;\n }\n return true;\n}\n\nfunction areTexts(value: Record<string, unknown>, keys: readonly string[]): boolean {\n return keys.every((key) => typeof value[key] === \"string\");\n}\n\nfunction areNumbersAt(value: Record<string, unknown>, keys: readonly string[]): boolean {\n return keys.every((key) => isNumber(value[key]));\n}\n\n/**\n * True when `value` has the shape of a {@link WireEvent}: a known kind, a finite `seq` and `t`, and the\n * fields that kind carries with the right types. An unknown kind is refused too: the agent of a newer\n * protocol version sends only the kinds this one knows, or bumps the version.\n *\n * @param value One element of an `events` message.\n * @returns True when the model can apply it without reading a missing field.\n * @since 1.0.0\n */\nexport function isWireEvent(value: unknown): value is WireEvent {\n if (!isRecord(value) || !isNumber(value[\"seq\"]) || !isNumber(value[\"t\"])) return false;\n switch (value[\"k\"]) {\n case \"owner+\":\n return (\n areNumbersAt(value, [\"id\", \"parent\"]) &&\n typeof value[\"label\"] === \"string\" &&\n isOptionalText(value[\"name\"])\n );\n case \"owner-\":\n case \"comp-\":\n case \"unmount\":\n return isNumber(value[\"id\"]);\n case \"signal+\":\n return (\n areNumbersAt(value, [\"id\", \"owner\"]) &&\n typeof value[\"preview\"] === \"string\" &&\n isOptionalText(value[\"name\"])\n );\n case \"write\":\n return (\n areNumbersAt(value, [\"id\", \"writer\"]) &&\n typeof value[\"preview\"] === \"string\" &&\n areNumbers(value[\"observers\"])\n );\n case \"comp+\":\n return (\n areNumbersAt(value, [\"id\", \"owner\"]) &&\n (value[\"kind\"] === \"memo\" || value[\"kind\"] === \"effect\") &&\n typeof value[\"render\"] === \"boolean\" &&\n isOptionalText(value[\"name\"])\n );\n case \"run\":\n return (\n areNumbersAt(value, [\"id\", \"start\", \"duration\"]) &&\n typeof value[\"changed\"] === \"boolean\" &&\n areNumbers(value[\"sources\"])\n );\n case \"mount\":\n return isNumber(value[\"id\"]) && typeof value[\"name\"] === \"string\";\n case \"resource\":\n return (\n areNumbersAt(value, [\"id\", \"owner\"]) &&\n typeof value[\"state\"] === \"string\" &&\n isOptionalText(value[\"name\"])\n );\n case \"error\":\n return (\n isNumber(value[\"id\"]) &&\n typeof value[\"message\"] === \"string\" &&\n isOptionalText(value[\"code\"])\n );\n case \"hydration\":\n return areTexts(value, [\"message\", \"expected\", \"actual\"]);\n case \"flush+\":\n return areNumbersAt(value, [\"render\", \"user\"]);\n case \"flush-\":\n return isNumber(value[\"runs\"]);\n case \"store+\":\n return areNumbersAt(value, [\"id\", \"owner\"]) && typeof value[\"name\"] === \"string\";\n default:\n return false;\n }\n}\n\nfunction isNodeState(value: unknown): boolean {\n return (\n isRecord(value) &&\n areNumbersAt(value, [\"id\", \"parent\"]) &&\n areTexts(value, [\"type\", \"label\", \"preview\", \"state\"]) &&\n isOptionalText(value[\"name\"]) &&\n areNumbers(value[\"sources\"]) &&\n areNumbers(value[\"observers\"])\n );\n}\n\nfunction isRunState(value: unknown): boolean {\n if (!isRecord(value) || !areNumbersAt(value, [\"seq\", \"t\", \"id\", \"start\", \"duration\", \"flush\"])) {\n return false;\n }\n const causes = value[\"causes\"];\n return (\n Array.isArray(causes) &&\n causes.every(\n (cause) =>\n isRecord(cause) && typeof cause[\"kind\"] === \"string\" && areNumbersAt(cause, [\"id\", \"seq\"]),\n )\n );\n}\n\nfunction isWriteState(value: unknown): boolean {\n return (\n isRecord(value) &&\n areNumbersAt(value, [\"seq\", \"t\", \"id\", \"writer\"]) &&\n typeof value[\"preview\"] === \"string\" &&\n areNumbers(value[\"observers\"])\n );\n}\n\nfunction isFlushState(value: unknown): boolean {\n return (\n isRecord(value) &&\n areNumbersAt(value, [\"seq\", \"startSeq\", \"start\", \"end\", \"render\", \"user\", \"runs\"]) &&\n typeof value[\"open\"] === \"boolean\"\n );\n}\n\nfunction isErrorState(value: unknown): boolean {\n return (\n isRecord(value) &&\n areNumbersAt(value, [\"seq\", \"t\", \"id\"]) &&\n typeof value[\"message\"] === \"string\" &&\n isOptionalText(value[\"code\"])\n );\n}\n\nfunction isHydrationState(value: unknown): boolean {\n return (\n isRecord(value) &&\n areNumbersAt(value, [\"seq\", \"t\"]) &&\n areTexts(value, [\"message\", \"expected\", \"actual\"])\n );\n}\n\nfunction every(value: unknown, check: (entry: unknown) => boolean): boolean {\n if (!Array.isArray(value)) return false;\n for (let index = 0; index < value.length; index += 1) {\n if (!check(value[index])) return false;\n }\n return true;\n}\n\n/**\n * True when `value` has the shape of a {@link ModelState}: the counters are numbers and every list holds\n * records with the fields the model and the views read.\n *\n * @param value The `result` of an ok `snapshot` response.\n * @returns True when {@link InspectorModel.importState} can load it.\n * @since 1.0.0\n */\nexport function isModelState(value: unknown): value is ModelState {\n return (\n isRecord(value) &&\n areNumbersAt(value, [\"lastSeq\", \"overflow\", \"unknown\"]) &&\n every(value[\"nodes\"], isNodeState) &&\n every(value[\"graveyard\"], isNodeState) &&\n every(value[\"runs\"], isRunState) &&\n every(value[\"writes\"], isWriteState) &&\n every(value[\"flushes\"], isFlushState) &&\n every(value[\"errors\"], isErrorState) &&\n every(value[\"hydration\"], isHydrationState)\n );\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import"./protocol.js";import{RingBuffer as e}from"./ring.js";const t={runs:2e4,writes:1e4,flushes:1e3,errors:200,graveyard:4e3,maxNodes:1e5};function n(e,t,n,r,i){return{id:e,type:t,label:n,name:void 0,parent:0,alive:!0,createdSeq:r,createdAt:i,disposedSeq:0,render:!1,isComponent:!1,mountedAt:0,sources:[],observers:null,runs:0,totalDuration:0,lastDuration:0,lastStart:0,lastRunSeq:0,lastChangeSeq:0,preview:``,writes:0,lastWriteSeq:0,lastWriter:0,state:``}}function r(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1)if(e[n]!==t[n])return!1;return!0}var i=class{limits;nodes=/* @__PURE__ */ new Map;graveyard=/* @__PURE__ */ new Map;runs;writes;flushes;errors;hydration;lastSeq=0;overflow=0;unknown=0;componentEpoch=0;get componentCacheSize(){return this.componentCache.size}onBury;openFlush=null;flushRuns=0;leaves=/* @__PURE__ */ new Map;componentCache=/* @__PURE__ */ new Map;constructor(n={}){this.limits={...t,...n},this.runs=new e(this.limits.runs),this.writes=new e(this.limits.writes),this.flushes=new e(this.limits.flushes),this.errors=new e(this.limits.errors),this.hydration=new e(this.limits.errors)}node(e){return this.nodes.get(e)??this.graveyard.get(e)}create(e,t,r){if(this.nodes.size>=this.limits.maxNodes)return this.overflow+=1,null;let i=n(e.id,t,r,e.seq,e.t);return this.nodes.set(e.id,i),i}addLeaf(e,t){if(e===0)return;let n=this.leaves.get(e);if(n===void 0&&(n=[],this.leaves.set(e,n)),n.push(t),n.length>1024&&n.length%1024==0){let e=n.filter(e=>this.nodes.has(e));n.length=0,n.push(...e)}}bury(e,t){let n=[e];for(;n.length>0;){let e=n.pop();if(e===void 0)break;if(!this.nodes.has(e.id))continue;if(e.alive=!1,e.disposedSeq=t,this.nodes.delete(e.id),e.sources.length>0&&this.setSources(e,[]),e.observers!==null)for(let t of e.observers){let n=this.nodes.get(t);n!==void 0&&(n.sources=n.sources.filter(t=>t!==e.id))}if(e.observers=null,this.graveyard.set(e.id,e),this.graveyard.size>this.limits.graveyard){let e=this.graveyard.keys().next();e.done!==!0&&this.graveyard.delete(e.value)}this.componentCache.delete(e.id);let r=this.leaves.get(e.id);if(r!==void 0){this.leaves.delete(e.id);for(let e of r){let t=this.nodes.get(e);t!==void 0&&n.push(t)}}this.onBury?.(e)}}setSources(e,t){let n=e.sources;if(r(n,t))return;let i=new Set(t);for(let t of n)i.has(t)||this.nodes.get(t)?.observers?.delete(e.id);for(let n of t){let t=this.nodes.get(n);t!==void 0&&(t.observers===null&&(t.observers=/* @__PURE__ */ new Set),t.observers.add(e.id))}e.sources=t.slice()}apply(e){if(!(e.seq<=this.lastSeq))switch(this.lastSeq=e.seq,e.k){case`owner+`:{let t=this.create(e,`owner`,e.label);if(t===null)return;t.parent=e.parent,t.name=e.name;return}case`owner-`:case`comp-`:{let t=this.nodes.get(e.id);if(t===void 0){this.unknown+=1;return}t.isComponent&&(this.componentEpoch+=1),this.bury(t,e.seq);return}case`signal+`:{let t=this.create(e,`signal`,`signal`);if(t===null)return;t.parent=e.owner,t.name=e.name,t.preview=e.preview,this.addLeaf(e.owner,e.id);return}case`write`:{let t=this.nodes.get(e.id);if(t===void 0){this.unknown+=1;return}t.writes+=1,t.preview=e.preview,t.lastWriteSeq=e.seq,t.lastChangeSeq=e.seq,t.lastWriter=e.writer,this.writes.push({seq:e.seq,t:e.t,id:e.id,preview:e.preview,writer:e.writer,observers:e.observers});return}case`comp+`:{let t=this.create(e,e.kind,e.kind);if(t===null)return;t.parent=e.owner,t.name=e.name,t.render=e.render;return}case`run`:{let t=this.nodes.get(e.id);if(t===void 0){this.unknown+=1;return}let n=this.causesOf(t);t.runs+=1,t.totalDuration+=e.duration,t.lastDuration=e.duration,t.lastStart=e.start,t.lastRunSeq=e.seq,e.changed&&(t.lastChangeSeq=e.seq),this.setSources(t,e.sources),this.runs.push({seq:e.seq,t:e.t,id:e.id,start:e.start,duration:e.duration,changed:e.changed,flush:this.openFlush===null?0:this.openFlush.seq,causes:n}),this.openFlush!==null&&(this.flushRuns+=1);return}case`mount`:{let t=this.nodes.get(e.id);if(t===void 0){this.unknown+=1;return}t.isComponent=!0,t.label=`component`,t.name=e.name,t.mountedAt=e.t,this.componentEpoch+=1;return}case`unmount`:if(this.nodes.get(e.id)===void 0){this.unknown+=1;return}this.componentEpoch+=1;return;case`resource`:{let t=this.nodes.get(e.id);if(t===void 0){if(t=this.create(e,`resource`,`resource`)??void 0,t===void 0)return;t.parent=e.owner,this.addLeaf(e.owner,e.id)}t.state=e.state,e.name!==void 0&&(t.name=e.name),t.lastChangeSeq=e.seq;return}case`store+`:{let t=this.create(e,`store`,`store`);if(t===null)return;t.parent=e.owner,t.name=e.name,this.addLeaf(e.owner,e.id);return}case`error`:this.errors.push({seq:e.seq,t:e.t,id:e.id,message:e.message,code:e.code});return;case`hydration`:this.hydration.push({seq:e.seq,t:e.t,message:e.message,expected:e.expected,actual:e.actual});return;case`flush+`:{let t={seq:e.seq,startSeq:e.seq,start:e.t,end:e.t,render:e.render,user:e.user,runs:0,open:!0};this.flushes.push(t),this.openFlush=t,this.flushRuns=0;return}case`flush-`:{let t=this.openFlush;if(t===null)return;t.end=e.t,t.runs=this.flushRuns,t.open=!1,this.openFlush=null;return}}}causesOf(e){if(e.runs===0)return[{kind:`initial`,id:0,seq:e.createdSeq}];let t=[];for(let n of e.sources){let r=this.nodes.get(n);r===void 0||r.lastChangeSeq<=e.lastRunSeq||t.push({kind:r.type===`signal`?`write`:`run`,id:n,seq:r.lastChangeSeq})}return t.length===0&&t.push({kind:`unknown`,id:0,seq:e.lastRunSeq}),t.sort((e,t)=>e.seq-t.seq),t}componentOf(e){let t=this.componentCache.get(e);if(t!==void 0&&t.epoch===this.componentEpoch)return t.component;let n=[],r=0,i=e,a=this.nodes.size+this.graveyard.size+1;for(let e=0;e<a;e+=1){let e=this.componentCache.get(i);if(e!==void 0&&e.epoch===this.componentEpoch){r=e.component;break}let t=this.node(i);if(t===void 0)break;if(n.push(i),t.isComponent){r=t.id;break}if(t.parent===0)break;i=t.parent}let o={epoch:this.componentEpoch,component:r};for(let e of n)this.componentCache.set(e,o);return this.componentCache.size>this.limits.maxNodes&&this.componentCache.clear(),r}exportState(){let e=e=>({...e,sources:e.sources.slice(),observers:e.observers===null?[]:[...e.observers]});return{lastSeq:this.lastSeq,overflow:this.overflow,unknown:this.unknown,nodes:[...this.nodes.values()].map(e),graveyard:[...this.graveyard.values()].map(e),runs:this.runs.toArray(),writes:this.writes.toArray(),flushes:this.flushes.toArray().map(e=>({...e})),errors:this.errors.toArray(),hydration:this.hydration.toArray()}}importState(e){this.nodes.clear(),this.graveyard.clear(),this.runs.clear(),this.writes.clear(),this.flushes.clear(),this.errors.clear(),this.hydration.clear(),this.componentCache.clear(),this.openFlush=null,this.flushRuns=0;let t=e=>({...e,sources:e.sources.slice(),observers:e.observers.length===0?null:new Set(e.observers)});this.leaves.clear();for(let n of e.nodes){let e=t(n);this.nodes.set(n.id,e),(e.type===`signal`||e.type===`store`||e.type===`resource`)&&this.addLeaf(e.parent,e.id)}for(let n of e.graveyard)this.graveyard.set(n.id,t(n));for(let t of e.runs)this.runs.push(t);for(let t of e.writes)this.writes.push(t);for(let t of e.flushes){let e={...t};this.flushes.push(e),e.open&&(this.openFlush=e)}for(let t of e.errors)this.errors.push(t);for(let t of e.hydration)this.hydration.push(t);this.lastSeq=e.lastSeq,this.overflow=e.overflow,this.unknown=e.unknown,this.componentEpoch+=1}};function a(e,t){let n=new i(t);for(let t of e)n.apply(t);return n}export{t as DEFAULT_LIMITS,i as InspectorModel,a as foldEvents};
1
+ import"./protocol.js";import{RingBuffer as e}from"./ring.js";const t={runs:2e4,writes:1e4,flushes:1e3,errors:200,graveyard:4e3,maxNodes:1e5};function n(e,t,n,r,i){return{id:e,type:t,label:n,name:void 0,parent:0,alive:!0,createdSeq:r,createdAt:i,disposedSeq:0,render:!1,isComponent:!1,mountedAt:0,sources:[],observers:null,runs:0,totalDuration:0,lastDuration:0,lastStart:0,lastRunSeq:0,lastChangeSeq:0,preview:``,writes:0,lastWriteSeq:0,lastWriter:0,state:``}}function r(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1)if(e[n]!==t[n])return!1;return!0}var i=class{limits;nodes=/* @__PURE__ */ new Map;graveyard=/* @__PURE__ */ new Map;runs;writes;flushes;errors;hydration;lastSeq=0;overflow=0;unknown=0;componentEpoch=0;get componentCacheSize(){return this.componentCache.size}onBury;openFlush=null;flushRuns=0;leaves=/* @__PURE__ */ new Map;componentCache=/* @__PURE__ */ new Map;constructor(n={}){this.limits={...t,...n},this.runs=new e(this.limits.runs),this.writes=new e(this.limits.writes),this.flushes=new e(this.limits.flushes),this.errors=new e(this.limits.errors),this.hydration=new e(this.limits.errors)}node(e){return this.nodes.get(e)??this.graveyard.get(e)}create(e,t,r){if(this.nodes.size>=this.limits.maxNodes)return this.overflow+=1,null;let i=n(e.id,t,r,e.seq,e.t);return this.nodes.set(e.id,i),i}addLeaf(e,t){if(e===0)return;let n=this.leaves.get(e);if(n===void 0&&(n=[],this.leaves.set(e,n)),n.push(t),n.length>1024&&n.length%1024==0){let e=n.filter(e=>this.nodes.has(e));n.length=0,n.push(...e)}}bury(e,t){let n=[e];for(;n.length>0;){let e=n.pop();if(e===void 0)break;if(!this.nodes.has(e.id))continue;if(e.alive=!1,e.disposedSeq=t,this.nodes.delete(e.id),e.sources.length>0&&this.setSources(e,[]),e.observers!==null)for(let t of e.observers){let n=this.nodes.get(t);n!==void 0&&(n.sources=n.sources.filter(t=>t!==e.id))}e.observers=null,this.graveyard.set(e.id,e),this.trimGraveyard(),this.componentCache.delete(e.id);let r=this.leaves.get(e.id);if(r!==void 0){this.leaves.delete(e.id);for(let e of r){let t=this.nodes.get(e);t!==void 0&&n.push(t)}}this.onBury?.(e)}}trimGraveyard(){for(;this.graveyard.size>this.limits.graveyard;){let e=this.graveyard.keys().next();if(e.done===!0)return;this.graveyard.delete(e.value)}}setSources(e,t){let n=e.sources;if(r(n,t))return;let i=new Set(t);for(let t of n)i.has(t)||this.nodes.get(t)?.observers?.delete(e.id);for(let n of t){let t=this.nodes.get(n);t!==void 0&&(t.observers===null&&(t.observers=/* @__PURE__ */ new Set),t.observers.add(e.id))}e.sources=t.slice()}apply(e){if(!(e.seq<=this.lastSeq))switch(this.lastSeq=e.seq,e.k){case`owner+`:{let t=this.create(e,`owner`,e.label);if(t===null)return;t.parent=e.parent,t.name=e.name;return}case`owner-`:case`comp-`:{let t=this.nodes.get(e.id);if(t===void 0){this.unknown+=1;return}t.isComponent&&(this.componentEpoch+=1),this.bury(t,e.seq);return}case`signal+`:{let t=this.create(e,`signal`,`signal`);if(t===null)return;t.parent=e.owner,t.name=e.name,t.preview=e.preview,this.addLeaf(e.owner,e.id);return}case`write`:{let t=this.nodes.get(e.id);if(t===void 0){this.unknown+=1;return}t.writes+=1,t.preview=e.preview,t.lastWriteSeq=e.seq,t.lastChangeSeq=e.seq,t.lastWriter=e.writer,this.writes.push({seq:e.seq,t:e.t,id:e.id,preview:e.preview,writer:e.writer,observers:e.observers});return}case`comp+`:{let t=this.create(e,e.kind,e.kind);if(t===null)return;t.parent=e.owner,t.name=e.name,t.render=e.render;return}case`run`:{let t=this.nodes.get(e.id);if(t===void 0){this.unknown+=1;return}let n=this.causesOf(t);t.runs+=1,t.totalDuration+=e.duration,t.lastDuration=e.duration,t.lastStart=e.start,t.lastRunSeq=e.seq,e.changed&&(t.lastChangeSeq=e.seq),this.setSources(t,e.sources),this.runs.push({seq:e.seq,t:e.t,id:e.id,start:e.start,duration:e.duration,changed:e.changed,flush:this.openFlush===null?0:this.openFlush.seq,causes:n}),this.openFlush!==null&&(this.flushRuns+=1);return}case`mount`:{let t=this.nodes.get(e.id);if(t===void 0){this.unknown+=1;return}t.isComponent=!0,t.label=`component`,t.name=e.name,t.mountedAt=e.t,this.componentEpoch+=1;return}case`unmount`:if(this.nodes.get(e.id)===void 0){this.unknown+=1;return}this.componentEpoch+=1;return;case`resource`:{let t=this.nodes.get(e.id);if(t===void 0){if(t=this.create(e,`resource`,`resource`)??void 0,t===void 0)return;t.parent=e.owner,this.addLeaf(e.owner,e.id)}t.state=e.state,e.name!==void 0&&(t.name=e.name),t.lastChangeSeq=e.seq;return}case`store+`:{let t=this.create(e,`store`,`store`);if(t===null)return;t.parent=e.owner,t.name=e.name,this.addLeaf(e.owner,e.id);return}case`error`:this.errors.push({seq:e.seq,t:e.t,id:e.id,message:e.message,code:e.code});return;case`hydration`:this.hydration.push({seq:e.seq,t:e.t,message:e.message,expected:e.expected,actual:e.actual});return;case`flush+`:{let t={seq:e.seq,startSeq:e.seq,start:e.t,end:e.t,render:e.render,user:e.user,runs:0,open:!0};this.flushes.push(t),this.openFlush=t,this.flushRuns=0;return}case`flush-`:{let t=this.openFlush;if(t===null)return;t.end=e.t,t.runs=this.flushRuns,t.open=!1,this.openFlush=null;return}}}causesOf(e){if(e.runs===0)return[{kind:`initial`,id:0,seq:e.createdSeq}];let t=[];for(let n of e.sources){let r=this.nodes.get(n);r===void 0||r.lastChangeSeq<=e.lastRunSeq||t.push({kind:r.type===`signal`?`write`:`run`,id:n,seq:r.lastChangeSeq})}return t.length===0&&t.push({kind:`unknown`,id:0,seq:e.lastRunSeq}),t.sort((e,t)=>e.seq-t.seq),t}componentOf(e){let t=this.componentCache.get(e);if(t!==void 0&&t.epoch===this.componentEpoch)return t.component;let n=[],r=0,i=e,a=this.nodes.size+this.graveyard.size+1;for(let e=0;e<a;e+=1){let e=this.componentCache.get(i);if(e!==void 0&&e.epoch===this.componentEpoch){r=e.component;break}let t=this.node(i);if(t===void 0)break;if(n.push(i),t.isComponent){r=t.id;break}if(t.parent===0)break;i=t.parent}let o={epoch:this.componentEpoch,component:r};for(let e of n)this.componentCache.set(e,o);return this.componentCache.size>this.limits.maxNodes&&this.componentCache.clear(),r}exportState(){let e=e=>({...e,sources:e.sources.slice(),observers:e.observers===null?[]:[...e.observers]});return{lastSeq:this.lastSeq,overflow:this.overflow,unknown:this.unknown,nodes:[...this.nodes.values()].map(e),graveyard:[...this.graveyard.values()].map(e),runs:this.runs.toArray(),writes:this.writes.toArray(),flushes:this.flushes.toArray().map(e=>({...e})),errors:this.errors.toArray(),hydration:this.hydration.toArray()}}importState(e){this.nodes.clear(),this.graveyard.clear(),this.runs.clear(),this.writes.clear(),this.flushes.clear(),this.errors.clear(),this.hydration.clear(),this.componentCache.clear(),this.openFlush=null,this.flushRuns=0;let t=e=>({...e,sources:e.sources.slice(),observers:e.observers.length===0?null:new Set(e.observers)});this.leaves.clear();for(let n of e.nodes){let e=t(n);this.nodes.set(n.id,e),(e.type===`signal`||e.type===`store`||e.type===`resource`)&&this.addLeaf(e.parent,e.id)}for(let n of e.graveyard)this.graveyard.set(n.id,t(n));this.trimGraveyard();for(let t of e.runs)this.runs.push(t);for(let t of e.writes)this.writes.push(t);for(let t of e.flushes){let e={...t};this.flushes.push(e),e.open&&(this.openFlush=e)}for(let t of e.errors)this.errors.push(t);for(let t of e.hydration)this.hydration.push(t);this.lastSeq=e.lastSeq,this.overflow=e.overflow,this.unknown=e.unknown,this.componentEpoch+=1}};function a(e,t){let n=new i(t);for(let t of e)n.apply(t);return n}export{t as DEFAULT_LIMITS,i as InspectorModel,a as foldEvents};
2
2
  //# sourceMappingURL=model.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"model.js","names":["DEFAULT_LIMITS","blank","seq","t","sameIds","a","b","i","InspectorModel","limits","RingBuffer","id","record","event","type","label","owner","list","alive","leaf","stack","next","observer","reader","oldest","owned","leafId","previous","keep","source","causes","open","cached","path","component","currentId","limit","steps","hit","current","entry","visited","plain","flush","revive","state","run","write","copy","error","mismatch","foldEvents","model","events"],"sources":["../../src/model.ts"],"sourcesContent":["import { NO_ID, type WireEvent } from \"./protocol\";\nimport { RingBuffer } from \"./ring\";\n\n/**\n * The kinds of node the inspector tracks: an owner, a signal, a memo, an effect, a resource or a store.\n *\n * @since 1.0.0\n */\nexport type NodeType = \"owner\" | \"signal\" | \"memo\" | \"effect\" | \"resource\" | \"store\";\n\n/**\n * Everything the model knows about one node. Live nodes are in {@link InspectorModel.nodes}; disposed\n * ones are kept in {@link InspectorModel.graveyard} for a while.\n *\n * @since 1.0.0\n */\nexport interface NodeRecord {\n /** Id assigned by the agent. Never {@link NO_ID}. */\n id: number;\n /** What kind of node this is. */\n type: NodeType;\n /**\n * How the node is labeled: `owner` or `root` for owners, `memo` or `effect` for computations,\n * `signal`, `resource` or `store`, and `component` once a component was mounted on the owner.\n */\n label: string;\n /** The name the code gave the node, or `undefined`. */\n name: string | undefined;\n /** Owner id, or {@link NO_ID} for roots and detached nodes. */\n parent: number;\n /** False once the node left the live set: disposed, cascaded from its owner, or garbage collected. */\n alive: boolean;\n /** Sequence number of the event that created the node. */\n createdSeq: number;\n /** Time of the creation event, in milliseconds on the agent's clock. */\n createdAt: number;\n /** Sequence number of the event that removed the node, or 0 while it is alive. */\n disposedSeq: number;\n /** Computations: created in the render phase (the runtime's render flag). False otherwise. */\n render: boolean;\n /** True once a component was mounted on this owner. */\n isComponent: boolean;\n /** Time the component was mounted (agent clock, in milliseconds), or 0 when it never was. */\n mountedAt: number;\n /** Computations: ids of the nodes read by the last run. Empty for other nodes. */\n sources: number[];\n /** Ids of the computations that currently read this node. `null` when none ever did. */\n observers: Set<number> | null;\n /** Number of runs recorded. 0 for nodes that never run. */\n runs: number;\n /** Sum of the durations of all runs, in milliseconds. */\n totalDuration: number;\n /** Duration of the last run, in milliseconds. */\n lastDuration: number;\n /** Start of the last run, in milliseconds as the runtime reported it. */\n lastStart: number;\n /** Sequence number of the last run, or 0 when it never ran. */\n lastRunSeq: number;\n /**\n * Sequence number of the last change: a write for a signal, a run that changed the value for a\n * computation, or a state report for a resource. 0 when there was none.\n */\n lastChangeSeq: number;\n /** Signals: a short text of the current value. Empty for other nodes. */\n preview: string;\n /** Number of writes to a signal. 0 for other nodes. */\n writes: number;\n /** Sequence number of the last write, or 0 when never written. */\n lastWriteSeq: number;\n /**\n * Id of the computation that performed the last write, or {@link NO_ID} for a write from outside\n * any computation (an event handler, for example) and when the node was never written.\n */\n lastWriter: number;\n /** Resources: the last reported state, for example `\"pending\"` or `\"ready\"`. Empty for other nodes. */\n state: string;\n}\n\n/**\n * Why a computation ran. A run has one cause per source that changed since the computation's previous\n * run; see {@link RunRecord.causes}.\n *\n * @since 1.0.0\n */\nexport interface Cause {\n /**\n * `write` when the source is a signal that was written, `run` when the source is a memo or a\n * resource that changed, `initial` for the first run, and `unknown` when no recorded source changed.\n */\n kind: \"write\" | \"run\" | \"initial\" | \"unknown\";\n /** The source that changed: a signal or memo ({@link NO_ID} for `initial` and `unknown`). */\n id: number;\n /**\n * Sequence number of that change: the write or the run of the source, the creation for `initial`,\n * or the last run for `unknown`.\n */\n seq: number;\n}\n\n/**\n * One run of an effect or a memo, as recorded.\n *\n * @since 1.0.0\n */\nexport interface RunRecord {\n /** Sequence number of the run event. */\n seq: number;\n /** Time the event was received (agent clock, in milliseconds). */\n t: number;\n /** The computation that ran. */\n id: number;\n /** When the run started, in milliseconds as the runtime reported it. */\n start: number;\n /** How long the run took, in milliseconds. */\n duration: number;\n /** True when the computation's value changed. */\n changed: boolean;\n /** Sequence number of the flush it belongs to ({@link FlushRecord.seq}), or 0 when it ran outside a flush. */\n flush: number;\n /** Why it ran: see {@link Cause}. */\n causes: Cause[];\n}\n\n/**\n * One signal write, as recorded.\n *\n * @since 1.0.0\n */\nexport interface WriteRecord {\n /** Sequence number of the write event. */\n seq: number;\n /** Time the event was received (agent clock, in milliseconds). */\n t: number;\n /** The signal that was written. */\n id: number;\n /** A short text of the value written. */\n preview: string;\n /** Computation that wrote ({@link NO_ID}: outside code such as an event handler). */\n writer: number;\n /** Ids of the computations that read the signal when it was written. */\n observers: number[];\n}\n\n/**\n * One flush of the effect queues. A flush is recorded only when at least one of its runs is recorded.\n *\n * @since 1.0.0\n */\nexport interface FlushRecord {\n /** Sequence number of the flush-start event. */\n seq: number;\n /** The same value as {@link FlushRecord.seq}: the sequence number of the flush-start event. */\n startSeq: number;\n /** When the flush started (agent clock, in milliseconds). */\n start: number;\n /** When the flush ended (agent clock, in milliseconds). Equal to `start` while the flush is open. */\n end: number;\n /** Effects in the render queue when the flush started. */\n render: number;\n /** Effects in the user queue when the flush started. */\n user: number;\n /**\n * Runs recorded during the flush. The model counts them itself, so runs the agent ignored (the\n * tools' own) are not included. 0 while the flush is open.\n */\n runs: number;\n /** True until the flush-end event arrives. */\n open: boolean;\n}\n\n/**\n * An error the runtime reported.\n *\n * @since 1.0.0\n */\nexport interface ErrorRecord {\n /** Sequence number of the error event. */\n seq: number;\n /** Time the event was received (agent clock, in milliseconds). */\n t: number;\n /** The node the error came from, or {@link NO_ID} when it had no known owner. */\n id: number;\n /** The thrown value as text (see `errorMessage`). */\n message: string;\n /** A diagnostic code the runtime attached, for example `WRITE_IN_MEMO`, or `undefined`. */\n code: string | undefined;\n}\n\n/**\n * A hydration mismatch the runtime reported.\n *\n * @since 1.0.0\n */\nexport interface HydrationRecord {\n /** Sequence number of the mismatch event. */\n seq: number;\n /** Time the event was received (agent clock, in milliseconds). */\n t: number;\n /** What the runtime reported, as text. */\n message: string;\n /** The expected side, as text: a string, or the HTML of an element. Cut to 200 characters. */\n expected: string;\n /** The actual side, as text: a string, or the HTML of an element. Cut to 200 characters. */\n actual: string;\n}\n\n/**\n * Capacities of the model's bounded buffers. When passed to the constructor each field is optional;\n * the values left out are the defaults in {@link DEFAULT_LIMITS}.\n *\n * @since 1.0.0\n */\nexport interface ModelLimits {\n /**\n * Run records kept for timelines and causal chains.\n *\n * @default 20000\n */\n runs: number;\n /**\n * Write records kept for causal chains.\n *\n * @default 10000\n */\n writes: number;\n /**\n * Flush records kept.\n *\n * @default 1000\n */\n flushes: number;\n /**\n * Error records kept, and hydration records kept (each buffer on its own).\n *\n * @default 200\n */\n errors: number;\n /**\n * Disposed nodes kept so that old runs can still be named.\n *\n * @default 4000\n */\n graveyard: number;\n /**\n * Live nodes tracked. A node created beyond this count is counted in {@link InspectorModel.overflow}\n * and ignored; later events about it count as unknown.\n *\n * @default 100000\n */\n maxNodes: number;\n}\n\n/**\n * The default capacities of the model's buffers, used for each limit that the caller leaves out.\n *\n * @example\n * import { DEFAULT_LIMITS, foldEvents } from \"@shadow-js/devtools\";\n * import type { WireEvent } from \"@shadow-js/devtools\";\n *\n * const events: WireEvent[] = [];\n * const model = foldEvents(events, { ...DEFAULT_LIMITS, runs: 500 });\n * @since 1.0.0\n */\nexport const DEFAULT_LIMITS: Readonly<ModelLimits> = {\n /** Run records kept. @default 20000 */\n runs: 20_000,\n /** Write records kept. @default 10000 */\n writes: 10_000,\n /** Flush records kept. @default 1000 */\n flushes: 1_000,\n /** Error records kept, and hydration records kept. @default 200 */\n errors: 200,\n /** Disposed nodes kept. @default 4000 */\n graveyard: 4_000,\n /** Live nodes tracked. @default 100000 */\n maxNodes: 100_000,\n};\n\n/**\n * The JSON-safe state of a model: what the agent's `state()` returns (for a remote panel) and what\n * {@link InspectorModel.importState} accepts.\n *\n * @since 1.0.0\n */\nexport interface ModelState {\n /** Sequence number of the last applied event. */\n lastSeq: number;\n /** Nodes ignored because `maxNodes` was reached. */\n overflow: number;\n /** Events that referred to a node the model does not know. */\n unknown: number;\n /** Live nodes, in insertion order, with observers as arrays. */\n nodes: Array<Omit<NodeRecord, \"observers\"> & { observers: number[] }>;\n /** Disposed nodes, oldest first, with observers as arrays. */\n graveyard: Array<Omit<NodeRecord, \"observers\"> & { observers: number[] }>;\n /** Run records, oldest first. */\n runs: RunRecord[];\n /** Write records, oldest first. */\n writes: WriteRecord[];\n /** Flush records, oldest first. */\n flushes: FlushRecord[];\n /** Error records, oldest first. */\n errors: ErrorRecord[];\n /** Hydration records, oldest first. */\n hydration: HydrationRecord[];\n}\n\nfunction blank(id: number, type: NodeType, label: string, seq: number, t: number): NodeRecord {\n return {\n id,\n type,\n label,\n name: undefined,\n parent: NO_ID,\n alive: true,\n createdSeq: seq,\n createdAt: t,\n disposedSeq: 0,\n render: false,\n isComponent: false,\n mountedAt: 0,\n sources: [],\n observers: null,\n runs: 0,\n totalDuration: 0,\n lastDuration: 0,\n lastStart: 0,\n lastRunSeq: 0,\n lastChangeSeq: 0,\n preview: \"\",\n writes: 0,\n lastWriteSeq: 0,\n lastWriter: NO_ID,\n state: \"\",\n };\n}\n\nfunction sameIds(a: readonly number[], b: readonly number[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i += 1) if (a[i] !== b[i]) return false;\n return true;\n}\n\n/**\n * The inspector's incremental model of the reactive graph: a pure fold over {@link WireEvent}s. The\n * same events always produce the same state, on the page or in a remote panel. `apply` is O(1) for\n * most events and O(sources) for a run; memory is bounded by {@link ModelLimits}.\n *\n * A development tool: its events come from the dev hook, which exists only in development builds.\n *\n * @example\n * import { InspectorModel, snapshotOf } from \"@shadow-js/devtools\";\n * import type { WireEvent } from \"@shadow-js/devtools\";\n *\n * const events: WireEvent[] = [];\n * const model = new InspectorModel();\n * for (const event of events) model.apply(event);\n * snapshotOf(model).counts.nodes;\n * @since 1.0.0\n */\nexport class InspectorModel {\n /** The capacities in effect: the defaults with the constructor's overrides applied. */\n readonly limits: Readonly<ModelLimits>;\n /** Live nodes by id. */\n readonly nodes: Map<number, NodeRecord> = new Map();\n /**\n * Recently disposed nodes, oldest first, kept so that history can still be named. Holds at most\n * `limits.graveyard` nodes.\n */\n readonly graveyard: Map<number, NodeRecord> = new Map();\n /** Run records, the newest kept (capacity `limits.runs`). */\n readonly runs: RingBuffer<RunRecord>;\n /** Write records, the newest kept (capacity `limits.writes`). */\n readonly writes: RingBuffer<WriteRecord>;\n /** Flush records, the newest kept (capacity `limits.flushes`). */\n readonly flushes: RingBuffer<FlushRecord>;\n /** Error records, the newest kept (capacity `limits.errors`). */\n readonly errors: RingBuffer<ErrorRecord>;\n /** Hydration records, the newest kept (capacity `limits.errors`). */\n readonly hydration: RingBuffer<HydrationRecord>;\n /** Sequence number of the last applied event. */\n lastSeq = 0;\n /** Nodes ignored because `maxNodes` was reached. */\n overflow = 0;\n /** Events that referred to a node the model does not know. */\n unknown = 0;\n /**\n * Changes whenever a component is mounted, unmounted or disposed. Cached component lookups from an\n * older epoch are recomputed.\n */\n componentEpoch = 0;\n /** Entries in the component lookup cache. Never more than `limits.maxNodes` after a lookup. */\n get componentCacheSize(): number {\n return this.componentCache.size;\n }\n\n /**\n * Called once for every node that leaves the live set, after it moved to the graveyard: when it is\n * disposed, or when its owner is disposed (cascade). The agent uses it to drop its own references.\n */\n onBury: ((record: NodeRecord) => void) | undefined;\n private openFlush: FlushRecord | null = null;\n private flushRuns = 0;\n /** Signals, stores and resources by owner: they have no lifecycle event of their own. */\n private readonly leaves: Map<number, number[]> = new Map();\n private readonly componentCache: Map<number, { epoch: number; component: number }> = new Map();\n\n /**\n * @param limits Overrides for the buffer capacities. Values left out take the defaults in\n * {@link DEFAULT_LIMITS}.\n */\n constructor(limits: Partial<ModelLimits> = {}) {\n this.limits = { ...DEFAULT_LIMITS, ...limits };\n this.runs = new RingBuffer(this.limits.runs);\n this.writes = new RingBuffer(this.limits.writes);\n this.flushes = new RingBuffer(this.limits.flushes);\n this.errors = new RingBuffer(this.limits.errors);\n this.hydration = new RingBuffer(this.limits.errors);\n }\n\n /**\n * The node with this id: alive, or recently disposed (in the graveyard).\n *\n * @param id The node id.\n * @returns The record, or `undefined` when the model does not know the id.\n */\n node(id: number): NodeRecord | undefined {\n return this.nodes.get(id) ?? this.graveyard.get(id);\n }\n\n private create(\n event: WireEvent & { id: number },\n type: NodeType,\n label: string,\n ): NodeRecord | null {\n if (this.nodes.size >= this.limits.maxNodes) {\n this.overflow += 1;\n return null;\n }\n const record = blank(event.id, type, label, event.seq, event.t);\n this.nodes.set(event.id, record);\n return record;\n }\n\n /** Remembers that a leaf node (signal, store, resource) belongs to `owner`. */\n private addLeaf(owner: number, id: number): void {\n if (owner === NO_ID) return;\n let list = this.leaves.get(owner);\n if (list === undefined) {\n list = [];\n this.leaves.set(owner, list);\n }\n list.push(id);\n // Leaves that died on their own (garbage collected) would pile up on a long-lived owner.\n if (list.length > 1024 && list.length % 1024 === 0) {\n const alive = list.filter((leaf) => this.nodes.has(leaf));\n list.length = 0;\n list.push(...alive);\n }\n }\n\n private bury(record: NodeRecord, seq: number): void {\n // Iterative: the cascade follows owner -> leaves only, but disposal order is user controlled.\n const stack: NodeRecord[] = [record];\n while (stack.length > 0) {\n const next = stack.pop();\n if (next === undefined) break;\n if (!this.nodes.has(next.id)) continue;\n next.alive = false;\n next.disposedSeq = seq;\n this.nodes.delete(next.id);\n if (next.sources.length > 0) this.setSources(next, []);\n if (next.observers !== null) {\n for (const observer of next.observers) {\n const reader = this.nodes.get(observer);\n if (reader !== undefined) reader.sources = reader.sources.filter((id) => id !== next.id);\n }\n }\n next.observers = null;\n this.graveyard.set(next.id, next);\n if (this.graveyard.size > this.limits.graveyard) {\n const oldest = this.graveyard.keys().next();\n if (oldest.done !== true) this.graveyard.delete(oldest.value);\n }\n this.componentCache.delete(next.id);\n const owned = this.leaves.get(next.id);\n if (owned !== undefined) {\n this.leaves.delete(next.id);\n for (const leafId of owned) {\n const leaf = this.nodes.get(leafId);\n if (leaf !== undefined) stack.push(leaf);\n }\n }\n this.onBury?.(next);\n }\n }\n\n private setSources(record: NodeRecord, next: readonly number[]): void {\n const previous = record.sources;\n if (sameIds(previous, next)) return;\n const keep = new Set(next);\n for (const id of previous) {\n if (keep.has(id)) continue;\n this.nodes.get(id)?.observers?.delete(record.id);\n }\n for (const id of next) {\n const source = this.nodes.get(id);\n if (source === undefined) continue;\n if (source.observers === null) source.observers = new Set();\n source.observers.add(record.id);\n }\n record.sources = next.slice();\n }\n\n /**\n * Applies one event. Events must arrive in `seq` order: an event whose `seq` is not greater than\n * {@link InspectorModel.lastSeq} is ignored, so a remote panel can receive events it already holds.\n * An event about a node the model does not know is counted in `unknown`. A node created beyond\n * `maxNodes` is counted in `overflow` and ignored.\n *\n * @param event The event, with the sequence number and time the agent gave it.\n */\n apply(event: WireEvent): void {\n if (event.seq <= this.lastSeq) return;\n this.lastSeq = event.seq;\n switch (event.k) {\n case \"owner+\": {\n const record = this.create(event, \"owner\", event.label);\n if (record === null) return;\n record.parent = event.parent;\n record.name = event.name;\n return;\n }\n case \"owner-\":\n case \"comp-\": {\n const record = this.nodes.get(event.id);\n if (record === undefined) {\n this.unknown += 1;\n return;\n }\n if (record.isComponent) this.componentEpoch += 1;\n this.bury(record, event.seq);\n return;\n }\n case \"signal+\": {\n const record = this.create(event, \"signal\", \"signal\");\n if (record === null) return;\n record.parent = event.owner;\n record.name = event.name;\n record.preview = event.preview;\n this.addLeaf(event.owner, event.id);\n return;\n }\n case \"write\": {\n const record = this.nodes.get(event.id);\n if (record === undefined) {\n this.unknown += 1;\n return;\n }\n record.writes += 1;\n record.preview = event.preview;\n record.lastWriteSeq = event.seq;\n record.lastChangeSeq = event.seq;\n record.lastWriter = event.writer;\n this.writes.push({\n seq: event.seq,\n t: event.t,\n id: event.id,\n preview: event.preview,\n writer: event.writer,\n observers: event.observers,\n });\n return;\n }\n case \"comp+\": {\n const record = this.create(event, event.kind, event.kind);\n if (record === null) return;\n record.parent = event.owner;\n record.name = event.name;\n record.render = event.render;\n return;\n }\n case \"run\": {\n const record = this.nodes.get(event.id);\n if (record === undefined) {\n this.unknown += 1;\n return;\n }\n const causes = this.causesOf(record);\n record.runs += 1;\n record.totalDuration += event.duration;\n record.lastDuration = event.duration;\n record.lastStart = event.start;\n record.lastRunSeq = event.seq;\n if (event.changed) record.lastChangeSeq = event.seq;\n this.setSources(record, event.sources);\n this.runs.push({\n seq: event.seq,\n t: event.t,\n id: event.id,\n start: event.start,\n duration: event.duration,\n changed: event.changed,\n flush: this.openFlush === null ? 0 : this.openFlush.seq,\n causes,\n });\n if (this.openFlush !== null) this.flushRuns += 1;\n return;\n }\n case \"mount\": {\n const record = this.nodes.get(event.id);\n if (record === undefined) {\n this.unknown += 1;\n return;\n }\n record.isComponent = true;\n record.label = \"component\";\n record.name = event.name;\n record.mountedAt = event.t;\n this.componentEpoch += 1;\n return;\n }\n case \"unmount\": {\n const record = this.nodes.get(event.id);\n if (record === undefined) {\n this.unknown += 1;\n return;\n }\n // The owner's disposal follows; keep the flag so the component stays attributable.\n this.componentEpoch += 1;\n return;\n }\n case \"resource\": {\n let record = this.nodes.get(event.id);\n if (record === undefined) {\n record = this.create(event, \"resource\", \"resource\") ?? undefined;\n if (record === undefined) return;\n record.parent = event.owner;\n this.addLeaf(event.owner, event.id);\n }\n record.state = event.state;\n if (event.name !== undefined) record.name = event.name;\n record.lastChangeSeq = event.seq;\n return;\n }\n case \"store+\": {\n const record = this.create(event, \"store\", \"store\");\n if (record === null) return;\n record.parent = event.owner;\n record.name = event.name;\n this.addLeaf(event.owner, event.id);\n return;\n }\n case \"error\":\n this.errors.push({\n seq: event.seq,\n t: event.t,\n id: event.id,\n message: event.message,\n code: event.code,\n });\n return;\n case \"hydration\":\n this.hydration.push({\n seq: event.seq,\n t: event.t,\n message: event.message,\n expected: event.expected,\n actual: event.actual,\n });\n return;\n case \"flush+\": {\n const record: FlushRecord = {\n seq: event.seq,\n startSeq: event.seq,\n start: event.t,\n end: event.t,\n render: event.render,\n user: event.user,\n runs: 0,\n open: true,\n };\n this.flushes.push(record);\n this.openFlush = record;\n this.flushRuns = 0;\n return;\n }\n case \"flush-\": {\n const open = this.openFlush;\n if (open === null) return;\n open.end = event.t;\n open.runs = this.flushRuns;\n open.open = false;\n this.openFlush = null;\n return;\n }\n }\n }\n\n /** Why a computation is about to run again: sources that changed since its last run. */\n private causesOf(record: NodeRecord): Cause[] {\n if (record.runs === 0) return [{ kind: \"initial\", id: NO_ID, seq: record.createdSeq }];\n const causes: Cause[] = [];\n for (const id of record.sources) {\n const source = this.nodes.get(id);\n if (source === undefined || source.lastChangeSeq <= record.lastRunSeq) continue;\n causes.push({\n kind: source.type === \"signal\" ? \"write\" : \"run\",\n id,\n seq: source.lastChangeSeq,\n });\n }\n if (causes.length === 0) causes.push({ kind: \"unknown\", id: NO_ID, seq: record.lastRunSeq });\n causes.sort((a, b) => a.seq - b.seq);\n return causes;\n }\n\n /**\n * The nearest component that owns `id`: the node itself when it is a component, otherwise the first\n * component above it in the owner chain (the graveyard included). The answer is cached until the next\n * component mount, unmount or disposal.\n *\n * @param id The node id.\n * @returns The component's id, or {@link NO_ID} when no component owns the node.\n */\n componentOf(id: number): number {\n const cached = this.componentCache.get(id);\n if (cached !== undefined && cached.epoch === this.componentEpoch) return cached.component;\n // Walk up to the first component; every node on the way shares the answer, so cache them all.\n const path: number[] = [];\n let component: number = NO_ID;\n let currentId = id;\n const limit = this.nodes.size + this.graveyard.size + 1;\n for (let steps = 0; steps < limit; steps += 1) {\n const hit = this.componentCache.get(currentId);\n if (hit !== undefined && hit.epoch === this.componentEpoch) {\n component = hit.component;\n break;\n }\n const current = this.node(currentId);\n if (current === undefined) break;\n path.push(currentId);\n if (current.isComponent) {\n component = current.id;\n break;\n }\n if (current.parent === NO_ID) break;\n currentId = current.parent;\n }\n const entry = { epoch: this.componentEpoch, component };\n for (const visited of path) this.componentCache.set(visited, entry);\n if (this.componentCache.size > this.limits.maxNodes) this.componentCache.clear();\n return component;\n }\n\n /**\n * A serializable copy of the whole state, safe to send as JSON. {@link InspectorModel.importState}\n * rebuilds an equal model from it.\n *\n * @returns The state.\n */\n exportState(): ModelState {\n const plain = (record: NodeRecord): ModelState[\"nodes\"][number] => ({\n ...record,\n sources: record.sources.slice(),\n observers: record.observers === null ? [] : [...record.observers],\n });\n return {\n lastSeq: this.lastSeq,\n overflow: this.overflow,\n unknown: this.unknown,\n nodes: [...this.nodes.values()].map(plain),\n graveyard: [...this.graveyard.values()].map(plain),\n runs: this.runs.toArray(),\n writes: this.writes.toArray(),\n flushes: this.flushes.toArray().map((flush) => ({ ...flush })),\n errors: this.errors.toArray(),\n hydration: this.hydration.toArray(),\n };\n }\n\n /**\n * Replaces the whole state with `state`. A remote panel does this with the agent's state, and the\n * agent does it with an empty state to implement `clear`. The capacities of this model do not change:\n * when `state` holds more records than a buffer can keep, the newest are kept.\n *\n * @param state The state to load.\n */\n importState(state: ModelState): void {\n this.nodes.clear();\n this.graveyard.clear();\n this.runs.clear();\n this.writes.clear();\n this.flushes.clear();\n this.errors.clear();\n this.hydration.clear();\n this.componentCache.clear();\n this.openFlush = null;\n this.flushRuns = 0;\n const revive = (plain: ModelState[\"nodes\"][number]): NodeRecord => ({\n ...plain,\n sources: plain.sources.slice(),\n observers: plain.observers.length === 0 ? null : new Set(plain.observers),\n });\n this.leaves.clear();\n for (const plain of state.nodes) {\n const record = revive(plain);\n this.nodes.set(plain.id, record);\n if (record.type === \"signal\" || record.type === \"store\" || record.type === \"resource\") {\n this.addLeaf(record.parent, record.id);\n }\n }\n for (const plain of state.graveyard) this.graveyard.set(plain.id, revive(plain));\n for (const run of state.runs) this.runs.push(run);\n for (const write of state.writes) this.writes.push(write);\n for (const flush of state.flushes) {\n const copy = { ...flush };\n this.flushes.push(copy);\n if (copy.open) this.openFlush = copy;\n }\n for (const error of state.errors) this.errors.push(error);\n for (const mismatch of state.hydration) this.hydration.push(mismatch);\n this.lastSeq = state.lastSeq;\n this.overflow = state.overflow;\n this.unknown = state.unknown;\n this.componentEpoch += 1;\n }\n}\n\n/**\n * Builds a model from a finished event stream. A pure function: the same input always gives the same\n * model. A development tool, like {@link InspectorModel}.\n *\n * @param events Events in `seq` order.\n * @param limits Buffer capacities. Values left out take the defaults in {@link DEFAULT_LIMITS}.\n * @returns The folded model.\n * @example\n * import { foldEvents } from \"@shadow-js/devtools\";\n * import type { WireEvent } from \"@shadow-js/devtools\";\n *\n * const events: WireEvent[] = [];\n * const model = foldEvents(events, { runs: 1000 });\n * @since 1.0.0\n */\nexport function foldEvents(\n events: Iterable<WireEvent>,\n limits?: Partial<ModelLimits>,\n): InspectorModel {\n const model = new InspectorModel(limits);\n for (const event of events) model.apply(event);\n return model;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"model.js","names":["DEFAULT_LIMITS","blank","seq","t","sameIds","a","b","i","InspectorModel","limits","RingBuffer","id","record","event","type","label","owner","list","alive","leaf","stack","next","observer","reader","owned","leafId","oldest","previous","keep","source","causes","open","cached","path","component","currentId","limit","steps","hit","current","entry","visited","plain","flush","revive","state","run","write","copy","error","mismatch","foldEvents","model","events"],"sources":["../../src/model.ts"],"sourcesContent":["import { NO_ID, type WireEvent } from \"./protocol\";\nimport { RingBuffer } from \"./ring\";\n\n/**\n * The kinds of node the inspector tracks: an owner, a signal, a memo, an effect, a resource or a store.\n *\n * @since 1.0.0\n */\nexport type NodeType = \"owner\" | \"signal\" | \"memo\" | \"effect\" | \"resource\" | \"store\";\n\n/**\n * Everything the model knows about one node. Live nodes are in {@link InspectorModel.nodes}; disposed\n * ones are kept in {@link InspectorModel.graveyard} for a while.\n *\n * @since 1.0.0\n */\nexport interface NodeRecord {\n /** Id assigned by the agent. Never {@link NO_ID}. */\n id: number;\n /** What kind of node this is. */\n type: NodeType;\n /**\n * How the node is labeled: `owner` or `root` for owners, `memo` or `effect` for computations,\n * `signal`, `resource` or `store`, and `component` once a component was mounted on the owner.\n */\n label: string;\n /** The name the code gave the node, or `undefined`. */\n name: string | undefined;\n /** Owner id, or {@link NO_ID} for roots and detached nodes. */\n parent: number;\n /** False once the node left the live set: disposed, cascaded from its owner, or garbage collected. */\n alive: boolean;\n /** Sequence number of the event that created the node. */\n createdSeq: number;\n /** Time of the creation event, in milliseconds on the agent's clock. */\n createdAt: number;\n /** Sequence number of the event that removed the node, or 0 while it is alive. */\n disposedSeq: number;\n /** Computations: created in the render phase (the runtime's render flag). False otherwise. */\n render: boolean;\n /** True once a component was mounted on this owner. */\n isComponent: boolean;\n /** Time the component was mounted (agent clock, in milliseconds), or 0 when it never was. */\n mountedAt: number;\n /** Computations: ids of the nodes read by the last run. Empty for other nodes. */\n sources: number[];\n /** Ids of the computations that currently read this node. `null` when none ever did. */\n observers: Set<number> | null;\n /** Number of runs recorded. 0 for nodes that never run. */\n runs: number;\n /** Sum of the durations of all runs, in milliseconds. */\n totalDuration: number;\n /** Duration of the last run, in milliseconds. */\n lastDuration: number;\n /** Start of the last run, in milliseconds as the runtime reported it. */\n lastStart: number;\n /** Sequence number of the last run, or 0 when it never ran. */\n lastRunSeq: number;\n /**\n * Sequence number of the last change: a write for a signal, a run that changed the value for a\n * computation, or a state report for a resource. 0 when there was none.\n */\n lastChangeSeq: number;\n /** Signals: a short text of the current value. Empty for other nodes. */\n preview: string;\n /** Number of writes to a signal. 0 for other nodes. */\n writes: number;\n /** Sequence number of the last write, or 0 when never written. */\n lastWriteSeq: number;\n /**\n * Id of the computation that performed the last write, or {@link NO_ID} for a write from outside\n * any computation (an event handler, for example) and when the node was never written.\n */\n lastWriter: number;\n /** Resources: the last reported state, for example `\"pending\"` or `\"ready\"`. Empty for other nodes. */\n state: string;\n}\n\n/**\n * Why a computation ran. A run has one cause per source that changed since the computation's previous\n * run; see {@link RunRecord.causes}.\n *\n * @since 1.0.0\n */\nexport interface Cause {\n /**\n * `write` when the source is a signal that was written, `run` when the source is a memo or a\n * resource that changed, `initial` for the first run, and `unknown` when no recorded source changed.\n */\n kind: \"write\" | \"run\" | \"initial\" | \"unknown\";\n /** The source that changed: a signal or memo ({@link NO_ID} for `initial` and `unknown`). */\n id: number;\n /**\n * Sequence number of that change: the write or the run of the source, the creation for `initial`,\n * or the last run for `unknown`.\n */\n seq: number;\n}\n\n/**\n * One run of an effect or a memo, as recorded.\n *\n * @since 1.0.0\n */\nexport interface RunRecord {\n /** Sequence number of the run event. */\n seq: number;\n /** Time the event was received (agent clock, in milliseconds). */\n t: number;\n /** The computation that ran. */\n id: number;\n /** When the run started, in milliseconds as the runtime reported it. */\n start: number;\n /** How long the run took, in milliseconds. */\n duration: number;\n /** True when the computation's value changed. */\n changed: boolean;\n /** Sequence number of the flush it belongs to ({@link FlushRecord.seq}), or 0 when it ran outside a flush. */\n flush: number;\n /** Why it ran: see {@link Cause}. */\n causes: Cause[];\n}\n\n/**\n * One signal write, as recorded.\n *\n * @since 1.0.0\n */\nexport interface WriteRecord {\n /** Sequence number of the write event. */\n seq: number;\n /** Time the event was received (agent clock, in milliseconds). */\n t: number;\n /** The signal that was written. */\n id: number;\n /** A short text of the value written. */\n preview: string;\n /** Computation that wrote ({@link NO_ID}: outside code such as an event handler). */\n writer: number;\n /** Ids of the computations that read the signal when it was written. */\n observers: number[];\n}\n\n/**\n * One flush of the effect queues. A flush is recorded only when at least one of its runs is recorded.\n *\n * @since 1.0.0\n */\nexport interface FlushRecord {\n /** Sequence number of the flush-start event. */\n seq: number;\n /** The same value as {@link FlushRecord.seq}: the sequence number of the flush-start event. */\n startSeq: number;\n /** When the flush started (agent clock, in milliseconds). */\n start: number;\n /** When the flush ended (agent clock, in milliseconds). Equal to `start` while the flush is open. */\n end: number;\n /** Effects in the render queue when the flush started. */\n render: number;\n /** Effects in the user queue when the flush started. */\n user: number;\n /**\n * Runs recorded during the flush. The model counts them itself, so runs the agent ignored (the\n * tools' own) are not included. 0 while the flush is open.\n */\n runs: number;\n /** True until the flush-end event arrives. */\n open: boolean;\n}\n\n/**\n * An error the runtime reported.\n *\n * @since 1.0.0\n */\nexport interface ErrorRecord {\n /** Sequence number of the error event. */\n seq: number;\n /** Time the event was received (agent clock, in milliseconds). */\n t: number;\n /** The node the error came from, or {@link NO_ID} when it had no known owner. */\n id: number;\n /** The thrown value as text (see `errorMessage`). */\n message: string;\n /** A diagnostic code the runtime attached, for example `WRITE_IN_MEMO`, or `undefined`. */\n code: string | undefined;\n}\n\n/**\n * A hydration mismatch the runtime reported.\n *\n * @since 1.0.0\n */\nexport interface HydrationRecord {\n /** Sequence number of the mismatch event. */\n seq: number;\n /** Time the event was received (agent clock, in milliseconds). */\n t: number;\n /** What the runtime reported, as text. */\n message: string;\n /** The expected side, as text: a string, or the HTML of an element. Cut to 200 characters. */\n expected: string;\n /** The actual side, as text: a string, or the HTML of an element. Cut to 200 characters. */\n actual: string;\n}\n\n/**\n * Capacities of the model's bounded buffers. When passed to the constructor each field is optional;\n * the values left out are the defaults in {@link DEFAULT_LIMITS}.\n *\n * @since 1.0.0\n */\nexport interface ModelLimits {\n /**\n * Run records kept for timelines and causal chains.\n *\n * @default 20000\n */\n runs: number;\n /**\n * Write records kept for causal chains.\n *\n * @default 10000\n */\n writes: number;\n /**\n * Flush records kept.\n *\n * @default 1000\n */\n flushes: number;\n /**\n * Error records kept, and hydration records kept (each buffer on its own).\n *\n * @default 200\n */\n errors: number;\n /**\n * Disposed nodes kept so that old runs can still be named.\n *\n * @default 4000\n */\n graveyard: number;\n /**\n * Live nodes tracked. A node created beyond this count is counted in {@link InspectorModel.overflow}\n * and ignored; later events about it count as unknown.\n *\n * @default 100000\n */\n maxNodes: number;\n}\n\n/**\n * The default capacities of the model's buffers, used for each limit that the caller leaves out.\n *\n * @example\n * import { DEFAULT_LIMITS, foldEvents } from \"@shadow-js/devtools\";\n * import type { WireEvent } from \"@shadow-js/devtools\";\n *\n * const events: WireEvent[] = [];\n * const model = foldEvents(events, { ...DEFAULT_LIMITS, runs: 500 });\n * @since 1.0.0\n */\nexport const DEFAULT_LIMITS: Readonly<ModelLimits> = {\n /** Run records kept. @default 20000 */\n runs: 20_000,\n /** Write records kept. @default 10000 */\n writes: 10_000,\n /** Flush records kept. @default 1000 */\n flushes: 1_000,\n /** Error records kept, and hydration records kept. @default 200 */\n errors: 200,\n /** Disposed nodes kept. @default 4000 */\n graveyard: 4_000,\n /** Live nodes tracked. @default 100000 */\n maxNodes: 100_000,\n};\n\n/**\n * The JSON-safe state of a model: what the agent's `state()` returns (for a remote panel) and what\n * {@link InspectorModel.importState} accepts.\n *\n * @since 1.0.0\n */\nexport interface ModelState {\n /** Sequence number of the last applied event. */\n lastSeq: number;\n /** Nodes ignored because `maxNodes` was reached. */\n overflow: number;\n /** Events that referred to a node the model does not know. */\n unknown: number;\n /** Live nodes, in insertion order, with observers as arrays. */\n nodes: Array<Omit<NodeRecord, \"observers\"> & { observers: number[] }>;\n /** Disposed nodes, oldest first, with observers as arrays. */\n graveyard: Array<Omit<NodeRecord, \"observers\"> & { observers: number[] }>;\n /** Run records, oldest first. */\n runs: RunRecord[];\n /** Write records, oldest first. */\n writes: WriteRecord[];\n /** Flush records, oldest first. */\n flushes: FlushRecord[];\n /** Error records, oldest first. */\n errors: ErrorRecord[];\n /** Hydration records, oldest first. */\n hydration: HydrationRecord[];\n}\n\nfunction blank(id: number, type: NodeType, label: string, seq: number, t: number): NodeRecord {\n return {\n id,\n type,\n label,\n name: undefined,\n parent: NO_ID,\n alive: true,\n createdSeq: seq,\n createdAt: t,\n disposedSeq: 0,\n render: false,\n isComponent: false,\n mountedAt: 0,\n sources: [],\n observers: null,\n runs: 0,\n totalDuration: 0,\n lastDuration: 0,\n lastStart: 0,\n lastRunSeq: 0,\n lastChangeSeq: 0,\n preview: \"\",\n writes: 0,\n lastWriteSeq: 0,\n lastWriter: NO_ID,\n state: \"\",\n };\n}\n\nfunction sameIds(a: readonly number[], b: readonly number[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i += 1) if (a[i] !== b[i]) return false;\n return true;\n}\n\n/**\n * The inspector's incremental model of the reactive graph: a pure fold over {@link WireEvent}s. The\n * same events always produce the same state, on the page or in a remote panel. `apply` is O(1) for\n * most events and O(sources) for a run; memory is bounded by {@link ModelLimits}.\n *\n * A development tool: its events come from the dev hook, which exists only in development builds.\n *\n * @example\n * import { InspectorModel, snapshotOf } from \"@shadow-js/devtools\";\n * import type { WireEvent } from \"@shadow-js/devtools\";\n *\n * const events: WireEvent[] = [];\n * const model = new InspectorModel();\n * for (const event of events) model.apply(event);\n * snapshotOf(model).counts.nodes;\n * @since 1.0.0\n */\nexport class InspectorModel {\n /** The capacities in effect: the defaults with the constructor's overrides applied. */\n readonly limits: Readonly<ModelLimits>;\n /** Live nodes by id. */\n readonly nodes: Map<number, NodeRecord> = new Map();\n /**\n * Recently disposed nodes, oldest first, kept so that history can still be named. Holds at most\n * `limits.graveyard` nodes.\n */\n readonly graveyard: Map<number, NodeRecord> = new Map();\n /** Run records, the newest kept (capacity `limits.runs`). */\n readonly runs: RingBuffer<RunRecord>;\n /** Write records, the newest kept (capacity `limits.writes`). */\n readonly writes: RingBuffer<WriteRecord>;\n /** Flush records, the newest kept (capacity `limits.flushes`). */\n readonly flushes: RingBuffer<FlushRecord>;\n /** Error records, the newest kept (capacity `limits.errors`). */\n readonly errors: RingBuffer<ErrorRecord>;\n /** Hydration records, the newest kept (capacity `limits.errors`). */\n readonly hydration: RingBuffer<HydrationRecord>;\n /** Sequence number of the last applied event. */\n lastSeq = 0;\n /** Nodes ignored because `maxNodes` was reached. */\n overflow = 0;\n /** Events that referred to a node the model does not know. */\n unknown = 0;\n /**\n * Changes whenever a component is mounted, unmounted or disposed. Cached component lookups from an\n * older epoch are recomputed.\n */\n componentEpoch = 0;\n /** Entries in the component lookup cache. Never more than `limits.maxNodes` after a lookup. */\n get componentCacheSize(): number {\n return this.componentCache.size;\n }\n\n /**\n * Called once for every node that leaves the live set, after it moved to the graveyard: when it is\n * disposed, or when its owner is disposed (cascade). The agent uses it to drop its own references.\n */\n onBury: ((record: NodeRecord) => void) | undefined;\n private openFlush: FlushRecord | null = null;\n private flushRuns = 0;\n /** Signals, stores and resources by owner: they have no lifecycle event of their own. */\n private readonly leaves: Map<number, number[]> = new Map();\n private readonly componentCache: Map<number, { epoch: number; component: number }> = new Map();\n\n /**\n * @param limits Overrides for the buffer capacities. Values left out take the defaults in\n * {@link DEFAULT_LIMITS}.\n */\n constructor(limits: Partial<ModelLimits> = {}) {\n this.limits = { ...DEFAULT_LIMITS, ...limits };\n this.runs = new RingBuffer(this.limits.runs);\n this.writes = new RingBuffer(this.limits.writes);\n this.flushes = new RingBuffer(this.limits.flushes);\n this.errors = new RingBuffer(this.limits.errors);\n this.hydration = new RingBuffer(this.limits.errors);\n }\n\n /**\n * The node with this id: alive, or recently disposed (in the graveyard).\n *\n * @param id The node id.\n * @returns The record, or `undefined` when the model does not know the id.\n */\n node(id: number): NodeRecord | undefined {\n return this.nodes.get(id) ?? this.graveyard.get(id);\n }\n\n private create(\n event: WireEvent & { id: number },\n type: NodeType,\n label: string,\n ): NodeRecord | null {\n if (this.nodes.size >= this.limits.maxNodes) {\n this.overflow += 1;\n return null;\n }\n const record = blank(event.id, type, label, event.seq, event.t);\n this.nodes.set(event.id, record);\n return record;\n }\n\n /** Remembers that a leaf node (signal, store, resource) belongs to `owner`. */\n private addLeaf(owner: number, id: number): void {\n if (owner === NO_ID) return;\n let list = this.leaves.get(owner);\n if (list === undefined) {\n list = [];\n this.leaves.set(owner, list);\n }\n list.push(id);\n // Leaves that died on their own (garbage collected) would pile up on a long-lived owner.\n if (list.length > 1024 && list.length % 1024 === 0) {\n const alive = list.filter((leaf) => this.nodes.has(leaf));\n list.length = 0;\n list.push(...alive);\n }\n }\n\n private bury(record: NodeRecord, seq: number): void {\n // Iterative: the cascade follows owner -> leaves only, but disposal order is user controlled.\n const stack: NodeRecord[] = [record];\n while (stack.length > 0) {\n const next = stack.pop();\n if (next === undefined) break;\n if (!this.nodes.has(next.id)) continue;\n next.alive = false;\n next.disposedSeq = seq;\n this.nodes.delete(next.id);\n if (next.sources.length > 0) this.setSources(next, []);\n if (next.observers !== null) {\n for (const observer of next.observers) {\n const reader = this.nodes.get(observer);\n if (reader !== undefined) reader.sources = reader.sources.filter((id) => id !== next.id);\n }\n }\n next.observers = null;\n this.graveyard.set(next.id, next);\n this.trimGraveyard();\n this.componentCache.delete(next.id);\n const owned = this.leaves.get(next.id);\n if (owned !== undefined) {\n this.leaves.delete(next.id);\n for (const leafId of owned) {\n const leaf = this.nodes.get(leafId);\n if (leaf !== undefined) stack.push(leaf);\n }\n }\n this.onBury?.(next);\n }\n }\n\n /** Drops the oldest disposed nodes until the graveyard holds at most `limits.graveyard`. */\n private trimGraveyard(): void {\n // A loop, not one deletion: a state imported from a larger model starts above the limit.\n while (this.graveyard.size > this.limits.graveyard) {\n const oldest = this.graveyard.keys().next();\n if (oldest.done === true) return;\n this.graveyard.delete(oldest.value);\n }\n }\n\n private setSources(record: NodeRecord, next: readonly number[]): void {\n const previous = record.sources;\n if (sameIds(previous, next)) return;\n const keep = new Set(next);\n for (const id of previous) {\n if (keep.has(id)) continue;\n this.nodes.get(id)?.observers?.delete(record.id);\n }\n for (const id of next) {\n const source = this.nodes.get(id);\n if (source === undefined) continue;\n if (source.observers === null) source.observers = new Set();\n source.observers.add(record.id);\n }\n record.sources = next.slice();\n }\n\n /**\n * Applies one event. Events must arrive in `seq` order: an event whose `seq` is not greater than\n * {@link InspectorModel.lastSeq} is ignored, so a remote panel can receive events it already holds.\n * An event about a node the model does not know is counted in `unknown`. A node created beyond\n * `maxNodes` is counted in `overflow` and ignored.\n *\n * @param event The event, with the sequence number and time the agent gave it.\n */\n apply(event: WireEvent): void {\n if (event.seq <= this.lastSeq) return;\n this.lastSeq = event.seq;\n switch (event.k) {\n case \"owner+\": {\n const record = this.create(event, \"owner\", event.label);\n if (record === null) return;\n record.parent = event.parent;\n record.name = event.name;\n return;\n }\n case \"owner-\":\n case \"comp-\": {\n const record = this.nodes.get(event.id);\n if (record === undefined) {\n this.unknown += 1;\n return;\n }\n if (record.isComponent) this.componentEpoch += 1;\n this.bury(record, event.seq);\n return;\n }\n case \"signal+\": {\n const record = this.create(event, \"signal\", \"signal\");\n if (record === null) return;\n record.parent = event.owner;\n record.name = event.name;\n record.preview = event.preview;\n this.addLeaf(event.owner, event.id);\n return;\n }\n case \"write\": {\n const record = this.nodes.get(event.id);\n if (record === undefined) {\n this.unknown += 1;\n return;\n }\n record.writes += 1;\n record.preview = event.preview;\n record.lastWriteSeq = event.seq;\n record.lastChangeSeq = event.seq;\n record.lastWriter = event.writer;\n this.writes.push({\n seq: event.seq,\n t: event.t,\n id: event.id,\n preview: event.preview,\n writer: event.writer,\n observers: event.observers,\n });\n return;\n }\n case \"comp+\": {\n const record = this.create(event, event.kind, event.kind);\n if (record === null) return;\n record.parent = event.owner;\n record.name = event.name;\n record.render = event.render;\n return;\n }\n case \"run\": {\n const record = this.nodes.get(event.id);\n if (record === undefined) {\n this.unknown += 1;\n return;\n }\n const causes = this.causesOf(record);\n record.runs += 1;\n record.totalDuration += event.duration;\n record.lastDuration = event.duration;\n record.lastStart = event.start;\n record.lastRunSeq = event.seq;\n if (event.changed) record.lastChangeSeq = event.seq;\n this.setSources(record, event.sources);\n this.runs.push({\n seq: event.seq,\n t: event.t,\n id: event.id,\n start: event.start,\n duration: event.duration,\n changed: event.changed,\n flush: this.openFlush === null ? 0 : this.openFlush.seq,\n causes,\n });\n if (this.openFlush !== null) this.flushRuns += 1;\n return;\n }\n case \"mount\": {\n const record = this.nodes.get(event.id);\n if (record === undefined) {\n this.unknown += 1;\n return;\n }\n record.isComponent = true;\n record.label = \"component\";\n record.name = event.name;\n record.mountedAt = event.t;\n this.componentEpoch += 1;\n return;\n }\n case \"unmount\": {\n const record = this.nodes.get(event.id);\n if (record === undefined) {\n this.unknown += 1;\n return;\n }\n // The owner's disposal follows; keep the flag so the component stays attributable.\n this.componentEpoch += 1;\n return;\n }\n case \"resource\": {\n let record = this.nodes.get(event.id);\n if (record === undefined) {\n record = this.create(event, \"resource\", \"resource\") ?? undefined;\n if (record === undefined) return;\n record.parent = event.owner;\n this.addLeaf(event.owner, event.id);\n }\n record.state = event.state;\n if (event.name !== undefined) record.name = event.name;\n record.lastChangeSeq = event.seq;\n return;\n }\n case \"store+\": {\n const record = this.create(event, \"store\", \"store\");\n if (record === null) return;\n record.parent = event.owner;\n record.name = event.name;\n this.addLeaf(event.owner, event.id);\n return;\n }\n case \"error\":\n this.errors.push({\n seq: event.seq,\n t: event.t,\n id: event.id,\n message: event.message,\n code: event.code,\n });\n return;\n case \"hydration\":\n this.hydration.push({\n seq: event.seq,\n t: event.t,\n message: event.message,\n expected: event.expected,\n actual: event.actual,\n });\n return;\n case \"flush+\": {\n const record: FlushRecord = {\n seq: event.seq,\n startSeq: event.seq,\n start: event.t,\n end: event.t,\n render: event.render,\n user: event.user,\n runs: 0,\n open: true,\n };\n this.flushes.push(record);\n this.openFlush = record;\n this.flushRuns = 0;\n return;\n }\n case \"flush-\": {\n const open = this.openFlush;\n if (open === null) return;\n open.end = event.t;\n open.runs = this.flushRuns;\n open.open = false;\n this.openFlush = null;\n return;\n }\n }\n }\n\n /** Why a computation is about to run again: sources that changed since its last run. */\n private causesOf(record: NodeRecord): Cause[] {\n if (record.runs === 0) return [{ kind: \"initial\", id: NO_ID, seq: record.createdSeq }];\n const causes: Cause[] = [];\n for (const id of record.sources) {\n const source = this.nodes.get(id);\n if (source === undefined || source.lastChangeSeq <= record.lastRunSeq) continue;\n causes.push({\n kind: source.type === \"signal\" ? \"write\" : \"run\",\n id,\n seq: source.lastChangeSeq,\n });\n }\n if (causes.length === 0) causes.push({ kind: \"unknown\", id: NO_ID, seq: record.lastRunSeq });\n causes.sort((a, b) => a.seq - b.seq);\n return causes;\n }\n\n /**\n * The nearest component that owns `id`: the node itself when it is a component, otherwise the first\n * component above it in the owner chain (the graveyard included). The answer is cached until the next\n * component mount, unmount or disposal.\n *\n * @param id The node id.\n * @returns The component's id, or {@link NO_ID} when no component owns the node.\n */\n componentOf(id: number): number {\n const cached = this.componentCache.get(id);\n if (cached !== undefined && cached.epoch === this.componentEpoch) return cached.component;\n // Walk up to the first component; every node on the way shares the answer, so cache them all.\n const path: number[] = [];\n let component: number = NO_ID;\n let currentId = id;\n const limit = this.nodes.size + this.graveyard.size + 1;\n for (let steps = 0; steps < limit; steps += 1) {\n const hit = this.componentCache.get(currentId);\n if (hit !== undefined && hit.epoch === this.componentEpoch) {\n component = hit.component;\n break;\n }\n const current = this.node(currentId);\n if (current === undefined) break;\n path.push(currentId);\n if (current.isComponent) {\n component = current.id;\n break;\n }\n if (current.parent === NO_ID) break;\n currentId = current.parent;\n }\n const entry = { epoch: this.componentEpoch, component };\n for (const visited of path) this.componentCache.set(visited, entry);\n if (this.componentCache.size > this.limits.maxNodes) this.componentCache.clear();\n return component;\n }\n\n /**\n * A serializable copy of the whole state, safe to send as JSON. {@link InspectorModel.importState}\n * rebuilds an equal model from it.\n *\n * @returns The state.\n */\n exportState(): ModelState {\n const plain = (record: NodeRecord): ModelState[\"nodes\"][number] => ({\n ...record,\n sources: record.sources.slice(),\n observers: record.observers === null ? [] : [...record.observers],\n });\n return {\n lastSeq: this.lastSeq,\n overflow: this.overflow,\n unknown: this.unknown,\n nodes: [...this.nodes.values()].map(plain),\n graveyard: [...this.graveyard.values()].map(plain),\n runs: this.runs.toArray(),\n writes: this.writes.toArray(),\n flushes: this.flushes.toArray().map((flush) => ({ ...flush })),\n errors: this.errors.toArray(),\n hydration: this.hydration.toArray(),\n };\n }\n\n /**\n * Replaces the whole state with `state`. A remote panel does this with the agent's state, and the\n * agent does it with an empty state to implement `clear`. The capacities of this model do not change:\n * when `state` holds more records than a buffer can keep, the newest are kept.\n *\n * @param state The state to load.\n */\n importState(state: ModelState): void {\n this.nodes.clear();\n this.graveyard.clear();\n this.runs.clear();\n this.writes.clear();\n this.flushes.clear();\n this.errors.clear();\n this.hydration.clear();\n this.componentCache.clear();\n this.openFlush = null;\n this.flushRuns = 0;\n const revive = (plain: ModelState[\"nodes\"][number]): NodeRecord => ({\n ...plain,\n sources: plain.sources.slice(),\n observers: plain.observers.length === 0 ? null : new Set(plain.observers),\n });\n this.leaves.clear();\n for (const plain of state.nodes) {\n const record = revive(plain);\n this.nodes.set(plain.id, record);\n if (record.type === \"signal\" || record.type === \"store\" || record.type === \"resource\") {\n this.addLeaf(record.parent, record.id);\n }\n }\n for (const plain of state.graveyard) this.graveyard.set(plain.id, revive(plain));\n this.trimGraveyard();\n for (const run of state.runs) this.runs.push(run);\n for (const write of state.writes) this.writes.push(write);\n for (const flush of state.flushes) {\n const copy = { ...flush };\n this.flushes.push(copy);\n if (copy.open) this.openFlush = copy;\n }\n for (const error of state.errors) this.errors.push(error);\n for (const mismatch of state.hydration) this.hydration.push(mismatch);\n this.lastSeq = state.lastSeq;\n this.overflow = state.overflow;\n this.unknown = state.unknown;\n this.componentEpoch += 1;\n }\n}\n\n/**\n * Builds a model from a finished event stream. A pure function: the same input always gives the same\n * model. A development tool, like {@link InspectorModel}.\n *\n * @param events Events in `seq` order.\n * @param limits Buffer capacities. Values left out take the defaults in {@link DEFAULT_LIMITS}.\n * @returns The folded model.\n * @example\n * import { foldEvents } from \"@shadow-js/devtools\";\n * import type { WireEvent } from \"@shadow-js/devtools\";\n *\n * const events: WireEvent[] = [];\n * const model = foldEvents(events, { runs: 1000 });\n * @since 1.0.0\n */\nexport function foldEvents(\n events: Iterable<WireEvent>,\n limits?: Partial<ModelLimits>,\n): InspectorModel {\n const model = new InspectorModel(limits);\n for (const event of events) model.apply(event);\n return model;\n}\n"],"mappings":"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"}