@expressive/inspect 0.0.1

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 ADDED
@@ -0,0 +1,64 @@
1
+ <h1 align="center">@expressive/inspect</h1>
2
+
3
+ <p align="center">
4
+ In-process inspector for Expressive MVC state.
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://www.npmjs.com/package/@expressive/inspect"><img alt="NPM" src="https://badge.fury.io/js/%40expressive%2Finspect.svg"></a>
9
+ </p>
10
+
11
+ ---
12
+
13
+ Query the live model graph of an [Expressive MVC](https://github.com/gabeklein/expressive-mvc) app from a test, a Playwright spec, or the browser console. No UI, no network - a base layer for agents, devtools, and test helpers.
14
+
15
+ ```bash
16
+ npm install --save-dev @expressive/inspect
17
+ ```
18
+
19
+ ## Install into the app
20
+
21
+ ```ts
22
+ // first import of the app entry, before any State is constructed
23
+ import '@expressive/inspect/install';
24
+ ```
25
+
26
+ Attaches to every `State` and publishes `globalThis.__EXPRESSIVE_INSPECT__`. For a shipped build, call `attach()` behind your own dev flag instead of the side-effect import.
27
+
28
+ ## In process
29
+
30
+ ```ts
31
+ import { find, roots } from '@expressive/inspect';
32
+
33
+ const composer = find('Composer'); // by label, or by id
34
+ composer.state // the raw instance
35
+ composer.parent / composer.children // ownership
36
+ composer.get('draft') // stored value, no getter fires
37
+ const frames = await composer.act((s) => s.submit('hi')); // run, settle, see what changed
38
+ ```
39
+
40
+ ## Across a boundary
41
+
42
+ ```ts
43
+ import { inspect } from '@expressive/inspect/playwright';
44
+
45
+ const api = inspect(page); // Page, Frame, or Locator
46
+ await api.get('Composer.draft');
47
+ const frames = await api.around(() => page.click('#submit'));
48
+ ```
49
+
50
+ Console: `__EXPRESSIVE_INSPECT__.get('Composer.draft')`, `__EXPRESSIVE_INSPECT__.journal.record({ level: 'keys', paths: ['Composer.draft'] })`.
51
+
52
+ ## Journal
53
+
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.
55
+
56
+ ## Orphans
57
+
58
+ Instances a host never committed - a render thrown away, a StrictMode twin - stay out of `models()` and `tree()`. `orphans()` lists them, `warnings()` counts them.
59
+
60
+ ## Minified builds
61
+
62
+ Property names survive minification; class names do not. Each class gets a stable `typeId` and a construction `site`. Name them with `label(Type, 'Composer')`, `static displayName`, or `resolve({ T3: 'Composer' })`, or keep names at build time (`keepNames`, `keep_classnames`).
63
+
64
+ Full reference: [skills/inspect.md](https://github.com/gabeklein/expressive-mvc/blob/main/skills/inspect.md).
@@ -0,0 +1,54 @@
1
+ import { C as Level, E as act, S as Frame, T as Query, _ as warnings, a as Node, b as resolve, c as detach, d as instances, f as models, g as tree, h as set, i as Model, l as find, m as roots, n as serialize, o as attach, p as orphans, r as Instance, s as call, t as parsePath, u as get, v as TypeInfo, w as Options, x as Event, y as label } from "./serialize-FF81tr-L.js";
2
+
3
+ //#region src/index.d.ts
4
+ declare const journal: {
5
+ record(options?: Options): Required<Options>;
6
+ frames(query?: Query): Frame[];
7
+ history(query: Query): Array<{
8
+ seq: number;
9
+ at: number;
10
+ event: Event;
11
+ }>;
12
+ downstream(from: number): Frame[];
13
+ seq(): number;
14
+ export(query?: Query): string;
15
+ clear(): void;
16
+ reset(): void;
17
+ };
18
+ declare const inspect: {
19
+ attach: typeof attach;
20
+ detach: typeof detach;
21
+ find: typeof find;
22
+ instances: typeof instances;
23
+ roots: typeof roots;
24
+ orphans: typeof orphans;
25
+ warnings: typeof warnings;
26
+ models: typeof models;
27
+ tree: typeof tree;
28
+ get: typeof get;
29
+ set: typeof set;
30
+ call: typeof call;
31
+ act: typeof act;
32
+ label: typeof label;
33
+ resolve: typeof resolve;
34
+ journal: {
35
+ record(options?: Options): Required<Options>;
36
+ frames(query?: Query): Frame[];
37
+ history(query: Query): Array<{
38
+ seq: number;
39
+ at: number;
40
+ event: Event;
41
+ }>;
42
+ downstream(from: number): Frame[];
43
+ seq(): number;
44
+ export(query?: Query): string;
45
+ clear(): void;
46
+ reset(): void;
47
+ };
48
+ };
49
+ declare global {
50
+ var __EXPRESSIVE_INSPECT__: typeof inspect | undefined;
51
+ }
52
+ //#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, warnings };
54
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
1
+ import { _ as label, a as find, b as serialize, c as models, d as set, f as tree, g as journal$1, h as act, i as detach, l as orphans, m as wrapAll, n as attach, o as get, p as warnings, r as call, s as instances, t as Instance, u as roots, v as resolve, y as parsePath } from "./inspect-D4NC5Mx5.js";
2
+
3
+ //#region src/index.ts
4
+ const journal = {
5
+ ...journal$1,
6
+ record(options) {
7
+ const config = journal$1.record(options);
8
+ if (config.calls && config.level !== "off") wrapAll();
9
+ return config;
10
+ }
11
+ };
12
+ const inspect = {
13
+ attach,
14
+ detach,
15
+ find,
16
+ instances,
17
+ roots,
18
+ orphans,
19
+ warnings,
20
+ models,
21
+ tree,
22
+ get,
23
+ set,
24
+ call,
25
+ act,
26
+ label,
27
+ resolve,
28
+ journal
29
+ };
30
+
31
+ //#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, warnings };
33
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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, warnings, wrapAll } from './inspect';\nimport { act, journal as base, type Options } from './journal';\nimport { label, resolve } from './types';\n\nexport type { Model, Node } from './inspect';\nexport type { Event, Frame, Level, Options, Query } from './journal';\nexport type { TypeInfo } from './types';\nexport { Instance, act, attach, call, detach, find, get, instances, label, models, orphans, resolve, roots, set, tree, warnings };\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};\n\nexport const inspect = {\n attach,\n detach,\n find,\n instances,\n roots,\n orphans,\n warnings,\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":";;;AAUA,MAAa,UAAU;CACrB,GAAGA;CACH,OAAO,SAAmB;EACxB,MAAM,SAASA,UAAK,OAAO,QAAQ;AACnC,MAAI,OAAO,SAAS,OAAO,UAAU,MAAO,UAAS;AACrD,SAAO;;CAEV;AAED,MAAa,UAAU;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD"}
@@ -0,0 +1,641 @@
1
+ import { Context, State } from "@expressive/mvc";
2
+ import { isElement } from "@expressive/mvc/runtime";
3
+
4
+ //#region src/serialize.ts
5
+ const MAX_STRING = 240;
6
+ const MAX_ARRAY = 24;
7
+ const MAX_KEYS = 40;
8
+ function parsePath(raw) {
9
+ const trimmed = raw.trim();
10
+ const dot = trimmed.indexOf(".");
11
+ if (dot <= 0) return { target: trimmed };
12
+ return {
13
+ target: trimmed.slice(0, dot),
14
+ path: trimmed.slice(dot + 1)
15
+ };
16
+ }
17
+ /** Raw stored entries of a State - never reads through accessors. */
18
+ function entries(state) {
19
+ return new Map(state);
20
+ }
21
+ /** Walk `path` without triggering getters, factories, or suspense. */
22
+ function walk(value, path) {
23
+ if (!path) return value;
24
+ let cur = value;
25
+ for (const part of path.split(".")) {
26
+ if (cur == null || typeof cur !== "object") return void 0;
27
+ if (cur instanceof State) cur = entries(cur).get(part);
28
+ else if (cur instanceof Map) cur = cur.get(part);
29
+ else cur = cur[part];
30
+ }
31
+ return cur;
32
+ }
33
+ function ref(state) {
34
+ return {
35
+ $ref: String(state),
36
+ $type: state.constructor.name
37
+ };
38
+ }
39
+ /** JSON-safe, size-capped view. Nested States collapse to `$ref`. */
40
+ function serialize(value, depth = 2) {
41
+ if (value instanceof State) {
42
+ const out = ref(value);
43
+ for (const [key, child] of [...entries(value)].slice(0, MAX_KEYS)) out[key] = write(child, depth - 1);
44
+ return out;
45
+ }
46
+ return write(value, depth);
47
+ }
48
+ function write(value, depth) {
49
+ if (value == null || typeof value === "boolean") return value;
50
+ if (typeof value === "number") return Number.isFinite(value) ? value : String(value);
51
+ if (typeof value === "string") return value.length > MAX_STRING ? `${value.slice(0, MAX_STRING - 1)}…` : value;
52
+ if (typeof value === "bigint") return `${value}n`;
53
+ if (typeof value !== "object") return void 0;
54
+ if (value instanceof State) return ref(value);
55
+ if (value instanceof Date) return value.toISOString();
56
+ if (value instanceof Error) return {
57
+ name: value.name,
58
+ message: value.message
59
+ };
60
+ if (typeof value.then === "function") return "(pending)";
61
+ const items = list(value);
62
+ if (items) {
63
+ if (depth <= 0) return `[${items.length}]`;
64
+ const out = items.slice(0, MAX_ARRAY).map((row) => write(row, depth - 1));
65
+ if (items.length > MAX_ARRAY) out.push(`…+${items.length - MAX_ARRAY}`);
66
+ return out;
67
+ }
68
+ const pairs = value instanceof Map ? [...value].map(([k, v]) => [String(k), v]) : Object.entries(value);
69
+ if (depth <= 0) return `{${pairs.length}}`;
70
+ const out = {};
71
+ for (const [key, child] of pairs.slice(0, MAX_KEYS)) {
72
+ if (key === "is") continue;
73
+ const next = write(child, depth - 1);
74
+ if (next !== void 0) out[key] = next;
75
+ }
76
+ if (pairs.length > MAX_KEYS) out["…"] = pairs.length - MAX_KEYS;
77
+ return out;
78
+ }
79
+ function list(value) {
80
+ if (Array.isArray(value)) return value;
81
+ if (value instanceof Map) return void 0;
82
+ if (typeof value[Symbol.iterator] === "function") return [...value];
83
+ }
84
+
85
+ //#endregion
86
+ //#region src/types.ts
87
+ const info = /* @__PURE__ */ new Map();
88
+ const labels = /* @__PURE__ */ new Map();
89
+ const names = /* @__PURE__ */ new Map();
90
+ let count = 0;
91
+ function register(Type) {
92
+ let row = info.get(Type);
93
+ if (!row) {
94
+ const site = String((/* @__PURE__ */ new Error()).stack).split("\n").slice(1, 16).join("\n");
95
+ info.set(Type, row = {
96
+ typeId: `T${++count}`,
97
+ type: "",
98
+ site
99
+ });
100
+ }
101
+ return row;
102
+ }
103
+ function seen(Type) {
104
+ const row = register(Type);
105
+ row.type = labelOf(Type);
106
+ return { ...row };
107
+ }
108
+ function labelOf(Type) {
109
+ const row = register(Type);
110
+ const explicit = labels.get(Type) ?? Type.displayName;
111
+ if (explicit) return explicit;
112
+ const named = names.get(row.typeId) ?? names.get(row.site);
113
+ if (named) return named;
114
+ return Type.name.length > 2 ? Type.name : row.typeId;
115
+ }
116
+ function label(Type, name) {
117
+ labels.set(Type, name);
118
+ }
119
+ /** Supply resolved names keyed by `typeId` or `site`, e.g. from a source-map pass. */
120
+ function resolve(table) {
121
+ for (const [key, name] of Object.entries(table)) names.set(key, name);
122
+ }
123
+ function forget() {
124
+ info.clear();
125
+ labels.clear();
126
+ names.clear();
127
+ count = 0;
128
+ }
129
+
130
+ //#endregion
131
+ //#region src/journal.ts
132
+ const FRAME_CAP = 500;
133
+ const frames = [];
134
+ const config = {
135
+ level: "off",
136
+ types: [],
137
+ paths: [],
138
+ keys: [],
139
+ calls: false
140
+ };
141
+ let seq = 0;
142
+ let open;
143
+ let cause;
144
+ const journal = {
145
+ record(options = {}) {
146
+ Object.assign(config, options);
147
+ if (!options.level && options.calls && config.level === "off") config.level = "keys";
148
+ return {
149
+ ...config,
150
+ types: [...config.types],
151
+ paths: [...config.paths],
152
+ keys: [...config.keys]
153
+ };
154
+ },
155
+ frames(query = {}) {
156
+ const out = [];
157
+ for (const frame of frames) {
158
+ if (query.since !== void 0 && frame.seq <= query.since) continue;
159
+ if (query.cause !== void 0 && frame.cause !== query.cause) continue;
160
+ const events = frame.events.filter((e) => matches(e, query));
161
+ if (events.length) out.push({
162
+ ...frame,
163
+ events
164
+ });
165
+ }
166
+ return out;
167
+ },
168
+ history(query) {
169
+ const out = [];
170
+ for (const frame of journal.frames(query)) for (const event of frame.events) out.push({
171
+ seq: frame.seq,
172
+ at: frame.at,
173
+ event
174
+ });
175
+ return out;
176
+ },
177
+ downstream(from) {
178
+ const roots = new Set([from]);
179
+ return frames.filter((frame) => {
180
+ if (frame.cause === void 0 || !roots.has(frame.cause)) return false;
181
+ roots.add(frame.seq);
182
+ return true;
183
+ });
184
+ },
185
+ seq() {
186
+ return seq;
187
+ },
188
+ export(query = {}) {
189
+ return journal.frames(query).flatMap((frame) => frame.events.map((event) => JSON.stringify({
190
+ seq: frame.seq,
191
+ at: frame.at,
192
+ cause: frame.cause,
193
+ ...event
194
+ }))).join("\n");
195
+ },
196
+ clear() {
197
+ frames.length = 0;
198
+ open = void 0;
199
+ cause = void 0;
200
+ seq = 0;
201
+ },
202
+ reset() {
203
+ journal.clear();
204
+ Object.assign(config, {
205
+ level: "off",
206
+ types: [],
207
+ paths: [],
208
+ keys: [],
209
+ calls: false
210
+ });
211
+ }
212
+ };
213
+ function matches(event, query) {
214
+ return (!query.type || event.type === query.type) && (!query.id || event.id === query.id) && (!query.key || event.key === query.key);
215
+ }
216
+ /** Filters OR together; none set records everything. Without a key only type-level filters apply. */
217
+ function wants(state, key) {
218
+ if (config.level === "off") return false;
219
+ const { types, paths, keys } = config;
220
+ if (!types.length && !paths.length && !keys.length) return true;
221
+ const Type = state.constructor;
222
+ const label = labelOf(Type);
223
+ if (types.includes(label)) return true;
224
+ if (key !== void 0 && keys.includes(key)) return true;
225
+ const targets = [
226
+ label,
227
+ seen(Type).typeId,
228
+ String(state)
229
+ ];
230
+ for (const address of paths) {
231
+ const { target, path } = parsePath(address);
232
+ if (targets.includes(target) && (key === void 0 || path === key)) return true;
233
+ }
234
+ return false;
235
+ }
236
+ function recordsCalls() {
237
+ return config.calls && config.level !== "off";
238
+ }
239
+ /** Run `work` with recording forced on; returns the frames it produced. */
240
+ async function act(work) {
241
+ const level = config.level;
242
+ const start = seq;
243
+ if (level === "off") config.level = "values";
244
+ try {
245
+ await work();
246
+ await new Promise((resolve) => setTimeout(resolve, 0));
247
+ } finally {
248
+ config.level = level;
249
+ }
250
+ return journal.frames({ since: start });
251
+ }
252
+ function note(state, key, store) {
253
+ const name = String(key);
254
+ if (!wants(state, name)) return;
255
+ const event = {
256
+ id: String(state),
257
+ type: labelOf(state.constructor),
258
+ key: name,
259
+ kind: typeof key === "string" && store.has(key) ? "update" : "event"
260
+ };
261
+ if (config.level === "values" && event.kind === "update") event.value = serialize(store.get(name), 1);
262
+ push(event);
263
+ }
264
+ function noteCall(state, key, args) {
265
+ if (!recordsCalls() || !wants(state, key)) return;
266
+ const event = {
267
+ id: String(state),
268
+ type: labelOf(state.constructor),
269
+ key,
270
+ kind: "call"
271
+ };
272
+ if (config.level === "values") event.args = args.map((arg) => serialize(arg, 1));
273
+ push(event);
274
+ }
275
+ function noteDestroy(state) {
276
+ if (!wants(state)) return;
277
+ push({
278
+ id: String(state),
279
+ type: labelOf(state.constructor),
280
+ key: "",
281
+ kind: "destroy"
282
+ });
283
+ }
284
+ function push(event) {
285
+ if (!open) {
286
+ const frame = {
287
+ seq: ++seq,
288
+ at: Date.now(),
289
+ events: []
290
+ };
291
+ if (cause !== void 0) frame.cause = cause;
292
+ open = frame;
293
+ frames.push(frame);
294
+ if (frames.length > FRAME_CAP) frames.splice(0, frames.length - FRAME_CAP);
295
+ queueMicrotask(() => {
296
+ open = void 0;
297
+ cause = frame.seq;
298
+ queueMicrotask(() => {
299
+ if (seq === frame.seq) cause = void 0;
300
+ });
301
+ });
302
+ }
303
+ open.events.push(event);
304
+ }
305
+
306
+ //#endregion
307
+ //#region src/inspect.ts
308
+ const Reaper = typeof FinalizationRegistry === "function" ? FinalizationRegistry : class {
309
+ register() {}
310
+ unregister() {}
311
+ };
312
+ const weak = (state) => typeof WeakRef === "function" ? new WeakRef(state) : { deref: () => state };
313
+ const live = /* @__PURE__ */ new Map();
314
+ const hooks = /* @__PURE__ */ new Map();
315
+ const wrapped = /* @__PURE__ */ new WeakSet();
316
+ const wrappers = /* @__PURE__ */ new WeakMap();
317
+ const spans = /* @__PURE__ */ new WeakMap();
318
+ const reaper = new Reaper(collected);
319
+ let version = 0;
320
+ let cached;
321
+ let lost = 0;
322
+ /** Registered instances still reachable, claimed or not. */
323
+ function registered() {
324
+ return [...live.values()].map((row) => row.held ?? row.ref.deref()).filter((state) => !!state);
325
+ }
326
+ /** Instances on the mainline: claimed, or too young to judge, or every one when no host renders. */
327
+ function* mainline() {
328
+ const parents = ownership();
329
+ for (const state of registered()) if (!orphaned(state, parents)) yield state;
330
+ }
331
+ function orphaned(state, parents, seen = /* @__PURE__ */ new Set()) {
332
+ const span = spans.get(state);
333
+ if (span.claimed || !span.settled || !hosted()) return false;
334
+ if (provided(state)) {
335
+ claim(state);
336
+ return false;
337
+ }
338
+ const owner = parents.get(state);
339
+ if (!owner || seen.has(owner)) return true;
340
+ seen.add(state);
341
+ if (orphaned(owner, parents, seen)) return true;
342
+ if (spans.get(owner).claimed) claim(state);
343
+ return false;
344
+ }
345
+ function claim(state) {
346
+ const span = spans.get(state);
347
+ if (span.claimed) return;
348
+ span.claimed = true;
349
+ const row = live.get(String(state));
350
+ if (row) {
351
+ row.held = state;
352
+ reaper.unregister(state);
353
+ }
354
+ version++;
355
+ }
356
+ /** A `static global` instance currently holding its slot in the root context. */
357
+ function provided(state) {
358
+ const type = state.constructor;
359
+ if (!(typeof type.global === "function" ? type.global(state) : type.global)) return false;
360
+ for (const set of Context.root.provide.values()) if (set) {
361
+ for (const [member] of set) if (member === state) return true;
362
+ }
363
+ return false;
364
+ }
365
+ function hosted() {
366
+ try {
367
+ isElement(null);
368
+ return true;
369
+ } catch {
370
+ return false;
371
+ }
372
+ }
373
+ /** A registered, unclaimed instance was garbage collected - it was abandoned. */
374
+ function collected(id) {
375
+ if (!live.delete(id)) return;
376
+ lost++;
377
+ version++;
378
+ }
379
+ /** Navigable view of one instance - identity, ownership, reads, observation. One per state; outlives it. */
380
+ var Instance = class Instance {
381
+ constructor(state) {
382
+ this.state = state;
383
+ }
384
+ static of(state) {
385
+ let instance = wrappers.get(state);
386
+ if (!instance) wrappers.set(state, instance = new Instance(state));
387
+ return instance;
388
+ }
389
+ get id() {
390
+ return String(this.state);
391
+ }
392
+ get type() {
393
+ return seen(this.state.constructor).type;
394
+ }
395
+ get alive() {
396
+ return live.has(this.id);
397
+ }
398
+ /** Reached by a host commit (`mount`), a claimed owner, or `static global`. Always true without a host. */
399
+ get claimed() {
400
+ return !orphaned(this.state, ownership());
401
+ }
402
+ /** Registration time (ms). */
403
+ get since() {
404
+ return spans.get(this.state).since;
405
+ }
406
+ /** Destruction time (ms), once destroyed. */
407
+ get until() {
408
+ return spans.get(this.state).until;
409
+ }
410
+ get parent() {
411
+ const owner = ownership().get(this.state);
412
+ return owner && Instance.of(owner);
413
+ }
414
+ get children() {
415
+ const parents = ownership();
416
+ return [...registered()].filter((state) => parents.get(state) === this.state).map(Instance.of);
417
+ }
418
+ /** First instance in this subtree, depth-first, matching `where` (a label or predicate). */
419
+ find(where) {
420
+ const test = typeof where === "string" ? (h) => h.type === where : where;
421
+ for (const child of this.children) {
422
+ if (test(child)) return child;
423
+ const deep = child.find(test);
424
+ if (deep) return deep;
425
+ }
426
+ }
427
+ get(path) {
428
+ return serialize(walk(this.state, path));
429
+ }
430
+ model() {
431
+ return describe(this.state, ownership());
432
+ }
433
+ /** Run `work`, settle, and return the frames it produced. */
434
+ act(work) {
435
+ return act(() => work(this.state));
436
+ }
437
+ /** Call `fn` on each update, and with `null` on destroy; `keys` narrows updates. Returns unsubscribe. */
438
+ watch(fn, keys) {
439
+ const updates = this.state.set((key) => {
440
+ if (typeof key === "string" && (!keys || keys.includes(key))) fn(key);
441
+ });
442
+ const destroy = this.state.get(null, () => fn(null));
443
+ return () => {
444
+ updates();
445
+ destroy();
446
+ };
447
+ }
448
+ frames(query = {}) {
449
+ return journal.frames({
450
+ ...query,
451
+ id: this.id
452
+ });
453
+ }
454
+ };
455
+ function attach(Type = State) {
456
+ if (!hooks.has(Type)) hooks.set(Type, Type.on(function() {
457
+ const self = this.is;
458
+ const id = String(self);
459
+ const span = {
460
+ since: Date.now(),
461
+ claimed: false,
462
+ settled: false
463
+ };
464
+ seen(self.constructor);
465
+ live.set(id, { ref: weak(self) });
466
+ spans.set(self, span);
467
+ reaper.register(self, id, self);
468
+ setTimeout(() => {
469
+ span.settled = true;
470
+ }, 0);
471
+ version++;
472
+ if (recordsCalls()) wrap(self);
473
+ mounts(self);
474
+ const stop = self.set((key) => {
475
+ const store = entries(self);
476
+ if (typeof key === "string" && typeof store.get(key) === "object") version++;
477
+ note(self, key, store);
478
+ });
479
+ return () => {
480
+ stop();
481
+ live.delete(id);
482
+ reaper.unregister(self);
483
+ span.until = Date.now();
484
+ version++;
485
+ noteDestroy(self);
486
+ };
487
+ }));
488
+ return () => {
489
+ hooks.get(Type)?.();
490
+ hooks.delete(Type);
491
+ };
492
+ }
493
+ function detach() {
494
+ for (const stop of hooks.values()) stop();
495
+ hooks.clear();
496
+ live.clear();
497
+ cached = void 0;
498
+ lost = 0;
499
+ journal.reset();
500
+ forget();
501
+ }
502
+ /** Resolve an id or label to an instance - mainline first, then orphans. */
503
+ function find(target) {
504
+ const byId = live.get(target);
505
+ const held = byId && (byId.held ?? byId.ref.deref());
506
+ if (held) return Instance.of(held);
507
+ for (const pool of [mainline(), abandoned()]) for (const state of pool) if (seen(state.constructor).type === target) return Instance.of(state);
508
+ }
509
+ function instances() {
510
+ return [...mainline()].map(Instance.of);
511
+ }
512
+ /** Registered and settled, but never claimed by a host commit, an owner, or root context. */
513
+ function orphans() {
514
+ return [...abandoned()].map(Instance.of);
515
+ }
516
+ function* abandoned() {
517
+ const parents = ownership();
518
+ for (const state of registered()) if (orphaned(state, parents)) yield state;
519
+ }
520
+ /** Counts worth a look: live orphans, and unclaimed instances the collector already reaped. */
521
+ function warnings() {
522
+ return {
523
+ orphans: orphans().length,
524
+ collected: lost
525
+ };
526
+ }
527
+ function roots() {
528
+ const parents = ownership();
529
+ return instances().filter((instance) => !parents.has(instance.state));
530
+ }
531
+ function models() {
532
+ const parents = ownership();
533
+ return [...mainline()].map((state) => describe(state, parents));
534
+ }
535
+ function tree() {
536
+ const parents = ownership();
537
+ const nodes = /* @__PURE__ */ new Map();
538
+ const out = [];
539
+ for (const state of mainline()) nodes.set(state, {
540
+ id: String(state),
541
+ type: seen(state.constructor).type,
542
+ children: []
543
+ });
544
+ for (const [state, node] of nodes) {
545
+ const parent = parents.get(state);
546
+ const owner = parent && nodes.get(parent);
547
+ if (owner) owner.children.push(node);
548
+ else out.push(node);
549
+ }
550
+ return out;
551
+ }
552
+ function get(address) {
553
+ if (!address) return models();
554
+ const { target, path } = parsePath(address);
555
+ return find(target)?.get(path);
556
+ }
557
+ function set(address, value) {
558
+ const { target, path } = parsePath(address);
559
+ const instance = find(target);
560
+ if (!instance || !path) throw new Error(`No model at ${address}.`);
561
+ const dot = path.lastIndexOf(".");
562
+ const key = path.slice(dot + 1);
563
+ const owner = dot < 0 ? instance.state : walk(instance.state, path.slice(0, dot));
564
+ if (owner == null || typeof owner !== "object") throw new Error(`No model at ${address}.`);
565
+ if (owner instanceof Map) owner.set(key, value);
566
+ else owner[key] = value;
567
+ }
568
+ async function call(address, ...args) {
569
+ const { target, path } = parsePath(address);
570
+ const state = find(target)?.state;
571
+ const method = state && path && state[path];
572
+ if (typeof method !== "function") throw new Error(`No method at ${address}.`);
573
+ return serialize(await method.apply(state, args));
574
+ }
575
+ function wrapAll() {
576
+ for (const state of registered()) wrap(state);
577
+ }
578
+ /** Observe the host commit: adapters call `mount?.()` once an instance is placed. */
579
+ function mounts(state) {
580
+ const target = state;
581
+ const original = target.mount;
582
+ Object.defineProperty(target, "mount", {
583
+ configurable: true,
584
+ writable: true,
585
+ value(...args) {
586
+ claim(state);
587
+ return typeof original === "function" ? original.apply(this, args) : void 0;
588
+ }
589
+ });
590
+ }
591
+ function describe(state, parents) {
592
+ const stored = entries(state);
593
+ const declared = Object.entries(Object.getOwnPropertyDescriptors(state)).filter(([, desc]) => typeof desc.get === "function").map(([key]) => key);
594
+ const model = {
595
+ id: String(state),
596
+ ...seen(state.constructor),
597
+ keys: [...stored.keys()],
598
+ absent: declared.filter((key) => !stored.has(key))
599
+ };
600
+ const parent = parents.get(state);
601
+ if (parent) model.parent = String(parent);
602
+ return model;
603
+ }
604
+ function wrap(state) {
605
+ if (wrapped.has(state)) return;
606
+ wrapped.add(state);
607
+ const target = state;
608
+ const seenKeys = new Set(["render"]);
609
+ for (let proto = Object.getPrototypeOf(state); proto !== State.prototype; proto = Object.getPrototypeOf(proto)) for (const [key, desc] of Object.entries(Object.getOwnPropertyDescriptors(proto))) {
610
+ if (seenKeys.has(key) || typeof desc.get !== "function" || desc.get !== desc.set) continue;
611
+ seenKeys.add(key);
612
+ const original = target[key];
613
+ target[key] = function(...args) {
614
+ noteCall(state, key, args);
615
+ return original.apply(this, args);
616
+ };
617
+ }
618
+ }
619
+ function ownership() {
620
+ if (cached?.version !== version) {
621
+ const parents = /* @__PURE__ */ new Map();
622
+ for (const owner of registered()) for (const [key, value] of entries(owner)) if (Object.getOwnPropertyDescriptor(owner, key)?.enumerable || !(value instanceof State)) own(parents, owner, value);
623
+ cached = {
624
+ version,
625
+ parents
626
+ };
627
+ }
628
+ return cached.parents;
629
+ }
630
+ function own(parents, owner, value) {
631
+ if (value instanceof State) {
632
+ if (!parents.has(value) && value !== owner) parents.set(value, owner);
633
+ } else if (value && typeof value === "object" && Symbol.iterator in value) {
634
+ const items = value instanceof Map ? value.values() : value;
635
+ for (const item of items) if (item instanceof State) own(parents, owner, item);
636
+ }
637
+ }
638
+
639
+ //#endregion
640
+ export { label as _, find as a, serialize as b, models as c, set as d, tree as f, journal as g, act as h, detach as i, orphans as l, wrapAll as m, attach as n, get as o, warnings as p, call as r, instances as s, Instance as t, roots as u, resolve as v, parsePath as y };
641
+ //# sourceMappingURL=inspect-D4NC5Mx5.js.map
@@ -0,0 +1 @@
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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"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,10 @@
1
+ import { n as attach } from "./inspect-D4NC5Mx5.js";
2
+ import inspect from "./index.js";
3
+
4
+ //#region src/install.ts
5
+ attach();
6
+ globalThis.__EXPRESSIVE_INSPECT__ = inspect;
7
+
8
+ //#endregion
9
+ export { };
10
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","names":[],"sources":["../src/install.ts"],"sourcesContent":["import { attach, inspect } from './index';\n\nattach();\n\nglobalThis.__EXPRESSIVE_INSPECT__ = inspect;\n"],"mappings":";;;;AAEA,QAAQ;AAER,WAAW,yBAAyB"}
@@ -0,0 +1,33 @@
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
@@ -0,0 +1,53 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,123 @@
1
+ import { State } from "@expressive/mvc";
2
+
3
+ //#region src/journal.d.ts
4
+ type Level = 'off' | 'keys' | 'values';
5
+ interface Options {
6
+ level?: Level;
7
+ /** Labels to record. */
8
+ types?: string[];
9
+ /** `Type.key` addresses to record - label, `typeId`, or instance id on the left, property on the right. */
10
+ paths?: string[];
11
+ /** Property names to record on any type. */
12
+ keys?: string[];
13
+ /** Also record method calls (wraps instance methods except `render`). */
14
+ calls?: boolean;
15
+ }
16
+ interface Event {
17
+ id: string;
18
+ type: string;
19
+ key: string;
20
+ kind: 'update' | 'event' | 'call' | 'destroy';
21
+ value?: unknown;
22
+ args?: unknown[];
23
+ }
24
+ interface Frame {
25
+ seq: number;
26
+ at: number;
27
+ /** Frame whose flush scheduled the work that opened this one. */
28
+ cause?: number;
29
+ events: Event[];
30
+ }
31
+ interface Query {
32
+ since?: number;
33
+ type?: string;
34
+ id?: string;
35
+ key?: string;
36
+ cause?: number;
37
+ }
38
+ /** Run `work` with recording forced on; returns the frames it produced. */
39
+ declare function act(work: () => unknown): Promise<Frame[]>;
40
+ //#endregion
41
+ //#region src/types.d.ts
42
+ interface TypeInfo {
43
+ /** Opaque, stable for the life of the process. */
44
+ typeId: string;
45
+ /** Resolved display name - see `label`. */
46
+ type: string;
47
+ /** Construction stack captured at first sight, for a source-map resolver. */
48
+ site: string;
49
+ }
50
+ declare function label(Type: typeof State, name: string): void;
51
+ /** Supply resolved names keyed by `typeId` or `site`, e.g. from a source-map pass. */
52
+ declare function resolve(table: Record<string, string>): void;
53
+ //#endregion
54
+ //#region src/inspect.d.ts
55
+ interface Model extends TypeInfo {
56
+ id: string;
57
+ parent?: string;
58
+ /** Keys with a stored value. */
59
+ keys: string[];
60
+ /** Declared keys with no stored value yet - lazy, pending, or uncomputed. */
61
+ absent: string[];
62
+ }
63
+ interface Node {
64
+ id: string;
65
+ type: string;
66
+ children: Node[];
67
+ }
68
+ /** Navigable view of one instance - identity, ownership, reads, observation. One per state; outlives it. */
69
+ declare class Instance {
70
+ readonly state: State;
71
+ private constructor();
72
+ static of(state: State): Instance;
73
+ get id(): string;
74
+ get type(): string;
75
+ get alive(): boolean;
76
+ /** Reached by a host commit (`mount`), a claimed owner, or `static global`. Always true without a host. */
77
+ get claimed(): boolean;
78
+ /** Registration time (ms). */
79
+ get since(): number;
80
+ /** Destruction time (ms), once destroyed. */
81
+ get until(): number | undefined;
82
+ get parent(): Instance | undefined;
83
+ get children(): Instance[];
84
+ /** First instance in this subtree, depth-first, matching `where` (a label or predicate). */
85
+ find(where: string | ((instance: Instance) => boolean)): Instance | undefined;
86
+ get(path?: string): unknown;
87
+ model(): Model;
88
+ /** Run `work`, settle, and return the frames it produced. */
89
+ act(work: (state: State) => unknown): Promise<Frame[]>;
90
+ /** Call `fn` on each update, and with `null` on destroy; `keys` narrows updates. Returns unsubscribe. */
91
+ watch(fn: (key: string | null) => void, keys?: string[]): () => void;
92
+ frames(query?: Query): Frame[];
93
+ }
94
+ declare function attach(Type?: typeof State): () => void;
95
+ declare function detach(): void;
96
+ /** Resolve an id or label to an instance - mainline first, then orphans. */
97
+ declare function find(target: string): Instance | undefined;
98
+ declare function instances(): Instance[];
99
+ /** Registered and settled, but never claimed by a host commit, an owner, or root context. */
100
+ declare function orphans(): Instance[];
101
+ /** Counts worth a look: live orphans, and unclaimed instances the collector already reaped. */
102
+ declare function warnings(): {
103
+ orphans: number;
104
+ collected: number;
105
+ };
106
+ declare function roots(): Instance[];
107
+ declare function models(): Model[];
108
+ declare function tree(): Node[];
109
+ declare function get(address?: string): unknown;
110
+ declare function set(address: string, value: unknown): void;
111
+ declare function call(address: string, ...args: unknown[]): Promise<unknown>;
112
+ //#endregion
113
+ //#region src/serialize.d.ts
114
+ type Address = {
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
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@expressive/inspect",
3
+ "version": "0.0.1",
4
+ "description": "In-process inspector for Expressive MVC - registry, ownership tree, path queries, and a frame journal for agents, tests, and devtools.",
5
+ "keywords": [
6
+ "expressive",
7
+ "mvc",
8
+ "devtools",
9
+ "inspector",
10
+ "state"
11
+ ],
12
+ "license": "MIT",
13
+ "author": {
14
+ "name": "Gabe Klein",
15
+ "email": "gabe@termtm.com",
16
+ "url": "https://github.com/gabeklein"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/gabeklein/expressive-mvc.git"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "type": "module",
26
+ "sideEffects": [
27
+ "./dist/install.js",
28
+ "./src/install.ts"
29
+ ],
30
+ "main": "dist/index.js",
31
+ "types": "dist/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "default": "./dist/index.js"
36
+ },
37
+ "./install": {
38
+ "types": "./dist/install.d.ts",
39
+ "default": "./dist/install.js"
40
+ },
41
+ "./playwright": {
42
+ "types": "./dist/playwright.d.ts",
43
+ "default": "./dist/playwright.js"
44
+ }
45
+ },
46
+ "engines": {
47
+ "node": ">=20"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "scripts": {
53
+ "build": "tsdown",
54
+ "test": "tsc --noEmit && vitest run --coverage"
55
+ },
56
+ "peerDependencies": {
57
+ "@expressive/mvc": "^0.84.0"
58
+ },
59
+ "devDependencies": {
60
+ "@expressive/mvc": "workspace:*",
61
+ "tsdown": "^0.20.1"
62
+ }
63
+ }