@expressive/inspect 0.5.2 → 0.6.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 +31 -1
- package/dist/bridge.d.ts +1 -1
- package/dist/bridge.js +1 -1
- package/dist/{dispatch-Dnd88-J9.js → dispatch-BmtaBKeH.js} +4 -3
- package/dist/dispatch-BmtaBKeH.js.map +1 -0
- package/dist/dispatch-DNcCJUE-.d.ts +5 -0
- package/dist/http.d.ts +35 -0
- package/dist/http.js +51 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/{inspect-CEzAKgBy.d.ts → inspect-TS7ZMfCa.d.ts} +1 -1
- package/dist/protocol-BApRTxGk.js +74 -0
- package/dist/protocol-BApRTxGk.js.map +1 -0
- package/dist/vite/client.d.ts +2 -3
- package/dist/vite/client.js +14 -39
- package/dist/vite/client.js.map +1 -1
- package/dist/vite.js +4 -30
- package/dist/vite.js.map +1 -1
- package/package.json +9 -3
- package/dist/dispatch-Dnd88-J9.js.map +0 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
Query the live instance 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.
|
|
13
|
+
Query the live instance 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 or server handler - a base layer for agents, devtools, and test helpers.
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
npm install --save-dev @expressive/inspect
|
|
@@ -68,6 +68,36 @@ Local callers only; browser and proxied (tunnel) requests are refused.
|
|
|
68
68
|
|
|
69
69
|
Console: `__EXPRESSIVE_INSPECT__.get('Composer.draft')`, `__EXPRESSIVE_INSPECT__.journal.record({ level: 'keys', paths: ['Composer.draft'] })`.
|
|
70
70
|
|
|
71
|
+
## From your own server
|
|
72
|
+
|
|
73
|
+
`@expressive/inspect/http` installs the inspector and exports `handle`, which serves it from a backend's own server - same body and answers as the relay; `GET` describes the process. `handle` takes each host's native handler arguments, so it mounts as a route anywhere, nested routers included. Import it dynamically behind your dev flag, so the package can stay a devDependency:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// top of the server entry - installs, so it precedes any Model
|
|
77
|
+
const inspect = dev && (await import('@expressive/inspect/http')).handle;
|
|
78
|
+
|
|
79
|
+
// Bun
|
|
80
|
+
Bun.serve({ routes: inspect ? { '/__inspect': inspect } : undefined, fetch: app });
|
|
81
|
+
|
|
82
|
+
// Express, Connect, node:http
|
|
83
|
+
if (inspect) app.use('/__inspect', inspect);
|
|
84
|
+
|
|
85
|
+
// Deno, TCP listeners
|
|
86
|
+
if (inspect && pathname == '/__inspect') return inspect(req, info);
|
|
87
|
+
|
|
88
|
+
// Hono
|
|
89
|
+
if (inspect) app.all('/__inspect', (c) => inspect(c.req.raw, { ip: getConnInfo(c).remote.address }));
|
|
90
|
+
|
|
91
|
+
// any Request plus the socket address
|
|
92
|
+
if (inspect) return inspect(req, { ip });
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Missing or non-loopback callers get 403, as do browser and proxied requests. A JSON array `express.json()` already read is used as is; mount before any other body parser. Next route handlers and Workers expose no socket address and are unsupported.
|
|
96
|
+
|
|
97
|
+
Instances the entry's static imports construct at module scope exist before its body runs. To see those, install from the dev script - `bun --preload @expressive/inspect/install` or `node --import @expressive/inspect/install` - and keep the `/http` import for `handle`.
|
|
98
|
+
|
|
99
|
+
A static import works only with the package as a runtime dependency. A bundled server resolves dynamic imports at build time, so the package must be installed when building.
|
|
100
|
+
|
|
71
101
|
## Journal
|
|
72
102
|
|
|
73
103
|
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.
|
package/dist/bridge.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as Summary, E as Frame, O as Options, S as Select, k as Query, x as Act } from "./inspect-
|
|
1
|
+
import { A as Summary, E as Frame, O as Options, S as Select, k as Query, x as Act } from "./inspect-TS7ZMfCa.js";
|
|
2
2
|
import inspect$1 from "./index.js";
|
|
3
3
|
|
|
4
4
|
//#region src/devtools.d.ts
|
package/dist/bridge.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as unsettled, n as tick, r as unreached, t as bracket } from "./bracket-D3xwitlE.js";
|
|
2
|
-
import { t as dispatch } from "./dispatch-
|
|
2
|
+
import { t as dispatch } from "./dispatch-BmtaBKeH.js";
|
|
3
3
|
|
|
4
4
|
//#region src/devtools.ts
|
|
5
5
|
const describe = (targets) => targets.map((target) => `\n ${target.type} ${target.title} ${target.url}`).join("");
|
|
@@ -4,17 +4,18 @@ function dispatch(first, second) {
|
|
|
4
4
|
const call = second ?? first;
|
|
5
5
|
const api = globalThis.__EXPRESSIVE_INSPECT__;
|
|
6
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
|
+
const missing = () => Object.assign(/* @__PURE__ */ new Error(`No inspector method ${call[0].join(".")}.`), { [Symbol.for("expressive-inspect.missing")]: true });
|
|
7
8
|
let target = api;
|
|
8
9
|
let owner;
|
|
9
10
|
for (const key of call[0]) {
|
|
10
|
-
if (!Object.prototype.hasOwnProperty.call(target, key)) throw
|
|
11
|
+
if (!Object.prototype.hasOwnProperty.call(target, key)) throw missing();
|
|
11
12
|
owner = target;
|
|
12
13
|
target = target[key];
|
|
13
14
|
}
|
|
14
|
-
if (typeof target != "function") throw
|
|
15
|
+
if (typeof target != "function") throw missing();
|
|
15
16
|
return target.apply(owner, call[1]);
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
//#endregion
|
|
19
20
|
export { dispatch as t };
|
|
20
|
-
//# sourceMappingURL=dispatch-
|
|
21
|
+
//# sourceMappingURL=dispatch-BmtaBKeH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch-BmtaBKeH.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 const missing = () =>\n Object.assign(new Error(`No inspector method ${call[0].join('.')}.`), { [Symbol.for('expressive-inspect.missing')]: true });\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)) throw missing();\n\n owner = target;\n target = (target as Record<string, unknown>)[key];\n }\n\n if (typeof target != 'function') throw missing();\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,MAAM,gBACJ,OAAO,uBAAO,IAAI,MAAM,uBAAuB,KAAK,GAAG,KAAK,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,IAAI,6BAA6B,GAAG,MAAM,CAAC;CAE7H,IAAI,SAAkB;CACtB,IAAI;AAEJ,MAAK,MAAM,OAAO,KAAK,IAAI;AACzB,MAAI,CAAC,OAAO,UAAU,eAAe,KAAK,QAAQ,IAAI,CAAE,OAAM,SAAS;AAEvE,UAAQ;AACR,WAAU,OAAmC;;AAG/C,KAAI,OAAO,UAAU,WAAY,OAAM,SAAS;AAEhD,QAAO,OAAO,MAAM,OAAO,KAAK,GAAG"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//#region src/protocol.d.ts
|
|
2
|
+
interface NodeRequest extends AsyncIterable<unknown> {
|
|
3
|
+
method?: string;
|
|
4
|
+
headers: object;
|
|
5
|
+
socket: {
|
|
6
|
+
remoteAddress?: string;
|
|
7
|
+
};
|
|
8
|
+
body?: unknown;
|
|
9
|
+
}
|
|
10
|
+
interface NodeResponse {
|
|
11
|
+
headersSent?: boolean;
|
|
12
|
+
statusCode: number;
|
|
13
|
+
setHeader(name: string, value: string): unknown;
|
|
14
|
+
end(data: string): unknown;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/http.d.ts
|
|
18
|
+
/** Where the caller's address comes from: given outright, Bun's `server`, or Deno's `info`. */
|
|
19
|
+
type Source = {
|
|
20
|
+
ip: string | null | undefined;
|
|
21
|
+
} | {
|
|
22
|
+
requestIP(req: Request): {
|
|
23
|
+
address: string;
|
|
24
|
+
} | null;
|
|
25
|
+
} | {
|
|
26
|
+
remoteAddr: {
|
|
27
|
+
hostname: string;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
/** Serve an installed inspector over HTTP, to loopback callers only. */
|
|
31
|
+
declare function handle(req: Request, source: Source): Promise<Response>;
|
|
32
|
+
declare function handle(req: NodeRequest, res: NodeResponse): Promise<void>;
|
|
33
|
+
//#endregion
|
|
34
|
+
export { handle };
|
|
35
|
+
//# sourceMappingURL=http.d.ts.map
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import "./install.js";
|
|
2
|
+
import { c as reply, i as headers, n as answer, o as local, r as body, s as parse, t as CALL_TIMEOUT } from "./protocol-BApRTxGk.js";
|
|
3
|
+
|
|
4
|
+
//#region src/http.ts
|
|
5
|
+
const USAGE = "GET describes this process; POST with [method, ...args] calls the inspector.";
|
|
6
|
+
async function handle(req, second) {
|
|
7
|
+
if (typeof second.end == "function") {
|
|
8
|
+
const node = req;
|
|
9
|
+
const text = () => body(node).then((text) => text || (Array.isArray(node.body) ? JSON.stringify(node.body) : ""));
|
|
10
|
+
const [status, data] = await respond(node.method, node.socket?.remoteAddress, headers(node), text);
|
|
11
|
+
return reply(second, status, data);
|
|
12
|
+
}
|
|
13
|
+
const web = req;
|
|
14
|
+
const [status, data] = await respond(web.method, address(web, second), web.headers, () => web.text());
|
|
15
|
+
return Response.json(data, { status });
|
|
16
|
+
}
|
|
17
|
+
function address(req, source) {
|
|
18
|
+
if ("ip" in source) return source.ip;
|
|
19
|
+
if ("requestIP" in source) return source.requestIP(req)?.address;
|
|
20
|
+
if ("remoteAddr" in source) return source.remoteAddr.hostname;
|
|
21
|
+
}
|
|
22
|
+
async function respond(method, ip, headers, text) {
|
|
23
|
+
if (!globalThis.__EXPRESSIVE_INSPECT__) return [404, { error: "Inspector not installed." }];
|
|
24
|
+
if (!local(ip, headers)) return [403, { error: "Local callers only; browser and proxied requests are refused." }];
|
|
25
|
+
if (method === "GET") return [200, identity()];
|
|
26
|
+
if (method !== "POST") return [405, { error: USAGE }];
|
|
27
|
+
const call = parse(await text().catch(() => ""));
|
|
28
|
+
if (!call) return [400, { error: "Body must be a JSON array: [method, ...args]." }];
|
|
29
|
+
let timer;
|
|
30
|
+
const expire = new Promise((resolve) => timer = setTimeout(resolve, CALL_TIMEOUT));
|
|
31
|
+
const result = await Promise.race([answer(call), expire]);
|
|
32
|
+
clearTimeout(timer);
|
|
33
|
+
if (!result) return [504, { error: "Call did not answer." }];
|
|
34
|
+
if (result.error) return [result.status ?? 500, { error: result.error }];
|
|
35
|
+
return [200, result.value];
|
|
36
|
+
}
|
|
37
|
+
function identity() {
|
|
38
|
+
const process = globalThis.process;
|
|
39
|
+
return {
|
|
40
|
+
process: true,
|
|
41
|
+
pid: process?.pid ?? null,
|
|
42
|
+
cwd: process?.cwd?.() ?? null,
|
|
43
|
+
started: Math.round(performance.timeOrigin),
|
|
44
|
+
runtime: globalThis.navigator?.userAgent ?? null,
|
|
45
|
+
title: process?.title ?? null
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
export { handle };
|
|
51
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","names":[],"sources":["../src/http.ts"],"sourcesContent":["import './install';\nimport { answer, body, CALL_TIMEOUT, headers, local, parse, reply, type Answer, type NodeRequest, type NodeResponse } from './protocol';\n\n/** Where the caller's address comes from: given outright, Bun's `server`, or Deno's `info`. */\ntype Source =\n | { ip: string | null | undefined }\n | { requestIP(req: Request): { address: string } | null }\n | { remoteAddr: { hostname: string } };\n\ntype Result = [status: number, data: unknown];\n\nconst USAGE = 'GET describes this process; POST with [method, ...args] calls the inspector.';\n\n/** Serve an installed inspector over HTTP, to loopback callers only. */\nexport function handle(req: Request, source: Source): Promise<Response>;\nexport function handle(req: NodeRequest, res: NodeResponse): Promise<void>;\nexport async function handle(req: Request | NodeRequest, second: Source | NodeResponse) {\n if (typeof (second as NodeResponse).end == 'function') {\n const node = req as NodeRequest;\n const text = () => body(node).then((text) => text || (Array.isArray(node.body) ? JSON.stringify(node.body) : ''));\n const [status, data] = await respond(node.method, node.socket?.remoteAddress, headers(node), text);\n\n return reply(second as NodeResponse, status, data);\n }\n\n const web = req as Request;\n const [status, data] = await respond(web.method, address(web, second as Source), web.headers, () => web.text());\n\n return Response.json(data, { status });\n}\n\nfunction address(req: Request, source: Source) {\n if ('ip' in source) return source.ip;\n if ('requestIP' in source) return source.requestIP(req)?.address;\n if ('remoteAddr' in source) return source.remoteAddr.hostname;\n}\n\nasync function respond(\n method: string | undefined,\n ip: string | null | undefined,\n headers: { has(name: string): boolean },\n text: () => Promise<string>\n): Promise<Result> {\n if (!globalThis.__EXPRESSIVE_INSPECT__) return [404, { error: 'Inspector not installed.' }];\n if (!local(ip, headers)) return [403, { error: 'Local callers only; browser and proxied requests are refused.' }];\n if (method === 'GET') return [200, identity()];\n if (method !== 'POST') return [405, { error: USAGE }];\n\n const call = parse(await text().catch(() => ''));\n\n if (!call) return [400, { error: 'Body must be a JSON array: [method, ...args].' }];\n\n let timer: Parameters<typeof clearTimeout>[0];\n const expire = new Promise<undefined>((resolve) => (timer = setTimeout(resolve, CALL_TIMEOUT)));\n const result: Answer | undefined = await Promise.race([answer(call), expire]);\n\n clearTimeout(timer);\n\n if (!result) return [504, { error: 'Call did not answer.' }];\n if (result.error) return [result.status ?? 500, { error: result.error }];\n\n return [200, result.value];\n}\n\nfunction identity() {\n const process = (globalThis as { process?: { pid?: number; title?: string; cwd?(): string } }).process;\n\n return {\n process: true,\n pid: process?.pid ?? null,\n cwd: process?.cwd?.() ?? null,\n started: Math.round(performance.timeOrigin),\n runtime: globalThis.navigator?.userAgent ?? null,\n title: process?.title ?? null\n };\n}\n"],"mappings":";;;;AAWA,MAAM,QAAQ;AAKd,eAAsB,OAAO,KAA4B,QAA+B;AACtF,KAAI,OAAQ,OAAwB,OAAO,YAAY;EACrD,MAAM,OAAO;EACb,MAAM,aAAa,KAAK,KAAK,CAAC,MAAM,SAAS,SAAS,MAAM,QAAQ,KAAK,KAAK,GAAG,KAAK,UAAU,KAAK,KAAK,GAAG,IAAI;EACjH,MAAM,CAAC,QAAQ,QAAQ,MAAM,QAAQ,KAAK,QAAQ,KAAK,QAAQ,eAAe,QAAQ,KAAK,EAAE,KAAK;AAElG,SAAO,MAAM,QAAwB,QAAQ,KAAK;;CAGpD,MAAM,MAAM;CACZ,MAAM,CAAC,QAAQ,QAAQ,MAAM,QAAQ,IAAI,QAAQ,QAAQ,KAAK,OAAiB,EAAE,IAAI,eAAe,IAAI,MAAM,CAAC;AAE/G,QAAO,SAAS,KAAK,MAAM,EAAE,QAAQ,CAAC;;AAGxC,SAAS,QAAQ,KAAc,QAAgB;AAC7C,KAAI,QAAQ,OAAQ,QAAO,OAAO;AAClC,KAAI,eAAe,OAAQ,QAAO,OAAO,UAAU,IAAI,EAAE;AACzD,KAAI,gBAAgB,OAAQ,QAAO,OAAO,WAAW;;AAGvD,eAAe,QACb,QACA,IACA,SACA,MACiB;AACjB,KAAI,CAAC,WAAW,uBAAwB,QAAO,CAAC,KAAK,EAAE,OAAO,4BAA4B,CAAC;AAC3F,KAAI,CAAC,MAAM,IAAI,QAAQ,CAAE,QAAO,CAAC,KAAK,EAAE,OAAO,iEAAiE,CAAC;AACjH,KAAI,WAAW,MAAO,QAAO,CAAC,KAAK,UAAU,CAAC;AAC9C,KAAI,WAAW,OAAQ,QAAO,CAAC,KAAK,EAAE,OAAO,OAAO,CAAC;CAErD,MAAM,OAAO,MAAM,MAAM,MAAM,CAAC,YAAY,GAAG,CAAC;AAEhD,KAAI,CAAC,KAAM,QAAO,CAAC,KAAK,EAAE,OAAO,iDAAiD,CAAC;CAEnF,IAAI;CACJ,MAAM,SAAS,IAAI,SAAoB,YAAa,QAAQ,WAAW,SAAS,aAAa,CAAE;CAC/F,MAAM,SAA6B,MAAM,QAAQ,KAAK,CAAC,OAAO,KAAK,EAAE,OAAO,CAAC;AAE7E,cAAa,MAAM;AAEnB,KAAI,CAAC,OAAQ,QAAO,CAAC,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAC5D,KAAI,OAAO,MAAO,QAAO,CAAC,OAAO,UAAU,KAAK,EAAE,OAAO,OAAO,OAAO,CAAC;AAExE,QAAO,CAAC,KAAK,OAAO,MAAM;;AAG5B,SAAS,WAAW;CAClB,MAAM,UAAW,WAA8E;AAE/F,QAAO;EACL,SAAS;EACT,KAAK,SAAS,OAAO;EACrB,KAAK,SAAS,OAAO,IAAI;EACzB,SAAS,KAAK,MAAM,YAAY,WAAW;EAC3C,SAAS,WAAW,WAAW,aAAa;EAC5C,OAAO,SAAS,SAAS;EAC1B"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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 ModelInfo, s as call, t as Health, u as get, v as TypeInfo, w as serialize, x as Act, y as label } from "./inspect-
|
|
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 ModelInfo, s as call, t as Health, u as get, v as TypeInfo, w as serialize, x as Act, y as label } from "./inspect-TS7ZMfCa.js";
|
|
2
2
|
|
|
3
3
|
//#region src/index.d.ts
|
|
4
4
|
declare const journal: {
|
|
@@ -161,4 +161,4 @@ declare function set(address: string, value: unknown): void;
|
|
|
161
161
|
declare function call(address: string, ...args: unknown[]): Promise<unknown>;
|
|
162
162
|
//#endregion
|
|
163
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, ModelInfo 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-
|
|
164
|
+
//# sourceMappingURL=inspect-TS7ZMfCa.d.ts.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { n as tick, t as bracket } from "./bracket-D3xwitlE.js";
|
|
2
|
+
import { t as dispatch } from "./dispatch-BmtaBKeH.js";
|
|
3
|
+
|
|
4
|
+
//#region src/protocol.ts
|
|
5
|
+
const CALL_TIMEOUT = 1e4;
|
|
6
|
+
const FOREIGN = [
|
|
7
|
+
"origin",
|
|
8
|
+
"sec-fetch-site",
|
|
9
|
+
"forwarded",
|
|
10
|
+
"x-forwarded-for",
|
|
11
|
+
"x-real-ip",
|
|
12
|
+
"cf-connecting-ip"
|
|
13
|
+
];
|
|
14
|
+
const LOOPBACK = /^((::ffff:)?127(\.\d{1,3}){3}|::1)$/;
|
|
15
|
+
const MISSING = Symbol.for("expressive-inspect.missing");
|
|
16
|
+
const FAILED = "Call failed.";
|
|
17
|
+
function local(ip, headers) {
|
|
18
|
+
return !FOREIGN.some((header) => headers.has(header)) && LOOPBACK.test(ip ?? "");
|
|
19
|
+
}
|
|
20
|
+
function parse(text) {
|
|
21
|
+
try {
|
|
22
|
+
const input = JSON.parse(text);
|
|
23
|
+
if (Array.isArray(input) && typeof input[0] == "string") return [input[0].split("."), input.slice(1)];
|
|
24
|
+
} catch {}
|
|
25
|
+
}
|
|
26
|
+
const journalCall = (method, ...args) => dispatch([["journal", method], args]);
|
|
27
|
+
async function act(step, options) {
|
|
28
|
+
if (!Array.isArray(step) || typeof step[0] != "string") throw new Error("act takes one call: [\"act\", [method, ...args], options?].");
|
|
29
|
+
const { value, frames, settled, pending, missing } = await bracket({
|
|
30
|
+
record: (...options) => journalCall("record", ...options),
|
|
31
|
+
seq: () => journalCall("seq"),
|
|
32
|
+
frames: (query) => journalCall("frames", query),
|
|
33
|
+
get: (address) => dispatch([["get"], [address]])
|
|
34
|
+
}, () => dispatch([step[0].split("."), step.slice(1)]), tick, options, true);
|
|
35
|
+
return {
|
|
36
|
+
value: value ?? null,
|
|
37
|
+
frames,
|
|
38
|
+
settled,
|
|
39
|
+
pending,
|
|
40
|
+
missing
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async function answer(call) {
|
|
44
|
+
try {
|
|
45
|
+
const value = call[0].join(".") == "act" ? await act(call[1][0], call[1][1]) : await dispatch(call);
|
|
46
|
+
return { value: JSON.parse(JSON.stringify(value) ?? "null") };
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (!(error instanceof Error)) return { error: String(error) || FAILED };
|
|
49
|
+
return {
|
|
50
|
+
error: error.message || FAILED,
|
|
51
|
+
status: MISSING in error ? 404 : void 0
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function headers({ headers }) {
|
|
56
|
+
const fetched = typeof headers.has == "function";
|
|
57
|
+
return { has: (name) => fetched ? headers.has(name) : name in headers };
|
|
58
|
+
}
|
|
59
|
+
async function body(req) {
|
|
60
|
+
const decoder = new TextDecoder();
|
|
61
|
+
let text = "";
|
|
62
|
+
for await (const chunk of req) text += typeof chunk == "string" ? chunk : decoder.decode(chunk, { stream: true });
|
|
63
|
+
return text + decoder.decode();
|
|
64
|
+
}
|
|
65
|
+
function reply(res, status, data) {
|
|
66
|
+
if (res.headersSent) return;
|
|
67
|
+
res.statusCode = status;
|
|
68
|
+
res.setHeader("Content-Type", "application/json");
|
|
69
|
+
res.end(JSON.stringify(data ?? null));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
export { journalCall as a, reply as c, headers as i, answer as n, local as o, body as r, parse as s, CALL_TIMEOUT as t };
|
|
74
|
+
//# sourceMappingURL=protocol-BApRTxGk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol-BApRTxGk.js","names":[],"sources":["../src/protocol.ts"],"sourcesContent":["import { bracket } from './bracket';\nimport { dispatch, type Call } from './dispatch';\nimport type { Options, Query } from './journal';\nimport { tick, type Act } from './settle';\n\nexport const CALL_TIMEOUT = 10000;\n\nexport type Answer = { value?: unknown; error?: string; status?: number };\n\nexport interface NodeRequest extends AsyncIterable<unknown> {\n method?: string;\n headers: object;\n socket: { remoteAddress?: string };\n body?: unknown;\n}\n\nexport interface NodeResponse {\n headersSent?: boolean;\n statusCode: number;\n setHeader(name: string, value: string): unknown;\n end(data: string): unknown;\n}\n\nconst FOREIGN = ['origin', 'sec-fetch-site', 'forwarded', 'x-forwarded-for', 'x-real-ip', 'cf-connecting-ip'];\nconst LOOPBACK = /^((::ffff:)?127(\\.\\d{1,3}){3}|::1)$/;\nconst MISSING = Symbol.for('expressive-inspect.missing');\nconst FAILED = 'Call failed.';\n\nexport function local(ip: string | null | undefined, headers: { has(name: string): boolean }) {\n return !FOREIGN.some((header) => headers.has(header)) && LOOPBACK.test(ip ?? '');\n}\n\nexport function 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\nexport const journalCall = (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[]) => journalCall('record', ...options),\n seq: () => journalCall('seq'),\n frames: (query: Query) => journalCall('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\nexport async function answer(call: Call): Promise<Answer> {\n try {\n const value = call[0].join('.') == 'act' ? await act(call[1][0], call[1][1] as Act) : await dispatch(call);\n\n return { value: JSON.parse(JSON.stringify(value) ?? 'null') };\n } catch (error) {\n if (!(error instanceof Error)) return { error: String(error) || FAILED };\n\n return { error: error.message || FAILED, status: MISSING in error ? 404 : undefined };\n }\n}\n\nexport function headers({ headers }: NodeRequest) {\n const fetched = typeof (headers as Headers).has == 'function';\n\n return { has: (name: string) => (fetched ? (headers as Headers).has(name) : name in headers) };\n}\n\nexport async function body(req: NodeRequest) {\n const decoder = new TextDecoder();\n let text = '';\n\n for await (const chunk of req) text += typeof chunk == 'string' ? chunk : decoder.decode(chunk as Uint8Array, { stream: true });\n\n return text + decoder.decode();\n}\n\nexport function reply(res: NodeResponse, status: number, data: unknown) {\n if (res.headersSent) return;\n\n res.statusCode = status;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify(data ?? null));\n}\n"],"mappings":";;;;AAKA,MAAa,eAAe;AAkB5B,MAAM,UAAU;CAAC;CAAU;CAAkB;CAAa;CAAmB;CAAa;CAAmB;AAC7G,MAAM,WAAW;AACjB,MAAM,UAAU,OAAO,IAAI,6BAA6B;AACxD,MAAM,SAAS;AAEf,SAAgB,MAAM,IAA+B,SAAyC;AAC5F,QAAO,CAAC,QAAQ,MAAM,WAAW,QAAQ,IAAI,OAAO,CAAC,IAAI,SAAS,KAAK,MAAM,GAAG;;AAGlF,SAAgB,MAAM,MAAgC;AACpD,KAAI;EACF,MAAM,QAAiB,KAAK,MAAM,KAAK;AAEvC,MAAI,MAAM,QAAQ,MAAM,IAAI,OAAO,MAAM,MAAM,SAAU,QAAO,CAAC,MAAM,GAAG,MAAM,IAAI,EAAE,MAAM,MAAM,EAAE,CAAC;SAC/F;;AAGV,MAAa,eAAe,QAAgB,GAAG,SAAoB,SAAS,CAAC,CAAC,WAAW,OAAO,EAAE,KAAK,CAAC;AAExG,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,YAAY,UAAU,GAAG,QAAQ;EACpE,WAAW,YAAY,MAAM;EAC7B,SAAS,UAAiB,YAAY,UAAU,MAAM;EACtD,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,eAAsB,OAAO,MAA6B;AACxD,KAAI;EACF,MAAM,QAAQ,KAAK,GAAG,KAAK,IAAI,IAAI,QAAQ,MAAM,IAAI,KAAK,GAAG,IAAI,KAAK,GAAG,GAAU,GAAG,MAAM,SAAS,KAAK;AAE1G,SAAO,EAAE,OAAO,KAAK,MAAM,KAAK,UAAU,MAAM,IAAI,OAAO,EAAE;UACtD,OAAO;AACd,MAAI,EAAE,iBAAiB,OAAQ,QAAO,EAAE,OAAO,OAAO,MAAM,IAAI,QAAQ;AAExE,SAAO;GAAE,OAAO,MAAM,WAAW;GAAQ,QAAQ,WAAW,QAAQ,MAAM;GAAW;;;AAIzF,SAAgB,QAAQ,EAAE,WAAwB;CAChD,MAAM,UAAU,OAAQ,QAAoB,OAAO;AAEnD,QAAO,EAAE,MAAM,SAAkB,UAAW,QAAoB,IAAI,KAAK,GAAG,QAAQ,SAAU;;AAGhG,eAAsB,KAAK,KAAkB;CAC3C,MAAM,UAAU,IAAI,aAAa;CACjC,IAAI,OAAO;AAEX,YAAW,MAAM,SAAS,IAAK,SAAQ,OAAO,SAAS,WAAW,QAAQ,QAAQ,OAAO,OAAqB,EAAE,QAAQ,MAAM,CAAC;AAE/H,QAAO,OAAO,QAAQ,QAAQ;;AAGhC,SAAgB,MAAM,KAAmB,QAAgB,MAAe;AACtE,KAAI,IAAI,YAAa;AAErB,KAAI,aAAa;AACjB,KAAI,UAAU,gBAAgB,mBAAmB;AACjD,KAAI,IAAI,KAAK,UAAU,QAAQ,KAAK,CAAC"}
|
package/dist/vite/client.d.ts
CHANGED
package/dist/vite/client.js
CHANGED
|
@@ -1,24 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as dispatch } from "../dispatch-Dnd88-J9.js";
|
|
1
|
+
import { a as journalCall, n as answer } from "../protocol-BApRTxGk.js";
|
|
3
2
|
|
|
4
3
|
//#region src/vite/client.ts
|
|
5
|
-
const journal = (method, ...args) => dispatch([["journal", method], args]);
|
|
6
|
-
async function act(step, options) {
|
|
7
|
-
if (!Array.isArray(step) || typeof step[0] != "string") throw new Error("act takes one call: [\"act\", [method, ...args], options?].");
|
|
8
|
-
const { value, frames, settled, pending, missing } = await bracket({
|
|
9
|
-
record: (...options) => journal("record", ...options),
|
|
10
|
-
seq: () => journal("seq"),
|
|
11
|
-
frames: (query) => journal("frames", query),
|
|
12
|
-
get: (address) => dispatch([["get"], [address]])
|
|
13
|
-
}, () => dispatch([step[0].split("."), step.slice(1)]), tick, options, true);
|
|
14
|
-
return {
|
|
15
|
-
value: value ?? null,
|
|
16
|
-
frames,
|
|
17
|
-
settled,
|
|
18
|
-
pending,
|
|
19
|
-
missing
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
4
|
const RELOAD = "@expressive/inspect.reload";
|
|
23
5
|
/** Keep a reload's reason past the reload, for the next page's journal. */
|
|
24
6
|
function remember(reason) {
|
|
@@ -36,11 +18,11 @@ function recall() {
|
|
|
36
18
|
}
|
|
37
19
|
function connect(hot) {
|
|
38
20
|
const id = Math.random().toString(36).slice(2, 8);
|
|
39
|
-
if (
|
|
21
|
+
if (journalCall("record").level === "off") journalCall("record", { level: "keys" });
|
|
40
22
|
const reloaded = recall();
|
|
41
|
-
if (reloaded)
|
|
23
|
+
if (reloaded) journalCall("hot", "reload", reloaded);
|
|
42
24
|
hot.on("vite:beforeUpdate", ({ updates }) => {
|
|
43
|
-
|
|
25
|
+
journalCall("hot", "update", updates.map((update) => update.path));
|
|
44
26
|
});
|
|
45
27
|
hot.on("vite:beforeFullReload", ({ path, triggeredBy } = {}) => {
|
|
46
28
|
remember({
|
|
@@ -50,23 +32,16 @@ function connect(hot) {
|
|
|
50
32
|
});
|
|
51
33
|
globalThis.addEventListener?.("expressive:reload", (event) => remember(event.detail));
|
|
52
34
|
hot.on("expressive-inspect:ask", async ({ rid, call }) => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
});
|
|
64
|
-
} catch (error) {
|
|
65
|
-
hot.send("expressive-inspect:answer", {
|
|
66
|
-
rid,
|
|
67
|
-
error: error instanceof Error ? error.message : String(error)
|
|
68
|
-
});
|
|
69
|
-
}
|
|
35
|
+
const reply = call ? await answer(call) : { value: {
|
|
36
|
+
id,
|
|
37
|
+
url: location.href,
|
|
38
|
+
title: document.title,
|
|
39
|
+
top: window.self === window.top
|
|
40
|
+
} };
|
|
41
|
+
hot.send("expressive-inspect:answer", {
|
|
42
|
+
rid,
|
|
43
|
+
...reply
|
|
44
|
+
});
|
|
70
45
|
});
|
|
71
46
|
hot.send("expressive-inspect:hello", { id });
|
|
72
47
|
}
|
package/dist/vite/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":[],"sources":["../../src/vite/client.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../../src/vite/client.ts"],"sourcesContent":["import type { Call } from '../dispatch';\nimport { answer, journalCall } from '../protocol';\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 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 (journalCall('record').level === 'off') journalCall('record', { level: 'keys' });\n\n const reloaded = recall();\n\n if (reloaded) journalCall('hot', 'reload', reloaded);\n\n hot.on('vite:beforeUpdate', ({ updates }: { updates: { path: string }[] }) => {\n journalCall('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 const reply = call\n ? await answer(call)\n : { value: { id, url: location.href, title: document.title, top: window.self === window.top } };\n\n hot.send('expressive-inspect:answer', { rid, ...reply });\n });\n\n hot.send('expressive-inspect:hello', { id });\n}\n"],"mappings":";;;AAaA,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,YAAY,SAAS,CAAC,UAAU,MAAO,aAAY,UAAU,EAAE,OAAO,QAAQ,CAAC;CAEnF,MAAM,WAAW,QAAQ;AAEzB,KAAI,SAAU,aAAY,OAAO,UAAU,SAAS;AAEpD,KAAI,GAAG,sBAAsB,EAAE,cAA+C;AAC5E,cAAY,OAAO,UAAU,QAAQ,KAAK,WAAW,OAAO,KAAK,CAAC;GAClE;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;EAC7D,MAAM,QAAQ,OACV,MAAM,OAAO,KAAK,GAClB,EAAE,OAAO;GAAE;GAAI,KAAK,SAAS;GAAM,OAAO,SAAS;GAAO,KAAK,OAAO,SAAS,OAAO;GAAK,EAAE;AAEjG,MAAI,KAAK,6BAA6B;GAAE;GAAK,GAAG;GAAO,CAAC;GACxD;AAEF,KAAI,KAAK,4BAA4B,EAAE,IAAI,CAAC"}
|
package/dist/vite.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { c as reply, i as headers, o as local, r as body, s as parse, t as CALL_TIMEOUT } from "./protocol-BApRTxGk.js";
|
|
2
|
+
|
|
1
3
|
//#region src/vite/plugin.ts
|
|
2
4
|
const VIRTUAL = "virtual:expressive-inspect";
|
|
3
5
|
const RESOLVED = "\0" + VIRTUAL;
|
|
@@ -7,7 +9,6 @@ import { connect } from '@expressive/inspect/vite/client';
|
|
|
7
9
|
if (import.meta.hot) connect(import.meta.hot);
|
|
8
10
|
`;
|
|
9
11
|
const LIST_TIMEOUT = 1e3;
|
|
10
|
-
const CALL_TIMEOUT = 1e4;
|
|
11
12
|
const USAGE = "GET /__inspect lists pages; POST /__inspect/:id with [method, ...args] calls one.";
|
|
12
13
|
function plugin(url) {
|
|
13
14
|
let base = "/";
|
|
@@ -76,7 +77,7 @@ function relay(ws) {
|
|
|
76
77
|
return (await Promise.all([...live().values()].map((client) => ask(client, void 0, LIST_TIMEOUT)))).flatMap((answer) => answer && !answer.error ? [answer.value] : []);
|
|
77
78
|
}
|
|
78
79
|
return async (req, res) => {
|
|
79
|
-
if (!local(req)) return reply(res, 403, { error: "Local callers only; browser and proxied requests are refused." });
|
|
80
|
+
if (!local(req.socket.remoteAddress, headers(req))) return reply(res, 403, { error: "Local callers only; browser and proxied requests are refused." });
|
|
80
81
|
const id = decodeURIComponent(req.url.split("?")[0].slice(1));
|
|
81
82
|
if (req.method === "GET" && !id) return reply(res, 200, await list());
|
|
82
83
|
if (req.method !== "POST") return reply(res, 405, { error: USAGE });
|
|
@@ -93,37 +94,10 @@ function relay(ws) {
|
|
|
93
94
|
}
|
|
94
95
|
const answer = await ask(client, call, CALL_TIMEOUT);
|
|
95
96
|
if (!answer) return reply(res, 504, { error: "Page did not answer." });
|
|
96
|
-
if (answer.error) return reply(res, 500, { error: answer.error });
|
|
97
|
+
if (answer.error) return reply(res, answer.status ?? 500, { error: answer.error });
|
|
97
98
|
reply(res, 200, answer.value);
|
|
98
99
|
};
|
|
99
100
|
}
|
|
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
101
|
|
|
128
102
|
//#endregion
|
|
129
103
|
//#region src/vite/index.ts
|
package/dist/vite.js.map
CHANGED
|
@@ -1 +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;\
|
|
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';\nimport { body, CALL_TIMEOUT, headers, local, parse, reply, type Answer } from '../protocol';\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;\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\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.socket.remoteAddress, headers(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, answer.status ?? 500, { error: answer.error });\n\n reply(res, 200, answer.value);\n };\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.
|
|
3
|
+
"version": "0.6.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",
|
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
"type": "module",
|
|
26
26
|
"sideEffects": [
|
|
27
27
|
"./dist/install.js",
|
|
28
|
-
"./src/install.ts"
|
|
28
|
+
"./src/install.ts",
|
|
29
|
+
"./dist/http.js",
|
|
30
|
+
"./src/http.ts"
|
|
29
31
|
],
|
|
30
32
|
"main": "dist/index.js",
|
|
31
33
|
"types": "dist/index.d.ts",
|
|
@@ -38,6 +40,10 @@
|
|
|
38
40
|
"types": "./dist/install.d.ts",
|
|
39
41
|
"default": "./dist/install.js"
|
|
40
42
|
},
|
|
43
|
+
"./http": {
|
|
44
|
+
"types": "./dist/http.d.ts",
|
|
45
|
+
"default": "./dist/http.js"
|
|
46
|
+
},
|
|
41
47
|
"./bridge": {
|
|
42
48
|
"types": "./dist/bridge.d.ts",
|
|
43
49
|
"default": "./dist/bridge.js"
|
|
@@ -64,7 +70,7 @@
|
|
|
64
70
|
"test": "bun run typecheck && bun run coverage"
|
|
65
71
|
},
|
|
66
72
|
"peerDependencies": {
|
|
67
|
-
"@expressive/mvc": "^0.
|
|
73
|
+
"@expressive/mvc": "^0.92.0",
|
|
68
74
|
"vite": ">=5"
|
|
69
75
|
},
|
|
70
76
|
"peerDependenciesMeta": {
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|