Skip to content

Instantly share code, notes, and snippets.

@justrhysism
Last active January 20, 2021 01:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save justrhysism/d316694dbacb931a3778b97e876fb5bd to your computer and use it in GitHub Desktop.
Save justrhysism/d316694dbacb931a3778b97e876fb5bd to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
// Available variables:
// - Machine
// - interpret
// - assign
// - send
// - sendParent
// - spawn
// - raise
// - actions
// - XState (all XState exports)
const stepRefId = (id) => `${id}Ref`;
const undoStepRefId = (refId) => refId.split('Ref')[0];
const stepMachineFactory = (props) => {
const { id, process = async () => void 0 } = props;
return Machine({
id,
initial: 'initial',
states: {
initial: {
on: {
START: 'ready',
},
},
ready: {
on: {
TOUCHED: 'progress',
},
},
progress: {
on: {
RUN: 'processing',
},
},
processing: {
invoke: {
src: process,
onDone: {
target: 'complete',
},
onError: {
target: 'invalid',
},
},
on: {},
},
complete: {
entry: sendParent({ type: 'STEP_COMPLETE', id }),
on: {
TOUCHED: 'progress',
RUN: 'processing',
OUTDATED: 'outdated',
},
},
invalid: {
on: {
TOUCHED: 'progress',
RUN: 'processing',
OUTDATED: 'outdated',
},
},
outdated: {
on: {
TOUCHED: 'progress',
RUN: 'processing',
},
},
locked: {
type: 'final',
},
},
});
};
const workflowFactory = (steps) => {
const _steps = steps ?? [];
const firstStepId = _steps[0]?.id;
const stepSpawns = Object.fromEntries(
_steps.map(({ id, process }) => [stepRefId(id), () => spawn(stepMachineFactory({ id, process }), { sync: true })])
);
const stepRefs = Object.fromEntries(Object.keys(stepSpawns).map((k) => [k, null]));
const graphIndexMap = new Map();
const graphMap = new Map(
_steps.map(({ id }, index) => [id, { id, refId: stepRefId(id), index, children: [], parents: [] }])
);
_steps.forEach((step) => {
if (!step.deps) return;
const graphItem = graphMap.get(step.id);
if (!graphItem) return;
step.deps.forEach((depId) => {
const depGraphItem = graphMap.get(depId);
if (!depGraphItem) return;
graphItem.parents.push(depGraphItem);
depGraphItem.children.push(graphItem);
});
// Update index map
graphIndexMap.set(graphItem.index, graphItem);
});
return Machine({
id: 'workflow',
initial: 'initial',
context: {
currentStep: firstStepId,
graphMap,
graphIndexMap,
...stepRefs,
},
states: {
initial: {
entry: assign({
...stepSpawns,
}),
on: {
'': 'start',
},
},
start: {
entry: send('START', { to: (context) => context[stepRefId(firstStepId)] }),
on: {
'': 'idle',
},
},
idle: {
on: {
RUN: 'running',
STEP_COMPLETE: {
actions: assign((context, { id }) => {
const completeStepGraphItem = context.graphMap.get(id);
if (!completeStepGraphItem) return {};
const readyStepRefIds = completeStepGraphItem.children.map((item) => item.refId) ?? [];
// Ready dependent steps and move to first
if (readyStepRefIds.length) {
readyStepRefIds.forEach((refId) => {
context[refId]?.send('START');
});
const nextStepId = undoStepRefId(readyStepRefIds[0]);
return { currentStep: nextStepId };
}
// No dependent steps, move to next step in order
const nextIndex = completeStepGraphItem.index + 1;
const nextStepGraphItem = context.graphIndexMap.get(nextIndex);
if (!nextStepGraphItem) return;
return { currentStep: nextStepGraphItem.id };
}),
},
'currentStep.UPDATE': {
actions: assign({
currentStep: (_, { value }) => value,
}),
cond: (context, { value }) => context[`${value}Ref`]?.state.value !== 'initial',
},
},
},
running: {
on: { STOP: 'idle' },
},
},
});
};
const chanceInTen = (chance) => Math.random() * 10 < chance - 0.1;
const processStep = () =>
new Promise((resolve, reject) => setTimeout(() => (chanceInTen(7) ? resolve() : reject()), 1500));
const workflowMachine = workflowFactory([
{ id: 'details', process: processStep },
{ id: 'initiatives', process: processStep, deps: ['details'] },
{ id: 'schedule', process: processStep, deps: ['initiatives'] },
{ id: 'variations', process: processStep, deps: ['initiatives'] },
{ id: 'run', process: processStep, deps: ['details', 'variations'] },
]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment