@statorjs/stator 2.6.0 → 2.8.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.
@@ -52,6 +52,8 @@ export {
52
52
  export { scheduleSessionEffects, wireAppEffects } from './effects.ts'
53
53
  export type { HttpConfig } from './http.ts'
54
54
  export { buildHonoApp } from './http.ts'
55
+ export type { InspectPayload, InspectRoute, InspectRouteMethod } from './inspect.ts'
56
+ export { buildInspectPayload } from './inspect.ts'
55
57
  export type { InstanceHandle } from './instance-proxy.ts'
56
58
  export { createInstanceProxy, defForProxy } from './instance-proxy.ts'
57
59
  export { logger, scopedLogger, setLogLevel } from './logger.ts'
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Dev state inspection — the data feed for the inspector's Machines view.
3
+ *
4
+ * Serves the caller's OWN session: the payload is scoped to the session the
5
+ * request's cookie addresses (app-lifecycle machines are process-global and
6
+ * labeled as such by their lifecycle). Registered only by the dev servers —
7
+ * machine context is working state and may hold anything, so this endpoint
8
+ * never exists in production.
9
+ *
10
+ * Read-only by construction: no actors are instantiated, no session lock is
11
+ * taken, nothing is dispatched. Session snapshots come straight from the
12
+ * persistence store — a machine never touched by this session reads as null
13
+ * (it would start from the def's initial context on first dispatch).
14
+ */
15
+
16
+ import { describeMachine, type MachineDescription } from '../engine/describe.ts'
17
+ import type { Snapshot } from '../engine/types.ts'
18
+ import { codeHashOf } from './machine-hash.ts'
19
+ import type { MachineStore } from './machine-store.ts'
20
+ import { type DiscoveredRoute, HTTP_METHODS } from './route-discovery.ts'
21
+ import { isStatorQueryRoute, isStatorRoute } from './routing.ts'
22
+
23
+ export interface InspectRouteMethod {
24
+ /** `page` = defineRoute, `data` = defineApiRoute({method:'GET'}), `api` = the rest. */
25
+ kind: 'page' | 'data' | 'api'
26
+ reads: string[]
27
+ /** Page routes only: whether the route opened an SSE live channel. */
28
+ live?: boolean
29
+ }
30
+
31
+ export interface InspectRoute {
32
+ urlPath: string
33
+ methods: Record<string, InspectRouteMethod>
34
+ }
35
+
36
+ export interface InspectPayload {
37
+ buildId?: string
38
+ /** All registered machines, described + their running code hash. */
39
+ machines: Array<MachineDescription & { hash?: string }>
40
+ /** The caller's session's persisted snapshots (null = never touched). */
41
+ session: Record<string, Snapshot<unknown> | null>
42
+ /** App-lifecycle live snapshots — process-global, same for every caller. */
43
+ app: Record<string, Snapshot<unknown>>
44
+ routes: InspectRoute[]
45
+ }
46
+
47
+ export async function buildInspectPayload(opts: {
48
+ store: MachineStore
49
+ routes: DiscoveredRoute[]
50
+ sessionId?: string
51
+ buildId?: string
52
+ }): Promise<InspectPayload> {
53
+ const defs = [...opts.store.allDefs()].sort((a, b) => a.name.localeCompare(b.name))
54
+
55
+ const machines = defs.map((def) => {
56
+ const hash = codeHashOf(def)
57
+ return { ...describeMachine(def), ...(hash !== undefined ? { hash } : {}) }
58
+ })
59
+
60
+ const session: Record<string, Snapshot<unknown> | null> = {}
61
+ if (opts.sessionId !== undefined) {
62
+ for (const def of defs) {
63
+ if (def.lifecycle !== 'session') continue
64
+ session[def.name] =
65
+ ((await opts.store.persistence.get(
66
+ opts.sessionId,
67
+ def.name,
68
+ )) as Snapshot<unknown> | null) ?? null
69
+ }
70
+ }
71
+
72
+ const app: Record<string, Snapshot<unknown>> = {}
73
+ for (const def of defs) {
74
+ if (def.lifecycle !== 'app') continue
75
+ const handle = opts.store.appInstance(def.name)
76
+ if (handle) app[def.name] = handle.actor.getSnapshot()
77
+ }
78
+
79
+ const routes = opts.routes.map((route): InspectRoute => {
80
+ const methods: Record<string, InspectRouteMethod> = {}
81
+ for (const method of HTTP_METHODS) {
82
+ const def = route[method]
83
+ if (!def) continue
84
+ const reads = def.reads.map((d) => d.name)
85
+ if (isStatorRoute(def)) {
86
+ methods[method] = { kind: 'page', reads, live: def.live }
87
+ } else {
88
+ methods[method] = { kind: isStatorQueryRoute(def) ? 'data' : 'api', reads }
89
+ }
90
+ }
91
+ return { urlPath: route.urlPath, methods }
92
+ })
93
+
94
+ return {
95
+ ...(opts.buildId !== undefined ? { buildId: opts.buildId } : {}),
96
+ machines,
97
+ session,
98
+ app,
99
+ routes,
100
+ }
101
+ }
@@ -41,10 +41,12 @@ export interface DiscoveredRoute {
41
41
  * Walk the routes directory recursively and build URL patterns from file
42
42
  * paths. Conventions:
43
43
  *
44
- * - `routes/foo.ts` → `/foo`
45
- * - `routes/foo/index.ts` → `/foo`
46
- * - `routes/foo/[id].ts` → `/foo/:id` (path param `id`)
47
- * - `routes/[a]/[b].ts` → `/:a/:b`
44
+ * - `routes/foo.ts` → `/foo`
45
+ * - `routes/foo/index.ts` → `/foo`
46
+ * - `routes/foo/[id].ts` → `/foo/:id` (path param `id`)
47
+ * - `routes/[a]/[b].ts` → `/:a/:b`
48
+ * - `routes/foo/[...rest].ts` → `/foo/*rest` (catch-all: terminal, matches
49
+ * zero+ segments, param is the raw remainder incl. slashes)
48
50
  *
49
51
  * Files may export any combination of `GET`/`POST`/`PUT`/`PATCH`/`DELETE`.
50
52
  * GET is a `defineRoute` (page renderer) or a `defineApiRoute` declaring