@expressive/inspect 0.1.1 → 0.3.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../src/vite/client.ts"],"sourcesContent":["import { dispatch, type Call } from '../dispatch';\nimport { bracket } from '../bracket';\nimport type { Options, Query } from '../journal';\nimport { tick, type Act } from '../settle';\n\nexport interface Hot {\n on(event: string, listener: (data: any) => unknown): void;\n send(event: string, data?: unknown): void;\n}\n\nexport interface Ask {\n rid: number;\n call?: Call;\n}\n\nconst journal = (method: string, ...args: unknown[]) => dispatch([['journal', method], args]) as any;\n\nasync function act(step: unknown, options?: Act) {\n if (!Array.isArray(step) || typeof step[0] != 'string')\n throw new Error('act takes one call: [\"act\", [method, ...args], options?].');\n\n const remote = {\n record: (...options: Options[]) => journal('record', ...options),\n seq: () => journal('seq'),\n frames: (query: Query) => journal('frames', query),\n get: (address: string) => dispatch([['get'], [address]])\n };\n\n const { value, frames, settled, pending, missing } = await bracket(remote, () => dispatch([step[0].split('.'), step.slice(1)]), tick, options, true);\n\n return { value: value ?? null, frames, settled, pending, missing };\n}\n\nconst RELOAD = '@expressive/inspect.reload';\n\n/** Keep a reload's reason past the reload, for the next page's journal. */\nfunction remember(reason: unknown) {\n try {\n sessionStorage.setItem(RELOAD, JSON.stringify(reason));\n } catch {}\n}\n\nfunction recall() {\n try {\n const reason = sessionStorage.getItem(RELOAD);\n\n if (reason === null) return;\n\n sessionStorage.removeItem(RELOAD);\n return JSON.parse(reason);\n } catch {}\n}\n\nexport function connect(hot: Hot) {\n const id = Math.random().toString(36).slice(2, 8);\n\n if (journal('record').level === 'off') journal('record', { level: 'keys' });\n\n const reloaded = recall();\n\n if (reloaded) journal('hot', 'reload', reloaded);\n\n hot.on('vite:beforeUpdate', ({ updates }: { updates: { path: string }[] }) => {\n journal('hot', 'update', updates.map((update) => update.path));\n });\n\n hot.on('vite:beforeFullReload', ({ path, triggeredBy }: { path?: string; triggeredBy?: string } = {}) => {\n remember({ path, triggeredBy });\n });\n\n globalThis.addEventListener?.('expressive:reload', (event) => remember((event as CustomEvent).detail));\n\n hot.on('expressive-inspect:ask', async ({ rid, call }: Ask) => {\n try {\n const value = !call\n ? { id, url: location.href, title: document.title, top: window.self === window.top }\n : call[0].join('.') == 'act'\n ? await act(call[1][0], call[1][1] as Act)\n : await dispatch(call);\n\n hot.send('expressive-inspect:answer', { rid, value: JSON.parse(JSON.stringify(value) ?? 'null') });\n } catch (error) {\n hot.send('expressive-inspect:answer', { rid, error: error instanceof Error ? error.message : String(error) });\n }\n });\n\n hot.send('expressive-inspect:hello', { id });\n}\n"],"mappings":";;;;AAeA,MAAM,WAAW,QAAgB,GAAG,SAAoB,SAAS,CAAC,CAAC,WAAW,OAAO,EAAE,KAAK,CAAC;AAE7F,eAAe,IAAI,MAAe,SAAe;AAC/C,KAAI,CAAC,MAAM,QAAQ,KAAK,IAAI,OAAO,KAAK,MAAM,SAC5C,OAAM,IAAI,MAAM,8DAA4D;CAS9E,MAAM,EAAE,OAAO,QAAQ,SAAS,SAAS,YAAY,MAAM,QAP5C;EACb,SAAS,GAAG,YAAuB,QAAQ,UAAU,GAAG,QAAQ;EAChE,WAAW,QAAQ,MAAM;EACzB,SAAS,UAAiB,QAAQ,UAAU,MAAM;EAClD,MAAM,YAAoB,SAAS,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC;EACzD,QAEgF,SAAS,CAAC,KAAK,GAAG,MAAM,IAAI,EAAE,KAAK,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,SAAS,KAAK;AAEpJ,QAAO;EAAE,OAAO,SAAS;EAAM;EAAQ;EAAS;EAAS;EAAS;;AAGpE,MAAM,SAAS;;AAGf,SAAS,SAAS,QAAiB;AACjC,KAAI;AACF,iBAAe,QAAQ,QAAQ,KAAK,UAAU,OAAO,CAAC;SAChD;;AAGV,SAAS,SAAS;AAChB,KAAI;EACF,MAAM,SAAS,eAAe,QAAQ,OAAO;AAE7C,MAAI,WAAW,KAAM;AAErB,iBAAe,WAAW,OAAO;AACjC,SAAO,KAAK,MAAM,OAAO;SACnB;;AAGV,SAAgB,QAAQ,KAAU;CAChC,MAAM,KAAK,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,EAAE;AAEjD,KAAI,QAAQ,SAAS,CAAC,UAAU,MAAO,SAAQ,UAAU,EAAE,OAAO,QAAQ,CAAC;CAE3E,MAAM,WAAW,QAAQ;AAEzB,KAAI,SAAU,SAAQ,OAAO,UAAU,SAAS;AAEhD,KAAI,GAAG,sBAAsB,EAAE,cAA+C;AAC5E,UAAQ,OAAO,UAAU,QAAQ,KAAK,WAAW,OAAO,KAAK,CAAC;GAC9D;AAEF,KAAI,GAAG,0BAA0B,EAAE,MAAM,gBAAyD,EAAE,KAAK;AACvG,WAAS;GAAE;GAAM;GAAa,CAAC;GAC/B;AAEF,YAAW,mBAAmB,sBAAsB,UAAU,SAAU,MAAsB,OAAO,CAAC;AAEtG,KAAI,GAAG,0BAA0B,OAAO,EAAE,KAAK,WAAgB;AAC7D,MAAI;GACF,MAAM,QAAQ,CAAC,OACX;IAAE;IAAI,KAAK,SAAS;IAAM,OAAO,SAAS;IAAO,KAAK,OAAO,SAAS,OAAO;IAAK,GAClF,KAAK,GAAG,KAAK,IAAI,IAAI,QACnB,MAAM,IAAI,KAAK,GAAG,IAAI,KAAK,GAAG,GAAU,GACxC,MAAM,SAAS,KAAK;AAE1B,OAAI,KAAK,6BAA6B;IAAE;IAAK,OAAO,KAAK,MAAM,KAAK,UAAU,MAAM,IAAI,OAAO;IAAE,CAAC;WAC3F,OAAO;AACd,OAAI,KAAK,6BAA6B;IAAE;IAAK,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAAE,CAAC;;GAE/G;AAEF,KAAI,KAAK,4BAA4B,EAAE,IAAI,CAAC"}
package/dist/vite.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region src/vite/index.d.ts
4
+ declare function inspect(): Plugin;
5
+ //#endregion
6
+ export { inspect as default };
7
+ //# sourceMappingURL=vite.d.ts.map
package/dist/vite.js ADDED
@@ -0,0 +1,136 @@
1
+ //#region src/vite/plugin.ts
2
+ const VIRTUAL = "virtual:expressive-inspect";
3
+ const RESOLVED = "\0" + VIRTUAL;
4
+ const DEPS = ["@expressive/inspect/install", "@expressive/inspect/vite/client"];
5
+ const SOURCE = `import '@expressive/inspect/install';
6
+ import { connect } from '@expressive/inspect/vite/client';
7
+ if (import.meta.hot) connect(import.meta.hot);
8
+ `;
9
+ const LIST_TIMEOUT = 1e3;
10
+ const CALL_TIMEOUT = 1e4;
11
+ const USAGE = "GET /__inspect lists pages; POST /__inspect/:id with [method, ...args] calls one.";
12
+ function plugin(url) {
13
+ let base = "/";
14
+ return {
15
+ name: "expressive-inspect",
16
+ apply: "serve",
17
+ config() {
18
+ if (url.includes("/node_modules/")) return { optimizeDeps: { include: DEPS } };
19
+ },
20
+ configResolved(config) {
21
+ base = config.base;
22
+ },
23
+ resolveId(id) {
24
+ if (id === VIRTUAL) return RESOLVED;
25
+ },
26
+ load(id) {
27
+ if (id === RESOLVED) return SOURCE;
28
+ },
29
+ transformIndexHtml() {
30
+ return [{
31
+ tag: "script",
32
+ attrs: {
33
+ type: "module",
34
+ src: `${base}@id/${VIRTUAL}`
35
+ },
36
+ injectTo: "head-prepend"
37
+ }];
38
+ },
39
+ configureServer(server) {
40
+ server.middlewares.use("/__inspect", relay(server.ws));
41
+ }
42
+ };
43
+ }
44
+ function relay(ws) {
45
+ const pages = /* @__PURE__ */ new Map();
46
+ const pending = /* @__PURE__ */ new Map();
47
+ let next = 0;
48
+ ws.on("expressive-inspect:hello", ({ id }, client) => {
49
+ pages.set(id, client);
50
+ });
51
+ ws.on("expressive-inspect:answer", ({ rid, ...answer }) => {
52
+ pending.get(rid)?.(answer);
53
+ });
54
+ function ask(client, call, timeout) {
55
+ return new Promise((resolve) => {
56
+ const rid = ++next;
57
+ const timer = setTimeout(settle, timeout);
58
+ function settle(answer) {
59
+ clearTimeout(timer);
60
+ pending.delete(rid);
61
+ resolve(answer);
62
+ }
63
+ pending.set(rid, settle);
64
+ client.send("expressive-inspect:ask", {
65
+ rid,
66
+ call
67
+ });
68
+ });
69
+ }
70
+ function live() {
71
+ const open = ws.clients;
72
+ for (const [id, client] of pages) if (!open.has(client)) pages.delete(id);
73
+ return pages;
74
+ }
75
+ async function list() {
76
+ return (await Promise.all([...live().values()].map((client) => ask(client, void 0, LIST_TIMEOUT)))).flatMap((answer) => answer && !answer.error ? [answer.value] : []);
77
+ }
78
+ return async (req, res) => {
79
+ if (!local(req)) return reply(res, 403, { error: "Local callers only; browser and proxied requests are refused." });
80
+ const id = decodeURIComponent(req.url.split("?")[0].slice(1));
81
+ if (req.method === "GET" && !id) return reply(res, 200, await list());
82
+ if (req.method !== "POST") return reply(res, 405, { error: USAGE });
83
+ const call = parse(await body(req));
84
+ if (!call) return reply(res, 400, { error: "Body must be a JSON array: [method, ...args]." });
85
+ const open = live();
86
+ const client = id ? open.get(id) : open.size === 1 ? [...open.values()][0] : void 0;
87
+ if (!client) {
88
+ const error = id ? `No page ${id}.` : open.size ? "Several pages connected - POST /__inspect/:id." : "No pages connected.";
89
+ return reply(res, !id && open.size ? 409 : 404, {
90
+ error,
91
+ pages: await list()
92
+ });
93
+ }
94
+ const answer = await ask(client, call, CALL_TIMEOUT);
95
+ if (!answer) return reply(res, 504, { error: "Page did not answer." });
96
+ if (answer.error) return reply(res, 500, { error: answer.error });
97
+ reply(res, 200, answer.value);
98
+ };
99
+ }
100
+ const FOREIGN = [
101
+ "origin",
102
+ "sec-fetch-site",
103
+ "forwarded",
104
+ "x-forwarded-for",
105
+ "x-real-ip",
106
+ "cf-connecting-ip"
107
+ ];
108
+ function local(req) {
109
+ return !FOREIGN.some((header) => header in req.headers) && /^(::ffff:)?127\.|^::1$/.test(req.socket.remoteAddress ?? "");
110
+ }
111
+ function parse(text) {
112
+ try {
113
+ const input = JSON.parse(text);
114
+ if (Array.isArray(input) && typeof input[0] == "string") return [input[0].split("."), input.slice(1)];
115
+ } catch {}
116
+ }
117
+ async function body(req) {
118
+ let text = "";
119
+ for await (const chunk of req) text += chunk;
120
+ return text;
121
+ }
122
+ function reply(res, status, data) {
123
+ res.statusCode = status;
124
+ res.setHeader("Content-Type", "application/json");
125
+ res.end(JSON.stringify(data ?? null));
126
+ }
127
+
128
+ //#endregion
129
+ //#region src/vite/index.ts
130
+ function inspect() {
131
+ return plugin(import.meta.url);
132
+ }
133
+
134
+ //#endregion
135
+ export { inspect as default };
136
+ //# sourceMappingURL=vite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.js","names":[],"sources":["../src/vite/plugin.ts","../src/vite/index.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { Plugin, WebSocketClient, WebSocketServer } from 'vite';\n\nimport type { Call } from '../dispatch';\n\nconst VIRTUAL = 'virtual:expressive-inspect';\nconst RESOLVED = '\\0' + VIRTUAL;\nconst DEPS = ['@expressive/inspect/install', '@expressive/inspect/vite/client'];\n\nconst SOURCE = `import '@expressive/inspect/install';\nimport { connect } from '@expressive/inspect/vite/client';\nif (import.meta.hot) connect(import.meta.hot);\n`;\n\nexport const LIST_TIMEOUT = 1000;\nexport const CALL_TIMEOUT = 10000;\n\nconst USAGE = 'GET /__inspect lists pages; POST /__inspect/:id with [method, ...args] calls one.';\n\nexport function plugin(url: string): Plugin {\n let base = '/';\n\n return {\n name: 'expressive-inspect',\n apply: 'serve',\n config() {\n if (url.includes('/node_modules/')) return { optimizeDeps: { include: DEPS } };\n },\n configResolved(config) {\n base = config.base;\n },\n resolveId(id) {\n if (id === VIRTUAL) return RESOLVED;\n },\n load(id) {\n if (id === RESOLVED) return SOURCE;\n },\n transformIndexHtml() {\n return [\n {\n tag: 'script',\n attrs: { type: 'module', src: `${base}@id/${VIRTUAL}` },\n injectTo: 'head-prepend'\n }\n ];\n },\n configureServer(server) {\n server.middlewares.use('/__inspect', relay(server.ws));\n }\n };\n}\n\ntype Answer = { value?: unknown; error?: string };\n\nexport function relay(ws: Pick<WebSocketServer, 'on' | 'clients'>) {\n const pages = new Map<string, WebSocketClient>();\n const pending = new Map<number, (answer: Answer) => void>();\n let next = 0;\n\n ws.on('expressive-inspect:hello', ({ id }: { id: string }, client: WebSocketClient) => {\n pages.set(id, client);\n });\n\n ws.on('expressive-inspect:answer', ({ rid, ...answer }: Answer & { rid: number }) => {\n pending.get(rid)?.(answer);\n });\n\n function ask(client: WebSocketClient, call: Call | undefined, timeout: number) {\n return new Promise<Answer | undefined>((resolve) => {\n const rid = ++next;\n const timer = setTimeout(settle, timeout);\n\n function settle(answer?: Answer) {\n clearTimeout(timer);\n pending.delete(rid);\n resolve(answer);\n }\n\n pending.set(rid, settle);\n client.send('expressive-inspect:ask', { rid, call });\n });\n }\n\n function live() {\n const open = ws.clients;\n\n for (const [id, client] of pages) if (!open.has(client)) pages.delete(id);\n\n return pages;\n }\n\n async function list() {\n const answers = await Promise.all([...live().values()].map((client) => ask(client, undefined, LIST_TIMEOUT)));\n\n return answers.flatMap((answer) => (answer && !answer.error ? [answer.value] : []));\n }\n\n return async (req: IncomingMessage, res: ServerResponse) => {\n if (!local(req)) return reply(res, 403, { error: 'Local callers only; browser and proxied requests are refused.' });\n\n const id = decodeURIComponent(req.url!.split('?')[0].slice(1));\n\n if (req.method === 'GET' && !id) return reply(res, 200, await list());\n if (req.method !== 'POST') return reply(res, 405, { error: USAGE });\n\n const call = parse(await body(req));\n\n if (!call) return reply(res, 400, { error: 'Body must be a JSON array: [method, ...args].' });\n\n const open = live();\n const client = id ? open.get(id) : open.size === 1 ? [...open.values()][0] : undefined;\n\n if (!client) {\n const error = id ? `No page ${id}.` : open.size ? 'Several pages connected - POST /__inspect/:id.' : 'No pages connected.';\n return reply(res, !id && open.size ? 409 : 404, { error, pages: await list() });\n }\n\n const answer = await ask(client, call, CALL_TIMEOUT);\n\n if (!answer) return reply(res, 504, { error: 'Page did not answer.' });\n if (answer.error) return reply(res, 500, { error: answer.error });\n\n reply(res, 200, answer.value);\n };\n}\n\nconst FOREIGN = ['origin', 'sec-fetch-site', 'forwarded', 'x-forwarded-for', 'x-real-ip', 'cf-connecting-ip'];\n\nfunction local(req: IncomingMessage) {\n return (\n !FOREIGN.some((header) => header in req.headers) &&\n /^(::ffff:)?127\\.|^::1$/.test(req.socket.remoteAddress ?? '')\n );\n}\n\nfunction parse(text: string): Call | undefined {\n try {\n const input: unknown = JSON.parse(text);\n\n if (Array.isArray(input) && typeof input[0] == 'string') return [input[0].split('.'), input.slice(1)];\n } catch {}\n}\n\nasync function body(req: IncomingMessage) {\n let text = '';\n for await (const chunk of req) text += chunk;\n return text;\n}\n\nfunction reply(res: ServerResponse, status: number, data: unknown) {\n res.statusCode = status;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify(data ?? null));\n}\n","import type { Plugin } from 'vite';\n\nimport { plugin } from './plugin';\n\nexport default function inspect(): Plugin {\n return plugin(import.meta.url);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expressive/inspect",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "In-process inspector for Expressive MVC - registry, ownership tree, path queries, and a frame journal for agents, tests, and devtools.",
5
5
  "keywords": [
6
6
  "expressive",
@@ -38,9 +38,17 @@
38
38
  "types": "./dist/install.d.ts",
39
39
  "default": "./dist/install.js"
40
40
  },
41
- "./playwright": {
42
- "types": "./dist/playwright.d.ts",
43
- "default": "./dist/playwright.js"
41
+ "./bridge": {
42
+ "types": "./dist/bridge.d.ts",
43
+ "default": "./dist/bridge.js"
44
+ },
45
+ "./vite": {
46
+ "types": "./dist/vite.d.ts",
47
+ "default": "./dist/vite.js"
48
+ },
49
+ "./vite/client": {
50
+ "types": "./dist/vite/client.d.ts",
51
+ "default": "./dist/vite/client.js"
44
52
  }
45
53
  },
46
54
  "engines": {
@@ -54,10 +62,17 @@
54
62
  "test": "tsc --noEmit && vitest run --coverage"
55
63
  },
56
64
  "peerDependencies": {
57
- "@expressive/mvc": "^0.85.0"
65
+ "@expressive/mvc": "^0.87.0",
66
+ "vite": ">=5"
67
+ },
68
+ "peerDependenciesMeta": {
69
+ "vite": {
70
+ "optional": true
71
+ }
58
72
  },
59
73
  "devDependencies": {
60
74
  "@expressive/mvc": "workspace:*",
61
- "tsdown": "^0.20.1"
75
+ "tsdown": "^0.20.1",
76
+ "vite": "^7.3.1"
62
77
  }
63
78
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"inspect-D4NC5Mx5.js","names":["run"],"sources":["../src/serialize.ts","../src/types.ts","../src/journal.ts","../src/inspect.ts"],"sourcesContent":["import { State } from '@expressive/mvc';\n\nconst MAX_STRING = 240;\nconst MAX_ARRAY = 24;\nconst MAX_KEYS = 40;\n\nexport type Address = { target: string; path?: string };\n\nexport function parsePath(raw: string): Address {\n const trimmed = raw.trim();\n const dot = trimmed.indexOf('.');\n if (dot <= 0) return { target: trimmed };\n return { target: trimmed.slice(0, dot), path: trimmed.slice(dot + 1) };\n}\n\n/** Raw stored entries of a State - never reads through accessors. */\nexport function entries(state: State): Map<string, unknown> {\n return new Map(state);\n}\n\n/** Walk `path` without triggering getters, factories, or suspense. */\nexport function walk(value: unknown, path?: string): unknown {\n if (!path) return value;\n let cur: unknown = value;\n for (const part of path.split('.')) {\n if (cur == null || typeof cur !== 'object') return undefined;\n if (cur instanceof State) cur = entries(cur).get(part);\n else if (cur instanceof Map) cur = cur.get(part);\n else cur = (cur as Record<string, unknown>)[part];\n }\n return cur;\n}\n\nexport function ref(state: State) {\n return { $ref: String(state), $type: state.constructor.name };\n}\n\n/** JSON-safe, size-capped view. Nested States collapse to `$ref`. */\nexport function serialize(value: unknown, depth = 2): unknown {\n if (value instanceof State) {\n const out: Record<string, unknown> = ref(value);\n for (const [key, child] of [...entries(value)].slice(0, MAX_KEYS))\n out[key] = write(child, depth - 1);\n return out;\n }\n return write(value, depth);\n}\n\nfunction write(value: unknown, depth: number): unknown {\n if (value == null || typeof value === 'boolean') return value;\n if (typeof value === 'number') return Number.isFinite(value) ? value : String(value);\n if (typeof value === 'string')\n return value.length > MAX_STRING ? `${value.slice(0, MAX_STRING - 1)}…` : value;\n if (typeof value === 'bigint') return `${value}n`;\n if (typeof value !== 'object') return undefined;\n if (value instanceof State) return ref(value);\n if (value instanceof Date) return value.toISOString();\n if (value instanceof Error) return { name: value.name, message: value.message };\n if (typeof (value as PromiseLike<unknown>).then === 'function') return '(pending)';\n\n const items = list(value);\n\n if (items) {\n if (depth <= 0) return `[${items.length}]`;\n const out = items.slice(0, MAX_ARRAY).map((row) => write(row, depth - 1));\n if (items.length > MAX_ARRAY) out.push(`…+${items.length - MAX_ARRAY}`);\n return out;\n }\n\n const pairs = value instanceof Map\n ? [...value].map(([k, v]) => [String(k), v] as const)\n : Object.entries(value);\n\n if (depth <= 0) return `{${pairs.length}}`;\n\n const out: Record<string, unknown> = {};\n for (const [key, child] of pairs.slice(0, MAX_KEYS)) {\n if (key === 'is') continue;\n const next = write(child, depth - 1);\n if (next !== undefined) out[key] = next;\n }\n if (pairs.length > MAX_KEYS) out['…'] = pairs.length - MAX_KEYS;\n return out;\n}\n\nfunction list(value: object): unknown[] | undefined {\n if (Array.isArray(value)) return value;\n if (value instanceof Map) return undefined;\n if (typeof (value as Iterable<unknown>)[Symbol.iterator] === 'function')\n return [...(value as Iterable<unknown>)];\n return undefined;\n}\n","import { State } from '@expressive/mvc';\n\nexport interface TypeInfo {\n /** Opaque, stable for the life of the process. */\n typeId: string;\n /** Resolved display name - see `label`. */\n type: string;\n /** Construction stack captured at first sight, for a source-map resolver. */\n site: string;\n}\n\nconst info = new Map<typeof State, TypeInfo>();\nconst labels = new Map<typeof State, string>();\nconst names = new Map<string, string>();\nlet count = 0;\n\nfunction register(Type: typeof State): TypeInfo {\n let row = info.get(Type);\n if (!row) {\n const site = String(new Error().stack).split('\\n').slice(1, 16).join('\\n');\n info.set(Type, (row = { typeId: `T${++count}`, type: '', site }));\n }\n return row;\n}\n\nexport function seen(Type: typeof State): TypeInfo {\n const row = register(Type);\n row.type = labelOf(Type);\n return { ...row };\n}\n\nexport function labelOf(Type: typeof State): string {\n const row = register(Type);\n const explicit = labels.get(Type) ?? (Type as { displayName?: string }).displayName;\n if (explicit) return explicit;\n const named = names.get(row.typeId) ?? names.get(row.site);\n if (named) return named;\n return Type.name.length > 2 ? Type.name : row.typeId;\n}\n\nexport function label(Type: typeof State, name: string): void {\n labels.set(Type, name);\n}\n\n/** Supply resolved names keyed by `typeId` or `site`, e.g. from a source-map pass. */\nexport function resolve(table: Record<string, string>): void {\n for (const [key, name] of Object.entries(table)) names.set(key, name);\n}\n\nexport function forget(): void {\n info.clear();\n labels.clear();\n names.clear();\n count = 0;\n}\n","import { State } from '@expressive/mvc';\n\nimport { parsePath, serialize } from './serialize';\nimport { labelOf, seen } from './types';\n\nexport type Level = 'off' | 'keys' | 'values';\n\nexport interface Options {\n level?: Level;\n /** Labels to record. */\n types?: string[];\n /** `Type.key` addresses to record - label, `typeId`, or instance id on the left, property on the right. */\n paths?: string[];\n /** Property names to record on any type. */\n keys?: string[];\n /** Also record method calls (wraps instance methods except `render`). */\n calls?: boolean;\n}\n\nexport interface Event {\n id: string;\n type: string;\n key: string;\n kind: 'update' | 'event' | 'call' | 'destroy';\n value?: unknown;\n args?: unknown[];\n}\n\nexport interface Frame {\n seq: number;\n at: number;\n /** Frame whose flush scheduled the work that opened this one. */\n cause?: number;\n events: Event[];\n}\n\nexport interface Query {\n since?: number;\n type?: string;\n id?: string;\n key?: string;\n cause?: number;\n}\n\nconst FRAME_CAP = 500;\n\nconst frames: Frame[] = [];\nconst config: Required<Options> = { level: 'off', types: [], paths: [], keys: [], calls: false };\nlet seq = 0;\nlet open: Frame | undefined;\nlet cause: number | undefined;\n\nexport const journal = {\n record(options: Options = {}): Required<Options> {\n Object.assign(config, options);\n if (!options.level && options.calls && config.level === 'off') config.level = 'keys';\n return { ...config, types: [...config.types], paths: [...config.paths], keys: [...config.keys] };\n },\n\n frames(query: Query = {}): Frame[] {\n const out: Frame[] = [];\n for (const frame of frames) {\n if (query.since !== undefined && frame.seq <= query.since) continue;\n if (query.cause !== undefined && frame.cause !== query.cause) continue;\n const events = frame.events.filter((e) => matches(e, query));\n if (events.length) out.push({ ...frame, events });\n }\n return out;\n },\n\n history(query: Query): Array<{ seq: number; at: number; event: Event }> {\n const out = [];\n for (const frame of journal.frames(query))\n for (const event of frame.events) out.push({ seq: frame.seq, at: frame.at, event });\n return out;\n },\n\n /** Frames reachable from `seq` through `cause` links, in order. */\n downstream(from: number): Frame[] {\n const roots = new Set([from]);\n return frames.filter((frame) => {\n if (frame.cause === undefined || !roots.has(frame.cause)) return false;\n roots.add(frame.seq);\n return true;\n });\n },\n\n /** Current sequence - pass as `since` to read only what follows. */\n seq(): number {\n return seq;\n },\n\n /** One event per line, for bulk export to a sidecar. */\n export(query: Query = {}): string {\n return journal\n .frames(query)\n .flatMap((frame) =>\n frame.events.map((event) =>\n JSON.stringify({ seq: frame.seq, at: frame.at, cause: frame.cause, ...event })\n )\n )\n .join('\\n');\n },\n\n clear(): void {\n frames.length = 0;\n open = undefined;\n cause = undefined;\n seq = 0;\n },\n\n reset(): void {\n journal.clear();\n Object.assign(config, { level: 'off', types: [], paths: [], keys: [], calls: false });\n }\n};\n\nfunction matches(event: Event, query: Query) {\n return (\n (!query.type || event.type === query.type) &&\n (!query.id || event.id === query.id) &&\n (!query.key || event.key === query.key)\n );\n}\n\n/** Filters OR together; none set records everything. Without a key only type-level filters apply. */\nexport function wants(state: State, key?: string): boolean {\n if (config.level === 'off') return false;\n\n const { types, paths, keys } = config;\n if (!types.length && !paths.length && !keys.length) return true;\n\n const Type = state.constructor as typeof State;\n const label = labelOf(Type);\n if (types.includes(label)) return true;\n if (key !== undefined && keys.includes(key)) return true;\n\n const targets = [label, seen(Type).typeId, String(state)];\n for (const address of paths) {\n const { target, path } = parsePath(address);\n if (targets.includes(target) && (key === undefined || path === key)) return true;\n }\n return false;\n}\n\nexport function recordsCalls(): boolean {\n return config.calls && config.level !== 'off';\n}\n\n/** Run `work` with recording forced on; returns the frames it produced. */\nexport async function act(work: () => unknown): Promise<Frame[]> {\n const level = config.level;\n const start = seq;\n if (level === 'off') config.level = 'values';\n try {\n await work();\n await new Promise((resolve) => setTimeout(resolve, 0));\n } finally {\n config.level = level;\n }\n return journal.frames({ since: start });\n}\n\nexport function note(state: State, key: unknown, store: Map<string, unknown>): void {\n const name = String(key);\n if (!wants(state, name)) return;\n\n const event: Event = {\n id: String(state),\n type: labelOf(state.constructor as typeof State),\n key: name,\n kind: typeof key === 'string' && store.has(key) ? 'update' : 'event'\n };\n\n if (config.level === 'values' && event.kind === 'update')\n event.value = serialize(store.get(name), 1);\n\n push(event);\n}\n\nexport function noteCall(state: State, key: string, args: unknown[]): void {\n if (!recordsCalls() || !wants(state, key)) return;\n\n const event: Event = { id: String(state), type: labelOf(state.constructor as typeof State), key, kind: 'call' };\n if (config.level === 'values') event.args = args.map((arg) => serialize(arg, 1));\n push(event);\n}\n\nexport function noteDestroy(state: State): void {\n if (!wants(state)) return;\n push({ id: String(state), type: labelOf(state.constructor as typeof State), key: '', kind: 'destroy' });\n}\n\nfunction push(event: Event) {\n if (!open) {\n const frame: Frame = { seq: ++seq, at: Date.now(), events: [] };\n if (cause !== undefined) frame.cause = cause;\n open = frame;\n frames.push(frame);\n if (frames.length > FRAME_CAP) frames.splice(0, frames.length - FRAME_CAP);\n queueMicrotask(() => {\n open = undefined;\n cause = frame.seq;\n queueMicrotask(() => {\n if (seq === frame.seq) cause = undefined;\n });\n });\n }\n open.events.push(event);\n}\n","import { Context, State } from '@expressive/mvc';\nimport { isElement } from '@expressive/mvc/runtime';\n\nimport { act as run, journal, note, noteCall, noteDestroy, recordsCalls, type Frame, type Query } from './journal';\nimport { entries, parsePath, serialize, walk } from './serialize';\nimport { forget, seen, type TypeInfo } from './types';\n\nexport interface Model extends TypeInfo {\n id: string;\n parent?: string;\n /** Keys with a stored value. */\n keys: string[];\n /** Declared keys with no stored value yet - lazy, pending, or uncomputed. */\n absent: string[];\n}\n\nexport interface Node {\n id: string;\n type: string;\n children: Node[];\n}\n\ninterface Span {\n since: number;\n until?: number;\n claimed: boolean;\n settled: boolean;\n}\n\ninterface Row {\n ref: { deref(): State | undefined };\n held?: State;\n}\n\nconst Reaper: typeof FinalizationRegistry =\n typeof FinalizationRegistry === 'function'\n ? FinalizationRegistry\n : (class {\n register() {}\n unregister() {}\n } as unknown as typeof FinalizationRegistry);\n\nconst weak = (state: State): Row['ref'] =>\n typeof WeakRef === 'function' ? new WeakRef(state) : { deref: () => state };\n\nconst live = new Map<string, Row>();\nconst hooks = new Map<typeof State, () => boolean>();\nconst wrapped = new WeakSet<State>();\nconst wrappers = new WeakMap<State, Instance>();\nconst spans = new WeakMap<State, Span>();\nconst reaper = new Reaper<string>(collected);\n\nlet version = 0;\nlet cached: { version: number; parents: Map<State, State> } | undefined;\nlet lost = 0;\n\n/** Registered instances still reachable, claimed or not. */\nfunction registered(): State[] {\n return [...live.values()]\n .map((row) => row.held ?? row.ref.deref())\n .filter((state): state is State => !!state);\n}\n\n/** Instances on the mainline: claimed, or too young to judge, or every one when no host renders. */\nfunction* mainline(): Generator<State> {\n const parents = ownership();\n for (const state of registered()) if (!orphaned(state, parents)) yield state;\n}\n\nfunction orphaned(state: State, parents: Map<State, State>, seen = new Set<State>()): boolean {\n const span = spans.get(state)!;\n if (span.claimed || !span.settled || !hosted()) return false;\n if (provided(state)) {\n claim(state);\n return false;\n }\n const owner = parents.get(state);\n if (!owner || seen.has(owner)) return true;\n seen.add(state);\n if (orphaned(owner, parents, seen)) return true;\n if (spans.get(owner)!.claimed) claim(state);\n return false;\n}\n\nfunction claim(state: State) {\n const span = spans.get(state)!;\n if (span.claimed) return;\n span.claimed = true;\n const row = live.get(String(state));\n if (row) {\n row.held = state;\n reaper.unregister(state);\n }\n version++;\n}\n\n/** A `static global` instance currently holding its slot in the root context. */\nfunction provided(state: State): boolean {\n const type = state.constructor as typeof State;\n const global = typeof type.global === 'function' ? type.global(state) : type.global;\n if (!global) return false;\n for (const set of Context.root.provide.values())\n if (set) for (const [member] of set) if (member === state) return true;\n return false;\n}\n\nfunction hosted(): boolean {\n try {\n isElement(null);\n return true;\n } catch {\n return false;\n }\n}\n\n/** A registered, unclaimed instance was garbage collected - it was abandoned. */\nexport function collected(id: string): void {\n if (!live.delete(id)) return;\n lost++;\n version++;\n}\n\n/** Navigable view of one instance - identity, ownership, reads, observation. One per state; outlives it. */\nexport class Instance {\n private constructor(readonly state: State) {}\n\n static of(state: State): Instance {\n let instance = wrappers.get(state);\n if (!instance) wrappers.set(state, (instance = new Instance(state)));\n return instance;\n }\n\n get id() {\n return String(this.state);\n }\n\n get type() {\n return seen(this.state.constructor as typeof State).type;\n }\n\n get alive() {\n return live.has(this.id);\n }\n\n /** Reached by a host commit (`mount`), a claimed owner, or `static global`. Always true without a host. */\n get claimed() {\n return !orphaned(this.state, ownership());\n }\n\n /** Registration time (ms). */\n get since() {\n return spans.get(this.state)!.since;\n }\n\n /** Destruction time (ms), once destroyed. */\n get until() {\n return spans.get(this.state)!.until;\n }\n\n get parent(): Instance | undefined {\n const owner = ownership().get(this.state);\n return owner && Instance.of(owner);\n }\n\n get children(): Instance[] {\n const parents = ownership();\n return [...registered()]\n .filter((state) => parents.get(state) === this.state)\n .map(Instance.of);\n }\n\n /** First instance in this subtree, depth-first, matching `where` (a label or predicate). */\n find(where: string | ((instance: Instance) => boolean)): Instance | undefined {\n const test = typeof where === 'string' ? (h: Instance) => h.type === where : where;\n for (const child of this.children) {\n if (test(child)) return child;\n const deep = child.find(test);\n if (deep) return deep;\n }\n return undefined;\n }\n\n get(path?: string): unknown {\n return serialize(walk(this.state, path));\n }\n\n model(): Model {\n return describe(this.state, ownership());\n }\n\n /** Run `work`, settle, and return the frames it produced. */\n act(work: (state: State) => unknown): Promise<Frame[]> {\n return run(() => work(this.state));\n }\n\n /** Call `fn` on each update, and with `null` on destroy; `keys` narrows updates. Returns unsubscribe. */\n watch(fn: (key: string | null) => void, keys?: string[]): () => void {\n const updates = this.state.set((key) => {\n if (typeof key === 'string' && (!keys || keys.includes(key))) fn(key);\n });\n const destroy = this.state.get(null, () => fn(null));\n return () => {\n updates();\n destroy();\n };\n }\n\n frames(query: Query = {}): Frame[] {\n return journal.frames({ ...query, id: this.id });\n }\n}\n\nexport function attach(Type: typeof State = State): () => void {\n if (!hooks.has(Type))\n hooks.set(\n Type,\n Type.on(function (this: State) {\n const self = this.is;\n const id = String(self);\n const span: Span = { since: Date.now(), claimed: false, settled: false };\n seen(self.constructor as typeof State);\n live.set(id, { ref: weak(self) });\n spans.set(self, span);\n reaper.register(self, id, self);\n setTimeout(() => {\n span.settled = true;\n }, 0);\n version++;\n if (recordsCalls()) wrap(self);\n mounts(self);\n const stop = self.set((key) => {\n const store = entries(self);\n if (typeof key === 'string' && typeof store.get(key) === 'object') version++;\n note(self, key, store);\n });\n return () => {\n stop();\n live.delete(id);\n reaper.unregister(self);\n span.until = Date.now();\n version++;\n noteDestroy(self);\n };\n })\n );\n\n return () => {\n hooks.get(Type)?.();\n hooks.delete(Type);\n };\n}\n\nexport function detach(): void {\n for (const stop of hooks.values()) stop();\n hooks.clear();\n live.clear();\n cached = undefined;\n lost = 0;\n journal.reset();\n forget();\n}\n\n/** Resolve an id or label to an instance - mainline first, then orphans. */\nexport function find(target: string): Instance | undefined {\n const byId = live.get(target);\n const held = byId && (byId.held ?? byId.ref.deref());\n if (held) return Instance.of(held);\n for (const pool of [mainline(), abandoned()])\n for (const state of pool)\n if (seen(state.constructor as typeof State).type === target) return Instance.of(state);\n return undefined;\n}\n\nexport function instances(): Instance[] {\n return [...mainline()].map(Instance.of);\n}\n\n/** Registered and settled, but never claimed by a host commit, an owner, or root context. */\nexport function orphans(): Instance[] {\n return [...abandoned()].map(Instance.of);\n}\n\nfunction* abandoned(): Generator<State> {\n const parents = ownership();\n for (const state of registered()) if (orphaned(state, parents)) yield state;\n}\n\n/** Counts worth a look: live orphans, and unclaimed instances the collector already reaped. */\nexport function warnings(): { orphans: number; collected: number } {\n return { orphans: orphans().length, collected: lost };\n}\n\nexport function roots(): Instance[] {\n const parents = ownership();\n return instances().filter((instance) => !parents.has(instance.state));\n}\n\nexport function models(): Model[] {\n const parents = ownership();\n return [...mainline()].map((state) => describe(state, parents));\n}\n\nexport function tree(): Node[] {\n const parents = ownership();\n const nodes = new Map<State, Node>();\n const out: Node[] = [];\n\n for (const state of mainline())\n nodes.set(state, { id: String(state), type: seen(state.constructor as typeof State).type, children: [] });\n\n for (const [state, node] of nodes) {\n const parent = parents.get(state);\n const owner = parent && nodes.get(parent);\n if (owner) owner.children.push(node);\n else out.push(node);\n }\n\n return out;\n}\n\nexport function get(address?: string): unknown {\n if (!address) return models();\n const { target, path } = parsePath(address);\n return find(target)?.get(path);\n}\n\nexport function set(address: string, value: unknown): void {\n const { target, path } = parsePath(address);\n const instance = find(target);\n if (!instance || !path) throw new Error(`No model at ${address}.`);\n\n const dot = path.lastIndexOf('.');\n const key = path.slice(dot + 1);\n const owner = dot < 0 ? instance.state : walk(instance.state, path.slice(0, dot));\n\n if (owner == null || typeof owner !== 'object') throw new Error(`No model at ${address}.`);\n\n if (owner instanceof Map) owner.set(key, value);\n else (owner as Record<string, unknown>)[key] = value;\n}\n\nexport async function call(address: string, ...args: unknown[]): Promise<unknown> {\n const { target, path } = parsePath(address);\n const state = find(target)?.state;\n const method = state && path && (state as unknown as Record<string, unknown>)[path];\n\n if (typeof method !== 'function') throw new Error(`No method at ${address}.`);\n\n return serialize(await method.apply(state, args));\n}\n\nexport function wrapAll(): void {\n for (const state of registered()) wrap(state);\n}\n\n/** Observe the host commit: adapters call `mount?.()` once an instance is placed. */\nfunction mounts(state: State) {\n const target = state as State & { mount?: (...args: unknown[]) => unknown };\n const original = target.mount;\n\n Object.defineProperty(target, 'mount', {\n configurable: true,\n writable: true,\n value(this: unknown, ...args: unknown[]) {\n claim(state);\n return typeof original === 'function' ? original.apply(this, args) : undefined;\n }\n });\n}\n\nfunction describe(state: State, parents: Map<State, State>): Model {\n const stored = entries(state);\n const declared = Object.entries(Object.getOwnPropertyDescriptors(state))\n .filter(([, desc]) => typeof desc.get === 'function')\n .map(([key]) => key);\n const model: Model = {\n id: String(state),\n ...seen(state.constructor as typeof State),\n keys: [...stored.keys()],\n absent: declared.filter((key) => !stored.has(key))\n };\n const parent = parents.get(state);\n if (parent) model.parent = String(parent);\n return model;\n}\n\nfunction wrap(state: State) {\n if (wrapped.has(state)) return;\n wrapped.add(state);\n\n const target = state as unknown as Record<string, Function>;\n const seenKeys = new Set<string>(['render']);\n\n for (let proto = Object.getPrototypeOf(state); proto !== State.prototype; proto = Object.getPrototypeOf(proto))\n for (const [key, desc] of Object.entries(Object.getOwnPropertyDescriptors(proto))) {\n if (seenKeys.has(key) || typeof desc.get !== 'function' || desc.get !== desc.set) continue;\n\n seenKeys.add(key);\n\n const original = target[key];\n\n target[key] = function (this: unknown, ...args: unknown[]) {\n noteCall(state, key, args);\n return original.apply(this, args);\n };\n }\n}\n\nfunction ownership(): Map<State, State> {\n if (cached?.version !== version) {\n const parents = new Map<State, State>();\n\n for (const owner of registered())\n for (const [key, value] of entries(owner)) {\n const field = Object.getOwnPropertyDescriptor(owner, key)?.enumerable;\n if (field || !(value instanceof State)) own(parents, owner, value);\n }\n\n cached = { version, parents };\n }\n\n return cached.parents;\n}\n\nfunction own(parents: Map<State, State>, owner: State, value: unknown) {\n if (value instanceof State) {\n if (!parents.has(value) && value !== owner) parents.set(value, owner);\n } else if (value && typeof value === 'object' && Symbol.iterator in value) {\n const items = value instanceof Map ? value.values() : (value as Iterable<unknown>);\n for (const item of items) if (item instanceof State) own(parents, owner, item);\n }\n}\n"],"mappings":"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"}
@@ -1,33 +0,0 @@
1
- import { S as Frame, T as Query, w as Options } from "./serialize-FF81tr-L.js";
2
- import inspect$1 from "./index.js";
3
-
4
- //#region src/playwright.d.ts
5
- /**
6
- * Anything that can run a function in the page: Playwright `Page`, `Frame`, or
7
- * `Locator` (which passes the element first), puppeteer `Page` or `Frame`.
8
- */
9
- interface Evaluates {
10
- evaluate(fn: (...args: any[]) => unknown, arg?: unknown): Promise<unknown>;
11
- }
12
- /**
13
- * Drive the page's inspector from a test. Every method is one `evaluate`;
14
- * `around` brackets a step with the journal forced on and returns its frames.
15
- */
16
- declare function inspect(target: Evaluates): {
17
- get: (address?: string) => Promise<unknown>;
18
- set: (address: string, value: unknown) => Promise<void>;
19
- call: (address: string, ...args: unknown[]) => Promise<unknown>;
20
- models: () => Promise<ReturnType<typeof inspect$1.models>>;
21
- tree: () => Promise<ReturnType<typeof inspect$1.tree>>;
22
- journal: {
23
- record: (options?: Options) => Promise<Required<Options>>;
24
- seq: () => Promise<number>;
25
- frames: (query?: Query) => Promise<Frame[]>;
26
- history: (query: Query) => Promise<ReturnType<typeof inspect$1.journal.history>>;
27
- clear: () => Promise<void>;
28
- };
29
- around(step: () => unknown): Promise<Frame[]>;
30
- };
31
- //#endregion
32
- export { Evaluates, inspect };
33
- //# sourceMappingURL=playwright.d.ts.map
@@ -1,53 +0,0 @@
1
- //#region src/playwright.ts
2
- function bridge(first, second) {
3
- const call = second ?? first;
4
- const api = globalThis.__EXPRESSIVE_INSPECT__;
5
- if (!api) throw new Error("@expressive/inspect is not attached in this page - make '@expressive/inspect/install' the first import of the app entry.");
6
- let target = api;
7
- let owner;
8
- for (const key of call[0]) {
9
- owner = target;
10
- target = target[key];
11
- }
12
- return target.apply(owner, call[1]);
13
- }
14
- /**
15
- * Drive the page's inspector from a test. Every method is one `evaluate`;
16
- * `around` brackets a step with the journal forced on and returns its frames.
17
- */
18
- function inspect(target) {
19
- const remote = (...path) => (...args) => target.evaluate(bridge, [path, args]);
20
- const record = remote("journal", "record");
21
- const seq = remote("journal", "seq");
22
- const frames = remote("journal", "frames");
23
- return {
24
- get: remote("get"),
25
- set: remote("set"),
26
- call: remote("call"),
27
- models: remote("models"),
28
- tree: remote("tree"),
29
- journal: {
30
- record,
31
- seq,
32
- frames,
33
- history: remote("journal", "history"),
34
- clear: remote("journal", "clear")
35
- },
36
- async around(step) {
37
- const before = await record();
38
- const since = await seq();
39
- if (before.level === "off") await record({ level: "values" });
40
- try {
41
- await step();
42
- await target.evaluate(() => new Promise((resolve) => setTimeout(resolve, 0)));
43
- return await frames({ since });
44
- } finally {
45
- if (before.level === "off") await record({ level: "off" });
46
- }
47
- }
48
- };
49
- }
50
-
51
- //#endregion
52
- export { inspect };
53
- //# sourceMappingURL=playwright.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"playwright.js","names":[],"sources":["../src/playwright.ts"],"sourcesContent":["import type { inspect as Inspect } from './index';\nimport type { Frame, Options, Query } from './journal';\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\ntype Call = [path: string[], args: unknown[]];\n\nfunction bridge(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 owner = target;\n target = (target as Record<string, unknown>)[key];\n }\n\n return (target as Function).apply(owner, call[1]);\n}\n\n/**\n * Drive the page's inspector from a test. Every method is one `evaluate`;\n * `around` brackets a step with the journal forced on and returns its frames.\n */\nexport function inspect(target: Evaluates) {\n const remote =\n <R>(...path: string[]) =>\n (...args: unknown[]) =>\n target.evaluate(bridge, [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) => 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\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 clear: remote<void>('journal', 'clear') as () => Promise<void>\n },\n\n async around(step: () => unknown): Promise<Frame[]> {\n const before = await record();\n const since = await seq();\n\n if (before.level === 'off') await record({ level: 'values' });\n\n try {\n await step();\n await target.evaluate(() => new Promise((resolve) => setTimeout(resolve, 0)));\n return await frames({ since });\n } finally {\n if (before.level === 'off') await record({ level: 'off' });\n }\n }\n };\n}\n"],"mappings":";AAaA,SAAS,OAAO,OAAgB,QAAkB;CAChD,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,UAAQ;AACR,WAAU,OAAmC;;AAG/C,QAAQ,OAAoB,MAAM,OAAO,KAAK,GAAG;;;;;;AAOnD,SAAgB,QAAQ,QAAmB;CACzC,MAAM,UACA,GAAG,UACN,GAAG,SACF,OAAO,SAAS,QAAQ,CAAC,MAAM,KAAK,CAAS;CAEjD,MAAM,SAAS,OAA0B,WAAW,SAAS;CAC7D,MAAM,MAAM,OAAe,WAAW,MAAM;CAC5C,MAAM,SAAS,OAAgB,WAAW,SAAS;AAEnD,QAAO;EACL,KAAK,OAAgB,MAAM;EAC3B,KAAK,OAAa,MAAM;EACxB,MAAM,OAAgB,OAAO;EAC7B,QAAQ,OAA0C,SAAS;EAC3D,MAAM,OAAwC,OAAO;EAErD,SAAS;GACC;GACH;GACG;GACR,SAAS,OAAmD,WAAW,UAAU;GAGjF,OAAO,OAAa,WAAW,QAAQ;GACxC;EAED,MAAM,OAAO,MAAuC;GAClD,MAAM,SAAS,MAAM,QAAQ;GAC7B,MAAM,QAAQ,MAAM,KAAK;AAEzB,OAAI,OAAO,UAAU,MAAO,OAAM,OAAO,EAAE,OAAO,UAAU,CAAC;AAE7D,OAAI;AACF,UAAM,MAAM;AACZ,UAAM,OAAO,eAAe,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC,CAAC;AAC7E,WAAO,MAAM,OAAO,EAAE,OAAO,CAAC;aACtB;AACR,QAAI,OAAO,UAAU,MAAO,OAAM,OAAO,EAAE,OAAO,OAAO,CAAC;;;EAG/D"}