Skip to content

Commit

Permalink
[v5] systemId for spawn() (#3990)
Browse files Browse the repository at this point in the history
* Add systemId to spawn() options

* Add changeset
  • Loading branch information
davidkpiano committed May 18, 2023
1 parent 9dd2ce8 commit fe6db14
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 13 deletions.
19 changes: 19 additions & 0 deletions .changeset/great-kangaroos-confess.md
@@ -0,0 +1,19 @@
---
'xstate': major
---

You can now add a `systemId` to spawned actors to reference them anywhere in the system.

```ts
const machine = createMachine({
// ...
context: ({ spawn }) => ({
actorRef: spawn(
createMachine({
// ...
}),
{ systemId: 'actorRef' }
)
})
});
```
8 changes: 1 addition & 7 deletions .vscode/launch.json
Expand Up @@ -6,13 +6,7 @@
"request": "launch",
"name": "Jest Current File",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": [
"${file}",
"--config",
"jest.config.js",
"--no-cache",
"--testTimeout=2147483647"
],
"args": ["${file}", "--config", "jest.config.js", "--no-cache"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/spawn.ts
Expand Up @@ -21,6 +21,7 @@ export function createSpawner<
mutCapturedActions: InvokeActionObject[]
): Spawner {
return (src, options = {}) => {
const { systemId } = options;
if (isString(src)) {
const referenced = resolveReferencedActor(machine.options.actors[src]);

Expand Down Expand Up @@ -49,7 +50,8 @@ export function createSpawner<
src: actorRef, // TODO
ref: actorRef,
meta: undefined,
input
input,
systemId
}) as any as InvokeActionObject
);

Expand All @@ -65,7 +67,8 @@ export function createSpawner<
const actorRef = interpret(src, {
id: options.id || 'anonymous',
parent: self,
input: options.input
input: options.input,
systemId
});

mutCapturedActions.push(
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/types.ts
Expand Up @@ -127,6 +127,7 @@ export type Spawner = <T extends ActorBehavior<any, any> | string>( // TODO: rea
behavior: T,
options?: Partial<{
id: string;
systemId?: string;
input: any;
}>
) => T extends ActorBehavior<infer TActorEvent, infer TActorEmitted>
Expand Down
81 changes: 77 additions & 4 deletions packages/core/test/system.test.ts
Expand Up @@ -5,11 +5,12 @@ import {
assign,
createMachine,
interpret,
sendTo
sendTo,
stop
} from '../src/index.ts';

describe('system', () => {
it('should register an actor (implicit system)', (done) => {
it('should register an invoked actor', (done) => {
type MySystem = ActorSystem<{
actors: {
receiver: ActorRef<{ type: 'HELLO' }>;
Expand Down Expand Up @@ -51,7 +52,56 @@ describe('system', () => {
interpret(machine).start();
});

it('system can be immediatelly accessed outside the actor', () => {
it('should register a spawned actor', (done) => {
type MySystem = ActorSystem<{
actors: {
receiver: ActorRef<{ type: 'HELLO' }>;
};
}>;

const machine = createMachine({
id: 'parent',
context: ({ spawn }) => ({
ref: spawn(
fromCallback((_, receive) => {
receive((event) => {
expect(event.type).toBe('HELLO');
done();
});
}),
{ systemId: 'receiver' }
)
}),
on: {
toggle: {
actions: assign({
machineRef: ({ spawn }) => {
return spawn(
createMachine({
id: 'childmachine',
entry: ({ system }) => {
const receiver = (system as MySystem)?.get('receiver');

if (receiver) {
receiver.send({ type: 'HELLO' });
} else {
throw new Error('no');
}
}
})
);
}
})
}
}
});

const actor = interpret(machine).start();

actor.send({ type: 'toggle' });
});

it('system can be immediately accessed outside the actor', () => {
const machine = createMachine({
invoke: {
systemId: 'someChild',
Expand All @@ -71,7 +121,7 @@ describe('system', () => {
expect(actor.system.get('test')).toBe(actor);
});

it('should remove actor from receptionist if stopped', () => {
it('should remove invoked actor from receptionist if stopped', () => {
const machine = createMachine({
initial: 'active',
states: {
Expand All @@ -97,6 +147,29 @@ describe('system', () => {
expect(actor.system.get('test')).toBeUndefined();
});

it('should remove spawned actor from receptionist if stopped', () => {
const machine = createMachine({
context: ({ spawn }) => ({
ref: spawn(createMachine({}), {
systemId: 'test'
})
}),
on: {
toggle: {
actions: stop(({ context }) => context.ref)
}
}
});

const actor = interpret(machine).start();

expect(actor.system.get('test')).toBeDefined();

actor.send({ type: 'toggle' });

expect(actor.system.get('test')).toBeUndefined();
});

it('should throw an error if an actor with the system ID already exists', () => {
const machine = createMachine({
initial: 'inactive',
Expand Down

0 comments on commit fe6db14

Please sign in to comment.