ifandelse / machina.js

js ex machina - finite state machines in JavaScript
http://machina-js.org/
Other
1.93k stars 147 forks source link

TypeScript Declaration File #158

Open todd opened 6 years ago

todd commented 6 years ago

Quick question for @ifandelse - would you accept a PR that lands a TypeScript declaration file in this library? I've been using Machina in quite a few of my TypeScript projects and type information has been a major want of mine when using it in that stack.

The alternative would be to land the declaration file in DefinitelyTyped, but, generally speaking, the developer experience is a lot better if library authors include them in their packages.

However, including the type information in the library would necessitate a long-term commitment to keep that file up to date with API changes. Machina's API seems fairly stable, so I'm not sure this is a huge problem, but it has bitten some people in the past (see: https://github.com/moment/moment/pull/3280).

Regardless, I'm probably going to create these typings when I have a free moment. Just need to know if I should open the PR against this repo or DefinitelyTyped.

Thanks!

ifandelse commented 6 years ago

@todd Thanks for being patient! :-) I'd definitely be up for a TypeScript declaration file PR. I'm familiar (ish) with TypeScript from a distance, but haven't ever put a declaration file together, so this is new territory for me. I'd likely reach out to folks who are more familiar (like yourself) for help especially when/if big changes happen in the API. But if this can help folks during dev time, it's a win!

miedmondson commented 5 years ago

Newbie attempt, based on reading the docs.

machina.d.ts

declare module 'machina' {

  interface Fsm {
    // static methods
    extend: ({}) => Fsm
    // instance members
    initialState: string
    eventListeners: {}
    states: {}
    inputQueue: []
    namespace: string
    targetReplayState: string
    state: string
    priorState: string
    priorAction: string
    currentAction: string
    currentActionArgs: string
    initialize: () => void
    // prototype members
    emit: (eventName: string, data: any) => void
    handle: (eventName: string, data: any) => void
    transition: (state: string) => void
    processQueue: (type: 'transition' | 'handler') => void
    clearQueue: (type?: 'transition' | 'handler', stateName?: string) => void
    deferUntilTransition: (stateName?: string) => void
    deferAndTransition: (stateName?: string) => void
    compositeState: () => string
    on: (eventName: string, callback: (data: any) => void) => void
    off: (eventName: string, callback: (data: any) => void) => void
  }

  interface BehavioralFsm {
    // static methods
    extend: ({}) => BehavioralFsm
    // instance members
    initialState: string
    eventListeners: {}
    states: {}
    inputQueue: []
    namespace: string
    // prototype members
    initialize: () => void
    emit: (eventName: string, data: any) => void
    handle: (client: Fsm, inputType: string, data: any) => void
    transition: (client: Fsm, state: string) => void
    processQueue: (client: Fsm) => void
    clearQueue: (client: Fsm, stateName?: string) => void
    deferUntilTransition: (client: Fsm, stateName?: string) => void
    deferAndTransition: (client: Fsm, stateName?: string) => void
    compositeState: (client: Fsm) => string
    on: (eventName: string, callback: (data: any) => void) => void
    off: (eventName?: string, callback?: (data: any) => void) => void
    // property to track machina-related metadata
    __machina__: {
      targetReplayState: string
      state: string
      priorState: string
      priorAction: string
      currentAction: string
      currentActionArgs: any[]
      initialize: () => void
      inputQueue: any[]
      inExitHandler: boolean
    }
  }

  type DefaultOptions = {
    initialState: string
    eventListeners: {}
    states: {}
    namespace: string
    useSafeEmit: boolean
    hierarchy: {}
    pendingDelegations: {}
  }

  type DefaultClientMeta = {
    inputQueue: any[]
    targetReplayState: string
    state: string
    priorState: string
    priorAction: string
    currentAction: string
    currentActionArgs: any[]
    inExitHandler: boolean
  }

  type Subscription = {
    off: () => void
  }

  interface machina {
    Fsm: Fsm,
    BehavioralFsm: BehavioralFsm,
    utils: {
      makeFsmNamespace: () => string
      listenToChild: (fsm: Fsm, child: Fsm) => Subscription
      getLeaklessArgs: () => any[]
      getDefaultOptions: () => DefaultOptions
      getDefaultClientMeta: () => DefaultClientMeta
      createUUID: () => string
    },
    eventListeners: {}
    emit: (eventName: string, data: any) => void
    on: (eventName: string, callback: (data: any) => void) => Subscription
    off: (eventName: string, callback: (data: any) => void) => Subscription
  }

  const machina: machina

  export = machina
}

It doesn't support multiple arguments for event payloads, but then the docs strongly recommend using only a single argument.

king612 commented 5 years ago

Did this ever get done? I'd love to be able to use it in my Node typescript workflow app.

todd commented 5 years ago

I started working on them over the holidays, but obviously got a little side-tracked ;-)

I'll try to clean up what I have so far and open a work-in-progress PR so other folks can potentially contribute.

miedmondson commented 5 years ago

I expanded a bit on the declaration of my earlier comment -- it's still based on the Wiki docs though.

declare module 'machina' {

  type EventCallback = (data: any) => void

  interface Fsm {
    // static methods
    extend: ({}) => Fsm
    // instance members
    initialState: string
    eventListeners: {}
    states: {}
    inputQueue: []
    namespace: string
    targetReplayState: string
    state: string
    priorState: string
    priorAction: string
    currentAction: string
    currentActionArgs: string
    initialize: () => void
    // prototype members
    emit: (eventName: string, data?: any) => void
    handle: (eventName: string, data: any) => void
    transition: (state: string) => void
    processQueue: (type: 'transition' | 'handler') => void
    clearQueue: (type?: 'transition' | 'handler', stateName?: string) => void
    deferUntilTransition: (stateName?: string) => void
    deferAndTransition: (stateName?: string) => void
    compositeState: () => string
    on: (eventName: string, callback: EventCallback) => void
    off: (eventName: string, callback: EventCallback) => void
  }

  interface BehavioralFsm {
    // static methods
    extend: ({}) => BehavioralFsm
    // instance members
    initialState: string
    eventListeners: {}
    states: {}
    inputQueue: []
    namespace: string
    // prototype members
    initialize: () => void
    emit: (eventName: string, data?: any) => void
    handle: (client: Fsm, inputType: string, data: any) => void
    transition: (client: Fsm, state: string) => void
    processQueue: (client: Fsm) => void
    clearQueue: (client: Fsm, stateName?: string) => void
    deferUntilTransition: (client: Fsm, stateName?: string) => void
    deferAndTransition: (client: Fsm, stateName?: string) => void
    compositeState: (client: Fsm) => string
    on: (eventName: string, callback: (data?: any) => void) => void
    off: (eventName?: string, callback?: (data?: any) => void) => void
    // property to track machina-related metadata
    __machina__: {
      targetReplayState: string
      state: string
      priorState: string
      priorAction: string
      currentAction: string
      currentActionArgs: any[]
      initialize: () => void
      inputQueue: any[]
      inExitHandler: boolean
    }
  }

  const eventListeners: {}

  function emit(eventName: string, data?: any): void
  function on(eventName: string, callback: EventCallback): Subscription
  function off(eventName: string, callback: EventCallback): Subscription

  namespace utils {
    function makeFsmNamespace(): string
    function listenToChild(fsm: Fsm, child: Fsm): Subscription
    function getLeaklessArgs(): any[]
    function getDefaultOptions(): DefaultOptions
    function getDefaultClientMeta(): DefaultClientMeta
    function createUUID(): string
  }

  type DefaultOptions = {
    initialState: string
    eventListeners: {}
    states: {}
    namespace: string
    useSafeEmit: boolean
    hierarchy: {}
    pendingDelegations: {}
  }

  type DefaultClientMeta = {
    inputQueue: any[]
    targetReplayState: string
    state: string
    priorState: string
    priorAction: string
    currentAction: string
    currentActionArgs: any[]
    inExitHandler: boolean
  }

  interface Subscription {
    off: () => void
  }

  type TransitionEventData = {
    client?: string  // only applicable in BehavioralFsm instances
    fromState: string
    action: string
    toState: string
    namespace: string
  }

  type HandlingEventData = {
    client?: string  // only applicable in BehavioralFsm instances
    inputType: string
    delegated: boolean  // only applicable in hierarchical scenarios
    ticket: undefined // only applicable in hierarchical scenarios
    namespace: string
  }

  type HandledEventData = {
    client?: string  // only applicable in BehavioralFsm instances
    inputType: string
    delegated: boolean  // only applicable in hierarchical scenarios
    ticket: undefined  // only applicable in hierarchical scenarios
    namespace: string
  }

  type NohandlerEventData = {
    client?: string  // only applicable in BehavioralFsm instances
    inputType: string
    delegated: boolean  // only applicable in hierarchical scenarios
    ticket: undefined  // only applicable in hierarchical scenarios
    namespace: string
    args: any[]
  }

  type InvalidstateEventData = {
    client?: string  // only applicable in BehavioralFsm instances
    namespace: string
    state: string
    attemptedState: string
  }

  type QueuedArgs = {
    inputType: string
    delegated: boolean  // only applicable in hierarchical scenarios
    ticket: undefined  // only applicable in hierarchical scenarios
  }

  type DeferredEventData = {
    client?: string  // only applicable in BehavioralFsm instances
    state: string,
    namespace: string,
    queuedArgs: {
        args: QueuedArgs[]
        type: 'transition' // currently this is the only possible value
        untilState: string
    }
  }

}
dynajoe commented 5 years ago

It may actually be easier to maintain to port this module to use typescript. Then type definitions come along with the package install.

todd commented 5 years ago

@joeandaverde I've considered rolling my own version written in TS based on the comments I left in #165. Given the inactivity on that PR, I may submit what little I've got at the moment to DefinitelyTyped and let folks contribute from there as a stopgap.

sfrooster commented 2 years ago

@todd long-time no... you know. I thought I'd see if there'd been any progress on type definitions for this since... when was that, 2016? And found myself here. From what I can tell, there are a couple of attempts above, and there's your WIT PR, and that's about where things have gotten to?

I did notice someone mentioning along the way that the documentation was a little sparse and I remember us looking at the code to figure-out how various things work, so I will first offer-up the following, which I only found today, and does provided a-lot of that documentation we thought we were missing (or maybe that was only me): https://github.com/ifandelse/machina.js/wiki/API

That said, are you still working on this PR? I haven't looked at it yet, but I might like to either assist in rounding it out and getting it added to DefinitelyTyped, or create my own PR based on this one if you're no longer working on it.

I guess I'm asking you were this work is being done if it's still being done (here in this PR, a PR on DT, a TS rewrite of machina - wherever) and then see if maybe I can assist.

todd commented 2 years ago

:wave: Hey there, @sfrooster! Hope you're well, friend.

I got the ball rolling in #165, but, as you can probably tell, I haven't touched it in a while. Progress stalled out with a lack of feedback from @ifandelse and I got busy with a bunch of other things. At this point, landing some type definitions in DefinitelyTyped might be the path of least resistance to get something usable out. I did start playing around with the idea of doing a TypeScript-native project inspired by machina, but never got farther than the experimenting stage.

Happy to get you collaborator access to my machina fork if you wanna riff on these defs a bit more. Unfortunately, I'm probably unlikely to have the capacity to come back to this any time soon.