Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ActorRef #1163

Merged
merged 73 commits into from
May 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
b18c710
Revert state.children to object instead of array
Apr 9, 2020
5c3e82c
Update snapshot tests
Apr 9, 2020
8c85e8b
WIP incorporating actorref classes
Apr 10, 2020
42bbea4
Merge branch 'master' into v5/actorref
Apr 12, 2020
559398b
Merge branch 'next' into v5/actorref
Apr 12, 2020
477060c
Fixing tests (WIP)
Apr 12, 2020
10a8819
Make Interpreter implement ActorRef
Apr 12, 2020
34f958e
Add start() to ActorRef interface
Apr 19, 2020
9482731
Remove spawn (WIP)
Apr 19, 2020
091e3c1
WIP: spawn without spawn()
Apr 19, 2020
7763a59
Clean-up WIP
Apr 20, 2020
73898d1
Went wrong direction; ids should be explicitly named
Apr 21, 2020
0a8bafa
WIP
Apr 24, 2020
705f12b
Remove idNameMap, fix tests
Apr 24, 2020
20f5bc4
Add behaviors, remove global spawn!
Apr 25, 2020
2a4dde5
More behavior spawning
Apr 26, 2020
4e9ea1f
Refactor ActorRef creators
davidkpiano Apr 28, 2020
20e08e3
Remove createInvocableActor
davidkpiano Apr 28, 2020
21e4ed7
Remove original Actor interface
davidkpiano Apr 28, 2020
654f550
Remove actions.start, add actions.invoke, change actions.stop, remove…
davidkpiano Apr 29, 2020
e196ebb
Fix types
davidkpiano Apr 29, 2020
713e247
Cleaning up activity references
davidkpiano Apr 29, 2020
8c521ff
Simplify ActorRef type, change spawn* to invoke*
davidkpiano Apr 29, 2020
310d77c
Remove interpreter.initialized property, remove spawnMachine method
davidkpiano Apr 29, 2020
c7c70a1
Fixing types WIP
davidkpiano Apr 29, 2020
a23717c
Add behavior package, add spawn.from, add createBehaviorFrom, fix (mo…
davidkpiano May 3, 2020
2fd378f
Add ActorRefFrom type, include initial emitted value in behavior
davidkpiano May 4, 2020
fdeb773
Fixing types
davidkpiano May 6, 2020
9925bce
Typedoc
davidkpiano May 6, 2020
da6b28f
Rename: BehaviorActorRef -> ObservableActorRef
davidkpiano May 8, 2020
79fb79b
Merge branch 'next' into v5/actorref
davidkpiano May 9, 2020
32aa2de
Fix tests
davidkpiano May 9, 2020
8d8daf7
Revert interpreter.current -> interpreter.state & fix types
davidkpiano May 9, 2020
2af052e
Update index.md
FredyC May 9, 2020
bafa9c1
Merge pull request #1181 from FredyC/patch-3
davidkpiano May 9, 2020
55862c0
Move and export ActorRef and ActorRefFrom types
davidkpiano May 9, 2020
0958fda
Fix ActorRef type references
davidkpiano May 9, 2020
f24a8e0
Enforce subscription
davidkpiano May 9, 2020
eebcf42
Refactor ObservableActorRef (behavior private, id -> name)
davidkpiano May 9, 2020
371ad3f
Change initial -> current
davidkpiano May 9, 2020
b331613
Remove ref
davidkpiano May 9, 2020
b97997f
Remove interpreter.spawn
davidkpiano May 9, 2020
1316980
Remove comment
davidkpiano May 9, 2020
c1bba85
Refactor isActorRef
davidkpiano May 9, 2020
b714988
Remove spies
davidkpiano May 9, 2020
499d68b
Symbol -> Symbol.for
davidkpiano May 9, 2020
62f49ca
Fix types
davidkpiano May 9, 2020
985052a
Fix build
davidkpiano May 9, 2020
086b63b
Fix build (again)
davidkpiano May 9, 2020
13918f4
Add simple mailbox
davidkpiano May 10, 2020
194d2b9
doesn't exist -> already exists (for clarity)
djD-REK May 10, 2020
84dbd2e
Merge pull request #1182 from djD-REK/patch-1
davidkpiano May 10, 2020
f685e67
Update README.md
davidkpiano May 10, 2020
5869c88
Ensure actions are executed from a restored initial state. Fixes #1174
davidkpiano May 10, 2020
dbc6a16
Add changeset
davidkpiano May 10, 2020
5b0c2f4
Fix wording on test
davidkpiano May 10, 2020
b967db6
Merge pull request #1183 from davidkpiano/davidkpiano/1174
davidkpiano May 10, 2020
9ddcd94
Separate Actor and ActorRef
davidkpiano May 12, 2020
326db72
Fixed an issue with invoked service not being correctly started
Andarist May 13, 2020
560e28f
Merge pull request #1185 from davidkpiano/fix/1180
davidkpiano May 13, 2020
942ef24
Merge branch 'master' into next
davidkpiano May 16, 2020
0de2d4d
Address failling tests (temporarily)
davidkpiano May 16, 2020
e0163c0
Merge branch 'next' into v5/actorref
davidkpiano May 16, 2020
7713bba
Rename services -> behaviors
davidkpiano May 16, 2020
13a272c
Fix observer next null check
davidkpiano May 16, 2020
1a0e07d
Remove observer.complete being called after observer.error
davidkpiano May 16, 2020
c67923f
Add comment for getInitialState(self)
davidkpiano May 16, 2020
468ff9b
Move setting processingStatus outside of loop
davidkpiano May 16, 2020
85c938d
Remove ActionTypes.Start, use ActionTypes.Invoke
davidkpiano May 16, 2020
4260d6c
More services -> behaviors renaming
davidkpiano May 16, 2020
5569d75
Fix services -> behaviors in UseMachine.vue test
davidkpiano May 16, 2020
390eaaa
Add changeset + clean up invoke.test.ts
davidkpiano May 21, 2020
374d9f1
Modify changeset
davidkpiano May 21, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/great-toes-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'xstate': patch
---

Fixed an issue with invoked service not being correctly started if other service got stopped in a subsequent microstep (in response to raised or null event).
105 changes: 105 additions & 0 deletions .changeset/rotten-rivers-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
'xstate': major
---

**Breaking:** Activities are no longer a separate concept. Instead, activities are invoked. Internally, this is how activities worked. The API is consolidated so that `activities` are no longer a property of the state node or machine options:

```diff
import { createMachine } from 'xstate';
+import { invokeActivity } from 'xstate/invoke';

const machine = createMachine(
{
// ...
- activities: 'someActivity',
+ invoke: {
+ src: 'someActivity'
+ }
},
{
- activities: {
+ behaviors: {
- someActivity: ((context, event) => {
+ someActivity: invokeActivity((context, event) => {
// ... some continuous activity

return () => {
// dispose activity
}
})
}
}
);
```

**Breaking:** The `services` option passed as the second argument to `createMachine(config, options)` is renamed to `behaviors`. Each value in `behaviors`should be a function that takes in `context` and `event` and returns a [behavior](TODO: link). The provided invoke creators are:

- `invokeActivity`
- `invokePromise`
- `invokeCallback`
- `invokeObservable`
- `invokeMachine`

```diff
import { createMachine } from 'xstate';
+import { invokePromise } from 'xstate/invoke';

const machine = createMachine(
{
// ...
invoke: {
src: 'fetchFromAPI'
}
},
{
- services: {
+ behaviors: {
- fetchFromAPI: ((context, event) => {
+ fetchFromAPI: invokePromise((context, event) => {
// ... (return a promise)
})
}
}
);
```

**Breaking:** The `state.children` property is now a mapping of invoked actor IDs to their `ActorRef` instances.

**Breaking:** The way that you interface with invoked/spawned actors is now through `ActorRef` instances. An `ActorRef` is an opaque reference to an `Actor`, which should be never referenced directly.

**Breaking:** The `spawn` function is no longer imported globally. Spawning actors is now done inside of `assign(...)`, as seen below:

```diff
-import { createMachine, spawn } from 'xstate';
+import { createMachine } from 'xstate';

const machine = createMachine({
// ...
entry: assign({
- someRef: (context, event) => {
+ someRef: (context, event, { spawn }) => {
- return spawn(somePromise);
+ return spawn.from(somePromise);
}
})
});

```

**Breaking:** The `src` of an `invoke` config is now either a string that references the machine's `options.behaviors`, or a `BehaviorCreator`, which is a function that takes in `context` and `event` and returns a `Behavior`:

```diff
import { createMachine } from 'xstate';
+import { invokePromise } from 'xstate/invoke';

const machine = createMachine({
// ...
invoke: {
- src: (context, event) => somePromise
+ src: invokePromise((context, event) => somePromise)
}
// ...
});
```

**Breaking:** The `origin` of an `SCXML.Event` is no longer a string, but an `ActorRef` instance.
5 changes: 5 additions & 0 deletions .changeset/wet-swans-deny.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'xstate': minor
---

Actions from a restored state provided as a custom initial state to `interpret(machine).start(initialState)` are now executed properly. See #1174 for more information.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ JavaScript and TypeScript [finite state machines](https://en.wikipedia.org/wiki/
- [💚 `@xstate/vue`](https://github.com/davidkpiano/xstate/tree/master/packages/xstate-vue) - Vue composition functions and utilities for using XState in Vue applications
- [✅ `@xstate/test`](https://github.com/davidkpiano/xstate/tree/master/packages/xstate-test) - Model-based testing utilities for XState

## Templates

Get started by forking one of these templates on CodeSandbox:

- [XState Template](https://codesandbox.io/s/xstate-example-template-m4ckv) - no framework
- [XState + TypeScript Template](https://codesandbox.io/s/xstate-typescript-template-s9kz8) - no framework
- [XState + React Template](https://codesandbox.io/s/xstate-react-template-3t2tg)
- [XState + React + TypeScript Template](https://codesandbox.io/s/xstate-react-typescript-template-wjdvn)
- [XState + Vue Template](https://codesandbox.io/s/xstate-vue-template-composition-api-1n23l)
- [XState + Svelte Template](https://codesandbox.io/s/xstate-svelte-template-jflv1)

## Super quick start

```bash
Expand Down
14 changes: 9 additions & 5 deletions docs/packages/xstate-react/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ A [React hook](https://reactjs.org/hooks) that interprets the given `machine` an
**Arguments**

- `machine` - An [XState machine](https://xstate.js.org/docs/guides/machines.html).
- `options` (optional) - [Interpreter options](https://xstate.js.org/docs/guides/interpretation.html#options) OR one of the following Machine Config options: `guards`, `actions`, `activities`, `services`, `delays`, `immediate`, `context`, or `state`.
- `options` (optional) - [Interpreter options](https://xstate.js.org/docs/guides/interpretation.html#options) OR one of the following Machine Config options: `guards`, `actions`, `activities`, `behaviors`, `delays`, `immediate`, `context`, or `state`.

**Returns** a tuple of `[state, send, service]`:

Expand Down Expand Up @@ -148,13 +148,16 @@ const Fetcher = ({ onFetch = () => new Promise(res => res('some data')) }) => {
};
```

## Configuring Machines <Badge text="0.7+"/>
## Configuring Machines

Existing machines can be configured by passing the machine options as the 2nd argument of `useMachine(machine, options)`.

Example: the `'fetchData'` service and `'notifySuccess'` action are both configurable:

```js
import { createMachine } from 'xstate';
import { invokePromise } from 'xstate/invoke';

const fetchMachine = Machine({
id: 'fetch',
initial: 'idle',
Expand Down Expand Up @@ -200,9 +203,10 @@ const Fetcher = ({ onResolve }) => {
actions: {
notifySuccess: (ctx) => onResolve(ctx.data)
},
services: {
fetchData: (_, e) =>
fetch(`some/api/${e.query}`).then((res) => res.json())
behaviors: {
fetchData: invokePromise((_, event) =>
fetch(`some/api/${event.query}`).then((res) => res.json())
)
}
});

Expand Down
17 changes: 9 additions & 8 deletions docs/packages/xstate-test/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
This package contains utilities for facilitating [model-based testing](https://en.wikipedia.org/wiki/Model-based_testing) for any software.

- **Talk**: [Write Fewer Tests! From Automation to Autogeneration](https://slides.com/davidkhourshid/mbt) at React Rally 2019 ([🎥 Video](https://www.youtube.com/watch?v=tpNmPKjPSFQ))
- [Model-Based Testing in React with State Machines](https://css-tricks.com/model-based-testing-in-react-with-state-machines/)

## Quick Start

Expand Down Expand Up @@ -49,7 +50,7 @@ const toggleMachine = Machine({
/* ... */
},
meta: {
test: async page => {
test: async (page) => {
await page.waitFor('input:checked');
}
}
Expand All @@ -59,7 +60,7 @@ const toggleMachine = Machine({
/* ... */
},
meta: {
test: async page => {
test: async (page) => {
await page.waitFor('input:not(:checked)');
}
}
Expand All @@ -78,7 +79,7 @@ const toggleMachine = Machine(/* ... */);

const toggleModel = createModel(toggleMachine).withEvents({
TOGGLE: {
exec: async page => {
exec: async (page) => {
await page.click('input');
}
}
Expand All @@ -93,9 +94,9 @@ const toggleModel = createModel(toggleMachine).withEvents({
describe('toggle', () => {
const testPlans = toggleModel.getShortestPathPlans();

testPlans.forEach(plan => {
testPlans.forEach((plan) => {
describe(plan.description, () => {
plan.paths.forEach(path => {
plan.paths.forEach((path) => {
it(path.description, async () => {
// do any setup, then...

Expand Down Expand Up @@ -142,7 +143,7 @@ Provides testing details for each event. Each key in `eventsMap` is an object wh
```js
const toggleModel = createModel(toggleMachine).withEvents({
TOGGLE: {
exec: async page => {
exec: async (page) => {
await page.click('input');
}
}
Expand All @@ -167,7 +168,7 @@ const todosModel = createModel(todosMachine).withEvents({
const plans = todosModel.getShortestPathPlans({
// Tell the algorithm to limit state/event adjacency map to states
// that have less than 5 todos
filter: state => state.context.todos.length < 5
filter: (state) => state.context.todos.length < 5
});
```

Expand Down Expand Up @@ -195,7 +196,7 @@ Tests that all state nodes were covered (traversed) in the exected tests.
// Only test coverage for state nodes with a `.meta` property defined:

testModel.testCoverage({
filter: stateNode => !!stateNode.meta
filter: (stateNode) => !!stateNode.meta
});
```

Expand Down
2 changes: 1 addition & 1 deletion docs/packages/xstate-vue/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ A [Vue composition function](https://vue-composition-api-rfc.netlify.com/) that
**Arguments**

- `machine` - An [XState machine](https://xstate.js.org/docs/guides/machines.html).
- `options` (optional) - [Interpreter options](https://xstate.js.org/docs/guides/interpretation.html#options) OR one of the following Machine Config options: `guards`, `actions`, `activities`, `services`, `delays`, `immediate`, `context`, or `state`.
- `options` (optional) - [Interpreter options](https://xstate.js.org/docs/guides/interpretation.html#options) OR one of the following Machine Config options: `guards`, `actions`, `activities`, `behaviors`, `delays`, `immediate`, `context`, or `state`.

**Returns** `{ state, send, service}`:

Expand Down
24 changes: 12 additions & 12 deletions docs/tutorials/reddit.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ function invokeFetchSubreddit(context) {
const { subreddit } = context;

return fetch(`https://www.reddit.com/r/${subreddit}.json`)
.then(response => response.json())
.then(json => json.data.children.map(child => child.data));
.then((response) => response.json())
.then((json) => json.data.children.map((child) => child.data));
}

const redditMachine = Machine({
Expand Down Expand Up @@ -207,9 +207,9 @@ import { assert } from 'chai';
import { redditMachine } from '../path/to/redditMachine';

describe('reddit machine (live)', () => {
it('should load posts of a selected subreddit', done => {
it('should load posts of a selected subreddit', (done) => {
const redditService = interpret(redditMachine)
.onTransition(state => {
.onTransition((state) => {
// when the state finally reaches 'selected.loaded',
// the test has succeeded.

Expand Down Expand Up @@ -249,11 +249,11 @@ const App = () => {
<main>
<header>
<select
onChange={e => {
onChange={(e) => {
send('SELECT', { name: e.target.value });
}}
>
{subreddits.map(subreddit => {
{subreddits.map((subreddit) => {
return <option key={subreddit}>{subreddit}</option>;
})}
</select>
Expand All @@ -263,7 +263,7 @@ const App = () => {
{current.matches({ selected: 'loading' }) && <div>Loading...</div>}
{current.matches({ selected: 'loaded' }) && (
<ul>
{posts.map(post => (
{posts.map((post) => (
<li key={post.title}>{post.title}</li>
))}
</ul>
Expand All @@ -284,7 +284,7 @@ Consider two machines:
- A `subredditMachine`, which is the machine responsible for loading and displaying its specified subreddit

```js
const createSubredditMachine = subreddit => {
const createSubredditMachine = (subreddit) => {
return Machine({
id: 'subreddit',
initial: 'loading',
Expand Down Expand Up @@ -362,7 +362,7 @@ const Subreddit = ({ name }) => {
return (
<div>
Failed to load posts.{' '}
<button onClick={_ => send('RETRY')}>Retry?</button>
<button onClick={(_) => send('RETRY')}>Retry?</button>
</div>
);
}
Expand All @@ -381,11 +381,11 @@ const Subreddit = ({ name }) => {
<h2>{subreddit}</h2>
<small>
Last updated: {lastUpdated}{' '}
<button onClick={_ => send('REFRESH')}>Refresh</button>
<button onClick={(_) => send('REFRESH')}>Refresh</button>
</small>
</header>
<ul>
{posts.map(post => {
{posts.map((post) => {
return <li key={post.id}>{post.title}</li>;
})}
</ul>
Expand Down Expand Up @@ -468,7 +468,7 @@ const redditMachine = Machine({
SELECT: {
target: '.selected',
actions: assign((context, event) => {
// Use the existing subreddit actor if one doesn't exist
// Use the existing subreddit actor if one already exists
let subreddit = context.subreddits[event.name];

if (subreddit) {
Expand Down
7 changes: 7 additions & 0 deletions packages/core/actor/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"main": "dist/xstate.cjs.js",
"module": "dist/xstate.esm.js",
"preconstruct": {
"source": "../src/Actor"
}
}
7 changes: 7 additions & 0 deletions packages/core/behavior/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"main": "dist/xstate.cjs.js",
"module": "dist/xstate.esm.js",
"preconstruct": {
"source": "../src/behavior"
}
}
6 changes: 4 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"files": [
"dist",
"actions",
"invoke"
"invoke",
"behavior"
],
"keywords": [
"statechart",
Expand Down Expand Up @@ -49,7 +50,8 @@
"entrypoints": [
".",
"actions",
"invoke"
"invoke",
"behavior"
]
}
}
Loading