Skip to content
This repository was archived by the owner on Mar 5, 2023. It is now read-only.

Conversation

Andarist
Copy link
Collaborator

No description provided.

# Conflicts:
#	packages/xstate-compiled/package.json
#	packages/xstate-compiled/src/extractMachines.ts
#	packages/xstate-compiled/tsconfig.json
#	yarn.lock
@changeset-bot
Copy link

changeset-bot bot commented Sep 15, 2020

🦋 Changeset detected

Latest commit: 0305fc1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
xstate-codegen Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@Andarist Andarist marked this pull request as ready for review September 15, 2020 06:59
@mattpocock
Copy link
Owner

Very exciting. I've added a complex machine test case into master which tests parallel states, nested states etc. When I tried it on this branch, the tests failed. Could you pull that into this PR and try to make it pass?

@mattpocock
Copy link
Owner

Found a machine in my work repo that fails on the branch. I've added it to master - pull it in to see the changes. Copying here as well in case merging is a PITA:

import { Machine, send, assign } from '@xstate/compiled';

type Attendee = {
  name: string;
  email: string;
  id: string;
};

interface Context {
  initialAttendees: Attendee[];
  attendeesToCreate: Attendee[];
  attendeesInList: Attendee[];
  attendeeIdsToDelete: Set<string>;
}

type Event =
  | { type: 'ADD_ATTENDEE'; name: string; email: string }
  | { type: 'EDIT_ATTENDEE'; id: string; name: string; email: string }
  | { type: 'REMOVE_ATTENDEE'; id: string }
  | {
      type: 'GO_BACK';
    }
  | {
      type: 'SUBMIT';
    }
  | {
      type: 'REPORT_ERROR';
    }
  | {
      type: 'done.invoke.createViewing';
      data: string;
    };

const assignAttendee = assign<
  Context,
  Extract<Event, { type: 'ADD_ATTENDEE' }>
>((context, event) => {
  const newAttendee = {
    id: '1',
    email: event.email,
    name: event.name,
  };
  return {
    attendeesToCreate: [...context.attendeesToCreate, newAttendee],
    attendeesInList: [...context.attendeesInList, newAttendee],
  };
});

export const addViewingAttendeesMachine = Machine<
  Context,
  Event,
  'addViewingAttendees'
>({
  id: 'addViewingAttendees',
  context: {
    attendeesToCreate: [],
    attendeeIdsToDelete: new Set(),
    initialAttendees: [],
    attendeesInList: [],
  },
  initial: 'idle',
  states: {
    idle: {
      on: {
        GO_BACK: [
          {
            cond: 'inCreateMode',
            actions: 'goToPrevPage',
          },
          {
            cond: 'inEditMode',
            actions: 'goBackToEditOverview',
          },
        ],
        ADD_ATTENDEE: [
          {
            actions: [assignAttendee],
          },
        ],
        REMOVE_ATTENDEE: {
          actions: [
            assign((context, event) => {
              let attendeeIdsToDelete = new Set(context.attendeeIdsToDelete);
              attendeeIdsToDelete.add(event.id);

              return {
                attendeeIdsToDelete,
                attendeesInList: context.attendeesInList.filter(
                  (attendee) => attendee.id !== event.id,
                ),
                attendeesToCreate: context.attendeesToCreate.filter(
                  (attendee) => attendee.id !== event.id,
                ),
              };
            }),
          ],
        },
        EDIT_ATTENDEE: [
          {
            actions: [
              assign((context, event) => {
                const attendeeWasOnInitialList = context.initialAttendees.some(
                  (attendee) => {
                    return attendee.id === event.id;
                  },
                );

                let attendeeIdsToDelete = new Set(context.attendeeIdsToDelete);
                if (attendeeWasOnInitialList) {
                  attendeeIdsToDelete.add(event.id);
                }

                return {
                  attendeeIdsToDelete,
                  attendeesToCreate: [
                    ...context.attendeesToCreate.filter(
                      (attendee) => attendee.id !== event.id,
                    ),
                    { email: event.email, id: event.id, name: event.name },
                  ],
                  attendeesInList: context.attendeesInList.map((attendee) => {
                    if (attendee.id === event.id) {
                      return {
                        id: event.id,
                        name: event.name,
                        email: event.email,
                      };
                    }
                    return attendee;
                  }),
                };
              }),
            ],
          },
        ],
      },
      initial: 'initial',
      states: {
        initial: {
          on: {
            SUBMIT: [
              {
                cond: 'hasNotAddedAnyInvitees',
                target: 'isWarningThatUserIsNotInvitingAnyone',
              },
              {
                cond: 'currentFormStateIsValidAndInCreateMode',
                target: '#creating',
              },
              {
                cond: 'currentFormStateIsValidAndInUpdateMode',
                target: '#updating',
              },
            ],
          },
        },
        isWarningThatUserIsNotInvitingAnyone: {
          on: {
            ADD_ATTENDEE: {
              actions: [assignAttendee],
              target: 'initial',
            },
            SUBMIT: [
              {
                cond: 'currentFormStateIsValidAndInCreateMode',
                target: '#creating',
              },
              {
                cond: 'currentFormStateIsValidAndInUpdateMode',
                target: '#updating',
              },
            ],
          },
        },
        errored: {},
      },
    },
    creating: {
      id: 'creating',
      invoke: {
        src: 'createViewing',
        onDone: {
          target: 'idle',
          actions: 'goToSuccessPage',
        },
        onError: {
          target: 'idle.errored',
        },
      },
    },
    updating: {
      id: 'updating',
      initial: 'checkingAttendees',
      states: {
        checkingAttendees: {
          always: [
            {
              cond: (context) =>
                Array.from(context.attendeeIdsToDelete).length > 0,
              target: 'deletingExcessGuests',
            },
            {
              cond: (context) => context.attendeesToCreate.length > 0,
              target: 'creatingNewAndEditedGuests',
            },
            {
              target: 'complete',
            },
          ],
        },
        deletingExcessGuests: {
          invoke: {
            src: 'deleteExcessGuests',
            onDone: [
              {
                cond: (context) => context.attendeesToCreate.length > 0,
                target: 'creatingNewAndEditedGuests',
              },
              {
                target: 'complete',
              },
            ],
            onError: 'errored',
          },
        },
        creatingNewAndEditedGuests: {
          invoke: {
            src: 'createNewGuests',
            onDone: [
              {
                target: 'complete',
              },
            ],
            onError: 'errored',
          },
        },
        errored: {
          entry: send({
            type: 'REPORT_ERROR',
          }),
        },
        complete: {
          type: 'final',
          entry: ['goBackToEditOverview', 'showToastWithChangesSaved'],
        },
      },
    },
  },
});

@Andarist
Copy link
Collaborator Author

Ah, i knew this has to happen - was just hoping that a little bit later :P i think i need to bring back the schema-based approach, just need to make the logic more tolerant to matching any state-likes (previously ive tried to be too strict about compound/atomic/etc). Will work on that today/tomorrow - hopefully wont take too long. That approach will allow me to just skip over extracting context which is problematic here

# Conflicts:
#	packages/xstate-compiled/src/introspectMachine.ts
#	yarn.lock
@danielkcz
Copy link

danielkcz commented Jan 7, 2021

@Andarist Do you think you will find time to continue on this? I am not sure if this PR solves it specifically, but missing support for path aliases from tsconfig.json pains me. Or if you can give some pointers on the next steps, I can attempt to finish it by myself.

@Andarist
Copy link
Collaborator Author

Andarist commented Jan 7, 2021

@Andarist Do you think you will find time to continue on this?

I have actually got back to working on this already earlier this week.

I am not sure if this PR solves it specifically, but missing support for path aliases from tsconfig.json pains me.

It should because file resolution is delegated to TS and it's tsconfig.json-aware. Unless you would have some very specific setup for which the current logic fails to locate the tsconfig.json correctly - that would be solvable with an additiona tsconfigPath option or smth but I don't expect that this will be required.

Or if you can give some pointers on the next steps, I can attempt to finish it by myself.

We could definitely use some help if you'd like to collaborate on this. I have very limited time to work on all of this (2 kids at home 😉) and there are some things to figure out in relation to IDE, linting etc. We have some additional plans to restructure this tool plus provide VSCode extension - gonna be posting more about this soon but the implementation definitely won't happen over night.

Andarist added 6 commits April 9, 2021 15:28
# Conflicts:
#	packages/xstate-compiled/examples/createMachineOptions.machine.ts
#	packages/xstate-compiled/examples/options.machine.ts
#	packages/xstate-compiled/package.json
#	packages/xstate-compiled/src/extractMachines.ts
#	packages/xstate-compiled/src/index.ts
#	packages/xstate-compiled/src/introspectMachine.ts
#	yarn.lock
@Andarist Andarist force-pushed the rewrite-to-ts-morph branch from d8f8b82 to 9a7c2d1 Compare April 9, 2021 16:25
@Andarist
Copy link
Collaborator Author

Andarist commented Apr 9, 2021

I've pushed out my recent work on this - it works for the majority of cases, existing tests pass. However, if I fail to extract things the DX ain't great because no helpful errors are printed to the user. It's very important to get this right as without that this is not really that usable.

I've also dropped temporarily support for choose since based on type-info alone I could not access used guards/actions within it. I've experimented with some kinda crazy types that could replace XState's choose and which would enable me to extract the required information at the type-level. Here it is:
TS playground

This relies on type inference so explicit type params for TContext and TEvent are not allowed with this. I need to think about the implications of that.

@Andarist Andarist closed this Apr 29, 2021
@VanTanev
Copy link

Was this closed accidentally?

@Andarist
Copy link
Collaborator Author

@VanTanev no, this branch was created on my fork and I've pushed it out to the origin and created this PR: #78 , in the hope to fix the problem with snapshot releases that I've tried to add to this. Unfortunately, that didn't fix the problem with those releases 😬

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants