Add JSON field parser with flexible type transformation
This commit is contained in:
		| @@ -33,6 +33,41 @@ export const RecordModel = z.object({ | |||||||
| }); | }); | ||||||
| export type RecordModel = z.infer<typeof RecordModel>; | export type RecordModel = z.infer<typeof RecordModel>; | ||||||
|  |  | ||||||
|  | /******* JSON FIELD *******/ | ||||||
|  | export const pbJsonField = (maxSizeInBytes: number = 1048576) => { | ||||||
|  |   const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]); | ||||||
|  |   const jsonSchema: z.ZodType<any> = 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 *******/ | ||||||
| @@_RECORDS_@@ | @@_RECORDS_@@ | ||||||
|  |  | ||||||
|   | |||||||
| @@ -69,18 +69,26 @@ export function stringifyContent(collections: CollectionModel[], opts: GenerateO | |||||||
|         break; |         break; | ||||||
|  |  | ||||||
|       case "file": |       case "file": | ||||||
|         // if (collectionName === "tests") console.log(`${collectionName}:`, field); |  | ||||||
|         const maxSelectFile = field.maxSelect; |  | ||||||
|         // TODO: implement maxSize, mimeTypes, protected, thumbs |         // 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; |         break; | ||||||
|  |  | ||||||
|       case "json": |       case "json": | ||||||
|         // TODO: implement maxSize and json schema |         schema = field.maxSize > 0 ? `pbJsonField(${field.maxSize})` : "pbJsonField()"; | ||||||
|         schema = "z.any()"; |  | ||||||
|         break; |         break; | ||||||
|  |  | ||||||
|       case "number": |       case "number": | ||||||
|  |         // if (collectionName === "testFiles") console.log(`${collectionName}:`, field); | ||||||
|         const maxNumber = field.maxNumber; |         const maxNumber = field.maxNumber; | ||||||
|         const minNumber = field.minNumber; |         const minNumber = field.minNumber; | ||||||
|         const noDecimal = field.noDecimal; |         const noDecimal = field.noDecimal; | ||||||
| @@ -119,7 +127,7 @@ export function stringifyContent(collections: CollectionModel[], opts: GenerateO | |||||||
|         break; |         break; | ||||||
|  |  | ||||||
|       case "geoPoint": |       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; |         break; | ||||||
|  |  | ||||||
|       default: |       default: | ||||||
| @@ -136,7 +144,7 @@ export function stringifyContent(collections: CollectionModel[], opts: GenerateO | |||||||
|     if (!domains?.length) return ""; |     if (!domains?.length) return ""; | ||||||
|     const domainsList = domains.map((domain) => `"${domain}"`).join(", "); |     const domainsList = domains.map((domain) => `"${domain}"`).join(", "); | ||||||
|     const messageType = isWhitelist ? "isn't one of the allowed ones" : "is one of the disallowed ones"; |     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 { |   return { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user