This repository has no description
1type ActionState<T> =
2 | { kind: "idle" }
3 | { kind: "loading" }
4 | { kind: "ready"; value: T }
5 | { kind: "failed"; error: string };
6type Operation<T, Args extends unknown[] = []> = (...args: Args) => Promise<T>;
7
8const defaultError = (cause: unknown) => (cause instanceof Error ? cause.message : String(cause));
9
10const create = <T, Args extends unknown[] = []>(
11 operation: Operation<T, Args>,
12 initial: ActionState<T>,
13 toError: typeof defaultError
14) => {
15 let state = $state<ActionState<T>>(initial);
16 let revision = 0;
17 let args: Args | undefined;
18
19 const run: Operation<void, Args> = async (...nextArgs) => {
20 const currentRevision = ++revision;
21 args = nextArgs;
22 state = { kind: "loading" };
23 try {
24 const value = await operation(...nextArgs);
25 if (currentRevision !== revision) return;
26 args = undefined;
27 state = { kind: "ready", value };
28 } catch (cause) {
29 if (currentRevision !== revision) return;
30 args = undefined;
31 state = { kind: "failed", error: toError(cause) };
32 }
33 };
34
35 return {
36 get data() {
37 return state.kind === "ready" ? state.value : undefined;
38 },
39 get error() {
40 return state.kind === "failed" ? state.error : undefined;
41 },
42 get loading() {
43 return state.kind === "loading";
44 },
45 get args() {
46 return state.kind === "loading" ? args : undefined;
47 },
48 run,
49 update(update: (current: T) => T): boolean {
50 if (state.kind !== "ready") return false;
51 revision++;
52 state = { kind: "ready", value: update(state.value) };
53 return true;
54 }
55 };
56};
57
58// loads run automatically and rerun when reactive values change:
59// const post = createLoad(() => api.getPost(id));
60//
61// actions only run when called:
62// const save = createAction(api.savePost);
63// await save.run(draft);
64//
65// both expose .data, .error, .loading, and .args.
66export const createAction = <T, Args extends unknown[] = []>(
67 operation: Operation<T, Args>,
68 toError: typeof defaultError = defaultError
69) => create(operation, { kind: "idle" }, toError);
70
71export const createLoad = <T>(load: Operation<T>, toError: typeof defaultError = defaultError) => {
72 const action = create(load, { kind: "loading" }, toError);
73 $effect(() => void action.run());
74 return action;
75};