import { IRelationDescriptor, IModelDescriptor, ForwardRefFunction, IRelation, ISelectQueryBuilder, QueryScope } from './interfaces.js'; import { Constructor } from '@spinajs/di'; import { SelectQueryBuilder } from './builders.js'; import type { ModelBase } from './model.js'; import { Orm } from './orm.js'; import { OrmDriver } from './driver.js'; /** * Pure, in-memory set algebra over model collections. Nothing here touches the database — * apply a result with `relation.set(...)` and persist it with `sync()` / `update()` / `save()`. */ export declare class Dataset { /** * Calculates the symmetric difference between the relation data and the provided dataset — * members of either set that are not in the other. In-memory only; persist with `sync()`. * * @param dataset - data to compare * @param callback - function to compare objects, if none provided - primary key value is used */ static diff(dataset: R[], callback?: (a: R, b: R) => boolean): (datasetB: R[], pKey: string[]) => any[]; /** * Calculates the intersection between the relation data and the provided dataset. * In-memory only; persist with `sync()`. * * @param dataset - dataset to compare * @param callback - function to compare models, if not set it is compared by primary key value */ static intersection(dataset: R[], callback?: (a: R, b: R) => boolean): (datasetB: R[], pKey: string[]) => any[]; /** * Calculates the union of the relation data and the provided dataset. Members already * present ( by primary key or comparator ) are kept once, the relation's own instance * winning over the incoming duplicate. Unsaved models compare by reference, so they are * always appended. In-memory only; persist with `sync()`. * * @param dataset - data to add * @param callback - function to compare models, if not set it is compared by primary key value */ static union(dataset: R[], callback?: (a: R, b: R) => boolean): (datasetB: R[], pKey: string[]) => R[]; } /** * Iterable list of populated relation entities * * It allows to add / remove objects to relation */ export declare abstract class Relation, O extends ModelBase, Q extends typeof ModelBase = typeof ModelBase> extends Array implements IRelation { protected Owner: O; protected Relation: IRelationDescriptor; TargetModelDescriptor: IModelDescriptor | null; Populated: boolean; protected Driver: OrmDriver; protected Model: Constructor | ForwardRefFunction; /** * Array methods that derive a new collection ( `splice`, `filter`, `slice`, `concat`, * `map`, … ) construct `new this.constructor[Symbol.species](len)` by default. That would * call this class's constructor with no relation descriptor, and the very next line * dereferences `this.Relation.TargetModel` — so `order.Items.splice(0, 1)` threw * `Cannot read properties of undefined (reading 'TargetModel')`. * * Deriving plain arrays is also the right semantics: a slice of a relation is a list of * models, not a relation with an owner. */ static get [Symbol.species](): ArrayConstructor; constructor(Owner: O, Relation: IRelationDescriptor, objects?: R[]); /** * The owner-side value this relation joins on. * * A relation names exactly ONE source column (`IRelationDescriptor.PrimaryKey`), so a * composite-key owner must contribute only that column's value. `Owner.PrimaryKeyValue` * would be a tuple, which binds an array into a single `?` and fails with * `SQLITE_RANGE: column index out of range`. * * For a single-column key `Relation.PrimaryKey` IS the model's key column, so this returns * exactly what `Owner.PrimaryKeyValue` did. */ protected get OwnerJoinValue(): any; /** * Removes all members matching the predicate. In-memory only — the database changes on the * next `sync()` ( orphan delete ) or `save()` ( orphan policy ). * * @param compare - predicate selecting members to remove */ remove(compare: (a: R) => boolean): R[]; /** * Removes the given model or models, matched by primary key ( an unsaved model, having no * key, is matched by reference ). In-memory only — persist with `sync()` / `save()`. * * @param obj - data to remove */ remove(obj: R | R[]): R[]; /** * Delete all objects from relation ( alias for empty ) */ clear(): Promise; /** * Clears relation data */ empty(): void; /** * Synchronize relation data with db * NOTE: it removes data from db that are not in relation * * @param obj - object to add * @param mode - insert mode */ abstract sync(): Promise; /** * Updates or ads data to relation * It will not delete data from db that are not in relation. It will only update or insert new data. * Only dirty models are updated. */ abstract update(): Promise; /** * Calculates the intersection between this relation and the provided dataset. Pure * computation — apply it with `set()` and persist with `sync()` / `save()`. * * @param dataset - dataset to compare * @param callback - function to compare models, if not set it is compared by primary key value * @returns members present in both sets */ intersection(dataset: R[], callback?: (a: R, b: R) => boolean): R[]; /** * Adds the dataset's members to this relation, skipping members already present ( compared * by primary key, or by the callback ). In-memory only — nothing is written until `sync()`, * `update()` or `save()`. * * @param dataset - data to add * @param callback - function to compare models, if not set it is compared by primary key value */ union(dataset: R[], callback?: (a: R, b: R) => boolean): void; /** * Calculates the symmetric difference between this relation and the dataset — members of * this relation that are not in the dataset, plus members of the dataset that are not in * this relation. Pure computation — apply it with `set()` and persist with `sync()`. * * @param dataset - data to compare * @param callback - function to compare objects, if none provided - primary key value is used */ diff(dataset: R[], callback?: (a: R, b: R) => boolean): R[]; /** * Clears the relation and replaces its members with the new dataset ( or with the result of * a `Dataset.diff` / `Dataset.intersection` / `Dataset.union` closure ). In-memory only — * persist with `sync()` / `update()` / `save()`. * * @param obj - replacement data, or a closure receiving the current members and the primary key columns */ set(obj: R[] | ((data: R[], pKey: string[]) => R[])): void; /** * Populates this relation ( loads all data related to owner of this relation) */ abstract populate(callback?: (this: ISelectQueryBuilder & Q['_queryScopes']) => void): Promise; } export declare class SingleRelation { protected _owner: O; protected model: Constructor | ForwardRefFunction | null; protected Relation: IRelationDescriptor | null; TargetModelDescriptor: IModelDescriptor | null; protected Orm: Orm; Value: R | null | undefined; Populated: boolean; constructor(_owner: O, model: Constructor | ForwardRefFunction | null, Relation: IRelationDescriptor | null, object?: R); /** * Attaches `obj` and persists the owner. One transaction, so the attach and the owner * update cannot half-apply. Nested inside a caller's transaction this takes a savepoint. */ set(obj: R): Promise; /** * Points this relation at `obj` and writes the owner's foreign-key column to match: the * target's join-column value — its primary key unless `@BelongsTo` names another column * ( `Relation.PrimaryKey` ) — or NULL when detaching. No database access. * * The column is what the snapshot records and what the diff compares, so it has to follow * the relation - otherwise a detach would leave column and relation disagreeing and the model * dirty forever. An unsaved target has no key yet, so the column holds whatever that empty key * reads as ( `undefined`, or `null` once `setDefaults()` has filled it from the column default ) * until the unit of work inserts the parent and backfills it; `toSql()` reads the same join * column off `Value` at write time either way. * * @param obj - the related model, or null to clear the relation */ attach(obj: R | null): void; detach(): void; /** * Deletes the related row and clears the owner's foreign key. One transaction: these used * to be two independent statements, so a throw between them left the owner pointing at a * row that no longer exists. */ remove(): Promise; /** * Loads the model this relation points at. * * Queries the target table directly, filtered on the column the relation declares as its * join key ( `Relation.PrimaryKey` ) — the same column `BelongsToRelation.compile()` joins * on for the eager path. It is *not* the target model's own primary key: `@BelongsTo` * accepts an explicit third argument for exactly this case, and the two only coincide * because the decorator defaults one from the other, which is why filtering on the target * PK went unnoticed. * * @param callback - optional callback applied to the target query */ populate(callback?: (this: SelectQueryBuilder) => void): Promise; } export declare class ManyQueryRelationList extends Relation> { remove(_compare: (a: R) => boolean): R[]; remove(_obj: R | R[]): R[]; sync(): Promise; update(): Promise; intersection(_dataset: R[], _callback?: (a: R, b: R) => boolean): R[]; union(_dataset: R[], _callback?: (a: R, b: R) => boolean): void; diff(_dataset: R[], _callback?: (a: R, b: R) => boolean): R[]; set(_obj: R[] | ((data: R[], pKey: string[]) => R[])): void; populate(_callback?: (this: ISelectQueryBuilder & QueryScope) => void): Promise; constructor(owner: O, relation: IRelationDescriptor, objects?: R[]); } export declare class SingleQueryRelation extends SingleRelation { constructor(owner: O, object: R); } export declare class ManyToManyRelationList extends Relation> { protected junctionModelDescriptor: IModelDescriptor | null; constructor(owner: O, relation: IRelationDescriptor, objects?: T[]); /** * Deletes from db data that are not in relation * * @param data relation data * @returns */ protected _dbDiff(data: T[]): Promise; /** * Synchronizes relation data to db * Deletes from db entries that are not in relation and adds entries that are not in db * Sets foreign key to relational data * * One transaction: the junction upserts and the orphan delete used to be independent * statements. Nested inside a caller's transaction this takes a savepoint. */ sync(): Promise; /** * Adds missing rows to the database without deleting anything: unsaved members are * inserted, members without a junction row get one. Existing junction rows are left * untouched. */ update(): Promise; /** * Finds the junction model's relation pointing at `model`, by constructor identity — the * junction's PROPERTY name is the author's choice ( `Order`, `Tag`, … ) and has nothing to * do with the target class name. */ protected junctionRelationFor(model: Constructor | ForwardRefFunction): IRelationDescriptor; /** * The write itself, without a transaction of its own, so `sync()` can share one. * * Unsaved members are inserted first — a junction row written for a model with no primary * key would carry a NULL foreign key. Members that already have a junction row are skipped: * the junction's own primary key is auto-generated, so re-inserting the pair would DUPLICATE * the link rather than upsert it ( which is exactly what repeated `sync()` calls used to do ). */ protected _update(): Promise; populate(callback?: (this: ISelectQueryBuilder & Q['_queryScopes']) => void): Promise; } export declare class OneToManyRelationList extends Relation> { /** * Deletes from db data that are not in relation * * @param data relation data * @returns */ protected _dbDiff(data: T[]): Promise; /** * Populates this relation ( loads all data related to owner of this relation) * * Pushes the rows into THIS list rather than routing through `Owner.attach()`: attach flags * the owner dirty ( a read must not create unsaved changes ), feeds every sibling relation * with the same target model, and drops discriminated subclass rows because their * constructor is not the declared target. Only attach's back-reference wiring is kept. */ populate(callback?: (this: ISelectQueryBuilder & Q['_queryScopes']) => void): Promise; /** * Synchronizes relation data to db * Deletes from db entries that are not in relation and adds entries that are not in db * Sets foreign key to relational data * * Inserts or updates models that are dirty only. * * The whole synchronization is one transaction: the orphan delete used to run as an * independent statement, so a throw between it and the writes left the database * inconsistent with the in-memory graph. Nested inside a caller's transaction this takes * a savepoint rather than opening a second one. */ sync(): Promise; /** * Updates or ads data to relation * It will not delete data from db that are not in relation. It will only update or insert new data. * Only dirty models are updated. */ update(): Promise; /** The write itself, without a transaction of its own, so `sync()` can share one. */ protected _update(): Promise; } //# sourceMappingURL=relation-objects.d.ts.map