From 801f98bfc2b7bede596826e9ce9a79f6a801e94a Mon Sep 17 00:00:00 2001 From: "Leonardo F.C" Date: Fri, 15 Dec 2023 21:05:25 -0300 Subject: [PATCH 1/8] fix(codegen): relations indentation --- src/codegen/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codegen/index.ts b/src/codegen/index.ts index b9b3494..97b36c4 100644 --- a/src/codegen/index.ts +++ b/src/codegen/index.ts @@ -192,7 +192,7 @@ export type ${t.typeName}Collection = { : `{ ${t.relations .map((col) => `${col.name}: ${col.target};`) - .join('\n' + ' '.repeat(8))} + .join('\n' + indent.repeat(2))} }` }; }; From 70747d56888fffd6e901e146c00dc0eab38afccc Mon Sep 17 00:00:00 2001 From: "Leonardo F.C" Date: Fri, 15 Dec 2023 21:06:37 -0300 Subject: [PATCH 2/8] feat(codegen): add collectionId and collectionName on record response --- src/codegen/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/codegen/index.ts b/src/codegen/index.ts index 97b36c4..4a811ac 100644 --- a/src/codegen/index.ts +++ b/src/codegen/index.ts @@ -154,6 +154,8 @@ ${tables export type ${t.typeName}Response = { ${t.columns.response.join('\n' + indent)} + collectionId: '${t.id}'; + collectionName: '${t.name}'; } & ${ t.type === 'base' ? 'BaseCollectionRecord' From 28a921f3ea018b7ddc92f1e73e864d5e2a526df7 Mon Sep 17 00:00:00 2001 From: "Leonardo F.C" Date: Fri, 15 Dec 2023 21:10:29 -0300 Subject: [PATCH 3/8] feat(codegen): add base & auth fields on create & update --- src/codegen/index.ts | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/codegen/index.ts b/src/codegen/index.ts index 4a811ac..8169a73 100644 --- a/src/codegen/index.ts +++ b/src/codegen/index.ts @@ -127,6 +127,11 @@ type BaseCollectionRecord = { updated: string; }; +// https://pocketbase.io/docs/api-records/#create-record +type BaseCollectionRecordCreate = { + id?: string; +}; + // https://pocketbase.io/docs/collections/#auth-collection type AuthCollectionRecord = { id: string; @@ -138,6 +143,27 @@ type AuthCollectionRecord = { verified: boolean; }; +// https://pocketbase.io/docs/api-records/#create-record +type AuthCollectionRecordCreate = { + id?: string; + username?: string; + email?: string; + emailVisibility?: boolean; + verified?: boolean; + password: string; + passwordConfirm: string; +}; + +// https://pocketbase.io/docs/api-records/#update-record +type AuthCollectionRecordUpdate = { + username?: string; + email?: string; + emailVisibility?: boolean; + verified?: boolean; + password?: string; + passwordConfirm?: string; +}; + // https://pocketbase.io/docs/collections/#view-collection type ViewCollectionRecord = { id: string; @@ -170,11 +196,11 @@ ${ : ` export type ${t.typeName}Create = { ${t.columns.create.join('\n' + indent)} -}; +} & ${t.type === "base" ? "BaseCollectionRecordCreate" : "AuthCollectionRecordCreate"}; export type ${t.typeName}Update = { ${t.columns.update.join('\n' + indent)} -}; +}${t.type === "base" ? "" : " & AuthCollectionRecordUpdate" }; ` } export type ${t.typeName}Collection = { From 6e22f1dcf1205e8e019d4f53f6639e0cabc32f64 Mon Sep 17 00:00:00 2001 From: "Leonardo F.C" Date: Fri, 15 Dec 2023 22:22:13 -0300 Subject: [PATCH 4/8] feat(codegen): response fields can't be undefined --- src/codegen/index.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/codegen/index.ts b/src/codegen/index.ts index 8169a73..3fdde98 100644 --- a/src/codegen/index.ts +++ b/src/codegen/index.ts @@ -245,7 +245,7 @@ function getFieldType(field: Field, { response, create, update }: Columns) { const req = field.required ? '' : '?'; const addResponse = (type: string, name = field.name) => - response.push(`${name}${req}: ${type};`); + response.push(`${name}: ${type};`); const addCreate = (type: string, name = field.name) => create.push(`${name}${req}: ${type};`); const addUpdate = (type: string, name = field.name) => @@ -287,10 +287,13 @@ function getFieldType(field: Field, { response, create, update }: Columns) { break; } case 'select': { - const singleType = field.options.values + const single = field.options.maxSelect === 1; + const values = !field.required && single + ? ["", ...field.options.values] + : field.options.values; + const singleType = values .map((v) => `'${v}'`) .join(' | '); - const single = field.options.maxSelect === 1; const type = single ? `${singleType}` : `MaybeArray<${singleType}>`; addResponse(single ? singleType : `Array<${singleType}>`); From 2fc2efa074c6936f4d1d9a41c8b71ccbcff5e413 Mon Sep 17 00:00:00 2001 From: "Leonardo F.C" Date: Fri, 15 Dec 2023 22:23:59 -0300 Subject: [PATCH 5/8] feat: add requestKey on TypedBaseQueryParams --- src/record-service.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/record-service.ts b/src/record-service.ts index 7bc433f..229393f 100644 --- a/src/record-service.ts +++ b/src/record-service.ts @@ -114,7 +114,14 @@ export interface TypedBaseQueryParams< S extends Fields > { fields?: FieldsParam, S>; + requestKey?: string | null; + /** + * @deprecated use `requestKey:null` instead + */ $autoCancel?: boolean; + /** + * @deprecated use `requestKey:string` instead + */ $cancelKey?: string; } From e302f20f5194bba4d7d816fc34044de75943d599 Mon Sep 17 00:00:00 2001 From: "Leonardo F.C" Date: Fri, 15 Dec 2023 22:26:15 -0300 Subject: [PATCH 6/8] feat: update RecordModal --- src/types.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/types.ts b/src/types.ts index 814f215..bbb374e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,3 @@ -import { RecordModel as PocketBaseRecord } from 'pocketbase'; - export type Simplify = T extends infer o ? { [K in keyof o]: o[K] } : never; export type ArrayInnerType = T extends Array ? V : T; export type Values = T[keyof T]; @@ -44,17 +42,9 @@ export interface GenericSchema { export type TypedRecord< Data extends BaseRecord, Expand extends GenericExpand = {} -> = Pick & - Data & { - load(data: Data): void; - $load(data: Data): void; - clone(): TypedRecord; - $clone(): TypedRecord; - export(): Data; - $export(): Data; - - expand: Expand; - }; +> = Data & { + expand: Expand; +}; export interface SystemFields { id: string; From a5226caed6ae22c34c73d8ac0237ec782dbf7f36 Mon Sep 17 00:00:00 2001 From: "Leonardo F.C" Date: Sun, 17 Dec 2023 23:26:49 -0300 Subject: [PATCH 7/8] Merge branch 'support-pocketbase-v0.20' into support-pocketbase-v0.20 --- .changeset/lovely-dancers-clap.md | 5 + .changeset/pre.json | 10 + CHANGELOG.md | 36 ++-- README.md | 227 +++++++++++-------- example/Database.d.ts | 158 ++++++++++---- example/collections.json | 347 ++++++++++++++++++++++++++++++ example/index.ts | 65 ++++-- package.json | 2 +- src/codegen/index.ts | 124 +++++------ src/expand.ts | 63 ------ src/fields.ts | 12 -- src/filter.ts | 2 +- src/index.ts | 20 +- src/queryParams.ts | 186 ++++++++++++++++ src/record-service.ts | 214 ++++++------------ src/sort.ts | 6 +- src/types.ts | 33 +-- 17 files changed, 1036 insertions(+), 474 deletions(-) create mode 100644 .changeset/lovely-dancers-clap.md create mode 100644 .changeset/pre.json create mode 100644 example/collections.json delete mode 100644 src/expand.ts delete mode 100644 src/fields.ts create mode 100644 src/queryParams.ts diff --git a/.changeset/lovely-dancers-clap.md b/.changeset/lovely-dancers-clap.md new file mode 100644 index 0000000..0682c4a --- /dev/null +++ b/.changeset/lovely-dancers-clap.md @@ -0,0 +1,5 @@ +--- +"typed-pocketbase": patch +--- + +test version diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 0000000..aa37428 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,10 @@ +{ + "mode": "pre", + "tag": "next", + "initialVersions": { + "typed-pocketbase": "0.0.8" + }, + "changesets": [ + "lovely-dancers-clap" + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index cc69e95..2dace2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,71 +1,77 @@ # typed-pocketbase +## 0.0.9-next.0 + +### Patch Changes + +- 2530dd0: test version + ## 0.0.8 ### Patch Changes -- bbadb7c: Fix: Filter with empty and()/or() results in Invalid filter parameters. +- bbadb7c: Fix: Filter with empty and()/or() results in Invalid filter parameters. ## 0.0.7 ### Patch Changes -- 3c875d8: add shebang +- 3c875d8: add shebang ## 0.0.6 ### Patch Changes -- 4b59926: fix multiple field type +- 4b59926: fix multiple field type ## 0.0.5 ### Patch Changes -- 19bce54: fix multiple relation type generation +- 19bce54: fix multiple relation type generation ## 0.0.4 ### Patch Changes -- 7aa71f3: support json fields and Date and URL objects -- 0a96bf9: allow Date type for date fields +- 7aa71f3: support json fields and Date and URL objects +- 0a96bf9: allow Date type for date fields ## 0.0.3 ### Patch Changes -- dd52f65: rework codegen cli and support all field types and modifiers -- 4201a28: export TypedRecord -- 5b60b0c: fix entrypoints and file thumbs type when none are specified -- 376dc88: Add expand and nested filter support +- dd52f65: rework codegen cli and support all field types and modifiers +- 4201a28: export TypedRecord +- 5b60b0c: fix entrypoints and file thumbs type when none are specified +- 376dc88: Add expand and nested filter support ## 0.0.3-next.3 ### Patch Changes -- rework codegen cli and support all field types and modifiers +- rework codegen cli and support all field types and modifiers ## 0.0.3-next.2 ### Patch Changes -- export TypedRecord +- export TypedRecord ## 0.0.3-next.1 ### Patch Changes -- fix entrypoints and file thumbs type when none are specified +- fix entrypoints and file thumbs type when none are specified ## 0.0.3-next.0 ### Patch Changes -- Add expand and nested filter support +- Add expand and nested filter support ## 0.0.2 ### Patch Changes -- 957d47a: fix export type issue +- 957d47a: fix export type issue diff --git a/README.md b/README.md index 243c84b..52d560a 100644 --- a/README.md +++ b/README.md @@ -42,13 +42,17 @@ const db = new PocketBase('http://localhost:8090') as TypedPocketBase; Enjoy full type-safety: ```ts -import { neq, sort, fields } from 'typed-pocketbase'; - -db.collection('posts').getList(1, 10, { - fields: fields('id', 'title', 'content'), - sort: sort('-date'), +import { createOptions, neq, sort } from 'typed-pocketbase'; + +db.collection('posts').getList(1, 10, createOptions({ + select: { + id: true, + title: true, + content: true + } + sort: '-date', filter: neq('content', '') -}); +})); ``` Supported methods @@ -63,22 +67,18 @@ Supported methods ## Selecting fields -Use the `fields` function to select the properties: - -**Note:** Don´t use `expand` when selecting fields - ```ts -import { fields } from 'typed-pocketbase'; - -db.collection('posts').getFullList({ - fields: fields('id', 'title', 'content') -}); - -// conditionally select fields -// falsy values are excluded -db.collection('posts').getFullList({ - fields: fields(shouldIncludeId && 'id', 'title', 'content') -}); +import { createOptions } from 'typed-pocketbase'; + +db.collection('posts').getFullList( + createOptions({ + select: { + id: showId, + title: true, + content: true + } + }) +); ``` ## Filtering columns @@ -86,48 +86,60 @@ db.collection('posts').getFullList({ Use the `and`, `or` and other utility functions to filter rows: ```ts -import { and, or, eq } from 'typed-pocketbase'; +import { createOptions, and, or, eq } from 'typed-pocketbase'; // get all posts created in 2022 -db.collection('posts').getFullList({ - // a "manual" filter is a tuple of length 3 - filter: and(['date', '<', '2023-01-01'], ['data', '>=', '2022-01-01']) -}); +db.collection('posts').getFullList( + createOptions({ + // a "manual" filter is a tuple of length 3 + filter: and(['date', '<', '2023-01-01'], ['data', '>=', '2022-01-01']) + }) +); // get all posts expect for those created in 2022 -db.collection('posts').getFullList({ - filter: or(['date', '>=', '2023-01-01'], ['data', '<', '2022-01-01']) -}); +db.collection('posts').getFullList( + createOptions({ + filter: or(['date', '>=', '2023-01-01'], ['data', '<', '2022-01-01']) + }) +); // get all posts that were create at '2023-01-01' -db.collection('posts').getFullList({ - filter: eq('date', '2023-01-01') -}); +db.collection('posts').getFullList( + createOptions({ + filter: eq('date', '2023-01-01') + }) +); // combine or/and with helpers and manual filters -db.collection('posts').getFullList({ - filter: or( - // - ['date', '>=', '2023-01-01'], - lt('date', '2022-01-01') - ) -}); +db.collection('posts').getFullList( + createOptions({ + filter: or( + // + ['date', '>=', '2023-01-01'], + lt('date', '2022-01-01') + ) + }) +); // conditionally filter rows // falsy values are excluded -db.collection('posts').getFullList({ - filter: and( - // - gte('date', '2022-01-01'), - !untilNow && lt('date', '2023-01-01') - ) -}); +db.collection('posts').getFullList( + createOptions({ + filter: and( + // + gte('date', '2022-01-01'), + !untilNow && lt('date', '2023-01-01') + ) + }) +); // filter for columns in relations // works up to 6 levels deep, including the top level -db.collection('posts').getFullList({ - filter: eq('owner.name', 'me') -}); +db.collection('posts').getFullList( + createOptions({ + filter: eq('owner.name', 'me') + }) +); ``` Most filter operators are available as short hand function. @@ -139,63 +151,106 @@ Visit the [pocketbase documentation](https://pocketbase.io/docs/api-records/) to Use `sort`, `asc` and `desc` to sort the rows: ```ts -import { sort, asc, desc } from 'typed-pocketbase'; +import { createOptions, sort, asc, desc } from 'typed-pocketbase'; -db.collection('posts').getFullList({ - // sort by descending 'date' - sort: desc('date') -}); +db.collection('posts').getFullList( + createOptions({ + // sort by descending 'date' + sort: desc('date') + }) +); -db.collection('posts').getFullList({ - // sort by descending 'date' and ascending 'title' - sort: sort('-date', '+title') -}); +db.collection('posts').getFullList( + createOptions({ + // sort by descending 'date' and ascending 'title' + sort: sort('-date', '+title') + }) +); -db.collection('posts').getFullList({ - // sort by descending 'date' and ascending 'title' - sort: sort(desc('date'), asc('title')) -}); +db.collection('posts').getFullList( + createOptions({ + // sort by descending 'date' and ascending 'title' + sort: sort(desc('date'), asc('title')) + }) +); // you can mix functions with +/- prefixes -db.collection('posts').getFullList({ - // sort by descending 'date' and ascending 'title' - sort: sort(desc('date'), '+title') -}); +db.collection('posts').getFullList( + createOptions({ + // sort by descending 'date' and ascending 'title' + sort: sort(desc('date'), '+title') + }) +); // conditionally sort rows // falsy values are excluded -db.collection('posts').getFullList({ - sort: sort( - // - desc('date'), - sortTitle && asc('title') - ) -}); +db.collection('posts').getFullList( + createOptions({ + sort: sort( + // + desc('date'), + sortTitle && asc('title') + ) + }) +); ``` ## Expanding -Use the `expand` function to expand relations: - -**Note:** Don´t use `fields` when expanding as fields only works for the top level and `expand` would end up as an empty object +The `createOptions` function automatically expands your models: ```ts -import { expand } from 'typed-pocketbase'; +import { createOptions } from 'typed-pocketbase'; -db.collection('posts').getFullList({ - expand: expand({ - user: true +db.collection('posts').getFullList( + createOptions({ + $expand: { + user: true + } + }) +); + +// select nested columns +db.collection('posts').getFullList( + createOptions({ + $expand: { + user: { + name: true + avatar: true + } + } }) -}); +); // nested expand -db.collection('posts').getFullList({ - expand: expand({ - user: { - profile: true +db.collection('posts').getFullList( + createOptions({ + $expand: { + user: { + $expand: { + profile: true + } + } + } + }) +); +``` + +[Back relation expanding](https://pocketbase.io/docs/working-with-relations/#back-relation-expand) is support aswell: + +```ts +import { createOptions } from 'typed-pocketbase'; + +db.collection('user').getFullList( + createOptions({ + $expand: { + 'posts(user)': { + title: true, + created: true + } } }) -}); +); ``` ## License diff --git a/example/Database.d.ts b/example/Database.d.ts index 1938cec..c2e01a6 100644 --- a/example/Database.d.ts +++ b/example/Database.d.ts @@ -3,27 +3,26 @@ */ // https://pocketbase.io/docs/collections/#base-collection -type BaseCollectionRecord = { +interface BaseCollectionRecord { id: string; created: string; updated: string; -}; + collectionId: string; + collectionName: string; +} // https://pocketbase.io/docs/collections/#auth-collection -type AuthCollectionRecord = { - id: string; - created: string; - updated: string; +interface AuthCollectionRecord extends BaseCollectionRecord { username: string; email: string; emailVisibility: boolean; verified: boolean; -}; +} // https://pocketbase.io/docs/collections/#view-collection -type ViewCollectionRecord = { +interface ViewCollectionRecord { id: string; -}; +} // utilities @@ -31,24 +30,25 @@ type MaybeArray = T | T[]; // ===== users ===== -export type UsersResponse = { +export interface UsersResponse extends AuthCollectionRecord { + collectionName: 'users'; name?: string; avatar?: string; -} & AuthCollectionRecord; +} -export type UsersCreate = { +export interface UsersCreate { name?: string; avatar?: string; -}; +} -export type UsersUpdate = { +export interface UsersUpdate { name?: string; avatar?: string; -}; +} -export type UsersCollection = { +export interface UsersCollection { type: 'auth'; - collectionId: '_pb_users_auth_'; + collectionId: string; collectionName: 'users'; response: UsersResponse; create: UsersCreate; @@ -56,43 +56,41 @@ export type UsersCollection = { relations: { 'posts(owner)': PostsCollection[]; }; -}; +} // ===== posts ===== -export type PostsResponse = { +export interface PostsResponse extends BaseCollectionRecord { + collectionName: 'posts'; title: string; - slug: string; - date?: string; content?: string; published?: boolean; owner?: string; - metadata?: any; -} & BaseCollectionRecord; + slug: string; + date?: string; +} -export type PostsCreate = { +export interface PostsCreate { title: string; - slug: string; - date?: string | Date; content?: string; published?: boolean; owner?: string; - metadata?: any; -}; + slug: string; + date?: string | Date; +} -export type PostsUpdate = { +export interface PostsUpdate { title?: string; - slug?: string; - date?: string | Date; content?: string; published?: boolean; owner?: string; - metadata?: any; -}; + slug?: string; + date?: string | Date; +} -export type PostsCollection = { +export interface PostsCollection { type: 'base'; - collectionId: 'sbrth2mzfnqba9e'; + collectionId: string; collectionName: 'posts'; response: PostsResponse; create: PostsCreate; @@ -100,11 +98,99 @@ export type PostsCollection = { relations: { owner: UsersCollection; }; -}; +} + +// ===== test ===== + +export interface TestResponse extends BaseCollectionRecord { + collectionName: 'test'; + test?: string; + editor?: string; + number?: number; + bool?: boolean; + email?: string; + url?: string; + date?: string; + select?: 'a' | 'b' | 'c' | 'd'; + file?: string; + relation?: string; + json?: any; +} + +export interface TestCreate { + test?: string; + editor?: string; + number?: number; + bool?: boolean; + email?: string; + url?: string | URL; + date?: string | Date; + select?: 'a' | 'b' | 'c' | 'd'; + file?: string; + relation?: string; + json?: any; +} + +export interface TestUpdate { + test?: string; + editor?: string; + number?: number; + 'number+'?: number; + 'number-'?: number; + bool?: boolean; + email?: string; + url?: string | URL; + date?: string | Date; + select?: 'a' | 'b' | 'c' | 'd'; + file?: string; + relation?: string; + json?: any; +} + +export interface TestCollection { + type: 'base'; + collectionId: string; + collectionName: 'test'; + response: TestResponse; + create: TestCreate; + update: TestUpdate; + relations: { + relation: Test2Collection; + }; +} + +// ===== test2 ===== + +export interface Test2Response extends BaseCollectionRecord { + collectionName: 'test2'; + test?: string; +} + +export interface Test2Create { + test?: string; +} + +export interface Test2Update { + test?: string; +} + +export interface Test2Collection { + type: 'base'; + collectionId: string; + collectionName: 'test2'; + response: Test2Response; + create: Test2Create; + update: Test2Update; + relations: { + 'test(relation)': TestCollection; + }; +} // ===== Schema ===== export type Schema = { users: UsersCollection; posts: PostsCollection; + test: TestCollection; + test2: Test2Collection; }; diff --git a/example/collections.json b/example/collections.json new file mode 100644 index 0000000..ed21d92 --- /dev/null +++ b/example/collections.json @@ -0,0 +1,347 @@ +[ + { + "id": "_pb_users_auth_", + "name": "users", + "type": "auth", + "system": false, + "schema": [ + { + "system": false, + "id": "users_name", + "name": "name", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "users_avatar", + "name": "avatar", + "type": "file", + "required": false, + "presentable": false, + "unique": false, + "options": { + "mimeTypes": [ + "image/jpeg", + "image/png", + "image/svg+xml", + "image/gif", + "image/webp" + ], + "thumbs": null, + "maxSelect": 1, + "maxSize": 5242880, + "protected": false + } + } + ], + "indexes": [], + "listRule": "id = @request.auth.id", + "viewRule": "id = @request.auth.id", + "createRule": "", + "updateRule": "id = @request.auth.id", + "deleteRule": "id = @request.auth.id", + "options": { + "allowEmailAuth": true, + "allowOAuth2Auth": true, + "allowUsernameAuth": true, + "exceptEmailDomains": null, + "manageRule": null, + "minPasswordLength": 8, + "onlyEmailDomains": null, + "onlyVerified": false, + "requireEmail": false + } + }, + { + "id": "33ls010vik702sx", + "name": "test2", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "8ddvq0tw", + "name": "test", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + } + ], + "indexes": [], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }, + { + "id": "83fhlsv1gon0cmg", + "name": "test", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "zfuw6sof", + "name": "test", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "qgv35ajo", + "name": "editor", + "type": "editor", + "required": false, + "presentable": false, + "unique": false, + "options": { + "convertUrls": false + } + }, + { + "system": false, + "id": "1e5y2axc", + "name": "number", + "type": "number", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + }, + { + "system": false, + "id": "hptn8exo", + "name": "bool", + "type": "bool", + "required": false, + "presentable": false, + "unique": false, + "options": {} + }, + { + "system": false, + "id": "pb6ndpxg", + "name": "email", + "type": "email", + "required": false, + "presentable": false, + "unique": false, + "options": { + "exceptDomains": null, + "onlyDomains": null + } + }, + { + "system": false, + "id": "x2titrzc", + "name": "url", + "type": "url", + "required": false, + "presentable": false, + "unique": false, + "options": { + "exceptDomains": null, + "onlyDomains": null + } + }, + { + "system": false, + "id": "qyvrrpgf", + "name": "date", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + }, + { + "system": false, + "id": "bmsnitya", + "name": "select", + "type": "select", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": ["a", "b", "c", "d"] + } + }, + { + "system": false, + "id": "gokg1h4f", + "name": "file", + "type": "file", + "required": false, + "presentable": false, + "unique": false, + "options": { + "mimeTypes": [], + "thumbs": [], + "maxSelect": 1, + "maxSize": 5242880, + "protected": false + } + }, + { + "system": false, + "id": "kswhkufu", + "name": "relation", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "33ls010vik702sx", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "system": false, + "id": "qe9d8ctq", + "name": "json", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 2000000 + } + } + ], + "indexes": ["CREATE UNIQUE INDEX `idx_iTJ39NK` ON `test` (`relation`)"], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }, + { + "id": "vs0t2jp78fg1hbw", + "name": "posts", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "iayzkqkp", + "name": "title", + "type": "text", + "required": true, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "system": false, + "id": "57vqfjrw", + "name": "content", + "type": "editor", + "required": false, + "presentable": false, + "unique": false, + "options": { + "convertUrls": false + } + }, + { + "system": false, + "id": "bv9hckq2", + "name": "published", + "type": "bool", + "required": false, + "presentable": false, + "unique": false, + "options": {} + }, + { + "system": false, + "id": "jf1xoxbn", + "name": "owner", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "_pb_users_auth_", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "system": false, + "id": "av9mjr2g", + "name": "slug", + "type": "text", + "required": true, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" + } + }, + { + "system": false, + "id": "gzxmjrwt", + "name": "date", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + } + ], + "indexes": ["CREATE UNIQUE INDEX `idx_FWAp4S2` ON `posts` (`slug`)"], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + } +] diff --git a/example/index.ts b/example/index.ts index a5f6c49..3280d43 100644 --- a/example/index.ts +++ b/example/index.ts @@ -1,40 +1,63 @@ import PocketBase from 'pocketbase'; import { Schema } from './Database.js'; -import { TypedPocketBase, fields, expand, eq, asc } from '../src/index.js'; +import { TypedPocketBase, eq, asc } from '../src/index.js'; +import { createOptions } from '../src/queryParams.js'; const db = new PocketBase('http://localhost:8090') as TypedPocketBase; -await db.admins.authWithPassword('test@test.com', 'secretpassword'); +await db.admins.authWithPassword('admin@example.com', 'secretpassword'); { - const posts = await db.collection('posts').getFullList({ - fields: fields('id', 'title', 'slug', 'content'), - sort: asc('date'), - filter: eq('published', true) - }); + const posts = await db.collection('posts').getFullList( + createOptions({ + select: { + id: true, + title: true, + slug: true, + content: true, + $expand: { + owner: { + $expand: { + 'posts(owner)': { + owner: true + } + } + } + } + }, + sort: '+date', + filter: eq('published', true) + }) + ); console.log(posts); } { - const posts = await db.collection('posts').getFullList({ - sort: asc('date'), - filter: eq('owner.email', 'user@test.com'), - expand: expand({ - owner: true + const posts = await db.collection('posts').getFullList( + createOptions({ + select: { + $expand: { + owner: true + } + }, + sort: asc('date'), + filter: eq('owner.email', 'user@test.com') }) - }); + ); console.log(posts[0].expand.owner); } { - const post = await db - .collection('posts') - .getFirstListItem(eq('owner.email', 'user@test.com'), { - expand: expand({ - owner: true - }) - }); - + const post = await db.collection('posts').getFirstListItem( + eq('owner.email', 'user@test.com'), + createOptions({ + select: { + $expand: { + owner: true + } + } + }) + ); console.log(post); } diff --git a/package.json b/package.json index 561a129..df607e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "typed-pocketbase", - "version": "0.0.8", + "version": "0.0.9-next.0", "description": "Add types to the PocketBase JavaScript SDK", "author": "David Plugge", "repository": { diff --git a/src/codegen/index.ts b/src/codegen/index.ts index 3fdde98..cca1734 100644 --- a/src/codegen/index.ts +++ b/src/codegen/index.ts @@ -18,33 +18,15 @@ interface Relation { target: string; } -generateTypes({ - url: 'http://localhost:8090', - email: 'admin@example.com', - password: 'secretpassword' -}); - export async function generateTypes({ url, email, password }: GenerateOptions) { const pb = new PocketBase(url); await pb.admins.authWithPassword(email, password); const collections: Collection[] = await pb.collections.getFullList(); - - // collections.forEach((c) => { - // console.log(c.schema); - // }); - - const data = await pb.collection('test').getOne('ctc37gu21lv4is8', { - expand: 'hello(field)' - }); - console.log(JSON.stringify(data, null, 2)); - - return ''; - const deferred: Array<() => void> = []; - const tables = collections.map((c) => { - const typeName = pascalCase(c.name); + const tables = collections.map((collection) => { + const typeName = pascalCase(collection.name); const columns: Columns = { create: [], @@ -53,19 +35,16 @@ export async function generateTypes({ url, email, password }: GenerateOptions) { }; const relations: Relation[] = []; - c.schema.forEach((field) => { + collection.schema.forEach((field) => { getFieldType(field, columns); if (field.type === 'relation') { deferred.push(() => { const target = tableMap.get(field.options.collectionId); - const targetCollection = collectionMap.get( - field.options.collectionId - ); - if (!target || !targetCollection) + if (!target) throw new Error( - `Collection ${field.options.collectionId} not found for relation ${c.name}.${field.name}` + `Collection ${field.options.collectionId} not found for relation ${collection.name}.${field.name}` ); relations.push({ @@ -80,7 +59,7 @@ export async function generateTypes({ url, email, password }: GenerateOptions) { * @see https://pocketbase.io/docs/expanding-relations/#indirect-expand */ - const indicies = targetCollection.indexes.map(parseIndex); + const indicies = collection.indexes.map(parseIndex); const isUnique = indicies.some( (i) => @@ -91,7 +70,7 @@ export async function generateTypes({ url, email, password }: GenerateOptions) { ); target.relations.push({ - name: `'${c.name}(${field.name})'`, + name: `'${collection.name}(${field.name})'`, target: `${typeName}Collection${isUnique ? '' : '[]'}` }); }); @@ -99,9 +78,9 @@ export async function generateTypes({ url, email, password }: GenerateOptions) { }); return { - id: c.id, - name: c.name, - type: c.type, + id: collection.id, + name: collection.name, + type: collection.type, typeName, columns, relations @@ -109,7 +88,6 @@ export async function generateTypes({ url, email, password }: GenerateOptions) { }); const tableMap = new Map(tables.map((t) => [t.id, t])); - const collectionMap = new Map(collections.map((c) => [c.id, c])); deferred.forEach((c) => c()); @@ -121,112 +99,110 @@ export async function generateTypes({ url, email, password }: GenerateOptions) { */ // https://pocketbase.io/docs/collections/#base-collection -type BaseCollectionRecord = { +interface BaseCollectionRecord { id: string; created: string; updated: string; -}; + collectionId: string; + collectionName: string; +} // https://pocketbase.io/docs/api-records/#create-record -type BaseCollectionRecordCreate = { +interface BaseCollectionRecordCreate { id?: string; -}; +} // https://pocketbase.io/docs/collections/#auth-collection -type AuthCollectionRecord = { - id: string; - created: string; - updated: string; +interface AuthCollectionRecord extends BaseCollectionRecord { username: string; email: string; emailVisibility: boolean; verified: boolean; -}; +} // https://pocketbase.io/docs/api-records/#create-record -type AuthCollectionRecordCreate = { - id?: string; +interface AuthCollectionRecordCreate extends BaseCollectionRecordCreate { username?: string; email?: string; emailVisibility?: boolean; verified?: boolean; password: string; passwordConfirm: string; -}; +} // https://pocketbase.io/docs/api-records/#update-record -type AuthCollectionRecordUpdate = { +interface AuthCollectionRecordUpdate { username?: string; email?: string; emailVisibility?: boolean; verified?: boolean; password?: string; passwordConfirm?: string; -}; +} // https://pocketbase.io/docs/collections/#view-collection -type ViewCollectionRecord = { +interface ViewCollectionRecord { id: string; -}; +} // utilities type MaybeArray = T | T[]; ${tables - .map((t) => - ` -// ===== ${t.name} ===== - -export type ${t.typeName}Response = { - ${t.columns.response.join('\n' + indent)} - collectionId: '${t.id}'; - collectionName: '${t.name}'; -} & ${ + .map((t) => { + const baseRecord = t.type === 'base' ? 'BaseCollectionRecord' : t.type === 'auth' - ? 'AuthCollectionRecord' - : 'ViewCollectionRecord' - }; + ? 'AuthCollectionRecord' + : 'ViewCollectionRecord'; + + return ` +// ===== ${t.name} ===== + +export interface ${t.typeName}Response extends ${baseRecord} { + collectionName: '${t.name}'; + ${t.columns.response.join('\n' + indent)} +} ${ // view collections are readonly t.type === 'view' ? '' : ` -export type ${t.typeName}Create = { +export interface ${t.typeName}Create extends ${t.type === "base" ? "BaseCollectionRecordCreate" : "AuthCollectionRecordCreate"} { ${t.columns.create.join('\n' + indent)} -} & ${t.type === "base" ? "BaseCollectionRecordCreate" : "AuthCollectionRecordCreate"}; +} -export type ${t.typeName}Update = { +export interface ${t.typeName}Update${t.type === "base" ? "" : " extends AuthCollectionRecordUpdate"} { ${t.columns.update.join('\n' + indent)} -}${t.type === "base" ? "" : " & AuthCollectionRecordUpdate" }; +} ` } -export type ${t.typeName}Collection = { +export interface ${t.typeName}Collection { type: '${t.type}'; - collectionId: '${t.id}'; + collectionId: string; collectionName: '${t.name}'; response: ${t.typeName}Response;${ - t.type === 'view' - ? '' - : ` + t.type === 'view' + ? '' + : ` create: ${t.typeName}Create; update: ${t.typeName}Update;` - } + } relations: ${ t.relations.length === 0 - ? '{}' + ? 'Record' : `{ ${t.relations .map((col) => `${col.name}: ${col.target};`) .join('\n' + indent.repeat(2))} }` }; -}; +} -`.trim() - ) +`.trim(); + }) .join('\n\n')} // ===== Schema ===== diff --git a/src/expand.ts b/src/expand.ts deleted file mode 100644 index 2cc24c3..0000000 --- a/src/expand.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { - TypedRecord, - GenericCollection, - GenericExpand, - ArrayInnerType, - Simplify -} from './types.js'; - -export type ExpandParam< - T extends GenericCollection, - E extends GenericExpand -> = { - __record__?: T; - __expand__?: E; -} & string; - -export type Expand = { - [K in keyof T['relations']]?: - | true - | Expand>; -}; - -type RelationToTypedRecord< - T extends GenericCollection | GenericCollection[], - E extends GenericExpand = {} -> = T extends GenericCollection[] - ? TypedRecord[] - : T extends GenericCollection - ? TypedRecord - : never; - -export type UnpackExpand< - T extends GenericCollection, - E extends Expand -> = Simplify<{ - [K in keyof E & keyof T['relations']]: RelationToTypedRecord< - T['relations'][K], - E[K] extends Expand - ? UnpackExpand, E[K]> - : {} - >; -}>; - -export function expand>( - expand: E -): ExpandParam> { - const expands: string[] = []; - - process(expand); - - function process(obj: any, prefix: string[] = []) { - for (const key in obj) { - const value = obj[key]; - if (value === true) { - expands.push([...prefix, key].join('.')); - } else if (typeof value === 'object') { - process(value, [...prefix, key]); - } - } - } - - return [...new Set(expands)].join(','); -} diff --git a/src/fields.ts b/src/fields.ts deleted file mode 100644 index 2dcc2da..0000000 --- a/src/fields.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { BaseRecord } from './types.js'; - -export type FieldsParam = { - __record__?: T; - __select__?: S; -} & string; - -export function fields( - ...fields: (S | false | undefined | null | keyof T)[] -): FieldsParam { - return fields.filter(Boolean).join(','); -} diff --git a/src/filter.ts b/src/filter.ts index cd22a77..09beed5 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -58,7 +58,7 @@ function serializeFilter([key, op, val]: ActualFilter) { return `${String(key)}${op}${val}`; } -function serializeFilters(filters: FilterInput[]) { +export function serializeFilters(filters: FilterInput[]) { return filters .filter(Boolean) .map((filter) => diff --git a/src/index.ts b/src/index.ts index e79258e..64cf288 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,21 @@ -import PocketBase from 'pocketbase'; +import PocketBase, { RecordModel, RecordService } from 'pocketbase'; import { GenericSchema } from './types.js'; import { TypedRecordService } from './record-service.js'; -export { fields } from './fields.js'; +export { + createOptions, + ResolveSelect, + ResolveSelectWithExpand, + Select, + SelectWithExpand, + TypedBaseQueryParams, + TypedFullListQueryParams, + TypedListQueryParams, + TypedRecordFullListQueryParams, + TypedRecordListQueryParams, + TypedRecordQueryParams +} from './queryParams.js'; + export { and, or, @@ -16,14 +29,13 @@ export { nlike, Filter } from './filter.js'; -export { expand } from './expand.js'; export { asc, desc, sort, Sort } from './sort.js'; export { GenericSchema, GenericCollection, TypedRecord } from './types.js'; -// @ts-expect-error typescript... export interface TypedPocketBase extends PocketBase { collection( idOrName: C ): TypedRecordService; + collection(idOrName: string): RecordService; } diff --git a/src/queryParams.ts b/src/queryParams.ts new file mode 100644 index 0000000..fea1eee --- /dev/null +++ b/src/queryParams.ts @@ -0,0 +1,186 @@ +import { FilterParam } from './filter.js'; +import { Sort } from './sort.js'; +import { + ArrayInnerType, + GenericCollection, + MaybeArray, + MaybeMakeArray, + Prettify, + RecordWithExpandToDotPath +} from './types.js'; + +export type Select = { + [K in keyof Collection['response']]?: boolean; +}; + +export type SelectWithExpand = + Select & { + $expand?: { + [K in keyof Collection['relations']]?: + | SelectWithExpand> + | boolean; + }; + }; + +export type ResolveSelect< + TCollection extends GenericCollection, + TSelect extends Select +> = Extract extends never + ? TCollection['response'] + : { + [K in keyof TSelect & + keyof TCollection['response'] as TSelect[K] extends true + ? K + : never]: TCollection['response'][K]; + }; + +export type ResolveSelectWithExpand< + TCollection extends GenericCollection, + TSelect extends Select +> = Prettify< + ResolveSelect & + ('$expand' extends keyof TSelect + ? { + expand: { + [Relation in keyof TSelect['$expand'] & + keyof TCollection['relations'] as TSelect['$expand'][Relation] extends false + ? never + : Relation]: TSelect['$expand'][Relation] extends true + ? MaybeMakeArray< + TCollection['relations'][Relation], + ArrayInnerType< + TCollection['relations'][Relation] + >['response'] + > + : TSelect['$expand'][Relation] extends object + ? MaybeMakeArray< + TCollection['relations'][Relation], + ResolveSelectWithExpand< + ArrayInnerType< + TCollection['relations'][Relation] + >, + TSelect['$expand'][Relation] + > + > + : never; + }; + } + : {}) +>; + +export interface CreateOptions { + < + TCollection extends GenericCollection, + TSelect extends SelectWithExpand + >( + options: TypedRecordFullListQueryParams + ): TypedRecordFullListQueryParams; + < + TCollection extends GenericCollection, + TSelect extends SelectWithExpand + >( + options: TypedRecordListQueryParams + ): TypedRecordListQueryParams; + < + TCollection extends GenericCollection, + TSelect extends SelectWithExpand + >( + options: TypedRecordQueryParams + ): TypedRecordQueryParams; +} + +export const createOptions: CreateOptions = (options) => { + const fields: string[] = []; + const expand: string[] = []; + + if (options.select) { + (function recurse( + { $expand, ...rest }: SelectWithExpand, + parent: string[] = [] + ) { + if (Object.keys(rest).length === 0) { + fields.push([...parent, '*'].join('.')); + } else { + for (const key in rest) { + if (rest[key]) { + fields.push([...parent, key].join('.')); + } + } + } + + if ($expand) { + for (const key in $expand) { + const sub = $expand[key]; + if (sub === true) { + expand.push([...parent, key].join('.')); + fields.push([...parent, 'expand', key, '*'].join('.')); + } else if (sub) { + expand.push([...parent, key].join('.')); + recurse(sub, [...parent, 'expand', key]); + } + } + } + })(options.select); + } else { + fields.push('*'); + } + + let { sort } = options as any; + if (Array.isArray(sort)) { + sort = sort.join(','); + } + + return { + ...options, + fields: fields.join(','), + expand: expand.join(','), + sort + }; +}; + +export interface TypedBaseQueryParams { + select?: TSelect; + requestKey?: string | null; + /** + * @deprecated use `requestKey:null` instead + */ + $autoCancel?: boolean; + /** + * @deprecated use `requestKey:string` instead + */ + $cancelKey?: string; +} + +export interface TypedListQueryParams< + TCollection extends GenericCollection, + TSelect +> extends TypedBaseQueryParams { + page?: number; + perPage?: number; + sort?: MaybeArray>; + filter?: FilterParam>; +} + +export interface TypedFullListQueryParams< + TCollection extends GenericCollection, + TSelect +> extends TypedListQueryParams { + batch?: number; +} + +export interface TypedRecordQueryParams + extends TypedBaseQueryParams { + select?: TSelect; +} + +export interface TypedRecordListQueryParams< + TCollection extends GenericCollection, + TSelect +> extends TypedListQueryParams, + TypedRecordQueryParams {} + +export interface TypedRecordFullListQueryParams< + TCollection extends GenericCollection, + TSelect +> extends TypedFullListQueryParams, + TypedRecordQueryParams {} diff --git a/src/record-service.ts b/src/record-service.ts index 229393f..d636116 100644 --- a/src/record-service.ts +++ b/src/record-service.ts @@ -1,165 +1,93 @@ +import { ListResult, RecordAuthResponse, RecordService } from 'pocketbase'; +import { GenericCollection } from './types.js'; +import { Filter } from './filter.js'; import { - ListResult, - RecordService, - RecordSubscription, - UnsubscribeFunc -} from 'pocketbase'; -import { - Simplify, - GenericCollection, - TypedRecord, - Fields, - Columns, - GenericExpand, - LooseAutocomplete, - RecordWithExpandToDotPath -} from './types.js'; -import { FieldsParam } from './fields.js'; -import { FilterInput, FilterParam } from './filter.js'; -import { SortParam } from './sort.js'; -import { ExpandParam } from './expand.js'; + ResolveSelectWithExpand, + SelectWithExpand, + TypedRecordFullListQueryParams, + TypedRecordListQueryParams, + TypedRecordQueryParams +} from './queryParams.js'; // @ts-expect-error export interface TypedRecordService - extends RecordService { - getFullList< - Select extends Fields = Fields, - Expand extends GenericExpand = {} - >( - queryParams?: TypedRecordFullListQueryParams - ): Promise< - TypedRecord, Select>>, Expand>[] - >; - - getFullList< - Select extends Fields = Fields, - Expand extends GenericExpand = {} - >( + extends RecordService { + getFullList>( + options?: TypedRecordFullListQueryParams + ): Promise[]>; + getFullList>( batch?: number, - queryParams?: TypedRecordListQueryParams - ): Promise< - TypedRecord, Select>>, Expand>[] - >; + options?: TypedRecordListQueryParams + ): Promise[]>; - getList< - Select extends Fields = Fields, - Expand extends GenericExpand = {} - >( + getList>( page?: number, perPage?: number, - queryParams?: TypedRecordListQueryParams - ): Promise< - ListResult< - TypedRecord, Select>>, Expand> - > - >; + options?: TypedRecordListQueryParams + ): Promise>>; - getFirstListItem< - Select extends Fields = Fields, - Expand extends GenericExpand = {} - >( - filter: FilterInput>, - queryParams?: TypedRecordListQueryParams - ): Promise< - TypedRecord, Select>>, Expand> - >; + getFirstListItem>( + filter: Filter, + options?: TypedRecordListQueryParams + ): Promise>; - getOne< - Select extends Fields = Fields, - Expand extends GenericExpand = {} - >( + getOne>( id: string, - queryParams?: TypedRecordQueryParams - ): Promise< - TypedRecord, Select>>, Expand> - >; + options?: TypedRecordQueryParams + ): Promise>; - create< - Select extends Fields = Fields, - Expand extends GenericExpand = {} - >( + create>( bodyParams: Collection['create'], - queryParams?: TypedRecordQueryParams - ): Promise< - TypedRecord, Select>>, Expand> - >; + options?: TypedRecordQueryParams + ): Promise>; - update< - Select extends Fields = Fields, - Expand extends GenericExpand = {} - >( + update>( id: string, bodyParams: Collection['update'], - queryParams?: TypedRecordQueryParams + options?: TypedRecordQueryParams + ): Promise>; + + // ===== AUTH ===== + authWithPassword>( + usernameOrEmail: string, + password: string, + options?: TypedRecordQueryParams ): Promise< - TypedRecord, Select>>, Expand> + RecordAuthResponse> >; - subscribe( - topic: LooseAutocomplete<'*'>, - callback: ( - data: RecordSubscription>> - ) => void - ): Promise; - - subscribe( - callback: ( - data: RecordSubscription>> - ) => void - ): Promise; -} - -export interface TypedBaseQueryParams< - T extends GenericCollection, - S extends Fields -> { - fields?: FieldsParam, S>; - requestKey?: string | null; - /** - * @deprecated use `requestKey:null` instead - */ - $autoCancel?: boolean; - /** - * @deprecated use `requestKey:string` instead - */ - $cancelKey?: string; -} - -export interface TypedListQueryParams< - T extends GenericCollection, - S extends Fields -> extends TypedBaseQueryParams { - page?: number; - perPage?: number; - sort?: SortParam>; - filter?: FilterParam>; -} + authWithOAuth2Code>( + provider: string, + code: string, + codeVerifier: string, + redirectUrl: string, + createData?: { + [key: string]: any; + }, + options?: TypedRecordQueryParams + ): Promise< + RecordAuthResponse> + >; -export interface TypedFullListQueryParams< - T extends GenericCollection, - S extends Fields -> extends TypedListQueryParams { - batch?: number; -} + authWithOAuth2>( + provider: string, + code: string, + codeVerifier: string, + redirectUrl: string, + createData?: { + [key: string]: any; + }, + bodyParams?: { + [key: string]: any; + }, + options?: TypedRecordQueryParams + ): Promise< + RecordAuthResponse> + >; -export interface TypedRecordQueryParams< - T extends GenericCollection, - S extends Fields, - E extends GenericExpand -> extends TypedBaseQueryParams { - expand?: ExpandParam; + authRefresh>( + options?: TypedRecordQueryParams + ): Promise< + RecordAuthResponse> + >; } - -export interface TypedRecordListQueryParams< - T extends GenericCollection, - S extends Fields, - E extends GenericExpand -> extends TypedListQueryParams, - TypedRecordQueryParams {} - -export interface TypedRecordFullListQueryParams< - T extends GenericCollection, - S extends Fields, - E extends GenericExpand -> extends TypedFullListQueryParams, - TypedRecordQueryParams {} diff --git a/src/sort.ts b/src/sort.ts index 05e54f8..ca3c040 100644 --- a/src/sort.ts +++ b/src/sort.ts @@ -8,9 +8,9 @@ export type SortParam = { __record__?: T; } & string; -export type Sort = SortParam< - RecordWithExpandToDotPath ->; +export type Sort = + | SortParam> + | PrefixedSortItem>; export type PrefixedSortItem = T extends string ? `${'+' | '-'}${T}` : never; diff --git a/src/types.ts b/src/types.ts index bbb374e..3562e42 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,15 +1,17 @@ -export type Simplify = T extends infer o ? { [K in keyof o]: o[K] } : never; +export type Prettify = T extends infer o ? { [K in keyof o]: o[K] } : never; +export type MaybeArray = T | T[]; +export type MaybeMakeArray = T extends any[] ? Out[] : Out; export type ArrayInnerType = T extends Array ? V : T; export type Values = T[keyof T]; -export type Overide = Simplify & B>; +export type Overide = Prettify & B>; export type RemoveIndex = { [K in keyof T as string extends K ? never : number extends K - ? never - : symbol extends K - ? never - : K]: T[K]; + ? never + : symbol extends K + ? never + : K]: T[K]; }; export type LooseAutocomplete = T | (string & {}); export type UnionToIntersection = ( @@ -26,18 +28,19 @@ export type BaseSystemFields = { updated: string; }; -export interface GenericCollection { +export type GenericCollection = { + type: string; collectionId: string; collectionName: string; response: BaseRecord; create?: BaseRecord; update?: BaseRecord; relations: Record; -} +}; -export interface GenericSchema { +export type GenericSchema = { [K: string]: GenericCollection; -} +}; export type TypedRecord< Data extends BaseRecord, @@ -62,10 +65,10 @@ export type Expands = { ? TypedRecord< T['relations'][K][number], Expands - >[] + >[] : T['relations'][K] extends GenericCollection - ? TypedRecord> - : never; + ? TypedRecord> + : never; }; export type GenericExpand = Record< @@ -98,8 +101,8 @@ type _RecordWithExpandToDotPath< [...Path, K & string] >; }> - >); + >); -export type RecordWithExpandToDotPath = Simplify< +export type RecordWithExpandToDotPath = Prettify< _RecordWithExpandToDotPath >; From 1fe414325927c98f9add41884e2996ac69753555 Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 19 Dec 2023 13:05:20 +0100 Subject: [PATCH 8/8] Fix merge conflicts (#25) Thank you! --- src/codegen/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/codegen/index.ts b/src/codegen/index.ts index cca1734..abdc1c4 100644 --- a/src/codegen/index.ts +++ b/src/codegen/index.ts @@ -162,21 +162,21 @@ ${tables // ===== ${t.name} ===== export interface ${t.typeName}Response extends ${baseRecord} { - collectionName: '${t.name}'; - ${t.columns.response.join('\n' + indent)} + collectionName: '${t.name}';${!t.columns.response.length ? '' : ` + ${t.columns.response.join('\n' + indent)}`} } ${ // view collections are readonly t.type === 'view' ? '' : ` -export interface ${t.typeName}Create extends ${t.type === "base" ? "BaseCollectionRecordCreate" : "AuthCollectionRecordCreate"} { +export interface ${t.typeName}Create extends ${t.type === "base" ? "BaseCollectionRecordCreate" : "AuthCollectionRecordCreate"} ${!t.columns.create.length ? '{}' : `{ ${t.columns.create.join('\n' + indent)} -} +}`} -export interface ${t.typeName}Update${t.type === "base" ? "" : " extends AuthCollectionRecordUpdate"} { +export interface ${t.typeName}Update${t.type === "base" ? "" : " extends AuthCollectionRecordUpdate"} ${!t.columns.update.length ? '{}' : `{ ${t.columns.update.join('\n' + indent)} -} +}`} ` } export interface ${t.typeName}Collection {