Last active
May 2, 2020 05:34
-
-
Save nem035/e284b7cc631000e67fa20d888951a307 to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ---------------------------------------------- | |
// 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