Files
bknd/app/src/data/data-schema.ts
T
2025-08-12 22:13:09 +02:00

103 lines
3.5 KiB
TypeScript

import { objectTransform } from "bknd/utils";
import { MediaField, mediaFieldConfigSchema } from "../media/MediaField";
import { FieldClassMap } from "data/fields";
import { RelationClassMap, RelationFieldClassMap } from "data/relations";
import { entityConfigSchema, entityTypes } from "data/entities";
import { primaryFieldTypes, baseFieldConfigSchema } from "./fields";
import { s } from "bknd/utils";
import { $object, $record } from "modules/mcp";
export const FIELDS = {
...FieldClassMap,
...RelationFieldClassMap,
media: { schema: mediaFieldConfigSchema, field: MediaField },
};
export const FIELD_TYPES = Object.keys(FIELDS);
export type FieldType = keyof typeof FIELDS;
export const RELATIONS = RelationClassMap;
export const fieldsSchemaObject = objectTransform(FIELDS, (field, name) => {
return s.strictObject(
{
name: s.string().optional(), // @todo: verify, old schema wasn't strict (req in UI)
type: s.literal(name),
config: field.schema.optional(),
},
{
title: name,
},
);
});
export const fieldsSchema = s.anyOf(Object.values(fieldsSchemaObject));
export const entityFields = s.record(fieldsSchema, { default: {} });
export type TAppDataField = s.Static<typeof fieldsSchema>;
export type TAppDataEntityFields = s.Static<typeof entityFields>;
export const entitiesSchema = s.strictObject({
name: s.string().optional(), // @todo: verify, old schema wasn't strict (req in UI)
type: s.string({ enum: entityTypes, default: "regular" }).optional(),
config: entityConfigSchema.optional(),
fields: entityFields.optional(),
});
export type TAppDataEntity = s.Static<typeof entitiesSchema>;
export const simpleEntitiesSchema = s.strictObject({
type: s.string({ enum: entityTypes, default: "regular" }).optional(),
config: entityConfigSchema.optional(),
fields: s
.record(
s.object({
type: s.anyOf([s.string({ enum: FIELD_TYPES }), s.string()]),
config: baseFieldConfigSchema.optional(),
}),
{ default: {} },
)
.optional(),
});
export const relationsSchema = Object.entries(RelationClassMap).map(([name, relationClass]) => {
return s.strictObject(
{
type: s.literal(name),
source: s.string(),
target: s.string(),
config: relationClass.schema.optional(),
},
{
title: name,
},
);
});
export type TAppDataRelation = s.Static<(typeof relationsSchema)[number]>;
export const indicesSchema = s.strictObject({
entity: s.string(),
fields: s.array(s.string(), { minItems: 1 }),
unique: s.boolean({ default: false }).optional(),
});
export const dataConfigSchema = $object("config_data", {
basepath: s.string({ default: "/api/data" }).optional(),
default_primary_format: s.string({ enum: primaryFieldTypes, default: "integer" }).optional(),
entities: $record("config_data_entities", entitiesSchema, { default: {} }).optional(),
relations: $record(
"config_data_relations",
s.anyOf(relationsSchema),
{
default: {},
},
s.strictObject({
type: s.string({ enum: Object.keys(RelationClassMap) }),
source: s.string(),
target: s.string(),
config: s.object({}).optional(),
}),
).optional(),
indices: $record("config_data_indices", indicesSchema, {
default: {},
mcp: { update: false },
}).optional(),
}).strict();
export type AppDataConfig = s.Static<typeof dataConfigSchema>;