Skip to content

Instantly share code, notes, and snippets.

@ggoodman
Last active March 10, 2020 18:15
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ggoodman/d26ee53823b0a6ce42c178dd9c89cb18 to your computer and use it in GitHub Desktop.
Save ggoodman/d26ee53823b0a6ce42c178dd9c89cb18 to your computer and use it in GitHub Desktop.
Definition for a state machine that tracks a node http request through its lifecycle
{
"PendingSocket": {
"kind": "Intermediate",
"onEnter": [
{}
],
"onEvent": {
"Error": [
{
"targetStates": [
"Error"
]
}
],
"Socket": [
{
"targetStates": [
"SocketObtained"
]
}
]
},
"onExit": [
{}
]
},
"SocketObtained": {
"kind": "Intermediate",
"onEnter": [
{
"targetStates": [
"SocketConnected"
]
},
{}
],
"onEvent": {
"Error": [
{
"targetStates": [
"Error"
]
}
],
"Connect": [
{
"targetStates": [
"SocketConnected"
]
}
]
},
"onExit": [
{}
]
},
"SocketConnected": {
"kind": "Intermediate",
"onEnter": [
{}
],
"onEvent": {
"Error": [
{
"targetStates": [
"Error"
]
}
],
"Response": [
{
"targetStates": [
"Responded"
]
}
]
},
"onExit": [
{}
]
},
"Responded": {
"kind": "Final",
"onEnter": [],
"onEvent": {},
"onExit": []
},
"Error": {
"kind": "Final",
"onEnter": [],
"onEvent": {},
"onExit": []
}
}
export enum RequestEventKind {
Error = 'Error',
Socket = 'Socket',
Connect = 'Connect',
Response = 'Response',
}
export type ErrorEvent = {
eventName: RequestEventKind.Error;
err: NodeJS.ErrnoException;
};
export type SocketEvent = {
eventName: RequestEventKind.Socket;
socket: Net.Socket;
};
export type ConnectEvent = {
eventName: RequestEventKind.Connect;
};
export type ResponseEvent = {
eventName: RequestEventKind.Response;
res: Http.IncomingMessage;
};
export type RequestEvent = ErrorEvent | SocketEvent | ConnectEvent | ResponseEvent;
export enum RequestStateKind {
PendingSocket = 'PendingSocket',
SocketObtained = 'SocketObtained',
SocketConnected = 'SocketConnected',
Responded = 'Responded',
Error = 'Error',
}
export type PendingSocketState = FSM.IntermediateState<{
stateName: RequestStateKind.PendingSocket;
disposer: DisposableStore;
req: Http.ClientRequest;
}>;
export type SocketObtainedState = FSM.IntermediateState<{
stateName: RequestStateKind.SocketObtained;
disposer: DisposableStore;
req: Http.ClientRequest;
socket: Net.Socket;
}>;
export type SocketConnectedState = FSM.IntermediateState<{
stateName: RequestStateKind.SocketConnected;
disposer: DisposableStore;
req: Http.ClientRequest;
}>;
export type RespondedState = FSM.FinalState<{
stateName: RequestStateKind.Responded;
req: Http.ClientRequest;
res: Http.IncomingMessage;
}>;
export type ErrorState = FSM.FinalState<{
stateName: RequestStateKind.Error;
}>;
export type RequestState =
| PendingSocketState
| SocketObtainedState
| SocketConnectedState
| RespondedState
| ErrorState;
const machineTemplate = createMachineFactory<RequestEvent, RequestState>()
.state(RequestStateKind.PendingSocket, builder =>
builder
.onEnterExecute((state, _event, send) => {
state.disposer.add(
Event.fromNodeEventEmitter<NodeJS.ErrnoException>(
state.req,
'error'
)(err => send({ eventName: RequestEventKind.Error, err }))
);
state.disposer.add(
Event.fromNodeEventEmitter<Net.Socket>(
state.req,
'socket'
)(socket => send({ eventName: RequestEventKind.Socket, socket }))
);
})
.onEventTransition(
RequestEventKind.Error,
(_state, event) => {
return {
stateName: RequestStateKind.Error,
err: event.err,
};
},
{ targetStates: [RequestStateKind.Error] }
)
.onEventTransition(
RequestEventKind.Socket,
(state, event) => {
return {
stateName: RequestStateKind.SocketObtained,
disposer: new DisposableStore(),
req: state.req,
socket: event.socket,
};
},
{ targetStates: [RequestStateKind.SocketObtained] }
)
.onExitExecute(state => {
state.disposer.dispose();
})
)
.state(RequestStateKind.SocketObtained, builder =>
builder
// If the socket isn't connecting, we transition straight to
// the SocketConnected state.
.onEnterTransition(
state => {
return {
stateName: RequestStateKind.SocketConnected,
disposer: new DisposableStore(),
req: state.req,
};
},
{
condition: state => !state.socket.connecting,
targetStates: [RequestStateKind.SocketConnected],
}
)
//
.onEnterExecute((state, _event, send) => {
state.disposer.add(
Event.fromNodeEventEmitter<NodeJS.ErrnoException>(
state.socket,
'error'
)(err => send({ eventName: RequestEventKind.Error, err }))
);
state.disposer.add(
Event.fromNodeEventEmitter<Net.Socket>(
state.socket,
'connect'
)(() => send({ eventName: RequestEventKind.Connect }))
);
})
.onEventTransition(
RequestEventKind.Error,
(_state, event) => {
return {
stateName: RequestStateKind.Error,
err: event.err,
};
},
{ targetStates: [RequestStateKind.Error] }
)
.onEventTransition(
RequestEventKind.Connect,
state => {
return {
stateName: RequestStateKind.SocketConnected,
disposer: new DisposableStore(),
req: state.req,
};
},
{ targetStates: [RequestStateKind.SocketConnected] }
)
.onExitExecute(state => {
state.disposer.dispose();
})
)
.state(RequestStateKind.SocketConnected, builder =>
builder
.onEnterExecute((state, _event, send) => {
state.disposer.add(
Event.fromNodeEventEmitter<NodeJS.ErrnoException>(
state.req,
'error'
)(err => send({ eventName: RequestEventKind.Error, err }))
);
state.disposer.add(
Event.fromNodeEventEmitter<Http.IncomingMessage>(
state.req,
'response'
)(res => send({ eventName: RequestEventKind.Response, res }))
);
})
.onEventTransition(
RequestEventKind.Error,
(_state, event) => {
return {
stateName: RequestStateKind.Error,
err: event.err,
};
},
{ targetStates: [RequestStateKind.Error] }
)
.onEventTransition(
RequestEventKind.Response,
(state, event) => {
return {
stateName: RequestStateKind.Responded,
req: state.req,
res: event.res,
};
},
{ targetStates: [RequestStateKind.Responded] }
)
.onExitExecute(state => {
state.disposer.dispose();
})
)
.finalState(RequestStateKind.Responded)
.finalState(RequestStateKind.Error);
// This produces the content of definition.json
console.log(JSON.stringify(machineTemplate, null, 2));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment