@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.
- package/CHANGELOG.md +16 -0
- package/dist/dev/agent.js +9 -5
- package/dist/dev/agent.js.map +1 -1
- package/dist/dev/backend.d.ts +6 -0
- package/dist/dev/backend.js.map +1 -1
- package/dist/dev/hook.js +6 -5
- package/dist/dev/hook.js.map +1 -1
- package/dist/dev/internal/describe-element.js +109 -0
- package/dist/dev/internal/describe-element.js.map +1 -0
- package/dist/dev/internal/wire-guard.js +143 -0
- package/dist/dev/internal/wire-guard.js.map +1 -0
- package/dist/dev/model.d.ts +2 -0
- package/dist/dev/model.js +10 -4
- package/dist/dev/model.js.map +1 -1
- package/dist/dev/transport.d.ts +62 -2
- package/dist/dev/transport.js +294 -20
- package/dist/dev/transport.js.map +1 -1
- package/dist/prod/agent.js +1 -1
- package/dist/prod/agent.js.map +1 -1
- package/dist/prod/backend.js.map +1 -1
- package/dist/prod/hook.js.map +1 -1
- package/dist/prod/internal/describe-element.js +2 -0
- package/dist/prod/internal/describe-element.js.map +1 -0
- package/dist/prod/internal/wire-guard.js +2 -0
- package/dist/prod/internal/wire-guard.js.map +1 -0
- package/dist/prod/model.js +1 -1
- package/dist/prod/model.js.map +1 -1
- package/dist/prod/transport.js +1 -1
- package/dist/prod/transport.js.map +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire-guard.js","names":[],"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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"}
|
package/dist/dev/model.d.ts
CHANGED
|
@@ -361,6 +361,8 @@ export declare class InspectorModel {
|
|
|
361
361
|
/** Remembers that a leaf node (signal, store, resource) belongs to `owner`. */
|
|
362
362
|
private addLeaf;
|
|
363
363
|
private bury;
|
|
364
|
+
/** Drops the oldest disposed nodes until the graveyard holds at most `limits.graveyard`. */
|
|
365
|
+
private trimGraveyard;
|
|
364
366
|
private setSources;
|
|
365
367
|
/**
|
|
366
368
|
* Applies one event. Events must arrive in `seq` order: an event whose `seq` is not greater than
|
package/dist/dev/model.js
CHANGED
|
@@ -186,10 +186,7 @@ var InspectorModel = class {
|
|
|
186
186
|
}
|
|
187
187
|
next.observers = null;
|
|
188
188
|
this.graveyard.set(next.id, next);
|
|
189
|
-
|
|
190
|
-
const oldest = this.graveyard.keys().next();
|
|
191
|
-
if (oldest.done !== true) this.graveyard.delete(oldest.value);
|
|
192
|
-
}
|
|
189
|
+
this.trimGraveyard();
|
|
193
190
|
this.componentCache.delete(next.id);
|
|
194
191
|
const owned = this.leaves.get(next.id);
|
|
195
192
|
if (owned !== void 0) {
|
|
@@ -202,6 +199,14 @@ var InspectorModel = class {
|
|
|
202
199
|
this.onBury?.(next);
|
|
203
200
|
}
|
|
204
201
|
}
|
|
202
|
+
/** Drops the oldest disposed nodes until the graveyard holds at most `limits.graveyard`. */
|
|
203
|
+
trimGraveyard() {
|
|
204
|
+
while (this.graveyard.size > this.limits.graveyard) {
|
|
205
|
+
const oldest = this.graveyard.keys().next();
|
|
206
|
+
if (oldest.done === true) return;
|
|
207
|
+
this.graveyard.delete(oldest.value);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
205
210
|
setSources(record, next) {
|
|
206
211
|
const previous = record.sources;
|
|
207
212
|
if (sameIds(previous, next)) return;
|
|
@@ -518,6 +523,7 @@ var InspectorModel = class {
|
|
|
518
523
|
if (record.type === "signal" || record.type === "store" || record.type === "resource") this.addLeaf(record.parent, record.id);
|
|
519
524
|
}
|
|
520
525
|
for (const plain of state.graveyard) this.graveyard.set(plain.id, revive(plain));
|
|
526
|
+
this.trimGraveyard();
|
|
521
527
|
for (const run of state.runs) this.runs.push(run);
|
|
522
528
|
for (const write of state.writes) this.writes.push(write);
|
|
523
529
|
for (const flush of state.flushes) {
|
package/dist/dev/model.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.js","names":[],"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":[],"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":";;;;;;;;;;;;;;AAuQA,MAAa,iBAAwC;;CAEnD,MAAM;;CAEN,QAAQ;;CAER,SAAS;;CAET,QAAQ;;CAER,WAAW;;CAEX,UAAU;AACZ;AA+BA,SAAS,MAAM,IAAY,MAAgB,OAAe,KAAa,GAAuB;CAC5F,OAAO;EACL;EACA;EACA;EACA,MAAM,KAAA;EACN,QAAA;EACA,OAAO;EACP,YAAY;EACZ,WAAW;EACX,aAAa;EACb,QAAQ;EACR,aAAa;EACb,WAAW;EACX,SAAS,CAAC;EACV,WAAW;EACX,MAAM;EACN,eAAe;EACf,cAAc;EACd,WAAW;EACX,YAAY;EACZ,eAAe;EACf,SAAS;EACT,QAAQ;EACR,cAAc;EACd,YAAA;EACA,OAAO;CACT;AACF;AAEA,SAAS,QAAQ,GAAsB,GAA+B;CACpE,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;CAClC,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,IAAI,OAAO;CAChE,OAAO;AACT;;;;;;;;;;;;;;;;;;AAmBA,IAAa,iBAAb,MAA4B;;CAE1B;;CAEA,wBAA0C,IAAI,IAAI;;;;;CAKlD,4BAA8C,IAAI,IAAI;;CAEtD;;CAEA;;CAEA;;CAEA;;CAEA;;CAEA,UAAU;;CAEV,WAAW;;CAEX,UAAU;;;;;CAKV,iBAAiB;;CAEjB,IAAI,qBAA6B;EAC/B,OAAO,KAAK,eAAe;CAC7B;;;;;CAMA;CACA,YAAwC;CACxC,YAAoB;;CAEpB,yBAAiD,IAAI,IAAI;CACzD,iCAAqF,IAAI,IAAI;;;;;CAM7F,YAAY,SAA+B,CAAC,GAAG;EAC7C,KAAK,SAAS;GAAE,GAAG;GAAgB,GAAG;EAAO;EAC7C,KAAK,OAAO,IAAI,WAAW,KAAK,OAAO,IAAI;EAC3C,KAAK,SAAS,IAAI,WAAW,KAAK,OAAO,MAAM;EAC/C,KAAK,UAAU,IAAI,WAAW,KAAK,OAAO,OAAO;EACjD,KAAK,SAAS,IAAI,WAAW,KAAK,OAAO,MAAM;EAC/C,KAAK,YAAY,IAAI,WAAW,KAAK,OAAO,MAAM;CACpD;;;;;;;CAQA,KAAK,IAAoC;EACvC,OAAO,KAAK,MAAM,IAAI,EAAE,KAAK,KAAK,UAAU,IAAI,EAAE;CACpD;CAEA,OACE,OACA,MACA,OACmB;EACnB,IAAI,KAAK,MAAM,QAAQ,KAAK,OAAO,UAAU;GAC3C,KAAK,YAAY;GACjB,OAAO;EACT;EACA,MAAM,SAAS,MAAM,MAAM,IAAI,MAAM,OAAO,MAAM,KAAK,MAAM,CAAC;EAC9D,KAAK,MAAM,IAAI,MAAM,IAAI,MAAM;EAC/B,OAAO;CACT;;CAGA,QAAgB,OAAe,IAAkB;EAC/C,IAAI,UAAA,GAAiB;EACrB,IAAI,OAAO,KAAK,OAAO,IAAI,KAAK;EAChC,IAAI,SAAS,KAAA,GAAW;GACtB,OAAO,CAAC;GACR,KAAK,OAAO,IAAI,OAAO,IAAI;EAC7B;EACA,KAAK,KAAK,EAAE;EAEZ,IAAI,KAAK,SAAS,QAAQ,KAAK,SAAS,SAAS,GAAG;GAClD,MAAM,QAAQ,KAAK,QAAQ,SAAS,KAAK,MAAM,IAAI,IAAI,CAAC;GACxD,KAAK,SAAS;GACd,KAAK,KAAK,GAAG,KAAK;EACpB;CACF;CAEA,KAAa,QAAoB,KAAmB;EAElD,MAAM,QAAsB,CAAC,MAAM;EACnC,OAAO,MAAM,SAAS,GAAG;GACvB,MAAM,OAAO,MAAM,IAAI;GACvB,IAAI,SAAS,KAAA,GAAW;GACxB,IAAI,CAAC,KAAK,MAAM,IAAI,KAAK,EAAE,GAAG;GAC9B,KAAK,QAAQ;GACb,KAAK,cAAc;GACnB,KAAK,MAAM,OAAO,KAAK,EAAE;GACzB,IAAI,KAAK,QAAQ,SAAS,GAAG,KAAK,WAAW,MAAM,CAAC,CAAC;GACrD,IAAI,KAAK,cAAc,MACrB,KAAK,MAAM,YAAY,KAAK,WAAW;IACrC,MAAM,SAAS,KAAK,MAAM,IAAI,QAAQ;IACtC,IAAI,WAAW,KAAA,GAAW,OAAO,UAAU,OAAO,QAAQ,QAAQ,OAAO,OAAO,KAAK,EAAE;GACzF;GAEF,KAAK,YAAY;GACjB,KAAK,UAAU,IAAI,KAAK,IAAI,IAAI;GAChC,KAAK,cAAc;GACnB,KAAK,eAAe,OAAO,KAAK,EAAE;GAClC,MAAM,QAAQ,KAAK,OAAO,IAAI,KAAK,EAAE;GACrC,IAAI,UAAU,KAAA,GAAW;IACvB,KAAK,OAAO,OAAO,KAAK,EAAE;IAC1B,KAAK,MAAM,UAAU,OAAO;KAC1B,MAAM,OAAO,KAAK,MAAM,IAAI,MAAM;KAClC,IAAI,SAAS,KAAA,GAAW,MAAM,KAAK,IAAI;IACzC;GACF;GACA,KAAK,SAAS,IAAI;EACpB;CACF;;CAGA,gBAA8B;EAE5B,OAAO,KAAK,UAAU,OAAO,KAAK,OAAO,WAAW;GAClD,MAAM,SAAS,KAAK,UAAU,KAAK,CAAC,CAAC,KAAK;GAC1C,IAAI,OAAO,SAAS,MAAM;GAC1B,KAAK,UAAU,OAAO,OAAO,KAAK;EACpC;CACF;CAEA,WAAmB,QAAoB,MAA+B;EACpE,MAAM,WAAW,OAAO;EACxB,IAAI,QAAQ,UAAU,IAAI,GAAG;EAC7B,MAAM,OAAO,IAAI,IAAI,IAAI;EACzB,KAAK,MAAM,MAAM,UAAU;GACzB,IAAI,KAAK,IAAI,EAAE,GAAG;GAClB,KAAK,MAAM,IAAI,EAAE,CAAC,EAAE,WAAW,OAAO,OAAO,EAAE;EACjD;EACA,KAAK,MAAM,MAAM,MAAM;GACrB,MAAM,SAAS,KAAK,MAAM,IAAI,EAAE;GAChC,IAAI,WAAW,KAAA,GAAW;GAC1B,IAAI,OAAO,cAAc,MAAM,OAAO,4BAAY,IAAI,IAAI;GAC1D,OAAO,UAAU,IAAI,OAAO,EAAE;EAChC;EACA,OAAO,UAAU,KAAK,MAAM;CAC9B;;;;;;;;;CAUA,MAAM,OAAwB;EAC5B,IAAI,MAAM,OAAO,KAAK,SAAS;EAC/B,KAAK,UAAU,MAAM;EACrB,QAAQ,MAAM,GAAd;GACE,KAAK,UAAU;IACb,MAAM,SAAS,KAAK,OAAO,OAAO,SAAS,MAAM,KAAK;IACtD,IAAI,WAAW,MAAM;IACrB,OAAO,SAAS,MAAM;IACtB,OAAO,OAAO,MAAM;IACpB;GACF;GACA,KAAK;GACL,KAAK,SAAS;IACZ,MAAM,SAAS,KAAK,MAAM,IAAI,MAAM,EAAE;IACtC,IAAI,WAAW,KAAA,GAAW;KACxB,KAAK,WAAW;KAChB;IACF;IACA,IAAI,OAAO,aAAa,KAAK,kBAAkB;IAC/C,KAAK,KAAK,QAAQ,MAAM,GAAG;IAC3B;GACF;GACA,KAAK,WAAW;IACd,MAAM,SAAS,KAAK,OAAO,OAAO,UAAU,QAAQ;IACpD,IAAI,WAAW,MAAM;IACrB,OAAO,SAAS,MAAM;IACtB,OAAO,OAAO,MAAM;IACpB,OAAO,UAAU,MAAM;IACvB,KAAK,QAAQ,MAAM,OAAO,MAAM,EAAE;IAClC;GACF;GACA,KAAK,SAAS;IACZ,MAAM,SAAS,KAAK,MAAM,IAAI,MAAM,EAAE;IACtC,IAAI,WAAW,KAAA,GAAW;KACxB,KAAK,WAAW;KAChB;IACF;IACA,OAAO,UAAU;IACjB,OAAO,UAAU,MAAM;IACvB,OAAO,eAAe,MAAM;IAC5B,OAAO,gBAAgB,MAAM;IAC7B,OAAO,aAAa,MAAM;IAC1B,KAAK,OAAO,KAAK;KACf,KAAK,MAAM;KACX,GAAG,MAAM;KACT,IAAI,MAAM;KACV,SAAS,MAAM;KACf,QAAQ,MAAM;KACd,WAAW,MAAM;IACnB,CAAC;IACD;GACF;GACA,KAAK,SAAS;IACZ,MAAM,SAAS,KAAK,OAAO,OAAO,MAAM,MAAM,MAAM,IAAI;IACxD,IAAI,WAAW,MAAM;IACrB,OAAO,SAAS,MAAM;IACtB,OAAO,OAAO,MAAM;IACpB,OAAO,SAAS,MAAM;IACtB;GACF;GACA,KAAK,OAAO;IACV,MAAM,SAAS,KAAK,MAAM,IAAI,MAAM,EAAE;IACtC,IAAI,WAAW,KAAA,GAAW;KACxB,KAAK,WAAW;KAChB;IACF;IACA,MAAM,SAAS,KAAK,SAAS,MAAM;IACnC,OAAO,QAAQ;IACf,OAAO,iBAAiB,MAAM;IAC9B,OAAO,eAAe,MAAM;IAC5B,OAAO,YAAY,MAAM;IACzB,OAAO,aAAa,MAAM;IAC1B,IAAI,MAAM,SAAS,OAAO,gBAAgB,MAAM;IAChD,KAAK,WAAW,QAAQ,MAAM,OAAO;IACrC,KAAK,KAAK,KAAK;KACb,KAAK,MAAM;KACX,GAAG,MAAM;KACT,IAAI,MAAM;KACV,OAAO,MAAM;KACb,UAAU,MAAM;KAChB,SAAS,MAAM;KACf,OAAO,KAAK,cAAc,OAAO,IAAI,KAAK,UAAU;KACpD;IACF,CAAC;IACD,IAAI,KAAK,cAAc,MAAM,KAAK,aAAa;IAC/C;GACF;GACA,KAAK,SAAS;IACZ,MAAM,SAAS,KAAK,MAAM,IAAI,MAAM,EAAE;IACtC,IAAI,WAAW,KAAA,GAAW;KACxB,KAAK,WAAW;KAChB;IACF;IACA,OAAO,cAAc;IACrB,OAAO,QAAQ;IACf,OAAO,OAAO,MAAM;IACpB,OAAO,YAAY,MAAM;IACzB,KAAK,kBAAkB;IACvB;GACF;GACA,KAAK;IAEH,IADe,KAAK,MAAM,IAAI,MAAM,EAC3B,MAAM,KAAA,GAAW;KACxB,KAAK,WAAW;KAChB;IACF;IAEA,KAAK,kBAAkB;IACvB;GAEF,KAAK,YAAY;IACf,IAAI,SAAS,KAAK,MAAM,IAAI,MAAM,EAAE;IACpC,IAAI,WAAW,KAAA,GAAW;KACxB,SAAS,KAAK,OAAO,OAAO,YAAY,UAAU,KAAK,KAAA;KACvD,IAAI,WAAW,KAAA,GAAW;KAC1B,OAAO,SAAS,MAAM;KACtB,KAAK,QAAQ,MAAM,OAAO,MAAM,EAAE;IACpC;IACA,OAAO,QAAQ,MAAM;IACrB,IAAI,MAAM,SAAS,KAAA,GAAW,OAAO,OAAO,MAAM;IAClD,OAAO,gBAAgB,MAAM;IAC7B;GACF;GACA,KAAK,UAAU;IACb,MAAM,SAAS,KAAK,OAAO,OAAO,SAAS,OAAO;IAClD,IAAI,WAAW,MAAM;IACrB,OAAO,SAAS,MAAM;IACtB,OAAO,OAAO,MAAM;IACpB,KAAK,QAAQ,MAAM,OAAO,MAAM,EAAE;IAClC;GACF;GACA,KAAK;IACH,KAAK,OAAO,KAAK;KACf,KAAK,MAAM;KACX,GAAG,MAAM;KACT,IAAI,MAAM;KACV,SAAS,MAAM;KACf,MAAM,MAAM;IACd,CAAC;IACD;GACF,KAAK;IACH,KAAK,UAAU,KAAK;KAClB,KAAK,MAAM;KACX,GAAG,MAAM;KACT,SAAS,MAAM;KACf,UAAU,MAAM;KAChB,QAAQ,MAAM;IAChB,CAAC;IACD;GACF,KAAK,UAAU;IACb,MAAM,SAAsB;KAC1B,KAAK,MAAM;KACX,UAAU,MAAM;KAChB,OAAO,MAAM;KACb,KAAK,MAAM;KACX,QAAQ,MAAM;KACd,MAAM,MAAM;KACZ,MAAM;KACN,MAAM;IACR;IACA,KAAK,QAAQ,KAAK,MAAM;IACxB,KAAK,YAAY;IACjB,KAAK,YAAY;IACjB;GACF;GACA,KAAK,UAAU;IACb,MAAM,OAAO,KAAK;IAClB,IAAI,SAAS,MAAM;IACnB,KAAK,MAAM,MAAM;IACjB,KAAK,OAAO,KAAK;IACjB,KAAK,OAAO;IACZ,KAAK,YAAY;IACjB;GACF;EACF;CACF;;CAGA,SAAiB,QAA6B;EAC5C,IAAI,OAAO,SAAS,GAAG,OAAO,CAAC;GAAE,MAAM;GAAW,IAAA;GAAW,KAAK,OAAO;EAAW,CAAC;EACrF,MAAM,SAAkB,CAAC;EACzB,KAAK,MAAM,MAAM,OAAO,SAAS;GAC/B,MAAM,SAAS,KAAK,MAAM,IAAI,EAAE;GAChC,IAAI,WAAW,KAAA,KAAa,OAAO,iBAAiB,OAAO,YAAY;GACvE,OAAO,KAAK;IACV,MAAM,OAAO,SAAS,WAAW,UAAU;IAC3C;IACA,KAAK,OAAO;GACd,CAAC;EACH;EACA,IAAI,OAAO,WAAW,GAAG,OAAO,KAAK;GAAE,MAAM;GAAW,IAAA;GAAW,KAAK,OAAO;EAAW,CAAC;EAC3F,OAAO,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG;EACnC,OAAO;CACT;;;;;;;;;CAUA,YAAY,IAAoB;EAC9B,MAAM,SAAS,KAAK,eAAe,IAAI,EAAE;EACzC,IAAI,WAAW,KAAA,KAAa,OAAO,UAAU,KAAK,gBAAgB,OAAO,OAAO;EAEhF,MAAM,OAAiB,CAAC;EACxB,IAAI,YAAA;EACJ,IAAI,YAAY;EAChB,MAAM,QAAQ,KAAK,MAAM,OAAO,KAAK,UAAU,OAAO;EACtD,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,SAAS,GAAG;GAC7C,MAAM,MAAM,KAAK,eAAe,IAAI,SAAS;GAC7C,IAAI,QAAQ,KAAA,KAAa,IAAI,UAAU,KAAK,gBAAgB;IAC1D,YAAY,IAAI;IAChB;GACF;GACA,MAAM,UAAU,KAAK,KAAK,SAAS;GACnC,IAAI,YAAY,KAAA,GAAW;GAC3B,KAAK,KAAK,SAAS;GACnB,IAAI,QAAQ,aAAa;IACvB,YAAY,QAAQ;IACpB;GACF;GACA,IAAI,QAAQ,WAAA,GAAkB;GAC9B,YAAY,QAAQ;EACtB;EACA,MAAM,QAAQ;GAAE,OAAO,KAAK;GAAgB;EAAU;EACtD,KAAK,MAAM,WAAW,MAAM,KAAK,eAAe,IAAI,SAAS,KAAK;EAClE,IAAI,KAAK,eAAe,OAAO,KAAK,OAAO,UAAU,KAAK,eAAe,MAAM;EAC/E,OAAO;CACT;;;;;;;CAQA,cAA0B;EACxB,MAAM,SAAS,YAAqD;GAClE,GAAG;GACH,SAAS,OAAO,QAAQ,MAAM;GAC9B,WAAW,OAAO,cAAc,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,SAAS;EAClE;EACA,OAAO;GACL,SAAS,KAAK;GACd,UAAU,KAAK;GACf,SAAS,KAAK;GACd,OAAO,CAAC,GAAG,KAAK,MAAM,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK;GACzC,WAAW,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK;GACjD,MAAM,KAAK,KAAK,QAAQ;GACxB,QAAQ,KAAK,OAAO,QAAQ;GAC5B,SAAS,KAAK,QAAQ,QAAQ,CAAC,CAAC,KAAK,WAAW,EAAE,GAAG,MAAM,EAAE;GAC7D,QAAQ,KAAK,OAAO,QAAQ;GAC5B,WAAW,KAAK,UAAU,QAAQ;EACpC;CACF;;;;;;;;CASA,YAAY,OAAyB;EACnC,KAAK,MAAM,MAAM;EACjB,KAAK,UAAU,MAAM;EACrB,KAAK,KAAK,MAAM;EAChB,KAAK,OAAO,MAAM;EAClB,KAAK,QAAQ,MAAM;EACnB,KAAK,OAAO,MAAM;EAClB,KAAK,UAAU,MAAM;EACrB,KAAK,eAAe,MAAM;EAC1B,KAAK,YAAY;EACjB,KAAK,YAAY;EACjB,MAAM,UAAU,WAAoD;GAClE,GAAG;GACH,SAAS,MAAM,QAAQ,MAAM;GAC7B,WAAW,MAAM,UAAU,WAAW,IAAI,OAAO,IAAI,IAAI,MAAM,SAAS;EAC1E;EACA,KAAK,OAAO,MAAM;EAClB,KAAK,MAAM,SAAS,MAAM,OAAO;GAC/B,MAAM,SAAS,OAAO,KAAK;GAC3B,KAAK,MAAM,IAAI,MAAM,IAAI,MAAM;GAC/B,IAAI,OAAO,SAAS,YAAY,OAAO,SAAS,WAAW,OAAO,SAAS,YACzE,KAAK,QAAQ,OAAO,QAAQ,OAAO,EAAE;EAEzC;EACA,KAAK,MAAM,SAAS,MAAM,WAAW,KAAK,UAAU,IAAI,MAAM,IAAI,OAAO,KAAK,CAAC;EAC/E,KAAK,cAAc;EACnB,KAAK,MAAM,OAAO,MAAM,MAAM,KAAK,KAAK,KAAK,GAAG;EAChD,KAAK,MAAM,SAAS,MAAM,QAAQ,KAAK,OAAO,KAAK,KAAK;EACxD,KAAK,MAAM,SAAS,MAAM,SAAS;GACjC,MAAM,OAAO,EAAE,GAAG,MAAM;GACxB,KAAK,QAAQ,KAAK,IAAI;GACtB,IAAI,KAAK,MAAM,KAAK,YAAY;EAClC;EACA,KAAK,MAAM,SAAS,MAAM,QAAQ,KAAK,OAAO,KAAK,KAAK;EACxD,KAAK,MAAM,YAAY,MAAM,WAAW,KAAK,UAAU,KAAK,QAAQ;EACpE,KAAK,UAAU,MAAM;EACrB,KAAK,WAAW,MAAM;EACtB,KAAK,UAAU,MAAM;EACrB,KAAK,kBAAkB;CACzB;AACF;;;;;;;;;;;;;;;;AAiBA,SAAgB,WACd,QACA,QACgB;CAChB,MAAM,QAAQ,IAAI,eAAe,MAAM;CACvC,KAAK,MAAM,SAAS,QAAQ,MAAM,MAAM,KAAK;CAC7C,OAAO;AACT"}
|
package/dist/dev/transport.d.ts
CHANGED
|
@@ -19,6 +19,17 @@ export declare const CHANNEL = "shadowjs-devtools";
|
|
|
19
19
|
* A message the agent side sends to the panel: the answer to a `hello`, a batch of events, or the
|
|
20
20
|
* answer to a request. All fields are plain data.
|
|
21
21
|
*
|
|
22
|
+
* Every message {@link serveAgent} sends carries `instance`, a random id of that served agent. A reloaded
|
|
23
|
+
* page starts a new agent with a new id, so a panel that sees the id change knows the state it holds is
|
|
24
|
+
* from a page that is gone, even when the new agent's sequence numbers happen to be higher than the old
|
|
25
|
+
* ones. The field is optional so that messages of an older agent still pass {@link isAgentMessage}.
|
|
26
|
+
*
|
|
27
|
+
* An answer whose JSON text is longer than {@link SINGLE_RESPONSE_CHARS} (the complete state of a very
|
|
28
|
+
* busy page) is not one `response` message: the agent sends it as `chunk` messages of at most
|
|
29
|
+
* {@link CHUNK_CHARS} characters each, and {@link connectRemote} joins them. Smaller answers keep the
|
|
30
|
+
* single `response` message, so an older panel keeps working with a newer agent for everything it could
|
|
31
|
+
* receive before.
|
|
32
|
+
*
|
|
22
33
|
* @since 1.0.0
|
|
23
34
|
*/
|
|
24
35
|
export type AgentMessage = {
|
|
@@ -26,12 +37,14 @@ export type AgentMessage = {
|
|
|
26
37
|
v: number;
|
|
27
38
|
from: "agent";
|
|
28
39
|
type: "hello";
|
|
40
|
+
instance?: string;
|
|
29
41
|
} | {
|
|
30
42
|
channel: typeof CHANNEL;
|
|
31
43
|
v: number;
|
|
32
44
|
from: "agent";
|
|
33
45
|
type: "events";
|
|
34
46
|
events: WireEvent[];
|
|
47
|
+
instance?: string;
|
|
35
48
|
} | {
|
|
36
49
|
channel: typeof CHANNEL;
|
|
37
50
|
v: number;
|
|
@@ -39,6 +52,23 @@ export type AgentMessage = {
|
|
|
39
52
|
type: "response";
|
|
40
53
|
id: number;
|
|
41
54
|
response: AgentResponse;
|
|
55
|
+
instance?: string;
|
|
56
|
+
} | {
|
|
57
|
+
channel: typeof CHANNEL;
|
|
58
|
+
v: number;
|
|
59
|
+
from: "agent";
|
|
60
|
+
type: "chunk";
|
|
61
|
+
/** The id of the request the chunked answer belongs to. */
|
|
62
|
+
id: number;
|
|
63
|
+
/** The index of this piece, from 0. */
|
|
64
|
+
part: number;
|
|
65
|
+
/** How many pieces the answer has. */
|
|
66
|
+
parts: number;
|
|
67
|
+
/** The length of the whole JSON text, in characters: the panel checks the pieces add up to it. */
|
|
68
|
+
size: number;
|
|
69
|
+
/** This piece of the JSON text of the answer. */
|
|
70
|
+
data: string;
|
|
71
|
+
instance?: string;
|
|
42
72
|
};
|
|
43
73
|
/**
|
|
44
74
|
* A message the panel side sends to the agent: a `hello`, or a request. The agent answers a request
|
|
@@ -84,8 +114,9 @@ export interface Transport {
|
|
|
84
114
|
}
|
|
85
115
|
/**
|
|
86
116
|
* True for a well-formed message sent by an agent. Anything else on the channel is ignored. The check
|
|
87
|
-
* covers the envelope (`channel`, `from`, `v`, `type`) and the fields each message type needs
|
|
88
|
-
*
|
|
117
|
+
* covers the envelope (`channel`, `from`, `v`, `type`) and the fields each message type needs, including
|
|
118
|
+
* the bounds of a `chunk` (piece number below the count, count up to {@link MAX_CHUNKS}, text up to
|
|
119
|
+
* {@link CHUNK_CHARS}). It does not validate every event inside an `events` message.
|
|
89
120
|
*
|
|
90
121
|
* @param value A received message.
|
|
91
122
|
* @returns True when `value` is an {@link AgentMessage}.
|
|
@@ -296,6 +327,24 @@ export interface ServeOptions extends PageHooks {
|
|
|
296
327
|
* @default 2000
|
|
297
328
|
*/
|
|
298
329
|
batch?: number;
|
|
330
|
+
/**
|
|
331
|
+
* The id this served agent puts on every message it sends (see {@link AgentMessage}). Leave it unset:
|
|
332
|
+
* a random id is made for each call. Tests set it to get a known value.
|
|
333
|
+
*/
|
|
334
|
+
instance?: string;
|
|
335
|
+
/**
|
|
336
|
+
* Longest piece of a chunked answer, in characters. Values above {@link CHUNK_CHARS} are lowered to
|
|
337
|
+
* it, because the panel and the bridge refuse larger pieces.
|
|
338
|
+
*
|
|
339
|
+
* @default CHUNK_CHARS
|
|
340
|
+
*/
|
|
341
|
+
chunkChars?: number;
|
|
342
|
+
/**
|
|
343
|
+
* Longest answer sent as one message, in characters of its JSON text. A longer one is sent as chunks.
|
|
344
|
+
*
|
|
345
|
+
* @default SINGLE_RESPONSE_CHARS
|
|
346
|
+
*/
|
|
347
|
+
singleResponseChars?: number;
|
|
299
348
|
}
|
|
300
349
|
/**
|
|
301
350
|
* Exposes an agent over a transport: forwards its event stream to the panel and answers the panel's
|
|
@@ -326,6 +375,12 @@ export interface RemoteOptions {
|
|
|
326
375
|
* their defaults.
|
|
327
376
|
*/
|
|
328
377
|
limits?: Partial<ModelLimits>;
|
|
378
|
+
/**
|
|
379
|
+
* How long to wait for the next piece of a chunked answer before giving the request up, in milliseconds.
|
|
380
|
+
*
|
|
381
|
+
* @default CHUNK_TIMEOUT_MS
|
|
382
|
+
*/
|
|
383
|
+
chunkTimeoutMs?: number;
|
|
329
384
|
}
|
|
330
385
|
/**
|
|
331
386
|
* Connects a panel to a remote agent: asks for the complete state, then applies the event stream
|
|
@@ -333,6 +388,11 @@ export interface RemoteOptions {
|
|
|
333
388
|
* contains are skipped (sequence numbers), so no event is lost or applied twice. Disposing the backend
|
|
334
389
|
* closes the transport.
|
|
335
390
|
*
|
|
391
|
+
* When the agent cannot give its state (an answer the bridge refused as too large, or a state the panel
|
|
392
|
+
* cannot read), `ready` stays pending and `backend.error()` returns the reason after a change notification,
|
|
393
|
+
* so a panel can show it instead of waiting. A message from another agent instance (a reloaded page)
|
|
394
|
+
* discards the state and asks for a new one.
|
|
395
|
+
*
|
|
336
396
|
* @param transport The channel to the agent.
|
|
337
397
|
* @param options Model limits.
|
|
338
398
|
* @returns A {@link Backend}.
|