@typeonce/effect-machine-devtools 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/NOTICE +4 -0
  2. package/README.md +13 -13
  3. package/dist/DevToolsProtocol.d.ts +66 -630
  4. package/dist/DevToolsProtocol.d.ts.map +1 -1
  5. package/dist/DevToolsProtocol.js +0 -190
  6. package/dist/DevToolsProtocol.js.map +1 -1
  7. package/dist/MachineDocument.d.ts +38 -18
  8. package/dist/MachineDocument.d.ts.map +1 -1
  9. package/dist/MachineDocument.js +14 -12
  10. package/dist/MachineDocument.js.map +1 -1
  11. package/dist/MachineRegistry.d.ts +22 -2
  12. package/dist/MachineRegistry.d.ts.map +1 -1
  13. package/dist/MachineWalkthrough.d.ts +171 -0
  14. package/dist/MachineWalkthrough.d.ts.map +1 -0
  15. package/dist/MachineWalkthrough.js +98 -0
  16. package/dist/MachineWalkthrough.js.map +1 -0
  17. package/dist/ProjectInspector.d.ts +0 -2
  18. package/dist/ProjectInspector.d.ts.map +1 -1
  19. package/dist/ProjectInspector.js.map +1 -1
  20. package/dist/client/assets/index-BsorPSDS.css +1 -0
  21. package/dist/client/assets/index-eiptehRd.js +37 -0
  22. package/dist/client/index.html +4 -4
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +2 -2
  26. package/dist/index.js.map +1 -1
  27. package/dist/internal/devServer.d.ts.map +1 -1
  28. package/dist/internal/devServer.js +4 -64
  29. package/dist/internal/devServer.js.map +1 -1
  30. package/dist/internal/evaluationWorker.d.ts.map +1 -1
  31. package/dist/internal/evaluationWorker.js +7 -263
  32. package/dist/internal/evaluationWorker.js.map +1 -1
  33. package/dist/internal/machineDocument.d.ts.map +1 -1
  34. package/dist/internal/machineDocument.js +3 -1
  35. package/dist/internal/machineDocument.js.map +1 -1
  36. package/dist/internal/machineWalkthrough.d.ts +26 -0
  37. package/dist/internal/machineWalkthrough.d.ts.map +1 -0
  38. package/dist/internal/machineWalkthrough.js +256 -0
  39. package/dist/internal/machineWalkthrough.js.map +1 -0
  40. package/dist/internal/projectInspector.d.ts.map +1 -1
  41. package/dist/internal/projectInspector.js +1 -11
  42. package/dist/internal/projectInspector.js.map +1 -1
  43. package/index.html +2 -2
  44. package/package.json +8 -7
  45. package/src/DevToolsProtocol.ts +0 -285
  46. package/src/MachineDocument.ts +21 -19
  47. package/src/MachineWalkthrough.ts +199 -0
  48. package/src/ProjectInspector.ts +0 -5
  49. package/src/index.ts +2 -2
  50. package/src/internal/browser/chart-layout-policy.ts +206 -0
  51. package/src/internal/browser/chart-layout.ts +686 -0
  52. package/src/internal/browser/chart-model.ts +244 -0
  53. package/src/internal/browser/chart-renderer.ts +774 -0
  54. package/src/internal/browser/example-machine.ts +15 -6
  55. package/src/internal/browser/input-form.ts +1 -418
  56. package/src/internal/browser/invoke-outcomes-example.ts +231 -0
  57. package/src/internal/browser/parallel-completion-example.ts +199 -0
  58. package/src/internal/browser/planner-example.ts +2 -0
  59. package/src/internal/browser/protocol-events-example.ts +200 -0
  60. package/src/internal/browser/styles.css +974 -342
  61. package/src/internal/browser/transition-semantics-example.ts +245 -0
  62. package/src/internal/browser/visualizer-analysis.ts +52 -0
  63. package/src/internal/browser/visualizer-app.ts +874 -706
  64. package/src/internal/browser/visualizer-model.ts +104 -0
  65. package/src/internal/devServer.ts +4 -82
  66. package/src/internal/evaluationWorker.ts +7 -371
  67. package/src/internal/machineDocument.ts +3 -1
  68. package/src/internal/machineWalkthrough.ts +355 -0
  69. package/src/internal/projectInspector.ts +3 -31
  70. package/dist/MachineSimulator.d.ts +0 -169
  71. package/dist/MachineSimulator.d.ts.map +0 -1
  72. package/dist/MachineSimulator.js +0 -98
  73. package/dist/MachineSimulator.js.map +0 -1
  74. package/dist/client/assets/index-B49Tjawc.js +0 -14
  75. package/dist/client/assets/index-BKH0cOG2.css +0 -1
  76. package/dist/internal/machineSimulator.d.ts +0 -5
  77. package/dist/internal/machineSimulator.d.ts.map +0 -1
  78. package/dist/internal/machineSimulator.js +0 -156
  79. package/dist/internal/machineSimulator.js.map +0 -1
  80. package/src/MachineSimulator.ts +0 -154
  81. package/src/internal/browser/simulation-client.ts +0 -20
  82. package/src/internal/machineSimulator.ts +0 -199
@@ -72,6 +72,110 @@ export const triggerLabel = (transition: VisualizationTransition): string => {
72
72
  }
73
73
  }
74
74
 
75
+ const propertyAccess = (key: string): string => /^[$A-Z_a-z][$\w]*$/.test(key) ? `.${key}` : `[${JSON.stringify(key)}]`
76
+
77
+ const pathAccess = (path: string): string => path.split(".").map(propertyAccess).join("")
78
+
79
+ const nearestCompoundScope = (
80
+ document: VisualizationDocument,
81
+ source: string
82
+ ): string | undefined => {
83
+ const states = new Map(document.states.map((state) => [state.path, state]))
84
+ let current = states.get(source)
85
+ while (current !== undefined) {
86
+ if (current.type === "compound") return current.path
87
+ current = current.parent === null ? undefined : states.get(current.parent)
88
+ }
89
+ return undefined
90
+ }
91
+
92
+ const localPathAccess = (
93
+ document: VisualizationDocument,
94
+ source: string,
95
+ path: string
96
+ ): string | undefined => {
97
+ const scope = nearestCompoundScope(document, source)
98
+ if (scope === undefined) return undefined
99
+ if (path === scope) return ""
100
+ const prefix = `${scope}.`
101
+ return path.startsWith(prefix) ? pathAccess(path.slice(prefix.length)) : undefined
102
+ }
103
+
104
+ /** Canonical Effect Machine selector represented by one retained transition branch. */
105
+ export const branchTargetApi = (
106
+ document: VisualizationDocument,
107
+ source: string,
108
+ branch: VisualizationBranch
109
+ ): string | undefined => {
110
+ const { selection } = branch
111
+ if (selection.kind === "none") return "to.none"
112
+ const path = selection.path
113
+ if (path === null || selection.scope === null) return undefined
114
+
115
+ let api: string | undefined
116
+ if (selection.kind === "history" && selection.scope === "full") {
117
+ api = `to.history${pathAccess(path)}`
118
+ } else if (selection.scope === "local") {
119
+ const local = localPathAccess(document, source, path)
120
+ if (local === undefined) return undefined
121
+ switch (selection.kind) {
122
+ case "state":
123
+ api = local === "" ? "to.local.with" : `to.local${local}()`
124
+ break
125
+ case "choice":
126
+ api = local === "" ? undefined : `to.local${local}()`
127
+ break
128
+ case "initial":
129
+ api = local === "" ? undefined : `to.local${local}.initial`
130
+ break
131
+ case "update":
132
+ api = local === "" ? "to.local.update" : undefined
133
+ break
134
+ case "history":
135
+ break
136
+ }
137
+ } else if (selection.scope === "branch") {
138
+ switch (selection.kind) {
139
+ case "state":
140
+ case "choice":
141
+ api = `to.branch${pathAccess(path)}()`
142
+ break
143
+ case "initial":
144
+ api = `to.branch${pathAccess(path)}.initial`
145
+ break
146
+ case "update":
147
+ api = `to.branch${pathAccess(path)}.update`
148
+ break
149
+ case "history":
150
+ break
151
+ }
152
+ } else if (selection.scope === "full") {
153
+ switch (selection.kind) {
154
+ case "state":
155
+ case "choice":
156
+ api = `to.full${pathAccess(path)}()`
157
+ break
158
+ case "initial":
159
+ api = `to.full${pathAccess(path)}.initial`
160
+ break
161
+ case "history":
162
+ api = `to.history${pathAccess(path)}`
163
+ break
164
+ case "update":
165
+ break
166
+ }
167
+ } else if (selection.scope === "initial") {
168
+ if (selection.kind === "state") api = `to${pathAccess(path)}()`
169
+ if (selection.kind === "initial") api = `to${pathAccess(path)}.initial`
170
+ }
171
+
172
+ if (api === undefined || selection.kind === "update") return api
173
+ return branch.updates.reduce(
174
+ (expression, owner) => `${expression}.updating(to.branch${pathAccess(owner)})`,
175
+ api
176
+ )
177
+ }
178
+
75
179
  const buildInitialPaths = (document: VisualizationDocument): ReadonlySet<string> => {
76
180
  const initial = new Set<string>([document.initial.target])
77
181
  for (const state of document.states) {
@@ -1,13 +1,11 @@
1
1
  import { type ChokidarOptions, type FSWatcher, watch } from "chokidar"
2
2
  import * as Effect from "effect/Effect"
3
- import * as Schema from "effect/Schema"
4
3
  import * as Stream from "effect/Stream"
5
4
  import type { IncomingMessage, ServerResponse } from "node:http"
6
5
  import { isAbsolute, relative, resolve } from "node:path"
7
6
  import { fileURLToPath } from "node:url"
8
7
  import { createServer, type Plugin, type ViteDevServer } from "vite"
9
8
  import type * as DevServer from "../DevServer.js"
10
- import * as DevToolsProtocol from "../DevToolsProtocol.js"
11
9
  import * as MachineRegistry from "../MachineRegistry.js"
12
10
  import * as ProjectInspector from "../ProjectInspector.js"
13
11
 
@@ -51,51 +49,8 @@ const writeJson = (response: ServerResponse, value: unknown, status = 200): void
51
49
  response.end(JSON.stringify(value))
52
50
  }
53
51
 
54
- const readJson = (request: IncomingMessage): Promise<unknown> =>
55
- new Promise((resolveBody, reject) => {
56
- const chunks: Array<Buffer> = []
57
- let size = 0
58
- request.on("data", (chunk: Buffer) => {
59
- size += chunk.byteLength
60
- if (size > 1_000_000) {
61
- reject(new Error("Simulation requests are limited to 1 MB"))
62
- request.destroy()
63
- return
64
- }
65
- chunks.push(chunk)
66
- })
67
- request.on("end", () => {
68
- try {
69
- resolveBody(JSON.parse(Buffer.concat(chunks).toString("utf8")))
70
- } catch (cause) {
71
- reject(cause)
72
- }
73
- })
74
- request.on("error", reject)
75
- })
76
-
77
- const staleSimulation = (
78
- request: DevToolsProtocol.SimulationRequest,
79
- message: string
80
- ): DevToolsProtocol.SimulationFailed => ({
81
- _tag: "SimulationFailed",
82
- protocolVersion: DevToolsProtocol.protocolVersion,
83
- key: request.key,
84
- revision: request.revision,
85
- inputIssues: [],
86
- diagnostics: [{
87
- severity: "error",
88
- code: "simulation-stale",
89
- message,
90
- location: { file: request.source.file, line: null, column: null },
91
- statePath: null
92
- }]
93
- })
94
-
95
52
  const apiPlugin = (
96
- root: string,
97
- registry: MachineRegistry.MachineRegistry["Service"],
98
- inspector: ProjectInspector.ProjectInspector["Service"]
53
+ registry: MachineRegistry.MachineRegistry["Service"]
99
54
  ): Plugin => ({
100
55
  name: "effect-machine-devtools-api",
101
56
  configureServer(server) {
@@ -110,37 +65,6 @@ const apiPlugin = (
110
65
  )
111
66
  return
112
67
  }
113
- if (request.method === "POST" && request.url === "/api/simulations") {
114
- void Effect.runPromise(
115
- Effect.gen(function*() {
116
- const body = yield* Effect.tryPromise({
117
- try: () => readJson(request),
118
- catch: (cause) => cause
119
- })
120
- const simulationRequest = yield* Schema.decodeUnknownEffect(DevToolsProtocol.SimulationRequest)(body)
121
- const snapshot = yield* registry.get
122
- const current = snapshot.results.find((result) => result.key === simulationRequest.key)
123
- if (current === undefined || current._tag === "Failed") {
124
- return staleSimulation(simulationRequest, "The machine is no longer available; restart the simulation")
125
- }
126
- if (
127
- current.document.revision !== simulationRequest.revision ||
128
- current.document.source?.file !== simulationRequest.source.file ||
129
- current.document.source.exportName !== simulationRequest.source.exportName
130
- ) {
131
- return staleSimulation(
132
- simulationRequest,
133
- "The machine changed; restart the simulation from its latest revision"
134
- )
135
- }
136
- return yield* inspector.simulate(simulationRequest, { root })
137
- })
138
- ).then(
139
- (result) => writeJson(response, result),
140
- (cause) => writeJson(response, { message: String(cause) }, 400)
141
- )
142
- return
143
- }
144
68
  if (request.url !== "/api/events") {
145
69
  next()
146
70
  return
@@ -167,8 +91,7 @@ const apiPlugin = (
167
91
  const acquire = (
168
92
  ErrorType: DevServerErrorConstructor,
169
93
  options: DevServer.Options,
170
- registry: MachineRegistry.MachineRegistry["Service"],
171
- inspector: ProjectInspector.ProjectInspector["Service"]
94
+ registry: MachineRegistry.MachineRegistry["Service"]
172
95
  ): Effect.Effect<ViteDevServer, DevServer.DevServerError, never> =>
173
96
  Effect.tryPromise({
174
97
  try: async () => {
@@ -176,7 +99,7 @@ const acquire = (
176
99
  root: packageRoot,
177
100
  appType: "spa",
178
101
  logLevel: "error",
179
- plugins: [apiPlugin(options.root, registry, inspector)],
102
+ plugins: [apiPlugin(registry)],
180
103
  server: {
181
104
  host: options.host,
182
105
  port: options.port,
@@ -239,9 +162,8 @@ export const run = (
239
162
  > =>
240
163
  Effect.gen(function*() {
241
164
  const registry = yield* MachineRegistry.MachineRegistry
242
- const inspector = yield* ProjectInspector.ProjectInspector
243
165
  const server = yield* Effect.acquireRelease(
244
- acquire(ErrorType, options, registry, inspector),
166
+ acquire(ErrorType, options, registry),
245
167
  (server) => Effect.promise(() => server.close())
246
168
  )
247
169
  yield* Effect.acquireRelease(
@@ -2,9 +2,8 @@ import * as NodeWorkerRunner from "@effect/platform-node/NodeWorkerRunner"
2
2
  import { Machine } from "@typeonce/effect-machine"
3
3
  import * as Effect from "effect/Effect"
4
4
  import * as Schema from "effect/Schema"
5
- import * as SchemaIssue from "effect/SchemaIssue"
6
5
  import * as WorkerRunner from "effect/unstable/workers/WorkerRunner"
7
- import { isAbsolute, relative, resolve } from "node:path"
6
+ import { resolve } from "node:path"
8
7
  import { pathToFileURL } from "node:url"
9
8
  import type { ViteDevServer } from "vite"
10
9
  import * as DevToolsProtocol from "../DevToolsProtocol.js"
@@ -23,14 +22,8 @@ interface EvaluationResponse {
23
22
  readonly results: ReadonlyArray<DevToolsProtocol.MachineResult>
24
23
  }
25
24
 
26
- interface SimulationWorkerRequest {
27
- readonly _tag: "Simulate"
28
- readonly root: string
29
- readonly request: DevToolsProtocol.SimulationRequest
30
- }
31
-
32
- type WorkerRequest = EvaluationRequest | SimulationWorkerRequest
33
- type WorkerResponse = EvaluationResponse | DevToolsProtocol.SimulationResult
25
+ type WorkerRequest = EvaluationRequest
26
+ type WorkerResponse = EvaluationResponse
34
27
 
35
28
  const isMachine = (value: unknown): value is Machine.Machine.Any =>
36
29
  typeof value === "object" &&
@@ -68,23 +61,7 @@ const messageOf = (cause: unknown): string => {
68
61
  // Fall back to the runtime string representation below.
69
62
  }
70
63
  const rendered = String(cause)
71
- return rendered.length > 0 ? rendered : "The planner failed without a diagnostic message"
72
- }
73
-
74
- const jsonValue = (value: unknown): Schema.Json => {
75
- const seen = new WeakSet<object>()
76
- const encoded = JSON.stringify(value, (_key, current: unknown) => {
77
- if (typeof current === "bigint") return `${current}n`
78
- if (typeof current === "function") return `[Function ${current.name || "anonymous"}]`
79
- if (typeof current === "symbol") return String(current)
80
- if (typeof current === "undefined") return null
81
- if (typeof current === "object" && current !== null) {
82
- if (seen.has(current)) return "[Circular]"
83
- seen.add(current)
84
- }
85
- return current
86
- })
87
- return encoded === undefined ? null : JSON.parse(encoded) as Schema.Json
64
+ return rendered.length > 0 ? rendered : "The evaluator failed without a diagnostic message"
88
65
  }
89
66
 
90
67
  const failed = (
@@ -168,353 +145,12 @@ const handle = (server: ViteDevServer, request: EvaluationRequest): Effect.Effec
168
145
  Effect.map((results) => ({ _tag: "InspectedMachines" as const, results: results.flat() }))
169
146
  )
170
147
 
171
- interface DynamicMicrostep {
172
- readonly next: unknown
173
- readonly event: unknown
174
- readonly transitions: ReadonlyArray<Machine.Machine.RetainedTransition>
175
- readonly commands: ReadonlyArray<Machine.Command>
176
- readonly raisedEvents: ReadonlyArray<unknown>
177
- readonly emittedEvents: ReadonlyArray<unknown>
178
- readonly exitPaths: ReadonlyArray<string>
179
- readonly entryPaths: ReadonlyArray<string>
180
- readonly changed: boolean
181
- }
182
-
183
- interface DynamicPlan {
184
- readonly startingState?: unknown
185
- readonly state?: unknown
186
- readonly next?: unknown
187
- readonly commands: ReadonlyArray<Machine.Command>
188
- readonly emittedEvents: ReadonlyArray<unknown>
189
- readonly microsteps: ReadonlyArray<DynamicMicrostep>
190
- readonly done: boolean
191
- readonly output: unknown
192
- }
193
-
194
- const planInitial = Machine.planInitial as unknown as (
195
- machine: Machine.Machine.Any,
196
- ...input: ReadonlyArray<unknown>
197
- ) => Effect.Effect<DynamicPlan, unknown>
198
-
199
- const plan = Machine.plan as (
200
- machine: Machine.Machine.Any,
201
- snapshot: unknown,
202
- event: unknown
203
- ) => Effect.Effect<DynamicPlan, unknown>
204
-
205
- const encodeSnapshot = Machine.encodeSnapshot as (
206
- machine: Machine.Machine.Any,
207
- snapshot: unknown
208
- ) => Effect.Effect<DevToolsProtocol.EncodedSnapshot, unknown>
209
-
210
- const decodeSnapshot = Machine.decodeSnapshot as (
211
- machine: Machine.Machine.Any,
212
- snapshot: unknown
213
- ) => Effect.Effect<unknown, unknown>
214
-
215
- const validateSchemaInput = (
216
- schema: Schema.Top,
217
- input: unknown
218
- ): Effect.Effect<void, Schema.SchemaError> =>
219
- schema.makeEffect(input as never, { parseOptions: { errors: "all" } }).pipe(
220
- Effect.asVoid,
221
- Effect.mapError((issue) => new Schema.SchemaError(issue))
222
- )
223
-
224
- const isJsonSchemaRecord = (value: unknown): value is Record<string, unknown> =>
225
- typeof value === "object" && value !== null && !Array.isArray(value)
226
-
227
- const resolveJsonSchemaReference = (
228
- value: unknown,
229
- definitions: Readonly<Record<string, unknown>>
230
- ): Record<string, unknown> | undefined => {
231
- if (!isJsonSchemaRecord(value)) return undefined
232
- if (typeof value.$ref !== "string" || !value.$ref.startsWith("#/$defs/")) return value
233
- const target = definitions[decodeURIComponent(value.$ref.slice("#/$defs/".length))]
234
- return isJsonSchemaRecord(target) ? target : undefined
235
- }
236
-
237
- const inputEventTags = (schema: Machine.Machine.TaggedSchema): ReadonlySet<string> => {
238
- const document = Schema.toJsonSchemaDocument(schema)
239
- const root = resolveJsonSchemaReference(document.schema, document.definitions) ?? document.schema
240
- const variants = isJsonSchemaRecord(root) && Array.isArray(root.anyOf)
241
- ? root.anyOf
242
- : isJsonSchemaRecord(root) && Array.isArray(root.oneOf)
243
- ? root.oneOf
244
- : [root]
245
- const tags = new Set<string>()
246
- for (const variant of variants) {
247
- const resolved = resolveJsonSchemaReference(variant, document.definitions)
248
- if (resolved === undefined || !isJsonSchemaRecord(resolved.properties)) continue
249
- const tag = resolveJsonSchemaReference(resolved.properties._tag, document.definitions)
250
- if (tag === undefined) continue
251
- if (typeof tag.const === "string" || typeof tag.const === "number") tags.add(String(tag.const))
252
- if (Array.isArray(tag.enum)) {
253
- tag.enum.forEach((value) => {
254
- if (typeof value === "string" || typeof value === "number") tags.add(String(value))
255
- })
256
- }
257
- }
258
- return tags
259
- }
260
-
261
- const inputEventSchemas = Machine.inputEventSchemas as (
262
- machine: Machine.Machine.Any
263
- ) => ReadonlyArray<Machine.Machine.TaggedSchema>
264
-
265
- const validateInitialInput = (
266
- machine: Machine.Machine.Any,
267
- request: DevToolsProtocol.StartSimulation
268
- ): Effect.Effect<void, Schema.SchemaError | Error> => {
269
- if (machine.input === undefined) {
270
- return Object.hasOwn(request, "input")
271
- ? Effect.fail(new Error("This machine does not accept startup input"))
272
- : Effect.void
273
- }
274
- return validateSchemaInput(machine.input, request.input)
275
- }
276
-
277
- const validateEventInput = (
278
- machine: Machine.Machine.Any,
279
- event: unknown
280
- ): Effect.Effect<void, Schema.SchemaError | Error> => {
281
- if (typeof event !== "object" || event === null || !("_tag" in event)) {
282
- return Effect.fail(new Error("A public event requires a _tag discriminator"))
283
- }
284
- const tag = String(event._tag)
285
- const schemas = inputEventSchemas(machine)
286
- const schema = schemas.find((schema) => inputEventTags(schema).has(tag))
287
- return schema === undefined
288
- ? Effect.fail(new Error(`This machine does not accept the public event ${tag}`))
289
- : validateSchemaInput(schema, event)
290
- }
291
-
292
- const configuration = Machine.configuration as (
293
- machine: Machine.Machine.Any,
294
- snapshot: unknown
295
- ) => ReadonlyArray<{ readonly path: string }>
296
-
297
- const enabled = Machine.enabled as (
298
- machine: Machine.Machine.Any,
299
- snapshot: unknown
300
- ) => ReadonlyArray<PropertyKey>
301
-
302
- const simulationEvent = (machine: Machine.Machine.Any, value: Schema.Json): unknown => {
303
- if (typeof value !== "object" || value === null || Array.isArray(value) || !("_tag" in value)) return value
304
- const tag = value._tag
305
- if (typeof tag !== "string" && typeof tag !== "number") return value
306
- if (!Object.hasOwn(machine.events, tag)) return value
307
- const constructor = Reflect.get(machine.events, tag)
308
- if (typeof constructor !== "function") return value
309
- const { _tag: _, ...payload } = value
310
- return constructor(payload)
311
- }
312
-
313
- const simulationSnapshot = (
314
- machine: Machine.Machine.Any,
315
- snapshot: unknown
316
- ): DevToolsProtocol.SimulationSnapshot => {
317
- const publicEvents = new Set(Reflect.ownKeys(machine.events).map(String))
318
- return {
319
- activePaths: configuration(machine, snapshot).map((node) => node.path),
320
- candidateEvents: enabled(machine, snapshot).map(String).filter((event) => publicEvents.has(event))
321
- }
322
- }
323
-
324
- const trigger = (value: Machine.Machine.TransitionTrigger): MachineDocument.Trigger => {
325
- switch (value.type) {
326
- case "event":
327
- return { type: "event", event: String(value.event) }
328
- case "always":
329
- return { type: "always" }
330
- case "done":
331
- return { type: "done" }
332
- case "choice":
333
- return { type: "choice" }
334
- case "invoke":
335
- return { type: "invoke", id: value.id, outcome: value.outcome }
336
- }
337
- }
338
-
339
- const commandTarget = (target: unknown): string => {
340
- if (typeof target === "string") return target
341
- if (typeof target === "object" && target !== null && "id" in target) return String(target.id)
342
- return String(target)
343
- }
344
-
345
- const command = (value: Machine.Command): DevToolsProtocol.PlannedCommand =>
346
- value._tag === "SendTo"
347
- ? { _tag: "SendTo", target: commandTarget(value.target), event: jsonValue(value.event) }
348
- : { _tag: "Stop", target: commandTarget(value.child) }
349
-
350
- const microstep = (
351
- machine: Machine.Machine.Any,
352
- value: DynamicMicrostep,
353
- index: number
354
- ): DevToolsProtocol.SimulationMicrostep => ({
355
- index,
356
- event: jsonValue(value.event),
357
- transitions: value.transitions.map((transition) => ({
358
- source: transition.source,
359
- trigger: trigger(transition.trigger),
360
- reenter: transition.reenter,
361
- branchIndex: transition.branchIndex,
362
- branchKey: transition.branchKey ?? null,
363
- target: transition.target ?? null,
364
- resolvedTarget: transition.resolvedTarget ?? null,
365
- updates: [...transition.updates]
366
- })),
367
- commands: value.commands.map(command),
368
- raisedEvents: value.raisedEvents.map(jsonValue),
369
- emittedEvents: value.emittedEvents.map(jsonValue),
370
- exitPaths: [...value.exitPaths],
371
- entryPaths: [...value.entryPaths],
372
- activePaths: configuration(machine, value.next).map((node) => node.path),
373
- changed: value.changed
374
- })
375
-
376
- const simulationDiagnostic = (
377
- request: DevToolsProtocol.SimulationRequest,
378
- code: string,
379
- cause: unknown
380
- ): DevToolsProtocol.SimulationFailed => ({
381
- _tag: "SimulationFailed",
382
- protocolVersion: DevToolsProtocol.protocolVersion,
383
- key: request.key,
384
- revision: request.revision,
385
- inputIssues: inputIssuesOf(cause),
386
- diagnostics: [diagnostic(request.source.file, code, messageOf(cause))]
387
- })
388
-
389
- const inputIssuesOf = (cause: unknown): ReadonlyArray<DevToolsProtocol.InputIssue> => {
390
- const schemaError = Schema.isSchemaError(cause)
391
- ? cause
392
- : typeof cause === "object" && cause !== null && "cause" in cause && Schema.isSchemaError(cause.cause)
393
- ? cause.cause
394
- : undefined
395
- if (schemaError === undefined) return []
396
- return SchemaIssue.makeFormatterStandardSchemaV1()(schemaError.issue).issues.map((issue) => ({
397
- path: issue.path?.map((part) => typeof part === "number" ? part : String(part)) ?? [],
398
- message: issue.message
399
- }))
400
- }
401
-
402
- const isProjectFile = (root: string, file: string): boolean => {
403
- const absoluteRoot = resolve(root)
404
- const absoluteFile = resolve(absoluteRoot, file)
405
- const projectPath = relative(absoluteRoot, absoluteFile)
406
- return projectPath !== "" && !projectPath.startsWith("..") && !isAbsolute(projectPath)
407
- }
408
-
409
- const loadSimulationMachine = (
410
- server: ViteDevServer,
411
- workerRequest: SimulationWorkerRequest
412
- ): Effect.Effect<Machine.Machine.Any, unknown> => {
413
- const request = workerRequest.request
414
- if (!isProjectFile(workerRequest.root, request.source.file)) {
415
- return Effect.fail(new Error("The requested machine source is outside the project root"))
416
- }
417
- if (request.source.exportName === null) {
418
- return Effect.fail(new Error("The requested machine does not have an exported module binding"))
419
- }
420
- return Effect.tryPromise({
421
- try: () => server.ssrLoadModule(pathToFileURL(resolve(workerRequest.root, request.source.file)).href),
422
- catch: (cause) => cause
423
- }).pipe(
424
- Effect.flatMap((module) => {
425
- const candidate = module[request.source.exportName!]
426
- return isMachine(candidate)
427
- ? Effect.succeed(candidate)
428
- : Effect.fail(new Error(`Export ${request.source.exportName} is not an Effect Machine`))
429
- })
430
- )
431
- }
432
-
433
- const makeSimulationReady = (
434
- request: DevToolsProtocol.SimulationRequest,
435
- machine: Machine.Machine.Any,
436
- before: unknown,
437
- after: unknown,
438
- planResult: DynamicPlan
439
- ): Effect.Effect<DevToolsProtocol.SimulationReady, unknown> =>
440
- Effect.map(encodeSnapshot(machine, after), (snapshot) => {
441
- const step = request._tag === "StartSimulation" ? 0 : request.step + 1
442
- const frame: DevToolsProtocol.SimulationFrame = {
443
- step,
444
- trigger: request._tag === "StartSimulation"
445
- ? {
446
- _tag: "Initial",
447
- ...(Object.hasOwn(request, "input") ? { input: request.input } : {})
448
- }
449
- : { _tag: "Event", event: request.event },
450
- before: simulationSnapshot(machine, before),
451
- after: simulationSnapshot(machine, after),
452
- microsteps: planResult.microsteps.map((value, index) => microstep(machine, value, index)),
453
- commands: planResult.commands.map(command),
454
- emittedEvents: planResult.emittedEvents.map(jsonValue),
455
- done: planResult.done,
456
- ...(planResult.done && planResult.output !== undefined ? { output: jsonValue(planResult.output) } : {})
457
- }
458
- return {
459
- _tag: "SimulationReady",
460
- protocolVersion: DevToolsProtocol.protocolVersion,
461
- key: request.key,
462
- revision: request.revision,
463
- step,
464
- snapshot,
465
- current: frame.after,
466
- frame
467
- }
468
- })
469
-
470
- const simulate = (
471
- server: ViteDevServer,
472
- workerRequest: SimulationWorkerRequest
473
- ): Effect.Effect<DevToolsProtocol.SimulationResult> => {
474
- const request = workerRequest.request
475
- return loadSimulationMachine(server, workerRequest).pipe(
476
- Effect.flatMap((machine) => {
477
- if (request._tag === "StartSimulation") {
478
- return Effect.flatMap(validateInitialInput(machine, request), () => {
479
- const planned = Object.hasOwn(request, "input")
480
- ? planInitial(machine, request.input)
481
- : planInitial(machine)
482
- return Effect.flatMap(planned, (result) => {
483
- const before = result.startingState ?? result.state
484
- const after = result.state
485
- if (before === undefined || after === undefined) {
486
- return Effect.fail(new Error("The initial planner did not return a state snapshot"))
487
- }
488
- return makeSimulationReady(request, machine, before, after, result)
489
- })
490
- })
491
- }
492
- return Effect.flatMap(validateEventInput(machine, request.event), () =>
493
- Effect.flatMap(
494
- decodeSnapshot(machine, request.snapshot),
495
- (before) =>
496
- Effect.flatMap(plan(machine, before, simulationEvent(machine, request.event)), (result) => {
497
- if (result.next === undefined) return Effect.fail(new Error("The planner did not return a next snapshot"))
498
- return makeSimulationReady(request, machine, before, result.next, result)
499
- })
500
- ))
501
- }),
502
- Effect.catch((cause) => Effect.succeed(simulationDiagnostic(request, "simulation-planning-failed", cause)))
503
- )
504
- }
505
-
506
- export const run = (server: ViteDevServer): Promise<void> => {
507
- return Effect.gen(function*() {
148
+ export const run = (server: ViteDevServer): Promise<void> =>
149
+ Effect.gen(function*() {
508
150
  const platform = yield* WorkerRunner.WorkerRunnerPlatform
509
151
  const runner = yield* platform.start<WorkerResponse, WorkerRequest>()
510
- yield* runner.run((_portId, request) => {
511
- const response: Effect.Effect<WorkerResponse> = request._tag === "InspectMachines"
512
- ? handle(server, request)
513
- : simulate(server, request)
514
- return Effect.flatMap(response, (value) => runner.send(0, value))
515
- })
152
+ yield* runner.run((_portId, request) => Effect.flatMap(handle(server, request), (value) => runner.send(0, value)))
516
153
  }).pipe(
517
154
  Effect.provide(NodeWorkerRunner.layer),
518
155
  Effect.runPromise
519
156
  )
520
- }
@@ -169,7 +169,7 @@ export const make = <M extends Machine.Machine.Any>(
169
169
 
170
170
  const initial = Machine.initialDefinition(machine)
171
171
  return {
172
- schemaVersion: 2,
172
+ schemaVersion: 3,
173
173
  revision: options.revision ?? 0,
174
174
  source: options.source ?? null,
175
175
  machineId: machine.id ?? "Machine",
@@ -190,6 +190,8 @@ export const make = <M extends Machine.Machine.Any>(
190
190
  parent: node.parent ?? null,
191
191
  children: [...childPaths.get(node.path) ?? []],
192
192
  initial: node.initial ?? null,
193
+ valueSchema: node.schema === undefined ? null : inputSchema(node.schema),
194
+ outputSchema: node.output === undefined ? null : inputSchema(node.output),
193
195
  transitionIds: [...transitionIds.get(node.path) ?? []],
194
196
  activityIds: [...activityIds.get(node.path) ?? []]
195
197
  })),