Skip to content

Instantly share code, notes, and snippets.

@nem035
Last active May 2, 2020 05:34
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 nem035/e284b7cc631000e67fa20d888951a307 to your computer and use it in GitHub Desktop.
Save nem035/e284b7cc631000e67fa20d888951a307 to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
// ----------------------------------------------
// fake DB stuff
// ----------------------------------------------
const logger = console;
let COUNTER_UNTIL_DB_IS_A_GOOD_BOY = 3;
const fakeDb = {
isConnected: false,
openConnection: async () => {
COUNTER_UNTIL_DB_IS_A_GOOD_BOY -= 1;
await new Promise((resolve, reject) =>
setTimeout(COUNTER_UNTIL_DB_IS_A_GOOD_BOY === 0 ? resolve : reject, 2000)
);
fakeDb.isConnected = COUNTER_UNTIL_DB_IS_A_GOOD_BOY === 0;
},
closeConnection: async () => {
await new Promise((resolve) => setTimeout(resolve, 2000));
fakeDb.isConnected = false;
COUNTER_UNTIL_DB_IS_A_GOOD_BOY = 3;
},
};
function openConnection() {
return fakeDb.openConnection();
}
function closeConnection() {
return fakeDb.closeConnection();
}
const onOpenConnectionAttempt = () => {};
const onOpenConnectionSuccess = () => {};
const onOpenConnectionError = () => {};
const onCloseConnectionSuccess = () => {};
const onCloseConnectionError = () => {};
const onDisconnection = () => {};
const onReconnection = () => {};
// ----------------------------------------------
// xstate stuff
// ----------------------------------------------
const dbConnectionStateMachine = Machine(
{
// state machine identifier
id: "DBConnectionMachine",
// initial state
initial: "closed",
// context for entire machine
context: {
openConnectionAttempts: 0,
openConnectionError: null,
disconnectionError: null,
closeConnectionError: null,
},
// state definitions
states: {
// the initial state for the db
// the "disconnected" event in mongoose cannot reliably predict the disconnection reason.
// the event may happen due to the code explicitly closing the connection, the database
// server crashing, or network connectivity issues.
//
// this is why have a "closed" state and a "disconnected" state.
//
// The goal is to distinguish between intentional disconnect (which triggers "closing")
// and a disconnect due to some kind of failure (which triggers "disconnecting")
closed: {
entry: ["resetOpenConnectionAttempts"],
on: {
OPEN_CONNECTION: {
target: "openingConnection",
},
},
},
// state representing active process of opening an initial db connection.
//
// if initial db connection fails, Mongoose will not attempt to reconnect.
// it will emit an 'error' event, and the promise mongoose.connect() returns will reject.
//
// this is why the target state in case of an error is the same state, i.e. a self transition.
//
// note: the failure (and the subsequent reconnection attempt) will be delayed by serverSelectionTimeoutMS
openingConnection: {
entry: ["incrementOpenConnectionAttempts"],
invoke: {
id: "openConnection",
src: openConnection,
// called when promise returned in src is resolved
onDone: {
// log what happened, update the context, and transition to "connected"
target: "connected",
actions: ["onOpenConnectionSuccess"],
},
// called when promise returned in src is rejected
onError: {
// if we failed to open a connection, try again via self transition
target: "openingConnection",
// log what happened, update the context, then try again
actions: ["onOpenConnectionError"],
},
},
},
// state representing an active db connection.
connected: {
on: {
CLOSE_CONNECTION: {
target: "closingConnection",
},
DISCONNECT: {
target: "disconnected",
actions: ["onDisconnection"],
},
},
},
// state representing closing a connection
// if the db closing fails, we still transition to the closed state
// but we remember the error.
closingConnection: {
invoke: {
id: "closeConnection",
src: closeConnection,
onDone: {
target: "closed",
actions: ["onCloseConnectionSuccess"],
},
onError: {
target: "closed",
actions: ["onCloseConnectionError"],
},
},
},
// this state represents any loss of connection after an initial connection is established
// we can only reach it using the "DISCONNECTED" event from the "connected" state
disconnected: {
on: {
RECONNECT: {
target: "connected",
actions: ["onReconnection"],
},
},
},
},
},
{
actions: {
incrementOpenConnectionAttempts: assign({
openConnectionAttempts: (context) => {
// all open connection attempts are counted
const openConnectionAttempts = context.openConnectionAttempts + 1;
onOpenConnectionAttempt({
...context,
openConnectionAttempts,
});
return openConnectionAttempts;
},
}),
// when a whole new open connection cycle starts
// we reset the attempts counter
resetOpenConnectionAttempts: assign({
openConnectionAttempts: () => 0,
}),
onOpenConnectionSuccess: assign({
openConnectionError: (context) => {
onOpenConnectionSuccess(context);
// set "context.openConnectionError" to null
return null;
},
}),
onOpenConnectionError: assign({
openConnectionError: (context, event) => {
onOpenConnectionError(context, event);
// update "context.openConnectionError" with the rejection reason
// from promise returned in "invoke.src" in the "connecting" state
return event.data;
},
}),
onCloseConnectionError: assign({
closeConnectionError: (context, event) => {
onCloseConnectionError(context, event);
// update "context.closeConnectionError" with the rejection reason
// from promise return in "invoke.src" in the "disconnecting" state
return event.data;
},
}),
onCloseConnectionSuccess: assign({
closeConnectionError: () => {
onCloseConnectionSuccess();
// set "context.closeConnectionError" to null
return null;
},
}),
onDisconnection: assign({
disconnectionError: (context, event) => {
onDisconnection(context, event);
// update "context.disconnectionError" with the rejection reason
// from promise return in "invoke.src" in the "disconnecting" state
return event.data;
},
}),
onReconnection: assign({
disconnectionError: (context) => {
onReconnection(context);
// clear "context.disconnectionError"
return null;
},
}),
},
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment