@sensinum/astro-strapi-loader
Version:
Astro loader for Strapi CMS
162 lines (161 loc) • 6.81 kB
JavaScript
import { z } from "zod";
export class StrapiSchemaGenerator {
constructor(contentTypes, strict = false) {
this.contentTypes = [];
this.strict = false;
this.contentTypes = contentTypes;
this.strict = strict;
}
generateAttributeSchema(attribute, pupulatedRelations = []) {
const ref = this;
switch (attribute.type) {
case "string":
let schema = z.string();
if (attribute.required)
schema = schema.min(1);
if (attribute.minLength)
schema = schema.min(attribute.minLength);
if (attribute.maxLength)
schema = schema.max(attribute.maxLength);
if (attribute.regex)
schema = schema.regex(new RegExp(attribute.regex));
return schema;
case "text":
case "richtext":
return z.string();
case "email":
return z.string().email();
case "password":
return z.string();
case "integer":
let intSchema = z.number().int();
if (attribute.required)
intSchema = intSchema.min(1);
if (attribute.min)
intSchema = intSchema.min(attribute.min);
if (attribute.max)
intSchema = intSchema.max(attribute.max);
return intSchema;
case "biginteger":
case "float":
case "decimal":
let numberSchema = z.number();
if (attribute.required)
numberSchema = numberSchema.min(0);
if (attribute.min)
numberSchema = numberSchema.min(attribute.min);
if (attribute.max)
numberSchema = numberSchema.max(attribute.max);
return numberSchema;
case "boolean":
return z.boolean();
case "date":
case "datetime":
return z.string();
case "time":
return z.string();
case "timestamp":
return z.number();
case "json":
return z.any();
case "enumeration":
if (!attribute.enum)
throw new Error("Enumeration type requires enum values");
return z.enum(attribute.enum).nullable();
case "media":
return z.object({
name: z.string(),
alternativeText: z.string().optional().nullable(),
caption: z.string().optional().nullable(),
width: z.number().optional(),
height: z.number().optional(),
formats: z.any().nullable(),
hash: z.string(),
ext: z.string().optional(),
mime: z.string(),
size: z.number(),
url: z.string(),
previewUrl: z.string().optional().nullable(),
provider: z.string(),
createdAt: z.string(),
updatedAt: z.string(),
});
case "relation":
if (!attribute.relation)
throw new Error("Relation type requires relation target");
const targetType = ref.contentTypes.find((contentType) => contentType.apiID === attribute.target ||
contentType.uid === attribute.target);
if (!targetType)
throw new Error(`Target type ${attribute.relation} not found`);
if (attribute.targetAttribute && pupulatedRelations.includes(attribute.targetAttribute)) {
return null;
}
const relationSchema = ref.generateContentTypeSchema(targetType.schema, attribute.targetAttribute ? [...pupulatedRelations, attribute.targetAttribute] : pupulatedRelations);
switch (attribute.relation) {
case "oneToOne":
case "manyToOne":
return relationSchema;
case "oneToMany":
case "manyToMany":
return z.array(relationSchema);
default:
throw new Error(`Unsupported relation type: ${attribute.relationType}`);
}
case "component":
if (!attribute.component)
throw new Error("Component type requires component name");
// TODO: Implement component schema generation
return z.any();
case "dynamiczone":
// TODO: Implement dynamiczone schema generation
return z.any();
default:
return z.any();
}
}
generateContentTypeSchema(contentTypeSchema, pupulatedRelations) {
const ref = this;
const shape = Object.entries(contentTypeSchema.attributes).reduce((acc, [key, attribute]) => {
try {
const schema = ref.generateAttributeSchema(attribute, pupulatedRelations);
if (schema) {
return {
...acc,
[key]: ref.strict && attribute.required
? schema
: schema.nullable().optional(),
};
}
return acc;
}
catch (error) {
console.warn("Error generating attribute schema", error);
return acc;
}
}, {
id: z.number().optional(),
documentId: z.string().optional(),
createdAt: z.string().datetime().optional(),
updatedAt: z.string().datetime().optional(),
});
return z.object(shape);
}
generateSchema(contentTypeName) {
const contentType = this.contentTypes.find((contentType) => contentType.apiID === contentTypeName);
if (!contentType) {
throw new Error(`Content type ${contentTypeName} not found`);
}
return this.generateContentTypeSchema(contentType.schema);
}
generateAllSchemas() {
const ref = this;
const schemas = {};
ref.contentTypes.forEach((contentType) => {
const { schema } = contentType;
const { singularName, pluralName, kind } = schema;
const collectionName = kind === "collectionType" ? pluralName : singularName;
schemas[collectionName] = ref.generateContentTypeSchema(schema);
});
return schemas;
}
}