mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-19 13:56:04 +00:00
public commit
This commit is contained in:
130
app/src/data/relations/PolymorphicRelation.ts
Normal file
130
app/src/data/relations/PolymorphicRelation.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { type Static, Type } from "core/utils";
|
||||
import type { Entity, EntityManager } from "../entities";
|
||||
import { NumberField, TextField } from "../fields";
|
||||
import type { RepoQuery } from "../server/data-query-impl";
|
||||
import { EntityRelation, type KyselyJsonFrom, type KyselyQueryBuilder } from "./EntityRelation";
|
||||
import { EntityRelationAnchor } from "./EntityRelationAnchor";
|
||||
import { type RelationType, RelationTypes } from "./relation-types";
|
||||
|
||||
export type PolymorphicRelationConfig = Static<typeof PolymorphicRelation.schema>;
|
||||
|
||||
// @todo: what about cascades?
|
||||
export class PolymorphicRelation extends EntityRelation<typeof PolymorphicRelation.schema> {
|
||||
static override schema = Type.Composite(
|
||||
[
|
||||
EntityRelation.schema,
|
||||
Type.Object({
|
||||
targetCardinality: Type.Optional(Type.Number())
|
||||
})
|
||||
],
|
||||
{
|
||||
additionalProperties: false
|
||||
}
|
||||
);
|
||||
|
||||
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: number): Partial<RepoQuery> {
|
||||
const info = this.queryInfo(entity);
|
||||
|
||||
return {
|
||||
where: {
|
||||
[this.getReferenceField().name]: info.reference_other,
|
||||
[this.getEntityIdField().name]: id
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
buildWith(entity: Entity, qb: KyselyQueryBuilder, jsonFrom: KyselyJsonFrom) {
|
||||
const { other, whereLhs, reference, entityRef, otherRef } = this.queryInfo(entity);
|
||||
const limit = other.cardinality === 1 ? 1 : 5;
|
||||
|
||||
return qb.select((eb) =>
|
||||
jsonFrom(
|
||||
eb
|
||||
.selectFrom(other.entity.name)
|
||||
.select(other.entity.getSelect(other.entity.name))
|
||||
.where(whereLhs, "=", reference)
|
||||
.whereRef(entityRef, "=", otherRef)
|
||||
.limit(limit)
|
||||
).as(other.reference)
|
||||
);
|
||||
}
|
||||
|
||||
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(): NumberField {
|
||||
return new NumberField("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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user