Skip to content

Commit

Permalink
Add TagsFrom helper type (#3835)
Browse files Browse the repository at this point in the history
* add TagsFrom helper type

* simplify TagsFrom helper

* move TagsFrom tests to typegenTypes

* remove unused import

* Apply suggestions from code review

---------

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
  • Loading branch information
with-heart and Andarist committed Feb 18, 2023
1 parent 32ed8c4 commit 4314720
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 12 deletions.
31 changes: 31 additions & 0 deletions .changeset/mean-oranges-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
'xstate': minor
---

The new `TagsFrom` helper type extracts the type of `tags` from a state machine when typegen is enabled:

```ts
const machine = createMachine({
// `tags` attached to machine via typegen
tsTypes: {} as import('./machine.typegen').Typegen0,
tags: ['a', 'b'],
states: {
idle: { tags: 'c' }
},
});

type Tags = TagsFrom<typeof machine>; // 'a' | 'b' | 'c'
```

If typegen is not enabled, `TagsFrom` returns `string`:

```ts
const machine = createMachine({
tags: ['a', 'b'],
states: {
idle: { tags: 'c' }
}
});

type Tags = TagsFrom<typeof machine>; // string
```
28 changes: 17 additions & 11 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,9 @@ type SimpleActionsOf<T extends BaseActionObject> = ActionObject<
/**
* Events that do not require payload
*/
export type SimpleEventsOf<TEvent extends EventObject> =
ExtractWithSimpleSupport<TEvent>;
export type SimpleEventsOf<
TEvent extends EventObject
> = ExtractWithSimpleSupport<TEvent>;

export type BaseAction<
TContext,
Expand Down Expand Up @@ -1984,18 +1985,23 @@ type Matches<TypegenEnabledArg, TypegenDisabledArg> = {
(stateValue: TypegenDisabledArg): any;
};

export type StateValueFrom<TMachine extends AnyStateMachine> =
StateFrom<TMachine>['matches'] extends Matches<
infer TypegenEnabledArg,
infer TypegenDisabledArg
>
? TMachine['__TResolvedTypesMeta'] extends TypegenEnabled
? TypegenEnabledArg
: TypegenDisabledArg
: never;
export type StateValueFrom<
TMachine extends AnyStateMachine
> = StateFrom<TMachine>['matches'] extends Matches<
infer TypegenEnabledArg,
infer TypegenDisabledArg
>
? TMachine['__TResolvedTypesMeta'] extends TypegenEnabled
? TypegenEnabledArg
: TypegenDisabledArg
: never;

export type PredictableActionArgumentsExec = (
action: ActionObject<unknown, EventObject>,
context: unknown,
_event: SCXML.Event<EventObject>
) => void;

export type TagsFrom<TMachine extends AnyStateMachine> = Parameters<
StateFrom<TMachine>['hasTag']
>[0];
38 changes: 37 additions & 1 deletion packages/core/test/typeHelpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
EventFrom,
interpret,
MachineOptionsFrom,
StateValueFrom
StateValueFrom,
TagsFrom
} from '../src';
import { createModel } from '../src/model';
import { TypegenMeta } from '../src/typegenTypes';
Expand Down Expand Up @@ -428,3 +429,38 @@ describe('EmittedFrom', () => {
acceptState("isn't any");
});
});

describe('tags', () => {
it('derives tags from StateMachine when typegen is enabled', () => {
interface TypesMeta extends TypegenMeta {
tags: 'a' | 'b' | 'c';
}
const machine = createMachine({
tsTypes: {} as TypesMeta
});

type Tags = TagsFrom<typeof machine>;

const acceptTag = (_tag: Tags) => {};

acceptTag('a');
acceptTag('b');
acceptTag('c');
// @ts-expect-error d is not a valid tag
acceptTag('d');
});

it('derives string from StateMachine without typegen', () => {
const machine = createMachine({});

type Tags = TagsFrom<typeof machine>;

const acceptTag = (_tag: Tags) => {};

acceptTag('a');
acceptTag('b');
acceptTag('c');
// d is a valid tag, as is any string
acceptTag('d');
});
});

0 comments on commit 4314720

Please sign in to comment.