Open github0013 opened 3 years ago
@github0013 Are each of the id's unique on your machine?
@mattpocock yes
basicSwitch and basicSwitch2
{
"scripts": {
"dev": "next",
"codegen": "xstate-codegen \"src/**/**.machine.ts\" --outDir=\"src\""
},
"dependencies": {
"@xstate/inspect": "^0.4.1",
"@xstate/react": "^1.3.1",
"next": "^10.1.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"xstate": "^4.17.1",
"xstate-codegen": "^0.3.0"
},
"devDependencies": {
"@types/react": "^17.0.3",
"typescript": "^4.2.3"
}
}
import { Machine } from "@xstate/compiled"
interface Context {}
type Event = { type: "toggle" }
export default Machine<Context, Event, "basicSwitch">({
context: {},
initial: "active",
states: {
active: {
on: {
toggle: {
target: "inactive",
},
},
},
inactive: {
on: {
toggle: {
target: "active",
},
},
},
},
})
import { Machine } from "@xstate/compiled"
interface Context {}
type Event = { type: "toggle" }
export default Machine<Context, Event, "basicSwitch2">({
context: {},
initial: "active",
states: {
active: {
on: {
toggle: {
target: "inactive",
},
},
},
inactive: {
on: {
toggle: {
target: "active",
},
},
},
},
})
import { useMachine } from "@xstate/compiled/react"
import React from "react"
import machine from "src/machines/switch.machine"
interface Props {}
const BasicSwitch: React.FC<Props> = (props) => {
const [state, send] = useMachine(machine, {})
return (
<>
<h1>{state.toStrings().join(" > ")}</h1>
<pre>{JSON.stringify(state.context, null, 2)}</pre>
<button
onClick={() => {
send("toggle")
}}
>
toggle
</button>
</>
)
}
export default BasicSwitch
import {
EventObject,
SingleOrArray,
InvokeConfig,
StateMachine,
Actions,
DoneEventObject,
DelayedTransitions,
DelayConfig,
Activity,
Mapper,
PropertyMapper,
Condition,
StateValue,
ActionObject,
ActionFunction,
ActivityConfig,
DoneInvokeEvent,
ErrorPlatformEvent,
InvokeCreator,
assign,
send,
Expr,
InterpreterOptions,
} from 'xstate';
import {
StateWithMatches,
InterpreterWithMatches,
RegisteredMachine,
} from '@xstate/compiled';
import { Interpreter } from 'xstate/lib/interpreter';
import { State } from 'xstate/lib/State';
import { StateNode } from 'xstate/lib/StateNode';
declare module '@xstate/compiled' {
type TwoLevelPartial<T extends object> = { [K in keyof T]?: Partial<T[K]>};
/** Generated Types */
export class BasicSwitchStateMachine<
TContext,
TEvent extends EventObject,
Id extends 'basicSwitch'
> extends StateNodeWithGeneratedTypes<TContext, any, TEvent> {
id: Id;
states: StateNode<TContext, any, TEvent>['states'];
_matches:
| 'active'
| 'inactive'
_options: {
context?: Partial<TContext>;
guards?: {
};
actions?: {
};
services?: {
};
activities?: {
};
delays?: {
};
devTools?: boolean;
};
_subState: {
targets: '.active' | 'active' | '.inactive' | 'inactive';
sources: never;
states: {
active: {
targets: 'active' | 'inactive';
sources: 'toggle';
states: {
};
}
inactive: {
targets: 'active' | 'inactive';
sources: 'toggle';
states: {
};
}
};
};
withConfig(
options: TwoLevelPartial<BasicSwitchStateMachine<
TContext,
TEvent,
'basicSwitch'
>['_options']>
): this;
}
export class BasicSwitch2StateMachine<
TContext,
TEvent extends EventObject,
Id extends 'basicSwitch2'
> extends StateNodeWithGeneratedTypes<TContext, any, TEvent> {
id: Id;
states: StateNode<TContext, any, TEvent>['states'];
_matches:
| 'active'
| 'inactive'
_options: {
context?: Partial<TContext>;
guards?: {
};
actions?: {
};
services?: {
};
activities?: {
};
delays?: {
};
devTools?: boolean;
};
_subState: {
targets: '.active' | 'active' | '.inactive' | 'inactive';
sources: never;
states: {
active: {
targets: 'active' | 'inactive';
sources: 'toggle';
states: {
};
}
inactive: {
targets: 'active' | 'inactive';
sources: 'toggle';
states: {
};
}
};
};
withConfig(
options: TwoLevelPartial<BasicSwitch2StateMachine<
TContext,
TEvent,
'basicSwitch2'
>['_options']>
): this;
}
export interface RegisteredMachinesMap<TContext, TEvent extends EventObject> {
basicSwitch: BasicSwitchStateMachine<TContext, TEvent, 'basicSwitch'>
basicSwitch2: BasicSwitch2StateMachine<TContext, TEvent, 'basicSwitch2'>
}
/** Utility types */
export type InvokeConfig<
TContext,
TEvent extends EventObject,
TSubState extends SubState
> = {
/**
* The unique identifier for the invoked machine. If not specified, this
* will be the machine's own `id`, or the URL (from `src`).
*/
id?: string;
/**
* The source of the machine to be invoked, or the machine itself.
*/
src:
| string
| StateMachine<any, any, any>
| InvokeCreator<TContext, TEvent, any>;
/**
* If `true`, events sent to the parent service will be forwarded to the invoked service.
*
* Default: `false`
*/
autoForward?: boolean;
/**
* @deprecated
*
* Use `autoForward` property instead of `forward`. Support for `forward` will get removed in the future.
*/
forward?: boolean;
/**
* Data from the parent machine's context to set as the (partial or full) context
* for the invoked child machine.
*
* Data should be mapped to match the child machine's context shape.
*/
data?:
| Mapper<TContext, TEvent, any>
| PropertyMapper<TContext, TEvent, any>;
/**
* The transition to take upon the invoked child machine reaching its final top-level state.
*/
onDone?:
| TSubState['targets']
| SingleOrArray<TransitionConfig<TContext, DoneEventObject, TSubState>>;
/**
* The transition to take upon the invoked child machine sending an error event.
*/
onError?:
| TSubState['targets']
| SingleOrArray<TransitionConfig<TContext, ErrorPlatformEvent, TSubState>>;
};
export type RegisteredMachine<
TContext,
TEvent extends EventObject
> = RegisteredMachinesMap<TContext, TEvent>[keyof RegisteredMachinesMap<
TContext,
TEvent
>];
export class StateNodeWithGeneratedTypes<
TContext,
TSchema,
TEvent extends EventObject
> extends StateNode<TContext, TSchema, TEvent> {}
export type DelayedTransitions<
TContext,
TEvent extends EventObject,
TSubState extends SubState
> =
| Record<
string | number,
| TSubState['targets']
| SingleOrArray<TransitionConfig<TContext, TEvent, TSubState>>
>
| Array<
TransitionConfig<TContext, TEvent, TSubState> & {
delay: number | string | Expr<TContext, TEvent, number>;
}
>;
export type InterpreterWithMatches<
TContext,
TSchema,
TEvent extends EventObject,
Id extends keyof RegisteredMachinesMap<TContext, TEvent>
> = Omit<Interpreter<TContext, TSchema, TEvent>, 'state'> & {
state: StateWithMatches<
TContext,
TEvent,
Id
>;
};
export type StateWithMatches<
TContext,
TEvent extends EventObject,
Id extends keyof RegisteredMachinesMap<TContext, TEvent>
> = Omit<State<TContext, TEvent>, 'matches'> & {
matches: (matches: RegisteredMachinesMap<TContext, TEvent>[Id]['_matches']) => boolean;
};
export function interpret<
TContext,
TSchema,
TEvent extends EventObject,
Id extends keyof RegisteredMachinesMap<TContext, TEvent>
>(
machine: Extract<RegisteredMachine<TContext, TEvent>, { id: Id }>,
options?: Partial<InterpreterOptions>
): InterpreterWithMatches<TContext, TSchema, TEvent, Id>;
export function Machine<
TContext,
TEvent extends EventObject,
Id extends keyof RegisteredMachinesMap<TContext, TEvent>
>(
config: MachineConfig<
TContext,
TEvent,
RegisteredMachinesMap<TContext, TEvent>[Id]['_subState']
>,
options?: TwoLevelPartial<
Extract<
RegisteredMachine<TContext, TEvent>,
{ id: Id }
>['_options']
>,
): RegisteredMachinesMap<TContext, TEvent>[Id];
export function createMachine<
TContext,
TEvent extends EventObject,
Id extends keyof RegisteredMachinesMap<TContext, TEvent>
>(
config: MachineConfig<
TContext,
TEvent,
RegisteredMachinesMap<TContext, TEvent>[Id]['_subState']
>,
options?: TwoLevelPartial<
Extract<
RegisteredMachine<TContext, TEvent>,
{ id: Id }
>['_options']
>,
): RegisteredMachinesMap<TContext, TEvent>[Id];
export interface MachineConfig<
TContext,
TEvent extends EventObject,
TSubState extends SubState
> extends StateNodeConfig<TContext, TEvent, TSubState> {
/**
* The initial context (extended state)
*/
context?: TContext | (() => TContext);
/**
* The machine's own version.
*/
version?: string;
}
export interface SubState {
targets: string;
sources: string;
states: Record<string, SubState>;
}
export type TransitionConfigTarget<TSubState extends SubState> =
| TSubState['targets']
| undefined;
export type TransitionTarget<TSubState extends SubState> = SingleOrArray<
TSubState['targets']
>;
export interface TransitionConfig<
TContext,
TEvent extends EventObject,
TSubState extends SubState
> {
cond?: Condition<TContext, TEvent>;
actions?: Actions<TContext, TEvent>;
in?: StateValue;
internal?: boolean;
target?: TransitionTarget<TSubState>;
meta?: Record<string, any>;
}
export type TransitionConfigOrTarget<
TContext,
TEvent extends EventObject,
TSubState extends SubState
> = SingleOrArray<
| TransitionConfigTarget<TSubState>
| TransitionConfig<TContext, TEvent, TSubState>
>;
export type TransitionsConfigMap<
TContext,
TEvent extends EventObject,
TSubState extends SubState
> = {
[K in TEvent['type']]?: TransitionConfigOrTarget<
TContext,
TEvent extends {
type: K;
}
? TEvent
: never,
TSubState
>;
} & {
''?: TransitionConfigOrTarget<TContext, TEvent, TSubState>;
} & {
'*'?: TransitionConfigOrTarget<TContext, TEvent, TSubState>;
};
export type TransitionsConfigArray<
TContext,
TEvent extends EventObject,
TSubState extends SubState
> = Array<TransitionsConfigMap<TContext, TEvent, TSubState>>;
export type TransitionsConfig<
TContext,
TEvent extends EventObject,
TSubState extends SubState
> =
| TransitionsConfigMap<TContext, TEvent, TSubState>
| TransitionsConfigArray<TContext, TEvent, TSubState>;
export interface StateNodeConfig<
TContext,
TEvent extends EventObject,
TSubState extends SubState
> {
/**
* The relative key of the state node, which represents its location in the overall state value.
* This is automatically determined by the configuration shape via the key where it was defined.
*/
key?: string;
/**
* The initial state node key.
*/
initial?: keyof TSubState['states'];
/**
* @deprecated
*/
parallel?: boolean | undefined;
/**
* The type of this state node:
*
* - `'atomic'` - no child state nodes
* - `'compound'` - nested child state nodes (XOR)
* - `'parallel'` - orthogonal nested child state nodes (AND)
* - `'history'` - history state node
* - `'final'` - final state node
*/
type?: 'atomic' | 'compound' | 'parallel' | 'final' | 'history';
/**
* The initial context (extended state) of the machine.
*
* Can be an object or a function that returns an object.
*/
context?: TContext | (() => TContext);
/**
* Indicates whether the state node is a history state node, and what
* type of history:
* shallow, deep, true (shallow), false (none), undefined (none)
*/
history?: 'shallow' | 'deep' | boolean | undefined;
/**
* The mapping of state node keys to their state node configurations (recursive).
*/
states?: {
[K in keyof TSubState['states']]: StateNodeConfig<
TContext,
TEvent,
TSubState['states'][K]
>;
};
/**
* The services to invoke upon entering this state node. These services will be stopped upon exiting this state node.
*/
invoke?: SingleOrArray<
| InvokeConfig<
TContext,
TEvent,
TSubState
>
| StateMachine<any, any, any>
>;
/**
* The mapping of event types to their potential transition(s).
*/
on?: TransitionsConfig<TContext, TEvent, TSubState>;
/**
* The action(s) to be executed upon entering the state node.
*
* @deprecated Use `entry` instead.
*/
onEntry?: Actions<
TContext,
Extract<TEvent, { type: TSubState['sources'] }>
>;
/**
* The action(s) to be executed upon entering the state node.
*/
entry?: Actions<TContext, Extract<TEvent, { type: TSubState['sources'] }>>;
/**
* The action(s) to be executed upon exiting the state node.
*
* @deprecated Use `exit` instead.
*/
onExit?: Actions<TContext, TEvent>;
/**
* The action(s) to be executed upon exiting the state node.
*/
exit?: Actions<TContext, TEvent>;
/**
* The potential transition(s) to be taken upon reaching a final child state node.
*
* This is equivalent to defining a `[done(id)]` transition on this state node's `on` property.
*/
onDone?:
| TSubState['targets']
| SingleOrArray<TransitionConfig<TContext, DoneEventObject, TSubState>>;
/**
* The mapping (or array) of delays (in milliseconds) to their potential transition(s).
* The delayed transitions are taken after the specified delay in an interpreter.
*/
after?: DelayedTransitions<TContext, TEvent, TSubState>;
/**
* An eventless transition that is always taken when this state node is active.
* Equivalent to a transition specified as an empty `''`' string in the `on` property.
*/
always?: TransitionConfigOrTarget<
TContext,
Extract<TEvent, { type: TSubState['sources'] }>,
TSubState
>;
/**
* The activities to be started upon entering the state node,
* and stopped upon exiting the state node.
*/
activities?: SingleOrArray<
Activity<TContext, Extract<TEvent, { type: TSubState['sources'] }>>
>;
/**
* @private
*/
parent?: StateNode<TContext, any, TEvent>;
strict?: boolean | undefined;
/**
* The meta data associated with this state node, which will be returned in State instances.
*/
meta?: any;
/**
* The data sent with the "done.state._id_" event if this is a final state node.
*
* The data will be evaluated with the current `context` and placed on the `.data` property
* of the event.
*/
data?:
| Mapper<TContext, TEvent, any>
| PropertyMapper<TContext, TEvent, any>;
/**
* The unique ID of the state node, which can be referenced as a transition target via the
* `#id` syntax.
*/
id?: string | undefined;
/**
* The string delimiter for serializing the path to a string. The default is "."
*/
delimiter?: string;
/**
* The order this state node appears. Corresponds to the implicit SCXML document order.
*/
order?: number;
}
// @ts-ignore
export { mapState, actions, assign, send, sendParent, sendUpdate, forwardTo, matchState, spawn, doneInvoke } from 'xstate';
}
import {
EventObject,
} from 'xstate';
import {
StateWithMatches,
InterpreterWithMatches,
RegisteredMachinesMap,
RegisteredMachine,
} from '@xstate/compiled';
declare module '@xstate/compiled/react' {
export function useMachine<
TContext,
TSchema,
TEvent extends EventObject,
Id extends keyof RegisteredMachinesMap<TContext, TEvent>
>(
machine: Extract<RegisteredMachine<TContext, TEvent>, { id: Id }>,
options: Extract<
RegisteredMachine<TContext, TEvent>,
{ id: Id }
>['_options'],
): [
StateWithMatches<
TContext,
TEvent,
Id
>,
InterpreterWithMatches<TContext, TSchema, TEvent, Id>['send'],
InterpreterWithMatches<TContext, TSchema, TEvent, Id>,
];
export function useService<
TContext,
TSchema,
TEvent extends EventObject,
Id extends keyof RegisteredMachinesMap<TContext, TEvent>
>(
service: InterpreterWithMatches<TContext, TSchema, TEvent, Id>
): [
StateWithMatches<
TContext,
TEvent,
Id
>,
InterpreterWithMatches<TContext, TSchema, TEvent, Id>['send'],
InterpreterWithMatches<TContext, TSchema, TEvent, Id>,
];
}
Thanks for the detailed repro. Can't see anything you're doing wrong. What happens if you remove outDir
?
Basically same thing.
outDir
yarn codegen
yarn codegen
toggle
When I only have one machine ts file like below,
using![Screen Shot 2021-04-02 at 15 34 29](https://user-images.githubusercontent.com/809378/113388541-5ba23a00-93c9-11eb-91a6-c2a0eb6cff56.png)
send
on react side works as expected. It shows the mismatching event name, and it autocompletes.However, if I create another machine ts file, then it generates for the new one fine, but then![Screen Shot 2021-04-02 at 15 36 40](https://user-images.githubusercontent.com/809378/113388785-cf444700-93c9-11eb-98d7-2be8dbedda90.png)
send
error message and autocompletion get disappeared.send signature when works
send signature when doesn't