mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
Enhanced OneToOneRelation to allow source to create target with a mapping field and added a limit. Updated PolymorphicRelation to return a TextField for entity_id instead of NumberField, improving type consistency.
121 lines
4.2 KiB
TypeScript
121 lines
4.2 KiB
TypeScript
import type { ExpressionBuilder } from "kysely";
|
|
import type { Entity, EntityManager } from "../entities";
|
|
import { NumberField, TextField } from "../fields";
|
|
import type { RepoQuery } from "../server/query";
|
|
import { EntityRelation, type KyselyJsonFrom, type KyselyQueryBuilder } from "./EntityRelation";
|
|
import { EntityRelationAnchor } from "./EntityRelationAnchor";
|
|
import { type RelationType, RelationTypes } from "./relation-types";
|
|
import { s } from "bknd/utils";
|
|
import type { PrimaryFieldType } from "bknd";
|
|
|
|
export type PolymorphicRelationConfig = s.Static<typeof PolymorphicRelation.schema>;
|
|
|
|
// @todo: what about cascades?
|
|
export class PolymorphicRelation extends EntityRelation<typeof PolymorphicRelation.schema> {
|
|
static override schema = s.strictObject({
|
|
targetCardinality: s.number().optional(),
|
|
...EntityRelation.schema.properties,
|
|
});
|
|
|
|
constructor(source: Entity, target: Entity, config: Partial<PolymorphicRelationConfig> = {}) {
|
|
const mappedBy = config.mappedBy || target.name;
|
|
const inversedBy = config.inversedBy || source.name;
|
|
|
|
// if target can be multiple, allow it. otherwise unlimited
|
|
const targetCardinality =
|
|
typeof config.targetCardinality === "number" && config.targetCardinality > 0
|
|
? config.targetCardinality
|
|
: undefined;
|
|
const sourceAnchor = new EntityRelationAnchor(source, inversedBy, 1);
|
|
const targetAnchor = new EntityRelationAnchor(target, mappedBy, targetCardinality);
|
|
super(sourceAnchor, targetAnchor, config);
|
|
|
|
this.directions = ["source"];
|
|
}
|
|
|
|
type(): RelationType {
|
|
return RelationTypes.Polymorphic;
|
|
}
|
|
|
|
private queryInfo(entity: Entity) {
|
|
const other = this.other(entity);
|
|
const whereLhs = `${other.entity.name}.${this.getReferenceField().name}`;
|
|
const reference = `${entity.name}.${this.config.mappedBy}`;
|
|
|
|
// this is used for "getReferenceQuery"
|
|
const reference_other = `${other.entity.name}.${this.config.mappedBy}`;
|
|
|
|
const entityRef = `${entity.name}.${entity.getPrimaryField().name}`;
|
|
const otherRef = `${other.entity.name}.${this.getEntityIdField().name}`;
|
|
|
|
const groupBy = `${entity.name}.${entity.getPrimaryField().name}`;
|
|
|
|
return {
|
|
other,
|
|
whereLhs,
|
|
reference,
|
|
reference_other,
|
|
entityRef,
|
|
otherRef,
|
|
groupBy,
|
|
};
|
|
}
|
|
|
|
buildJoin(entity: Entity, qb: KyselyQueryBuilder) {
|
|
const { other, whereLhs, reference, entityRef, otherRef, groupBy } = this.queryInfo(entity);
|
|
|
|
return qb
|
|
.innerJoin(other.entity.name, (join) =>
|
|
join.onRef(entityRef, "=", otherRef).on(whereLhs, "=", reference),
|
|
)
|
|
.groupBy(groupBy);
|
|
}
|
|
|
|
override getReferenceQuery(entity: Entity, id: PrimaryFieldType): Partial<RepoQuery> {
|
|
const info = this.queryInfo(entity);
|
|
|
|
return {
|
|
where: {
|
|
[this.getReferenceField().name]: info.reference_other,
|
|
[this.getEntityIdField().name]: id,
|
|
},
|
|
};
|
|
}
|
|
|
|
buildWith(entity: Entity) {
|
|
const { other, whereLhs, reference, entityRef, otherRef } = this.queryInfo(entity);
|
|
|
|
return (eb: ExpressionBuilder<any, any>) =>
|
|
eb
|
|
.selectFrom(other.entity.name)
|
|
.where(whereLhs, "=", reference)
|
|
.whereRef(entityRef, "=", otherRef)
|
|
.$if(other.cardinality === 1, (qb) => qb.limit(1));
|
|
}
|
|
|
|
override isListableFor(entity: Entity): boolean {
|
|
// @todo: only make listable if many? check cardinality
|
|
return this.source.entity.name === entity.name && this.target.cardinality !== 1;
|
|
}
|
|
|
|
getReferenceField(): TextField {
|
|
return new TextField("reference", { hidden: true, fillable: ["create"] });
|
|
}
|
|
|
|
getEntityIdField(): TextField {
|
|
return new TextField("entity_id", { hidden: true, fillable: ["create"] });
|
|
}
|
|
|
|
initialize(em: EntityManager<any>) {
|
|
const referenceField = this.getReferenceField();
|
|
const entityIdField = this.getEntityIdField();
|
|
|
|
if (!this.target.entity.field(referenceField.name)) {
|
|
this.target.entity.addField(referenceField);
|
|
}
|
|
if (!this.target.entity.field(entityIdField.name)) {
|
|
this.target.entity.addField(entityIdField);
|
|
}
|
|
}
|
|
}
|