1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | 'use strict';
|
7 |
|
8 | exports.safeRequire = safeRequire;
|
9 | exports.fieldsToArray = fieldsToArray;
|
10 | exports.selectFields = selectFields;
|
11 | exports.sanitizeQuery = sanitizeQuery;
|
12 | exports.parseSettings = parseSettings;
|
13 | exports.mergeSettings = exports.deepMerge = deepMerge;
|
14 | exports.deepMergeProperty = deepMergeProperty;
|
15 | exports.isPlainObject = isPlainObject;
|
16 | exports.defineCachedRelations = defineCachedRelations;
|
17 | exports.sortObjectsByIds = sortObjectsByIds;
|
18 | exports.setScopeValuesFromWhere = setScopeValuesFromWhere;
|
19 | exports.mergeQuery = mergeQuery;
|
20 | exports.mergeIncludes = mergeIncludes;
|
21 | exports.createPromiseCallback = createPromiseCallback;
|
22 | exports.uniq = uniq;
|
23 | exports.toRegExp = toRegExp;
|
24 | exports.hasRegExpFlags = hasRegExpFlags;
|
25 | exports.idEquals = idEquals;
|
26 | exports.findIndexOf = findIndexOf;
|
27 | exports.collectTargetIds = collectTargetIds;
|
28 | exports.idName = idName;
|
29 | exports.rankArrayElements = rankArrayElements;
|
30 | exports.idsHaveDuplicates = idsHaveDuplicates;
|
31 | exports.isClass = isClass;
|
32 | exports.escapeRegExp = escapeRegExp;
|
33 | exports.applyParentProperty = applyParentProperty;
|
34 |
|
35 | const g = require('strong-globalize')();
|
36 | const traverse = require('traverse');
|
37 | const assert = require('assert');
|
38 | const debug = require('debug')('loopback:juggler:utils');
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 | const BUILDER_PARENT_SETTING = 'parentRef';
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 | const PARENT_PROPERTY_NAME = '__parent';
|
51 |
|
52 | function safeRequire(module) {
|
53 | try {
|
54 | return require(module);
|
55 | } catch (e) {
|
56 | g.log('Run "{{npm install loopback-datasource-juggler}} %s" command ',
|
57 | 'to use {{loopback-datasource-juggler}} using %s database engine',
|
58 | module, module);
|
59 | process.exit(1);
|
60 | }
|
61 | }
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 | function setScopeValuesFromWhere(data, where, targetModel) {
|
71 | for (const i in where) {
|
72 | if (i === 'and') {
|
73 |
|
74 | for (let w = 0, n = where[i].length; w < n; w++) {
|
75 | setScopeValuesFromWhere(data, where[i][w], targetModel);
|
76 | }
|
77 | continue;
|
78 | }
|
79 | const prop = targetModel.definition.properties[i];
|
80 | if (prop) {
|
81 | const val = where[i];
|
82 | if (typeof val !== 'object' || val instanceof prop.type ||
|
83 | prop.type.name === 'ObjectID' ||
|
84 | prop.type.name === 'uuidFromString') {
|
85 |
|
86 | data[i] = where[i];
|
87 | }
|
88 | }
|
89 | }
|
90 | }
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 | function mergeIncludes(destination, source) {
|
101 | const destArray = convertToArray(destination);
|
102 | const sourceArray = convertToArray(source);
|
103 | if (destArray.length === 0) {
|
104 | return sourceArray;
|
105 | }
|
106 | if (sourceArray.length === 0) {
|
107 | return destArray;
|
108 | }
|
109 | const relationNames = [];
|
110 | const resultArray = [];
|
111 | for (const j in sourceArray) {
|
112 | const sourceEntry = sourceArray[j];
|
113 | const sourceEntryRelationName = (typeof (sourceEntry.rel || sourceEntry.relation) === 'string') ?
|
114 | sourceEntry.relation : Object.keys(sourceEntry)[0];
|
115 | relationNames.push(sourceEntryRelationName);
|
116 | resultArray.push(sourceEntry);
|
117 | }
|
118 | for (const i in destArray) {
|
119 | const destEntry = destArray[i];
|
120 | const destEntryRelationName = (typeof (destEntry.rel || destEntry.relation) === 'string') ?
|
121 | destEntry.relation : Object.keys(destEntry)[0];
|
122 | if (relationNames.indexOf(destEntryRelationName) === -1) {
|
123 | resultArray.push(destEntry);
|
124 | }
|
125 | }
|
126 | return resultArray;
|
127 | }
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 | function convertToArray(include) {
|
138 | if (typeof include === 'string') {
|
139 | const obj = {};
|
140 | obj[include] = true;
|
141 | return [obj];
|
142 | } else if (isPlainObject(include)) {
|
143 |
|
144 | if (include.rel || include.relation) {
|
145 | return [include];
|
146 | }
|
147 |
|
148 | const newInclude = [];
|
149 | for (const key in include) {
|
150 | const obj = {};
|
151 | obj[key] = include[key];
|
152 | newInclude.push(obj);
|
153 | }
|
154 | return newInclude;
|
155 | } else if (Array.isArray(include)) {
|
156 | const normalized = [];
|
157 | for (const i in include) {
|
158 | const includeEntry = include[i];
|
159 | if (typeof includeEntry === 'string') {
|
160 | const obj = {};
|
161 | obj[includeEntry] = true;
|
162 | normalized.push(obj);
|
163 | } else {
|
164 | normalized.push(includeEntry);
|
165 | }
|
166 | }
|
167 | return normalized;
|
168 | }
|
169 | return [];
|
170 | }
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 | function mergeQuery(base, update, spec) {
|
181 | if (!update) {
|
182 | return;
|
183 | }
|
184 | spec = spec || {};
|
185 | base = base || {};
|
186 |
|
187 | if (update.where && Object.keys(update.where).length > 0) {
|
188 | if (base.where && Object.keys(base.where).length > 0) {
|
189 | base.where = {and: [base.where, update.where]};
|
190 | } else {
|
191 | base.where = update.where;
|
192 | }
|
193 | }
|
194 |
|
195 |
|
196 | if (spec.include !== false && update.include) {
|
197 | if (!base.include) {
|
198 | base.include = update.include;
|
199 | } else {
|
200 | if (spec.nestedInclude === true) {
|
201 |
|
202 |
|
203 |
|
204 | const saved = base.include;
|
205 | base.include = {};
|
206 | base.include[update.include] = saved;
|
207 | } else {
|
208 |
|
209 |
|
210 | base.include = mergeIncludes(base.include, update.include);
|
211 | }
|
212 | }
|
213 | }
|
214 |
|
215 | if (spec.collect !== false && update.collect) {
|
216 | base.collect = update.collect;
|
217 | }
|
218 |
|
219 |
|
220 | if (spec.fields !== false && update.fields !== undefined) {
|
221 | base.fields = update.fields;
|
222 | } else if (update.fields !== undefined) {
|
223 | base.fields = [].concat(base.fields).concat(update.fields);
|
224 | }
|
225 |
|
226 |
|
227 | if ((!base.order || spec.order === false) && update.order) {
|
228 | base.order = update.order;
|
229 | }
|
230 |
|
231 |
|
232 | if (spec.limit !== false && update.limit !== undefined) {
|
233 | base.limit = update.limit;
|
234 | }
|
235 |
|
236 | const skip = spec.skip !== false && spec.offset !== false;
|
237 |
|
238 | if (skip && update.skip !== undefined) {
|
239 | base.skip = update.skip;
|
240 | }
|
241 |
|
242 | if (skip && update.offset !== undefined) {
|
243 | base.offset = update.offset;
|
244 | }
|
245 |
|
246 | return base;
|
247 | }
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 | function fieldsToArray(fields, properties, excludeUnknown) {
|
257 | if (!fields) return;
|
258 |
|
259 |
|
260 | let result = properties;
|
261 | let i, n;
|
262 |
|
263 | if (typeof fields === 'string') {
|
264 | result = [fields];
|
265 | } else if (Array.isArray(fields) && fields.length > 0) {
|
266 |
|
267 | result = fields;
|
268 | } else if ('object' === typeof fields) {
|
269 |
|
270 | const included = [];
|
271 | const excluded = [];
|
272 | const keys = Object.keys(fields);
|
273 | if (!keys.length) return;
|
274 |
|
275 | for (i = 0, n = keys.length; i < n; i++) {
|
276 | const k = keys[i];
|
277 | if (fields[k]) {
|
278 | included.push(k);
|
279 | } else if ((k in fields) && !fields[k]) {
|
280 | excluded.push(k);
|
281 | }
|
282 | }
|
283 | if (included.length > 0) {
|
284 | result = included;
|
285 | } else if (excluded.length > 0) {
|
286 | for (i = 0, n = excluded.length; i < n; i++) {
|
287 | const index = result.indexOf(excluded[i]);
|
288 | if (index !== -1) result.splice(index, 1);
|
289 | }
|
290 | }
|
291 | }
|
292 |
|
293 | let fieldArray = [];
|
294 | if (excludeUnknown) {
|
295 | for (i = 0, n = result.length; i < n; i++) {
|
296 | if (properties.indexOf(result[i]) !== -1) {
|
297 | fieldArray.push(result[i]);
|
298 | }
|
299 | }
|
300 | } else {
|
301 | fieldArray = result;
|
302 | }
|
303 | return fieldArray;
|
304 | }
|
305 |
|
306 | function selectFields(fields) {
|
307 |
|
308 | return function(obj) {
|
309 | const result = {};
|
310 | let key;
|
311 |
|
312 | for (let i = 0; i < fields.length; i++) {
|
313 | key = fields[i];
|
314 |
|
315 | result[key] = obj[key];
|
316 | }
|
317 | return result;
|
318 | };
|
319 | }
|
320 |
|
321 | function isProhibited(key, prohibitedKeys) {
|
322 | if (!prohibitedKeys || !prohibitedKeys.length) return false;
|
323 | if (typeof key !== 'string') {
|
324 | return false;
|
325 | }
|
326 | for (const k of prohibitedKeys) {
|
327 | if (k === key) return true;
|
328 |
|
329 | if (key.split('.').indexOf(k) !== -1) return true;
|
330 | }
|
331 | return false;
|
332 | }
|
333 |
|
334 |
|
335 |
|
336 |
|
337 |
|
338 |
|
339 | function isRegExpOperator(operator) {
|
340 | return ['like', 'nlike', 'ilike', 'nilike', 'regexp'].includes(operator);
|
341 | }
|
342 |
|
343 |
|
344 |
|
345 |
|
346 |
|
347 |
|
348 |
|
349 | function escapeRegExp(str) {
|
350 | assert.strictEqual(typeof str, 'string', 'String required for regexp escaping');
|
351 | try {
|
352 | new RegExp(str);
|
353 | return str;
|
354 | } catch (unused) {
|
355 | console.warn(
|
356 | 'Auto-escaping invalid RegExp value %j supplied by the caller. ' +
|
357 | 'Please note this behavior may change in the future.',
|
358 | str,
|
359 | );
|
360 | return str.replace(/[\-\[\]\/\{\}\(\)\+\?\.\\\^\$\|]/g, '\\$&');
|
361 | }
|
362 | }
|
363 |
|
364 |
|
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 |
|
372 | function sanitizeQuery(query, options) {
|
373 | debug('Sanitizing query object: %j', query);
|
374 | if (typeof query !== 'object' || query === null) {
|
375 | return query;
|
376 | }
|
377 | options = options || {};
|
378 | if (typeof options === 'string') {
|
379 |
|
380 | options = {normalizeUndefinedInQuery: options};
|
381 | }
|
382 | const prohibitedKeys = options.prohibitedKeys;
|
383 | const offendingKeys = [];
|
384 | const normalizeUndefinedInQuery = options.normalizeUndefinedInQuery;
|
385 | const maxDepth = options.maxDepth || Number.MAX_SAFE_INTEGER;
|
386 |
|
387 |
|
388 | const result = traverse(query).forEach(function(x) {
|
389 | |
390 |
|
391 |
|
392 | if (this.circular) {
|
393 | const msg = g.f('The query object is circular');
|
394 | const err = new Error(msg);
|
395 | err.statusCode = 400;
|
396 | err.code = 'QUERY_OBJECT_IS_CIRCULAR';
|
397 | throw err;
|
398 | }
|
399 | if (this.level > maxDepth) {
|
400 | const msg = g.f('The query object exceeds maximum depth %d', maxDepth);
|
401 | const err = new Error(msg);
|
402 | err.statusCode = 400;
|
403 | err.code = 'QUERY_OBJECT_TOO_DEEP';
|
404 | throw err;
|
405 | }
|
406 | |
407 |
|
408 |
|
409 |
|
410 | if (isProhibited(this.key, prohibitedKeys)) {
|
411 | offendingKeys.push(this.key);
|
412 | this.remove();
|
413 | return;
|
414 | }
|
415 |
|
416 | |
417 |
|
418 |
|
419 | if (x === undefined) {
|
420 | switch (normalizeUndefinedInQuery) {
|
421 | case 'nullify':
|
422 | this.update(null);
|
423 | break;
|
424 | case 'throw':
|
425 | throw new Error(g.f('Unexpected `undefined` in query'));
|
426 | case 'ignore':
|
427 | default:
|
428 | this.remove();
|
429 | }
|
430 | }
|
431 |
|
432 | if (!Array.isArray(x) && (typeof x === 'object' && x !== null &&
|
433 | x.constructor !== Object)) {
|
434 |
|
435 | this.update(x, true);
|
436 | return x;
|
437 | }
|
438 |
|
439 | if (isRegExpOperator(this.key) && typeof x === 'string') {
|
440 | return escapeRegExp(x);
|
441 | }
|
442 |
|
443 | return x;
|
444 | });
|
445 |
|
446 | if (offendingKeys.length) {
|
447 | console.error(
|
448 | g.f(
|
449 | 'Potential security alert: hidden/protected properties %j are used in query.',
|
450 | offendingKeys,
|
451 | ),
|
452 | );
|
453 | }
|
454 | return result;
|
455 | }
|
456 |
|
457 | const url = require('url');
|
458 | const qs = require('qs');
|
459 |
|
460 |
|
461 |
|
462 |
|
463 |
|
464 |
|
465 | function parseSettings(urlStr) {
|
466 | if (!urlStr) {
|
467 | return {};
|
468 | }
|
469 | const uri = url.parse(urlStr, false);
|
470 | const settings = {};
|
471 | settings.connector = uri.protocol && uri.protocol.split(':')[0];
|
472 | settings.host = settings.hostname = uri.hostname;
|
473 | settings.port = uri.port && Number(uri.port);
|
474 | settings.user = settings.username = uri.auth && uri.auth.split(':')[0];
|
475 | settings.password = uri.auth && uri.auth.split(':')[1];
|
476 | settings.database = uri.pathname && uri.pathname.split('/')[1];
|
477 | settings.url = urlStr;
|
478 | if (uri.query) {
|
479 | const params = qs.parse(uri.query);
|
480 | for (const p in params) {
|
481 | settings[p] = params[p];
|
482 | }
|
483 | }
|
484 | return settings;
|
485 | }
|
486 |
|
487 |
|
488 |
|
489 |
|
490 |
|
491 |
|
492 |
|
493 |
|
494 |
|
495 |
|
496 |
|
497 |
|
498 |
|
499 |
|
500 |
|
501 |
|
502 | function deepMerge(base, extras) {
|
503 |
|
504 | const array = Array.isArray(base) && (Array.isArray(extras) || !extras);
|
505 | let dst = array && [] || {};
|
506 |
|
507 | if (array) {
|
508 |
|
509 | extras = extras || [];
|
510 |
|
511 | dst = dst.concat(base);
|
512 |
|
513 | extras.forEach(function(e) {
|
514 | if (dst.indexOf(e) === -1) {
|
515 | dst.push(e);
|
516 | }
|
517 | });
|
518 | } else {
|
519 | if (base != null && typeof base === 'object') {
|
520 |
|
521 | Object.keys(base).forEach(function(key) {
|
522 | if (base[key] && typeof base[key] === 'object') {
|
523 |
|
524 | dst[key] = deepMerge(base[key]);
|
525 | } else {
|
526 | dst[key] = base[key];
|
527 | }
|
528 | });
|
529 | }
|
530 | if (extras != null && typeof extras === 'object') {
|
531 |
|
532 | Object.keys(extras).forEach(function(key) {
|
533 | const extra = extras[key];
|
534 | if (extra == null || typeof extra !== 'object') {
|
535 |
|
536 | dst[key] = extra;
|
537 | } else {
|
538 |
|
539 | if (base == null || typeof base !== 'object' ||
|
540 | base[key] == null) {
|
541 |
|
542 | dst[key] = extra;
|
543 | } else {
|
544 |
|
545 | dst[key] = deepMerge(base[key], extra);
|
546 | }
|
547 | }
|
548 | });
|
549 | }
|
550 | }
|
551 |
|
552 | return dst;
|
553 | }
|
554 |
|
555 |
|
556 |
|
557 |
|
558 |
|
559 |
|
560 |
|
561 |
|
562 |
|
563 | function deepMergeProperty(base, extras) {
|
564 | const mergedObject = deepMerge({key: base}, {key: extras});
|
565 | const mergedProperty = mergedObject.key;
|
566 | return mergedProperty;
|
567 | }
|
568 |
|
569 | const numberIsFinite = Number.isFinite || function(value) {
|
570 | return typeof value === 'number' && isFinite(value);
|
571 | };
|
572 |
|
573 |
|
574 |
|
575 |
|
576 |
|
577 |
|
578 |
|
579 |
|
580 |
|
581 |
|
582 | function rankArrayElements(array, rank) {
|
583 | if (!Array.isArray(array) || !numberIsFinite(rank))
|
584 | return array;
|
585 |
|
586 | array.forEach(function(el) {
|
587 |
|
588 | if (!el || typeof el != 'object' || Array.isArray(el))
|
589 | return;
|
590 |
|
591 |
|
592 | if (el.__rank)
|
593 | return;
|
594 |
|
595 |
|
596 | Object.defineProperty(el, '__rank', {
|
597 | writable: false,
|
598 | enumerable: false,
|
599 | configurable: false,
|
600 | value: rank,
|
601 | });
|
602 | });
|
603 | return array;
|
604 | }
|
605 |
|
606 |
|
607 |
|
608 |
|
609 |
|
610 | function defineCachedRelations(obj) {
|
611 | if (!obj.__cachedRelations) {
|
612 | Object.defineProperty(obj, '__cachedRelations', {
|
613 | writable: true,
|
614 | enumerable: false,
|
615 | configurable: true,
|
616 | value: {},
|
617 | });
|
618 | }
|
619 | }
|
620 |
|
621 |
|
622 |
|
623 |
|
624 |
|
625 |
|
626 | function isPlainObject(obj) {
|
627 | return (typeof obj === 'object') && (obj !== null) &&
|
628 | (obj.constructor === Object);
|
629 | }
|
630 |
|
631 | function sortObjectsByIds(idName, ids, objects, strict) {
|
632 | ids = ids.map(function(id) {
|
633 | return (typeof id === 'object') ? String(id) : id;
|
634 | });
|
635 |
|
636 | const indexOf = function(x) {
|
637 | const isObj = (typeof x[idName] === 'object');
|
638 | const id = isObj ? String(x[idName]) : x[idName];
|
639 | return ids.indexOf(id);
|
640 | };
|
641 |
|
642 | const heading = [];
|
643 | const tailing = [];
|
644 |
|
645 | objects.forEach(function(x) {
|
646 | if (typeof x === 'object') {
|
647 | const idx = indexOf(x);
|
648 | if (strict && idx === -1) return;
|
649 | idx === -1 ? tailing.push(x) : heading.push(x);
|
650 | }
|
651 | });
|
652 |
|
653 | heading.sort(function(x, y) {
|
654 | const a = indexOf(x);
|
655 | const b = indexOf(y);
|
656 | if (a === -1 || b === -1) return 1;
|
657 | if (a === b) return 0;
|
658 | if (a > b) return 1;
|
659 | if (a < b) return -1;
|
660 | });
|
661 |
|
662 | return heading.concat(tailing);
|
663 | }
|
664 |
|
665 | function createPromiseCallback() {
|
666 | let cb;
|
667 | const promise = new Promise(function(resolve, reject) {
|
668 | cb = function(err, data) {
|
669 | if (err) return reject(err);
|
670 | return resolve(data);
|
671 | };
|
672 | });
|
673 | cb.promise = promise;
|
674 | return cb;
|
675 | }
|
676 |
|
677 | function isBsonType(value) {
|
678 |
|
679 | return value.hasOwnProperty('_bsontype') ||
|
680 | value.constructor.prototype.hasOwnProperty('_bsontype');
|
681 | }
|
682 |
|
683 |
|
684 |
|
685 |
|
686 |
|
687 |
|
688 | function uniq(a) {
|
689 | const uniqArray = [];
|
690 | if (!a) {
|
691 | return uniqArray;
|
692 | }
|
693 | assert(Array.isArray(a), 'array argument is required');
|
694 | const comparableA = a.map(
|
695 | item => isBsonType(item) ? item.toString() : item,
|
696 | );
|
697 | for (let i = 0, n = comparableA.length; i < n; i++) {
|
698 | if (comparableA.indexOf(comparableA[i]) === i) {
|
699 | uniqArray.push(a[i]);
|
700 | }
|
701 | }
|
702 | return uniqArray;
|
703 | }
|
704 |
|
705 |
|
706 |
|
707 |
|
708 |
|
709 |
|
710 | function toRegExp(regex) {
|
711 | const isString = typeof regex === 'string';
|
712 | const isRegExp = regex instanceof RegExp;
|
713 |
|
714 | if (!(isString || isRegExp))
|
715 | return new Error(g.f('Invalid argument, must be a string, {{regex}} literal, or ' +
|
716 | '{{RegExp}} object'));
|
717 |
|
718 | if (isRegExp)
|
719 | return regex;
|
720 |
|
721 | if (!hasRegExpFlags(regex))
|
722 | return new RegExp(regex);
|
723 |
|
724 |
|
725 | const flags = regex.split('/').pop().split('');
|
726 | const validFlags = ['i', 'g', 'm'];
|
727 | const invalidFlags = [];
|
728 | flags.forEach(function(flag) {
|
729 | if (validFlags.indexOf(flag) === -1)
|
730 | invalidFlags.push(flag);
|
731 | });
|
732 |
|
733 | const hasInvalidFlags = invalidFlags.length > 0;
|
734 | if (hasInvalidFlags)
|
735 | return new Error(g.f('Invalid {{regex}} flags: %s', invalidFlags));
|
736 |
|
737 |
|
738 | const expression = regex.substr(1, regex.lastIndexOf('/') - 1);
|
739 | return new RegExp(expression, flags.join(''));
|
740 | }
|
741 |
|
742 | function hasRegExpFlags(regex) {
|
743 | return regex instanceof RegExp ?
|
744 | regex.toString().split('/').pop() :
|
745 | !!regex.match(/.*\/.+$/);
|
746 | }
|
747 |
|
748 |
|
749 |
|
750 | function idEquals(id1, id2) {
|
751 | if (id1 === id2) {
|
752 | return true;
|
753 | }
|
754 |
|
755 | if ((typeof id1 === 'number' && typeof id2 === 'string') ||
|
756 | (typeof id1 === 'string' && typeof id2 === 'number')) {
|
757 | return id1 == id2;
|
758 | }
|
759 |
|
760 | id1 = JSON.stringify(id1);
|
761 | id2 = JSON.stringify(id2);
|
762 | if (id1 === id2) {
|
763 | return true;
|
764 | }
|
765 |
|
766 | return false;
|
767 | }
|
768 |
|
769 |
|
770 |
|
771 | function findIndexOf(arr, target, isEqual) {
|
772 | if (!isEqual) {
|
773 | return arr.indexOf(target);
|
774 | }
|
775 |
|
776 | for (let i = 0; i < arr.length; i++) {
|
777 | if (isEqual(arr[i], target)) { return i; }
|
778 | }
|
779 |
|
780 | return -1;
|
781 | }
|
782 |
|
783 |
|
784 |
|
785 |
|
786 |
|
787 |
|
788 |
|
789 | function collectTargetIds(targetData, idPropertyName) {
|
790 | const targetIds = [];
|
791 | for (let i = 0; i < targetData.length; i++) {
|
792 | const targetId = targetData[i][idPropertyName];
|
793 | targetIds.push(targetId);
|
794 | }
|
795 | const IdQuery = {
|
796 | inq: uniq(targetIds),
|
797 | };
|
798 | return IdQuery;
|
799 | }
|
800 |
|
801 |
|
802 |
|
803 |
|
804 |
|
805 |
|
806 | function idName(m) {
|
807 | return m.definition.idName() || 'id';
|
808 | }
|
809 |
|
810 |
|
811 |
|
812 |
|
813 |
|
814 |
|
815 |
|
816 | function idsHaveDuplicates(ids) {
|
817 |
|
818 | let hasDuplicates = undefined;
|
819 | let i, j;
|
820 | if (typeof Set === 'function') {
|
821 | const uniqueIds = new Set();
|
822 | for (i = 0; i < ids.length; ++i) {
|
823 | const idType = typeof ids[i];
|
824 | if (idType === 'string' || idType === 'number') {
|
825 | if (uniqueIds.has(ids[i])) {
|
826 | hasDuplicates = true;
|
827 | break;
|
828 | } else {
|
829 | uniqueIds.add(ids[i]);
|
830 | }
|
831 | } else {
|
832 |
|
833 | break;
|
834 | }
|
835 | }
|
836 | if (hasDuplicates === undefined && uniqueIds.size === ids.length) {
|
837 | hasDuplicates = false;
|
838 | }
|
839 | }
|
840 | if (hasDuplicates === undefined) {
|
841 |
|
842 |
|
843 | for (i = 0; i < ids.length && hasDuplicates === undefined; ++i) {
|
844 | for (j = 0; j < i; ++j) {
|
845 | if (idEquals(ids[i], ids[j])) {
|
846 | hasDuplicates = true;
|
847 | break;
|
848 | }
|
849 | }
|
850 | }
|
851 | }
|
852 | return hasDuplicates === true;
|
853 | }
|
854 |
|
855 | function isClass(fn) {
|
856 | return fn && fn.toString().startsWith('class ');
|
857 | }
|
858 |
|
859 |
|
860 |
|
861 |
|
862 |
|
863 |
|
864 |
|
865 |
|
866 | function applyParentProperty(element, parent) {
|
867 | assert.strictEqual(typeof element, 'object', 'Non object element given to assign parent');
|
868 | const {constructor: {modelBuilder: {settings: builderSettings} = {}} = {}} = element;
|
869 | if (!builderSettings || !builderSettings[BUILDER_PARENT_SETTING]) {
|
870 |
|
871 | return;
|
872 | }
|
873 |
|
874 | if (element.hasOwnProperty(PARENT_PROPERTY_NAME)) {
|
875 |
|
876 | const existingParent = element[PARENT_PROPERTY_NAME];
|
877 | if (existingParent && existingParent !== parent) {
|
878 |
|
879 | g.warn('Re-assigning child model instance to another parent than the original!\n' +
|
880 | 'Although supported, this is not a recommended practice: ' +
|
881 | `${element.constructor.name} -> ${parent.constructor.name}\n` +
|
882 | 'You should create an independent copy of the child model using `new Model(CHILD)` OR ' +
|
883 | '`new Model(CHILD.toJSON())` and assign to new parent');
|
884 | }
|
885 | element[PARENT_PROPERTY_NAME] = parent;
|
886 | } else {
|
887 |
|
888 | Object.defineProperty(element, PARENT_PROPERTY_NAME, {
|
889 | value: parent,
|
890 | writable: true,
|
891 | enumerable: false,
|
892 | configurable: false,
|
893 | });
|
894 | }
|
895 | }
|