Skip to content

Instantly share code, notes, and snippets.

@schalkventer
Created February 11, 2026 06:03
Show Gist options
  • Select an option

  • Save schalkventer/9d0f773f0a90a4cd99e67a0eee3b0add to your computer and use it in GitHub Desktop.

Select an option

Save schalkventer/9d0f773f0a90a4cd99e67a0eee3b0add to your computer and use it in GitHub Desktop.
πŸ‘½ OCRUD
import { useEffect } from "react";
import { createCrudService } from "../crud";
import { faker as f } from "@faker-js/faker";
/**
* Mocking function to emulate network latency for demonstration purposes.
*/
const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
/**
* Mocked HTTP server responses for demonstration purposes.
*/
const http = {
get: async (): Promise<Task[]> => {
await delay(3000);
return Array.from({ length: 100 }, () => ({
id: f.string.uuid() as Task["id"],
label: f.lorem.sentence(),
completed: f.datatype.boolean(),
}));
},
post: async (values: Pick<Task, "label">) => {
console.log("post", values);
await delay(3000);
return {
id: f.string.uuid() as Task["id"],
};
},
put: async (values: Pick<Task, "id" | "label" | "completed">) => {
console.log("put", values);
await delay(3000);
return {
id: values.id,
};
},
delete: async (id: Task["id"]) => {
console.log("delete", id);
await delay(3000);
return {
id,
};
},
};
/**
* Basic item to be used in example, if for example the this is a to-do app.
*/
type Task = {
id: string & { __brand: "EXAMPLE_ID" };
label: string;
completed: boolean;
};
const service = createCrudService<Task>({
entity: "task",
pull: http.get,
push: async ({ type, value }) => {
if (type === "insert") return http.post(value);
if (type === "remove") return http.delete(value);
return http.put(value);
},
});
type Overlay =
| {
variant: "adding";
details?: never;
}
| {
variant: "editing" | "removing";
details: {
target: Task["id"];
};
};
export const ExampleComponent = () => {
const [overlay, setOverlay] = useState<"null" | "adding" | "editing" | "removing">(null);
useEffect(() => {
service.sync();
}, []);
const list = service.useList();
const syncing = service.useSyncing();
if (!list) return <p>Loading...</p>;
return (
<>
<div>{syncing ? "πŸ”" : "βœ…"}</div>
<ul>
{Object.values(list).map((x) => (
<li key={x.id}>
<input
type="checkbox"
checked={x.completed}
onChange={() => {
service.update(x.id, (y) => {
y.completed = !y.completed;
});
}}
/>
<span>{x.label}</span> - {x.completed ? "Completed" : "Not Completed"}
</li>
))}
</ul>
</>
);
};
import { produce } from "immer";
import { createStore, useStore } from "zustand";
import { recordFrom } from "@qr-fox/types";
export type CrudStore<T extends { id: string }> = {
updated: number;
pending: boolean;
list: Record<T["id"], T> | null;
pulling: number;
pushing: number;
};
export const createCrudService = <T extends { id: K }, K extends string = string>(config: {
entity: string;
pull: () => Promise<T[]>;
push: (
props:
| { type: "remove"; value: T["id"] }
| { type: "insert"; value: Omit<T, "id"> }
| { type: "update"; value: T }
) => Promise<{ id: T["id"] }>;
}) => {
const { pull, push, entity } = config;
const store = createStore<CrudStore<T>>(() => ({
pending: false,
pulling: 0,
pushing: 0,
list: null,
updated: 0,
}));
const inner = (fn: (current: Record<T["id"], T>) => void) => {
if (!store.getState().list) {
throw new Error(`Cannot mutate ${entity} list before it is loaded.`);
}
const prev = store.getState().list;
const next = produce(prev, fn);
store.setState({
list: next,
updated: Date.now(),
});
};
const sync = async () => {
if (store.getState().pulling > 0) {
return store.setState({ pending: true });
}
store.setState((x) => ({ pulling: x.pulling + 1 }));
const response = await pull();
store.setState({
list: recordFrom(response),
updated: Date.now(),
});
store.setState((x) => ({ pulling: x.pulling - 1 }));
};
return {
sync,
remove: async (id: T["id"]) => {
store.setState((x) => ({ pushing: x.pushing + 1 }));
inner((list) => delete list[id]);
await push({ type: "remove", value: id });
const { pending, pulling } = store.getState();
if (pending && pulling === 1) {
store.setState({ pending: false, pulling: 0 });
return sync();
}
store.setState((x) => ({ pushing: x.pushing + 1 }));
},
insert: async (item: Omit<T, "id">) => {
store.setState((x) => ({ pushing: x.pushing + 1 }));
const response = await push({ type: "insert", value: item });
if (!response?.id) {
throw new Error(`Failed to insert ${entity}. Response did not contain id.`);
}
inner((x) => {
x[response.id] = { ...item, id: response.id } as T;
});
const { pending, pulling } = store.getState();
if (pending && pulling === 1) {
store.setState({ pending: false, pulling: 0 });
return sync();
}
store.setState((x) => ({ pushing: x.pushing + 1 }));
},
update: async (id: T["id"], mutate: (current: T) => void) => {
const { list } = store.getState();
if (!list) {
throw new Error(`Cannot update ${entity} list before it is loaded.`);
}
const prev = list[id];
const next = produce(prev, mutate);
if (prev.id !== next.id) {
throw new Error(`Cannot change id of ${entity} from "${prev.id}" to "${next.id}".`);
}
store.setState((x) => ({ pushing: x.pushing + 1 }));
inner((x) => {
x[id] = next;
});
await push({ type: "update", value: next });
const { pending, pulling } = store.getState();
if (pending && pulling === 1) {
store.setState({ pending: false, pulling: 0 });
return sync();
}
store.setState((x) => ({ pushing: x.pushing + 1 }));
},
useList: () => useStore(store, (x) => x.list),
useUpdated: () => useStore(store, (x) => x.updated),
usePushing: () => useStore(store, (x) => x.pushing),
usePulling: () => useStore(store, (x) => x.pulling),
useSyncing: () => useStore(store, (x) => x.pushing > 0 || x.pulling > 0 || x.pending),
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment