solid-pb/src/hooks.ts

160 lines
4.8 KiB
TypeScript

import { createEffect, onCleanup } from "solid-js";
import { createStore } from "solid-js/store";
import { RecordModel, RecordSubscription } from "pocketbase";
import * as pbf from "@nedpals/pbf";
import { z } from "zod";
import { nanoid } from "nanoid";
import { usePB } from "./context.js";
interface IUseRecordOptions {
realtime?: boolean;
validator?: z.AnyZodObject;
expand?: string[];
}
export function useRecord<T>(collection: string, id: () => string, options?: IUseRecordOptions) {
const pb = usePB();
const requestKey = nanoid();
const [record, setRecord] = createStore<{
loading: boolean,
record: T & RecordModel | null,
error: Error | null,
}>({
loading: true,
error: null,
record: null,
});
const v = options?.validator ?? ({ parse: (i) => id });
createEffect(() => {
const cid = id();
const setRecordChecked: typeof setRecord = (...args) => {
if (cid !== id()) return;
(setRecord as any)(...args);
}
setRecordChecked("loading", () => true)
setRecordChecked("error", () => null)
pb.collection(collection).getOne(cid, {
expand: options?.expand ? options.expand.join(",") : undefined,
requestKey,
}).then((value) => {
setRecordChecked("record", () => v.parse(value) as T & RecordModel)
}).catch((error) => {
setRecordChecked("error", () => error as Error)
}).finally(() => {
setRecordChecked("loading", () => false)
});
const sub = pb.realtime.subscribe(collection + "/" + cid, (value) => {
//TODO: Check what actions exist and if some needs special handling
setRecordChecked("record", () => v.parse(value.record) as T & RecordModel)
}, {
query: {
expand: options?.expand ? options.expand.join(",") : undefined,
requestKey: requestKey + "-rt",
}
});
sub.catch(err => { console.error("Error in realtime subscription", err) });
onCleanup(() => {
pb.cancelRequest(requestKey);
pb.cancelRequest(requestKey + "-rt");
sub.then(s => s());
});
})
return record;
}
interface IUseRecordsOptions {
realtime?: boolean;
validator?: z.AnyZodObject;
filter?: () => pbf.MaybeFilter<pbf.Filter> | undefined,
expand?: string[];
}
export function useRecords<T>(collection: string, options: IUseRecordsOptions = { expand: [] }) {
const pb = usePB();
const requestKey = nanoid();
const fstr = () => options.filter && options.filter() ? pbf.stringify(options.filter()) : undefined;
const v = options.validator ?? ({ parse: (i) => i });
const [records, setRecords] = createStore<{
loading: boolean,
records: (T & RecordModel)[],
error: Error | null,
}>({
records: [],
loading: true,
error: null,
});
createEffect(() => {
const cf = fstr();
const setRecordChecked: typeof setRecords = (...args) => {
if (cf !== fstr()) return;
(setRecords as any)(...args);
}
setRecordChecked("loading", () => true)
setRecordChecked("error", () => null)
pb.collection(collection).getFullList({
requestKey,
filter: cf,
expand: options?.expand ? options.expand.join(",") : undefined,
}).then((values) => {
setRecordChecked("records", () => values.map(value => v.parse(value) as T & RecordModel))
}).catch((error) => {
setRecordChecked("error", () => error as Error)
}).finally(() => {
setRecordChecked("loading", () => false)
});
const sub = pb.realtime.subscribe(collection, (event: RecordSubscription<T & RecordModel>) => {
console.log("Event", event);
const rec = v.parse(event.record) as T & RecordModel;
switch (event.action) {
case "create":
setRecordChecked("records", (records) => [...records, rec]);
break;
case "update":
setRecordChecked("records", (records) => records.map(r => r.id === rec.id ? rec : r));
break;
case "delete":
setRecordChecked("records", (records) => records.filter(r => r.id !== rec.id));
break;
default:
console.log("Unknown action", event.action);
}
}, {
requestKey: requestKey + "-rt",
query: {
filter: cf,
expand: options?.expand ? options.expand.join(",") : undefined,
}
});
sub.then(unsub => { console.log("Subscribed to realtime changes!"); });
sub.catch(err => { console.error("Error in realtime subscription", err) });
onCleanup(() => {
pb.cancelRequest(requestKey);
pb.cancelRequest(requestKey + "-rt");
sub.then(s => s());
});
})
return records;
}