@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,2604 @@
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_validations = require("./validations.js");
6
+ const require_lib_model = require("./model.js");
7
+ const require_lib_scope = require("./scope.js");
8
+ const require_lib_connectors_memory = require("./connectors/memory.js");
9
+ //#region src/lib/relation-definition.ts
10
+ var require_relation_definition = /* @__PURE__ */ require_runtime.__commonJSMin(((exports) => {
11
+ /*!
12
+ * Dependencies
13
+ */
14
+ const assert = require("assert");
15
+ const util = require("util");
16
+ const utils = require_lib_utils;
17
+ const i8n = require("inflection");
18
+ const defineScope = require_lib_scope.defineScope;
19
+ const g = require_lib_globalize();
20
+ const mergeQuery = utils.mergeQuery;
21
+ const idEquals = utils.idEquals;
22
+ const idsHaveDuplicates = utils.idsHaveDuplicates;
23
+ const ModelBaseClass = require_lib_model;
24
+ const applyFilter = require_lib_connectors_memory.applyFilter;
25
+ const ValidationError = require_lib_validations.ValidationError;
26
+ const deprecated = require("depd")("loopback-datasource-juggler");
27
+ const debug = require("debug")("loopback:relations");
28
+ function callbackToPromise(fn) {
29
+ return new Promise(function(resolve, reject) {
30
+ fn(function(err, result) {
31
+ if (err) return reject(err);
32
+ resolve(result);
33
+ });
34
+ });
35
+ }
36
+ const RelationTypes = {
37
+ belongsTo: "belongsTo",
38
+ hasMany: "hasMany",
39
+ hasOne: "hasOne",
40
+ hasAndBelongsToMany: "hasAndBelongsToMany",
41
+ referencesMany: "referencesMany",
42
+ embedsOne: "embedsOne",
43
+ embedsMany: "embedsMany"
44
+ };
45
+ const RelationClasses = {
46
+ belongsTo: BelongsTo,
47
+ hasMany: HasMany,
48
+ hasManyThrough: HasManyThrough,
49
+ hasOne: HasOne,
50
+ hasAndBelongsToMany: HasAndBelongsToMany,
51
+ referencesMany: ReferencesMany,
52
+ embedsOne: EmbedsOne,
53
+ embedsMany: EmbedsMany
54
+ };
55
+ exports.Relation = Relation;
56
+ exports.RelationDefinition = RelationDefinition;
57
+ exports.RelationTypes = RelationTypes;
58
+ exports.RelationClasses = RelationClasses;
59
+ exports.HasMany = HasMany;
60
+ exports.HasManyThrough = HasManyThrough;
61
+ exports.HasOne = HasOne;
62
+ exports.HasAndBelongsToMany = HasAndBelongsToMany;
63
+ exports.BelongsTo = BelongsTo;
64
+ exports.ReferencesMany = ReferencesMany;
65
+ exports.EmbedsOne = EmbedsOne;
66
+ exports.EmbedsMany = EmbedsMany;
67
+ function normalizeType(type) {
68
+ if (!type) return type;
69
+ const t1 = type.toLowerCase();
70
+ for (const t2 in RelationTypes) if (t2.toLowerCase() === t1) return t2;
71
+ return null;
72
+ }
73
+ function extendScopeMethods(definition, scopeMethods, ext) {
74
+ let customMethods = [];
75
+ let relationClass = RelationClasses[definition.type];
76
+ if (definition.type === RelationTypes.hasMany && definition.modelThrough) relationClass = RelationClasses.hasManyThrough;
77
+ if (typeof ext === "function") customMethods = ext.call(definition, scopeMethods, relationClass);
78
+ else if (typeof ext === "object") {
79
+ function createFunc(definition, relationMethod) {
80
+ return function() {
81
+ const relation = new relationClass(definition, this);
82
+ return relationMethod.apply(relation, arguments);
83
+ };
84
+ }
85
+ for (const key in ext) {
86
+ const relationMethod = ext[key];
87
+ const method = scopeMethods[key] = createFunc(definition, relationMethod);
88
+ if (relationMethod.shared) sharedMethod(definition, key, method, relationMethod);
89
+ customMethods.push(key);
90
+ }
91
+ }
92
+ return [].concat(customMethods || []);
93
+ }
94
+ function bindRelationMethods(relation, relationMethod, definition) {
95
+ const methods = definition.methods || {};
96
+ Object.keys(methods).forEach(function(m) {
97
+ if (typeof methods[m] !== "function") return;
98
+ relationMethod[m] = methods[m].bind(relation);
99
+ });
100
+ }
101
+ function preventFkOverride(inst, data, fkProp) {
102
+ if (!fkProp) return void 0;
103
+ if (data[fkProp] !== void 0 && !idEquals(data[fkProp], inst[fkProp])) return new Error(g.f("Cannot override foreign key %s from %s to %s", fkProp, inst[fkProp], data[fkProp]));
104
+ }
105
+ /**
106
+ * Relation definition class. Use to define relationships between models.
107
+ * @param {Object} definition
108
+ * @class RelationDefinition
109
+ */
110
+ function RelationDefinition(definition) {
111
+ if (!(this instanceof RelationDefinition)) return new RelationDefinition(definition);
112
+ definition = definition || {};
113
+ this.name = definition.name;
114
+ assert(this.name, "Relation name is missing");
115
+ this.type = normalizeType(definition.type);
116
+ assert(this.type, "Invalid relation type: " + definition.type);
117
+ this.modelFrom = definition.modelFrom;
118
+ assert(this.modelFrom, "Source model is required");
119
+ this.keyFrom = definition.keyFrom;
120
+ this.modelTo = definition.modelTo;
121
+ this.keyTo = definition.keyTo;
122
+ this.polymorphic = definition.polymorphic;
123
+ if (typeof this.polymorphic !== "object") assert(this.modelTo, "Target model is required");
124
+ this.modelThrough = definition.modelThrough;
125
+ this.keyThrough = definition.keyThrough;
126
+ this.multiple = definition.multiple;
127
+ this.properties = definition.properties || {};
128
+ this.options = definition.options || {};
129
+ this.scope = definition.scope;
130
+ this.embed = definition.embed === true;
131
+ this.methods = definition.methods || {};
132
+ }
133
+ RelationDefinition.prototype.toJSON = function() {
134
+ const polymorphic = typeof this.polymorphic === "object";
135
+ let modelToName = this.modelTo && this.modelTo.modelName;
136
+ if (!modelToName && polymorphic && this.type === "belongsTo") modelToName = "<polymorphic>";
137
+ const json = {
138
+ name: this.name,
139
+ type: this.type,
140
+ modelFrom: this.modelFrom.modelName,
141
+ keyFrom: this.keyFrom,
142
+ modelTo: modelToName,
143
+ keyTo: this.keyTo,
144
+ multiple: this.multiple
145
+ };
146
+ if (this.modelThrough) {
147
+ json.modelThrough = this.modelThrough.modelName;
148
+ json.keyThrough = this.keyThrough;
149
+ }
150
+ if (polymorphic) json.polymorphic = this.polymorphic;
151
+ return json;
152
+ };
153
+ /**
154
+ * Define a relation scope method
155
+ * @param {String} name of the method
156
+ * @param {Function} function to define
157
+ */
158
+ RelationDefinition.prototype.defineMethod = function(name, fn) {
159
+ const relationClass = RelationClasses[this.type];
160
+ const relationName = this.name;
161
+ const modelFrom = this.modelFrom;
162
+ const definition = this;
163
+ let method;
164
+ if (definition.multiple) {
165
+ const scope = this.modelFrom.scopes[this.name];
166
+ if (!scope) throw new Error(g.f("Unknown relation {{scope}}: %s", this.name));
167
+ method = scope.defineMethod(name, function() {
168
+ const relation = new relationClass(definition, this);
169
+ return fn.apply(relation, arguments);
170
+ });
171
+ } else {
172
+ definition.methods[name] = fn;
173
+ method = function() {
174
+ const rel = this[relationName];
175
+ return rel[name].apply(rel, arguments);
176
+ };
177
+ }
178
+ if (method && fn.shared) {
179
+ sharedMethod(definition, name, method, fn);
180
+ modelFrom.prototype["__" + name + "__" + relationName] = method;
181
+ }
182
+ return method;
183
+ };
184
+ /**
185
+ * Apply the configured scope to the filter/query object.
186
+ * @param {Object} modelInstance
187
+ * @param {Object} filter (where, order, limit, fields, ...)
188
+ */
189
+ RelationDefinition.prototype.applyScope = function(modelInstance, filter) {
190
+ filter = filter || {};
191
+ filter.where = filter.where || {};
192
+ if ((this.type !== "belongsTo" || this.type === "hasOne") && typeof this.polymorphic === "object") {
193
+ const discriminator = this.polymorphic.discriminator;
194
+ if (this.polymorphic.invert) filter.where[discriminator] = this.modelTo.modelName;
195
+ else filter.where[discriminator] = this.modelFrom.modelName;
196
+ }
197
+ let scope;
198
+ if (typeof this.scope === "function") scope = this.scope.call(this, modelInstance, filter);
199
+ else scope = this.scope;
200
+ if (typeof scope === "object") mergeQuery(filter, scope);
201
+ };
202
+ /**
203
+ * Apply the configured properties to the target object.
204
+ * @param {Object} modelInstance
205
+ * @param {Object} target
206
+ */
207
+ RelationDefinition.prototype.applyProperties = function(modelInstance, obj) {
208
+ let source = modelInstance, target = obj;
209
+ if (this.options.invertProperties) {
210
+ source = obj;
211
+ target = modelInstance;
212
+ }
213
+ if (this.options.embedsProperties) {
214
+ target = target.__data[this.name] = {};
215
+ target[this.keyTo] = source[this.keyTo];
216
+ }
217
+ let k, key;
218
+ if (typeof this.properties === "function") {
219
+ const data = this.properties.call(this, source, target);
220
+ for (k in data) target[k] = data[k];
221
+ } else if (Array.isArray(this.properties)) for (k = 0; k < this.properties.length; k++) {
222
+ key = this.properties[k];
223
+ target[key] = source[key];
224
+ }
225
+ else if (typeof this.properties === "object") for (k in this.properties) {
226
+ key = this.properties[k];
227
+ target[key] = source[k];
228
+ }
229
+ if ((this.type !== "belongsTo" || this.type === "hasOne") && typeof this.polymorphic === "object") {
230
+ const discriminator = this.polymorphic.discriminator;
231
+ if (this.polymorphic.invert) target[discriminator] = this.modelTo.modelName;
232
+ else target[discriminator] = this.modelFrom.modelName;
233
+ }
234
+ };
235
+ /**
236
+ * A relation attaching to a given model instance
237
+ * @param {RelationDefinition|Object} definition
238
+ * @param {Object} modelInstance
239
+ * @returns {Relation}
240
+ * @constructor
241
+ * @class Relation
242
+ */
243
+ function Relation(definition, modelInstance) {
244
+ if (!(this instanceof Relation)) return new Relation(definition, modelInstance);
245
+ if (!(definition instanceof RelationDefinition)) definition = new RelationDefinition(definition);
246
+ this.definition = definition;
247
+ this.modelInstance = modelInstance;
248
+ }
249
+ Relation.prototype.resetCache = function(cache) {
250
+ cache = cache || void 0;
251
+ this.modelInstance.__cachedRelations[this.definition.name] = cache;
252
+ };
253
+ Relation.prototype.getCache = function() {
254
+ return this.modelInstance.__cachedRelations[this.definition.name];
255
+ };
256
+ Relation.prototype.callScopeMethod = function(methodName) {
257
+ const args = Array.prototype.slice.call(arguments, 1);
258
+ const rel = this.modelInstance[this.definition.name];
259
+ if (rel && typeof rel[methodName] === "function") return rel[methodName].apply(rel, args);
260
+ else throw new Error(g.f("Unknown scope method: %s", methodName));
261
+ };
262
+ /**
263
+ * Fetch the related model(s) - this is a helper method to unify access.
264
+ * @param (Boolean|Object} condOrRefresh refresh or conditions object
265
+ * @param {Object} [options] Options
266
+ * @param {Function} cb callback
267
+ */
268
+ Relation.prototype.fetch = function(_condOrRefresh, _options, _cb) {
269
+ this.modelInstance[this.definition.name].apply(this.modelInstance, arguments);
270
+ };
271
+ /**
272
+ * HasMany subclass
273
+ * @param {RelationDefinition|Object} definition
274
+ * @param {Object} modelInstance
275
+ * @returns {HasMany}
276
+ * @constructor
277
+ * @class HasMany
278
+ */
279
+ function HasMany(definition, modelInstance) {
280
+ if (!(this instanceof HasMany)) return new HasMany(definition, modelInstance);
281
+ assert(definition.type === RelationTypes.hasMany);
282
+ Relation.apply(this, arguments);
283
+ }
284
+ util.inherits(HasMany, Relation);
285
+ HasMany.prototype.removeFromCache = function(id) {
286
+ const cache = this.modelInstance.__cachedRelations[this.definition.name];
287
+ const idName = this.definition.modelTo.definition.idName();
288
+ if (Array.isArray(cache)) {
289
+ for (let i = 0, n = cache.length; i < n; i++) if (idEquals(cache[i][idName], id)) return cache.splice(i, 1);
290
+ }
291
+ return null;
292
+ };
293
+ HasMany.prototype.addToCache = function(inst) {
294
+ if (!inst) return;
295
+ let cache = this.modelInstance.__cachedRelations[this.definition.name];
296
+ if (cache === void 0) cache = this.modelInstance.__cachedRelations[this.definition.name] = [];
297
+ const idName = this.definition.modelTo.definition.idName();
298
+ if (Array.isArray(cache)) {
299
+ for (let i = 0, n = cache.length; i < n; i++) if (idEquals(cache[i][idName], inst[idName])) {
300
+ cache[i] = inst;
301
+ return;
302
+ }
303
+ cache.push(inst);
304
+ }
305
+ };
306
+ /**
307
+ * HasManyThrough subclass
308
+ * @param {RelationDefinition|Object} definition
309
+ * @param {Object} modelInstance
310
+ * @returns {HasManyThrough}
311
+ * @constructor
312
+ * @class HasManyThrough
313
+ */
314
+ function HasManyThrough(definition, modelInstance) {
315
+ if (!(this instanceof HasManyThrough)) return new HasManyThrough(definition, modelInstance);
316
+ assert(definition.type === RelationTypes.hasMany);
317
+ assert(definition.modelThrough);
318
+ HasMany.apply(this, arguments);
319
+ }
320
+ util.inherits(HasManyThrough, HasMany);
321
+ /**
322
+ * BelongsTo subclass
323
+ * @param {RelationDefinition|Object} definition
324
+ * @param {Object} modelInstance
325
+ * @returns {BelongsTo}
326
+ * @constructor
327
+ * @class BelongsTo
328
+ */
329
+ function BelongsTo(definition, modelInstance) {
330
+ if (!(this instanceof BelongsTo)) return new BelongsTo(definition, modelInstance);
331
+ assert(definition.type === RelationTypes.belongsTo);
332
+ Relation.apply(this, arguments);
333
+ }
334
+ util.inherits(BelongsTo, Relation);
335
+ /**
336
+ * HasAndBelongsToMany subclass
337
+ * @param {RelationDefinition|Object} definition
338
+ * @param {Object} modelInstance
339
+ * @returns {HasAndBelongsToMany}
340
+ * @constructor
341
+ * @class HasAndBelongsToMany
342
+ */
343
+ function HasAndBelongsToMany(definition, modelInstance) {
344
+ if (!(this instanceof HasAndBelongsToMany)) return new HasAndBelongsToMany(definition, modelInstance);
345
+ assert(definition.type === RelationTypes.hasAndBelongsToMany);
346
+ Relation.apply(this, arguments);
347
+ }
348
+ util.inherits(HasAndBelongsToMany, Relation);
349
+ /**
350
+ * HasOne subclass
351
+ * @param {RelationDefinition|Object} definition
352
+ * @param {Object} modelInstance
353
+ * @returns {HasOne}
354
+ * @constructor
355
+ * @class HasOne
356
+ */
357
+ function HasOne(definition, modelInstance) {
358
+ if (!(this instanceof HasOne)) return new HasOne(definition, modelInstance);
359
+ assert(definition.type === RelationTypes.hasOne);
360
+ Relation.apply(this, arguments);
361
+ }
362
+ util.inherits(HasOne, Relation);
363
+ /**
364
+ * EmbedsOne subclass
365
+ * @param {RelationDefinition|Object} definition
366
+ * @param {Object} modelInstance
367
+ * @returns {EmbedsOne}
368
+ * @constructor
369
+ * @class EmbedsOne
370
+ */
371
+ function EmbedsOne(definition, modelInstance) {
372
+ if (!(this instanceof EmbedsOne)) return new EmbedsOne(definition, modelInstance);
373
+ assert(definition.type === RelationTypes.embedsOne);
374
+ Relation.apply(this, arguments);
375
+ }
376
+ util.inherits(EmbedsOne, Relation);
377
+ /**
378
+ * EmbedsMany subclass
379
+ * @param {RelationDefinition|Object} definition
380
+ * @param {Object} modelInstance
381
+ * @returns {EmbedsMany}
382
+ * @constructor
383
+ * @class EmbedsMany
384
+ */
385
+ function EmbedsMany(definition, modelInstance) {
386
+ if (!(this instanceof EmbedsMany)) return new EmbedsMany(definition, modelInstance);
387
+ assert(definition.type === RelationTypes.embedsMany);
388
+ Relation.apply(this, arguments);
389
+ }
390
+ util.inherits(EmbedsMany, Relation);
391
+ /**
392
+ * ReferencesMany subclass
393
+ * @param {RelationDefinition|Object} definition
394
+ * @param {Object} modelInstance
395
+ * @returns {ReferencesMany}
396
+ * @constructor
397
+ * @class ReferencesMany
398
+ */
399
+ function ReferencesMany(definition, modelInstance) {
400
+ if (!(this instanceof ReferencesMany)) return new ReferencesMany(definition, modelInstance);
401
+ assert(definition.type === RelationTypes.referencesMany);
402
+ Relation.apply(this, arguments);
403
+ }
404
+ util.inherits(ReferencesMany, Relation);
405
+ /*!
406
+ * Find the relation by foreign key
407
+ * @param {*} foreignKey The foreign key
408
+ * @returns {Array} The array of matching relation objects
409
+ */
410
+ function findBelongsTo(modelFrom, modelTo, keyTo) {
411
+ return Object.keys(modelFrom.relations).map(function(k) {
412
+ return modelFrom.relations[k];
413
+ }).filter(function(rel) {
414
+ return rel.type === RelationTypes.belongsTo && rel.modelTo === modelTo && (keyTo === void 0 || rel.keyTo === keyTo);
415
+ }).map(function(rel) {
416
+ return rel.keyFrom;
417
+ });
418
+ }
419
+ /*!
420
+ * Look up a model by name from the list of given models
421
+ * @param {Object} models Models keyed by name
422
+ * @param {String} modelName The model name
423
+ * @returns {*} The matching model class
424
+ */
425
+ function lookupModel(models, modelName) {
426
+ if (models[modelName]) return models[modelName];
427
+ const lookupClassName = modelName.toLowerCase();
428
+ for (const name in models) if (name.toLowerCase() === lookupClassName) return models[name];
429
+ }
430
+ function lookupModelTo(modelFrom, modelToRef, params, singularize) {
431
+ let modelTo;
432
+ if (typeof modelToRef !== "string") modelTo = modelToRef;
433
+ else {
434
+ let modelToName;
435
+ modelTo = params.model || modelToRef;
436
+ if (typeof modelTo === "string") {
437
+ modelToName = modelTo;
438
+ modelToName = (singularize ? i8n.singularize(modelToName) : modelToName).toLowerCase();
439
+ modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName);
440
+ }
441
+ if (!modelTo) {
442
+ const relationToName = params.as || modelToRef;
443
+ modelToName = (singularize ? i8n.singularize(relationToName) : relationToName).toLowerCase();
444
+ modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName);
445
+ }
446
+ }
447
+ if (typeof modelTo !== "function") throw new Error(g.f("Could not find relation %s for model %s", params.as, modelFrom.modelName));
448
+ return modelTo;
449
+ }
450
+ function normalizeRelationAs(params, relationName) {
451
+ if (typeof relationName === "string") params.as = params.as || relationName;
452
+ return params;
453
+ }
454
+ function normalizePolymorphic(polymorphic, relationName) {
455
+ assert(polymorphic, "polymorphic param can't be false, null or undefined");
456
+ assert(!Array.isArray(polymorphic, "unexpected type for polymorphic param: 'Array'"));
457
+ let selector;
458
+ if (typeof polymorphic === "string") selector = polymorphic;
459
+ if (polymorphic === true) selector = relationName;
460
+ if (typeof polymorphic == "object") selector = polymorphic.selector || polymorphic.as;
461
+ selector = selector || relationName || "reference";
462
+ if (typeof polymorphic !== "object") polymorphic = {};
463
+ polymorphic.selector = selector;
464
+ polymorphic.foreignKey = polymorphic.foreignKey || i8n.camelize(selector + "_id", true);
465
+ polymorphic.discriminator = polymorphic.discriminator || i8n.camelize(selector + "_type", true);
466
+ return polymorphic;
467
+ }
468
+ /**
469
+ * Define a "one to many" relationship by specifying the model name
470
+ *
471
+ * Examples:
472
+ * ```
473
+ * User.hasMany(Post, {as: 'posts', foreignKey: 'authorId'});
474
+ * ```
475
+ *
476
+ * ```
477
+ * Book.hasMany(Chapter);
478
+ * ```
479
+ * Or, equivalently:
480
+ * ```
481
+ * Book.hasMany('chapters', {model: Chapter});
482
+ * ```
483
+ * @param {Model} modelFrom Source model class
484
+ * @param {Object|String} modelToRef Reference to Model object to which you are
485
+ * creating the relation: model instance, model name, or name of relation to model.
486
+ * @options {Object} params Configuration parameters; see below.
487
+ * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model.
488
+ * @property {String} foreignKey Property name of foreign key field.
489
+ * @property {Object} model Model object
490
+ */
491
+ RelationDefinition.hasMany = function hasMany(modelFrom, modelToRef, params) {
492
+ const thisClassName = modelFrom.modelName;
493
+ params = params || {};
494
+ normalizeRelationAs(params, modelToRef);
495
+ const modelTo = lookupModelTo(modelFrom, modelToRef, params, true);
496
+ const relationName = params.as || i8n.camelize(modelTo.pluralModelName, true);
497
+ let fk = params.foreignKey || i8n.camelize(thisClassName + "_id", true);
498
+ let keyThrough = params.keyThrough || i8n.camelize(modelTo.modelName + "_id", true);
499
+ const pkName = params.primaryKey || modelFrom.dataSource.idName(modelFrom.modelName) || "id";
500
+ let discriminator, polymorphic;
501
+ if (params.polymorphic) {
502
+ polymorphic = normalizePolymorphic(params.polymorphic, relationName);
503
+ if (params.invert) {
504
+ polymorphic.invert = true;
505
+ keyThrough = polymorphic.foreignKey;
506
+ }
507
+ discriminator = polymorphic.discriminator;
508
+ if (!params.invert) fk = polymorphic.foreignKey;
509
+ if (!params.through) modelTo.dataSource.defineProperty(modelTo.modelName, discriminator, {
510
+ type: "string",
511
+ index: true
512
+ });
513
+ }
514
+ const definition = new RelationDefinition({
515
+ name: relationName,
516
+ type: RelationTypes.hasMany,
517
+ modelFrom,
518
+ keyFrom: pkName,
519
+ keyTo: fk,
520
+ modelTo,
521
+ multiple: true,
522
+ properties: params.properties,
523
+ scope: params.scope,
524
+ options: params.options,
525
+ keyThrough,
526
+ polymorphic
527
+ });
528
+ definition.modelThrough = params.through;
529
+ modelFrom.relations[relationName] = definition;
530
+ if (!params.through) modelTo.dataSource.defineForeignKey(modelTo.modelName, fk, modelFrom.modelName, pkName);
531
+ const scopeMethods = {
532
+ findById: scopeMethod(definition, "findById"),
533
+ destroy: scopeMethod(definition, "destroyById"),
534
+ updateById: scopeMethod(definition, "updateById"),
535
+ exists: scopeMethod(definition, "exists")
536
+ };
537
+ const findByIdFunc = scopeMethods.findById;
538
+ modelFrom.prototype["__findById__" + relationName] = findByIdFunc;
539
+ const destroyByIdFunc = scopeMethods.destroy;
540
+ modelFrom.prototype["__destroyById__" + relationName] = destroyByIdFunc;
541
+ const updateByIdFunc = scopeMethods.updateById;
542
+ modelFrom.prototype["__updateById__" + relationName] = updateByIdFunc;
543
+ const existsByIdFunc = scopeMethods.exists;
544
+ modelFrom.prototype["__exists__" + relationName] = existsByIdFunc;
545
+ if (definition.modelThrough) {
546
+ scopeMethods.create = scopeMethod(definition, "create");
547
+ scopeMethods.add = scopeMethod(definition, "add");
548
+ scopeMethods.remove = scopeMethod(definition, "remove");
549
+ const addFunc = scopeMethods.add;
550
+ modelFrom.prototype["__link__" + relationName] = addFunc;
551
+ const removeFunc = scopeMethods.remove;
552
+ modelFrom.prototype["__unlink__" + relationName] = removeFunc;
553
+ } else {
554
+ scopeMethods.create = scopeMethod(definition, "create");
555
+ scopeMethods.build = scopeMethod(definition, "build");
556
+ }
557
+ const customMethods = extendScopeMethods(definition, scopeMethods, params.scopeMethods);
558
+ for (let i = 0; i < customMethods.length; i++) {
559
+ const methodName = customMethods[i];
560
+ const method = scopeMethods[methodName];
561
+ if (typeof method === "function" && method.shared === true) modelFrom.prototype["__" + methodName + "__" + relationName] = method;
562
+ }
563
+ defineScope(modelFrom.prototype, params.through || modelTo, relationName, function() {
564
+ const filter = {};
565
+ filter.where = {};
566
+ filter.where[fk] = this[pkName];
567
+ definition.applyScope(this, filter);
568
+ if (definition.modelThrough) {
569
+ let throughRelationName;
570
+ for (const r in definition.modelThrough.relations) {
571
+ const relation = definition.modelThrough.relations[r];
572
+ if (relation.type === RelationTypes.belongsTo && (relation.polymorphic && !relation.modelTo || relation.modelTo === definition.modelTo) && relation.keyFrom === definition.keyThrough) {
573
+ throughRelationName = relation.name;
574
+ break;
575
+ }
576
+ }
577
+ if (definition.polymorphic && definition.polymorphic.invert) {
578
+ filter.collect = definition.polymorphic.selector;
579
+ filter.include = filter.collect;
580
+ } else {
581
+ filter.collect = throughRelationName || i8n.camelize(modelTo.modelName, true);
582
+ filter.include = filter.collect;
583
+ }
584
+ }
585
+ return filter;
586
+ }, scopeMethods, definition.options);
587
+ return definition;
588
+ };
589
+ function scopeMethod(definition, methodName) {
590
+ let relationClass = RelationClasses[definition.type];
591
+ if (definition.type === RelationTypes.hasMany && definition.modelThrough) relationClass = RelationClasses.hasManyThrough;
592
+ const method = function() {
593
+ const relation = new relationClass(definition, this);
594
+ return relation[methodName].apply(relation, arguments);
595
+ };
596
+ const relationMethod = relationClass.prototype[methodName];
597
+ if (relationMethod.shared) sharedMethod(definition, methodName, method, relationMethod);
598
+ return method;
599
+ }
600
+ function sharedMethod(definition, methodName, method, relationMethod) {
601
+ method.shared = true;
602
+ method.accepts = relationMethod.accepts;
603
+ method.returns = relationMethod.returns;
604
+ method.http = relationMethod.http;
605
+ method.description = relationMethod.description;
606
+ }
607
+ /**
608
+ * Find a related item by foreign key
609
+ * @param {*} fkId The foreign key
610
+ * @param {Object} [options] Options
611
+ * @param {Function} cb The callback function
612
+ */
613
+ HasMany.prototype.findById = function(fkId, options, cb) {
614
+ if (typeof options === "function" && cb === void 0) {
615
+ cb = options;
616
+ options = {};
617
+ }
618
+ const modelTo = this.definition.modelTo;
619
+ const modelFrom = this.definition.modelFrom;
620
+ const fk = this.definition.keyTo;
621
+ const pk = this.definition.keyFrom;
622
+ const modelInstance = this.modelInstance;
623
+ const idName = this.definition.modelTo.definition.idName();
624
+ const filter = {};
625
+ filter.where = {};
626
+ filter.where[idName] = fkId;
627
+ filter.where[fk] = modelInstance[pk];
628
+ cb = cb || utils.createPromiseCallback();
629
+ if (filter.where[fk] === void 0) {
630
+ process.nextTick(cb);
631
+ return cb.promise;
632
+ }
633
+ this.definition.applyScope(modelInstance, filter);
634
+ modelTo.findOne(filter, options, function(err, inst) {
635
+ if (err) return cb(err);
636
+ if (!inst) {
637
+ err = new Error(g.f("No instance with {{id}} %s found for %s", fkId, modelTo.modelName));
638
+ err.statusCode = 404;
639
+ return cb(err);
640
+ }
641
+ if (inst[fk] != null && idEquals(inst[fk], modelInstance[pk])) cb(null, inst);
642
+ else {
643
+ err = new Error(g.f("Key mismatch: %s.%s: %s, %s.%s: %s", modelFrom.modelName, pk, modelInstance[pk], modelTo.modelName, fk, inst[fk]));
644
+ err.statusCode = 400;
645
+ cb(err);
646
+ }
647
+ });
648
+ return cb.promise;
649
+ };
650
+ /**
651
+ * Find a related item by foreign key
652
+ * @param {*} fkId The foreign key
653
+ * @param {Object} [options] Options
654
+ * @param {Function} cb The callback function
655
+ */
656
+ HasMany.prototype.exists = function(fkId, options, cb) {
657
+ if (typeof options === "function" && cb === void 0) {
658
+ cb = options;
659
+ options = {};
660
+ }
661
+ const fk = this.definition.keyTo;
662
+ const pk = this.definition.keyFrom;
663
+ const modelInstance = this.modelInstance;
664
+ cb = cb || utils.createPromiseCallback();
665
+ this.findById(fkId, function(err, inst) {
666
+ if (err) return cb(err);
667
+ if (!inst) return cb(null, false);
668
+ if (inst[fk] && inst[fk].toString() === modelInstance[pk].toString()) cb(null, true);
669
+ else cb(null, false);
670
+ });
671
+ return cb.promise;
672
+ };
673
+ /**
674
+ * Update a related item by foreign key
675
+ * @param {*} fkId The foreign key
676
+ * @param {Object} Changes to the data
677
+ * @param {Object} [options] Options
678
+ * @param {Function} cb The callback function
679
+ */
680
+ HasMany.prototype.updateById = function(fkId, data, options, cb) {
681
+ if (typeof options === "function" && cb === void 0) {
682
+ cb = options;
683
+ options = {};
684
+ }
685
+ cb = cb || utils.createPromiseCallback();
686
+ const fk = this.definition.keyTo;
687
+ this.findById(fkId, options, function(err, inst) {
688
+ if (err) return cb && cb(err);
689
+ const fkErr = preventFkOverride(inst, data, fk);
690
+ if (fkErr) return cb(fkErr);
691
+ inst.updateAttributes(data, options, cb);
692
+ });
693
+ return cb.promise;
694
+ };
695
+ /**
696
+ * Delete a related item by foreign key
697
+ * @param {*} fkId The foreign key
698
+ * @param {Object} [options] Options
699
+ * @param {Function} cb The callback function
700
+ */
701
+ HasMany.prototype.destroyById = function(fkId, options, cb) {
702
+ if (typeof options === "function" && cb === void 0) {
703
+ cb = options;
704
+ options = {};
705
+ }
706
+ cb = cb || utils.createPromiseCallback();
707
+ const self = this;
708
+ this.findById(fkId, options, function(err, inst) {
709
+ if (err) return cb(err);
710
+ self.removeFromCache(fkId);
711
+ inst.destroy(options, cb);
712
+ });
713
+ return cb.promise;
714
+ };
715
+ const throughKeys = function(definition) {
716
+ const modelThrough = definition.modelThrough;
717
+ const pk2 = definition.modelTo.definition.idName();
718
+ let fk1, fk2;
719
+ if (typeof definition.polymorphic === "object") {
720
+ fk1 = definition.keyTo;
721
+ if (definition.polymorphic.invert) fk2 = definition.polymorphic.foreignKey;
722
+ else fk2 = definition.keyThrough;
723
+ } else if (definition.modelFrom === definition.modelTo) return findBelongsTo(modelThrough, definition.modelTo, pk2).sort(function(fk1, _fk2) {
724
+ return definition.keyTo === fk1 ? -1 : 1;
725
+ });
726
+ else {
727
+ fk1 = findBelongsTo(modelThrough, definition.modelFrom, definition.keyFrom)[0];
728
+ fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2)[0];
729
+ }
730
+ return [fk1, fk2];
731
+ };
732
+ /**
733
+ * Find a related item by foreign key
734
+ * @param {*} fkId The foreign key value
735
+ * @param {Object} [options] Options
736
+ * @param {Function} cb The callback function
737
+ */
738
+ HasManyThrough.prototype.findById = function(fkId, options, cb) {
739
+ if (typeof options === "function" && cb === void 0) {
740
+ cb = options;
741
+ options = {};
742
+ }
743
+ const self = this;
744
+ const modelTo = this.definition.modelTo;
745
+ const pk = this.definition.keyFrom;
746
+ const modelInstance = this.modelInstance;
747
+ const modelThrough = this.definition.modelThrough;
748
+ cb = cb || utils.createPromiseCallback();
749
+ self.exists(fkId, options, function(err, exists) {
750
+ if (err || !exists) {
751
+ if (!err) {
752
+ err = new Error(g.f("No relation found in %s for (%s.%s,%s.%s)", modelThrough.modelName, self.definition.modelFrom.modelName, modelInstance[pk], modelTo.modelName, fkId));
753
+ err.statusCode = 404;
754
+ }
755
+ return cb(err);
756
+ }
757
+ modelTo.findById(fkId, options, function(err, inst) {
758
+ if (err) return cb(err);
759
+ if (!inst) {
760
+ err = new Error(g.f("No instance with id %s found for %s", fkId, modelTo.modelName));
761
+ err.statusCode = 404;
762
+ return cb(err);
763
+ }
764
+ cb(err, inst);
765
+ });
766
+ });
767
+ return cb.promise;
768
+ };
769
+ /**
770
+ * Delete a related item by foreign key
771
+ * @param {*} fkId The foreign key
772
+ * @param {Object} [options] Options
773
+ * @param {Function} cb The callback function
774
+ */
775
+ HasManyThrough.prototype.destroyById = function(fkId, options, cb) {
776
+ if (typeof options === "function" && cb === void 0) {
777
+ cb = options;
778
+ options = {};
779
+ }
780
+ const self = this;
781
+ const modelTo = this.definition.modelTo;
782
+ const pk = this.definition.keyFrom;
783
+ const modelInstance = this.modelInstance;
784
+ const modelThrough = this.definition.modelThrough;
785
+ cb = cb || utils.createPromiseCallback();
786
+ self.exists(fkId, options, function(err, exists) {
787
+ if (err || !exists) {
788
+ if (!err) {
789
+ err = new Error(g.f("No record found in %s for (%s.%s ,%s.%s)", modelThrough.modelName, self.definition.modelFrom.modelName, modelInstance[pk], modelTo.modelName, fkId));
790
+ err.statusCode = 404;
791
+ }
792
+ return cb(err);
793
+ }
794
+ self.remove(fkId, options, function(err) {
795
+ if (err) return cb(err);
796
+ modelTo.deleteById(fkId, options, cb);
797
+ });
798
+ });
799
+ return cb.promise;
800
+ };
801
+ HasManyThrough.prototype.create = function create(data, options, cb) {
802
+ if (typeof options === "function" && cb === void 0) {
803
+ cb = options;
804
+ options = {};
805
+ }
806
+ const self = this;
807
+ const definition = this.definition;
808
+ const modelTo = definition.modelTo;
809
+ const modelThrough = definition.modelThrough;
810
+ if (typeof data === "function" && !cb) {
811
+ cb = data;
812
+ data = {};
813
+ }
814
+ cb = cb || utils.createPromiseCallback();
815
+ const modelInstance = this.modelInstance;
816
+ modelTo.create(data, options, function(err, to) {
817
+ if (err) return cb(err, to);
818
+ const pk2 = definition.modelTo.definition.idName();
819
+ const keys = throughKeys(definition);
820
+ const fk1 = keys[0];
821
+ const fk2 = keys[1];
822
+ function createRelation(to, next) {
823
+ const d = {}, q = {}, filter = { where: q };
824
+ d[fk1] = q[fk1] = modelInstance[definition.keyFrom];
825
+ d[fk2] = q[fk2] = to[pk2];
826
+ definition.applyProperties(modelInstance, d);
827
+ definition.applyScope(modelInstance, filter);
828
+ modelThrough.findOrCreate(filter, d, options, function(e, _through) {
829
+ if (e) to.destroy(options, function() {
830
+ next(e);
831
+ });
832
+ else {
833
+ self.addToCache(to);
834
+ next(err, to);
835
+ }
836
+ });
837
+ }
838
+ if (!Array.isArray(to)) createRelation(to, cb);
839
+ else Promise.all(to.map(function(item) {
840
+ return callbackToPromise(function(done) {
841
+ createRelation(item, done);
842
+ });
843
+ })).then(function(results) {
844
+ cb(null, results);
845
+ }, cb);
846
+ });
847
+ return cb.promise;
848
+ };
849
+ /**
850
+ * Add the target model instance to the 'hasMany' relation
851
+ * @param {Object|ID} acInst The actual instance or id value
852
+ * @param {Object} [data] Optional data object for the through model to be created
853
+ * @param {Object} [options] Options
854
+ * @param {Function} [cb] Callback function
855
+ */
856
+ HasManyThrough.prototype.add = function(acInst, data, options, cb) {
857
+ if (typeof options === "function" && cb === void 0) {
858
+ cb = options;
859
+ options = {};
860
+ }
861
+ const self = this;
862
+ const definition = this.definition;
863
+ const modelThrough = definition.modelThrough;
864
+ const pk1 = definition.keyFrom;
865
+ if (typeof data === "function") {
866
+ cb = data;
867
+ data = {};
868
+ }
869
+ const query = {};
870
+ data = data || {};
871
+ cb = cb || utils.createPromiseCallback();
872
+ const pk2 = definition.modelTo.definition.idName();
873
+ const keys = throughKeys(definition);
874
+ const fk1 = keys[0];
875
+ const fk2 = keys[1];
876
+ query[fk1] = this.modelInstance[pk1];
877
+ query[fk2] = acInst instanceof definition.modelTo ? acInst[pk2] : acInst;
878
+ const filter = { where: query };
879
+ definition.applyScope(this.modelInstance, filter);
880
+ data[fk1] = this.modelInstance[pk1];
881
+ data[fk2] = acInst instanceof definition.modelTo ? acInst[pk2] : acInst;
882
+ definition.applyProperties(this.modelInstance, data);
883
+ modelThrough.findOrCreate(filter, data, options, function(err, ac) {
884
+ if (!err) {
885
+ if (acInst instanceof definition.modelTo) self.addToCache(acInst);
886
+ }
887
+ cb(err, ac);
888
+ });
889
+ return cb.promise;
890
+ };
891
+ /**
892
+ * Check if the target model instance is related to the 'hasMany' relation
893
+ * @param {Object|ID} acInst The actual instance or id value
894
+ */
895
+ HasManyThrough.prototype.exists = function(acInst, options, cb) {
896
+ if (typeof options === "function" && cb === void 0) {
897
+ cb = options;
898
+ options = {};
899
+ }
900
+ const definition = this.definition;
901
+ const modelThrough = definition.modelThrough;
902
+ const pk1 = definition.keyFrom;
903
+ const query = {};
904
+ const pk2 = definition.modelTo.definition.idName();
905
+ const keys = throughKeys(definition);
906
+ const fk1 = keys[0];
907
+ const fk2 = keys[1];
908
+ query[fk1] = this.modelInstance[pk1];
909
+ query[fk2] = acInst instanceof definition.modelTo ? acInst[pk2] : acInst;
910
+ const filter = { where: query };
911
+ definition.applyScope(this.modelInstance, filter);
912
+ cb = cb || utils.createPromiseCallback();
913
+ modelThrough.count(filter.where, options, function(err, ac) {
914
+ cb(err, ac > 0);
915
+ });
916
+ return cb.promise;
917
+ };
918
+ /**
919
+ * Remove the target model instance from the 'hasMany' relation
920
+ * @param {Object|ID) acInst The actual instance or id value
921
+ */
922
+ HasManyThrough.prototype.remove = function(acInst, options, cb) {
923
+ if (typeof options === "function" && cb === void 0) {
924
+ cb = options;
925
+ options = {};
926
+ }
927
+ const self = this;
928
+ const definition = this.definition;
929
+ const modelThrough = definition.modelThrough;
930
+ const pk1 = definition.keyFrom;
931
+ const query = {};
932
+ const pk2 = definition.modelTo.definition.idName();
933
+ const keys = throughKeys(definition);
934
+ const fk1 = keys[0];
935
+ const fk2 = keys[1];
936
+ query[fk1] = this.modelInstance[pk1];
937
+ query[fk2] = acInst instanceof definition.modelTo ? acInst[pk2] : acInst;
938
+ const filter = { where: query };
939
+ definition.applyScope(this.modelInstance, filter);
940
+ cb = cb || utils.createPromiseCallback();
941
+ modelThrough.deleteAll(filter.where, options, function(err) {
942
+ if (!err) self.removeFromCache(query[fk2]);
943
+ cb(err);
944
+ });
945
+ return cb.promise;
946
+ };
947
+ /**
948
+ * Declare "belongsTo" relation that sets up a one-to-one connection with
949
+ * another model, such that each instance of the declaring model "belongs to"
950
+ * one instance of the other model.
951
+ *
952
+ * For example, if an application includes users and posts, and each post can
953
+ * be written by exactly one user. The following code specifies that `Post` has
954
+ * a reference called `author` to the `User` model via the `userId` property of
955
+ * `Post` as the foreign key.
956
+ * ```
957
+ * Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});
958
+ * ```
959
+ *
960
+ * This optional parameter default value is false, so the related object will
961
+ * be loaded from cache if available.
962
+ *
963
+ * @param {Object|String} modelToRef Reference to Model object to which you are
964
+ * creating the relation: model instance, model name, or name of relation to model.
965
+ * @options {Object} params Configuration parameters; see below.
966
+ * @property {String} as Name of the property in the referring model that
967
+ * corresponds to the foreign key field in the related model.
968
+ * @property {String} foreignKey Name of foreign key property.
969
+ *
970
+ */
971
+ RelationDefinition.belongsTo = function(modelFrom, modelToRef, params) {
972
+ let modelTo, discriminator, polymorphic;
973
+ params = params || {};
974
+ let pkName, relationName, fk;
975
+ if (params.polymorphic) {
976
+ relationName = params.as || (typeof modelToRef === "string" ? modelToRef : null);
977
+ polymorphic = normalizePolymorphic(params.polymorphic, relationName);
978
+ modelTo = null;
979
+ pkName = params.primaryKey || params.idName || "id";
980
+ fk = polymorphic.foreignKey;
981
+ discriminator = polymorphic.discriminator;
982
+ if (polymorphic.idType) modelFrom.dataSource.defineProperty(modelFrom.modelName, fk, {
983
+ type: polymorphic.idType,
984
+ index: true
985
+ });
986
+ else modelFrom.dataSource.defineForeignKey(modelFrom.modelName, fk, modelFrom.modelName, pkName);
987
+ modelFrom.dataSource.defineProperty(modelFrom.modelName, discriminator, {
988
+ type: "string",
989
+ index: true
990
+ });
991
+ } else {
992
+ normalizeRelationAs(params, modelToRef);
993
+ modelTo = lookupModelTo(modelFrom, modelToRef, params);
994
+ pkName = params.primaryKey || modelTo.dataSource.idName(modelTo.modelName) || "id";
995
+ relationName = params.as || i8n.camelize(modelTo.modelName, true);
996
+ fk = params.foreignKey || relationName + "Id";
997
+ modelFrom.dataSource.defineForeignKey(modelFrom.modelName, fk, modelTo.modelName, pkName);
998
+ }
999
+ const definition = modelFrom.relations[relationName] = new RelationDefinition({
1000
+ name: relationName,
1001
+ type: RelationTypes.belongsTo,
1002
+ modelFrom,
1003
+ keyFrom: fk,
1004
+ keyTo: pkName,
1005
+ modelTo,
1006
+ multiple: false,
1007
+ properties: params.properties,
1008
+ scope: params.scope,
1009
+ options: params.options,
1010
+ polymorphic,
1011
+ methods: params.methods
1012
+ });
1013
+ Object.defineProperty(modelFrom.prototype, relationName, {
1014
+ enumerable: true,
1015
+ configurable: true,
1016
+ get: function() {
1017
+ const relation = new BelongsTo(definition, this);
1018
+ const relationMethod = relation.related.bind(relation);
1019
+ relationMethod.get = relation.get.bind(relation);
1020
+ relationMethod.getAsync = function() {
1021
+ deprecated(g.f("BelongsTo method \"getAsync()\" is deprecated, use \"get()\" instead."));
1022
+ return this.get.apply(this, arguments);
1023
+ };
1024
+ relationMethod.update = relation.update.bind(relation);
1025
+ relationMethod.destroy = relation.destroy.bind(relation);
1026
+ if (!polymorphic) {
1027
+ relationMethod.create = relation.create.bind(relation);
1028
+ relationMethod.build = relation.build.bind(relation);
1029
+ relationMethod._targetClass = definition.modelTo.modelName;
1030
+ }
1031
+ bindRelationMethods(relation, relationMethod, definition);
1032
+ return relationMethod;
1033
+ }
1034
+ });
1035
+ const fn = function() {
1036
+ this[relationName].apply(this, arguments);
1037
+ };
1038
+ modelFrom.prototype["__get__" + relationName] = fn;
1039
+ return definition;
1040
+ };
1041
+ BelongsTo.prototype.create = function(targetModelData, options, cb) {
1042
+ if (typeof options === "function" && cb === void 0) {
1043
+ cb = options;
1044
+ options = {};
1045
+ }
1046
+ const self = this;
1047
+ const modelTo = this.definition.modelTo;
1048
+ const fk = this.definition.keyFrom;
1049
+ const pk = this.definition.keyTo;
1050
+ const modelInstance = this.modelInstance;
1051
+ if (typeof targetModelData === "function" && !cb) {
1052
+ cb = targetModelData;
1053
+ targetModelData = {};
1054
+ }
1055
+ cb = cb || utils.createPromiseCallback();
1056
+ this.definition.applyProperties(modelInstance, targetModelData || {});
1057
+ modelTo.create(targetModelData, options, function(err, targetModel) {
1058
+ if (!err) {
1059
+ modelInstance[fk] = targetModel[pk];
1060
+ if (modelInstance.isNewRecord()) {
1061
+ self.resetCache(targetModel);
1062
+ if (cb) cb(err, targetModel);
1063
+ } else modelInstance.save(options, function(err, _inst) {
1064
+ if (cb && err) return cb(err);
1065
+ self.resetCache(targetModel);
1066
+ if (cb) cb(err, targetModel);
1067
+ });
1068
+ } else if (cb) cb(err);
1069
+ });
1070
+ return cb.promise;
1071
+ };
1072
+ BelongsTo.prototype.build = function(targetModelData) {
1073
+ const modelTo = this.definition.modelTo;
1074
+ this.definition.applyProperties(this.modelInstance, targetModelData || {});
1075
+ return new modelTo(targetModelData);
1076
+ };
1077
+ BelongsTo.prototype.update = function(targetModelData, options, cb) {
1078
+ if (typeof options === "function" && cb === void 0) {
1079
+ cb = options;
1080
+ options = {};
1081
+ }
1082
+ cb = cb || utils.createPromiseCallback();
1083
+ const definition = this.definition;
1084
+ const fk = definition.keyTo;
1085
+ this.fetch(options, function(err, inst) {
1086
+ if (inst instanceof ModelBaseClass) {
1087
+ const fkErr = preventFkOverride(inst, targetModelData, fk);
1088
+ if (fkErr) return cb(fkErr);
1089
+ inst.updateAttributes(targetModelData, options, cb);
1090
+ } else cb(new Error(g.f("{{BelongsTo}} relation %s is empty", definition.name)));
1091
+ });
1092
+ return cb.promise;
1093
+ };
1094
+ BelongsTo.prototype.destroy = function(options, cb) {
1095
+ if (typeof options === "function" && cb === void 0) {
1096
+ cb = options;
1097
+ options = {};
1098
+ }
1099
+ const definition = this.definition;
1100
+ const modelInstance = this.modelInstance;
1101
+ const fk = definition.keyFrom;
1102
+ cb = cb || utils.createPromiseCallback();
1103
+ this.fetch(options, function(err, targetModel) {
1104
+ if (targetModel instanceof ModelBaseClass) {
1105
+ modelInstance[fk] = null;
1106
+ modelInstance.save(options, function(err, targetModel) {
1107
+ if (cb && err) return cb(err);
1108
+ if (cb) cb(err, targetModel);
1109
+ });
1110
+ } else cb(new Error(g.f("{{BelongsTo}} relation %s is empty", definition.name)));
1111
+ });
1112
+ return cb.promise;
1113
+ };
1114
+ /**
1115
+ * Define the method for the belongsTo relation itself
1116
+ * It will support one of the following styles:
1117
+ * - order.customer(refresh, options, callback): Load the target model instance asynchronously
1118
+ * - order.customer(customer): Synchronous setter of the target model instance
1119
+ * - order.customer(): Synchronous getter of the target model instance
1120
+ *
1121
+ * @param refresh
1122
+ * @param params
1123
+ * @returns {*}
1124
+ */
1125
+ BelongsTo.prototype.related = function(condOrRefresh, options, cb) {
1126
+ const self = this;
1127
+ const modelFrom = this.definition.modelFrom;
1128
+ let modelTo = this.definition.modelTo;
1129
+ const pk = this.definition.keyTo;
1130
+ const fk = this.definition.keyFrom;
1131
+ const modelInstance = this.modelInstance;
1132
+ let discriminator;
1133
+ let scopeQuery = null;
1134
+ let newValue;
1135
+ if (condOrRefresh instanceof ModelBaseClass && options === void 0 && cb === void 0) {
1136
+ newValue = condOrRefresh;
1137
+ condOrRefresh = false;
1138
+ } else if (typeof condOrRefresh === "function" && options === void 0 && cb === void 0) {
1139
+ cb = condOrRefresh;
1140
+ condOrRefresh = false;
1141
+ } else if (typeof options === "function" && cb === void 0) {
1142
+ cb = options;
1143
+ options = {};
1144
+ }
1145
+ if (!newValue) scopeQuery = condOrRefresh;
1146
+ if (typeof this.definition.polymorphic === "object") discriminator = this.definition.polymorphic.discriminator;
1147
+ let cachedValue;
1148
+ if (!condOrRefresh) cachedValue = self.getCache();
1149
+ if (newValue) {
1150
+ modelInstance[fk] = newValue[pk];
1151
+ if (discriminator) modelInstance[discriminator] = newValue.constructor.modelName;
1152
+ this.definition.applyProperties(modelInstance, newValue);
1153
+ self.resetCache(newValue);
1154
+ } else if (typeof cb === "function") {
1155
+ if (discriminator) {
1156
+ let modelToName = modelInstance[discriminator];
1157
+ if (typeof modelToName !== "string") throw new Error(g.f("{{Polymorphic}} model not found: `%s` not set", discriminator));
1158
+ modelToName = modelToName.toLowerCase();
1159
+ modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName);
1160
+ if (!modelTo) throw new Error(g.f("{{Polymorphic}} model not found: `%s`", modelToName));
1161
+ }
1162
+ if (cachedValue === void 0 || !(cachedValue instanceof ModelBaseClass)) {
1163
+ const query = { where: {} };
1164
+ query.where[pk] = modelInstance[fk];
1165
+ if (query.where[pk] === void 0 || query.where[pk] === null) return process.nextTick(cb);
1166
+ this.definition.applyScope(modelInstance, query);
1167
+ if (scopeQuery) mergeQuery(query, scopeQuery);
1168
+ if (Array.isArray(query.fields) && query.fields.indexOf(pk) === -1) query.fields.push(pk);
1169
+ modelTo.findOne(query, options, function(err, inst) {
1170
+ if (err) return cb(err);
1171
+ if (!inst) return cb(null, null);
1172
+ if (inst[pk] != null && modelInstance[fk] != null && inst[pk].toString() === modelInstance[fk].toString()) {
1173
+ self.resetCache(inst);
1174
+ cb(null, inst);
1175
+ } else {
1176
+ err = new Error(g.f("Key mismatch: %s.%s: %s, %s.%s: %s", self.definition.modelFrom.modelName, fk, modelInstance[fk], modelTo.modelName, pk, inst[pk]));
1177
+ err.statusCode = 400;
1178
+ cb(err);
1179
+ }
1180
+ });
1181
+ return modelInstance[fk];
1182
+ } else {
1183
+ cb(null, cachedValue);
1184
+ return cachedValue;
1185
+ }
1186
+ } else if (condOrRefresh === void 0) return cachedValue;
1187
+ else {
1188
+ modelInstance[fk] = newValue;
1189
+ self.resetCache();
1190
+ }
1191
+ };
1192
+ /**
1193
+ * Define a Promise-based method for the belongsTo relation itself
1194
+ * - order.customer.get(cb): Load the target model instance asynchronously
1195
+ *
1196
+ * @param {Function} cb Callback of the form function (err, inst)
1197
+ * @returns {Promise | Undefined} returns promise if callback is omitted
1198
+ */
1199
+ BelongsTo.prototype.get = function(options, cb) {
1200
+ if (typeof options === "function" && cb === void 0) {
1201
+ cb = options;
1202
+ options = {};
1203
+ }
1204
+ cb = cb || utils.createPromiseCallback();
1205
+ this.related(true, options, cb);
1206
+ return cb.promise;
1207
+ };
1208
+ /**
1209
+ * A hasAndBelongsToMany relation creates a direct many-to-many connection with
1210
+ * another model, with no intervening model. For example, if your application
1211
+ * includes users and groups, with each group having many users and each user
1212
+ * appearing in many groups, you could declare the models this way:
1213
+ * ```
1214
+ * User.hasAndBelongsToMany('groups', {model: Group, foreignKey: 'groupId'});
1215
+ * ```
1216
+ *
1217
+ * @param {Object|String} modelToRef Reference to Model object to which you are
1218
+ * creating the relation: model instance, model name, or name of relation to model.
1219
+ * @options {Object} params Configuration parameters; see below.
1220
+ * @property {String} as Name of the property in the referring model that
1221
+ * corresponds to the foreign key field in the related model.
1222
+ * @property {String} foreignKey Property name of foreign key field.
1223
+ * @property {Object} model Model object
1224
+ */
1225
+ RelationDefinition.hasAndBelongsToMany = function hasAndBelongsToMany(modelFrom, modelToRef, params) {
1226
+ params = params || {};
1227
+ normalizeRelationAs(params, modelToRef);
1228
+ const modelTo = lookupModelTo(modelFrom, modelToRef, params, true);
1229
+ const models = modelFrom.dataSource.modelBuilder.models;
1230
+ if (!params.through) {
1231
+ if (params.polymorphic) throw new Error(g.f("{{Polymorphic}} relations need a through model"));
1232
+ if (params.throughTable) params.through = modelFrom.dataSource.define(params.throughTable);
1233
+ else {
1234
+ const name1 = modelFrom.modelName + modelTo.modelName;
1235
+ const name2 = modelTo.modelName + modelFrom.modelName;
1236
+ params.through = lookupModel(models, name1) || lookupModel(models, name2) || modelFrom.dataSource.define(name1);
1237
+ }
1238
+ }
1239
+ const options = {
1240
+ as: params.as,
1241
+ through: params.through
1242
+ };
1243
+ options.properties = params.properties;
1244
+ options.scope = params.scope;
1245
+ options.options = params.options;
1246
+ if (params.polymorphic) {
1247
+ const relationName = params.as || i8n.camelize(modelTo.pluralModelName, true);
1248
+ const polymorphic = normalizePolymorphic(params.polymorphic, relationName);
1249
+ options.polymorphic = polymorphic;
1250
+ if (typeof params.through.prototype[polymorphic.selector] !== "function") params.through.belongsTo(polymorphic.selector, { polymorphic: true });
1251
+ } else params.through.belongsTo(modelFrom);
1252
+ params.through.belongsTo(modelTo);
1253
+ return this.hasMany(modelFrom, modelTo, options);
1254
+ };
1255
+ /**
1256
+ * A HasOne relation creates a one-to-one connection from modelFrom to modelTo.
1257
+ * This relation indicates that each instance of a model contains or possesses
1258
+ * one instance of another model. For example, each supplier in your application
1259
+ * has only one account.
1260
+ *
1261
+ * @param {Function} modelFrom The declaring model class
1262
+ * @param {Object|String} modelToRef Reference to Model object to which you are
1263
+ * creating the relation: model instance, model name, or name of relation to model.
1264
+ * @options {Object} params Configuration parameters; see below.
1265
+ * @property {String} as Name of the property in the referring model that
1266
+ * corresponds to the foreign key field in the related model.
1267
+ * @property {String} foreignKey Property name of foreign key field.
1268
+ * @property {Object} model Model object
1269
+ */
1270
+ RelationDefinition.hasOne = function(modelFrom, modelToRef, params) {
1271
+ params = params || {};
1272
+ normalizeRelationAs(params, modelToRef);
1273
+ const modelTo = lookupModelTo(modelFrom, modelToRef, params);
1274
+ const pk = params.primaryKey || modelFrom.dataSource.idName(modelFrom.modelName) || "id";
1275
+ const relationName = params.as || i8n.camelize(modelTo.modelName, true);
1276
+ let fk = params.foreignKey || i8n.camelize(modelFrom.modelName + "_id", true);
1277
+ let discriminator, polymorphic;
1278
+ if (params.polymorphic) {
1279
+ polymorphic = normalizePolymorphic(params.polymorphic, relationName);
1280
+ fk = polymorphic.foreignKey;
1281
+ discriminator = polymorphic.discriminator;
1282
+ if (!params.through) modelTo.dataSource.defineProperty(modelTo.modelName, discriminator, {
1283
+ type: "string",
1284
+ index: true
1285
+ });
1286
+ }
1287
+ const definition = modelFrom.relations[relationName] = new RelationDefinition({
1288
+ name: relationName,
1289
+ type: RelationTypes.hasOne,
1290
+ modelFrom,
1291
+ keyFrom: pk,
1292
+ keyTo: fk,
1293
+ modelTo,
1294
+ multiple: false,
1295
+ properties: params.properties,
1296
+ scope: params.scope,
1297
+ options: params.options,
1298
+ polymorphic,
1299
+ methods: params.methods
1300
+ });
1301
+ modelTo.dataSource.defineForeignKey(modelTo.modelName, fk, modelFrom.modelName, pk);
1302
+ Object.defineProperty(modelFrom.prototype, relationName, {
1303
+ enumerable: true,
1304
+ configurable: true,
1305
+ get: function() {
1306
+ const relation = new HasOne(definition, this);
1307
+ const relationMethod = relation.related.bind(relation);
1308
+ relationMethod.get = relation.get.bind(relation);
1309
+ relationMethod.getAsync = function() {
1310
+ deprecated(g.f("HasOne method \"getAsync()\" is deprecated, use \"get()\" instead."));
1311
+ return this.get.apply(this, arguments);
1312
+ };
1313
+ relationMethod.create = relation.create.bind(relation);
1314
+ relationMethod.build = relation.build.bind(relation);
1315
+ relationMethod.update = relation.update.bind(relation);
1316
+ relationMethod.destroy = relation.destroy.bind(relation);
1317
+ relationMethod._targetClass = definition.modelTo.modelName;
1318
+ bindRelationMethods(relation, relationMethod, definition);
1319
+ return relationMethod;
1320
+ }
1321
+ });
1322
+ modelFrom.prototype["__get__" + relationName] = function() {
1323
+ this[relationName].apply(this, arguments);
1324
+ };
1325
+ modelFrom.prototype["__create__" + relationName] = function() {
1326
+ this[relationName].create.apply(this, arguments);
1327
+ };
1328
+ modelFrom.prototype["__update__" + relationName] = function() {
1329
+ this[relationName].update.apply(this, arguments);
1330
+ };
1331
+ modelFrom.prototype["__destroy__" + relationName] = function() {
1332
+ this[relationName].destroy.apply(this, arguments);
1333
+ };
1334
+ return definition;
1335
+ };
1336
+ /**
1337
+ * Create a target model instance
1338
+ * @param {Object} targetModelData The target model data
1339
+ * @callback {Function} [cb] Callback function
1340
+ * @param {String|Object} err Error string or object
1341
+ * @param {Object} The newly created target model instance
1342
+ */
1343
+ HasOne.prototype.create = function(targetModelData, options, cb) {
1344
+ if (typeof options === "function" && cb === void 0) {
1345
+ cb = options;
1346
+ options = {};
1347
+ }
1348
+ const self = this;
1349
+ const modelTo = this.definition.modelTo;
1350
+ const fk = this.definition.keyTo;
1351
+ const pk = this.definition.keyFrom;
1352
+ const modelInstance = this.modelInstance;
1353
+ if (typeof targetModelData === "function" && !cb) {
1354
+ cb = targetModelData;
1355
+ targetModelData = {};
1356
+ }
1357
+ targetModelData = targetModelData || {};
1358
+ cb = cb || utils.createPromiseCallback();
1359
+ targetModelData[fk] = modelInstance[pk];
1360
+ const query = { where: {} };
1361
+ query.where[fk] = targetModelData[fk];
1362
+ this.definition.applyScope(modelInstance, query);
1363
+ this.definition.applyProperties(modelInstance, targetModelData);
1364
+ modelTo.findOrCreate(query, targetModelData, options, function(err, targetModel, created) {
1365
+ if (err) return cb && cb(err);
1366
+ if (created) {
1367
+ self.resetCache(targetModel);
1368
+ if (cb) cb(err, targetModel);
1369
+ } else if (cb) cb(new Error(g.f("{{HasOne}} relation cannot create more than one instance of %s", modelTo.modelName)));
1370
+ });
1371
+ return cb.promise;
1372
+ };
1373
+ HasOne.prototype.update = function(targetModelData, options, cb) {
1374
+ if (typeof options === "function" && cb === void 0) {
1375
+ cb = options;
1376
+ options = {};
1377
+ }
1378
+ cb = cb || utils.createPromiseCallback();
1379
+ const definition = this.definition;
1380
+ const fk = this.definition.keyTo;
1381
+ this.fetch(function(err, targetModel) {
1382
+ if (targetModel instanceof ModelBaseClass) {
1383
+ const fkErr = preventFkOverride(targetModel, targetModelData, fk);
1384
+ if (fkErr) return cb(fkErr);
1385
+ targetModel.updateAttributes(targetModelData, options, cb);
1386
+ } else cb(new Error(g.f("{{HasOne}} relation %s is empty", definition.name)));
1387
+ });
1388
+ return cb.promise;
1389
+ };
1390
+ HasOne.prototype.destroy = function(options, cb) {
1391
+ if (typeof options === "function" && cb === void 0) {
1392
+ cb = options;
1393
+ options = {};
1394
+ }
1395
+ cb = cb || utils.createPromiseCallback();
1396
+ const definition = this.definition;
1397
+ this.fetch(function(err, targetModel) {
1398
+ if (targetModel instanceof ModelBaseClass) targetModel.destroy(options, cb);
1399
+ else cb(new Error(g.f("{{HasOne}} relation %s is empty", definition.name)));
1400
+ });
1401
+ return cb.promise;
1402
+ };
1403
+ /**
1404
+ * Create a target model instance
1405
+ * @param {Object} targetModelData The target model data
1406
+ * @callback {Function} [cb] Callback function
1407
+ * @param {String|Object} err Error string or object
1408
+ * @param {Object} The newly created target model instance
1409
+ */
1410
+ HasMany.prototype.create = function(targetModelData, options, cb) {
1411
+ if (typeof options === "function" && cb === void 0) {
1412
+ cb = options;
1413
+ options = {};
1414
+ }
1415
+ const self = this;
1416
+ const modelTo = this.definition.modelTo;
1417
+ const fk = this.definition.keyTo;
1418
+ const pk = this.definition.keyFrom;
1419
+ const modelInstance = this.modelInstance;
1420
+ if (typeof targetModelData === "function" && !cb) {
1421
+ cb = targetModelData;
1422
+ targetModelData = {};
1423
+ }
1424
+ targetModelData = targetModelData || {};
1425
+ cb = cb || utils.createPromiseCallback();
1426
+ const fkAndProps = function(item) {
1427
+ item[fk] = modelInstance[pk];
1428
+ self.definition.applyProperties(modelInstance, item);
1429
+ };
1430
+ const apply = function(data, fn) {
1431
+ if (Array.isArray(data)) data.forEach(fn);
1432
+ else fn(data);
1433
+ };
1434
+ apply(targetModelData, fkAndProps);
1435
+ modelTo.create(targetModelData, options, function(err, targetModel) {
1436
+ if (!err) {
1437
+ apply(targetModel, self.addToCache.bind(self));
1438
+ if (cb) cb(err, targetModel);
1439
+ } else if (cb) cb(err);
1440
+ });
1441
+ return cb.promise;
1442
+ };
1443
+ /**
1444
+ * Build a target model instance
1445
+ * @param {Object} targetModelData The target model data
1446
+ * @returns {Object} The newly built target model instance
1447
+ */
1448
+ HasMany.prototype.build = HasOne.prototype.build = function(targetModelData) {
1449
+ const modelTo = this.definition.modelTo;
1450
+ const pk = this.definition.keyFrom;
1451
+ const fk = this.definition.keyTo;
1452
+ targetModelData = targetModelData || {};
1453
+ targetModelData[fk] = this.modelInstance[pk];
1454
+ this.definition.applyProperties(this.modelInstance, targetModelData);
1455
+ return new modelTo(targetModelData);
1456
+ };
1457
+ /**
1458
+ * Define the method for the hasOne relation itself
1459
+ * It will support one of the following styles:
1460
+ * - order.customer(refresh, callback): Load the target model instance asynchronously
1461
+ * - order.customer(customer): Synchronous setter of the target model instance
1462
+ * - order.customer(): Synchronous getter of the target model instance
1463
+ *
1464
+ * @param {Boolean} refresh Reload from the data source
1465
+ * @param {Object|Function} params Query parameters
1466
+ * @returns {Object}
1467
+ */
1468
+ HasOne.prototype.related = function(condOrRefresh, options, cb) {
1469
+ const self = this;
1470
+ const modelTo = this.definition.modelTo;
1471
+ const fk = this.definition.keyTo;
1472
+ const pk = this.definition.keyFrom;
1473
+ const definition = this.definition;
1474
+ const modelInstance = this.modelInstance;
1475
+ let newValue;
1476
+ if (condOrRefresh instanceof ModelBaseClass && options === void 0 && cb === void 0) {
1477
+ newValue = condOrRefresh;
1478
+ condOrRefresh = false;
1479
+ } else if (typeof condOrRefresh === "function" && options === void 0 && cb === void 0) {
1480
+ cb = condOrRefresh;
1481
+ condOrRefresh = false;
1482
+ } else if (typeof options === "function" && cb === void 0) {
1483
+ cb = options;
1484
+ options = {};
1485
+ }
1486
+ let cachedValue;
1487
+ if (!condOrRefresh) cachedValue = self.getCache();
1488
+ if (newValue) {
1489
+ newValue[fk] = modelInstance[pk];
1490
+ self.resetCache(newValue);
1491
+ } else if (typeof cb === "function") if (cachedValue === void 0) {
1492
+ const query = { where: {} };
1493
+ query.where[fk] = modelInstance[pk];
1494
+ definition.applyScope(modelInstance, query);
1495
+ modelTo.findOne(query, options, function(err, inst) {
1496
+ if (err) return cb(err);
1497
+ if (!inst) return cb(null, null);
1498
+ if (inst[fk] != null && modelInstance[pk] != null && inst[fk].toString() === modelInstance[pk].toString()) {
1499
+ self.resetCache(inst);
1500
+ cb(null, inst);
1501
+ } else {
1502
+ err = new Error(g.f("Key mismatch: %s.%s: %s, %s.%s: %s", self.definition.modelFrom.modelName, pk, modelInstance[pk], modelTo.modelName, fk, inst[fk]));
1503
+ err.statusCode = 400;
1504
+ cb(err);
1505
+ }
1506
+ });
1507
+ return modelInstance[pk];
1508
+ } else {
1509
+ cb(null, cachedValue);
1510
+ return cachedValue;
1511
+ }
1512
+ else if (condOrRefresh === void 0) return cachedValue;
1513
+ else {
1514
+ newValue[fk] = modelInstance[pk];
1515
+ self.resetCache();
1516
+ }
1517
+ };
1518
+ /**
1519
+ * Define a Promise-based method for the hasOne relation itself
1520
+ * - order.customer.get(cb): Load the target model instance asynchronously
1521
+ *
1522
+ * @param {Function} cb Callback of the form function (err, inst)
1523
+ * @returns {Promise | Undefined} Returns promise if cb is omitted
1524
+ */
1525
+ HasOne.prototype.get = function(options, cb) {
1526
+ if (typeof options === "function" && cb === void 0) {
1527
+ cb = options;
1528
+ options = {};
1529
+ }
1530
+ cb = cb || utils.createPromiseCallback();
1531
+ this.related(true, cb);
1532
+ return cb.promise;
1533
+ };
1534
+ RelationDefinition.embedsOne = function(modelFrom, modelToRef, params) {
1535
+ params = params || {};
1536
+ normalizeRelationAs(params, modelToRef);
1537
+ const modelTo = lookupModelTo(modelFrom, modelToRef, params);
1538
+ modelFrom.modelName;
1539
+ const relationName = params.as || i8n.camelize(modelTo.modelName, true) + "Item";
1540
+ let propertyName = params.property || i8n.camelize(modelTo.modelName, true);
1541
+ const idName = modelTo.dataSource.idName(modelTo.modelName) || "id";
1542
+ if (relationName === propertyName) {
1543
+ propertyName = "_" + propertyName;
1544
+ debug("EmbedsOne property cannot be equal to relation name: forcing property %s for relation %s", propertyName, relationName);
1545
+ }
1546
+ const definition = modelFrom.relations[relationName] = new RelationDefinition({
1547
+ name: relationName,
1548
+ type: RelationTypes.embedsOne,
1549
+ modelFrom,
1550
+ keyFrom: propertyName,
1551
+ keyTo: idName,
1552
+ modelTo,
1553
+ multiple: false,
1554
+ properties: params.properties,
1555
+ scope: params.scope,
1556
+ options: params.options,
1557
+ embed: true,
1558
+ methods: params.methods
1559
+ });
1560
+ const opts = Object.assign(params.options && params.options.property ? params.options.property : {}, { type: modelTo });
1561
+ if (params.default === true) opts.default = function() {
1562
+ return new modelTo();
1563
+ };
1564
+ else if (typeof params.default === "object") opts.default = (function(def) {
1565
+ return function() {
1566
+ return new modelTo(def);
1567
+ };
1568
+ })(params.default);
1569
+ modelFrom.dataSource.defineProperty(modelFrom.modelName, propertyName, opts);
1570
+ if (definition.options.validate !== false) modelFrom.validate(relationName, function(err) {
1571
+ const inst = this[propertyName];
1572
+ if (inst instanceof modelTo) {
1573
+ if (!inst.isValid()) {
1574
+ const first = Object.keys(inst.errors)[0];
1575
+ const msg = "is invalid: `" + first + "` " + inst.errors[first];
1576
+ this.errors.add(relationName, msg, "invalid");
1577
+ err(false);
1578
+ }
1579
+ }
1580
+ });
1581
+ Object.defineProperty(modelFrom.prototype, relationName, {
1582
+ enumerable: true,
1583
+ configurable: true,
1584
+ get: function() {
1585
+ const relation = new EmbedsOne(definition, this);
1586
+ const relationMethod = relation.related.bind(relation);
1587
+ relationMethod.create = relation.create.bind(relation);
1588
+ relationMethod.build = relation.build.bind(relation);
1589
+ relationMethod.update = relation.update.bind(relation);
1590
+ relationMethod.destroy = relation.destroy.bind(relation);
1591
+ relationMethod.value = relation.embeddedValue.bind(relation);
1592
+ relationMethod._targetClass = definition.modelTo.modelName;
1593
+ bindRelationMethods(relation, relationMethod, definition);
1594
+ return relationMethod;
1595
+ }
1596
+ });
1597
+ modelFrom.prototype["__get__" + relationName] = function() {
1598
+ this[relationName].apply(this, arguments);
1599
+ };
1600
+ modelFrom.prototype["__create__" + relationName] = function() {
1601
+ this[relationName].create.apply(this, arguments);
1602
+ };
1603
+ modelFrom.prototype["__update__" + relationName] = function() {
1604
+ this[relationName].update.apply(this, arguments);
1605
+ };
1606
+ modelFrom.prototype["__destroy__" + relationName] = function() {
1607
+ this[relationName].destroy.apply(this, arguments);
1608
+ };
1609
+ return definition;
1610
+ };
1611
+ EmbedsOne.prototype.related = function(condOrRefresh, options, cb) {
1612
+ const modelTo = this.definition.modelTo;
1613
+ const modelInstance = this.modelInstance;
1614
+ const propertyName = this.definition.keyFrom;
1615
+ let newValue;
1616
+ if (condOrRefresh instanceof ModelBaseClass && options === void 0 && cb === void 0) {
1617
+ newValue = condOrRefresh;
1618
+ condOrRefresh = false;
1619
+ } else if (typeof condOrRefresh === "function" && options === void 0 && cb === void 0) {
1620
+ cb = condOrRefresh;
1621
+ condOrRefresh = false;
1622
+ } else if (typeof options === "function" && cb === void 0) {
1623
+ cb = options;
1624
+ options = {};
1625
+ }
1626
+ if (newValue) {
1627
+ if (newValue instanceof modelTo) {
1628
+ this.definition.applyProperties(modelInstance, newValue);
1629
+ modelInstance.setAttribute(propertyName, newValue);
1630
+ }
1631
+ return;
1632
+ }
1633
+ const embeddedInstance = this.embeddedValue();
1634
+ if (embeddedInstance) embeddedInstance.__persisted = true;
1635
+ if (typeof cb === "function") process.nextTick(function() {
1636
+ cb(null, embeddedInstance);
1637
+ });
1638
+ else if (condOrRefresh === void 0) return embeddedInstance;
1639
+ };
1640
+ EmbedsOne.prototype.prepareEmbeddedInstance = function(inst) {
1641
+ if (inst && inst.triggerParent !== "function") {
1642
+ const self = this;
1643
+ const propertyName = this.definition.keyFrom;
1644
+ const modelInstance = this.modelInstance;
1645
+ if (this.definition.options.persistent) inst.__persisted = !!inst[this.definition.keyTo];
1646
+ else inst.__persisted = true;
1647
+ inst.triggerParent = function(actionName, callback) {
1648
+ if (actionName === "save") {
1649
+ const embeddedValue = self.embeddedValue();
1650
+ modelInstance.updateAttribute(propertyName, embeddedValue, function(err, modelInst) {
1651
+ callback(err, err ? null : modelInst);
1652
+ });
1653
+ } else if (actionName === "destroy") {
1654
+ modelInstance.unsetAttribute(propertyName, true);
1655
+ modelInstance.save(function(err, modelInst) {
1656
+ callback(err, modelInst);
1657
+ });
1658
+ } else process.nextTick(callback);
1659
+ };
1660
+ const originalTrigger = inst.trigger;
1661
+ inst.trigger = function(actionName, work, data, callback) {
1662
+ if (typeof work === "function") {
1663
+ const originalWork = work;
1664
+ work = function(next) {
1665
+ originalWork.call(this, function(done) {
1666
+ inst.triggerParent(actionName, function(_err, _inst) {
1667
+ next(done);
1668
+ });
1669
+ });
1670
+ };
1671
+ }
1672
+ originalTrigger.call(this, actionName, work, data, callback);
1673
+ };
1674
+ }
1675
+ };
1676
+ EmbedsOne.prototype.embeddedValue = function(modelInstance) {
1677
+ modelInstance = modelInstance || this.modelInstance;
1678
+ const embeddedValue = modelInstance[this.definition.keyFrom];
1679
+ this.prepareEmbeddedInstance(embeddedValue);
1680
+ return embeddedValue;
1681
+ };
1682
+ EmbedsOne.prototype.create = function(targetModelData, options, cb) {
1683
+ if (typeof options === "function" && cb === void 0) {
1684
+ cb = options;
1685
+ options = {};
1686
+ }
1687
+ const modelTo = this.definition.modelTo;
1688
+ const propertyName = this.definition.keyFrom;
1689
+ const modelInstance = this.modelInstance;
1690
+ if (typeof targetModelData === "function" && !cb) {
1691
+ cb = targetModelData;
1692
+ targetModelData = {};
1693
+ }
1694
+ targetModelData = targetModelData || {};
1695
+ cb = cb || utils.createPromiseCallback();
1696
+ const inst = this.callScopeMethod("build", targetModelData);
1697
+ const updateEmbedded = function(callback) {
1698
+ if (modelInstance.isNewRecord()) {
1699
+ modelInstance.setAttribute(propertyName, inst);
1700
+ modelInstance.save(options, function(err) {
1701
+ callback(err, err ? null : inst);
1702
+ });
1703
+ } else modelInstance.updateAttribute(propertyName, inst, options, function(err) {
1704
+ callback(err, err ? null : inst);
1705
+ });
1706
+ };
1707
+ if (this.definition.options.persistent) inst.save(options, function(err) {
1708
+ if (err) return cb(err, inst);
1709
+ updateEmbedded(cb);
1710
+ });
1711
+ else {
1712
+ const context = {
1713
+ Model: modelTo,
1714
+ instance: inst,
1715
+ options: options || {},
1716
+ hookState: {}
1717
+ };
1718
+ modelTo.notifyObserversOf("before save", context, function(err) {
1719
+ if (err) return process.nextTick(function() {
1720
+ cb(err);
1721
+ });
1722
+ err = inst.isValid() ? null : new ValidationError(inst);
1723
+ if (err) process.nextTick(function() {
1724
+ cb(err);
1725
+ });
1726
+ else updateEmbedded(function(err, inst) {
1727
+ if (err) return cb(err);
1728
+ context.instance = inst;
1729
+ modelTo.notifyObserversOf("after save", context, function(err) {
1730
+ cb(err, err ? null : inst);
1731
+ });
1732
+ });
1733
+ });
1734
+ }
1735
+ return cb.promise;
1736
+ };
1737
+ EmbedsOne.prototype.build = function(targetModelData) {
1738
+ const modelTo = this.definition.modelTo;
1739
+ const modelInstance = this.modelInstance;
1740
+ const propertyName = this.definition.keyFrom;
1741
+ const forceId = this.definition.options.forceId;
1742
+ const persistent = this.definition.options.persistent;
1743
+ const connector = modelTo.dataSource.connector;
1744
+ targetModelData = targetModelData || {};
1745
+ this.definition.applyProperties(modelInstance, targetModelData);
1746
+ const pk = this.definition.keyTo;
1747
+ const pkProp = modelTo.definition.properties[pk];
1748
+ let assignId = forceId || targetModelData[pk] === void 0;
1749
+ assignId = assignId && !persistent && pkProp && pkProp.generated;
1750
+ if (assignId && typeof connector.generateId === "function") {
1751
+ const id = connector.generateId(modelTo.modelName, targetModelData, pk);
1752
+ targetModelData[pk] = id;
1753
+ }
1754
+ const embeddedInstance = new modelTo(targetModelData);
1755
+ modelInstance[propertyName] = embeddedInstance;
1756
+ this.prepareEmbeddedInstance(embeddedInstance);
1757
+ return embeddedInstance;
1758
+ };
1759
+ EmbedsOne.prototype.update = function(targetModelData, options, cb) {
1760
+ if (typeof options === "function" && cb === void 0) {
1761
+ cb = options;
1762
+ options = {};
1763
+ }
1764
+ const modelTo = this.definition.modelTo;
1765
+ const modelInstance = this.modelInstance;
1766
+ const propertyName = this.definition.keyFrom;
1767
+ const data = targetModelData instanceof ModelBaseClass ? targetModelData.toObject() : targetModelData;
1768
+ const embeddedInstance = this.embeddedValue();
1769
+ if (embeddedInstance instanceof modelTo) {
1770
+ cb = cb || utils.createPromiseCallback();
1771
+ const hookState = {};
1772
+ let context = {
1773
+ Model: modelTo,
1774
+ currentInstance: embeddedInstance,
1775
+ data,
1776
+ options: options || {},
1777
+ hookState
1778
+ };
1779
+ modelTo.notifyObserversOf("before save", context, function(err) {
1780
+ if (err) return cb(err);
1781
+ embeddedInstance.setAttributes(context.data);
1782
+ if (!embeddedInstance.isValid()) return cb(new ValidationError(embeddedInstance));
1783
+ modelInstance.save(function(err, inst) {
1784
+ if (err) return cb(err);
1785
+ context = {
1786
+ Model: modelTo,
1787
+ instance: inst ? inst[propertyName] : embeddedInstance,
1788
+ options: options || {},
1789
+ hookState
1790
+ };
1791
+ modelTo.notifyObserversOf("after save", context, function(err) {
1792
+ cb(err, context.instance);
1793
+ });
1794
+ });
1795
+ });
1796
+ } else if (!embeddedInstance && cb) return this.callScopeMethod("create", data, cb);
1797
+ else if (!embeddedInstance) return this.callScopeMethod("build", data);
1798
+ return cb.promise;
1799
+ };
1800
+ EmbedsOne.prototype.destroy = function(options, cb) {
1801
+ if (typeof options === "function" && cb === void 0) {
1802
+ cb = options;
1803
+ options = {};
1804
+ }
1805
+ cb = cb || utils.createPromiseCallback();
1806
+ const modelTo = this.definition.modelTo;
1807
+ const modelInstance = this.modelInstance;
1808
+ const propertyName = this.definition.keyFrom;
1809
+ const embeddedInstance = modelInstance[propertyName];
1810
+ if (!embeddedInstance) {
1811
+ cb();
1812
+ return cb.promise;
1813
+ }
1814
+ modelInstance.unsetAttribute(propertyName, true);
1815
+ const context = {
1816
+ Model: modelTo,
1817
+ instance: embeddedInstance,
1818
+ options: options || {},
1819
+ hookState: {}
1820
+ };
1821
+ modelTo.notifyObserversOf("before delete", context, function(err) {
1822
+ if (err) return cb(err);
1823
+ modelInstance.save(function(err, _result) {
1824
+ if (err) return cb(err);
1825
+ modelTo.notifyObserversOf("after delete", context, cb);
1826
+ });
1827
+ });
1828
+ return cb.promise;
1829
+ };
1830
+ RelationDefinition.embedsMany = function embedsMany(modelFrom, modelToRef, params) {
1831
+ params = params || {};
1832
+ normalizeRelationAs(params, modelToRef);
1833
+ const modelTo = lookupModelTo(modelFrom, modelToRef, params, true);
1834
+ modelFrom.modelName;
1835
+ const relationName = params.as || i8n.camelize(modelTo.modelName, true) + "List";
1836
+ let propertyName = params.property || i8n.camelize(modelTo.pluralModelName, true);
1837
+ const idName = modelTo.dataSource.idName(modelTo.modelName) || "id";
1838
+ if (relationName === propertyName) {
1839
+ propertyName = "_" + propertyName;
1840
+ debug("EmbedsMany property cannot be equal to relation name: forcing property %s for relation %s", propertyName, relationName);
1841
+ }
1842
+ const definition = modelFrom.relations[relationName] = new RelationDefinition({
1843
+ name: relationName,
1844
+ type: RelationTypes.embedsMany,
1845
+ modelFrom,
1846
+ keyFrom: propertyName,
1847
+ keyTo: idName,
1848
+ modelTo,
1849
+ multiple: true,
1850
+ properties: params.properties,
1851
+ scope: params.scope,
1852
+ options: params.options,
1853
+ embed: true
1854
+ });
1855
+ const opts = Object.assign(params.options && params.options.property ? params.options.property : {}, params.options && params.options.omitDefaultEmbeddedItem ? { type: [modelTo] } : {
1856
+ type: [modelTo],
1857
+ default: function() {
1858
+ return [];
1859
+ }
1860
+ });
1861
+ modelFrom.dataSource.defineProperty(modelFrom.modelName, propertyName, opts);
1862
+ if (typeof modelTo.dataSource.connector.generateId !== "function") modelFrom.validate(propertyName, function(err) {
1863
+ const self = this;
1864
+ const embeddedList = this[propertyName] || [];
1865
+ let hasErrors = false;
1866
+ embeddedList.forEach(function(item, idx) {
1867
+ if (item instanceof modelTo && item[idName] == void 0) {
1868
+ hasErrors = true;
1869
+ let msg = "contains invalid item at index `" + idx + "`:";
1870
+ msg += " `" + idName + "` is blank";
1871
+ self.errors.add(propertyName, msg, "invalid");
1872
+ }
1873
+ });
1874
+ if (hasErrors) err(false);
1875
+ });
1876
+ if (!params.polymorphic) modelFrom.validate(propertyName, function(err) {
1877
+ if (idsHaveDuplicates((this[propertyName] || []).map(function(m) {
1878
+ return m[idName] && m[idName].toString();
1879
+ }))) {
1880
+ this.errors.add(propertyName, "contains duplicate `" + idName + "`", "uniqueness");
1881
+ err(false);
1882
+ }
1883
+ }, { code: "uniqueness" });
1884
+ if (definition.options.validate !== false) modelFrom.validate(propertyName, function(err) {
1885
+ const self = this;
1886
+ const embeddedList = this[propertyName] || [];
1887
+ let hasErrors = false;
1888
+ embeddedList.forEach(function(item, idx) {
1889
+ if (item instanceof modelTo) {
1890
+ if (!item.isValid()) {
1891
+ hasErrors = true;
1892
+ const id = item[idName];
1893
+ const first = Object.keys(item.errors)[0];
1894
+ let msg = id ? "contains invalid item: `" + id + "`" : "contains invalid item at index `" + idx + "`";
1895
+ msg += " (`" + first + "` " + item.errors[first] + ")";
1896
+ self.errors.add(propertyName, msg, "invalid");
1897
+ }
1898
+ } else {
1899
+ hasErrors = true;
1900
+ self.errors.add(propertyName, "contains invalid item", "invalid");
1901
+ }
1902
+ });
1903
+ if (hasErrors) err(false);
1904
+ });
1905
+ const scopeMethods = {
1906
+ findById: scopeMethod(definition, "findById"),
1907
+ destroy: scopeMethod(definition, "destroyById"),
1908
+ updateById: scopeMethod(definition, "updateById"),
1909
+ exists: scopeMethod(definition, "exists"),
1910
+ add: scopeMethod(definition, "add"),
1911
+ remove: scopeMethod(definition, "remove"),
1912
+ get: scopeMethod(definition, "get"),
1913
+ set: scopeMethod(definition, "set"),
1914
+ unset: scopeMethod(definition, "unset"),
1915
+ at: scopeMethod(definition, "at"),
1916
+ value: scopeMethod(definition, "embeddedValue")
1917
+ };
1918
+ const findByIdFunc = scopeMethods.findById;
1919
+ modelFrom.prototype["__findById__" + relationName] = findByIdFunc;
1920
+ const destroyByIdFunc = scopeMethods.destroy;
1921
+ modelFrom.prototype["__destroyById__" + relationName] = destroyByIdFunc;
1922
+ const updateByIdFunc = scopeMethods.updateById;
1923
+ modelFrom.prototype["__updateById__" + relationName] = updateByIdFunc;
1924
+ const addFunc = scopeMethods.add;
1925
+ modelFrom.prototype["__link__" + relationName] = addFunc;
1926
+ const removeFunc = scopeMethods.remove;
1927
+ modelFrom.prototype["__unlink__" + relationName] = removeFunc;
1928
+ scopeMethods.create = scopeMethod(definition, "create");
1929
+ scopeMethods.build = scopeMethod(definition, "build");
1930
+ scopeMethods.related = scopeMethod(definition, "related");
1931
+ if (!definition.options.persistent) scopeMethods.destroyAll = scopeMethod(definition, "destroyAll");
1932
+ const customMethods = extendScopeMethods(definition, scopeMethods, params.scopeMethods);
1933
+ for (let i = 0; i < customMethods.length; i++) {
1934
+ const methodName = customMethods[i];
1935
+ const method = scopeMethods[methodName];
1936
+ if (typeof method === "function" && method.shared === true) modelFrom.prototype["__" + methodName + "__" + relationName] = method;
1937
+ }
1938
+ const scopeDefinition = defineScope(modelFrom.prototype, modelTo, relationName, function() {
1939
+ return {};
1940
+ }, scopeMethods, definition.options);
1941
+ scopeDefinition.related = scopeMethods.related;
1942
+ return definition;
1943
+ };
1944
+ EmbedsMany.prototype.prepareEmbeddedInstance = function(inst) {
1945
+ if (inst && inst.triggerParent !== "function") {
1946
+ const self = this;
1947
+ const propertyName = this.definition.keyFrom;
1948
+ const modelInstance = this.modelInstance;
1949
+ if (this.definition.options.persistent) inst.__persisted = !!inst[this.definition.keyTo];
1950
+ else inst.__persisted = true;
1951
+ inst.triggerParent = function(actionName, callback) {
1952
+ if (actionName === "save" || actionName === "destroy") {
1953
+ const embeddedList = self.embeddedList();
1954
+ if (actionName === "destroy") {
1955
+ const index = embeddedList.indexOf(inst);
1956
+ if (index > -1) embeddedList.splice(index, 1);
1957
+ }
1958
+ modelInstance.updateAttribute(propertyName, embeddedList, function(err, modelInst) {
1959
+ callback(err, err ? null : modelInst);
1960
+ });
1961
+ } else process.nextTick(callback);
1962
+ };
1963
+ const originalTrigger = inst.trigger;
1964
+ inst.trigger = function(actionName, work, data, callback) {
1965
+ if (typeof work === "function") {
1966
+ const originalWork = work;
1967
+ work = function(next) {
1968
+ originalWork.call(this, function(done) {
1969
+ inst.triggerParent(actionName, function(_err, _inst) {
1970
+ next(done);
1971
+ });
1972
+ });
1973
+ };
1974
+ }
1975
+ originalTrigger.call(this, actionName, work, data, callback);
1976
+ };
1977
+ }
1978
+ };
1979
+ EmbedsMany.prototype.embeddedList = EmbedsMany.prototype.embeddedValue = function(modelInstance) {
1980
+ modelInstance = modelInstance || this.modelInstance;
1981
+ const embeddedList = modelInstance[this.definition.keyFrom] || [];
1982
+ embeddedList.forEach(this.prepareEmbeddedInstance.bind(this));
1983
+ return embeddedList;
1984
+ };
1985
+ EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, options, cb) {
1986
+ const modelTo = this.definition.modelTo;
1987
+ this.modelInstance;
1988
+ let actualCond = {};
1989
+ if (typeof condOrRefresh === "function" && options === void 0 && cb === void 0) {
1990
+ cb = condOrRefresh;
1991
+ condOrRefresh = false;
1992
+ } else if (typeof options === "function" && cb === void 0) {
1993
+ cb = options;
1994
+ options = {};
1995
+ }
1996
+ if (typeof condOrRefresh === "object") actualCond = condOrRefresh;
1997
+ let embeddedList = this.embeddedList(receiver);
1998
+ this.definition.applyScope(receiver, actualCond);
1999
+ const params = mergeQuery(actualCond, scopeParams);
2000
+ if (params.where && Object.keys(params.where).length > 0) embeddedList = embeddedList ? embeddedList.filter(applyFilter(params)) : embeddedList;
2001
+ const returnRelated = function(list) {
2002
+ if (params.include) modelTo.include(list, params.include, options, cb);
2003
+ else process.nextTick(function() {
2004
+ cb(null, list);
2005
+ });
2006
+ };
2007
+ returnRelated(embeddedList);
2008
+ };
2009
+ EmbedsMany.prototype.findById = function(fkId, options, cb) {
2010
+ if (typeof options === "function" && cb === void 0) {
2011
+ cb = options;
2012
+ options = {};
2013
+ }
2014
+ const pk = this.definition.keyTo;
2015
+ const modelTo = this.definition.modelTo;
2016
+ this.modelInstance;
2017
+ const embeddedList = this.embeddedList();
2018
+ const find = function(id) {
2019
+ for (let i = 0; i < embeddedList.length; i++) {
2020
+ const item = embeddedList[i];
2021
+ if (idEquals(item[pk], id)) return item;
2022
+ }
2023
+ return null;
2024
+ };
2025
+ let item = find(fkId.toString());
2026
+ item = item instanceof modelTo ? item : null;
2027
+ if (typeof cb === "function") process.nextTick(function() {
2028
+ cb(null, item);
2029
+ });
2030
+ return item;
2031
+ };
2032
+ EmbedsMany.prototype.exists = function(fkId, options, cb) {
2033
+ if (typeof options === "function" && cb === void 0) {
2034
+ cb = options;
2035
+ options = {};
2036
+ }
2037
+ const modelTo = this.definition.modelTo;
2038
+ return this.findById(fkId, options, function(err, inst) {
2039
+ if (cb) cb(err, inst instanceof modelTo);
2040
+ }) instanceof modelTo;
2041
+ };
2042
+ EmbedsMany.prototype.updateById = function(fkId, data, options, cb) {
2043
+ if (typeof options === "function" && cb === void 0) {
2044
+ cb = options;
2045
+ options = {};
2046
+ }
2047
+ if (typeof data === "function") {
2048
+ cb = data;
2049
+ data = {};
2050
+ }
2051
+ options = options || {};
2052
+ const modelTo = this.definition.modelTo;
2053
+ const propertyName = this.definition.keyFrom;
2054
+ const modelInstance = this.modelInstance;
2055
+ const embeddedList = this.embeddedList();
2056
+ const inst = this.findById(fkId);
2057
+ if (inst instanceof modelTo) {
2058
+ const hookState = {};
2059
+ let context = {
2060
+ Model: modelTo,
2061
+ currentInstance: inst,
2062
+ data,
2063
+ options,
2064
+ hookState
2065
+ };
2066
+ modelTo.notifyObserversOf("before save", context, function(err) {
2067
+ if (err) return cb && cb(err);
2068
+ inst.setAttributes(data);
2069
+ err = inst.isValid() ? null : new ValidationError(inst);
2070
+ if (err && typeof cb === "function") return process.nextTick(function() {
2071
+ cb(err, inst);
2072
+ });
2073
+ context = {
2074
+ Model: modelTo,
2075
+ instance: inst,
2076
+ options,
2077
+ hookState
2078
+ };
2079
+ if (typeof cb === "function") modelInstance.updateAttribute(propertyName, embeddedList, options, function(err) {
2080
+ if (err) return cb(err, inst);
2081
+ modelTo.notifyObserversOf("after save", context, function(err) {
2082
+ cb(err, inst);
2083
+ });
2084
+ });
2085
+ else modelTo.notifyObserversOf("after save", context, function(err) {
2086
+ if (!err) return;
2087
+ debug("Unhandled error in \"after save\" hooks: %s", err.stack || err);
2088
+ });
2089
+ });
2090
+ } else if (typeof cb === "function") process.nextTick(function() {
2091
+ cb(null, null);
2092
+ });
2093
+ return inst;
2094
+ };
2095
+ EmbedsMany.prototype.destroyById = function(fkId, options, cb) {
2096
+ if (typeof options === "function" && cb === void 0) {
2097
+ cb = options;
2098
+ options = {};
2099
+ }
2100
+ const modelTo = this.definition.modelTo;
2101
+ const propertyName = this.definition.keyFrom;
2102
+ const modelInstance = this.modelInstance;
2103
+ const embeddedList = this.embeddedList();
2104
+ const inst = fkId instanceof modelTo ? fkId : this.findById(fkId);
2105
+ if (inst instanceof modelTo) {
2106
+ const context = {
2107
+ Model: modelTo,
2108
+ instance: inst,
2109
+ options: options || {},
2110
+ hookState: {}
2111
+ };
2112
+ modelTo.notifyObserversOf("before delete", context, function(err) {
2113
+ if (err) return cb(err);
2114
+ const index = embeddedList.indexOf(inst);
2115
+ if (index > -1) embeddedList.splice(index, 1);
2116
+ if (typeof cb !== "function") return;
2117
+ modelInstance.updateAttribute(propertyName, embeddedList, context.options, function(err) {
2118
+ if (err) return cb(err);
2119
+ modelTo.notifyObserversOf("after delete", context, function(err) {
2120
+ cb(err);
2121
+ });
2122
+ });
2123
+ });
2124
+ } else if (typeof cb === "function") process.nextTick(cb);
2125
+ return inst;
2126
+ };
2127
+ EmbedsMany.prototype.destroyAll = function(where, options, cb) {
2128
+ if (typeof options === "function" && cb === void 0) {
2129
+ cb = options;
2130
+ options = {};
2131
+ } else if (typeof where === "function" && options === void 0 && cb === void 0) {
2132
+ cb = where;
2133
+ where = {};
2134
+ }
2135
+ const propertyName = this.definition.keyFrom;
2136
+ const modelInstance = this.modelInstance;
2137
+ let embeddedList = this.embeddedList();
2138
+ if (where && Object.keys(where).length > 0) {
2139
+ const filter = applyFilter({ where });
2140
+ const reject = function(v) {
2141
+ return !filter(v);
2142
+ };
2143
+ embeddedList = embeddedList ? embeddedList.filter(reject) : embeddedList;
2144
+ } else embeddedList = [];
2145
+ if (typeof cb === "function") modelInstance.updateAttribute(propertyName, embeddedList, options || {}, function(err) {
2146
+ cb(err);
2147
+ });
2148
+ else modelInstance.setAttribute(propertyName, embeddedList, options || {});
2149
+ };
2150
+ EmbedsMany.prototype.get = EmbedsMany.prototype.findById;
2151
+ EmbedsMany.prototype.set = EmbedsMany.prototype.updateById;
2152
+ EmbedsMany.prototype.unset = EmbedsMany.prototype.destroyById;
2153
+ EmbedsMany.prototype.at = function(index, cb) {
2154
+ const modelTo = this.definition.modelTo;
2155
+ this.modelInstance;
2156
+ let item = this.embeddedList()[parseInt(index)];
2157
+ item = item instanceof modelTo ? item : null;
2158
+ if (typeof cb === "function") process.nextTick(function() {
2159
+ cb(null, item);
2160
+ });
2161
+ return item;
2162
+ };
2163
+ EmbedsMany.prototype.create = function(targetModelData, options, cb) {
2164
+ this.definition.keyTo;
2165
+ const modelTo = this.definition.modelTo;
2166
+ const propertyName = this.definition.keyFrom;
2167
+ const modelInstance = this.modelInstance;
2168
+ if (typeof options === "function" && cb === void 0) {
2169
+ cb = options;
2170
+ options = {};
2171
+ }
2172
+ if (typeof targetModelData === "function" && !cb) {
2173
+ cb = targetModelData;
2174
+ targetModelData = {};
2175
+ }
2176
+ targetModelData = targetModelData || {};
2177
+ cb = cb || utils.createPromiseCallback();
2178
+ const inst = this.callScopeMethod("build", targetModelData);
2179
+ const embeddedList = this.embeddedList();
2180
+ const updateEmbedded = function(callback) {
2181
+ if (modelInstance.isNewRecord()) {
2182
+ modelInstance.setAttribute(propertyName, embeddedList);
2183
+ modelInstance.save(options, function(err) {
2184
+ callback(err, err ? null : inst);
2185
+ });
2186
+ } else modelInstance.updateAttribute(propertyName, embeddedList, options, function(err) {
2187
+ callback(err, err ? null : inst);
2188
+ });
2189
+ };
2190
+ if (this.definition.options.persistent) inst.save(function(err) {
2191
+ if (err) return cb(err, inst);
2192
+ updateEmbedded(cb);
2193
+ });
2194
+ else {
2195
+ const err = inst.isValid() ? null : new ValidationError(inst);
2196
+ if (err) process.nextTick(function() {
2197
+ cb(err);
2198
+ });
2199
+ else {
2200
+ const context = {
2201
+ Model: modelTo,
2202
+ instance: inst,
2203
+ options: options || {},
2204
+ hookState: {}
2205
+ };
2206
+ modelTo.notifyObserversOf("before save", context, function(err) {
2207
+ if (err) return cb(err);
2208
+ updateEmbedded(function(err, inst) {
2209
+ if (err) return cb(err, null);
2210
+ modelTo.notifyObserversOf("after save", context, function(err) {
2211
+ cb(err, err ? null : inst);
2212
+ });
2213
+ });
2214
+ });
2215
+ }
2216
+ }
2217
+ return cb.promise;
2218
+ };
2219
+ EmbedsMany.prototype.build = function(targetModelData) {
2220
+ const modelTo = this.definition.modelTo;
2221
+ const modelInstance = this.modelInstance;
2222
+ const forceId = this.definition.options.forceId;
2223
+ const persistent = this.definition.options.persistent;
2224
+ const propertyName = this.definition.keyFrom;
2225
+ const connector = modelTo.dataSource.connector;
2226
+ const pk = this.definition.keyTo;
2227
+ const pkProp = modelTo.definition.properties[pk];
2228
+ const pkType = pkProp && pkProp.type;
2229
+ const embeddedList = this.embeddedList();
2230
+ targetModelData = targetModelData || {};
2231
+ let assignId = forceId || targetModelData[pk] === void 0;
2232
+ assignId = assignId && !persistent;
2233
+ if (assignId && pkType === Number) {
2234
+ const ids = embeddedList.map(function(m) {
2235
+ return typeof m[pk] === "number" ? m[pk] : 0;
2236
+ });
2237
+ if (ids.length > 0) targetModelData[pk] = Math.max.apply(null, ids) + 1;
2238
+ else targetModelData[pk] = 1;
2239
+ } else if (assignId && typeof connector.generateId === "function") {
2240
+ const id = connector.generateId(modelTo.modelName, targetModelData, pk);
2241
+ targetModelData[pk] = id;
2242
+ }
2243
+ this.definition.applyProperties(modelInstance, targetModelData);
2244
+ const inst = new modelTo(targetModelData);
2245
+ if (this.definition.options.prepend) {
2246
+ embeddedList.unshift(inst);
2247
+ modelInstance[propertyName] = embeddedList;
2248
+ } else {
2249
+ embeddedList.push(inst);
2250
+ modelInstance[propertyName] = embeddedList;
2251
+ }
2252
+ this.prepareEmbeddedInstance(inst);
2253
+ return inst;
2254
+ };
2255
+ /**
2256
+ * Add the target model instance to the 'embedsMany' relation
2257
+ * @param {Object|ID} acInst The actual instance or id value
2258
+ */
2259
+ EmbedsMany.prototype.add = function(acInst, data, options, cb) {
2260
+ if (typeof options === "function" && cb === void 0) {
2261
+ cb = options;
2262
+ options = {};
2263
+ } else if (typeof data === "function" && options === void 0 && cb === void 0) {
2264
+ cb = data;
2265
+ data = {};
2266
+ }
2267
+ cb = cb || utils.createPromiseCallback();
2268
+ const self = this;
2269
+ const definition = this.definition;
2270
+ const modelTo = this.definition.modelTo;
2271
+ const modelInstance = this.modelInstance;
2272
+ const defOpts = definition.options;
2273
+ const belongsTo = defOpts.belongsTo && modelTo.relations[defOpts.belongsTo];
2274
+ if (!belongsTo) throw new Error("Invalid reference: " + defOpts.belongsTo || "(none)");
2275
+ const fk2 = belongsTo.keyTo;
2276
+ const pk2 = belongsTo.modelTo.definition.idName() || "id";
2277
+ const query = {};
2278
+ query[fk2] = acInst instanceof belongsTo.modelTo ? acInst[pk2] : acInst;
2279
+ const filter = { where: query };
2280
+ belongsTo.applyScope(modelInstance, filter);
2281
+ belongsTo.modelTo.findOne(filter, options, function(err, ref) {
2282
+ if (ref instanceof belongsTo.modelTo) {
2283
+ const inst = self.build(data || {});
2284
+ inst[defOpts.belongsTo](ref);
2285
+ modelInstance.save(function(err) {
2286
+ cb(err, err ? null : inst);
2287
+ });
2288
+ } else cb(null, null);
2289
+ });
2290
+ return cb.promise;
2291
+ };
2292
+ /**
2293
+ * Remove the target model instance from the 'embedsMany' relation
2294
+ * @param {Object|ID) acInst The actual instance or id value
2295
+ */
2296
+ EmbedsMany.prototype.remove = function(acInst, options, cb) {
2297
+ if (typeof options === "function" && cb === void 0) {
2298
+ cb = options;
2299
+ options = {};
2300
+ }
2301
+ const self = this;
2302
+ const definition = this.definition;
2303
+ const modelTo = this.definition.modelTo;
2304
+ const modelInstance = this.modelInstance;
2305
+ const defOpts = definition.options;
2306
+ const belongsTo = defOpts.belongsTo && modelTo.relations[defOpts.belongsTo];
2307
+ if (!belongsTo) throw new Error("Invalid reference: " + defOpts.belongsTo || "(none)");
2308
+ const fk2 = belongsTo.keyTo;
2309
+ const pk2 = belongsTo.modelTo.definition.idName() || "id";
2310
+ const query = {};
2311
+ query[fk2] = acInst instanceof belongsTo.modelTo ? acInst[pk2] : acInst;
2312
+ const filter = { where: query };
2313
+ belongsTo.applyScope(modelInstance, filter);
2314
+ cb = cb || utils.createPromiseCallback();
2315
+ modelInstance[definition.name](filter, options, function(err, items) {
2316
+ if (err) return cb(err);
2317
+ items.forEach(function(item) {
2318
+ self.unset(item);
2319
+ });
2320
+ modelInstance.save(options, function(err) {
2321
+ cb(err);
2322
+ });
2323
+ });
2324
+ return cb.promise;
2325
+ };
2326
+ RelationDefinition.referencesMany = function referencesMany(modelFrom, modelToRef, params) {
2327
+ params = params || {};
2328
+ normalizeRelationAs(params, modelToRef);
2329
+ const modelTo = lookupModelTo(modelFrom, modelToRef, params, true);
2330
+ modelFrom.modelName;
2331
+ const relationName = params.as || i8n.camelize(modelTo.pluralModelName, true);
2332
+ const fk = params.foreignKey || i8n.camelize(modelTo.modelName + "_ids", true);
2333
+ const idName = modelTo.dataSource.idName(modelTo.modelName) || "id";
2334
+ const idType = modelTo.definition.properties[idName].type;
2335
+ const definition = modelFrom.relations[relationName] = new RelationDefinition({
2336
+ name: relationName,
2337
+ type: RelationTypes.referencesMany,
2338
+ modelFrom,
2339
+ keyFrom: fk,
2340
+ keyTo: idName,
2341
+ modelTo,
2342
+ multiple: true,
2343
+ properties: params.properties,
2344
+ scope: params.scope,
2345
+ options: params.options
2346
+ });
2347
+ modelFrom.dataSource.defineProperty(modelFrom.modelName, fk, {
2348
+ type: [idType],
2349
+ default: function() {
2350
+ return [];
2351
+ }
2352
+ });
2353
+ modelFrom.validate(relationName, function(err) {
2354
+ if (idsHaveDuplicates(this[fk] || [])) {
2355
+ const msg = "contains duplicate `" + modelTo.modelName + "` instance";
2356
+ this.errors.add(relationName, msg, "uniqueness");
2357
+ err(false);
2358
+ }
2359
+ }, { code: "uniqueness" });
2360
+ const scopeMethods = {
2361
+ findById: scopeMethod(definition, "findById"),
2362
+ destroy: scopeMethod(definition, "destroyById"),
2363
+ updateById: scopeMethod(definition, "updateById"),
2364
+ exists: scopeMethod(definition, "exists"),
2365
+ add: scopeMethod(definition, "add"),
2366
+ remove: scopeMethod(definition, "remove"),
2367
+ at: scopeMethod(definition, "at")
2368
+ };
2369
+ const findByIdFunc = scopeMethods.findById;
2370
+ modelFrom.prototype["__findById__" + relationName] = findByIdFunc;
2371
+ const destroyByIdFunc = scopeMethods.destroy;
2372
+ modelFrom.prototype["__destroyById__" + relationName] = destroyByIdFunc;
2373
+ const updateByIdFunc = scopeMethods.updateById;
2374
+ modelFrom.prototype["__updateById__" + relationName] = updateByIdFunc;
2375
+ const addFunc = scopeMethods.add;
2376
+ modelFrom.prototype["__link__" + relationName] = addFunc;
2377
+ const removeFunc = scopeMethods.remove;
2378
+ modelFrom.prototype["__unlink__" + relationName] = removeFunc;
2379
+ scopeMethods.create = scopeMethod(definition, "create");
2380
+ scopeMethods.build = scopeMethod(definition, "build");
2381
+ scopeMethods.related = scopeMethod(definition, "related");
2382
+ const customMethods = extendScopeMethods(definition, scopeMethods, params.scopeMethods);
2383
+ for (let i = 0; i < customMethods.length; i++) {
2384
+ const methodName = customMethods[i];
2385
+ const method = scopeMethods[methodName];
2386
+ if (typeof method === "function" && method.shared === true) modelFrom.prototype["__" + methodName + "__" + relationName] = method;
2387
+ }
2388
+ const scopeDefinition = defineScope(modelFrom.prototype, modelTo, relationName, function() {
2389
+ return {};
2390
+ }, scopeMethods, definition.options);
2391
+ scopeDefinition.related = scopeMethods.related;
2392
+ return definition;
2393
+ };
2394
+ ReferencesMany.prototype.related = function(receiver, scopeParams, condOrRefresh, options, cb) {
2395
+ const fk = this.definition.keyFrom;
2396
+ const modelTo = this.definition.modelTo;
2397
+ this.definition.name;
2398
+ const modelInstance = this.modelInstance;
2399
+ const self = receiver;
2400
+ let actualCond = {};
2401
+ if (typeof condOrRefresh === "function" && options === void 0 && cb === void 0) {
2402
+ cb = condOrRefresh;
2403
+ condOrRefresh = void 0;
2404
+ } else if (typeof options === "function" && cb === void 0) {
2405
+ cb = options;
2406
+ options = {};
2407
+ if (typeof condOrRefresh === "boolean") condOrRefresh = {};
2408
+ }
2409
+ actualCond = condOrRefresh || {};
2410
+ const ids = self[fk] || [];
2411
+ this.definition.applyScope(modelInstance, actualCond);
2412
+ const params = mergeQuery(actualCond, scopeParams);
2413
+ return modelTo.findByIds(ids, params, options, cb);
2414
+ };
2415
+ ReferencesMany.prototype.findById = function(fkId, options, cb) {
2416
+ if (typeof options === "function" && cb === void 0) {
2417
+ cb = options;
2418
+ options = {};
2419
+ }
2420
+ const modelTo = this.definition.modelTo;
2421
+ const modelFrom = this.definition.modelFrom;
2422
+ this.definition.name;
2423
+ const modelInstance = this.modelInstance;
2424
+ const pk = this.definition.keyTo;
2425
+ const fk = this.definition.keyFrom;
2426
+ if (typeof fkId === "object") fkId = fkId.toString();
2427
+ const ids = modelInstance[fk] || [];
2428
+ const filter = {};
2429
+ this.definition.applyScope(modelInstance, filter);
2430
+ cb = cb || utils.createPromiseCallback();
2431
+ modelTo.findByIds([fkId], filter, options, function(err, instances) {
2432
+ if (err) return cb(err);
2433
+ const inst = instances[0];
2434
+ if (!inst) {
2435
+ err = new Error(g.f("No instance with {{id}} %s found for %s", fkId, modelTo.modelName));
2436
+ err.statusCode = 404;
2437
+ return cb(err);
2438
+ }
2439
+ if (utils.findIndexOf(ids, inst[pk], idEquals) > -1) cb(null, inst);
2440
+ else {
2441
+ err = new Error(g.f("Key mismatch: %s.%s: %s, %s.%s: %s", modelFrom.modelName, fk, modelInstance[fk], modelTo.modelName, pk, inst[pk]));
2442
+ err.statusCode = 400;
2443
+ cb(err);
2444
+ }
2445
+ });
2446
+ return cb.promise;
2447
+ };
2448
+ ReferencesMany.prototype.exists = function(fkId, options, cb) {
2449
+ if (typeof options === "function" && cb === void 0) {
2450
+ cb = options;
2451
+ options = {};
2452
+ }
2453
+ const fk = this.definition.keyFrom;
2454
+ const ids = this.modelInstance[fk] || [];
2455
+ cb = cb || utils.createPromiseCallback();
2456
+ process.nextTick(function() {
2457
+ cb(null, utils.findIndexOf(ids, fkId, idEquals) > -1);
2458
+ });
2459
+ return cb.promise;
2460
+ };
2461
+ ReferencesMany.prototype.updateById = function(fkId, data, options, cb) {
2462
+ if (typeof options === "function" && cb === void 0) {
2463
+ cb = options;
2464
+ options = {};
2465
+ } else if (typeof data === "function" && options === void 0 && cb === void 0) {
2466
+ cb = data;
2467
+ data = {};
2468
+ }
2469
+ cb = cb || utils.createPromiseCallback();
2470
+ this.findById(fkId, options, function(err, inst) {
2471
+ if (err) return cb(err);
2472
+ inst.updateAttributes(data, options, cb);
2473
+ });
2474
+ return cb.promise;
2475
+ };
2476
+ ReferencesMany.prototype.destroyById = function(fkId, options, cb) {
2477
+ if (typeof options === "function" && cb === void 0) {
2478
+ cb = options;
2479
+ options = {};
2480
+ }
2481
+ const self = this;
2482
+ cb = cb || utils.createPromiseCallback();
2483
+ this.findById(fkId, function(err, inst) {
2484
+ if (err) return cb(err);
2485
+ self.remove(inst, function(_err, _ids) {
2486
+ inst.destroy(cb);
2487
+ });
2488
+ });
2489
+ return cb.promise;
2490
+ };
2491
+ ReferencesMany.prototype.at = function(index, options, cb) {
2492
+ if (typeof options === "function" && cb === void 0) {
2493
+ cb = options;
2494
+ options = {};
2495
+ }
2496
+ const fk = this.definition.keyFrom;
2497
+ const ids = this.modelInstance[fk] || [];
2498
+ cb = cb || utils.createPromiseCallback();
2499
+ this.findById(ids[index], options, cb);
2500
+ return cb.promise;
2501
+ };
2502
+ ReferencesMany.prototype.create = function(targetModelData, options, cb) {
2503
+ if (typeof options === "function" && cb === void 0) {
2504
+ cb = options;
2505
+ options = {};
2506
+ }
2507
+ const definition = this.definition;
2508
+ this.definition.modelTo;
2509
+ this.definition.name;
2510
+ const modelInstance = this.modelInstance;
2511
+ const pk = this.definition.keyTo;
2512
+ const fk = this.definition.keyFrom;
2513
+ if (typeof targetModelData === "function" && !cb) {
2514
+ cb = targetModelData;
2515
+ targetModelData = {};
2516
+ }
2517
+ targetModelData = targetModelData || {};
2518
+ cb = cb || utils.createPromiseCallback();
2519
+ const ids = modelInstance[fk] || [];
2520
+ this.callScopeMethod("build", targetModelData).save(options, function(err, inst) {
2521
+ if (err) return cb(err, inst);
2522
+ let id = inst[pk];
2523
+ if (typeof id === "object") id = id.toString();
2524
+ if (definition.options.prepend) ids.unshift(id);
2525
+ else ids.push(id);
2526
+ modelInstance.updateAttribute(fk, ids, options, function(err, _modelInst) {
2527
+ cb(err, inst);
2528
+ });
2529
+ });
2530
+ return cb.promise;
2531
+ };
2532
+ ReferencesMany.prototype.build = function(targetModelData) {
2533
+ const modelTo = this.definition.modelTo;
2534
+ targetModelData = targetModelData || {};
2535
+ this.definition.applyProperties(this.modelInstance, targetModelData);
2536
+ return new modelTo(targetModelData);
2537
+ };
2538
+ /**
2539
+ * Add the target model instance to the 'embedsMany' relation
2540
+ * @param {Object|ID} acInst The actual instance or id value
2541
+ */
2542
+ ReferencesMany.prototype.add = function(acInst, options, cb) {
2543
+ if (typeof options === "function" && cb === void 0) {
2544
+ cb = options;
2545
+ options = {};
2546
+ }
2547
+ const definition = this.definition;
2548
+ const modelTo = this.definition.modelTo;
2549
+ const modelInstance = this.modelInstance;
2550
+ const pk = this.definition.keyTo;
2551
+ const fk = this.definition.keyFrom;
2552
+ const insert = function(inst, done) {
2553
+ let id = inst[pk];
2554
+ if (typeof id === "object") id = id.toString();
2555
+ const ids = modelInstance[fk] || [];
2556
+ if (definition.options.prepend) ids.unshift(id);
2557
+ else ids.push(id);
2558
+ modelInstance.updateAttribute(fk, ids, options, function(err) {
2559
+ done(err, err ? null : inst);
2560
+ });
2561
+ };
2562
+ cb = cb || utils.createPromiseCallback();
2563
+ if (acInst instanceof modelTo) insert(acInst, cb);
2564
+ else {
2565
+ const filter = { where: {} };
2566
+ filter.where[pk] = acInst;
2567
+ definition.applyScope(modelInstance, filter);
2568
+ modelTo.findOne(filter, options, function(err, inst) {
2569
+ if (err || !inst) return cb(err, null);
2570
+ insert(inst, cb);
2571
+ });
2572
+ }
2573
+ return cb.promise;
2574
+ };
2575
+ /**
2576
+ * Remove the target model instance from the 'embedsMany' relation
2577
+ * @param {Object|ID) acInst The actual instance or id value
2578
+ */
2579
+ ReferencesMany.prototype.remove = function(acInst, options, cb) {
2580
+ if (typeof options === "function" && cb === void 0) {
2581
+ cb = options;
2582
+ options = {};
2583
+ }
2584
+ const definition = this.definition;
2585
+ const modelInstance = this.modelInstance;
2586
+ const pk = this.definition.keyTo;
2587
+ const fk = this.definition.keyFrom;
2588
+ const ids = modelInstance[fk] || [];
2589
+ const id = acInst instanceof definition.modelTo ? acInst[pk] : acInst;
2590
+ cb = cb || utils.createPromiseCallback();
2591
+ const index = utils.findIndexOf(ids, id, idEquals);
2592
+ if (index > -1) {
2593
+ ids.splice(index, 1);
2594
+ modelInstance.updateAttribute(fk, ids, options, function(err, inst) {
2595
+ cb(err, inst[fk] || []);
2596
+ });
2597
+ } else process.nextTick(function() {
2598
+ cb(null, ids);
2599
+ });
2600
+ return cb.promise;
2601
+ };
2602
+ }));
2603
+ //#endregion
2604
+ module.exports = require_relation_definition();