mirror of
https://github.com/bknd-io/bknd/
synced 2026-08-02 16:16:02 +00:00
610e263477
Updated `RelationField` to dynamically determine the type based on `target_field_type`. Added a test for proper TypeScript generation with text primary fields in system entities.
94 lines
2.8 KiB
TypeScript
94 lines
2.8 KiB
TypeScript
import type { EntityManager } from "../entities";
|
|
import { Field, baseFieldConfigSchema } from "../fields";
|
|
import type { EntityRelation } from "./EntityRelation";
|
|
import type { EntityRelationAnchor } from "./EntityRelationAnchor";
|
|
import type { TFieldTSType } from "data/entities/EntityTypescript";
|
|
import { s } from "bknd/utils";
|
|
|
|
const CASCADES = ["cascade", "set null", "set default", "restrict", "no action"] as const;
|
|
|
|
export const relationFieldConfigSchema = s.strictObject({
|
|
reference: s.string(),
|
|
target: s.string(), // @todo: potentially has to be an instance!
|
|
target_field: s.string({ default: "id" }).optional(),
|
|
target_field_type: s.string({ enum: ["text", "integer"], default: "integer" }).optional(),
|
|
on_delete: s.string({ enum: CASCADES, default: "set null" }).optional(),
|
|
...baseFieldConfigSchema.properties,
|
|
});
|
|
|
|
export type RelationFieldConfig = s.Static<typeof relationFieldConfigSchema>;
|
|
export type RelationFieldBaseConfig = { label?: string };
|
|
|
|
export class RelationField extends Field<RelationFieldConfig> {
|
|
override readonly type = "relation";
|
|
|
|
protected getSchema() {
|
|
return relationFieldConfigSchema;
|
|
}
|
|
|
|
static create(
|
|
relation: EntityRelation,
|
|
target: EntityRelationAnchor,
|
|
config?: RelationFieldBaseConfig,
|
|
) {
|
|
const name = [
|
|
target.reference ?? target.entity.name,
|
|
target.entity.getPrimaryField().name,
|
|
].join("_");
|
|
|
|
return new RelationField(name, {
|
|
...config,
|
|
required: relation.required,
|
|
reference: target.reference,
|
|
target: target.entity.name,
|
|
target_field: target.entity.getPrimaryField().name,
|
|
target_field_type: target.entity.getPrimaryField().fieldType,
|
|
});
|
|
}
|
|
|
|
reference() {
|
|
return this.config.reference;
|
|
}
|
|
|
|
target() {
|
|
return this.config.target;
|
|
}
|
|
|
|
targetField(): string {
|
|
return this.config.target_field!;
|
|
}
|
|
|
|
override schema() {
|
|
return Object.freeze({
|
|
...super.schema()!,
|
|
type: this.config.target_field_type ?? "integer",
|
|
references: `${this.config.target}.${this.config.target_field}`,
|
|
onDelete: this.config.on_delete ?? "set null",
|
|
});
|
|
}
|
|
|
|
override transformRetrieve(value: any): any {
|
|
return value;
|
|
}
|
|
|
|
override async transformPersist(value: any, em: EntityManager<any>): Promise<any> {
|
|
throw new Error("RelationField: This function should not be called");
|
|
}
|
|
|
|
override toJsonSchema() {
|
|
return this.toSchemaWrapIfRequired(
|
|
s.number({
|
|
$ref: `${this.config?.target}#/properties/${this.config?.target_field}`,
|
|
}),
|
|
);
|
|
}
|
|
|
|
override toType(): TFieldTSType {
|
|
const type = this.config.target_field_type === "integer" ? "number" : "string";
|
|
return {
|
|
...super.toType(),
|
|
type,
|
|
};
|
|
}
|
|
}
|