@echojs-ecosystem/devtools 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ <div align="center">
2
+
3
+ # @echojs-ecosystem/devtools
4
+
5
+ **Runtime registry and event timeline for EchoJS DevTools — no UI, zero overhead when disabled.**
6
+
7
+ [![npm](https://img.shields.io/npm/v/@echojs-ecosystem/devtools)](https://www.npmjs.com/package/@echojs-ecosystem/devtools)
8
+ [![docs](https://img.shields.io/badge/docs-echojs.dev-blue)](https://echojs.dev/docs/packages/devtools)
9
+
10
+ </div>
11
+
12
+ ---
13
+
14
+ Infrastructure layer for EchoJS DevTools. Provides a **global bridge**, **node registry**, and **FIFO event timeline**. Integrating packages (store, query, router, …) register automatically — end users rarely call these APIs directly.
15
+
16
+ ## Features
17
+
18
+ - **Registry** — nodes with `type`, `id`, `name`, optional `getSnapshot()`
19
+ - **Timeline** — event log (default max 500 events) with subscribe API
20
+ - **Global bridge** — `globalThis.__ECHOJS_DEVTOOLS__`
21
+ - **Production-safe** — `setDevtoolsEnabled(false)` makes all APIs no-ops
22
+ - **Zero dependencies** — `sideEffects: false`, tree-shakeable
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ npm install @echojs-ecosystem/devtools
28
+ ```
29
+
30
+ Or via the meta-package:
31
+
32
+ ```bash
33
+ npm install @echojs-ecosystem/framework
34
+ ```
35
+
36
+ ```ts
37
+ import { setDevtoolsEnabled } from "@echojs-ecosystem/framework/devtools";
38
+ ```
39
+
40
+ ## Quick start
41
+
42
+ ```ts
43
+ import {
44
+ registerDevtoolsNode,
45
+ emitDevtoolsEvent,
46
+ subscribeTimeline,
47
+ setDevtoolsEnabled,
48
+ } from "@echojs-ecosystem/devtools";
49
+
50
+ setDevtoolsEnabled(import.meta.env.DEV);
51
+
52
+ const node = registerDevtoolsNode({
53
+ type: "store",
54
+ id: "auth",
55
+ name: "authStore",
56
+ getSnapshot: () => ({ token: authStore.value() }),
57
+ });
58
+
59
+ emitDevtoolsEvent({
60
+ source: "store",
61
+ type: "changed",
62
+ nodeId: "auth",
63
+ payload: { value, prevValue },
64
+ });
65
+
66
+ subscribeTimeline((event) => console.log(event));
67
+ node.unregister();
68
+ ```
69
+
70
+ ## Node types
71
+
72
+ `store` · `query` · `mutation` · `router` · `persist` · `url-state` · `ui-provider` · `signal` · `custom`
73
+
74
+ ## API
75
+
76
+ | Export | Description |
77
+ |--------|-------------|
78
+ | `registerDevtoolsNode` | Register inspectable node |
79
+ | `emitDevtoolsEvent` | Append timeline event |
80
+ | `getNodeSnapshot` / `getAllSnapshots` | Read current state |
81
+ | `subscribeTimeline` | Listen to events |
82
+ | `getDevtoolsBridge` | Full bridge access |
83
+ | `setDevtoolsEnabled` | Toggle all DevTools APIs |
84
+ | `createDevtoolsId`, `safeSerialize` | Utilities |
85
+
86
+ ## Documentation
87
+
88
+ [echojs.dev/docs/packages/devtools](https://echojs.dev/docs/packages/devtools)
@@ -0,0 +1,111 @@
1
+ type DevtoolsNodeType = 'store' | 'query' | 'mutation' | 'router' | 'persist' | 'url-state' | 'ui-provider' | 'signal' | 'custom';
2
+ type DevtoolsEventSource = 'store' | 'query' | 'mutation' | 'router' | 'persist' | 'url-state' | 'ui' | 'custom';
3
+ type DevtoolsNode = {
4
+ id: string;
5
+ type: DevtoolsNodeType;
6
+ name?: string;
7
+ getSnapshot?: () => unknown;
8
+ };
9
+ type RegisterDevtoolsNodeInput = {
10
+ id?: string;
11
+ type: DevtoolsNodeType;
12
+ name?: string;
13
+ getSnapshot?: () => unknown;
14
+ };
15
+ type RegisteredNode = {
16
+ unregister(): void;
17
+ };
18
+ type DevtoolsSnapshot = {
19
+ id: string;
20
+ type: DevtoolsNodeType;
21
+ name?: string;
22
+ state: unknown;
23
+ timestamp: number;
24
+ };
25
+ type DevtoolsEvent = {
26
+ id: string;
27
+ timestamp: number;
28
+ source: DevtoolsEventSource;
29
+ type: string;
30
+ nodeId?: string;
31
+ payload?: unknown;
32
+ };
33
+ type EmitDevtoolsEventInput = {
34
+ id?: string;
35
+ timestamp?: number;
36
+ source: DevtoolsEventSource;
37
+ type: string;
38
+ nodeId?: string;
39
+ payload?: unknown;
40
+ };
41
+ type TimelineListener = (event: DevtoolsEvent) => void;
42
+ type DevtoolsRegistryAPI = {
43
+ register(input: RegisterDevtoolsNodeInput): RegisteredNode;
44
+ unregister(nodeId: string): boolean;
45
+ get(nodeId: string): DevtoolsNode | undefined;
46
+ getSnapshot(nodeId: string): DevtoolsSnapshot | undefined;
47
+ getAllSnapshots(): DevtoolsSnapshot[];
48
+ list(): DevtoolsNode[];
49
+ };
50
+ type DevtoolsTimelineAPI = {
51
+ emit(input: EmitDevtoolsEventInput): DevtoolsEvent | null;
52
+ getEvents(): readonly DevtoolsEvent[];
53
+ subscribe(listener: TimelineListener): () => void;
54
+ setMaxEvents(max: number): void;
55
+ clear(): void;
56
+ };
57
+ type EchoJSDevtoolsBridge = {
58
+ registry: DevtoolsRegistryAPI;
59
+ timeline: DevtoolsTimelineAPI;
60
+ };
61
+ declare const DEVTOOLS_GLOBAL_HOOK_KEY: "__ECHOJS_DEVTOOLS__";
62
+ declare global {
63
+ var __ECHOJS_DEVTOOLS__: EchoJSDevtoolsBridge | undefined;
64
+ }
65
+
66
+ declare const setDevtoolsEnabled: (enabled: boolean) => void;
67
+ declare const isDevtoolsEnabled: () => boolean;
68
+ declare const createDevtoolsBridge: () => EchoJSDevtoolsBridge;
69
+ declare const getOrCreateDevtoolsBridge: () => EchoJSDevtoolsBridge;
70
+ declare const getDevtoolsBridge: () => EchoJSDevtoolsBridge | null;
71
+ /** @internal Resets bridge state — tests only */
72
+ declare const __resetDevtoolsCoreForTests: () => void;
73
+
74
+ declare const createDevtoolsId: (prefix?: string) => string;
75
+ /** @internal Test helper */
76
+ declare const resetDevtoolsIds: () => void;
77
+
78
+ type SafeSerializeOptions = {
79
+ maxDepth?: number;
80
+ };
81
+ declare const safeSerialize: (value: unknown, options?: SafeSerializeOptions) => unknown;
82
+
83
+ declare class DevtoolsRegistry implements DevtoolsRegistryAPI {
84
+ #private;
85
+ register(input: RegisterDevtoolsNodeInput): RegisteredNode;
86
+ unregister(nodeId: string): boolean;
87
+ get(nodeId: string): DevtoolsNode | undefined;
88
+ getSnapshot(nodeId: string): DevtoolsSnapshot | undefined;
89
+ getAllSnapshots(): DevtoolsSnapshot[];
90
+ list(): DevtoolsNode[];
91
+ clear(): void;
92
+ }
93
+
94
+ declare class DevtoolsTimeline implements DevtoolsTimelineAPI {
95
+ #private;
96
+ emit(input: EmitDevtoolsEventInput): DevtoolsEvent;
97
+ getEvents(): readonly DevtoolsEvent[];
98
+ subscribe(listener: TimelineListener): () => void;
99
+ setMaxEvents(max: number): void;
100
+ clear(): void;
101
+ }
102
+
103
+ declare const registerDevtoolsNode: (input: RegisterDevtoolsNodeInput) => RegisteredNode;
104
+ declare const unregisterDevtoolsNode: (nodeId: string) => boolean;
105
+ declare const emitDevtoolsEvent: (input: EmitDevtoolsEventInput) => DevtoolsEvent | null;
106
+ declare const getNodeSnapshot: (nodeId: string) => DevtoolsSnapshot | undefined;
107
+ declare const getAllSnapshots: () => DevtoolsSnapshot[];
108
+ declare const getTimelineEvents: () => readonly DevtoolsEvent[];
109
+ declare const subscribeTimeline: (listener: TimelineListener) => (() => void);
110
+
111
+ export { DEVTOOLS_GLOBAL_HOOK_KEY, type DevtoolsEvent, type DevtoolsEventSource, type DevtoolsNode, type DevtoolsNodeType, DevtoolsRegistry, type DevtoolsRegistryAPI, type DevtoolsSnapshot, DevtoolsTimeline, type DevtoolsTimelineAPI, type EchoJSDevtoolsBridge, type EmitDevtoolsEventInput, type RegisterDevtoolsNodeInput, type RegisteredNode, type TimelineListener, __resetDevtoolsCoreForTests, createDevtoolsBridge, createDevtoolsId, emitDevtoolsEvent, getAllSnapshots, getDevtoolsBridge, getNodeSnapshot, getOrCreateDevtoolsBridge, getTimelineEvents, isDevtoolsEnabled, registerDevtoolsNode, resetDevtoolsIds, safeSerialize, setDevtoolsEnabled, subscribeTimeline, unregisterDevtoolsNode };
package/dist/index.js ADDED
@@ -0,0 +1,260 @@
1
+ // src/utils/serialize.ts
2
+ var DEFAULT_MAX_DEPTH = 12;
3
+ var safeSerialize = (value, options = {}) => {
4
+ const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
5
+ const seen = /* @__PURE__ */ new WeakSet();
6
+ const walk = (current, depth) => {
7
+ if (depth > maxDepth) return "[MaxDepth]";
8
+ if (current === void 0) return void 0;
9
+ if (current === null) return null;
10
+ const valueType = typeof current;
11
+ if (valueType === "bigint") return `${current.toString()}n`;
12
+ if (valueType === "symbol") return current.toString();
13
+ if (valueType === "function") {
14
+ const fn = current;
15
+ return `[Function ${fn.name || "anonymous"}]`;
16
+ }
17
+ if (valueType !== "object") return current;
18
+ if (seen.has(current)) return "[Circular]";
19
+ if (current instanceof Date) return current.toISOString();
20
+ if (current instanceof RegExp) return current.toString();
21
+ if (current instanceof Map) {
22
+ seen.add(current);
23
+ return {
24
+ __type: "Map",
25
+ entries: [...current.entries()].map(([key, val]) => [walk(key, depth + 1), walk(val, depth + 1)])
26
+ };
27
+ }
28
+ if (current instanceof Set) {
29
+ seen.add(current);
30
+ return {
31
+ __type: "Set",
32
+ values: [...current.values()].map((val) => walk(val, depth + 1))
33
+ };
34
+ }
35
+ seen.add(current);
36
+ if (Array.isArray(current)) {
37
+ return current.map((item) => walk(item, depth + 1));
38
+ }
39
+ const record = current;
40
+ const output = {};
41
+ for (const key of Object.keys(record)) {
42
+ try {
43
+ output[key] = walk(record[key], depth + 1);
44
+ } catch {
45
+ output[key] = "[Unserializable]";
46
+ }
47
+ }
48
+ return output;
49
+ };
50
+ try {
51
+ return walk(value, 0);
52
+ } catch {
53
+ return "[Unserializable]";
54
+ }
55
+ };
56
+
57
+ // src/registry/snapshot.ts
58
+ var captureNodeSnapshot = (node) => {
59
+ let state = null;
60
+ if (node.getSnapshot) {
61
+ try {
62
+ state = safeSerialize(node.getSnapshot());
63
+ } catch {
64
+ state = "[SnapshotError]";
65
+ }
66
+ }
67
+ return {
68
+ id: node.id,
69
+ type: node.type,
70
+ name: node.name,
71
+ state,
72
+ timestamp: Date.now()
73
+ };
74
+ };
75
+
76
+ // src/utils/id.ts
77
+ var counters = /* @__PURE__ */ new Map();
78
+ var createDevtoolsId = (prefix = "node") => {
79
+ const next = (counters.get(prefix) ?? 0) + 1;
80
+ counters.set(prefix, next);
81
+ return `${prefix}_${next}`;
82
+ };
83
+ var resetDevtoolsIds = () => {
84
+ counters.clear();
85
+ };
86
+
87
+ // src/registry/node.ts
88
+ var normalizeDevtoolsNode = (input) => ({
89
+ id: input.id ?? createDevtoolsId(input.type),
90
+ type: input.type,
91
+ name: input.name,
92
+ getSnapshot: input.getSnapshot
93
+ });
94
+
95
+ // src/registry/registry.ts
96
+ var DevtoolsRegistry = class {
97
+ #nodes = /* @__PURE__ */ new Map();
98
+ register(input) {
99
+ const node = normalizeDevtoolsNode(input);
100
+ if (this.#nodes.has(node.id)) {
101
+ throw new Error(`@echojs-ecosystem/devtools: node "${node.id}" is already registered`);
102
+ }
103
+ this.#nodes.set(node.id, node);
104
+ return {
105
+ unregister: () => {
106
+ this.unregister(node.id);
107
+ }
108
+ };
109
+ }
110
+ unregister(nodeId) {
111
+ return this.#nodes.delete(nodeId);
112
+ }
113
+ get(nodeId) {
114
+ return this.#nodes.get(nodeId);
115
+ }
116
+ getSnapshot(nodeId) {
117
+ const node = this.#nodes.get(nodeId);
118
+ if (!node) return void 0;
119
+ return captureNodeSnapshot(node);
120
+ }
121
+ getAllSnapshots() {
122
+ return [...this.#nodes.values()].map((node) => captureNodeSnapshot(node));
123
+ }
124
+ list() {
125
+ return [...this.#nodes.values()];
126
+ }
127
+ clear() {
128
+ this.#nodes.clear();
129
+ }
130
+ };
131
+
132
+ // src/timeline/event.ts
133
+ var createDevtoolsEvent = (input) => ({
134
+ id: input.id ?? createDevtoolsId("event"),
135
+ timestamp: input.timestamp ?? Date.now(),
136
+ source: input.source,
137
+ type: input.type,
138
+ nodeId: input.nodeId,
139
+ payload: input.payload === void 0 ? void 0 : safeSerialize(input.payload)
140
+ });
141
+
142
+ // src/timeline/timeline.ts
143
+ var DEFAULT_MAX_EVENTS = 500;
144
+ var DevtoolsTimeline = class {
145
+ #events = [];
146
+ #maxEvents = DEFAULT_MAX_EVENTS;
147
+ #listeners = /* @__PURE__ */ new Set();
148
+ emit(input) {
149
+ const event = createDevtoolsEvent(input);
150
+ this.#events.push(event);
151
+ if (this.#events.length > this.#maxEvents) {
152
+ this.#events.splice(0, this.#events.length - this.#maxEvents);
153
+ }
154
+ for (const listener of this.#listeners) {
155
+ listener(event);
156
+ }
157
+ return event;
158
+ }
159
+ getEvents() {
160
+ return this.#events;
161
+ }
162
+ subscribe(listener) {
163
+ this.#listeners.add(listener);
164
+ return () => {
165
+ this.#listeners.delete(listener);
166
+ };
167
+ }
168
+ setMaxEvents(max) {
169
+ this.#maxEvents = Math.max(1, max);
170
+ if (this.#events.length > this.#maxEvents) {
171
+ this.#events.splice(0, this.#events.length - this.#maxEvents);
172
+ }
173
+ }
174
+ clear() {
175
+ this.#events.length = 0;
176
+ }
177
+ };
178
+
179
+ // src/types.ts
180
+ var DEVTOOLS_GLOBAL_HOOK_KEY = "__ECHOJS_DEVTOOLS__";
181
+
182
+ // src/bridge/global-hook.ts
183
+ var readGlobalDevtoolsHook = () => globalThis[DEVTOOLS_GLOBAL_HOOK_KEY];
184
+ var writeGlobalDevtoolsHook = (bridge) => {
185
+ const host = globalThis;
186
+ if (host[DEVTOOLS_GLOBAL_HOOK_KEY]) return;
187
+ host[DEVTOOLS_GLOBAL_HOOK_KEY] = bridge;
188
+ };
189
+ var clearGlobalDevtoolsHook = () => {
190
+ delete globalThis[DEVTOOLS_GLOBAL_HOOK_KEY];
191
+ };
192
+
193
+ // src/bridge/bridge.ts
194
+ var devtoolsEnabled = true;
195
+ var noopRegisteredNode = { unregister: () => {
196
+ } };
197
+ var setDevtoolsEnabled = (enabled) => {
198
+ devtoolsEnabled = enabled;
199
+ };
200
+ var isDevtoolsEnabled = () => devtoolsEnabled;
201
+ var createDevtoolsBridge = () => ({
202
+ registry: new DevtoolsRegistry(),
203
+ timeline: new DevtoolsTimeline()
204
+ });
205
+ var getOrCreateDevtoolsBridge = () => {
206
+ const existing = readGlobalDevtoolsHook();
207
+ if (existing) return existing;
208
+ const bridge = createDevtoolsBridge();
209
+ writeGlobalDevtoolsHook(bridge);
210
+ return bridge;
211
+ };
212
+ var getDevtoolsBridge = () => {
213
+ if (!devtoolsEnabled) return null;
214
+ return readGlobalDevtoolsHook() ?? getOrCreateDevtoolsBridge();
215
+ };
216
+ var whenDevtoolsEnabled = (run) => {
217
+ if (!devtoolsEnabled) return null;
218
+ return run(getOrCreateDevtoolsBridge());
219
+ };
220
+ var disabledRegisteredNode = () => noopRegisteredNode;
221
+ var __resetDevtoolsCoreForTests = () => {
222
+ devtoolsEnabled = true;
223
+ clearGlobalDevtoolsHook();
224
+ };
225
+
226
+ // src/index.ts
227
+ var registerDevtoolsNode = (input) => {
228
+ if (!isDevtoolsEnabled()) return disabledRegisteredNode();
229
+ return whenDevtoolsEnabled((bridge) => bridge.registry.register(input));
230
+ };
231
+ var unregisterDevtoolsNode = (nodeId) => {
232
+ if (!isDevtoolsEnabled()) return false;
233
+ return whenDevtoolsEnabled((bridge) => bridge.registry.unregister(nodeId)) ?? false;
234
+ };
235
+ var emitDevtoolsEvent = (input) => {
236
+ if (!isDevtoolsEnabled()) return null;
237
+ return whenDevtoolsEnabled((bridge) => bridge.timeline.emit(input));
238
+ };
239
+ var getNodeSnapshot = (nodeId) => {
240
+ if (!isDevtoolsEnabled()) return void 0;
241
+ return whenDevtoolsEnabled((bridge) => bridge.registry.getSnapshot(nodeId)) ?? void 0;
242
+ };
243
+ var getAllSnapshots = () => {
244
+ if (!isDevtoolsEnabled()) return [];
245
+ return whenDevtoolsEnabled((bridge) => bridge.registry.getAllSnapshots()) ?? [];
246
+ };
247
+ var getTimelineEvents = () => {
248
+ if (!isDevtoolsEnabled()) return [];
249
+ return whenDevtoolsEnabled((bridge) => bridge.timeline.getEvents()) ?? [];
250
+ };
251
+ var subscribeTimeline = (listener) => {
252
+ if (!isDevtoolsEnabled()) return () => {
253
+ };
254
+ return whenDevtoolsEnabled((bridge) => bridge.timeline.subscribe(listener)) ?? (() => {
255
+ });
256
+ };
257
+
258
+ export { DEVTOOLS_GLOBAL_HOOK_KEY, DevtoolsRegistry, DevtoolsTimeline, __resetDevtoolsCoreForTests, createDevtoolsBridge, createDevtoolsId, emitDevtoolsEvent, getAllSnapshots, getDevtoolsBridge, getNodeSnapshot, getOrCreateDevtoolsBridge, getTimelineEvents, isDevtoolsEnabled, registerDevtoolsNode, resetDevtoolsIds, safeSerialize, setDevtoolsEnabled, subscribeTimeline, unregisterDevtoolsNode };
259
+ //# sourceMappingURL=index.js.map
260
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/serialize.ts","../src/registry/snapshot.ts","../src/utils/id.ts","../src/registry/node.ts","../src/registry/registry.ts","../src/timeline/event.ts","../src/timeline/timeline.ts","../src/types.ts","../src/bridge/global-hook.ts","../src/bridge/bridge.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export type SafeSerializeOptions = {\n maxDepth?: number\n}\n\nconst DEFAULT_MAX_DEPTH = 12\n\nexport const safeSerialize = (value: unknown, options: SafeSerializeOptions = {}): unknown => {\n const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH\n const seen = new WeakSet<object>()\n\n const walk = (current: unknown, depth: number): unknown => {\n if (depth > maxDepth) return '[MaxDepth]'\n\n if (current === undefined) return undefined\n if (current === null) return null\n\n const valueType = typeof current\n\n if (valueType === 'bigint') return `${current.toString()}n`\n if (valueType === 'symbol') return current.toString()\n if (valueType === 'function') {\n const fn = current as (...args: unknown[]) => unknown\n return `[Function ${fn.name || 'anonymous'}]`\n }\n if (valueType !== 'object') return current\n\n if (seen.has(current as object)) return '[Circular]'\n\n if (current instanceof Date) return current.toISOString()\n if (current instanceof RegExp) return current.toString()\n if (current instanceof Map) {\n seen.add(current)\n return {\n __type: 'Map',\n entries: [...current.entries()].map(([key, val]) => [walk(key, depth + 1), walk(val, depth + 1)]),\n }\n }\n if (current instanceof Set) {\n seen.add(current)\n return {\n __type: 'Set',\n values: [...current.values()].map((val) => walk(val, depth + 1)),\n }\n }\n\n seen.add(current as object)\n\n if (Array.isArray(current)) {\n return current.map((item) => walk(item, depth + 1))\n }\n\n const record = current as Record<string, unknown>\n const output: Record<string, unknown> = {}\n\n for (const key of Object.keys(record)) {\n try {\n output[key] = walk(record[key], depth + 1)\n } catch {\n output[key] = '[Unserializable]'\n }\n }\n\n return output\n }\n\n try {\n return walk(value, 0)\n } catch {\n return '[Unserializable]'\n }\n}\n","import type { DevtoolsNode, DevtoolsSnapshot } from '../types'\nimport { safeSerialize } from '../utils/serialize'\n\nexport const captureNodeSnapshot = (node: DevtoolsNode): DevtoolsSnapshot => {\n let state: unknown = null\n\n if (node.getSnapshot) {\n try {\n state = safeSerialize(node.getSnapshot())\n } catch {\n state = '[SnapshotError]'\n }\n }\n\n return {\n id: node.id,\n type: node.type,\n name: node.name,\n state,\n timestamp: Date.now(),\n }\n}\n","const counters = new Map<string, number>()\n\nexport const createDevtoolsId = (prefix = 'node'): string => {\n const next = (counters.get(prefix) ?? 0) + 1\n counters.set(prefix, next)\n return `${prefix}_${next}`\n}\n\n/** @internal Test helper */\nexport const resetDevtoolsIds = (): void => {\n counters.clear()\n}\n","import type { DevtoolsNode, RegisterDevtoolsNodeInput } from '../types'\nimport { createDevtoolsId } from '../utils/id'\n\nexport const normalizeDevtoolsNode = (input: RegisterDevtoolsNodeInput): DevtoolsNode => ({\n id: input.id ?? createDevtoolsId(input.type),\n type: input.type,\n name: input.name,\n getSnapshot: input.getSnapshot,\n})\n","import { captureNodeSnapshot } from './snapshot'\nimport { normalizeDevtoolsNode } from './node'\nimport type {\n DevtoolsNode,\n DevtoolsRegistryAPI,\n DevtoolsSnapshot,\n RegisterDevtoolsNodeInput,\n RegisteredNode,\n} from '../types'\n\nexport class DevtoolsRegistry implements DevtoolsRegistryAPI {\n readonly #nodes = new Map<string, DevtoolsNode>()\n\n register(input: RegisterDevtoolsNodeInput): RegisteredNode {\n const node = normalizeDevtoolsNode(input)\n\n if (this.#nodes.has(node.id)) {\n throw new Error(`@echojs-ecosystem/devtools: node \"${node.id}\" is already registered`)\n }\n\n this.#nodes.set(node.id, node)\n\n return {\n unregister: () => {\n this.unregister(node.id)\n },\n }\n }\n\n unregister(nodeId: string): boolean {\n return this.#nodes.delete(nodeId)\n }\n\n get(nodeId: string): DevtoolsNode | undefined {\n return this.#nodes.get(nodeId)\n }\n\n getSnapshot(nodeId: string): DevtoolsSnapshot | undefined {\n const node = this.#nodes.get(nodeId)\n if (!node) return undefined\n return captureNodeSnapshot(node)\n }\n\n getAllSnapshots(): DevtoolsSnapshot[] {\n return [...this.#nodes.values()].map((node) => captureNodeSnapshot(node))\n }\n\n list(): DevtoolsNode[] {\n return [...this.#nodes.values()]\n }\n\n clear(): void {\n this.#nodes.clear()\n }\n}\n","import type { DevtoolsEvent, EmitDevtoolsEventInput } from '../types'\nimport { createDevtoolsId } from '../utils/id'\nimport { safeSerialize } from '../utils/serialize'\n\nexport const createDevtoolsEvent = (input: EmitDevtoolsEventInput): DevtoolsEvent => ({\n id: input.id ?? createDevtoolsId('event'),\n timestamp: input.timestamp ?? Date.now(),\n source: input.source,\n type: input.type,\n nodeId: input.nodeId,\n payload:\n input.payload === undefined ? undefined : safeSerialize(input.payload),\n})\n","import { createDevtoolsEvent } from './event'\nimport type {\n DevtoolsEvent,\n DevtoolsTimelineAPI,\n EmitDevtoolsEventInput,\n TimelineListener,\n} from '../types'\n\nconst DEFAULT_MAX_EVENTS = 500\n\nexport class DevtoolsTimeline implements DevtoolsTimelineAPI {\n #events: DevtoolsEvent[] = []\n #maxEvents = DEFAULT_MAX_EVENTS\n readonly #listeners = new Set<TimelineListener>()\n\n emit(input: EmitDevtoolsEventInput): DevtoolsEvent {\n const event = createDevtoolsEvent(input)\n this.#events.push(event)\n\n if (this.#events.length > this.#maxEvents) {\n this.#events.splice(0, this.#events.length - this.#maxEvents)\n }\n\n for (const listener of this.#listeners) {\n listener(event)\n }\n\n return event\n }\n\n getEvents(): readonly DevtoolsEvent[] {\n return this.#events\n }\n\n subscribe(listener: TimelineListener): () => void {\n this.#listeners.add(listener)\n return () => {\n this.#listeners.delete(listener)\n }\n }\n\n setMaxEvents(max: number): void {\n this.#maxEvents = Math.max(1, max)\n if (this.#events.length > this.#maxEvents) {\n this.#events.splice(0, this.#events.length - this.#maxEvents)\n }\n }\n\n clear(): void {\n this.#events.length = 0\n }\n}\n","export type DevtoolsNodeType =\n | 'store'\n | 'query'\n | 'mutation'\n | 'router'\n | 'persist'\n | 'url-state'\n | 'ui-provider'\n | 'signal'\n | 'custom'\n\nexport type DevtoolsEventSource =\n | 'store'\n | 'query'\n | 'mutation'\n | 'router'\n | 'persist'\n | 'url-state'\n | 'ui'\n | 'custom'\n\nexport type DevtoolsNode = {\n id: string\n type: DevtoolsNodeType\n name?: string\n getSnapshot?: () => unknown\n}\n\nexport type RegisterDevtoolsNodeInput = {\n id?: string\n type: DevtoolsNodeType\n name?: string\n getSnapshot?: () => unknown\n}\n\nexport type RegisteredNode = {\n unregister(): void\n}\n\nexport type DevtoolsSnapshot = {\n id: string\n type: DevtoolsNodeType\n name?: string\n state: unknown\n timestamp: number\n}\n\nexport type DevtoolsEvent = {\n id: string\n timestamp: number\n source: DevtoolsEventSource\n type: string\n nodeId?: string\n payload?: unknown\n}\n\nexport type EmitDevtoolsEventInput = {\n id?: string\n timestamp?: number\n source: DevtoolsEventSource\n type: string\n nodeId?: string\n payload?: unknown\n}\n\nexport type TimelineListener = (event: DevtoolsEvent) => void\n\nexport type DevtoolsRegistryAPI = {\n register(input: RegisterDevtoolsNodeInput): RegisteredNode\n unregister(nodeId: string): boolean\n get(nodeId: string): DevtoolsNode | undefined\n getSnapshot(nodeId: string): DevtoolsSnapshot | undefined\n getAllSnapshots(): DevtoolsSnapshot[]\n list(): DevtoolsNode[]\n}\n\nexport type DevtoolsTimelineAPI = {\n emit(input: EmitDevtoolsEventInput): DevtoolsEvent | null\n getEvents(): readonly DevtoolsEvent[]\n subscribe(listener: TimelineListener): () => void\n setMaxEvents(max: number): void\n clear(): void\n}\n\nexport type EchoJSDevtoolsBridge = {\n registry: DevtoolsRegistryAPI\n timeline: DevtoolsTimelineAPI\n}\n\nexport const DEVTOOLS_GLOBAL_HOOK_KEY = '__ECHOJS_DEVTOOLS__' as const\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __ECHOJS_DEVTOOLS__: EchoJSDevtoolsBridge | undefined\n}\n","import type { EchoJSDevtoolsBridge } from '../types'\nimport { DEVTOOLS_GLOBAL_HOOK_KEY } from '../types'\n\ntype GlobalHookHost = typeof globalThis & {\n [DEVTOOLS_GLOBAL_HOOK_KEY]?: EchoJSDevtoolsBridge\n}\n\nexport const readGlobalDevtoolsHook = (): EchoJSDevtoolsBridge | undefined =>\n (globalThis as GlobalHookHost)[DEVTOOLS_GLOBAL_HOOK_KEY]\n\nexport const writeGlobalDevtoolsHook = (bridge: EchoJSDevtoolsBridge): void => {\n const host = globalThis as GlobalHookHost\n if (host[DEVTOOLS_GLOBAL_HOOK_KEY]) return\n host[DEVTOOLS_GLOBAL_HOOK_KEY] = bridge\n}\n\nexport const clearGlobalDevtoolsHook = (): void => {\n delete (globalThis as GlobalHookHost)[DEVTOOLS_GLOBAL_HOOK_KEY]\n}\n","import { DevtoolsRegistry } from '../registry/registry'\nimport { DevtoolsTimeline } from '../timeline/timeline'\nimport {\n clearGlobalDevtoolsHook,\n readGlobalDevtoolsHook,\n writeGlobalDevtoolsHook,\n} from './global-hook'\nimport type { EchoJSDevtoolsBridge } from '../types'\n\nlet devtoolsEnabled = true\n\nconst noopRegisteredNode = { unregister: () => {} }\n\nexport const setDevtoolsEnabled = (enabled: boolean): void => {\n devtoolsEnabled = enabled\n}\n\nexport const isDevtoolsEnabled = (): boolean => devtoolsEnabled\n\nexport const createDevtoolsBridge = (): EchoJSDevtoolsBridge => ({\n registry: new DevtoolsRegistry(),\n timeline: new DevtoolsTimeline(),\n})\n\nexport const getOrCreateDevtoolsBridge = (): EchoJSDevtoolsBridge => {\n const existing = readGlobalDevtoolsHook()\n if (existing) return existing\n\n const bridge = createDevtoolsBridge()\n writeGlobalDevtoolsHook(bridge)\n return bridge\n}\n\nexport const getDevtoolsBridge = (): EchoJSDevtoolsBridge | null => {\n if (!devtoolsEnabled) return null\n return readGlobalDevtoolsHook() ?? getOrCreateDevtoolsBridge()\n}\n\nexport const whenDevtoolsEnabled = <T>(run: (bridge: EchoJSDevtoolsBridge) => T): T | null => {\n if (!devtoolsEnabled) return null\n return run(getOrCreateDevtoolsBridge())\n}\n\nexport const disabledRegisteredNode = (): typeof noopRegisteredNode => noopRegisteredNode\n\n/** @internal Resets bridge state — tests only */\nexport const __resetDevtoolsCoreForTests = (): void => {\n devtoolsEnabled = true\n clearGlobalDevtoolsHook()\n}\n","export {\n setDevtoolsEnabled,\n isDevtoolsEnabled,\n getDevtoolsBridge,\n getOrCreateDevtoolsBridge,\n createDevtoolsBridge,\n __resetDevtoolsCoreForTests,\n} from './bridge/bridge'\n\nexport { DEVTOOLS_GLOBAL_HOOK_KEY } from './types'\n\nexport type {\n DevtoolsNodeType,\n DevtoolsEventSource,\n DevtoolsNode,\n RegisterDevtoolsNodeInput,\n RegisteredNode,\n DevtoolsSnapshot,\n DevtoolsEvent,\n EmitDevtoolsEventInput,\n TimelineListener,\n DevtoolsRegistryAPI,\n DevtoolsTimelineAPI,\n EchoJSDevtoolsBridge,\n} from './types'\n\nexport { createDevtoolsId, resetDevtoolsIds } from './utils/id'\nexport { safeSerialize } from './utils/serialize'\n\nexport { DevtoolsRegistry } from './registry/registry'\nexport { DevtoolsTimeline } from './timeline/timeline'\n\nimport {\n disabledRegisteredNode,\n getDevtoolsBridge,\n isDevtoolsEnabled,\n whenDevtoolsEnabled,\n} from './bridge/bridge'\nimport type {\n DevtoolsSnapshot,\n DevtoolsEvent,\n EmitDevtoolsEventInput,\n RegisterDevtoolsNodeInput,\n RegisteredNode,\n TimelineListener,\n} from './types'\n\nexport const registerDevtoolsNode = (input: RegisterDevtoolsNodeInput): RegisteredNode => {\n if (!isDevtoolsEnabled()) return disabledRegisteredNode()\n return whenDevtoolsEnabled((bridge) => bridge.registry.register(input))!\n}\n\nexport const unregisterDevtoolsNode = (nodeId: string): boolean => {\n if (!isDevtoolsEnabled()) return false\n return whenDevtoolsEnabled((bridge) => bridge.registry.unregister(nodeId)) ?? false\n}\n\nexport const emitDevtoolsEvent = (input: EmitDevtoolsEventInput): DevtoolsEvent | null => {\n if (!isDevtoolsEnabled()) return null\n return whenDevtoolsEnabled((bridge) => bridge.timeline.emit(input))\n}\n\nexport const getNodeSnapshot = (nodeId: string): DevtoolsSnapshot | undefined => {\n if (!isDevtoolsEnabled()) return undefined\n return whenDevtoolsEnabled((bridge) => bridge.registry.getSnapshot(nodeId)) ?? undefined\n}\n\nexport const getAllSnapshots = (): DevtoolsSnapshot[] => {\n if (!isDevtoolsEnabled()) return []\n return whenDevtoolsEnabled((bridge) => bridge.registry.getAllSnapshots()) ?? []\n}\n\nexport const getTimelineEvents = (): readonly DevtoolsEvent[] => {\n if (!isDevtoolsEnabled()) return []\n return whenDevtoolsEnabled((bridge) => bridge.timeline.getEvents()) ?? []\n}\n\nexport const subscribeTimeline = (listener: TimelineListener): (() => void) => {\n if (!isDevtoolsEnabled()) return () => {}\n return whenDevtoolsEnabled((bridge) => bridge.timeline.subscribe(listener)) ?? (() => {})\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@echojs-ecosystem/devtools",
3
+ "version": "0.1.0",
4
+ "description": "Runtime registry and timeline infrastructure for EchoJS DevTools",
5
+ "files": [
6
+ "dist"
7
+ ],
8
+ "type": "module",
9
+ "sideEffects": false,
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ },
15
+ "./package.json": "./package.json"
16
+ },
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "check-types": "tsc -p tsconfig.json --noEmit",
20
+ "typecheck": "tsc -p tsconfig.json --noEmit",
21
+ "lint": "oxlint .",
22
+ "lint:fix": "oxlint . --fix",
23
+ "format": "oxfmt --check .",
24
+ "format:fix": "oxfmt .",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest",
27
+ "test:coverage": "vitest run --coverage"
28
+ },
29
+ "devDependencies": {
30
+ "@echojs-ecosystem/oxc-config": "workspace:*",
31
+ "typescript": "5.9.2",
32
+ "vitest": "^4.1.4"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/echojs-ecosystem/echojs-core.git",
40
+ "directory": "packages/devtools"
41
+ }
42
+ }