Skip to content

Instantly share code, notes, and snippets.

@timruffles
Last active November 26, 2025 15:43
Show Gist options
  • Select an option

  • Save timruffles/4aa264059265b412a9bebd007dfaa0a0 to your computer and use it in GitHub Desktop.

Select an option

Save timruffles/4aa264059265b412a9bebd007dfaa0a0 to your computer and use it in GitHub Desktop.
operation - like Result and various other ADTs in Haskell etc
export type Operation<T, E = Error> = typeof NotStarted | typeof Waiting | Complete<T> | Failed<E>;
export enum OperationState {
NotStartedState = "NotStarted",
WaitingState = "Waiting",
CompleteState = "Complete",
FailedState = "Failed",
}
export const NotStarted = {
opState: OperationState.NotStartedState,
} as const;
export const Waiting = {
opState: OperationState.WaitingState,
} as const;
export class Complete<T> {
readonly opState = OperationState.CompleteState;
constructor(readonly value: T) {}
}
export class Failed<E> {
readonly opState = OperationState.FailedState;
constructor(readonly error: E) {}
static fromCatch(e: Error | any): Failed<{ name: string; message: string }> {
if (e instanceof Error) {
const { name, message } = e;
return new Failed({ name, message });
}
return new Failed({
name: "Unknown",
message: "An unknown error occurred",
});
}
static fromError(e: Error): Failed<{ name: string; message: string }> {
const { name, message } = e;
return new Failed({ name, message });
}
}
export type Handler<T, E, R> = {
notStarted: () => R;
waiting: () => R;
complete: (t: T) => R;
failed: (e: E) => R;
};
type BooleanHandler<T, R> = {
present: (t: T) => R;
absent: () => R;
};
export function isWaiting<T, E>(op: Operation<T, E>): op is typeof Waiting {
return op.opState === OperationState.WaitingState;
}
export function isComplete<T, E>(op: Operation<T, E>): op is Complete<T> {
return op.opState === OperationState.CompleteState;
}
export function isFailed<E>(op: Operation<any, E>): op is Failed<E> {
return op.opState === OperationState.FailedState;
}
export function handle<T, E, R>(op: Operation<T, E>, handler: Handler<T, E, R>): R {
switch (op.opState) {
case OperationState.CompleteState:
return handler.complete(op.value);
case OperationState.FailedState:
return handler.failed(op.error);
case OperationState.NotStartedState:
return handler.notStarted();
case OperationState.WaitingState:
return handler.waiting();
}
}
// transform both value containing cases of the operation - useful for turning a complex op type into a displayable one
export const mapOperation = <T, U, E, F>(
op: Operation<T, E>,
mapComplete: (t: T) => U,
mapFailed: (e: E) => F,
): Operation<U, F> =>
op.opState === OperationState.CompleteState
? new Complete(mapComplete(op.value))
: op.opState === OperationState.FailedState
? new Failed(mapFailed(op.error))
: op;
export const handlePresence = <T, E, R>(op: Operation<T, E>, handler: BooleanHandler<T, R>): R =>
op.opState === OperationState.CompleteState ? handler.present(op.value) : handler.absent();
export const mapComplete = <T, E, U>(op: Operation<T, E>, fn: (t: T) => U): Operation<U, E> =>
op.opState === OperationState.CompleteState ? new Complete(fn(op.value)) : op;
export const mapCompleteAndGet = <T, E, U>(
op: Operation<T, E>,
fn: (t: T) => U,
getAbsent: () => U,
): U => (op.opState === OperationState.CompleteState ? fn(op.value) : getAbsent());
export const mapAndGetPresence = <T, E, R>(
op: Operation<T, E>,
handler: BooleanHandler<T, R>,
): R =>
op.opState === OperationState.CompleteState ? handler.present(op.value) : handler.absent();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment