Skip to content

Commit

Permalink
feat(core): xstate v5 support
Browse files Browse the repository at this point in the history
Updates xstate-tree to be compatible with xstate v5

Closes #58

BREAKING CHANGES: v1 style builders (buildView etc) have been removed, along with a handful of testing utilities and a behavioural change with children and final states. They will no longer be automatically from views and instead must be manually stopped using `stopChild`
  • Loading branch information
UberMouse committed Jan 9, 2024
1 parent da4c5fb commit 036f362
Show file tree
Hide file tree
Showing 28 changed files with 1,358 additions and 1,624 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/xstate-tree.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ jobs:
run: npm run lint
- name: Run Tests
run: npm run test
- name: Test examples
run: npm run test-examples
- name: Build
run: npm run build
- name: API Extractor
Expand Down
155 changes: 78 additions & 77 deletions examples/todomvc/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,107 +5,108 @@ import {
RoutingEvent,
createXStateTreeMachine,
} from "@koordinates/xstate-tree";
import { assign } from "@xstate/immer";
import React from "react";
import { map } from "rxjs/operators";
import { createMachine, type ActorRefFrom, spawn } from "xstate";
import {
type ActorRefFrom,
setup,
assign,
assertEvent,
fromEventObservable,
} from "xstate";

import { TodoMachine } from "./Todo";
import { todos$, type Todo } from "./models";
import { activeTodos, allTodos, completedTodos } from "./routes";

type Context = {
todos: Todo[];
actors: Record<string, ActorRefFrom<typeof TodoMachine>>;
actors: ActorRefFrom<typeof TodoMachine>[];
filter: "all" | "active" | "completed";
};
type Events =
| { type: "SYNC_TODOS"; todos: Todo[] }
| RoutingEvent<typeof activeTodos>
| RoutingEvent<typeof allTodos>
| RoutingEvent<typeof completedTodos>;
| RoutingEvent<typeof activeTodos | typeof allTodos | typeof completedTodos>;

const TodosSlot = multiSlot("Todos");
const machine =
/** @xstate-layout N4IgpgJg5mDOIC5QBcD2FUFoCGAHXAdAMaoB2EArkWgE4DEiouqsAlsq2YyAB6ICMAFgBMBAJwTBANgkBWAOwAOWQGYpwgDQgAngMEEp-RYNn8FABmHmxMlQF87WtBhz46AZQCaAOQDCAfQAVAHkAEWD3bmY2Di4kXkRMeSkCEWTzRUUbYUV5c1ktXQRMflKCMyUxQXk1Y2FShyd0LDxcDwAJYIB1fwBBX0CASQA1AFEgsIiolnZOUm4+BBUagitzFWFZasVzczMpQsThFeVFFV35fjzLQUaQZxa3d06e3oAZN4nwyPjo2bjQIt+FJFKsxNYxLIbLJNoIxIpDsUVFDUsJBGcVGIrPxjrdHPdmq42s9uv5fMEALIABTeo0Co1CXymvxmsXm8UWJWsBB2ljUVThe2EBx0iWRYlR6JUmOxuIc+NI6Dg3AeROIZEo1FQNGmMTmC0SslBVRqIJE-HywsEgkRVwIlWqan40tM-DuqtaBEVgWa8BZeoBCQQV1EUhUFSyjrNNtFwZs4kjpysKkUwjU7sJnoAFthYD6MH6mKz9RzDeYCDCwxGTfzbfH4VUk+tU+n8R78Lr-uzAYkLeW0lIMll1Ll8ojMGiUhGzkIU2JWw4gA */
createMachine(
{
context: { todos: [], actors: {}, filter: "all" },
tsTypes: {} as import("./App.typegen").Typegen0,
schema: { context: {} as Context, events: {} as Events },
predictableActionArguments: true,
invoke: {
src: "syncTodos",
id: "syncTodos",
},
id: "todo-app",
initial: "conductor",
on: {
SYNC_TODOS: {
actions: "syncTodos",
target: ".conductor",
},
SHOW_ACTIVE_TODOS: {
actions: "setFilter",
},
SHOW_ALL_TODOS: {
actions: "setFilter",
setup({
types: { context: {} as Context, events: {} as Events },
actions: {
syncTodos: assign({
todos: ({ event: e }) => {
assertEvent(e, "SYNC_TODOS");

return e.todos;
},
SHOW_COMPLETED_TODOS: {
actions: "setFilter",
actors: ({ event, spawn }) => {
assertEvent(event, "SYNC_TODOS");

return event.todos.map((todo) =>
spawn("TodoMachine", { input: todo, id: TodosSlot.getId(todo.id) })
);
},
}),
setFilter: assign({
filter: ({ event: e }) =>
e.type === "SHOW_ACTIVE_TODOS"
? "active"
: e.type === "SHOW_COMPLETED_TODOS"
? "completed"
: "all",
}),
},
guards: {
hasTodos: ({ context }) => context.todos.length > 0,
},
actors: {
TodoMachine,
syncTodos: fromEventObservable(() => {
return todos$.pipe(
map((todos): Events => ({ type: "SYNC_TODOS", todos }))
);
}),
},
}).createMachine({
context: { todos: [], actors: [], filter: "all" },
invoke: {
src: "syncTodos",
id: "syncTodos",
},
id: "todo-app",
initial: "conductor",
on: {
SYNC_TODOS: {
actions: "syncTodos",
target: ".conductor",
},
states: {
conductor: {
always: [
{
cond: "hasTodos",
target: "hasTodos",
},
{
target: "noTodos",
},
],
},
noTodos: {},
hasTodos: {},
SHOW_ACTIVE_TODOS: {
actions: "setFilter",
},
},
{
actions: {
syncTodos: assign((ctx, e) => {
ctx.todos = e.todos;

ctx.todos.forEach((todo) => {
if (!ctx.actors[todo.id]) {
ctx.actors[todo.id] = spawn(
TodoMachine.withContext({ ...TodoMachine.context, todo }),
TodosSlot.getId(todo.id)
);
}
});
}),
setFilter: assign((ctx, e) => {
ctx.filter =
e.type === "SHOW_ACTIVE_TODOS"
? "active"
: e.type === "SHOW_COMPLETED_TODOS"
? "completed"
: "all";
}),
SHOW_ALL_TODOS: {
actions: "setFilter",
},
guards: {
hasTodos: (ctx) => ctx.todos.length > 0,
SHOW_COMPLETED_TODOS: {
actions: "setFilter",
},
services: {
syncTodos: () => {
return todos$.pipe(
map((todos): Events => ({ type: "SYNC_TODOS", todos }))
);
},
},
states: {
conductor: {
always: [
{
guard: "hasTodos",
target: "hasTodos",
},
{
target: "noTodos",
},
],
},
}
);
noTodos: {},
hasTodos: {},
},
});

export const TodoApp = createXStateTreeMachine(machine, {
selectors({ ctx, inState }) {
Expand Down
74 changes: 35 additions & 39 deletions examples/todomvc/App.typegen.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,36 @@
// This file was automatically generated. Edits will be overwritten

export interface Typegen0 {
"@@xstate/typegen": true;
internalEvents: {
"": { type: "" };
"done.invoke.syncTodos": {
type: "done.invoke.syncTodos";
data: unknown;
__tip: "See the XState TS docs to learn how to strongly type this.";
};
"error.platform.syncTodos": {
type: "error.platform.syncTodos";
data: unknown;
};
"xstate.init": { type: "xstate.init" };
};
invokeSrcNameMap: {
syncTodos: "done.invoke.syncTodos";
};
missingImplementations: {
actions: never;
services: never;
guards: never;
delays: never;
};
eventsCausingActions: {
setFilter: "SHOW_ACTIVE_TODOS" | "SHOW_ALL_TODOS" | "SHOW_COMPLETED_TODOS";
syncTodos: "SYNC_TODOS";
};
eventsCausingServices: {
syncTodos: "xstate.init";
};
eventsCausingGuards: {
hasTodos: "";
};
eventsCausingDelays: {};
matchesStates: "conductor" | "hasTodos" | "noTodos";
tags: never;
}
// This file was automatically generated. Edits will be overwritten

export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
"done.invoke.syncTodos": { type: "done.invoke.syncTodos"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"error.platform.syncTodos": { type: "error.platform.syncTodos"; data: unknown };
"xstate.init": { type: "xstate.init" };
};
invokeSrcNameMap: {
"syncTodos": "done.invoke.syncTodos";
};
missingImplementations: {
actions: "setFilter" | "syncTodos";
delays: never;
guards: never;
services: "syncTodos";
};
eventsCausingActions: {
"setFilter": "SHOW_ACTIVE_TODOS" | "SHOW_ALL_TODOS" | "SHOW_COMPLETED_TODOS";
"syncTodos": "SYNC_TODOS";
};
eventsCausingDelays: {

};
eventsCausingGuards: {

};
eventsCausingServices: {
"syncTodos": "xstate.init";
};
matchesStates: "conductor" | "hasTodos" | "noTodos";
tags: never;
}

Loading

0 comments on commit 036f362

Please sign in to comment.