Skip to content

Instantly share code, notes, and snippets.

@michaelsbradleyjr
Last active August 2, 2019 00:57
Show Gist options
  • Select an option

  • Save michaelsbradleyjr/ab221d5242c9da02fc97cb24affc16e3 to your computer and use it in GitHub Desktop.

Select an option

Save michaelsbradleyjr/ab221d5242c9da02fc97cb24affc16e3 to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
/* global Machine XState actions assign interpret raise send sendParent
setTimeout spawn window */
// in starting, want services to initially go to checking and if not auto start then go to stopped
// in starting, want failed to have a null event that checks if can fail; if it's okay to fail then go to stopped but don't send STOP event, otherwise send STOP event
// in running, want services to initially go to checking and if stopped then go to stopped otherwise go to running
// in runnin, when services goes to running should invoke the someserviceRunner which setups up duplex comm
// maybe can use null event with guard in starting/running instead of an explicit checking state but uncertain
// about interaction w/ invoke and entry, i.e. if null event condition is true then will invoke and/or entry not happen? need to experiment
// need a SHUTDOWN even that puts shutdown flag in the context and '' on running should not only check allStopped but also if shutdown flag is in the context
class Module {
constructor ({name}) {
this.name = name;
}
static randomInt(min, max) {
max = max + 1; // so the range is inclusive of max
return Math.floor(Math.random() * (max - min)) + min;
}
static doFail() {
const fail = Module.randomInt(1, 20);
if (fail === 1) return true;
return false;
}
static timeout() {
return Module.randomInt(1000, 3000);
}
async failMaybe(message) {
return await (new Promise((resolve, reject) => {
if (Module.doFail()) {
setTimeout(() => {
reject(new Error(`${this.name} ${message}`));
}, Module.timeout());
} else {
setTimeout(resolve, Module.timeout());
}
}));
}
restart() {
return this.failMaybe(Module.messages.FAILED_TO_RESTART);
}
start() {
return this.failMaybe(Module.messages.FAILED_TO_START);
}
stop() {
return this.failMaybe(Module.messages.FAILED_TO_STOP);
}
}
Module.messages = {
FAILED_TO_RESTART: 'failed to restart for some reason',
FAILED_TO_START: 'failed to start for some reason',
FAILED_TO_STOP: 'failed to stop cleanly for some reason, but it has been forcibly stopped'
};
class Blockchain extends Module {
constructor () {
super({name: 'blockchain'});
}
}
class Compiler extends Module {
constructor () {
super({name: 'compiler'});
}
}
const modules = {
blockchain: new Blockchain(),
compiler: new Compiler()
};
const context = {};
const acts = {
clearError: assign((context, event) => {
const serviceName = [event.type.split('.').pop()];
context = {...context};
delete context[serviceName];
return context;
}),
flagShutdown: assign((context, event) => ({
...context,
shutdown: true
})),
restartAll: send({type: 'RESTART', services: '*'}),
stop: send('STOP'),
stopAll: send({type: 'STOP', services: '*'}),
stopBlockchain: send({type: 'STOP', services: 'blockchain'}),
stopCompiler: send({type: 'STOP', services: 'compiler'}),
storeError: assign((context, event) => ({
...context,
[event.type.split('.').pop()]: {
error: event.data,
message: event.data.message
}
}))
};
function allTest(matches, states) {
return function (context, event, meta) {
let cond, state;
if (meta) ({cond, state} = meta);
if (state) {
matches = [].concat(matches);
for (const match of matches) {
if (state.matches(match)) {
return Object.values(
state.value[match]
).every(state => [].concat(states).includes(state));
}
}
return undefined;
} else {
return undefined;
}
};
}
function serviceMatchTest(eventName, serviceName) {
return function (context, event, meta) {
let cond, state;
if (meta) ({cond, state} = meta);
if (state && event.services) {
if (event.type !== eventName) return false;
if (event.services === '*') return true;
if ([].concat(event.services).includes(serviceName)) return true;
return false;
} else {
return undefined;
}
};
}
const guards = {
allStarted: allTest('starting', 'started'),
allStopped: allTest(['starting', 'running'], ['crashed', 'failed', 'stopped']),
canShutdown: (context, event, meta) => context.shutdown && guards.allStopped(context, event, meta),
isBlockchainRestart: serviceMatchTest('RESTART', 'blockchain'),
isBlockchainStop: serviceMatchTest('STOP', 'blockchain'),
isCompilerRestart: serviceMatchTest('RESTART', 'compiler'),
isCompilerStop: serviceMatchTest('STOP', 'compiler')
};
const services = {
blockchainRestarter: () => modules.blockchain.restart(),
blockchainStarter: () => modules.blockchain.start(),
blockchainStopper: () => modules.blockchain.stop(),
compilerRestarter: () => modules.compiler.restart(),
compilerStarter: () => modules.compiler.start(),
compilerStopper: () => modules.compiler.stop(),
};
const starting = {
blockchain: {
initial: 'starting',
states: {
starting: {
invoke: {
src: 'blockchainStarter',
onDone: {
target: 'started'
},
onError: {
target: 'failed',
actions: 'storeError'
}
},
on: {
STOP: 'stopping'
}
},
started: {
on: {
STOP: 'stopping'
}
},
failed: {
entry: 'stop'
},
stopping: {
invoke: {
src: 'blockchainStopper',
onDone: {
target: 'stopped'
},
onError: {
target: 'failed',
actions: 'storeError'
}
}
},
stopped: {}
}
},
compiler: {
initial: 'starting',
states: {
starting: {
invoke: {
src: 'compilerStarter',
onDone: {
target: 'started'
},
onError: {
target: 'failed',
actions: 'storeError'
}
},
on: {
STOP: 'stopping'
}
},
started: {
on: {
STOP: 'stopping'
}
},
failed: {
entry: 'stop'
},
stopping: {
invoke: {
src: 'compilerStopper',
onDone: {
target: 'stopped'
},
onError: {
target: 'failed',
actions: 'storeError'
}
}
},
stopped: {}
}
}
};
const running = {
blockchain: {
initial: 'running',
states: {
running: {
// invoke: 'blockchainRunner' --> should be able to transition to crashed, should clear error
on: {
RESTART: {
target: 'restarting',
cond: 'isBlockchainRestart'
},
STOP: {
target: 'stopping',
cond: 'isBlockchainStop'
}
}
},
crashed: {
on: {
RESTART: {
target: 'restarting',
cond: 'isBlockchainRestart'
}
}
},
failed: {
on: {
RESTART: {
target: 'restarting',
cond: 'isBlockchainRestart'
}
}
},
stopping: {
invoke: {
src: 'blockchainStopper',
onDone: {
target: 'stopped',
actions: 'clearError'
},
onError: {
target: 'failed',
actions: 'storeError'
}
},
on: {
RESTART: {
target: 'restarting',
cond: 'isBlockchainRestart'
}
}
},
stopped: {
on: {
RESTART: {
target: 'restarting',
cond: 'isBlockchainRestart'
}
}
},
restarting: {
invoke: {
src: 'blockchainRestarter',
onDone: {
target: 'running',
actions: 'clearError'
},
onError: {
target: 'failed',
actions: 'storeError'
}
},
on: {
RESTART: {
target: 'restarting',
cond: 'isBlockchainRestart'
},
STOP: {
target: 'stopping',
cond: 'isBlockchainStop'
}
}
}
}
},
compiler: {
initial: 'running',
states: {
running: {
// invoke: 'compilerRunner' --> should be able to transition to crashed, should clear error
on: {
RESTART: {
target: 'restarting',
cond: 'isCompilerRestart'
},
STOP: {
target: 'stopping',
cond: 'isCompilerStop'
}
}
},
crashed: {
on: {
RESTART: {
target: 'restarting',
cond: 'isCompilerRestart'
}
}
},
failed: {
on: {
RESTART: {
target: 'restarting',
cond: 'isCompilerRestart'
}
}
},
stopping: {
invoke: {
src: 'compilerStopper',
onDone: {
target: 'stopped',
actions: 'clearError'
},
onError: {
target: 'failed',
actions: 'storeError'
}
},
on: {
RESTART: {
target: 'restarting',
cond: 'isCompilerRestart'
}
}
},
stopped: {
on: {
RESTART: {
target: 'restarting',
cond: 'isCompilerRestart'
}
}
},
restarting: {
invoke: {
src: 'compilerRestarter',
onDone: {
target: 'running',
actions: 'clearError'
},
onError: {
target: 'failed',
actions: 'storeError'
}
},
on: {
RESTART: {
target: 'restarting',
cond: 'isCompilerRestart'
},
STOP: {
target: 'stopping',
cond: 'isCompilerStop'
}
}
}
}
}
};
const newAppInstance = 'new EmbarkRun(options, configs)';
const embarkRun = Machine(
{
id: 'embark run [--options]',
context: context,
initial: 'setting up',
states: {
'setting up': {
type: 'parallel',
states: {
'CLI options ➡︎ Application options': {},
'processing DApp configs': {}
},
on: {
READY: newAppInstance
}
},
[newAppInstance]: {
on: {
EXECUTE: 'starting'
}
},
starting: {
type: 'parallel',
states: {
...starting
},
on: {
'': [
{
target: 'running',
cond: 'allStarted'
},
{
target: 'stopped',
cond: 'allStopped'
}
]
}
},
running: {
type: 'parallel',
states: {
...running
},
on: {
'': {
target: 'stopped',
cond: 'canShutdown'
},
RESTART_ALL: {
internal: true,
actions: 'restartAll'
},
SHUTDOWN: {
internal: true,
actions: ['flagShutdown', 'stopAll']
},
STOP_ALL: {
internal: true,
actions: 'stopAll'
}
}
},
stopped: {
type: 'final'
}
}
},
{
actions: { ...acts },
guards: { ...guards },
services: { ...services }
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment