@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.
- package/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +576 -2
- package/dist/dev/agent.d.ts +239 -0
- package/dist/dev/agent.js +626 -0
- package/dist/dev/agent.js.map +1 -0
- package/dist/dev/backend.d.ts +53 -0
- package/dist/dev/backend.js +53 -0
- package/dist/dev/backend.js.map +1 -0
- package/dist/dev/hook.d.ts +83 -0
- package/dist/dev/hook.js +136 -0
- package/dist/dev/hook.js.map +1 -0
- package/dist/dev/index.d.ts +17 -0
- package/dist/dev/index.js +17 -0
- package/dist/dev/inspection.d.ts +226 -0
- package/dist/dev/inspection.js +263 -0
- package/dist/dev/inspection.js.map +1 -0
- package/dist/dev/inspector.d.ts +331 -0
- package/dist/dev/inspector.js +429 -0
- package/dist/dev/inspector.js.map +1 -0
- package/dist/dev/model.d.ts +418 -0
- package/dist/dev/model.js +559 -0
- package/dist/dev/model.js.map +1 -0
- package/dist/dev/mount.d.ts +207 -0
- package/dist/dev/mount.js +256 -0
- package/dist/dev/mount.js.map +1 -0
- package/dist/dev/overlay.d.ts +102 -0
- package/dist/dev/overlay.js +176 -0
- package/dist/dev/overlay.js.map +1 -0
- package/dist/dev/preview.d.ts +74 -0
- package/dist/dev/preview.js +122 -0
- package/dist/dev/preview.js.map +1 -0
- package/dist/dev/protocol.d.ts +288 -0
- package/dist/dev/protocol.js +109 -0
- package/dist/dev/protocol.js.map +1 -0
- package/dist/dev/ring.d.ts +50 -0
- package/dist/dev/ring.js +94 -0
- package/dist/dev/ring.js.map +1 -0
- package/dist/dev/transport.d.ts +347 -0
- package/dist/dev/transport.js +394 -0
- package/dist/dev/transport.js.map +1 -0
- package/dist/dev/ui/details.js +294 -0
- package/dist/dev/ui/details.js.map +1 -0
- package/dist/dev/ui/inspection-state.d.ts +43 -0
- package/dist/dev/ui/inspection-state.js +62 -0
- package/dist/dev/ui/inspection-state.js.map +1 -0
- package/dist/dev/ui/issues.js +85 -0
- package/dist/dev/ui/issues.js.map +1 -0
- package/dist/dev/ui/panel.d.ts +55 -0
- package/dist/dev/ui/panel.js +160 -0
- package/dist/dev/ui/panel.js.map +1 -0
- package/dist/dev/ui/rows.d.ts +44 -0
- package/dist/dev/ui/rows.js +132 -0
- package/dist/dev/ui/rows.js.map +1 -0
- package/dist/dev/ui/state-tab.js +88 -0
- package/dist/dev/ui/state-tab.js.map +1 -0
- package/dist/dev/ui/state.d.ts +192 -0
- package/dist/dev/ui/state.js +147 -0
- package/dist/dev/ui/state.js.map +1 -0
- package/dist/dev/ui/styles.d.ts +31 -0
- package/dist/dev/ui/styles.js +113 -0
- package/dist/dev/ui/styles.js.map +1 -0
- package/dist/dev/ui/timeline.js +135 -0
- package/dist/dev/ui/timeline.js.map +1 -0
- package/dist/dev/ui/tree.js +186 -0
- package/dist/dev/ui/tree.js.map +1 -0
- package/dist/prod/agent.js +2 -0
- package/dist/prod/agent.js.map +1 -0
- package/dist/prod/backend.js +2 -0
- package/dist/prod/backend.js.map +1 -0
- package/dist/prod/hook.js +2 -0
- package/dist/prod/hook.js.map +1 -0
- package/dist/prod/index.js +1 -0
- package/dist/prod/inspection.js +2 -0
- package/dist/prod/inspection.js.map +1 -0
- package/dist/prod/inspector.js +2 -0
- package/dist/prod/inspector.js.map +1 -0
- package/dist/prod/model.js +2 -0
- package/dist/prod/model.js.map +1 -0
- package/dist/prod/mount.js +2 -0
- package/dist/prod/mount.js.map +1 -0
- package/dist/prod/overlay.js +11 -0
- package/dist/prod/overlay.js.map +1 -0
- package/dist/prod/preview.js +2 -0
- package/dist/prod/preview.js.map +1 -0
- package/dist/prod/protocol.js +2 -0
- package/dist/prod/protocol.js.map +1 -0
- package/dist/prod/ring.js +2 -0
- package/dist/prod/ring.js.map +1 -0
- package/dist/prod/transport.js +2 -0
- package/dist/prod/transport.js.map +1 -0
- package/dist/prod/ui/details.js +2 -0
- package/dist/prod/ui/details.js.map +1 -0
- package/dist/prod/ui/inspection-state.js +2 -0
- package/dist/prod/ui/inspection-state.js.map +1 -0
- package/dist/prod/ui/issues.js +2 -0
- package/dist/prod/ui/issues.js.map +1 -0
- package/dist/prod/ui/panel.js +2 -0
- package/dist/prod/ui/panel.js.map +1 -0
- package/dist/prod/ui/rows.js +2 -0
- package/dist/prod/ui/rows.js.map +1 -0
- package/dist/prod/ui/state-tab.js +2 -0
- package/dist/prod/ui/state-tab.js.map +1 -0
- package/dist/prod/ui/state.js +2 -0
- package/dist/prod/ui/state.js.map +1 -0
- package/dist/prod/ui/styles.js +82 -0
- package/dist/prod/ui/styles.js.map +1 -0
- package/dist/prod/ui/timeline.js +2 -0
- package/dist/prod/ui/timeline.js.map +1 -0
- package/dist/prod/ui/tree.js +2 -0
- package/dist/prod/ui/tree.js.map +1 -0
- package/package.json +69 -4
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
//#region src/inspection.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* One line of an inspection view. Every field is text, so a row is plain data and can cross a
|
|
4
|
+
* message channel unchanged.
|
|
5
|
+
*
|
|
6
|
+
* @since 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
export interface InspectionRow {
|
|
9
|
+
/** Stable within the source (a query hash, a route match index, or a fixed name). */
|
|
10
|
+
readonly id: string;
|
|
11
|
+
/** What the row is about: a query key, a route pattern, or a fixed label such as `location`. */
|
|
12
|
+
readonly label: string;
|
|
13
|
+
/** The details, as one line of text. */
|
|
14
|
+
readonly detail: string;
|
|
15
|
+
/** A short state word for the row, such as `success`, `fetching` or `idle`. */
|
|
16
|
+
readonly state: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Something that can describe itself to a devtools panel: a router, a query cache, or any other part of
|
|
20
|
+
* the app. `rows()` is called when the panel asks, so it reports the current state, not a copy taken
|
|
21
|
+
* earlier. It may throw; the agent reports the error to the panel instead of failing the request.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* import { type InspectionSource, registerInspection } from "@shadow-js/devtools";
|
|
25
|
+
*
|
|
26
|
+
* const source: InspectionSource = {
|
|
27
|
+
* title: "Feature flags",
|
|
28
|
+
* rows: () => [{ id: "beta", label: "beta", detail: "on", state: "on" }],
|
|
29
|
+
* };
|
|
30
|
+
* registerInspection("flags", source);
|
|
31
|
+
* @since 1.0.0
|
|
32
|
+
*/
|
|
33
|
+
export interface InspectionSource {
|
|
34
|
+
/** The heading of the view, for example `Router`. */
|
|
35
|
+
readonly title: string;
|
|
36
|
+
/** The rows now. At most {@link MAX_INSPECTION_ROWS} are sent. */
|
|
37
|
+
rows(): readonly InspectionRow[];
|
|
38
|
+
/**
|
|
39
|
+
* Optional. Calls `notify` whenever the rows may have changed, and returns the function that stops the
|
|
40
|
+
* calls. The extension's page side listens to it, so the panel refreshes at the moment of the change
|
|
41
|
+
* instead of on a timer. A source without it is read when the panel asks.
|
|
42
|
+
*/
|
|
43
|
+
subscribe?(notify: () => void): () => void;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A registered source with its name (the key the app registered it under).
|
|
47
|
+
*
|
|
48
|
+
* @since 1.0.0
|
|
49
|
+
*/
|
|
50
|
+
export interface InspectionEntry {
|
|
51
|
+
/** The name the source was registered under. Unique per hook. */
|
|
52
|
+
readonly name: string;
|
|
53
|
+
/** The source. */
|
|
54
|
+
readonly source: InspectionSource;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* What the agent answers to an `inspect` request: every registered source, read now. A source that
|
|
58
|
+
* threw has no rows and an `error` message instead.
|
|
59
|
+
*
|
|
60
|
+
* @since 1.0.0
|
|
61
|
+
*/
|
|
62
|
+
export interface InspectionSnapshot {
|
|
63
|
+
/** The registration name. */
|
|
64
|
+
readonly name: string;
|
|
65
|
+
/** The heading of the view. */
|
|
66
|
+
readonly title: string;
|
|
67
|
+
/** The rows (at most {@link MAX_INSPECTION_ROWS}). */
|
|
68
|
+
readonly rows: readonly InspectionRow[];
|
|
69
|
+
/** Why the rows could not be read, or `undefined` when they were. */
|
|
70
|
+
readonly error?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The part of a router the router inspection reads. A `Router` from `@shadow-js/router` fits this
|
|
74
|
+
* shape; it is structural, so devtools does not depend on the router package.
|
|
75
|
+
*
|
|
76
|
+
* @since 1.0.0
|
|
77
|
+
*/
|
|
78
|
+
export interface RouterLike {
|
|
79
|
+
/** The router's live state (accessors). */
|
|
80
|
+
readonly state: {
|
|
81
|
+
location(): {
|
|
82
|
+
readonly pathname: string;
|
|
83
|
+
readonly search: string;
|
|
84
|
+
readonly hash: string;
|
|
85
|
+
} | undefined;
|
|
86
|
+
status(): string;
|
|
87
|
+
pending(): boolean;
|
|
88
|
+
error(): unknown;
|
|
89
|
+
matches(): readonly {
|
|
90
|
+
readonly id: string;
|
|
91
|
+
readonly pattern: string;
|
|
92
|
+
readonly params: Readonly<Record<string, string>>;
|
|
93
|
+
}[];
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* One query as the query cache reports it (a `QueryInfo` snapshot of `@shadow-js/query`).
|
|
98
|
+
*
|
|
99
|
+
* @since 1.0.0
|
|
100
|
+
*/
|
|
101
|
+
export interface QueryInfoLike {
|
|
102
|
+
/** The key of the query (plain data). */
|
|
103
|
+
readonly queryKey: unknown;
|
|
104
|
+
/** The hash of the key, which identifies the cache entry. */
|
|
105
|
+
readonly queryHash: string;
|
|
106
|
+
/** `pending`, `success` or `error`. */
|
|
107
|
+
readonly status: string;
|
|
108
|
+
/** `idle`, `fetching` or `paused`. */
|
|
109
|
+
readonly fetchStatus: string;
|
|
110
|
+
/** Time (ms since epoch) of the last successful fetch or write; `0` when there was none. */
|
|
111
|
+
readonly dataUpdatedAt: number;
|
|
112
|
+
/** True after an invalidation, until the next successful fetch or write. */
|
|
113
|
+
readonly isInvalidated: boolean;
|
|
114
|
+
/** Mounted observers of the query. */
|
|
115
|
+
readonly observers: number;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The part of a query client the query inspection reads: its cache, as snapshots. A `QueryClient` from
|
|
119
|
+
* `@shadow-js/query` fits this shape.
|
|
120
|
+
*
|
|
121
|
+
* @since 1.0.0
|
|
122
|
+
*/
|
|
123
|
+
export interface QueryLike {
|
|
124
|
+
/** The query cache, read through its snapshots, and told about its changes. */
|
|
125
|
+
readonly queryCache: {
|
|
126
|
+
/** Every query, as snapshots. Untracked. */
|
|
127
|
+
find(): readonly QueryInfoLike[];
|
|
128
|
+
/** Calls `listener` for every change of the cache; returns the function that stops it. */
|
|
129
|
+
subscribe(listener: () => void): () => void;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Turns a source's rows into plain rows, cut to the row limit. Each field is coerced to text and cut,
|
|
134
|
+
* so a source cannot send an object, a function or an unbounded string to the panel.
|
|
135
|
+
*
|
|
136
|
+
* @param rows The rows a source returned.
|
|
137
|
+
* @returns At most {@link MAX_INSPECTION_ROWS} plain rows, and a last row that says how many were cut.
|
|
138
|
+
* @example
|
|
139
|
+
* import { type InspectionRow, sanitizeRows } from "@shadow-js/devtools";
|
|
140
|
+
*
|
|
141
|
+
* const rows: InspectionRow[] = [{ id: "a", label: "a", detail: "b", state: "c" }];
|
|
142
|
+
* sanitizeRows(rows); // one plain row
|
|
143
|
+
* @since 1.0.0
|
|
144
|
+
*/
|
|
145
|
+
export declare function sanitizeRows(rows: readonly InspectionRow[]): InspectionRow[];
|
|
146
|
+
/**
|
|
147
|
+
* Describes a router: its location, whether a navigation is pending, the error it last reported, and
|
|
148
|
+
* the chain of matched routes. The source reads the router when the panel asks.
|
|
149
|
+
*
|
|
150
|
+
* @param router The router (`createRouter` from `@shadow-js/router`).
|
|
151
|
+
* @param title The heading of the view. Default `Router`.
|
|
152
|
+
* @returns An {@link InspectionSource}.
|
|
153
|
+
* @example
|
|
154
|
+
* import { type RouterLike, registerInspection, routerInspection } from "@shadow-js/devtools";
|
|
155
|
+
*
|
|
156
|
+
* declare const router: RouterLike; // the router from your app's createRouter()
|
|
157
|
+
* registerInspection("router", routerInspection(router));
|
|
158
|
+
* @since 1.0.0
|
|
159
|
+
*/
|
|
160
|
+
export declare function routerInspection(router: RouterLike, title?: string): InspectionSource;
|
|
161
|
+
/**
|
|
162
|
+
* Describes a query cache: one row per query, with its key, fetch state, observers and age. The
|
|
163
|
+
* source reads the cache when the panel asks, and it subscribes to the cache so that a change is
|
|
164
|
+
* pushed to a connected panel as soon as it happens. A client without the `inspection` feature of
|
|
165
|
+
* `@shadow-js/query` cannot notify, so the panel then reads the cache only when it asks.
|
|
166
|
+
*
|
|
167
|
+
* @param client The query client (`createQueryClient` from `@shadow-js/query`, created with
|
|
168
|
+
* `features: [inspection]` for live updates), or anything with the same `queryCache.find()`.
|
|
169
|
+
* @param title The heading of the view. Default `Queries`.
|
|
170
|
+
* @returns An {@link InspectionSource}.
|
|
171
|
+
* @example
|
|
172
|
+
* import { type QueryLike, queryInspection, registerInspection } from "@shadow-js/devtools";
|
|
173
|
+
*
|
|
174
|
+
* declare const client: QueryLike; // the query client of your app
|
|
175
|
+
* registerInspection("query", queryInspection(client));
|
|
176
|
+
* @since 1.0.0
|
|
177
|
+
*/
|
|
178
|
+
export declare function queryInspection(client: QueryLike, title?: string): InspectionSource;
|
|
179
|
+
/**
|
|
180
|
+
* Registers an inspection source on the installed devtools hook, under `name`, so a panel can show it
|
|
181
|
+
* (see the `inspect` request of {@link createAgent}). Re-registering a name replaces its source. Returns
|
|
182
|
+
* `false` when nothing was registered: no hook is installed (no development build, or the hook of
|
|
183
|
+
* another protocol version), the name is empty, or {@link MAX_INSPECTIONS} sources are already
|
|
184
|
+
* registered under other names. A development tool; production builds register nothing.
|
|
185
|
+
*
|
|
186
|
+
* @param name The name to show the source under. Not empty.
|
|
187
|
+
* @param source The source.
|
|
188
|
+
* @returns True when the source is registered.
|
|
189
|
+
* @example
|
|
190
|
+
* import { type RouterLike, registerInspection, routerInspection } from "@shadow-js/devtools";
|
|
191
|
+
*
|
|
192
|
+
* declare const router: RouterLike; // the router from your app's createRouter()
|
|
193
|
+
* registerInspection("router", routerInspection(router));
|
|
194
|
+
* @since 1.0.0
|
|
195
|
+
*/
|
|
196
|
+
export declare function registerInspection(name: string, source: InspectionSource): boolean;
|
|
197
|
+
/**
|
|
198
|
+
* Reads every registered source, now, as plain snapshots. A source that throws is reported with its
|
|
199
|
+
* `error` and no rows. Used by the agent for the `inspect` request.
|
|
200
|
+
*
|
|
201
|
+
* @param entries The registered entries (the hook's `inspections()`).
|
|
202
|
+
* @returns One snapshot per entry, in registration order.
|
|
203
|
+
* @example
|
|
204
|
+
* import { readInspections } from "@shadow-js/devtools";
|
|
205
|
+
*
|
|
206
|
+
* const snapshots = readInspections([]); // []
|
|
207
|
+
* @since 1.0.0
|
|
208
|
+
*/
|
|
209
|
+
export declare function readInspections(entries: readonly InspectionEntry[]): InspectionSnapshot[];
|
|
210
|
+
/**
|
|
211
|
+
* Reads the answer of an `inspect` request. The panel gets it from the page, so it checks the shape
|
|
212
|
+
* and the bounds before it trusts anything: every field is text, there are at most
|
|
213
|
+
* {@link MAX_INSPECTIONS} sources and at most {@link MAX_INSPECTION_ROWS} + 1 rows each.
|
|
214
|
+
*
|
|
215
|
+
* @param value The `result` of an ok `inspect` response.
|
|
216
|
+
* @returns The snapshots, or `null` when the value is not an answer to `inspect`.
|
|
217
|
+
* @example
|
|
218
|
+
* import { parseInspectResult } from "@shadow-js/devtools";
|
|
219
|
+
*
|
|
220
|
+
* parseInspectResult({ sources: [] }); // []
|
|
221
|
+
* parseInspectResult({ nope: true }); // null
|
|
222
|
+
* @since 1.0.0
|
|
223
|
+
*/
|
|
224
|
+
export declare function parseInspectResult(value: unknown): InspectionSnapshot[] | null;
|
|
225
|
+
//#endregion
|
|
226
|
+
//# sourceMappingURL=inspection.d.ts.map
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import "./protocol.js";
|
|
2
|
+
import { getHook } from "./hook.js";
|
|
3
|
+
import { errorMessage, preview } from "./preview.js";
|
|
4
|
+
//#region src/inspection.ts
|
|
5
|
+
/** Longest text of one row field, in characters. Longer text is cut with an ellipsis. */
|
|
6
|
+
const FIELD_LENGTH = 200;
|
|
7
|
+
/**
|
|
8
|
+
* The JSON text of plain data (query keys and route params are plain data), cut to the field length.
|
|
9
|
+
* Falls back to {@link preview} when the value does not serialize (a cycle, a bigint).
|
|
10
|
+
*
|
|
11
|
+
* @param value The value.
|
|
12
|
+
* @returns One line of text.
|
|
13
|
+
*/
|
|
14
|
+
function jsonText(value) {
|
|
15
|
+
try {
|
|
16
|
+
const text = JSON.stringify(value);
|
|
17
|
+
return text === void 0 ? preview(value, FIELD_LENGTH) : clip(text);
|
|
18
|
+
} catch {
|
|
19
|
+
return preview(value, FIELD_LENGTH);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Cuts text to the row field length, with an ellipsis when it was longer.
|
|
24
|
+
*
|
|
25
|
+
* @param text The text.
|
|
26
|
+
* @returns The text, at most {@link FIELD_LENGTH} characters.
|
|
27
|
+
*/
|
|
28
|
+
function clip(text) {
|
|
29
|
+
return text.length > FIELD_LENGTH ? `${text.slice(0, 199)}…` : text;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Turns a source's rows into plain rows, cut to the row limit. Each field is coerced to text and cut,
|
|
33
|
+
* so a source cannot send an object, a function or an unbounded string to the panel.
|
|
34
|
+
*
|
|
35
|
+
* @param rows The rows a source returned.
|
|
36
|
+
* @returns At most {@link MAX_INSPECTION_ROWS} plain rows, and a last row that says how many were cut.
|
|
37
|
+
* @example
|
|
38
|
+
* import { type InspectionRow, sanitizeRows } from "@shadow-js/devtools";
|
|
39
|
+
*
|
|
40
|
+
* const rows: InspectionRow[] = [{ id: "a", label: "a", detail: "b", state: "c" }];
|
|
41
|
+
* sanitizeRows(rows); // one plain row
|
|
42
|
+
* @since 1.0.0
|
|
43
|
+
*/
|
|
44
|
+
function sanitizeRows(rows) {
|
|
45
|
+
const out = [];
|
|
46
|
+
const limit = Math.min(rows.length, 500);
|
|
47
|
+
for (let index = 0; index < limit; index += 1) {
|
|
48
|
+
const row = rows[index];
|
|
49
|
+
if (row === void 0) continue;
|
|
50
|
+
out.push({
|
|
51
|
+
id: clip(String(row.id)),
|
|
52
|
+
label: clip(String(row.label)),
|
|
53
|
+
detail: clip(String(row.detail)),
|
|
54
|
+
state: clip(String(row.state))
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (rows.length > 500) out.push({
|
|
58
|
+
id: "truncated",
|
|
59
|
+
label: "…",
|
|
60
|
+
detail: `${rows.length - 500} more rows not shown`,
|
|
61
|
+
state: "truncated"
|
|
62
|
+
});
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Describes a router: its location, whether a navigation is pending, the error it last reported, and
|
|
67
|
+
* the chain of matched routes. The source reads the router when the panel asks.
|
|
68
|
+
*
|
|
69
|
+
* @param router The router (`createRouter` from `@shadow-js/router`).
|
|
70
|
+
* @param title The heading of the view. Default `Router`.
|
|
71
|
+
* @returns An {@link InspectionSource}.
|
|
72
|
+
* @example
|
|
73
|
+
* import { type RouterLike, registerInspection, routerInspection } from "@shadow-js/devtools";
|
|
74
|
+
*
|
|
75
|
+
* declare const router: RouterLike; // the router from your app's createRouter()
|
|
76
|
+
* registerInspection("router", routerInspection(router));
|
|
77
|
+
* @since 1.0.0
|
|
78
|
+
*/
|
|
79
|
+
function routerInspection(router, title = "Router") {
|
|
80
|
+
return {
|
|
81
|
+
title,
|
|
82
|
+
rows() {
|
|
83
|
+
const rows = [];
|
|
84
|
+
const location = router.state.location();
|
|
85
|
+
const status = router.state.status();
|
|
86
|
+
rows.push({
|
|
87
|
+
id: "location",
|
|
88
|
+
label: "location",
|
|
89
|
+
detail: location === void 0 ? "none" : `${location.pathname}${location.search}${location.hash}`,
|
|
90
|
+
state: status
|
|
91
|
+
});
|
|
92
|
+
rows.push({
|
|
93
|
+
id: "pending",
|
|
94
|
+
label: "pending",
|
|
95
|
+
detail: String(router.state.pending()),
|
|
96
|
+
state: router.state.pending() ? "pending" : "settled"
|
|
97
|
+
});
|
|
98
|
+
const error = router.state.error();
|
|
99
|
+
if (error !== void 0) rows.push({
|
|
100
|
+
id: "error",
|
|
101
|
+
label: "error",
|
|
102
|
+
detail: errorMessage(error),
|
|
103
|
+
state: "errored"
|
|
104
|
+
});
|
|
105
|
+
router.state.matches().forEach((match, index) => {
|
|
106
|
+
rows.push({
|
|
107
|
+
id: `match:${index}`,
|
|
108
|
+
label: match.pattern,
|
|
109
|
+
detail: jsonText(match.params),
|
|
110
|
+
state: match.id
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
return rows;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Describes a query cache: one row per query, with its key, fetch state, observers and age. The
|
|
119
|
+
* source reads the cache when the panel asks, and it subscribes to the cache so that a change is
|
|
120
|
+
* pushed to a connected panel as soon as it happens. A client without the `inspection` feature of
|
|
121
|
+
* `@shadow-js/query` cannot notify, so the panel then reads the cache only when it asks.
|
|
122
|
+
*
|
|
123
|
+
* @param client The query client (`createQueryClient` from `@shadow-js/query`, created with
|
|
124
|
+
* `features: [inspection]` for live updates), or anything with the same `queryCache.find()`.
|
|
125
|
+
* @param title The heading of the view. Default `Queries`.
|
|
126
|
+
* @returns An {@link InspectionSource}.
|
|
127
|
+
* @example
|
|
128
|
+
* import { type QueryLike, queryInspection, registerInspection } from "@shadow-js/devtools";
|
|
129
|
+
*
|
|
130
|
+
* declare const client: QueryLike; // the query client of your app
|
|
131
|
+
* registerInspection("query", queryInspection(client));
|
|
132
|
+
* @since 1.0.0
|
|
133
|
+
*/
|
|
134
|
+
function queryInspection(client, title = "Queries") {
|
|
135
|
+
return {
|
|
136
|
+
title,
|
|
137
|
+
subscribe(notify) {
|
|
138
|
+
try {
|
|
139
|
+
return client.queryCache.subscribe(() => notify());
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (error instanceof TypeError) return () => void 0;
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
rows() {
|
|
146
|
+
return client.queryCache.find().map((query) => {
|
|
147
|
+
const flags = [query.fetchStatus, `${query.observers} observers`];
|
|
148
|
+
if (query.isInvalidated) flags.push("invalidated");
|
|
149
|
+
flags.push(query.dataUpdatedAt > 0 ? `updated ${new Date(query.dataUpdatedAt).toISOString()}` : "never updated");
|
|
150
|
+
return {
|
|
151
|
+
id: query.queryHash,
|
|
152
|
+
label: jsonText(query.queryKey),
|
|
153
|
+
detail: flags.join(", "),
|
|
154
|
+
state: query.status
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Registers an inspection source on the installed devtools hook, under `name`, so a panel can show it
|
|
162
|
+
* (see the `inspect` request of {@link createAgent}). Re-registering a name replaces its source. Returns
|
|
163
|
+
* `false` when nothing was registered: no hook is installed (no development build, or the hook of
|
|
164
|
+
* another protocol version), the name is empty, or {@link MAX_INSPECTIONS} sources are already
|
|
165
|
+
* registered under other names. A development tool; production builds register nothing.
|
|
166
|
+
*
|
|
167
|
+
* @param name The name to show the source under. Not empty.
|
|
168
|
+
* @param source The source.
|
|
169
|
+
* @returns True when the source is registered.
|
|
170
|
+
* @example
|
|
171
|
+
* import { type RouterLike, registerInspection, routerInspection } from "@shadow-js/devtools";
|
|
172
|
+
*
|
|
173
|
+
* declare const router: RouterLike; // the router from your app's createRouter()
|
|
174
|
+
* registerInspection("router", routerInspection(router));
|
|
175
|
+
* @since 1.0.0
|
|
176
|
+
*/
|
|
177
|
+
function registerInspection(name, source) {
|
|
178
|
+
if (name === "") return false;
|
|
179
|
+
const hook = getHook();
|
|
180
|
+
if (hook === void 0 || hook.inspect === void 0) return false;
|
|
181
|
+
return hook.inspect(name, source);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Reads every registered source, now, as plain snapshots. A source that throws is reported with its
|
|
185
|
+
* `error` and no rows. Used by the agent for the `inspect` request.
|
|
186
|
+
*
|
|
187
|
+
* @param entries The registered entries (the hook's `inspections()`).
|
|
188
|
+
* @returns One snapshot per entry, in registration order.
|
|
189
|
+
* @example
|
|
190
|
+
* import { readInspections } from "@shadow-js/devtools";
|
|
191
|
+
*
|
|
192
|
+
* const snapshots = readInspections([]); // []
|
|
193
|
+
* @since 1.0.0
|
|
194
|
+
*/
|
|
195
|
+
function readInspections(entries) {
|
|
196
|
+
return entries.map((entry) => {
|
|
197
|
+
try {
|
|
198
|
+
return {
|
|
199
|
+
name: entry.name,
|
|
200
|
+
title: clip(String(entry.source.title)),
|
|
201
|
+
rows: sanitizeRows(entry.source.rows())
|
|
202
|
+
};
|
|
203
|
+
} catch (error) {
|
|
204
|
+
return {
|
|
205
|
+
name: entry.name,
|
|
206
|
+
title: clip(String(entry.source.title)),
|
|
207
|
+
rows: [],
|
|
208
|
+
error: errorMessage(error)
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function isRecord(value) {
|
|
214
|
+
return typeof value === "object" && value !== null;
|
|
215
|
+
}
|
|
216
|
+
function isRow(value) {
|
|
217
|
+
return isRecord(value) && typeof value["id"] === "string" && typeof value["label"] === "string" && typeof value["detail"] === "string" && typeof value["state"] === "string";
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Reads the answer of an `inspect` request. The panel gets it from the page, so it checks the shape
|
|
221
|
+
* and the bounds before it trusts anything: every field is text, there are at most
|
|
222
|
+
* {@link MAX_INSPECTIONS} sources and at most {@link MAX_INSPECTION_ROWS} + 1 rows each.
|
|
223
|
+
*
|
|
224
|
+
* @param value The `result` of an ok `inspect` response.
|
|
225
|
+
* @returns The snapshots, or `null` when the value is not an answer to `inspect`.
|
|
226
|
+
* @example
|
|
227
|
+
* import { parseInspectResult } from "@shadow-js/devtools";
|
|
228
|
+
*
|
|
229
|
+
* parseInspectResult({ sources: [] }); // []
|
|
230
|
+
* parseInspectResult({ nope: true }); // null
|
|
231
|
+
* @since 1.0.0
|
|
232
|
+
*/
|
|
233
|
+
function parseInspectResult(value) {
|
|
234
|
+
if (!isRecord(value) || !Array.isArray(value["sources"])) return null;
|
|
235
|
+
const sources = value["sources"];
|
|
236
|
+
if (sources.length > 16) return null;
|
|
237
|
+
const out = [];
|
|
238
|
+
for (const entry of sources) {
|
|
239
|
+
if (!isRecord(entry)) return null;
|
|
240
|
+
const name = entry["name"];
|
|
241
|
+
const title = entry["title"];
|
|
242
|
+
const rows = entry["rows"];
|
|
243
|
+
const error = entry["error"];
|
|
244
|
+
if (typeof name !== "string" || typeof title !== "string" || !Array.isArray(rows)) return null;
|
|
245
|
+
if (rows.length > 501 || !rows.every(isRow)) return null;
|
|
246
|
+
if (error !== void 0 && typeof error !== "string") return null;
|
|
247
|
+
out.push(error === void 0 ? {
|
|
248
|
+
name,
|
|
249
|
+
title,
|
|
250
|
+
rows
|
|
251
|
+
} : {
|
|
252
|
+
name,
|
|
253
|
+
title,
|
|
254
|
+
rows,
|
|
255
|
+
error
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return out;
|
|
259
|
+
}
|
|
260
|
+
//#endregion
|
|
261
|
+
export { parseInspectResult, queryInspection, readInspections, registerInspection, routerInspection, sanitizeRows };
|
|
262
|
+
|
|
263
|
+
//# sourceMappingURL=inspection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspection.js","names":[],"sources":["../../src/inspection.ts"],"sourcesContent":["import { getHook } from \"./hook\";\nimport { errorMessage, preview } from \"./preview\";\nimport { MAX_INSPECTION_ROWS, MAX_INSPECTIONS } from \"./protocol\";\n\n/** Longest text of one row field, in characters. Longer text is cut with an ellipsis. */\nconst FIELD_LENGTH = 200;\n\n/**\n * One line of an inspection view. Every field is text, so a row is plain data and can cross a\n * message channel unchanged.\n *\n * @since 1.0.0\n */\nexport interface InspectionRow {\n /** Stable within the source (a query hash, a route match index, or a fixed name). */\n readonly id: string;\n /** What the row is about: a query key, a route pattern, or a fixed label such as `location`. */\n readonly label: string;\n /** The details, as one line of text. */\n readonly detail: string;\n /** A short state word for the row, such as `success`, `fetching` or `idle`. */\n readonly state: string;\n}\n\n/**\n * Something that can describe itself to a devtools panel: a router, a query cache, or any other part of\n * the app. `rows()` is called when the panel asks, so it reports the current state, not a copy taken\n * earlier. It may throw; the agent reports the error to the panel instead of failing the request.\n *\n * @example\n * import { type InspectionSource, registerInspection } from \"@shadow-js/devtools\";\n *\n * const source: InspectionSource = {\n * title: \"Feature flags\",\n * rows: () => [{ id: \"beta\", label: \"beta\", detail: \"on\", state: \"on\" }],\n * };\n * registerInspection(\"flags\", source);\n * @since 1.0.0\n */\nexport interface InspectionSource {\n /** The heading of the view, for example `Router`. */\n readonly title: string;\n /** The rows now. At most {@link MAX_INSPECTION_ROWS} are sent. */\n rows(): readonly InspectionRow[];\n /**\n * Optional. Calls `notify` whenever the rows may have changed, and returns the function that stops the\n * calls. The extension's page side listens to it, so the panel refreshes at the moment of the change\n * instead of on a timer. A source without it is read when the panel asks.\n */\n subscribe?(notify: () => void): () => void;\n}\n\n/**\n * A registered source with its name (the key the app registered it under).\n *\n * @since 1.0.0\n */\nexport interface InspectionEntry {\n /** The name the source was registered under. Unique per hook. */\n readonly name: string;\n /** The source. */\n readonly source: InspectionSource;\n}\n\n/**\n * What the agent answers to an `inspect` request: every registered source, read now. A source that\n * threw has no rows and an `error` message instead.\n *\n * @since 1.0.0\n */\nexport interface InspectionSnapshot {\n /** The registration name. */\n readonly name: string;\n /** The heading of the view. */\n readonly title: string;\n /** The rows (at most {@link MAX_INSPECTION_ROWS}). */\n readonly rows: readonly InspectionRow[];\n /** Why the rows could not be read, or `undefined` when they were. */\n readonly error?: string;\n}\n\n/**\n * The part of a router the router inspection reads. A `Router` from `@shadow-js/router` fits this\n * shape; it is structural, so devtools does not depend on the router package.\n *\n * @since 1.0.0\n */\nexport interface RouterLike {\n /** The router's live state (accessors). */\n readonly state: {\n location():\n | { readonly pathname: string; readonly search: string; readonly hash: string }\n | undefined;\n status(): string;\n pending(): boolean;\n error(): unknown;\n matches(): readonly {\n readonly id: string;\n readonly pattern: string;\n readonly params: Readonly<Record<string, string>>;\n }[];\n };\n}\n\n/**\n * One query as the query cache reports it (a `QueryInfo` snapshot of `@shadow-js/query`).\n *\n * @since 1.0.0\n */\nexport interface QueryInfoLike {\n /** The key of the query (plain data). */\n readonly queryKey: unknown;\n /** The hash of the key, which identifies the cache entry. */\n readonly queryHash: string;\n /** `pending`, `success` or `error`. */\n readonly status: string;\n /** `idle`, `fetching` or `paused`. */\n readonly fetchStatus: string;\n /** Time (ms since epoch) of the last successful fetch or write; `0` when there was none. */\n readonly dataUpdatedAt: number;\n /** True after an invalidation, until the next successful fetch or write. */\n readonly isInvalidated: boolean;\n /** Mounted observers of the query. */\n readonly observers: number;\n}\n\n/**\n * The part of a query client the query inspection reads: its cache, as snapshots. A `QueryClient` from\n * `@shadow-js/query` fits this shape.\n *\n * @since 1.0.0\n */\nexport interface QueryLike {\n /** The query cache, read through its snapshots, and told about its changes. */\n readonly queryCache: {\n /** Every query, as snapshots. Untracked. */\n find(): readonly QueryInfoLike[];\n /** Calls `listener` for every change of the cache; returns the function that stops it. */\n subscribe(listener: () => void): () => void;\n };\n}\n\n/**\n * The JSON text of plain data (query keys and route params are plain data), cut to the field length.\n * Falls back to {@link preview} when the value does not serialize (a cycle, a bigint).\n *\n * @param value The value.\n * @returns One line of text.\n */\nfunction jsonText(value: unknown): string {\n try {\n const text = JSON.stringify(value);\n return text === undefined ? preview(value, FIELD_LENGTH) : clip(text);\n } catch {\n return preview(value, FIELD_LENGTH);\n }\n}\n\n/**\n * Cuts text to the row field length, with an ellipsis when it was longer.\n *\n * @param text The text.\n * @returns The text, at most {@link FIELD_LENGTH} characters.\n */\nfunction clip(text: string): string {\n return text.length > FIELD_LENGTH ? `${text.slice(0, FIELD_LENGTH - 1)}…` : text;\n}\n\n/**\n * Turns a source's rows into plain rows, cut to the row limit. Each field is coerced to text and cut,\n * so a source cannot send an object, a function or an unbounded string to the panel.\n *\n * @param rows The rows a source returned.\n * @returns At most {@link MAX_INSPECTION_ROWS} plain rows, and a last row that says how many were cut.\n * @example\n * import { type InspectionRow, sanitizeRows } from \"@shadow-js/devtools\";\n *\n * const rows: InspectionRow[] = [{ id: \"a\", label: \"a\", detail: \"b\", state: \"c\" }];\n * sanitizeRows(rows); // one plain row\n * @since 1.0.0\n */\nexport function sanitizeRows(rows: readonly InspectionRow[]): InspectionRow[] {\n const out: InspectionRow[] = [];\n const limit = Math.min(rows.length, MAX_INSPECTION_ROWS);\n for (let index = 0; index < limit; index += 1) {\n const row = rows[index];\n if (row === undefined) continue;\n out.push({\n id: clip(String(row.id)),\n label: clip(String(row.label)),\n detail: clip(String(row.detail)),\n state: clip(String(row.state)),\n });\n }\n if (rows.length > MAX_INSPECTION_ROWS) {\n out.push({\n id: \"truncated\",\n label: \"…\",\n detail: `${rows.length - MAX_INSPECTION_ROWS} more rows not shown`,\n state: \"truncated\",\n });\n }\n return out;\n}\n\n/**\n * Describes a router: its location, whether a navigation is pending, the error it last reported, and\n * the chain of matched routes. The source reads the router when the panel asks.\n *\n * @param router The router (`createRouter` from `@shadow-js/router`).\n * @param title The heading of the view. Default `Router`.\n * @returns An {@link InspectionSource}.\n * @example\n * import { type RouterLike, registerInspection, routerInspection } from \"@shadow-js/devtools\";\n *\n * declare const router: RouterLike; // the router from your app's createRouter()\n * registerInspection(\"router\", routerInspection(router));\n * @since 1.0.0\n */\nexport function routerInspection(router: RouterLike, title = \"Router\"): InspectionSource {\n return {\n title,\n rows() {\n const rows: InspectionRow[] = [];\n const location = router.state.location();\n const status = router.state.status();\n rows.push({\n id: \"location\",\n label: \"location\",\n detail:\n location === undefined\n ? \"none\"\n : `${location.pathname}${location.search}${location.hash}`,\n state: status,\n });\n rows.push({\n id: \"pending\",\n label: \"pending\",\n detail: String(router.state.pending()),\n state: router.state.pending() ? \"pending\" : \"settled\",\n });\n const error = router.state.error();\n if (error !== undefined) {\n rows.push({ id: \"error\", label: \"error\", detail: errorMessage(error), state: \"errored\" });\n }\n router.state.matches().forEach((match, index) => {\n rows.push({\n id: `match:${index}`,\n label: match.pattern,\n detail: jsonText(match.params),\n state: match.id,\n });\n });\n return rows;\n },\n };\n}\n\n/**\n * Describes a query cache: one row per query, with its key, fetch state, observers and age. The\n * source reads the cache when the panel asks, and it subscribes to the cache so that a change is\n * pushed to a connected panel as soon as it happens. A client without the `inspection` feature of\n * `@shadow-js/query` cannot notify, so the panel then reads the cache only when it asks.\n *\n * @param client The query client (`createQueryClient` from `@shadow-js/query`, created with\n * `features: [inspection]` for live updates), or anything with the same `queryCache.find()`.\n * @param title The heading of the view. Default `Queries`.\n * @returns An {@link InspectionSource}.\n * @example\n * import { type QueryLike, queryInspection, registerInspection } from \"@shadow-js/devtools\";\n *\n * declare const client: QueryLike; // the query client of your app\n * registerInspection(\"query\", queryInspection(client));\n * @since 1.0.0\n */\nexport function queryInspection(client: QueryLike, title = \"Queries\"): InspectionSource {\n return {\n title,\n subscribe(notify) {\n try {\n return client.queryCache.subscribe(() => notify());\n } catch (error) {\n // Only the missing `inspection` feature is expected here (a TypeError); anything else is a bug.\n if (error instanceof TypeError) return () => undefined;\n throw error;\n }\n },\n rows() {\n return client.queryCache.find().map((query) => {\n const flags: string[] = [query.fetchStatus, `${query.observers} observers`];\n if (query.isInvalidated) flags.push(\"invalidated\");\n flags.push(\n query.dataUpdatedAt > 0\n ? `updated ${new Date(query.dataUpdatedAt).toISOString()}`\n : \"never updated\",\n );\n return {\n id: query.queryHash,\n label: jsonText(query.queryKey),\n detail: flags.join(\", \"),\n state: query.status,\n };\n });\n },\n };\n}\n\n/**\n * Registers an inspection source on the installed devtools hook, under `name`, so a panel can show it\n * (see the `inspect` request of {@link createAgent}). Re-registering a name replaces its source. Returns\n * `false` when nothing was registered: no hook is installed (no development build, or the hook of\n * another protocol version), the name is empty, or {@link MAX_INSPECTIONS} sources are already\n * registered under other names. A development tool; production builds register nothing.\n *\n * @param name The name to show the source under. Not empty.\n * @param source The source.\n * @returns True when the source is registered.\n * @example\n * import { type RouterLike, registerInspection, routerInspection } from \"@shadow-js/devtools\";\n *\n * declare const router: RouterLike; // the router from your app's createRouter()\n * registerInspection(\"router\", routerInspection(router));\n * @since 1.0.0\n */\nexport function registerInspection(name: string, source: InspectionSource): boolean {\n if (!__DEV__) return false;\n if (name === \"\") return false;\n const hook = getHook();\n if (hook === undefined || hook.inspect === undefined) return false;\n return hook.inspect(name, source);\n}\n\n/**\n * Reads every registered source, now, as plain snapshots. A source that throws is reported with its\n * `error` and no rows. Used by the agent for the `inspect` request.\n *\n * @param entries The registered entries (the hook's `inspections()`).\n * @returns One snapshot per entry, in registration order.\n * @example\n * import { readInspections } from \"@shadow-js/devtools\";\n *\n * const snapshots = readInspections([]); // []\n * @since 1.0.0\n */\nexport function readInspections(entries: readonly InspectionEntry[]): InspectionSnapshot[] {\n return entries.map((entry) => {\n try {\n return {\n name: entry.name,\n title: clip(String(entry.source.title)),\n rows: sanitizeRows(entry.source.rows()),\n };\n } catch (error) {\n return {\n name: entry.name,\n title: clip(String(entry.source.title)),\n rows: [],\n error: errorMessage(error),\n };\n }\n });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction isRow(value: unknown): value is InspectionRow {\n return (\n isRecord(value) &&\n typeof value[\"id\"] === \"string\" &&\n typeof value[\"label\"] === \"string\" &&\n typeof value[\"detail\"] === \"string\" &&\n typeof value[\"state\"] === \"string\"\n );\n}\n\n/**\n * Reads the answer of an `inspect` request. The panel gets it from the page, so it checks the shape\n * and the bounds before it trusts anything: every field is text, there are at most\n * {@link MAX_INSPECTIONS} sources and at most {@link MAX_INSPECTION_ROWS} + 1 rows each.\n *\n * @param value The `result` of an ok `inspect` response.\n * @returns The snapshots, or `null` when the value is not an answer to `inspect`.\n * @example\n * import { parseInspectResult } from \"@shadow-js/devtools\";\n *\n * parseInspectResult({ sources: [] }); // []\n * parseInspectResult({ nope: true }); // null\n * @since 1.0.0\n */\nexport function parseInspectResult(value: unknown): InspectionSnapshot[] | null {\n if (!isRecord(value) || !Array.isArray(value[\"sources\"])) return null;\n const sources: unknown[] = value[\"sources\"];\n if (sources.length > MAX_INSPECTIONS) return null;\n const out: InspectionSnapshot[] = [];\n for (const entry of sources) {\n if (!isRecord(entry)) return null;\n const name = entry[\"name\"];\n const title = entry[\"title\"];\n const rows = entry[\"rows\"];\n const error = entry[\"error\"];\n if (typeof name !== \"string\" || typeof title !== \"string\" || !Array.isArray(rows)) return null;\n if (rows.length > MAX_INSPECTION_ROWS + 1 || !rows.every(isRow)) return null;\n if (error !== undefined && typeof error !== \"string\") return null;\n out.push(error === undefined ? { name, title, rows } : { name, title, rows, error });\n }\n return out;\n}\n"],"mappings":"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"}
|