From d4dfed96034a65364ccecfc8cceecba408ea7b04 Mon Sep 17 00:00:00 2001 From: GarandPLG Date: Fri, 10 Oct 2025 08:52:43 +0200 Subject: [PATCH] Add JSON field parser with flexible type transformation --- assets/stubs/index.ts | 35 +++++++++++++++++++++++++++++++++++ src/content.ts | 22 +++++++++++++++------- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/assets/stubs/index.ts b/assets/stubs/index.ts index 8d6e3f9..4c5ed0e 100644 --- a/assets/stubs/index.ts +++ b/assets/stubs/index.ts @@ -33,6 +33,41 @@ export const RecordModel = z.object({ }); export type RecordModel = z.infer; +/******* JSON FIELD *******/ +export const pbJsonField = (maxSizeInBytes: number = 1048576) => { + const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]); + const jsonSchema: z.ZodType = z.lazy(() => + z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]) + ); + + const stringTransform = z.string() + .max(maxSizeInBytes, `JSON field cannot exceed ${maxSizeInBytes} bytes`) + .transform((val) => { + if (val === "true") return true; + if (val === "false") return false; + if (val === "null") return null; + + if ((val.startsWith('[') && val.endsWith(']'))||(val.startsWith('{') && val.endsWith('}'))) + try { + return JSON.parse(val); + } catch { + return val; + } + + const num = Number(val); + if (!isNaN(num) && isFinite(num) && val.trim() !== '') + return num; + + if (val.startsWith('"') && val.endsWith('"')) + return val.slice(1, -1); + + return val; + }); + + return z.union([jsonSchema, stringTransform]); +}; + + /******* RECORDS *******/ @@_RECORDS_@@ diff --git a/src/content.ts b/src/content.ts index a31ab25..8f55c37 100644 --- a/src/content.ts +++ b/src/content.ts @@ -69,18 +69,26 @@ export function stringifyContent(collections: CollectionModel[], opts: GenerateO break; case "file": - // if (collectionName === "tests") console.log(`${collectionName}:`, field); - const maxSelectFile = field.maxSelect; // TODO: implement maxSize, mimeTypes, protected, thumbs - schema = `z.string()${maxSelectFile === 1 ? "" : `.array()${maxSelectFile ? `.max(${maxSelectFile})` : ""}`}`; + // NOTE: PocketBase API returns only filenames in responses, limiting validation capabilities. + // Full file validation (size, MIME types, etc.) should be implemented at the create record endpoint. + // const mimeTypesArray: string[] = field.mimeTypes || []; + // const protectedFile: boolean = field.protected; + // const thumbsFileArray: string[] = field.thumbs || []; + // const maxSizeFile: number = field.maxSize; + const maxSelectFile: number = field.maxSelect; + const fileFieldMaxSelect = maxSelectFile ? `.max(${maxSelectFile})` : ""; + const fileFieldTypeArray = maxSelectFile === 1 ? "" : `.array()${fileFieldMaxSelect}`; + + schema = `z.string()${fileFieldTypeArray}`; break; case "json": - // TODO: implement maxSize and json schema - schema = "z.any()"; + schema = field.maxSize > 0 ? `pbJsonField(${field.maxSize})` : "pbJsonField()"; break; case "number": + // if (collectionName === "testFiles") console.log(`${collectionName}:`, field); const maxNumber = field.maxNumber; const minNumber = field.minNumber; const noDecimal = field.noDecimal; @@ -119,7 +127,7 @@ export function stringifyContent(collections: CollectionModel[], opts: GenerateO break; case "geoPoint": - schema = "z.object({ lat: z.number().min(-90).max(90), lng: z.number().min(-180).max(180) })"; + schema = "z.object({ lat: z.number().min(-90).max(90), lon: z.number().min(-180).max(180) })"; break; default: @@ -136,7 +144,7 @@ export function stringifyContent(collections: CollectionModel[], opts: GenerateO if (!domains?.length) return ""; const domainsList = domains.map((domain) => `"${domain}"`).join(", "); const messageType = isWhitelist ? "isn't one of the allowed ones" : "is one of the disallowed ones"; - return `.refine((email) => { const domain = email.split("@")[1]; return domain && ${isWhitelist ? "" : "!"}[${domainsList}].includes(domain); }, { message: "Invalid email, email domain ${messageType}" })`; + return `.refine((email) => { const domain = email.split("@")[1]; const domainsArray = [${domainsList}]; return domain && ${isWhitelist ? "" : "!"}domainsArray.includes(domain); }, { message: "Invalid email, email domain ${messageType}" })`; }; return {