const JsError = Error export namespace Op { export type Status = "loading" | "error" | "ready" export type Loading = {status: "loading"} export type Error = {status: "error", reason: string} export type Ready = {status: "ready", payload: X} export type For = Loading | Error | Ready export type Setter = (op: For) => void export type Payload = O extends Op.Ready ? X : never; export const loading = (): For => ({status: "loading"}) export const error = (reason: string): For => ({status: "error", reason}) export const ready = (payload: X): For => ({status: "ready", payload}) export const is = Object.freeze({ loading: (op: For): op is Op.Loading => op.status === "loading", error: (op: For): op is Op.Error => op.status === "error", ready: (op: For): op is Op.Ready => op.status === "ready", }) export function payload(op: For) { return (op.status === "ready") ? op.payload : undefined } export function reason(op: For) { return (op.status === "error") ? op.reason : undefined } export type Choices = { loading: () => R error: (reason: string) => R ready: (payload: X) => R } export function select(op: For, choices: Choices) { switch (op.status) { case "loading": return choices.loading() case "error": return choices.error(op.reason) case "ready": return choices.ready(op.payload) default: console.error("op", op) throw new JsError("invalid op status") } } export async function load( set_op: Setter, operation: () => Promise, ) { set_op(loading()) try { const payload = await operation() set_op(ready(payload)) return payload as X } catch (err) { const reason = (err instanceof JsError) ? err.message : (typeof err === "string") ? err : "error" set_op(error(reason)) throw err } } export function morph(op: For, transmute: (a: A) => B) { return select>(op, { loading: () => loading(), error: reason => error(reason), ready: a => ready(transmute(a)), }) } export function all[]>(...ops: O) { const error = ops.find(is.error) return ( error ? error : ops.every(is.ready) ? ready(ops.map(payload)) : loading() ) as For<{[K in keyof O]: Payload}> } }