@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,625 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const require_runtime = require("../_virtual/_rolldown/runtime.js");
|
|
3
|
+
const require_lib_globalize = require("./globalize.js");
|
|
4
|
+
//#region src/lib/utils.ts
|
|
5
|
+
var require_utils = /* @__PURE__ */ require_runtime.__commonJSMin(((exports) => {
|
|
6
|
+
exports.safeRequire = safeRequire;
|
|
7
|
+
exports.fieldsToArray = fieldsToArray;
|
|
8
|
+
exports.selectFields = selectFields;
|
|
9
|
+
exports.sanitizeQuery = sanitizeQuery;
|
|
10
|
+
exports.parseSettings = parseSettings;
|
|
11
|
+
exports.mergeSettings = exports.deepMerge = deepMerge;
|
|
12
|
+
exports.deepMergeProperty = deepMergeProperty;
|
|
13
|
+
exports.isPlainObject = isPlainObject;
|
|
14
|
+
exports.defineCachedRelations = defineCachedRelations;
|
|
15
|
+
exports.sortObjectsByIds = sortObjectsByIds;
|
|
16
|
+
exports.setScopeValuesFromWhere = setScopeValuesFromWhere;
|
|
17
|
+
exports.mergeQuery = mergeQuery;
|
|
18
|
+
exports.mergeIncludes = mergeIncludes;
|
|
19
|
+
exports.createPromiseCallback = createPromiseCallback;
|
|
20
|
+
exports.uniq = uniq;
|
|
21
|
+
exports.toRegExp = toRegExp;
|
|
22
|
+
exports.hasRegExpFlags = hasRegExpFlags;
|
|
23
|
+
exports.idEquals = idEquals;
|
|
24
|
+
exports.findIndexOf = findIndexOf;
|
|
25
|
+
exports.collectTargetIds = collectTargetIds;
|
|
26
|
+
exports.idName = idName;
|
|
27
|
+
exports.rankArrayElements = rankArrayElements;
|
|
28
|
+
exports.idsHaveDuplicates = idsHaveDuplicates;
|
|
29
|
+
exports.isClass = isClass;
|
|
30
|
+
exports.escapeRegExp = escapeRegExp;
|
|
31
|
+
exports.applyParentProperty = applyParentProperty;
|
|
32
|
+
const g = require_lib_globalize();
|
|
33
|
+
const traverse = require("neotraverse/legacy");
|
|
34
|
+
const assert = require("assert");
|
|
35
|
+
const debug = require("debug")("loopback:juggler:utils");
|
|
36
|
+
/**
|
|
37
|
+
* The name of the property in modelBuilder settings that will enable the child parent reference functionality
|
|
38
|
+
* @type {string}
|
|
39
|
+
*/
|
|
40
|
+
const BUILDER_PARENT_SETTING = "parentRef";
|
|
41
|
+
/**
|
|
42
|
+
* The property name that should be defined on each child instance if parent feature flag enabled
|
|
43
|
+
* @type {string}
|
|
44
|
+
*/
|
|
45
|
+
const PARENT_PROPERTY_NAME = "__parent";
|
|
46
|
+
const REGEXP_OPERATORS = new Set([
|
|
47
|
+
"like",
|
|
48
|
+
"nlike",
|
|
49
|
+
"ilike",
|
|
50
|
+
"nilike",
|
|
51
|
+
"regexp"
|
|
52
|
+
]);
|
|
53
|
+
function safeRequire(module$1) {
|
|
54
|
+
try {
|
|
55
|
+
return require(module$1);
|
|
56
|
+
} catch {
|
|
57
|
+
g.log("Run \"{{npm install loopback-datasource-juggler}} %s\" command ", "to use {{loopback-datasource-juggler}} using %s database engine", module$1, module$1);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function setScopeValuesFromWhere(data, where, targetModel) {
|
|
62
|
+
for (const i in where) {
|
|
63
|
+
if (i === "and") {
|
|
64
|
+
for (let w = 0, n = where[i].length; w < n; w++) setScopeValuesFromWhere(data, where[i][w], targetModel);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const prop = targetModel.definition.properties[i];
|
|
68
|
+
if (prop) {
|
|
69
|
+
const val = where[i];
|
|
70
|
+
if (typeof val !== "object" || val instanceof prop.type || prop.type.name === "ObjectID" || prop.type.name === "uuidFromString") data[i] = where[i];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Merge include options of default scope with runtime include option.
|
|
76
|
+
* exhibits the _.extend behaviour. Property value of source overrides
|
|
77
|
+
* property value of destination if property name collision occurs
|
|
78
|
+
* @param {String|Array|Object} destination The default value of `include` option
|
|
79
|
+
* @param {String|Array|Object} source The runtime value of `include` option
|
|
80
|
+
* @returns {Object}
|
|
81
|
+
*/
|
|
82
|
+
function mergeIncludes(destination, source) {
|
|
83
|
+
const destArray = convertToArray(destination);
|
|
84
|
+
const sourceArray = convertToArray(source);
|
|
85
|
+
if (destArray.length === 0) return sourceArray;
|
|
86
|
+
if (sourceArray.length === 0) return destArray;
|
|
87
|
+
const relationNames = [];
|
|
88
|
+
const resultArray = [];
|
|
89
|
+
for (const j in sourceArray) {
|
|
90
|
+
const sourceEntry = sourceArray[j];
|
|
91
|
+
const sourceEntryRelationName = typeof (sourceEntry.rel || sourceEntry.relation) === "string" ? sourceEntry.relation : Object.keys(sourceEntry)[0];
|
|
92
|
+
relationNames.push(sourceEntryRelationName);
|
|
93
|
+
resultArray.push(sourceEntry);
|
|
94
|
+
}
|
|
95
|
+
for (const i in destArray) {
|
|
96
|
+
const destEntry = destArray[i];
|
|
97
|
+
const destEntryRelationName = typeof (destEntry.rel || destEntry.relation) === "string" ? destEntry.relation : Object.keys(destEntry)[0];
|
|
98
|
+
if (relationNames.indexOf(destEntryRelationName) === -1) resultArray.push(destEntry);
|
|
99
|
+
}
|
|
100
|
+
return resultArray;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Converts input parameter into array of objects which wraps the value.
|
|
104
|
+
* "someValue" is converted to [{"someValue":true}]
|
|
105
|
+
* ["someValue"] is converted to [{"someValue":true}]
|
|
106
|
+
* {"someValue":true} is converted to [{"someValue":true}]
|
|
107
|
+
* @param {String|Array|Object} param - Input parameter to be converted
|
|
108
|
+
* @returns {Array}
|
|
109
|
+
*/
|
|
110
|
+
function convertToArray(include) {
|
|
111
|
+
if (typeof include === "string") {
|
|
112
|
+
const obj = {};
|
|
113
|
+
obj[include] = true;
|
|
114
|
+
return [obj];
|
|
115
|
+
} else if (isPlainObject(include)) {
|
|
116
|
+
if (include.rel || include.relation) return [include];
|
|
117
|
+
const newInclude = [];
|
|
118
|
+
for (const key in include) {
|
|
119
|
+
const obj = {};
|
|
120
|
+
obj[key] = include[key];
|
|
121
|
+
newInclude.push(obj);
|
|
122
|
+
}
|
|
123
|
+
return newInclude;
|
|
124
|
+
} else if (Array.isArray(include)) {
|
|
125
|
+
const normalized = [];
|
|
126
|
+
for (const i in include) {
|
|
127
|
+
const includeEntry = include[i];
|
|
128
|
+
if (typeof includeEntry === "string") {
|
|
129
|
+
const obj = {};
|
|
130
|
+
obj[includeEntry] = true;
|
|
131
|
+
normalized.push(obj);
|
|
132
|
+
} else normalized.push(includeEntry);
|
|
133
|
+
}
|
|
134
|
+
return normalized;
|
|
135
|
+
}
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
/*!
|
|
139
|
+
* Merge query parameters
|
|
140
|
+
* @param {Object} base The base object to contain the merged results
|
|
141
|
+
* @param {Object} update The object containing updates to be merged
|
|
142
|
+
* @param {Object} spec Optionally specifies parameters to exclude (set to false)
|
|
143
|
+
* @returns {*|Object} The base object
|
|
144
|
+
* @private
|
|
145
|
+
*/
|
|
146
|
+
function mergeQuery(base, update, spec) {
|
|
147
|
+
if (!update) return;
|
|
148
|
+
spec = spec || {};
|
|
149
|
+
base = base || {};
|
|
150
|
+
if (update.where && Object.keys(update.where).length > 0) if (base.where && Object.keys(base.where).length > 0) base.where = { and: [base.where, update.where] };
|
|
151
|
+
else base.where = update.where;
|
|
152
|
+
if (spec.include !== false && update.include) if (!base.include) base.include = update.include;
|
|
153
|
+
else if (spec.nestedInclude === true) {
|
|
154
|
+
const saved = base.include;
|
|
155
|
+
base.include = {};
|
|
156
|
+
base.include[update.include] = saved;
|
|
157
|
+
} else base.include = mergeIncludes(base.include, update.include);
|
|
158
|
+
if (spec.collect !== false && update.collect) base.collect = update.collect;
|
|
159
|
+
if (spec.fields !== false && update.fields !== void 0) base.fields = update.fields;
|
|
160
|
+
else if (update.fields !== void 0) base.fields = [].concat(base.fields).concat(update.fields);
|
|
161
|
+
if ((!base.order || spec.order === false) && update.order) base.order = update.order;
|
|
162
|
+
if (spec.limit !== false && update.limit !== void 0) base.limit = update.limit;
|
|
163
|
+
const skip = spec.skip !== false && spec.offset !== false;
|
|
164
|
+
if (skip && update.skip !== void 0) base.skip = update.skip;
|
|
165
|
+
if (skip && update.offset !== void 0) base.offset = update.offset;
|
|
166
|
+
return base;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Normalize fields to an array of included properties
|
|
170
|
+
* @param {String|String[]|Object} fields Fields filter
|
|
171
|
+
* @param {String[]} properties Property names
|
|
172
|
+
* @param {Boolean} excludeUnknown To exclude fields that are unknown properties
|
|
173
|
+
* @returns {String[]} An array of included property names
|
|
174
|
+
*/
|
|
175
|
+
function fieldsToArray(fields, properties, excludeUnknown) {
|
|
176
|
+
if (!fields) return;
|
|
177
|
+
let result = properties;
|
|
178
|
+
let i, n;
|
|
179
|
+
if (typeof fields === "string") result = [fields];
|
|
180
|
+
else if (Array.isArray(fields) && fields.length > 0) result = fields;
|
|
181
|
+
else if ("object" === typeof fields) {
|
|
182
|
+
const included = [];
|
|
183
|
+
const excluded = [];
|
|
184
|
+
const keys = Object.keys(fields);
|
|
185
|
+
if (!keys.length) return;
|
|
186
|
+
for (i = 0, n = keys.length; i < n; i++) {
|
|
187
|
+
const k = keys[i];
|
|
188
|
+
if (fields[k]) included.push(k);
|
|
189
|
+
else if (k in fields && !fields[k]) excluded.push(k);
|
|
190
|
+
}
|
|
191
|
+
if (included.length > 0) result = included;
|
|
192
|
+
else if (excluded.length > 0) for (i = 0, n = excluded.length; i < n; i++) {
|
|
193
|
+
const index = result.indexOf(excluded[i]);
|
|
194
|
+
if (index !== -1) result.splice(index, 1);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
let fieldArray = [];
|
|
198
|
+
if (excludeUnknown) {
|
|
199
|
+
for (i = 0, n = result.length; i < n; i++) if (properties.indexOf(result[i]) !== -1) fieldArray.push(result[i]);
|
|
200
|
+
} else fieldArray = result;
|
|
201
|
+
return fieldArray;
|
|
202
|
+
}
|
|
203
|
+
function selectFields(fields) {
|
|
204
|
+
return function(obj) {
|
|
205
|
+
const result = {};
|
|
206
|
+
let key;
|
|
207
|
+
for (let i = 0; i < fields.length; i++) {
|
|
208
|
+
key = fields[i];
|
|
209
|
+
result[key] = obj[key];
|
|
210
|
+
}
|
|
211
|
+
return result;
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function isProhibited(key, prohibitedKeys) {
|
|
215
|
+
if (!prohibitedKeys || !prohibitedKeys.length) return false;
|
|
216
|
+
if (typeof key !== "string") return false;
|
|
217
|
+
const keySegments = key.indexOf(".") === -1 ? null : key.split(".");
|
|
218
|
+
for (const k of prohibitedKeys) {
|
|
219
|
+
if (k === key) return true;
|
|
220
|
+
if (keySegments && keySegments.indexOf(k) !== -1) return true;
|
|
221
|
+
}
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Accept an operator key and return whether it is used for a regular expression query or not
|
|
226
|
+
* @param {string} operator
|
|
227
|
+
* @returns {boolean}
|
|
228
|
+
*/
|
|
229
|
+
function isRegExpOperator(operator) {
|
|
230
|
+
return REGEXP_OPERATORS.has(operator);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Accept a RegExp string and make sure that any special characters for RegExp are escaped in case they
|
|
234
|
+
* create an invalid Regexp
|
|
235
|
+
* @param {string} str
|
|
236
|
+
* @returns {string}
|
|
237
|
+
*/
|
|
238
|
+
function escapeRegExp(str) {
|
|
239
|
+
assert.strictEqual(typeof str, "string", "String required for regexp escaping");
|
|
240
|
+
try {
|
|
241
|
+
new RegExp(str);
|
|
242
|
+
return str;
|
|
243
|
+
} catch {
|
|
244
|
+
console.warn("Auto-escaping invalid RegExp value %j supplied by the caller. Please note this behavior may change in the future.", str);
|
|
245
|
+
return str.replace(/[-[\]/{}()+?.\\^$|]/g, "\\$&");
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Sanitize the query object
|
|
250
|
+
* @param query {object} The query object
|
|
251
|
+
* @param options
|
|
252
|
+
* @property normalizeUndefinedInQuery {String} either "nullify", "throw" or "ignore" (default: "ignore")
|
|
253
|
+
* @property prohibitedKeys {String[]} An array of prohibited keys to be removed
|
|
254
|
+
* @returns {*}
|
|
255
|
+
*/
|
|
256
|
+
function sanitizeQuery(query, options) {
|
|
257
|
+
debug("Sanitizing query object: %j", query);
|
|
258
|
+
if (typeof query !== "object" || query === null) return query;
|
|
259
|
+
options = options || {};
|
|
260
|
+
if (typeof options === "string") options = { normalizeUndefinedInQuery: options };
|
|
261
|
+
const prohibitedKeys = options.prohibitedKeys;
|
|
262
|
+
const offendingKeys = [];
|
|
263
|
+
const normalizeUndefinedInQuery = options.normalizeUndefinedInQuery;
|
|
264
|
+
const maxDepth = options.maxDepth || Number.MAX_SAFE_INTEGER;
|
|
265
|
+
const result = traverse(query).forEach(function(x) {
|
|
266
|
+
/**
|
|
267
|
+
* Security risk if the client passes in a very deep where object
|
|
268
|
+
*/
|
|
269
|
+
if (this.circular) {
|
|
270
|
+
const msg = g.f("The query object is circular");
|
|
271
|
+
const err = new Error(msg);
|
|
272
|
+
err.statusCode = 400;
|
|
273
|
+
err.code = "QUERY_OBJECT_IS_CIRCULAR";
|
|
274
|
+
throw err;
|
|
275
|
+
}
|
|
276
|
+
if (this.level > maxDepth) {
|
|
277
|
+
const msg = g.f("The query object exceeds maximum depth %d", maxDepth);
|
|
278
|
+
const err = new Error(msg);
|
|
279
|
+
err.statusCode = 400;
|
|
280
|
+
err.code = "QUERY_OBJECT_TOO_DEEP";
|
|
281
|
+
throw err;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Make sure prohibited keys are removed from the query to prevent
|
|
285
|
+
* sensitive values from being guessed
|
|
286
|
+
*/
|
|
287
|
+
if (isProhibited(this.key, prohibitedKeys)) {
|
|
288
|
+
offendingKeys.push(this.key);
|
|
289
|
+
this.remove();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Handle undefined values
|
|
294
|
+
*/
|
|
295
|
+
if (x === void 0) switch (normalizeUndefinedInQuery) {
|
|
296
|
+
case "nullify":
|
|
297
|
+
this.update(null);
|
|
298
|
+
break;
|
|
299
|
+
case "throw": throw new Error(g.f("Unexpected `undefined` in query"));
|
|
300
|
+
default: this.remove();
|
|
301
|
+
}
|
|
302
|
+
if (!Array.isArray(x) && typeof x === "object" && x !== null && x.constructor !== Object) {
|
|
303
|
+
this.update(x, true);
|
|
304
|
+
return x;
|
|
305
|
+
}
|
|
306
|
+
if (isRegExpOperator(this.key) && typeof x === "string") return escapeRegExp(x);
|
|
307
|
+
return x;
|
|
308
|
+
});
|
|
309
|
+
if (offendingKeys.length) console.error(g.f("Potential security alert: hidden/protected properties %j are used in query.", offendingKeys));
|
|
310
|
+
return result;
|
|
311
|
+
}
|
|
312
|
+
const url = require("url");
|
|
313
|
+
const qs = require("qs");
|
|
314
|
+
/**
|
|
315
|
+
* Parse a URL into a settings object
|
|
316
|
+
* @param {String} urlStr The URL for connector settings
|
|
317
|
+
* @returns {Object} The settings object
|
|
318
|
+
*/
|
|
319
|
+
function parseSettings(urlStr) {
|
|
320
|
+
if (!urlStr) return {};
|
|
321
|
+
const uri = url.parse(urlStr, false);
|
|
322
|
+
const settings = {};
|
|
323
|
+
settings.connector = uri.protocol && uri.protocol.split(":")[0];
|
|
324
|
+
settings.host = settings.hostname = uri.hostname;
|
|
325
|
+
settings.port = uri.port && Number(uri.port);
|
|
326
|
+
settings.user = settings.username = uri.auth && uri.auth.split(":")[0];
|
|
327
|
+
settings.password = uri.auth && uri.auth.split(":")[1];
|
|
328
|
+
settings.database = uri.pathname && uri.pathname.split("/")[1];
|
|
329
|
+
settings.url = urlStr;
|
|
330
|
+
if (uri.query) {
|
|
331
|
+
const params = qs.parse(uri.query);
|
|
332
|
+
for (const p in params) settings[p] = params[p];
|
|
333
|
+
}
|
|
334
|
+
return settings;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Objects deep merge
|
|
338
|
+
*
|
|
339
|
+
* Forked from https://github.com/nrf110/deepmerge/blob/master/index.js
|
|
340
|
+
*
|
|
341
|
+
* The original function tries to merge array items if they are objects, this
|
|
342
|
+
* was changed to always push new items in arrays, independently of their type.
|
|
343
|
+
*
|
|
344
|
+
* NOTE: The function operates as a deep clone when called with a single object
|
|
345
|
+
* argument.
|
|
346
|
+
*
|
|
347
|
+
* @param {Object} base The base object
|
|
348
|
+
* @param {Object} extras The object to merge with base
|
|
349
|
+
* @returns {Object} The merged object
|
|
350
|
+
*/
|
|
351
|
+
function deepMerge(base, extras) {
|
|
352
|
+
const array = Array.isArray(base) && (Array.isArray(extras) || !extras);
|
|
353
|
+
let dst = array && [] || {};
|
|
354
|
+
if (array) {
|
|
355
|
+
extras = extras || [];
|
|
356
|
+
dst = base.slice();
|
|
357
|
+
const seen = new Set(dst);
|
|
358
|
+
for (let i = 0; i < extras.length; i++) {
|
|
359
|
+
const extraItem = extras[i];
|
|
360
|
+
if (seen.has(extraItem)) continue;
|
|
361
|
+
seen.add(extraItem);
|
|
362
|
+
dst.push(extraItem);
|
|
363
|
+
}
|
|
364
|
+
} else {
|
|
365
|
+
if (base != null && typeof base === "object") {
|
|
366
|
+
const baseKeys = Object.keys(base);
|
|
367
|
+
for (let i = 0; i < baseKeys.length; i++) {
|
|
368
|
+
const key = baseKeys[i];
|
|
369
|
+
if (base[key] && typeof base[key] === "object") dst[key] = deepMerge(base[key]);
|
|
370
|
+
else dst[key] = base[key];
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (extras != null && typeof extras === "object") {
|
|
374
|
+
const extraKeys = Object.keys(extras);
|
|
375
|
+
for (let i = 0; i < extraKeys.length; i++) {
|
|
376
|
+
const key = extraKeys[i];
|
|
377
|
+
const extra = extras[key];
|
|
378
|
+
if (extra == null || typeof extra !== "object") dst[key] = extra;
|
|
379
|
+
else if (base == null || typeof base !== "object" || base[key] == null) dst[key] = extra;
|
|
380
|
+
else dst[key] = deepMerge(base[key], extra);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return dst;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Properties deep merge
|
|
388
|
+
* Similar as deepMerge but also works on single properties of any type
|
|
389
|
+
*
|
|
390
|
+
* @param {Object} base The base property
|
|
391
|
+
* @param {Object} extras The property to merge with base
|
|
392
|
+
* @returns {Object} The merged property
|
|
393
|
+
*/
|
|
394
|
+
function deepMergeProperty(base, extras) {
|
|
395
|
+
return deepMerge({ key: base }, { key: extras }).key;
|
|
396
|
+
}
|
|
397
|
+
const numberIsFinite = Number.isFinite || function(value) {
|
|
398
|
+
return typeof value === "number" && isFinite(value);
|
|
399
|
+
};
|
|
400
|
+
/**
|
|
401
|
+
* Adds a property __rank to array elements of type object {}
|
|
402
|
+
* If an inner element already has the __rank property it is not altered
|
|
403
|
+
* NOTE: the function mutates the provided array
|
|
404
|
+
*
|
|
405
|
+
* @param array The original array
|
|
406
|
+
* @param rank The rank to apply to array elements
|
|
407
|
+
* @return rankedArray The original array with newly ranked elements
|
|
408
|
+
*/
|
|
409
|
+
function rankArrayElements(array, rank) {
|
|
410
|
+
if (!Array.isArray(array) || !numberIsFinite(rank)) return array;
|
|
411
|
+
for (let i = 0, n = array.length; i < n; i++) {
|
|
412
|
+
const el = array[i];
|
|
413
|
+
if (!el || typeof el != "object" || Array.isArray(el)) continue;
|
|
414
|
+
if (el.__rank) continue;
|
|
415
|
+
Object.defineProperty(el, "__rank", {
|
|
416
|
+
writable: false,
|
|
417
|
+
enumerable: false,
|
|
418
|
+
configurable: false,
|
|
419
|
+
value: rank
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
return array;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Define an non-enumerable __cachedRelations property
|
|
426
|
+
* @param {Object} obj The obj to receive the __cachedRelations
|
|
427
|
+
*/
|
|
428
|
+
function defineCachedRelations(obj) {
|
|
429
|
+
if (!obj.__cachedRelations) Object.defineProperty(obj, "__cachedRelations", {
|
|
430
|
+
writable: true,
|
|
431
|
+
enumerable: false,
|
|
432
|
+
configurable: true,
|
|
433
|
+
value: {}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Check if the argument is plain object
|
|
438
|
+
* @param {*} obj The obj value
|
|
439
|
+
* @returns {boolean}
|
|
440
|
+
*/
|
|
441
|
+
function isPlainObject(obj) {
|
|
442
|
+
return typeof obj === "object" && obj !== null && obj.constructor === Object;
|
|
443
|
+
}
|
|
444
|
+
function sortObjectsByIds(idName, ids, objects, strict) {
|
|
445
|
+
ids = ids.map(function(id) {
|
|
446
|
+
return typeof id === "object" ? String(id) : id;
|
|
447
|
+
});
|
|
448
|
+
const ranks = /* @__PURE__ */ new Map();
|
|
449
|
+
for (let i = 0; i < ids.length; i++) if (!ranks.has(ids[i])) ranks.set(ids[i], i);
|
|
450
|
+
const heading = [];
|
|
451
|
+
const tailing = [];
|
|
452
|
+
objects.forEach(function(x) {
|
|
453
|
+
if (typeof x === "object") {
|
|
454
|
+
const id = typeof x[idName] === "object" ? String(x[idName]) : x[idName];
|
|
455
|
+
const idx = ranks.has(id) ? ranks.get(id) : -1;
|
|
456
|
+
if (strict && idx === -1) return;
|
|
457
|
+
if (idx === -1) tailing.push(x);
|
|
458
|
+
else heading.push({
|
|
459
|
+
idx,
|
|
460
|
+
value: x
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
heading.sort(function(x, y) {
|
|
465
|
+
return x.idx - y.idx;
|
|
466
|
+
});
|
|
467
|
+
const sorted = Array.from({ length: heading.length + tailing.length });
|
|
468
|
+
for (let i = 0; i < heading.length; i++) sorted[i] = heading[i].value;
|
|
469
|
+
for (let i = 0; i < tailing.length; i++) sorted[heading.length + i] = tailing[i];
|
|
470
|
+
return sorted;
|
|
471
|
+
}
|
|
472
|
+
function createPromiseCallback() {
|
|
473
|
+
let cb;
|
|
474
|
+
const promise = new Promise(function(resolve, reject) {
|
|
475
|
+
cb = function(err, data) {
|
|
476
|
+
if (err) return reject(err);
|
|
477
|
+
return resolve(data);
|
|
478
|
+
};
|
|
479
|
+
});
|
|
480
|
+
cb.promise = promise;
|
|
481
|
+
return cb;
|
|
482
|
+
}
|
|
483
|
+
function isBsonType(value) {
|
|
484
|
+
return value.hasOwnProperty("_bsontype") || value.constructor.prototype.hasOwnProperty("_bsontype");
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Dedupe an array
|
|
488
|
+
* @param {Array} an array
|
|
489
|
+
* @returns {Array} an array with unique items
|
|
490
|
+
*/
|
|
491
|
+
function uniq(a) {
|
|
492
|
+
const uniqArray = [];
|
|
493
|
+
if (!a) return uniqArray;
|
|
494
|
+
assert(Array.isArray(a), "array argument is required");
|
|
495
|
+
const seen = /* @__PURE__ */ new Set();
|
|
496
|
+
for (let i = 0, n = a.length; i < n; i++) {
|
|
497
|
+
const item = a[i];
|
|
498
|
+
const comparable = isBsonType(item) ? item.toString() : item;
|
|
499
|
+
if (seen.has(comparable)) continue;
|
|
500
|
+
seen.add(comparable);
|
|
501
|
+
uniqArray.push(item);
|
|
502
|
+
}
|
|
503
|
+
return uniqArray;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Converts a string, regex literal, or a RegExp object to a RegExp object.
|
|
507
|
+
* @param {String|Object} The string, regex literal, or RegExp object to convert
|
|
508
|
+
* @returns {Object} A RegExp object
|
|
509
|
+
*/
|
|
510
|
+
function toRegExp(regex) {
|
|
511
|
+
const isString = typeof regex === "string";
|
|
512
|
+
const isRegExp = regex instanceof RegExp;
|
|
513
|
+
if (!(isString || isRegExp)) return new Error(g.f("Invalid argument, must be a string, {{regex}} literal, or {{RegExp}} object"));
|
|
514
|
+
if (isRegExp) return regex;
|
|
515
|
+
if (!hasRegExpFlags(regex)) return new RegExp(regex);
|
|
516
|
+
const flags = regex.split("/").pop().split("");
|
|
517
|
+
const validFlags = [
|
|
518
|
+
"i",
|
|
519
|
+
"g",
|
|
520
|
+
"m"
|
|
521
|
+
];
|
|
522
|
+
const invalidFlags = [];
|
|
523
|
+
flags.forEach(function(flag) {
|
|
524
|
+
if (validFlags.indexOf(flag) === -1) invalidFlags.push(flag);
|
|
525
|
+
});
|
|
526
|
+
if (invalidFlags.length > 0) return new Error(g.f("Invalid {{regex}} flags: %s", invalidFlags));
|
|
527
|
+
const expression = regex.substr(1, regex.lastIndexOf("/") - 1);
|
|
528
|
+
return new RegExp(expression, flags.join(""));
|
|
529
|
+
}
|
|
530
|
+
function hasRegExpFlags(regex) {
|
|
531
|
+
return regex instanceof RegExp ? regex.toString().split("/").pop() : !!regex.match(/.*\/.+$/);
|
|
532
|
+
}
|
|
533
|
+
function idEquals(id1, id2) {
|
|
534
|
+
if (id1 === id2) return true;
|
|
535
|
+
if (typeof id1 === "number" && typeof id2 === "string" || typeof id1 === "string" && typeof id2 === "number") return id1 == id2;
|
|
536
|
+
id1 = JSON.stringify(id1);
|
|
537
|
+
id2 = JSON.stringify(id2);
|
|
538
|
+
if (id1 === id2) return true;
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
function findIndexOf(arr, target, isEqual) {
|
|
542
|
+
if (!isEqual) return arr.indexOf(target);
|
|
543
|
+
for (let i = 0; i < arr.length; i++) if (isEqual(arr[i], target)) return i;
|
|
544
|
+
return -1;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Returns an object that queries targetIds.
|
|
548
|
+
* @param {Array} The array of targetData
|
|
549
|
+
* @param {String} The Id property name of target model
|
|
550
|
+
* @returns {Object} The object that queries targetIds
|
|
551
|
+
*/
|
|
552
|
+
function collectTargetIds(targetData, idPropertyName) {
|
|
553
|
+
const targetIds = [];
|
|
554
|
+
for (let i = 0; i < targetData.length; i++) {
|
|
555
|
+
const targetId = targetData[i][idPropertyName];
|
|
556
|
+
targetIds.push(targetId);
|
|
557
|
+
}
|
|
558
|
+
return { inq: uniq(targetIds) };
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Find the idKey of a Model.
|
|
562
|
+
* @param {ModelConstructor} m - Model Constructor
|
|
563
|
+
* @returns {String}
|
|
564
|
+
*/
|
|
565
|
+
function idName(m) {
|
|
566
|
+
return m.definition.idName() || "id";
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Check a list of IDs to see if there are any duplicates.
|
|
570
|
+
*
|
|
571
|
+
* @param {Array} The array of IDs to check
|
|
572
|
+
* @returns {boolean} If any duplicates were found
|
|
573
|
+
*/
|
|
574
|
+
function idsHaveDuplicates(ids) {
|
|
575
|
+
let hasDuplicates = void 0;
|
|
576
|
+
let i, j;
|
|
577
|
+
if (typeof Set === "function") {
|
|
578
|
+
const uniqueIds = /* @__PURE__ */ new Set();
|
|
579
|
+
for (i = 0; i < ids.length; ++i) {
|
|
580
|
+
const idType = typeof ids[i];
|
|
581
|
+
if (idType === "string" || idType === "number") if (uniqueIds.has(ids[i])) {
|
|
582
|
+
hasDuplicates = true;
|
|
583
|
+
break;
|
|
584
|
+
} else uniqueIds.add(ids[i]);
|
|
585
|
+
else break;
|
|
586
|
+
}
|
|
587
|
+
if (hasDuplicates === void 0 && uniqueIds.size === ids.length) hasDuplicates = false;
|
|
588
|
+
}
|
|
589
|
+
if (hasDuplicates === void 0) {
|
|
590
|
+
for (i = 0; i < ids.length && hasDuplicates === void 0; ++i) for (j = 0; j < i; ++j) if (idEquals(ids[i], ids[j])) {
|
|
591
|
+
hasDuplicates = true;
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return hasDuplicates === true;
|
|
596
|
+
}
|
|
597
|
+
function isClass(fn) {
|
|
598
|
+
return fn && fn.toString().startsWith("class ");
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Accept an element, and attach the __parent property to it, unless no object given, while also
|
|
602
|
+
* making sure to check for already created properties
|
|
603
|
+
*
|
|
604
|
+
* @param {object} element
|
|
605
|
+
* @param {Model} parent
|
|
606
|
+
*/
|
|
607
|
+
function applyParentProperty(element, parent) {
|
|
608
|
+
assert.strictEqual(typeof element, "object", "Non object element given to assign parent");
|
|
609
|
+
const { constructor: { modelBuilder: { settings: builderSettings } = {} } = {} } = element;
|
|
610
|
+
if (!builderSettings || !builderSettings[BUILDER_PARENT_SETTING]) return;
|
|
611
|
+
if (element.hasOwnProperty(PARENT_PROPERTY_NAME)) {
|
|
612
|
+
const existingParent = element[PARENT_PROPERTY_NAME];
|
|
613
|
+
if (existingParent && existingParent !== parent) g.warn(`Re-assigning child model instance to another parent than the original!
|
|
614
|
+
Although supported, this is not a recommended practice: ${element.constructor.name} -> ${parent.constructor.name}\nYou should create an independent copy of the child model using \`new Model(CHILD)\` OR \`new Model(CHILD.toJSON())\` and assign to new parent`);
|
|
615
|
+
element[PARENT_PROPERTY_NAME] = parent;
|
|
616
|
+
} else Object.defineProperty(element, PARENT_PROPERTY_NAME, {
|
|
617
|
+
value: parent,
|
|
618
|
+
writable: true,
|
|
619
|
+
enumerable: false,
|
|
620
|
+
configurable: false
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
}));
|
|
624
|
+
//#endregion
|
|
625
|
+
module.exports = require_utils();
|