Skip to content

Instantly share code, notes, and snippets.

@lightningspirit
Created June 30, 2024 11:52
Show Gist options
  • Save lightningspirit/ba3a3997e4ce64e7ce9df358cff958f9 to your computer and use it in GitHub Desktop.
Save lightningspirit/ba3a3997e4ce64e7ce9df358cff958f9 to your computer and use it in GitHub Desktop.
Testing PocketBase typing system in JS SDK
import PocketBase, {
BaseAuthStore,
ListResult,
LogService,
RecordFullListOptions,
RecordListOptions,
RecordModel,
RecordOptions,
RecordService,
} from 'pocketbase'
import { PageProps } from '../../../.next/types/app/(app)/(with-white-header)/page'
type Split<
S extends string,
D extends string,
> = S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] : [S]
type Trim<S extends string> = S extends ` ${infer T}`
? Trim<T>
: S extends `${infer T} `
? Trim<T>
: S
type NonEmptyArray<T> = T extends [infer F, ...infer R] ? T : undefined
type JoinUnion<T> = T extends [infer F, ...infer R]
? F extends string
? `${Trim<F>}` | JoinUnion<R>
: undefined
: never
type SplitStringToUnion<
S extends string | undefined,
D extends string,
> = S extends ''
? undefined
: S extends string
? NonEmptyArray<Split<S, D>> extends infer T
? JoinUnion<T>
: undefined
: undefined
type Test1 = SplitStringToUnion<'id, mimetype, updated', ','> // 'id' | 'mimetype' | 'updated'
type Test2 = SplitStringToUnion<'', ','> // undefined
type Test3 = SplitStringToUnion<undefined, ','> // undefined
type Test4 = SplitStringToUnion<'singleValue', ','>
declare function splitStringToUnion<
I extends string | undefined = undefined,
O extends I extends string
? SplitStringToUnion<I, ','>
: undefined = I extends string ? SplitStringToUnion<I, ','> : undefined,
>(i?: I): O
const withString = splitStringToUnion('id, mimetype, updated')
const withEmptyFields = splitStringToUnion('')
const withUndefined = splitStringToUnion(undefined)
const noOption1 = splitStringToUnion()
type ExtractFields<O extends RecordListOptions | undefined> = O extends {
fields: infer F
}
? F extends string
? SplitStringToUnion<F, ','>
: undefined
: undefined
// type ExtractFields<O extends { fields?: string }> = O extends {
// fields: infer F
// }
// ? F extends string
// ? Trim<F> extends `${infer First}, ${infer Rest}`
// ? Trim<First> | ExtractFields<{ fields: Trim<Rest> }>
// : Trim<F>
// : never
// : never
// type ExtractFields<
// O extends { [s: string]: any } | undefined = undefined,
// K extends keyof O | undefined = undefined,
// > = K extends keyof O
// ? O extends { [s: string]: any }
// ? SplitStringToUnion<O[K], ','>
// : undefined
// : undefined
type opt1 = ExtractFields<{
fields: 'id, mimetype, updated'
}>
type opt2 = ExtractFields<{}>
type opt3 = ExtractFields<undefined>
// function extractFields<
// O extends Options,
// R = O extends { fields: infer F }
// ? F extends string
// ? SplitStringToUnion<F, ','>
// : undefined
// : undefined,
// >(o?: O): R
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I,
) => void
? I
: never
type MergePicks<U> =
UnionToIntersection<U> extends infer O ? { [K in keyof O]: O[K] } : never
type IntersectWithFields<T, F> = F extends keyof T ? Pick<T, F> : never
type Test = MergePicks<
IntersectWithFields<Page, ExtractFields<{ fields: 'id, title, content' }>>
>
type Test12 = IntersectWithFields<
Page,
ExtractFields<{ fields: 'id, title, content' }>
>
type TestIntersect = IntersectWithFields<Page, 'title' | 'slug'> // should be { title: string; slug: string; }
const p = {} as Page
declare function intersectWithFields<
T extends Record<string, any>,
F extends { fields: string },
>(obj: T, fields: F): MergePicks<IntersectWithFields<T, ExtractFields<F>>>
const foo = intersectWithFields<
Page,
{ fields: 'id, title, content, language' }
>(p, { fields: 'id, title, content, language' })
type RecordMapGeneric = {
[s: string]: Partial<RecordModel>
}
type Mapper<
RMap extends RecordMapGeneric,
MMap extends {
id?: string
created?: Date
updated?: Date
expand?: any
} & {
[K in keyof RMap]: any
},
> = {
[K in keyof RMap]: (record: RMap[K]) => MMap[K]
}
export class TypedRecordService<
RMap extends RecordMapGeneric,
MMap extends { [K in keyof RMap]: any },
K extends keyof RMap,
> extends RecordService<MMap[K]> {
private mapper: Mapper<RMap, MMap>
private idOrName: K
constructor(
client: PocketBase,
collectionIdOrName: K,
mapper: Mapper<RMap, MMap>,
) {
super(client, collectionIdOrName.toString())
this.idOrName = collectionIdOrName
this.mapper = mapper
}
getList<
O extends RecordListOptions | undefined,
F extends string | undefined = O extends { fields: infer T }
? T extends string
? SplitStringToUnion<T, ','>
: undefined
: undefined,
T extends object = F extends keyof MMap[K] ? Pick<MMap[K], F> : MMap[K],
// T extends O extends { fields: infer F }
// ? F extends string
// ? IntersectWithFields<MMap[K], SplitStringToUnion<F, ','>>
// : MMap[K]
// : MMap[K],
>(page?: number, perPage?: number, options?: O): Promise<ListResult<T>> {
return super.getList(page, perPage, options)
}
}
// TESTS
interface RecordPage extends RecordModel {
title: string
slug: string
content: string
language: string
show_in_menu: 'copyright' | 'footer'
priority: number
public: boolean
}
type RecordMap = {
pages: RecordPage
}
export type ModelMap = {
pages: Page
}
const typedRecordService = new TypedRecordService<RecordMap, ModelMap, 'pages'>(
new PocketBase(),
'pages',
{} as any,
)
typedRecordService
.getList(1, 50, {
fields: 'title',
})
.then((result) => {
console.log(result.items.at(0)?.title)
})
typedRecordService
.getList(1, 50, {
// no fields
})
.then((result) => {
console.log(result.items.at(0)?.title)
})
typedRecordService.getList(1, 50).then((result) => {
console.log(result.items.at(0)?.title)
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment