Skip to content

Commit

Permalink
Make spawn input required when defined (with tests)
Browse files Browse the repository at this point in the history
  • Loading branch information
SandroMaglione committed Dec 9, 2024
1 parent 88d7fca commit 7dc0b12
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 48 deletions.
99 changes: 57 additions & 42 deletions packages/core/src/spawn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
IsNotNever,
ProvidedActor,
RequiredActorOptions,
TODO
TODO,
type RequiredLogicInput
} from './types.ts';
import { resolveReferencedActor } from './utils.ts';

Expand All @@ -36,42 +37,56 @@ type SpawnOptions<
>
: never;

export type Spawner<TActor extends ProvidedActor> = IsLiteralString<
TActor['src']
> extends true
? {
<TSrc extends TActor['src']>(
logic: TSrc,
...[options]: SpawnOptions<TActor, TSrc>
): ActorRefFromLogic<GetConcreteByKey<TActor, 'src', TSrc>['logic']>;
<TLogic extends AnyActorLogic>(
src: TLogic,
options?: {
id?: never;
systemId?: string;
input?: InputFrom<TLogic>;
syncSnapshot?: boolean;
}
): ActorRefFromLogic<TLogic>;
}
: <TLogic extends AnyActorLogic | string>(
src: TLogic,
options?: {
id?: string;
systemId?: string;
input?: TLogic extends string ? unknown : InputFrom<TLogic>;
syncSnapshot?: boolean;
export type Spawner<TActor extends ProvidedActor> =
IsLiteralString<TActor['src']> extends true
? {
<TSrc extends TActor['src']>(
logic: TSrc,
...[options]: SpawnOptions<TActor, TSrc>
): ActorRefFromLogic<GetConcreteByKey<TActor, 'src', TSrc>['logic']>;
<TLogic extends AnyActorLogic>(
src: TLogic,
...[options]: ConditionalRequired<
[
options?: {
id?: never;
systemId?: string;
input?: InputFrom<TLogic>;
syncSnapshot?: boolean;
} & { [K in RequiredLogicInput<TLogic>]: unknown }
],
IsNotNever<RequiredLogicInput<TLogic>>
>
): ActorRefFromLogic<TLogic>;
}
) => TLogic extends AnyActorLogic ? ActorRefFromLogic<TLogic> : AnyActorRef;
: <TLogic extends AnyActorLogic | string>(
src: TLogic,
...[options]: ConditionalRequired<
[
options?: {
id?: string;
systemId?: string;
input?: TLogic extends string ? unknown : InputFrom<TLogic>;
syncSnapshot?: boolean;
} & (TLogic extends AnyActorLogic
? { [K in RequiredLogicInput<TLogic>]: unknown }
: {})
],
IsNotNever<
TLogic extends AnyActorLogic ? RequiredLogicInput<TLogic> : never
>
>
) => TLogic extends AnyActorLogic
? ActorRefFromLogic<TLogic>
: AnyActorRef;

export function createSpawner(
actorScope: AnyActorScope,
{ machine, context }: AnyMachineSnapshot,
event: AnyEventObject,
spawnedChildren: Record<string, AnyActorRef>
): Spawner<any> {
const spawn: Spawner<any> = (src, options = {}) => {
const { systemId, input } = options;
const spawn: Spawner<any> = ((src, options) => {
if (typeof src === 'string') {
const logic = resolveReferencedActor(machine, src);

Expand All @@ -82,38 +97,38 @@ export function createSpawner(
}

const actorRef = createActor(logic, {
id: options.id,
id: options?.id,
parent: actorScope.self,
syncSnapshot: options.syncSnapshot,
syncSnapshot: options?.syncSnapshot,
input:
typeof input === 'function'
? input({
typeof options?.input === 'function'
? options.input({
context,
event,
self: actorScope.self
})
: input,
: options?.input,
src,
systemId
systemId: options?.systemId
}) as any;

spawnedChildren[actorRef.id] = actorRef;

return actorRef;
} else {
const actorRef = createActor(src, {
id: options.id,
id: options?.id,
parent: actorScope.self,
syncSnapshot: options.syncSnapshot,
input: options.input,
syncSnapshot: options?.syncSnapshot,
input: options?.input,
src,
systemId
systemId: options?.systemId
});

return actorRef;
}
};
return (src, options) => {
}) as Spawner<any>;
return ((src, options) => {
const actorRef = spawn(src, options) as TODO; // TODO: fix types
spawnedChildren[actorRef.id] = actorRef;
actorScope.defer(() => {
Expand All @@ -123,5 +138,5 @@ export function createSpawner(
actorRef.start();
});
return actorRef;
};
}) as Spawner<any>;
}
15 changes: 9 additions & 6 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import type { MachineSnapshot } from './State.ts';
import type { StateMachine } from './StateMachine.ts';
import type { StateNode } from './StateNode.ts';
import { AssignArgs } from './actions/assign.ts';
import { ExecutableRaiseAction } from './actions/raise.ts';
import { ExecutableSendToAction } from './actions/send.ts';
import { PromiseActorLogic } from './actors/promise.ts';
import { Guard, GuardPredicate, UnknownGuard } from './guards.ts';
import type { Actor, ProcessingStatus } from './createActor.ts';
import { Guard, GuardPredicate, UnknownGuard } from './guards.ts';
import { InspectionEvent } from './inspection.ts';
import { Spawner } from './spawn.ts';
import { AnyActorSystem, Clock } from './system.js';
import { InspectionEvent } from './inspection.ts';
import { ExecutableRaiseAction } from './actions/raise.ts';
import { ExecutableSendToAction } from './actions/send.ts';

export type Identity<T> = { [K in keyof T]: T[K] };

Expand Down Expand Up @@ -805,8 +805,8 @@ export type InvokeConfig<
>
>;
/**
* The transition to take upon the invoked child machine sending an
* error event.
* The transition to take upon the invoked child machine sending an error
* event.
*/
onError?:
| string
Expand Down Expand Up @@ -2452,6 +2452,9 @@ export type RequiredActorOptions<TActor extends ProvidedActor> =
| (undefined extends TActor['id'] ? never : 'id')
| (undefined extends InputFrom<TActor['logic']> ? never : 'input');

export type RequiredLogicInput<TLogic extends AnyActorLogic> =
undefined extends InputFrom<TLogic> ? never : 'input';

type ExtractLiteralString<T extends string | undefined> = T extends string
? string extends T
? never
Expand Down
18 changes: 18 additions & 0 deletions packages/core/test/spawn.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ActorRefFrom, createActor, createMachine } from '../src';

describe('spawn inside machine', () => {
it('input is required when defined in actor', () => {
const childMachine = createMachine({
types: { input: {} as { value: number } }
});
const machine = createMachine({
types: {} as { context: { ref: ActorRefFrom<typeof childMachine> } },
context: ({ spawn }) => ({
ref: spawn(childMachine, { input: { value: 42 }, systemId: 'test' })
})
});

const actor = createActor(machine).start();
expect(actor.system.get('test')).toBeDefined();
});
});
49 changes: 49 additions & 0 deletions packages/core/test/spawn.types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { ActorRefFrom, assign, createMachine } from '../src';

describe('spawn inside machine', () => {
it('input is required when defined in actor', () => {
const childMachine = createMachine({
types: { input: {} as { value: number } }
});
createMachine({
types: {} as { context: { ref: ActorRefFrom<typeof childMachine> } },
context: ({ spawn }) => ({
ref: spawn(childMachine, { input: { value: 42 } })
}),
initial: 'idle',
states: {
Idle: {
on: {
event: {
actions: assign(({ spawn }) => ({
ref: spawn(childMachine, { input: { value: 42 } })
}))
}
}
}
}
});
});

it('input is not required when not defined in actor', () => {
const childMachine = createMachine({});
createMachine({
types: {} as { context: { ref: ActorRefFrom<typeof childMachine> } },
context: ({ spawn }) => ({
ref: spawn(childMachine)
}),
initial: 'idle',
states: {
Idle: {
on: {
some: {
actions: assign(({ spawn }) => ({
ref: spawn(childMachine)
}))
}
}
}
}
});
});
});

0 comments on commit 7dc0b12

Please sign in to comment.