API @deepkit/orm
npm install @deepkit/orm
Database abstraction. Use createSession() to create a work session with transaction support. Using this class in your code indicates that you can work with common and most basic database semantics.
This means that you can use the deepkit/type database API that works across a variety of database engines
like MySQL, PostgreSQL, SQLite, and MongoDB. A generic database adapter you can use if the API of You can specify a more specialized adapter like MysqlDatabaseAdapter/MongoDatabaseAdapter with special API for MySQL/Mongo. This is a container knowing what entities are registered. It is able to register and resolve based on Type | ReflectionClass | ClassType. This container is necessary since TypeScript interfaces have no identity (TypeObjectLiteral) and property types are not equal by identity.
This means there can be multiple ReflectionClass describing the same structure/type.
We need to do type comparison to get always the correct (registered) ReflectionClass. Class to register a new database and resolve a schema/type to a database. Every query resolving gets its own formatter. This a generic query abstraction which should supports most basics database interactions. All query implementations should extend this since db agnostic consumers are probably
coded against this interface via Database Error event emitted when unit of work commit failed inserting new items. Error event emitted when unit of work commit failed updating existing items. This event is emitted when an error occurs in async database operation, like query, commit, connect, etc.
In event.databaseSession.adapter you can access the adapter that caused the error.
In event.error you can access the caught error.
In event.classSchema and event.query you might find additional context, but not necessarily. Hydrates not completely populated item and makes it completely accessible.
This is necessary if you want to load fields that were excluded from the query via lazyLoad(). Type guard for a specialised database adapter. Can be used to
use special methods from an adapter on a generic Database object. Converts a scala value to a primary key fields object. Wraps whatever error into a DatabaseError, if it's not already a DatabaseError.Classes
export class Database<ADAPTER extends DatabaseAdapter = DatabaseAdapter> {
name: string;
/**
* If set, all created Database instances are registered here.
*/
static registry?: Database[];
/**
* The entity schema registry.
*/
readonly entityRegistry: DatabaseEntityRegistry;
stopwatch?: Stopwatch;
/**
* Creates a new DatabaseQuery instance which can be used to query data.
* - Entity instances ARE NOT cached or tracked.
*
* Use a DatabaseSession (createSession()) with its query() in your workflow to enable
* identity map.
*
* ```typescript
* const session = database.createSession();
*
* const item = await session.query(MyType).findOne();
* item.name = 'changed';
* await session.commit(); //only necessary when you changed items received by this session
* ```
*/
readonly query: ReturnType<this[][]>[];
readonly raw: ReturnType<this[][]>[];
logger: Logger;
eventDispatcher: EventDispatcher;
pluginRegistry: DatabasePluginRegistry;
constructor(public readonly adapter: ADAPTER, schemas: (Type | ClassType | ReflectionClass<any>)[] = []);
setEventDispatcher(eventDispatcher: EventDispatcher);
setLogger(logger: Logger);
registerPlugin(...plugins: DatabasePlugin[]): void;
static createClass<T extends DatabaseAdapter>(name: string, adapter: T, schemas: (ClassType | ReflectionClass<any>)[] = []): ClassType<Database<T>>;
/**
* Register a new event listener for given token.
*
* order: The lower the order, the sooner the listener is called. Default is 0.
*/
listen<T extends EventToken<any>>(eventToken: T, callback: EventListenerCallback<T>, order: number = ): EventDispatcherUnsubscribe;
/**
* Tells the adapter to disconnect. It reconnects automatically when necessary.
*/
disconnect(force?: boolean): void;
/**
* Creates a new database session. This is the preferred way of working with the database
* and to enjoy all ORM features. Call DatabaseSession.commit to persist changes all at once
* in the most performant way possible. The creation of a DatabaseSession is very low cost,
* so creating many or often is the preferred way.
**
* All entity instances fetched/stored during this session are cached and tracked automatically.
*
* Note: This is not equal to a database transaction. A session means a work block
* where you need to fetch, change, and save entity instances. Every instance fetched
* stays in the identity-map of that session and keeps it alive, so make sure
* to not keep a session for too long (especially not cross requests).
* @example
* ```typescript
* const database = new Database(...);
*
* express.on('request', async (req) => {
* const session = database.createSession();
* const user = session.query(User).filter({id: req.params.id}).findOne();
* user.name = req.params.name;
* await session.commit(); //session will be garbage collected and should NOT be stored for the next request
* });
* ```
*/
createSession(): DatabaseSession<ADAPTER>;
/**
* Executes given callback in a new session and automatically commits it when executed successfully.
* This has the same semantics as `createSession`.
*/
async session<T>(worker: (session: DatabaseSession<ADAPTER>) => Promise<T>): Promise<T>;
/**
* Executes an async callback inside of a new transactional session. If the callback succeeds (not throwing), the
* session is automatically committed (and thus its transaction committed and all changes flushed).
* If the callback throws, the session executes rollback() automatically, and the error is rethrown.
*
* ```typescript
* await database.transaction(async (session) => {
* await session.query(...);
* session.add(...);
*
* //...
* });
* ```
*/
async transaction<T>(callback: (session: DatabaseSession<ADAPTER>) => Promise<T>): Promise<T>;
/**
* Creates a new reference.
*
* If you work with a DatabaseSession, use DatabaseSession.getReference instead to
* maintain object identity.
*
* ```
* const user = database.getReference(User, 1);
* ```
*/
getReference<T>(classType: ClassType<T> | ReflectionClass<any>, primaryKey: PrimaryKeyFields<T>): T;
/**
* Registers a new entity to this database.
* This is mainly used for db migration utilities and active record.
* If you want to use active record, you have to assign your entities first to a database using this method.
*/
registerEntity(...entities: (Type | AbstractClassType | ReflectionClass<any>)[]): void;
register<T>(options?: EntityOptions, type?: ReceiveType<T>): this;
getEntity(name: string): ReflectionClass<any>;
/**
* Makes sure the schemas types, indices, uniques, etc are reflected in the database.
*
* WARNING: DON'T USE THIS IN PRODUCTION AS THIS CAN CAUSE EASILY DATA LOSS.
* SEE THE MIGRATION DOCUMENTATION TO UNDERSTAND ITS IMPLICATIONS.
*/
async migrate(options: Partial<MigrateOptions> = {});
/**
* Simple direct persist. The persistence layer (batch) inserts or updates the record
* depending on the state of the given items. This is different to createSession()+add() in a way
* that `DatabaseSession.add` adds the given items to the queue (which is then committed using commit())
* while this `database.persist` just simply inserts/updates the given items immediately,
* completely bypassing the advantages of the unit of work for multiple items.
*
* You should prefer the add/remove and commit() workflow to fully utilizing database performance.
*/
async persist(...items: OrmEntity[]);
/**
* Same as persist(), but allows to specify the type that should be used for the given items.
*/
async persistAs<T extends OrmEntity>(items: T[], type?: ReceiveType<T>);
/**
* Simple direct remove. The persistence layer (batch) removes all given items.
* This is different to createSession()+remove() in a way that `DatabaseSession.remove` adds the given items to the queue
* (which is then committed using commit()) while this `database.remove` just simply removes the given items immediately,
* completely bypassing the advantages of the unit of work for multiple items.
*
* You should prefer the add/remove and commit() workflow to fully utilizing database performance.
*/
async remove(...items: OrmEntity[]);
/**
* Same as remove(), but allows to specify the type that should be used for the given items.
*/
async removeAs<T extends OrmEntity>(items: T[], type?: ReceiveType<T>);
}
export class ActiveRecord {
constructor(...args: any[]);
static getDatabase(): Database<any>;
static registerDatabase(database: Database<any>): void;
async save(): Promise<void>;
async remove(): Promise<void>;
static query<T extends typeof ActiveRecord>(this: T): Query<InstanceType<T>>;
static reference<T extends typeof ActiveRecord>(this: T, primaryKey: any | PrimaryKeyFields<InstanceType<T>>): InstanceType<T>;
}
export abstract class DatabaseAdapterQueryFactory {
abstract createQuery<T extends OrmEntity>(type?: ReceiveType<T> | ClassType<T> | AbstractClassType<T> | ReflectionClass<T>): Query<T>;
}
export abstract class DatabasePersistence {
abstract remove<T extends OrmEntity>(classSchema: ReflectionClass<T>, items: T[]): Promise<void>;
abstract insert<T extends OrmEntity>(classSchema: ReflectionClass<T>, items: T[]): Promise<void>;
abstract update<T extends OrmEntity>(classSchema: ReflectionClass<T>, changeSets: DatabasePersistenceChangeSet<T>[]): Promise<void>;
/**
* When DatabasePersistence instance is not used anymore, this function will be called.
* Good place to release a connection for example.
*/
abstract release(): void;
}
export class RawFactory<A extends Array<any>> {
create<T = any>(...args: A): any;
}
export class MigrateOptions {
/**
* Whether drop statements should be issued, like DROP TABLE, DROP INDEX, etc.
*
* Default false.
*/
drop: boolean;
/**
* Whether drop statements should be issued for indexes/uniques, like DROP INDEX.
*/
dropIndex: boolean;
/**
* Whether create/drop statements should be issued for indexes/uniques, like CREATE/ INDEX/DROP INDEX.
*/
skipIndex: boolean;
/**
* Whether foreign key constraints should be created/dropped.
*/
skipForeignKey: boolean;
isDropIndex();
isIndex();
isForeignKey();
isDropSchema();
}
export abstract class DatabaseAdapter {
eventDispatcher: EventDispatcher;
logger: Logger;
stopwatch?: Stopwatch;
setEventDispatcher(eventDispatcher: EventDispatcher);
setLogger(logger: Logger);
abstract queryFactory(session: DatabaseSession<this>): DatabaseAdapterQueryFactory;
rawFactory(session: DatabaseSession<this>): RawFactory<any>;
abstract createPersistence(session: DatabaseSession<this>): DatabasePersistence;
abstract createTransaction(session: DatabaseSession<this>): DatabaseTransaction;
abstract disconnect(force?: boolean): void;
abstract migrate(options: MigrateOptions, entityRegistry: DatabaseEntityRegistry): Promise<void>;
/**
* Unique adapter name to be used in DatabaseField to apply certain adapter specific behavior per field.
*/
abstract getName(): string;
abstract getSchemaName(): string;
abstract isNativeForeignKeyConstraintSupported(): boolean;
}
Query
is sufficient.export class DatabaseEntityRegistry {
static from(items: (Type | ReflectionClass<any> | ClassType)[]);
all(): ReflectionClass<any>[];
forMigration(): ReflectionClass<any>[];
add(...types: (Type | ReflectionClass<any> | ClassType)[]): void;
remove(type: Type | ReflectionClass<any> | ClassType): void;
getFromInstance<T>(item: T): ReflectionClass<any>;
get(type: Type | ReflectionClass<any> | ClassType): ReflectionClass<any>;
}
export class DatabaseSessionRound<ADAPTER extends DatabaseAdapter> {
constructor(protected round: number = , protected session: DatabaseSession<any>, protected eventDispatcher: EventDispatcher, public logger: Logger, protected identityMap?: IdentityMap);
isInCommit();
isCommitted();
add(items: Iterable<OrmEntity>, classSchema?: ReflectionClass<any>): void;
remove(items: OrmEntity[], schema?: ReflectionClass<any>);
async commit(persistence: DatabasePersistence);
}
export abstract class DatabaseTransaction {
static transactionCounter: number;
ended: boolean;
abstract begin(): Promise<void>;
abstract commit(): Promise<void>;
abstract rollback(): Promise<void>;
constructor(public id: number = DatabaseTransaction.transactionCounter++);
}
export class DatabaseSession<ADAPTER extends DatabaseAdapter = DatabaseAdapter> {
readonly id;
round: number;
withIdentityMap;
/**
* When this session belongs to a transaction, then this is set.
* All connection handlers should make sure that when a query/persistence object
* requests a connection, it should always be the same for a given transaction.
* (that's how transaction work). The connection between a transaction
* and connection should be unlinked when the transaction commits/rollbacks.
*/
assignedTransaction?: ReturnType<this[][]>;
readonly identityMap;
/**
* Creates a new DatabaseQuery instance which can be used to query and manipulate data.
*/
readonly query: ReturnType<this[][]>[];
readonly raw: ReturnType<this[][]>[];
static readonly onUpdatePre: EventToken<UnitOfWorkUpdateEvent<any>>;
static readonly onUpdatePost: EventToken<UnitOfWorkUpdateEvent<any>>;
static readonly onInsertPre: EventToken<UnitOfWorkEvent<any>>;
static readonly onInsertPost: EventToken<UnitOfWorkEvent<any>>;
static readonly onDeletePre: EventToken<UnitOfWorkEvent<any>>;
static readonly onDeletePost: EventToken<UnitOfWorkEvent<any>>;
static readonly onCommitPre: EventToken<UnitOfWorkCommitEvent<any>>;
constructor(public readonly adapter: ADAPTER, public readonly entityRegistry: DatabaseEntityRegistry, public readonly eventDispatcher: EventDispatcher, public pluginRegistry: DatabasePluginRegistry, public logger: Logger, public stopwatch?: Stopwatch);
/**
* Marks this session as transactional. On the next query or flush()/commit() a transaction on the database adapter is started.
* Use flush(), commit(), and rollback() to control the transaction behavior. All created query objects from this session
* are running in this transaction as well.
*
* The transaction is released when commit()/rollback() is executed.
*
* YOU MUST USE COMMIT() OR ROLLBACK() TO RELEASE THE TRANSACTION.
*
* When the transaction is released then
* this session is not marked as transactional anymore. You have to use useTransaction() again if you want to
* have a new transaction on this session.
*
* @example
* ```typescript
* const session = database.createSession();
* session.useTransaction();
* try {
* // add some data
* session.add(new User('New User'));
*
* // flush in-between changes to the database
* // without closing the transaction
* await session.flush();
*
* // query some data
* const users = await session.query(User).filter({ id: 1 }).find();
*
* // finish transaction
* await session.commit();
* } catch (error) {
* await session.rollback();
* throw error;
* }
*```
*/
useTransaction(): ReturnType<this[][]>;
/**
* Whether a transaction is assigned to this session.
*/
hasTransaction(): boolean;
/**
* Saves all open changes (pending inserts, updates, deletions) in optimized batches to the database.
*
* If a transaction is assigned, this will automatically call a transaction commit and the transaction released.
*
* Use flush() if you don't want to end the transaction and keep making changes to the current transaction.
*/
async commit();
/**
* If a transaction is assigned, a transaction rollback is executed and the transaction released.
* You have to use useTransaction() again or create a new session with useTransaction() to start a new transaction.
*
* This does not roll back changes made to objects in memory.
*/
async rollback(): Promise<void>;
/**
* If a transaction is assigned, a transaction commit is executed.
*
* This does not commit changes made to your objects in memory. Use commit() for that instead (which executes commitTransaction() as well).
*/
async commitTransaction(): Promise<void>;
/**
* Executes an async callback inside of a new transaction. If the callback succeeds (not throwing), the
* session is automatically committed (and thus its transaction committed and all changes flushed).
* If the callback throws, the session executes rollback() automatically, and the error is rethrown.
*
* ```typescript
* await session.transaction(async (session) => {
* await session.query(...);
* session.add(...);
*
* //...
* });
* ```
*/
async transaction<T>(callback: (session: this) => Promise<T>): Promise<T>;
from<T>(hook: DatabaseSessionHookConstructor<T>): T;
/**
* Creates or returns an existing reference.
*
* If no instance is known in the identity map, it creates a proxy reference (where only primary keys are populated).
* You can work with this entity instance to assign new references, but reading for not-hydrated values is not possible.
* Writing not-hydrated is possible and lead to a change in the change-detection. Completely hydrate the object using
* the `hydrateEntity` function.
*
* ```
* const user = session.getReference(User, 1);
* ```
*/
getReference<T>(classType: ClassType<T> | ReflectionClass<T>, primaryKey: any | PrimaryKeyFields<T>): T;
/**
* Adds a single or multiple items to the to add/update queue. Use session.commit() to persist all queued items to the database.
*
* This works like Git: you add files, and later commit all in one batch.
*/
add(...items: OrmEntity[]): this;
/**
* Adds a single or multiple items for a particular type to the to add/update queue. Use session.commit() to persist all queued items to the database.
*
* This works like Git: you add files, and later commit all in one batch.
*/
addAs<T extends OrmEntity>(items: T[], type?: ReceiveType<T> | ReflectionClass<any>): this;
/**
* Adds item to the remove queue. Use session.commit() to remove queued items from the database all at once.
*/
remove(...items: OrmEntity[]): this;
/**
* Adds item to the remove queue for a particular type. Use session.commit() to remove queued items from the database all at once.
*/
removeAs<T extends OrmEntity>(items: T[], type?: ReceiveType<T> | ReflectionClass<any>): this;
/**
* Resets all scheduled changes (add() and remove() calls).
*
* This does not reset changes made to your objects in memory.
*/
reset();
getHydrator(): HydratorFn;
async hydrateEntity<T extends object>(item: T);
/**
* Saves all open changes (pending inserts, updates, deletions) in optimized batches to the database.
*
* The transaction (if there is any) is still alive. You can call flush() multiple times in an active transaction.
* commit() does the same as flush() but also automatically commits and closes the transaction.
*/
async flush();
}
export class DatabaseRegistry {
constructor(protected injectorContext: InjectorContext, protected readonly databaseTypes: {
module: InjectorModule<any>;
classType: ClassType<Database<any>>;
}[] = [], protected migrateOnStartup: boolean = false);
setMigrateOnStartup(v: boolean);
onShutDown();
addDatabase(database: ClassType, options: {
migrateOnStartup?: boolean;
} = {}, module: InjectorModule<any>);
removeDatabase(database: ClassType);
getDatabaseTypes();
isMigrateOnStartup(database: ClassType): boolean;
addDatabaseInstance(database: Database);
init();
getDatabaseForEntity(entity: ReflectionClass<any> | ClassType): Database<any>;
getDatabases(): Database<any>[];
getDatabase(classType: ClassType): Database<any> | undefined;
getDatabaseByName(name: string): Database<any> | undefined;
}
export class ClassState<T = any> {
snapshot;
primaryKeyExtractor;
primaryKeyHashGenerator;
simplePrimaryKeyHashGenerator;
changeDetector;
constructor(public classSchema: ReflectionClass<T>);
}
export class IdentityMap {
registry;
deleteMany<T>(classSchema: ReflectionClass<T>, pks: Partial<T>[]);
deleteManyBySimplePK<T>(classSchema: ReflectionClass<T>, pks: PrimaryKeyFields<any>[]);
clear<T>();
isKnown<T extends OrmEntity>(item: T): boolean;
storeMany<T>(classSchema: ReflectionClass<T>, items: T[]);
store<T>(classSchema: ReflectionClass<T>, item: T);
getByHash<T>(classSchema: ReflectionClass<T>, pk: PKHash): T | undefined;
}
export class Formatter {
withIdentityMap: boolean;
constructor(protected rootClassSchema: ReflectionClass<any>, protected serializer: Serializer, protected hydrator?: HydratorFn, protected identityMap?: IdentityMap);
hydrate<T extends OrmEntity>(model: DatabaseQueryModel<T, any, any>, dbRecord: DBRecord): any;
}
export class DatabaseQueryModel<T extends OrmEntity, FILTER extends FilterQuery<T> = FilterQuery<T>, SORT extends Sort<T> = Sort<T>> {
withIdentityMap: boolean;
withChangeDetection: boolean;
filter?: FILTER;
having?: FILTER;
groupBy: Set<string>;
for?: | ;
aggregate;
select: Set<string>;
lazyLoad: Set<string>;
joins: DatabaseJoinModel<any>[];
skip?: number;
itemsPerPage: number;
limit?: number;
parameters: {
[name: string]: any;
};
sort?: SORT;
readonly change;
returning: (keyof T & string)[];
batchSize?: number;
/**
* The adapter name is set by the database adapter when the query is created.
*/
adapterName: string;
isLazyLoaded(field: string): boolean;
changed(): void;
hasSort(): boolean;
/**
* Whether limit/skip is activated.
*/
hasPaging(): boolean;
setParameters(parameters: {
[name: string]: any;
});
clone(): this;
/**
* Whether only a subset of fields are selected.
*/
isPartial();
/**
* Whether only a subset of fields are selected.
*/
isAggregate();
getFirstSelect();
isSelected(field: string): boolean;
hasJoins();
hasParameters(): boolean;
}
export class BaseQuery<T extends OrmEntity> {
_: () => T;
model: DatabaseQueryModel<T>;
constructor(public readonly classSchema: ReflectionClass<any>, model?: DatabaseQueryModel<T>);
/**
* Returns a new query with the same model transformed by the modifier.
*
* This allows to use more dynamic query composition functions.
*
* To support joins queries `BaseQuery` is necessary as query type.
*
* @example
* ```typescript
* function joinFrontendData(query: BaseQuery<Product>) {
* return query
* .useJoinWith('images').select('sort').end()
* .useJoinWith('brand').select('id', 'name', 'website').end()
* }
*
* const products = await database.query(Product).use(joinFrontendData).find();
* ```
* @reflection never
*/
use<Q, R, A extends any[]>(modifier: (query: Q, ...args: A) => R, ...args: A): this;
/**
* Same as `use`, but the method indicates it is terminating the query.
* @reflection never
*/
fetch<Q, R>(modifier: (query: Q) => R): R;
/**
* For MySQL/Postgres SELECT FOR SHARE.
* Has no effect in SQLite/MongoDB.
*/
forShare(): this;
/**
* For MySQL/Postgres SELECT FOR UPDATE.
* Has no effect in SQLite/MongoDB.
*/
forUpdate(): this;
withBatchSize(batchSize: number): this;
groupBy<K extends FieldName<T>[]>(...field: K): this;
withSum<K extends FieldName<T>, AS extends string>(field: K, as?: AS): Replace<this, Resolve<this> & {
[K in [
AS
] as AS]: number;
}>;
withGroupConcat<K extends FieldName<T>, AS extends string>(field: K, as?: AS): Replace<this, Resolve<this> & {
[C in [
AS
] as AS]: T[K][];
}>;
withCount<K extends FieldName<T>, AS extends string>(field: K, as?: AS): Replace<this, Resolve<this> & {
[K in [
AS
] as AS]: number;
}>;
withMax<K extends FieldName<T>, AS extends string>(field: K, as?: AS): Replace<this, Resolve<this> & {
[K in [
AS
] as AS]: number;
}>;
withMin<K extends FieldName<T>, AS extends string>(field: K, as?: AS): Replace<this, Resolve<this> & {
[K in [
AS
] as AS]: number;
}>;
withAverage<K extends FieldName<T>, AS extends string>(field: K, as?: AS): Replace<this, Resolve<this> & {
[K in [
AS
] as AS]: number;
}>;
aggregateField<K extends FieldName<T>, AS extends string>(field: K, func: string, as?: AS): Replace<this, Resolve<this> & {
[K in [
AS
] as AS]: number;
}>;
/**
* Excludes given fields from the query.
*
* This is mainly useful for performance reasons, to prevent loading unnecessary data.
*
* Use `hydrateEntity(item)` to completely load the entity.
*/
lazyLoad<K extends (keyof Resolve<this>)[]>(...select: K): this;
/**
* Limits the query to the given fields.
*
* This is useful for security reasons, to prevent leaking sensitive data,
* and also for performance reasons, to prevent loading unnecessary data.
*
* Note: This changes the return type of the query (findOne(), find(), etc) to exclude the fields that are not selected.
* This makes the query return simple objects instead of entities. To maintained nominal types use `lazyLoad()` instead.
*/
select<K extends (keyof Resolve<this>)[]>(...select: K): Replace<this, Pick<Resolve<this>, K[number]>>;
returning(...fields: FieldName<T>[]): this;
skip(value?: number): this;
/**
* Sets the page size when `page(x)` is used.
*/
itemsPerPage(value: number): this;
/**
* Applies limit/skip operations correctly to basically have a paging functionality.
* Make sure to call itemsPerPage() before you call page.
*/
page(page: number): this;
limit(value?: number): this;
parameter(name: string, value: any): this;
parameters(parameters: {
[name: string]: any;
}): this;
/**
* Identity mapping is used to store all created entity instances in a pool.
* If a query fetches an already known entity instance, the old will be picked.
* This ensures object instances uniqueness and generally saves CPU circles.
*
* This disabled entity tracking, forcing always to create new entity instances.
*
* For queries created on the database object (database.query(T)), this is disabled
* per default. Only on sessions (const session = database.createSession(); session.query(T))
* is the identity map enabled per default, and can be disabled with this method.
*/
disableIdentityMap(): this;
/**
* When fetching objects from the database, for each object will a snapshot be generated,
* on which change-detection happens. This behavior is not necessary when only fetching
* data and never modifying its objects (when for example returning data to the client directly).
* When this is the case, you can disable change-detection entirely for the returned objects.
* Note: Persisting/committing (database.persist(), session.commit) won't detect any changes
* when change-detection is disabled.
*/
disableChangeDetection(): this;
having(filter?: this[][]): this;
/**
* Narrow the query result.
*
* Note: previous filter conditions are preserved.
*/
filter(filter?: this[][]): this;
/**
* Narrow the query result by field-specific conditions.
*
* This can be helpful to work around the type issue that when `T` is another
* generic type there must be a type assertion to use {@link filter}.
*
* Note: previous filter conditions are preserved.
*/
filterField<K extends keyof T & string>(name: K, value: FilterQuery<T>[K]): this;
/**
* Clear all filter conditions.
*/
clearFilter(): this;
sort(sort?: this[][]): this;
orderBy<K extends FieldName<T>>(field: K, direction: | = ): this;
clone(): this;
/**
* Adds a left join in the filter. Does NOT populate the reference with values.
* Accessing `field` in the entity (if not optional field) results in an error.
*/
join<K extends keyof ReferenceFields<T>, ENTITY extends OrmEntity = FindEntity<T[K]>>(field: K, type: | = , populate: boolean = false, configure?: Configure<ENTITY>): this;
/**
* Adds a left join in the filter. Does NOT populate the reference with values.
* Accessing `field` in the entity (if not optional field) results in an error.
* Returns JoinDatabaseQuery to further specify the join, which you need to `.end()`
*/
useJoin<K extends keyof ReferenceFields<T>, ENTITY extends OrmEntity = FindEntity<T[K]>>(field: K): JoinDatabaseQuery<ENTITY, this>;
/**
* Adds a left join in the filter and populates the result set WITH reference field accordingly.
*/
joinWith<K extends keyof ReferenceFields<T>, ENTITY extends OrmEntity = FindEntity<T[K]>>(field: K, configure?: Configure<ENTITY>): this;
/**
* Adds a left join in the filter and populates the result set WITH reference field accordingly.
* Returns JoinDatabaseQuery to further specify the join, which you need to `.end()`
*/
useJoinWith<K extends keyof ReferenceFields<T>, ENTITY extends OrmEntity = FindEntity<T[K]>>(field: K): JoinDatabaseQuery<ENTITY, this>;
getJoin<K extends keyof ReferenceFields<T>, ENTITY extends OrmEntity = FindEntity<T[K]>>(field: K): JoinDatabaseQuery<ENTITY, this>;
/**
* Adds an inner join in the filter and populates the result set WITH reference field accordingly.
*/
innerJoinWith<K extends keyof ReferenceFields<T>, ENTITY extends OrmEntity = FindEntity<T[K]>>(field: K, configure?: Configure<ENTITY>): this;
/**
* Adds an inner join in the filter and populates the result set WITH reference field accordingly.
* Returns JoinDatabaseQuery to further specify the join, which you need to `.end()`
*/
useInnerJoinWith<K extends keyof ReferenceFields<T>, ENTITY extends OrmEntity = FindEntity<T[K]>>(field: K): JoinDatabaseQuery<ENTITY, this>;
/**
* Adds an inner join in the filter. Does NOT populate the reference with values.
* Accessing `field` in the entity (if not optional field) results in an error.
*/
innerJoin<K extends keyof ReferenceFields<T>, ENTITY extends OrmEntity = FindEntity<T[K]>>(field: K, configure?: Configure<ENTITY>): this;
/**
* Adds an inner join in the filter. Does NOT populate the reference with values.
* Accessing `field` in the entity (if not optional field) results in an error.
* Returns JoinDatabaseQuery to further specify the join, which you need to `.end()`
*/
useInnerJoin<K extends keyof ReferenceFields<T>, ENTITY extends OrmEntity = FindEntity<T[K]>>(field: K): JoinDatabaseQuery<ENTITY, this>;
}
export abstract class GenericQueryResolver<T extends object, ADAPTER extends DatabaseAdapter = DatabaseAdapter, MODEL extends DatabaseQueryModel<T> = DatabaseQueryModel<T>> {
constructor(protected classSchema: ReflectionClass<T>, protected session: DatabaseSession<ADAPTER>);
abstract count(model: MODEL): Promise<number>;
abstract find(model: MODEL): Promise<T[]>;
abstract findOneOrUndefined(model: MODEL): Promise<T | undefined>;
abstract delete(model: MODEL, deleteResult: DeleteResult<T>): Promise<void>;
abstract patch(model: MODEL, value: Changes<T>, patchResult: PatchResult<T>): Promise<void>;
explain(model: MODEL, op: QueryExplainOp, option?: unknown): Promise<any>;
}
export class Query<T extends OrmEntity> extends BaseQuery<T> {
static readonly onFetch: EventToken<QueryDatabaseEvent<any>>;
static readonly onDeletePre: EventToken<QueryDatabaseDeleteEvent<any>>;
static readonly onDeletePost: EventToken<QueryDatabaseDeleteEvent<any>>;
static readonly onPatchPre: EventToken<QueryDatabasePatchEvent<any>>;
static readonly onPatchPost: EventToken<QueryDatabasePatchEvent<any>>;
static is<T extends ClassType<Query<any>>>(v: Query<any>, type: T): v is InstanceType<T>;
constructor(classSchema: ReflectionClass<T>, protected session: DatabaseSession<any>, public resolver: GenericQueryResolver<T>);
/**
* Returns the explain query result for the given operation.
*
* Use `logExplain` instead to log the explain result on the next operation.
*/
async explain(op: QueryExplainOp, ...args: ExplainParameters<this[]>): Promise<ExplainResult<this[]>>;
/**
* Logs the explain query result of the next operation to the logger.
*/
logExplain(...args: ExplainParameters<this[]>): this;
static from<Q extends Query<any> & {
_: () => T;
}, T extends ReturnType<InstanceType<B>[]>, B extends ClassType<Query<any>>>(this: B, query: Q): Replace<InstanceType<B>, Resolve<Q>>;
lift<B extends ClassType<Query<any>>, T extends ReturnType<InstanceType<B>[]>, THIS extends Query<any> & {
_: () => T;
}>(this: THIS, query: B): Replace<InstanceType<B>, Resolve<this>> & Pick<this, Methods<this>>;
/**
* Clones the query and returns a new instance.
* This happens automatically for each modification, so you don't need to call it manually.
*
* ```typescript
* let query1 = database.query(User);
* let query2 = query1.filter({name: 'Peter'});
* // query1 is not modified, query2 is a new instance with the filter applied
* ```
*/
clone(): this;
/**
* Returns the number of items matching the query.
*
* @throws DatabaseError
*/
async count(fromHas: boolean = false): Promise<number>;
/**
* Fetches all items matching the query.
*
* @throws DatabaseError
*/
async find(): Promise<Resolve<this>[]>;
/**
* Fetches a single item matching the query or undefined.
*
* @throws DatabaseError
*/
async findOneOrUndefined(): Promise<Resolve<this> | undefined>;
/**
* Fetches a single item matching the query.
*
* @throws DatabaseError
*/
async findOne(): Promise<Resolve<this>>;
/**
* Deletes all items matching the query.
*
* @throws DatabaseDeleteError
*/
async deleteMany(): Promise<DeleteResult<T>>;
/**
* Deletes a single item matching the query.
*
* @throws DatabaseDeleteError
*/
async deleteOne(): Promise<DeleteResult<T>>;
/**
* Updates all items matching the query with the given patch.
*
* @throws DatabasePatchError
* @throws UniqueConstraintFailure
*/
async patchMany(patch: ChangesInterface<T> | DeepPartial<T>): Promise<PatchResult<T>>;
/**
* Updates a single item matching the query with the given patch.
*
* @throws DatabasePatchError
* @throws UniqueConstraintFailure
*/
async patchOne(patch: ChangesInterface<T> | DeepPartial<T>): Promise<PatchResult<T>>;
/**
* Returns true if the query matches at least one item.
*
* @throws DatabaseError
*/
async has(): Promise<boolean>;
/**
* Returns the primary keys of the query.
*
* ```typescript
* const ids = await database.query(User).ids();
* // ids: number[]
* ```
*
* @throws DatabaseError
*/
async ids(singleKey?: false): Promise<PrimaryKeyFields<T>[]>;
async ids(singleKey: true): Promise<PrimaryKeyType<T>[]>;
async ids(singleKey: boolean = false): Promise<PrimaryKeyFields<T>[] | PrimaryKeyType<T>[]>;
/**
* Returns the specified field of the query from all items.
*
* ```typescript
* const usernames = await database.query(User).findField('username');
* // usernames: string[]
* ```
*
* @throws DatabaseError
*/
async findField<K extends FieldName<T>>(name: K): Promise<T[K][]>;
/**
* Returns the specified field of the query from a single item, throws if not found.
*
* ```typescript
* const username = await database.query(User).findOneField('username');
* ```
*
* @throws ItemNotFound if no item is found
* @throws DatabaseError
*/
async findOneField<K extends FieldName<T>>(name: K): Promise<T[K]>;
/**
* Returns the specified field of the query from a single item or undefined.
*
* @throws DatabaseError
*/
async findOneFieldOrUndefined<K extends FieldName<T>>(name: K): Promise<T[K] | undefined>;
}
export class JoinDatabaseQuery<T extends OrmEntity, PARENT extends BaseQuery<any>> extends BaseQuery<T> {
constructor(classSchema: ReflectionClass<any>, public query: BaseQuery<any>, public parentQuery?: PARENT);
clone(parentQuery?: PARENT): this;
end(): PARENT;
}
export class MemoryQuery<T extends OrmEntity> extends Query<T> {
isMemoryDb();
}
export class MemoryQueryFactory extends DatabaseAdapterQueryFactory {
constructor(protected adapter: MemoryDatabaseAdapter, protected databaseSession: DatabaseSession<any>);
get scopedLogger();
createQuery<T extends OrmEntity>(classType?: ReceiveType<T> | AbstractClassType<T> | ReflectionClass<T>): MemoryQuery<T>;
}
export class MemoryDatabaseTransaction extends DatabaseTransaction {
async begin(): Promise<void>;
async commit(): Promise<void>;
async rollback(): Promise<void>;
}
export class MemoryPersistence extends DatabasePersistence {
constructor(private adapter: MemoryDatabaseAdapter);
async remove<T extends OrmEntity>(classSchema: ReflectionClass<T>, items: T[]): Promise<void>;
async insert<T extends OrmEntity>(classSchema: ReflectionClass<T>, items: T[]): Promise<void>;
async update<T extends OrmEntity>(classSchema: ReflectionClass<T>, changeSets: DatabasePersistenceChangeSet<T>[]): Promise<void>;
async release();
}
export class MemoryDatabaseAdapter extends DatabaseAdapter {
async migrate(options: MigrateOptions, entityRegistry: DatabaseEntityRegistry);
isNativeForeignKeyConstraintSupported(): boolean;
createTransaction(session: DatabaseSession<this>): MemoryDatabaseTransaction;
getStore<T>(classSchema: ReflectionClass<T>): SimpleStore<T>;
createPersistence(): DatabasePersistence;
disconnect(force?: boolean): void;
getName(): string;
getSchemaName(): string;
queryFactory(databaseSession: DatabaseSession<this>): MemoryQueryFactory;
}
export class DatabaseEvent extends BaseEvent {
}
export class UnitOfWorkCommitEvent<T> extends DatabaseEvent {
constructor(public readonly databaseSession: DatabaseSession<any>);
}
export class UnitOfWorkEvent<T> extends DatabaseEvent {
constructor(public readonly classSchema: ReflectionClass<T>, public readonly databaseSession: DatabaseSession<any>, public readonly items: T[]);
isSchemaOf<T>(classType: ClassType<T>): this is UnitOfWorkEvent<T>;
getPrimaryKeys(): PrimaryKeyType<T>[];
}
export class UnitOfWorkUpdateEvent<T extends object> extends DatabaseEvent {
constructor(public readonly classSchema: ReflectionClass<T>, public readonly databaseSession: DatabaseSession<any>, public readonly changeSets: DatabasePersistenceChangeSet<T>[]);
isSchemaOf<T extends object>(classType: ClassType<T>): this is UnitOfWorkUpdateEvent<T>;
}
export class QueryDatabaseEvent<T extends OrmEntity> extends DatabaseEvent {
constructor(public readonly databaseSession: DatabaseSession<any>, public readonly classSchema: ReflectionClass<T>, public query: Query<T>);
isSchemaOf<T extends OrmEntity>(classType: ClassType<T>): this is QueryDatabaseDeleteEvent<T>;
}
export class DatabaseErrorEvent extends DatabaseEvent {
constructor(public readonly error: Error, public readonly databaseSession: DatabaseSession<any>, public readonly classSchema?: ReflectionClass<any>, public readonly query?: Query<any>);
}
export class DatabaseErrorInsertEvent extends DatabaseErrorEvent {
inserts: OrmEntity[];
}
export class DatabaseErrorUpdateEvent extends DatabaseErrorEvent {
changeSets: DatabasePersistenceChangeSet<OrmEntity>[];
}
export class QueryDatabaseDeleteEvent<T extends OrmEntity> extends DatabaseEvent {
constructor(public readonly databaseSession: DatabaseSession<any>, public readonly classSchema: ReflectionClass<T>, public query: Query<T>, public readonly deleteResult: DeleteResult<T>);
isSchemaOf<T extends OrmEntity>(classType: ClassType<T>): this is QueryDatabaseDeleteEvent<T>;
}
export class QueryDatabasePatchEvent<T extends object> extends DatabaseEvent {
returning: (keyof T & string)[];
constructor(public readonly databaseSession: DatabaseSession<any>, public readonly classSchema: ReflectionClass<T>, public query: Query<T>, public readonly patch: Changes<T>, public readonly patchResult: PatchResult<T>);
isSchemaOf<T extends object>(classType: ClassType<T>): this is QueryDatabasePatchEvent<T>;
}
export class DatabasePluginRegistry {
add(plugin: DatabasePlugin): void;
hasPlugin<T>(classType: ClassType<T>): boolean;
getPlugins<T>(classType: ClassType<T>): T[];
getPlugin<T>(classType: ClassType<T>): T;
}
export class SoftDeleteSession {
constructor(protected session: DatabaseSession<any>);
setDeletedBy<T extends SoftDeleteEntity>(classType: ClassType<T> | ReflectionClass<T>, deletedBy: T[]): this;
restore<T extends SoftDeleteEntity>(item: T): this;
}
export class SoftDeleteQuery<T extends SoftDeleteEntity> extends Query<T> {
includeSoftDeleted: boolean;
setDeletedBy?: T[];
clone(): this;
/**
* Enables fetching, updating, and deleting of soft-deleted records.
*/
withSoftDeleted(): this;
/**
* Includes only soft deleted records.
*/
isSoftDeleted(): this;
deletedBy(value: T[]): this;
async restoreOne();
async restoreMany();
async hardDeleteOne();
async hardDeleteMany();
}
export class SoftDeletePlugin implements DatabasePlugin {
onRegister(database: Database<any>): void;
enable<T extends SoftDeleteEntity>(...classSchemaOrTypes: (ReflectionClass<T> | ClassType<T>)[]);
disable(...classSchemaOrTypes: (ReflectionClass<any> | ClassType)[]);
}
export class LogEntity {
id: number & PrimaryKey & AutoIncrement;
created: Date;
author: string;
changedFields: string[];
reference: any;
constructor(public type: LogType);
}
export class LogSession {
constructor(session: DatabaseSession<any>);
setAuthor(author: any): this;
setAuthorForInstance(instance: any, author: any): this;
}
export class LogQuery<T extends OrmEntity> extends Query<T> {
logAuthor?: any;
byLogAuthor(author?: any): this;
clone(): this;
}
export class LogPlugin implements DatabasePlugin {
options: LoginPluginOptions;
constructor(options: Partial<LoginPluginOptions> = {});
isIncluded(classType: ReflectionClass<any>);
getLogEntityCollectionName(schema: ReflectionClass<any>): string;
getLogEntity(schema: ReflectionClass<any> | ClassType): ClassType<LogEntity>;
createLog(databaseSession: DatabaseSession<any>, type: LogType, reflectionClass: ReflectionClass<any>, primaryKey: PrimaryKeyFields<any>): LogEntity;
onRegister(database: Database<any>): void;
}
Events
EventToken<DatabaseErrorEvent>
Errors
export class SessionClosedException extends CustomError {
}
export class ItemNotFound extends Error {
}
export class DatabaseError extends CustomError {
}
export class DatabaseInsertError extends DatabaseError {
constructor(public readonly entity: ReflectionClass<any>, public readonly items: OrmEntity[], ...args: ConstructorParameters<typeof DatabaseError>);
}
export class DatabaseUpdateError extends DatabaseError {
constructor(public readonly entity: ReflectionClass<any>, public readonly changeSets: DatabasePersistenceChangeSet<any>[], ...args: ConstructorParameters<typeof DatabaseError>);
}
export class DatabasePatchError extends DatabaseError {
constructor(public readonly entity: ReflectionClass<any>, public readonly query: DatabaseQueryModel<any>, public readonly changeSets: Changes<any>, ...args: ConstructorParameters<typeof DatabaseError>);
}
export class DatabaseDeleteError extends DatabaseError {
readonly query?: DatabaseQueryModel<any>;
readonly items?: OrmEntity[];
constructor(public readonly entity: ReflectionClass<any>, ...args: ConstructorParameters<typeof DatabaseError>);
}
export class DatabaseValidationError extends DatabaseError {
constructor(public readonly classSchema: ReflectionClass<any>, public readonly errors: ValidationErrorItem[]);
}
export class UniqueConstraintFailure extends DatabaseError {
}
Functions
<T extends OrmEntity>(item: T): Promise<T>
<T extends DatabaseAdapter>(database: Database<any>, adapterClassType: AbstractClassType<T>): database is Database<T>
const database = new Database(...); //we don't know the adapter
if (isDatabaseOf(database, SQLDatabaseAdapter)) {
// cool, we can use `where(sql)` which is only available for SQLDatabaseAdapter
database.query(User).where(sql`id > 2`).find();
//or raw SQL queries
database.raw(sql`SELECT count(*) FROM ${User}`).find();
}
(entity: any): entity is ActiveRecordClassType
(schema: ReflectionClass<any>, primaryKey: any): { [name: string]: any; }
<T>(classSchema: ReflectionClass<T>): ClassState<T>
<T extends OrmEntity>(item: T): InstanceState<T>
<T extends OrmEntity>(classState: ClassState<T>, item: T): InstanceState<T>
(item: any, hydrator: (item: any) => Promise<void>): void
(reflectionClass: ReflectionClass<any>, filter: FilterQuery<any>): string[]
<T>(reflectionClass: ReflectionClass<any>, filter: FilterQuery<T>, parameters: { [name: string]: any; }): any
<T, K extends keyof T, Q extends FilterQuery<T>>(classType: ClassType<T> | ReflectionClass<T>, filter: Q, converter: Converter, fieldNamesMap?: QueryFieldNames, customMapping?: QueryCustomFields): Q
<T extends OrmEntity>(items: Iterable<[ReflectionClass<any>, T]>): Map<ReflectionClass<any>, T[]>
<T extends { [index: string]: any; }>(target: T, query: FilterQuery<T>): boolean
<T extends { [index: string]: any; }>(items: T[], query: FilterQuery<T>): T[]
<T extends object>(item: T): Changes<T>
(classSchema: ReflectionClass<any>, templateRegistry: TemplateRegistry): (data: any) => PrimaryKeyFields<any>
(error: Error | string): Error
<T>(reflectionClass: ReflectionClass<T>, pk: { [name: string]: any; }, identityMap?: IdentityMap, pool?: Map<string, T>, ReferenceClass?: ClassType): T
Types
interface ActiveRecordClassType {
new(...args: any[]): ActiveRecord;
getDatabase(): Database<any>;
registerDatabase(database: Database<any>): void;
query(): any;
}
interface DatabasePersistenceChangeSet<T extends object> {
changes: ItemChanges<T>;
item: T;
primaryKey: PrimaryKeyFields<T>;
}
interface DatabaseSessionHookConstructor<C> {
new<T extends DatabaseSession<any>>(session: T): C;
}
interface DatabaseSessionHook<T extends DatabaseSession<any>> {
}
type PKHash = string;
type HydratorFn = (item: any) => Promise<void>;
type SORT_ORDER = 'asc' | 'desc' | any;
type Sort<T extends OrmEntity, ORDER extends SORT_ORDER = SORT_ORDER> = { [P in keyof T & string]?: ORDER };
interface DatabaseJoinModel<T extends OrmEntity> {
//this is the parent classSchema, the foreign classSchema is stored in `query`
classSchema: ReflectionClass<T>,
propertySchema: ReflectionProperty,
type: 'left' | 'inner' | string,
populate: boolean,
//defines the field name under which the database engine populated the results.
//necessary for the formatter to pick it up, convert and set correctly the real field name
as?: string,
query: BaseQuery<T>,
foreignPrimaryKey: ReflectionProperty,
}
type QuerySelector<T> = {
// Comparison
$eq?: T;
$gt?: T;
$gte?: T;
$in?: T[];
$lt?: T;
$lte?: T;
$ne?: T;
$nin?: T[];
$like?: T;
// Logical
$not?: T extends string ? (QuerySelector<T> | RegExp) : QuerySelector<T>;
$regex?: T extends string ? (RegExp | string) : never;
//special deepkit/type type
$parameter?: string;
};
type RootQuerySelector<T> = {
$and?: Array<FilterQuery<T>>;
$nor?: Array<FilterQuery<T>>;
$or?: Array<FilterQuery<T>>;
// we could not find a proper TypeScript generic to support nested queries e.g. 'user.friends.name'
// this will mark all unrecognized properties as any (including nested queries)
[deepPath: string]: any;
};
type Condition<T> = MongoAltQuery<T> | QuerySelector<MongoAltQuery<T>>;
type FilterQuery<T> = {
[P in keyof T & string]?: Condition<T[P]>;
} &
RootQuerySelector<T>;
interface QueryClassType<T> {
create(query: BaseQuery<any>): QueryClassType<T>;
}
type Configure<T extends OrmEntity> = (query: BaseQuery<T>) => BaseQuery<T> | void;
type QueryExplainOp = 'count' | 'find' | 'findOne' | 'delete' | 'patch';
interface FindQuery<T> {
findOneOrUndefined(): Promise<T | undefined>;
findOne(): Promise<T>;
find(): Promise<T[]>;
}
type Methods<T> = { [K in keyof T]: K extends keyof Query<any> ? never : T[K] extends ((...args: any[]) => any) ? K : never }[keyof T];
type ExplainParameters<T extends GenericQueryResolver<any>> = T extends {
explain: (model: any, op: any, ...options: infer A) => any
} ? A : never;
type ExplainResult<T extends GenericQueryResolver<any>> = T extends { explain: (...args: any[]) => infer R } ? R : never;
type AnyQuery<T extends OrmEntity> = BaseQuery<T>;
type Converter = (convertClass: ReflectionClass<any>, path: string, value: any) => any;
type QueryFieldNames = { [name: string]: boolean };
type QueryCustomFields = { [name: string]: (name: string, value: any, fieldNames: QueryFieldNames, converter: Converter) => any };
type FlattenIfArray<T> = T extends Array<any> ? T[0] : T;
type FieldName<T> = keyof T & string;
type Placeholder<T> = () => T;
type Resolve<T extends { _: Placeholder<any> }> = ReturnType<T['_']>;
type Replace<T, R> = T & { _: Placeholder<R> };
interface OrmEntity {
}
type PatchResult<T> = { modified: number, returning: { [name in keyof T & string]?: T[name][] }, primaryKeys: PrimaryKeyType<T>[] };
type DeleteResult<T> = { modified: number, primaryKeys: PrimaryKeyFields<T>[] };
interface DatabasePlugin {
onRegister(database: Database<any>): void;
}