diff --git a/.changeset-xstate-test-alpha/.flat-melons-tan.md b/.changeset-xstate-test-alpha/.flat-melons-tan.md new file mode 100644 index 0000000000..ba2461b133 --- /dev/null +++ b/.changeset-xstate-test-alpha/.flat-melons-tan.md @@ -0,0 +1,9 @@ +--- +'@xstate/test': patch +--- + +author: @Silverwolf90 +pr: #3380 +commit: cec8587 + +Narrow down the `event` type passed to `EventExecutor` from the corresponding key of the `events` object diff --git a/.changeset-xstate-test-alpha/.flat-trees-suffer.md b/.changeset-xstate-test-alpha/.flat-trees-suffer.md new file mode 100644 index 0000000000..ad77194cef --- /dev/null +++ b/.changeset-xstate-test-alpha/.flat-trees-suffer.md @@ -0,0 +1,9 @@ +--- +'@xstate/test': patch +--- + +author: @Andarist +pr: #3367 +commit: b8cf8cfdc + +Fixed `getShortestPathsTo` issue that caused candidate paths to be incorrectly removed while deduplicating generated paths. diff --git a/.changeset-xstate-test-alpha/.good-countries-allow.md b/.changeset-xstate-test-alpha/.good-countries-allow.md new file mode 100644 index 0000000000..2d8523f177 --- /dev/null +++ b/.changeset-xstate-test-alpha/.good-countries-allow.md @@ -0,0 +1,5 @@ +--- +'@xstate/test': minor +--- + +Improved test output by removing id from description diff --git a/.changeset-xstate-test-alpha/.mighty-terms-shake.md b/.changeset-xstate-test-alpha/.mighty-terms-shake.md new file mode 100644 index 0000000000..a89ce5a7a2 --- /dev/null +++ b/.changeset-xstate-test-alpha/.mighty-terms-shake.md @@ -0,0 +1,10 @@ +--- +'@xstate/graph': patch +'@xstate/test': patch +--- + +The `serializeState()` path traversal option now provides 3 arguments to the function passed in: + +1. `state` - the current state +2. `event` - the event that caused traversal to this state +3. `prevState` 🆕 - the state before the current state (may be `undefined`) diff --git a/.changeset-xstate-test-alpha/config.json b/.changeset-xstate-test-alpha/config.json new file mode 100644 index 0000000000..9dd1c40500 --- /dev/null +++ b/.changeset-xstate-test-alpha/config.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@1.6.3/schema.json", + "changelog": ["@changesets/changelog-github", { "repo": "statelyai/xstate" }], + "commit": false, + "linked": [], + "access": "public", + "baseBranch": "main", + "ignore": ["@xstate/analytics", "@xstate/scxml"], + "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { + "onlyUpdatePeerDependentsWhenOutOfRange": true, + "useCalculatedVersionForSnapshots": true + } +} diff --git a/.changeset-xstate-test-alpha/curly-windows-burn.md b/.changeset-xstate-test-alpha/curly-windows-burn.md new file mode 100644 index 0000000000..42604e8ce4 --- /dev/null +++ b/.changeset-xstate-test-alpha/curly-windows-burn.md @@ -0,0 +1,8 @@ +--- +'@xstate/graph': major +--- + +pr: #3036 +author: @davidkpiano + +Renamed `getAdjacencyMap` to `getValueAdjacencyMap`. diff --git a/.changeset-xstate-test-alpha/curly-windows-learn.md b/.changeset-xstate-test-alpha/curly-windows-learn.md new file mode 100644 index 0000000000..e97fe43e83 --- /dev/null +++ b/.changeset-xstate-test-alpha/curly-windows-learn.md @@ -0,0 +1,10 @@ +--- +'@xstate/graph': major +--- + +pr: #3036 +author: @davidkpiano + +Changed `getSimplePaths` to `getSimplePlans`, and `getShortestPaths` to `getShortestPlans`. Both of these functions can be passed a machine, and return `StatePlan[]`. + +Added functions `traverseSimplePlans`, `traverseShortestPlans`,`traverseShortestPlansFromTo`, `traverseSimplePlansTo` and `traverseSimplePlansFromTo`, which can be passed a `Behavior` and return `StatePlan[]`. diff --git a/.changeset-xstate-test-alpha/great-lions-buy.md b/.changeset-xstate-test-alpha/great-lions-buy.md new file mode 100644 index 0000000000..7f9699aa7b --- /dev/null +++ b/.changeset-xstate-test-alpha/great-lions-buy.md @@ -0,0 +1,12 @@ +--- +'@xstate/test': major +--- + +pr: #3036 + +author: @mattpocock +author: @davidkpiano + +Substantially simplified how paths and plans work in `TestModel`. Changed `getShortestPlans` and `getSimplePlans` to `getShortestPaths` and `getSimplePaths`. These functions now return an array of paths, instead of an array of plans which contain paths. + +Also added `getPaths`, which defaults to `getShortestPaths`. This can be passed a `pathGenerator` to customize how paths are generated. diff --git a/.changeset-xstate-test-alpha/lazy-turtles-brand.md b/.changeset-xstate-test-alpha/lazy-turtles-brand.md new file mode 100644 index 0000000000..2fa4eb8a18 --- /dev/null +++ b/.changeset-xstate-test-alpha/lazy-turtles-brand.md @@ -0,0 +1,37 @@ +--- +'@xstate/test': major +--- + +pr: #3036 +author: @mattpocock + +Moved event cases out of `events`, and into their own attribute called `eventCases`: + +```ts +const model = createTestModel(machine, { + eventCases: { + CHOOSE_CURRENCY: [ + { + currency: 'GBP' + }, + { + currency: 'USD' + } + ] + } +}); + +model.getPaths().forEach((path) => { + it(path.description, async () => { + await path.test({ + events: { + CHOOSE_CURRENCY: ({ event }) => { + console.log(event.currency); + } + } + }); + }); +}); +``` + +`eventCases` will also now always produce a new path, instead of only creating a path for the first case which matches. diff --git a/.changeset-xstate-test-alpha/lazy-turtles-bread.md b/.changeset-xstate-test-alpha/lazy-turtles-bread.md new file mode 100644 index 0000000000..78b5e21fe8 --- /dev/null +++ b/.changeset-xstate-test-alpha/lazy-turtles-bread.md @@ -0,0 +1,8 @@ +--- +'@xstate/test': major +--- + +pr: #3036 +author: @davidkpiano + +Removed `.testCoverage()`, and instead made `getPlans`, `getShortestPlans` and `getSimplePlans` cover all states and transitions enabled by event cases by default. diff --git a/.changeset-xstate-test-alpha/lazy-turtles-grand.md b/.changeset-xstate-test-alpha/lazy-turtles-grand.md new file mode 100644 index 0000000000..80fdb1529a --- /dev/null +++ b/.changeset-xstate-test-alpha/lazy-turtles-grand.md @@ -0,0 +1,10 @@ +--- +'@xstate/test': major +--- + +pr: #3036 +author: @davidkpiano + +Added validation on `createTestModel` to ensure that you don't include invalid machine configuration in your test machine. Invalid machine configs include `invoke`, `after`, and any actions with a `delay`. + +Added `createTestMachine`, which provides a slimmed-down API for creating machines which removes these types from the config type signature. diff --git a/.changeset-xstate-test-alpha/lazy-turtles-grate.md b/.changeset-xstate-test-alpha/lazy-turtles-grate.md new file mode 100644 index 0000000000..16d727aa79 --- /dev/null +++ b/.changeset-xstate-test-alpha/lazy-turtles-grate.md @@ -0,0 +1,47 @@ +--- +'@xstate/test': major +--- + +pr: #3036 +author: @davidkpiano + +`getShortestPaths()` and `getPaths()` will now traverse all _transitions_ by default, not just all events. + +Take this machine: + +```ts +const machine = createTestMachine({ + initial: 'toggledOn', + states: { + toggledOn: { + on: { + TOGGLE: 'toggledOff' + } + }, + toggledOff: { + on: { + TOGGLE: 'toggledOn' + } + } + } +}); +``` + +In `@xstate/test` version 0.x, this would run this path by default: + +```txt +toggledOn -> TOGGLE -> toggledOff +``` + +This is because it satisfies two conditions: + +1. Covers all states +2. Covers all events + +But this a complete test - it doesn't test if going from `toggledOff` to `toggledOn` works. + +Now, we seek to cover all transitions by default. So the path would be: + +```txt +toggledOn -> TOGGLE -> toggledOff -> TOGGLE -> toggledOn +``` diff --git a/.changeset-xstate-test-alpha/lazy-turtles-great.md b/.changeset-xstate-test-alpha/lazy-turtles-great.md new file mode 100644 index 0000000000..fb046d7657 --- /dev/null +++ b/.changeset-xstate-test-alpha/lazy-turtles-great.md @@ -0,0 +1,9 @@ +--- +'@xstate/test': minor +--- + +pr: #3036 +author: @mattpocock +author: @davidkpiano + +Added `path.testSync(...)` to allow for testing paths in sync-only environments, such as Cypress. diff --git a/.changeset-xstate-test-alpha/lazy-turtles-mate.md b/.changeset-xstate-test-alpha/lazy-turtles-mate.md new file mode 100644 index 0000000000..76b9d88938 --- /dev/null +++ b/.changeset-xstate-test-alpha/lazy-turtles-mate.md @@ -0,0 +1,29 @@ +--- +'@xstate/test': major +--- + +pr: #3036 +author: @mattpocock +author: @davidkpiano + +Moved `events` from `createTestModel` to `path.test`. + +Old: + +```ts +const model = createTestModel(machine, { + events: {} +}); +``` + +New: + +```ts +const paths = model.getPaths().forEach((path) => { + path.test({ + events: {} + }); +}); +``` + +This allows for easier usage of per-test mocks and per-test context. diff --git a/.changeset-xstate-test-alpha/lazy-turtles-trade.md b/.changeset-xstate-test-alpha/lazy-turtles-trade.md new file mode 100644 index 0000000000..242b8083a0 --- /dev/null +++ b/.changeset-xstate-test-alpha/lazy-turtles-trade.md @@ -0,0 +1,22 @@ +--- +'@xstate/test': major +--- + +pr: #3036 +author: @mattpocock +author: @davidkpiano + +Added `states` to `path.test()`: + +```ts +const paths = model.getPaths().forEach((path) => { + path.test({ + states: { + myState: () => {}, + 'myState.deep': () => {} + } + }); +}); +``` + +This allows you to define your tests outside of your machine, keeping the machine itself easy to read. diff --git a/.changeset-xstate-test-alpha/ninety-paws-roll.md b/.changeset-xstate-test-alpha/ninety-paws-roll.md new file mode 100644 index 0000000000..0693620787 --- /dev/null +++ b/.changeset-xstate-test-alpha/ninety-paws-roll.md @@ -0,0 +1,5 @@ +--- +'@xstate/graph': patch +--- + +Fixed an internal import to not import from `xstate/src` diff --git a/.changeset-xstate-test-alpha/plenty-insects-visit.md b/.changeset-xstate-test-alpha/plenty-insects-visit.md new file mode 100644 index 0000000000..b7c01b80c9 --- /dev/null +++ b/.changeset-xstate-test-alpha/plenty-insects-visit.md @@ -0,0 +1,22 @@ +--- +'@xstate/graph': patch +'@xstate/test': patch +--- + +author: @davidkpiano +pr: #3864 +commit: 59f3a8e + +Event cases are now specified as an array of event objects, instead of an object with event types as keys and event object payloads as values: + +```diff +const shortestPaths = getShortestPaths(someMachine, { +- eventCases: { +- click: [{ x: 10, y: 10 }, { x: 20, y: 20 }] +- } ++ events: [ ++ { type: 'click', x: 10, y: 10 }, ++ { type: 'click', x: 20, y: 20 } ++ ] +}); +``` diff --git a/.changeset-xstate-test-alpha/pre.json b/.changeset-xstate-test-alpha/pre.json new file mode 100644 index 0000000000..0b3815f370 --- /dev/null +++ b/.changeset-xstate-test-alpha/pre.json @@ -0,0 +1,32 @@ +{ + "mode": "pre", + "tag": "alpha", + "initialVersions": { + "xstate": "4.32.1", + "@xstate/analytics": "0.0.1", + "@xstate/fsm": "2.0.0", + "@xstate/graph": "2.0.0-alpha.0", + "@xstate/immer": "0.3.1", + "@xstate/inspect": "0.7.0", + "@xstate/react": "3.0.0", + "@xstate/scxml": "0.2.1", + "@xstate/svelte": "2.0.0", + "@xstate/test": "1.0.0-alpha.0", + "@xstate/vue": "2.0.0", + "@xstate/solid": "0.1.3" + }, + "changesets": [ + "curly-windows-burn", + "curly-windows-learn", + "great-lions-buy", + "lazy-turtles-brand", + "lazy-turtles-bread", + "lazy-turtles-grand", + "lazy-turtles-grate", + "lazy-turtles-great", + "lazy-turtles-mate", + "lazy-turtles-trade", + "ninety-paws-roll", + "plenty-insects-visit" + ] +} diff --git a/.changeset/cool-ducks-pretend.md b/.changeset/cool-ducks-pretend.md deleted file mode 100644 index b6afdcbfdb..0000000000 --- a/.changeset/cool-ducks-pretend.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -'xstate': patch ---- - -Removing the timeout that's built in to `waitFor` is now supported by explicitly passing an `Infinity` value. - -Example usage: - -```js -import { waitFor } from 'xstate/lib/waitFor'; - -// This will -const loggedInState = await waitFor( - loginService, - (state) => state.hasTag('loggedIn'), - { timeout: Infinity } -); -``` - -This fixes a bug that causes `waitFor` to reject with an error immediately due to the behaviour of `setTimeout`. diff --git a/.changeset/fast-ways-run.md b/.changeset/fast-ways-run.md deleted file mode 100644 index f02c404b5b..0000000000 --- a/.changeset/fast-ways-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'xstate': patch ---- - -Fixed an issue with typegen types not being able to provide events that had a union of strings as their `type` (such as `{ type: 'INC' | 'DEC'; value: number; }`). diff --git a/.changeset/few-pianos-begin.md b/.changeset/few-pianos-begin.md deleted file mode 100644 index a42de9ecee..0000000000 --- a/.changeset/few-pianos-begin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'xstate': patch ---- - -Fixed an issue with `EventFrom` not being able to extract events that had a union of strings as their `type` (such as `{ type: 'INC' | 'DEC'; value: number; }`). diff --git a/.changeset/late-poems-move.md b/.changeset/late-poems-move.md deleted file mode 100644 index 9481c520c9..0000000000 --- a/.changeset/late-poems-move.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'xstate': patch ---- - -Fixed an issue with action objects not receiving correct event types when used in the second argument to the `createMachine`. diff --git a/.changeset/lucky-tables-add.md b/.changeset/lucky-tables-add.md deleted file mode 100644 index 16ecd10b5b..0000000000 --- a/.changeset/lucky-tables-add.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@xstate/svelte': major ---- - -The major version of this package had to be bumped to allow integrating with the typegen. This package will now require TS version 4.0 or greater. - -When using hooks from `@xstate/svelte` it's recommended to skip providing explicit generics to them. Note that that generics list has changed since v1 and we now only accept a single generic, `TMachine`. diff --git a/.changeset/many-onions-listen.md b/.changeset/many-onions-listen.md deleted file mode 100644 index ce25ef9959..0000000000 --- a/.changeset/many-onions-listen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@xstate/svelte': patch ---- - -Fixed an issue with the internal interpreter created by `useMachine` being unsubscribed when its subscribers' count went to zero. The lifetime of this interpreter should be bound to the lifetime of the component that has created it. diff --git a/.changeset/poor-ducks-join.md b/.changeset/poor-ducks-join.md deleted file mode 100644 index 2286bfa5c4..0000000000 --- a/.changeset/poor-ducks-join.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@xstate/inspect": minor ---- - -`@xstate/inspect` will now target `https://stately.ai/viz` by default. You can target the old inspector by setting the config options like so: - -```ts -inspect({ - url: `https://statecharts.io/inspect`, -}); -``` diff --git a/.changeset/rich-dots-laugh.md b/.changeset/rich-dots-laugh.md deleted file mode 100644 index 20408436a4..0000000000 --- a/.changeset/rich-dots-laugh.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@xstate/svelte': patch ---- - -Added ESM build to fix some bundling issues, more information can be found [here](https://github.com/statelyai/xstate/issues/2642) diff --git a/.changeset/serious-melons-fold.md b/.changeset/serious-melons-fold.md deleted file mode 100644 index 88e1127d9f..0000000000 --- a/.changeset/serious-melons-fold.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'xstate': patch ---- - -The typings for `sendTo(...)` have been fixed. diff --git a/.changeset/stupid-plums-smile.md b/.changeset/stupid-plums-smile.md deleted file mode 100644 index 6f302c8d3d..0000000000 --- a/.changeset/stupid-plums-smile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'xstate': minor ---- - -Added a `StateValueFrom` helper that can be used to extract valid state values from a machine. This might specifically be useful with typegen because typegenless `state.matches` accepts `any` anyway. diff --git a/.changeset/tasty-chicken-itch.md b/.changeset/tasty-chicken-itch.md deleted file mode 100644 index 91c5aea351..0000000000 --- a/.changeset/tasty-chicken-itch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'xstate': patch ---- - -Fixed an issue with inline functions in the config object used as transition actions not having their argument types inferred. diff --git a/.changeset/thin-tools-repair.md b/.changeset/thin-tools-repair.md deleted file mode 100644 index acfe9f9671..0000000000 --- a/.changeset/thin-tools-repair.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'xstate': patch ---- - -Fixed an issue with default `TEvent` (`{ type: string }`) not being correctly provided to inline transition actions. diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index 9832a2d45a..29a6449206 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -7,5 +7,5 @@ "packages/xstate-test" ], "sandboxes": ["xstate-example-template-m4ckv", "xstate-react-template-3t2tg"], - "node": "14" + "node": "16" } diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 9896584299..eae072ceb3 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,4 @@ -blank_issues_enabled: false +blank_issues_enabled: true contact_links: - name: ✨ Feature Request url: https://github.com/statelyai/xstate/discussions/new diff --git a/.github/actions/ci-setup/action.yml b/.github/actions/ci-setup/action.yml index b5544914d9..04c462899a 100644 --- a/.github/actions/ci-setup/action.yml +++ b/.github/actions/ci-setup/action.yml @@ -2,10 +2,10 @@ name: 'CI setup' runs: using: 'composite' steps: - - name: Use Node.js 14.x - uses: actions/setup-node@v2 + - name: Use Node.js 16.x + uses: actions/setup-node@v3 with: - node-version: 14.x + node-version: 16.x - name: Install Dependencies run: yarn diff --git a/.github/workflows/codesee-arch-diagram.yml b/.github/workflows/codesee-arch-diagram.yml index 16b971e994..c311b3520e 100644 --- a/.github/workflows/codesee-arch-diagram.yml +++ b/.github/workflows/codesee-arch-diagram.yml @@ -5,77 +5,17 @@ on: pull_request_target: types: [opened, synchronize, reopened] -name: CodeSee Map +name: CodeSee + +permissions: + contents: read # to fetch code (actions/checkout) jobs: - test_map_action: + codesee: runs-on: ubuntu-latest continue-on-error: true - name: Run CodeSee Map Analysis + name: Analyze the repo with CodeSee steps: - - name: checkout - id: checkout - uses: actions/checkout@v2 - with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} - fetch-depth: 0 - - # codesee-detect-languages has an output with id languages. - - name: Detect Languages - id: detect-languages - uses: Codesee-io/codesee-detect-languages-action@latest - - - name: Configure JDK 16 - uses: actions/setup-java@v2 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).java }} - with: - java-version: '16' - distribution: 'zulu' - - # CodeSee Maps Go support uses a static binary so there's no setup step required. - - - name: Configure Node.js 14 - uses: actions/setup-node@v2 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).javascript }} - with: - node-version: '14' - - - name: Configure Python 3.x - uses: actions/setup-python@v2 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).python }} - with: - python-version: '3.x' - architecture: 'x64' - - - name: Configure Ruby '3.x' - uses: ruby/setup-ruby@v1 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).ruby }} - with: - ruby-version: '3.0' - - # CodeSee Maps Rust support uses a static binary so there's no setup step required. - - - name: Generate Map - id: generate-map - uses: Codesee-io/codesee-map-action@latest - with: - step: map - github_ref: ${{ github.ref }} - languages: ${{ steps.detect-languages.outputs.languages }} - - - name: Upload Map - id: upload-map - uses: Codesee-io/codesee-map-action@latest - with: - step: mapUpload - api_token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} - github_ref: ${{ github.ref }} - - - name: Insights - id: insights - uses: Codesee-io/codesee-map-action@latest + - uses: Codesee-io/codesee-action@v2 with: - step: insights - api_token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} - github_ref: ${{ github.ref }} + codesee-token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 28bf91aa20..9c15a0b094 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,16 +5,24 @@ on: branches: - main +permissions: {} jobs: build: + permissions: + contents: write # to push changes in repo (jamesives/github-pages-deploy-action) + runs-on: ubuntu-latest strategy: matrix: - node-version: [14.x] + node-version: [16.x] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + node-version: 16.x - name: Build docs working-directory: docs diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 83a7f4a057..4e30b48347 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -1,6 +1,16 @@ name: Node CI -on: [push, pull_request] +on: + push: + branches: + - main + - next + pull_request: + branches: + - '**' + +permissions: + contents: read # to fetch code (actions/checkout) jobs: build: @@ -9,6 +19,6 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: ./.github/actions/ci-setup - uses: ./.github/actions/ci-checks diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 683be94681..1c9f8791c2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,8 +7,14 @@ on: concurrency: ${{ github.workflow }}-${{ github.ref }} +permissions: {} jobs: release: + permissions: + contents: write # to create release (changesets/action) + issues: write # to post issue comments (changesets/action) + pull-requests: write # to create pull request (changesets/action) + if: github.repository == 'statelyai/xstate' timeout-minutes: 20 @@ -16,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: ./.github/actions/ci-setup - uses: ./.github/actions/ci-checks diff --git a/.gitignore b/.gitignore index 517cf68fdd..ad691bf7e6 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,10 @@ __examples/ src/invoke.ts *.tsbuildinfo +# JetBrains IDE's +.idea +.fleet + # Documentation docs-src/ netlify/ diff --git a/.node-version b/.node-version new file mode 100644 index 0000000000..7fd023741b --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +v16.15.0 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 5071687003..1eab28ed6e 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -2,75 +2,130 @@ ## Our Pledge -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. ## Our Standards -Examples of behavior that contributes to creating a positive environment -include: +Examples of behavior that contributes to a positive environment for our +community include: -- Using welcoming and inclusive language -- Being respectful of differing viewpoints and experiences -- Gracefully accepting constructive criticism -- Focusing on what is best for the community -- Showing empathy towards other community members +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall + community -Examples of unacceptable behavior by participants include: +Examples of unacceptable behavior include: -- The use of sexualized language or imagery and unwelcome sexual attention or - advances -- Trolling, insulting/derogatory comments, and personal or political attacks +- The use of sexualized language or imagery, and sexual attention or advances of + any kind +- Trolling, insulting or derogatory comments, and personal or political attacks - Public or private harassment -- Publishing others' private information, such as a physical or electronic - address, without explicit permission +- Publishing others' private information, such as a physical or email address, + without their explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting -## Our Responsibilities +## Enforcement Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. ## Scope -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at team@stately.ai. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. +reported to the community leaders responsible for enforcement at **team@stately.ai**. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. -[homepage]: https://www.contributor-covenant.org +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][mozilla coc]. -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][faq]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[mozilla coc]: https://github.com/mozilla/diversity +[faq]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f491cf4031..4b35333c33 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,23 +1,53 @@ # Contributing -Thank you for your interest in contributing to XState! This project is made possible by contributors like you, and we welcome any contributions to the code base and the documentation. +Thank you for your interest in contributing to XState! Contributors like you make this project possible, and we welcome any contributions to the code base and the documentation. + +There are several ways you can contribute to XState: + +- 📥 [Submit an issue](#submit-an-issue) +- ✨ [Solve an issue or make a change](#making-changes) +- 🖊️ [Write documentation](https://github.com/statelyai/docs) +- 💬 [Respond to support questions in the GitHub discussions](https://github.com/statelyai/xstate/discussions) +- 🛟 [Respond to questions in the Help channel on Discord](https://discord.gg/xstate) + +Please read [our code of conduct](https://github.com/statelyai/xstate/blob/main/CODE_OF_CONDUCT.md). ## Environment - Ensure you have the latest version of Node and Yarn. - Run `yarn` to install all needed dev dependencies. -## Making Changes +## Making changes Pull requests are encouraged. If you want to add a feature or fix a bug: -1. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) and [clone](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository) the [repository](https://github.com/statelyai/xstate) -2. [Create a separate branch](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/managing-branches) for your changes -3. Make your changes, and ensure that it is formatted by [Prettier](https://prettier.io) and type-checks without errors in [TypeScript](https://www.typescriptlang.org/) +1. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) and [clone](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository) the [repository](https://github.com/statelyai/xstate). +2. [Create a separate branch](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/managing-branches) for your changes. +3. Make your changes, and ensure that it is formatted by [Prettier](https://prettier.io) and type-checks without errors in [TypeScript](https://www.typescriptlang.org/). 4. Write tests that validate your change and/or fix. 5. Run `yarn build` and then run tests with `yarn test` (for all packages) or `yarn test:core` (for only changes to core XState). -6. For package changes, add docs inside the `/packages/*/README.md`. They will be copied on build to the corresponding `/docs/packages/*/index.md` file. -7. Create a changeset by running `yarn changeset`. [More info](https://github.com/atlassian/changesets). +6. For package changes, add docs inside the `/packages/*/README.md`. These docs will be copied on build to the corresponding `/docs/packages/*/index.md` file. +7. Create a changeset by running `yarn changeset`. [More about changesets](https://github.com/atlassian/changesets). 8. Push your branch and open a PR 🚀 -PRs are reviewed promptly and merged in within a day or two (or even within an hour), if everything looks good. +PRs are reviewed promptly and merged in within a day or two (or even within an hour) if everything looks good. + +## Submit an issue + +Issues and bug reports are also encouraged. If you want to submit an issue: + +1. Search [existing issues](https://github.com/statelyai/xstate/issues) to check if your issue already exists or has been solved. +2. [Create a new issue](https://github.com/statelyai/xstate/issues/new/choose) if your issue has not yet been submitted. +3. Ensure you fill out all the details in the issue template to help us understand the issue. + +We’ll try to respond promptly and address your issue as soon as possible. + +## Contributing to our docs + +Our [new docs](https://stately.ai/docs) are now in their own [docs repo](https://github.com/statelyai/docs). [Read the contribution guide for our Stately Studio and XState docs](https://github.com/statelyai/docs/blob/main/CONTRIBUTING.md). + +### Legacy docs and xstate.js.org + +The docs at `/docs` in this repo are legacy XState docs. They are built using [Vuepress](https://vuepress.vuejs.org) and deployed to [xstate.js.org/docs](https://xstate.js.org/docs) using GitHub pages from the `gh-pages` branch using the `pages build and deployment` workflow. + +The [xstate.js.org](https://xstate.js.org) landing page is currently stored at `index.html` and deployed from the `gh-pages` branch using the `pages build and deployment` workflow. diff --git a/README.md b/README.md index bcb4fcce04..58d63c506b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@


- XState + + + + XState logotype +
JavaScript state machines and statecharts
@@ -14,9 +18,9 @@ JavaScript and TypeScript [finite state machines](https://en.wikipedia.org/wiki/Finite-state_machine) and [statecharts](https://www.sciencedirect.com/science/article/pii/0167642387900359/pdf) for the modern web. -📖 [Read the documentation](https://xstate.js.org/docs) +### ✨ Create state machines visually → [state.new](https://state.new) -💙 [Explore our catalogue of examples](https://xstate-catalogue.com/) +📖 [Read the documentation](https://stately.ai/docs) ➡️ [Create state machines with the Stately Editor](https://stately.ai/editor) @@ -24,7 +28,7 @@ JavaScript and TypeScript [finite state machines](https://en.wikipedia.org/wiki/ 📑 Adheres to the [SCXML specification](https://www.w3.org/TR/scxml/) -💬 Chat on the [Stately Discord Community](https://discord.gg/KCtSX7Cdjh) +💬 Chat on the [Stately Discord Community](https://discord.gg/xstate) ## Packages @@ -34,6 +38,7 @@ JavaScript and TypeScript [finite state machines](https://en.wikipedia.org/wiki/ - [⚛️ `@xstate/react`](https://github.com/statelyai/xstate/tree/main/packages/xstate-react) - React hooks and utilities for using XState in React applications - [💚 `@xstate/vue`](https://github.com/statelyai/xstate/tree/main/packages/xstate-vue) - Vue composition functions and utilities for using XState in Vue applications - [🎷 `@xstate/svelte`](https://github.com/statelyai/xstate/tree/main/packages/xstate-svelte) - Svelte utilities for using XState in Svelte applications +- [🥏 `@xstate/solid`](https://github.com/statelyai/xstate/tree/main/packages/xstate-solid) - Solid hooks and utilities for using XState in Solid applications - [✅ `@xstate/test`](https://github.com/statelyai/xstate/tree/main/packages/xstate-test) - Model-Based-Testing utilities (using XState) for testing any software - [🔍 `@xstate/inspect`](https://github.com/statelyai/xstate/tree/main/packages/xstate-inspect) - Inspection utilities for XState @@ -58,7 +63,7 @@ npm install xstate ```js import { createMachine, interpret } from 'xstate'; -// Stateless machine definition +// State machine definition // machine.transition(...) is a pure function used by the interpreter. const toggleMachine = createMachine({ id: 'toggle', @@ -70,79 +75,18 @@ const toggleMachine = createMachine({ }); // Machine instance with internal state -const toggleService = interpret(toggleMachine) - .onTransition((state) => console.log(state.value)) - .start(); -// => 'inactive' - -toggleService.send('TOGGLE'); -// => 'active' - -toggleService.send('TOGGLE'); -// => 'inactive' -``` - -## Promise example - -[📉 See the visualization on stately.ai/viz](https://stately.ai/viz?gist=bbcb4379b36edea0458f597e5eec2f91) - -

-See the code - -```js -import { createMachine, interpret, assign } from 'xstate'; - -const fetchMachine = createMachine({ - id: 'Dog API', - initial: 'idle', - context: { - dog: null - }, - states: { - idle: { - on: { - FETCH: 'loading' - } - }, - loading: { - invoke: { - id: 'fetchDog', - src: (context, event) => - fetch('https://dog.ceo/api/breeds/image/random').then((data) => - data.json() - ), - onDone: { - target: 'resolved', - actions: assign({ - dog: (_, event) => event.data - }) - }, - onError: 'rejected' - }, - on: { - CANCEL: 'idle' - } - }, - resolved: { - type: 'final' - }, - rejected: { - on: { - FETCH: 'loading' - } - } - } -}); +const toggleActor = interpret(toggleMachine); +toggleActor.subscribe((state) => console.log(state.value)); +toggleActor.start(); +// => logs 'inactive' -const dogService = interpret(fetchMachine) - .onTransition((state) => console.log(state.value)) - .start(); +toggleActor.send({ type: 'TOGGLE' }); +// => logs 'active' -dogService.send('FETCH'); +toggleActor.send({ type: 'TOGGLE' }); +// => logs 'inactive' ``` -
- @@ -213,7 +157,9 @@ const lightMachine = createMachine({ const currentState = 'green'; -const nextState = lightMachine.transition(currentState, 'TIMER').value; +const nextState = lightMachine.transition(currentState, { + type: 'TIMER' +}).value; // => 'yellow' ``` @@ -272,12 +218,14 @@ const lightMachine = createMachine({ const currentState = 'yellow'; -const nextState = lightMachine.transition(currentState, 'TIMER').value; +const nextState = lightMachine.transition(currentState, { + type: 'TIMER' +}).value; // => { // red: 'walk' // } -lightMachine.transition('red.walk', 'PED_TIMER').value; +lightMachine.transition('red.walk', { type: 'PED_TIMER' }).value; // => { // red: 'wait' // } @@ -287,15 +235,18 @@ lightMachine.transition('red.walk', 'PED_TIMER').value; ```js // ... -const waitState = lightMachine.transition({ red: 'walk' }, 'PED_TIMER').value; +const waitState = lightMachine.transition( + { red: 'walk' }, + { type: 'PED_TIMER' } +).value; // => { red: 'wait' } -lightMachine.transition(waitState, 'PED_TIMER').value; +lightMachine.transition(waitState, { type: 'PED_TIMER' }).value; // => { red: 'stop' } -lightMachine.transition({ red: 'stop' }, 'TIMER').value; +lightMachine.transition({ red: 'stop' }, { type: 'TIMER' }).value; // => 'green' ``` @@ -364,7 +315,9 @@ const wordMachine = createMachine({ } }); -const boldState = wordMachine.transition('bold.off', 'TOGGLE_BOLD').value; +const boldState = wordMachine.transition('bold.off', { + type: 'TOGGLE_BOLD' +}).value; // { // bold: 'on', @@ -380,7 +333,7 @@ const nextState = wordMachine.transition( underline: 'on', list: 'bullets' }, - 'TOGGLE_ITALICS' + { type: 'TOGGLE_ITALICS' } ).value; // { @@ -420,25 +373,35 @@ const paymentMachine = createMachine({ } }); -const checkState = paymentMachine.transition('method.cash', 'SWITCH_CHECK'); +const checkState = paymentMachine.transition('method.cash', { + type: 'SWITCH_CHECK' +}); // => State { // value: { method: 'check' }, // history: State { ... } // } -const reviewState = paymentMachine.transition(checkState, 'NEXT'); +const reviewState = paymentMachine.transition(checkState, { type: 'NEXT' }); // => State { // value: 'review', // history: State { ... } // } -const previousState = paymentMachine.transition(reviewState, 'PREVIOUS').value; +const previousState = paymentMachine.transition(reviewState, { + type: 'PREVIOUS' +}).value; // => { method: 'check' } ``` +## Sponsors + +Special thanks to the sponsors who support this open-source project: + +Transloadit logo
Transloadit
+ ## SemVer Policy We understand the importance of the public contract and do not intend to release any breaking changes to the **runtime** API in a minor or patch release. We consider this with any changes we make to the XState libraries and aim to minimize their effects on existing users. diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index fe179ddff9..9ef4bd7490 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -1,3 +1,11 @@ +const { slugify } = require('@vuepress/shared-utils'); + +// TODO: remove once https://github.com/vuejs/vuepress/issues/1985 will be fixed +function slugifyWithBadges(str) { + // remove badges and use original slugify function + return slugify(str.replace(/]*\/>/, '')); +} + module.exports = { title: 'XState Docs', base: '/docs/', @@ -20,7 +28,7 @@ module.exports = { } }, markdown: { - toc: { includeLevel: [2, 3] } + toc: { includeLevel: [2, 3], slugify: slugifyWithBadges } }, head: [ ['script', { src: 'https://plausible.io/js/plausible.js', defer: 'defer' }] @@ -157,6 +165,7 @@ module.exports = { { title: '新闻和有用地址', children: [ + ['https://statelyai.canny.io', 'Roadmap'], '/updates/', [ 'https://github.com/statelyai/xstate/blob/main/CODE_OF_CONDUCT.md', @@ -284,6 +293,7 @@ module.exports = { { title: 'Actualités et liens utiles', children: [ + ['https://statelyai.canny.io', 'Roadmap'], '/fr/updates/', [ 'https://github.com/statelyai/xstate/blob/main/CODE_OF_CONDUCT.md', @@ -407,7 +417,7 @@ module.exports = { { title: 'News and Useful Links', children: [ - '/roadmap/', + ['https://statelyai.canny.io', 'Roadmap'], '/updates/', [ 'https://github.com/statelyai/xstate/blob/main/CODE_OF_CONDUCT.md', diff --git a/docs/.vuepress/styles/index.styl b/docs/.vuepress/styles/index.styl index 208e97d99f..9f46ab87f6 100644 --- a/docs/.vuepress/styles/index.styl +++ b/docs/.vuepress/styles/index.styl @@ -94,3 +94,7 @@ details { img[alt="Stately member"] { width: 1rem; } +picture > img { + /* Force light mode image for now */ + content: url(https://raw.githubusercontent.com/statelyai/public-assets/main/logos/xstate-logo-black-nobg.svg); +} diff --git a/docs/README.md b/docs/README.md index b2b4dfb4de..ce1502ed5b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,9 +1,13 @@


- XState + + + + XState logotype +
- State machines and statecharts for the modern web. + JavaScript state machines and statecharts

@@ -14,13 +18,17 @@ JavaScript and TypeScript [finite state machines](https://en.wikipedia.org/wiki/Finite-state_machine) and [statecharts](https://www.sciencedirect.com/science/article/pii/0167642387900359/pdf) for the modern web. -New to state machines and statecharts? [Read our introduction](/guides/introduction-to-state-machines-and-statecharts/). +📖 [Read the documentation](https://stately.ai/docs/xstate) + +💙 [Explore our catalogue of examples](https://xstate-catalogue.com/) + +➡️ [Create state machines with the Stately Editor](https://stately.ai/editor) -🖥 [Download our VS Code extension](https://marketplace.visualstudio.com/items?itemName=statelyai.stately-vscode). +🖥 [Download our VS Code extension](https://marketplace.visualstudio.com/items?itemName=statelyai.stately-vscode) 📑 Adheres to the [SCXML specification](https://www.w3.org/TR/scxml/) -💬 Chat on the [Stately Discord Community](https://discord.gg/KCtSX7Cdjh) +💬 Chat on the [Stately Discord Community](https://discord.gg/xstate) ## Packages @@ -30,6 +38,7 @@ New to state machines and statecharts? [Read our introduction](/guides/introduct - [⚛️ `@xstate/react`](https://github.com/statelyai/xstate/tree/main/packages/xstate-react) - React hooks and utilities for using XState in React applications - [💚 `@xstate/vue`](https://github.com/statelyai/xstate/tree/main/packages/xstate-vue) - Vue composition functions and utilities for using XState in Vue applications - [🎷 `@xstate/svelte`](https://github.com/statelyai/xstate/tree/main/packages/xstate-svelte) - Svelte utilities for using XState in Svelte applications +- [🥏 `@xstate/solid`](https://github.com/statelyai/xstate/tree/main/packages/xstate-solid) - Solid hooks and utilities for using XState in Solid applications - [✅ `@xstate/test`](https://github.com/statelyai/xstate/tree/main/packages/xstate-test) - Model-Based-Testing utilities (using XState) for testing any software - [🔍 `@xstate/inspect`](https://github.com/statelyai/xstate/tree/main/packages/xstate-inspect) - Inspection utilities for XState @@ -60,16 +69,8 @@ const toggleMachine = createMachine({ id: 'toggle', initial: 'inactive', states: { - inactive: { - on: { - TOGGLE: { target: 'active' } - } - }, - active: { - on: { - TOGGLE: { target: 'inactive' } - } - } + inactive: { on: { TOGGLE: 'active' } }, + active: { on: { TOGGLE: 'inactive' } } } }); @@ -79,10 +80,10 @@ const toggleService = interpret(toggleMachine) .start(); // => 'inactive' -toggleService.send({ type: 'TOGGLE' }); +toggleService.send('TOGGLE'); // => 'active' -toggleService.send({ type: 'TOGGLE' }); +toggleService.send('TOGGLE'); // => 'inactive' ``` @@ -90,6 +91,9 @@ toggleService.send({ type: 'TOGGLE' }); [📉 See the visualization on stately.ai/viz](https://stately.ai/viz?gist=bbcb4379b36edea0458f597e5eec2f91) +

+See the code + ```js import { createMachine, interpret, assign } from 'xstate'; @@ -102,7 +106,7 @@ const fetchMachine = createMachine({ states: { idle: { on: { - FETCH: { target: 'loading' } + FETCH: 'loading' } }, loading: { @@ -118,21 +122,19 @@ const fetchMachine = createMachine({ dog: (_, event) => event.data }) }, - onError: { - target: 'rejected' - } + onError: 'rejected' }, on: { - CANCEL: { target: 'idle' } + CANCEL: 'idle' } }, + resolved: { + type: 'final' + }, rejected: { on: { - FETCH: { target: 'loading' } + FETCH: 'loading' } - }, - resolved: { - type: 'final' } } }); @@ -141,9 +143,11 @@ const dogService = interpret(fetchMachine) .onTransition((state) => console.log(state.value)) .start(); -dogService.send({ type: 'FETCH' }); +dogService.send('FETCH'); ``` +
+ @@ -153,15 +157,18 @@ dogService.send({ type: 'FETCH' }); - [Hierarchical (Nested) State Machines](#hierarchical-nested-state-machines) - [Parallel State Machines](#parallel-state-machines) - [History States](#history-states) -- [Sponsors](#sponsors) ## Visualizer -**[Visualize, simulate, and share your statecharts in XState Viz!](https://stately.ai/viz)** +**[Visualize, simulate, inspect, and share your statecharts in XState Viz](https://stately.ai/viz)** + + + XState Viz + -XState Visualizer +**[stately.ai/viz](https://stately.ai/viz)** ## Why? @@ -177,7 +184,12 @@ Read [📽 the slides](http://slides.com/davidkhourshid/finite-state-machines) ( ## Finite State Machines -Light Machine + + Finite states +
+ Open in Stately Viz +
+
```js import { createMachine } from 'xstate'; @@ -188,17 +200,17 @@ const lightMachine = createMachine({ states: { green: { on: { - TIMER: { target: 'yellow' } + TIMER: 'yellow' } }, yellow: { on: { - TIMER: { target: 'red' } + TIMER: 'red' } }, red: { on: { - TIMER: { target: 'green' } + TIMER: 'green' } } } @@ -206,15 +218,19 @@ const lightMachine = createMachine({ const currentState = 'green'; -const nextState = lightMachine.transition(currentState, { type: 'TIMER' }) - .value; +const nextState = lightMachine.transition(currentState, 'TIMER').value; // => 'yellow' ``` ## Hierarchical (Nested) State Machines -Hierarchical Light Machine + + Hierarchical states +
+ Open in Stately Viz +
+
```js import { createMachine } from 'xstate'; @@ -224,12 +240,12 @@ const pedestrianStates = { states: { walk: { on: { - PED_TIMER: { target: 'wait' } + PED_TIMER: 'wait' } }, wait: { on: { - PED_TIMER: { target: 'stop' } + PED_TIMER: 'stop' } }, stop: {} @@ -242,17 +258,17 @@ const lightMachine = createMachine({ states: { green: { on: { - TIMER: { target: 'yellow' } + TIMER: 'yellow' } }, yellow: { on: { - TIMER: { target: 'red' } + TIMER: 'red' } }, red: { on: { - TIMER: { target: 'green' } + TIMER: 'green' }, ...pedestrianStates } @@ -261,13 +277,12 @@ const lightMachine = createMachine({ const currentState = 'yellow'; -const nextState = lightMachine.transition(currentState, { type: 'TIMER' }) - .value; +const nextState = lightMachine.transition(currentState, 'TIMER').value; // => { // red: 'walk' // } -lightMachine.transition('red.walk', { type: 'PED_TIMER' }).value; +lightMachine.transition('red.walk', 'PED_TIMER').value; // => { // red: 'wait' // } @@ -277,29 +292,29 @@ lightMachine.transition('red.walk', { type: 'PED_TIMER' }).value; ```js // ... -const waitState = lightMachine.transition( - { red: 'walk' }, - { type: 'PED_TIMER' } -).value; +const waitState = lightMachine.transition({ red: 'walk' }, 'PED_TIMER').value; // => { red: 'wait' } -lightMachine.transition(waitState, { type: 'PED_TIMER' }).value; +lightMachine.transition(waitState, 'PED_TIMER').value; // => { red: 'stop' } -lightMachine.transition({ red: 'stop' }, { type: 'TIMER' }).value; +lightMachine.transition({ red: 'stop' }, 'TIMER').value; // => 'green' ``` ## Parallel State Machines -Parallel state machine + + Parallel states +
+ Open in Stately Viz +
+
```js -import { createMachine } from 'xstate'; - const wordMachine = createMachine({ id: 'word', type: 'parallel', @@ -308,14 +323,10 @@ const wordMachine = createMachine({ initial: 'off', states: { on: { - on: { - TOGGLE_BOLD: { target: 'off' } - } + on: { TOGGLE_BOLD: 'off' } }, off: { - on: { - TOGGLE_BOLD: { target: 'on' } - } + on: { TOGGLE_BOLD: 'on' } } } }, @@ -323,14 +334,10 @@ const wordMachine = createMachine({ initial: 'off', states: { on: { - on: { - TOGGLE_UNDERLINE: { target: 'off' } - } + on: { TOGGLE_UNDERLINE: 'off' } }, off: { - on: { - TOGGLE_UNDERLINE: { target: 'on' } - } + on: { TOGGLE_UNDERLINE: 'on' } } } }, @@ -338,14 +345,10 @@ const wordMachine = createMachine({ initial: 'off', states: { on: { - on: { - TOGGLE_ITALICS: { target: 'off' } - } + on: { TOGGLE_ITALICS: 'off' } }, off: { - on: { - TOGGLE_ITALICS: { target: 'on' } - } + on: { TOGGLE_ITALICS: 'on' } } } }, @@ -353,30 +356,20 @@ const wordMachine = createMachine({ initial: 'none', states: { none: { - on: { - BULLETS: { target: 'bullets' }, - NUMBERS: { target: 'numbers' } - } + on: { BULLETS: 'bullets', NUMBERS: 'numbers' } }, bullets: { - on: { - NONE: { target: 'none' }, - NUMBERS: { target: 'numbers' } - } + on: { NONE: 'none', NUMBERS: 'numbers' } }, numbers: { - on: { - BULLETS: { target: 'bullets' }, - NONE: { target: 'none' } - } + on: { BULLETS: 'bullets', NONE: 'none' } } } } } }); -const boldState = wordMachine.transition('bold.off', { type: 'TOGGLE_BOLD' }) - .value; +const boldState = wordMachine.transition('bold.off', 'TOGGLE_BOLD').value; // { // bold: 'on', @@ -392,7 +385,7 @@ const nextState = wordMachine.transition( underline: 'on', list: 'bullets' }, - { type: 'TOGGLE_ITALICS' } + 'TOGGLE_ITALICS' ).value; // { @@ -405,11 +398,14 @@ const nextState = wordMachine.transition( ## History States -Machine with history state + + History state +
+ Open in Stately Viz +
+
```js -import { createMachine } from 'xstate'; - const paymentMachine = createMachine({ id: 'payment', initial: 'method', @@ -417,56 +413,53 @@ const paymentMachine = createMachine({ method: { initial: 'cash', states: { - cash: { - on: { - SWITCH_CHECK: { target: 'check' } - } - }, - check: { - on: { - SWITCH_CASH: { target: 'cash' } - } - }, + cash: { on: { SWITCH_CHECK: 'check' } }, + check: { on: { SWITCH_CASH: 'cash' } }, hist: { type: 'history' } }, - on: { - NEXT: { target: 'review' } - } + on: { NEXT: 'review' } }, review: { - on: { - PREVIOUS: { target: 'method.hist' } - } + on: { PREVIOUS: 'method.hist' } } } }); -const checkState = paymentMachine.transition('method.cash', { - type: 'SWITCH_CHECK' -}); +const checkState = paymentMachine.transition('method.cash', 'SWITCH_CHECK'); // => State { // value: { method: 'check' }, // history: State { ... } // } -const reviewState = paymentMachine.transition(checkState, { type: 'NEXT' }); +const reviewState = paymentMachine.transition(checkState, 'NEXT'); // => State { // value: 'review', // history: State { ... } // } -const previousState = paymentMachine.transition(reviewState, { - type: 'PREVIOUS' -}).value; +const previousState = paymentMachine.transition(reviewState, 'PREVIOUS').value; // => { method: 'check' } ``` -## Sponsors +## SemVer Policy + +We understand the importance of the public contract and do not intend to release any breaking changes to the **runtime** API in a minor or patch release. We consider this with any changes we make to the XState libraries and aim to minimize their effects on existing users. + +### Breaking changes + +XState executes much of the user logic itself. Therefore, almost any change to its behavior might be considered a breaking change. We recognize this as a potential problem but believe that treating every change as a breaking change is not practical. We do our best to implement new features thoughtfully to enable our users to implement their logic in a better, safer way. + +Any change _could_ affect how existing XState machines behave if those machines are using particular configurations. We do not introduce behavior changes on a whim and aim to avoid making changes that affect most existing machines. But we reserve the right to make _some_ behavior changes in minor releases. Our best judgment of the situation will always dictate such changes. Please always read our release notes before deciding to upgrade. + +### TypeScript changes + +We also reserve a similar right to adjust declared TypeScript definitions or drop support for older versions of TypeScript in a minor release. The TypeScript language itself evolves quickly and often introduces breaking changes in its minor releases. Our team is also continuously learning how to leverage TypeScript more effectively - and the types improve as a result. + +For these reasons, it is impractical for our team to be bound by decisions taken when an older version of TypeScript was its latest version or when we didn’t know how to declare our types in a better way. We won’t introduce declaration changes often - but we are more likely to do so than with runtime changes. -Huge thanks to the following companies for sponsoring `xstate`. You can sponsor further `xstate` development [on OpenCollective](https://opencollective.com/xstate). +### Packages - - +Most of the packages in the XState family declare a peer dependency on XState itself. We’ll be cautious about maintaining compatibility with already-released packages when releasing a new version of XState, **but** each release of packages depending on XState will always adjust the declared peer dependency range to include the latest version of XState. For example, you should always be able to update `xstate` without `@xstate/react`. But when you update `@xstate/react`, we highly recommend updating `xstate` too. diff --git a/docs/about/basic-statechart.png b/docs/about/basic-statechart.png new file mode 100644 index 0000000000..3a4266b577 Binary files /dev/null and b/docs/about/basic-statechart.png differ diff --git a/docs/about/concepts.md b/docs/about/concepts.md index cbf11c0da8..cacba75943 100644 --- a/docs/about/concepts.md +++ b/docs/about/concepts.md @@ -1,5 +1,9 @@ # Concepts +:::tip Check out our new docs! +🆕 Learn the concepts in [all-new Stately and XState documentation](https://stately.ai/docs/state-machines-and-statecharts). +::: + XState is a library for creating, interpreting, and executing finite state machines and statecharts, as well as managing invocations of those machines as actors. The following fundamental computer science concepts are important to know how to make the best use of XState, and in general for all your current and future software projects. ## Finite State Machines diff --git a/docs/about/deep-history-state-icon.png b/docs/about/deep-history-state-icon.png new file mode 100644 index 0000000000..fe093c85dd Binary files /dev/null and b/docs/about/deep-history-state-icon.png differ diff --git a/docs/about/final-state-icon.png b/docs/about/final-state-icon.png new file mode 100644 index 0000000000..d4cae61aed Binary files /dev/null and b/docs/about/final-state-icon.png differ diff --git a/docs/about/glossary.md b/docs/about/glossary.md index 5220c43b26..4c2c521c7f 100644 --- a/docs/about/glossary.md +++ b/docs/about/glossary.md @@ -1,117 +1,124 @@ # Glossary -Adapted from [The World of Statecharts (Glossary)](https://statecharts.dev/glossary/). +This glossary is a guide to the most common terms in statecharts and state machines. -## Action +:::tip Check out our new docs! +🆕 Find more terms in our [all-new Stately and XState glossary](https://stately.ai/docs/glossary). +::: -An action is an [effect](../guides/effects.md) that is executed as a reaction to a state transition. Actions are fire-and-forget; that is, they are executed without needing to wait for a response. +## State machines -## Actor +A state machine is a model that describes how the state of a process transitions to another state when an event occurs. -An actor is an entity that can send messages to other actors, receive messages, and designate its next behavior in response to a message, which may include spawning other actors. +State machines make building reliable software easier because they prevent impossible states and undesired transitions. -## Atomic state +## Statecharts -An atomic state is a state node that has no child states. +Statecharts are a visual extension to state machines enabling you to model more complex logic, including hierarchy, concurrency, and communication. -## Compound state +You may have used similar diagrams in the past to design user flows, plan data transformations or map app logic. Statecharts are another way of using boxes and arrows to represent these kinds of flows. These flows are also executable code you can use to control the logic directly in your application code. -A compound state has one or more child states. One of these child states must be the initial state, which is the default state node entered when the parent compound state is entered. +![basic statechart with an initial state transitioning through an event to another state, then transitioning through another event to a final state.](./basic-statechart.png) -## Condition +## States -See [guard](#guard). +A state describes the status of the machine. This status defines how the machine behaves when it receives an event. A state can be as simple as _active_ or _inactive_. These states are finite; the machine can only move through the predefined states. Machines can only be in one state at a time. [Parent states](#parent-and-child-states) and [parallel state](#parallel-states) can be used to express an overall state that is the combination of child states. -## Entry action +### Initial state -An entry action is an [action](#action) that is executed when its parent state is entered. +When a state machine starts, it enters the **initial state** first. The filled circle with an arrow pointing from the circle to the initial state icon represents the initial state. A machine can only have one top-level initial state. -## Event + -An event is an indication that something happened at a specific moment in time. Events are what state machines receive, and are what cause transitions to potentially be taken. +#### Initial states in child states -## Eventless transition +Inside a parent state, you must specify which child state is the initial state, which the machine enters automatically when it enters the parent state. The filled circle with an arrow pointing from the circle to the initial state icon represents the initial state. -An eventless transition is a transition that is automatically taken when its parent state is active. + -## Exit action +### Parent and child states -An exit action is an [action](#action) that is executed when its parent state is exited. +States can contain more states, also known as child states. These child states can only happen when the parent state is happening. -## External transition + -In SCXML, an external transition is a transition that exits the source state when the target state is a descendant of the source state. See [selecting transitions (SCXML)](https://www.w3.org/TR/scxml/#SelectingTransitions) for details. +![statechart with an initial state transitioning through an event to a parent state which contains two states. The second state transitions through an event to the final state.](./statechart-with-parent-and-child-states.png) -## Final state +### Final state -A final state is an indication that the state is "done", and no more events will be handled from it. +When a machine reaches the final state, it can no longer receive any events, and anything running inside it is canceled and cleaned up. The box with a surrounding border icon represents the final state. -## Guard +A machine can have multiple final states or no final states. -A guard is a Boolean expression that determines whether a transition is enabled (if the condition evaluates to _true_) or disabled (_false_). Also known as a [condition](#condition). + -## History state +### Parallel states -A history state is a pseudo-state that will remember and transition to the most recently active child states of its parent state, or a default target state. +A parallel state is a parent state separated into multiple regions of child states, where each region is active simultaneously. Regions are represented by a dashed line around each region. -## Initial state + -The initial state of a compound state is the default child state that is entered when the compound state is entered. +![basic statechart with an initial state transitioning through an event to a parallel state which contains two regions. Each region has its own states. There’s an event from the parallel state which ends in the final state.](./statechart-with-parallel-state.png) -## Internal event +### History state -An internal event is an event that is raised by the state machine itself. Internal events are processed immediately after the previous event. +A history state returns the machine to the most recently active state. The box with an H inside icon represents the history state. -## Internal transition +The history state can be deep or shallow: -In SCXML, an internal transition is a transition that transitions to a descendant target state without exiting the source state. This is the default transition behavior. See [selecting transitions (SCXML)](https://www.w3.org/TR/scxml/#SelectingTransitions) for details. +- A shallow history state remembers the immediate child’s state. +- A deep history state remembers the deepest active state or states inside its child states. -## Mathematical model of computation +The box with an H and asterisk inside icon represents the deep history state. -A mathematical model of computation is a way of describing how things are computed (given an input, what is the output?) based on a mathematical function. With state machines and statecharts, the pertinent function is the _state-transition function_ (see [Finite state machine: Mathematical model (Wikipedia)](https://en.wikipedia.org/wiki/Finite-state_machine#Mathematical_model)) + -See [Model of computation (Wikipedia)](https://en.wikipedia.org/wiki/Model_of_computation) and [Mathematical model (Wikipedia)](https://en.wikipedia.org/wiki/Mathematical_model) for more information. + -## Orthogonal state +## Transitions and events -See [parallel state](#parallel-state). +A machine moves from state to state through transitions. These transitions are caused by events. Events are deterministic; each combination of state and event always points to the same next state. -## Parallel state + -A parallel state is a compound state where all of its child states (known as _regions_) are active simultaneously. +### Guarded transitions -## Pseudostate +A guard is a condition that the machine checks when it goes through an event. If the condition is true, the machine follows the transition to the next state. If the condition is false, the machine follows the rest of the conditions to the next state. Any transition can be a guarded transition. -A transient state; e.g., an [initial state](#initial-state) or a [history state](#history-state). + -## Raised event +### Eventless transitions -See [internal event](#internal-event). +Eventless transitions are transitions without events. These transitions are **always** taken after any transition in their state if enabled; no event is necessary to trigger the transition. Eventless transitions are labeled “always” and often referred to as “always” transitions. -## Service + -A service is an interpreted [machine](#state-machine); i.e., an [actor](#actor) that represents a machine. +### Wildcard transitions -## State machine +Wildcard transitions are triggered by any event not already handled by the current state. Wildcard transitions are represented by an asterisk \*. -A state machine is a mathematical model of the behavior of a system. It describes the behavior through [states](#state), [events](#event), and [transitions](#transition). +Wildcard transitions are useful for logging untracked events and reducing code duplication. -## State + -A state represents the overall behavior of a state machine. In statecharts, the state is the aggregate of all active states (which can be atomic, compound, parallel, and/or final). + -## Transient state + -A transient state is a state that only has [eventless transitions](#eventless-transition). + -## Transition + -A transition is a description of which target [state(s)](#state) and [actions](#action) a state machine will immediately be in when a specific [event](#event) is taken in the transition's source state. +## Actors, actions and invoked actors -## Visual formalism +A statechart is an executable model of an actor. When you run a statechart, it becomes an actor; a running process that can receive messages, send messages and change its behavior based on the messages it receives, which can cause effects outside of the actor. -A visual formalism is an exact language (like a programming language) that primarily uses visual symbols (states, transitions, etc.) instead of only code or text. State diagrams and statecharts are visual formalisms. +While the statechart actor is running, it can run other processes called actions. -> Visual formalisms are diagrammatic and intuitive, yet mathematically rigorous languages. -> -> – https://link.springer.com/referenceworkentry/10.1007%2F978-0-387-39940-9_444 +An action can be fired upon entry or exit of a state and can also be fired on transitions. Entry and exit actions are fire-and-forget processes; once the machine has fired the action, it moves on and forgets the action. + + + + + +An invoked actor is an actor that can execute its own actions and communicate with the machine. These invoked actors are started in a state and stopped when the state is exited. diff --git a/docs/about/history-state-icon.png b/docs/about/history-state-icon.png new file mode 100644 index 0000000000..db33e4bf70 Binary files /dev/null and b/docs/about/history-state-icon.png differ diff --git a/docs/about/initial-state-icon.png b/docs/about/initial-state-icon.png new file mode 100644 index 0000000000..184828e240 Binary files /dev/null and b/docs/about/initial-state-icon.png differ diff --git a/docs/about/initial-state.png b/docs/about/initial-state.png new file mode 100644 index 0000000000..20aa6f0cf6 Binary files /dev/null and b/docs/about/initial-state.png differ diff --git a/docs/about/resources.md b/docs/about/resources.md index 6ddb14e963..4879ebfd45 100644 --- a/docs/about/resources.md +++ b/docs/about/resources.md @@ -9,7 +9,7 @@ Below is an incomplete collection of many resources for learning and applying XS :::tip Official Course Watch the official [State Machines in JavaScript with XState](https://frontendmasters.com/courses/xstate/) course by [@davidkpiano](https://twitter.com/davidkpiano) on Frontend Masters! In this course, you’ll learn the fundamentals of state machines and statecharts, from building your own without any libraries in pure JavaScript, up to using XState to take advantage of a wide variety of other features. -The workshop code examples are [open-sourced and available on GitHub](https://github.com/statelyai/frontend-masters-xstate-workshop). +The workshop code examples are [open-sourced and available on GitHub](https://github.com/davidkpiano/frontend-masters-xstate-workshop). There is also a follow-up course: [State Modeling with React and XState](https://frontendmasters.com/workshops/xstate-react/). Once you've gone through the first course, be sure to take this one too so you can learn how to apply state machines and statecharts to real-world apps and master advanced concepts such as spawning/invoking actors and model-based testing. ::: @@ -43,13 +43,14 @@ Explore a collection of professionally-designed state machines and statecharts i ## Articles +- [Building iOS Stopwatch functionality using XState](https://blog.lakbychance.com/building-ios-stopwatch-functionality-using-xstate) by [Lakshya Thakur](https://hashnode.com/@lakbychance) 2022-07-31 - [Using State Machines in Front-End Development](https://blog.picnic.nl/using-state-machines-in-front-end-development-c875ea1d5322) by [Danielle Richter](https://medium.com/@danielle.richter) 2021-10-27 - [Quick post: Modeling a video player with XState](https://dev.to/matiasfha/quick-post-modeling-a-video-player-with-xstate-eko) by [Matías Hernández Arellano](https://twitter.com/cafe_contech) on 2021-10-25 - [Getting Started with XState, React and Typescript (Part 2)](https://moduscreate.com/blog/getting-started-with-xstate-react-and-typescript-part-2/) by [Santiago Kent](https://twitter.com/moduscreate) on 2021-10-18 - [Getting Started with XState, React and Typescript (Part 1)](https://moduscreate.com/blog/getting-started-with-xstate-react-and-typescript-part-2/) by [Santiago Kent](https://twitter.com/moduscreate) on 2021-01-05 - [Untangling your Logic using State Machines](https://blog.robruizr.dev/untangling-your-logic-using-state-machines) by [Roberto Ruiz](https://twitter.com/robruizrdevs) on 2021-10-07 - [The power of XState](https://dev.to/manoryanir/the-power-of-x-state-1npg) by [Yanir Manor](https://dev.to/manoryanir) on 2021-10-04 -- [Building an acquisition Funnel in React with Xstate - Part 1](https://dev.indooroutdoor.io/building-an-acquisition-funnel-in-react-with-xstate) by [Jb Rocher](https://hashnode.com/@Araelath) +- [Building an acquisition Funnel in React with Xstate - Part 1](https://dev.indooroutdoor.io/building-an-acquisition-funnel-in-react-with-xstate) by [Jb Rocher](https://hashnode.com/@jibrocher) - [XState - Expero Night](https://slides.com/ivanbtrujillo/deck) by Iván Trujillo - [Orchestrating Serverless from Serverless](https://medium.com/@kjartanmuller/orchestrating-serverless-from-serverless-bcdb751ddd6c) by [Kjartan Rekdal Müller](https://medium.com/@kjartanmuller) on 2021-09-20 - [How to model application flows in React with finite state machines and XState](https://engineering.kablamo.com.au/posts/2021/finite-state-machines-and-xstate) by [Andrew McDowell](https://twitter.com/madole) on 2021-07-23 @@ -102,7 +103,7 @@ Explore a collection of professionally-designed state machines and statecharts i - [Learn and Apply XState with Vonage Video](https://dev.to/vonagedev/learn-and-apply-xstate-with-vonage-video-5dfg) by [Kelly Andrews](https://dev.to/kellyjandrews) on 2020-07-01 - [Article on Nexmo.com](https://www.nexmo.com/blog/2020/07/01/learn-and-apply-xstate-with-vonage-video) - [Introduction to XState](https://flaviocopes.com/xstate/) by [Flavio Copes](https://flaviocopes.com/) on 2020-06-26 -- [Multistep form handling with Finite State Machines, Formik and TypeScript](https://thewidlarzgroup.com/multistep-form-xstate-formik/) by Daniel Grychtoł on 2020-06-17 +- [Multistep form handling in React Native with Finite State Machines, Formik and TypeScript](https://thewidlarzgroup.com/multistep-form-xstate-formik/) by [Daniel Grychtoł](https://twitter.com/daniel_mark01) on 2020-06-17 - [Intro to XState - a true state management library for React](https://medium.com/@pavlo_lompas/intro-to-xstate-a-true-state-management-system-library-for-react-d8c0051c71e4) by [Pavlo Lompas](https://medium.com/@pavlo_lompas) on 2020-06-15 - [Remake of the 100 squares game](https://onehundred.now.sh/) by [@nikpundik](https://twitter.com/nikpundik) on 2020-06-05 - [Tweet with demo](https://twitter.com/nikpundik/status/1268936078737670145) diff --git a/docs/about/showcase.md b/docs/about/showcase.md index e6e502fe48..685059bea4 100644 --- a/docs/about/showcase.md +++ b/docs/about/showcase.md @@ -2,6 +2,7 @@ There are many developers and teams using XState [in the wild](https://github.com/statelyai/xstate/issues/255), to help control various aspects of their applications, in the frontend and backend, and in many different frameworks (or even without a framework!): +- [Nhost](https://nhost.io) uses XState to manage the authentication state and transitions of their [Vanilla, React and Vue libraries](https://github.com/nhost/nhost). Nhost is an Open Source Firebase Alternative with GraphQL. - [Amazon's Amplify UI](https://docs.amplify.aws/ui/) uses XState for its Authenticator component ([see here](https://github.com/aws-amplify/amplify-ui/blob/main/packages/ui/src/machines/authenticator/index.ts)) - The [Hashicorp Vault project](https://vaultproject.io) uses XState to control contextual user flows for getting new users up and running. The machines are [in the open-source repository](https://github.com/hashicorp/vault). - The [Service Workies game](https://serviceworkies.com/) has many levels whose flows are controlled by XState. @@ -18,3 +19,5 @@ There are many developers and teams using XState [in the wild](https://github.co - At [Cypress](https://cypress.io) we chose XState to manage state for our open source [Real World App](https://github.com/cypress-io/cypress-realworld-app). The app is a payment application used to demonstrate real-world usage of Cypress testing methods, patterns, and workflows. [The machines](https://github.com/cypress-io/cypress-realworld-app/tree/develop/src/machines) are used for several different types of scenarios present in modern, responsive web applications. - "We use XState at the backend in projects to control entity state" (https://www.linkedin.com/in/haltentech-team) - [Sunflower Land](https://sunflower-land.com/), a decentralized and community driven MetaVerse style game, uses XState to control and manage the user and session using a State Machine approach. ([see here](https://github.com/sunflower-land/sunflower-land)) +- "I used XState to create an (open source) version of the classic Tower of Hanoi puzzle game. Two state machines are used, one for screen interaction logic and the other for game interaction logic. Also includes automated test generation with model based testing tools." [Tower of Hanoi app](https://towerofhanoi.app) +- "XState completely changed how I think about programming complex logic for fullstack applications - all core logic for streaming summaries on [YouTube Summarized](https://www.youtubesummarized.com) is now encapsulated in XState machines both on the frontend and backend. The visual editor makes it easy to spot edge cases and error states that should be handled, resulting in a very robust and stable system." diff --git a/docs/about/statechart-with-parallel-state.png b/docs/about/statechart-with-parallel-state.png new file mode 100644 index 0000000000..e3d5e10567 Binary files /dev/null and b/docs/about/statechart-with-parallel-state.png differ diff --git a/docs/about/statechart-with-parent-and-child-states.png b/docs/about/statechart-with-parent-and-child-states.png new file mode 100644 index 0000000000..17439cb5e6 Binary files /dev/null and b/docs/about/statechart-with-parent-and-child-states.png differ diff --git a/docs/about/tutorials.md b/docs/about/tutorials.md index 388ac23555..c2a4e75202 100644 --- a/docs/about/tutorials.md +++ b/docs/about/tutorials.md @@ -7,7 +7,7 @@ - [XState - a TypeScript state machine with a lot of features](http://realfiction.net/2019/01/30/xstate-a-typescript-state-machine-with-a-lot-of-features) by Frank Quednau - An overview of creating state machines with XState and TypeScript - Includes a [full React example](https://github.com/flq/test-of-xstate) -- [Creating a Complex IVR System with Ease with XState](https://www.nexmo.com/blog/2019/06/20/creating-a-complex-ivr-system-with-ease-with-xstate/) by Yonatan Mevorach +- [Creating a Complex IVR System with Ease with XState](https://developer.vonage.com/blog/19/06/20/creating-a-complex-ivr-system-with-ease-with-xstate-dr) by Yonatan Mevorach - Tutorial on how to create an IVR (interactive voice response) system with XState, Express, and Nexmo - [My love letter to XState and statecharts ♥](https://dev.to/timdeschryver/my-love-letter-to-xstate-and-statecharts-287b) by [Tim Deschryver](https://dev.to/timdeschryver) - Overview of the benefits and experience of developing with XState diff --git a/docs/fr/guides/actions.md b/docs/fr/guides/actions.md index ab1385487e..f1f6f8d314 100644 --- a/docs/fr/guides/actions.md +++ b/docs/fr/guides/actions.md @@ -193,6 +193,26 @@ entry: send({ type: 'SOME_EVENT' }); ## Send action +::: warning + +The `send(...)` action creator is deprecated in favor of the `sendTo(...)` action creator: + +```diff +-send({ type: 'EVENT' }, { to: 'someActor' }); ++sendTo('someActor', { type: 'EVENT' }); +``` + +For sending events to self, `raise(...)` should be used: + +```diff +-send({ type: 'EVENT' }); ++raise({ type: 'EVENT' }); +``` + +The `send(...)` action creator will be removed in XState v5.0. + +::: + The `send(event)` action creator creates a special “send” action object that tells a service (i.e., [interpreted machine](./interpretation.md)) to send that event to itself. It queues an event to the running service, in the external event queue, which means the event is sent on the next “step” of the interpreter. | Argument | Type | Description | @@ -340,8 +360,7 @@ The `raise()` action creator queues an event to the statechart, in the internal | `event` | string or event object | The event to raise. | ```js -import { createMachine, actions } from 'xstate'; -const { raise } = actions; +import { createMachine, raise } from 'xstate'; const raiseActionDemo = createMachine({ id: 'raisedmo', diff --git a/docs/fr/guides/actors.md b/docs/fr/guides/actors.md index 4b686c00a7..f0646f5c24 100644 --- a/docs/fr/guides/actors.md +++ b/docs/fr/guides/actors.md @@ -73,7 +73,7 @@ Alternatively `spawn` accepts an options object as the second argument which may - `name` (optional) - a string uniquely identifying the actor. This should be unique for all spawned actors and invoked services. - `autoForward` - (optional) `true` if all events sent to this machine should also be sent (or _forwarded_) to the invoked child (`false` by default) -- `sync` - (optional) `true` if this machine should be automatically subscribed to the spawned child machine's state, the state will be stored as `.state` on the child machine ref +- `sync` - (optional) `true` if this machine should be automatically subscribed to the spawned child machine's state, the state can be retrieved from `.getSnapshot()` on the child machine ref ```js {13-14} import { createMachine, spawn } from 'xstate'; @@ -371,7 +371,7 @@ To do this, set `{ sync: true }` as an option to `spawn(...)`: // ... ``` -This will automatically subscribe the machine to the spawned child machine's state, which is kept updated and can be accessed via `getSnapshot()`: +This will automatically subscribe the machine to the spawned child machine's state, which is kept updated and can be accessed via `.getSnapshot()`: ```js someService.onTransition((state) => { @@ -385,20 +385,8 @@ someService.onTransition((state) => { }); ``` -```js -someService.onTransition((state) => { - const { someRef } = state.context; - - console.log(someRef.state); - // => State { - // value: ..., - // context: ... - // } -}); -``` - ::: warning -By default, `sync` is set to `false`. Never read an actor's `.state` when `sync` is disabled; otherwise, you will end up referencing stale state. +By default, `sync` is set to `false`. Never read an actor's state from `.getSnapshot()` when `sync` is disabled; otherwise, you will end up referencing stale state. ::: ## Sending Updates diff --git a/docs/fr/guides/communication.md b/docs/fr/guides/communication.md index 5b9b8c834e..24da9bc94e 100644 --- a/docs/fr/guides/communication.md +++ b/docs/fr/guides/communication.md @@ -157,7 +157,7 @@ const searchMachine = createMachine({ }, searching: { invoke: { - id: 'search' + id: 'search', src: search, onError: { target: 'failure', diff --git a/docs/fr/guides/context.md b/docs/fr/guides/context.md index e3765760af..d6c1797992 100644 --- a/docs/fr/guides/context.md +++ b/docs/fr/guides/context.md @@ -93,6 +93,27 @@ const counterMachine = createMachine({ }); ``` +The `context` property of the machine can also be initialized lazily; i.e., the context will not be created until the machine is actually created/used: + +```js +const counterMachine = createMachine({ + id: 'counter', + // initial context + context: () => ({ + count: 0, + message: 'Currently empty', + user: { + name: 'David' + }, + allowedToIncrement: true + // ... etc. + }), + states: { + // ... + } +}); +``` + For dynamic `context` (that is, `context` whose initial value is retrieved or provided externally), you can use a machine factory function that creates the machine with the provided context values (implementation may vary): ```js diff --git a/docs/fr/guides/models.md b/docs/fr/guides/models.md index 440f7db043..e24d6d0ed9 100644 --- a/docs/fr/guides/models.md +++ b/docs/fr/guides/models.md @@ -1,5 +1,11 @@ # Models +::: warning + +The `createModel(...)` function is deprecated and will be removed in XState version 5. It is recommended to use [Typegen](https://stately.ai/blog/introducing-typescript-typegen-for-xstate) instead. + +::: + In XState, you can model a machine's `context` and `events` externally by using `createModel(...)`. This provides a convenient way to strongly type `context` and `events`, as well as helpers for event creation, assignment and other implementation details in the future. Using `createModel(...)` is _completely optional_, and is meant to improve the developer experience. The main reasons for using it are: diff --git a/docs/fr/packages/xstate-inspect/index.md b/docs/fr/packages/xstate-inspect/index.md index b01cf2b62e..c483b261b9 100644 --- a/docs/fr/packages/xstate-inspect/index.md +++ b/docs/fr/packages/xstate-inspect/index.md @@ -7,8 +7,6 @@ The [@xstate/inspect package](https://github.com/statelyai/xstate/tree/main/pack - [XState + Vue](https://codesandbox.io/s/xstate-vue-viz-template-r5wd7) - [XState + React](https://codesandbox.io/s/xstate-react-viz-template-5wq3q) -![Inspector running from CodeSandbox](https://buttondown.s3.us-west-2.amazonaws.com/images/4c8c0db4-b4d5-408f-8684-57e94ff46c86.png) - [See CodeSandbox example here](https://codesandbox.io/s/xstate-vue-minute-timer-viz-1txmk) ## Installation diff --git a/docs/fr/packages/xstate-react/index.md b/docs/fr/packages/xstate-react/index.md index 58c429a563..6737c1573e 100644 --- a/docs/fr/packages/xstate-react/index.md +++ b/docs/fr/packages/xstate-react/index.md @@ -102,7 +102,7 @@ A [React hook](https://reactjs.org/hooks) that subscribes to emitted changes fro - `actor` - an actor-like object that contains `.send(...)` and `.subscribe(...)` methods. - `getSnapshot` - a function that should return the latest emitted value from the `actor`. - - Defaults to attempting to get the `actor.state`, or returning `undefined` if that does not exist. + - Defaults to attempting to get the snapshot from `actor.getSnapshot()`, or returning `undefined` if that does not exist. ```js const [state, send] = useActor(someSpawnedActor); @@ -224,51 +224,92 @@ const App = ({ service }) => { }; ``` -### `asEffect(action)` +### `createActorContext(machine)` -Ensures that the `action` is executed as an effect in `useEffect`, rather than being immediately executed. +_Since 3.1.0_ + +Returns a [React Context object](https://beta.reactjs.org/learn/passing-data-deeply-with-context) that interprets the `machine` and makes the interpreted actor available through React Context. There are helper methods for accessing state and the actor ref. **Arguments** -- `action` - An action function (e.g., `(context, event) => { alert(context.message) })`) +- `machine` - An [XState machine](https://xstate.js.org/docs/guides/machines.html) or a function that lazily returns a machine. -**Returns** a special action function that wraps the original so that `useMachine` knows to execute it in `useEffect`. +**Returns** -**Example** +Returns a React Context object that contains the following properties: -```jsx -const machine = createMachine({ - initial: 'focused', - states: { - focused: { - entry: 'focus' - } - } -}); +- `Provider` - a React Context Provider component with the following props: + - `machine` - An [XState machine](https://xstate.js.org/docs/guides/machines.html) that must be of the same type as the machine passed to `createActorContext(...)` +- `useActor()` - a React hook that returns a tuple of `[state, send]` from the React Context +- `useSelector(selector, compare?)` - a React hook that takes in a `selector` function and optional `compare` function and returns the selected value from the actor snapshot +- `useActorRef()` - a React hook that returns the actor ref of the interpreted `machine` -const Input = () => { - const inputRef = useRef(null); - const [state, send] = useMachine(machine, { - actions: { - focus: asEffect((context, event) => { - inputRef.current && inputRef.current.focus(); - }) - } - }); +Creating a React Context for the actor and providing it in app scope: - return ; -}; +```js +import { createActorContext } from '@xstate/react'; +import { someMachine } from '../path/to/someMachine'; +const SomeMachineContext = createActorContext(someMachine); +function App() { + return ( + + + + ); +} ``` -### `asLayoutEffect(action)` +Consuming the actor in a component: + +```js +import { SomeMachineContext } from '../path/to/SomeMachineContext'; +function SomeComponent() { + // Read full snapshot and get `send` function from `useActor()` + const [state, send] = SomeMachineContext.useActor(); + // Or derive a specific value from the snapshot with `useSelector()` + const count = SomeMachineContext.useSelector((state) => state.context.count); + return ( +
+

Count: {count}

+ +
+ ); +} +``` -Ensures that the `action` is executed as an effect in `useLayoutEffect`, rather than being immediately executed. +Reading the actor ref: -**Arguments** +```js +import { SomeMachineContext } from '../path/to/SomeMachineContext'; +function SomeComponent() { + const actorRef = SomeMachineContext.useActorRef(); + return ( +
+ +
+ ); +} +``` -- `action` - An action function (e.g., `(context, event) => { alert(context.message) })`) +Providing a similar machine: -**Returns** a special action function that wraps the original so that `useMachine` knows to execute it in `useLayoutEffect`. +```js +import { SomeMachineContext } from '../path/to/SomeMachineContext'; +import { someMachine } from '../path/to/someMachine'; +function SomeComponent() { + return ( + + someMachine.withConfig({ + /* ... */ + }) + } + > + + + ); +} +``` ### `useMachine(machine)` with `@xstate/fsm` @@ -411,7 +452,7 @@ const Fetcher = ({ onResolve }) => { switch (state.value) { case 'idle': return ( - ); diff --git a/docs/fr/packages/xstate-svelte/index.md b/docs/fr/packages/xstate-svelte/index.md index bbb283af8f..85b1d10b58 100644 --- a/docs/fr/packages/xstate-svelte/index.md +++ b/docs/fr/packages/xstate-svelte/index.md @@ -283,7 +283,7 @@ Example: the `'fetchData'` service and `'notifySuccess'` action are both configu {#if $state.value === 'idle'} - {:else if $state.value === 'loading'} diff --git a/docs/fr/packages/xstate-vue/index.md b/docs/fr/packages/xstate-vue/index.md index e222a33fee..61f76b78a7 100644 --- a/docs/fr/packages/xstate-vue/index.md +++ b/docs/fr/packages/xstate-vue/index.md @@ -101,7 +101,7 @@ _Since 0.5.0_ - `actor` - an actor-like object that contains `.send(...)` and `.subscribe(...)` methods. - `getSnapshot` - a function that should return the latest emitted value from the `actor`. - - Defaults to attempting to get the `actor.state`, or returning `undefined` if that does not exist. + - Defaults to attempting to get the snapshot from `actor.getSnapshot()`, or returning `undefined` if that does not exist. ```js import { useActor } from '@xstate/vue'; @@ -256,7 +256,7 @@ Example: the `'fetchData'` service and `'notifySuccess'` action are both configu ```vue