├── .DS_Store ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .vscode └── settings.json ├── README.md ├── docs ├── plugin-development.md └── todo.md ├── package-lock.json ├── package.json ├── rollup.config.js └── src ├── index.js └── util ├── config-editor.js ├── extract-metadata.js └── index.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qudo-code/state-machine-snacks/8fa4df8bd9d5353dea54807b22a8969323697210/.DS_Store -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true 5 | }, 6 | extends: ["@tivac"], 7 | parserOptions: { 8 | ecmaVersion: 2018, 9 | sourceType: "module", 10 | allowImportExportFromEverywhere: true, 11 | }, 12 | 13 | globals : { 14 | "module" : "readable", 15 | "import" : "readable", 16 | "require" : "readable", 17 | "process" : "readable", 18 | "__dirname" : "readable", 19 | "artifacts" : "readable", 20 | "TruffleContract" : "readable", 21 | "Web3" : "readable", 22 | }, 23 | 24 | plugins: ["svelte3"], 25 | overrides: [ 26 | { 27 | files: ['**/*.svelte'], 28 | processor: 'svelte3/svelte3' 29 | } 30 | ], 31 | settings: { 32 | "import/core-modules": ["svelte"] 33 | }, 34 | rules: { 35 | indent: ["error", 4], 36 | "linebreak-style": ["error", "unix"], 37 | quotes: ["error", "double"], 38 | semi: ["error", "always"], 39 | // Enforce newline consistency in objects 40 | "object-curly-newline": [ 41 | "warn", 42 | { 43 | // Object literals w/ 3+ properties need to use newlines 44 | ObjectExpression: { 45 | consistent: true, 46 | minProperties: 3 47 | }, 48 | 49 | // Destructuring w/ 6+ properties needs to use newlines 50 | ObjectPattern: { 51 | consistent: true, 52 | minProperties: 6 53 | }, 54 | 55 | // Imports w/ 4+ properties need to use newlines 56 | ImportDeclaration: { 57 | consistent: true, 58 | minProperties: 4 59 | }, 60 | 61 | // Named exports should always use newlines 62 | ExportDeclaration: "always" 63 | } 64 | ], 65 | 66 | "consistent-return": "off", 67 | 68 | "no-multiple-empty-lines": [ 69 | "warn", 70 | { 71 | max: 1, 72 | maxEOF: 1, 73 | maxBOF: 0 74 | } 75 | ], 76 | 77 | "padding-line-between-statements": [ 78 | "warn", 79 | // Always require a newline before returns 80 | { blankLine: "always", prev: "*", next: "return" }, 81 | 82 | // Always require a newline after directives 83 | { blankLine: "always", prev: "directive", next: "*" }, 84 | 85 | // Always require a newline after imports 86 | { blankLine: "always", prev: "import", next: "*" }, 87 | 88 | // Don't require a blank line between import statements 89 | { blankLine: "any", prev: "import", next: "import" }, 90 | 91 | // Newline after var blocks 92 | { blankLine: "always", prev: ["const", "let", "var"], next: "*" }, 93 | { 94 | blankLine: "any", 95 | prev: ["const", "let", "var"], 96 | next: ["const", "let", "var"] 97 | }, 98 | 99 | // Newline before conditionals/loops 100 | { 101 | blankLine: "always", 102 | prev: "*", 103 | next: ["if", "do", "while", "for"] 104 | }, 105 | 106 | // Newline after blocks 107 | { blankLine: "always", prev: "block-like", next: "*" } 108 | ], 109 | 110 | "no-restricted-syntax": [ 111 | "error", 112 | 113 | // with() 114 | "WithStatement" 115 | ], 116 | 117 | "no-restricted-globals": [ 118 | "error", 119 | { 120 | name: "isNaN", 121 | message: "isNaN is unsafe, use Number.isNaN" 122 | }, 123 | { 124 | name: "isFinite", 125 | message: "isFinite is unsafe, use Number.isFinite" 126 | } 127 | ] 128 | } 129 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | docs 4 | src 5 | .eslintrc.js 6 | .gitignore 7 | rollup.config.js -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 4, 3 | "eslint.validate": [ 4 | "javascript", 5 | "javascriptreact", 6 | "html", 7 | "svelte" 8 | ], 9 | "editor.codeActionsOnSave": { 10 | "source.fixAll.stylelint": true, 11 | "source.fixAll.eslint": true, 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ⚠️ A POC, not actively maintained. 2 | 3 | # State Machine Snacks (🍕) 4 | A framework built on [XState](https://xstate.js.org/docs/about/concepts.html) that provides bite sized snacks for developing with state machine machines. 🍕 aims to increase state machine adoption in modern day web apps by providing a suite of tools and plugins to inspire development and new ways of thinking. 5 | 6 | 🐤 [@me](https://twitter.com/qudolucas) on Twitter. 7 | 8 | ### What Is XState? 9 | XState is a library that allows us to create and interpret state machines in JavaScript. It is recommended you understand the basics of XState before using State Machine UI. 10 | 11 | ## 🚀 Getting Started 12 | For basic usage, 🍕 requires only a XState state machine config as an option. SMS will utilize this config to create a machine and return an XState service. 13 | 14 | | Options | Description | | 15 | | ----------- | ----------- | ----------- | 16 | | `config` | XState state machine config. | Required 17 | | `createMachine` | By default, the machine is created with `createMachine(config)`. You can overwrite this behavior with a function that will be passed the config and must return a XState machine instance. | Optional 18 | | `interpret` | By default, the service is interpreted via `interpret(machine)`. You can overwrite this behavior with a function that will be passed both the config and machine instance from the `createMachine()` step. | Optional 19 | | `plugins` | An array of plugins you want to add to the service. | Optional 20 | 21 | #### 🍕 w/Default Settings 22 | ```javascript 23 | import sms from "state-machine-snacks"; 24 | 25 | const config = { /* ...machine config */ }; 26 | 27 | // Create your service with 🍕. 28 | const service = sms({ 29 | config, 30 | }); 31 | 32 | service.start(); 33 | ``` 34 | 35 | #### 🍕 w/Advanced Initialization 36 | ```javascript 37 | import sms from "state-machine-snacks"; 38 | 39 | const config = { /* ...machine config */ }; 40 | 41 | // Create your service with 🍕 + additional settings. 42 | const service = sms({ 43 | config, 44 | 45 | createMachine : (config) => createMachine(config, { ...actions, ...services }), 46 | 47 | interpret : (config, machine) => interpret(machine).onTransition((state) => { 48 | console.log(state.value); 49 | }); 50 | }); 51 | 52 | service.start(); 53 | ``` 54 | 55 | ## 🔌 Plugins 56 | Plugins add additional functionality to an XState config and service. 🍕 provides a plugin runner and you can add plugins to your state machine by simply adding them to the `plugins : []` option when initializing your service. 57 | 58 | - Plugins can export helper functions to be used during plugin usage and state machine composition. 59 | - Plugins are located in their own repositories prefixed with `sms-plugin---`. You can find a list of currently available plugins below. 60 | - Plugins can be passed an object containing options for the plugin. 61 | 62 | ```javascript 63 | import sms from "state-machine-snacks"; 64 | import components from "sms-plugin---components"; 65 | import logger from "sms-plugin---logger"; 66 | 67 | const config = { /* ...machine config */ }; 68 | 69 | // Create our state machine with stateUI 70 | const service = sms({ 71 | // Required 72 | config, 73 | 74 | // Example plugin usage: 75 | plugins : [ 76 | components(), 77 | logger(), 78 | ] 79 | }); 80 | 81 | service.start(); 82 | ``` 83 | 84 | ### 📦 [Plugin Components](https://github.com/qudo-lucas/sms-plugin---components) 85 | 86 | Conditionally render components as you enter/exit states. 87 | 88 | 91 | 92 | ### 📦 [Plugin Logger](https://github.com/qudo-lucas/sms-plugin---logger) 93 | 94 | Provide useful logging when developing with XState. 95 | 96 | ### [WIP] Plugin Router 97 | Map browser URLs to specific states. 98 | 99 | ## 💻 Examples 100 | ### ✨ [Simple UI](https://github.com/qudo-lucas/sms-template---simple-ui) 101 | Example of a simple UI utilizing [State Machine Snacks](https://github.com/qudo-lucas/state-machine-snacks) and [Plugin Components](https://github.com/qudo-lucas/sms-plugin---components). See how you can use a state machine to render components. 102 | 103 | ## 🛠 Contribute 104 | #### Resources 105 | - [Plugin Development](/docs/plugin-development.md) 106 | - [Todo (Project Board)](https://github.com/qudo-lucas/state-machine-snacks/projects/1) 107 | 108 | #### Links to Everything 109 | - [https://github.com/qudo-lucas/state-machine-snacks](https://github.com/qudo-lucas/state-machine-snacks) 110 | - [https://github.com/qudo-lucas/sms-plugin---logger](https://github.com/qudo-lucas/sms-plugin---logger) 111 | - [https://github.com/qudo-lucas/sms-plugin---components](https://github.com/qudo-lucas/sms-plugin---components) 112 | - [https://github.com/qudo-lucas/sms-template---simple-ui](https://github.com/qudo-lucas/sms-template---simple-ui) 113 | - [https://github.com/qudo-lucas/sms-template---plugin](https://github.com/qudo-lucas/sms-template---plugin) 114 | -------------------------------------------------------------------------------- /docs/plugin-development.md: -------------------------------------------------------------------------------- 1 | [⬅ Back to 🍕](https://github.com/qudo-lucas/state-machine-snacks) 2 | # Plugin Development 3 | 4 | [👨🏽💻 Plugin Developer Template](https://github.com/qudo-lucas/sms-template---plugin) 5 | 6 | ### Dev Helpers 7 | There are helper functions located available via 🍕 that you should utilize throughout plugin development. 8 | ```javascript 9 | import { util } from "state-machine-snacks"; 10 | 11 | const { 12 | configEditor, 13 | extractMetadata, 14 | } = util; 15 | ``` 16 | | Helper | Description | Function | 17 | | ------ | ------ | ---- | 18 | | [Config Editor](#config-editor) | Useful during the `config` hook. | `configEditor` | 19 | | [Extract Metadata From State Chart](#extract-metadata) | Generates a map of states that have matadata. | `extractMetadata` | 20 | 21 | ## Config Editor 22 | Append things like events, context, and states to a users config without affecting any original values. 23 | 24 | **Note:** These helpers can only be used in the `config` hook during the plugin lifecycle. 25 | 26 | ```javascript 27 | import { assign } from "xstate"; 28 | import { util } from "state-machine-snacks"; 29 | 30 | const { 31 | configEditor, 32 | } = util; 33 | 34 | export default () => { 35 | config : (config) => { 36 | let result = { ...config }; 37 | 38 | // Add context, a place where we can store values that the user can also read. 39 | result = configEditor.addContext(result, { someContext : "some value" }) 40 | 41 | 42 | // Add an update event used to update context. 43 | result = configEditor.addEventListener(result, { plugin:myPluginName:UPDATE_STATE : { 44 | actions : assign({ 45 | someContext : (ctx, event) => event.data, 46 | }) 47 | }}) 48 | 49 | // Add a new state. 50 | result = configEditor.addState(result, { 51 | coolState : { 52 | entry : () => console.log("cool state bro) 53 | }, 54 | }) 55 | 56 | return result; 57 | }, 58 | }) 59 | ``` 60 | 61 | ### Adding Events 62 | `configEditor.addEventListener(config, event)` 63 | You can add events to the users config during the `config` hook with the `addEventListener` function. 64 | 65 | When adding event listeners, it is recommended that you prefix events with a pattern similar to the following example. 66 | 67 | **Example:** ```{ plugin:yourPluginName:WHATEVER_YOU_WANT : ".stateTwo" }``` 68 | | Args | Description | | 69 | | ----------- | ----------- | ----------- | 70 | | config | XState state machine config. | Required 71 | | event | XState event object. | Required 72 | 73 | ### Adding Context 74 | `configEditor.addContext(config, context)` 75 | You can add context to the users config during the `config` hook with the `addContext` function. 76 | 77 | 78 | **Example:** ```{ myPluginsContext : "some values for my plugin or the user" }``` 79 | | Args | Description | | 80 | | ----------- | ----------- | ----------- | 81 | | config | XState state machine config. | Required 82 | | context | Object to be appended to machine context. | Required 83 | 84 | ### Adding States 85 | `configEditor.addState(config, context)` 86 | You can add states to the users config during the `config` hook with the `addState` function. 87 | 88 | **Example:** ```{ myPluginsState : { entry : () => console.log("made it")}}``` 89 | | Args | Description | | 90 | | ----------- | ----------- | ----------- | 91 | | config | XState state machine config. | Required 92 | | state | Object to be appended to machine states. | Required 93 | 94 |