@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.
- package/LICENSE +25 -0
- package/NOTICE +23 -0
- package/README.md +74 -0
- package/dist/_virtual/_rolldown/runtime.js +4 -0
- package/dist/index.js +26 -0
- package/dist/lib/browser.depd.js +13 -0
- package/dist/lib/case-utils.js +21 -0
- package/dist/lib/connectors/kv-memory.js +158 -0
- package/dist/lib/connectors/memory.js +810 -0
- package/dist/lib/connectors/transient.js +126 -0
- package/dist/lib/dao.js +2445 -0
- package/dist/lib/datasource.js +2215 -0
- package/dist/lib/date-string.js +87 -0
- package/dist/lib/geo.js +244 -0
- package/dist/lib/globalize.js +33 -0
- package/dist/lib/hooks.js +79 -0
- package/dist/lib/id-utils.js +66 -0
- package/dist/lib/include.js +795 -0
- package/dist/lib/include_utils.js +104 -0
- package/dist/lib/introspection.js +37 -0
- package/dist/lib/jutil.js +65 -0
- package/dist/lib/kvao/delete-all.js +57 -0
- package/dist/lib/kvao/delete.js +43 -0
- package/dist/lib/kvao/expire.js +35 -0
- package/dist/lib/kvao/get.js +34 -0
- package/dist/lib/kvao/index.js +28 -0
- package/dist/lib/kvao/iterate-keys.js +38 -0
- package/dist/lib/kvao/keys.js +55 -0
- package/dist/lib/kvao/set.js +39 -0
- package/dist/lib/kvao/ttl.js +35 -0
- package/dist/lib/list.js +101 -0
- package/dist/lib/mixins.js +58 -0
- package/dist/lib/model-builder.js +608 -0
- package/dist/lib/model-definition.js +231 -0
- package/dist/lib/model-utils.js +368 -0
- package/dist/lib/model.js +586 -0
- package/dist/lib/observer.js +235 -0
- package/dist/lib/relation-definition.js +2604 -0
- package/dist/lib/relations.js +587 -0
- package/dist/lib/scope.js +392 -0
- package/dist/lib/transaction.js +183 -0
- package/dist/lib/types.js +58 -0
- package/dist/lib/utils.js +625 -0
- package/dist/lib/validations.js +742 -0
- package/dist/package.js +93 -0
- package/package.json +85 -0
- package/types/common.d.ts +28 -0
- package/types/connector.d.ts +52 -0
- package/types/datasource.d.ts +324 -0
- package/types/date-string.d.ts +21 -0
- package/types/inclusion-mixin.d.ts +44 -0
- package/types/index.d.ts +36 -0
- package/types/kv-model.d.ts +201 -0
- package/types/model.d.ts +368 -0
- package/types/observer-mixin.d.ts +174 -0
- package/types/persisted-model.d.ts +505 -0
- package/types/query.d.ts +108 -0
- package/types/relation-mixin.d.ts +577 -0
- package/types/relation.d.ts +301 -0
- package/types/scope.d.ts +92 -0
- package/types/transaction-mixin.d.ts +47 -0
- package/types/types.d.ts +65 -0
- 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();
|