Skip to content

Commit

Permalink
feat: add createActorFromLogic, createActorWith
Browse files Browse the repository at this point in the history
This adds two curried functions to help create actors
  • Loading branch information
boneskull committed Aug 20, 2024
1 parent 15b38ad commit a5be7c0
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 0 deletions.
102 changes: 102 additions & 0 deletions src/create-actor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {
type Actor,
type ActorOptions,
type AnyActorLogic,
createActor,
type InputFrom,
type IsNotNever,
} from 'xstate';

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

export type CreateActorOptions<TLogic extends AnyActorLogic> =
IsNotNever<RequiredOptions<TLogic>> extends true
? {input: ActorOptions<TLogic>['input']} & ActorOptions<TLogic>
: ActorOptions<TLogic>;

export type CurryCreateActorFromLogic = (() => CurryCreateActorFromLogic) &
(<TLogic extends AnyActorLogic>(
logic: TLogic,
options: CreateActorOptions<TLogic>,
) => Actor<TLogic>) &
(<TLogic extends AnyActorLogic>(
logic: TLogic,
) => CurryCreateActorFromLogicP1<TLogic>);

export type CurryCreateActorFromLogicP1<TLogic extends AnyActorLogic> = ((
options: CreateActorOptions<TLogic>,
) => Actor<TLogic>) &
(() => CurryCreateActorFromLogicP1<TLogic>);

export type CurryCreateActorWith = (() => CurryCreateActorWith) &
(<TLogic extends AnyActorLogic>(
options: CreateActorOptions<TLogic>,
logic: TLogic,
) => Actor<TLogic>) &
(<TLogic extends AnyActorLogic>(
options: CreateActorOptions<TLogic>,
) => CurryCreateActorWithP1<TLogic>);

export type CurryCreateActorWithP1<TLogic extends AnyActorLogic> = ((
logic: TLogic,
) => Actor<TLogic>) &
(() => CurryCreateActorWithP1<TLogic>);

export function createActorFromLogic(): typeof createActorFromLogic;

export function createActorFromLogic<TLogic extends AnyActorLogic>(
logic: TLogic,
): CurryCreateActorFromLogicP1<TLogic>;

export function createActorFromLogic<TLogic extends AnyActorLogic>(
logic: TLogic,
options: CreateActorOptions<TLogic>,
): Actor<TLogic>;

export function createActorFromLogic<TLogic extends AnyActorLogic>(
logic?: TLogic,
options?: CreateActorOptions<TLogic>,
) {
if (logic) {
if (options) {
return createActor(logic, options);
}

return ((options?: CreateActorOptions<TLogic>) => {
return options
? createActorFromLogic(logic, options)
: createActorFromLogic(logic);
}) as CurryCreateActorFromLogicP1<TLogic>;
}

return createActorFromLogic;
}

export function createActorWith(): typeof createActorWith;

export function createActorWith<TLogic extends AnyActorLogic>(
options: CreateActorOptions<TLogic>,
): CurryCreateActorWithP1<TLogic>;

export function createActorWith<TLogic extends AnyActorLogic>(
options: CreateActorOptions<TLogic>,
logic: TLogic,
): Actor<TLogic>;

export function createActorWith<TLogic extends AnyActorLogic>(
options?: CreateActorOptions<TLogic>,
logic?: TLogic,
) {
if (options) {
if (logic) {
return createActor(logic, options);
}

return ((logic?: TLogic) => {
return logic ? createActorWith(options, logic) : createActorWith(options);
}) as CurryCreateActorWithP1<TLogic>;
}

return createActorWith;
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export * from './actor.js';

export * from './create-actor.js';

export type * from './types.js';

export * from './until-done.js';
Expand Down
97 changes: 97 additions & 0 deletions test/create-actor.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {strict as assert} from 'node:assert';
import {afterEach, describe, it} from 'node:test';
import {Actor} from 'xstate';

import {createActorFromLogic, createActorWith} from '../src/create-actor.js';
import {dummyLogic} from './fixture.js';

describe('create-actor', () => {
describe('createActorFromLogic()', () => {
let actor: Actor<typeof dummyLogic>;

afterEach(() => {
if (actor) {
actor.stop();
}
});

describe('when called with logic and options', () => {
it('should create an actor with the provided logic and options', () => {
const options = {id: 'test-actor'};

actor = createActorFromLogic(dummyLogic, options);

assert.ok(actor instanceof Actor);
});
});

describe('when called with only logic', () => {
it('should return a curried function that accepts options', () => {
const curriedFn = createActorFromLogic(dummyLogic);

assert.equal(typeof curriedFn, 'function');

const options = {id: 'test-actor'};

actor = curriedFn(options);

assert.equal(actor.id, options.id);
assert.equal(actor.logic, dummyLogic);
});
});

describe('when called with no arguments', () => {
it('should return itself', () => {
const curriedFn = createActorFromLogic();

assert.equal(curriedFn, createActorFromLogic);
});
});
});

describe('createActorWith()', () => {
let actor: Actor<typeof dummyLogic>;

afterEach(() => {
if (actor) {
actor.stop();
}
});

describe('when called with logic and options', () => {
it('should create an actor with the provided logic and options', () => {
const options = {id: 'test-actor'};

actor = createActorWith(options, dummyLogic);

assert.ok(actor instanceof Actor);
assert.equal(actor.id, options.id);
assert.equal(actor.logic, dummyLogic);
});
});

describe('when called with only options', () => {
it('should return a curried function that accepts logic', () => {
const options = {id: 'test-actor'};

const curriedFn = createActorWith<typeof dummyLogic>(options);

assert.equal(typeof curriedFn, 'function');

actor = curriedFn(dummyLogic);

assert.ok(actor instanceof Actor);
assert.equal(actor.id, options.id);
assert.equal(actor.logic, dummyLogic);
});
});

describe('when called with no arguments', () => {
it('should return itself', () => {
const curriedFn = createActorWith();

assert.equal(curriedFn, createActorWith);
});
});
});
});

0 comments on commit a5be7c0

Please sign in to comment.