diff --git a/src/loader/load-entries.ts b/src/loader/load-entries.ts index be6fb82..563375d 100644 --- a/src/loader/load-entries.ts +++ b/src/loader/load-entries.ts @@ -64,6 +64,11 @@ export async function loadEntries( searchParams.set("filter", filters.join("&&")); } + // Add expand to search parameters + if (options.expand) { + searchParams.set("expand", options.expand.join(",")); + } + // Fetch entries from the collection const collectionRequest = await fetch( `${collectionUrl}?${searchParams.toString()}`, diff --git a/src/schema/generate-schema.ts b/src/schema/generate-schema.ts index 67e2f70..840aa16 100644 --- a/src/schema/generate-schema.ts +++ b/src/schema/generate-schema.ts @@ -1,9 +1,9 @@ import type { ZodSchema } from "astro/zod"; import { z } from "astro/zod"; +import type { PocketBaseCollection } from "../types/pocketbase-collection.type"; import type { PocketBaseLoaderOptions } from "../types/pocketbase-loader-options.type"; -import type { PocketBaseCollection } from "../types/pocketbase-schema.type"; import { getRemoteSchema } from "./get-remote-schema"; -import { parseSchema } from "./parse-schema"; +import { parseExpandedSchemaField, parseSchema } from "./parse-schema"; import { readLocalSchema } from "./read-local-schema"; import { transformFiles } from "./transform-files"; @@ -35,6 +35,7 @@ export async function generateSchema( token: string | undefined ): Promise { let collection: PocketBaseCollection | undefined; + const expandedFields: Record = {}; if (token) { // Try to get the schema directly from the PocketBase instance @@ -133,10 +134,51 @@ export async function generateSchema( } } - // Combine the basic schema with the parsed fields + if (options.expand && options.expand.length > 0) { + for (const expandedFieldName of options.expand) { + const [currentLevelFieldName, ...deeperExpandFields] = + getCurrentLevelExpandedFieldName(expandedFieldName); + + const expandedFieldDefinition = collection.fields.find( + (field) => field.name === currentLevelFieldName + ); + + if (!expandedFieldDefinition) { + throw new Error( + `The provided field in the expand property "${expandedFieldName}" is not present in the schema of the collection "${options.collectionName}".\nThis will lead to use unable to provide a definition for this field.` + ); + } + + if (!expandedFieldDefinition.collectionId) { + throw new Error( + `The provided field in the expand property "${expandedFieldName}" does not have an associated collection linked to it, we need this in order to know the shape of the related schema.` + ); + } + + const expandedSchema = await generateSchema( + { + collectionName: expandedFieldDefinition.collectionId, + superuserCredentials: options.superuserCredentials, + expand: deeperExpandFields.length ? deeperExpandFields : undefined, + localSchema: options.localSchema, + jsonSchemas: options.jsonSchemas, + improveTypes: options.improveTypes, + url: options.url + }, + token + ); + + expandedFields[expandedFieldName] = parseExpandedSchemaField( + expandedFieldDefinition, + expandedSchema + ); + } + } + const schema = z.object({ ...BASIC_SCHEMA, - ...fields + ...fields, + expand: z.optional(z.object(expandedFields)) }); // Get all file fields @@ -154,3 +196,15 @@ export async function generateSchema( transformFiles(options.url, fileFields, entry) ); } + +function getCurrentLevelExpandedFieldName(s: string): Array { + const fields = s.split("."); + + if (fields.length >= 7) { + throw new Error( + `Expand value ${s} exceeds 6 levels of depth that Pocketbase allows` + ); + } + + return fields; +} diff --git a/src/schema/get-remote-schema.ts b/src/schema/get-remote-schema.ts index 91dbeab..c47069f 100644 --- a/src/schema/get-remote-schema.ts +++ b/src/schema/get-remote-schema.ts @@ -1,5 +1,5 @@ +import type { PocketBaseCollection } from "../types/pocketbase-collection.type"; import type { PocketBaseLoaderOptions } from "../types/pocketbase-loader-options.type"; -import type { PocketBaseCollection } from "../types/pocketbase-schema.type"; /** * Fetches the schema for the specified collection from the PocketBase instance. diff --git a/src/schema/parse-schema.ts b/src/schema/parse-schema.ts index 2920869..f4e464b 100644 --- a/src/schema/parse-schema.ts +++ b/src/schema/parse-schema.ts @@ -1,8 +1,6 @@ -import { z } from "astro/zod"; -import type { - PocketBaseCollection, - PocketBaseSchemaEntry -} from "../types/pocketbase-schema.type"; +import { z, ZodSchema } from "astro/zod"; +import type { PocketBaseCollection } from "../types/pocketbase-collection.type"; +import type { PocketBaseSchemaEntry } from "../types/pocketbase-schema.type"; export function parseSchema( collection: PocketBaseCollection, @@ -101,6 +99,21 @@ export function parseSchema( return fields; } +export function parseExpandedSchemaField( + originalField: PocketBaseSchemaEntry, + expandedSchema: ZodSchema +): z.ZodType { + const isRequired = originalField.required; + let fieldType = parseSingleOrMultipleValues(originalField, expandedSchema); + + // If the field is not required, mark it as optional + if (!isRequired) { + fieldType = z.preprocess((val) => val || undefined, z.optional(fieldType)); + } + + return fieldType; +} + /** * Parse the field type based on the number of values it can have * @@ -114,9 +127,9 @@ function parseSingleOrMultipleValues( type: z.ZodType ): z.ZodType { // If the select allows multiple values, create an array of the enum - if (field.maxSelect === undefined || field.maxSelect === 1) { + if (field.maxSelect === undefined || field.maxSelect <= 1) { return type; - } else { - return z.array(type); } + + return z.array(type); } diff --git a/src/schema/read-local-schema.ts b/src/schema/read-local-schema.ts index 7a6caa3..9cf0177 100644 --- a/src/schema/read-local-schema.ts +++ b/src/schema/read-local-schema.ts @@ -1,6 +1,6 @@ import fs from "fs/promises"; import path from "path"; -import type { PocketBaseCollection } from "../types/pocketbase-schema.type"; +import type { PocketBaseCollection } from "../types/pocketbase-collection.type"; /** * Reads the local PocketBase schema file and returns the schema for the specified collection. diff --git a/src/types/pocketbase-collection.type.ts b/src/types/pocketbase-collection.type.ts new file mode 100644 index 0000000..e6c9ed5 --- /dev/null +++ b/src/types/pocketbase-collection.type.ts @@ -0,0 +1,23 @@ +import type { PocketBaseSchemaEntry } from "./pocketbase-schema.type"; + +/** + * Base interface for all PocketBase collections. + */ +export interface PocketBaseCollection { + /** + * ID of the collection. + */ + id: string; + /** + * Name of the collection + */ + name: string; + /** + * Type of the collection. + */ + type: "base" | "view" | "auth"; + /** + * Schema of the collection. + */ + fields: Array; +} diff --git a/src/types/pocketbase-entry.type.ts b/src/types/pocketbase-entry.type.ts index 7471d2f..47001d7 100644 --- a/src/types/pocketbase-entry.type.ts +++ b/src/types/pocketbase-entry.type.ts @@ -14,6 +14,10 @@ interface PocketBaseBaseEntry { * Name of the collection the entry belongs to. */ collectionName: string; + /** + * Optional property that contains all relational fields that have been expanded to contain their linked entry(s) + */ + expand?: Record>; } /** diff --git a/src/types/pocketbase-loader-options.type.ts b/src/types/pocketbase-loader-options.type.ts index 70d6967..371cd39 100644 --- a/src/types/pocketbase-loader-options.type.ts +++ b/src/types/pocketbase-loader-options.type.ts @@ -54,6 +54,19 @@ export interface PocketBaseLoaderOptions { * ``` */ filter?: string; + /** + * array of relational field names to auto expand when loading data from PocketBase. + * Valid syntax can be found in the [PocketBase documentation](https://pocketbase.io/docs/api-records/#listsearch-records) + * Example: + * ```ts + * // config: + * expand: ['relatedField1', 'relatedField2'] + * + * // request + * `?expand=relatedField1,relatedField2` + * ``` + */ + expand?: Array; /** * Credentials of a superuser to get full access to the PocketBase instance. * This is required to get automatic type generation without a local schema, to access all resources even if they are not public and to fetch content of hidden fields. diff --git a/src/types/pocketbase-schema.type.ts b/src/types/pocketbase-schema.type.ts index ff218cf..c89ada0 100644 --- a/src/types/pocketbase-schema.type.ts +++ b/src/types/pocketbase-schema.type.ts @@ -39,22 +39,10 @@ export interface PocketBaseSchemaEntry { * This is only present on "autodate" fields. */ onUpdate?: boolean; -} -/** - * Schema for a PocketBase collection. - */ -export interface PocketBaseCollection { - /** - * Name of the collection. - */ - name: string; - /** - * Type of the collection. - */ - type: "base" | "view" | "auth"; /** - * Schema of the collection. + * The associated collection id that the relation field is referencing + * This is only present on "relation" fields. */ - fields: Array; + collectionId?: string; } diff --git a/test/_mocks/insert-collection.ts b/test/_mocks/insert-collection.ts index 16f9918..0822447 100644 --- a/test/_mocks/insert-collection.ts +++ b/test/_mocks/insert-collection.ts @@ -1,11 +1,12 @@ import { assert } from "console"; +import type { PocketBaseCollection } from "../../src/types/pocketbase-collection.type"; import type { PocketBaseLoaderOptions } from "../../src/types/pocketbase-loader-options.type"; export async function insertCollection( fields: Array>, options: PocketBaseLoaderOptions, superuserToken: string -): Promise { +): Promise { const insertRequest = await fetch(new URL(`api/collections`, options.url), { method: "POST", headers: { @@ -19,4 +20,9 @@ export async function insertCollection( }); assert(insertRequest.status === 200, "Collection is not available."); + + const collection = await insertRequest.json(); + assert(collection.id, "Collection ID is not available."); + + return collection; } diff --git a/test/loader/load-entries.e2e-spec.ts b/test/loader/load-entries.e2e-spec.ts index 409f100..1044bfb 100644 --- a/test/loader/load-entries.e2e-spec.ts +++ b/test/loader/load-entries.e2e-spec.ts @@ -8,17 +8,19 @@ import { describe, expect, test, - vi + vi, + type Mock } from "vitest"; import { loadEntries } from "../../src/loader/load-entries"; -import { parseEntry } from "../../src/loader/parse-entry"; +import * as parseEntry from "../../src/loader/parse-entry"; +import type { PocketBaseEntry } from "../../src/types/pocketbase-entry.type"; import { getSuperuserToken } from "../../src/utils/get-superuser-token"; import { checkE2eConnection } from "../_mocks/check-e2e-connection"; import { createLoaderContext } from "../_mocks/create-loader-context"; import { createLoaderOptions } from "../_mocks/create-loader-options"; import { deleteCollection } from "../_mocks/delete-collection"; import { insertCollection } from "../_mocks/insert-collection"; -import { insertEntries } from "../_mocks/insert-entry"; +import { insertEntries, insertEntry } from "../_mocks/insert-entry"; vi.mock("../../src/loader/parse-entry"); @@ -28,6 +30,7 @@ describe("loadEntries", () => { const options = createLoaderOptions({ collectionName: "_superusers" }); let context: LoaderContext; let superuserToken: string; + let parsedEntrySpy: Mock; beforeAll(async () => { await checkE2eConnection(); @@ -35,6 +38,7 @@ describe("loadEntries", () => { beforeEach(async () => { context = createLoaderContext(); + parsedEntrySpy = vi.spyOn(parseEntry, "parseEntry") as Mock; assert(options.superuserCredentials, "Superuser credentials are not set."); assert( @@ -58,7 +62,7 @@ describe("loadEntries", () => { test("should fetch entries without errors", async () => { await loadEntries(options, context, superuserToken, undefined); - expect(parseEntry).toHaveBeenCalledOnce(); + expect(parsedEntrySpy).toHaveBeenCalledOnce(); }); test("should handle empty response gracefully", async () => { @@ -66,7 +70,7 @@ describe("loadEntries", () => { await loadEntries(testOptions, context, superuserToken, undefined); - expect(parseEntry).not.toHaveBeenCalled(); + expect(parsedEntrySpy).not.toHaveBeenCalled(); }); test("should load all pages", async () => { @@ -85,7 +89,7 @@ describe("loadEntries", () => { await loadEntries(testOptions, context, superuserToken, undefined); - expect(parseEntry).toHaveBeenCalledTimes(numberOfEntries); + expect(parsedEntrySpy).toHaveBeenCalledTimes(numberOfEntries); await deleteCollection(testOptions, superuserToken); }); @@ -120,17 +124,84 @@ describe("loadEntries", () => { ); await loadEntries(testOptions, context, superuserToken, undefined); - expect(parseEntry).toHaveBeenCalledTimes(numberOfEntries); + expect(parsedEntrySpy).toHaveBeenCalledTimes(numberOfEntries); await deleteCollection(testOptions, superuserToken); }); + test("should expand related fields in pages", async () => { + const RELATION_FIELD_NAME = "related"; + const BLUE_ENTRY_NAME_FIELD_VALUE = "blue entry"; + + const redCollectionOptions = { + ...options, + collectionName: `red_${randomUUID().replace(/-/g, "")}` + }; + + const blueCollectionOptions = { + ...options, + collectionName: `blue_${randomUUID().replace(/-/g, "")}` + }; + + const testOptions = { + ...options, + collectionName: redCollectionOptions.collectionName, + expand: [RELATION_FIELD_NAME] + }; + + const blueCollection = await insertCollection( + [ + { + name: "name", + type: "text" + } + ], + blueCollectionOptions, + superuserToken + ); + + await insertCollection( + [ + { + name: RELATION_FIELD_NAME, + type: "relation", + collectionId: blueCollection.id + } + ], + redCollectionOptions, + superuserToken + ); + + const blueEntry = await insertEntry( + { name: BLUE_ENTRY_NAME_FIELD_VALUE }, + blueCollectionOptions, + superuserToken + ); + + await insertEntry( + { [RELATION_FIELD_NAME]: blueEntry.id }, + redCollectionOptions, + superuserToken + ); + + await loadEntries(testOptions, context, superuserToken, undefined); + + const entryFromCall = parsedEntrySpy!.mock.calls[0][0]; + const relatedEntry = entryFromCall.expand?.related as PocketBaseEntry; + + // Ensure expand and related exist and are of expected type + expect(relatedEntry?.name).toBe(BLUE_ENTRY_NAME_FIELD_VALUE); + + await deleteCollection(redCollectionOptions, superuserToken); + await deleteCollection(blueCollectionOptions, superuserToken); + }); + describe("incremental updates", () => { test("should fetch all entries when updatedField is missing", async () => { const lastModified = new Date(Date.now() - DAY).toISOString(); await loadEntries(options, context, superuserToken, lastModified); - expect(parseEntry).toHaveBeenCalledOnce(); + expect(parsedEntrySpy).toHaveBeenCalledOnce(); }); test("should fetch updated entries", async () => { @@ -139,7 +210,7 @@ describe("loadEntries", () => { await loadEntries(testOptions, context, superuserToken, lastModified); - expect(parseEntry).toHaveBeenCalledOnce(); + expect(parsedEntrySpy).toHaveBeenCalledOnce(); }); test("should do nothing without updated entries", async () => { @@ -148,7 +219,7 @@ describe("loadEntries", () => { await loadEntries(testOptions, context, superuserToken, lastModified); - expect(parseEntry).not.toHaveBeenCalled(); + expect(parsedEntrySpy).not.toHaveBeenCalled(); }); test("should not fetch updated entries excluded from filter", async () => { @@ -161,7 +232,7 @@ describe("loadEntries", () => { await loadEntries(testOptions, context, superuserToken, lastModified); - expect(parseEntry).not.toHaveBeenCalled(); + expect(parsedEntrySpy).not.toHaveBeenCalled(); }); }); diff --git a/test/schema/generate-schema.e2e-spec.ts b/test/schema/generate-schema.e2e-spec.ts index fc390c9..e99be78 100644 --- a/test/schema/generate-schema.e2e-spec.ts +++ b/test/schema/generate-schema.e2e-spec.ts @@ -1,10 +1,13 @@ import type { ZodObject, ZodSchema } from "astro/zod"; +import { randomUUID } from "crypto"; import { afterEach, assert, beforeAll, describe, expect, it, vi } from "vitest"; import { generateSchema } from "../../src/schema/generate-schema"; import { transformFileUrl } from "../../src/schema/transform-files"; import { getSuperuserToken } from "../../src/utils/get-superuser-token"; import { checkE2eConnection } from "../_mocks/check-e2e-connection"; import { createLoaderOptions } from "../_mocks/create-loader-options"; +import { deleteCollection } from "../_mocks/delete-collection"; +import { insertCollection } from "../_mocks/insert-collection"; describe("generateSchema", () => { const options = createLoaderOptions({ collectionName: "_superusers" }); @@ -63,7 +66,8 @@ describe("generateSchema", () => { "emailVisibility", "verified", "created", - "updated" + "updated", + "expand" ]); }); @@ -85,7 +89,8 @@ describe("generateSchema", () => { "emailVisibility", "verified", "created", - "updated" + "updated", + "expand" ]); }); }); @@ -248,4 +253,77 @@ describe("generateSchema", () => { ) }); }); + + describe("expand field", async () => { + it("the related fields schema is provided for expanded fields", async () => { + const RELATION_FIELD_NAME = "related"; + + const redCollectionOptions = { + ...options, + collectionName: `red_${randomUUID().replace(/-/g, "")}` + }; + + const blueCollectionOptions = { + ...options, + collectionName: `blue_${randomUUID().replace(/-/g, "")}` + }; + + const blueCollection = await insertCollection( + [ + { + name: "name", + type: "text" + } + ], + blueCollectionOptions, + token + ); + + await insertCollection( + [ + { + name: RELATION_FIELD_NAME, + type: "relation", + collectionId: blueCollection.id, + maxSelect: 999 + } + ], + redCollectionOptions, + token + ); + + const testOptions = { + ...options, + collectionName: redCollectionOptions.collectionName, + expand: [RELATION_FIELD_NAME] + }; + const schema = (await generateSchema(testOptions, token)) as ZodObject< + Record> + >; + + const expandSchema = schema.shape.expand; + + const validArrayExpand = { + related: [ + { + collectionId: blueCollection.id, + collectionName: blueCollection.name, + id: "test", + name: "Blue Entry" + }, + { + collectionId: blueCollection.id, + collectionName: blueCollection.name, + id: "test", + name: "Blue Entry" + } + ] + }; + + expect(() => expandSchema.parse(validArrayExpand)).not.toThrow(); + + await deleteCollection(redCollectionOptions, token); + await deleteCollection(blueCollectionOptions, token); + }); + }); }); diff --git a/test/schema/parse-schema.spec.ts b/test/schema/parse-schema.spec.ts index c079ca5..9f1c53c 100644 --- a/test/schema/parse-schema.spec.ts +++ b/test/schema/parse-schema.spec.ts @@ -1,12 +1,13 @@ import { z } from "astro/zod"; import { describe, expect, test } from "vitest"; import { parseSchema } from "../../src/schema/parse-schema"; -import type { PocketBaseCollection } from "../../src/types/pocketbase-schema.type"; +import type { PocketBaseCollection } from "../../src/types/pocketbase-collection.type"; describe("parseSchema", () => { describe("number", () => { test("should parse number fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "numberCollection", type: "base", fields: [{ name: "age", type: "number", required: true, hidden: false }] @@ -25,6 +26,7 @@ describe("parseSchema", () => { test("should parse optional number fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "numberCollection", type: "base", fields: [ @@ -44,6 +46,7 @@ describe("parseSchema", () => { test("should parse optional number fields correctly with improved types", () => { const collection: PocketBaseCollection = { + id: "0", name: "numberCollection", type: "base", fields: [ @@ -65,6 +68,7 @@ describe("parseSchema", () => { describe("boolean", () => { test("should parse boolean fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "booleanCollection", type: "base", fields: [ @@ -85,6 +89,7 @@ describe("parseSchema", () => { test("should parse optional boolean fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "booleanCollection", type: "base", fields: [ @@ -104,6 +109,7 @@ describe("parseSchema", () => { test("should parse optional boolean fields correctly with improved types", () => { const collection: PocketBaseCollection = { + id: "0", name: "booleanCollection", type: "base", fields: [ @@ -125,6 +131,7 @@ describe("parseSchema", () => { describe("date", () => { test("should parse date fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "dateCollection", type: "base", fields: [ @@ -147,6 +154,7 @@ describe("parseSchema", () => { test("should parse optional date fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "dateCollection", type: "base", fields: [ @@ -168,6 +176,7 @@ describe("parseSchema", () => { describe("autodate", () => { test("should parse autodate fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "dateCollection", type: "base", fields: [ @@ -195,6 +204,7 @@ describe("parseSchema", () => { test("should parse optional autodate fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "dateCollection", type: "base", fields: [ @@ -219,6 +229,7 @@ describe("parseSchema", () => { test("should parse autodate fields with onCreate correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "dateCollection", type: "base", fields: [ @@ -246,6 +257,7 @@ describe("parseSchema", () => { describe("geoPoint", () => { test("should parse geoPoint fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "geoPointCollection", type: "base", fields: [ @@ -274,6 +286,7 @@ describe("parseSchema", () => { test("should parse optional geoPoint fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "geoPointCollection", type: "base", fields: [ @@ -300,6 +313,7 @@ describe("parseSchema", () => { describe("select", () => { test("should parse select fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "selectCollection", type: "base", fields: [ @@ -326,6 +340,7 @@ describe("parseSchema", () => { test("should throw an error if no values are defined", () => { const collection: PocketBaseCollection = { + id: "0", name: "selectCollection", type: "base", fields: [ @@ -343,6 +358,7 @@ describe("parseSchema", () => { test("should parse select fields with multiple values correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "selectCollection", type: "base", fields: [ @@ -372,6 +388,7 @@ describe("parseSchema", () => { test("should parse optional select fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "selectCollection", type: "base", fields: [ @@ -400,6 +417,7 @@ describe("parseSchema", () => { describe("relation", () => { test("should parse relation fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "relationCollection", type: "base", fields: [ @@ -425,6 +443,7 @@ describe("parseSchema", () => { test("should parse relation fields with multiple values correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "relationCollection", type: "base", fields: [ @@ -451,6 +470,7 @@ describe("parseSchema", () => { test("should parse optional relation fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "relationCollection", type: "base", fields: [ @@ -478,6 +498,7 @@ describe("parseSchema", () => { describe("file", () => { test("should parse file fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "fileCollection", type: "base", fields: [ @@ -503,6 +524,7 @@ describe("parseSchema", () => { test("should parse file fields with multiple values correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "fileCollection", type: "base", fields: [ @@ -536,6 +558,7 @@ describe("parseSchema", () => { test("should parse optional file fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "fileCollection", type: "base", fields: [ @@ -563,6 +586,7 @@ describe("parseSchema", () => { describe("json", () => { test("should parse json fields with custom schema correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "jsonCollection", type: "base", fields: [ @@ -601,6 +625,7 @@ describe("parseSchema", () => { test("should parse json fields without custom schema correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "jsonCollection", type: "base", fields: [ @@ -631,6 +656,7 @@ describe("parseSchema", () => { test("should parse optional json fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "jsonCollection", type: "base", fields: [ @@ -660,6 +686,7 @@ describe("parseSchema", () => { describe("text", () => { test("should parse text fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "stringCollection", type: "base", fields: [{ name: "name", type: "text", required: true, hidden: false }] @@ -678,6 +705,7 @@ describe("parseSchema", () => { test("should parse optional text fields correctly", () => { const collection: PocketBaseCollection = { + id: "0", name: "stringCollection", type: "base", fields: [{ name: "name", type: "text", required: false, hidden: false }]