diff --git a/.changeset/bright-machines-remember.md b/.changeset/bright-machines-remember.md
new file mode 100644
index 0000000..3955e10
--- /dev/null
+++ b/.changeset/bright-machines-remember.md
@@ -0,0 +1,5 @@
+---
+"@typeonce/effect-machine": minor
+---
+
+Add fully typed shallow and deep history states. History targets restore schema-validated state values, support typed defaults before the first capture, require only the initializers needed by shallow restoration, preserve parallel configurations, and round-trip through snapshot encoding and decoding.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e749014..4e612c3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -42,3 +42,25 @@ jobs:
- run: pnpm --dir ../.. build
- run: pnpm install --frozen-lockfile
- run: pnpm check
+
+ platformer-example:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: examples/platformer
+ steps:
+ - uses: actions/checkout@v7
+ - uses: pnpm/action-setup@v6
+ with:
+ package_json_file: examples/platformer/package.json
+ - uses: actions/setup-node@v7
+ with:
+ node-version: 24
+ cache: pnpm
+ cache-dependency-path: |
+ pnpm-lock.yaml
+ examples/platformer/pnpm-lock.yaml
+ - run: pnpm --dir ../.. install --frozen-lockfile
+ - run: pnpm --dir ../.. build
+ - run: pnpm install --frozen-lockfile
+ - run: pnpm check
diff --git a/README.md b/README.md
index d2f7330..85987c3 100644
--- a/README.md
+++ b/README.md
@@ -147,7 +147,7 @@ masquerade as an internal result.
## Statechart structure
-`Machine.defineStates` accepts atomic, compound, parallel, and final state
+`Machine.defineStates` accepts atomic, compound, parallel, final, and history
nodes:
```ts
@@ -205,6 +205,71 @@ Put data on the narrowest state where it is valid. If several sibling phases
share data, prefer storing it on their compound parent instead of copying it
into every child state.
+### History states
+
+A history pseudo-state remembers the last active configuration of its parent.
+It has no value schema and never appears in an active snapshot. History is
+shallow by default; use `history: "deep"` to retain the complete descendant
+configuration and its validated values:
+
+```ts
+const States = Machine.defineStates({
+ checkout: {
+ schema: Checkout,
+ initial: "shipping",
+ states: {
+ shipping: Shipping,
+ payment: {
+ schema: Payment,
+ initial: "cardEntry",
+ states: {
+ cardEntry: CardEntry,
+ verifying: Verifying
+ }
+ },
+ resume: { type: "history", history: "deep" }
+ }
+ },
+ support: Support
+})
+```
+
+Implement a typed default for the first transition before any configuration
+has been remembered, then target history without supplying a state value:
+
+```ts
+machine.handle({
+ checkout: {
+ history: {
+ resume: {
+ default: () => initialCheckoutSnapshot
+ }
+ }
+ },
+ support: {
+ on: {
+ Resume: ({ target }) => target.history.checkout.resume()
+ }
+ }
+})
+```
+
+Deep history restores every remembered descendant value. Shallow history
+restores the parent and direct-child values, then follows normal initial paths.
+Only compound or parallel states that shallow restoration can enter implicitly
+need an `initial` handler to construct those new child values:
+
+```ts
+payment: {
+ initial: ;
+ ;(({ state }) => new CardEntry({ attempt: state.attempt, cardNumber: "" }))
+}
+```
+
+Execution APIs remain unavailable until required history defaults and shallow
+initializers have been implemented. History records are part of logical
+snapshots and are schema-validated by `encodeSnapshot` and `decodeSnapshot`.
+
Transition between structurally related tagged states with `Machine.retag`.
The source `_tag` is discarded, compatible fields are reused, and missing or
incompatible required fields must be supplied:
@@ -215,13 +280,14 @@ const saving = Machine.retag(State.cases.Saving, editing)
## Choosing a target builder
-Transition contexts expose three typed target builders:
+Transition contexts expose four typed target builders:
-| Builder | Destination | Configuration behavior |
-| --------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
-| `target.local` | Inside the source's nearest compound scope | Keeps the compound value, active ancestors, and unrelated parallel regions |
-| `target.branch` | Anywhere under the source's active top-level root | Replaces the selected branch while keeping omitted active ancestor values and parallel regions |
-| `target.full` | Any top-level root | Builds a complete active snapshot for the selected root |
+| Builder | Destination | Configuration behavior |
+| ---------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
+| `target.local` | Inside the source's nearest compound scope | Keeps the compound value, active ancestors, and unrelated parallel regions |
+| `target.branch` | Anywhere under the source's active top-level root | Replaces the selected branch while keeping omitted active ancestor values and parallel regions |
+| `target.full` | Any top-level root | Builds a complete active snapshot for the selected root |
+| `target.history` | A declared history pseudo-state | Restores its parent's remembered configuration or runs its typed default |
When `target.local` or `target.branch` enters an inactive nested parallel
state, its callback must select every region, just like `initial` and
@@ -404,9 +470,9 @@ restrictions and delivery guarantees are documented on that API.
## Current limits
-History states and declarative first-class guards are not part of the current
-API. Ordinary TypeScript conditions implement guards. Use `Machine.after` for a
-cancellable state-scoped delayed event.
+Declarative first-class guards are not part of the current API. Ordinary
+TypeScript conditions implement guards. Use `Machine.after` for a cancellable
+state-scoped delayed event.
## Guidance for agents and contributors
@@ -434,8 +500,9 @@ TypeScript consumer with `skipLibCheck: false`.
The [platformer statechart example](./examples/platformer) is a playable SVG
demo centered on a schema-first character machine. It demonstrates nested
compound locomotion, parallel airborne motion and air-jump regions, independent
-facing and wall-contact regions, typed protocol events, state-scoped timers,
-and state-driven SVG transforms.
+facing and wall-contact regions, a pause/resume flow backed by typed deep
+history, typed protocol events, state-scoped timers, and state-driven SVG
+transforms.
The [Pokémon statechart example](./examples/pokemon) is a standalone React and
Vite project demonstrating compound and parallel states, state-scoped invokes,
diff --git a/docs/agent-guide.md b/docs/agent-guide.md
index 568ed59..f825b9c 100644
--- a/docs/agent-guide.md
+++ b/docs/agent-guide.md
@@ -104,7 +104,7 @@ its extra control is required:
the same operation with a returned transition value, not a separate action
API.
-## Atomic, compound, and parallel states
+## Atomic, compound, parallel, and history states
Use an atomic state when no child phase can be active beneath it.
@@ -201,13 +201,74 @@ const machine = Machine.make({
Do not repeat `type: "final"` in `handle`. Execution APIs reject a machine
until every declared output schema has an implementation.
+Declare a history pseudo-state below the active parent whose configuration it
+should remember. It has no schema, is excluded from active state identifiers,
+and is addressed only through `target.history`:
+
+```ts
+const States = Machine.defineStates({
+ checkout: {
+ schema: Checkout,
+ initial: "shipping",
+ states: {
+ shipping: Shipping,
+ payment: {
+ schema: Payment,
+ initial: "cardEntry",
+ states: {
+ cardEntry: CardEntry,
+ verifying: Verifying
+ }
+ },
+ recent: { type: "history" },
+ exact: { type: "history", history: "deep" }
+ }
+ },
+ support: Support
+})
+```
+
+Every history node needs a default parent snapshot for the first use:
+
+```ts
+checkout: {
+ history: {
+ recent: { default: () => initialCheckoutSnapshot },
+ exact: { default: () => initialCheckoutSnapshot }
+ }
+}
+```
+
+Target it without a value:
+
+```ts
+Resume: ({ target }) => target.history.checkout.exact()
+```
+
+Deep history restores the complete remembered subtree and its decoded values.
+Shallow history restores only parent and direct-child values. If the remembered
+child is compound, its configured initial child needs a freshly constructed
+value, so implement `initial` only on paths required by shallow history:
+
+```ts
+payment: {
+ initial: ({ state }) => new CardEntry({ attempt: state.attempt, cardNumber: "" })
+}
+```
+
+The machine's readiness type tracks missing defaults and shallow initializers.
+History is an overwriteable register, not a stack: restoration does not consume
+it, and the next parent exit replaces it. Entry actions and invokes run again;
+prior effects, actors, and timers are not rewound.
+
## Choosing a target
-| Builder | Use it when | What it preserves |
-| --------------- | -------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
-| `target.local` | The destination is inside the nearest compound scope containing the source | The compound value, active ancestors, and unrelated parallel regions |
-| `target.branch` | The destination is elsewhere under the active top-level root | Omitted current ancestor values and parallel regions |
-| `target.full` | The destination may be under any top-level root | Nothing is inferred for a newly selected root; build its complete active snapshot |
+| Builder | Use it when | What it preserves |
+| ---------------- | -------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
+| `target.local` | The destination is inside the nearest compound scope containing the source | The compound value, active ancestors, and unrelated parallel regions |
+| `target.branch` | The destination is elsewhere under the active top-level root | Omitted current ancestor values and parallel regions |
+| `target.full` | The destination may be under any top-level root | Nothing is inferred for a newly selected root; build its complete active snapshot |
+| `target.history` | The destination is a declared history pseudo-state | Its parent's remembered configuration, or its default before the first capture |
Entering an inactive parallel state through `target.local` or `target.branch`
requires a complete callback with one selection per region. A parallel state
@@ -611,10 +672,9 @@ a deeper statechart instead of casting away the diagnostic.
The current API does not include:
-- history states;
- declarative first-class guards;
- a complete inspectable graph for arbitrary transition Effects.
Use ordinary TypeScript conditions for guards and `Machine.after` for
state-scoped timers. Do not invent undocumented state-node properties such as
-`guard` or `history`.
+`guard`.
diff --git a/examples/platformer/README.md b/examples/platformer/README.md
index 9075310..0a3331b 100644
--- a/examples/platformer/README.md
+++ b/examples/platformer/README.md
@@ -21,25 +21,44 @@ pnpm check
- **W**, **up**, or **Space** — jump; press again once in the air for a double jump
- Touch either wall and jump — turn and kick away; repeat after returning to a wall
- **S** or **down** — duck while grounded; dive while airborne
+- **P** — pause and resume the exact playable configuration through deep history
- **R** — reset
## Statechart
`Character` is parallel: `locomotion`, `facing`, and `contact` update
-independently. The locomotion region is compound and makes `Grounded` and
-`Airborne` mutually exclusive. Each branch is compound again:
+independently. The locomotion region switches between `Playing` and `Paused`.
+Inside `Playing`, `Grounded` and `Airborne` are mutually exclusive. Each branch
+is compound again:
```text
Character (parallel)
├─ locomotion
-│ ├─ Grounded: Standing | Running | Ducking | Landing
-│ └─ Airborne (parallel)
-│ ├─ motion: Jumping | Falling | Diving
-│ └─ airJump: GroundLock | WallLock | Ready | Spent
+│ ├─ Playing
+│ │ ├─ Grounded: Standing | Running | Ducking | Landing
+│ │ ├─ Airborne (parallel)
+│ │ │ ├─ motion: Jumping | Falling | Diving
+│ │ │ └─ airJump: GroundLock | WallLock | Ready | Spent
+│ │ └─ resume (deep history)
+│ └─ Paused
├─ facing: Left | Right
└─ contact: NoWall | LeftWall | RightWall
```
+`Pause` exits `Playing`, which records its current deep configuration. Physics
+stops while `Paused`. `Resume` targets `Playing.resume`, restoring both the
+active descendants and their typed values: for example, an airborne wall jump
+returns with its `originY`, `startedAt`, `push`, jump kind, and air-jump lock.
+This is one saved configuration, not an undo stack; pausing again replaces the
+previous history. The history implementation also supplies a typed default
+`Playing` snapshot for the case where the history node is targeted before the
+region has ever been exited.
+
+State-scoped invocations follow normal statechart entry/exit semantics. Pausing
+cancels an active landing or air-jump timer, and restoring that state starts its
+invocation again. History restores state configuration and values, not elapsed
+wall-clock time or the adapter's past events.
+
State payloads live only where they are valid: `Landing` owns impact and resume
direction, while `Airborne` owns only the jump origin. Air-jump availability is
modeled entirely as state: lock states own cancellable readiness timers,
diff --git a/examples/platformer/index.html b/examples/platformer/index.html
index 555ce7d..5315be9 100644
--- a/examples/platformer/index.html
+++ b/examples/platformer/index.html
@@ -53,11 +53,17 @@
Orbit Courier
+
+
+ PAUSED
+ P resumes from deep history
+
AD move
W/Space jump ×2
S duck / dive
+ P pause / resume
R reset
@@ -72,33 +78,40 @@ Orbit Courier
locomotion
-
-
Grounded
-
- Standing
- Running
- Ducking
- Landing
-
+
+ Playing
+ Paused
+ resume · H*
-
-
Airborne
-
parallel regions
-
-
motion
+
+
+
Grounded
- Jumping
- Falling
- Diving
+ Standing
+ Running
+ Ducking
+ Landing
-
-
air jump
-
-
ground lock
-
wall lock
-
ready
-
spent
+
+
Airborne
+
parallel regions
+
+
motion
+
+ Jumping
+ Falling
+ Diving
+
+
+
+
air jump
+
+ ground lock
+ wall lock
+ ready
+ spent
+
@@ -140,7 +153,8 @@
wall contact
A small adapter owns coordinates and gravity. The machine owns legal behavior, and the box simply transforms to
- show its active state. Wall contact is tracked explicitly, so floor-corner jumps stay ordinary.
+ show its active state. Pause exits Playing; resume targets its deep-history node and restores the
+ exact grounded or airborne configuration, including state-local values.
diff --git a/examples/platformer/package.json b/examples/platformer/package.json
index b0fed9e..eed4cae 100644
--- a/examples/platformer/package.json
+++ b/examples/platformer/package.json
@@ -6,10 +6,11 @@
"type": "module",
"scripts": {
"dev": "vite",
+ "test": "vitest run",
"typecheck": "tsc --noEmit",
"build": "pnpm typecheck && vite build",
"preview": "vite preview",
- "check": "pnpm build"
+ "check": "pnpm test && pnpm build"
},
"dependencies": {
"@typeonce/effect-machine": "file:../..",
@@ -17,7 +18,8 @@
},
"devDependencies": {
"typescript": "6.0.3",
- "vite": "8.1.5"
+ "vite": "8.1.5",
+ "vitest": "4.1.10"
},
"packageManager": "pnpm@10.17.1",
"engines": {
diff --git a/examples/platformer/pnpm-lock.yaml b/examples/platformer/pnpm-lock.yaml
index 4846d66..b1a4472 100644
--- a/examples/platformer/pnpm-lock.yaml
+++ b/examples/platformer/pnpm-lock.yaml
@@ -1,4 +1,4 @@
-lockfileVersion: "9.0"
+lockfileVersion: '9.0'
settings:
autoInstallPeers: true
@@ -8,9 +8,10 @@ overrides:
effect: 4.0.0-beta.102
importers:
+
.:
dependencies:
- "@typeonce/effect-machine":
+ '@typeonce/effect-machine':
specifier: file:../..
version: file:../..(effect@4.0.0-beta.102)
effect:
@@ -23,214 +24,247 @@ importers:
vite:
specifier: 8.1.5
version: 8.1.5(yaml@2.9.0)
+ vitest:
+ specifier: 4.1.10
+ version: 4.1.10(vite@8.1.5(yaml@2.9.0))
packages:
- "@emnapi/core@1.11.1":
- resolution:
- { integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ== }
- "@emnapi/runtime@1.11.1":
- resolution:
- { integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw== }
+ '@emnapi/core@1.11.1':
+ resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==}
+
+ '@emnapi/runtime@1.11.1':
+ resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==}
+
+ '@emnapi/wasi-threads@1.2.2':
+ resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==}
- "@emnapi/wasi-threads@1.2.2":
- resolution:
- { integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA== }
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
- "@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4":
- resolution:
- { integrity: sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ== }
+ '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4':
+ resolution: {integrity: sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==}
cpu: [arm64]
os: [darwin]
- "@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4":
- resolution:
- { integrity: sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w== }
+ '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4':
+ resolution: {integrity: sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==}
cpu: [x64]
os: [darwin]
- "@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4":
- resolution:
- { integrity: sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw== }
+ '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4':
+ resolution: {integrity: sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==}
cpu: [arm64]
os: [linux]
- "@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4":
- resolution:
- { integrity: sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw== }
+ '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4':
+ resolution: {integrity: sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==}
cpu: [arm]
os: [linux]
- "@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4":
- resolution:
- { integrity: sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ== }
+ '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4':
+ resolution: {integrity: sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==}
cpu: [x64]
os: [linux]
- "@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4":
- resolution:
- { integrity: sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ== }
+ '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4':
+ resolution: {integrity: sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==}
cpu: [x64]
os: [win32]
- "@napi-rs/wasm-runtime@1.2.1":
- resolution:
- { integrity: sha512-KjZdi8Q1wh89gsVmghvbrMgWl6ZWmRmHV6wjB7/g4Zf0dyO+hH3neZUtuDNPO00qq5YE5RITVWvrIZKRaAmzGQ== }
- engines: { node: ^20.19.0 || ^22.13.0 || >=23.5.0 }
+ '@napi-rs/wasm-runtime@1.2.1':
+ resolution: {integrity: sha512-KjZdi8Q1wh89gsVmghvbrMgWl6ZWmRmHV6wjB7/g4Zf0dyO+hH3neZUtuDNPO00qq5YE5RITVWvrIZKRaAmzGQ==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0}
peerDependencies:
- "@emnapi/core": ^1.7.1 || ^2.0.0-alpha.3
- "@emnapi/runtime": ^1.7.1 || ^2.0.0-alpha.3
+ '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.3
+ '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.3
- "@oxc-project/types@0.139.0":
- resolution:
- { integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw== }
+ '@oxc-project/types@0.139.0':
+ resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==}
- "@rolldown/binding-android-arm64@1.1.5":
- resolution:
- { integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ== }
- engines: { node: ^20.19.0 || >=22.12.0 }
+ '@rolldown/binding-android-arm64@1.1.5':
+ resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
- "@rolldown/binding-darwin-arm64@1.1.5":
- resolution:
- { integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw== }
- engines: { node: ^20.19.0 || >=22.12.0 }
+ '@rolldown/binding-darwin-arm64@1.1.5':
+ resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
- "@rolldown/binding-darwin-x64@1.1.5":
- resolution:
- { integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g== }
- engines: { node: ^20.19.0 || >=22.12.0 }
+ '@rolldown/binding-darwin-x64@1.1.5':
+ resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==}
+ engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
- "@rolldown/binding-freebsd-x64@1.1.5":
- resolution:
- { integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA== }
- engines: { node: ^20.19.0 || >=22.12.0 }
+ '@rolldown/binding-freebsd-x64@1.1.5':
+ resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
- "@rolldown/binding-linux-arm-gnueabihf@1.1.5":
- resolution:
- { integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw== }
- engines: { node: ^20.19.0 || >=22.12.0 }
+ '@rolldown/binding-linux-arm-gnueabihf@1.1.5':
+ resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
- "@rolldown/binding-linux-arm64-gnu@1.1.5":
- resolution:
- { integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q== }
- engines: { node: ^20.19.0 || >=22.12.0 }
+ '@rolldown/binding-linux-arm64-gnu@1.1.5':
+ resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==}
+ engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
- "@rolldown/binding-linux-arm64-musl@1.1.5":
- resolution:
- { integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA== }
- engines: { node: ^20.19.0 || >=22.12.0 }
+ '@rolldown/binding-linux-arm64-musl@1.1.5':
+ resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
- "@rolldown/binding-linux-ppc64-gnu@1.1.5":
- resolution:
- { integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg== }
- engines: { node: ^20.19.0 || >=22.12.0 }
+ '@rolldown/binding-linux-ppc64-gnu@1.1.5':
+ resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
- "@rolldown/binding-linux-s390x-gnu@1.1.5":
- resolution:
- { integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA== }
- engines: { node: ^20.19.0 || >=22.12.0 }
+ '@rolldown/binding-linux-s390x-gnu@1.1.5':
+ resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
- "@rolldown/binding-linux-x64-gnu@1.1.5":
- resolution:
- { integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ== }
- engines: { node: ^20.19.0 || >=22.12.0 }
+ '@rolldown/binding-linux-x64-gnu@1.1.5':
+ resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
- "@rolldown/binding-linux-x64-musl@1.1.5":
- resolution:
- { integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg== }
- engines: { node: ^20.19.0 || >=22.12.0 }
+ '@rolldown/binding-linux-x64-musl@1.1.5':
+ resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
- "@rolldown/binding-openharmony-arm64@1.1.5":
- resolution:
- { integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw== }
- engines: { node: ^20.19.0 || >=22.12.0 }
+ '@rolldown/binding-openharmony-arm64@1.1.5':
+ resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
- "@rolldown/binding-wasm32-wasi@1.1.5":
- resolution:
- { integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA== }
- engines: { node: ^20.19.0 || >=22.12.0 }
+ '@rolldown/binding-wasm32-wasi@1.1.5':
+ resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
cpu: [wasm32]
- "@rolldown/binding-win32-arm64-msvc@1.1.5":
- resolution:
- { integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw== }
- engines: { node: ^20.19.0 || >=22.12.0 }
+ '@rolldown/binding-win32-arm64-msvc@1.1.5':
+ resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
- "@rolldown/binding-win32-x64-msvc@1.1.5":
- resolution:
- { integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA== }
- engines: { node: ^20.19.0 || >=22.12.0 }
+ '@rolldown/binding-win32-x64-msvc@1.1.5':
+ resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
- "@rolldown/pluginutils@1.0.1":
- resolution:
- { integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw== }
+ '@rolldown/pluginutils@1.0.1':
+ resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
- "@standard-schema/spec@1.1.0":
- resolution:
- { integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== }
+ '@standard-schema/spec@1.1.0':
+ resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
- "@tybys/wasm-util@0.10.3":
- resolution:
- { integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg== }
+ '@tybys/wasm-util@0.10.3':
+ resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==}
- "@typeonce/effect-machine@file:../..":
- resolution: { directory: ../.., type: directory }
- engines: { node: ">=20" }
+ '@typeonce/effect-machine@file:../..':
+ resolution: {directory: ../.., type: directory}
+ engines: {node: '>=20'}
peerDependencies:
effect: 4.0.0-beta.102
+ '@types/chai@5.2.3':
+ resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
+
+ '@types/deep-eql@4.0.2':
+ resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
+
+ '@types/estree@1.0.9':
+ resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
+
+ '@vitest/expect@4.1.10':
+ resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==}
+
+ '@vitest/mocker@4.1.10':
+ resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==}
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^6.0.0 || ^7.0.0 || ^8.0.0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
+
+ '@vitest/pretty-format@4.1.10':
+ resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==}
+
+ '@vitest/runner@4.1.10':
+ resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==}
+
+ '@vitest/snapshot@4.1.10':
+ resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==}
+
+ '@vitest/spy@4.1.10':
+ resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==}
+
+ '@vitest/utils@4.1.10':
+ resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==}
+
+ assertion-error@2.0.1:
+ resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+ engines: {node: '>=12'}
+
+ chai@6.2.2:
+ resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
+ engines: {node: '>=18'}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
detect-libc@2.1.2:
- resolution:
- { integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== }
- engines: { node: ">=8" }
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
effect@4.0.0-beta.102:
- resolution:
- { integrity: sha512-z8Y+Q76Hh/kjLFZrXu8tGn6e+tDsg45R+UHhxd190pXxD53OGwf/G/zDxXTkse4HJ5mobNZfitLfUCp4fMvu6w== }
+ resolution: {integrity: sha512-z8Y+Q76Hh/kjLFZrXu8tGn6e+tDsg45R+UHhxd190pXxD53OGwf/G/zDxXTkse4HJ5mobNZfitLfUCp4fMvu6w==}
+
+ es-module-lexer@2.3.1:
+ resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==}
+
+ estree-walker@3.0.3:
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
+ expect-type@1.4.0:
+ resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
+ engines: {node: '>=12.0.0'}
fast-check@4.9.0:
- resolution:
- { integrity: sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg== }
- engines: { node: ">=12.17.0" }
+ resolution: {integrity: sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg==}
+ engines: {node: '>=12.17.0'}
fdir@6.5.0:
- resolution:
- { integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== }
- engines: { node: ">=12.0.0" }
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
@@ -238,210 +272,207 @@ packages:
optional: true
find-my-way-ts@0.1.6:
- resolution:
- { integrity: sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA== }
+ resolution: {integrity: sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==}
fsevents@2.3.3:
- resolution:
- { integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== }
- engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 }
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
ini@7.0.0:
- resolution:
- { integrity: sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w== }
- engines: { node: ^22.22.2 || ^24.15.0 || >=26.0.0 }
+ resolution: {integrity: sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w==}
+ engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0}
kubernetes-types@1.30.0:
- resolution:
- { integrity: sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q== }
+ resolution: {integrity: sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==}
lightningcss-android-arm64@1.33.0:
- resolution:
- { integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg== }
- engines: { node: ">= 12.0.0" }
+ resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==}
+ engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [android]
lightningcss-darwin-arm64@1.33.0:
- resolution:
- { integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg== }
- engines: { node: ">= 12.0.0" }
+ resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==}
+ engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [darwin]
lightningcss-darwin-x64@1.33.0:
- resolution:
- { integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ== }
- engines: { node: ">= 12.0.0" }
+ resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==}
+ engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [darwin]
lightningcss-freebsd-x64@1.33.0:
- resolution:
- { integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg== }
- engines: { node: ">= 12.0.0" }
+ resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==}
+ engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [freebsd]
lightningcss-linux-arm-gnueabihf@1.33.0:
- resolution:
- { integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ== }
- engines: { node: ">= 12.0.0" }
+ resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==}
+ engines: {node: '>= 12.0.0'}
cpu: [arm]
os: [linux]
lightningcss-linux-arm64-gnu@1.33.0:
- resolution:
- { integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg== }
- engines: { node: ">= 12.0.0" }
+ resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==}
+ engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
lightningcss-linux-arm64-musl@1.33.0:
- resolution:
- { integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ== }
- engines: { node: ">= 12.0.0" }
+ resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==}
+ engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
lightningcss-linux-x64-gnu@1.33.0:
- resolution:
- { integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg== }
- engines: { node: ">= 12.0.0" }
+ resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==}
+ engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
lightningcss-linux-x64-musl@1.33.0:
- resolution:
- { integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw== }
- engines: { node: ">= 12.0.0" }
+ resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==}
+ engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
lightningcss-win32-arm64-msvc@1.33.0:
- resolution:
- { integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA== }
- engines: { node: ">= 12.0.0" }
+ resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==}
+ engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [win32]
lightningcss-win32-x64-msvc@1.33.0:
- resolution:
- { integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA== }
- engines: { node: ">= 12.0.0" }
+ resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==}
+ engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [win32]
lightningcss@1.33.0:
- resolution:
- { integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA== }
- engines: { node: ">= 12.0.0" }
+ resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==}
+ engines: {node: '>= 12.0.0'}
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
msgpackr-extract@3.0.4:
- resolution:
- { integrity: sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw== }
+ resolution: {integrity: sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==}
hasBin: true
msgpackr@2.0.5:
- resolution:
- { integrity: sha512-cef05H/dSYpLpqp3sj/qyZh5vhUYCalnaLO7j1yOmpsR0y/XwLVtK7r5gn+U/F7CTEfMowcGhlUQJDLcLf7jcA== }
+ resolution: {integrity: sha512-cef05H/dSYpLpqp3sj/qyZh5vhUYCalnaLO7j1yOmpsR0y/XwLVtK7r5gn+U/F7CTEfMowcGhlUQJDLcLf7jcA==}
multipasta@0.2.8:
- resolution:
- { integrity: sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q== }
+ resolution: {integrity: sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q==}
nanoid@3.3.16:
- resolution:
- { integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q== }
- engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 }
+ resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
node-gyp-build-optional-packages@5.2.2:
- resolution:
- { integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw== }
+ resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==}
hasBin: true
+ obug@2.1.4:
+ resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==}
+ engines: {node: '>=12.20.0'}
+
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
picocolors@1.1.1:
- resolution:
- { integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== }
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
picomatch@4.0.5:
- resolution:
- { integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A== }
- engines: { node: ">=12" }
+ resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==}
+ engines: {node: '>=12'}
postcss@8.5.25:
- resolution:
- { integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw== }
- engines: { node: ^10 || ^12 || >=14 }
+ resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==}
+ engines: {node: ^10 || ^12 || >=14}
pure-rand@8.4.2:
- resolution:
- { integrity: sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng== }
+ resolution: {integrity: sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==}
rolldown@1.1.5:
- resolution:
- { integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA== }
- engines: { node: ^20.19.0 || >=22.12.0 }
+ resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
+ siginfo@2.0.0:
+ resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+
source-map-js@1.2.1:
- resolution:
- { integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== }
- engines: { node: ">=0.10.0" }
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ stackback@0.0.2:
+ resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
+
+ std-env@4.2.0:
+ resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==}
+
+ tinybench@2.9.0:
+ resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+
+ tinyexec@1.2.4:
+ resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==}
+ engines: {node: '>=18'}
tinyglobby@0.2.17:
- resolution:
- { integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g== }
- engines: { node: ">=12.0.0" }
+ resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
+ engines: {node: '>=12.0.0'}
+
+ tinyrainbow@3.1.1:
+ resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==}
+ engines: {node: '>=14.0.0'}
toml@4.3.0:
- resolution:
- { integrity: sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A== }
- engines: { node: ">=20" }
+ resolution: {integrity: sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A==}
+ engines: {node: '>=20'}
tslib@2.8.1:
- resolution:
- { integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== }
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
typescript@6.0.3:
- resolution:
- { integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw== }
- engines: { node: ">=14.17" }
+ resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==}
+ engines: {node: '>=14.17'}
hasBin: true
uuid@14.0.1:
- resolution:
- { integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew== }
+ resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==}
hasBin: true
vite@8.1.5:
- resolution:
- { integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw== }
- engines: { node: ^20.19.0 || >=22.12.0 }
+ resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
- "@types/node": ^20.19.0 || >=22.12.0
- "@vitejs/devtools": ^0.3.0
+ '@types/node': ^20.19.0 || >=22.12.0
+ '@vitejs/devtools': ^0.3.0
esbuild: ^0.27.0 || ^0.28.0
- jiti: ">=1.21.0"
+ jiti: '>=1.21.0'
less: ^4.0.0
sass: ^1.70.0
sass-embedded: ^1.70.0
- stylus: ">=0.54.8"
+ stylus: '>=0.54.8'
sugarss: ^5.0.0
terser: ^5.16.0
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta:
- "@types/node":
+ '@types/node':
optional: true
- "@vitejs/devtools":
+ '@vitejs/devtools':
optional: true
esbuild:
optional: true
@@ -464,123 +495,227 @@ packages:
yaml:
optional: true
+ vitest@4.1.10:
+ resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==}
+ engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
+ hasBin: true
+ peerDependencies:
+ '@edge-runtime/vm': '*'
+ '@opentelemetry/api': ^1.9.0
+ '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
+ '@vitest/browser-playwright': 4.1.10
+ '@vitest/browser-preview': 4.1.10
+ '@vitest/browser-webdriverio': 4.1.10
+ '@vitest/coverage-istanbul': 4.1.10
+ '@vitest/coverage-v8': 4.1.10
+ '@vitest/ui': 4.1.10
+ happy-dom: '*'
+ jsdom: '*'
+ vite: ^6.0.0 || ^7.0.0 || ^8.0.0
+ peerDependenciesMeta:
+ '@edge-runtime/vm':
+ optional: true
+ '@opentelemetry/api':
+ optional: true
+ '@types/node':
+ optional: true
+ '@vitest/browser-playwright':
+ optional: true
+ '@vitest/browser-preview':
+ optional: true
+ '@vitest/browser-webdriverio':
+ optional: true
+ '@vitest/coverage-istanbul':
+ optional: true
+ '@vitest/coverage-v8':
+ optional: true
+ '@vitest/ui':
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+
+ why-is-node-running@2.3.0:
+ resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
+ engines: {node: '>=8'}
+ hasBin: true
+
yaml@2.9.0:
- resolution:
- { integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA== }
- engines: { node: ">= 14.6" }
+ resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==}
+ engines: {node: '>= 14.6'}
hasBin: true
snapshots:
- "@emnapi/core@1.11.1":
+
+ '@emnapi/core@1.11.1':
dependencies:
- "@emnapi/wasi-threads": 1.2.2
+ '@emnapi/wasi-threads': 1.2.2
tslib: 2.8.1
optional: true
- "@emnapi/runtime@1.11.1":
+ '@emnapi/runtime@1.11.1':
dependencies:
tslib: 2.8.1
optional: true
- "@emnapi/wasi-threads@1.2.2":
+ '@emnapi/wasi-threads@1.2.2':
dependencies:
tslib: 2.8.1
optional: true
- "@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4":
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4':
optional: true
- "@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4":
+ '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4':
optional: true
- "@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4":
+ '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4':
optional: true
- "@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4":
+ '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4':
optional: true
- "@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4":
+ '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4':
optional: true
- "@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4":
+ '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4':
optional: true
- "@napi-rs/wasm-runtime@1.2.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)":
+ '@napi-rs/wasm-runtime@1.2.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)':
dependencies:
- "@emnapi/core": 1.11.1
- "@emnapi/runtime": 1.11.1
- "@tybys/wasm-util": 0.10.3
+ '@emnapi/core': 1.11.1
+ '@emnapi/runtime': 1.11.1
+ '@tybys/wasm-util': 0.10.3
optional: true
- "@oxc-project/types@0.139.0": {}
+ '@oxc-project/types@0.139.0': {}
- "@rolldown/binding-android-arm64@1.1.5":
+ '@rolldown/binding-android-arm64@1.1.5':
optional: true
- "@rolldown/binding-darwin-arm64@1.1.5":
+ '@rolldown/binding-darwin-arm64@1.1.5':
optional: true
- "@rolldown/binding-darwin-x64@1.1.5":
+ '@rolldown/binding-darwin-x64@1.1.5':
optional: true
- "@rolldown/binding-freebsd-x64@1.1.5":
+ '@rolldown/binding-freebsd-x64@1.1.5':
optional: true
- "@rolldown/binding-linux-arm-gnueabihf@1.1.5":
+ '@rolldown/binding-linux-arm-gnueabihf@1.1.5':
optional: true
- "@rolldown/binding-linux-arm64-gnu@1.1.5":
+ '@rolldown/binding-linux-arm64-gnu@1.1.5':
optional: true
- "@rolldown/binding-linux-arm64-musl@1.1.5":
+ '@rolldown/binding-linux-arm64-musl@1.1.5':
optional: true
- "@rolldown/binding-linux-ppc64-gnu@1.1.5":
+ '@rolldown/binding-linux-ppc64-gnu@1.1.5':
optional: true
- "@rolldown/binding-linux-s390x-gnu@1.1.5":
+ '@rolldown/binding-linux-s390x-gnu@1.1.5':
optional: true
- "@rolldown/binding-linux-x64-gnu@1.1.5":
+ '@rolldown/binding-linux-x64-gnu@1.1.5':
optional: true
- "@rolldown/binding-linux-x64-musl@1.1.5":
+ '@rolldown/binding-linux-x64-musl@1.1.5':
optional: true
- "@rolldown/binding-openharmony-arm64@1.1.5":
+ '@rolldown/binding-openharmony-arm64@1.1.5':
optional: true
- "@rolldown/binding-wasm32-wasi@1.1.5":
+ '@rolldown/binding-wasm32-wasi@1.1.5':
dependencies:
- "@emnapi/core": 1.11.1
- "@emnapi/runtime": 1.11.1
- "@napi-rs/wasm-runtime": 1.2.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)
+ '@emnapi/core': 1.11.1
+ '@emnapi/runtime': 1.11.1
+ '@napi-rs/wasm-runtime': 1.2.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)
optional: true
- "@rolldown/binding-win32-arm64-msvc@1.1.5":
+ '@rolldown/binding-win32-arm64-msvc@1.1.5':
optional: true
- "@rolldown/binding-win32-x64-msvc@1.1.5":
+ '@rolldown/binding-win32-x64-msvc@1.1.5':
optional: true
- "@rolldown/pluginutils@1.0.1": {}
+ '@rolldown/pluginutils@1.0.1': {}
- "@standard-schema/spec@1.1.0": {}
+ '@standard-schema/spec@1.1.0': {}
- "@tybys/wasm-util@0.10.3":
+ '@tybys/wasm-util@0.10.3':
dependencies:
tslib: 2.8.1
optional: true
- "@typeonce/effect-machine@file:../..(effect@4.0.0-beta.102)":
+ '@typeonce/effect-machine@file:../..(effect@4.0.0-beta.102)':
dependencies:
effect: 4.0.0-beta.102
+ '@types/chai@5.2.3':
+ dependencies:
+ '@types/deep-eql': 4.0.2
+ assertion-error: 2.0.1
+
+ '@types/deep-eql@4.0.2': {}
+
+ '@types/estree@1.0.9': {}
+
+ '@vitest/expect@4.1.10':
+ dependencies:
+ '@standard-schema/spec': 1.1.0
+ '@types/chai': 5.2.3
+ '@vitest/spy': 4.1.10
+ '@vitest/utils': 4.1.10
+ chai: 6.2.2
+ tinyrainbow: 3.1.1
+
+ '@vitest/mocker@4.1.10(vite@8.1.5(yaml@2.9.0))':
+ dependencies:
+ '@vitest/spy': 4.1.10
+ estree-walker: 3.0.3
+ magic-string: 0.30.21
+ optionalDependencies:
+ vite: 8.1.5(yaml@2.9.0)
+
+ '@vitest/pretty-format@4.1.10':
+ dependencies:
+ tinyrainbow: 3.1.1
+
+ '@vitest/runner@4.1.10':
+ dependencies:
+ '@vitest/utils': 4.1.10
+ pathe: 2.0.3
+
+ '@vitest/snapshot@4.1.10':
+ dependencies:
+ '@vitest/pretty-format': 4.1.10
+ '@vitest/utils': 4.1.10
+ magic-string: 0.30.21
+ pathe: 2.0.3
+
+ '@vitest/spy@4.1.10': {}
+
+ '@vitest/utils@4.1.10':
+ dependencies:
+ '@vitest/pretty-format': 4.1.10
+ convert-source-map: 2.0.0
+ tinyrainbow: 3.1.1
+
+ assertion-error@2.0.1: {}
+
+ chai@6.2.2: {}
+
+ convert-source-map@2.0.0: {}
+
detect-libc@2.1.2: {}
effect@4.0.0-beta.102:
dependencies:
- "@standard-schema/spec": 1.1.0
+ '@standard-schema/spec': 1.1.0
fast-check: 4.9.0
find-my-way-ts: 0.1.6
ini: 7.0.0
@@ -591,6 +726,14 @@ snapshots:
uuid: 14.0.1
yaml: 2.9.0
+ es-module-lexer@2.3.1: {}
+
+ estree-walker@3.0.3:
+ dependencies:
+ '@types/estree': 1.0.9
+
+ expect-type@1.4.0: {}
+
fast-check@4.9.0:
dependencies:
pure-rand: 8.4.2
@@ -657,16 +800,20 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.33.0
lightningcss-win32-x64-msvc: 1.33.0
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
msgpackr-extract@3.0.4:
dependencies:
node-gyp-build-optional-packages: 5.2.2
optionalDependencies:
- "@msgpackr-extract/msgpackr-extract-darwin-arm64": 3.0.4
- "@msgpackr-extract/msgpackr-extract-darwin-x64": 3.0.4
- "@msgpackr-extract/msgpackr-extract-linux-arm": 3.0.4
- "@msgpackr-extract/msgpackr-extract-linux-arm64": 3.0.4
- "@msgpackr-extract/msgpackr-extract-linux-x64": 3.0.4
- "@msgpackr-extract/msgpackr-extract-win32-x64": 3.0.4
+ '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.4
+ '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.4
+ '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.4
+ '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.4
+ '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.4
+ '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.4
optional: true
msgpackr@2.0.5:
@@ -682,6 +829,10 @@ snapshots:
detect-libc: 2.1.2
optional: true
+ obug@2.1.4: {}
+
+ pathe@2.0.3: {}
+
picocolors@1.1.1: {}
picomatch@4.0.5: {}
@@ -696,32 +847,44 @@ snapshots:
rolldown@1.1.5:
dependencies:
- "@oxc-project/types": 0.139.0
- "@rolldown/pluginutils": 1.0.1
+ '@oxc-project/types': 0.139.0
+ '@rolldown/pluginutils': 1.0.1
optionalDependencies:
- "@rolldown/binding-android-arm64": 1.1.5
- "@rolldown/binding-darwin-arm64": 1.1.5
- "@rolldown/binding-darwin-x64": 1.1.5
- "@rolldown/binding-freebsd-x64": 1.1.5
- "@rolldown/binding-linux-arm-gnueabihf": 1.1.5
- "@rolldown/binding-linux-arm64-gnu": 1.1.5
- "@rolldown/binding-linux-arm64-musl": 1.1.5
- "@rolldown/binding-linux-ppc64-gnu": 1.1.5
- "@rolldown/binding-linux-s390x-gnu": 1.1.5
- "@rolldown/binding-linux-x64-gnu": 1.1.5
- "@rolldown/binding-linux-x64-musl": 1.1.5
- "@rolldown/binding-openharmony-arm64": 1.1.5
- "@rolldown/binding-wasm32-wasi": 1.1.5
- "@rolldown/binding-win32-arm64-msvc": 1.1.5
- "@rolldown/binding-win32-x64-msvc": 1.1.5
+ '@rolldown/binding-android-arm64': 1.1.5
+ '@rolldown/binding-darwin-arm64': 1.1.5
+ '@rolldown/binding-darwin-x64': 1.1.5
+ '@rolldown/binding-freebsd-x64': 1.1.5
+ '@rolldown/binding-linux-arm-gnueabihf': 1.1.5
+ '@rolldown/binding-linux-arm64-gnu': 1.1.5
+ '@rolldown/binding-linux-arm64-musl': 1.1.5
+ '@rolldown/binding-linux-ppc64-gnu': 1.1.5
+ '@rolldown/binding-linux-s390x-gnu': 1.1.5
+ '@rolldown/binding-linux-x64-gnu': 1.1.5
+ '@rolldown/binding-linux-x64-musl': 1.1.5
+ '@rolldown/binding-openharmony-arm64': 1.1.5
+ '@rolldown/binding-wasm32-wasi': 1.1.5
+ '@rolldown/binding-win32-arm64-msvc': 1.1.5
+ '@rolldown/binding-win32-x64-msvc': 1.1.5
+
+ siginfo@2.0.0: {}
source-map-js@1.2.1: {}
+ stackback@0.0.2: {}
+
+ std-env@4.2.0: {}
+
+ tinybench@2.9.0: {}
+
+ tinyexec@1.2.4: {}
+
tinyglobby@0.2.17:
dependencies:
fdir: 6.5.0(picomatch@4.0.5)
picomatch: 4.0.5
+ tinyrainbow@3.1.1: {}
+
toml@4.3.0: {}
tslib@2.8.1:
@@ -742,4 +905,34 @@ snapshots:
fsevents: 2.3.3
yaml: 2.9.0
+ vitest@4.1.10(vite@8.1.5(yaml@2.9.0)):
+ dependencies:
+ '@vitest/expect': 4.1.10
+ '@vitest/mocker': 4.1.10(vite@8.1.5(yaml@2.9.0))
+ '@vitest/pretty-format': 4.1.10
+ '@vitest/runner': 4.1.10
+ '@vitest/snapshot': 4.1.10
+ '@vitest/spy': 4.1.10
+ '@vitest/utils': 4.1.10
+ es-module-lexer: 2.3.1
+ expect-type: 1.4.0
+ magic-string: 0.30.21
+ obug: 2.1.4
+ pathe: 2.0.3
+ picomatch: 4.0.5
+ std-env: 4.2.0
+ tinybench: 2.9.0
+ tinyexec: 1.2.4
+ tinyglobby: 0.2.17
+ tinyrainbow: 3.1.1
+ vite: 8.1.5(yaml@2.9.0)
+ why-is-node-running: 2.3.0
+ transitivePeerDependencies:
+ - msw
+
+ why-is-node-running@2.3.0:
+ dependencies:
+ siginfo: 2.0.0
+ stackback: 0.0.2
+
yaml@2.9.0: {}
diff --git a/examples/platformer/src/game.ts b/examples/platformer/src/game.ts
index 8ad601f..b6544db 100644
--- a/examples/platformer/src/game.ts
+++ b/examples/platformer/src/game.ts
@@ -1,5 +1,5 @@
import type { Axis, CharacterEvent, CharacterSnapshot, LocomotionMode } from "./machine.ts"
-import { airJumpMode, facingDirection, locomotionState } from "./machine.ts"
+import { airJumpMode, facingDirection, isPaused, locomotionState } from "./machine.ts"
const FLOOR = 324
const SIZE = 30
@@ -11,7 +11,8 @@ const pose = {
Falling: "rotate(12 15 15)",
Ducking: "translate(0 15) scale(1 .5)",
Diving: "rotate(90 15 15) scale(.8 1.15)",
- Landing: "translate(-3 12) scale(1.2 .6)"
+ Landing: "translate(-3 12) scale(1.2 .6)",
+ Paused: "translate(0 0)"
} as const satisfies Record
export class GameAdapter {
@@ -43,6 +44,13 @@ export class GameAdapter {
if (this.snapshot === undefined) return
const dt = Math.min(seconds, 1 / 30)
const state = locomotionState(this.snapshot)
+
+ if (state._tag === "Paused") {
+ this.player.dataset.paused = "true"
+ return
+ }
+
+ delete this.player.dataset.paused
const mode = state._tag
const p = this.position
@@ -121,6 +129,15 @@ export class GameAdapter {
private readonly onKeyDown = (event: KeyboardEvent) => {
if (!this.isGameKey(event.code)) return
event.preventDefault()
+
+ if (event.code === "KeyP") {
+ if (event.repeat || this.snapshot === undefined) return
+ this.send(isPaused(this.snapshot) ? { _tag: "Resume" } : { _tag: "Pause", at: performance.now() })
+ return
+ }
+
+ if (this.snapshot !== undefined && isPaused(this.snapshot) && event.code !== "KeyR") return
+
const previousAxis = this.axis
this.held.add(event.code)
if (previousAxis !== this.axis) this.send({ _tag: "Move", axis: this.axis, at: performance.now() })
@@ -139,6 +156,7 @@ export class GameAdapter {
private readonly onKeyUp = (event: KeyboardEvent) => {
const previousAxis = this.axis
this.held.delete(event.code)
+ if (this.snapshot !== undefined && isPaused(this.snapshot)) return
if (previousAxis !== this.axis) this.send({ _tag: "Move", axis: this.axis, at: performance.now() })
if (event.code === "KeyS" || event.code === "ArrowDown") {
this.send({ _tag: "DownReleased", axis: this.axis, at: performance.now() })
@@ -151,6 +169,7 @@ export class GameAdapter {
"KeyD",
"KeyW",
"KeyS",
+ "KeyP",
"KeyR",
"Space",
"ArrowLeft",
diff --git a/examples/platformer/src/machine.test.ts b/examples/platformer/src/machine.test.ts
new file mode 100644
index 0000000..aba69ca
--- /dev/null
+++ b/examples/platformer/src/machine.test.ts
@@ -0,0 +1,89 @@
+import { Machine } from "@typeonce/effect-machine"
+import { Effect } from "effect"
+import { describe, expect, it } from "vitest"
+import { CharacterMachine, type CharacterSnapshot, Event } from "./machine.ts"
+
+const playingSnapshot = (snapshot: CharacterSnapshot) => {
+ const locomotion = snapshot.states.locomotion.state
+ if (locomotion.path !== "Character.locomotion.Playing") {
+ throw new Error(`Expected Playing, received ${locomotion.path}`)
+ }
+ return locomotion
+}
+
+const runPlan = (effect: Effect.Effect) =>
+ Effect.runPromise(effect as unknown as Effect.Effect)
+
+describe("platformer history integration", () => {
+ it("resumes the exact grounded leaf and its state-local value", async () => {
+ await runPlan(Effect.gen(function*() {
+ const initial = yield* Machine.planInitial(CharacterMachine)
+ const ducking = yield* Machine.plan(
+ CharacterMachine,
+ initial.state,
+ Event.cases.DownPressed.make({ at: 10 })
+ )
+ const beforePause = playingSnapshot(ducking.next)
+
+ const paused = yield* Machine.plan(
+ CharacterMachine,
+ ducking.next,
+ Event.cases.Pause.make({ at: 20 })
+ )
+ const pausedLocomotion = paused.next.states.locomotion.state
+ expect(pausedLocomotion.path).toBe("Character.locomotion.Paused")
+ expect(pausedLocomotion.value).toEqual({ _tag: "Paused", pausedAt: 20 })
+
+ const resumed = yield* Machine.plan(CharacterMachine, paused.next, Event.cases.Resume.make({}))
+ expect(playingSnapshot(resumed.next)).toEqual(beforePause)
+ expect(resumed.next.states.facing.state.path).toBe("Character.facing.Right")
+ expect(resumed.next.states.contact.state.path).toBe("Character.contact.NoWall")
+ }))
+ })
+
+ it("resumes both airborne parallel regions and all nested values", async () => {
+ await runPlan(Effect.gen(function*() {
+ const initial = yield* Machine.planInitial(CharacterMachine)
+ const airborne = yield* Machine.plan(
+ CharacterMachine,
+ initial.state,
+ Event.cases.JumpPressed.make({ at: 100, y: 207, wall: -1 })
+ )
+ const falling = yield* Machine.plan(
+ CharacterMachine,
+ airborne.next,
+ Event.cases.ApexReached.make({ y: 91 })
+ )
+ const beforePause = playingSnapshot(falling.next)
+
+ const paused = yield* Machine.plan(
+ CharacterMachine,
+ falling.next,
+ Event.cases.Pause.make({ at: 180 })
+ )
+ expect(paused.next.history?.["Character.locomotion.Playing.resume"]?.active).toEqual([
+ "Character",
+ "Character.locomotion",
+ "Character.locomotion.Playing",
+ "Character.locomotion.Playing.Airborne",
+ "Character.locomotion.Playing.Airborne.motion",
+ "Character.locomotion.Playing.Airborne.motion.Falling",
+ "Character.locomotion.Playing.Airborne.airJump",
+ "Character.locomotion.Playing.Airborne.airJump.AirJumpGroundLock"
+ ])
+
+ const resumed = yield* Machine.plan(CharacterMachine, paused.next, Event.cases.Resume.make({}))
+ const restored = playingSnapshot(resumed.next)
+ expect(restored).toEqual(beforePause)
+
+ if (restored.state.path !== "Character.locomotion.Playing.Airborne") {
+ throw new Error(`Expected Airborne, received ${restored.state.path}`)
+ }
+ expect(restored.state.value).toEqual({ _tag: "Airborne", originY: 207 })
+ expect(restored.state.states.motion.state.value).toEqual({ _tag: "Falling", apexY: 91 })
+ expect(restored.state.states.airJump.state.path).toBe(
+ "Character.locomotion.Playing.Airborne.airJump.AirJumpGroundLock"
+ )
+ }))
+ })
+})
diff --git a/examples/platformer/src/machine.ts b/examples/platformer/src/machine.ts
index 95299d2..4c53eda 100644
--- a/examples/platformer/src/machine.ts
+++ b/examples/platformer/src/machine.ts
@@ -9,6 +9,8 @@ const JumpKind = Schema.Literals(["Ground", "Double", "Wall"])
const State = Schema.TaggedUnion({
Character: {},
Locomotion: {},
+ Playing: {},
+ Paused: { pausedAt: Schema.Number },
Grounded: {},
Standing: {},
Running: { startedAt: Schema.Number },
@@ -42,6 +44,8 @@ export const Event = Schema.TaggedUnion({
DownReleased: { axis: Axis, at: Schema.Number },
ApexReached: { y: Schema.Number },
Landed: { impact: Schema.Number, axis: Axis, at: Schema.Number },
+ Pause: { at: Schema.Number },
+ Resume: {},
Reset: {}
})
@@ -62,43 +66,54 @@ export const CharacterStates = Machine.defineStates({
states: {
locomotion: {
schema: State.cases.Locomotion,
- initial: "Grounded",
+ initial: "Playing",
states: {
- Grounded: {
- schema: State.cases.Grounded,
- initial: "Standing",
+ Playing: {
+ schema: State.cases.Playing,
+ initial: "Grounded",
states: {
- Standing: State.cases.Standing,
- Running: State.cases.Running,
- Ducking: State.cases.Ducking,
- Landing: State.cases.Landing
- }
- },
- Airborne: {
- schema: State.cases.Airborne,
- type: "parallel",
- states: {
- motion: {
- schema: State.cases.Motion,
- initial: "Jumping",
+ Grounded: {
+ schema: State.cases.Grounded,
+ initial: "Standing",
states: {
- Jumping: State.cases.Jumping,
- Falling: State.cases.Falling,
- Diving: State.cases.Diving
+ Standing: State.cases.Standing,
+ Running: State.cases.Running,
+ Ducking: State.cases.Ducking,
+ Landing: State.cases.Landing
}
},
- airJump: {
- schema: State.cases.AirJump,
- initial: "AirJumpGroundLock",
+ Airborne: {
+ schema: State.cases.Airborne,
+ type: "parallel",
states: {
- AirJumpGroundLock: State.cases.AirJumpGroundLock,
- AirJumpWallLock: State.cases.AirJumpWallLock,
- AirJumpReady: State.cases.AirJumpReady,
- AirJumpSpent: State.cases.AirJumpSpent
+ motion: {
+ schema: State.cases.Motion,
+ initial: "Jumping",
+ states: {
+ Jumping: State.cases.Jumping,
+ Falling: State.cases.Falling,
+ Diving: State.cases.Diving
+ }
+ },
+ airJump: {
+ schema: State.cases.AirJump,
+ initial: "AirJumpGroundLock",
+ states: {
+ AirJumpGroundLock: State.cases.AirJumpGroundLock,
+ AirJumpWallLock: State.cases.AirJumpWallLock,
+ AirJumpReady: State.cases.AirJumpReady,
+ AirJumpSpent: State.cases.AirJumpSpent
+ }
+ }
}
+ },
+ resume: {
+ type: "history",
+ history: "deep"
}
}
- }
+ },
+ Paused: State.cases.Paused
}
},
facing: {
@@ -122,12 +137,29 @@ export const CharacterStates = Machine.defineStates({
}
})
+const initialPlaying = (): Machine.Machine.SnapshotByIdentifier<
+ typeof CharacterStates.states,
+ "Character.locomotion.Playing"
+> => ({
+ path: "Character.locomotion.Playing",
+ value: State.cases.Playing.make({}),
+ state: {
+ path: "Character.locomotion.Playing.Grounded",
+ value: State.cases.Grounded.make({}),
+ state: {
+ path: "Character.locomotion.Playing.Grounded.Standing",
+ value: State.cases.Standing.make({})
+ }
+ }
+})
+
const initialCharacter = () =>
CharacterStates.initial.Character(State.cases.Character.make({}), (character) =>
character
.locomotion(State.cases.Locomotion.make({}), (locomotion) =>
- locomotion.Grounded(State.cases.Grounded.make({}), (grounded) =>
- grounded.Standing(State.cases.Standing.make({}))))
+ locomotion.Playing(State.cases.Playing.make({}), (playing) =>
+ playing.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) =>
@@ -147,162 +179,181 @@ export const CharacterMachine = Machine.make({
states: {
locomotion: {
states: {
- Grounded: {
- on: {
- JumpPressed: ({ event, target }) =>
- target.full.Character(State.cases.Character.make({}), (character) =>
- character
- .locomotion(State.cases.Locomotion.make({}), (locomotion) =>
- locomotion.Airborne(State.cases.Airborne.make({ originY: event.y }), (airborne) =>
- airborne
- .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({})))
- .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({}))))
- },
- states: {
- Standing: {
- on: {
- Move: ({ event, target }) =>
- event.axis === 0
- ? undefined
- : target.local.Running(State.cases.Running.make({ startedAt: event.at })),
- DownPressed: ({ event, target }) =>
- target.local.Ducking(State.cases.Ducking.make({ startedAt: event.at }))
- }
- },
- Running: {
- on: {
- Move: ({ event, target }) =>
- event.axis === 0 ? target.local.Standing(State.cases.Standing.make({})) : undefined,
- DownPressed: ({ event, target }) =>
- target.local.Ducking(State.cases.Ducking.make({ startedAt: event.at }))
- }
- },
- Ducking: {
- on: {
- DownReleased: ({ event, target }) =>
- event.axis === 0
- ? target.local.Standing(State.cases.Standing.make({}))
- : target.local.Running(State.cases.Running.make({ startedAt: event.at }))
- }
- },
- Landing: {
- invoke: Machine.after("140 millis", InternalEvent.cases.LandingSettled.make({}), {
- id: "landing-settle"
- }),
- on: {
- Move: ({ event, state, target }) =>
- target.local.Landing(Machine.retag(State.cases.Landing, state, { resumeAxis: event.axis })),
- LandingSettled: ({ state, target }) =>
- state.resumeAxis === 0
- ? target.local.Standing(State.cases.Standing.make({}))
- : target.local.Running(State.cases.Running.make({ startedAt: state.landedAt + 140 }))
- }
+ Playing: {
+ history: {
+ resume: {
+ default: initialPlaying
}
- }
- },
- Airborne: {
+ },
on: {
- JumpPressed: Effect.fn(function*({ event, runtime }) {
- const machine = yield* runtime
- const push = awayFrom(event.wall)
- yield* machine.raise(
- push === 0
- ? InternalEvent.cases.TryAirJump.make({ at: event.at })
- : InternalEvent.cases.WallJump.make({ at: event.at, push })
- )
- }),
- 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
- })
- )
- )
+ Pause: ({ event, target }) =>
+ target.branch.Character.locomotion.Paused(State.cases.Paused.make({ pausedAt: event.at }))
},
states: {
- motion: {
+ Grounded: {
on: {
- DoubleJump: ({ event, target }) =>
- target.local.Jumping(
- State.cases.Jumping.make({ startedAt: event.at, push: 0, kind: "Double" })
- ),
- WallJump: ({ event, target }) =>
- target.local.Jumping(
- State.cases.Jumping.make({ startedAt: event.at, push: event.push, kind: "Wall" })
- )
+ JumpPressed: ({ event, target }) =>
+ target.full.Character(State.cases.Character.make({}), (character) =>
+ character
+ .locomotion(State.cases.Locomotion.make({}), (locomotion) =>
+ locomotion.Playing(State.cases.Playing.make({}), (playing) =>
+ playing.Airborne(State.cases.Airborne.make({ originY: event.y }), (airborne) =>
+ airborne
+ .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({})))
+ .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({}))))
},
states: {
- Jumping: {
+ Standing: {
on: {
- ApexReached: ({ event, target }) =>
- target.local.Falling(State.cases.Falling.make({ apexY: event.y })),
+ Move: ({ event, target }) =>
+ event.axis === 0
+ ? undefined
+ : target.local.Running(State.cases.Running.make({ startedAt: event.at })),
DownPressed: ({ event, target }) =>
- target.local.Diving(State.cases.Diving.make({ startedAt: event.at }))
+ target.local.Ducking(State.cases.Ducking.make({ startedAt: event.at }))
}
},
- Falling: {
+ Running: {
on: {
+ Move: ({ event, target }) =>
+ event.axis === 0 ? target.local.Standing(State.cases.Standing.make({})) : undefined,
DownPressed: ({ event, target }) =>
- target.local.Diving(State.cases.Diving.make({ startedAt: event.at }))
+ target.local.Ducking(State.cases.Ducking.make({ startedAt: event.at }))
+ }
+ },
+ Ducking: {
+ on: {
+ DownReleased: ({ event, target }) =>
+ event.axis === 0
+ ? target.local.Standing(State.cases.Standing.make({}))
+ : target.local.Running(State.cases.Running.make({ startedAt: event.at }))
}
},
- Diving: {}
+ Landing: {
+ invoke: Machine.after("140 millis", InternalEvent.cases.LandingSettled.make({}), {
+ id: "landing-settle"
+ }),
+ on: {
+ Move: ({ event, state, target }) =>
+ target.local.Landing(Machine.retag(State.cases.Landing, state, { resumeAxis: event.axis })),
+ LandingSettled: ({ state, target }) =>
+ state.resumeAxis === 0
+ ? target.local.Standing(State.cases.Standing.make({}))
+ : target.local.Running(State.cases.Running.make({ startedAt: state.landedAt + 140 }))
+ }
+ }
}
},
- airJump: {
+ Airborne: {
on: {
- WallJump: {
- reenter: true,
- transition: ({ target }) => target.local.AirJumpWallLock(State.cases.AirJumpWallLock.make({}))
- }
+ JumpPressed: Effect.fn(function*({ event, runtime }) {
+ const machine = yield* runtime
+ const push = awayFrom(event.wall)
+ yield* machine.raise(
+ push === 0
+ ? InternalEvent.cases.TryAirJump.make({ at: event.at })
+ : InternalEvent.cases.WallJump.make({ at: event.at, push })
+ )
+ }),
+ Landed: ({ event, target }) =>
+ target.branch.Character.locomotion.Playing.Grounded(
+ State.cases.Grounded.make({}),
+ (grounded) =>
+ grounded.Landing(
+ State.cases.Landing.make({
+ impact: event.impact,
+ resumeAxis: event.axis,
+ landedAt: event.at
+ })
+ )
+ )
},
states: {
- AirJumpGroundLock: {
- invoke: Machine.after("120 millis", InternalEvent.cases.AirJumpUnlocked.make({}), {
- id: "ground-air-jump-unlock"
- }),
- on: {
- AirJumpUnlocked: ({ target }) => target.local.AirJumpReady(State.cases.AirJumpReady.make({}))
- }
- },
- AirJumpWallLock: {
- invoke: Machine.after("240 millis", InternalEvent.cases.AirJumpUnlocked.make({}), {
- id: "wall-air-jump-unlock"
- }),
+ motion: {
on: {
- AirJumpUnlocked: ({ target }) => target.local.AirJumpReady(State.cases.AirJumpReady.make({}))
+ DoubleJump: ({ event, target }) =>
+ target.local.Jumping(
+ State.cases.Jumping.make({ startedAt: event.at, push: 0, kind: "Double" })
+ ),
+ WallJump: ({ event, target }) =>
+ target.local.Jumping(
+ State.cases.Jumping.make({ startedAt: event.at, push: event.push, kind: "Wall" })
+ )
+ },
+ states: {
+ Jumping: {
+ on: {
+ ApexReached: ({ event, target }) =>
+ target.local.Falling(State.cases.Falling.make({ apexY: event.y })),
+ DownPressed: ({ event, target }) =>
+ target.local.Diving(State.cases.Diving.make({ startedAt: event.at }))
+ }
+ },
+ Falling: {
+ on: {
+ DownPressed: ({ event, target }) =>
+ target.local.Diving(State.cases.Diving.make({ startedAt: event.at }))
+ }
+ },
+ Diving: {}
}
},
- AirJumpReady: {
+ airJump: {
on: {
- 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({}))
- })
+ WallJump: {
+ reenter: true,
+ transition: ({ target }) => target.local.AirJumpWallLock(State.cases.AirJumpWallLock.make({}))
+ }
+ },
+ states: {
+ AirJumpGroundLock: {
+ invoke: Machine.after("120 millis", InternalEvent.cases.AirJumpUnlocked.make({}), {
+ id: "ground-air-jump-unlock"
+ }),
+ on: {
+ 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({}))
+ }
+ },
+ AirJumpReady: {
+ on: {
+ 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({}))
+ })
+ }
+ },
+ AirJumpSpent: {}
}
- },
- AirJumpSpent: {}
+ }
}
}
}
+ },
+ Paused: {
+ on: {
+ Resume: ({ target }) => target.history.Character.locomotion.Playing.resume()
+ }
}
}
},
@@ -347,23 +398,35 @@ export const CharacterMachine = Machine.make({
export type CharacterSnapshot = Machine.Machine.Snapshot
export type CharacterEvent = Machine.Machine.InputEvent
+export const isPaused = (snapshot: CharacterSnapshot) =>
+ snapshot.states.locomotion.state.path === "Character.locomotion.Paused"
+
export const locomotionState = (snapshot: CharacterSnapshot) => {
const locomotion = snapshot.states.locomotion.state
- return locomotion.path === "Character.locomotion.Grounded"
- ? locomotion.state.value
- : locomotion.states.motion.state.value
+ if (locomotion.path === "Character.locomotion.Paused") {
+ return locomotion.value
+ }
+
+ const playing = locomotion.state
+ return playing.path === "Character.locomotion.Playing.Grounded"
+ ? playing.state.value
+ : playing.states.motion.state.value
}
export type LocomotionMode = ReturnType["_tag"]
export const locomotionMode = (snapshot: CharacterSnapshot): LocomotionMode => locomotionState(snapshot)._tag
-export const locomotionBranch = (snapshot: CharacterSnapshot) => snapshot.states.locomotion.state.value._tag
+export const locomotionBranch = (snapshot: CharacterSnapshot) => {
+ const locomotion = snapshot.states.locomotion.state
+ return locomotion.path === "Character.locomotion.Paused" ? locomotion.value._tag : locomotion.state.value._tag
+}
export const airJumpMode = (snapshot: CharacterSnapshot) => {
const locomotion = snapshot.states.locomotion.state
- return locomotion.path === "Character.locomotion.Airborne"
- ? locomotion.states.airJump.state.value._tag
+ return locomotion.path === "Character.locomotion.Playing" &&
+ locomotion.state.path === "Character.locomotion.Playing.Airborne"
+ ? locomotion.state.states.airJump.state.value._tag
: undefined
}
@@ -375,15 +438,21 @@ export const facingDirection = (snapshot: CharacterSnapshot) => snapshot.states.
export const activeStateData = (snapshot: CharacterSnapshot) => {
const locomotion = snapshot.states.locomotion.state
- const { _tag: _branch, ...branchData } = locomotion.value
- if (locomotion.path === "Character.locomotion.Grounded") {
- const { _tag: _leaf, ...leafData } = locomotion.state.value
+ if (locomotion.path === "Character.locomotion.Paused") {
+ const { _tag: _paused, ...pausedData } = locomotion.value
+ return pausedData
+ }
+
+ const playing = locomotion.state
+ const { _tag: _branch, ...branchData } = playing.value
+ if (playing.path === "Character.locomotion.Playing.Grounded") {
+ const { _tag: _leaf, ...leafData } = playing.state.value
return { ...branchData, ...leafData }
}
- const { _tag: _motion, ...motionData } = locomotion.states.motion.state.value
+ const { _tag: _motion, ...motionData } = playing.states.motion.state.value
return {
...branchData,
...motionData,
- airJump: locomotion.states.airJump.state.value._tag
+ airJump: playing.states.airJump.state.value._tag
}
}
diff --git a/examples/platformer/src/main.ts b/examples/platformer/src/main.ts
index 9048fc0..0eeb008 100644
--- a/examples/platformer/src/main.ts
+++ b/examples/platformer/src/main.ts
@@ -9,6 +9,7 @@ import {
CharacterMachine,
type CharacterSnapshot,
facingDirection,
+ isPaused,
locomotionBranch,
locomotionMode,
wallContact
@@ -23,6 +24,7 @@ const requiredElement = (selector: string) => {
const modeLabel = requiredElement("#active-mode")
const stateData = requiredElement("#state-data")
const lastEvent = requiredElement("#last-event")
+const gameView = requiredElement("#game")
const showEvent = (event: CharacterEvent) => {
const { _tag, ...payload } = event
@@ -49,10 +51,13 @@ const publish = (next: CharacterSnapshot) => {
const facing = facingDirection(next)
const contact = wallContact(next)
const airJump = airJumpMode(next)
+ const paused = isPaused(next)
modeLabel.textContent = [mode, airJump, contact, facing].filter(Boolean).join(" · ")
stateData.textContent = JSON.stringify(activeStateData(next))
+ gameView.classList.toggle("is-paused", paused)
const active = new Set([mode, facing, contact, locomotionBranch(next)])
+ if (!paused) active.add("Playing")
if (airJump !== undefined) active.add(airJump)
document.querySelectorAll("[data-node]").forEach((node) => {
node.classList.toggle("is-active", active.has(node.dataset.node ?? ""))
diff --git a/examples/platformer/src/styles.css b/examples/platformer/src/styles.css
index fd2e7f3..40bc109 100644
--- a/examples/platformer/src/styles.css
+++ b/examples/platformer/src/styles.css
@@ -146,6 +146,34 @@ h1 {
fill: #c77dff;
filter: drop-shadow(0 0 3px #c77dff);
}
+#player[data-paused="true"] {
+ opacity: 0.55;
+}
+.pause-overlay {
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity 140ms ease;
+}
+.pause-overlay rect {
+ fill: rgb(10 16 33 / 72%);
+}
+.pause-overlay text {
+ text-anchor: middle;
+ font-family: ui-monospace, monospace;
+}
+.pause-title {
+ fill: #e9fff9;
+ font-size: 30px;
+ font-weight: 800;
+ letter-spacing: 0.12em;
+}
+.pause-copy {
+ fill: #55d6be;
+ font-size: 11px;
+}
+#game.is-paused .pause-overlay {
+ opacity: 1;
+}
.controls {
display: flex;
flex-wrap: wrap;
@@ -234,6 +262,13 @@ kbd {
.branch + .branch {
margin-top: 7px;
}
+.play-state-row {
+ margin-bottom: 7px;
+}
+.playing-region {
+ padding-left: 7px;
+ border-left: 1px solid #304766;
+}
.branch h3 {
display: inline-block;
margin: 0 0 5px;
@@ -248,7 +283,9 @@ kbd {
display: inline-block;
margin-left: 4px;
color: #55d6be;
- font: 700 8px ui-monospace, monospace;
+ font:
+ 700 8px ui-monospace,
+ monospace;
letter-spacing: 0.06em;
text-transform: uppercase;
}
@@ -264,7 +301,9 @@ kbd {
display: block;
margin-bottom: 4px;
color: #637c98;
- font: 700 8px ui-monospace, monospace;
+ font:
+ 700 8px ui-monospace,
+ monospace;
letter-spacing: 0.08em;
text-transform: uppercase;
}
@@ -284,6 +323,12 @@ kbd {
monospace;
transition: 120ms ease;
}
+.state-row .history-node {
+ border-style: dashed;
+ border-color: #8a6bc4;
+ color: #b89de8;
+ background: rgb(138 107 196 / 8%);
+}
[data-node].is-active {
border-color: #55d6be !important;
color: #dffff8 !important;
diff --git a/src/Machine.ts b/src/Machine.ts
index 99c0ee7..bb637a6 100644
--- a/src/Machine.ts
+++ b/src/Machine.ts
@@ -101,10 +101,9 @@ type IsAny = 0 extends (1 & A) ? true : false
*
* **Gotchas**
*
- * History states and declarative first-class guards are not part of the
- * current API. Conditional behavior can be expressed in typed handlers with
- * ordinary TypeScript control flow. Use `after` for cancellable state-scoped
- * delayed events.
+ * Declarative first-class guards are not part of the current API. Conditional
+ * behavior can be expressed in typed handlers with ordinary TypeScript control
+ * flow. Use `after` for cancellable state-scoped delayed events.
*
* @category models
* @since 4.0.0
@@ -433,14 +432,26 @@ type StateDefinitionError = {
readonly "~effect/Machine/DefinitionError": Message
}
-type ValidateStateTree = {
- readonly [Key in keyof States]: ValidateStateNode
+type ActiveStateKey = Machine.ActiveStateKey
+
+type HistoryStateKey = Machine.HistoryStateKey
+
+type ValidateStateTree = {
+ readonly [Key in keyof States]: ValidateStateNode
}
-type ValidateStateNode = Node extends Machine.TaggedSchema ? unknown
+type ValidateStateNode = Node extends Machine.HistoryStateNodeConfig ?
+ AllowHistory extends true ? ValidateHistoryStateNode
+ : StateDefinitionError<"History states must be declared below an active parent state">
+ : Node extends Machine.TaggedSchema ? unknown
: Node extends { readonly schema: Machine.TaggedSchema } ? ValidateStateNodeConfig
: StateDefinitionError<"State nodes must be tagged schemas or state node configs">
+type ValidateHistoryStateNode = [
+ Extract
+] extends [never] ? unknown
+ : StateDefinitionError<"History states cannot declare schemas, children, initial states, or output">
+
type ValidateStateNodeConfig = Node extends
{ readonly states: infer Children } ? ValidateStateNodeWithChildren
: ValidateStateNodeWithoutChildren
@@ -456,7 +467,7 @@ type ValidateStateNodeWithChildren<
Node extends { readonly type: "final" } ? StateDefinitionError<"Final states cannot declare child states">
: Node extends { readonly type: "parallel" } ?
"initial" extends keyof Node ? StateDefinitionError<"Parallel states cannot declare an initial child">
- : { readonly states: ValidateStateTree } & ValidateOutputSchema
+ : { readonly states: ValidateStateTree } & ValidateOutputSchema
: "output" extends keyof Node ? StateDefinitionError<"Only final and parallel states can declare output">
: ValidateCompoundStateNode
: StateDefinitionError<"Child states must be a state tree">
@@ -464,8 +475,8 @@ type ValidateStateNodeWithChildren<
type ValidateCompoundStateNode<
Node extends { readonly schema: Machine.TaggedSchema },
Children extends Machine.StateSchemas
-> = Node extends { readonly initial: infer Initial } ? Initial extends Extract ? {
- readonly states: ValidateStateTree
+> = Node extends { readonly initial: infer Initial } ? Initial extends ActiveStateKey ? {
+ readonly states: ValidateStateTree
}
: StateDefinitionError<"Compound initial must be one of its direct child keys">
: StateDefinitionError<"Compound states must declare an initial child">
@@ -485,10 +496,11 @@ type DefineStateTreeInput = {
}
type DefineStateNodeInput = Node extends Machine.TaggedSchema ? Node
+ : Node extends Machine.HistoryStateNodeConfig ? Machine.HistoryStateNodeConfig
: Node extends { readonly type: "parallel"; readonly states: infer Children extends Machine.StateSchemas } ?
Omit & { readonly states: DefineStateTreeInput }
: Node extends { readonly states: infer Children extends Machine.StateSchemas } ? Omit & {
- readonly initial: Extract
+ readonly initial: ActiveStateKey
readonly states: DefineStateTreeInput
}
: Node
@@ -577,12 +589,12 @@ type InitialSnapshotBuilderWithPrefix<
States extends Machine.StateSchemas,
Prefix extends string = ""
> = {
- readonly [Key in Extract]: InitialSnapshotMethod
+ readonly [Key in ActiveStateKey]: InitialSnapshotMethod
}
type InitialSnapshotMethod<
States extends Machine.StateSchemas,
- StateId extends Extract,
+ StateId extends ActiveStateKey,
Prefix extends string
> =
& ((
@@ -595,7 +607,7 @@ type InitialSnapshotMethod<
type InitialSnapshotArguments<
States extends Machine.StateSchemas,
- StateId extends Extract,
+ StateId extends ActiveStateKey,
Prefix extends string,
Path extends string = Machine.JoinPath
> = States[StateId] extends infer Node ?
@@ -606,7 +618,7 @@ type InitialSnapshotArguments<
) => SnapshotBuilderComplete>
]
: Node extends { readonly states: infer Children extends Machine.StateSchemas } ?
- Node extends { readonly initial: infer Initial extends Extract } ? [
+ Node extends { readonly initial: infer Initial extends ActiveStateKey } ? [
value: Machine.NodeSchema["Type"],
state: (
builder: Pick, Initial>
@@ -618,7 +630,7 @@ type InitialSnapshotArguments<
type InitialSnapshotFromArguments<
States extends Machine.StateSchemas,
- StateId extends Extract,
+ StateId extends ActiveStateKey,
Prefix extends string,
Path extends string = Machine.JoinPath
> = States[StateId] extends infer Node ?
@@ -629,7 +641,7 @@ type InitialSnapshotFromArguments<
) => SnapshotBuilderComplete, boolean>
]
: Node extends { readonly states: infer Children extends Machine.StateSchemas } ?
- Node extends { readonly initial: infer Initial extends Extract } ? [
+ Node extends { readonly initial: infer Initial extends ActiveStateKey } ? [
input: Machine.NodeSchema["~type.make.in"],
state: (
builder: Pick, Initial>
@@ -641,7 +653,7 @@ type InitialSnapshotFromArguments<
type InitialSnapshotResult<
States extends Machine.StateSchemas,
- StateId extends Extract,
+ StateId extends ActiveStateKey,
Prefix extends string,
Path extends string = Machine.JoinPath
> = States[StateId] extends infer Node ?
@@ -652,7 +664,7 @@ type InitialSnapshotResult<
InitialSnapshotRegionsWithPrefix
>
: Node extends { readonly states: infer Children extends Machine.StateSchemas } ?
- Node extends { readonly initial: infer Initial extends Extract } ? Machine.CompoundSnapshot<
+ Node extends { readonly initial: infer Initial extends ActiveStateKey } ? Machine.CompoundSnapshot<
Path,
Machine.NodeSchema["Type"],
InitialSnapshotResult
@@ -665,13 +677,13 @@ type InitialSnapshotRegionsWithPrefix<
States extends Machine.StateSchemas,
Prefix extends string
> = {
- readonly [Key in Extract]: InitialSnapshotResult
+ readonly [Key in ActiveStateKey]: InitialSnapshotResult
}
type InitialParallelBuilder<
States extends Machine.StateSchemas,
Prefix extends string,
- Remaining extends Extract = Extract,
+ Remaining extends ActiveStateKey = ActiveStateKey,
Regions = {},
Constructed extends boolean = false
> =
@@ -705,12 +717,12 @@ type FullSnapshotBuilderWithPrefix<
States extends Machine.StateSchemas,
Prefix extends string = ""
> = {
- readonly [Key in Extract]: FullSnapshotMethod
+ readonly [Key in ActiveStateKey]: FullSnapshotMethod
}
type FullSnapshotMethod<
States extends Machine.StateSchemas,
- StateId extends Extract,
+ StateId extends ActiveStateKey,
Prefix extends string
> =
& ((
@@ -723,7 +735,7 @@ type FullSnapshotMethod<
type FullSnapshotArguments<
States extends Machine.StateSchemas,
- StateId extends Extract,
+ StateId extends ActiveStateKey,
Prefix extends string,
Path extends string = Machine.JoinPath
> = States[StateId] extends infer Node ?
@@ -744,7 +756,7 @@ type FullSnapshotArguments<
type FullSnapshotFromArguments<
States extends Machine.StateSchemas,
- StateId extends Extract,
+ StateId extends ActiveStateKey,
Prefix extends string,
Path extends string = Machine.JoinPath
> = States[StateId] extends infer Node ?
@@ -765,7 +777,7 @@ type FullSnapshotFromArguments<
type FullSnapshotResult<
States extends Machine.StateSchemas,
- StateId extends Extract,
+ StateId extends ActiveStateKey,
Prefix extends string,
Path extends string = Machine.JoinPath
> = Machine.SnapshotByIdentifierWithPath
@@ -773,7 +785,7 @@ type FullSnapshotResult<
type FullParallelBuilder<
States extends Machine.StateSchemas,
Prefix extends string,
- Remaining extends Extract = Extract,
+ Remaining extends ActiveStateKey = ActiveStateKey,
Regions = {},
Constructed extends boolean = false
> =
@@ -834,7 +846,7 @@ type StateIdentifierFromPath<
type LocalTargetResult<
AllStates extends Machine.StateSchemas,
States extends Machine.StateSchemas,
- StateId extends Extract,
+ StateId extends ActiveStateKey,
Prefix extends string,
Path extends string = Machine.JoinPath
> = States[StateId] extends { readonly states: infer Children extends Machine.StateSchemas } ?
@@ -848,8 +860,8 @@ type LocalTargetResultWithPrefix<
States extends Machine.StateSchemas,
Prefix extends string
> = {
- readonly [Key in Extract]: LocalTargetResult
-}[Extract]
+ readonly [Key in ActiveStateKey]: LocalTargetResult
+}[ActiveStateKey]
type LocalTargetBuilderWithPrefix<
AllStates extends Machine.StateSchemas,
@@ -857,13 +869,13 @@ type LocalTargetBuilderWithPrefix<
Prefix extends string,
Source extends Machine.StateIdentifier
> = {
- readonly [Key in Extract]: LocalTargetMethod
+ readonly [Key in ActiveStateKey]: LocalTargetMethod
}
type LocalTargetMethod<
AllStates extends Machine.StateSchemas,
States extends Machine.StateSchemas,
- StateId extends Extract,
+ StateId extends ActiveStateKey,
Prefix extends string,
Source extends Machine.StateIdentifier,
Path extends string = Machine.JoinPath
@@ -957,7 +969,7 @@ type LocalTargetBuilderForScope<
type BranchTargetResult<
AllStates extends Machine.StateSchemas,
States extends Machine.StateSchemas,
- StateId extends Extract,
+ StateId extends ActiveStateKey,
Prefix extends string,
Path extends string = Machine.JoinPath
> = States[StateId] extends { readonly states: infer Children extends Machine.StateSchemas } ?
@@ -971,8 +983,8 @@ type BranchTargetResultWithPrefix<
States extends Machine.StateSchemas,
Prefix extends string
> = {
- readonly [Key in Extract]: BranchTargetResult
-}[Extract]
+ readonly [Key in ActiveStateKey]: BranchTargetResult
+}[ActiveStateKey]
type BranchTargetBuilderWithPrefix<
AllStates extends Machine.StateSchemas,
@@ -980,13 +992,13 @@ type BranchTargetBuilderWithPrefix<
Prefix extends string,
Source extends Machine.StateIdentifier
> = {
- readonly [Key in Extract]: BranchTargetMethod
+ readonly [Key in ActiveStateKey]: BranchTargetMethod
}
type BranchTargetMethod<
AllStates extends Machine.StateSchemas,
States extends Machine.StateSchemas,
- StateId extends Extract,
+ StateId extends ActiveStateKey,
Prefix extends string,
Source extends Machine.StateIdentifier,
Path extends string = Machine.JoinPath
@@ -1051,12 +1063,82 @@ type BranchTargetMethod<
type BranchTargetBuilderForRoot<
States extends Machine.StateSchemas,
- Root extends Extract,
+ Root extends ActiveStateKey,
Source extends Machine.StateIdentifier
> = {
readonly [Key in Root]: BranchTargetMethod
}
+type HistoryContainingKey = {
+ readonly [Key in Extract]: States[Key] extends Machine.HistoryStateNodeConfig ? Key
+ : States[Key] extends { readonly states: infer Children extends Machine.StateSchemas } ?
+ [Machine.HistoryIdentifier] extends [never] ? never : Key
+ : never
+}[Extract]
+
+type HistoryTargetBuilderWithPrefix<
+ AllStates extends Machine.StateSchemas,
+ States extends Machine.StateSchemas,
+ Prefix extends string
+> = {
+ readonly [Key in HistoryContainingKey]: States[Key] extends Machine.HistoryStateNodeConfig ?
+ () => Machine.HistoryTarget<
+ AllStates,
+ Extract, Machine.HistoryIdentifier>
+ >
+ : States[Key] extends { readonly states: infer Children extends Machine.StateSchemas } ?
+ HistoryTargetBuilderWithPrefix>
+ : never
+}
+
+type HasDirectShallowHistory = {
+ readonly [Key in HistoryStateKey]: States[Key] extends { readonly history: "deep" } ? never : Key
+}[HistoryStateKey] extends never ? false : true
+
+type InitializerClosureForNode<
+ AllStates extends Machine.StateSchemas,
+ Node,
+ Path extends string
+> = Node extends { readonly type: "parallel"; readonly states: infer Children extends Machine.StateSchemas } ?
+ | Extract>
+ | InitializerClosuresForChildren
+ : Node extends { readonly states: infer Children extends Machine.StateSchemas; readonly initial: infer Initial } ?
+ | Extract>
+ | (Initial extends ActiveStateKey ? InitializerClosureForNode<
+ AllStates,
+ Children[Initial],
+ Machine.JoinPath
+ >
+ : never)
+ : never
+
+type InitializerClosuresForChildren<
+ AllStates extends Machine.StateSchemas,
+ States extends Machine.StateSchemas,
+ Prefix extends string
+> = {
+ readonly [Key in ActiveStateKey]: InitializerClosureForNode<
+ AllStates,
+ States[Key],
+ Machine.JoinPath
+ >
+}[ActiveStateKey]
+
+type RequiredHistoryInitializersWithPrefix<
+ AllStates extends Machine.StateSchemas,
+ States extends Machine.StateSchemas,
+ Prefix extends string
+> = {
+ readonly [Key in ActiveStateKey]: States[Key] extends {
+ readonly states: infer Children extends Machine.StateSchemas
+ } ?
+ | (HasDirectShallowHistory extends true ?
+ InitializerClosuresForChildren>
+ : never)
+ | RequiredHistoryInitializersWithPrefix>
+ : never
+}[ActiveStateKey]
+
type SpawnRequirements = Exclude<
Requirements,
Scope.Scope
@@ -1752,13 +1834,32 @@ export declare namespace Machine {
readonly states: StateTree
}
+ /**
+ * Pseudo-state that restores the last active configuration of its parent.
+ *
+ * History nodes are transition targets only. They never become active and
+ * therefore do not declare a state value schema or lifecycle handlers.
+ *
+ * @category models
+ * @since 4.0.0
+ */
+ export interface HistoryStateNodeConfig {
+ readonly type: "history"
+ /** Defaults to shallow history. */
+ readonly history?: "shallow" | "deep"
+ }
+
/**
* Configuration accepted for an object state node.
*
* @category models
* @since 4.0.0
*/
- export type StateNodeConfig = AtomicStateNodeConfig | CompoundStateNodeConfig | ParallelStateNodeConfig
+ export type StateNodeConfig =
+ | AtomicStateNodeConfig
+ | CompoundStateNodeConfig
+ | ParallelStateNodeConfig
+ | HistoryStateNodeConfig
/**
* Object state tree keyed by state path.
@@ -1876,9 +1977,10 @@ export declare namespace Machine {
export interface StateNode {
readonly path: string
readonly key: string
- readonly schema: TaggedSchema
+ readonly schema: TaggedSchema | undefined
readonly output: Schema.Top | undefined
- readonly type: "atomic" | "compound" | "parallel" | "final"
+ readonly type: "atomic" | "compound" | "parallel" | "final" | "history"
+ readonly history: "shallow" | "deep" | undefined
readonly parent: string | undefined
readonly children: ReadonlyArray
readonly initial: string | undefined
@@ -1950,13 +2052,75 @@ export declare namespace Machine {
States extends StateSchemas,
Prefix extends string = ""
> = {
- readonly [Key in Extract]: States[Key] extends { readonly states: infer Children }
- ? Children extends StateSchemas ?
- JoinPath | StateIdentifierWithPrefix>
- : JoinPath
+ readonly [Key in Extract]: States[Key] extends HistoryStateNodeConfig ? never
+ : States[Key] extends { readonly states: infer Children }
+ ? Children extends StateSchemas ?
+ JoinPath | StateIdentifierWithPrefix>
+ : JoinPath
: JoinPath
}[Extract]
+ /**
+ * Extracts the transition-only history pseudo-state paths in a definition.
+ *
+ * @category utility types
+ * @since 4.0.0
+ */
+ export type HistoryIdentifier = HistoryIdentifierWithPrefix
+
+ /** @internal */
+ export type HistoryIdentifierWithPrefix<
+ States extends StateSchemas,
+ Prefix extends string = ""
+ > = {
+ readonly [Key in Extract]: States[Key] extends HistoryStateNodeConfig ? JoinPath
+ : States[Key] extends { readonly states: infer Children extends StateSchemas } ?
+ HistoryIdentifierWithPrefix>
+ : never
+ }[Extract]
+
+ /** Active keys directly declared in a state tree. */
+ export type ActiveStateKey = {
+ readonly [Key in Extract]: States[Key] extends HistoryStateNodeConfig ? never : Key
+ }[Extract]
+
+ /** History pseudo-state keys directly declared in a state tree. */
+ export type HistoryStateKey = {
+ readonly [Key in Extract]: States[Key] extends HistoryStateNodeConfig ? Key : never
+ }[Extract]
+
+ /**
+ * Active states that must implement implicit initial-value construction for
+ * shallow history restoration.
+ *
+ * @category utility types
+ * @since 4.0.0
+ */
+ export type RequiredHistoryInitializers = [HistoryIdentifier] extends [never]
+ ? never
+ : Extract, StateIdentifier>
+
+ /** Active parent states that own one or more history pseudo-states. */
+ export type HistoryParentIdentifier = HistoryIdentifier extends infer HistoryId
+ ? HistoryId extends string ? Extract, StateIdentifier> : never
+ : never
+
+ /** History defaults and implicit initializers that remain unimplemented. */
+ export type MissingHistoryImplementations<
+ States extends StateSchemas,
+ UnhandledStates extends StateIdentifier
+ > = Extract | RequiredHistoryInitializers>
+
+ /** @internal Readiness proof required by planning and managed execution. */
+ export type EnsureHistoryImplementations<
+ States extends StateSchemas,
+ UnhandledStates extends StateIdentifier
+ > = [HistoryIdentifier] extends [never] ? unknown
+ : [MissingHistoryImplementations] extends [never] ? unknown :
+ {
+ readonly "~effect/Machine/MissingHistoryImplementation": MissingHistoryImplementations
+ }
+
/**
* Extracts a state-tree node by state path.
*
@@ -2151,11 +2315,11 @@ export declare namespace Machine {
Children extends StateSchemas,
Prefix extends StateIdentifier
> = {
- readonly [Key in Extract]: DirectFinalCompletionOutput<
+ readonly [Key in ActiveStateKey]: DirectFinalCompletionOutput<
States,
Extract, StateIdentifier>
>
- }[Extract]
+ }[ActiveStateKey]
/**
* Extracts the output passed when a state node completes.
@@ -2298,6 +2462,13 @@ export declare namespace Machine {
readonly output?: unknown
}
+ /** Encoded values and paths retained by one history pseudo-state. */
+ export interface EncodedSnapshotHistoryEntry {
+ readonly mode: "shallow" | "deep"
+ readonly active: ReadonlyArray
+ readonly values: Readonly>
+ }
+
/**
* Normalized data representation of a machine snapshot.
*
@@ -2314,6 +2485,7 @@ export declare namespace Machine {
readonly _tag: "MachineSnapshot"
readonly active: ReadonlyArray
readonly completed?: ReadonlyArray
+ readonly history?: Readonly>
}
/**
@@ -2327,6 +2499,13 @@ export declare namespace Machine {
readonly output: unknown
}
+ /** Decoded values and paths retained by one history pseudo-state. */
+ export interface SnapshotHistoryEntry {
+ readonly mode: "shallow" | "deep"
+ readonly active: ReadonlyArray
+ readonly values: Readonly>
+ }
+
/**
* Carries lifecycle metadata required to resume planning from a cloned
* snapshot.
@@ -2345,6 +2524,7 @@ export declare namespace Machine {
*/
export interface SnapshotMetadata {
readonly completed?: ReadonlyArray
+ readonly history?: Readonly>
}
/**
@@ -2422,8 +2602,8 @@ export declare namespace Machine {
States extends StateSchemas,
Prefix extends string
> = {
- readonly [Key in Extract]: SnapshotByIdentifierWithPath>
- }[Extract]
+ readonly [Key in ActiveStateKey]: SnapshotByIdentifierWithPath>
+ }[ActiveStateKey]
/**
* Extracts child snapshots under a parallel parent path prefix, keyed by
@@ -2436,7 +2616,7 @@ export declare namespace Machine {
States extends StateSchemas,
Prefix extends string
> = {
- readonly [Key in Extract]: SnapshotByIdentifierWithPath>
+ readonly [Key in ActiveStateKey]: SnapshotByIdentifierWithPath>
}
/**
@@ -2447,7 +2627,7 @@ export declare namespace Machine {
*/
export type SnapshotByIdentifierWithPath<
States extends StateSchemas,
- StateId extends Extract,
+ StateId extends ActiveStateKey,
Path extends string
> = States[StateId] extends { readonly type: "parallel"; readonly states: infer Children }
? Children extends StateSchemas ? ParallelSnapshot<
@@ -2472,8 +2652,8 @@ export declare namespace Machine {
* @since 4.0.0
*/
export type Snapshot = {
- readonly [StateId in Extract]: SnapshotByIdentifier>
- }[Extract]
+ readonly [StateId in ActiveStateKey]: SnapshotByIdentifier>
+ }[ActiveStateKey]
/**
* Extracts the root state identifier from a state path.
@@ -2560,6 +2740,28 @@ export declare namespace Machine {
>
}
+ /**
+ * Transition instruction that restores a history pseudo-state's parent.
+ *
+ * Unlike ordinary targets, history targets carry no state value. The
+ * planner resolves the remembered concrete configuration, or evaluates the
+ * history node's typed default when no record exists.
+ *
+ * @category models
+ * @since 4.0.0
+ */
+ export interface HistoryTarget<
+ States extends StateSchemas,
+ HistoryId extends HistoryIdentifier
+ > {
+ readonly [Model.HistoryTargetTypeId]: typeof Model.HistoryTargetTypeId
+ readonly path: HistoryId
+ readonly parent: Extract, StateIdentifier