From 08128645f997cd000846e9f5c078bb66ee3f6789 Mon Sep 17 00:00:00 2001 From: Patrick Moody <4031292+patmood@users.noreply.github.com> Date: Sun, 26 Feb 2023 15:19:01 -0800 Subject: [PATCH] Add support for `view` collection type (#50) * add view collection type --- Dockerfile | 2 +- dist/index.js | 84 +++++++++++++----------- package.json | 2 +- src/constants.ts | 7 ++ src/lib.ts | 2 + src/types.ts | 2 +- src/utils.ts | 9 ++- test/__snapshots__/fromJSON.test.ts.snap | 16 +++++ test/__snapshots__/lib.test.ts.snap | 7 ++ test/pb_schema.json | 66 +++++++++++++++++-- test/pocketbase-types-example.ts | 16 +++++ test/utils.test.ts | 1 + 12 files changed, 167 insertions(+), 47 deletions(-) diff --git a/Dockerfile b/Dockerfile index a85312c..8298bd2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # Dockerfile to run e2e integration tests against a test PocketBase server FROM node:16-alpine3.16 -ARG POCKETBASE_VERSION=0.12.2 +ARG POCKETBASE_VERSION=0.13.0 WORKDIR /app/output/ WORKDIR /app/ diff --git a/dist/index.js b/dist/index.js index 15ac4a3..394fccb 100755 --- a/dist/index.js +++ b/dist/index.js @@ -66,6 +66,12 @@ var AUTH_SYSTEM_FIELDS_DEFINITION = `export type AuthSystemFields = { username: string verified: boolean } & BaseSystemFields`; +var VIEW_SYSTEM_FIELDS_DEFINITION = `export type ViewSystemFields = { + id: ${RECORD_ID_STRING_NAME} + collectionId: string + collectionName: Collections + expand?: T +}`; // src/generics.ts function fieldNameToGeneric(name) { @@ -113,7 +119,14 @@ async function saveFile(outPath, typeString) { console.log(`Created typescript definitions at ${outPath}`); } function getSystemFields(type) { - return type === "auth" ? "AuthSystemFields" : "BaseSystemFields"; + switch (type) { + case "auth": + return "AuthSystemFields"; + case "view": + return "ViewSystemFields"; + default: + return "BaseSystemFields"; + } } function getOptionEnumName(recordName, fieldName) { return `${toPascalCase(recordName)}${toPascalCase(fieldName)}Options`; @@ -125,37 +138,54 @@ function getOptionValues(field) { return values.filter((val, i) => values.indexOf(val) === i); } -// src/lib.ts +// src/fields.ts var pbSchemaTypescriptMap = { bool: "boolean", date: DATE_STRING_TYPE_NAME, editor: HTML_STRING_NAME, email: "string", + text: "string", + url: "string", + number: "number", file: (fieldSchema) => fieldSchema.options.maxSelect && fieldSchema.options.maxSelect > 1 ? "string[]" : "string", json: (fieldSchema) => `null | ${fieldNameToGeneric(fieldSchema.name)}`, - number: "number", relation: (fieldSchema) => fieldSchema.options.maxSelect && fieldSchema.options.maxSelect === 1 ? RECORD_ID_STRING_NAME : `${RECORD_ID_STRING_NAME}[]`, select: (fieldSchema, collectionName) => { const valueType = fieldSchema.options.values ? getOptionEnumName(collectionName, fieldSchema.name) : "string"; return fieldSchema.options.maxSelect && fieldSchema.options.maxSelect > 1 ? `${valueType}[]` : valueType; }, - text: "string", - url: "string", user: (fieldSchema) => fieldSchema.options.maxSelect && fieldSchema.options.maxSelect > 1 ? `${RECORD_ID_STRING_NAME}[]` : RECORD_ID_STRING_NAME }; +function createTypeField(collectionName, fieldSchema) { + let typeStringOrFunc; + if (!(fieldSchema.type in pbSchemaTypescriptMap)) { + console.log(`WARNING: unknown type "${fieldSchema.type}" found in schema`); + typeStringOrFunc = "unknown"; + } else { + typeStringOrFunc = pbSchemaTypescriptMap[fieldSchema.type]; + } + const typeString = typeof typeStringOrFunc === "function" ? typeStringOrFunc(fieldSchema, collectionName) : typeStringOrFunc; + const fieldName = sanitizeFieldName(fieldSchema.name); + const required = fieldSchema.required ? "" : "?"; + return ` ${fieldName}${required}: ${typeString}`; +} +function createSelectOptions(recordName, schema) { + const selectFields = schema.filter((field) => field.type === "select"); + const typestring = selectFields.map( + (field) => `export enum ${getOptionEnumName(recordName, field.name)} { +${getOptionValues(field).map((val) => ` "${val}" = "${val}",`).join("\n")} +} +` + ).join("\n"); + return typestring; +} + +// src/lib.ts function generate(results) { const collectionNames = []; const recordTypes = []; const responseTypes = [RESPONSE_TYPE_COMMENT]; - results.sort((a, b) => { - if (a.name < b.name) { - return -1; - } - if (a.name > b.name) { - return 1; - } - return 0; - }).forEach((row) => { + results.sort((a, b) => a.name <= b.name ? -1 : 1).forEach((row) => { if (row.name) collectionNames.push(row.name); if (row.schema) { @@ -170,6 +200,7 @@ function generate(results) { ALIAS_TYPE_DEFINITIONS, BASE_SYSTEM_FIELDS_DEFINITION, AUTH_SYSTEM_FIELDS_DEFINITION, + VIEW_SYSTEM_FIELDS_DEFINITION, RECORD_TYPE_COMMENT, ...recordTypes, responseTypes.join("\n"), @@ -212,29 +243,6 @@ function createResponseType(collectionSchemaEntry) { const expandArgString = canExpand(schema) ? `` : ""; return `export type ${pascaleName}Response${genericArgsWithDefaults} = ${pascaleName}Record${genericArgsForRecord} & ${systemFields}${expandArgString}`; } -function createTypeField(collectionName, fieldSchema) { - let typeStringOrFunc; - if (!(fieldSchema.type in pbSchemaTypescriptMap)) { - console.log(`WARNING: unknown type "${fieldSchema.type}" found in schema`); - typeStringOrFunc = "unknown"; - } else { - typeStringOrFunc = pbSchemaTypescriptMap[fieldSchema.type]; - } - const typeString = typeof typeStringOrFunc === "function" ? typeStringOrFunc(fieldSchema, collectionName) : typeStringOrFunc; - const fieldName = sanitizeFieldName(fieldSchema.name); - const required = fieldSchema.required ? "" : "?"; - return ` ${fieldName}${required}: ${typeString}`; -} -function createSelectOptions(recordName, schema) { - const selectFields = schema.filter((field) => field.type === "select"); - const typestring = selectFields.map( - (field) => `export enum ${getOptionEnumName(recordName, field.name)} { -${getOptionValues(field).map((val) => ` "${val}" = "${val}",`).join("\n")} -} -` - ).join("\n"); - return typestring; -} // src/cli.ts async function main(options2) { @@ -259,7 +267,7 @@ async function main(options2) { import { program } from "commander"; // package.json -var version = "1.1.4"; +var version = "1.1.5"; // src/index.ts program.name("Pocketbase Typegen").version(version).description( diff --git a/package.json b/package.json index 04eba99..aa32184 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pocketbase-typegen", - "version": "1.1.4", + "version": "1.1.5", "description": "Generate pocketbase record types from your database", "main": "dist/index.js", "bin": { diff --git a/src/constants.ts b/src/constants.ts index 631ffd5..143b877 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -28,3 +28,10 @@ export const AUTH_SYSTEM_FIELDS_DEFINITION = `export type AuthSystemFields` + +export const VIEW_SYSTEM_FIELDS_DEFINITION = `export type ViewSystemFields = { + \tid: ${RECORD_ID_STRING_NAME} + \tcollectionId: string + \tcollectionName: Collections + \texpand?: T +}` diff --git a/src/lib.ts b/src/lib.ts index 8ac1e4f..dd492c9 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -6,6 +6,7 @@ import { EXPORT_COMMENT, RECORD_TYPE_COMMENT, RESPONSE_TYPE_COMMENT, + VIEW_SYSTEM_FIELDS_DEFINITION, } from "./constants" import { CollectionRecord, FieldSchema } from "./types" import { @@ -38,6 +39,7 @@ export function generate(results: Array): string { ALIAS_TYPE_DEFINITIONS, BASE_SYSTEM_FIELDS_DEFINITION, AUTH_SYSTEM_FIELDS_DEFINITION, + VIEW_SYSTEM_FIELDS_DEFINITION, RECORD_TYPE_COMMENT, ...recordTypes, responseTypes.join("\n"), diff --git a/src/types.ts b/src/types.ts index e2e914a..cdf07ab 100644 --- a/src/types.ts +++ b/src/types.ts @@ -32,7 +32,7 @@ export type FieldSchema = { export type CollectionRecord = { id: string - type: "base" | "auth" + type: "base" | "auth" | "view" name: string system: boolean listRule: string | null diff --git a/src/utils.ts b/src/utils.ts index 169512a..e14eca4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -26,7 +26,14 @@ export async function saveFile(outPath: string, typeString: string) { } export function getSystemFields(type: CollectionRecord["type"]) { - return type === "auth" ? "AuthSystemFields" : "BaseSystemFields" + switch (type) { + case "auth": + return "AuthSystemFields" + case "view": + return "ViewSystemFields" + default: + return "BaseSystemFields" + } } export function getOptionEnumName(recordName: string, fieldName: string) { diff --git a/test/__snapshots__/fromJSON.test.ts.snap b/test/__snapshots__/fromJSON.test.ts.snap index e6b6d08..f479a0e 100644 --- a/test/__snapshots__/fromJSON.test.ts.snap +++ b/test/__snapshots__/fromJSON.test.ts.snap @@ -9,6 +9,7 @@ export enum Collections { Base = "base", CustomAuth = "custom_auth", Everything = "everything", + MyView = "my_view", Posts = "posts", Users = "users", } @@ -35,6 +36,13 @@ export type AuthSystemFields = { verified: boolean } & BaseSystemFields +export type ViewSystemFields = { + id: RecordIdString + collectionId: string + collectionName: Collections + expand?: T +} + // Record types for each collection export type BaseRecord = { @@ -72,6 +80,12 @@ export type EverythingRecord = { + post_relation_field?: RecordIdString + text_field?: string + json_field?: null | Tjson_field +} + export type PostsRecord = { field?: string nonempty_field: string @@ -88,6 +102,7 @@ export type UsersRecord = { export type BaseResponse = BaseRecord & BaseSystemFields export type CustomAuthResponse = CustomAuthRecord & AuthSystemFields export type EverythingResponse = EverythingRecord & BaseSystemFields +export type MyViewResponse = MyViewRecord & ViewSystemFields export type PostsResponse = PostsRecord & BaseSystemFields export type UsersResponse = UsersRecord & AuthSystemFields @@ -95,6 +110,7 @@ export type CollectionRecords = { base: BaseRecord custom_auth: CustomAuthRecord everything: EverythingRecord + my_view: MyViewRecord posts: PostsRecord users: UsersRecord }" diff --git a/test/__snapshots__/lib.test.ts.snap b/test/__snapshots__/lib.test.ts.snap index b63523a..bc7967d 100644 --- a/test/__snapshots__/lib.test.ts.snap +++ b/test/__snapshots__/lib.test.ts.snap @@ -65,6 +65,13 @@ export type AuthSystemFields = { verified: boolean } & BaseSystemFields +export type ViewSystemFields = { + id: RecordIdString + collectionId: string + collectionName: Collections + expand?: T +} + // Record types for each collection export type BooksRecord = { diff --git a/test/pb_schema.json b/test/pb_schema.json index f3dfa87..a7c3a8b 100644 --- a/test/pb_schema.json +++ b/test/pb_schema.json @@ -207,6 +207,7 @@ "options": { "collectionId": "_pb_users_auth_", "cascadeDelete": false, + "minSelect": null, "maxSelect": 1, "displayFields": null } @@ -221,6 +222,7 @@ "options": { "collectionId": "rs7hepu8zl6kr8e", "cascadeDelete": false, + "minSelect": null, "maxSelect": 5, "displayFields": null } @@ -235,6 +237,7 @@ "options": { "collectionId": "z6b9mssubo9megi", "cascadeDelete": false, + "minSelect": null, "maxSelect": 1, "displayFields": null } @@ -323,11 +326,11 @@ } } ], - "listRule": null, - "viewRule": null, - "createRule": null, - "updateRule": null, - "deleteRule": null, + "listRule": "", + "viewRule": "", + "createRule": "", + "updateRule": "", + "deleteRule": "", "options": {} }, { @@ -392,5 +395,58 @@ "updateRule": null, "deleteRule": null, "options": {} + }, + { + "id": "ngpunwfmpl9x50r", + "name": "my_view", + "type": "view", + "system": false, + "schema": [ + { + "id": "iwh5jvyg", + "name": "post_relation_field", + "type": "relation", + "system": false, + "required": false, + "unique": false, + "options": { + "collectionId": "z6b9mssubo9megi", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "id": "ze7zu2ji", + "name": "text_field", + "type": "text", + "system": false, + "required": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }, + { + "id": "pbwoyo77", + "name": "json_field", + "type": "json", + "system": false, + "required": false, + "unique": false, + "options": {} + } + ], + "listRule": "", + "viewRule": "", + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": { + "query": "select id, post_relation_field, text_field, json_field from everything" + } } ] diff --git a/test/pocketbase-types-example.ts b/test/pocketbase-types-example.ts index 21f615f..9cee008 100644 --- a/test/pocketbase-types-example.ts +++ b/test/pocketbase-types-example.ts @@ -6,6 +6,7 @@ export enum Collections { Base = "base", CustomAuth = "custom_auth", Everything = "everything", + MyView = "my_view", Posts = "posts", Users = "users", } @@ -32,6 +33,13 @@ export type AuthSystemFields = { verified: boolean } & BaseSystemFields +export type ViewSystemFields = { + id: RecordIdString + collectionId: string + collectionName: Collections + expand?: T +} + // Record types for each collection export type BaseRecord = { @@ -69,6 +77,12 @@ export type EverythingRecord = { + post_relation_field?: RecordIdString + text_field?: string + json_field?: null | Tjson_field +} + export type PostsRecord = { field?: string nonempty_field: string @@ -85,6 +99,7 @@ export type UsersRecord = { export type BaseResponse = BaseRecord & BaseSystemFields export type CustomAuthResponse = CustomAuthRecord & AuthSystemFields export type EverythingResponse = EverythingRecord & BaseSystemFields +export type MyViewResponse = MyViewRecord & ViewSystemFields export type PostsResponse = PostsRecord & BaseSystemFields export type UsersResponse = UsersRecord & AuthSystemFields @@ -92,6 +107,7 @@ export type CollectionRecords = { base: BaseRecord custom_auth: CustomAuthRecord everything: EverythingRecord + my_view: MyViewRecord posts: PostsRecord users: UsersRecord } \ No newline at end of file diff --git a/test/utils.test.ts b/test/utils.test.ts index d857ac0..a71e449 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -35,6 +35,7 @@ describe("getSystemFields", () => { it("returns the system field type name for a given collection type", () => { expect(getSystemFields("base")).toBe("BaseSystemFields") expect(getSystemFields("auth")).toBe("AuthSystemFields") + expect(getSystemFields("view")).toBe("ViewSystemFields") }) })