@rindle/devtools 0.1.6

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/dist/core.js ADDED
@@ -0,0 +1,351 @@
1
+ // DevtoolsCore — the framework-agnostic engine (DEBUG-TOOLS-BROWSER-DESIGN.md §6.1). It attaches to
2
+ // a running client through the read-only seams (`Store.__attachDevtools` / `__inspect`, and the
3
+ // optimistic backend's `__inspect`), then maintains the §4 read-model: the mutation TIMELINE
4
+ // (reconstructed by diffing successive pending-stack snapshots), the QUERIES inspector, and the
5
+ // DELTA stream (off the raw per-query `ChangeEvent` tap). No DOM, no app-code changes, no hot path.
6
+ import { collectTables, summarizeAst } from "./ast.js";
7
+ /** Narrow a candidate backend to the optimistic capability (the `__inspect` probe, §6.2). */
8
+ function asOptimisticBackend(backend) {
9
+ return backend && typeof backend.__inspect === "function"
10
+ ? backend
11
+ : undefined;
12
+ }
13
+ const EMPTY_STATE = {
14
+ timeline: [],
15
+ queries: [],
16
+ deltas: [],
17
+ optimistic: undefined,
18
+ capabilities: { optimistic: false },
19
+ };
20
+ export class DevtoolsCore {
21
+ store;
22
+ backend;
23
+ detachStore;
24
+ timelineCap;
25
+ deltaCap;
26
+ sampleRows;
27
+ autoFlush;
28
+ now;
29
+ pollHandle;
30
+ // Timeline state: entries by stable id + an insertion-ordered id list (newest last).
31
+ timeline = new Map();
32
+ order = [];
33
+ /** The pending keys seen at the previous recompute — the diff basis for lifecycle transitions. */
34
+ prevPendingKeys = new Set();
35
+ // Delta ring.
36
+ deltas = [];
37
+ deltaSeq = 0;
38
+ /** Set by a `batch` delta, consumed by the next recompute: did view churn coincide with a
39
+ * confirmation this turn? (the §4.1 snap-back heuristic). */
40
+ churnSeen = false;
41
+ queries = [];
42
+ optimistic;
43
+ listeners = new Set();
44
+ dirty = false;
45
+ flushScheduled = false;
46
+ snapshot = EMPTY_STATE;
47
+ /** Optional deregistration from the global hub (set by {@link attachDevtools}). */
48
+ onDetach;
49
+ constructor(target, opts = {}) {
50
+ this.store = target.store;
51
+ this.backend = asOptimisticBackend(target.backend);
52
+ this.timelineCap = opts.timelineCap ?? 200;
53
+ this.deltaCap = opts.deltaCap ?? 500;
54
+ this.sampleRows = opts.sampleRows ?? 25;
55
+ this.autoFlush = opts.autoFlush ?? true;
56
+ this.now = opts.now ?? (() => Date.now());
57
+ this.detachStore = this.store.__attachDevtools({
58
+ onDelta: (qid, ev) => this.onDelta(qid, ev),
59
+ onResultType: (qid, rt) => this.onResultType(qid, rt),
60
+ });
61
+ const pollMs = opts.pollMs ?? 0;
62
+ if (pollMs > 0) {
63
+ this.pollHandle = setInterval(() => this.refresh(), pollMs);
64
+ // Don't keep a Node process alive on the poll timer (no-op in the browser).
65
+ this.pollHandle.unref?.();
66
+ }
67
+ // Seed the initial snapshot from whatever the app already holds (e.g. queries mounted before
68
+ // the pane attached).
69
+ this.refresh();
70
+ }
71
+ // --- public surface ----------------------------------------------------------
72
+ /** The current read-model. Reference-stable between updates (rebuilt only on recompute), so it is
73
+ * safe to feed a `useSyncExternalStore`-style binding. */
74
+ getState() {
75
+ return this.snapshot;
76
+ }
77
+ /** Subscribe to updates; fires immediately with the current state, then after each recompute. */
78
+ subscribe(listener) {
79
+ this.listeners.add(listener);
80
+ listener();
81
+ return () => {
82
+ this.listeners.delete(listener);
83
+ };
84
+ }
85
+ /** Force a synchronous recompute (also used by the safety-net poll and by tests). */
86
+ refresh() {
87
+ this.dirty = true;
88
+ this.flush();
89
+ }
90
+ /** Clear the delta stream ring (a panel "clear" affordance). */
91
+ clearDeltas() {
92
+ this.deltas = [];
93
+ this.refresh();
94
+ }
95
+ /** Drop every SETTLED timeline row (confirmed/dropped), keeping live pending ones + the deltas. */
96
+ clearHistory() {
97
+ for (let i = this.order.length - 1; i >= 0; i--) {
98
+ const id = this.order[i];
99
+ const e = this.timeline.get(id);
100
+ if (e && e.state !== "pending") {
101
+ this.timeline.delete(id);
102
+ this.order.splice(i, 1);
103
+ }
104
+ }
105
+ this.refresh();
106
+ }
107
+ /** Detach from the client: stop the poll, drop the store tap, deregister from the global hub. */
108
+ detach() {
109
+ if (this.pollHandle)
110
+ clearInterval(this.pollHandle);
111
+ this.detachStore();
112
+ this.onDetach?.();
113
+ this.listeners.clear();
114
+ }
115
+ // --- event taps --------------------------------------------------------------
116
+ onDelta(qid, ev) {
117
+ this.pushDelta(qid, ev);
118
+ // A `batch` carrying real changes is view churn — the signal the snap-back heuristic keys on.
119
+ if (ev.type === "batch" && ev.events.length > 0)
120
+ this.churnSeen = true;
121
+ this.markDirty();
122
+ }
123
+ onResultType(_qid, _rt) {
124
+ this.markDirty();
125
+ }
126
+ markDirty() {
127
+ this.dirty = true;
128
+ if (this.autoFlush && !this.flushScheduled) {
129
+ this.flushScheduled = true;
130
+ queueMicrotask(() => {
131
+ this.flushScheduled = false;
132
+ this.flush();
133
+ });
134
+ }
135
+ }
136
+ flush() {
137
+ if (!this.dirty)
138
+ return;
139
+ this.dirty = false;
140
+ this.recompute();
141
+ for (const l of this.listeners)
142
+ l();
143
+ }
144
+ // --- recompute ---------------------------------------------------------------
145
+ recompute() {
146
+ let opt;
147
+ if (this.backend) {
148
+ try {
149
+ opt = this.backend.__inspect();
150
+ }
151
+ catch {
152
+ opt = undefined; // a malformed/throwing dev hook never breaks the pane
153
+ }
154
+ }
155
+ if (opt)
156
+ this.reconcileTimeline(opt);
157
+ this.rebuildQueries(opt);
158
+ this.optimistic = opt;
159
+ this.churnSeen = false;
160
+ this.snapshot = {
161
+ timeline: this.order.map((id) => this.timeline.get(id)).filter((e) => !!e),
162
+ queries: this.queries,
163
+ deltas: this.deltas.slice(),
164
+ optimistic: opt,
165
+ capabilities: { optimistic: !!this.backend },
166
+ };
167
+ }
168
+ /** Reconstruct the fork/rebase lifecycle by diffing this pending snapshot against the previous
169
+ * one (DEBUG-TOOLS-BROWSER-DESIGN §4.1): new keys → invoked; vanished keys → confirmed (mid ≤
170
+ * confirmedLmid) or dropped; a fold's `f:<foldKey>` → `m:<mid>` transition is linked, not double
171
+ * counted. */
172
+ reconcileTimeline(opt) {
173
+ const now = this.now();
174
+ const cur = opt.pending;
175
+ const curKeys = new Set(cur.map((p) => p.key));
176
+ const removed = [];
177
+ for (const k of this.prevPendingKeys)
178
+ if (!curKeys.has(k))
179
+ removed.push(k);
180
+ const added = cur.filter((p) => !this.prevPendingKeys.has(p.key));
181
+ // Fold-flush linking: an `f:<foldKey>` that vanished as an `m:<mid>` of the same (name, args)
182
+ // appeared is one logical mutation crossing the wire — relabel in place (FOLDED-MUTATIONS §4.1).
183
+ const linkedF = new Set();
184
+ const linkedM = new Set();
185
+ for (const fkey of removed) {
186
+ if (!fkey.startsWith("f:"))
187
+ continue;
188
+ const fEntry = this.timeline.get(fkey);
189
+ if (!fEntry)
190
+ continue;
191
+ const match = added.find((p) => p.key.startsWith("m:") && !linkedM.has(p.key) && p.name === fEntry.name && argsEqual(p.args, fEntry.args));
192
+ if (!match)
193
+ continue;
194
+ this.renameEntry(fkey, match.key);
195
+ const e = this.timeline.get(match.key);
196
+ if (e) {
197
+ e.mid = match.mid;
198
+ e.tables = match.tables;
199
+ e.folded = true;
200
+ // A flushed fold has already left `folds`, so the m:<mid> snapshot carries no `fold` window —
201
+ // keep the one captured while it was debouncing and just mark it flushed.
202
+ const window = match.fold ?? e.fold;
203
+ e.fold = window ? { ...window, flushed: true } : undefined;
204
+ }
205
+ linkedF.add(fkey);
206
+ linkedM.add(match.key);
207
+ }
208
+ // Freshly invoked entries (not the m: side of a link).
209
+ for (const p of added) {
210
+ if (linkedM.has(p.key) || this.timeline.has(p.key))
211
+ continue;
212
+ this.addEntry({
213
+ id: p.key,
214
+ mid: p.mid,
215
+ name: p.name,
216
+ args: p.args,
217
+ tables: p.tables,
218
+ state: "pending",
219
+ folded: !!p.fold,
220
+ fold: p.fold,
221
+ invokedAt: now,
222
+ reconciledWithChurn: false,
223
+ affectedQueries: [],
224
+ });
225
+ }
226
+ // Refresh still-pending entries (a rebase re-invoke can change touched tables / args / mid).
227
+ for (const p of cur) {
228
+ const e = this.timeline.get(p.key);
229
+ if (!e || e.state !== "pending")
230
+ continue;
231
+ e.mid = p.mid;
232
+ e.args = p.args;
233
+ e.tables = p.tables;
234
+ if (p.fold) {
235
+ e.folded = true;
236
+ e.fold = p.fold;
237
+ }
238
+ }
239
+ // Settle vanished entries (those not consumed by a fold link).
240
+ for (const k of removed) {
241
+ if (linkedF.has(k))
242
+ continue;
243
+ const e = this.timeline.get(k);
244
+ if (!e || e.state !== "pending")
245
+ continue;
246
+ const confirmed = e.mid != null && e.mid <= opt.confirmedLmid;
247
+ e.state = confirmed ? "confirmed" : "dropped";
248
+ e.settledAt = now;
249
+ e.reconciledWithChurn = confirmed && this.churnSeen;
250
+ }
251
+ this.prevPendingKeys = curKeys;
252
+ }
253
+ rebuildQueries(opt) {
254
+ const pendingTables = new Set(opt?.pendingTables ?? []);
255
+ const queries = this.store.__inspect(this.sampleRows).queries.map((q) => {
256
+ const tables = [...collectTables(q.ast)];
257
+ return {
258
+ qid: q.qid,
259
+ ast: q.ast,
260
+ table: q.ast.table,
261
+ summary: summarizeAst(q.ast),
262
+ tables,
263
+ resultType: q.resultType,
264
+ rowCount: q.rowCount,
265
+ sample: q.sample,
266
+ pending: tables.some((t) => pendingTables.has(t)),
267
+ };
268
+ });
269
+ this.queries = queries;
270
+ // Recompute each timeline entry's affected queries against the CURRENT live views.
271
+ if (this.timeline.size > 0) {
272
+ for (const e of this.timeline.values()) {
273
+ const touched = new Set(e.tables);
274
+ e.affectedQueries = queries.filter((q) => q.tables.some((t) => touched.has(t))).map((q) => q.qid);
275
+ }
276
+ }
277
+ }
278
+ // --- delta ring --------------------------------------------------------------
279
+ pushDelta(qid, ev) {
280
+ if (ev.type === "hello") {
281
+ this.appendDelta(qid, "hello", 0, `hello · ${ev.schema.columns.length} col${ev.schema.columns.length === 1 ? "" : "s"}`);
282
+ }
283
+ else if (ev.type === "snapshot") {
284
+ const n = ev.adds.length;
285
+ this.appendDelta(qid, "snapshot", 0, `snapshot · +${n} row${n === 1 ? "" : "s"}${ev.last ? "" : " (chunk)"}`);
286
+ }
287
+ else {
288
+ for (const ch of ev.events) {
289
+ const depth = ch.path.length;
290
+ this.appendDelta(qid, ch.op.tag, depth, describeOp(ch.op) + (depth > 0 ? ` (child @${depth})` : ""));
291
+ }
292
+ }
293
+ }
294
+ appendDelta(qid, kind, depth, label) {
295
+ this.deltas.push({ seq: this.deltaSeq++, at: this.now(), qid, kind, depth, label });
296
+ if (this.deltas.length > this.deltaCap)
297
+ this.deltas.splice(0, this.deltas.length - this.deltaCap);
298
+ }
299
+ // --- timeline map bookkeeping ------------------------------------------------
300
+ addEntry(entry) {
301
+ this.timeline.set(entry.id, entry);
302
+ this.order.push(entry.id);
303
+ // Cap: evict the oldest SETTLED rows first; never drop a live pending row.
304
+ while (this.order.length > this.timelineCap) {
305
+ const idx = this.order.findIndex((id) => this.timeline.get(id)?.state !== "pending");
306
+ if (idx < 0)
307
+ break;
308
+ const [evicted] = this.order.splice(idx, 1);
309
+ this.timeline.delete(evicted);
310
+ }
311
+ }
312
+ renameEntry(oldId, newId) {
313
+ const e = this.timeline.get(oldId);
314
+ if (!e)
315
+ return;
316
+ this.timeline.delete(oldId);
317
+ e.id = newId;
318
+ this.timeline.set(newId, e);
319
+ const i = this.order.indexOf(oldId);
320
+ if (i >= 0)
321
+ this.order[i] = newId;
322
+ }
323
+ }
324
+ /** Structural args equality for fold-flush linking (args are JSON-serializable mutator inputs). */
325
+ function argsEqual(a, b) {
326
+ if (a === b)
327
+ return true;
328
+ try {
329
+ return JSON.stringify(a) === JSON.stringify(b);
330
+ }
331
+ catch {
332
+ return false;
333
+ }
334
+ }
335
+ function describeOp(op) {
336
+ if (op.tag === "add")
337
+ return `add ${rowPreview(op.node.row)}`;
338
+ if (op.tag === "remove")
339
+ return `remove ${rowPreview(op.row)}`;
340
+ return `edit ${rowPreview(op.old)} → ${rowPreview(op.new)}`;
341
+ }
342
+ function rowPreview(row) {
343
+ const head = row.slice(0, 3).map(cellStr).join(", ");
344
+ return `[${head}${row.length > 3 ? ", …" : ""}]`;
345
+ }
346
+ function cellStr(v) {
347
+ if (typeof v === "string")
348
+ return v.length > 16 ? `"${v.slice(0, 15)}…"` : `"${v}"`;
349
+ return String(v);
350
+ }
351
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { DevtoolsCore } from "./core.ts";
2
+ import type { DevtoolsCoreOptions, DevtoolsTarget } from "./types.ts";
3
+ /** The discovery surface a panel binds to: the set of attached cores + a change subscription. */
4
+ export interface DevtoolsHub {
5
+ readonly version: number;
6
+ readonly cores: readonly DevtoolsCore[];
7
+ /** Fires whenever a core attaches or detaches (so a panel can pick one up). */
8
+ subscribe(listener: () => void): () => void;
9
+ /** Register a core; returns its deregistration function. */
10
+ register(core: DevtoolsCore): () => void;
11
+ }
12
+ /** Get (creating on first use) the global devtools hub. A panel calls this to discover attached
13
+ * clients; it is safe to call before any `attachDevtools`. */
14
+ export declare function getDevtoolsHub(): DevtoolsHub;
15
+ /** The most recently attached core, or `undefined` — the common single-app convenience a panel uses. */
16
+ export declare function getDevtoolsCore(): DevtoolsCore | undefined;
17
+ /** Attach a devtools pane to a running client (DEBUG-TOOLS-BROWSER-DESIGN §6.2). Call this ONLY in a
18
+ * dev build (e.g. behind `import.meta.env.DEV` with a dynamic `import("@rindle/devtools")`) so the
19
+ * pane, its core, and this registration tree-shake out of production. Returns the {@link DevtoolsCore}
20
+ * (also discoverable via {@link getDevtoolsHub}); call `core.detach()` to unwind. */
21
+ export declare function attachDevtools(target: DevtoolsTarget, opts?: DevtoolsCoreOptions): DevtoolsCore;
22
+ //# sourceMappingURL=global.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"global.d.ts","sourceRoot":"","sources":["../src/global.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAItE,iGAAiG;AACjG,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IACxC,+EAA+E;IAC/E,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,IAAI,CAAC;CAC1C;AAmCD;+DAC+D;AAC/D,wBAAgB,cAAc,IAAI,WAAW,CAG5C;AAED,wGAAwG;AACxG,wBAAgB,eAAe,IAAI,YAAY,GAAG,SAAS,CAG1D;AAED;;;sFAGsF;AACtF,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG,YAAY,CAM/F"}
package/dist/global.js ADDED
@@ -0,0 +1,60 @@
1
+ // The opt-in global registry (DEBUG-TOOLS-BROWSER-DESIGN.md §6.2). The client core stays clean —
2
+ // no global mutable singleton lives in `@rindle/client`/`@rindle/optimistic`. Instead a dev build
3
+ // calls `attachDevtools(app)`, which constructs a {@link DevtoolsCore} and registers it on
4
+ // `globalThis.__RINDLE_DEVTOOLS__`, the same discovery pattern TanStack/Redux DevTools use. A panel
5
+ // finds the hub with {@link getDevtoolsHub} and subscribes — so it works whether it mounts before or
6
+ // after the app attaches.
7
+ import { DevtoolsCore } from "./core.js";
8
+ const GLOBAL_KEY = "__RINDLE_DEVTOOLS__";
9
+ class Hub {
10
+ version = 1;
11
+ cores = [];
12
+ listeners = new Set();
13
+ subscribe(listener) {
14
+ this.listeners.add(listener);
15
+ return () => {
16
+ this.listeners.delete(listener);
17
+ };
18
+ }
19
+ register(core) {
20
+ this.cores.push(core);
21
+ this.emit();
22
+ return () => {
23
+ const i = this.cores.indexOf(core);
24
+ if (i >= 0) {
25
+ this.cores.splice(i, 1);
26
+ this.emit();
27
+ }
28
+ };
29
+ }
30
+ emit() {
31
+ for (const l of this.listeners)
32
+ l();
33
+ }
34
+ }
35
+ function globalSlot() {
36
+ return globalThis;
37
+ }
38
+ /** Get (creating on first use) the global devtools hub. A panel calls this to discover attached
39
+ * clients; it is safe to call before any `attachDevtools`. */
40
+ export function getDevtoolsHub() {
41
+ const slot = globalSlot();
42
+ return (slot[GLOBAL_KEY] ??= new Hub());
43
+ }
44
+ /** The most recently attached core, or `undefined` — the common single-app convenience a panel uses. */
45
+ export function getDevtoolsCore() {
46
+ const cores = getDevtoolsHub().cores;
47
+ return cores[cores.length - 1];
48
+ }
49
+ /** Attach a devtools pane to a running client (DEBUG-TOOLS-BROWSER-DESIGN §6.2). Call this ONLY in a
50
+ * dev build (e.g. behind `import.meta.env.DEV` with a dynamic `import("@rindle/devtools")`) so the
51
+ * pane, its core, and this registration tree-shake out of production. Returns the {@link DevtoolsCore}
52
+ * (also discoverable via {@link getDevtoolsHub}); call `core.detach()` to unwind. */
53
+ export function attachDevtools(target, opts) {
54
+ // Default the safety-net poll on for real apps (a fold's debounced flush / a pending flip on an
55
+ // already-`complete` query move state with no event); tests construct DevtoolsCore directly.
56
+ const core = new DevtoolsCore(target, { pollMs: 500, ...opts });
57
+ core.onDetach = getDevtoolsHub().register(core);
58
+ return core;
59
+ }
60
+ //# sourceMappingURL=global.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"global.js","sourceRoot":"","sources":["../src/global.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,kGAAkG;AAClG,2FAA2F;AAC3F,oGAAoG;AACpG,qGAAqG;AACrG,0BAA0B;AAE1B,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAGzC,MAAM,UAAU,GAAG,qBAAqB,CAAC;AAYzC,MAAM,GAAG;IACE,OAAO,GAAG,CAAC,CAAC;IACZ,KAAK,GAAmB,EAAE,CAAC;IACnB,SAAS,GAAG,IAAI,GAAG,EAAc,CAAC;IAEnD,SAAS,CAAC,QAAoB;QAC5B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAClC,CAAC,CAAC;IACJ,CAAC;IAED,QAAQ,CAAC,IAAkB;QACzB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,GAAG,EAAE;YACV,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACnC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACX,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBACxB,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,CAAC;QACH,CAAC,CAAC;IACJ,CAAC;IAEO,IAAI;QACV,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,SAAS;YAAE,CAAC,EAAE,CAAC;IACtC,CAAC;CACF;AAED,SAAS,UAAU;IACjB,OAAO,UAAmE,CAAC;AAC7E,CAAC;AAED;+DAC+D;AAC/D,MAAM,UAAU,cAAc;IAC5B,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,IAAI,GAAG,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,eAAe;IAC7B,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC,KAAK,CAAC;IACrC,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACjC,CAAC;AAED;;;sFAGsF;AACtF,MAAM,UAAU,cAAc,CAAC,MAAsB,EAAE,IAA0B;IAC/E,gGAAgG;IAChG,6FAA6F;IAC7F,MAAM,IAAI,GAAG,IAAI,YAAY,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;IAChE,IAAI,CAAC,QAAQ,GAAG,cAAc,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { DevtoolsCore } from "./core.ts";
2
+ export { attachDevtools, getDevtoolsCore, getDevtoolsHub } from "./global.ts";
3
+ export type { DevtoolsHub } from "./global.ts";
4
+ export { collectTables, summarizeAst } from "./ast.ts";
5
+ export type { DeltaEntry, DeltaKind, DevtoolsBackend, DevtoolsCoreOptions, DevtoolsState, DevtoolsStore, DevtoolsTarget, FoldInspect, MutationState, OptimisticInspect, PendingInspect, QueryEntry, TimelineEntry, } from "./types.ts";
6
+ export type { Ast, ChangeEvent, QueryId, ResultType, StoreDevObserver, StoreInspect } from "@rindle/client";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC9E,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAEvD,YAAY,EACV,UAAU,EACV,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,cAAc,EACd,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,UAAU,EACV,aAAa,GACd,MAAM,YAAY,CAAC;AAIpB,YAAY,EAAE,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ // @rindle/devtools — the framework-agnostic in-browser devtools core (DEBUG-TOOLS-BROWSER-DESIGN.md).
2
+ // Attaches to a running client over the read-only seams and maintains the §4 read-model: a mutation
3
+ // TIMELINE (the fork/rebase loop made visible), a QUERIES inspector, and a DELTA stream. Dev-only;
4
+ // pair it with a panel (`@rindle/react-devtools`) or read `getDevtoolsCore().getState()` directly.
5
+ export { DevtoolsCore } from "./core.js";
6
+ export { attachDevtools, getDevtoolsCore, getDevtoolsHub } from "./global.js";
7
+ export { collectTables, summarizeAst } from "./ast.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,oGAAoG;AACpG,mGAAmG;AACnG,mGAAmG;AAEnG,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG9E,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,137 @@
1
+ import type { Ast, ResultType, StoreDevObserver, StoreInspect } from "@rindle/client";
2
+ export type { Ast, ChangeEvent, FlatChange, FlatOp, QueryId, ResultType, StoreDevObserver, StoreInspect } from "@rindle/client";
3
+ /** One folded entry's debounce window — mirror of `@rindle/optimistic`'s `FoldInspect`. */
4
+ export interface FoldInspect {
5
+ foldKey: string;
6
+ debounceMs: number;
7
+ maxWaitMs?: number;
8
+ deferAcrossWrites: boolean;
9
+ flushed: boolean;
10
+ }
11
+ /** One pending mutation — mirror of `@rindle/optimistic`'s `PendingInspect`. */
12
+ export interface PendingInspect {
13
+ key: string;
14
+ mid: number | null;
15
+ name: string;
16
+ args: unknown;
17
+ tables: string[];
18
+ fold?: FoldInspect;
19
+ }
20
+ /** A snapshot of the optimistic loop — mirror of `@rindle/optimistic`'s `OptimisticInspect`. */
21
+ export interface OptimisticInspect {
22
+ pending: PendingInspect[];
23
+ confirmedLmid: number;
24
+ nextMid: number;
25
+ appliedCv: number;
26
+ bufferedFrames: number;
27
+ pendingTables: string[];
28
+ }
29
+ /** The Store surface the core reads (a structural subset of `@rindle/client`'s `Store`). */
30
+ export interface DevtoolsStore {
31
+ __attachDevtools(observer: StoreDevObserver): () => void;
32
+ __inspect(sampleRows?: number): StoreInspect;
33
+ }
34
+ /** The optional optimistic-backend capability — present on `OptimisticBackend`, absent on a plain
35
+ * wasm/replica backend (then there is no mutation timeline, but queries + deltas still work). */
36
+ export interface DevtoolsBackend {
37
+ __inspect(): OptimisticInspect;
38
+ }
39
+ /** What {@link attachDevtools} binds to: a `createRindleClient` app, or any `{ store, backend }`. */
40
+ export interface DevtoolsTarget {
41
+ store: DevtoolsStore;
42
+ /** Narrowed to {@link DevtoolsBackend} at runtime when it carries `__inspect` (capability probe). */
43
+ backend?: unknown;
44
+ }
45
+ /** A mutation's place in the fork/rebase lifecycle (DEBUG-TOOLS-BROWSER-DESIGN §4.1). */
46
+ export type MutationState = "pending" | "confirmed" | "dropped";
47
+ /** One row of the mutation timeline — the optimistic loop made visible. */
48
+ export interface TimelineEntry {
49
+ /** Stable identity across snapshots: the pending key (`m:<mid>` once a mid is dealt, else
50
+ * `f:<foldKey>` while a fold debounces). Retained after the entry settles. */
51
+ id: string;
52
+ /** The wire mutation id, or `null` for a still-folding entry. */
53
+ mid: number | null;
54
+ name: string;
55
+ args: unknown;
56
+ /** Tables the mutator touched (its pending-axis footprint). */
57
+ tables: string[];
58
+ state: MutationState;
59
+ /** True for a debounced/folded write; `fold` carries its window while it is live. */
60
+ folded: boolean;
61
+ fold?: FoldInspect;
62
+ /** Devtools-clock ms at first observation (invoke). */
63
+ invokedAt: number;
64
+ /** Devtools-clock ms when it left the pending stack (confirmed or dropped). */
65
+ settledAt?: number;
66
+ /** Heuristic (§4.1): view churn coincided with this mutation's confirmation — a POSSIBLE
67
+ * snap-back (the optimistic prediction diverged from the authoritative server result). Labeled
68
+ * "possible" because unrelated server data released in the same coherent batch also shows churn;
69
+ * a precise signal needs a reconcile-boundary event (a future engine seam). */
70
+ reconciledWithChurn: boolean;
71
+ /** qids of live queries whose tables this mutation touches (computed against the current views). */
72
+ affectedQueries: number[];
73
+ }
74
+ /** One materialized view in the queries inspector (DEBUG-TOOLS-BROWSER-DESIGN §4.2). */
75
+ export interface QueryEntry {
76
+ qid: number;
77
+ ast: Ast;
78
+ /** The root table (`ast.table`). */
79
+ table: string;
80
+ /** A one-line human summary of the AST (table, filters, order, limit, relationships). */
81
+ summary: string;
82
+ /** Every base table the query reads (root + correlated subqueries). */
83
+ tables: string[];
84
+ resultType: ResultType;
85
+ rowCount: number;
86
+ sample: readonly unknown[];
87
+ /** Does any pending mutation touch this query's tables? (the §7.2 pending axis). */
88
+ pending: boolean;
89
+ }
90
+ /** The kind of a delta-stream row. `child` is an Add/Remove/Edit addressed at a NESTED path
91
+ * (`depth > 0`) — rindle's relationship-level change (DEBUG-TOOLS-BROWSER-DESIGN §4.3). */
92
+ export type DeltaKind = "hello" | "snapshot" | "add" | "remove" | "edit";
93
+ /** One entry of the live delta stream — the IVM change primitive made visible (§4.3). */
94
+ export interface DeltaEntry {
95
+ seq: number;
96
+ at: number;
97
+ qid: number;
98
+ kind: DeltaKind;
99
+ /** Nesting depth of the change path: 0 = top-level row, >0 = a child (relationship) change. */
100
+ depth: number;
101
+ /** A compact, human-readable description of the change. */
102
+ label: string;
103
+ }
104
+ /** The whole devtools snapshot a panel renders. Reference-stable arrays between updates where the
105
+ * underlying data did not change is NOT guaranteed — panels should treat each `getState()` as fresh. */
106
+ export interface DevtoolsState {
107
+ /** Newest-last mutation timeline (capped). */
108
+ timeline: TimelineEntry[];
109
+ /** Every live materialized view. */
110
+ queries: QueryEntry[];
111
+ /** Newest-last delta stream ring (capped). */
112
+ deltas: DeltaEntry[];
113
+ /** The raw optimistic-loop snapshot, when the backend exposes one (a "loop" summary line). */
114
+ optimistic?: OptimisticInspect;
115
+ capabilities: {
116
+ optimistic: boolean;
117
+ };
118
+ }
119
+ /** Construction options for {@link DevtoolsCore}. */
120
+ export interface DevtoolsCoreOptions {
121
+ /** Max timeline rows retained (oldest settled rows drop first). Default 200. */
122
+ timelineCap?: number;
123
+ /** Max delta-stream rows retained. Default 500. */
124
+ deltaCap?: number;
125
+ /** Per-query row sample size pulled from the store. Default 25. */
126
+ sampleRows?: number;
127
+ /** Safety-net poll interval (ms) that catches state moves with no event — a fold's debounced
128
+ * flush, or a pending flip on an already-`complete` query. Default 0 (off); {@link attachDevtools}
129
+ * turns it on. */
130
+ pollMs?: number;
131
+ /** Coalesce event-driven recomputes onto a microtask. Default true; tests pass `false` and drive
132
+ * recomputes explicitly via {@link DevtoolsCore.refresh}. */
133
+ autoFlush?: boolean;
134
+ /** Injectable clock (ms). Default `Date.now`; tests inject a deterministic counter. */
135
+ now?: () => number;
136
+ }
137
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,5 @@
1
+ // The devtools read-model (DEBUG-TOOLS-BROWSER-DESIGN.md §4) and the read-only seams the core
2
+ // attaches to. Everything here is derived, in dev, from state the client already holds — the core
3
+ // adds no hot-path instrumentation (§1.1 "surface, not instrument").
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,kGAAkG;AAClG,qEAAqE"}