UNPKG

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