npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ember-fsm-bridge

v2.0.0-alpha.1

Published

An Octane-compatible fork of ember-fsm

Downloads

678

Readme

Ember FSM Bridge

:warning: :warning: :warning:

This addon is primarily intended as a short-term "bridge" replacement for existing users of ember-fsm that need to upgrade to Ember 4.x. Use is strongly discouraged for new apps.

The ember-fsm addon was originally developed from 2014 to 2018, when it was put into maintenance mode, by Carsten Nielsen.

This fork has been minimally updated in order to ensure compatibility with Ember Octane / 4.x for an app that still uses it. As this is effectively a continuation of ember-fsm, to comply with SemVer best practices, I've set a major version of 2.x, but there are no other changes to the feature set.

I do not intend to add any new features and can't make any guarantees about future maintenance, as the app in question will likely eventually remove its dependency on this addon, and the library depends on older Ember concepts like mixins that are now discouraged. Some of the examples below also use outdated syntax. If you think the Ember community still needs a library like this with newer/better features or different architecture, please feel free to fork it and develop it further.

I can only recommend this version for use with Ember 3.28+ and Node 14, 16, or 18+. If you need a version compatible with earlier versions of Ember, you should stick with the original addon.

:warning: :warning: :warning:

A promise-aware finite state machine implementation for Ember

A wild (and current) traffic light demo appears!

import { Machine } from 'ember-fsm-bridge';

let trafficSignal = Machine.create({
  events: {
    cycle: {
      transitions: [
        { initialized: 'red' },
        { red: 'green' },
        { green: 'amber' },
        { amber: 'red' }
      ]
    },

    powerDown: {
      transition: { $all: 'off' }
    }
  }
});

trafficSignal.get('currentState');
// "initialized"

trafficSignal.send('cycle');
trafficSignal.get('currentState');
// "red"

trafficSignal.send('cycle');
trafficSignal.get('currentState')
// "green"

Getting Started

Install as an Ember Addon

ember install ember-fsm

Do you need this?

Try really hard not to need it, if you need it, I'm sorry. -- @heycarsten

Defining a State Machine

import { Machine } from 'ember-fsm-bridge';

let SleepyFSM = Machine.extend({
  // Here is where you define your state machine's state-specific configuration.
  // This section is optional.
  states: {
    // The default initial state is "initialized"
    initialState: 'awake'

    // If you'd like, you can choose to explicitly define the names of your
    // states:
    knownStates: ['sleeping', 'angry', 'awake', 'initialized', 'failed'],

    // You can define global per-state callbacks, they will fire whenever the
    // state will be entered, was entered, will be exited, or was exited.
    sleeping: {
      willEnter() { },
      didEnter() { },
      willExit() { },
      didExit() { }
    }
  },

  // Here's where you define your state machine's events, it is required.
  events: {
    sleep: {
      // You can define global per-event callbacks. These will fire for any
      // transition before or after this event.
      before() { },
      after() { },

      // This is where the event's transitions are defined, it is also aliased
      // to "transition". It can accept either a single object like one in the
      // array below, or an array of transition definition objects:
      transitions: [
        { awake: 'sleeping', doUnless: 'unableToSleep' },
        { awake: 'angry', doIf: 'unableToSleep' },
        { sleeping: '$same' }
      ]
    },

    // By default this error event is injected into your state machine for you,
    // you can override it and provide your own transitions and callbacks if
    // you'd like.
    error: {
      transition: { $all: 'failed' }
    }
  }
});

State Macros

For the sake of less typing (and less chances of introducing failure) the following macros can be used in transition definitions:

| Macro | Description | |:-----------|:------------------------------| | $all | Expands to all known states. | | $same | Expands to the same state as the from state. transition: { sleeping: '$same' } | | $initial | Expands to the initial state. |

Transition Guarding

You can specify that a transition be excluded or included in the event using doIf or doUnless. Consider SleepyFSM above, if we set unableToSleep to true then when we send in the sleep event, it will transition to the state angry because the transition { awake: 'sleeping' } will be excluded from the list.

doIf and doUnless are aliased to guard and unless respectively.

Transition Events & Callbacks

Given the SleepyFSM example above, suppose we ran the following:

let fsm = SleepyFSM.create();
fsm.send('sleep');

Here is the series of transition events that will occurr and the corresponding callbacks that will run and where they can be defined:

| Current State | Is Active | Event | Runs callbacks | |:--------------|:----------|:-------------------------|:--------------------------------------| | awake | false | beforeEvent | before on events and transitions | | awake | true | _activateTransition_ | internal | | awake | true | willExit | willExit on states and transitions | | awake | true | willEnter | willEnter on states and transitions | | sleeping | true | _setNewState_ | internal | | sleeping | true | didExit | didExit on states and transitions | | sleeping | true | didEnter | didEnter on states and transitions | | sleeping | false | _deactivateTransition_ | internal | | sleeping | false | afterEvent | after on events and transitions |

Some of the event names above also have aliases:

| Event | Aliases | |:--------------|:------------------| | beforeEvent | before | | afterEvent | after | | didEnter | enter, action | | didExit | exit |

Asynchronicity In Callbacks

If callbacks return a promise, the next callback in the chain will not fire until the promise is resolved. The return value of callbacks is stored in the transition's resolutions object. Likewise, rejections are stored in the rejections object of the transition.

Namespacing States

ember-fsm doesn't provide true sub-state support, but you can namespace your states. For example, suppose a portion of your state workflow is related in some way; you can prefix those states with a namespace:

  • ready
  • uploading.requestingUrl
  • uploading.sendingData
  • processing.enqueuing
  • processing.working
  • finished

When you define states like this, Ember.FSM automatically generates the following boolean accessor properties for you:

  • isInReady
  • isInUploading
  • isInUploadingRequestingUrl
  • isInUploadingSendingData
  • isInProcessing
  • isInProcessingEnqueuing
  • isInProcessingWorking
  • isInFinished

Stateful Mixin

When it comes to using ember-fsm in your application, you'll almost always want to use FSM.Stateful over sub-classing FSM.Machine. This way you can formalize a state workflow around something like file uploads where you might have to incorporate three different proceesses into on user experience.

Note: States and events are renamed in the mixin to fsmStates and fsmEvents respectively, to avoid conflict with core Ember properties.

Building these sorts of workflows implicitly as-you-code-along can be a recipie for massive sadness. So why be sad? Formalize that workflow! Here's an example of how adding ember-fsm to a controller can remove a lot of the tedious parts of workflow managment:

import Controller from '@ember/controller';
import { Stateful, reject } from 'ember-fsm-bridge';

// controllers/upload.js
export default Controller.extend(Stateful, {
  needs: 'notifier',

  actions: {
    uploadFile(file) {
      this.set('file', file);
      this.sendStateEvent('addFile');
    }
  },

  fsmStates: {
    initialState: 'nofile'
  },

  fsmEvents: {
    addFile: {
      transitions: {
        from:   ['nofile', 'failed'],
        to:     'ready',
        before: 'checkFile',
      }
    },

    startUpload: {
      transitions: {
        from:     'ready',
        to:       'uploading',
        before:   'getUploadURL',
        didEnter: 'performUpload',
        after:    'finishedUpload'
      }
    },

    finishUpload: {
      transition: { uploading: 'nofile', didEnter: 'reset' }
    }
  },

  reset() {
    this.set('file', null);
  },

  checkFile() {
    let file = this.get('file');

    if (file.size > 0) {
      return;
    } else {
      this.get('controllers.notifier').warn('file must have content');
      reject(); // A helper for throwing an error
    }
  },

  getUploadURL() {
    let fileName = this.get('file.name');

    let xhr = Ember.$.ajax('/api/signed_uploads', {
      type: 'put',
      data: { file: { name: fileName } }
    });

    xhr.then((payload) => {
      Ember.run(() => {
        this.set('uploadToURL', payload.signed_upload.url);
      });
    });

    return xhr; // Causes transition to block until promise is settled
  },

  performUpload() {
    return Ember.$.ajax(this.get('uploadToURL'), {
      type: 'put',
      data: this.get('file')
    });
  },

  finishedUpload() {
    this.get('controllers.notifier').success('Upload complete');
    this.sendStateEvent('finishUpload');
  }
});

Running tests

  • ember test – Runs the test suite on the current Ember version
  • ember test --server – Runs the test suite in "watch mode"
  • ember try:each – Runs the test suite against multiple Ember versions

Running the dummy application

Thanks

From @heycarsten in the original ember-fsm: