-
-
Notifications
You must be signed in to change notification settings - Fork 495
/
EntityRepository.ts
243 lines (212 loc) · 10.6 KB
/
EntityRepository.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
import type { CreateOptions, EntityManager, MergeOptions } from '../EntityManager';
import type { AssignOptions } from './EntityAssigner';
import type { EntityData, EntityName, AnyEntity, Primary, Loaded, FilterQuery, EntityDictionary, AutoPath, RequiredEntityData } from '../typings';
import type { CountOptions, DeleteOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, NativeInsertUpdateOptions, UpdateOptions } from '../drivers/IDatabaseDriver';
import type { IdentifiedReference, Reference } from './Reference';
import type { EntityLoaderOptions } from './EntityLoader';
export class EntityRepository<T extends object> {
constructor(protected readonly _em: EntityManager,
protected readonly entityName: EntityName<T>) { }
/**
* Tells the EntityManager to make an instance managed and persistent.
* The entity will be entered into the database at or before transaction commit or as a result of the flush operation.
*/
persist(entity: AnyEntity | AnyEntity[]): EntityManager {
return this.em.persist(entity);
}
/**
* Persists your entity immediately, flushing all not yet persisted changes to the database too.
* Equivalent to `em.persist(e).flush()`.
*/
async persistAndFlush(entity: AnyEntity | AnyEntity[]): Promise<void> {
await this.em.persistAndFlush(entity);
}
/**
* Tells the EntityManager to make an instance managed and persistent.
* The entity will be entered into the database at or before transaction commit or as a result of the flush operation.
*
* @deprecated use `persist()`
*/
persistLater(entity: AnyEntity | AnyEntity[]): void {
this.em.persistLater(entity);
}
/**
* Finds first entity matching your `where` query.
*/
async findOne<P extends string = never>(where: FilterQuery<T>, options?: FindOneOptions<T, P>): Promise<Loaded<T, P> | null> {
return this.em.findOne<T, P>(this.entityName, where, options);
}
/**
* Finds first entity matching your `where` query. If nothing found, it will throw an error.
* You can override the factory for creating this method via `options.failHandler` locally
* or via `Configuration.findOneOrFailHandler` globally.
*/
async findOneOrFail<P extends string = never>(where: FilterQuery<T>, options?: FindOneOrFailOptions<T, P>): Promise<Loaded<T, P>> {
return this.em.findOneOrFail<T, P>(this.entityName, where, options);
}
/**
* Creates or updates the entity, based on whether it is already present in the database.
* This method performs an `insert on conflict merge` query ensuring the database is in sync, returning a managed
* entity instance. The method accepts either `entityName` together with the entity `data`, or just entity instance.
*
* ```ts
* // insert into "author" ("age", "email") values (33, 'foo@bar.com') on conflict ("email") do update set "age" = 41
* const author = await em.getRepository(Author).upsert({ email: 'foo@bar.com', age: 33 });
* ```
*
* The entity data needs to contain either the primary key, or any other unique property. Let's consider the following example, where `Author.email` is a unique property:
*
* ```ts
* // insert into "author" ("age", "email") values (33, 'foo@bar.com') on conflict ("email") do update set "age" = 41
* // select "id" from "author" where "email" = 'foo@bar.com'
* const author = await em.getRepository(Author).upsert({ email: 'foo@bar.com', age: 33 });
* ```
*
* Depending on the driver support, this will either use a returning query, or a separate select query, to fetch the primary key if it's missing from the `data`.
*
* If the entity is already present in current context, there won't be any queries - instead, the entity data will be assigned and an explicit `flush` will be required for those changes to be persisted.
*/
async upsert(entityOrData?: EntityData<T> | T, options?: NativeInsertUpdateOptions<T>): Promise<T> {
return this.em.upsert<T>(this.entityName, entityOrData, options);
}
/**
* Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
*/
async find<P extends string = never>(where: FilterQuery<T>, options?: FindOptions<T, P>): Promise<Loaded<T, P>[]> {
return this.em.find<T, P>(this.entityName, where as FilterQuery<T>, options);
}
/**
* Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as tuple
* where first element is the array of entities and the second is the count.
*/
async findAndCount<P extends string = never>(where: FilterQuery<T>, options?: FindOptions<T, P>): Promise<[Loaded<T, P>[], number]> {
return this.em.findAndCount<T, P>(this.entityName, where, options);
}
/**
* Finds all entities of given type. You can pass additional options via the `options` parameter.
*/
async findAll<P extends string = never>(options?: FindOptions<T, P>): Promise<Loaded<T, P>[]> {
return this.em.find<T, P>(this.entityName, {} as FilterQuery<T>, options);
}
/**
* Marks entity for removal.
* A removed entity will be removed from the database at or before transaction commit or as a result of the flush operation.
*
* To remove entities by condition, use `em.nativeDelete()`.
*/
remove(entity: AnyEntity): EntityManager {
return this.em.remove(entity);
}
/**
* Removes an entity instance immediately, flushing all not yet persisted changes to the database too.
* Equivalent to `em.remove(e).flush()`
*/
async removeAndFlush(entity: AnyEntity): Promise<void> {
await this.em.removeAndFlush(entity);
}
/**
* Marks entity for removal.
* A removed entity will be removed from the database at or before transaction commit or as a result of the flush operation.
*
* @deprecated use `remove()`
*/
removeLater(entity: AnyEntity): void {
this.em.removeLater(entity);
}
/**
* Flushes all changes to objects that have been queued up to now to the database.
* This effectively synchronizes the in-memory state of managed objects with the database.
* This method is a shortcut for `em.flush()`, in other words, it will flush the whole UoW,
* not just entities registered via this particular repository.
*/
async flush(): Promise<void> {
return this.em.flush();
}
/**
* Fires native insert query. Calling this has no side effects on the context (identity map).
*/
async nativeInsert(data: T | EntityData<T>, options?: NativeInsertUpdateOptions<T>): Promise<Primary<T>> {
return this.em.nativeInsert<T>(this.entityName, data, options);
}
/**
* Fires native update query. Calling this has no side effects on the context (identity map).
*/
async nativeUpdate(where: FilterQuery<T>, data: EntityData<T>, options?: UpdateOptions<T>): Promise<number> {
return this.em.nativeUpdate(this.entityName, where, data, options);
}
/**
* Fires native delete query. Calling this has no side effects on the context (identity map).
*/
async nativeDelete(where: FilterQuery<T>, options?: DeleteOptions<T>): Promise<number> {
return this.em.nativeDelete(this.entityName, where, options);
}
/**
* Maps raw database result to an entity and merges it to this EntityManager.
*/
map(result: EntityDictionary<T>, options?: { schema?: string }): T {
return this.em.map(this.entityName, result, options);
}
/**
* Gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded
*/
getReference<PK extends keyof T>(id: Primary<T>, options: Omit<GetReferenceOptions, 'wrapped'> & { wrapped: true }): IdentifiedReference<T, PK>;
/**
* Gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded
*/
getReference(id: Primary<T> | Primary<T>[]): T;
/**
* Gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded
*/
getReference(id: Primary<T>, options: Omit<GetReferenceOptions, 'wrapped'> & { wrapped: false }): T;
/**
* Gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded
*/
getReference<PK extends keyof T = keyof T>(id: Primary<T>, options?: GetReferenceOptions): T | Reference<T> {
return this.em.getReference<T>(this.entityName, id, options);
}
/**
* Checks whether given property can be populated on the entity.
*/
canPopulate(property: string): boolean {
return this.em.canPopulate(this.entityName, property);
}
/**
* Loads specified relations in batch. This will execute one query for each relation, that will populate it on all of the specified entities.
*/
async populate<P extends string = never>(entities: T | T[], populate: AutoPath<T, P>[] | boolean, options?: EntityLoaderOptions<T, P>): Promise<Loaded<T, P>[]> {
return this.em.populate(entities as T, populate, options);
}
/**
* Creates new instance of given entity and populates it with given data.
* The entity constructor will be used unless you provide `{ managed: true }` in the options parameter.
* The constructor will be given parameters based on the defined constructor of the entity. If the constructor
* parameter matches a property name, its value will be extracted from `data`. If no matching property exists,
* the whole `data` parameter will be passed. This means we can also define `constructor(data: Partial<T>)` and
* `em.create()` will pass the data into it (unless we have a property named `data` too).
*/
create<P = never>(data: RequiredEntityData<T>, options?: CreateOptions): T {
return this.em.create(this.entityName, data, options);
}
/**
* Shortcut for `wrap(entity).assign(data, { em })`
*/
assign(entity: T, data: EntityData<T>, options?: AssignOptions): T {
return this.em.assign(entity, data, options);
}
/**
* Merges given entity to this EntityManager so it becomes managed. You can force refreshing of existing entities
* via second parameter. By default it will return already loaded entities without modifying them.
*/
merge(data: T | EntityData<T>, options?: MergeOptions): T {
return this.em.merge<T>(this.entityName, data, options);
}
/**
* Returns total number of entities matching your `where` query.
*/
async count<P extends string = never>(where: FilterQuery<T> = {} as FilterQuery<T>, options: CountOptions<T, P> = {}): Promise<number> {
return this.em.count<T, P>(this.entityName, where, options);
}
protected get em(): EntityManager {
return this._em.getContext(false);
}
}