UNPKG

19.8 kBJavaScriptView Raw
1"use strict";
2// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
3// Node module: @loopback/repository
4// This file is licensed under the MIT License.
5// License text available at https://opensource.org/licenses/MIT
6Object.defineProperty(exports, "__esModule", { value: true });
7exports.DefaultTransactionalRepository = exports.DefaultCrudRepository = exports.ensurePromise = exports.bindModel = exports.juggler = void 0;
8const tslib_1 = require("tslib");
9const assert_1 = tslib_1.__importDefault(require("assert"));
10const loopback_datasource_juggler_1 = tslib_1.__importDefault(require("loopback-datasource-juggler"));
11const errors_1 = require("../errors");
12const model_1 = require("../model");
13const relations_1 = require("../relations");
14const type_resolver_1 = require("../type-resolver");
15var juggler;
16(function (juggler) {
17 /* eslint-disable @typescript-eslint/no-unused-vars */
18 juggler.DataSource = loopback_datasource_juggler_1.default.DataSource;
19 juggler.ModelBase = loopback_datasource_juggler_1.default.ModelBase;
20 juggler.PersistedModel = loopback_datasource_juggler_1.default.PersistedModel;
21 juggler.KeyValueModel = loopback_datasource_juggler_1.default.KeyValueModel;
22 // eslint-disable-next-line @typescript-eslint/no-shadow
23 juggler.IsolationLevel = loopback_datasource_juggler_1.default.IsolationLevel;
24})(juggler = exports.juggler || (exports.juggler = {}));
25function isModelClass(propertyType) {
26 return (!(0, type_resolver_1.isTypeResolver)(propertyType) &&
27 typeof propertyType === 'function' &&
28 typeof propertyType.definition === 'object' &&
29 propertyType.toString().startsWith('class '));
30}
31/**
32 * This is a bridge to the legacy DAO class. The function mixes DAO methods
33 * into a model class and attach it to a given data source
34 * @param modelClass - Model class
35 * @param ds - Data source
36 * @returns {} The new model class with DAO (CRUD) operations
37 */
38function bindModel(modelClass, ds) {
39 const BoundModelClass = class extends modelClass {
40 };
41 BoundModelClass.attachTo(ds);
42 return BoundModelClass;
43}
44exports.bindModel = bindModel;
45/**
46 * Ensure the value is a promise
47 * @param p - Promise or void
48 */
49function ensurePromise(p) {
50 if (p && p instanceof Promise) {
51 return p;
52 }
53 else {
54 return Promise.reject(new Error('The value should be a Promise: ' + p));
55 }
56}
57exports.ensurePromise = ensurePromise;
58/**
59 * Default implementation of CRUD repository using legacy juggler model
60 * and data source
61 */
62class DefaultCrudRepository {
63 /**
64 * Constructor of DefaultCrudRepository
65 * @param entityClass - LoopBack 4 entity class
66 * @param dataSource - Legacy juggler data source
67 */
68 constructor(
69 // entityClass should have type "typeof T", but that's not supported by TSC
70 entityClass, dataSource) {
71 this.entityClass = entityClass;
72 this.dataSource = dataSource;
73 this.inclusionResolvers = new Map();
74 const definition = entityClass.definition;
75 (0, assert_1.default)(!!definition, `Entity ${entityClass.name} must have valid model definition.`);
76 (0, assert_1.default)(definition.idProperties().length > 0, `Entity ${entityClass.name} must have at least one id/pk property.`);
77 this.modelClass = this.ensurePersistedModel(entityClass);
78 }
79 // Create an internal legacy Model attached to the datasource
80 ensurePersistedModel(entityClass) {
81 const definition = entityClass.definition;
82 (0, assert_1.default)(!!definition, `Entity ${entityClass.name} must have valid model definition.`);
83 const dataSource = this.dataSource;
84 const model = dataSource.getModel(definition.name);
85 if (model) {
86 // The backing persisted model has been already defined.
87 return model;
88 }
89 return this.definePersistedModel(entityClass);
90 }
91 /**
92 * Creates a legacy persisted model class, attaches it to the datasource and
93 * returns it. This method can be overridden in sub-classes to acess methods
94 * and properties in the generated model class.
95 * @param entityClass - LB4 Entity constructor
96 */
97 definePersistedModel(entityClass) {
98 const dataSource = this.dataSource;
99 const definition = entityClass.definition;
100 // To handle circular reference back to the same model,
101 // we create a placeholder model that will be replaced by real one later
102 dataSource.getModel(definition.name, true /* forceCreate */);
103 // We need to convert property definitions from PropertyDefinition
104 // to plain data object because of a juggler limitation
105 const properties = {};
106 // We need to convert PropertyDefinition into the definition that
107 // the juggler understands
108 Object.entries(definition.properties).forEach(([key, value]) => {
109 // always clone value so that we do not modify the original model definition
110 // ensures that model definitions can be reused with multiple datasources
111 if (value.type === 'array' || value.type === Array) {
112 value = Object.assign({}, value, {
113 type: [value.itemType && this.resolvePropertyType(value.itemType)],
114 });
115 delete value.itemType;
116 }
117 else {
118 value = Object.assign({}, value, {
119 type: this.resolvePropertyType(value.type),
120 });
121 }
122 properties[key] = Object.assign({}, value);
123 });
124 const modelClass = dataSource.createModel(definition.name, properties, Object.assign(
125 // settings that users can override
126 { strict: true },
127 // user-defined settings
128 definition.settings,
129 // settings enforced by the framework
130 { strictDelete: false }));
131 modelClass.attachTo(dataSource);
132 return modelClass;
133 }
134 resolvePropertyType(type) {
135 const resolved = (0, type_resolver_1.resolveType)(type);
136 return isModelClass(resolved)
137 ? this.ensurePersistedModel(resolved)
138 : resolved;
139 }
140 /**
141 * @deprecated
142 * Function to create a constrained relation repository factory
143 *
144 * Use `this.createHasManyRepositoryFactoryFor()` instead
145 *
146 * @param relationName - Name of the relation defined on the source model
147 * @param targetRepo - Target repository instance
148 */
149 _createHasManyRepositoryFactoryFor(relationName, targetRepositoryGetter) {
150 return this.createHasManyRepositoryFactoryFor(relationName, targetRepositoryGetter);
151 }
152 /**
153 * Function to create a constrained relation repository factory
154 *
155 * @example
156 * ```ts
157 * class CustomerRepository extends DefaultCrudRepository<
158 * Customer,
159 * typeof Customer.prototype.id,
160 * CustomerRelations
161 * > {
162 * public readonly orders: HasManyRepositoryFactory<Order, typeof Customer.prototype.id>;
163 *
164 * constructor(
165 * protected db: juggler.DataSource,
166 * orderRepository: EntityCrudRepository<Order, typeof Order.prototype.id>,
167 * ) {
168 * super(Customer, db);
169 * this.orders = this._createHasManyRepositoryFactoryFor(
170 * 'orders',
171 * orderRepository,
172 * );
173 * }
174 * }
175 * ```
176 *
177 * @param relationName - Name of the relation defined on the source model
178 * @param targetRepo - Target repository instance
179 */
180 createHasManyRepositoryFactoryFor(relationName, targetRepositoryGetter) {
181 const meta = this.entityClass.definition.relations[relationName];
182 return (0, relations_1.createHasManyRepositoryFactory)(meta, targetRepositoryGetter);
183 }
184 /**
185 * Function to create a constrained hasManyThrough relation repository factory
186 *
187 * @example
188 * ```ts
189 * class CustomerRepository extends DefaultCrudRepository<
190 * Customer,
191 * typeof Customer.prototype.id,
192 * CustomerRelations
193 * > {
194 * public readonly cartItems: HasManyRepositoryFactory<CartItem, typeof Customer.prototype.id>;
195 *
196 * constructor(
197 * protected db: juggler.DataSource,
198 * cartItemRepository: EntityCrudRepository<CartItem, typeof, CartItem.prototype.id>,
199 * throughRepository: EntityCrudRepository<Through, typeof Through.prototype.id>,
200 * ) {
201 * super(Customer, db);
202 * this.cartItems = this.createHasManyThroughRepositoryFactoryFor(
203 * 'cartItems',
204 * cartItemRepository,
205 * );
206 * }
207 * }
208 * ```
209 *
210 * @param relationName - Name of the relation defined on the source model
211 * @param targetRepo - Target repository instance
212 * @param throughRepo - Through repository instance
213 */
214 createHasManyThroughRepositoryFactoryFor(relationName, targetRepositoryGetter, throughRepositoryGetter) {
215 const meta = this.entityClass.definition.relations[relationName];
216 return (0, relations_1.createHasManyThroughRepositoryFactory)(meta, targetRepositoryGetter, throughRepositoryGetter);
217 }
218 /**
219 * @deprecated
220 * Function to create a belongs to accessor
221 *
222 * Use `this.createBelongsToAccessorFor()` instead
223 *
224 * @param relationName - Name of the relation defined on the source model
225 * @param targetRepo - Target repository instance
226 */
227 _createBelongsToAccessorFor(relationName, targetRepositoryGetter) {
228 return this.createBelongsToAccessorFor(relationName, targetRepositoryGetter);
229 }
230 /**
231 * Function to create a belongs to accessor
232 *
233 * @param relationName - Name of the relation defined on the source model
234 * @param targetRepo - Target repository instance
235 */
236 createBelongsToAccessorFor(relationName, targetRepositoryGetter) {
237 const meta = this.entityClass.definition.relations[relationName];
238 return (0, relations_1.createBelongsToAccessor)(meta, targetRepositoryGetter, this);
239 }
240 /**
241 * @deprecated
242 * Function to create a constrained hasOne relation repository factory
243 *
244 * @param relationName - Name of the relation defined on the source model
245 * @param targetRepo - Target repository instance
246 */
247 _createHasOneRepositoryFactoryFor(relationName, targetRepositoryGetter) {
248 return this.createHasOneRepositoryFactoryFor(relationName, targetRepositoryGetter);
249 }
250 /**
251 * Function to create a constrained hasOne relation repository factory
252 *
253 * @param relationName - Name of the relation defined on the source model
254 * @param targetRepo - Target repository instance
255 */
256 createHasOneRepositoryFactoryFor(relationName, targetRepositoryGetter) {
257 const meta = this.entityClass.definition.relations[relationName];
258 return (0, relations_1.createHasOneRepositoryFactory)(meta, targetRepositoryGetter);
259 }
260 /**
261 * @deprecated
262 * Function to create a references many accessor
263 *
264 * Use `this.createReferencesManyAccessorFor()` instead
265 *
266 * @param relationName - Name of the relation defined on the source model
267 * @param targetRepo - Target repository instance
268 */
269 _createReferencesManyAccessorFor(relationName, targetRepoGetter) {
270 return this.createReferencesManyAccessorFor(relationName, targetRepoGetter);
271 }
272 /**
273 * Function to create a references many accessor
274 *
275 * @param relationName - Name of the relation defined on the source model
276 * @param targetRepo - Target repository instance
277 */
278 createReferencesManyAccessorFor(relationName, targetRepoGetter) {
279 const meta = this.entityClass.definition.relations[relationName];
280 return (0, relations_1.createReferencesManyAccessor)(meta, targetRepoGetter, this);
281 }
282 async create(entity, options) {
283 // perform persist hook
284 const data = this.entityToData(entity, options);
285 const model = await ensurePromise(this.modelClass.create(data, options));
286 return this.toEntity(model);
287 }
288 async createAll(entities, options) {
289 // perform persist hook
290 const data = entities.map(e => this.entityToData(e, options));
291 const models = await ensurePromise(this.modelClass.createAll(data, options));
292 return this.toEntities(models);
293 }
294 async save(entity, options) {
295 const id = this.entityClass.getIdOf(entity);
296 if (id == null) {
297 return this.create(entity, options);
298 }
299 else {
300 await this.replaceById(id, entity, options);
301 return new this.entityClass(entity.toObject());
302 }
303 }
304 async find(filter, options) {
305 const include = filter === null || filter === void 0 ? void 0 : filter.include;
306 const models = await ensurePromise(this.modelClass.find(this.normalizeFilter(filter), options));
307 const entities = this.toEntities(models);
308 return this.includeRelatedModels(entities, include, options);
309 }
310 async findOne(filter, options) {
311 const model = await ensurePromise(this.modelClass.findOne(this.normalizeFilter(filter), options));
312 if (!model)
313 return null;
314 const entity = this.toEntity(model);
315 const include = filter === null || filter === void 0 ? void 0 : filter.include;
316 const resolved = await this.includeRelatedModels([entity], include, options);
317 return resolved[0];
318 }
319 async findById(id, filter, options) {
320 const include = filter === null || filter === void 0 ? void 0 : filter.include;
321 const model = await ensurePromise(this.modelClass.findById(id, this.normalizeFilter(filter), options));
322 if (!model) {
323 throw new errors_1.EntityNotFoundError(this.entityClass, id);
324 }
325 const entity = this.toEntity(model);
326 const resolved = await this.includeRelatedModels([entity], include, options);
327 return resolved[0];
328 }
329 update(entity, options) {
330 return this.updateById(entity.getId(), entity, options);
331 }
332 async delete(entity, options) {
333 // perform persist hook
334 this.entityToData(entity, options);
335 return this.deleteById(entity.getId(), options);
336 }
337 async updateAll(data, where, options) {
338 where = where !== null && where !== void 0 ? where : {};
339 const persistedData = this.entityToData(data, options);
340 const result = await ensurePromise(this.modelClass.updateAll(where, persistedData, options));
341 return { count: result.count };
342 }
343 async updateById(id, data, options) {
344 if (id === undefined) {
345 throw new Error('Invalid Argument: id cannot be undefined');
346 }
347 const idProp = this.modelClass.definition.idName();
348 const where = {};
349 where[idProp] = id;
350 const result = await this.updateAll(data, where, options);
351 if (result.count === 0) {
352 throw new errors_1.EntityNotFoundError(this.entityClass, id);
353 }
354 }
355 async replaceById(id, data, options) {
356 try {
357 const payload = this.entityToData(data, options);
358 await ensurePromise(this.modelClass.replaceById(id, payload, options));
359 }
360 catch (err) {
361 if (err.statusCode === 404) {
362 throw new errors_1.EntityNotFoundError(this.entityClass, id);
363 }
364 throw err;
365 }
366 }
367 async deleteAll(where, options) {
368 const result = await ensurePromise(this.modelClass.deleteAll(where, options));
369 return { count: result.count };
370 }
371 async deleteById(id, options) {
372 const result = await ensurePromise(this.modelClass.deleteById(id, options));
373 if (result.count === 0) {
374 throw new errors_1.EntityNotFoundError(this.entityClass, id);
375 }
376 }
377 async count(where, options) {
378 const result = await ensurePromise(this.modelClass.count(where, options));
379 return { count: result };
380 }
381 exists(id, options) {
382 return ensurePromise(this.modelClass.exists(id, options));
383 }
384 async execute(...args) {
385 return ensurePromise(this.dataSource.execute(...args));
386 }
387 toEntity(model) {
388 return new this.entityClass(model.toObject());
389 }
390 toEntities(models) {
391 return models.map(m => this.toEntity(m));
392 }
393 /**
394 * Register an inclusion resolver for the related model name.
395 *
396 * @param relationName - Name of the relation defined on the source model
397 * @param resolver - Resolver function for getting related model entities
398 */
399 registerInclusionResolver(relationName, resolver) {
400 this.inclusionResolvers.set(relationName, resolver);
401 }
402 /**
403 * Returns model instances that include related models of this repository
404 * that have a registered resolver.
405 *
406 * @param entities - An array of entity instances or data
407 * @param include -Inclusion filter
408 * @param options - Options for the operations
409 */
410 async includeRelatedModels(entities, include, options) {
411 return (0, relations_1.includeRelatedModels)(this, entities, include, options);
412 }
413 /**
414 * This function works as a persist hook.
415 * It converts an entity from the CRUD operations' caller
416 * to a persistable data that can will be stored in the
417 * back-end database.
418 *
419 * User can extend `DefaultCrudRepository` then override this
420 * function to execute custom persist hook.
421 * @param entity The entity passed from CRUD operations' caller.
422 * @param options
423 */
424 entityToData(entity, options = {}) {
425 return this.ensurePersistable(entity, options);
426 }
427 /** Converts an entity object to a JSON object to check if it contains navigational property.
428 * Throws an error if `entity` contains navigational property.
429 *
430 * @param entity The entity passed from CRUD operations' caller.
431 * @param options
432 */
433 ensurePersistable(entity, options = {}) {
434 // FIXME(bajtos) Ideally, we should call toJSON() to convert R to data object
435 // Unfortunately that breaks replaceById for MongoDB connector, where we
436 // would call replaceId with id *argument* set to ObjectID value but
437 // id *property* set to string value.
438 /*
439 const data: AnyObject =
440 typeof entity.toJSON === 'function' ? entity.toJSON() : {...entity};
441 */
442 const data = new this.entityClass(entity);
443 (0, model_1.rejectNavigationalPropertiesInData)(this.entityClass, data);
444 return data;
445 }
446 /**
447 * Removes juggler's "include" filter as it does not apply to LoopBack 4
448 * relations.
449 *
450 * @param filter - Query filter
451 */
452 normalizeFilter(filter) {
453 if (!filter)
454 return undefined;
455 return { ...filter, include: undefined };
456 }
457}
458exports.DefaultCrudRepository = DefaultCrudRepository;
459/**
460 * Default implementation of CRUD repository using legacy juggler model
461 * and data source with beginTransaction() method for connectors which
462 * support Transactions
463 */
464class DefaultTransactionalRepository extends DefaultCrudRepository {
465 async beginTransaction(options) {
466 const dsOptions = options !== null && options !== void 0 ? options : {};
467 // juggler.Transaction still has the Promise/Callback variants of the
468 // Transaction methods
469 // so we need it cast it back
470 return (await this.dataSource.beginTransaction(dsOptions));
471 }
472}
473exports.DefaultTransactionalRepository = DefaultTransactionalRepository;
474//# sourceMappingURL=legacy-juggler-bridge.js.map
\No newline at end of file