@typeonce/effect-machine-devtools 0.23.0 → 0.25.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 (80) hide show
  1. package/NOTICE +4 -0
  2. package/README.md +14 -8
  3. package/dist/DevServer.d.ts +2 -1
  4. package/dist/DevServer.d.ts.map +1 -1
  5. package/dist/DevServer.js.map +1 -1
  6. package/dist/DevToolsProtocol.d.ts +167 -17
  7. package/dist/DevToolsProtocol.d.ts.map +1 -1
  8. package/dist/DevToolsProtocol.js +1 -1
  9. package/dist/MachineDocument.d.ts +98 -2
  10. package/dist/MachineDocument.d.ts.map +1 -1
  11. package/dist/MachineDocument.js +35 -1
  12. package/dist/MachineDocument.js.map +1 -1
  13. package/dist/MachineRegistry.d.ts +56 -6
  14. package/dist/MachineRegistry.d.ts.map +1 -1
  15. package/dist/MachineWalkthrough.d.ts +171 -0
  16. package/dist/MachineWalkthrough.d.ts.map +1 -0
  17. package/dist/MachineWalkthrough.js +98 -0
  18. package/dist/MachineWalkthrough.js.map +1 -0
  19. package/dist/client/assets/index-BTOAhezX.js +37 -0
  20. package/dist/client/assets/index-Cg7xzSkz.css +1 -0
  21. package/dist/client/index.html +4 -4
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +2 -2
  25. package/dist/index.js.map +1 -1
  26. package/dist/internal/devServer.d.ts +2 -1
  27. package/dist/internal/devServer.d.ts.map +1 -1
  28. package/dist/internal/devServer.js +4 -3
  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 +28 -9
  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 +65 -2
  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 +15 -9
  42. package/dist/internal/projectInspector.js.map +1 -1
  43. package/index.html +2 -2
  44. package/package.json +4 -3
  45. package/src/DevServer.ts +6 -2
  46. package/src/DevToolsProtocol.ts +1 -1
  47. package/src/MachineDocument.ts +56 -1
  48. package/src/MachineWalkthrough.ts +199 -0
  49. package/src/index.ts +2 -2
  50. package/src/internal/browser/chart-layout.ts +433 -0
  51. package/src/internal/browser/chart-model.ts +234 -0
  52. package/src/internal/browser/chart-renderer.ts +661 -0
  53. package/src/internal/browser/example-machine.ts +15 -6
  54. package/src/internal/browser/input-form.ts +252 -0
  55. package/src/internal/browser/invoke-outcomes-example.ts +231 -0
  56. package/src/internal/browser/parallel-completion-example.ts +199 -0
  57. package/src/internal/browser/planner-example.ts +145 -0
  58. package/src/internal/browser/protocol-events-example.ts +200 -0
  59. package/src/internal/browser/styles.css +959 -185
  60. package/src/internal/browser/transition-semantics-example.ts +236 -0
  61. package/src/internal/browser/visualizer-app.ts +818 -389
  62. package/src/internal/browser/visualizer-model.ts +104 -0
  63. package/src/internal/browser/visualizer.ts +1 -1
  64. package/src/internal/devServer.ts +9 -4
  65. package/src/internal/evaluationWorker.ts +28 -10
  66. package/src/internal/machineDocument.ts +77 -2
  67. package/src/internal/machineWalkthrough.ts +355 -0
  68. package/src/internal/projectInspector.ts +29 -14
  69. package/dist/MachineSimulator.d.ts +0 -169
  70. package/dist/MachineSimulator.d.ts.map +0 -1
  71. package/dist/MachineSimulator.js +0 -98
  72. package/dist/MachineSimulator.js.map +0 -1
  73. package/dist/client/assets/index-BGOhE3Ng.css +0 -1
  74. package/dist/client/assets/index-DiqGhsGR.js +0 -14
  75. package/dist/internal/machineSimulator.d.ts +0 -5
  76. package/dist/internal/machineSimulator.d.ts.map +0 -1
  77. package/dist/internal/machineSimulator.js +0 -156
  78. package/dist/internal/machineSimulator.js.map +0 -1
  79. package/src/MachineSimulator.ts +0 -154
  80. package/src/internal/machineSimulator.ts +0 -199
@@ -0,0 +1,252 @@
1
+ import type { InputSchema } from "../../MachineDocument.js"
2
+
3
+ type JsonPrimitive = string | number | boolean | null
4
+
5
+ export type InputField =
6
+ | {
7
+ readonly _tag: "String"
8
+ readonly title: string | undefined
9
+ readonly description: string | undefined
10
+ readonly defaultValue: string | undefined
11
+ readonly format: string | undefined
12
+ readonly minLength: number | undefined
13
+ readonly maxLength: number | undefined
14
+ readonly pattern: string | undefined
15
+ }
16
+ | {
17
+ readonly _tag: "Number"
18
+ readonly title: string | undefined
19
+ readonly description: string | undefined
20
+ readonly defaultValue: number | undefined
21
+ readonly integer: boolean
22
+ readonly minimum: number | undefined
23
+ readonly maximum: number | undefined
24
+ }
25
+ | {
26
+ readonly _tag: "Boolean"
27
+ readonly title: string | undefined
28
+ readonly description: string | undefined
29
+ readonly defaultValue: boolean | undefined
30
+ }
31
+ | {
32
+ readonly _tag: "Enum"
33
+ readonly title: string | undefined
34
+ readonly description: string | undefined
35
+ readonly values: ReadonlyArray<JsonPrimitive>
36
+ readonly defaultValue: JsonPrimitive | undefined
37
+ }
38
+ | {
39
+ readonly _tag: "Literal"
40
+ readonly title: string | undefined
41
+ readonly description: string | undefined
42
+ readonly value: JsonPrimitive
43
+ }
44
+ | {
45
+ readonly _tag: "Object"
46
+ readonly title: string | undefined
47
+ readonly description: string | undefined
48
+ readonly fields: ReadonlyArray<{
49
+ readonly key: string
50
+ readonly required: boolean
51
+ readonly field: InputField
52
+ }>
53
+ }
54
+ | {
55
+ readonly _tag: "Array"
56
+ readonly title: string | undefined
57
+ readonly description: string | undefined
58
+ readonly item: InputField
59
+ readonly minItems: number
60
+ readonly maxItems: number | undefined
61
+ }
62
+ | {
63
+ readonly _tag: "Union"
64
+ readonly title: string | undefined
65
+ readonly description: string | undefined
66
+ readonly alternatives: ReadonlyArray<InputField>
67
+ }
68
+ | {
69
+ readonly _tag: "Unsupported"
70
+ readonly title: string | undefined
71
+ readonly description: string | undefined
72
+ readonly reason: string
73
+ }
74
+
75
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
76
+ typeof value === "object" && value !== null && !Array.isArray(value)
77
+
78
+ const stringValue = (value: unknown): string | undefined => typeof value === "string" ? value : undefined
79
+
80
+ const numberValue = (value: unknown): number | undefined =>
81
+ typeof value === "number" && Number.isFinite(value) ? value : undefined
82
+
83
+ const primitiveValue = (value: unknown): JsonPrimitive | undefined =>
84
+ value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean"
85
+ ? value
86
+ : undefined
87
+
88
+ const resolveReference = (
89
+ value: unknown,
90
+ definitions: Readonly<Record<string, unknown>>,
91
+ seen: ReadonlySet<string> = new Set()
92
+ ): unknown => {
93
+ if (!isRecord(value) || typeof value.$ref !== "string" || !value.$ref.startsWith("#/$defs/")) return value
94
+ const name = decodeURIComponent(value.$ref.slice("#/$defs/".length))
95
+ if (seen.has(name)) return undefined
96
+ const next = definitions[name]
97
+ return resolveReference(next, definitions, new Set([...seen, name]))
98
+ }
99
+
100
+ const annotations = (schema: Record<string, unknown>) => ({
101
+ title: stringValue(schema.title),
102
+ description: stringValue(schema.description)
103
+ })
104
+
105
+ const mergeAllOf = (
106
+ schema: Record<string, unknown>,
107
+ definitions: Readonly<Record<string, unknown>>
108
+ ): Record<string, unknown> => {
109
+ if (!Array.isArray(schema.allOf)) return schema
110
+ const { allOf, ...base } = schema
111
+ return Object.assign(
112
+ base,
113
+ ...allOf
114
+ .map((part) => resolveReference(part, definitions))
115
+ .filter(isRecord)
116
+ .map((part) => mergeAllOf(part, definitions))
117
+ )
118
+ }
119
+
120
+ const effectNumberAlternative = (
121
+ alternatives: ReadonlyArray<unknown>,
122
+ definitions: Readonly<Record<string, unknown>>
123
+ ): Record<string, unknown> | undefined => {
124
+ if (alternatives.length !== 2) return undefined
125
+ const resolved = alternatives.map((alternative) => resolveReference(alternative, definitions))
126
+ const number = resolved.find((alternative) => isRecord(alternative) && alternative.type === "number")
127
+ const encodedNonFinite = resolved.find((alternative) => {
128
+ if (!isRecord(alternative) || alternative.type !== "string" || !Array.isArray(alternative.enum)) return false
129
+ const enumValues: ReadonlyArray<unknown> = alternative.enum
130
+ return enumValues.length === 3 &&
131
+ ["Infinity", "-Infinity", "NaN"].every((value) => enumValues.includes(value))
132
+ })
133
+ return isRecord(number) && encodedNonFinite !== undefined ? number : undefined
134
+ }
135
+
136
+ const project = (
137
+ value: unknown,
138
+ definitions: Readonly<Record<string, unknown>>
139
+ ): InputField => {
140
+ const referenced = resolveReference(value, definitions)
141
+ if (!isRecord(referenced)) {
142
+ return {
143
+ _tag: "Unsupported",
144
+ title: undefined,
145
+ description: undefined,
146
+ reason: "This input schema cannot be represented as fields."
147
+ }
148
+ }
149
+ const resolved = mergeAllOf(referenced, definitions)
150
+ const common = annotations(resolved)
151
+ const alternatives = Array.isArray(resolved.oneOf)
152
+ ? resolved.oneOf
153
+ : Array.isArray(resolved.anyOf)
154
+ ? resolved.anyOf
155
+ : undefined
156
+ if (alternatives !== undefined) {
157
+ const effectNumber = effectNumberAlternative(alternatives, definitions)
158
+ if (effectNumber !== undefined) {
159
+ return {
160
+ _tag: "Number",
161
+ ...common,
162
+ defaultValue: numberValue(resolved.default ?? effectNumber.default),
163
+ integer: false,
164
+ minimum: numberValue(resolved.minimum ?? effectNumber.minimum),
165
+ maximum: numberValue(resolved.maximum ?? effectNumber.maximum)
166
+ }
167
+ }
168
+ return {
169
+ _tag: "Union",
170
+ ...common,
171
+ alternatives: alternatives.map((alternative) => project(alternative, definitions))
172
+ }
173
+ }
174
+ const constant = primitiveValue(resolved.const)
175
+ if (constant !== undefined || resolved.const === null) {
176
+ return { _tag: "Literal", ...common, value: constant ?? null }
177
+ }
178
+ if (Array.isArray(resolved.enum)) {
179
+ const values = resolved.enum.map(primitiveValue).filter((item): item is JsonPrimitive => item !== undefined)
180
+ if (values.length > 0) {
181
+ return {
182
+ _tag: "Enum",
183
+ ...common,
184
+ values,
185
+ defaultValue: primitiveValue(resolved.default)
186
+ }
187
+ }
188
+ }
189
+ switch (resolved.type) {
190
+ case "string":
191
+ return {
192
+ _tag: "String",
193
+ ...common,
194
+ defaultValue: stringValue(resolved.default),
195
+ format: stringValue(resolved.format),
196
+ minLength: numberValue(resolved.minLength),
197
+ maxLength: numberValue(resolved.maxLength),
198
+ pattern: stringValue(resolved.pattern)
199
+ }
200
+ case "integer":
201
+ case "number":
202
+ return {
203
+ _tag: "Number",
204
+ ...common,
205
+ defaultValue: numberValue(resolved.default),
206
+ integer: resolved.type === "integer",
207
+ minimum: numberValue(resolved.minimum),
208
+ maximum: numberValue(resolved.maximum)
209
+ }
210
+ case "boolean":
211
+ return {
212
+ _tag: "Boolean",
213
+ ...common,
214
+ defaultValue: typeof resolved.default === "boolean" ? resolved.default : undefined
215
+ }
216
+ case "null":
217
+ return { _tag: "Literal", ...common, value: null }
218
+ case "object": {
219
+ if (!isRecord(resolved.properties)) return { _tag: "Object", ...common, fields: [] }
220
+ const required = new Set(
221
+ Array.isArray(resolved.required)
222
+ ? resolved.required.filter((item): item is string => typeof item === "string")
223
+ : []
224
+ )
225
+ return {
226
+ _tag: "Object",
227
+ ...common,
228
+ fields: Object.entries(resolved.properties).map(([key, child]) => ({
229
+ key,
230
+ required: required.has(key),
231
+ field: project(child, definitions)
232
+ }))
233
+ }
234
+ }
235
+ case "array":
236
+ return {
237
+ _tag: "Array",
238
+ ...common,
239
+ item: project(resolved.items, definitions),
240
+ minItems: numberValue(resolved.minItems) ?? 0,
241
+ maxItems: numberValue(resolved.maxItems)
242
+ }
243
+ default:
244
+ return {
245
+ _tag: "Unsupported",
246
+ ...common,
247
+ reason: "This input schema has no concrete JSON shape to render."
248
+ }
249
+ }
250
+ }
251
+
252
+ export const projectInputSchema = (document: InputSchema): InputField => project(document.schema, document.definitions)
@@ -0,0 +1,231 @@
1
+ import { Machine } from "@typeonce/effect-machine"
2
+ import { Effect, Schema, Stream } from "effect"
3
+
4
+ class ChildWorking extends Schema.TaggedClass<ChildWorking>("InvokeGalleryChildWorking")("Working", {
5
+ task: Schema.String
6
+ }) {}
7
+ class ChildDone extends Schema.TaggedClass<ChildDone>("InvokeGalleryChildDone")("Done", {
8
+ result: Schema.String
9
+ }) {}
10
+ class FinishChild extends Schema.TaggedClass<FinishChild>("InvokeGalleryFinishChild")("FinishChild", {}) {}
11
+
12
+ const ChildStates = Machine.states({
13
+ Working: ChildWorking,
14
+ Done: { schema: ChildDone, type: "final", output: Schema.String }
15
+ })
16
+
17
+ export const invokeGalleryChildMachine = Machine.make({
18
+ id: "invoke-gallery-child",
19
+ states: ChildStates.states,
20
+ events: Machine.events(FinishChild),
21
+ initial: (to) => to.Working().resolve(({ target }) => target.decoded(new ChildWorking({ task: "render-preview" })))
22
+ }).handle({
23
+ Working: {
24
+ on: {
25
+ FinishChild: (to) =>
26
+ to.full.Done().resolve(({ state, target }) =>
27
+ target.decoded(new ChildDone({ result: `${state.task}:complete` }))
28
+ )
29
+ }
30
+ },
31
+ Done: {
32
+ output: ({ state }) => state.result
33
+ }
34
+ })
35
+
36
+ const InvokedChild = Machine.child("preview-worker", invokeGalleryChildMachine)
37
+
38
+ class Gallery extends Schema.TaggedClass<Gallery>("InvokeGallery")("Gallery", {
39
+ selectedDemo: Schema.NullOr(Schema.String)
40
+ }) {}
41
+ class Choose extends Schema.TaggedClass<Choose>("InvokeGalleryChoose")("Choose", {}) {}
42
+ class LoadingDocument extends Schema.TaggedClass<LoadingDocument>("InvokeGalleryLoadingDocument")(
43
+ "LoadingDocument",
44
+ { request: Schema.String }
45
+ ) {}
46
+ class StreamingUpdates extends Schema.TaggedClass<StreamingUpdates>("InvokeGalleryStreamingUpdates")(
47
+ "StreamingUpdates",
48
+ { values: Schema.Array(Schema.Number) }
49
+ ) {}
50
+ class WaitingForTimeout extends Schema.TaggedClass<WaitingForTimeout>("InvokeGalleryWaitingForTimeout")(
51
+ "WaitingForTimeout",
52
+ { delay: Schema.Literal("2 seconds") }
53
+ ) {}
54
+ class WatchingProcess extends Schema.TaggedClass<WatchingProcess>("InvokeGalleryWatchingProcess")(
55
+ "WatchingProcess",
56
+ { revision: Schema.Number }
57
+ ) {}
58
+ class RunningChild extends Schema.TaggedClass<RunningChild>("InvokeGalleryRunningChild")("RunningChild", {}) {}
59
+ class Completed extends Schema.TaggedClass<Completed>("InvokeGalleryCompleted")("Completed", {
60
+ source: Schema.String,
61
+ result: Schema.String
62
+ }) {}
63
+ class Failed extends Schema.TaggedClass<Failed>("InvokeGalleryFailed")("Failed", {
64
+ source: Schema.String,
65
+ message: Schema.String
66
+ }) {}
67
+
68
+ class RunEffect extends Schema.TaggedClass<RunEffect>("InvokeGalleryRunEffect")("RunEffect", {
69
+ request: Schema.String
70
+ }) {}
71
+ class RunStream extends Schema.TaggedClass<RunStream>("InvokeGalleryRunStream")("RunStream", {}) {}
72
+ class RunTimer extends Schema.TaggedClass<RunTimer>("InvokeGalleryRunTimer")("RunTimer", {}) {}
73
+ class RunProcess extends Schema.TaggedClass<RunProcess>("InvokeGalleryRunProcess")("RunProcess", {}) {}
74
+ class RunChild extends Schema.TaggedClass<RunChild>("InvokeGalleryRunChild")("RunChild", {}) {}
75
+ class Reset extends Schema.TaggedClass<Reset>("InvokeGalleryReset")("Reset", {}) {}
76
+ class StreamValue extends Schema.TaggedClass<StreamValue>("InvokeGalleryStreamValue")("StreamValue", {
77
+ value: Schema.Number
78
+ }) {}
79
+
80
+ const GalleryEvents = Machine.events(RunEffect, RunStream, RunTimer, RunProcess, RunChild, Reset)
81
+ const GalleryInternalEvents = Machine.internalEvents(StreamValue)
82
+ const processLogic = Machine.logic({
83
+ initial: "starting" as "starting" | "ready",
84
+ run: () => Effect.fail("process stopped")
85
+ })
86
+
87
+ const GalleryStates = Machine.states({
88
+ Gallery: {
89
+ schema: Gallery,
90
+ initial: "Choose",
91
+ states: {
92
+ Choose,
93
+ LoadingDocument,
94
+ StreamingUpdates,
95
+ WaitingForTimeout,
96
+ WatchingProcess,
97
+ RunningChild
98
+ }
99
+ },
100
+ Completed,
101
+ Failed
102
+ })
103
+
104
+ export const invokeOutcomesMachine = Machine.make({
105
+ id: "invoke-outcomes",
106
+ states: GalleryStates.states,
107
+ events: GalleryEvents,
108
+ internalEvents: GalleryInternalEvents,
109
+ initial: (to) =>
110
+ to.Gallery.initial.resolve(({ target }) =>
111
+ target.decoded(
112
+ new Gallery({ selectedDemo: null }),
113
+ (gallery) => gallery.Choose.decoded(new Choose({}))
114
+ )
115
+ )
116
+ }).handle({
117
+ Gallery: {
118
+ initialize: ({ builder }) => builder.decoded(new Choose({})),
119
+ on: {
120
+ Reset: (to) => to.local.Choose().resolve(({ target }) => target.decoded(new Choose({})))
121
+ },
122
+ states: {
123
+ Choose: {
124
+ on: {
125
+ RunEffect: (to) =>
126
+ to.local.LoadingDocument().resolve(({ event, target }) =>
127
+ target.decoded(new LoadingDocument({ request: event.request }))
128
+ ),
129
+ RunStream: (to) =>
130
+ to.local.StreamingUpdates().resolve(({ target }) => target.decoded(new StreamingUpdates({ values: [] }))),
131
+ RunTimer: (to) =>
132
+ to.local.WaitingForTimeout().resolve(({ target }) =>
133
+ target.decoded(new WaitingForTimeout({ delay: "2 seconds" }))
134
+ ),
135
+ RunProcess: (to) =>
136
+ to.local.WatchingProcess().resolve(({ target }) => target.decoded(new WatchingProcess({ revision: 1 }))),
137
+ RunChild: (to) => to.local.RunningChild().resolve(({ target }) => target.decoded(new RunningChild({})))
138
+ }
139
+ },
140
+ LoadingDocument: {
141
+ invoke: (from) =>
142
+ from.effect(
143
+ "load-document",
144
+ ({ state }) =>
145
+ state.request === "fail" ? Effect.fail("document unavailable") : Effect.succeed("document loaded")
146
+ ).onDone((to) =>
147
+ to.full.Completed().resolve(({ output, target }) =>
148
+ target.decoded(new Completed({ source: "effect", result: output }))
149
+ )
150
+ ).onFailure((to) =>
151
+ to.full.Failed().resolve(({ error, target }) =>
152
+ target.decoded(new Failed({ source: "effect", message: error }))
153
+ )
154
+ )
155
+ },
156
+ StreamingUpdates: {
157
+ invoke: (from) =>
158
+ from.stream(
159
+ "document-updates",
160
+ () => Stream.fromIterable([1, 2, 3]).pipe(Stream.concat(Stream.fail("stream disconnected")))
161
+ ).onElement((to) =>
162
+ to.none.resolve(({ element }, enqueue) => {
163
+ enqueue.raise(GalleryInternalEvents.StreamValue({ value: element }))
164
+ })
165
+ ).onDone((to) =>
166
+ to.full.Completed().resolve(({ state, target }) =>
167
+ target.decoded(new Completed({ source: "stream", result: state.values.join(", ") }))
168
+ )
169
+ ).onFailure((to) =>
170
+ to.full.Failed().resolve(({ error, target }) =>
171
+ target.decoded(new Failed({ source: "stream", message: error }))
172
+ )
173
+ ),
174
+ on: {
175
+ StreamValue: (to) =>
176
+ to.local.StreamingUpdates().resolve(({ event, state, target }) =>
177
+ target.decoded(new StreamingUpdates({ values: [...state.values, event.value] }))
178
+ )
179
+ }
180
+ },
181
+ WaitingForTimeout: {
182
+ invoke: (from) =>
183
+ from.timer("request-timeout", "2 seconds").onDone((to) =>
184
+ to.full.Completed().resolve(({ target }) =>
185
+ target.decoded(new Completed({ source: "timer", result: "timeout elapsed" }))
186
+ )
187
+ )
188
+ },
189
+ WatchingProcess: {
190
+ invoke: (from) =>
191
+ from.logic("status-worker", {
192
+ address: Machine.childAddress("status-worker"),
193
+ logic: processLogic
194
+ }).onFailure((to) =>
195
+ to.full.Failed().resolve(({ error, target }) =>
196
+ target.decoded(new Failed({ source: "process", message: String(error) }))
197
+ )
198
+ ).onSnapshot((to) =>
199
+ to.branches({
200
+ ready: { title: "Worker reports ready", target: to.full.Completed() },
201
+ waiting: { title: "Worker is still starting", target: to.none }
202
+ }).resolve(({ select, snapshot }) =>
203
+ snapshot.state === "ready"
204
+ ? select.ready.decoded(new Completed({ source: "process", result: "ready" }))
205
+ : select.waiting()
206
+ )
207
+ )
208
+ },
209
+ RunningChild: {
210
+ invoke: (from) =>
211
+ from.child(InvokedChild).onDone((to) =>
212
+ to.full.Completed().resolve(({ output, target }) =>
213
+ target.decoded(new Completed({ source: "child machine", result: output }))
214
+ )
215
+ )
216
+ }
217
+ }
218
+ },
219
+ Completed: {
220
+ on: {
221
+ Reset: (to) =>
222
+ to.full.Gallery.initial.resolve(({ target }) => target.decoded(new Gallery({ selectedDemo: null })))
223
+ }
224
+ },
225
+ Failed: {
226
+ on: {
227
+ Reset: (to) =>
228
+ to.full.Gallery.initial.resolve(({ target }) => target.decoded(new Gallery({ selectedDemo: null })))
229
+ }
230
+ }
231
+ })
@@ -0,0 +1,199 @@
1
+ import { Machine } from "@typeonce/effect-machine"
2
+ import { Schema } from "effect"
3
+
4
+ class Cart extends Schema.TaggedClass<Cart>("ParallelCart")("Cart", {
5
+ items: Schema.Number
6
+ }) {}
7
+ class Order extends Schema.TaggedClass<Order>("ParallelOrder")("Order", {
8
+ orderId: Schema.String,
9
+ total: Schema.Number
10
+ }) {}
11
+ class Payment extends Schema.TaggedClass<Payment>("ParallelPayment")("Payment", {
12
+ attempts: Schema.Number
13
+ }) {}
14
+ class AwaitingAuthorization extends Schema.TaggedClass<AwaitingAuthorization>("ParallelAwaitingAuthorization")(
15
+ "AwaitingAuthorization",
16
+ {}
17
+ ) {}
18
+ class Authorized extends Schema.TaggedClass<Authorized>("ParallelAuthorized")("Authorized", {
19
+ authorizationId: Schema.String
20
+ }) {}
21
+ class Fulfillment extends Schema.TaggedClass<Fulfillment>("ParallelFulfillment")("Fulfillment", {
22
+ warehouse: Schema.String
23
+ }) {}
24
+ class WaitingForPayment extends Schema.TaggedClass<WaitingForPayment>("ParallelWaitingForPayment")(
25
+ "WaitingForPayment",
26
+ {}
27
+ ) {}
28
+ class Packing extends Schema.TaggedClass<Packing>("ParallelPacking")("Packing", {
29
+ packageCount: Schema.Number
30
+ }) {}
31
+ class Shipped extends Schema.TaggedClass<Shipped>("ParallelShipped")("Shipped", {
32
+ trackingCode: Schema.String
33
+ }) {}
34
+ class OrderComplete extends Schema.TaggedClass<OrderComplete>("ParallelOrderComplete")("OrderComplete", {
35
+ orderId: Schema.String
36
+ }) {}
37
+ class OrderCancelled extends Schema.TaggedClass<OrderCancelled>("ParallelOrderCancelled")(
38
+ "OrderCancelled",
39
+ { reason: Schema.String }
40
+ ) {}
41
+
42
+ class Checkout extends Schema.TaggedClass<Checkout>("ParallelCheckout")("Checkout", {
43
+ orderId: Schema.String,
44
+ total: Schema.Number
45
+ }) {}
46
+ class Authorize extends Schema.TaggedClass<Authorize>("ParallelAuthorize")("Authorize", {
47
+ authorizationId: Schema.String
48
+ }) {}
49
+ class DeclinePayment extends Schema.TaggedClass<DeclinePayment>("ParallelDeclinePayment")(
50
+ "DeclinePayment",
51
+ { reason: Schema.String }
52
+ ) {}
53
+ class Pack extends Schema.TaggedClass<Pack>("ParallelPack")("Pack", { packages: Schema.Number }) {}
54
+ class Ship extends Schema.TaggedClass<Ship>("ParallelShip")("Ship", { trackingCode: Schema.String }) {}
55
+ class CompleteAll extends Schema.TaggedClass<CompleteAll>("ParallelCompleteAll")("CompleteAll", {
56
+ authorizationId: Schema.String,
57
+ trackingCode: Schema.String
58
+ }) {}
59
+ class CancelOrder extends Schema.TaggedClass<CancelOrder>("ParallelCancelOrder")("CancelOrder", {
60
+ reason: Schema.String
61
+ }) {}
62
+ class RetryOrder extends Schema.TaggedClass<RetryOrder>("ParallelRetryOrder")("RetryOrder", {}) {}
63
+ class AutoShip extends Schema.TaggedClass<AutoShip>("ParallelAutoShip")("AutoShip", {}) {}
64
+
65
+ const ParallelInternalEvents = Machine.internalEvents(AutoShip)
66
+ const ParallelStates = Machine.states({
67
+ Cart,
68
+ Order: {
69
+ schema: Order,
70
+ type: "parallel",
71
+ states: {
72
+ payment: {
73
+ schema: Payment,
74
+ initial: "AwaitingAuthorization",
75
+ states: {
76
+ AwaitingAuthorization,
77
+ Authorized: { schema: Authorized, type: "final" }
78
+ }
79
+ },
80
+ fulfillment: {
81
+ schema: Fulfillment,
82
+ initial: "WaitingForPayment",
83
+ states: {
84
+ WaitingForPayment,
85
+ Packing,
86
+ Shipped: { schema: Shipped, type: "final" }
87
+ }
88
+ }
89
+ }
90
+ },
91
+ Complete: { schema: OrderComplete, type: "final", output: Schema.String },
92
+ Cancelled: OrderCancelled
93
+ })
94
+
95
+ export const parallelCompletionMachine = Machine.make({
96
+ id: "parallel-completion",
97
+ states: ParallelStates.states,
98
+ events: Machine.events(
99
+ Checkout,
100
+ Authorize,
101
+ DeclinePayment,
102
+ Pack,
103
+ Ship,
104
+ CompleteAll,
105
+ CancelOrder,
106
+ RetryOrder
107
+ ),
108
+ internalEvents: ParallelInternalEvents,
109
+ initial: (to) => to.Cart().resolve(({ target }) => target.decoded(new Cart({ items: 2 })))
110
+ }).handle({
111
+ Cart: {
112
+ on: {
113
+ Checkout: (to) =>
114
+ to.full.Order.initial.resolve(({ event, target }) =>
115
+ target.decoded(new Order({ orderId: event.orderId, total: event.total }))
116
+ )
117
+ }
118
+ },
119
+ Order: {
120
+ initialize: ({ builder }) => builder.payment.from({ attempts: 0 }).fulfillment.from({ warehouse: "north" }),
121
+ on: {
122
+ CancelOrder: (to) =>
123
+ to.full.Cancelled().resolve(({ event, target }) => target.decoded(new OrderCancelled({ reason: event.reason })))
124
+ },
125
+ onDone: (to) =>
126
+ to.full.Complete().resolve(({ target }) => target.decoded(new OrderComplete({ orderId: "completed-order" }))),
127
+ states: {
128
+ payment: {
129
+ initialize: ({ builder }) => builder.from(),
130
+ states: {
131
+ AwaitingAuthorization: {
132
+ on: {
133
+ Authorize: (to) =>
134
+ to.local.Authorized().resolve(({ event, target }) =>
135
+ target.decoded(new Authorized({ authorizationId: event.authorizationId }))
136
+ ),
137
+ CompleteAll: (to) =>
138
+ to.local.Authorized().resolve(({ event, target }) =>
139
+ target.decoded(new Authorized({ authorizationId: event.authorizationId }))
140
+ ),
141
+ DeclinePayment: (to) =>
142
+ to.full.Cancelled().resolve(({ event, target }) =>
143
+ target.decoded(new OrderCancelled({ reason: event.reason }))
144
+ )
145
+ }
146
+ }
147
+ }
148
+ },
149
+ fulfillment: {
150
+ initialize: ({ builder }) => builder.from(),
151
+ states: {
152
+ WaitingForPayment: {
153
+ on: {
154
+ Authorize: (to) =>
155
+ to.local.Packing().resolve(({ target }) => target.decoded(new Packing({ packageCount: 1 }))),
156
+ Pack: (to) =>
157
+ to.local.Packing().resolve(({ event, target }) =>
158
+ target.decoded(new Packing({ packageCount: event.packages }))
159
+ ),
160
+ CompleteAll: (to) =>
161
+ to.local.Shipped().resolve(({ event, target }) =>
162
+ target.decoded(new Shipped({ trackingCode: event.trackingCode }))
163
+ )
164
+ }
165
+ },
166
+ Packing: {
167
+ invoke: (from) =>
168
+ from.timer("packing-sla", "5 seconds").onDone((to) =>
169
+ to.none.resolve((_, enqueue) => {
170
+ enqueue.raise(ParallelInternalEvents.AutoShip())
171
+ })
172
+ ),
173
+ on: {
174
+ AutoShip: (to) =>
175
+ to.local.Shipped().resolve(({ target }) => target.decoded(new Shipped({ trackingCode: "automatic" }))),
176
+ Ship: (to) =>
177
+ to.local.Shipped().resolve(({ event, target }) =>
178
+ target.decoded(new Shipped({ trackingCode: event.trackingCode }))
179
+ ),
180
+ CompleteAll: (to) =>
181
+ to.local.Shipped().resolve(({ event, target }) =>
182
+ target.decoded(new Shipped({ trackingCode: event.trackingCode }))
183
+ )
184
+ }
185
+ }
186
+ }
187
+ }
188
+ }
189
+ },
190
+ Complete: {
191
+ output: ({ state }) => state.orderId
192
+ },
193
+ Cancelled: {
194
+ on: {
195
+ RetryOrder: (to) =>
196
+ to.full.Order.initial.resolve(({ target }) => target.decoded(new Order({ orderId: "retry", total: 0 })))
197
+ }
198
+ }
199
+ })