@vsaas/loopback-datasource-juggler 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +25 -0
  2. package/NOTICE +23 -0
  3. package/README.md +74 -0
  4. package/dist/_virtual/_rolldown/runtime.js +4 -0
  5. package/dist/index.js +26 -0
  6. package/dist/lib/browser.depd.js +13 -0
  7. package/dist/lib/case-utils.js +21 -0
  8. package/dist/lib/connectors/kv-memory.js +158 -0
  9. package/dist/lib/connectors/memory.js +810 -0
  10. package/dist/lib/connectors/transient.js +126 -0
  11. package/dist/lib/dao.js +2445 -0
  12. package/dist/lib/datasource.js +2215 -0
  13. package/dist/lib/date-string.js +87 -0
  14. package/dist/lib/geo.js +244 -0
  15. package/dist/lib/globalize.js +33 -0
  16. package/dist/lib/hooks.js +79 -0
  17. package/dist/lib/id-utils.js +66 -0
  18. package/dist/lib/include.js +795 -0
  19. package/dist/lib/include_utils.js +104 -0
  20. package/dist/lib/introspection.js +37 -0
  21. package/dist/lib/jutil.js +65 -0
  22. package/dist/lib/kvao/delete-all.js +57 -0
  23. package/dist/lib/kvao/delete.js +43 -0
  24. package/dist/lib/kvao/expire.js +35 -0
  25. package/dist/lib/kvao/get.js +34 -0
  26. package/dist/lib/kvao/index.js +28 -0
  27. package/dist/lib/kvao/iterate-keys.js +38 -0
  28. package/dist/lib/kvao/keys.js +55 -0
  29. package/dist/lib/kvao/set.js +39 -0
  30. package/dist/lib/kvao/ttl.js +35 -0
  31. package/dist/lib/list.js +101 -0
  32. package/dist/lib/mixins.js +58 -0
  33. package/dist/lib/model-builder.js +608 -0
  34. package/dist/lib/model-definition.js +231 -0
  35. package/dist/lib/model-utils.js +368 -0
  36. package/dist/lib/model.js +586 -0
  37. package/dist/lib/observer.js +235 -0
  38. package/dist/lib/relation-definition.js +2604 -0
  39. package/dist/lib/relations.js +587 -0
  40. package/dist/lib/scope.js +392 -0
  41. package/dist/lib/transaction.js +183 -0
  42. package/dist/lib/types.js +58 -0
  43. package/dist/lib/utils.js +625 -0
  44. package/dist/lib/validations.js +742 -0
  45. package/dist/package.js +93 -0
  46. package/package.json +85 -0
  47. package/types/common.d.ts +28 -0
  48. package/types/connector.d.ts +52 -0
  49. package/types/datasource.d.ts +324 -0
  50. package/types/date-string.d.ts +21 -0
  51. package/types/inclusion-mixin.d.ts +44 -0
  52. package/types/index.d.ts +36 -0
  53. package/types/kv-model.d.ts +201 -0
  54. package/types/model.d.ts +368 -0
  55. package/types/observer-mixin.d.ts +174 -0
  56. package/types/persisted-model.d.ts +505 -0
  57. package/types/query.d.ts +108 -0
  58. package/types/relation-mixin.d.ts +577 -0
  59. package/types/relation.d.ts +301 -0
  60. package/types/scope.d.ts +92 -0
  61. package/types/transaction-mixin.d.ts +47 -0
  62. package/types/types.d.ts +65 -0
  63. package/types/validation-mixin.d.ts +287 -0
@@ -0,0 +1,795 @@
1
+ "use strict";
2
+ const require_runtime = require("../_virtual/_rolldown/runtime.js");
3
+ const require_lib_globalize = require("./globalize.js");
4
+ const require_lib_utils = require("./utils.js");
5
+ const require_lib_list = require("./list.js");
6
+ const require_lib_include_utils = require("./include_utils.js");
7
+ //#region src/lib/include.ts
8
+ var require_include = /* @__PURE__ */ require_runtime.__commonJSMin(((exports, module) => {
9
+ const g = require_lib_globalize();
10
+ const utils = require_lib_utils;
11
+ const List = require_lib_list;
12
+ const includeUtils = require_lib_include_utils;
13
+ const isPlainObject = utils.isPlainObject;
14
+ const defineCachedRelations = utils.defineCachedRelations;
15
+ const uniq = utils.uniq;
16
+ const idName = utils.idName;
17
+ const debug = require("debug")("loopback:include");
18
+ const DISALLOWED_TYPES = [
19
+ "boolean",
20
+ "number",
21
+ "symbol",
22
+ "function"
23
+ ];
24
+ /*!
25
+ * Normalize the include to be an array
26
+ * @param include
27
+ * @returns {*}
28
+ */
29
+ function normalizeInclude(include) {
30
+ let newInclude;
31
+ if (typeof include === "string") return [include];
32
+ else if (isPlainObject(include)) {
33
+ newInclude = [];
34
+ const rel = include.rel || include.relation;
35
+ const obj = {};
36
+ if (typeof rel === "string") {
37
+ obj[rel] = new IncludeScope(include.scope);
38
+ newInclude.push(obj);
39
+ } else for (const key in include) {
40
+ obj[key] = include[key];
41
+ newInclude.push(obj);
42
+ }
43
+ return newInclude;
44
+ } else if (Array.isArray(include)) {
45
+ newInclude = [];
46
+ for (let i = 0, n = include.length; i < n; i++) {
47
+ const subIncludes = normalizeInclude(include[i]);
48
+ for (let j = 0, m = subIncludes.length; j < m; j++) newInclude.push(subIncludes[j]);
49
+ }
50
+ return newInclude;
51
+ } else if (DISALLOWED_TYPES.includes(typeof include)) {
52
+ debug("Ignoring invalid \"include\" value of type %s:", typeof include, include);
53
+ return [];
54
+ } else return include;
55
+ }
56
+ function IncludeScope(scope) {
57
+ this._scope = utils.deepMerge({}, scope || {});
58
+ if (this._scope.include) {
59
+ this._include = normalizeInclude(this._scope.include);
60
+ delete this._scope.include;
61
+ } else this._include = null;
62
+ }
63
+ IncludeScope.prototype.conditions = function() {
64
+ return utils.deepMerge({}, this._scope);
65
+ };
66
+ IncludeScope.prototype.include = function() {
67
+ return this._include;
68
+ };
69
+ /*!
70
+ * Look up a model by name from the list of given models
71
+ * @param {Object} models Models keyed by name
72
+ * @param {String} modelName The model name
73
+ * @returns {*} The matching model class
74
+ */
75
+ function lookupModel(models, modelName) {
76
+ if (models[modelName]) return models[modelName];
77
+ const lookupClassName = modelName.toLowerCase();
78
+ for (const name in models) if (name.toLowerCase() === lookupClassName) return models[name];
79
+ }
80
+ /**
81
+ * Utility Function to allow interleave before and after high computation tasks
82
+ * @param tasks
83
+ * @param callback
84
+ */
85
+ function execTasksWithInterLeave(tasks, callback) {
86
+ process.nextTick(function() {
87
+ try {
88
+ runParallelTasks(tasks, function(err, info) {
89
+ process.nextTick(function() {
90
+ callback(err, info);
91
+ });
92
+ });
93
+ } catch (err) {
94
+ callback(err);
95
+ }
96
+ });
97
+ }
98
+ function once(fn) {
99
+ let called = false;
100
+ return function() {
101
+ if (called) return;
102
+ called = true;
103
+ fn.apply(this, arguments);
104
+ };
105
+ }
106
+ function runParallelTasks(tasks, callback) {
107
+ if (!tasks || tasks.length === 0) return callback(null, []);
108
+ const results = Array.from({ length: tasks.length });
109
+ let pending = tasks.length;
110
+ let finished = false;
111
+ for (let i = 0; i < tasks.length; i++) {
112
+ const task = tasks[i];
113
+ const done = once(function(err, result) {
114
+ if (finished) return;
115
+ if (err) {
116
+ finished = true;
117
+ return callback(err);
118
+ }
119
+ results[i] = result;
120
+ pending--;
121
+ if (pending === 0) callback(null, results);
122
+ });
123
+ try {
124
+ task(done);
125
+ } catch (err) {
126
+ done(err);
127
+ }
128
+ }
129
+ }
130
+ function eachParallel(items, iterator, callback) {
131
+ if (!items || items.length === 0) return callback();
132
+ let pending = items.length;
133
+ let finished = false;
134
+ for (let i = 0; i < items.length; i++) {
135
+ const item = items[i];
136
+ const done = once(function(err) {
137
+ if (finished) return;
138
+ if (err) {
139
+ finished = true;
140
+ return callback(err);
141
+ }
142
+ pending--;
143
+ if (pending === 0) callback();
144
+ });
145
+ try {
146
+ iterator(item, done);
147
+ } catch (err) {
148
+ done(err);
149
+ }
150
+ }
151
+ }
152
+ /*!
153
+ * Include mixin for ./model.js
154
+ */
155
+ module.exports = Inclusion;
156
+ /**
157
+ * Inclusion - Model mixin.
158
+ *
159
+ * @class
160
+ */
161
+ function Inclusion() {}
162
+ /**
163
+ * Normalize includes - used in DataAccessObject
164
+ *
165
+ */
166
+ Inclusion.normalizeInclude = normalizeInclude;
167
+ /**
168
+ * Enables you to load relations of several objects and optimize numbers of requests.
169
+ *
170
+ * Examples:
171
+ *
172
+ * Load all users' posts with only one additional request:
173
+ * `User.include(users, 'posts', function() {});`
174
+ * Or
175
+ * `User.include(users, ['posts'], function() {});`
176
+ *
177
+ * Load all users posts and passports with two additional requests:
178
+ * `User.include(users, ['posts', 'passports'], function() {});`
179
+ *
180
+ * Load all passports owner (users), and all posts of each owner loaded:
181
+ *```Passport.include(passports, {owner: 'posts'}, function() {});
182
+ *``` Passport.include(passports, {owner: ['posts', 'passports']});
183
+ *``` Passport.include(passports, {owner: [{posts: 'images'}, 'passports']});
184
+ *
185
+ * @param {Array} objects Array of instances
186
+ * @param {String|Object|Array} include Which relations to load.
187
+ * @param {Object} [options] Options for CRUD
188
+ * @param {Function} cb Callback called when relations are loaded
189
+ *
190
+ */
191
+ Inclusion.include = function(objects, include, options, cb) {
192
+ if (typeof options === "function" && cb === void 0) {
193
+ cb = options;
194
+ options = {};
195
+ }
196
+ const self = this;
197
+ if (!include || Array.isArray(include) && include.length === 0 || Array.isArray(objects) && objects.length === 0 || isPlainObject(include) && Object.keys(include).length === 0) return process.nextTick(function() {
198
+ if (cb) cb(null, objects);
199
+ });
200
+ include = normalizeInclude(include);
201
+ debug("include: %j", include);
202
+ let inqLimit = 256;
203
+ if (self.dataSource && self.dataSource.settings && self.dataSource.settings.inqLimit) inqLimit = self.dataSource.settings.inqLimit;
204
+ eachParallel(include, function(item, callback) {
205
+ try {
206
+ processIncludeItem(objects, item, options, callback);
207
+ } catch (err) {
208
+ callback(err);
209
+ }
210
+ }, function(err) {
211
+ debug(err, objects);
212
+ if (cb) cb(err, objects);
213
+ });
214
+ /**
215
+ * Find related items with an array of foreign keys by page
216
+ * @param model The model class
217
+ * @param filter The query filter
218
+ * @param fkName The name of the foreign key property
219
+ * @param pageSize The size of page
220
+ * @param options Options
221
+ * @param cb
222
+ */
223
+ function findWithForeignKeysByPage(model, filter, fkName, pageSize, options, cb) {
224
+ try {
225
+ const opts = Object.assign({ prohibitProtectedPropertiesInQuery: true }, options);
226
+ model._sanitizeQuery(filter.where, opts);
227
+ model._coerce(filter.where, options);
228
+ } catch (e) {
229
+ return cb(e);
230
+ }
231
+ let foreignKeys = [];
232
+ if (filter.where[fkName]) foreignKeys = filter.where[fkName].inq;
233
+ else if (filter.where.and) {
234
+ for (const j in filter.where.and) if (filter.where.and[j][fkName] && Array.isArray(filter.where.and[j][fkName].inq)) {
235
+ foreignKeys = filter.where.and[j][fkName].inq;
236
+ break;
237
+ }
238
+ }
239
+ if (!foreignKeys.length) return cb(null, []);
240
+ if (filter.limit || filter.skip || filter.offset) pageSize = 1;
241
+ const size = foreignKeys.length;
242
+ if (size > inqLimit && pageSize <= 0) pageSize = inqLimit;
243
+ if (pageSize <= 0) return model.find(filter, options, cb);
244
+ let listOfFKs = [];
245
+ for (let i = 0; i < size; i += pageSize) {
246
+ let end = i + pageSize;
247
+ if (end > size) end = size;
248
+ listOfFKs.push(foreignKeys.slice(i, end));
249
+ }
250
+ const items = [];
251
+ listOfFKs = listOfFKs.filter(function(keys) {
252
+ return keys.length > 0;
253
+ });
254
+ eachParallel(listOfFKs, function(foreignKeys, done) {
255
+ const newFilter = {};
256
+ for (const f in filter) newFilter[f] = filter[f];
257
+ if (filter.where) {
258
+ newFilter.where = {};
259
+ for (const w in filter.where) newFilter.where[w] = filter.where[w];
260
+ }
261
+ newFilter.where[fkName] = foreignKeys.length === 1 ? foreignKeys[0] : { inq: foreignKeys };
262
+ model.find(newFilter, options, function(err, results) {
263
+ if (err) return done(err);
264
+ for (let i = 0; i < results.length; i++) items.push(results[i]);
265
+ done();
266
+ });
267
+ }, function(err) {
268
+ if (err) return cb(err);
269
+ cb(null, items);
270
+ });
271
+ }
272
+ function processIncludeItem(objs, include, options, cb) {
273
+ const relations = self.relations;
274
+ let relationName;
275
+ let subInclude = null, scope = null;
276
+ if (isPlainObject(include)) {
277
+ relationName = Object.keys(include)[0];
278
+ if (include[relationName] instanceof IncludeScope) {
279
+ scope = include[relationName];
280
+ subInclude = scope.include();
281
+ } else {
282
+ subInclude = include[relationName];
283
+ if (subInclude === true) subInclude = null;
284
+ }
285
+ } else {
286
+ relationName = include;
287
+ subInclude = null;
288
+ }
289
+ const relation = relations[relationName];
290
+ if (!relation) {
291
+ cb(new Error(g.f("Relation \"%s\" is not defined for %s model", relationName, self.modelName)));
292
+ return;
293
+ }
294
+ debug("Relation: %j", relation);
295
+ const polymorphic = relation.polymorphic;
296
+ if (!relation.modelTo) {
297
+ if (!relation.polymorphic) {
298
+ cb(new Error(g.f("{{Relation.modelTo}} is not defined for relation %s and is no {{polymorphic}}", relationName)));
299
+ return;
300
+ }
301
+ }
302
+ if (relation.options.disableInclude) {
303
+ debug("Relation is disabled from include", relation);
304
+ return cb();
305
+ }
306
+ const filter = scope && scope.conditions() || {};
307
+ if ((relation.multiple || relation.type === "belongsTo") && scope) {
308
+ const includeScope = {};
309
+ if (filter.fields && Array.isArray(subInclude) && relation.modelTo.relations) {
310
+ includeScope.fields = [];
311
+ subInclude.forEach(function(name) {
312
+ const rel = relation.modelTo.relations[name];
313
+ if (rel && rel.type === "belongsTo") includeScope.fields.push(rel.keyFrom);
314
+ });
315
+ }
316
+ utils.mergeQuery(filter, includeScope, { fields: false });
317
+ }
318
+ filter.where = filter.where || {};
319
+ let fields = filter.fields;
320
+ if (typeof fields === "string") filter.fields = fields = [fields];
321
+ if (Array.isArray(fields) && fields.indexOf(relation.keyTo) === -1) fields.push(relation.keyTo);
322
+ else if (isPlainObject(fields) && !fields[relation.keyTo]) fields[relation.keyTo] = true;
323
+ if (relation.multiple) {
324
+ if (relation.modelThrough) return includeHasManyThrough(cb);
325
+ if (relation.type === "embedsMany") return includeEmbeds(cb);
326
+ if (relation.type === "referencesMany") return includeReferencesMany(cb);
327
+ if (relation.type === "hasMany" && relation.multiple && !subInclude) return includeHasManySimple(cb);
328
+ return includeHasMany(cb);
329
+ } else {
330
+ if (polymorphic) {
331
+ if (relation.type === "hasOne") return includePolymorphicHasOne(cb);
332
+ return includePolymorphicBelongsTo(cb);
333
+ }
334
+ if (relation.type === "embedsOne") return includeEmbeds(cb);
335
+ return includeOneToOne(cb);
336
+ }
337
+ /**
338
+ * Handle inclusion of HasManyThrough/HasAndBelongsToMany/Polymorphic
339
+ * HasManyThrough relations
340
+ * @param callback
341
+ */
342
+ function includeHasManyThrough(callback) {
343
+ const sourceIds = [];
344
+ const objIdMap = {};
345
+ for (let i = 0; i < objs.length; i++) {
346
+ const obj = objs[i];
347
+ const sourceId = obj[relation.keyFrom];
348
+ if (sourceId) {
349
+ sourceIds.push(sourceId);
350
+ objIdMap[sourceId.toString()] = obj;
351
+ }
352
+ defineCachedRelations(obj);
353
+ obj.__cachedRelations[relationName] = [];
354
+ }
355
+ const throughFilter = { where: {} };
356
+ throughFilter.where[relation.keyTo] = { inq: uniq(sourceIds) };
357
+ if (polymorphic) {
358
+ const throughModel = polymorphic.invert ? relation.modelTo : relation.modelFrom;
359
+ throughFilter.where[polymorphic.discriminator] = throughModel.definition.name;
360
+ }
361
+ findWithForeignKeysByPage(relation.modelThrough, throughFilter, relation.keyTo, 0, options, throughFetchHandler);
362
+ /**
363
+ * Handle the results of Through model objects and fetch the modelTo items
364
+ * @param err
365
+ * @param {Array<Model>} throughObjs
366
+ * @returns {*}
367
+ */
368
+ function throughFetchHandler(err, throughObjs) {
369
+ if (err) return callback(err);
370
+ const targetIds = [];
371
+ const targetObjsMap = {};
372
+ for (let i = 0; i < throughObjs.length; i++) {
373
+ const throughObj = throughObjs[i];
374
+ const targetId = throughObj[relation.keyThrough];
375
+ if (targetId) {
376
+ targetIds.push(targetId);
377
+ const sourceObj = objIdMap[throughObj[relation.keyTo]];
378
+ const targetIdStr = targetId.toString();
379
+ (targetObjsMap[targetIdStr] = targetObjsMap[targetIdStr] || []).push(sourceObj);
380
+ }
381
+ }
382
+ const modelToIdName = idName(relation.modelTo);
383
+ filter.where[modelToIdName] = { inq: uniq(targetIds) };
384
+ if (Array.isArray(fields) && fields.indexOf(modelToIdName) === -1) fields.push(modelToIdName);
385
+ else if (isPlainObject(fields) && !fields[modelToIdName]) fields[modelToIdName] = true;
386
+ findWithForeignKeysByPage(relation.modelTo, filter, modelToIdName, 0, options, targetsFetchHandler);
387
+ function targetsFetchHandler(err, targets) {
388
+ if (err) return callback(err);
389
+ const tasks = [];
390
+ if (subInclude && targets) tasks.push(function subIncludesTask(next) {
391
+ relation.modelTo.include(targets, subInclude, options, next);
392
+ });
393
+ tasks.push(targetLinkingTask);
394
+ function targetLinkingTask(next) {
395
+ eachParallel(targets, linkManyToMany, next);
396
+ function linkManyToMany(target, next) {
397
+ const targetId = target[modelToIdName];
398
+ if (!targetId) return next(new Error(g.f("LinkManyToMany received target that doesn't contain required \"%s\"", modelToIdName)));
399
+ const objList = targetObjsMap[targetId.toString()];
400
+ eachParallel(objList, function(obj, next) {
401
+ if (!obj) return next();
402
+ obj.__cachedRelations[relationName].push(target);
403
+ processTargetObj(obj, next);
404
+ }, next);
405
+ }
406
+ }
407
+ execTasksWithInterLeave(tasks, callback);
408
+ }
409
+ }
410
+ }
411
+ /**
412
+ * Handle inclusion of ReferencesMany relation
413
+ * @param callback
414
+ */
415
+ function includeReferencesMany(callback) {
416
+ const modelToIdName = idName(relation.modelTo);
417
+ const allTargetIds = [];
418
+ const targetObjsMap = {};
419
+ for (let i = 0; i < objs.length; i++) {
420
+ const obj = objs[i];
421
+ let targetIds = obj[relation.keyFrom];
422
+ if (targetIds) {
423
+ if (typeof targetIds === "string") targetIds = JSON.parse(targetIds);
424
+ for (let j = 0; j < targetIds.length; j++) allTargetIds.push(targetIds[j]);
425
+ for (let j = 0; j < targetIds.length; j++) {
426
+ const targetIdStr = targetIds[j].toString();
427
+ (targetObjsMap[targetIdStr] = targetObjsMap[targetIdStr] || []).push(obj);
428
+ }
429
+ }
430
+ defineCachedRelations(obj);
431
+ obj.__cachedRelations[relationName] = [];
432
+ }
433
+ filter.where[relation.keyTo] = { inq: uniq(allTargetIds) };
434
+ relation.applyScope(null, filter);
435
+ /**
436
+ * Make the DB Call, fetch all target objects
437
+ */
438
+ findWithForeignKeysByPage(relation.modelTo, filter, relation.keyTo, 0, options, targetFetchHandler);
439
+ /**
440
+ * Handle the fetched target objects
441
+ * @param err
442
+ * @param {Array<Model>}targets
443
+ * @returns {*}
444
+ */
445
+ function targetFetchHandler(err, targets) {
446
+ if (err) return callback(err);
447
+ const tasks = [];
448
+ if (subInclude && targets) tasks.push(function subIncludesTask(next) {
449
+ relation.modelTo.include(targets, subInclude, options, next);
450
+ });
451
+ targets = utils.sortObjectsByIds(modelToIdName, allTargetIds, targets);
452
+ tasks.push(targetLinkingTask);
453
+ function targetLinkingTask(next) {
454
+ eachParallel(targets, linkManyToMany, next);
455
+ function linkManyToMany(target, next) {
456
+ const objList = targetObjsMap[target[relation.keyTo].toString()];
457
+ eachParallel(objList, function(obj, next) {
458
+ if (!obj) return next();
459
+ obj.__cachedRelations[relationName].push(target);
460
+ processTargetObj(obj, next);
461
+ }, next);
462
+ }
463
+ }
464
+ execTasksWithInterLeave(tasks, callback);
465
+ }
466
+ }
467
+ /**
468
+ * Handle inclusion of HasMany relation
469
+ * @param callback
470
+ */
471
+ function includeHasManySimple(callback) {
472
+ const objIdMap2 = includeUtils.buildOneToOneIdentityMapWithOrigKeys(objs, relation.keyFrom);
473
+ filter.where[relation.keyTo] = { inq: uniq(objIdMap2.getKeys()) };
474
+ relation.applyScope(null, filter);
475
+ findWithForeignKeysByPage(relation.modelTo, filter, relation.keyTo, 0, options, targetFetchHandler);
476
+ function targetFetchHandler(err, targets) {
477
+ if (err) return callback(err);
478
+ const targetsIdMap = includeUtils.buildOneToManyIdentityMapWithOrigKeys(targets, relation.keyTo);
479
+ includeUtils.join(objIdMap2, targetsIdMap, function(obj1, valueToMergeIn) {
480
+ defineCachedRelations(obj1);
481
+ obj1.__cachedRelations[relationName] = valueToMergeIn;
482
+ processTargetObj(obj1, function() {});
483
+ });
484
+ callback(err, objs);
485
+ }
486
+ }
487
+ /**
488
+ * Handle inclusion of HasMany relation
489
+ * @param callback
490
+ */
491
+ function includeHasMany(callback) {
492
+ const sourceIds = [];
493
+ const objIdMap = {};
494
+ for (let i = 0; i < objs.length; i++) {
495
+ const obj = objs[i];
496
+ const sourceId = obj[relation.keyFrom];
497
+ if (sourceId) {
498
+ sourceIds.push(sourceId);
499
+ objIdMap[sourceId.toString()] = obj;
500
+ }
501
+ defineCachedRelations(obj);
502
+ obj.__cachedRelations[relationName] = [];
503
+ }
504
+ filter.where[relation.keyTo] = { inq: uniq(sourceIds) };
505
+ relation.applyScope(null, filter);
506
+ options.partitionBy = relation.keyTo;
507
+ findWithForeignKeysByPage(relation.modelTo, filter, relation.keyTo, 0, options, targetFetchHandler);
508
+ /**
509
+ * Process fetched related objects
510
+ * @param err
511
+ * @param {Array<Model>} targets
512
+ * @returns {*}
513
+ */
514
+ function targetFetchHandler(err, targets) {
515
+ if (err) return callback(err);
516
+ const tasks = [];
517
+ if (subInclude && targets) tasks.push(function subIncludesTask(next) {
518
+ relation.modelTo.include(targets, subInclude, options, next);
519
+ });
520
+ tasks.push(targetLinkingTask);
521
+ function targetLinkingTask(next) {
522
+ if (targets.length === 0) return eachParallel(objs, function(obj, next) {
523
+ processTargetObj(obj, next);
524
+ }, next);
525
+ eachParallel(targets, linkManyToOne, function(err) {
526
+ if (err) return next(err);
527
+ const objsWithEmptyRelation = [];
528
+ for (let i = 0; i < objs.length; i++) if (objs[i].__cachedRelations[relationName].length === 0) objsWithEmptyRelation.push(objs[i]);
529
+ eachParallel(objsWithEmptyRelation, function(obj, next) {
530
+ processTargetObj(obj, next);
531
+ }, next);
532
+ });
533
+ function linkManyToOne(target, next) {
534
+ const relationValue = target[relation.keyTo];
535
+ eachParallel(Array.isArray(relationValue) ? relationValue : [relationValue], function(targetId, next) {
536
+ const obj = objIdMap[targetId.toString()];
537
+ if (!obj) return next();
538
+ obj.__cachedRelations[relationName].push(target);
539
+ processTargetObj(obj, next);
540
+ }, next);
541
+ }
542
+ }
543
+ execTasksWithInterLeave(tasks, callback);
544
+ }
545
+ }
546
+ /**
547
+ * Handle Inclusion of Polymorphic BelongsTo relation
548
+ * @param callback
549
+ */
550
+ function includePolymorphicBelongsTo(callback) {
551
+ const targetIdsByType = {};
552
+ const targetObjMapByType = {};
553
+ for (let i = 0; i < objs.length; i++) {
554
+ const obj = objs[i];
555
+ const modelType = obj[polymorphic.discriminator];
556
+ if (modelType) {
557
+ targetIdsByType[modelType] = targetIdsByType[modelType] || [];
558
+ targetObjMapByType[modelType] = targetObjMapByType[modelType] || {};
559
+ const targetIds = targetIdsByType[modelType];
560
+ const targetObjsMap = targetObjMapByType[modelType];
561
+ const targetId = obj[relation.keyFrom];
562
+ if (targetId) {
563
+ targetIds.push(targetId);
564
+ const targetIdStr = targetId.toString();
565
+ targetObjsMap[targetIdStr] = targetObjsMap[targetIdStr] || [];
566
+ targetObjsMap[targetIdStr].push(obj);
567
+ }
568
+ }
569
+ defineCachedRelations(obj);
570
+ obj.__cachedRelations[relationName] = null;
571
+ }
572
+ eachParallel(Object.keys(targetIdsByType), processPolymorphicType, callback);
573
+ /**
574
+ * Process Polymorphic objects of each type (modelType)
575
+ * @param {String} modelType
576
+ * @param callback
577
+ */
578
+ function processPolymorphicType(modelType, callback) {
579
+ const typeFilter = { where: {} };
580
+ utils.mergeQuery(typeFilter, filter);
581
+ const targetIds = targetIdsByType[modelType];
582
+ typeFilter.where[relation.keyTo] = { inq: uniq(targetIds) };
583
+ const Model = lookupModel(relation.modelFrom.dataSource.modelBuilder.models, modelType);
584
+ if (!Model) {
585
+ callback(new Error(g.f("Discriminator type %s specified but no model exists with such name", modelType)));
586
+ return;
587
+ }
588
+ relation.applyScope(null, typeFilter);
589
+ findWithForeignKeysByPage(Model, typeFilter, relation.keyTo, 0, options, targetFetchHandler);
590
+ /**
591
+ * Process fetched related objects
592
+ * @param err
593
+ * @param {Array<Model>} targets
594
+ * @returns {*}
595
+ */
596
+ function targetFetchHandler(err, targets) {
597
+ if (err) return callback(err);
598
+ const tasks = [];
599
+ if (subInclude && targets) tasks.push(function subIncludesTask(next) {
600
+ Model.include(targets, subInclude, options, next);
601
+ });
602
+ tasks.push(targetLinkingTask);
603
+ function targetLinkingTask(next) {
604
+ const targetObjsMap = targetObjMapByType[modelType];
605
+ eachParallel(targets, linkOneToMany, next);
606
+ function linkOneToMany(target, next) {
607
+ const objList = targetObjsMap[target[relation.keyTo].toString()];
608
+ eachParallel(objList, function(obj, next) {
609
+ if (!obj) return next();
610
+ obj.__cachedRelations[relationName] = target;
611
+ processTargetObj(obj, next);
612
+ }, next);
613
+ }
614
+ }
615
+ execTasksWithInterLeave(tasks, callback);
616
+ }
617
+ }
618
+ }
619
+ /**
620
+ * Handle Inclusion of Polymorphic HasOne relation
621
+ * @param callback
622
+ */
623
+ function includePolymorphicHasOne(callback) {
624
+ const sourceIds = [];
625
+ const objIdMap = {};
626
+ for (let i = 0; i < objs.length; i++) {
627
+ const obj = objs[i];
628
+ const sourceId = obj[relation.keyFrom];
629
+ if (sourceId) {
630
+ sourceIds.push(sourceId);
631
+ objIdMap[sourceId.toString()] = obj;
632
+ }
633
+ defineCachedRelations(obj);
634
+ obj.__cachedRelations[relationName] = null;
635
+ }
636
+ filter.where[relation.keyTo] = { inq: uniq(sourceIds) };
637
+ relation.applyScope(null, filter);
638
+ findWithForeignKeysByPage(relation.modelTo, filter, relation.keyTo, 0, options, targetFetchHandler);
639
+ /**
640
+ * Process fetched related objects
641
+ * @param err
642
+ * @param {Array<Model>} targets
643
+ * @returns {*}
644
+ */
645
+ function targetFetchHandler(err, targets) {
646
+ if (err) return callback(err);
647
+ const tasks = [];
648
+ if (subInclude && targets) tasks.push(function subIncludesTask(next) {
649
+ relation.modelTo.include(targets, subInclude, options, next);
650
+ });
651
+ tasks.push(targetLinkingTask);
652
+ function targetLinkingTask(next) {
653
+ eachParallel(targets, linkOneToOne, next);
654
+ function linkOneToOne(target, next) {
655
+ const sourceId = target[relation.keyTo];
656
+ if (!sourceId) return next();
657
+ const obj = objIdMap[sourceId.toString()];
658
+ if (!obj) return next();
659
+ obj.__cachedRelations[relationName] = target;
660
+ processTargetObj(obj, next);
661
+ }
662
+ }
663
+ execTasksWithInterLeave(tasks, callback);
664
+ }
665
+ }
666
+ /**
667
+ * Handle Inclusion of BelongsTo/HasOne relation
668
+ * @param callback
669
+ */
670
+ function includeOneToOne(callback) {
671
+ const targetIds = [];
672
+ const objTargetIdMap = {};
673
+ for (let i = 0; i < objs.length; i++) {
674
+ const obj = objs[i];
675
+ if (relation.type === "belongsTo") {
676
+ if (obj[relation.keyFrom] == null) {
677
+ defineCachedRelations(obj);
678
+ obj.__cachedRelations[relationName] = null;
679
+ debug("ID property \"%s\" is missing in item %j", relation.keyFrom, obj);
680
+ continue;
681
+ }
682
+ }
683
+ const targetId = obj[relation.keyFrom];
684
+ if (targetId) {
685
+ targetIds.push(targetId);
686
+ const targetIdStr = targetId.toString();
687
+ objTargetIdMap[targetIdStr] = objTargetIdMap[targetIdStr] || [];
688
+ objTargetIdMap[targetIdStr].push(obj);
689
+ } else debug("ID property \"%s\" is missing in item %j", relation.keyFrom, obj);
690
+ defineCachedRelations(obj);
691
+ obj.__cachedRelations[relationName] = null;
692
+ }
693
+ filter.where[relation.keyTo] = { inq: uniq(targetIds) };
694
+ relation.applyScope(null, filter);
695
+ findWithForeignKeysByPage(relation.modelTo, filter, relation.keyTo, 0, options, targetFetchHandler);
696
+ /**
697
+ * Process fetched related objects
698
+ * @param err
699
+ * @param {Array<Model>} targets
700
+ * @returns {*}
701
+ */
702
+ function targetFetchHandler(err, targets) {
703
+ if (err) return callback(err);
704
+ const tasks = [];
705
+ if (subInclude && targets) tasks.push(function subIncludesTask(next) {
706
+ relation.modelTo.include(targets, subInclude, options, next);
707
+ });
708
+ tasks.push(targetLinkingTask);
709
+ function targetLinkingTask(next) {
710
+ eachParallel(targets, linkOneToMany, next);
711
+ function linkOneToMany(target, next) {
712
+ const objList = objTargetIdMap[target[relation.keyTo].toString()];
713
+ eachParallel(objList, function(obj, next) {
714
+ if (!obj) return next();
715
+ obj.__cachedRelations[relationName] = target;
716
+ processTargetObj(obj, next);
717
+ }, next);
718
+ }
719
+ }
720
+ execTasksWithInterLeave(tasks, callback);
721
+ }
722
+ }
723
+ /**
724
+ * Handle Inclusion of EmbedsMany/EmbedsManyWithBelongsTo/EmbedsOne
725
+ * Relations. Since Embedded docs are part of parents, no need to make
726
+ * db calls. Let the related function be called for each object to fetch
727
+ * the results from cache.
728
+ *
729
+ * TODO: Optimize EmbedsManyWithBelongsTo relation DB Calls
730
+ * @param callback
731
+ */
732
+ function includeEmbeds(callback) {
733
+ eachParallel(objs, function(obj, next) {
734
+ processTargetObj(obj, next);
735
+ }, callback);
736
+ }
737
+ /**
738
+ * Process Each Model Object and make sure specified relations are included
739
+ * @param {Model} obj - Single Mode object for which inclusion is needed
740
+ * @param callback
741
+ * @returns {*}
742
+ */
743
+ function processTargetObj(obj, callback) {
744
+ const isInst = obj instanceof self;
745
+ if (relation.type === "belongsTo") {
746
+ if (obj[relation.keyFrom] === null || obj[relation.keyFrom] === void 0) {
747
+ defineCachedRelations(obj);
748
+ obj.__cachedRelations[relationName] = null;
749
+ if (isInst) obj.__data[relationName] = null;
750
+ else obj[relationName] = null;
751
+ return callback();
752
+ }
753
+ }
754
+ /**
755
+ * Sets the related objects as a property of Parent Object
756
+ * @param {Array<Model>|Model|null} result - Related Object/Objects
757
+ * @param cb
758
+ */
759
+ function setIncludeData(result, cb) {
760
+ if (isInst) {
761
+ if (Array.isArray(result) && !(result instanceof List)) result = new List(result, relation.modelTo);
762
+ obj.__data[relationName] = result;
763
+ } else obj[relationName] = result;
764
+ cb(null, result);
765
+ }
766
+ if (obj.__cachedRelations && obj.__cachedRelations[relationName] !== void 0) return setIncludeData(obj.__cachedRelations[relationName], callback);
767
+ const inst = obj instanceof self ? obj : new self(obj);
768
+ let related;
769
+ if ((relation.multiple || relation.type === "belongsTo") && scope) {
770
+ const includeScope = {};
771
+ const filter = scope.conditions();
772
+ if (filter.fields && Array.isArray(subInclude) && relation.modelTo.relations) {
773
+ includeScope.fields = [];
774
+ subInclude.forEach(function(name) {
775
+ const rel = relation.modelTo.relations[name];
776
+ if (rel && rel.type === "belongsTo") includeScope.fields.push(rel.keyFrom);
777
+ });
778
+ }
779
+ utils.mergeQuery(filter, includeScope, { fields: false });
780
+ related = inst[relationName].bind(inst, filter);
781
+ } else related = inst[relationName].bind(inst, void 0);
782
+ related(options, function(err, result) {
783
+ if (err) return callback(err);
784
+ else {
785
+ defineCachedRelations(obj);
786
+ obj.__cachedRelations[relationName] = result;
787
+ return setIncludeData(result, callback);
788
+ }
789
+ });
790
+ }
791
+ }
792
+ };
793
+ }));
794
+ //#endregion
795
+ module.exports = require_include();