@shadow-js/devtools 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +21 -0
  3. package/README.md +576 -2
  4. package/dist/dev/agent.d.ts +239 -0
  5. package/dist/dev/agent.js +626 -0
  6. package/dist/dev/agent.js.map +1 -0
  7. package/dist/dev/backend.d.ts +53 -0
  8. package/dist/dev/backend.js +53 -0
  9. package/dist/dev/backend.js.map +1 -0
  10. package/dist/dev/hook.d.ts +83 -0
  11. package/dist/dev/hook.js +136 -0
  12. package/dist/dev/hook.js.map +1 -0
  13. package/dist/dev/index.d.ts +17 -0
  14. package/dist/dev/index.js +17 -0
  15. package/dist/dev/inspection.d.ts +226 -0
  16. package/dist/dev/inspection.js +263 -0
  17. package/dist/dev/inspection.js.map +1 -0
  18. package/dist/dev/inspector.d.ts +331 -0
  19. package/dist/dev/inspector.js +429 -0
  20. package/dist/dev/inspector.js.map +1 -0
  21. package/dist/dev/model.d.ts +418 -0
  22. package/dist/dev/model.js +559 -0
  23. package/dist/dev/model.js.map +1 -0
  24. package/dist/dev/mount.d.ts +207 -0
  25. package/dist/dev/mount.js +256 -0
  26. package/dist/dev/mount.js.map +1 -0
  27. package/dist/dev/overlay.d.ts +102 -0
  28. package/dist/dev/overlay.js +176 -0
  29. package/dist/dev/overlay.js.map +1 -0
  30. package/dist/dev/preview.d.ts +74 -0
  31. package/dist/dev/preview.js +122 -0
  32. package/dist/dev/preview.js.map +1 -0
  33. package/dist/dev/protocol.d.ts +288 -0
  34. package/dist/dev/protocol.js +109 -0
  35. package/dist/dev/protocol.js.map +1 -0
  36. package/dist/dev/ring.d.ts +50 -0
  37. package/dist/dev/ring.js +94 -0
  38. package/dist/dev/ring.js.map +1 -0
  39. package/dist/dev/transport.d.ts +347 -0
  40. package/dist/dev/transport.js +394 -0
  41. package/dist/dev/transport.js.map +1 -0
  42. package/dist/dev/ui/details.js +294 -0
  43. package/dist/dev/ui/details.js.map +1 -0
  44. package/dist/dev/ui/inspection-state.d.ts +43 -0
  45. package/dist/dev/ui/inspection-state.js +62 -0
  46. package/dist/dev/ui/inspection-state.js.map +1 -0
  47. package/dist/dev/ui/issues.js +85 -0
  48. package/dist/dev/ui/issues.js.map +1 -0
  49. package/dist/dev/ui/panel.d.ts +55 -0
  50. package/dist/dev/ui/panel.js +160 -0
  51. package/dist/dev/ui/panel.js.map +1 -0
  52. package/dist/dev/ui/rows.d.ts +44 -0
  53. package/dist/dev/ui/rows.js +132 -0
  54. package/dist/dev/ui/rows.js.map +1 -0
  55. package/dist/dev/ui/state-tab.js +88 -0
  56. package/dist/dev/ui/state-tab.js.map +1 -0
  57. package/dist/dev/ui/state.d.ts +192 -0
  58. package/dist/dev/ui/state.js +147 -0
  59. package/dist/dev/ui/state.js.map +1 -0
  60. package/dist/dev/ui/styles.d.ts +31 -0
  61. package/dist/dev/ui/styles.js +113 -0
  62. package/dist/dev/ui/styles.js.map +1 -0
  63. package/dist/dev/ui/timeline.js +135 -0
  64. package/dist/dev/ui/timeline.js.map +1 -0
  65. package/dist/dev/ui/tree.js +186 -0
  66. package/dist/dev/ui/tree.js.map +1 -0
  67. package/dist/prod/agent.js +2 -0
  68. package/dist/prod/agent.js.map +1 -0
  69. package/dist/prod/backend.js +2 -0
  70. package/dist/prod/backend.js.map +1 -0
  71. package/dist/prod/hook.js +2 -0
  72. package/dist/prod/hook.js.map +1 -0
  73. package/dist/prod/index.js +1 -0
  74. package/dist/prod/inspection.js +2 -0
  75. package/dist/prod/inspection.js.map +1 -0
  76. package/dist/prod/inspector.js +2 -0
  77. package/dist/prod/inspector.js.map +1 -0
  78. package/dist/prod/model.js +2 -0
  79. package/dist/prod/model.js.map +1 -0
  80. package/dist/prod/mount.js +2 -0
  81. package/dist/prod/mount.js.map +1 -0
  82. package/dist/prod/overlay.js +11 -0
  83. package/dist/prod/overlay.js.map +1 -0
  84. package/dist/prod/preview.js +2 -0
  85. package/dist/prod/preview.js.map +1 -0
  86. package/dist/prod/protocol.js +2 -0
  87. package/dist/prod/protocol.js.map +1 -0
  88. package/dist/prod/ring.js +2 -0
  89. package/dist/prod/ring.js.map +1 -0
  90. package/dist/prod/transport.js +2 -0
  91. package/dist/prod/transport.js.map +1 -0
  92. package/dist/prod/ui/details.js +2 -0
  93. package/dist/prod/ui/details.js.map +1 -0
  94. package/dist/prod/ui/inspection-state.js +2 -0
  95. package/dist/prod/ui/inspection-state.js.map +1 -0
  96. package/dist/prod/ui/issues.js +2 -0
  97. package/dist/prod/ui/issues.js.map +1 -0
  98. package/dist/prod/ui/panel.js +2 -0
  99. package/dist/prod/ui/panel.js.map +1 -0
  100. package/dist/prod/ui/rows.js +2 -0
  101. package/dist/prod/ui/rows.js.map +1 -0
  102. package/dist/prod/ui/state-tab.js +2 -0
  103. package/dist/prod/ui/state-tab.js.map +1 -0
  104. package/dist/prod/ui/state.js +2 -0
  105. package/dist/prod/ui/state.js.map +1 -0
  106. package/dist/prod/ui/styles.js +82 -0
  107. package/dist/prod/ui/styles.js.map +1 -0
  108. package/dist/prod/ui/timeline.js +2 -0
  109. package/dist/prod/ui/timeline.js.map +1 -0
  110. package/dist/prod/ui/tree.js +2 -0
  111. package/dist/prod/ui/tree.js.map +1 -0
  112. package/package.json +69 -4
@@ -0,0 +1,288 @@
1
+ import { InspectionEntry, InspectionSource } from "./inspection.js";
2
+ //#region src/protocol.d.ts
3
+ /**
4
+ * Protocol version carried by the hook (`hook.version`). Bump it when the meaning of an existing
5
+ * event argument changes; adding event kinds does not require a bump.
6
+ *
7
+ * @example
8
+ * import { getHook, PROTOCOL_VERSION } from "@shadow-js/devtools";
9
+ *
10
+ * const speaks = getHook()?.version === PROTOCOL_VERSION; // true when a matching hook is installed
11
+ * @since 1.0.0
12
+ */
13
+ export declare const PROTOCOL_VERSION = 1;
14
+ /**
15
+ * Name of the global the runtime looks up: `globalThis.__SHADOWJS_DEVTOOLS_HOOK__`.
16
+ *
17
+ * @example
18
+ * import { HOOK_KEY } from "@shadow-js/devtools";
19
+ *
20
+ * const installed: unknown = Reflect.get(globalThis, HOOK_KEY);
21
+ * @since 1.0.0
22
+ */
23
+ export declare const HOOK_KEY = "__SHADOWJS_DEVTOOLS_HOOK__";
24
+ /**
25
+ * Event kinds of `hook.report(kind, ...)`, with the meaning of the arguments. An argument the table
26
+ * leaves blank is not sent for that kind. `null` means "no node" (for example, a computation outside
27
+ * any owner); `undefined` means "no name".
28
+ *
29
+ * | kind | a | b | c | d | e |
30
+ * | --- | --- | --- | --- | --- | --- |
31
+ * | `OWNER_CREATED` | owner | parent owner, or `null` for a root | `"root"` or `"owner"` | name, when known (the core runtime sends none) | |
32
+ * | `OWNER_DISPOSED` | owner | | | | |
33
+ * | `SIGNAL_CREATED` | signal | owner or `null` | name or `undefined` | initial value (shown as a preview) | |
34
+ * | `SIGNAL_WRITTEN` | signal | next value | writing computation, or `null` | observers (array of computations), or `null` | |
35
+ * | `COMPUTATION_CREATED` | computation | owner or `null` | `"memo"` or `"effect"` | name or `undefined` | render flag |
36
+ * | `COMPUTATION_RUN` | computation | start time (ms) | duration (ms) | value changed | sources (array of the nodes it read) |
37
+ * | `COMPUTATION_DISPOSED` | computation | | | | |
38
+ * | `COMPONENT_MOUNTED` | owner of the component | name or `undefined` | what the component returned: a DOM node, an array of them, or another value | | |
39
+ * | `COMPONENT_UNMOUNTED` | owner of the component | | | | |
40
+ * | `RESOURCE_STATE` | resource handle | state (string) | name or `undefined` | owner or `null` | |
41
+ * | `ERROR` | source owner or `null` | the thrown value | diagnostic code or `undefined` | | |
42
+ * | `HYDRATION_MISMATCH` | message | expected side (text or node; the web runtime sends the element key) | actual side (text or node; the web runtime sends none) | | |
43
+ * | `FLUSH_START` | render queue size | user queue size | | | |
44
+ * | `FLUSH_END` | computations run in the flush | | | | |
45
+ * | `STORE_CREATED` | store handle ({@link StoreHandle}) | owner or `null` | | | |
46
+ * | `TRANSITION_START` | | | | | |
47
+ * | `TRANSITION_END` | transitions resolved (count) | | | | |
48
+ *
49
+ * The agent ignores `TRANSITION_START` and `TRANSITION_END` today, and any other unknown kind.
50
+ *
51
+ * @example
52
+ * import { EVENT } from "@shadow-js/devtools";
53
+ *
54
+ * const kind = EVENT.COMPUTATION_RUN; // 6: a = computation, b = start, c = duration, d = changed, e = sources
55
+ * @since 1.0.0
56
+ */
57
+ export declare const EVENT: {
58
+ readonly OWNER_CREATED: 1;
59
+ readonly OWNER_DISPOSED: 2;
60
+ readonly SIGNAL_CREATED: 3;
61
+ readonly SIGNAL_WRITTEN: 4;
62
+ readonly COMPUTATION_CREATED: 5;
63
+ readonly COMPUTATION_RUN: 6;
64
+ readonly COMPUTATION_DISPOSED: 7;
65
+ readonly COMPONENT_MOUNTED: 8;
66
+ readonly COMPONENT_UNMOUNTED: 9;
67
+ readonly RESOURCE_STATE: 10;
68
+ readonly ERROR: 11;
69
+ readonly HYDRATION_MISMATCH: 12;
70
+ readonly FLUSH_START: 13;
71
+ readonly FLUSH_END: 14;
72
+ readonly STORE_CREATED: 15;
73
+ readonly TRANSITION_START: 16;
74
+ readonly TRANSITION_END: 17;
75
+ };
76
+ /**
77
+ * One of the numeric event kinds.
78
+ *
79
+ * @since 1.0.0
80
+ */
81
+ export type EventKind = (typeof EVENT)[keyof typeof EVENT];
82
+ /**
83
+ * What a runtime passes with `STORE_CREATED`, so tools can show and edit a store without knowing its
84
+ * implementation.
85
+ *
86
+ * @since 1.0.0
87
+ */
88
+ export interface StoreHandle {
89
+ /** Label shown in the store viewer. */
90
+ readonly name: string;
91
+ /** A plain copy of the current value (what `snapshot(store)` returns). */
92
+ snapshot(): unknown;
93
+ /** Writes `value` at `path` (property names and array indices) in one batched update. */
94
+ set(path: readonly (string | number)[], value: unknown): void;
95
+ }
96
+ /**
97
+ * The receiver side of the hook: an agent implements it to be told about events.
98
+ *
99
+ * @since 1.0.0
100
+ */
101
+ export interface HookSink {
102
+ /**
103
+ * Called synchronously for every reported event, with the arguments of {@link EVENT}. Must be cheap
104
+ * and must not throw; a throw is caught by the hook and counted as a fault.
105
+ */
106
+ receive(kind: number, a: unknown, b: unknown, c: unknown, d: unknown, e: unknown): void;
107
+ }
108
+ /**
109
+ * Most rows one inspection source may return. The rest are cut and a final row says so, so a panel
110
+ * never receives an unbounded list.
111
+ *
112
+ * @example
113
+ * import { MAX_INSPECTION_ROWS } from "@shadow-js/devtools";
114
+ * const cap: number = MAX_INSPECTION_ROWS; // 500
115
+ * @since 1.0.0
116
+ */
117
+ export declare const MAX_INSPECTION_ROWS = 500;
118
+ /**
119
+ * Most sources an app may register with {@link registerInspection}. Registering one more is refused,
120
+ * and the names already registered keep working.
121
+ *
122
+ * @example
123
+ * import { MAX_INSPECTIONS } from "@shadow-js/devtools";
124
+ * const cap: number = MAX_INSPECTIONS; // 16
125
+ * @since 1.0.0
126
+ */
127
+ export declare const MAX_INSPECTIONS = 16;
128
+ /**
129
+ * The object installed at `globalThis.__SHADOWJS_DEVTOOLS_HOOK__` by `installHook`. A runtime calls
130
+ * `report` on it; a tool attaches a {@link HookSink} to receive the events.
131
+ *
132
+ * @example
133
+ * import { EVENT, getHook } from "@shadow-js/devtools";
134
+ *
135
+ * const hook = getHook();
136
+ * hook?.report(EVENT.FLUSH_END, 0); // a flush that ran no computations
137
+ * @since 1.0.0
138
+ */
139
+ export interface DevtoolsHook {
140
+ /** Protocol version; compare with {@link PROTOCOL_VERSION} before reporting. */
141
+ readonly version: number;
142
+ /** Runtime to tools. Positional arguments, see {@link EVENT}. Never throws. */
143
+ report(kind: number, a?: unknown, b?: unknown, c?: unknown, d?: unknown, e?: unknown): void;
144
+ /**
145
+ * Registers a sink; returns the function that removes it. Events buffered before the first sink
146
+ * attached (up to the buffer size given to `installHook`) are replayed to that first sink only.
147
+ */
148
+ attach(sink: HookSink): () => void;
149
+ /** Number of attached sinks. */
150
+ readonly sinks: number;
151
+ /**
152
+ * Registers an inspection source under `name`; re-registering a name replaces it. Returns false when
153
+ * refused (the limit of {@link MAX_INSPECTIONS} is reached). Hooks of this package provide it; a
154
+ * hook made by hand need not.
155
+ */
156
+ inspect?(name: string, source: InspectionSource): boolean;
157
+ /** The registered inspection sources, in registration order. Hooks of this package provide it. */
158
+ inspections?(): readonly InspectionEntry[];
159
+ }
160
+ /**
161
+ * Events as they cross a process boundary: plain, JSON-safe objects that refer to nodes by id.
162
+ * `seq` is a gapless counter; `t` is the time in milliseconds on the reporting clock.
163
+ *
164
+ * @since 1.0.0
165
+ */
166
+ export type WireEvent = {
167
+ k: "owner+";
168
+ seq: number;
169
+ t: number;
170
+ id: number;
171
+ parent: number;
172
+ label: string;
173
+ name?: string;
174
+ } | {
175
+ k: "owner-";
176
+ seq: number;
177
+ t: number;
178
+ id: number;
179
+ } | {
180
+ k: "signal+";
181
+ seq: number;
182
+ t: number;
183
+ id: number;
184
+ owner: number;
185
+ name?: string;
186
+ preview: string;
187
+ } | {
188
+ k: "write";
189
+ seq: number;
190
+ t: number;
191
+ id: number;
192
+ preview: string;
193
+ writer: number;
194
+ observers: number[];
195
+ } | {
196
+ k: "comp+";
197
+ seq: number;
198
+ t: number;
199
+ id: number;
200
+ owner: number;
201
+ kind: "memo" | "effect";
202
+ render: boolean;
203
+ name?: string;
204
+ } | {
205
+ k: "run";
206
+ seq: number;
207
+ t: number;
208
+ id: number;
209
+ start: number;
210
+ duration: number;
211
+ changed: boolean;
212
+ sources: number[];
213
+ } | {
214
+ k: "comp-";
215
+ seq: number;
216
+ t: number;
217
+ id: number;
218
+ } | {
219
+ k: "mount";
220
+ seq: number;
221
+ t: number;
222
+ id: number;
223
+ name: string;
224
+ } | {
225
+ k: "unmount";
226
+ seq: number;
227
+ t: number;
228
+ id: number;
229
+ } | {
230
+ k: "resource";
231
+ seq: number;
232
+ t: number;
233
+ id: number;
234
+ owner: number;
235
+ state: string;
236
+ name?: string;
237
+ } | {
238
+ k: "error";
239
+ seq: number;
240
+ t: number;
241
+ id: number;
242
+ message: string;
243
+ code?: string;
244
+ } | {
245
+ k: "hydration";
246
+ seq: number;
247
+ t: number;
248
+ message: string;
249
+ expected: string;
250
+ actual: string;
251
+ } | {
252
+ k: "flush+";
253
+ seq: number;
254
+ t: number;
255
+ render: number;
256
+ user: number;
257
+ } | {
258
+ k: "flush-";
259
+ seq: number;
260
+ t: number;
261
+ runs: number;
262
+ } | {
263
+ k: "store+";
264
+ seq: number;
265
+ t: number;
266
+ id: number;
267
+ owner: number;
268
+ name: string;
269
+ };
270
+ /**
271
+ * Discriminant of {@link WireEvent}.
272
+ *
273
+ * @since 1.0.0
274
+ */
275
+ export type WireKind = WireEvent["k"];
276
+ /**
277
+ * The sentinel used where a wire event refers to "no node". The agent assigns node ids from 1, so
278
+ * `NO_ID` never names a node.
279
+ *
280
+ * @example
281
+ * import { NO_ID } from "@shadow-js/devtools";
282
+ *
283
+ * const isRoot = (parent: number): boolean => parent === NO_ID;
284
+ * @since 1.0.0
285
+ */
286
+ export declare const NO_ID = 0;
287
+ //#endregion
288
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1,109 @@
1
+ //#region src/protocol.ts
2
+ /**
3
+ * Protocol version carried by the hook (`hook.version`). Bump it when the meaning of an existing
4
+ * event argument changes; adding event kinds does not require a bump.
5
+ *
6
+ * @example
7
+ * import { getHook, PROTOCOL_VERSION } from "@shadow-js/devtools";
8
+ *
9
+ * const speaks = getHook()?.version === PROTOCOL_VERSION; // true when a matching hook is installed
10
+ * @since 1.0.0
11
+ */
12
+ const PROTOCOL_VERSION = 1;
13
+ /**
14
+ * Name of the global the runtime looks up: `globalThis.__SHADOWJS_DEVTOOLS_HOOK__`.
15
+ *
16
+ * @example
17
+ * import { HOOK_KEY } from "@shadow-js/devtools";
18
+ *
19
+ * const installed: unknown = Reflect.get(globalThis, HOOK_KEY);
20
+ * @since 1.0.0
21
+ */
22
+ const HOOK_KEY = "__SHADOWJS_DEVTOOLS_HOOK__";
23
+ /**
24
+ * Event kinds of `hook.report(kind, ...)`, with the meaning of the arguments. An argument the table
25
+ * leaves blank is not sent for that kind. `null` means "no node" (for example, a computation outside
26
+ * any owner); `undefined` means "no name".
27
+ *
28
+ * | kind | a | b | c | d | e |
29
+ * | --- | --- | --- | --- | --- | --- |
30
+ * | `OWNER_CREATED` | owner | parent owner, or `null` for a root | `"root"` or `"owner"` | name, when known (the core runtime sends none) | |
31
+ * | `OWNER_DISPOSED` | owner | | | | |
32
+ * | `SIGNAL_CREATED` | signal | owner or `null` | name or `undefined` | initial value (shown as a preview) | |
33
+ * | `SIGNAL_WRITTEN` | signal | next value | writing computation, or `null` | observers (array of computations), or `null` | |
34
+ * | `COMPUTATION_CREATED` | computation | owner or `null` | `"memo"` or `"effect"` | name or `undefined` | render flag |
35
+ * | `COMPUTATION_RUN` | computation | start time (ms) | duration (ms) | value changed | sources (array of the nodes it read) |
36
+ * | `COMPUTATION_DISPOSED` | computation | | | | |
37
+ * | `COMPONENT_MOUNTED` | owner of the component | name or `undefined` | what the component returned: a DOM node, an array of them, or another value | | |
38
+ * | `COMPONENT_UNMOUNTED` | owner of the component | | | | |
39
+ * | `RESOURCE_STATE` | resource handle | state (string) | name or `undefined` | owner or `null` | |
40
+ * | `ERROR` | source owner or `null` | the thrown value | diagnostic code or `undefined` | | |
41
+ * | `HYDRATION_MISMATCH` | message | expected side (text or node; the web runtime sends the element key) | actual side (text or node; the web runtime sends none) | | |
42
+ * | `FLUSH_START` | render queue size | user queue size | | | |
43
+ * | `FLUSH_END` | computations run in the flush | | | | |
44
+ * | `STORE_CREATED` | store handle ({@link StoreHandle}) | owner or `null` | | | |
45
+ * | `TRANSITION_START` | | | | | |
46
+ * | `TRANSITION_END` | transitions resolved (count) | | | | |
47
+ *
48
+ * The agent ignores `TRANSITION_START` and `TRANSITION_END` today, and any other unknown kind.
49
+ *
50
+ * @example
51
+ * import { EVENT } from "@shadow-js/devtools";
52
+ *
53
+ * const kind = EVENT.COMPUTATION_RUN; // 6: a = computation, b = start, c = duration, d = changed, e = sources
54
+ * @since 1.0.0
55
+ */
56
+ const EVENT = {
57
+ OWNER_CREATED: 1,
58
+ OWNER_DISPOSED: 2,
59
+ SIGNAL_CREATED: 3,
60
+ SIGNAL_WRITTEN: 4,
61
+ COMPUTATION_CREATED: 5,
62
+ COMPUTATION_RUN: 6,
63
+ COMPUTATION_DISPOSED: 7,
64
+ COMPONENT_MOUNTED: 8,
65
+ COMPONENT_UNMOUNTED: 9,
66
+ RESOURCE_STATE: 10,
67
+ ERROR: 11,
68
+ HYDRATION_MISMATCH: 12,
69
+ FLUSH_START: 13,
70
+ FLUSH_END: 14,
71
+ STORE_CREATED: 15,
72
+ TRANSITION_START: 16,
73
+ TRANSITION_END: 17
74
+ };
75
+ /**
76
+ * Most rows one inspection source may return. The rest are cut and a final row says so, so a panel
77
+ * never receives an unbounded list.
78
+ *
79
+ * @example
80
+ * import { MAX_INSPECTION_ROWS } from "@shadow-js/devtools";
81
+ * const cap: number = MAX_INSPECTION_ROWS; // 500
82
+ * @since 1.0.0
83
+ */
84
+ const MAX_INSPECTION_ROWS = 500;
85
+ /**
86
+ * Most sources an app may register with {@link registerInspection}. Registering one more is refused,
87
+ * and the names already registered keep working.
88
+ *
89
+ * @example
90
+ * import { MAX_INSPECTIONS } from "@shadow-js/devtools";
91
+ * const cap: number = MAX_INSPECTIONS; // 16
92
+ * @since 1.0.0
93
+ */
94
+ const MAX_INSPECTIONS = 16;
95
+ /**
96
+ * The sentinel used where a wire event refers to "no node". The agent assigns node ids from 1, so
97
+ * `NO_ID` never names a node.
98
+ *
99
+ * @example
100
+ * import { NO_ID } from "@shadow-js/devtools";
101
+ *
102
+ * const isRoot = (parent: number): boolean => parent === NO_ID;
103
+ * @since 1.0.0
104
+ */
105
+ const NO_ID = 0;
106
+ //#endregion
107
+ export { EVENT, HOOK_KEY, MAX_INSPECTIONS, MAX_INSPECTION_ROWS, NO_ID, PROTOCOL_VERSION };
108
+
109
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","names":[],"sources":["../../src/protocol.ts"],"sourcesContent":["/**\n * The dev-hook protocol: how a ShadowJS runtime reports its reactive graph to developer tools.\n *\n * The runtime (core, web and query) calls `hook.report(kind, a, b, c, d, e)` for every event, in\n * development builds only (`if (__DEV__) { ... }`). The arguments are positional and the objects\n * are the runtime's own nodes: the hook never copies them, so reporting allocates nothing. An agent\n * (see {@link createAgent}) turns them into serializable, id-based {@link WireEvent}s.\n *\n * The protocol is versioned by {@link PROTOCOL_VERSION}. A runtime reports only to a hook whose\n * version it knows, and the agent ignores events with an unknown kind, so old runtimes and new\n * tools (and the other way round) degrade instead of breaking.\n *\n * @packageDocumentation\n */\n\nimport type { InspectionEntry, InspectionSource } from \"./inspection\";\n\n/**\n * Protocol version carried by the hook (`hook.version`). Bump it when the meaning of an existing\n * event argument changes; adding event kinds does not require a bump.\n *\n * @example\n * import { getHook, PROTOCOL_VERSION } from \"@shadow-js/devtools\";\n *\n * const speaks = getHook()?.version === PROTOCOL_VERSION; // true when a matching hook is installed\n * @since 1.0.0\n */\nexport const PROTOCOL_VERSION = 1;\n\n/**\n * Name of the global the runtime looks up: `globalThis.__SHADOWJS_DEVTOOLS_HOOK__`.\n *\n * @example\n * import { HOOK_KEY } from \"@shadow-js/devtools\";\n *\n * const installed: unknown = Reflect.get(globalThis, HOOK_KEY);\n * @since 1.0.0\n */\nexport const HOOK_KEY = \"__SHADOWJS_DEVTOOLS_HOOK__\";\n\n/**\n * Event kinds of `hook.report(kind, ...)`, with the meaning of the arguments. An argument the table\n * leaves blank is not sent for that kind. `null` means \"no node\" (for example, a computation outside\n * any owner); `undefined` means \"no name\".\n *\n * | kind | a | b | c | d | e |\n * | --- | --- | --- | --- | --- | --- |\n * | `OWNER_CREATED` | owner | parent owner, or `null` for a root | `\"root\"` or `\"owner\"` | name, when known (the core runtime sends none) | |\n * | `OWNER_DISPOSED` | owner | | | | |\n * | `SIGNAL_CREATED` | signal | owner or `null` | name or `undefined` | initial value (shown as a preview) | |\n * | `SIGNAL_WRITTEN` | signal | next value | writing computation, or `null` | observers (array of computations), or `null` | |\n * | `COMPUTATION_CREATED` | computation | owner or `null` | `\"memo\"` or `\"effect\"` | name or `undefined` | render flag |\n * | `COMPUTATION_RUN` | computation | start time (ms) | duration (ms) | value changed | sources (array of the nodes it read) |\n * | `COMPUTATION_DISPOSED` | computation | | | | |\n * | `COMPONENT_MOUNTED` | owner of the component | name or `undefined` | what the component returned: a DOM node, an array of them, or another value | | |\n * | `COMPONENT_UNMOUNTED` | owner of the component | | | | |\n * | `RESOURCE_STATE` | resource handle | state (string) | name or `undefined` | owner or `null` | |\n * | `ERROR` | source owner or `null` | the thrown value | diagnostic code or `undefined` | | |\n * | `HYDRATION_MISMATCH` | message | expected side (text or node; the web runtime sends the element key) | actual side (text or node; the web runtime sends none) | | |\n * | `FLUSH_START` | render queue size | user queue size | | | |\n * | `FLUSH_END` | computations run in the flush | | | | |\n * | `STORE_CREATED` | store handle ({@link StoreHandle}) | owner or `null` | | | |\n * | `TRANSITION_START` | | | | | |\n * | `TRANSITION_END` | transitions resolved (count) | | | | |\n *\n * The agent ignores `TRANSITION_START` and `TRANSITION_END` today, and any other unknown kind.\n *\n * @example\n * import { EVENT } from \"@shadow-js/devtools\";\n *\n * const kind = EVENT.COMPUTATION_RUN; // 6: a = computation, b = start, c = duration, d = changed, e = sources\n * @since 1.0.0\n */\nexport const EVENT: {\n readonly OWNER_CREATED: 1;\n readonly OWNER_DISPOSED: 2;\n readonly SIGNAL_CREATED: 3;\n readonly SIGNAL_WRITTEN: 4;\n readonly COMPUTATION_CREATED: 5;\n readonly COMPUTATION_RUN: 6;\n readonly COMPUTATION_DISPOSED: 7;\n readonly COMPONENT_MOUNTED: 8;\n readonly COMPONENT_UNMOUNTED: 9;\n readonly RESOURCE_STATE: 10;\n readonly ERROR: 11;\n readonly HYDRATION_MISMATCH: 12;\n readonly FLUSH_START: 13;\n readonly FLUSH_END: 14;\n readonly STORE_CREATED: 15;\n readonly TRANSITION_START: 16;\n readonly TRANSITION_END: 17;\n} = {\n OWNER_CREATED: 1,\n OWNER_DISPOSED: 2,\n SIGNAL_CREATED: 3,\n SIGNAL_WRITTEN: 4,\n COMPUTATION_CREATED: 5,\n COMPUTATION_RUN: 6,\n COMPUTATION_DISPOSED: 7,\n COMPONENT_MOUNTED: 8,\n COMPONENT_UNMOUNTED: 9,\n RESOURCE_STATE: 10,\n ERROR: 11,\n HYDRATION_MISMATCH: 12,\n FLUSH_START: 13,\n FLUSH_END: 14,\n STORE_CREATED: 15,\n TRANSITION_START: 16,\n TRANSITION_END: 17,\n};\n\n/**\n * One of the numeric event kinds.\n *\n * @since 1.0.0\n */\nexport type EventKind = (typeof EVENT)[keyof typeof EVENT];\n\n/**\n * What a runtime passes with `STORE_CREATED`, so tools can show and edit a store without knowing its\n * implementation.\n *\n * @since 1.0.0\n */\nexport interface StoreHandle {\n /** Label shown in the store viewer. */\n readonly name: string;\n /** A plain copy of the current value (what `snapshot(store)` returns). */\n snapshot(): unknown;\n /** Writes `value` at `path` (property names and array indices) in one batched update. */\n set(path: readonly (string | number)[], value: unknown): void;\n}\n\n/**\n * The receiver side of the hook: an agent implements it to be told about events.\n *\n * @since 1.0.0\n */\nexport interface HookSink {\n /**\n * Called synchronously for every reported event, with the arguments of {@link EVENT}. Must be cheap\n * and must not throw; a throw is caught by the hook and counted as a fault.\n */\n receive(kind: number, a: unknown, b: unknown, c: unknown, d: unknown, e: unknown): void;\n}\n\n/**\n * Most rows one inspection source may return. The rest are cut and a final row says so, so a panel\n * never receives an unbounded list.\n *\n * @example\n * import { MAX_INSPECTION_ROWS } from \"@shadow-js/devtools\";\n * const cap: number = MAX_INSPECTION_ROWS; // 500\n * @since 1.0.0\n */\nexport const MAX_INSPECTION_ROWS = 500;\n\n/**\n * Most sources an app may register with {@link registerInspection}. Registering one more is refused,\n * and the names already registered keep working.\n *\n * @example\n * import { MAX_INSPECTIONS } from \"@shadow-js/devtools\";\n * const cap: number = MAX_INSPECTIONS; // 16\n * @since 1.0.0\n */\nexport const MAX_INSPECTIONS = 16;\n\n/**\n * The object installed at `globalThis.__SHADOWJS_DEVTOOLS_HOOK__` by `installHook`. A runtime calls\n * `report` on it; a tool attaches a {@link HookSink} to receive the events.\n *\n * @example\n * import { EVENT, getHook } from \"@shadow-js/devtools\";\n *\n * const hook = getHook();\n * hook?.report(EVENT.FLUSH_END, 0); // a flush that ran no computations\n * @since 1.0.0\n */\nexport interface DevtoolsHook {\n /** Protocol version; compare with {@link PROTOCOL_VERSION} before reporting. */\n readonly version: number;\n /** Runtime to tools. Positional arguments, see {@link EVENT}. Never throws. */\n report(kind: number, a?: unknown, b?: unknown, c?: unknown, d?: unknown, e?: unknown): void;\n /**\n * Registers a sink; returns the function that removes it. Events buffered before the first sink\n * attached (up to the buffer size given to `installHook`) are replayed to that first sink only.\n */\n attach(sink: HookSink): () => void;\n /** Number of attached sinks. */\n readonly sinks: number;\n /**\n * Registers an inspection source under `name`; re-registering a name replaces it. Returns false when\n * refused (the limit of {@link MAX_INSPECTIONS} is reached). Hooks of this package provide it; a\n * hook made by hand need not.\n */\n inspect?(name: string, source: InspectionSource): boolean;\n /** The registered inspection sources, in registration order. Hooks of this package provide it. */\n inspections?(): readonly InspectionEntry[];\n}\n\n/**\n * Events as they cross a process boundary: plain, JSON-safe objects that refer to nodes by id.\n * `seq` is a gapless counter; `t` is the time in milliseconds on the reporting clock.\n *\n * @since 1.0.0\n */\nexport type WireEvent =\n | {\n k: \"owner+\";\n seq: number;\n t: number;\n id: number;\n parent: number;\n label: string;\n name?: string;\n }\n | { k: \"owner-\"; seq: number; t: number; id: number }\n | {\n k: \"signal+\";\n seq: number;\n t: number;\n id: number;\n owner: number;\n name?: string;\n preview: string;\n }\n | {\n k: \"write\";\n seq: number;\n t: number;\n id: number;\n preview: string;\n writer: number;\n observers: number[];\n }\n | {\n k: \"comp+\";\n seq: number;\n t: number;\n id: number;\n owner: number;\n kind: \"memo\" | \"effect\";\n render: boolean;\n name?: string;\n }\n | {\n k: \"run\";\n seq: number;\n t: number;\n id: number;\n start: number;\n duration: number;\n changed: boolean;\n sources: number[];\n }\n | { k: \"comp-\"; seq: number; t: number; id: number }\n | { k: \"mount\"; seq: number; t: number; id: number; name: string }\n | { k: \"unmount\"; seq: number; t: number; id: number }\n | {\n k: \"resource\";\n seq: number;\n t: number;\n id: number;\n owner: number;\n state: string;\n name?: string;\n }\n | { k: \"error\"; seq: number; t: number; id: number; message: string; code?: string }\n | { k: \"hydration\"; seq: number; t: number; message: string; expected: string; actual: string }\n | { k: \"flush+\"; seq: number; t: number; render: number; user: number }\n | { k: \"flush-\"; seq: number; t: number; runs: number }\n | { k: \"store+\"; seq: number; t: number; id: number; owner: number; name: string };\n\n/**\n * Discriminant of {@link WireEvent}.\n *\n * @since 1.0.0\n */\nexport type WireKind = WireEvent[\"k\"];\n\n/**\n * The sentinel used where a wire event refers to \"no node\". The agent assigns node ids from 1, so\n * `NO_ID` never names a node.\n *\n * @example\n * import { NO_ID } from \"@shadow-js/devtools\";\n *\n * const isRoot = (parent: number): boolean => parent === NO_ID;\n * @since 1.0.0\n */\nexport const NO_ID = 0;\n"],"mappings":";;;;;;;;;;;AA2BA,MAAa,mBAAmB;;;;;;;;;;AAWhC,MAAa,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCxB,MAAa,QAkBT;CACF,eAAe;CACf,gBAAgB;CAChB,gBAAgB;CAChB,gBAAgB;CAChB,qBAAqB;CACrB,iBAAiB;CACjB,sBAAsB;CACtB,mBAAmB;CACnB,qBAAqB;CACrB,gBAAgB;CAChB,OAAO;CACP,oBAAoB;CACpB,aAAa;CACb,WAAW;CACX,eAAe;CACf,kBAAkB;CAClB,gBAAgB;AAClB;;;;;;;;;;AA8CA,MAAa,sBAAsB;;;;;;;;;;AAWnC,MAAa,kBAAkB;;;;;;;;;;;AA6H/B,MAAa,QAAQ"}
@@ -0,0 +1,50 @@
1
+ //#region src/ring.d.ts
2
+ /**
3
+ * A fixed-capacity ring buffer. `push` is O(1) and never allocates after construction; when full it
4
+ * overwrites the oldest item, so memory stays bounded however many items are recorded. Items are kept
5
+ * oldest first. Do not push `undefined`: `toArray` and the iterator skip such items, while `size` still
6
+ * counts them.
7
+ *
8
+ * @typeParam T The item type.
9
+ * @example
10
+ * import { RingBuffer } from "@shadow-js/devtools";
11
+ *
12
+ * const ring = new RingBuffer<number>(3);
13
+ * ring.push(1);
14
+ * ring.push(2);
15
+ * ring.push(3);
16
+ * ring.push(4);
17
+ * ring.toArray(); // [2, 3, 4]
18
+ * @since 1.0.0
19
+ */
20
+ export declare class RingBuffer<T> {
21
+ /** Maximum number of items kept. Fixed at construction. */
22
+ readonly capacity: number;
23
+ private readonly slots;
24
+ private head;
25
+ private count;
26
+ private overwritten;
27
+ /**
28
+ * @param capacity Maximum number of items kept (a positive integer).
29
+ * @throws {@link RangeError} When `capacity` is not a positive integer.
30
+ */
31
+ constructor(capacity: number);
32
+ /** Number of items currently held (at most `capacity`). */
33
+ get size(): number;
34
+ /** How many items were overwritten because the buffer was full. */
35
+ get dropped(): number;
36
+ /** Adds an item, evicting the oldest when full. Returns the evicted item, if any. */
37
+ push(item: T): T | undefined;
38
+ /** The item `index` positions after the oldest one, or `undefined` out of range. */
39
+ at(index: number): T | undefined;
40
+ /** The newest item, or `undefined` when empty. */
41
+ last(): T | undefined;
42
+ /** Items oldest first, as a new array. */
43
+ toArray(): T[];
44
+ /** Iterates oldest first without allocating an array. */
45
+ [Symbol.iterator](): IterableIterator<T>;
46
+ /** Removes every item (the `dropped` counter is kept). */
47
+ clear(): void;
48
+ }
49
+ //#endregion
50
+ //# sourceMappingURL=ring.d.ts.map
@@ -0,0 +1,94 @@
1
+ //#region src/ring.ts
2
+ /**
3
+ * A fixed-capacity ring buffer. `push` is O(1) and never allocates after construction; when full it
4
+ * overwrites the oldest item, so memory stays bounded however many items are recorded. Items are kept
5
+ * oldest first. Do not push `undefined`: `toArray` and the iterator skip such items, while `size` still
6
+ * counts them.
7
+ *
8
+ * @typeParam T The item type.
9
+ * @example
10
+ * import { RingBuffer } from "@shadow-js/devtools";
11
+ *
12
+ * const ring = new RingBuffer<number>(3);
13
+ * ring.push(1);
14
+ * ring.push(2);
15
+ * ring.push(3);
16
+ * ring.push(4);
17
+ * ring.toArray(); // [2, 3, 4]
18
+ * @since 1.0.0
19
+ */
20
+ var RingBuffer = class {
21
+ /** Maximum number of items kept. Fixed at construction. */
22
+ capacity;
23
+ slots;
24
+ head = 0;
25
+ count = 0;
26
+ overwritten = 0;
27
+ /**
28
+ * @param capacity Maximum number of items kept (a positive integer).
29
+ * @throws {@link RangeError} When `capacity` is not a positive integer.
30
+ */
31
+ constructor(capacity) {
32
+ if (!Number.isInteger(capacity) || capacity < 1) throw new RangeError("RingBuffer capacity must be a positive integer");
33
+ this.capacity = capacity;
34
+ this.slots = new Array(capacity).fill(void 0);
35
+ }
36
+ /** Number of items currently held (at most `capacity`). */
37
+ get size() {
38
+ return this.count;
39
+ }
40
+ /** How many items were overwritten because the buffer was full. */
41
+ get dropped() {
42
+ return this.overwritten;
43
+ }
44
+ /** Adds an item, evicting the oldest when full. Returns the evicted item, if any. */
45
+ push(item) {
46
+ const index = (this.head + this.count) % this.capacity;
47
+ let evicted;
48
+ if (this.count === this.capacity) {
49
+ evicted = this.slots[this.head];
50
+ this.slots[this.head] = item;
51
+ this.head = (this.head + 1) % this.capacity;
52
+ this.overwritten += 1;
53
+ } else {
54
+ this.slots[index] = item;
55
+ this.count += 1;
56
+ }
57
+ return evicted;
58
+ }
59
+ /** The item `index` positions after the oldest one, or `undefined` out of range. */
60
+ at(index) {
61
+ if (index < 0 || index >= this.count) return void 0;
62
+ return this.slots[(this.head + index) % this.capacity];
63
+ }
64
+ /** The newest item, or `undefined` when empty. */
65
+ last() {
66
+ return this.count === 0 ? void 0 : this.at(this.count - 1);
67
+ }
68
+ /** Items oldest first, as a new array. */
69
+ toArray() {
70
+ const out = [];
71
+ for (let i = 0; i < this.count; i += 1) {
72
+ const item = this.slots[(this.head + i) % this.capacity];
73
+ if (item !== void 0) out.push(item);
74
+ }
75
+ return out;
76
+ }
77
+ /** Iterates oldest first without allocating an array. */
78
+ *[Symbol.iterator]() {
79
+ for (let i = 0; i < this.count; i += 1) {
80
+ const item = this.slots[(this.head + i) % this.capacity];
81
+ if (item !== void 0) yield item;
82
+ }
83
+ }
84
+ /** Removes every item (the `dropped` counter is kept). */
85
+ clear() {
86
+ this.slots.fill(void 0);
87
+ this.head = 0;
88
+ this.count = 0;
89
+ }
90
+ };
91
+ //#endregion
92
+ export { RingBuffer };
93
+
94
+ //# sourceMappingURL=ring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ring.js","names":[],"sources":["../../src/ring.ts"],"sourcesContent":["/**\n * A fixed-capacity ring buffer. `push` is O(1) and never allocates after construction; when full it\n * overwrites the oldest item, so memory stays bounded however many items are recorded. Items are kept\n * oldest first. Do not push `undefined`: `toArray` and the iterator skip such items, while `size` still\n * counts them.\n *\n * @typeParam T The item type.\n * @example\n * import { RingBuffer } from \"@shadow-js/devtools\";\n *\n * const ring = new RingBuffer<number>(3);\n * ring.push(1);\n * ring.push(2);\n * ring.push(3);\n * ring.push(4);\n * ring.toArray(); // [2, 3, 4]\n * @since 1.0.0\n */\nexport class RingBuffer<T> {\n /** Maximum number of items kept. Fixed at construction. */\n readonly capacity: number;\n private readonly slots: Array<T | undefined>;\n private head = 0;\n private count = 0;\n private overwritten = 0;\n\n /**\n * @param capacity Maximum number of items kept (a positive integer).\n * @throws {@link RangeError} When `capacity` is not a positive integer.\n */\n constructor(capacity: number) {\n if (!Number.isInteger(capacity) || capacity < 1) {\n throw new RangeError(\"RingBuffer capacity must be a positive integer\");\n }\n this.capacity = capacity;\n this.slots = new Array<T | undefined>(capacity).fill(undefined);\n }\n\n /** Number of items currently held (at most `capacity`). */\n get size(): number {\n return this.count;\n }\n\n /** How many items were overwritten because the buffer was full. */\n get dropped(): number {\n return this.overwritten;\n }\n\n /** Adds an item, evicting the oldest when full. Returns the evicted item, if any. */\n push(item: T): T | undefined {\n const index = (this.head + this.count) % this.capacity;\n let evicted: T | undefined;\n if (this.count === this.capacity) {\n evicted = this.slots[this.head];\n this.slots[this.head] = item;\n this.head = (this.head + 1) % this.capacity;\n this.overwritten += 1;\n } else {\n this.slots[index] = item;\n this.count += 1;\n }\n return evicted;\n }\n\n /** The item `index` positions after the oldest one, or `undefined` out of range. */\n at(index: number): T | undefined {\n if (index < 0 || index >= this.count) return undefined;\n return this.slots[(this.head + index) % this.capacity];\n }\n\n /** The newest item, or `undefined` when empty. */\n last(): T | undefined {\n return this.count === 0 ? undefined : this.at(this.count - 1);\n }\n\n /** Items oldest first, as a new array. */\n toArray(): T[] {\n const out: T[] = [];\n for (let i = 0; i < this.count; i += 1) {\n const item = this.slots[(this.head + i) % this.capacity];\n if (item !== undefined) out.push(item);\n }\n return out;\n }\n\n /** Iterates oldest first without allocating an array. */\n *[Symbol.iterator](): IterableIterator<T> {\n for (let i = 0; i < this.count; i += 1) {\n const item = this.slots[(this.head + i) % this.capacity];\n if (item !== undefined) yield item;\n }\n }\n\n /** Removes every item (the `dropped` counter is kept). */\n clear(): void {\n this.slots.fill(undefined);\n this.head = 0;\n this.count = 0;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAkBA,IAAa,aAAb,MAA2B;;CAEzB;CACA;CACA,OAAe;CACf,QAAgB;CAChB,cAAsB;;;;;CAMtB,YAAY,UAAkB;EAC5B,IAAI,CAAC,OAAO,UAAU,QAAQ,KAAK,WAAW,GAC5C,MAAM,IAAI,WAAW,gDAAgD;EAEvE,KAAK,WAAW;EAChB,KAAK,QAAQ,IAAI,MAAqB,QAAQ,CAAC,CAAC,KAAK,KAAA,CAAS;CAChE;;CAGA,IAAI,OAAe;EACjB,OAAO,KAAK;CACd;;CAGA,IAAI,UAAkB;EACpB,OAAO,KAAK;CACd;;CAGA,KAAK,MAAwB;EAC3B,MAAM,SAAS,KAAK,OAAO,KAAK,SAAS,KAAK;EAC9C,IAAI;EACJ,IAAI,KAAK,UAAU,KAAK,UAAU;GAChC,UAAU,KAAK,MAAM,KAAK;GAC1B,KAAK,MAAM,KAAK,QAAQ;GACxB,KAAK,QAAQ,KAAK,OAAO,KAAK,KAAK;GACnC,KAAK,eAAe;EACtB,OAAO;GACL,KAAK,MAAM,SAAS;GACpB,KAAK,SAAS;EAChB;EACA,OAAO;CACT;;CAGA,GAAG,OAA8B;EAC/B,IAAI,QAAQ,KAAK,SAAS,KAAK,OAAO,OAAO,KAAA;EAC7C,OAAO,KAAK,OAAO,KAAK,OAAO,SAAS,KAAK;CAC/C;;CAGA,OAAsB;EACpB,OAAO,KAAK,UAAU,IAAI,KAAA,IAAY,KAAK,GAAG,KAAK,QAAQ,CAAC;CAC9D;;CAGA,UAAe;EACb,MAAM,MAAW,CAAC;EAClB,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,OAAO,KAAK,GAAG;GACtC,MAAM,OAAO,KAAK,OAAO,KAAK,OAAO,KAAK,KAAK;GAC/C,IAAI,SAAS,KAAA,GAAW,IAAI,KAAK,IAAI;EACvC;EACA,OAAO;CACT;;CAGA,EAAE,OAAO,YAAiC;EACxC,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,OAAO,KAAK,GAAG;GACtC,MAAM,OAAO,KAAK,OAAO,KAAK,OAAO,KAAK,KAAK;GAC/C,IAAI,SAAS,KAAA,GAAW,MAAM;EAChC;CACF;;CAGA,QAAc;EACZ,KAAK,MAAM,KAAK,KAAA,CAAS;EACzB,KAAK,OAAO;EACZ,KAAK,QAAQ;CACf;AACF"}