@expressive/inspect 0.1.0 → 0.2.0
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/README.md +25 -6
- package/dist/bracket-CWa4p56P.js +98 -0
- package/dist/bracket-CWa4p56P.js.map +1 -0
- package/dist/bridge.d.ts +53 -0
- package/dist/bridge.js +106 -0
- package/dist/bridge.js.map +1 -0
- package/dist/dispatch-Dnd88-J9.js +20 -0
- package/dist/dispatch-Dnd88-J9.js.map +1 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/{serialize-FF81tr-L.d.ts → inspect-CEXzL-mC.d.ts} +62 -21
- package/dist/{inspect-D4NC5Mx5.js → inspect-FXOcmfKP.js} +195 -57
- package/dist/inspect-FXOcmfKP.js.map +1 -0
- package/dist/install.js +4 -2
- package/dist/install.js.map +1 -1
- package/dist/vite/client.d.ts +16 -0
- package/dist/vite/client.js +49 -0
- package/dist/vite/client.js.map +1 -0
- package/dist/vite.d.ts +7 -0
- package/dist/vite.js +136 -0
- package/dist/vite.js.map +1 -0
- package/package.json +21 -6
- package/dist/inspect-D4NC5Mx5.js.map +0 -1
- package/dist/playwright.d.ts +0 -33
- package/dist/playwright.js +0 -53
- package/dist/playwright.js.map +0 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
Query the live model graph of an [Expressive MVC](https://github.com/gabeklein/expressive-mvc) app from a test, a Playwright
|
|
13
|
+
Query the live model graph of an [Expressive MVC](https://github.com/gabeklein/expressive-mvc) app from a test, a browser driver (Playwright, puppeteer, CDP), or the browser console. No UI, and no network beyond an opt-in dev-server relay - a base layer for agents, devtools, and test helpers.
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
npm install --save-dev @expressive/inspect
|
|
@@ -40,22 +40,41 @@ const frames = await composer.act((s) => s.submit('hi')); // run, settle, see
|
|
|
40
40
|
## Across a boundary
|
|
41
41
|
|
|
42
42
|
```ts
|
|
43
|
-
import { inspect } from '@expressive/inspect/
|
|
43
|
+
import { inspect } from '@expressive/inspect/bridge';
|
|
44
44
|
|
|
45
|
-
const api = inspect(page); //
|
|
45
|
+
const api = inspect(page); // anything with evaluate(fn, arg)
|
|
46
46
|
await api.get('Composer.draft');
|
|
47
|
-
const frames = await api.
|
|
47
|
+
const frames = await api.act(() => page.click('#submit'));
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
A Node process under `--inspect`, or a browser with a debug port: `inspect(await devtools())`, `devtools` from the same entry.
|
|
51
|
+
|
|
52
|
+
## From a running dev server
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// vite.config.ts - installs the inspector and relays to open pages
|
|
56
|
+
import inspect from '@expressive/inspect/vite';
|
|
57
|
+
|
|
58
|
+
export default defineConfig({ plugins: [inspect()] });
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
curl localhost:5173/__inspect # connected pages, iframes included
|
|
63
|
+
curl localhost:5173/__inspect/<id> -d '["get", "Composer.draft"]'
|
|
64
|
+
curl localhost:5173/__inspect/<id> -d '["act", ["call", "Composer.submit", "hi"]]' # { value, frames, settled, pending }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Local callers only; browser and proxied (tunnel) requests are refused.
|
|
68
|
+
|
|
50
69
|
Console: `__EXPRESSIVE_INSPECT__.get('Composer.draft')`, `__EXPRESSIVE_INSPECT__.journal.record({ level: 'keys', paths: ['Composer.draft'] })`.
|
|
51
70
|
|
|
52
71
|
## Journal
|
|
53
72
|
|
|
54
|
-
Off by default. `journal.record({ level, types, paths, keys, calls })`, then `frames({ since })`. A frame is one batch of writes plus its flush; work an effect defers carries a `cause` back to the frame that scheduled it. `export()` emits NDJSON for a sidecar.
|
|
73
|
+
Off by default (the Vite relay records `keys` from load). `journal.record({ level, types, paths, keys, calls })`, then `frames({ since })`. A frame is one batch of writes plus its flush; work an effect defers carries a `cause` back to the frame that scheduled it. `export()` emits NDJSON for a sidecar; `summary()` digests it per instance.
|
|
55
74
|
|
|
56
75
|
## Orphans
|
|
57
76
|
|
|
58
|
-
Instances a host never committed - a render thrown away, a StrictMode twin - stay out of `models()` and `tree()`. `orphans()` lists them, `
|
|
77
|
+
Instances a host never committed - a render thrown away, a StrictMode twin - stay out of `models()` and `tree()`. `orphans()` lists them, `health()` counts them - along with loaded mvc copies and caught reports.
|
|
59
78
|
|
|
60
79
|
## Minified builds
|
|
61
80
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
//#region src/settle.ts
|
|
2
|
+
const SETTLE_TIMEOUT = 1e3;
|
|
3
|
+
const tick = () => new Promise((resolve) => setTimeout(resolve, 0));
|
|
4
|
+
/** Wait a macrotask at a time until one passes with no new frame. Resolves `false` if `timeout` passes first. */
|
|
5
|
+
async function settle(seq, wait = tick, timeout = SETTLE_TIMEOUT) {
|
|
6
|
+
const end = Date.now() + timeout;
|
|
7
|
+
for (let last = await seq();;) {
|
|
8
|
+
await wait();
|
|
9
|
+
const next = await seq();
|
|
10
|
+
if (next === last) return true;
|
|
11
|
+
if (Date.now() >= end) return false;
|
|
12
|
+
last = next;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function unsettled(timeout = SETTLE_TIMEOUT) {
|
|
16
|
+
console.warn(`Still active after ${timeout}ms - frames may be incomplete. Narrow the recording ({ record: { types, paths, keys } }) to exclude background work, or pass { timeout }.`);
|
|
17
|
+
}
|
|
18
|
+
/** `until` not met within `timeout` - carries the frames recorded so far and what never arrived. */
|
|
19
|
+
function unreached(timeout, pending, missing, frames, hint = "") {
|
|
20
|
+
const list = pending.map((address) => missing.includes(address) ? `${address} (names no instance)` : address);
|
|
21
|
+
return Object.assign(/* @__PURE__ */ new Error(`Not reached within ${timeout}ms: ${list.join(", ")}.${hint}`), {
|
|
22
|
+
frames,
|
|
23
|
+
pending
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/bracket.ts
|
|
29
|
+
/** Resolve `address.key` to the instance `address` names - label, id, or owner path - so frames match it alone. */
|
|
30
|
+
/** Resolve `address.key` to the instance `address` names - label, id, or owner path - as the `id.key` its frames carry. */
|
|
31
|
+
async function locate(remote, target) {
|
|
32
|
+
const dot = target.lastIndexOf(".");
|
|
33
|
+
const found = await remote.get(target.slice(0, dot));
|
|
34
|
+
if (!found?.$ref) throw new Error(`until ${target}: ${target.slice(0, dot)} names no State.`);
|
|
35
|
+
return `${found.$ref}.${target.slice(dot + 1)}`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Run `step` recording values, wait for `until`, settle, and return what it produced; the prior recording is restored.
|
|
39
|
+
* `local` steps run here, so activity for `until` counts once their synchronous writes flush, not once they resolve.
|
|
40
|
+
*/
|
|
41
|
+
async function bracket(remote, step, wait, { until = [], timeout = SETTLE_TIMEOUT, record } = {}, local) {
|
|
42
|
+
const before = await remote.record();
|
|
43
|
+
const since = await remote.seq();
|
|
44
|
+
const window = {
|
|
45
|
+
...before,
|
|
46
|
+
...record
|
|
47
|
+
};
|
|
48
|
+
const filtered = window.types.length + window.paths.length + window.keys.length > 0;
|
|
49
|
+
const values = typeof until == "string" || Array.isArray(until) ? void 0 : until;
|
|
50
|
+
const addresses = values ? [] : [].concat(until);
|
|
51
|
+
const paths = filtered ? [...window.paths, ...addresses.filter((address) => address.indexOf(".") === address.lastIndexOf("."))] : [];
|
|
52
|
+
await remote.record({
|
|
53
|
+
...record,
|
|
54
|
+
level: "values",
|
|
55
|
+
...filtered && { paths }
|
|
56
|
+
});
|
|
57
|
+
try {
|
|
58
|
+
const running = step();
|
|
59
|
+
let from;
|
|
60
|
+
if (local) {
|
|
61
|
+
await void 0;
|
|
62
|
+
from = await remote.seq();
|
|
63
|
+
}
|
|
64
|
+
const value = await running;
|
|
65
|
+
from ??= await remote.seq();
|
|
66
|
+
const end = Date.now() + timeout;
|
|
67
|
+
const pending = new Set(values ? Object.keys(values) : addresses);
|
|
68
|
+
const targets = /* @__PURE__ */ new Map();
|
|
69
|
+
for (const address of addresses) targets.set(address, await locate(remote, address));
|
|
70
|
+
if (filtered && addresses.some((address) => address.indexOf(".") !== address.lastIndexOf("."))) await remote.record({ paths: [...paths, ...targets.values()] });
|
|
71
|
+
while (pending.size) {
|
|
72
|
+
if (values) {
|
|
73
|
+
for (const address of pending) if (JSON.stringify(await remote.get(address)) === JSON.stringify(values[address])) pending.delete(address);
|
|
74
|
+
} else for (const frame of await remote.frames({ since: from })) for (const event of frame.events) for (const address of pending) if (targets.get(address) === `${event.id}.${event.key}`) pending.delete(address);
|
|
75
|
+
if (!pending.size || Date.now() >= end) break;
|
|
76
|
+
await wait();
|
|
77
|
+
}
|
|
78
|
+
const settled = !pending.size && await settle(remote.seq, wait, Math.max(0, end - Date.now()));
|
|
79
|
+
const missing = [];
|
|
80
|
+
if (values) {
|
|
81
|
+
for (const address of pending) if (await remote.get(address.slice(0, address.lastIndexOf("."))) === void 0) missing.push(address);
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
value,
|
|
85
|
+
frames: await remote.frames({ since }),
|
|
86
|
+
settled,
|
|
87
|
+
pending: [...pending],
|
|
88
|
+
missing,
|
|
89
|
+
timeout
|
|
90
|
+
};
|
|
91
|
+
} finally {
|
|
92
|
+
await remote.record(before);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
//#endregion
|
|
97
|
+
export { unsettled as i, tick as n, unreached as r, bracket as t };
|
|
98
|
+
//# sourceMappingURL=bracket-CWa4p56P.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bracket-CWa4p56P.js","names":[],"sources":["../src/settle.ts","../src/bracket.ts"],"sourcesContent":["import type { Frame, Options } from './journal';\n\nexport const SETTLE_TIMEOUT = 1000;\n\nexport interface Act {\n /**\n * What to wait for before settling: `Type.key` / `id.key` addresses that must each see a frame after the\n * step's synchronous writes, or `{ [address]: value }` pairs that must each hold that value.\n */\n until?: string | string[] | Record<string, unknown>;\n /** Longest to wait, in ms; default 1000. */\n timeout?: number;\n /** Recording filters for this window, instead of the journal's - `{ paths: [], types: [], keys: [] }` records everything. */\n record?: Omit<Options, 'level'>;\n}\n\nexport const tick = () => new Promise((resolve) => setTimeout(resolve, 0));\n\n/** Wait a macrotask at a time until one passes with no new frame. Resolves `false` if `timeout` passes first. */\nexport async function settle(seq: () => number | Promise<number>, wait: () => unknown = tick, timeout = SETTLE_TIMEOUT) {\n const end = Date.now() + timeout;\n\n for (let last = await seq(); ; ) {\n await wait();\n\n const next = await seq();\n\n if (next === last) return true;\n if (Date.now() >= end) return false;\n\n last = next;\n }\n}\n\nexport function unsettled(timeout = SETTLE_TIMEOUT) {\n console.warn(\n `Still active after ${timeout}ms - frames may be incomplete. Narrow the recording ({ record: { types, paths, keys } }) to exclude background work, or pass { timeout }.`\n );\n}\n\n/** `until` not met within `timeout` - carries the frames recorded so far and what never arrived. */\nexport function unreached(timeout: number, pending: string[], missing: string[], frames: Frame[], hint = '') {\n const list = pending.map((address) => (missing.includes(address) ? `${address} (names no instance)` : address));\n return Object.assign(new Error(`Not reached within ${timeout}ms: ${list.join(', ')}.${hint}`), { frames, pending });\n}\n","import type { Frame, Options, Query } from './journal';\nimport { SETTLE_TIMEOUT, settle, type Act } from './settle';\n\nexport interface Remote {\n record(options?: Options): Required<Options> | Promise<Required<Options>>;\n seq(): number | Promise<number>;\n frames(query: Query): Frame[] | Promise<Frame[]>;\n get(address: string): unknown;\n}\n\n/** Resolve `address.key` to the instance `address` names - label, id, or owner path - so frames match it alone. */\n/** Resolve `address.key` to the instance `address` names - label, id, or owner path - as the `id.key` its frames carry. */\nasync function locate(remote: Remote, target: string) {\n const dot = target.lastIndexOf('.');\n const found = (await remote.get(target.slice(0, dot))) as { $ref?: string } | undefined;\n\n if (!found?.$ref) throw new Error(`until ${target}: ${target.slice(0, dot)} names no State.`);\n\n return `${found.$ref}.${target.slice(dot + 1)}`;\n}\n\n/**\n * Run `step` recording values, wait for `until`, settle, and return what it produced; the prior recording is restored.\n * `local` steps run here, so activity for `until` counts once their synchronous writes flush, not once they resolve.\n */\nexport async function bracket(\n remote: Remote,\n step: () => unknown,\n wait: () => unknown,\n { until = [], timeout = SETTLE_TIMEOUT, record }: Act = {},\n local?: boolean\n) {\n const before = await remote.record();\n const since = await remote.seq();\n const window = { ...before, ...record };\n const filtered = window.types.length + window.paths.length + window.keys.length > 0;\n const values = typeof until == 'string' || Array.isArray(until) ? undefined : until;\n const addresses = values ? [] : ([] as string[]).concat(until as string | string[]);\n const paths = filtered ? [...window.paths, ...addresses.filter((address) => address.indexOf('.') === address.lastIndexOf('.'))] : [];\n\n await remote.record({ ...record, level: 'values', ...(filtered && { paths }) });\n\n try {\n const running = step();\n let from: number | undefined;\n\n if (local) {\n await undefined;\n from = await remote.seq();\n }\n\n const value = await running;\n\n from ??= await remote.seq();\n const end = Date.now() + timeout;\n const pending = new Set(values ? Object.keys(values) : addresses);\n const targets = new Map<string, string>();\n\n for (const address of addresses) targets.set(address, await locate(remote, address));\n\n if (filtered && addresses.some((address) => address.indexOf('.') !== address.lastIndexOf('.')))\n await remote.record({ paths: [...paths, ...targets.values()] });\n\n while (pending.size) {\n if (values) {\n for (const address of pending)\n if (JSON.stringify(await remote.get(address)) === JSON.stringify(values[address])) pending.delete(address);\n } else\n for (const frame of await remote.frames({ since: from }))\n for (const event of frame.events)\n for (const address of pending) if (targets.get(address) === `${event.id}.${event.key}`) pending.delete(address);\n\n if (!pending.size || Date.now() >= end) break;\n\n await wait();\n }\n\n const settled = !pending.size && (await settle(remote.seq, wait, Math.max(0, end - Date.now())));\n const missing: string[] = [];\n\n if (values)\n for (const address of pending)\n if ((await remote.get(address.slice(0, address.lastIndexOf('.')))) === undefined) missing.push(address);\n\n return { value, frames: await remote.frames({ since }), settled, pending: [...pending], missing, timeout };\n } finally {\n await remote.record(before);\n }\n}\n"],"mappings":"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"}
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { A as Summary, E as Frame, O as Options, S as Select, k as Query, x as Act } from "./inspect-CEXzL-mC.js";
|
|
2
|
+
import inspect$1 from "./index.js";
|
|
3
|
+
|
|
4
|
+
//#region src/devtools.d.ts
|
|
5
|
+
/** An entry of a CDP endpoint's `/json/list`. */
|
|
6
|
+
interface Target {
|
|
7
|
+
type: string;
|
|
8
|
+
title: string;
|
|
9
|
+
url: string;
|
|
10
|
+
webSocketDebuggerUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Connect to a Chrome DevTools Protocol endpoint - a Node process under `--inspect`, or a browser with a
|
|
14
|
+
* debug port - as an `evaluate` target for `inspect()`. `pick` narrows `/json/list` by predicate, or by a
|
|
15
|
+
* string matched against title and URL; more than one match throws with the list. A `ws://` URL connects directly.
|
|
16
|
+
*/
|
|
17
|
+
declare function devtools(endpoint?: string, pick?: string | ((target: Target) => boolean)): Promise<Evaluates & {
|
|
18
|
+
close(): void;
|
|
19
|
+
}>;
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/bridge.d.ts
|
|
22
|
+
/**
|
|
23
|
+
* Anything that can run a function in the page: Playwright `Page`, `Frame`, or
|
|
24
|
+
* `Locator` (which passes the element first), puppeteer `Page` or `Frame`.
|
|
25
|
+
*/
|
|
26
|
+
interface Evaluates {
|
|
27
|
+
evaluate(fn: (...args: any[]) => unknown, arg?: unknown): Promise<unknown>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Drive the page's inspector from a test. Every method is one `evaluate`;
|
|
31
|
+
* `act` runs a step recording values, settles, and returns the frames it produced.
|
|
32
|
+
*/
|
|
33
|
+
declare function inspect(target: Evaluates): {
|
|
34
|
+
get: (address?: string, select?: Select) => Promise<unknown>;
|
|
35
|
+
set: (address: string, value: unknown) => Promise<void>;
|
|
36
|
+
call: (address: string, ...args: unknown[]) => Promise<unknown>;
|
|
37
|
+
models: () => Promise<ReturnType<typeof inspect$1.models>>;
|
|
38
|
+
tree: () => Promise<ReturnType<typeof inspect$1.tree>>;
|
|
39
|
+
health: () => Promise<ReturnType<typeof inspect$1.health>>;
|
|
40
|
+
journal: {
|
|
41
|
+
record: (options?: Options) => Promise<Required<Options>>;
|
|
42
|
+
seq: () => Promise<number>;
|
|
43
|
+
frames: (query?: Query) => Promise<Frame[]>;
|
|
44
|
+
history: (query: Query) => Promise<ReturnType<typeof inspect$1.journal.history>>;
|
|
45
|
+
export: (query?: Query) => Promise<string>;
|
|
46
|
+
summary: (query?: Query) => Promise<Summary[]>;
|
|
47
|
+
clear: () => Promise<void>;
|
|
48
|
+
};
|
|
49
|
+
act(step: () => unknown, options?: Act): Promise<Frame[]>;
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
export { Evaluates, type Target, devtools, inspect };
|
|
53
|
+
//# sourceMappingURL=bridge.d.ts.map
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { i as unsettled, n as tick, r as unreached, t as bracket } from "./bracket-CWa4p56P.js";
|
|
2
|
+
import { t as dispatch } from "./dispatch-Dnd88-J9.js";
|
|
3
|
+
|
|
4
|
+
//#region src/devtools.ts
|
|
5
|
+
const describe = (targets) => targets.map((target) => `\n ${target.type} ${target.title} ${target.url}`).join("");
|
|
6
|
+
/**
|
|
7
|
+
* Connect to a Chrome DevTools Protocol endpoint - a Node process under `--inspect`, or a browser with a
|
|
8
|
+
* debug port - as an `evaluate` target for `inspect()`. `pick` narrows `/json/list` by predicate, or by a
|
|
9
|
+
* string matched against title and URL; more than one match throws with the list. A `ws://` URL connects directly.
|
|
10
|
+
*/
|
|
11
|
+
async function devtools(endpoint = "http://127.0.0.1:9229", pick) {
|
|
12
|
+
let url = endpoint;
|
|
13
|
+
if (!endpoint.startsWith("ws")) {
|
|
14
|
+
const targets = (await (await fetch(`${endpoint}/json/list`)).json()).filter((target) => target.webSocketDebuggerUrl);
|
|
15
|
+
const match = typeof pick == "string" ? (target) => target.title.includes(pick) || target.url.includes(pick) : pick;
|
|
16
|
+
const found = match ? targets.filter(match) : targets;
|
|
17
|
+
if (found.length !== 1) throw new Error(found.length ? `Several debug targets at ${endpoint} - narrow with pick, e.g. (t) => t.type === 'page' && t.url.includes('…'):${describe(found)}` : `No debug target at ${endpoint}${typeof pick == "string" ? ` matches "${pick}"` : pick ? " matches pick" : ""}.${describe(targets)}`);
|
|
18
|
+
url = found[0].webSocketDebuggerUrl;
|
|
19
|
+
}
|
|
20
|
+
const socket = new WebSocket(url);
|
|
21
|
+
const waiting = /* @__PURE__ */ new Map();
|
|
22
|
+
let id = 0;
|
|
23
|
+
await new Promise((resolve, reject) => {
|
|
24
|
+
socket.onopen = resolve;
|
|
25
|
+
socket.onerror = () => reject(/* @__PURE__ */ new Error(`Could not connect to ${url}.`));
|
|
26
|
+
});
|
|
27
|
+
socket.onmessage = ({ data }) => {
|
|
28
|
+
const reply = JSON.parse(String(data));
|
|
29
|
+
waiting.get(reply.id)?.(reply);
|
|
30
|
+
waiting.delete(reply.id);
|
|
31
|
+
};
|
|
32
|
+
socket.onclose = () => {
|
|
33
|
+
for (const settle of waiting.values()) settle({ error: { message: "Debug connection closed." } });
|
|
34
|
+
waiting.clear();
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
async evaluate(fn, arg) {
|
|
38
|
+
const { error, result } = await new Promise((resolve) => {
|
|
39
|
+
waiting.set(++id, resolve);
|
|
40
|
+
socket.send(JSON.stringify({
|
|
41
|
+
id,
|
|
42
|
+
method: "Runtime.evaluate",
|
|
43
|
+
params: {
|
|
44
|
+
expression: `(${fn})(${JSON.stringify(arg)})`,
|
|
45
|
+
awaitPromise: true,
|
|
46
|
+
returnByValue: true
|
|
47
|
+
}
|
|
48
|
+
}));
|
|
49
|
+
});
|
|
50
|
+
if (error) throw new Error(error.message);
|
|
51
|
+
const { exceptionDetails } = result;
|
|
52
|
+
if (exceptionDetails) throw new Error(exceptionDetails.exception?.description ?? exceptionDetails.text);
|
|
53
|
+
return result.result.value;
|
|
54
|
+
},
|
|
55
|
+
close() {
|
|
56
|
+
socket.close();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/bridge.ts
|
|
63
|
+
const BRIDGE_HINT = " On the bridge, activity counts once the step resolves, so a step that awaits the change never counts for an address - use { [address]: value }.";
|
|
64
|
+
/**
|
|
65
|
+
* Drive the page's inspector from a test. Every method is one `evaluate`;
|
|
66
|
+
* `act` runs a step recording values, settles, and returns the frames it produced.
|
|
67
|
+
*/
|
|
68
|
+
function inspect(target) {
|
|
69
|
+
const remote = (...path) => (...args) => target.evaluate(dispatch, [path, args]);
|
|
70
|
+
const record = remote("journal", "record");
|
|
71
|
+
const seq = remote("journal", "seq");
|
|
72
|
+
const frames = remote("journal", "frames");
|
|
73
|
+
return {
|
|
74
|
+
get: remote("get"),
|
|
75
|
+
set: remote("set"),
|
|
76
|
+
call: remote("call"),
|
|
77
|
+
models: remote("models"),
|
|
78
|
+
tree: remote("tree"),
|
|
79
|
+
health: remote("health"),
|
|
80
|
+
journal: {
|
|
81
|
+
record,
|
|
82
|
+
seq,
|
|
83
|
+
frames,
|
|
84
|
+
history: remote("journal", "history"),
|
|
85
|
+
export: remote("journal", "export"),
|
|
86
|
+
summary: remote("journal", "summary"),
|
|
87
|
+
clear: remote("journal", "clear")
|
|
88
|
+
},
|
|
89
|
+
async act(step, options = {}) {
|
|
90
|
+
const result = await bracket({
|
|
91
|
+
record,
|
|
92
|
+
seq,
|
|
93
|
+
frames,
|
|
94
|
+
get: remote("get")
|
|
95
|
+
}, step, () => target.evaluate(tick), options);
|
|
96
|
+
const addressed = typeof options.until == "string" || Array.isArray(options.until);
|
|
97
|
+
if (result.pending.length) throw unreached(result.timeout, result.pending, result.missing, result.frames, addressed ? BRIDGE_HINT : "");
|
|
98
|
+
if (!result.settled) unsettled(result.timeout);
|
|
99
|
+
return result.frames;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
//#endregion
|
|
105
|
+
export { devtools, inspect };
|
|
106
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","names":[],"sources":["../src/devtools.ts","../src/bridge.ts"],"sourcesContent":["import type { Evaluates } from './bridge';\n\n/** An entry of a CDP endpoint's `/json/list`. */\nexport interface Target {\n type: string;\n title: string;\n url: string;\n webSocketDebuggerUrl?: string;\n}\n\ntype Reply = {\n error?: { message: string };\n result?: { result: { value?: unknown }; exceptionDetails?: { text: string; exception?: { description?: string } } };\n};\n\nconst describe = (targets: Target[]) => targets.map((target) => `\\n ${target.type} ${target.title} ${target.url}`).join('');\n\n/**\n * Connect to a Chrome DevTools Protocol endpoint - a Node process under `--inspect`, or a browser with a\n * debug port - as an `evaluate` target for `inspect()`. `pick` narrows `/json/list` by predicate, or by a\n * string matched against title and URL; more than one match throws with the list. A `ws://` URL connects directly.\n */\nexport async function devtools(\n endpoint = 'http://127.0.0.1:9229',\n pick?: string | ((target: Target) => boolean)\n): Promise<Evaluates & { close(): void }> {\n let url = endpoint;\n\n if (!endpoint.startsWith('ws')) {\n const targets = ((await (await fetch(`${endpoint}/json/list`)).json()) as Target[]).filter((target) => target.webSocketDebuggerUrl);\n const match =\n typeof pick == 'string' ? (target: Target) => target.title.includes(pick) || target.url.includes(pick) : pick;\n const found = match ? targets.filter(match) : targets;\n\n if (found.length !== 1)\n throw new Error(\n found.length\n ? `Several debug targets at ${endpoint} - narrow with pick, e.g. (t) => t.type === 'page' && t.url.includes('…'):${describe(found)}`\n : `No debug target at ${endpoint}${typeof pick == 'string' ? ` matches \"${pick}\"` : pick ? ' matches pick' : ''}.${describe(targets)}`\n );\n\n url = found[0].webSocketDebuggerUrl!;\n }\n\n const socket = new WebSocket(url);\n const waiting = new Map<number, (reply: Reply) => void>();\n let id = 0;\n\n await new Promise((resolve, reject) => {\n socket.onopen = resolve;\n socket.onerror = () => reject(new Error(`Could not connect to ${url}.`));\n });\n\n socket.onmessage = ({ data }) => {\n const reply = JSON.parse(String(data));\n waiting.get(reply.id)?.(reply);\n waiting.delete(reply.id);\n };\n\n socket.onclose = () => {\n for (const settle of waiting.values()) settle({ error: { message: 'Debug connection closed.' } });\n waiting.clear();\n };\n\n return {\n async evaluate(fn, arg) {\n const { error, result } = await new Promise<Reply>((resolve) => {\n waiting.set(++id, resolve);\n socket.send(\n JSON.stringify({\n id,\n method: 'Runtime.evaluate',\n params: { expression: `(${fn})(${JSON.stringify(arg)})`, awaitPromise: true, returnByValue: true }\n })\n );\n });\n\n if (error) throw new Error(error.message);\n\n const { exceptionDetails } = result!;\n\n if (exceptionDetails) throw new Error(exceptionDetails.exception?.description ?? exceptionDetails.text);\n\n return result!.result.value;\n },\n close() {\n socket.close();\n }\n };\n}\n","import type { inspect as Inspect } from './index';\nimport { dispatch, type Call } from './dispatch';\nimport { bracket } from './bracket';\nimport { tick, unreached, unsettled, type Act } from './settle';\nimport type { Frame, Options, Query, Summary } from './journal';\nimport type { Select } from './serialize';\n\nexport { devtools, type Target } from './devtools';\n\nconst BRIDGE_HINT =\n ' On the bridge, activity counts once the step resolves, so a step that awaits the change never counts for an address - use { [address]: value }.';\n\n/**\n * Anything that can run a function in the page: Playwright `Page`, `Frame`, or\n * `Locator` (which passes the element first), puppeteer `Page` or `Frame`.\n */\nexport interface Evaluates {\n evaluate(fn: (...args: any[]) => unknown, arg?: unknown): Promise<unknown>;\n}\n\n/**\n * Drive the page's inspector from a test. Every method is one `evaluate`;\n * `act` runs a step recording values, settles, and returns the frames it produced.\n */\nexport function inspect(target: Evaluates) {\n const remote =\n <R>(...path: string[]) =>\n (...args: unknown[]) =>\n target.evaluate(dispatch, [path, args] as Call) as Promise<R>;\n\n const record = remote<Required<Options>>('journal', 'record');\n const seq = remote<number>('journal', 'seq');\n const frames = remote<Frame[]>('journal', 'frames');\n\n return {\n get: remote<unknown>('get') as (address?: string, select?: Select) => Promise<unknown>,\n set: remote<void>('set') as (address: string, value: unknown) => Promise<void>,\n call: remote<unknown>('call') as (address: string, ...args: unknown[]) => Promise<unknown>,\n models: remote<ReturnType<typeof Inspect.models>>('models') as () => Promise<ReturnType<typeof Inspect.models>>,\n tree: remote<ReturnType<typeof Inspect.tree>>('tree') as () => Promise<ReturnType<typeof Inspect.tree>>,\n health: remote<ReturnType<typeof Inspect.health>>('health') as () => Promise<ReturnType<typeof Inspect.health>>,\n\n journal: {\n record: record as (options?: Options) => Promise<Required<Options>>,\n seq: seq as () => Promise<number>,\n frames: frames as (query?: Query) => Promise<Frame[]>,\n history: remote<ReturnType<typeof Inspect.journal.history>>('journal', 'history') as (\n query: Query\n ) => Promise<ReturnType<typeof Inspect.journal.history>>,\n export: remote<string>('journal', 'export') as (query?: Query) => Promise<string>,\n summary: remote<Summary[]>('journal', 'summary') as (query?: Query) => Promise<Summary[]>,\n clear: remote<void>('journal', 'clear') as () => Promise<void>\n },\n\n async act(step: () => unknown, options: Act = {}): Promise<Frame[]> {\n const result = await bracket({ record, seq, frames, get: remote('get') }, step, () => target.evaluate(tick), options);\n const addressed = typeof options.until == 'string' || Array.isArray(options.until);\n\n if (result.pending.length)\n throw unreached(result.timeout, result.pending, result.missing, result.frames, addressed ? BRIDGE_HINT : '');\n if (!result.settled) unsettled(result.timeout);\n\n return result.frames;\n }\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/dispatch.ts
|
|
2
|
+
/** Serialized by `evaluate` into the page - must stay self-contained. Walks own members of the inspector only. */
|
|
3
|
+
function dispatch(first, second) {
|
|
4
|
+
const call = second ?? first;
|
|
5
|
+
const api = globalThis.__EXPRESSIVE_INSPECT__;
|
|
6
|
+
if (!api) throw new Error("@expressive/inspect is not attached in this page - make '@expressive/inspect/install' the first import of the app entry.");
|
|
7
|
+
let target = api;
|
|
8
|
+
let owner;
|
|
9
|
+
for (const key of call[0]) {
|
|
10
|
+
if (!Object.prototype.hasOwnProperty.call(target, key)) throw new Error(`No inspector method ${call[0].join(".")}.`);
|
|
11
|
+
owner = target;
|
|
12
|
+
target = target[key];
|
|
13
|
+
}
|
|
14
|
+
if (typeof target != "function") throw new Error(`No inspector method ${call[0].join(".")}.`);
|
|
15
|
+
return target.apply(owner, call[1]);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { dispatch as t };
|
|
20
|
+
//# sourceMappingURL=dispatch-Dnd88-J9.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch-Dnd88-J9.js","names":[],"sources":["../src/dispatch.ts"],"sourcesContent":["export type Call = [path: string[], args: unknown[]];\n\n/** Serialized by `evaluate` into the page - must stay self-contained. Walks own members of the inspector only. */\nexport function dispatch(first: unknown, second?: unknown) {\n const call = (second ?? first) as Call;\n const api = (globalThis as { __EXPRESSIVE_INSPECT__?: Record<string, unknown> }).__EXPRESSIVE_INSPECT__;\n\n if (!api)\n throw new Error(\n \"@expressive/inspect is not attached in this page - make '@expressive/inspect/install' the first import of the app entry.\"\n );\n\n let target: unknown = api;\n let owner: unknown;\n\n for (const key of call[0]) {\n if (!Object.prototype.hasOwnProperty.call(target, key))\n throw new Error(`No inspector method ${call[0].join('.')}.`);\n\n owner = target;\n target = (target as Record<string, unknown>)[key];\n }\n\n if (typeof target != 'function') throw new Error(`No inspector method ${call[0].join('.')}.`);\n\n return target.apply(owner, call[1]);\n}\n"],"mappings":";;AAGA,SAAgB,SAAS,OAAgB,QAAkB;CACzD,MAAM,OAAQ,UAAU;CACxB,MAAM,MAAO,WAAoE;AAEjF,KAAI,CAAC,IACH,OAAM,IAAI,MACR,2HACD;CAEH,IAAI,SAAkB;CACtB,IAAI;AAEJ,MAAK,MAAM,OAAO,KAAK,IAAI;AACzB,MAAI,CAAC,OAAO,UAAU,eAAe,KAAK,QAAQ,IAAI,CACpD,OAAM,IAAI,MAAM,uBAAuB,KAAK,GAAG,KAAK,IAAI,CAAC,GAAG;AAE9D,UAAQ;AACR,WAAU,OAAmC;;AAG/C,KAAI,OAAO,UAAU,WAAY,OAAM,IAAI,MAAM,uBAAuB,KAAK,GAAG,KAAK,IAAI,CAAC,GAAG;AAE7F,QAAO,OAAO,MAAM,OAAO,KAAK,GAAG"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { C as Level, E as
|
|
1
|
+
import { A as Summary, C as parsePath, D as Level, E as Frame, O as Options, S as Select, T as Event, _ as tree, a as act, b as resolve, c as detach, d as health, f as instances, g as set, h as roots, i as Node, k as Query, l as find, m as orphans, n as Instance, o as attach, p as models, r as Model, s as call, t as Health, u as get, v as TypeInfo, w as serialize, x as Act, y as label } from "./inspect-CEXzL-mC.js";
|
|
2
2
|
|
|
3
3
|
//#region src/index.d.ts
|
|
4
4
|
declare const journal: {
|
|
5
5
|
record(options?: Options): Required<Options>;
|
|
6
|
+
clear(): void;
|
|
6
7
|
frames(query?: Query): Frame[];
|
|
7
8
|
history(query: Query): Array<{
|
|
8
9
|
seq: number;
|
|
@@ -12,7 +13,7 @@ declare const journal: {
|
|
|
12
13
|
downstream(from: number): Frame[];
|
|
13
14
|
seq(): number;
|
|
14
15
|
export(query?: Query): string;
|
|
15
|
-
|
|
16
|
+
summary(query?: Query): Summary[];
|
|
16
17
|
reset(): void;
|
|
17
18
|
};
|
|
18
19
|
declare const inspect: {
|
|
@@ -22,7 +23,7 @@ declare const inspect: {
|
|
|
22
23
|
instances: typeof instances;
|
|
23
24
|
roots: typeof roots;
|
|
24
25
|
orphans: typeof orphans;
|
|
25
|
-
|
|
26
|
+
health: typeof health;
|
|
26
27
|
models: typeof models;
|
|
27
28
|
tree: typeof tree;
|
|
28
29
|
get: typeof get;
|
|
@@ -33,6 +34,7 @@ declare const inspect: {
|
|
|
33
34
|
resolve: typeof resolve;
|
|
34
35
|
journal: {
|
|
35
36
|
record(options?: Options): Required<Options>;
|
|
37
|
+
clear(): void;
|
|
36
38
|
frames(query?: Query): Frame[];
|
|
37
39
|
history(query: Query): Array<{
|
|
38
40
|
seq: number;
|
|
@@ -42,7 +44,7 @@ declare const inspect: {
|
|
|
42
44
|
downstream(from: number): Frame[];
|
|
43
45
|
seq(): number;
|
|
44
46
|
export(query?: Query): string;
|
|
45
|
-
|
|
47
|
+
summary(query?: Query): Summary[];
|
|
46
48
|
reset(): void;
|
|
47
49
|
};
|
|
48
50
|
};
|
|
@@ -50,5 +52,5 @@ declare global {
|
|
|
50
52
|
var __EXPRESSIVE_INSPECT__: typeof inspect | undefined;
|
|
51
53
|
}
|
|
52
54
|
//#endregion
|
|
53
|
-
export { type Event, type Frame, Instance, type Level, type Model, type Node, type Options, type Query, type TypeInfo, act, attach, call, inspect as default, inspect, detach, find, get, instances, journal, label, models, orphans, parsePath, resolve, roots, serialize, set, tree
|
|
55
|
+
export { type Act, type Event, type Frame, type Health, Instance, type Level, type Model, type Node, type Options, type Query, type Select, type Summary, type TypeInfo, act, attach, call, inspect as default, inspect, detach, find, get, health, instances, journal, label, models, orphans, parsePath, resolve, roots, serialize, set, tree };
|
|
54
56
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as
|
|
1
|
+
import { _ as journal$1, a as clearCaught, b as parsePath, c as get, d as models, f as orphans, g as wrapAll, h as tree, i as call, l as health, m as set, n as act, o as detach, p as roots, r as attach, s as find, t as Instance, u as instances, v as label, x as serialize, y as resolve } from "./inspect-FXOcmfKP.js";
|
|
2
2
|
|
|
3
3
|
//#region src/index.ts
|
|
4
4
|
const journal = {
|
|
@@ -7,6 +7,10 @@ const journal = {
|
|
|
7
7
|
const config = journal$1.record(options);
|
|
8
8
|
if (config.calls && config.level !== "off") wrapAll();
|
|
9
9
|
return config;
|
|
10
|
+
},
|
|
11
|
+
clear() {
|
|
12
|
+
journal$1.clear();
|
|
13
|
+
clearCaught();
|
|
10
14
|
}
|
|
11
15
|
};
|
|
12
16
|
const inspect = {
|
|
@@ -16,7 +20,7 @@ const inspect = {
|
|
|
16
20
|
instances,
|
|
17
21
|
roots,
|
|
18
22
|
orphans,
|
|
19
|
-
|
|
23
|
+
health,
|
|
20
24
|
models,
|
|
21
25
|
tree,
|
|
22
26
|
get,
|
|
@@ -29,5 +33,5 @@ const inspect = {
|
|
|
29
33
|
};
|
|
30
34
|
|
|
31
35
|
//#endregion
|
|
32
|
-
export { Instance, act, attach, call, inspect as default, inspect, detach, find, get, instances, journal, label, models, orphans, parsePath, resolve, roots, serialize, set, tree
|
|
36
|
+
export { Instance, act, attach, call, inspect as default, inspect, detach, find, get, health, instances, journal, label, models, orphans, parsePath, resolve, roots, serialize, set, tree };
|
|
33
37
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["base"],"sources":["../src/index.ts"],"sourcesContent":["import { attach, call, detach, find, get, Instance, instances, models, orphans, roots, set, tree,
|
|
1
|
+
{"version":3,"file":"index.js","names":["base"],"sources":["../src/index.ts"],"sourcesContent":["import { act, attach, call, clearCaught, detach, find, get, health, Instance, instances, models, orphans, roots, set, tree, wrapAll } from './inspect';\nimport { journal as base, type Options } from './journal';\nimport { label, resolve } from './types';\n\nexport type { Health, Model, Node } from './inspect';\nexport type { Event, Frame, Level, Options, Query, Summary } from './journal';\nexport type { Act } from './settle';\nexport type { Select } from './serialize';\nexport type { TypeInfo } from './types';\nexport { Instance, act, attach, call, detach, find, get, health, instances, label, models, orphans, resolve, roots, set, tree };\nexport { parsePath, serialize } from './serialize';\n\nexport const journal = {\n ...base,\n record(options?: Options) {\n const config = base.record(options);\n if (config.calls && config.level !== 'off') wrapAll();\n return config;\n },\n clear() {\n base.clear();\n clearCaught();\n }\n};\n\nexport const inspect = {\n attach,\n detach,\n find,\n instances,\n roots,\n orphans,\n health,\n models,\n tree,\n get,\n set,\n call,\n act,\n label,\n resolve,\n journal\n};\n\ndeclare global {\n var __EXPRESSIVE_INSPECT__: typeof inspect | undefined;\n}\n\nexport default inspect;\n"],"mappings":";;;AAYA,MAAa,UAAU;CACrB,GAAGA;CACH,OAAO,SAAmB;EACxB,MAAM,SAASA,UAAK,OAAO,QAAQ;AACnC,MAAI,OAAO,SAAS,OAAO,UAAU,MAAO,UAAS;AACrD,SAAO;;CAET,QAAQ;AACN,YAAK,OAAO;AACZ,eAAa;;CAEhB;AAED,MAAa,UAAU;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { State } from "@expressive/mvc";
|
|
1
|
+
import { Caught, State } from "@expressive/mvc";
|
|
2
2
|
|
|
3
3
|
//#region src/journal.d.ts
|
|
4
4
|
type Level = 'off' | 'keys' | 'values';
|
|
@@ -17,7 +17,7 @@ interface Event {
|
|
|
17
17
|
id: string;
|
|
18
18
|
type: string;
|
|
19
19
|
key: string;
|
|
20
|
-
kind: 'update' | 'event' | 'call' | 'destroy';
|
|
20
|
+
kind: 'update' | 'event' | 'call' | 'destroy' | 'caught';
|
|
21
21
|
value?: unknown;
|
|
22
22
|
args?: unknown[];
|
|
23
23
|
}
|
|
@@ -28,6 +28,21 @@ interface Frame {
|
|
|
28
28
|
cause?: number;
|
|
29
29
|
events: Event[];
|
|
30
30
|
}
|
|
31
|
+
interface Summary {
|
|
32
|
+
id: string;
|
|
33
|
+
type: string;
|
|
34
|
+
/** Seq of the last frame touching this instance. */
|
|
35
|
+
last: number;
|
|
36
|
+
/** Updates and events per key, with the last recorded value at `values` level. */
|
|
37
|
+
keys: Record<string, {
|
|
38
|
+
count: number;
|
|
39
|
+
value?: unknown;
|
|
40
|
+
}>;
|
|
41
|
+
calls: Record<string, number>;
|
|
42
|
+
/** `caught` reports. */
|
|
43
|
+
caught: number;
|
|
44
|
+
destroyed: boolean;
|
|
45
|
+
}
|
|
31
46
|
interface Query {
|
|
32
47
|
since?: number;
|
|
33
48
|
type?: string;
|
|
@@ -35,8 +50,32 @@ interface Query {
|
|
|
35
50
|
key?: string;
|
|
36
51
|
cause?: number;
|
|
37
52
|
}
|
|
38
|
-
|
|
39
|
-
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/serialize.d.ts
|
|
55
|
+
type Address = {
|
|
56
|
+
target: string;
|
|
57
|
+
path?: string;
|
|
58
|
+
};
|
|
59
|
+
declare function parsePath(raw: string): Address;
|
|
60
|
+
/** `true` takes a value as `serialize` would; an object picks keys and descends, applying to each element of a list. */
|
|
61
|
+
type Select = true | {
|
|
62
|
+
[key: string]: Select;
|
|
63
|
+
};
|
|
64
|
+
/** JSON-safe, size-capped view. Nested States collapse to `$ref`. */
|
|
65
|
+
declare function serialize(value: unknown, depth?: number): unknown;
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/settle.d.ts
|
|
68
|
+
interface Act {
|
|
69
|
+
/**
|
|
70
|
+
* What to wait for before settling: `Type.key` / `id.key` addresses that must each see a frame after the
|
|
71
|
+
* step's synchronous writes, or `{ [address]: value }` pairs that must each hold that value.
|
|
72
|
+
*/
|
|
73
|
+
until?: string | string[] | Record<string, unknown>;
|
|
74
|
+
/** Longest to wait, in ms; default 1000. */
|
|
75
|
+
timeout?: number;
|
|
76
|
+
/** Recording filters for this window, instead of the journal's - `{ paths: [], types: [], keys: [] }` records everything. */
|
|
77
|
+
record?: Omit<Options, 'level'>;
|
|
78
|
+
}
|
|
40
79
|
//#endregion
|
|
41
80
|
//#region src/types.d.ts
|
|
42
81
|
interface TypeInfo {
|
|
@@ -65,6 +104,8 @@ interface Node {
|
|
|
65
104
|
type: string;
|
|
66
105
|
children: Node[];
|
|
67
106
|
}
|
|
107
|
+
declare const CASES: readonly ["Destroyed", "Inactive", "Getter", "Init", "Effect"];
|
|
108
|
+
type Case = (typeof CASES)[number];
|
|
68
109
|
/** Navigable view of one instance - identity, ownership, reads, observation. One per state; outlives it. */
|
|
69
110
|
declare class Instance {
|
|
70
111
|
readonly state: State;
|
|
@@ -83,10 +124,10 @@ declare class Instance {
|
|
|
83
124
|
get children(): Instance[];
|
|
84
125
|
/** First instance in this subtree, depth-first, matching `where` (a label or predicate). */
|
|
85
126
|
find(where: string | ((instance: Instance) => boolean)): Instance | undefined;
|
|
86
|
-
get(path?: string): unknown;
|
|
127
|
+
get(path?: string, select?: Select): unknown;
|
|
87
128
|
model(): Model;
|
|
88
129
|
/** Run `work`, settle, and return the frames it produced. */
|
|
89
|
-
act(work: (state: State) => unknown): Promise<Frame[]>;
|
|
130
|
+
act(work: (state: State) => unknown, options?: Act): Promise<Frame[]>;
|
|
90
131
|
/** Call `fn` on each update, and with `null` on destroy; `keys` narrows updates. Returns unsubscribe. */
|
|
91
132
|
watch(fn: (key: string | null) => void, keys?: string[]): () => void;
|
|
92
133
|
frames(query?: Query): Frame[];
|
|
@@ -98,26 +139,26 @@ declare function find(target: string): Instance | undefined;
|
|
|
98
139
|
declare function instances(): Instance[];
|
|
99
140
|
/** Registered and settled, but never claimed by a host commit, an owner, or root context. */
|
|
100
141
|
declare function orphans(): Instance[];
|
|
101
|
-
|
|
102
|
-
|
|
142
|
+
interface Health {
|
|
143
|
+
/** Live instances no host, owner, or root slot claimed. */
|
|
103
144
|
orphans: number;
|
|
145
|
+
/** Unclaimed instances the collector already reaped. */
|
|
104
146
|
collected: number;
|
|
105
|
-
|
|
147
|
+
/** Loaded copies of `@expressive/mvc` - more than 1 means inspect cannot see every State. */
|
|
148
|
+
copies: number;
|
|
149
|
+
/** `Caught` reports by case, including ones an app handler went on to handle; zeroed by `journal.clear()`. */
|
|
150
|
+
caught: Record<Case, number>;
|
|
151
|
+
}
|
|
152
|
+
/** Counts worth a look before trusting what inspect shows. */
|
|
153
|
+
declare function health(): Health;
|
|
106
154
|
declare function roots(): Instance[];
|
|
107
155
|
declare function models(): Model[];
|
|
108
156
|
declare function tree(): Node[];
|
|
109
|
-
|
|
157
|
+
/** Run `work` recording values; returns the frames it produced once `until` holds and activity goes quiet. */
|
|
158
|
+
declare function act(work: () => unknown, options?: Act): Promise<Frame[]>;
|
|
159
|
+
declare function get(address?: string, select?: Select): unknown;
|
|
110
160
|
declare function set(address: string, value: unknown): void;
|
|
111
161
|
declare function call(address: string, ...args: unknown[]): Promise<unknown>;
|
|
112
162
|
//#endregion
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
target: string;
|
|
116
|
-
path?: string;
|
|
117
|
-
};
|
|
118
|
-
declare function parsePath(raw: string): Address;
|
|
119
|
-
/** JSON-safe, size-capped view. Nested States collapse to `$ref`. */
|
|
120
|
-
declare function serialize(value: unknown, depth?: number): unknown;
|
|
121
|
-
//#endregion
|
|
122
|
-
export { Level as C, act as E, Frame as S, Query as T, warnings as _, Node as a, resolve as b, detach as c, instances as d, models as f, tree as g, set as h, Model as i, find as l, roots as m, serialize as n, attach as o, orphans as p, Instance as r, call as s, parsePath as t, get as u, TypeInfo as v, Options as w, Event as x, label as y };
|
|
123
|
-
//# sourceMappingURL=serialize-FF81tr-L.d.ts.map
|
|
163
|
+
export { Summary as A, parsePath as C, Level as D, Frame as E, Options as O, Select as S, Event as T, tree as _, act as a, resolve as b, detach as c, health as d, instances as f, set as g, roots as h, Node as i, Query as k, find as l, orphans as m, Instance as n, attach as o, models as p, Model as r, call as s, Health as t, get as u, TypeInfo as v, serialize as w, Act as x, label as y };
|
|
164
|
+
//# sourceMappingURL=inspect-CEXzL-mC.d.ts.map
|