iten-devtools 0.4.1 → 0.5.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 CHANGED
@@ -1,5 +1,74 @@
1
1
  # iten-devtools
2
2
 
3
- Reserved package for upcoming developer tools for `iten`.
3
+ Adapter-neutral devtools helpers for `iten`.
4
4
 
5
- Use `iten-core` or `jotai-iten` today.
5
+ This package does not render a UI. It subscribes to any `ItenCore`-like router and keeps a bounded event log that custom panels, tests, browser extensions, or embedded app debug views can render.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install iten-devtools
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```ts
16
+ import { createItenCore, createRoute, defineRoutes, route } from 'iten-core'
17
+ import { createItenDevtools } from 'iten-devtools'
18
+
19
+ const routes = defineRoutes({
20
+ home: route(),
21
+ detail: route<{ id: string }>(),
22
+ })
23
+
24
+ const toRoute = createRoute(routes)
25
+
26
+ const router = createItenCore({
27
+ initial: toRoute({ name: 'home' }),
28
+ })
29
+
30
+ const devtools = createItenDevtools({ router })
31
+
32
+ await router.navigate({ target: toRoute({ name: 'detail', params: { id: '42' } }) })
33
+
34
+ devtools.getSnapshot().events
35
+ ```
36
+
37
+ ## Snapshot Shape
38
+
39
+ ```ts
40
+ type DevtoolsSnapshot = {
41
+ state: RouterState
42
+ events: DevtoolsEvent[]
43
+ }
44
+ ```
45
+
46
+ Each event includes:
47
+
48
+ - `type`: `init`, `route`, `pending`, `error`, `idle`, or `history`
49
+ - `timestamp`
50
+ - `route`
51
+ - `pendingRoute`
52
+ - `error`
53
+ - `canGoBack`
54
+ - `historyLength`
55
+
56
+ ## API
57
+
58
+ ```ts
59
+ const devtools = createItenDevtools({
60
+ router,
61
+ maxEvents: 100,
62
+ now: Date.now,
63
+ })
64
+
65
+ const stop = devtools.subscribe({
66
+ listener: (snapshot) => {
67
+ console.log(snapshot.events)
68
+ },
69
+ })
70
+
71
+ devtools.clear()
72
+ stop()
73
+ devtools.dispose()
74
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,89 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/index.ts
3
+ const DEFAULT_MAX_EVENTS = 100;
4
+ function sameRoute({ left, right }) {
5
+ return JSON.stringify(left) === JSON.stringify(right);
6
+ }
7
+ function classifyEvent({ previous, next }) {
8
+ if (!previous) return "init";
9
+ if (next.error && next.error !== previous.error) return "error";
10
+ if (!sameRoute({
11
+ left: previous.route,
12
+ right: next.route
13
+ })) return "route";
14
+ if (!sameRoute({
15
+ left: previous.pendingRoute,
16
+ right: next.pendingRoute
17
+ })) return "pending";
18
+ if (previous.isNavigating !== next.isNavigating) return next.isNavigating ? "pending" : "idle";
19
+ if (previous.history.length !== next.history.length) return "history";
20
+ return "idle";
21
+ }
22
+ function createItenDevtools({ router, maxEvents = DEFAULT_MAX_EVENTS, now = Date.now }) {
23
+ const listeners = /* @__PURE__ */ new Set();
24
+ const events = [];
25
+ let currentState = router.getState();
26
+ let previousState = null;
27
+ let nextId = 1;
28
+ let disposed = false;
29
+ function emit({ state }) {
30
+ if (disposed) return;
31
+ currentState = state;
32
+ const event = {
33
+ id: nextId,
34
+ type: classifyEvent({
35
+ previous: previousState,
36
+ next: state
37
+ }),
38
+ timestamp: now(),
39
+ route: state.route,
40
+ pendingRoute: state.pendingRoute,
41
+ error: state.error?.error ?? null,
42
+ canGoBack: state.canGoBack,
43
+ historyLength: state.history.length
44
+ };
45
+ nextId += 1;
46
+ previousState = state;
47
+ events.push(event);
48
+ if (events.length > maxEvents) events.splice(0, events.length - maxEvents);
49
+ notify();
50
+ }
51
+ function getSnapshot() {
52
+ return {
53
+ state: currentState,
54
+ events: [...events]
55
+ };
56
+ }
57
+ function notify() {
58
+ const snapshot = getSnapshot();
59
+ for (const listener of listeners) listener(snapshot);
60
+ }
61
+ const stopRouter = router.subscribe({ listener: (state) => {
62
+ emit({ state });
63
+ } });
64
+ emit({ state: currentState });
65
+ function subscribe({ listener }) {
66
+ listeners.add(listener);
67
+ return () => {
68
+ listeners.delete(listener);
69
+ };
70
+ }
71
+ function clear() {
72
+ events.splice(0, events.length);
73
+ notify();
74
+ }
75
+ function dispose() {
76
+ disposed = true;
77
+ stopRouter();
78
+ listeners.clear();
79
+ events.splice(0, events.length);
80
+ }
81
+ return {
82
+ getSnapshot,
83
+ subscribe,
84
+ clear,
85
+ dispose
86
+ };
87
+ }
88
+ //#endregion
89
+ exports.createItenDevtools = createItenDevtools;
@@ -0,0 +1,44 @@
1
+ import { RouteMapDef, RouteUnion, RouterState } from "iten-core";
2
+
3
+ //#region src/index.d.ts
4
+ type DevtoolsRouter<M extends RouteMapDef> = {
5
+ getState: () => RouterState<M>;
6
+ subscribe: (input: {
7
+ listener: (state: RouterState<M>) => void;
8
+ }) => () => void;
9
+ };
10
+ type DevtoolsEventType = "init" | "route" | "pending" | "error" | "idle" | "history";
11
+ type DevtoolsEvent<M extends RouteMapDef> = {
12
+ id: number;
13
+ type: DevtoolsEventType;
14
+ timestamp: number;
15
+ route: RouteUnion<M> | null;
16
+ pendingRoute: RouteUnion<M> | null;
17
+ error: unknown;
18
+ canGoBack: boolean;
19
+ historyLength: number;
20
+ };
21
+ type DevtoolsSnapshot<M extends RouteMapDef> = {
22
+ state: RouterState<M>;
23
+ events: DevtoolsEvent<M>[];
24
+ };
25
+ type CreateDevtoolsInput<M extends RouteMapDef> = {
26
+ router: DevtoolsRouter<M>;
27
+ maxEvents?: number | undefined;
28
+ now?: (() => number) | undefined;
29
+ };
30
+ type ItenDevtools<M extends RouteMapDef> = {
31
+ getSnapshot: () => DevtoolsSnapshot<M>;
32
+ subscribe: (input: {
33
+ listener: (snapshot: DevtoolsSnapshot<M>) => void;
34
+ }) => () => void;
35
+ clear: () => void;
36
+ dispose: () => void;
37
+ };
38
+ declare function createItenDevtools<M extends RouteMapDef>({
39
+ router,
40
+ maxEvents,
41
+ now
42
+ }: CreateDevtoolsInput<M>): ItenDevtools<M>;
43
+ //#endregion
44
+ export { CreateDevtoolsInput, DevtoolsEvent, DevtoolsEventType, DevtoolsRouter, DevtoolsSnapshot, ItenDevtools, createItenDevtools };
@@ -0,0 +1,44 @@
1
+ import { RouteMapDef, RouteUnion, RouterState } from "iten-core";
2
+
3
+ //#region src/index.d.ts
4
+ type DevtoolsRouter<M extends RouteMapDef> = {
5
+ getState: () => RouterState<M>;
6
+ subscribe: (input: {
7
+ listener: (state: RouterState<M>) => void;
8
+ }) => () => void;
9
+ };
10
+ type DevtoolsEventType = "init" | "route" | "pending" | "error" | "idle" | "history";
11
+ type DevtoolsEvent<M extends RouteMapDef> = {
12
+ id: number;
13
+ type: DevtoolsEventType;
14
+ timestamp: number;
15
+ route: RouteUnion<M> | null;
16
+ pendingRoute: RouteUnion<M> | null;
17
+ error: unknown;
18
+ canGoBack: boolean;
19
+ historyLength: number;
20
+ };
21
+ type DevtoolsSnapshot<M extends RouteMapDef> = {
22
+ state: RouterState<M>;
23
+ events: DevtoolsEvent<M>[];
24
+ };
25
+ type CreateDevtoolsInput<M extends RouteMapDef> = {
26
+ router: DevtoolsRouter<M>;
27
+ maxEvents?: number | undefined;
28
+ now?: (() => number) | undefined;
29
+ };
30
+ type ItenDevtools<M extends RouteMapDef> = {
31
+ getSnapshot: () => DevtoolsSnapshot<M>;
32
+ subscribe: (input: {
33
+ listener: (snapshot: DevtoolsSnapshot<M>) => void;
34
+ }) => () => void;
35
+ clear: () => void;
36
+ dispose: () => void;
37
+ };
38
+ declare function createItenDevtools<M extends RouteMapDef>({
39
+ router,
40
+ maxEvents,
41
+ now
42
+ }: CreateDevtoolsInput<M>): ItenDevtools<M>;
43
+ //#endregion
44
+ export { CreateDevtoolsInput, DevtoolsEvent, DevtoolsEventType, DevtoolsRouter, DevtoolsSnapshot, ItenDevtools, createItenDevtools };
package/dist/index.mjs ADDED
@@ -0,0 +1,88 @@
1
+ //#region src/index.ts
2
+ const DEFAULT_MAX_EVENTS = 100;
3
+ function sameRoute({ left, right }) {
4
+ return JSON.stringify(left) === JSON.stringify(right);
5
+ }
6
+ function classifyEvent({ previous, next }) {
7
+ if (!previous) return "init";
8
+ if (next.error && next.error !== previous.error) return "error";
9
+ if (!sameRoute({
10
+ left: previous.route,
11
+ right: next.route
12
+ })) return "route";
13
+ if (!sameRoute({
14
+ left: previous.pendingRoute,
15
+ right: next.pendingRoute
16
+ })) return "pending";
17
+ if (previous.isNavigating !== next.isNavigating) return next.isNavigating ? "pending" : "idle";
18
+ if (previous.history.length !== next.history.length) return "history";
19
+ return "idle";
20
+ }
21
+ function createItenDevtools({ router, maxEvents = DEFAULT_MAX_EVENTS, now = Date.now }) {
22
+ const listeners = /* @__PURE__ */ new Set();
23
+ const events = [];
24
+ let currentState = router.getState();
25
+ let previousState = null;
26
+ let nextId = 1;
27
+ let disposed = false;
28
+ function emit({ state }) {
29
+ if (disposed) return;
30
+ currentState = state;
31
+ const event = {
32
+ id: nextId,
33
+ type: classifyEvent({
34
+ previous: previousState,
35
+ next: state
36
+ }),
37
+ timestamp: now(),
38
+ route: state.route,
39
+ pendingRoute: state.pendingRoute,
40
+ error: state.error?.error ?? null,
41
+ canGoBack: state.canGoBack,
42
+ historyLength: state.history.length
43
+ };
44
+ nextId += 1;
45
+ previousState = state;
46
+ events.push(event);
47
+ if (events.length > maxEvents) events.splice(0, events.length - maxEvents);
48
+ notify();
49
+ }
50
+ function getSnapshot() {
51
+ return {
52
+ state: currentState,
53
+ events: [...events]
54
+ };
55
+ }
56
+ function notify() {
57
+ const snapshot = getSnapshot();
58
+ for (const listener of listeners) listener(snapshot);
59
+ }
60
+ const stopRouter = router.subscribe({ listener: (state) => {
61
+ emit({ state });
62
+ } });
63
+ emit({ state: currentState });
64
+ function subscribe({ listener }) {
65
+ listeners.add(listener);
66
+ return () => {
67
+ listeners.delete(listener);
68
+ };
69
+ }
70
+ function clear() {
71
+ events.splice(0, events.length);
72
+ notify();
73
+ }
74
+ function dispose() {
75
+ disposed = true;
76
+ stopRouter();
77
+ listeners.clear();
78
+ events.splice(0, events.length);
79
+ }
80
+ return {
81
+ getSnapshot,
82
+ subscribe,
83
+ clear,
84
+ dispose
85
+ };
86
+ }
87
+ //#endregion
88
+ export { createItenDevtools };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "iten-devtools",
3
- "version": "0.4.1",
4
- "description": "DevTools for iten — ultralight in-memory router for embedded JS apps. Coming soon.",
3
+ "version": "0.5.0",
4
+ "description": "Devtools helpers for iten — inspect ultralight in-memory router state",
5
5
  "keywords": [
6
6
  "devtools",
7
7
  "router",
@@ -19,13 +19,43 @@
19
19
  "url": "https://github.com/andrew-bierman/iten/issues"
20
20
  },
21
21
  "homepage": "https://github.com/andrew-bierman/iten#readme",
22
- "main": "index.js",
22
+ "type": "module",
23
+ "sideEffects": false,
24
+ "exports": {
25
+ ".": {
26
+ "bun": "./src/index.ts",
27
+ "import": {
28
+ "types": "./dist/index.d.ts",
29
+ "default": "./dist/index.mjs"
30
+ },
31
+ "require": {
32
+ "types": "./dist/index.d.cts",
33
+ "default": "./dist/index.cjs"
34
+ }
35
+ }
36
+ },
37
+ "main": "./dist/index.cjs",
38
+ "module": "./dist/index.mjs",
39
+ "types": "./dist/index.d.ts",
23
40
  "files": [
24
- "index.js",
25
- "README.md"
41
+ "dist",
42
+ "src"
26
43
  ],
44
+ "scripts": {
45
+ "build": "tsdown",
46
+ "typecheck": "tsc --noEmit",
47
+ "typecheck:types": "bun run build && tsc -p tsconfig.type-tests.json",
48
+ "test": "bun test ./test"
49
+ },
50
+ "dependencies": {
51
+ "iten-core": "^0.5.0"
52
+ },
27
53
  "publishConfig": {
28
54
  "access": "public",
29
55
  "registry": "https://registry.npmjs.org/"
56
+ },
57
+ "devDependencies": {
58
+ "tsdown": "0.21.9",
59
+ "typescript": "^5.7.0"
30
60
  }
31
61
  }
package/src/index.ts ADDED
@@ -0,0 +1,152 @@
1
+ import type { RouteMapDef, RouterState, RouteUnion } from 'iten-core'
2
+
3
+ export type DevtoolsRouter<M extends RouteMapDef> = {
4
+ getState: () => RouterState<M>
5
+ subscribe: (input: { listener: (state: RouterState<M>) => void }) => () => void
6
+ }
7
+
8
+ export type DevtoolsEventType = 'init' | 'route' | 'pending' | 'error' | 'idle' | 'history'
9
+
10
+ export type DevtoolsEvent<M extends RouteMapDef> = {
11
+ id: number
12
+ type: DevtoolsEventType
13
+ timestamp: number
14
+ route: RouteUnion<M> | null
15
+ pendingRoute: RouteUnion<M> | null
16
+ error: unknown
17
+ canGoBack: boolean
18
+ historyLength: number
19
+ }
20
+
21
+ export type DevtoolsSnapshot<M extends RouteMapDef> = {
22
+ state: RouterState<M>
23
+ events: DevtoolsEvent<M>[]
24
+ }
25
+
26
+ export type CreateDevtoolsInput<M extends RouteMapDef> = {
27
+ router: DevtoolsRouter<M>
28
+ maxEvents?: number | undefined
29
+ now?: (() => number) | undefined
30
+ }
31
+
32
+ export type ItenDevtools<M extends RouteMapDef> = {
33
+ getSnapshot: () => DevtoolsSnapshot<M>
34
+ subscribe: (input: { listener: (snapshot: DevtoolsSnapshot<M>) => void }) => () => void
35
+ clear: () => void
36
+ dispose: () => void
37
+ }
38
+
39
+ const DEFAULT_MAX_EVENTS = 100
40
+
41
+ function sameRoute<M extends RouteMapDef>({
42
+ left,
43
+ right,
44
+ }: {
45
+ left: RouteUnion<M> | null
46
+ right: RouteUnion<M> | null
47
+ }): boolean {
48
+ return JSON.stringify(left) === JSON.stringify(right)
49
+ }
50
+
51
+ function classifyEvent<M extends RouteMapDef>({
52
+ previous,
53
+ next,
54
+ }: {
55
+ previous: RouterState<M> | null
56
+ next: RouterState<M>
57
+ }): DevtoolsEventType {
58
+ if (!previous) return 'init'
59
+ if (next.error && next.error !== previous.error) return 'error'
60
+ if (!sameRoute({ left: previous.route, right: next.route })) return 'route'
61
+ if (!sameRoute({ left: previous.pendingRoute, right: next.pendingRoute })) return 'pending'
62
+ if (previous.isNavigating !== next.isNavigating) return next.isNavigating ? 'pending' : 'idle'
63
+ if (previous.history.length !== next.history.length) return 'history'
64
+ return 'idle'
65
+ }
66
+
67
+ export function createItenDevtools<M extends RouteMapDef>({
68
+ router,
69
+ maxEvents = DEFAULT_MAX_EVENTS,
70
+ now = Date.now,
71
+ }: CreateDevtoolsInput<M>): ItenDevtools<M> {
72
+ const listeners = new Set<(snapshot: DevtoolsSnapshot<M>) => void>()
73
+ const events: DevtoolsEvent<M>[] = []
74
+ let currentState = router.getState()
75
+ let previousState: RouterState<M> | null = null
76
+ let nextId = 1
77
+ let disposed = false
78
+
79
+ function emit({ state }: { state: RouterState<M> }): void {
80
+ if (disposed) return
81
+ currentState = state
82
+ const event: DevtoolsEvent<M> = {
83
+ id: nextId,
84
+ type: classifyEvent({ previous: previousState, next: state }),
85
+ timestamp: now(),
86
+ route: state.route,
87
+ pendingRoute: state.pendingRoute,
88
+ error: state.error?.error ?? null,
89
+ canGoBack: state.canGoBack,
90
+ historyLength: state.history.length,
91
+ }
92
+ nextId += 1
93
+ previousState = state
94
+ events.push(event)
95
+ if (events.length > maxEvents) {
96
+ events.splice(0, events.length - maxEvents)
97
+ }
98
+ notify()
99
+ }
100
+
101
+ function getSnapshot(): DevtoolsSnapshot<M> {
102
+ return {
103
+ state: currentState,
104
+ events: [...events],
105
+ }
106
+ }
107
+
108
+ function notify(): void {
109
+ const snapshot = getSnapshot()
110
+ for (const listener of listeners) {
111
+ listener(snapshot)
112
+ }
113
+ }
114
+
115
+ const stopRouter = router.subscribe({
116
+ listener: (state) => {
117
+ emit({ state })
118
+ },
119
+ })
120
+
121
+ emit({ state: currentState })
122
+
123
+ function subscribe({
124
+ listener,
125
+ }: {
126
+ listener: (snapshot: DevtoolsSnapshot<M>) => void
127
+ }): () => void {
128
+ listeners.add(listener)
129
+ return () => {
130
+ listeners.delete(listener)
131
+ }
132
+ }
133
+
134
+ function clear(): void {
135
+ events.splice(0, events.length)
136
+ notify()
137
+ }
138
+
139
+ function dispose(): void {
140
+ disposed = true
141
+ stopRouter()
142
+ listeners.clear()
143
+ events.splice(0, events.length)
144
+ }
145
+
146
+ return {
147
+ getSnapshot,
148
+ subscribe,
149
+ clear,
150
+ dispose,
151
+ }
152
+ }
package/index.js DELETED
@@ -1 +0,0 @@
1
- throw new Error('iten-devtools is reserved for upcoming iten developer tools.')