@typeonce/effect-machine 0.29.0 → 0.31.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 +47 -2
- package/dist/Machine.d.ts +38 -0
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +35 -0
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/atom.d.ts +2 -0
- package/dist/internal/machine/atom.d.ts.map +1 -1
- package/dist/internal/machine/atom.js +63 -11
- package/dist/internal/machine/atom.js.map +1 -1
- package/dist/internal/machine/machine.d.ts +1 -0
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +1 -0
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +1 -0
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +19 -0
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +74 -4
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.js +52 -3
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
- package/docs/effect-atom-react.md +48 -1
- package/docs/machine-review.md +2 -1
- package/package.json +1 -1
- package/src/Machine.ts +114 -0
- package/src/internal/machine/atom.ts +83 -18
- package/src/internal/machine/machine.ts +2 -0
- package/src/internal/machine/planner.ts +25 -0
- package/src/unstable/reactivity/AtomMachine.ts +104 -4
|
@@ -48,6 +48,7 @@ export const inspection = internal.inspection;
|
|
|
48
48
|
export const childEmissions = internal.childEmissions;
|
|
49
49
|
const InvalidSelectorPathTypeId = "~effect/reactivity/AtomMachine/InvalidSelectorPath";
|
|
50
50
|
const SelectorProjectionTypeId = "~effect/reactivity/AtomMachine/SelectorProjection";
|
|
51
|
+
const InvalidCanEventTypeId = "~effect/reactivity/AtomMachine/InvalidCanEvent";
|
|
51
52
|
/**
|
|
52
53
|
* Selects the typed value for an active state path.
|
|
53
54
|
*
|
|
@@ -159,6 +160,33 @@ export const selectSnapshotChild = dual(2, internal.selectSnapshotChild);
|
|
|
159
160
|
* @since 0.4.0
|
|
160
161
|
*/
|
|
161
162
|
export const matches = dual(2, internal.matches);
|
|
163
|
+
/**
|
|
164
|
+
* Reactively tests whether a concrete event would be accepted by a running
|
|
165
|
+
* machine.
|
|
166
|
+
*
|
|
167
|
+
* Declare the returned projection once, then apply it to compatible machine
|
|
168
|
+
* bridges. Repeated applications to the same bridge return the same atom. An
|
|
169
|
+
* event atom is read reactively when acceptance depends on a changing payload.
|
|
170
|
+
*
|
|
171
|
+
* Startup remains in the source `AsyncResult`. Active snapshots use
|
|
172
|
+
* {@link Machine.can}; done and stopped snapshots produce `false`, while
|
|
173
|
+
* runtime and schema failures remain in the typed failure channel.
|
|
174
|
+
*
|
|
175
|
+
* **Example**
|
|
176
|
+
*
|
|
177
|
+
* ```ts
|
|
178
|
+
* const submitAllowed = AtomMachine.can(AuthEvents.Submitted())
|
|
179
|
+
* const canSubmitAtom = submitAllowed(authMachineAtom)
|
|
180
|
+
*
|
|
181
|
+
* const submitEvent = Atom.map(draftAtom, (draft) =>
|
|
182
|
+
* AuthEvents.Submitted({ draft }))
|
|
183
|
+
* const reactiveSubmitAllowed = AtomMachine.can(submitEvent)
|
|
184
|
+
* ```
|
|
185
|
+
*
|
|
186
|
+
* @category combinators
|
|
187
|
+
* @since 0.31.0
|
|
188
|
+
*/
|
|
189
|
+
export const can = internal.can;
|
|
162
190
|
/**
|
|
163
191
|
* Returns whether a state path is active in a directly owned child.
|
|
164
192
|
*
|
|
@@ -257,6 +285,26 @@ export const familyChild = internal.familyChild;
|
|
|
257
285
|
* @since 0.4.0
|
|
258
286
|
*/
|
|
259
287
|
export const make = internal.make;
|
|
288
|
+
/**
|
|
289
|
+
* Specializes a machine definition into a reusable bridge constructor.
|
|
290
|
+
*
|
|
291
|
+
* The returned function preserves the machine's startup input arity and exact
|
|
292
|
+
* bridge type. Every call creates a fresh `MachineAtom`; it does not cache by
|
|
293
|
+
* input or start the machine before an `AtomRegistry` reads or mounts it.
|
|
294
|
+
*
|
|
295
|
+
* **Example**
|
|
296
|
+
*
|
|
297
|
+
* ```ts
|
|
298
|
+
* const makeSearchMachine = AtomMachine.factory(searchMachine)
|
|
299
|
+
* const search = makeSearchMachine({ query: "effect" })
|
|
300
|
+
*
|
|
301
|
+
* type SearchMachineAtom = ReturnType<typeof makeSearchMachine>
|
|
302
|
+
* ```
|
|
303
|
+
*
|
|
304
|
+
* @category constructors
|
|
305
|
+
* @since 0.30.0
|
|
306
|
+
*/
|
|
307
|
+
export const factory = internal.factory;
|
|
260
308
|
/**
|
|
261
309
|
* Creates a lazy atom bridge from a decoded logical snapshot.
|
|
262
310
|
*
|
|
@@ -269,11 +317,12 @@ export const make = internal.make;
|
|
|
269
317
|
*/
|
|
270
318
|
export const resume = internal.resume;
|
|
271
319
|
/**
|
|
272
|
-
*
|
|
320
|
+
* Binds `AtomMachine` constructors to a shared Effect runtime.
|
|
273
321
|
*
|
|
274
322
|
* Use this when an application runs many machines from the same service layer.
|
|
275
|
-
* The returned
|
|
276
|
-
* while every call to `make` still creates an
|
|
323
|
+
* The returned interface keeps runtime provisioning at the composition seam,
|
|
324
|
+
* while every call to `make` or a specialized `factory` still creates an
|
|
325
|
+
* independent machine bridge.
|
|
277
326
|
*
|
|
278
327
|
* @category constructors
|
|
279
328
|
* @since 0.4.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AtomMachine.js","sourceRoot":"","sources":["../../../src/unstable/reactivity/AtomMachine.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAMtC,OAAO,KAAK,QAAQ,MAAM,gCAAgC,CAAA;AAK1D;;;;;GAKG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAA;AAE9D;;;;;GAKG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAA;AAMpE,MAAM,0BAA0B,GAAG,qDAAqD,CAAA;AAoIxF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,SAAS,GAE+C,QAAQ,CAAC,SAAS,CAAA;AAEvF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,UAAU,GAE+D,QAAQ,CAAC,UAAU,CAAA;AAEzG;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAMvB,QAAQ,CAAC,cAAc,CAAA;AAwJ3B,MAAM,yBAAyB,GAAG,oDAAoD,CAAA;AACtF,MAAM,wBAAwB,GAAG,mDAAmD,CAAA;
|
|
1
|
+
{"version":3,"file":"AtomMachine.js","sourceRoot":"","sources":["../../../src/unstable/reactivity/AtomMachine.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAMtC,OAAO,KAAK,QAAQ,MAAM,gCAAgC,CAAA;AAK1D;;;;;GAKG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAA;AAE9D;;;;;GAKG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAA;AAMpE,MAAM,0BAA0B,GAAG,qDAAqD,CAAA;AAoIxF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,SAAS,GAE+C,QAAQ,CAAC,SAAS,CAAA;AAEvF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,UAAU,GAE+D,QAAQ,CAAC,UAAU,CAAA;AAEzG;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAMvB,QAAQ,CAAC,cAAc,CAAA;AAwJ3B,MAAM,yBAAyB,GAAG,oDAAoD,CAAA;AACtF,MAAM,wBAAwB,GAAG,mDAAmD,CAAA;AACpF,MAAM,qBAAqB,GAAG,gDAAgD,CAAA;AAkD9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,CAAC,MAAM,MAAM,GA0Cf,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;AAE5B;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,cAAc,GA0CvB,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAA;AAEpC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,WAAW,GAsCpB,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAA;AAEjC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAoC5B,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,mBAAmB,CAAC,CAAA;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,CAAC,MAAM,OAAO,GAsChB,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;AAE7B;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,MAAM,GAAG,GAGZ,QAAQ,CAAC,GAAG,CAAA;AAEhB;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,YAAY,GA2BrB,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAA;AAElC,MAAM,uBAAuB,GAAG,kDAAkD,CAAA;AAkJlF,MAAM,yBAAyB,GAAG,oDAAoD,CAAA;AAyGtF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,MAAM,MAAM,GAkBgE,QAAQ,CAAC,MAAa,CAAA;AAEzG;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,MAAM,WAAW,GAYqC,QAAQ,CAAC,WAAkB,CAAA;AAExF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,CAAC,MAAM,IAAI,GAqDb,QAAQ,CAAC,IAAI,CAAA;AAEjB;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,OAAO,GAM+C,QAAQ,CAAC,OAAO,CAAA;AAEnF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,MAAM,GASf,QAAQ,CAAC,MAAM,CAAA;AAEnB;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,IAAI,GAEoB,QAAQ,CAAC,IAAI,CAAA"}
|
|
@@ -48,7 +48,7 @@ import { createContext, type ReactNode, useContext } from "react"
|
|
|
48
48
|
import { AuthMachine, type AuthMachineInput } from "../machines/auth-machine"
|
|
49
49
|
import { MachineAtoms } from "../lib/atom-runtime"
|
|
50
50
|
|
|
51
|
-
const makeAuthMachine =
|
|
51
|
+
const makeAuthMachine = MachineAtoms.factory(AuthMachine)
|
|
52
52
|
type AuthMachineAtom = ReturnType<typeof makeAuthMachine>
|
|
53
53
|
|
|
54
54
|
const AuthMachineContext = createContext<AuthMachineAtom | null>(null)
|
|
@@ -183,6 +183,53 @@ function SubmitButton() {
|
|
|
183
183
|
`useAtomSet` mounts the writable atom and does not subscribe the component to
|
|
184
184
|
its value.
|
|
185
185
|
|
|
186
|
+
## Query concrete event acceptance
|
|
187
|
+
|
|
188
|
+
`AtomMachine.can` turns one concrete event input into a reusable machine
|
|
189
|
+
projection. Declare the projection once, then apply it to the React-owned
|
|
190
|
+
machine. Repeated applications to the same machine return the same atom:
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
import { AtomMachine } from "@typeonce/effect-machine/reactivity"
|
|
194
|
+
import { useAtomSet, useAtomSuspense } from "@effect/atom-react"
|
|
195
|
+
import { AuthEvents } from "../machines/auth-machine"
|
|
196
|
+
|
|
197
|
+
const submitAllowed = AtomMachine.can(AuthEvents.Submitted())
|
|
198
|
+
|
|
199
|
+
function SubmitButton() {
|
|
200
|
+
const machine = useAuthMachine()
|
|
201
|
+
const canSubmit = useAtomSuspense(submitAllowed(machine)).value
|
|
202
|
+
const send = useAtomSet(machine.send)
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<button
|
|
206
|
+
disabled={!canSubmit}
|
|
207
|
+
onClick={() => send(AuthEvents.Submitted())}
|
|
208
|
+
>
|
|
209
|
+
Continue
|
|
210
|
+
</button>
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Startup still suspends, startup and runtime failures reach the error boundary,
|
|
216
|
+
and invalid event input for an active machine remains a
|
|
217
|
+
`MachineSchemaDecodeError`. Done and stopped machines return `false`.
|
|
218
|
+
|
|
219
|
+
When acceptance depends on a changing payload, project an event atom instead:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
import { Atom } from "effect/unstable/reactivity"
|
|
223
|
+
|
|
224
|
+
const submitEvent = Atom.map(draftAtom, (draft) =>
|
|
225
|
+
AuthEvents.Submitted({ draft }))
|
|
226
|
+
|
|
227
|
+
const submitAllowed = AtomMachine.can(submitEvent)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Changes to `draftAtom` recompute acceptance. The event atom contains the event
|
|
231
|
+
input itself rather than an `AsyncResult`.
|
|
232
|
+
|
|
186
233
|
## Whole-result and custom selections
|
|
187
234
|
|
|
188
235
|
Reading the full result is correct when a component renders the complete
|
package/docs/machine-review.md
CHANGED
|
@@ -71,7 +71,8 @@ Use `useMachineAtom` when one React subtree owns the workflow, including a
|
|
|
71
71
|
machine with startup input:
|
|
72
72
|
|
|
73
73
|
```tsx
|
|
74
|
-
const
|
|
74
|
+
const makeProcessMachine = machineAtoms.factory(processMachine)
|
|
75
|
+
const machine = useMachineAtom(() => makeProcessMachine(input))
|
|
75
76
|
```
|
|
76
77
|
|
|
77
78
|
Pass the stable machine through props or Context. Startup input is captured
|
package/package.json
CHANGED
package/src/Machine.ts
CHANGED
|
@@ -9301,6 +9301,120 @@ export const enabled: <
|
|
|
9301
9301
|
state: Machine.Snapshot<States>
|
|
9302
9302
|
) => ReadonlyArray<Machine.TagOf<Events[number]>> = internal.enabled as any
|
|
9303
9303
|
|
|
9304
|
+
/**
|
|
9305
|
+
* Tests whether a concrete event would select at least one transition from a
|
|
9306
|
+
* decoded snapshot.
|
|
9307
|
+
*
|
|
9308
|
+
* **Details**
|
|
9309
|
+
*
|
|
9310
|
+
* Required handlers are accepted from their structural eligibility.
|
|
9311
|
+
* Declinable handlers run their resolver only far enough to decide whether
|
|
9312
|
+
* they accept the event. Any commands, emissions, or raised events collected
|
|
9313
|
+
* during that check are discarded.
|
|
9314
|
+
*
|
|
9315
|
+
* Event input is decoded through the machine's public event protocol. Invalid
|
|
9316
|
+
* input fails with `MachineSchemaDecodeError`. Final snapshots and valid events
|
|
9317
|
+
* with no accepting handler return `false`.
|
|
9318
|
+
*
|
|
9319
|
+
* **Gotchas**
|
|
9320
|
+
*
|
|
9321
|
+
* This query does not execute transitions or stabilize the resulting machine.
|
|
9322
|
+
* It does not run entry, exit, always, completion, child lifecycle, or command
|
|
9323
|
+
* effects. A `true` result therefore describes event acceptance only.
|
|
9324
|
+
*
|
|
9325
|
+
* **Example**
|
|
9326
|
+
*
|
|
9327
|
+
* ```ts
|
|
9328
|
+
* const canCheckout = Machine.can(checkoutMachine)
|
|
9329
|
+
*
|
|
9330
|
+
* const canSubmit = yield* canCheckout(snapshot, {
|
|
9331
|
+
* _tag: "SubmitOrder"
|
|
9332
|
+
* })
|
|
9333
|
+
* ```
|
|
9334
|
+
*
|
|
9335
|
+
* @category getters
|
|
9336
|
+
* @since 0.30.0
|
|
9337
|
+
*/
|
|
9338
|
+
export const can: {
|
|
9339
|
+
<
|
|
9340
|
+
const States extends Machine.StateSchemas,
|
|
9341
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
9342
|
+
const Emits extends ReadonlyArray<Machine.TaggedSchema>,
|
|
9343
|
+
const Input extends Schema.Top = typeof Schema.Void,
|
|
9344
|
+
UnhandledStates extends Machine.StateIdentifier<States> = Machine.StateIdentifier<States>,
|
|
9345
|
+
E = never,
|
|
9346
|
+
R = never,
|
|
9347
|
+
InitialE = never,
|
|
9348
|
+
InitialR = never,
|
|
9349
|
+
FinalStates extends Machine.StateIdentifier<States> = never,
|
|
9350
|
+
Output = never,
|
|
9351
|
+
OutputStates extends Machine.StateIdentifier<States> = never,
|
|
9352
|
+
InputEvents extends ReadonlyArray<Machine.TaggedSchema> = Events,
|
|
9353
|
+
ParentEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly []
|
|
9354
|
+
>(
|
|
9355
|
+
machine:
|
|
9356
|
+
& Machine<
|
|
9357
|
+
States,
|
|
9358
|
+
Events,
|
|
9359
|
+
Input,
|
|
9360
|
+
UnhandledStates,
|
|
9361
|
+
E,
|
|
9362
|
+
R,
|
|
9363
|
+
InitialE,
|
|
9364
|
+
InitialR,
|
|
9365
|
+
FinalStates,
|
|
9366
|
+
Output,
|
|
9367
|
+
Emits,
|
|
9368
|
+
OutputStates,
|
|
9369
|
+
InputEvents,
|
|
9370
|
+
ParentEvents
|
|
9371
|
+
>
|
|
9372
|
+
& EnsureExecutable<States, UnhandledStates, OutputStates>
|
|
9373
|
+
& Machine.RootCompatible<ParentEvents>
|
|
9374
|
+
): (
|
|
9375
|
+
state: Machine.Snapshot<States>,
|
|
9376
|
+
event: Machine.EventInputOf<InputEvents>
|
|
9377
|
+
) => Effect.Effect<boolean, MachineSchemaDecodeError>
|
|
9378
|
+
<
|
|
9379
|
+
const States extends Machine.StateSchemas,
|
|
9380
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
9381
|
+
const Emits extends ReadonlyArray<Machine.TaggedSchema>,
|
|
9382
|
+
const Input extends Schema.Top = typeof Schema.Void,
|
|
9383
|
+
UnhandledStates extends Machine.StateIdentifier<States> = Machine.StateIdentifier<States>,
|
|
9384
|
+
E = never,
|
|
9385
|
+
R = never,
|
|
9386
|
+
InitialE = never,
|
|
9387
|
+
InitialR = never,
|
|
9388
|
+
FinalStates extends Machine.StateIdentifier<States> = never,
|
|
9389
|
+
Output = never,
|
|
9390
|
+
OutputStates extends Machine.StateIdentifier<States> = never,
|
|
9391
|
+
InputEvents extends ReadonlyArray<Machine.TaggedSchema> = Events,
|
|
9392
|
+
ParentEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly []
|
|
9393
|
+
>(
|
|
9394
|
+
machine:
|
|
9395
|
+
& Machine<
|
|
9396
|
+
States,
|
|
9397
|
+
Events,
|
|
9398
|
+
Input,
|
|
9399
|
+
UnhandledStates,
|
|
9400
|
+
E,
|
|
9401
|
+
R,
|
|
9402
|
+
InitialE,
|
|
9403
|
+
InitialR,
|
|
9404
|
+
FinalStates,
|
|
9405
|
+
Output,
|
|
9406
|
+
Emits,
|
|
9407
|
+
OutputStates,
|
|
9408
|
+
InputEvents,
|
|
9409
|
+
ParentEvents
|
|
9410
|
+
>
|
|
9411
|
+
& EnsureExecutable<States, UnhandledStates, OutputStates>
|
|
9412
|
+
& Machine.RootCompatible<ParentEvents>,
|
|
9413
|
+
state: Machine.Snapshot<States>,
|
|
9414
|
+
event: Machine.EventInputOf<InputEvents>
|
|
9415
|
+
): Effect.Effect<boolean, MachineSchemaDecodeError>
|
|
9416
|
+
} = internal.can as any
|
|
9417
|
+
|
|
9304
9418
|
/**
|
|
9305
9419
|
* Plans the next state snapshot synchronously.
|
|
9306
9420
|
*
|
|
@@ -79,6 +79,8 @@ const preparedByMachineAtom = new WeakMap<
|
|
|
79
79
|
Atom.Atom<AsyncResult.AsyncResult<Machine.Prepared<any, any, any, any, any, any, any>, any>>
|
|
80
80
|
>()
|
|
81
81
|
|
|
82
|
+
const machineByMachineAtom = new WeakMap<object, Machine.Machine.Any>()
|
|
83
|
+
|
|
82
84
|
type WeakFamilyEntry<Value extends object> = {
|
|
83
85
|
readonly ref: WeakRef<Value>
|
|
84
86
|
}
|
|
@@ -422,7 +424,8 @@ const makeChildSelector = <StartError>(
|
|
|
422
424
|
}
|
|
423
425
|
|
|
424
426
|
const makeFromRefAtom = <State, Event, Error, Output, StartError, Emitted>(
|
|
425
|
-
ref: Atom.Atom<AsyncResult.AsyncResult<Machine.MachineRef<State, Event, Error, Output, Emitted>, StartError
|
|
427
|
+
ref: Atom.Atom<AsyncResult.AsyncResult<Machine.MachineRef<State, Event, Error, Output, Emitted>, StartError>>,
|
|
428
|
+
machine: Machine.Machine.Any
|
|
426
429
|
): MachineAtom<State, Event, Error, Output, StartError, Emitted> => {
|
|
427
430
|
const snapshot = Atom.readable((
|
|
428
431
|
get
|
|
@@ -498,7 +501,7 @@ const makeFromRefAtom = <State, Event, Error, Output, StartError, Emitted>(
|
|
|
498
501
|
const optionalRef = Atom.mapResult(ref, Option.some)
|
|
499
502
|
const child = makeChildSelector<StartError>(optionalRef as any)
|
|
500
503
|
|
|
501
|
-
|
|
504
|
+
const result = {
|
|
502
505
|
ref,
|
|
503
506
|
snapshot,
|
|
504
507
|
state: Atom.mapResult(snapshot, (snapshot) => snapshot.state),
|
|
@@ -507,6 +510,8 @@ const makeFromRefAtom = <State, Event, Error, Output, StartError, Emitted>(
|
|
|
507
510
|
stop,
|
|
508
511
|
child
|
|
509
512
|
}
|
|
513
|
+
machineByMachineAtom.set(result, machine)
|
|
514
|
+
return result
|
|
510
515
|
}
|
|
511
516
|
|
|
512
517
|
type SnapshotNode<State> = State extends Machine.Machine.AtomicSnapshot<string, unknown> ?
|
|
@@ -716,6 +721,57 @@ export const matchesChild = <
|
|
|
716
721
|
Option.exists((snapshot) => Option.isSome(Topology.getSnapshotByPath(snapshot, path)))
|
|
717
722
|
).pipe(Atom.withEquality(Equal.equals)))
|
|
718
723
|
|
|
724
|
+
export const can = (event: unknown) => {
|
|
725
|
+
const byBridge = new WeakMap<object, Atom.Atom<AsyncResult.AsyncResult<boolean, any>>>()
|
|
726
|
+
return (self: MachineAtom<any, any, any, any, any, any>): Atom.Atom<AsyncResult.AsyncResult<boolean, any>> => {
|
|
727
|
+
const cached = byBridge.get(self)
|
|
728
|
+
if (cached !== undefined) return cached
|
|
729
|
+
|
|
730
|
+
const machine = machineByMachineAtom.get(self)
|
|
731
|
+
const query: (
|
|
732
|
+
state: Machine.Machine.Snapshot<any>,
|
|
733
|
+
event: unknown
|
|
734
|
+
) => Effect.Effect<boolean, Machine.MachineSchemaDecodeError> = machine === undefined
|
|
735
|
+
? () => Effect.die(new Error("AtomMachine.can requires a machine atom created by AtomMachine"))
|
|
736
|
+
: internalMachine.can(machine) as (
|
|
737
|
+
state: Machine.Machine.Snapshot<any>,
|
|
738
|
+
event: unknown
|
|
739
|
+
) => Effect.Effect<boolean, Machine.MachineSchemaDecodeError>
|
|
740
|
+
|
|
741
|
+
const result = Atom.readable((get): AsyncResult.AsyncResult<boolean, any> => {
|
|
742
|
+
const current = get(self.snapshot)
|
|
743
|
+
const previous = get.self<AsyncResult.AsyncResult<boolean, any>>()
|
|
744
|
+
if (AsyncResult.isInitial(current)) {
|
|
745
|
+
return AsyncResult.initial(current.waiting)
|
|
746
|
+
} else if (AsyncResult.isFailure(current)) {
|
|
747
|
+
return AsyncResult.failureWithPrevious(current.cause, {
|
|
748
|
+
previous,
|
|
749
|
+
waiting: current.waiting
|
|
750
|
+
})
|
|
751
|
+
} else if (current.value.status === "error") {
|
|
752
|
+
return AsyncResult.failureWithPrevious(current.value.cause, {
|
|
753
|
+
previous,
|
|
754
|
+
waiting: current.waiting
|
|
755
|
+
})
|
|
756
|
+
} else if (current.value.status !== "active") {
|
|
757
|
+
return AsyncResult.success(false, { waiting: current.waiting })
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const input = Atom.isAtom(event) ? get(event) : event
|
|
761
|
+
const exit = Effect.runSyncExit(query(current.value.state, input))
|
|
762
|
+
return exit._tag === "Success"
|
|
763
|
+
? AsyncResult.success(exit.value, { waiting: current.waiting })
|
|
764
|
+
: AsyncResult.failureWithPrevious(exit.cause, {
|
|
765
|
+
previous,
|
|
766
|
+
waiting: current.waiting
|
|
767
|
+
})
|
|
768
|
+
}).pipe(Atom.withEquality(Equal.equals))
|
|
769
|
+
|
|
770
|
+
byBridge.set(self, result)
|
|
771
|
+
return result
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
719
775
|
type MachineResumeRequirementsOf<M extends Machine.Machine.Any> = MachineResumeRequirements<
|
|
720
776
|
Machine.Machine.Services<M>,
|
|
721
777
|
Machine.Machine.Event<M>,
|
|
@@ -796,11 +852,14 @@ export const make: {
|
|
|
796
852
|
} = ((machine: Machine.Machine.Any, ...args: ReadonlyArray<unknown>) => {
|
|
797
853
|
const prepared = Atom.make(() => internalMachine.prepare(machine as any, ...(args as [])))
|
|
798
854
|
const ref = Atom.make((get) => startPreparedMachineAtomEffect(get, prepared as any))
|
|
799
|
-
const result = makeFromRefAtom(ref as any)
|
|
855
|
+
const result = makeFromRefAtom(ref as any, machine)
|
|
800
856
|
preparedByMachineAtom.set(result, prepared as any)
|
|
801
857
|
return result
|
|
802
858
|
}) as any
|
|
803
859
|
|
|
860
|
+
export const factory =
|
|
861
|
+
((machine: Machine.Machine.Any) => (...args: ReadonlyArray<unknown>) => (make as any)(machine, ...args)) as any
|
|
862
|
+
|
|
804
863
|
export const resume: {
|
|
805
864
|
<M extends Machine.Machine.Any>(
|
|
806
865
|
machine:
|
|
@@ -812,7 +871,7 @@ export const resume: {
|
|
|
812
871
|
): ResumedMachineAtomOf<M, never>
|
|
813
872
|
} = ((machine: Machine.Machine.Any, snapshot: Machine.Machine.Snapshot<any>) => {
|
|
814
873
|
const ref = Atom.make((get) => resumeMachineAtomEffect(get, machine, snapshot))
|
|
815
|
-
return makeFromRefAtom(ref as any)
|
|
874
|
+
return makeFromRefAtom(ref as any, machine)
|
|
816
875
|
}) as any
|
|
817
876
|
|
|
818
877
|
const makeWithRuntime = (
|
|
@@ -822,7 +881,7 @@ const makeWithRuntime = (
|
|
|
822
881
|
): MachineAtom<any, any, any, any, any, any> => {
|
|
823
882
|
const prepared = runtime.atom(() => internalMachine.prepare(machine as any, ...(args as [])))
|
|
824
883
|
const ref = runtime.atom((get) => startPreparedMachineAtomEffect(get, prepared as any))
|
|
825
|
-
const result = makeFromRefAtom(ref as any)
|
|
884
|
+
const result = makeFromRefAtom(ref as any, machine)
|
|
826
885
|
preparedByMachineAtom.set(result, prepared as any)
|
|
827
886
|
return result
|
|
828
887
|
}
|
|
@@ -833,7 +892,7 @@ const resumeWithRuntime = (
|
|
|
833
892
|
snapshot: Machine.Machine.Snapshot<any>
|
|
834
893
|
): MachineAtom<any, any, any, any, any, any> => {
|
|
835
894
|
const ref = runtime.atom((get) => resumeMachineAtomEffect(get, machine, snapshot))
|
|
836
|
-
return makeFromRefAtom(ref as any)
|
|
895
|
+
return makeFromRefAtom(ref as any, machine)
|
|
837
896
|
}
|
|
838
897
|
|
|
839
898
|
type FamilyBridge = MachineAtom<any, never, any, any, any, any> | ChildMachineAtom<any, any>
|
|
@@ -901,19 +960,25 @@ export const familyChild = (
|
|
|
901
960
|
|
|
902
961
|
export const bind = <Services, RuntimeError>(
|
|
903
962
|
runtime: Atom.AtomRuntime<Services, RuntimeError>
|
|
904
|
-
): Bound<Services, RuntimeError> =>
|
|
905
|
-
|
|
963
|
+
): Bound<Services, RuntimeError> => {
|
|
964
|
+
const makeBound =
|
|
906
965
|
((machine: Machine.Machine.Any, ...args: ReadonlyArray<unknown>) =>
|
|
907
966
|
makeWithRuntime(runtime, machine, args)) as Bound<
|
|
908
967
|
Services,
|
|
909
968
|
RuntimeError
|
|
910
|
-
>["make"]
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
969
|
+
>["make"]
|
|
970
|
+
return {
|
|
971
|
+
make: makeBound,
|
|
972
|
+
factory:
|
|
973
|
+
((machine: Machine.Machine.Any) => (...args: ReadonlyArray<unknown>) =>
|
|
974
|
+
(makeBound as any)(machine, ...args)) as Bound<Services, RuntimeError>["factory"],
|
|
975
|
+
resume:
|
|
976
|
+
((machine: Machine.Machine.Any, snapshot: Machine.Machine.Snapshot<any>) =>
|
|
977
|
+
resumeWithRuntime(runtime, machine, snapshot)) as Bound<Services, RuntimeError>["resume"],
|
|
978
|
+
family: ((machine: Machine.Machine.Any, options: FamilyOptions) =>
|
|
979
|
+
makeFamily(
|
|
980
|
+
(input) => makeWithRuntime(runtime, machine, [input]),
|
|
981
|
+
options
|
|
982
|
+
)) as Bound<Services, RuntimeError>["family"]
|
|
983
|
+
}
|
|
984
|
+
}
|
|
@@ -2176,6 +2176,8 @@ export const enabled = <
|
|
|
2176
2176
|
state: Machine.Snapshot<States>
|
|
2177
2177
|
): ReadonlyArray<Machine.TagOf<Events[number]>> => internalPlanner.enabled(machine as any, state)
|
|
2178
2178
|
|
|
2179
|
+
export const can = internalPlanner.can
|
|
2180
|
+
|
|
2179
2181
|
export const plan: <
|
|
2180
2182
|
const States extends Machine.StateSchemas,
|
|
2181
2183
|
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
@@ -1876,6 +1876,17 @@ export const enabled = <
|
|
|
1876
1876
|
return tags
|
|
1877
1877
|
}
|
|
1878
1878
|
|
|
1879
|
+
const canSync = (
|
|
1880
|
+
machine: Machine.Any,
|
|
1881
|
+
state: Machine.Snapshot<any>,
|
|
1882
|
+
event: unknown
|
|
1883
|
+
): boolean => {
|
|
1884
|
+
const decodedEvent = decodeEventSync(machine, event)
|
|
1885
|
+
if (isFinalState(machine, state)) return false
|
|
1886
|
+
const configuration = normalizeConfigurationSync(machine, state)
|
|
1887
|
+
return selectEventTransitions(machine, configuration, decodedEvent as any).length > 0
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1879
1890
|
const microstep = <
|
|
1880
1891
|
const States extends Machine.StateSchemas,
|
|
1881
1892
|
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
@@ -2300,6 +2311,20 @@ const planningEffect = <A>(thunk: () => A): Effect.Effect<A, InfiniteTransitionE
|
|
|
2300
2311
|
}
|
|
2301
2312
|
})
|
|
2302
2313
|
|
|
2314
|
+
const schemaEffect = <A>(thunk: () => A): Effect.Effect<A, MachineSchemaDecodeError> =>
|
|
2315
|
+
Effect.suspend(() => {
|
|
2316
|
+
try {
|
|
2317
|
+
return Effect.succeed(thunk())
|
|
2318
|
+
} catch (error) {
|
|
2319
|
+
return error instanceof MachineSchemaDecodeError ? Effect.fail(error) : Effect.die(error)
|
|
2320
|
+
}
|
|
2321
|
+
})
|
|
2322
|
+
|
|
2323
|
+
export const can = (...args: readonly [Machine.Any] | readonly [Machine.Any, Machine.Snapshot<any>, unknown]) => {
|
|
2324
|
+
const query = (state: Machine.Snapshot<any>, event: unknown) => schemaEffect(() => canSync(args[0], state, event))
|
|
2325
|
+
return args.length === 1 ? query : query(args[1], args[2])
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2303
2328
|
export const plan = (machine: Machine.Any, state: Machine.Snapshot<any>, event: unknown) =>
|
|
2304
2329
|
planningEffect(() => planSync(machine as any, state, event as any))
|
|
2305
2330
|
|
|
@@ -358,6 +358,7 @@ type ChildSnapshot<Child extends Machine.ChildMachine.Any> = Machine.Machine.Sna
|
|
|
358
358
|
|
|
359
359
|
const InvalidSelectorPathTypeId = "~effect/reactivity/AtomMachine/InvalidSelectorPath"
|
|
360
360
|
const SelectorProjectionTypeId = "~effect/reactivity/AtomMachine/SelectorProjection"
|
|
361
|
+
const InvalidCanEventTypeId = "~effect/reactivity/AtomMachine/InvalidCanEvent"
|
|
361
362
|
|
|
362
363
|
type SelectorProjectionKind =
|
|
363
364
|
| "select"
|
|
@@ -374,6 +375,30 @@ interface SelectorProjection<Kind extends SelectorProjectionKind, Path extends s
|
|
|
374
375
|
}
|
|
375
376
|
}
|
|
376
377
|
|
|
378
|
+
type EnsureCanEvent<AcceptedEvent, Input> = [Input] extends [AcceptedEvent] ? unknown : {
|
|
379
|
+
readonly [InvalidCanEventTypeId]: {
|
|
380
|
+
readonly input: Input
|
|
381
|
+
readonly accepted: AcceptedEvent
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
interface CanProjection<Input> {
|
|
386
|
+
<
|
|
387
|
+
State extends Machine.Machine.AtomicSnapshot<string, unknown>,
|
|
388
|
+
AcceptedEvent,
|
|
389
|
+
Error,
|
|
390
|
+
Output,
|
|
391
|
+
StartError,
|
|
392
|
+
Emitted
|
|
393
|
+
>(
|
|
394
|
+
self:
|
|
395
|
+
& MachineAtom<State, AcceptedEvent, Error, Output, StartError, Emitted>
|
|
396
|
+
& EnsureCanEvent<AcceptedEvent, Input>
|
|
397
|
+
): Atom.Atom<
|
|
398
|
+
AsyncResult.AsyncResult<boolean, StartError | Error | Machine.MachineSchemaDecodeError>
|
|
399
|
+
>
|
|
400
|
+
}
|
|
401
|
+
|
|
377
402
|
type EnsureSelectorPath<State, Path extends string> = [Path] extends [SnapshotIdentifier<State>] ? unknown : {
|
|
378
403
|
readonly [InvalidSelectorPathTypeId]: Path
|
|
379
404
|
}
|
|
@@ -695,6 +720,37 @@ export const matches: {
|
|
|
695
720
|
): Atom.Atom<AsyncResult.AsyncResult<boolean, StartError | Error>>
|
|
696
721
|
} = dual(2, internal.matches)
|
|
697
722
|
|
|
723
|
+
/**
|
|
724
|
+
* Reactively tests whether a concrete event would be accepted by a running
|
|
725
|
+
* machine.
|
|
726
|
+
*
|
|
727
|
+
* Declare the returned projection once, then apply it to compatible machine
|
|
728
|
+
* bridges. Repeated applications to the same bridge return the same atom. An
|
|
729
|
+
* event atom is read reactively when acceptance depends on a changing payload.
|
|
730
|
+
*
|
|
731
|
+
* Startup remains in the source `AsyncResult`. Active snapshots use
|
|
732
|
+
* {@link Machine.can}; done and stopped snapshots produce `false`, while
|
|
733
|
+
* runtime and schema failures remain in the typed failure channel.
|
|
734
|
+
*
|
|
735
|
+
* **Example**
|
|
736
|
+
*
|
|
737
|
+
* ```ts
|
|
738
|
+
* const submitAllowed = AtomMachine.can(AuthEvents.Submitted())
|
|
739
|
+
* const canSubmitAtom = submitAllowed(authMachineAtom)
|
|
740
|
+
*
|
|
741
|
+
* const submitEvent = Atom.map(draftAtom, (draft) =>
|
|
742
|
+
* AuthEvents.Submitted({ draft }))
|
|
743
|
+
* const reactiveSubmitAllowed = AtomMachine.can(submitEvent)
|
|
744
|
+
* ```
|
|
745
|
+
*
|
|
746
|
+
* @category combinators
|
|
747
|
+
* @since 0.31.0
|
|
748
|
+
*/
|
|
749
|
+
export const can: {
|
|
750
|
+
<Input>(event: Atom.Atom<Input>): CanProjection<Input>
|
|
751
|
+
<const Input>(event: Input): CanProjection<Input>
|
|
752
|
+
} = internal.can
|
|
753
|
+
|
|
698
754
|
/**
|
|
699
755
|
* Returns whether a state path is active in a directly owned child.
|
|
700
756
|
*
|
|
@@ -906,7 +962,7 @@ type ResumedMachineAtomOf<M extends Machine.Machine.Any, RuntimeError> = Machine
|
|
|
906
962
|
>
|
|
907
963
|
|
|
908
964
|
/**
|
|
909
|
-
*
|
|
965
|
+
* `AtomMachine` constructors bound to one owned Effect runtime.
|
|
910
966
|
*
|
|
911
967
|
* @category models
|
|
912
968
|
* @since 0.4.0
|
|
@@ -929,6 +985,22 @@ export interface Bound<Services, RuntimeError = never> {
|
|
|
929
985
|
...args: MachineInputArgsOf<M>
|
|
930
986
|
) => MachineAtomOf<M, RuntimeError>
|
|
931
987
|
|
|
988
|
+
/**
|
|
989
|
+
* Specializes a machine definition into a reusable bridge constructor.
|
|
990
|
+
*
|
|
991
|
+
* Every call creates an independent machine bridge. Startup remains lazy
|
|
992
|
+
* and begins only when an `AtomRegistry` reads or mounts the bridge.
|
|
993
|
+
*
|
|
994
|
+
* @since 0.30.0
|
|
995
|
+
*/
|
|
996
|
+
readonly factory: <M extends Machine.Machine.Any>(
|
|
997
|
+
machine:
|
|
998
|
+
& M
|
|
999
|
+
& EnsureBoundRequirements<Services, NoInfer<M>>
|
|
1000
|
+
& EnsureMachineExecutable<NoInfer<M>>
|
|
1001
|
+
& Machine.Machine.RootCompatible<Machine.Machine.ParentEvents<NoInfer<M>>>
|
|
1002
|
+
) => (...args: MachineInputArgsOf<M>) => MachineAtomOf<M, RuntimeError>
|
|
1003
|
+
|
|
932
1004
|
/** Creates a lazy bridge from a decoded logical snapshot. */
|
|
933
1005
|
readonly resume: <M extends Machine.Machine.Any>(
|
|
934
1006
|
machine:
|
|
@@ -1140,6 +1212,33 @@ export const make: {
|
|
|
1140
1212
|
>
|
|
1141
1213
|
} = internal.make
|
|
1142
1214
|
|
|
1215
|
+
/**
|
|
1216
|
+
* Specializes a machine definition into a reusable bridge constructor.
|
|
1217
|
+
*
|
|
1218
|
+
* The returned function preserves the machine's startup input arity and exact
|
|
1219
|
+
* bridge type. Every call creates a fresh `MachineAtom`; it does not cache by
|
|
1220
|
+
* input or start the machine before an `AtomRegistry` reads or mounts it.
|
|
1221
|
+
*
|
|
1222
|
+
* **Example**
|
|
1223
|
+
*
|
|
1224
|
+
* ```ts
|
|
1225
|
+
* const makeSearchMachine = AtomMachine.factory(searchMachine)
|
|
1226
|
+
* const search = makeSearchMachine({ query: "effect" })
|
|
1227
|
+
*
|
|
1228
|
+
* type SearchMachineAtom = ReturnType<typeof makeSearchMachine>
|
|
1229
|
+
* ```
|
|
1230
|
+
*
|
|
1231
|
+
* @category constructors
|
|
1232
|
+
* @since 0.30.0
|
|
1233
|
+
*/
|
|
1234
|
+
export const factory: <M extends Machine.Machine.Any>(
|
|
1235
|
+
machine:
|
|
1236
|
+
& M
|
|
1237
|
+
& EnsureNoExternalRequirements<MachineRequirementsOf<NoInfer<M>>>
|
|
1238
|
+
& EnsureMachineExecutable<NoInfer<M>>
|
|
1239
|
+
& Machine.Machine.RootCompatible<Machine.Machine.ParentEvents<NoInfer<M>>>
|
|
1240
|
+
) => (...args: MachineInputArgsOf<M>) => MachineAtomOf<M, never> = internal.factory
|
|
1241
|
+
|
|
1143
1242
|
/**
|
|
1144
1243
|
* Creates a lazy atom bridge from a decoded logical snapshot.
|
|
1145
1244
|
*
|
|
@@ -1162,11 +1261,12 @@ export const resume: {
|
|
|
1162
1261
|
} = internal.resume
|
|
1163
1262
|
|
|
1164
1263
|
/**
|
|
1165
|
-
*
|
|
1264
|
+
* Binds `AtomMachine` constructors to a shared Effect runtime.
|
|
1166
1265
|
*
|
|
1167
1266
|
* Use this when an application runs many machines from the same service layer.
|
|
1168
|
-
* The returned
|
|
1169
|
-
* while every call to `make` still creates an
|
|
1267
|
+
* The returned interface keeps runtime provisioning at the composition seam,
|
|
1268
|
+
* while every call to `make` or a specialized `factory` still creates an
|
|
1269
|
+
* independent machine bridge.
|
|
1170
1270
|
*
|
|
1171
1271
|
* @category constructors
|
|
1172
1272
|
* @since 0.4.0
|