Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions .prettierignore

This file was deleted.

5 changes: 0 additions & 5 deletions .prettierrc.json

This file was deleted.

3 changes: 3 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["dprint.dprint"]
}
28 changes: 28 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"dprint.path": "node_modules/.bin/dprint",
"editor.formatOnSave": true,
"editor.defaultFormatter": "dprint.dprint",
"editor.formatOnSaveMode": "file",
"files.insertFinalNewline": true,
"[typescript]": {
"editor.defaultFormatter": "dprint.dprint"
},
"[typescriptreact]": {
"editor.defaultFormatter": "dprint.dprint"
},
"[javascript]": {
"editor.defaultFormatter": "dprint.dprint"
},
"[javascriptreact]": {
"editor.defaultFormatter": "dprint.dprint"
},
"[json]": {
"editor.defaultFormatter": "dprint.dprint"
},
"[jsonc]": {
"editor.defaultFormatter": "dprint.dprint"
},
"[markdown]": {
"editor.defaultFormatter": "dprint.dprint"
}
}
32 changes: 18 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ require upgrading Effect in lockstep; do not override the peer to another beta.

```ts
import { Machine } from "@typeonce/effect-machine"
import { AtomMachine } from "@typeonce/effect-machine/reactivity"
import { ClusterMachine } from "@typeonce/effect-machine/cluster"
import { AtomMachine } from "@typeonce/effect-machine/reactivity"
```

Each ESM entrypoint is independent and tree-shakeable. Importing the root does
Expand All @@ -37,8 +37,8 @@ property contains the individual tagged schemas, and each case has a typed
`make` constructor.

```ts
import { Schema } from "effect"
import { Machine } from "@typeonce/effect-machine"
import { Schema } from "effect"

const State = Schema.TaggedUnion({
Idle: {},
Expand Down Expand Up @@ -256,7 +256,9 @@ effects in `Machine.action`; actions are staged during planning and run by the
managed runtime before it publishes the next state.

```ts
Save: ({ target }) => Machine.action(writeAuditLog, target.local.Saving.from())
const handlers = {
Save: ({ target }) => Machine.action(writeAuditLog, target.local.Saving.from())
}
```

The one-argument form returns `void` after staging. The two-argument form
Expand All @@ -283,16 +285,18 @@ state interrupts the child. For a one-shot Effect, `Machine.invokeEffect` maps
typed success and failure values directly to internal events:

```ts
invoke: ({ state }) =>
Machine.invokeEffect({
id: "save",
effect: save(state),
onSuccess: (entry) => InternalEvent.cases.Saved.make({ id: entry.id }),
onFailure: (error) =>
InternalEvent.cases.SaveFailed.make({
message: String(error)
})
})
const loading = {
invoke: ({ state }) =>
Machine.invokeEffect({
id: "save",
effect: save(state),
onSuccess: (entry) => InternalEvent.cases.Saved.make({ id: entry.id }),
onFailure: (error) =>
InternalEvent.cases.SaveFailed.make({
message: String(error)
})
})
}
```

Omit `onFailure` when the Effect cannot fail. Defects and interruption remain
Expand Down Expand Up @@ -344,8 +348,8 @@ Exporting one descriptor remains the clearest module boundary.
disposing the registry-owned reference stops it.

```ts
import { Atom } from "effect/unstable/reactivity"
import { AtomMachine } from "@typeonce/effect-machine/reactivity"
import { Atom } from "effect/unstable/reactivity"

const runtime = Atom.runtime(AppLayer)
const machines = AtomMachine.bind(runtime)
Expand Down
32 changes: 32 additions & 0 deletions dprint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"$schema": "https://dprint.dev/schemas/v0.json",
"incremental": false,
"includes": ["**/*.{ts,tsx,js,jsx,json,md}"],
"indentWidth": 2,
"lineWidth": 120,
"newLineKind": "lf",
"typescript": {
"semiColons": "asi",
"quoteStyle": "alwaysDouble",
"trailingCommas": "never",
"operatorPosition": "maintain",
"arrowFunction.useParentheses": "force"
},
"excludes": [
"LLMS.md",
"**/dist",
"**/build",
"**/docs",
"**/coverage",
"packages/**/CHANGELOG.md",
"!scratchpad/**/*",
".agents",
".context",
".specs"
],
"plugins": [
"https://plugins.dprint.dev/typescript-0.93.4.wasm",
"https://plugins.dprint.dev/markdown-0.20.0.wasm",
"https://plugins.dprint.dev/json-0.21.1.wasm"
]
}
68 changes: 30 additions & 38 deletions examples/platformer/src/machine.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import { Machine } from "@typeonce/effect-machine"
import { Effect, Schema } from "effect"

// Domain schemas are shared by state payloads and the public physics protocol.
export const Axis = Schema.Literals([-1, 0, 1])
Expand Down Expand Up @@ -127,12 +127,11 @@ const initialCharacter = () =>
character
.locomotion(State.cases.Locomotion.make({}), (locomotion) =>
locomotion.Grounded(State.cases.Grounded.make({}), (grounded) =>
grounded.Standing(State.cases.Standing.make({}))
)
)
.facing(State.cases.Facing.make({}), (facing) => facing.Right(State.cases.Right.make({})))
.contact(State.cases.WallContact.make({}), (contact) => contact.NoWall(State.cases.NoWall.make({})))
)
grounded.Standing(State.cases.Standing.make({}))))
.facing(State.cases.Facing.make({}), (facing) =>
facing.Right(State.cases.Right.make({})))
.contact(State.cases.WallContact.make({}), (contact) =>
contact.NoWall(State.cases.NoWall.make({}))))

export const CharacterMachine = Machine.make({
id: "PlatformerCharacter",
Expand All @@ -159,22 +158,17 @@ export const CharacterMachine = Machine.make({
.motion(State.cases.Motion.make({}), (motion) =>
motion.Jumping(
State.cases.Jumping.make({ startedAt: event.at, push: 0, kind: "Ground" })
)
)
))
.airJump(State.cases.AirJump.make({}), (airJump) =>
airJump.AirJumpGroundLock(State.cases.AirJumpGroundLock.make({}))
)
)
)
.facing(State.cases.Facing.make({}), (facing) => facing.Right(State.cases.Right.make({})))
airJump.AirJumpGroundLock(State.cases.AirJumpGroundLock.make({})))))
.facing(State.cases.Facing.make({}), (facing) =>
facing.Right(State.cases.Right.make({})))
.contact(State.cases.WallContact.make({}), (contact) =>
event.wall === -1
? contact.LeftWall(State.cases.LeftWall.make({}))
: event.wall === 1
? contact.RightWall(State.cases.RightWall.make({}))
: contact.NoWall(State.cases.NoWall.make({}))
)
)
? contact.RightWall(State.cases.RightWall.make({}))
: contact.NoWall(State.cases.NoWall.make({}))))
},
states: {
Standing: {
Expand Down Expand Up @@ -220,7 +214,7 @@ export const CharacterMachine = Machine.make({
},
Airborne: {
on: {
JumpPressed: Effect.fn(function* ({ event, runtime }) {
JumpPressed: Effect.fn(function*({ event, runtime }) {
const machine = yield* runtime
const push = awayFrom(event.wall)
yield* machine.raise(
Expand All @@ -230,14 +224,16 @@ export const CharacterMachine = Machine.make({
)
}),
Landed: ({ event, target }) =>
target.branch.Character.locomotion.Grounded(State.cases.Grounded.make({}), (grounded) =>
grounded.Landing(
State.cases.Landing.make({
impact: event.impact,
resumeAxis: event.axis,
landedAt: event.at
})
)
target.branch.Character.locomotion.Grounded(
State.cases.Grounded.make({}),
(grounded) =>
grounded.Landing(
State.cases.Landing.make({
impact: event.impact,
resumeAxis: event.axis,
landedAt: event.at
})
)
)
},
states: {
Expand Down Expand Up @@ -274,8 +270,7 @@ export const CharacterMachine = Machine.make({
on: {
WallJump: {
reenter: true,
transition: ({ target }) =>
target.local.AirJumpWallLock(State.cases.AirJumpWallLock.make({}))
transition: ({ target }) => target.local.AirJumpWallLock(State.cases.AirJumpWallLock.make({}))
}
},
states: {
Expand All @@ -284,22 +279,20 @@ export const CharacterMachine = Machine.make({
id: "ground-air-jump-unlock"
}),
on: {
AirJumpUnlocked: ({ target }) =>
target.local.AirJumpReady(State.cases.AirJumpReady.make({}))
AirJumpUnlocked: ({ target }) => target.local.AirJumpReady(State.cases.AirJumpReady.make({}))
}
},
AirJumpWallLock: {
invoke: Machine.after("240 millis", InternalEvent.cases.AirJumpUnlocked.make({}), {
id: "wall-air-jump-unlock"
}),
on: {
AirJumpUnlocked: ({ target }) =>
target.local.AirJumpReady(State.cases.AirJumpReady.make({}))
AirJumpUnlocked: ({ target }) => target.local.AirJumpReady(State.cases.AirJumpReady.make({}))
}
},
AirJumpReady: {
on: {
TryAirJump: Effect.fn(function* ({ event, runtime, target }) {
TryAirJump: Effect.fn(function*({ event, runtime, target }) {
const machine = yield* runtime
yield* machine.raise(InternalEvent.cases.DoubleJump.make({ at: event.at }))
return target.local.AirJumpSpent(State.cases.AirJumpSpent.make({}))
Expand All @@ -325,8 +318,7 @@ export const CharacterMachine = Machine.make({
},
Right: {
on: {
Move: ({ event, target }) =>
event.axis === -1 ? target.local.Left(State.cases.Left.make({})) : undefined,
Move: ({ event, target }) => event.axis === -1 ? target.local.Left(State.cases.Left.make({})) : undefined,
WallJump: ({ event, target }) =>
event.push === -1 ? target.local.Left(State.cases.Left.make({})) : undefined
}
Expand All @@ -339,8 +331,8 @@ export const CharacterMachine = Machine.make({
event.wall === -1
? target.local.LeftWall(State.cases.LeftWall.make({}))
: event.wall === 1
? target.local.RightWall(State.cases.RightWall.make({}))
: target.local.NoWall(State.cases.NoWall.make({}))
? target.local.RightWall(State.cases.RightWall.make({}))
: target.local.NoWall(State.cases.NoWall.make({}))
},
states: {
NoWall: {},
Expand Down
10 changes: 5 additions & 5 deletions examples/platformer/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import "./styles.css"
import { Effect, Fiber, Stream } from "effect"
import { Machine } from "@typeonce/effect-machine"
import { Effect, Fiber, Stream } from "effect"
import { GameAdapter } from "./game.ts"
import {
activeStateData,
airJumpMode,
type CharacterEvent,
CharacterMachine,
type CharacterSnapshot,
facingDirection,
locomotionBranch,
locomotionMode,
wallContact,
type CharacterEvent,
type CharacterSnapshot
wallContact
} from "./machine.ts"

const requiredElement = <ElementType extends Element>(selector: string) => {
Expand Down Expand Up @@ -59,7 +59,7 @@ const publish = (next: CharacterSnapshot) => {
})
}

const program = Effect.gen(function* () {
const program = Effect.gen(function*() {
const actor = yield* Machine.start(CharacterMachine)
deliver = (event) => Effect.runFork(actor.send(event).pipe(Effect.catchTag("StoppedError", () => Effect.void)))
publish(yield* actor.state)
Expand Down
6 changes: 3 additions & 3 deletions examples/pokemon/src/machine.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Effect, Schema } from "effect"
import { Machine } from "@typeonce/effect-machine"
import { Atom } from "effect/unstable/reactivity"
import { AtomMachine } from "@typeonce/effect-machine/reactivity"
import { Effect, Schema } from "effect"
import { Atom } from "effect/unstable/reactivity"
import { ReplaceMachine } from "./machines/replace.ts"
import { SelectionMachine } from "./machines/selection.ts"
import { Pokemon, PokemonService, ReplaceInTeam } from "./pokemon.ts"
Expand All @@ -18,7 +18,7 @@ export const ReplaceChild = Machine.child("replace", ReplaceMachine)
const machine = Machine.make({
states: States.states,
events: [ReplaceInTeam],
initial: Effect.fn(function* () {
initial: Effect.fn(function*() {
const pk = yield* PokemonService
const team = yield* pk.getRandomTeam()
return States.initial.ActiveTeam(new ActiveTeam({ team }))
Expand Down
4 changes: 2 additions & 2 deletions examples/pokemon/src/machines/replace.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import { Machine } from "@typeonce/effect-machine"
import { Effect, Schema } from "effect"
import { Pokemon, PokemonService, ReplaceInTeam } from "../pokemon.ts"

class Idle extends Schema.TaggedClass<Idle>("Idle")("Idle", {}) {}
Expand All @@ -24,7 +24,7 @@ const ReplaceWithRandomMachine = Machine.invoke({
Machine.effect(
Effect.sleep("500 millis").pipe(
Effect.andThen(
Effect.gen(function* () {
Effect.gen(function*() {
const pk = yield* PokemonService
const pokemon = yield* pk.getRandomPokemon()
return new Replaced({ pokemon })
Expand Down
10 changes: 4 additions & 6 deletions examples/pokemon/src/machines/selection.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Effect, Option, Schema } from "effect"
import { Machine } from "@typeonce/effect-machine"
import { Effect, Option, Schema } from "effect"
import { Pokemon, PokemonService, ReplaceInTeam } from "../pokemon.ts"

class Form extends Schema.TaggedClass<Form>("Form")("Form", {}) {}
Expand Down Expand Up @@ -49,7 +49,7 @@ const SearchMachine = ({ searchText }: { searchText: string }) =>
Machine.effect(
Effect.sleep("500 millis").pipe(
Effect.andThen(
Effect.gen(function* () {
Effect.gen(function*() {
const pk = yield* PokemonService
const pokemon = yield* pk.getByName(searchText)
return new SearchResult({ result: pokemon })
Expand Down Expand Up @@ -95,8 +95,7 @@ export const SelectionMachine = Machine.make({
SelectionStates.initial.form(new Form(), (form) =>
form
.search(new Search({ searchText: "" }), (search) => search.NoPokemon(new NoPokemon()))
.selection(new Selection(), (selection) => selection.Unselected(new Unselected()))
)
.selection(new Selection(), (selection) => selection.Unselected(new Unselected())))
}).handle({
form: {
states: {
Expand All @@ -117,8 +116,7 @@ export const SelectionMachine = Machine.make({
target.full.form(new Form(), (form) =>
form
.search(new Search({ searchText: "" }), (search) => search.NoPokemon(new NoPokemon()))
.selection(new Selection(), (selection) => selection.Unselected(new Unselected()))
)
.selection(new Selection(), (selection) => selection.Unselected(new Unselected())))
)
)
}
Expand Down
Loading