update demendecies and update types generation

This commit is contained in:
2025-10-05 18:10:08 +02:00
parent e895487743
commit ec9ef486c4
7 changed files with 318 additions and 230 deletions

View File

@@ -1,13 +1,16 @@
import { sortBy } from "es-toolkit";
import type { CollectionModel, SchemaField } from "pocketbase";
import type { CollectionModel, CollectionField } from "pocketbase";
import type { GenerateOpts } from "./server/utils.ts";
export function stringifyContent(collections: CollectionModel[], opts: GenerateOpts) {
function getCollectionSelectFields() {
return collections.flatMap((collection) =>
collection.schema
.filter((field) => field.type === "select")
.map((field) => ({ name: opts.nameEnumField(collection.name, field.name), values: (field.options.values ?? []) as string[] })),
collection.fields
.filter((field: CollectionField) => field.type === "select")
.map((field: CollectionField) => ({
name: opts.nameEnumField(collection.name, field.name),
values: ((field as any).options?.values ?? []) as string[],
})),
);
}
@@ -19,64 +22,111 @@ export function stringifyContent(collections: CollectionModel[], opts: GenerateO
return `export const ${valuesName} = [\n\t${values.map((value) => `"${value}"`).join(",\n\t")},\n] as const;\nexport const ${schemaName} = z.enum(${valuesName});\nexport type ${typeName} = z.infer<typeof ${schemaName}>;\nexport const ${enumName} = ${schemaName}.enum;`;
}
function stringifyRecord({ name, schema }: CollectionModel) {
function stringifyRecord({ name, fields }: CollectionModel) {
const schemaName = opts.nameRecordSchema(name);
const typeName = opts.nameRecordType(name);
const fields = sortBy(schema, ["name"]).map((field) => stringifyField(field, name));
return `export const ${schemaName} = z.object({\n\t...RecordModel.omit({ expand: true }).shape,\n\tcollectionName: z.literal("${name}"),\n\t${fields.join(",\n\t")},\n});\nexport type ${typeName} = z.infer<typeof ${schemaName}>;`;
// Filter out system fields that are already inherited from RecordModel or should be omitted
const systemFields = new Set([
"id",
"created",
"updated",
"collectionId",
"collectionName",
"expand", // inherited from BaseModel/RecordModel
"password",
"tokenKey", // should not be in response schema
]);
const customFields = fields.filter((field) => !systemFields.has(field.name));
const fieldStrings = sortBy(customFields, ["name"]).map((field) => stringifyField(field, name));
return `export const ${schemaName} = z.object({\n\t...RecordModel.omit({ expand: true }).shape,\n\tcollectionName: z.literal("${name}"),\n\t${fieldStrings.join(",\n\t")},\n});\nexport type ${typeName} = z.infer<typeof ${schemaName}>;`;
}
function stringifyField(field: SchemaField, collectionName: string) {
let schema: string | undefined;
if (field.type === "bool") schema = stringifyBoolField(field);
else if (field.type === "date") schema = stringifyDateField(field);
else if (field.type === "editor") schema = stringifyEditorField(field);
else if (field.type === "email") schema = stringifyEmailField(field);
else if (field.type === "file") schema = stringifyFileField(field);
else if (field.type === "json") schema = stringifyJsonField(field);
else if (field.type === "number") schema = stringifyNumberField(field);
else if (field.type === "relation") schema = stringifyRelationField(field);
else if (field.type === "select") schema = stringifySelectField(field, collectionName);
else if (field.type === "text") schema = stringifyTextField(field);
else if (field.type === "url") schema = stringifyUrlField(field);
// TODO: manage unknown field type
return `${field.name}: ${schema}${field.required ? "" : ".optional()"}`;
function stringifyField(field: CollectionField, collectionName: string) {
let schema: string;
switch (field.type) {
case "bool":
schema = stringifyBoolField(field);
break;
case "date":
schema = stringifyDateField(field);
break;
case "editor":
schema = stringifyEditorField(field);
break;
case "email":
schema = stringifyEmailField(field);
break;
case "file":
schema = stringifyFileField(field);
break;
case "json":
schema = stringifyJsonField(field);
break;
case "number":
schema = stringifyNumberField(field);
break;
case "relation":
schema = stringifyRelationField(field);
break;
case "select":
schema = stringifySelectField(field, collectionName);
break;
case "text":
schema = stringifyTextField(field);
break;
case "url":
schema = stringifyUrlField(field);
break;
default:
console.warn(`Unknown field type "${field.type}" for field "${field.name}". Using z.any() as fallback.`);
schema = "z.any()";
break;
}
return `${field.name}: ${schema}${(field as any).required ? "" : ".optional()"}`;
}
function stringifyBoolField(_: SchemaField) {
function stringifyBoolField(_: CollectionField) {
return "z.boolean()";
}
function stringifyDateField(_field: SchemaField) {
function stringifyDateField(_field: CollectionField) {
// TODO: implement min and max
return "z.string().pipe(z.coerce.date())";
}
function stringifyEditorField(_field: SchemaField) {
function stringifyEditorField(_field: CollectionField) {
// TODO: implement convertUrls
return "z.string()";
}
function stringifyEmailField(_field: SchemaField) {
function stringifyEmailField(_field: CollectionField) {
// TODO: implement exceptDomains and onlyDomains
return "z.string().email()";
}
function stringifyFileField({ options: { maxSelect } }: SchemaField) {
function stringifyFileField(field: CollectionField) {
const maxSelect = (field as any).options?.maxSelect;
// TODO: implement maxSize, mimeTypes, protected, thumbs
return `z.string()${maxSelect === 1 ? "" : `.array().max(${maxSelect})`}`;
return `z.string()${maxSelect === 1 ? "" : `.array()${maxSelect ? `.max(${maxSelect})` : ""}`}`;
}
function stringifyJsonField(_field: SchemaField) {
function stringifyJsonField(_field: CollectionField) {
// TODO: implement maxSize and json schema
return "z.any()";
}
function stringifyNumberField({ options: { max, min, noDecimal } }: SchemaField) {
function stringifyNumberField(field: CollectionField) {
const options = (field as any).options || {};
const { max, min, noDecimal } = options;
return `z.number()${noDecimal ? ".int()" : ""}${min ? `.min(${min})` : ""}${max ? `.max(${max})` : ""}`;
}
function stringifyRelationField({ options, required }: SchemaField) {
function stringifyRelationField(field: CollectionField) {
const options = (field as any).options || {};
const required = (field as any).required;
const { maxSelect, minSelect } = options;
// TODO: implement cascadeDelete, displayFields
const min = minSelect ? `.min(${minSelect})` : "";
@@ -86,17 +136,20 @@ export function stringifyContent(collections: CollectionModel[], opts: GenerateO
return `z.string()${isOptional}${multiple}`;
}
function stringifySelectField({ name, options: { maxSelect } }: SchemaField, collectionName: string) {
function stringifySelectField(field: CollectionField, collectionName: string) {
const maxSelect = (field as any).options?.maxSelect;
// TODO: implement values
return `${opts.nameEnumSchema(opts.nameEnumField(collectionName, name))}${maxSelect === 1 ? "" : `.array().max(${maxSelect})`}`;
return `${opts.nameEnumSchema(opts.nameEnumField(collectionName, field.name))}${maxSelect === 1 ? "" : `.array().max(${maxSelect})`}`;
}
function stringifyTextField({ options: { max, min } }: SchemaField) {
function stringifyTextField(field: CollectionField) {
const options = (field as any).options || {};
const { max, min } = options;
// TODO: implement pattern
return `z.string()${min ? `.min(${min})` : ""}${max ? `.max(${max})` : ""}`;
}
function stringifyUrlField(_field: SchemaField) {
function stringifyUrlField(_field: CollectionField) {
// TODO: implement exceptDomains and onlyDomains
return "z.string().url()";
}

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env node
#!/usr/bin/env bun
import { Config, type ResolvedConfig } from "../config.ts";
import pkg from "../../package.json" with { type: "json" };