First commit
This commit is contained in:
commit
7d13a3ea8b
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
dist/
|
||||
node_modules
|
29
package.json
Normal file
29
package.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "solid-pb",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"dev": "tsc -w",
|
||||
"build": "tsc -w",
|
||||
"prepublish": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nedpals/pbf": "^1.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"pocketbase": "^0.25.1",
|
||||
"solid-js": "^1.9.4",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
15
src/context.ts
Normal file
15
src/context.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { createContext, useContext } from "solid-js";
|
||||
import PocketBaseClient from "pocketbase";
|
||||
|
||||
|
||||
const PBContext = createContext<PocketBaseClient>();
|
||||
|
||||
export const usePB = () => {
|
||||
const ctx = useContext(PBContext)
|
||||
if (!ctx) throw new Error("This hook must be used within a PBContext.Provider!");
|
||||
|
||||
return ctx;
|
||||
};
|
||||
|
||||
|
||||
export default PBContext;
|
159
src/hooks.ts
Normal file
159
src/hooks.ts
Normal file
@ -0,0 +1,159 @@
|
||||
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 { 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 = crypto.randomUUID();
|
||||
|
||||
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 = crypto.randomUUID();
|
||||
|
||||
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;
|
||||
}
|
3
src/index.ts
Normal file
3
src/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { default as PBContext, usePB } from "./context.js";
|
||||
export { useRecord, useRecords } from "./hooks.js";
|
||||
|
12
tsconfig.json
Normal file
12
tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "nodenext",
|
||||
"target": "ESNext",
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
47
yarn.lock
Normal file
47
yarn.lock
Normal file
@ -0,0 +1,47 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@nedpals/pbf@^1.3.2":
|
||||
version "1.3.2"
|
||||
resolved "https://npm.hibas123.de/@nedpals/pbf/-/pbf-1.3.2.tgz#53b00c65103fbaa90c217ea0a4183119d9f9817e"
|
||||
integrity sha512-vTEUIbVF8AiufclVzor2HWMoTrnIZn74NTu+B7HlNlB+QR9HEq/9Rf/hufX9QOt54iOoJukP2x3ASsDUKl48MQ==
|
||||
|
||||
csstype@^3.1.0:
|
||||
version "3.1.3"
|
||||
resolved "https://npm.hibas123.de/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
|
||||
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
|
||||
|
||||
pocketbase@^0.25.1:
|
||||
version "0.25.1"
|
||||
resolved "https://npm.hibas123.de/pocketbase/-/pocketbase-0.25.1.tgz#e2bb606d2e39a992d9b328d67a24a9879ba7337d"
|
||||
integrity sha512-2IH0KLI/qMNR/E17C7BGWX2FxW7Tead+igLHOWZ45P56v/NyVT18Jnmddeft+3qWWGL1Hog2F8bc4orWV/+Fcg==
|
||||
|
||||
seroval-plugins@^1.1.0:
|
||||
version "1.2.1"
|
||||
resolved "https://npm.hibas123.de/seroval-plugins/-/seroval-plugins-1.2.1.tgz#fa535e70ade8af553634b2b5c80d8a6fd8c2ff72"
|
||||
integrity sha512-H5vs53+39+x4Udwp4J5rNZfgFuA+Lt+uU+09w1gYBVWomtAl98B+E9w7yC05Xc81/HgLvJdlyqJbU0fJCKCmdw==
|
||||
|
||||
seroval@^1.1.0:
|
||||
version "1.2.1"
|
||||
resolved "https://npm.hibas123.de/seroval/-/seroval-1.2.1.tgz#fc671d63445923ab64f7abaf3967c83901382f40"
|
||||
integrity sha512-yBxFFs3zmkvKNmR0pFSU//rIsYjuX418TnlDmc2weaq5XFDqDIV/NOMPBoLrbxjLH42p4UzRuXHryXh9dYcKcw==
|
||||
|
||||
solid-js@^1.9.4:
|
||||
version "1.9.4"
|
||||
resolved "https://npm.hibas123.de/solid-js/-/solid-js-1.9.4.tgz#da9b5645f10875a631d93335cd50525ff36b6c27"
|
||||
integrity sha512-ipQl8FJ31bFUoBNScDQTG3BjN6+9Rg+Q+f10bUbnO6EOTTf5NGerJeHc7wyu5I4RMHEl/WwZwUmy/PTRgxxZ8g==
|
||||
dependencies:
|
||||
csstype "^3.1.0"
|
||||
seroval "^1.1.0"
|
||||
seroval-plugins "^1.1.0"
|
||||
|
||||
typescript@^5.7.3:
|
||||
version "5.7.3"
|
||||
resolved "https://npm.hibas123.de/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e"
|
||||
integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==
|
||||
|
||||
zod@^3.24.2:
|
||||
version "3.24.2"
|
||||
resolved "https://npm.hibas123.de/zod/-/zod-3.24.2.tgz#8efa74126287c675e92f46871cfc8d15c34372b3"
|
||||
integrity sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==
|
Loading…
x
Reference in New Issue
Block a user