js-sdk
js-sdk copied to clipboard
Recommended way of typing records?
What is the best practice of getting typed fields for my records? I have tried a few different approaches, but I haven't found any straightforward way.
In listResult1
, I'm limited by ListResult is not exported from the sdk
In listResult2
, the getList function is not generic.
import PocketBase, {ListResult, Record} from 'pocketbase'
const client = new PocketBase('http://127.0.0.1:8090');
interface MyItem extends Record {
name: string,
age: number
}
async function example() {
// ListResult type is not exported
const listResult1: ListResult<MyItem> = await client.records.getList("my-item", 1, 20)
// getList() is not generic
const listResult2 = await client.records.getList<MyItem>("my-item", 1, 20)
// the age field resolves to "any" instead of "number"
listResult1.items[0].age
listResult2.items[0].age
}
i was running into the same problem just now and i would also prefer if the getList (and all the other functions to manage records) would be generic.
As a workaround you can just cast in the meantime:
import PocketBase, { Record } from 'pocketbase'
interface BananaRecord extends Record {
foo: string
bar: string
}
const client = new PocketBase('http://127.0.0.1:8090')
async function getBananas(): Promise<BananaRecord[]> {
const result = await client.records.getList('game')
return result.items as BananaRecord[]
}
@mnorlin Using type assertion as suggested by @silberjan is the easiest way to have typed records at the moment.
The generics suggestion is a nice one, although it will be a little hacky since the generic type cannot be used directly as constructor, but it is doable and I'll consider it for the next major release (v0.8.0) that will be shipped with the changes from https://github.com/pocketbase/pocketbase/issues/376.
What about making the Records
class generic, expose it and letting users instantiate it themselves with the record name as parameter. this way you would not need to touch the functions in SubCrudService
.
Could look something like this:
import PocketBase, { Record, Records } from 'pocketbase'
interface BananaRecord extends Record {
foo: string
bar: string
}
const client = new PocketBase('http://127.0.0.1:8090')
const bananaRecordsClient = new Records<BananaRecord>('banana', client) // pass record name, and client here
async function getBananas(): Promise<BananaRecord[]> {
const result = await bananaRecordsClient.getList() // no need to supply record name here anymore
return result.items
}
It's a little too verbose in my opinion. I think the namespaced approach (aka. client.records.getList<T>
) is more readable and easier to use.
Pairing it with sql-ts would also safe you time and errors manually typing the db schema.
I'm currently using casting but having generics would be great, e.g. getList<T>()
.
Or the client could be modified to allow passing in all of the collection names and corresponding types + allow us to override the generics when calling the methods.
In my case to get types I do the following, very easy...
export interface CategorieProps {
id: string;
name: string;
}
export interface SectionProps {
header: string;
items: CategorieProps[];
}
Example doing a request....
const records = (await client.records.getFullList('sections', 200, {
sort: 'created',
expand: 'sections_categorie',
})) as unknown as CategorieProps[];
This will be a game changer for me
The same issue, I resolved to copy and exporting the types in another file , needed it to provide generics to a react-query custom hook
the custom hook
import { useQuery, UseQueryOptions } from "@tanstack/react-query"
import { client } from "../../../pocketbase/config";
import {Record} from "pocketbase";
import { ListResult } from "../types/pb-types";
interface T {
key: string[];
filter?: string;
expand?: string;
rqOptions?: UseQueryOptions<ListResult<Record>,unknown,any,string[]>;
}
export const useCollection =({key,filter="",expand="",rqOptions={}}:T)=>{
const fetcherFunction = async () => {
return await client.records.getList(
key[0],
1,
50,
{
filter: `${filter}`,
expand:expand,
}
);
};
return useQuery< ListResult<Record>,unknown,ListResult<Record>,string[]>(key, fetcherFunction,rqOptions);
}
the external file with types
export declare abstract class BaseModel {
id: string;
created: string;
updated: string;
constructor(data?: { [key: string]: any });
/**
* Loads `data` into the current model.
*/
load(data: { [key: string]: any }): void;
/**
* Returns whether the current loaded data represent a stored db record.
*/
get isNew(): boolean;
/**
* Robust deep clone of a model.
*/
clone(): BaseModel;
/**
* Exports all model properties as a new plain object.
*/
export(): {
[key: string]: any;
};
}
export declare class ListResult<M extends BaseModel> {
page: number;
perPage: number;
totalItems: number;
totalPages: number;
items: Array<M>;
constructor(
page: number,
perPage: number,
totalItems: number,
totalPages: number,
items: Array<M>
);
}
and used it like this
import React from 'react'
import { useParams } from 'react-router-dom';
import { useCollection } from '../Shared/hooks/useCollection';
import { useLocation } from 'react-router-dom';
import { User,Admin,Record} from 'pocketbase';
interface TenantProps {
user: User | Admin | null
}
export const Tenant: React.FC<TenantProps> = ({}) => {
const params = useParams();
const location = useLocation()
const tenantsQuery = useCollection({ key: ["tenants"],rqOptions:{
select: (data) => {
return data.items.filter((item) => item.id === params.tenantId)
}
} });
console.log(" params === ", tenantsQuery?.data)
return (
<div className='w-full min-h-full flex-center-col'>
{params.tenantId}
</div>
);
}
I don't know if this is bad practice but it solves my problem
Generics would be ideal for me. Are they planned?
@profispojka Yes, actually I've pushed a v0.8.0-rc1 pre-release earlier today and you can give it a try - https://github.com/pocketbase/js-sdk/releases/tag/v0.8.0-rc1.
Please note that this works only with PocketBase v0.8+ APIs.
Since it is still a pre-release, to install the SDK you have to use the next
tag:
npm install pocketbase@next --save
Documentation on the new SDK methods could be found in the temporary rc
branch readme - https://github.com/pocketbase/js-sdk/blob/rc/README.md.