@vsaas/loopback-datasource-juggler 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +25 -0
- package/NOTICE +23 -0
- package/README.md +74 -0
- package/dist/_virtual/_rolldown/runtime.js +4 -0
- package/dist/index.js +26 -0
- package/dist/lib/browser.depd.js +13 -0
- package/dist/lib/case-utils.js +21 -0
- package/dist/lib/connectors/kv-memory.js +158 -0
- package/dist/lib/connectors/memory.js +810 -0
- package/dist/lib/connectors/transient.js +126 -0
- package/dist/lib/dao.js +2445 -0
- package/dist/lib/datasource.js +2215 -0
- package/dist/lib/date-string.js +87 -0
- package/dist/lib/geo.js +244 -0
- package/dist/lib/globalize.js +33 -0
- package/dist/lib/hooks.js +79 -0
- package/dist/lib/id-utils.js +66 -0
- package/dist/lib/include.js +795 -0
- package/dist/lib/include_utils.js +104 -0
- package/dist/lib/introspection.js +37 -0
- package/dist/lib/jutil.js +65 -0
- package/dist/lib/kvao/delete-all.js +57 -0
- package/dist/lib/kvao/delete.js +43 -0
- package/dist/lib/kvao/expire.js +35 -0
- package/dist/lib/kvao/get.js +34 -0
- package/dist/lib/kvao/index.js +28 -0
- package/dist/lib/kvao/iterate-keys.js +38 -0
- package/dist/lib/kvao/keys.js +55 -0
- package/dist/lib/kvao/set.js +39 -0
- package/dist/lib/kvao/ttl.js +35 -0
- package/dist/lib/list.js +101 -0
- package/dist/lib/mixins.js +58 -0
- package/dist/lib/model-builder.js +608 -0
- package/dist/lib/model-definition.js +231 -0
- package/dist/lib/model-utils.js +368 -0
- package/dist/lib/model.js +586 -0
- package/dist/lib/observer.js +235 -0
- package/dist/lib/relation-definition.js +2604 -0
- package/dist/lib/relations.js +587 -0
- package/dist/lib/scope.js +392 -0
- package/dist/lib/transaction.js +183 -0
- package/dist/lib/types.js +58 -0
- package/dist/lib/utils.js +625 -0
- package/dist/lib/validations.js +742 -0
- package/dist/package.js +93 -0
- package/package.json +85 -0
- package/types/common.d.ts +28 -0
- package/types/connector.d.ts +52 -0
- package/types/datasource.d.ts +324 -0
- package/types/date-string.d.ts +21 -0
- package/types/inclusion-mixin.d.ts +44 -0
- package/types/index.d.ts +36 -0
- package/types/kv-model.d.ts +201 -0
- package/types/model.d.ts +368 -0
- package/types/observer-mixin.d.ts +174 -0
- package/types/persisted-model.d.ts +505 -0
- package/types/query.d.ts +108 -0
- package/types/relation-mixin.d.ts +577 -0
- package/types/relation.d.ts +301 -0
- package/types/scope.d.ts +92 -0
- package/types/transaction-mixin.d.ts +47 -0
- package/types/types.d.ts +65 -0
- package/types/validation-mixin.d.ts +287 -0
package/dist/lib/dao.js
ADDED
|
@@ -0,0 +1,2445 @@
|
|
|
1
|
+
const require_runtime = require("../_virtual/_rolldown/runtime.js");
|
|
2
|
+
const require_lib_globalize = require("./globalize.js");
|
|
3
|
+
const require_lib_jutil = require("./jutil.js");
|
|
4
|
+
const require_lib_geo = require("./geo.js");
|
|
5
|
+
const require_lib_utils = require("./utils.js");
|
|
6
|
+
const require_lib_list = require("./list.js");
|
|
7
|
+
require("./model-utils.js");
|
|
8
|
+
const require_lib_validations = require("./validations.js");
|
|
9
|
+
require("./model.js");
|
|
10
|
+
const require_lib_scope = require("./scope.js");
|
|
11
|
+
const require_lib_connectors_memory = require("./connectors/memory.js");
|
|
12
|
+
const require_lib_relations = require("./relations.js");
|
|
13
|
+
const require_lib_include = require("./include.js");
|
|
14
|
+
const require_lib_transaction = require("./transaction.js");
|
|
15
|
+
//#region src/lib/dao.ts
|
|
16
|
+
var require_dao = /* @__PURE__ */ require_runtime.__commonJSMin(((exports, module) => {
|
|
17
|
+
/*!
|
|
18
|
+
* Module exports class Model
|
|
19
|
+
*/
|
|
20
|
+
module.exports = DataAccessObject;
|
|
21
|
+
/*!
|
|
22
|
+
* Module dependencies
|
|
23
|
+
*/
|
|
24
|
+
const g = require_lib_globalize();
|
|
25
|
+
const jutil = require_lib_jutil;
|
|
26
|
+
const ValidationError = require_lib_validations.ValidationError;
|
|
27
|
+
const Relation = require_lib_relations;
|
|
28
|
+
const Inclusion = require_lib_include;
|
|
29
|
+
const List = require_lib_list;
|
|
30
|
+
const geo = require_lib_geo;
|
|
31
|
+
const Memory = require_lib_connectors_memory.Memory;
|
|
32
|
+
const utils = require_lib_utils;
|
|
33
|
+
utils.fieldsToArray;
|
|
34
|
+
utils.sanitizeQuery;
|
|
35
|
+
const setScopeValuesFromWhere = utils.setScopeValuesFromWhere;
|
|
36
|
+
const idEquals = utils.idEquals;
|
|
37
|
+
const mergeQuery = utils.mergeQuery;
|
|
38
|
+
const assert = require("assert");
|
|
39
|
+
require("debug")("loopback:dao");
|
|
40
|
+
/**
|
|
41
|
+
* Base class for all persistent objects.
|
|
42
|
+
* Provides a common API to access any database connector.
|
|
43
|
+
* This class describes only abstract behavior. Refer to the specific connector for additional details.
|
|
44
|
+
*
|
|
45
|
+
* `DataAccessObject` mixes `Inclusion` classes methods.
|
|
46
|
+
* @class DataAccessObject
|
|
47
|
+
*/
|
|
48
|
+
function DataAccessObject() {
|
|
49
|
+
if (DataAccessObject._mixins) {
|
|
50
|
+
const self = this;
|
|
51
|
+
const args = arguments;
|
|
52
|
+
DataAccessObject._mixins.forEach(function(m) {
|
|
53
|
+
m.call(self, args);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function idName(m) {
|
|
58
|
+
return m.definition.idName() || "id";
|
|
59
|
+
}
|
|
60
|
+
function getIdValue(m, data) {
|
|
61
|
+
return data && data[idName(m)];
|
|
62
|
+
}
|
|
63
|
+
function copyData(from, to) {
|
|
64
|
+
for (const key in from) to[key] = from[key];
|
|
65
|
+
}
|
|
66
|
+
function once(fn) {
|
|
67
|
+
let called = false;
|
|
68
|
+
return function() {
|
|
69
|
+
if (called) return;
|
|
70
|
+
called = true;
|
|
71
|
+
fn.apply(this, arguments);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function mapParallel(items, iterator, callback) {
|
|
75
|
+
if (!items || items.length === 0) return callback(null, []);
|
|
76
|
+
const results = Array.from({ length: items.length });
|
|
77
|
+
let pending = items.length;
|
|
78
|
+
let finished = false;
|
|
79
|
+
for (let i = 0; i < items.length; i++) {
|
|
80
|
+
const item = items[i];
|
|
81
|
+
const index = i;
|
|
82
|
+
const done = once(function(err, result) {
|
|
83
|
+
if (finished) return;
|
|
84
|
+
if (err) {
|
|
85
|
+
finished = true;
|
|
86
|
+
return callback(err);
|
|
87
|
+
}
|
|
88
|
+
results[index] = result;
|
|
89
|
+
pending--;
|
|
90
|
+
if (pending === 0) callback(null, results);
|
|
91
|
+
});
|
|
92
|
+
try {
|
|
93
|
+
iterator(item, done);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
done(err);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function convertSubsetOfPropertiesByType(inst, data) {
|
|
100
|
+
const typedData = {};
|
|
101
|
+
for (const key in data) {
|
|
102
|
+
typedData[key] = inst[key];
|
|
103
|
+
if (typeof typedData[key] === "object" && typedData[key] !== null && typeof typedData[key].toObject === "function") typedData[key] = typedData[key].toObject();
|
|
104
|
+
}
|
|
105
|
+
return typedData;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Apply strict check for model's data.
|
|
109
|
+
* Notice: Please note this method modifies `inst` when `strict` is `validate`.
|
|
110
|
+
*/
|
|
111
|
+
function applyStrictCheck(model, strict, data, inst, cb) {
|
|
112
|
+
const props = model.definition.properties;
|
|
113
|
+
const keys = Object.keys(data);
|
|
114
|
+
const result = {};
|
|
115
|
+
for (let i = 0; i < keys.length; i++) {
|
|
116
|
+
const key = keys[i];
|
|
117
|
+
if (props[key]) result[key] = data[key];
|
|
118
|
+
else if (strict && strict !== "filter") inst.__unknownProperties.push(key);
|
|
119
|
+
}
|
|
120
|
+
cb(null, result);
|
|
121
|
+
}
|
|
122
|
+
function byIdQuery(m, id) {
|
|
123
|
+
const pk = idName(m);
|
|
124
|
+
const query = { where: {} };
|
|
125
|
+
query.where[pk] = id;
|
|
126
|
+
return query;
|
|
127
|
+
}
|
|
128
|
+
function isWhereByGivenId(Model, where, idValue) {
|
|
129
|
+
const keys = Object.keys(where);
|
|
130
|
+
if (keys.length != 1) return false;
|
|
131
|
+
const pk = idName(Model);
|
|
132
|
+
if (keys[0] !== pk) return false;
|
|
133
|
+
return where[pk] === idValue;
|
|
134
|
+
}
|
|
135
|
+
function errorModelNotFound(idValue) {
|
|
136
|
+
const msg = g.f("Could not update attributes. {{Object}} with {{id}} %s does not exist!", idValue);
|
|
137
|
+
const error = new Error(msg);
|
|
138
|
+
error.statusCode = error.status = 404;
|
|
139
|
+
return error;
|
|
140
|
+
}
|
|
141
|
+
function invokeConnectorMethod(connector, method, Model, args, options, cb) {
|
|
142
|
+
const dataSource = Model.getDataSource();
|
|
143
|
+
const opts = dataSource.isTransaction && !options.transaction ? Object.assign(options, { transaction: dataSource.currentTransaction }) : options;
|
|
144
|
+
const optionsSupported = connector[method].length >= args.length + 3;
|
|
145
|
+
const transaction = opts.transaction;
|
|
146
|
+
if (transaction) {
|
|
147
|
+
if (!optionsSupported) return process.nextTick(function() {
|
|
148
|
+
cb(new Error(g.f("The connector does not support {{method}} within a transaction", method)));
|
|
149
|
+
});
|
|
150
|
+
if (transaction.ensureActive && !transaction.ensureActive(cb)) return;
|
|
151
|
+
}
|
|
152
|
+
const modelName = Model.modelName;
|
|
153
|
+
let fullArgs;
|
|
154
|
+
if (!optionsSupported && method === "count") fullArgs = [
|
|
155
|
+
modelName,
|
|
156
|
+
cb,
|
|
157
|
+
args[0]
|
|
158
|
+
];
|
|
159
|
+
else {
|
|
160
|
+
fullArgs = [modelName].concat(args);
|
|
161
|
+
if (optionsSupported) fullArgs.push(opts);
|
|
162
|
+
fullArgs.push(cb);
|
|
163
|
+
}
|
|
164
|
+
connector[method].apply(connector, fullArgs);
|
|
165
|
+
}
|
|
166
|
+
DataAccessObject._forDB = function(data) {
|
|
167
|
+
if (!(this.getDataSource().isRelational && this.getDataSource().isRelational())) return data;
|
|
168
|
+
const res = {};
|
|
169
|
+
for (const propName in data) {
|
|
170
|
+
const type = this.getPropertyType(propName);
|
|
171
|
+
const value = data[propName];
|
|
172
|
+
if (value !== null && (type === "JSON" || type === "Any" || type === "Object" || value instanceof Array)) res[propName] = JSON.stringify(value);
|
|
173
|
+
else res[propName] = value;
|
|
174
|
+
}
|
|
175
|
+
return res;
|
|
176
|
+
};
|
|
177
|
+
DataAccessObject.defaultScope = function(target, inst) {
|
|
178
|
+
let scope = this.definition.settings.scope;
|
|
179
|
+
if (typeof scope === "function") scope = this.definition.settings.scope.call(this, target, inst);
|
|
180
|
+
return scope;
|
|
181
|
+
};
|
|
182
|
+
DataAccessObject.applyScope = function(query, inst) {
|
|
183
|
+
const scope = this.defaultScope(query, inst) || {};
|
|
184
|
+
if (typeof scope === "object") mergeQuery(query, scope || {}, this.definition.settings.scope);
|
|
185
|
+
};
|
|
186
|
+
DataAccessObject.applyProperties = function(data, inst) {
|
|
187
|
+
let properties = this.definition.settings.properties;
|
|
188
|
+
properties = properties || this.definition.settings.attributes;
|
|
189
|
+
if (typeof properties === "object") Object.assign(data, properties);
|
|
190
|
+
else if (typeof properties === "function") Object.assign(data, properties.call(this, data, inst) || {});
|
|
191
|
+
else if (properties !== false) {
|
|
192
|
+
const scope = this.defaultScope(data, inst) || {};
|
|
193
|
+
if (typeof scope.where === "object") setScopeValuesFromWhere(data, scope.where, this);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
DataAccessObject.lookupModel = function(_data) {
|
|
197
|
+
return this;
|
|
198
|
+
};
|
|
199
|
+
/**
|
|
200
|
+
* Get the connector instance for the given model class
|
|
201
|
+
* @returns {Connector} The connector instance
|
|
202
|
+
*/
|
|
203
|
+
DataAccessObject.getConnector = function() {
|
|
204
|
+
return this.getDataSource().connector;
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Create an instance of Model with given data and save to the attached data source. Callback is optional.
|
|
208
|
+
* Example:
|
|
209
|
+
*```js
|
|
210
|
+
* User.create({first: 'Joe', last: 'Bob'}, function(err, user) {
|
|
211
|
+
* console.log(user instanceof User); // true
|
|
212
|
+
* });
|
|
213
|
+
* ```
|
|
214
|
+
* Note: You must include a callback and use the created model provided in the callback if your code depends on your model being
|
|
215
|
+
* saved or having an ID.
|
|
216
|
+
*
|
|
217
|
+
* @param {Object} [data] Optional data object
|
|
218
|
+
* @param {Object} [options] Options for create
|
|
219
|
+
* @param {Function} [cb] Callback function called with these arguments:
|
|
220
|
+
* - err (null or Error)
|
|
221
|
+
* - instance (null or Model)
|
|
222
|
+
*/
|
|
223
|
+
DataAccessObject.create = function(data, options, cb) {
|
|
224
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
225
|
+
if (connectionPromise) return connectionPromise;
|
|
226
|
+
let Model = this;
|
|
227
|
+
const connector = Model.getConnector();
|
|
228
|
+
assert(typeof connector.create === "function", "create() must be implemented by the connector");
|
|
229
|
+
const self = this;
|
|
230
|
+
if (options === void 0 && cb === void 0) {
|
|
231
|
+
if (typeof data === "function") {
|
|
232
|
+
cb = data;
|
|
233
|
+
data = {};
|
|
234
|
+
}
|
|
235
|
+
} else if (cb === void 0) {
|
|
236
|
+
if (typeof options === "function") {
|
|
237
|
+
cb = options;
|
|
238
|
+
options = {};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
data = data || {};
|
|
242
|
+
options = options || {};
|
|
243
|
+
cb = cb || utils.createPromiseCallback();
|
|
244
|
+
assert(typeof data === "object", "The data argument must be an object or array");
|
|
245
|
+
assert(typeof options === "object", "The options argument must be an object");
|
|
246
|
+
assert(typeof cb === "function", "The cb argument must be a function");
|
|
247
|
+
const hookState = {};
|
|
248
|
+
if (Array.isArray(data)) {
|
|
249
|
+
for (let i = 0, n = data.length; i < n; i++) if (data[i] === void 0) data[i] = {};
|
|
250
|
+
mapParallel(data, function(item, done) {
|
|
251
|
+
self.create(item, options, function(err, result) {
|
|
252
|
+
done(null, {
|
|
253
|
+
err,
|
|
254
|
+
result: result || item
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
}, function(err, results) {
|
|
258
|
+
if (err) return cb(err, results);
|
|
259
|
+
let errors = null;
|
|
260
|
+
const data = [];
|
|
261
|
+
for (let i = 0, n = results.length; i < n; i++) {
|
|
262
|
+
if (results[i].err) {
|
|
263
|
+
if (!errors) errors = [];
|
|
264
|
+
errors[i] = results[i].err;
|
|
265
|
+
}
|
|
266
|
+
data[i] = results[i].result;
|
|
267
|
+
}
|
|
268
|
+
cb(errors, data);
|
|
269
|
+
});
|
|
270
|
+
return cb.promise;
|
|
271
|
+
}
|
|
272
|
+
const enforced = {};
|
|
273
|
+
let obj;
|
|
274
|
+
const idValue = getIdValue(this, data);
|
|
275
|
+
try {
|
|
276
|
+
if (data instanceof Model && !idValue) obj = data;
|
|
277
|
+
else obj = new Model(data);
|
|
278
|
+
this.applyProperties(enforced, obj);
|
|
279
|
+
obj.setAttributes(enforced);
|
|
280
|
+
} catch (err) {
|
|
281
|
+
process.nextTick(function() {
|
|
282
|
+
cb(err);
|
|
283
|
+
});
|
|
284
|
+
return cb.promise;
|
|
285
|
+
}
|
|
286
|
+
Model = this.lookupModel(data);
|
|
287
|
+
if (Model !== obj.constructor) obj = new Model(data);
|
|
288
|
+
let context = {
|
|
289
|
+
Model,
|
|
290
|
+
instance: obj,
|
|
291
|
+
isNewInstance: true,
|
|
292
|
+
hookState,
|
|
293
|
+
options
|
|
294
|
+
};
|
|
295
|
+
Model.notifyObserversOf("before save", context, function(err) {
|
|
296
|
+
if (err) return cb(err);
|
|
297
|
+
data = obj.toObject(true);
|
|
298
|
+
if (options.validate === false) return create();
|
|
299
|
+
if (options.validate === void 0 && Model.settings.automaticValidation === false) return create();
|
|
300
|
+
obj.isValid(function(valid) {
|
|
301
|
+
if (valid) create();
|
|
302
|
+
else cb(new ValidationError(obj), obj);
|
|
303
|
+
}, data, options);
|
|
304
|
+
});
|
|
305
|
+
function create() {
|
|
306
|
+
obj.trigger("create", function(createDone) {
|
|
307
|
+
obj.trigger("save", function(saveDone) {
|
|
308
|
+
const _idName = idName(Model);
|
|
309
|
+
let val = Model._sanitizeData(obj.toObject(true), options);
|
|
310
|
+
function createCallback(err, id, rev) {
|
|
311
|
+
if (id) {
|
|
312
|
+
obj.__data[_idName] = id;
|
|
313
|
+
defineReadonlyProp(obj, _idName, id);
|
|
314
|
+
}
|
|
315
|
+
if (rev) obj._rev = rev;
|
|
316
|
+
if (err) return cb(err, obj);
|
|
317
|
+
obj.__persisted = true;
|
|
318
|
+
const context = {
|
|
319
|
+
Model,
|
|
320
|
+
data: val,
|
|
321
|
+
isNewInstance: true,
|
|
322
|
+
hookState,
|
|
323
|
+
options
|
|
324
|
+
};
|
|
325
|
+
Model.notifyObserversOf("loaded", context, function(err) {
|
|
326
|
+
if (err) return cb(err);
|
|
327
|
+
if (Model.settings.updateOnLoad) obj.setAttributes(context.data);
|
|
328
|
+
saveDone.call(obj, function() {
|
|
329
|
+
createDone.call(obj, function() {
|
|
330
|
+
if (err) return cb(err, obj);
|
|
331
|
+
const context = {
|
|
332
|
+
Model,
|
|
333
|
+
instance: obj,
|
|
334
|
+
isNewInstance: true,
|
|
335
|
+
hookState,
|
|
336
|
+
options
|
|
337
|
+
};
|
|
338
|
+
if (options.notify !== false) Model.notifyObserversOf("after save", context, function(err) {
|
|
339
|
+
cb(err, obj);
|
|
340
|
+
});
|
|
341
|
+
else cb(null, obj);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
val = applyDefaultsOnWrites(val, Model.definition);
|
|
347
|
+
context = {
|
|
348
|
+
Model,
|
|
349
|
+
data: val,
|
|
350
|
+
isNewInstance: true,
|
|
351
|
+
currentInstance: obj,
|
|
352
|
+
hookState,
|
|
353
|
+
options
|
|
354
|
+
};
|
|
355
|
+
Model.notifyObserversOf("persist", context, function(err, ctx) {
|
|
356
|
+
if (err) return cb(err);
|
|
357
|
+
val = ctx.data;
|
|
358
|
+
invokeConnectorMethod(connector, "create", Model, [obj.constructor._forDB(ctx.data)], options, createCallback);
|
|
359
|
+
});
|
|
360
|
+
}, obj, cb);
|
|
361
|
+
}, obj, cb);
|
|
362
|
+
}
|
|
363
|
+
return cb.promise;
|
|
364
|
+
};
|
|
365
|
+
/**
|
|
366
|
+
* Create an instances of Model with given data array and save to the attached data source. Callback is optional.
|
|
367
|
+
* Example:
|
|
368
|
+
*```js
|
|
369
|
+
* User.createAll([{first: 'Joe', last: 'Bob'},{first: 'Tom', last: 'Cat'}], function(err, users) {
|
|
370
|
+
* console.log(users[0] instanceof User); // true
|
|
371
|
+
* });
|
|
372
|
+
* ```
|
|
373
|
+
* Note: You must include a callback and use the created models provided in the callback if your code depends on your model being
|
|
374
|
+
* saved or having an ID.
|
|
375
|
+
*
|
|
376
|
+
* @param {Object} [dataArray] Optional data object with array of records
|
|
377
|
+
* @param {Object} [options] Options for create
|
|
378
|
+
* @param {Function} [cb] Callback function called with these arguments:
|
|
379
|
+
* - err (null or Error)
|
|
380
|
+
* - instance (null or Models)
|
|
381
|
+
*/
|
|
382
|
+
DataAccessObject.createAll = function(dataArray, options, cb) {
|
|
383
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
384
|
+
if (connectionPromise) return connectionPromise;
|
|
385
|
+
let Model = this;
|
|
386
|
+
const connector = Model.getConnector();
|
|
387
|
+
if (!connector.multiInsertSupported) return this.create(dataArray, options, cb);
|
|
388
|
+
assert(typeof connector.createAll === "function", "createAll() must be implemented by the connector");
|
|
389
|
+
if (options === void 0 && cb === void 0) {
|
|
390
|
+
if (typeof dataArray === "function") {
|
|
391
|
+
cb = dataArray;
|
|
392
|
+
dataArray = [];
|
|
393
|
+
}
|
|
394
|
+
} else if (cb === void 0) {
|
|
395
|
+
if (typeof options === "function") {
|
|
396
|
+
cb = options;
|
|
397
|
+
options = {};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
dataArray = dataArray || [];
|
|
401
|
+
options = options || {};
|
|
402
|
+
cb = cb || utils.createPromiseCallback();
|
|
403
|
+
assert(typeof dataArray === "object" && dataArray.length, "The data argument must be an array with length > 0");
|
|
404
|
+
assert(typeof options === "object", "The options argument must be an object");
|
|
405
|
+
assert(typeof cb === "function", "The cb argument must be a function");
|
|
406
|
+
const validationPromises = [];
|
|
407
|
+
for (let index = 0; index < dataArray.length; index++) {
|
|
408
|
+
const data = dataArray[index];
|
|
409
|
+
const hookState = {};
|
|
410
|
+
const enforced = {};
|
|
411
|
+
let obj;
|
|
412
|
+
try {
|
|
413
|
+
obj = new Model(data);
|
|
414
|
+
this.applyProperties(enforced, obj);
|
|
415
|
+
obj.setAttributes(enforced);
|
|
416
|
+
} catch (err) {
|
|
417
|
+
process.nextTick(function() {
|
|
418
|
+
cb(err);
|
|
419
|
+
});
|
|
420
|
+
return cb.promise;
|
|
421
|
+
}
|
|
422
|
+
Model = this.lookupModel(data);
|
|
423
|
+
if (Model !== obj.constructor) obj = new Model(data);
|
|
424
|
+
const context = {
|
|
425
|
+
Model,
|
|
426
|
+
instance: obj,
|
|
427
|
+
isNewInstance: true,
|
|
428
|
+
hookState,
|
|
429
|
+
options
|
|
430
|
+
};
|
|
431
|
+
const promise = new Promise((resolve, reject) => {
|
|
432
|
+
Model.notifyObserversOf("before save", context, function(err) {
|
|
433
|
+
if (err) return reject({
|
|
434
|
+
error: err,
|
|
435
|
+
data: obj
|
|
436
|
+
});
|
|
437
|
+
const d = obj.toObject(true);
|
|
438
|
+
if (options.validate === false) return resolve(obj);
|
|
439
|
+
if (options.validate === void 0 && Model.settings.automaticValidation === false) return resolve(obj);
|
|
440
|
+
obj.isValid(function(valid) {
|
|
441
|
+
if (valid) resolve(obj);
|
|
442
|
+
else reject({
|
|
443
|
+
error: new ValidationError(obj),
|
|
444
|
+
data: obj
|
|
445
|
+
});
|
|
446
|
+
}, d, options);
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
validationPromises.push(promise);
|
|
450
|
+
}
|
|
451
|
+
Promise.all(validationPromises).then((objArray) => {
|
|
452
|
+
const values = [];
|
|
453
|
+
const valMap = /* @__PURE__ */ new Map();
|
|
454
|
+
objArray.forEach((obj) => {
|
|
455
|
+
const val = Model._sanitizeData(obj.toObject(true), options);
|
|
456
|
+
values.push(val);
|
|
457
|
+
valMap.set(obj, applyDefaultsOnWrites(val, Model.definition));
|
|
458
|
+
});
|
|
459
|
+
const _idName = idName(Model);
|
|
460
|
+
function createCallback(err, savedArray) {
|
|
461
|
+
if (err) return cb(err, objArray);
|
|
462
|
+
const contextArr = savedArray.map((obj, i) => {
|
|
463
|
+
return {
|
|
464
|
+
Model,
|
|
465
|
+
data: obj,
|
|
466
|
+
isNewInstance: true,
|
|
467
|
+
hookState: context[i].hookState,
|
|
468
|
+
options: context[i].options
|
|
469
|
+
};
|
|
470
|
+
});
|
|
471
|
+
Model.notifyObserversOf("loaded", contextArr, function(err) {
|
|
472
|
+
if (err) return cb(err);
|
|
473
|
+
const afterSavePromises = [];
|
|
474
|
+
savedArray.map((obj, i) => {
|
|
475
|
+
const dataModel = context[i].currentInstance;
|
|
476
|
+
const id = obj[_idName];
|
|
477
|
+
if (id) {
|
|
478
|
+
dataModel.__data[_idName] = id;
|
|
479
|
+
defineReadonlyProp(dataModel, _idName, id);
|
|
480
|
+
}
|
|
481
|
+
dataModel.__persisted = true;
|
|
482
|
+
if (Model.settings.updateOnLoad) dataModel.setAttributes(obj);
|
|
483
|
+
const contxt = {
|
|
484
|
+
Model,
|
|
485
|
+
instance: dataModel,
|
|
486
|
+
isNewInstance: true,
|
|
487
|
+
hookState: context[i].hookState,
|
|
488
|
+
options: context[i].options
|
|
489
|
+
};
|
|
490
|
+
let afterSavePromise;
|
|
491
|
+
if (options.notify !== false) {
|
|
492
|
+
afterSavePromise = new Promise((resolve, reject) => {
|
|
493
|
+
Model.notifyObserversOf("after save", contxt, function(err) {
|
|
494
|
+
if (err) reject(err);
|
|
495
|
+
else resolve(dataModel);
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
afterSavePromises.push(afterSavePromise);
|
|
499
|
+
} else afterSavePromises.push(Promise.resolve(dataModel));
|
|
500
|
+
});
|
|
501
|
+
Promise.all(afterSavePromises).then((saved) => {
|
|
502
|
+
cb(null, saved);
|
|
503
|
+
}).catch((err) => {
|
|
504
|
+
cb(err, objArray);
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
const context = objArray.map((obj) => {
|
|
509
|
+
return {
|
|
510
|
+
Model,
|
|
511
|
+
data: valMap.get(obj),
|
|
512
|
+
isNewInstance: true,
|
|
513
|
+
currentInstance: obj,
|
|
514
|
+
hookState: {},
|
|
515
|
+
options
|
|
516
|
+
};
|
|
517
|
+
});
|
|
518
|
+
new Promise((resolve, reject) => {
|
|
519
|
+
Model.notifyObserversOf("persist", context, function(err, ctx) {
|
|
520
|
+
if (err) return reject(err);
|
|
521
|
+
resolve(ctx.map((obj) => {
|
|
522
|
+
return obj.currentInstance.constructor._forDB(obj.data);
|
|
523
|
+
}).filter((objData) => !!objData));
|
|
524
|
+
});
|
|
525
|
+
}).then((objDataArray) => {
|
|
526
|
+
invokeConnectorMethod(connector, "createAll", Model, [objDataArray], options, createCallback);
|
|
527
|
+
}).catch((err) => {
|
|
528
|
+
if (err) cb(err);
|
|
529
|
+
});
|
|
530
|
+
}).catch((err) => {
|
|
531
|
+
if (err) cb(err.error, err.data);
|
|
532
|
+
});
|
|
533
|
+
return cb.promise;
|
|
534
|
+
};
|
|
535
|
+
function applyDefaultsOnWrites(obj, modelDefinition) {
|
|
536
|
+
for (const key in modelDefinition.properties) {
|
|
537
|
+
const prop = modelDefinition.properties[key];
|
|
538
|
+
if ("applyDefaultOnWrites" in prop && !prop.applyDefaultOnWrites && prop.default !== void 0 && prop.default === obj[key]) delete obj[key];
|
|
539
|
+
}
|
|
540
|
+
return obj;
|
|
541
|
+
}
|
|
542
|
+
function stillConnecting(dataSource, obj, args) {
|
|
543
|
+
if (typeof args[args.length - 1] === "function") return dataSource.ready(obj, args);
|
|
544
|
+
const promiseArgs = Array.prototype.slice.call(args);
|
|
545
|
+
promiseArgs.callee = args.callee;
|
|
546
|
+
const cb = utils.createPromiseCallback();
|
|
547
|
+
promiseArgs.push(cb);
|
|
548
|
+
if (dataSource.ready(obj, promiseArgs)) return cb.promise;
|
|
549
|
+
else return false;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Update or insert a model instance: update exiting record if one is found, such that parameter `data.id` matches `id` of model instance;
|
|
553
|
+
* otherwise, insert a new record.
|
|
554
|
+
*
|
|
555
|
+
* NOTE: No setters, validations, or hooks are applied when using upsert.
|
|
556
|
+
* `updateOrCreate` and `patchOrCreate` are aliases
|
|
557
|
+
* @param {Object} data The model instance data
|
|
558
|
+
* @param {Object} [options] Options for upsert
|
|
559
|
+
* @param {Function} cb The callback function (optional).
|
|
560
|
+
*/
|
|
561
|
+
DataAccessObject.updateOrCreate = DataAccessObject.patchOrCreate = DataAccessObject.upsert = function(data, options, cb) {
|
|
562
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
563
|
+
if (connectionPromise) return connectionPromise;
|
|
564
|
+
if (options === void 0 && cb === void 0) {
|
|
565
|
+
if (typeof data === "function") {
|
|
566
|
+
cb = data;
|
|
567
|
+
data = {};
|
|
568
|
+
}
|
|
569
|
+
} else if (cb === void 0) {
|
|
570
|
+
if (typeof options === "function") {
|
|
571
|
+
cb = options;
|
|
572
|
+
options = {};
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
cb = cb || utils.createPromiseCallback();
|
|
576
|
+
data = data || {};
|
|
577
|
+
options = options || {};
|
|
578
|
+
assert(typeof data === "object", "The data argument must be an object");
|
|
579
|
+
assert(typeof options === "object", "The options argument must be an object");
|
|
580
|
+
assert(typeof cb === "function", "The cb argument must be a function");
|
|
581
|
+
if (Array.isArray(data)) {
|
|
582
|
+
cb(/* @__PURE__ */ new Error("updateOrCreate does not support bulk mode or any array input"));
|
|
583
|
+
return cb.promise;
|
|
584
|
+
}
|
|
585
|
+
const hookState = {};
|
|
586
|
+
const self = this;
|
|
587
|
+
let Model = this;
|
|
588
|
+
const connector = Model.getConnector();
|
|
589
|
+
const id = getIdValue(this, data);
|
|
590
|
+
if (id === void 0 || id === null) return this.create(data, options, cb);
|
|
591
|
+
let doValidate = void 0;
|
|
592
|
+
if (options.validate === void 0) if (Model.settings.validateUpsert === void 0) {
|
|
593
|
+
if (Model.settings.automaticValidation !== void 0) doValidate = Model.settings.automaticValidation;
|
|
594
|
+
} else doValidate = Model.settings.validateUpsert;
|
|
595
|
+
else doValidate = options.validate;
|
|
596
|
+
if (this.settings.forceId) {
|
|
597
|
+
options = Object.create(options);
|
|
598
|
+
options.validate = !!doValidate;
|
|
599
|
+
Model.findById(id, options, function(err, model) {
|
|
600
|
+
if (err) return cb(err);
|
|
601
|
+
if (!model) return cb(errorModelNotFound(id));
|
|
602
|
+
model.updateAttributes(data, options, cb);
|
|
603
|
+
});
|
|
604
|
+
return cb.promise;
|
|
605
|
+
}
|
|
606
|
+
const context = {
|
|
607
|
+
Model,
|
|
608
|
+
query: byIdQuery(Model, id),
|
|
609
|
+
hookState,
|
|
610
|
+
options
|
|
611
|
+
};
|
|
612
|
+
Model.notifyObserversOf("access", context, doUpdateOrCreate);
|
|
613
|
+
function doUpdateOrCreate(err, ctx) {
|
|
614
|
+
if (err) return cb(err);
|
|
615
|
+
const isOriginalQuery = isWhereByGivenId(Model, ctx.query.where, id);
|
|
616
|
+
if (connector.updateOrCreate && isOriginalQuery) {
|
|
617
|
+
let context = {
|
|
618
|
+
Model,
|
|
619
|
+
where: ctx.query.where,
|
|
620
|
+
data,
|
|
621
|
+
hookState,
|
|
622
|
+
options
|
|
623
|
+
};
|
|
624
|
+
Model.notifyObserversOf("before save", context, function(err, ctx) {
|
|
625
|
+
if (err) return cb(err);
|
|
626
|
+
data = ctx.data;
|
|
627
|
+
let update = data;
|
|
628
|
+
let inst = data;
|
|
629
|
+
if (!(data instanceof Model)) inst = new Model(data, { applyDefaultValues: false });
|
|
630
|
+
update = inst.toObject(false);
|
|
631
|
+
Model.applyProperties(update, inst);
|
|
632
|
+
Model = Model.lookupModel(update);
|
|
633
|
+
if (doValidate === false) callConnector();
|
|
634
|
+
else inst.isValid(function(valid) {
|
|
635
|
+
if (!valid) if (doValidate) return cb(new ValidationError(inst), inst);
|
|
636
|
+
else {
|
|
637
|
+
g.warn("Ignoring validation errors in {{updateOrCreate()}}:");
|
|
638
|
+
g.warn(" %s", new ValidationError(inst).message);
|
|
639
|
+
}
|
|
640
|
+
callConnector();
|
|
641
|
+
}, update, options);
|
|
642
|
+
function callConnector() {
|
|
643
|
+
update = Model._sanitizeData(update, options);
|
|
644
|
+
context = {
|
|
645
|
+
Model,
|
|
646
|
+
where: ctx.where,
|
|
647
|
+
data: update,
|
|
648
|
+
currentInstance: inst,
|
|
649
|
+
hookState: ctx.hookState,
|
|
650
|
+
options
|
|
651
|
+
};
|
|
652
|
+
Model.notifyObserversOf("persist", context, function(err) {
|
|
653
|
+
if (err) return done(err);
|
|
654
|
+
invokeConnectorMethod(connector, "updateOrCreate", Model, [update], options, done);
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
function done(err, data, info) {
|
|
658
|
+
if (err) return cb(err);
|
|
659
|
+
const context = {
|
|
660
|
+
Model,
|
|
661
|
+
data,
|
|
662
|
+
isNewInstance: info && info.isNewInstance,
|
|
663
|
+
hookState: ctx.hookState,
|
|
664
|
+
options
|
|
665
|
+
};
|
|
666
|
+
Model.notifyObserversOf("loaded", context, function(err) {
|
|
667
|
+
if (err) return cb(err);
|
|
668
|
+
let obj;
|
|
669
|
+
if (data && !(data instanceof Model)) {
|
|
670
|
+
inst._initProperties(data, { persisted: true });
|
|
671
|
+
obj = inst;
|
|
672
|
+
} else obj = data;
|
|
673
|
+
if (err) cb(err, obj);
|
|
674
|
+
else {
|
|
675
|
+
const context = {
|
|
676
|
+
Model,
|
|
677
|
+
instance: obj,
|
|
678
|
+
isNewInstance: info ? info.isNewInstance : void 0,
|
|
679
|
+
hookState,
|
|
680
|
+
options
|
|
681
|
+
};
|
|
682
|
+
if (options.notify !== false) Model.notifyObserversOf("after save", context, function(err) {
|
|
683
|
+
cb(err, obj);
|
|
684
|
+
});
|
|
685
|
+
else cb(null, obj);
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
} else {
|
|
691
|
+
const opts = { notify: false };
|
|
692
|
+
if (ctx.options && ctx.options.transaction) opts.transaction = ctx.options.transaction;
|
|
693
|
+
Model.findOne({ where: ctx.query.where }, opts, function(err, inst) {
|
|
694
|
+
if (err) return cb(err);
|
|
695
|
+
if (!isOriginalQuery) delete data[idName(Model)];
|
|
696
|
+
if (inst) inst.updateAttributes(data, options, cb);
|
|
697
|
+
else {
|
|
698
|
+
Model = self.lookupModel(data);
|
|
699
|
+
new Model(data).save(options, cb);
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
return cb.promise;
|
|
705
|
+
};
|
|
706
|
+
/**
|
|
707
|
+
* Update or insert a model instance based on the search criteria.
|
|
708
|
+
* If there is a single instance retrieved, update the retrieved model.
|
|
709
|
+
* Creates a new model if no model instances were found.
|
|
710
|
+
* Returns an error if multiple instances are found.
|
|
711
|
+
* @param {Object} [where] `where` filter, like
|
|
712
|
+
* ```
|
|
713
|
+
* { key: val, key2: {gt: 'val2'}, ...}
|
|
714
|
+
* ```
|
|
715
|
+
* <br/>see
|
|
716
|
+
* [Where filter](https://docs.strongloop.com/display/LB/Where+filter#Wherefilter-Whereclauseforothermethods).
|
|
717
|
+
* @param {Object} data The model instance data to insert.
|
|
718
|
+
* @callback {Function} callback Callback function called with `cb(err, obj)` signature.
|
|
719
|
+
* @param {Error} err Error object; see [Error object](http://docs.strongloop.com/display/LB/Error+object).
|
|
720
|
+
* @param {Object} model Updated model instance.
|
|
721
|
+
*/
|
|
722
|
+
DataAccessObject.patchOrCreateWithWhere = DataAccessObject.upsertWithWhere = function(where, data, options, cb) {
|
|
723
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
724
|
+
if (connectionPromise) return connectionPromise;
|
|
725
|
+
if (cb === void 0) {
|
|
726
|
+
if (typeof options === "function") {
|
|
727
|
+
cb = options;
|
|
728
|
+
options = {};
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
cb = cb || utils.createPromiseCallback();
|
|
732
|
+
options = options || {};
|
|
733
|
+
assert(typeof where === "object", "The where argument must be an object");
|
|
734
|
+
assert(typeof data === "object", "The data argument must be an object");
|
|
735
|
+
assert(typeof options === "object", "The options argument must be an object");
|
|
736
|
+
assert(typeof cb === "function", "The cb argument must be a function");
|
|
737
|
+
if (Object.keys(data).length === 0) {
|
|
738
|
+
const err = /* @__PURE__ */ new Error("data object cannot be empty!");
|
|
739
|
+
err.statusCode = 400;
|
|
740
|
+
process.nextTick(function() {
|
|
741
|
+
cb(err);
|
|
742
|
+
});
|
|
743
|
+
return cb.promise;
|
|
744
|
+
}
|
|
745
|
+
const hookState = {};
|
|
746
|
+
const self = this;
|
|
747
|
+
let Model = this;
|
|
748
|
+
const connector = Model.getConnector();
|
|
749
|
+
const query = { where };
|
|
750
|
+
const context = {
|
|
751
|
+
Model,
|
|
752
|
+
query,
|
|
753
|
+
hookState,
|
|
754
|
+
options
|
|
755
|
+
};
|
|
756
|
+
Model.notifyObserversOf("access", context, doUpsertWithWhere);
|
|
757
|
+
function doUpsertWithWhere(err, ctx) {
|
|
758
|
+
if (err) return cb(err);
|
|
759
|
+
ctx.data = data;
|
|
760
|
+
if (connector.upsertWithWhere) {
|
|
761
|
+
let context = {
|
|
762
|
+
Model,
|
|
763
|
+
where: ctx.query.where,
|
|
764
|
+
data: ctx.data,
|
|
765
|
+
hookState,
|
|
766
|
+
options
|
|
767
|
+
};
|
|
768
|
+
Model.notifyObserversOf("before save", context, function(err, ctx) {
|
|
769
|
+
if (err) return cb(err);
|
|
770
|
+
data = ctx.data;
|
|
771
|
+
let update = data;
|
|
772
|
+
let inst = data;
|
|
773
|
+
if (!(data instanceof Model)) inst = new Model(data, { applyDefaultValues: false });
|
|
774
|
+
update = inst.toObject(false);
|
|
775
|
+
Model.applyScope(query);
|
|
776
|
+
Model.applyProperties(update, inst);
|
|
777
|
+
Model = Model.lookupModel(update);
|
|
778
|
+
if (options.validate === false) return callConnector();
|
|
779
|
+
if (options.validate === void 0 && Model.settings.automaticValidation === false) return callConnector();
|
|
780
|
+
inst.isValid(function(valid) {
|
|
781
|
+
if (!valid) return cb(new ValidationError(inst), inst);
|
|
782
|
+
callConnector();
|
|
783
|
+
}, update, options);
|
|
784
|
+
function callConnector() {
|
|
785
|
+
try {
|
|
786
|
+
ctx.where = Model._sanitizeQuery(ctx.where, options);
|
|
787
|
+
ctx.where = Model._coerce(ctx.where, options);
|
|
788
|
+
update = Model._sanitizeData(update, options);
|
|
789
|
+
update = Model._coerce(update, options);
|
|
790
|
+
} catch (err) {
|
|
791
|
+
return process.nextTick(function() {
|
|
792
|
+
cb(err);
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
context = {
|
|
796
|
+
Model,
|
|
797
|
+
where: ctx.where,
|
|
798
|
+
data: update,
|
|
799
|
+
currentInstance: inst,
|
|
800
|
+
hookState: ctx.hookState,
|
|
801
|
+
options
|
|
802
|
+
};
|
|
803
|
+
Model.notifyObserversOf("persist", context, function(err) {
|
|
804
|
+
if (err) return done(err);
|
|
805
|
+
invokeConnectorMethod(connector, "upsertWithWhere", Model, [ctx.where, update], options, done);
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
function done(err, data, info) {
|
|
809
|
+
if (err) return cb(err);
|
|
810
|
+
const contxt = {
|
|
811
|
+
Model,
|
|
812
|
+
data,
|
|
813
|
+
isNewInstance: info && info.isNewInstance,
|
|
814
|
+
hookState: ctx.hookState,
|
|
815
|
+
options
|
|
816
|
+
};
|
|
817
|
+
Model.notifyObserversOf("loaded", contxt, function(err) {
|
|
818
|
+
if (err) return cb(err);
|
|
819
|
+
let obj;
|
|
820
|
+
if (contxt.data && !(contxt.data instanceof Model)) {
|
|
821
|
+
inst._initProperties(contxt.data, { persisted: true });
|
|
822
|
+
obj = inst;
|
|
823
|
+
} else obj = contxt.data;
|
|
824
|
+
const context = {
|
|
825
|
+
Model,
|
|
826
|
+
instance: obj,
|
|
827
|
+
isNewInstance: info ? info.isNewInstance : void 0,
|
|
828
|
+
hookState,
|
|
829
|
+
options
|
|
830
|
+
};
|
|
831
|
+
Model.notifyObserversOf("after save", context, function(err) {
|
|
832
|
+
cb(err, obj);
|
|
833
|
+
});
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
} else {
|
|
838
|
+
const opts = { notify: false };
|
|
839
|
+
if (ctx.options && ctx.options.transaction) opts.transaction = ctx.options.transaction;
|
|
840
|
+
self.find({ where: ctx.query.where }, opts, function(err, instances) {
|
|
841
|
+
if (err) return cb(err);
|
|
842
|
+
const modelsLength = instances.length;
|
|
843
|
+
if (modelsLength === 0) self.create(data, options, cb);
|
|
844
|
+
else if (modelsLength === 1) instances[0].updateAttributes(data, options, cb);
|
|
845
|
+
else process.nextTick(function() {
|
|
846
|
+
const error = /* @__PURE__ */ new Error("There are multiple instances found.Upsert Operation will not be performed!");
|
|
847
|
+
error.statusCode = 400;
|
|
848
|
+
cb(error);
|
|
849
|
+
});
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return cb.promise;
|
|
854
|
+
};
|
|
855
|
+
/**
|
|
856
|
+
* Replace or insert a model instance: replace exiting record if one is found, such that parameter `data.id` matches `id` of model instance;
|
|
857
|
+
* otherwise, insert a new record.
|
|
858
|
+
*
|
|
859
|
+
* @param {Object} data The model instance data
|
|
860
|
+
* @param {Object} [options] Options for replaceOrCreate
|
|
861
|
+
* @param {Function} cb The callback function (optional).
|
|
862
|
+
*/
|
|
863
|
+
DataAccessObject.replaceOrCreate = function replaceOrCreate(data, options, cb) {
|
|
864
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
865
|
+
if (connectionPromise) return connectionPromise;
|
|
866
|
+
if (cb === void 0) {
|
|
867
|
+
if (typeof options === "function") {
|
|
868
|
+
cb = options;
|
|
869
|
+
options = {};
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
cb = cb || utils.createPromiseCallback();
|
|
873
|
+
data = data || {};
|
|
874
|
+
options = options || {};
|
|
875
|
+
assert(typeof data === "object", "The data argument must be an object");
|
|
876
|
+
assert(typeof options === "object", "The options argument must be an object");
|
|
877
|
+
assert(typeof cb === "function", "The cb argument must be a function");
|
|
878
|
+
const hookState = {};
|
|
879
|
+
const self = this;
|
|
880
|
+
let Model = this;
|
|
881
|
+
const connector = Model.getConnector();
|
|
882
|
+
let id = getIdValue(this, data);
|
|
883
|
+
if (id === void 0 || id === null) return this.create(data, options, cb);
|
|
884
|
+
if (this.settings.forceId) return Model.replaceById(id, data, options, cb);
|
|
885
|
+
let inst;
|
|
886
|
+
if (data instanceof Model) inst = data;
|
|
887
|
+
else inst = new Model(data);
|
|
888
|
+
const strict = inst.__strict;
|
|
889
|
+
const context = {
|
|
890
|
+
Model,
|
|
891
|
+
query: byIdQuery(Model, id),
|
|
892
|
+
hookState,
|
|
893
|
+
options
|
|
894
|
+
};
|
|
895
|
+
Model.notifyObserversOf("access", context, doReplaceOrCreate);
|
|
896
|
+
function doReplaceOrCreate(err, ctx) {
|
|
897
|
+
if (err) return cb(err);
|
|
898
|
+
const isOriginalQuery = isWhereByGivenId(Model, ctx.query.where, id);
|
|
899
|
+
const where = ctx.query.where;
|
|
900
|
+
if (connector.replaceOrCreate && isOriginalQuery) {
|
|
901
|
+
let context = {
|
|
902
|
+
Model,
|
|
903
|
+
instance: inst,
|
|
904
|
+
hookState,
|
|
905
|
+
options
|
|
906
|
+
};
|
|
907
|
+
Model.notifyObserversOf("before save", context, function(err, ctx) {
|
|
908
|
+
if (err) return cb(err);
|
|
909
|
+
let update = inst.toObject(false);
|
|
910
|
+
if (strict) applyStrictCheck(Model, strict, update, inst, validateAndCallConnector);
|
|
911
|
+
else validateAndCallConnector();
|
|
912
|
+
function validateAndCallConnector(err) {
|
|
913
|
+
if (err) return cb(err);
|
|
914
|
+
Model.applyProperties(update, inst);
|
|
915
|
+
Model = Model.lookupModel(update);
|
|
916
|
+
if (options.validate === false) return callConnector();
|
|
917
|
+
if (options.validate === void 0 && Model.settings.automaticValidation === false) return callConnector();
|
|
918
|
+
inst.isValid(function(valid) {
|
|
919
|
+
if (!valid) return cb(new ValidationError(inst), inst);
|
|
920
|
+
callConnector();
|
|
921
|
+
}, update, options);
|
|
922
|
+
function callConnector() {
|
|
923
|
+
update = Model._sanitizeData(update, options);
|
|
924
|
+
context = {
|
|
925
|
+
Model,
|
|
926
|
+
where,
|
|
927
|
+
data: update,
|
|
928
|
+
currentInstance: inst,
|
|
929
|
+
hookState: ctx.hookState,
|
|
930
|
+
options
|
|
931
|
+
};
|
|
932
|
+
Model.notifyObserversOf("persist", context, function(err) {
|
|
933
|
+
if (err) return done(err);
|
|
934
|
+
invokeConnectorMethod(connector, "replaceOrCreate", Model, [context.data], options, done);
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
function done(err, data, info) {
|
|
938
|
+
if (err) return cb(err);
|
|
939
|
+
const context = {
|
|
940
|
+
Model,
|
|
941
|
+
data,
|
|
942
|
+
isNewInstance: info ? info.isNewInstance : void 0,
|
|
943
|
+
hookState: ctx.hookState,
|
|
944
|
+
options
|
|
945
|
+
};
|
|
946
|
+
Model.notifyObserversOf("loaded", context, function(err) {
|
|
947
|
+
if (err) return cb(err);
|
|
948
|
+
let obj;
|
|
949
|
+
if (data && !(data instanceof Model)) {
|
|
950
|
+
inst._initProperties(data, { persisted: true });
|
|
951
|
+
obj = inst;
|
|
952
|
+
} else obj = data;
|
|
953
|
+
if (err) cb(err, obj);
|
|
954
|
+
else {
|
|
955
|
+
const context = {
|
|
956
|
+
Model,
|
|
957
|
+
instance: obj,
|
|
958
|
+
isNewInstance: info ? info.isNewInstance : void 0,
|
|
959
|
+
hookState,
|
|
960
|
+
options
|
|
961
|
+
};
|
|
962
|
+
Model.notifyObserversOf("after save", context, function(err) {
|
|
963
|
+
cb(err, obj, info);
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
} else {
|
|
971
|
+
const opts = { notify: false };
|
|
972
|
+
if (ctx.options && ctx.options.transaction) opts.transaction = ctx.options.transaction;
|
|
973
|
+
Model.findOne({ where: ctx.query.where }, opts, function(err, found) {
|
|
974
|
+
if (err) return cb(err);
|
|
975
|
+
if (!isOriginalQuery) {
|
|
976
|
+
const pkName = idName(Model);
|
|
977
|
+
delete data[pkName];
|
|
978
|
+
if (found) id = found[pkName];
|
|
979
|
+
}
|
|
980
|
+
if (found) self.replaceById(id, data, options, cb);
|
|
981
|
+
else {
|
|
982
|
+
Model = self.lookupModel(data);
|
|
983
|
+
new Model(data).save(options, cb);
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return cb.promise;
|
|
989
|
+
};
|
|
990
|
+
/**
|
|
991
|
+
* Find one record that matches specified query criteria. Same as `find`, but limited to one record, and this function returns an
|
|
992
|
+
* object, not a collection.
|
|
993
|
+
* If the specified instance is not found, then create it using data provided as second argument.
|
|
994
|
+
*
|
|
995
|
+
* @param {Object} query Search conditions. See [find](#dataaccessobjectfindquery-callback) for query format.
|
|
996
|
+
* For example: `{where: {test: 'me'}}`.
|
|
997
|
+
* @param {Object} data Object to create.
|
|
998
|
+
* @param {Object} [options] Option for findOrCreate
|
|
999
|
+
* @param {Function} cb Callback called with (err, instance, created)
|
|
1000
|
+
*/
|
|
1001
|
+
DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb) {
|
|
1002
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
1003
|
+
if (connectionPromise) return connectionPromise;
|
|
1004
|
+
assert(arguments.length >= 1, "At least one argument is required");
|
|
1005
|
+
if (data === void 0 && options === void 0 && cb === void 0) {
|
|
1006
|
+
assert(typeof query === "object", "Single argument must be data object");
|
|
1007
|
+
data = query;
|
|
1008
|
+
query = { where: data };
|
|
1009
|
+
} else if (options === void 0 && cb === void 0) {
|
|
1010
|
+
if (typeof data === "function") {
|
|
1011
|
+
cb = data;
|
|
1012
|
+
data = query;
|
|
1013
|
+
query = { where: data };
|
|
1014
|
+
}
|
|
1015
|
+
} else if (cb === void 0) {
|
|
1016
|
+
if (typeof options === "function") {
|
|
1017
|
+
cb = options;
|
|
1018
|
+
options = {};
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
cb = cb || utils.createPromiseCallback();
|
|
1022
|
+
query = query || { where: {} };
|
|
1023
|
+
data = data || {};
|
|
1024
|
+
options = options || {};
|
|
1025
|
+
assert(typeof query === "object", "The query argument must be an object");
|
|
1026
|
+
assert(typeof data === "object", "The data argument must be an object");
|
|
1027
|
+
assert(typeof options === "object", "The options argument must be an object");
|
|
1028
|
+
assert(typeof cb === "function", "The cb argument must be a function");
|
|
1029
|
+
const hookState = {};
|
|
1030
|
+
const Model = this;
|
|
1031
|
+
const self = this;
|
|
1032
|
+
const connector = Model.getConnector();
|
|
1033
|
+
function _findOrCreate(query, data, currentInstance) {
|
|
1034
|
+
function findOrCreateCallback(err, data, created) {
|
|
1035
|
+
if (err) return cb(err);
|
|
1036
|
+
const context = {
|
|
1037
|
+
Model,
|
|
1038
|
+
data,
|
|
1039
|
+
isNewInstance: created,
|
|
1040
|
+
hookState,
|
|
1041
|
+
options
|
|
1042
|
+
};
|
|
1043
|
+
Model.notifyObserversOf("loaded", context, function(err, ctx) {
|
|
1044
|
+
if (err) return cb(err);
|
|
1045
|
+
data = ctx.data;
|
|
1046
|
+
const Model = self.lookupModel(data);
|
|
1047
|
+
let obj;
|
|
1048
|
+
if (data) {
|
|
1049
|
+
const ctorOpts = {
|
|
1050
|
+
fields: query.fields,
|
|
1051
|
+
applySetters: false,
|
|
1052
|
+
persisted: true,
|
|
1053
|
+
applyDefaultValues: false
|
|
1054
|
+
};
|
|
1055
|
+
obj = new Model(data, ctorOpts);
|
|
1056
|
+
}
|
|
1057
|
+
if (created) {
|
|
1058
|
+
const context = {
|
|
1059
|
+
Model,
|
|
1060
|
+
instance: obj,
|
|
1061
|
+
isNewInstance: true,
|
|
1062
|
+
hookState,
|
|
1063
|
+
options
|
|
1064
|
+
};
|
|
1065
|
+
Model.notifyObserversOf("after save", context, function(err) {
|
|
1066
|
+
if (cb.promise) cb(err, [obj, created]);
|
|
1067
|
+
else cb(err, obj, created);
|
|
1068
|
+
});
|
|
1069
|
+
} else if (cb.promise) cb(err, [obj, created]);
|
|
1070
|
+
else cb(err, obj, created);
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
data = Model._sanitizeData(data, options);
|
|
1074
|
+
const context = {
|
|
1075
|
+
Model,
|
|
1076
|
+
where: query.where,
|
|
1077
|
+
data,
|
|
1078
|
+
isNewInstance: true,
|
|
1079
|
+
currentInstance,
|
|
1080
|
+
hookState,
|
|
1081
|
+
options
|
|
1082
|
+
};
|
|
1083
|
+
Model.notifyObserversOf("persist", context, function(err) {
|
|
1084
|
+
if (err) return cb(err);
|
|
1085
|
+
invokeConnectorMethod(connector, "findOrCreate", Model, [query, self._forDB(context.data)], options, findOrCreateCallback);
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
if (connector.findOrCreate) {
|
|
1089
|
+
query.limit = 1;
|
|
1090
|
+
try {
|
|
1091
|
+
this._normalize(query, options);
|
|
1092
|
+
} catch (err) {
|
|
1093
|
+
process.nextTick(function() {
|
|
1094
|
+
cb(err);
|
|
1095
|
+
});
|
|
1096
|
+
return cb.promise;
|
|
1097
|
+
}
|
|
1098
|
+
this.applyScope(query);
|
|
1099
|
+
const context = {
|
|
1100
|
+
Model,
|
|
1101
|
+
query,
|
|
1102
|
+
hookState,
|
|
1103
|
+
options
|
|
1104
|
+
};
|
|
1105
|
+
Model.notifyObserversOf("access", context, function(err, ctx) {
|
|
1106
|
+
if (err) return cb(err);
|
|
1107
|
+
const query = ctx.query;
|
|
1108
|
+
const enforced = {};
|
|
1109
|
+
const Model = self.lookupModel(data);
|
|
1110
|
+
const obj = data instanceof Model ? data : new Model(data);
|
|
1111
|
+
Model.applyProperties(enforced, obj);
|
|
1112
|
+
obj.setAttributes(enforced);
|
|
1113
|
+
const context = {
|
|
1114
|
+
Model,
|
|
1115
|
+
instance: obj,
|
|
1116
|
+
isNewInstance: true,
|
|
1117
|
+
hookState,
|
|
1118
|
+
options
|
|
1119
|
+
};
|
|
1120
|
+
Model.notifyObserversOf("before save", context, function(err, ctx) {
|
|
1121
|
+
if (err) return cb(err);
|
|
1122
|
+
const obj = ctx.instance;
|
|
1123
|
+
const data = obj.toObject(true);
|
|
1124
|
+
if (options.validate === false) return _findOrCreate(query, data, obj);
|
|
1125
|
+
if (options.validate === void 0 && Model.settings.automaticValidation === false) return _findOrCreate(query, data, obj);
|
|
1126
|
+
obj.isValid(function(valid) {
|
|
1127
|
+
if (valid) _findOrCreate(query, data, obj);
|
|
1128
|
+
else cb(new ValidationError(obj), obj);
|
|
1129
|
+
}, data, options);
|
|
1130
|
+
});
|
|
1131
|
+
});
|
|
1132
|
+
} else Model.findOne(query, options, function(err, record) {
|
|
1133
|
+
if (err) return cb(err);
|
|
1134
|
+
if (record) if (cb.promise) return cb(null, [record, false]);
|
|
1135
|
+
else return cb(null, record, false);
|
|
1136
|
+
Model.create(data, options, function(err, record) {
|
|
1137
|
+
if (cb.promise) cb(err, [record, record != null]);
|
|
1138
|
+
else cb(err, record, record != null);
|
|
1139
|
+
});
|
|
1140
|
+
});
|
|
1141
|
+
return cb.promise;
|
|
1142
|
+
};
|
|
1143
|
+
/**
|
|
1144
|
+
* Check whether a model instance exists in database
|
|
1145
|
+
*
|
|
1146
|
+
* @param {id} id Identifier of object (primary key value)
|
|
1147
|
+
* @param {Object} [options] Options
|
|
1148
|
+
* @param {Function} cb Callback function called with (err, exists: Bool)
|
|
1149
|
+
*/
|
|
1150
|
+
DataAccessObject.exists = function exists(id, options, cb) {
|
|
1151
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
1152
|
+
if (connectionPromise) return connectionPromise;
|
|
1153
|
+
assert(arguments.length >= 1, "The id argument is required");
|
|
1154
|
+
if (cb === void 0) {
|
|
1155
|
+
if (typeof options === "function") {
|
|
1156
|
+
cb = options;
|
|
1157
|
+
options = {};
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
cb = cb || utils.createPromiseCallback();
|
|
1161
|
+
options = options || {};
|
|
1162
|
+
assert(typeof options === "object", "The options argument must be an object");
|
|
1163
|
+
assert(typeof cb === "function", "The cb argument must be a function");
|
|
1164
|
+
if (id !== void 0 && id !== null && id !== "") this.count(byIdQuery(this, id).where, options, function(err, count) {
|
|
1165
|
+
cb(err, err ? false : count === 1);
|
|
1166
|
+
});
|
|
1167
|
+
else process.nextTick(function() {
|
|
1168
|
+
cb(new Error(g.f("{{Model::exists}} requires the {{id}} argument")));
|
|
1169
|
+
});
|
|
1170
|
+
return cb.promise;
|
|
1171
|
+
};
|
|
1172
|
+
/**
|
|
1173
|
+
* Find model instance by ID.
|
|
1174
|
+
*
|
|
1175
|
+
* Example:
|
|
1176
|
+
* ```js
|
|
1177
|
+
* User.findById(23, function(err, user) {
|
|
1178
|
+
* console.info(user.id); // 23
|
|
1179
|
+
* });
|
|
1180
|
+
* ```
|
|
1181
|
+
*
|
|
1182
|
+
* @param {*} id Primary key value
|
|
1183
|
+
* @param {Object} [filter] The filter that contains `include` or `fields`.
|
|
1184
|
+
* Other settings such as `where`, `order`, `limit`, or `offset` will be
|
|
1185
|
+
* ignored.
|
|
1186
|
+
* @param {Object} [options] Options
|
|
1187
|
+
* @param {Function} cb Callback called with (err, instance)
|
|
1188
|
+
*/
|
|
1189
|
+
DataAccessObject.findById = function findById(id, filter, options, cb) {
|
|
1190
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
1191
|
+
if (connectionPromise) return connectionPromise;
|
|
1192
|
+
assert(arguments.length >= 1, "The id argument is required");
|
|
1193
|
+
if (options === void 0 && cb === void 0) {
|
|
1194
|
+
if (typeof filter === "function") {
|
|
1195
|
+
cb = filter;
|
|
1196
|
+
filter = {};
|
|
1197
|
+
}
|
|
1198
|
+
} else if (cb === void 0) {
|
|
1199
|
+
if (typeof options === "function") {
|
|
1200
|
+
cb = options;
|
|
1201
|
+
options = {};
|
|
1202
|
+
if (typeof filter === "object" && !(filter.include || filter.fields)) {
|
|
1203
|
+
options = filter;
|
|
1204
|
+
filter = {};
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
cb = cb || utils.createPromiseCallback();
|
|
1209
|
+
options = options || {};
|
|
1210
|
+
filter = filter || {};
|
|
1211
|
+
assert(typeof filter === "object", "The filter argument must be an object");
|
|
1212
|
+
assert(typeof options === "object", "The options argument must be an object");
|
|
1213
|
+
assert(typeof cb === "function", "The cb argument must be a function");
|
|
1214
|
+
if (isPKMissing(this, cb)) return cb.promise;
|
|
1215
|
+
else if (id == null || id === "") process.nextTick(function() {
|
|
1216
|
+
cb(new Error(g.f("{{Model::findById}} requires the {{id}} argument")));
|
|
1217
|
+
});
|
|
1218
|
+
else {
|
|
1219
|
+
const query = byIdQuery(this, id);
|
|
1220
|
+
if (filter.include) query.include = filter.include;
|
|
1221
|
+
if (filter.fields) query.fields = filter.fields;
|
|
1222
|
+
this.findOne(query, options, cb);
|
|
1223
|
+
}
|
|
1224
|
+
return cb.promise;
|
|
1225
|
+
};
|
|
1226
|
+
/**
|
|
1227
|
+
* Find model instances by ids
|
|
1228
|
+
* @param {Array} ids An array of ids
|
|
1229
|
+
* @param {Object} query Query filter
|
|
1230
|
+
* @param {Object} [options] Options
|
|
1231
|
+
* @param {Function} cb Callback called with (err, instance)
|
|
1232
|
+
*/
|
|
1233
|
+
DataAccessObject.findByIds = function(ids, query, options, cb) {
|
|
1234
|
+
if (options === void 0 && cb === void 0) {
|
|
1235
|
+
if (typeof query === "function") {
|
|
1236
|
+
cb = query;
|
|
1237
|
+
query = {};
|
|
1238
|
+
}
|
|
1239
|
+
} else if (cb === void 0) {
|
|
1240
|
+
if (typeof options === "function") {
|
|
1241
|
+
cb = options;
|
|
1242
|
+
options = {};
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
cb = cb || utils.createPromiseCallback();
|
|
1246
|
+
options = options || {};
|
|
1247
|
+
query = query || {};
|
|
1248
|
+
assert(Array.isArray(ids), "The ids argument must be an array");
|
|
1249
|
+
assert(typeof query === "object", "The query argument must be an object");
|
|
1250
|
+
assert(typeof options === "object", "The options argument must be an object");
|
|
1251
|
+
assert(typeof cb === "function", "The cb argument must be a function");
|
|
1252
|
+
if (isPKMissing(this, cb)) return cb.promise;
|
|
1253
|
+
else if (ids.length === 0) {
|
|
1254
|
+
process.nextTick(function() {
|
|
1255
|
+
cb(null, []);
|
|
1256
|
+
});
|
|
1257
|
+
return cb.promise;
|
|
1258
|
+
}
|
|
1259
|
+
const filter = { where: {} };
|
|
1260
|
+
const pk = idName(this);
|
|
1261
|
+
filter.where[pk] = { inq: [].concat(ids) };
|
|
1262
|
+
mergeQuery(filter, query || {});
|
|
1263
|
+
const toSortObjectsByIds = filter.order ? false : true;
|
|
1264
|
+
this.find(filter, options, function(err, results) {
|
|
1265
|
+
cb(err, toSortObjectsByIds ? utils.sortObjectsByIds(pk, ids, results) : results);
|
|
1266
|
+
});
|
|
1267
|
+
return cb.promise;
|
|
1268
|
+
};
|
|
1269
|
+
DataAccessObject.all = function() {
|
|
1270
|
+
return DataAccessObject.find.apply(this, arguments);
|
|
1271
|
+
};
|
|
1272
|
+
/**
|
|
1273
|
+
* Find all instances of Model that match the specified query.
|
|
1274
|
+
* Fields used for filter and sort should be declared with `{index: true}` in model definition.
|
|
1275
|
+
* See [Querying models](http://docs.strongloop.com/display/DOC/Querying+models) for more information.
|
|
1276
|
+
*
|
|
1277
|
+
* For example, find the second page of ten users over age 21 in descending order exluding the password property.
|
|
1278
|
+
*
|
|
1279
|
+
* ```js
|
|
1280
|
+
* User.find({
|
|
1281
|
+
* where: {
|
|
1282
|
+
* age: {gt: 21}},
|
|
1283
|
+
* order: 'age DESC',
|
|
1284
|
+
* limit: 10,
|
|
1285
|
+
* skip: 10,
|
|
1286
|
+
* fields: {password: false}
|
|
1287
|
+
* },
|
|
1288
|
+
* console.log
|
|
1289
|
+
* );
|
|
1290
|
+
* ```
|
|
1291
|
+
*
|
|
1292
|
+
* @options {Object} [query] Optional JSON object that specifies query criteria and parameters.
|
|
1293
|
+
* @property {Object} where Search criteria in JSON format `{ key: val, key2: {gt: 'val2'}}`.
|
|
1294
|
+
* Operations:
|
|
1295
|
+
* - gt: >
|
|
1296
|
+
* - gte: >=
|
|
1297
|
+
* - lt: <
|
|
1298
|
+
* - lte: <=
|
|
1299
|
+
* - between
|
|
1300
|
+
* - inq: IN
|
|
1301
|
+
* - nin: NOT IN
|
|
1302
|
+
* - neq: !=
|
|
1303
|
+
* - like: LIKE
|
|
1304
|
+
* - nlike: NOT LIKE
|
|
1305
|
+
* - ilike: ILIKE
|
|
1306
|
+
* - nilike: NOT ILIKE
|
|
1307
|
+
* - regexp: REGEXP
|
|
1308
|
+
*
|
|
1309
|
+
* You can also use `and` and `or` operations. See [Querying models](http://docs.strongloop.com/display/DOC/Querying+models) for more information.
|
|
1310
|
+
* @property {String|Object|Array} include Allows you to load relations of several objects and optimize numbers of requests.
|
|
1311
|
+
* Format examples;
|
|
1312
|
+
* - `'posts'`: Load posts
|
|
1313
|
+
* - `['posts', 'passports']`: Load posts and passports
|
|
1314
|
+
* - `{'owner': 'posts'}`: Load owner and owner's posts
|
|
1315
|
+
* - `{'owner': ['posts', 'passports']}`: Load owner, owner's posts, and owner's passports
|
|
1316
|
+
* - `{'owner': [{posts: 'images'}, 'passports']}`: Load owner, owner's posts, owner's posts' images, and owner's passports
|
|
1317
|
+
* See `DataAccessObject.include()`.
|
|
1318
|
+
* @property {String} order Sort order. Format: `'key1 ASC, key2 DESC'`
|
|
1319
|
+
* @property {Number} limit Maximum number of instances to return.
|
|
1320
|
+
* @property {Number} skip Number of instances to skip.
|
|
1321
|
+
* @property {Number} offset Alias for `skip`.
|
|
1322
|
+
* @property {Object|Array|String} fields Included/excluded fields.
|
|
1323
|
+
* - `['foo']` or `'foo'` - include only the foo property
|
|
1324
|
+
* - `['foo', 'bar']` - include the foo and bar properties. Format:
|
|
1325
|
+
* - `{foo: true}` - include only foo
|
|
1326
|
+
* - `{bat: false}` - include all properties, exclude bat
|
|
1327
|
+
*
|
|
1328
|
+
* @param {Function} cb Optional callback function. Call this function with two arguments: `err` (null or Error) and an array of instances.
|
|
1329
|
+
* @return {Promise} results If no callback function is provided, a promise (which resolves to an array of instances) is returned
|
|
1330
|
+
*/
|
|
1331
|
+
DataAccessObject.find = function find(query, options, cb) {
|
|
1332
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
1333
|
+
if (connectionPromise) return connectionPromise;
|
|
1334
|
+
if (options === void 0 && cb === void 0) {
|
|
1335
|
+
if (typeof query === "function") {
|
|
1336
|
+
cb = query;
|
|
1337
|
+
query = {};
|
|
1338
|
+
}
|
|
1339
|
+
} else if (cb === void 0) {
|
|
1340
|
+
if (typeof options === "function") {
|
|
1341
|
+
cb = options;
|
|
1342
|
+
options = {};
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
cb = cb || utils.createPromiseCallback();
|
|
1346
|
+
query = query || {};
|
|
1347
|
+
options = options || {};
|
|
1348
|
+
assert(typeof query === "object", "The query argument must be an object");
|
|
1349
|
+
assert(typeof options === "object", "The options argument must be an object");
|
|
1350
|
+
assert(typeof cb === "function", "The cb argument must be a function");
|
|
1351
|
+
const hookState = {};
|
|
1352
|
+
const self = this;
|
|
1353
|
+
const connector = self.getConnector();
|
|
1354
|
+
assert(typeof connector.all === "function", "all() must be implemented by the connector");
|
|
1355
|
+
try {
|
|
1356
|
+
this._normalize(query, options);
|
|
1357
|
+
} catch (err) {
|
|
1358
|
+
process.nextTick(function() {
|
|
1359
|
+
cb(err);
|
|
1360
|
+
});
|
|
1361
|
+
return cb.promise;
|
|
1362
|
+
}
|
|
1363
|
+
this.applyScope(query);
|
|
1364
|
+
let near = query && geo.nearFilter(query.where);
|
|
1365
|
+
const supportsGeo = !!connector.buildNearFilter;
|
|
1366
|
+
let geoQueryObject;
|
|
1367
|
+
if (near) {
|
|
1368
|
+
if (supportsGeo) connector.buildNearFilter(query, near);
|
|
1369
|
+
else if (query.where) {
|
|
1370
|
+
if (options.notify === false) queryGeo(query);
|
|
1371
|
+
else withNotifyGeo();
|
|
1372
|
+
function withNotifyGeo() {
|
|
1373
|
+
const context = {
|
|
1374
|
+
Model: self,
|
|
1375
|
+
query,
|
|
1376
|
+
hookState,
|
|
1377
|
+
options
|
|
1378
|
+
};
|
|
1379
|
+
self.notifyObserversOf("access", context, function(err, ctx) {
|
|
1380
|
+
if (err) return cb(err);
|
|
1381
|
+
queryGeo(ctx.query);
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
return cb.promise;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
function geoCallback(err, data) {
|
|
1388
|
+
const memory = new Memory();
|
|
1389
|
+
const modelName = self.modelName;
|
|
1390
|
+
if (err) cb(err);
|
|
1391
|
+
else if (Array.isArray(data)) {
|
|
1392
|
+
memory.define({
|
|
1393
|
+
properties: self.dataSource.definitions[modelName].properties,
|
|
1394
|
+
settings: self.dataSource.definitions[modelName].settings,
|
|
1395
|
+
model: self
|
|
1396
|
+
});
|
|
1397
|
+
data.forEach(function(obj) {
|
|
1398
|
+
memory.create(modelName, obj, options, function() {});
|
|
1399
|
+
});
|
|
1400
|
+
memory.all(modelName, geoQueryObject, options, allCb);
|
|
1401
|
+
} else cb(null, []);
|
|
1402
|
+
}
|
|
1403
|
+
function queryGeo(query) {
|
|
1404
|
+
geoQueryObject = query;
|
|
1405
|
+
near = query && geo.nearFilter(query.where);
|
|
1406
|
+
invokeConnectorMethod(connector, "all", self, [{}], options, geoCallback);
|
|
1407
|
+
}
|
|
1408
|
+
function allCb(err, data) {
|
|
1409
|
+
let normalizedIncludes = null;
|
|
1410
|
+
let includedRelationNames = null;
|
|
1411
|
+
if (query && query.include && !query.collect) {
|
|
1412
|
+
normalizedIncludes = Inclusion.normalizeInclude(query.include || []);
|
|
1413
|
+
includedRelationNames = [];
|
|
1414
|
+
for (let i = 0; i < normalizedIncludes.length; i++) {
|
|
1415
|
+
const inc = normalizedIncludes[i];
|
|
1416
|
+
includedRelationNames.push(utils.isPlainObject(inc) ? Object.keys(inc)[0] : inc);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
if (!err && Array.isArray(data)) mapParallel(data, function(item, next) {
|
|
1420
|
+
const Model = self.lookupModel(item);
|
|
1421
|
+
if (options.notify === false) buildResult(item, next);
|
|
1422
|
+
else withNotify(item, next);
|
|
1423
|
+
function buildResult(data, callback) {
|
|
1424
|
+
const ctorOpts = {
|
|
1425
|
+
fields: query.fields,
|
|
1426
|
+
applySetters: false,
|
|
1427
|
+
persisted: true,
|
|
1428
|
+
applyDefaultValues: false
|
|
1429
|
+
};
|
|
1430
|
+
let obj;
|
|
1431
|
+
try {
|
|
1432
|
+
obj = new Model(data, ctorOpts);
|
|
1433
|
+
} catch (err) {
|
|
1434
|
+
return callback(err);
|
|
1435
|
+
}
|
|
1436
|
+
if (query && query.include) if (query.collect) {
|
|
1437
|
+
obj = obj.__cachedRelations[query.collect];
|
|
1438
|
+
if (obj === null) obj = void 0;
|
|
1439
|
+
} else {
|
|
1440
|
+
for (let i = 0; i < includedRelationNames.length; i++) {
|
|
1441
|
+
const relationName = includedRelationNames[i];
|
|
1442
|
+
let included = obj.__cachedRelations[relationName];
|
|
1443
|
+
if (Array.isArray(included)) included = new List(included, null, obj);
|
|
1444
|
+
if (included) obj.__data[relationName] = included;
|
|
1445
|
+
}
|
|
1446
|
+
delete obj.__data.__cachedRelations;
|
|
1447
|
+
}
|
|
1448
|
+
callback(null, obj);
|
|
1449
|
+
}
|
|
1450
|
+
function withNotify(data, callback) {
|
|
1451
|
+
const context = {
|
|
1452
|
+
Model,
|
|
1453
|
+
data,
|
|
1454
|
+
isNewInstance: false,
|
|
1455
|
+
hookState,
|
|
1456
|
+
options
|
|
1457
|
+
};
|
|
1458
|
+
Model.notifyObserversOf("loaded", context, function(err) {
|
|
1459
|
+
if (err) return callback(err);
|
|
1460
|
+
buildResult(context.data, callback);
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
}, function(err, results) {
|
|
1464
|
+
if (err) return cb(err);
|
|
1465
|
+
results = results.filter(isDefined);
|
|
1466
|
+
if (data && data.countBeforeLimit) results.countBeforeLimit = data.countBeforeLimit;
|
|
1467
|
+
if (!supportsGeo && near) results = geo.filter(results, near);
|
|
1468
|
+
cb(err, results);
|
|
1469
|
+
});
|
|
1470
|
+
else cb(err, data || []);
|
|
1471
|
+
}
|
|
1472
|
+
if (options.notify === false) invokeConnectorMethod(connector, "all", self, [query], options, allCb);
|
|
1473
|
+
else {
|
|
1474
|
+
const context = {
|
|
1475
|
+
Model: this,
|
|
1476
|
+
query,
|
|
1477
|
+
hookState,
|
|
1478
|
+
options
|
|
1479
|
+
};
|
|
1480
|
+
this.notifyObserversOf("access", context, function(err, ctx) {
|
|
1481
|
+
if (err) return cb(err);
|
|
1482
|
+
invokeConnectorMethod(connector, "all", self, [ctx.query], options, allCb);
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
return cb.promise;
|
|
1486
|
+
};
|
|
1487
|
+
function isDefined(value) {
|
|
1488
|
+
return value !== void 0;
|
|
1489
|
+
}
|
|
1490
|
+
/**
|
|
1491
|
+
* Find one record, same as `find`, but limited to one result. This function returns an object, not a collection.
|
|
1492
|
+
*
|
|
1493
|
+
* @param {Object} query Search conditions. See [find](#dataaccessobjectfindquery-callback) for query format.
|
|
1494
|
+
* For example: `{where: {test: 'me'}}`.
|
|
1495
|
+
* @param {Object} [options] Options
|
|
1496
|
+
* @param {Function} cb Callback function called with (err, instance)
|
|
1497
|
+
*/
|
|
1498
|
+
DataAccessObject.findOne = function findOne(query, options, cb) {
|
|
1499
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
1500
|
+
if (connectionPromise) return connectionPromise;
|
|
1501
|
+
if (options === void 0 && cb === void 0) {
|
|
1502
|
+
if (typeof query === "function") {
|
|
1503
|
+
cb = query;
|
|
1504
|
+
query = {};
|
|
1505
|
+
}
|
|
1506
|
+
} else if (cb === void 0) {
|
|
1507
|
+
if (typeof options === "function") {
|
|
1508
|
+
cb = options;
|
|
1509
|
+
options = {};
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
cb = cb || utils.createPromiseCallback();
|
|
1513
|
+
query = query || {};
|
|
1514
|
+
options = options || {};
|
|
1515
|
+
assert(typeof query === "object", "The query argument must be an object");
|
|
1516
|
+
assert(typeof options === "object", "The options argument must be an object");
|
|
1517
|
+
assert(typeof cb === "function", "The cb argument must be a function");
|
|
1518
|
+
query.limit = 1;
|
|
1519
|
+
this.find(query, options, function(err, collection) {
|
|
1520
|
+
if (err || !collection || !collection.length > 0) return cb(err, null);
|
|
1521
|
+
cb(err, collection[0]);
|
|
1522
|
+
});
|
|
1523
|
+
return cb.promise;
|
|
1524
|
+
};
|
|
1525
|
+
/**
|
|
1526
|
+
* Destroy all matching records.
|
|
1527
|
+
* Delete all model instances from data source. Note: destroyAll method does not destroy hooks.
|
|
1528
|
+
* Example:
|
|
1529
|
+
*````js
|
|
1530
|
+
* Product.destroyAll({price: {gt: 99}}, function(err) {
|
|
1531
|
+
// removed matching products
|
|
1532
|
+
* });
|
|
1533
|
+
* ````
|
|
1534
|
+
*
|
|
1535
|
+
* @param {Object} [where] Optional object that defines the criteria. This is a "where" object. Do NOT pass a filter object.
|
|
1536
|
+
* @param {Object) [options] Options
|
|
1537
|
+
* @param {Function} [cb] Callback called with (err, info)
|
|
1538
|
+
*/
|
|
1539
|
+
DataAccessObject.remove = DataAccessObject.deleteAll = DataAccessObject.destroyAll = function destroyAll(where, options, cb) {
|
|
1540
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
1541
|
+
if (connectionPromise) return connectionPromise;
|
|
1542
|
+
const Model = this;
|
|
1543
|
+
const connector = Model.getConnector();
|
|
1544
|
+
assert(typeof connector.destroyAll === "function", "destroyAll() must be implemented by the connector");
|
|
1545
|
+
if (options === void 0 && cb === void 0) {
|
|
1546
|
+
if (typeof where === "function") {
|
|
1547
|
+
cb = where;
|
|
1548
|
+
where = {};
|
|
1549
|
+
}
|
|
1550
|
+
} else if (cb === void 0) {
|
|
1551
|
+
if (typeof options === "function") {
|
|
1552
|
+
cb = options;
|
|
1553
|
+
options = {};
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
cb = cb || utils.createPromiseCallback();
|
|
1557
|
+
where = where || {};
|
|
1558
|
+
options = options || {};
|
|
1559
|
+
assert(typeof where === "object", "The where argument must be an object");
|
|
1560
|
+
assert(typeof options === "object", "The options argument must be an object");
|
|
1561
|
+
assert(typeof cb === "function", "The cb argument must be a function");
|
|
1562
|
+
const hookState = {};
|
|
1563
|
+
let query = { where };
|
|
1564
|
+
this.applyScope(query);
|
|
1565
|
+
where = query.where;
|
|
1566
|
+
if (options.notify === false) doDelete(where);
|
|
1567
|
+
else {
|
|
1568
|
+
query = { where: whereIsEmpty(where) ? {} : where };
|
|
1569
|
+
const context = {
|
|
1570
|
+
Model,
|
|
1571
|
+
query,
|
|
1572
|
+
hookState,
|
|
1573
|
+
options
|
|
1574
|
+
};
|
|
1575
|
+
Model.notifyObserversOf("access", context, function(err, ctx) {
|
|
1576
|
+
if (err) return cb(err);
|
|
1577
|
+
const context = {
|
|
1578
|
+
Model,
|
|
1579
|
+
where: ctx.query.where,
|
|
1580
|
+
hookState,
|
|
1581
|
+
options
|
|
1582
|
+
};
|
|
1583
|
+
Model.notifyObserversOf("before delete", context, function(err, ctx) {
|
|
1584
|
+
if (err) return cb(err);
|
|
1585
|
+
doDelete(ctx.where);
|
|
1586
|
+
});
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
function doDelete(where) {
|
|
1590
|
+
whereIsEmpty(where);
|
|
1591
|
+
if (whereIsEmpty(where)) invokeConnectorMethod(connector, "destroyAll", Model, [{}], options, done);
|
|
1592
|
+
else {
|
|
1593
|
+
try {
|
|
1594
|
+
where = Model._sanitizeQuery(where, options);
|
|
1595
|
+
where = Model._coerce(where, options);
|
|
1596
|
+
} catch (err) {
|
|
1597
|
+
return process.nextTick(function() {
|
|
1598
|
+
cb(err);
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
invokeConnectorMethod(connector, "destroyAll", Model, [where], options, done);
|
|
1602
|
+
}
|
|
1603
|
+
function done(err, info) {
|
|
1604
|
+
if (err) return cb(err);
|
|
1605
|
+
if (options.notify === false) return cb(err, info);
|
|
1606
|
+
const context = {
|
|
1607
|
+
Model,
|
|
1608
|
+
where,
|
|
1609
|
+
hookState,
|
|
1610
|
+
options,
|
|
1611
|
+
info
|
|
1612
|
+
};
|
|
1613
|
+
Model.notifyObserversOf("after delete", context, function(err) {
|
|
1614
|
+
cb(err, info);
|
|
1615
|
+
});
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
return cb.promise;
|
|
1619
|
+
};
|
|
1620
|
+
function whereIsEmpty(where) {
|
|
1621
|
+
return !where || typeof where === "object" && Object.keys(where).length === 0;
|
|
1622
|
+
}
|
|
1623
|
+
/**
|
|
1624
|
+
* Delete the record with the specified ID.
|
|
1625
|
+
* Aliases are `destroyById` and `deleteById`.
|
|
1626
|
+
* @param {*} id The id value
|
|
1627
|
+
* @param {Function} cb Callback called with (err)
|
|
1628
|
+
*/
|
|
1629
|
+
DataAccessObject.removeById = DataAccessObject.destroyById = DataAccessObject.deleteById = function deleteById(id, options, cb) {
|
|
1630
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
1631
|
+
if (connectionPromise) return connectionPromise;
|
|
1632
|
+
assert(arguments.length >= 1, "The id argument is required");
|
|
1633
|
+
if (cb === void 0) {
|
|
1634
|
+
if (typeof options === "function") {
|
|
1635
|
+
cb = options;
|
|
1636
|
+
options = {};
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
options = options || {};
|
|
1640
|
+
cb = cb || utils.createPromiseCallback();
|
|
1641
|
+
assert(typeof options === "object", "The options argument must be an object");
|
|
1642
|
+
assert(typeof cb === "function", "The cb argument must be a function");
|
|
1643
|
+
if (isPKMissing(this, cb)) return cb.promise;
|
|
1644
|
+
else if (id == null || id === "") {
|
|
1645
|
+
process.nextTick(function() {
|
|
1646
|
+
cb(new Error(g.f("{{Model::deleteById}} requires the {{id}} argument")));
|
|
1647
|
+
});
|
|
1648
|
+
return cb.promise;
|
|
1649
|
+
}
|
|
1650
|
+
const Model = this;
|
|
1651
|
+
this.remove(byIdQuery(this, id).where, options, function(err, info) {
|
|
1652
|
+
if (err) return cb(err);
|
|
1653
|
+
const deleted = info && info.count > 0;
|
|
1654
|
+
if (Model.settings.strictDelete && !deleted) {
|
|
1655
|
+
err = new Error(g.f("No instance with {{id}} %s found for %s", id, Model.modelName));
|
|
1656
|
+
err.code = "NOT_FOUND";
|
|
1657
|
+
err.statusCode = 404;
|
|
1658
|
+
return cb(err);
|
|
1659
|
+
}
|
|
1660
|
+
cb(null, info);
|
|
1661
|
+
});
|
|
1662
|
+
return cb.promise;
|
|
1663
|
+
};
|
|
1664
|
+
/**
|
|
1665
|
+
* Return count of matched records. Optional query parameter allows you to count filtered set of model instances.
|
|
1666
|
+
* Example:
|
|
1667
|
+
*
|
|
1668
|
+
*```js
|
|
1669
|
+
* User.count({approved: true}, function(err, count) {
|
|
1670
|
+
* console.log(count); // 2081
|
|
1671
|
+
* });
|
|
1672
|
+
* ```
|
|
1673
|
+
*
|
|
1674
|
+
* @param {Object} [where] Search conditions (optional)
|
|
1675
|
+
* @param {Object} [options] Options
|
|
1676
|
+
* @param {Function} cb Callback, called with (err, count)
|
|
1677
|
+
*/
|
|
1678
|
+
DataAccessObject.count = function(where, options, cb) {
|
|
1679
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
1680
|
+
if (connectionPromise) return connectionPromise;
|
|
1681
|
+
if (options === void 0 && cb === void 0) {
|
|
1682
|
+
if (typeof where === "function") {
|
|
1683
|
+
cb = where;
|
|
1684
|
+
where = {};
|
|
1685
|
+
}
|
|
1686
|
+
} else if (cb === void 0) {
|
|
1687
|
+
if (typeof options === "function") {
|
|
1688
|
+
cb = options;
|
|
1689
|
+
options = {};
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
cb = cb || utils.createPromiseCallback();
|
|
1693
|
+
where = where || {};
|
|
1694
|
+
options = options || {};
|
|
1695
|
+
assert(typeof where === "object", "The where argument must be an object");
|
|
1696
|
+
assert(typeof options === "object", "The options argument must be an object");
|
|
1697
|
+
assert(typeof cb === "function", "The cb argument must be a function");
|
|
1698
|
+
const Model = this;
|
|
1699
|
+
const connector = Model.getConnector();
|
|
1700
|
+
assert(typeof connector.count === "function", "count() must be implemented by the connector");
|
|
1701
|
+
assert(connector.count.length >= 3, "count() must take at least 3 arguments");
|
|
1702
|
+
const hookState = {};
|
|
1703
|
+
const query = { where };
|
|
1704
|
+
this.applyScope(query);
|
|
1705
|
+
where = query.where;
|
|
1706
|
+
try {
|
|
1707
|
+
where = Model._sanitizeQuery(where, options);
|
|
1708
|
+
where = this._coerce(where, options);
|
|
1709
|
+
} catch (err) {
|
|
1710
|
+
process.nextTick(function() {
|
|
1711
|
+
cb(err);
|
|
1712
|
+
});
|
|
1713
|
+
return cb.promise;
|
|
1714
|
+
}
|
|
1715
|
+
const context = {
|
|
1716
|
+
Model,
|
|
1717
|
+
query: { where },
|
|
1718
|
+
hookState,
|
|
1719
|
+
options
|
|
1720
|
+
};
|
|
1721
|
+
this.notifyObserversOf("access", context, function(err, ctx) {
|
|
1722
|
+
if (err) return cb(err);
|
|
1723
|
+
invokeConnectorMethod(connector, "count", Model, [ctx.query.where], options, cb);
|
|
1724
|
+
});
|
|
1725
|
+
return cb.promise;
|
|
1726
|
+
};
|
|
1727
|
+
/**
|
|
1728
|
+
* Save instance. If the instance does not have an ID, call `create` instead.
|
|
1729
|
+
* Triggers: validate, save, update or create.
|
|
1730
|
+
* @options {Object} options Optional options to use.
|
|
1731
|
+
* @property {Boolean} validate Default is true.
|
|
1732
|
+
* @property {Boolean} throws Default is false.
|
|
1733
|
+
* @param {Function} cb Callback function with err and object arguments
|
|
1734
|
+
*/
|
|
1735
|
+
DataAccessObject.prototype.save = function(options, cb) {
|
|
1736
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
1737
|
+
if (connectionPromise) return connectionPromise;
|
|
1738
|
+
const Model = this.constructor;
|
|
1739
|
+
if (typeof options === "function") {
|
|
1740
|
+
cb = options;
|
|
1741
|
+
options = {};
|
|
1742
|
+
}
|
|
1743
|
+
cb = cb || utils.createPromiseCallback();
|
|
1744
|
+
options = options || {};
|
|
1745
|
+
assert(typeof options === "object", "The options argument should be an object");
|
|
1746
|
+
assert(typeof cb === "function", "The cb argument should be a function");
|
|
1747
|
+
if (isPKMissing(Model, cb)) return cb.promise;
|
|
1748
|
+
else if (this.isNewRecord()) return Model.create(this, options, cb);
|
|
1749
|
+
const hookState = {};
|
|
1750
|
+
if (options.validate === void 0) if (Model.settings.automaticValidation === void 0) options.validate = true;
|
|
1751
|
+
else options.validate = Model.settings.automaticValidation;
|
|
1752
|
+
if (options.throws === void 0) options.throws = false;
|
|
1753
|
+
const inst = this;
|
|
1754
|
+
const connector = inst.getConnector();
|
|
1755
|
+
let context = {
|
|
1756
|
+
Model,
|
|
1757
|
+
instance: inst,
|
|
1758
|
+
hookState,
|
|
1759
|
+
options
|
|
1760
|
+
};
|
|
1761
|
+
Model.notifyObserversOf("before save", context, function(err) {
|
|
1762
|
+
if (err) return cb(err);
|
|
1763
|
+
let data = inst.toObject(true);
|
|
1764
|
+
Model.applyProperties(data, inst);
|
|
1765
|
+
inst.setAttributes(data);
|
|
1766
|
+
if (!options.validate) return save();
|
|
1767
|
+
inst.isValid(function(valid) {
|
|
1768
|
+
if (valid) save();
|
|
1769
|
+
else {
|
|
1770
|
+
const err = new ValidationError(inst);
|
|
1771
|
+
if (options.throws) throw err;
|
|
1772
|
+
cb(err, inst);
|
|
1773
|
+
}
|
|
1774
|
+
}, data, options);
|
|
1775
|
+
function save() {
|
|
1776
|
+
inst.trigger("save", function(saveDone) {
|
|
1777
|
+
inst.trigger("update", function(updateDone) {
|
|
1778
|
+
data = Model._sanitizeData(data, options);
|
|
1779
|
+
function saveCallback(err, unusedData, result) {
|
|
1780
|
+
if (err) return cb(err, inst);
|
|
1781
|
+
const context = {
|
|
1782
|
+
Model,
|
|
1783
|
+
data,
|
|
1784
|
+
isNewInstance: result && result.isNewInstance,
|
|
1785
|
+
hookState,
|
|
1786
|
+
options
|
|
1787
|
+
};
|
|
1788
|
+
Model.notifyObserversOf("loaded", context, function(err, ctx) {
|
|
1789
|
+
if (err) return cb(err);
|
|
1790
|
+
data = ctx.data;
|
|
1791
|
+
inst._initProperties(data, { persisted: true });
|
|
1792
|
+
const context = {
|
|
1793
|
+
Model,
|
|
1794
|
+
instance: inst,
|
|
1795
|
+
isNewInstance: result && result.isNewInstance,
|
|
1796
|
+
hookState,
|
|
1797
|
+
options
|
|
1798
|
+
};
|
|
1799
|
+
Model.notifyObserversOf("after save", context, function(err) {
|
|
1800
|
+
if (err) return cb(err, inst);
|
|
1801
|
+
updateDone.call(inst, function() {
|
|
1802
|
+
saveDone.call(inst, function() {
|
|
1803
|
+
cb(err, inst);
|
|
1804
|
+
});
|
|
1805
|
+
});
|
|
1806
|
+
});
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
context = {
|
|
1810
|
+
Model,
|
|
1811
|
+
data,
|
|
1812
|
+
where: byIdQuery(Model, getIdValue(Model, inst)).where,
|
|
1813
|
+
currentInstance: inst,
|
|
1814
|
+
hookState,
|
|
1815
|
+
options
|
|
1816
|
+
};
|
|
1817
|
+
Model.notifyObserversOf("persist", context, function(err, ctx) {
|
|
1818
|
+
if (err) return cb(err);
|
|
1819
|
+
data = ctx.data;
|
|
1820
|
+
invokeConnectorMethod(connector, "save", Model, [Model._forDB(data)], options, saveCallback);
|
|
1821
|
+
});
|
|
1822
|
+
}, data, cb);
|
|
1823
|
+
}, data, cb);
|
|
1824
|
+
}
|
|
1825
|
+
});
|
|
1826
|
+
return cb.promise;
|
|
1827
|
+
};
|
|
1828
|
+
/**
|
|
1829
|
+
* Update multiple instances that match the where clause
|
|
1830
|
+
*
|
|
1831
|
+
* Example:
|
|
1832
|
+
*
|
|
1833
|
+
*```js
|
|
1834
|
+
* Employee.update({managerId: 'x001'}, {managerId: 'x002'}, function(err) {
|
|
1835
|
+
* ...
|
|
1836
|
+
* });
|
|
1837
|
+
* ```
|
|
1838
|
+
*
|
|
1839
|
+
* @param {Object} [where] Search conditions (optional)
|
|
1840
|
+
* @param {Object} data Changes to be made
|
|
1841
|
+
* @param {Object} [options] Options for update
|
|
1842
|
+
* @param {Function} cb Callback, called with (err, info)
|
|
1843
|
+
*/
|
|
1844
|
+
DataAccessObject.update = DataAccessObject.updateAll = function(where, data, options, cb) {
|
|
1845
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
1846
|
+
if (connectionPromise) return connectionPromise;
|
|
1847
|
+
assert(arguments.length >= 1, "At least one argument is required");
|
|
1848
|
+
if (data === void 0 && options === void 0 && cb === void 0 && arguments.length === 1) {
|
|
1849
|
+
data = where;
|
|
1850
|
+
where = {};
|
|
1851
|
+
} else if (options === void 0 && cb === void 0) {
|
|
1852
|
+
if (typeof data === "function") {
|
|
1853
|
+
cb = data;
|
|
1854
|
+
data = where;
|
|
1855
|
+
where = {};
|
|
1856
|
+
}
|
|
1857
|
+
} else if (cb === void 0) {
|
|
1858
|
+
if (typeof options === "function") {
|
|
1859
|
+
cb = options;
|
|
1860
|
+
options = {};
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
data = data || {};
|
|
1864
|
+
options = options || {};
|
|
1865
|
+
cb = cb || utils.createPromiseCallback();
|
|
1866
|
+
assert(typeof where === "object", "The where argument must be an object");
|
|
1867
|
+
assert(typeof data === "object", "The data argument must be an object");
|
|
1868
|
+
assert(typeof options === "object", "The options argument must be an object");
|
|
1869
|
+
assert(typeof cb === "function", "The cb argument must be a function");
|
|
1870
|
+
const Model = this;
|
|
1871
|
+
const connector = Model.getDataSource().connector;
|
|
1872
|
+
assert(typeof connector.update === "function", "update() must be implemented by the connector");
|
|
1873
|
+
const hookState = {};
|
|
1874
|
+
const query = { where };
|
|
1875
|
+
this.applyScope(query);
|
|
1876
|
+
this.applyProperties(data);
|
|
1877
|
+
let doValidate = false;
|
|
1878
|
+
if (options.validate === void 0) if (Model.settings.validateUpdate === void 0) {
|
|
1879
|
+
if (Model.settings.automaticValidation !== void 0) doValidate = Model.settings.automaticValidation;
|
|
1880
|
+
} else doValidate = Model.settings.validateUpdate;
|
|
1881
|
+
else doValidate = options.validate;
|
|
1882
|
+
where = query.where;
|
|
1883
|
+
const context = {
|
|
1884
|
+
Model,
|
|
1885
|
+
query: { where },
|
|
1886
|
+
hookState,
|
|
1887
|
+
options
|
|
1888
|
+
};
|
|
1889
|
+
Model.notifyObserversOf("access", context, function(err, ctx) {
|
|
1890
|
+
if (err) return cb(err);
|
|
1891
|
+
const context = {
|
|
1892
|
+
Model,
|
|
1893
|
+
where: ctx.query.where,
|
|
1894
|
+
data,
|
|
1895
|
+
hookState,
|
|
1896
|
+
options
|
|
1897
|
+
};
|
|
1898
|
+
Model.notifyObserversOf("before save", context, function(err, ctx) {
|
|
1899
|
+
if (err) return cb(err);
|
|
1900
|
+
data = ctx.data;
|
|
1901
|
+
let inst = data;
|
|
1902
|
+
if (!(data instanceof Model)) try {
|
|
1903
|
+
inst = new Model(data, { applyDefaultValues: false });
|
|
1904
|
+
} catch (err) {
|
|
1905
|
+
return cb(err);
|
|
1906
|
+
}
|
|
1907
|
+
if (doValidate === false) doUpdate(ctx.where, ctx.data);
|
|
1908
|
+
else inst.isValid(function(valid) {
|
|
1909
|
+
if (!valid) return cb(new ValidationError(inst));
|
|
1910
|
+
doUpdate(ctx.where, ctx.data);
|
|
1911
|
+
}, options);
|
|
1912
|
+
});
|
|
1913
|
+
});
|
|
1914
|
+
function doUpdate(where, data) {
|
|
1915
|
+
try {
|
|
1916
|
+
where = Model._sanitizeQuery(where, options);
|
|
1917
|
+
where = Model._coerce(where, options);
|
|
1918
|
+
data = Model._sanitizeData(data, options);
|
|
1919
|
+
data = Model._coerce(data, options);
|
|
1920
|
+
} catch (err) {
|
|
1921
|
+
return process.nextTick(function() {
|
|
1922
|
+
cb(err);
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
function updateCallback(err, info) {
|
|
1926
|
+
if (err) return cb(err);
|
|
1927
|
+
const context = {
|
|
1928
|
+
Model,
|
|
1929
|
+
where,
|
|
1930
|
+
data,
|
|
1931
|
+
hookState,
|
|
1932
|
+
options,
|
|
1933
|
+
info
|
|
1934
|
+
};
|
|
1935
|
+
Model.notifyObserversOf("after save", context, function(err, _ctx) {
|
|
1936
|
+
return cb(err, info);
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
const context = {
|
|
1940
|
+
Model,
|
|
1941
|
+
where,
|
|
1942
|
+
data,
|
|
1943
|
+
hookState,
|
|
1944
|
+
options
|
|
1945
|
+
};
|
|
1946
|
+
Model.notifyObserversOf("persist", context, function(err, ctx) {
|
|
1947
|
+
if (err) return cb(err);
|
|
1948
|
+
data = ctx.data;
|
|
1949
|
+
invokeConnectorMethod(connector, "update", Model, [where, Model._forDB(data)], options, updateCallback);
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
return cb.promise;
|
|
1953
|
+
};
|
|
1954
|
+
DataAccessObject.prototype.isNewRecord = function() {
|
|
1955
|
+
return !this.__persisted;
|
|
1956
|
+
};
|
|
1957
|
+
/**
|
|
1958
|
+
* Return connector of current record
|
|
1959
|
+
* @private
|
|
1960
|
+
*/
|
|
1961
|
+
DataAccessObject.prototype.getConnector = function() {
|
|
1962
|
+
return this.getDataSource().connector;
|
|
1963
|
+
};
|
|
1964
|
+
/**
|
|
1965
|
+
* Delete object from persistence
|
|
1966
|
+
*
|
|
1967
|
+
* Triggers `destroy` hook (async) before and after destroying object
|
|
1968
|
+
*
|
|
1969
|
+
* @param {Object} [options] Options for delete
|
|
1970
|
+
* @param {Function} cb Callback
|
|
1971
|
+
*/
|
|
1972
|
+
DataAccessObject.prototype.remove = DataAccessObject.prototype.delete = DataAccessObject.prototype.destroy = function(options, cb) {
|
|
1973
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
1974
|
+
if (connectionPromise) return connectionPromise;
|
|
1975
|
+
if (cb === void 0 && typeof options === "function") {
|
|
1976
|
+
cb = options;
|
|
1977
|
+
options = {};
|
|
1978
|
+
}
|
|
1979
|
+
cb = cb || utils.createPromiseCallback();
|
|
1980
|
+
options = options || {};
|
|
1981
|
+
assert(typeof options === "object", "The options argument should be an object");
|
|
1982
|
+
assert(typeof cb === "function", "The cb argument should be a function");
|
|
1983
|
+
const inst = this;
|
|
1984
|
+
const connector = this.getConnector();
|
|
1985
|
+
const Model = this.constructor;
|
|
1986
|
+
const id = getIdValue(this.constructor, this);
|
|
1987
|
+
const hookState = {};
|
|
1988
|
+
if (isPKMissing(Model, cb)) return cb.promise;
|
|
1989
|
+
const context = {
|
|
1990
|
+
Model,
|
|
1991
|
+
query: byIdQuery(Model, id),
|
|
1992
|
+
hookState,
|
|
1993
|
+
options
|
|
1994
|
+
};
|
|
1995
|
+
Model.notifyObserversOf("access", context, function(err, ctx) {
|
|
1996
|
+
if (err) return cb(err);
|
|
1997
|
+
const context = {
|
|
1998
|
+
Model,
|
|
1999
|
+
where: ctx.query.where,
|
|
2000
|
+
instance: inst,
|
|
2001
|
+
hookState,
|
|
2002
|
+
options
|
|
2003
|
+
};
|
|
2004
|
+
Model.notifyObserversOf("before delete", context, function(err, ctx) {
|
|
2005
|
+
if (err) return cb(err);
|
|
2006
|
+
doDeleteInstance(ctx.where);
|
|
2007
|
+
});
|
|
2008
|
+
});
|
|
2009
|
+
function doDeleteInstance(where) {
|
|
2010
|
+
if (!isWhereByGivenId(Model, where, id)) {
|
|
2011
|
+
Model.deleteAll(where, { notify: false }, function(err, info) {
|
|
2012
|
+
if (err) return cb(err, false);
|
|
2013
|
+
const deleted = info && info.count > 0;
|
|
2014
|
+
if (Model.settings.strictDelete && !deleted) {
|
|
2015
|
+
err = new Error(g.f("No instance with {{id}} %s found for %s", id, Model.modelName));
|
|
2016
|
+
err.code = "NOT_FOUND";
|
|
2017
|
+
err.statusCode = 404;
|
|
2018
|
+
return cb(err, false);
|
|
2019
|
+
}
|
|
2020
|
+
const context = {
|
|
2021
|
+
Model,
|
|
2022
|
+
where,
|
|
2023
|
+
instance: inst,
|
|
2024
|
+
hookState,
|
|
2025
|
+
options,
|
|
2026
|
+
info
|
|
2027
|
+
};
|
|
2028
|
+
Model.notifyObserversOf("after delete", context, function(err) {
|
|
2029
|
+
cb(err, info);
|
|
2030
|
+
});
|
|
2031
|
+
});
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
inst.trigger("destroy", function(destroyed) {
|
|
2035
|
+
function destroyCallback(err, info) {
|
|
2036
|
+
if (err) return cb(err);
|
|
2037
|
+
const deleted = info && info.count > 0;
|
|
2038
|
+
if (Model.settings.strictDelete && !deleted) {
|
|
2039
|
+
err = new Error(g.f("No instance with {{id}} %s found for %s", id, Model.modelName));
|
|
2040
|
+
err.code = "NOT_FOUND";
|
|
2041
|
+
err.statusCode = 404;
|
|
2042
|
+
return cb(err);
|
|
2043
|
+
}
|
|
2044
|
+
destroyed(function() {
|
|
2045
|
+
const context = {
|
|
2046
|
+
Model,
|
|
2047
|
+
where,
|
|
2048
|
+
instance: inst,
|
|
2049
|
+
hookState,
|
|
2050
|
+
options,
|
|
2051
|
+
info
|
|
2052
|
+
};
|
|
2053
|
+
Model.notifyObserversOf("after delete", context, function(err) {
|
|
2054
|
+
cb(err, info);
|
|
2055
|
+
});
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
invokeConnectorMethod(connector, "destroy", Model, [id], options, destroyCallback);
|
|
2059
|
+
}, null, cb);
|
|
2060
|
+
}
|
|
2061
|
+
return cb.promise;
|
|
2062
|
+
};
|
|
2063
|
+
/**
|
|
2064
|
+
* Set a single attribute.
|
|
2065
|
+
* Equivalent to `setAttributes({name: value})`
|
|
2066
|
+
*
|
|
2067
|
+
* @param {String} name Name of property
|
|
2068
|
+
* @param {Mixed} value Value of property
|
|
2069
|
+
*/
|
|
2070
|
+
DataAccessObject.prototype.setAttribute = function setAttribute(name, value) {
|
|
2071
|
+
this[name] = value;
|
|
2072
|
+
};
|
|
2073
|
+
/**
|
|
2074
|
+
* Update a single attribute.
|
|
2075
|
+
* Equivalent to `updateAttributes({name: value}, cb)`
|
|
2076
|
+
*
|
|
2077
|
+
* @param {String} name Name of property
|
|
2078
|
+
* @param {Mixed} value Value of property
|
|
2079
|
+
* @param {Function} cb Callback function called with (err, instance)
|
|
2080
|
+
*/
|
|
2081
|
+
DataAccessObject.prototype.updateAttribute = function updateAttribute(name, value, options, cb) {
|
|
2082
|
+
const data = {};
|
|
2083
|
+
data[name] = value;
|
|
2084
|
+
return this.updateAttributes(data, options, cb);
|
|
2085
|
+
};
|
|
2086
|
+
/**
|
|
2087
|
+
* Update set of attributes.
|
|
2088
|
+
*
|
|
2089
|
+
* @trigger `change` hook
|
|
2090
|
+
* @param {Object} data Data to update
|
|
2091
|
+
*/
|
|
2092
|
+
DataAccessObject.prototype.setAttributes = function setAttributes(data) {
|
|
2093
|
+
if (typeof data !== "object") return;
|
|
2094
|
+
this.constructor.applyProperties(data, this);
|
|
2095
|
+
const Model = this.constructor;
|
|
2096
|
+
const inst = this;
|
|
2097
|
+
for (const key in data) inst.setAttribute(key, data[key]);
|
|
2098
|
+
Model.emit("set", inst);
|
|
2099
|
+
};
|
|
2100
|
+
DataAccessObject.prototype.unsetAttribute = function unsetAttribute(name, nullify) {
|
|
2101
|
+
if (nullify || this.constructor.definition.settings.persistUndefinedAsNull) this[name] = this.__data[name] = null;
|
|
2102
|
+
else {
|
|
2103
|
+
delete this[name];
|
|
2104
|
+
delete this.__data[name];
|
|
2105
|
+
}
|
|
2106
|
+
};
|
|
2107
|
+
/**
|
|
2108
|
+
* Replace set of attributes.
|
|
2109
|
+
* Performs validation before replacing.
|
|
2110
|
+
*
|
|
2111
|
+
* @trigger `validation`, `save` and `update` hooks
|
|
2112
|
+
* @param {Object} data Data to replace
|
|
2113
|
+
* @param {Object} [options] Options for replace
|
|
2114
|
+
* @param {Function} cb Callback function called with (err, instance)
|
|
2115
|
+
*/
|
|
2116
|
+
DataAccessObject.prototype.replaceAttributes = function(data, options, cb) {
|
|
2117
|
+
const Model = this.constructor;
|
|
2118
|
+
const id = getIdValue(this.constructor, this);
|
|
2119
|
+
return Model.replaceById(id, data, options, cb);
|
|
2120
|
+
};
|
|
2121
|
+
DataAccessObject.replaceById = function(id, data, options, cb) {
|
|
2122
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
2123
|
+
if (connectionPromise) return connectionPromise;
|
|
2124
|
+
if (cb === void 0) {
|
|
2125
|
+
if (typeof options === "function") {
|
|
2126
|
+
cb = options;
|
|
2127
|
+
options = {};
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
cb = cb || utils.createPromiseCallback();
|
|
2131
|
+
options = options || {};
|
|
2132
|
+
assert(typeof data === "object" && data !== null, "The data argument must be an object");
|
|
2133
|
+
assert(typeof options === "object", "The options argument must be an object");
|
|
2134
|
+
assert(typeof cb === "function", "The cb argument must be a function");
|
|
2135
|
+
const connector = this.getConnector();
|
|
2136
|
+
let err;
|
|
2137
|
+
if (typeof connector.replaceById !== "function") {
|
|
2138
|
+
err = new Error(g.f("The connector %s does not support {{replaceById}} operation. This is not a bug in LoopBack. Please contact the authors of the connector, preferably via GitHub issues.", connector.name));
|
|
2139
|
+
return cb(err);
|
|
2140
|
+
}
|
|
2141
|
+
const pkName = idName(this);
|
|
2142
|
+
if (!data[pkName]) data[pkName] = id;
|
|
2143
|
+
let Model = this;
|
|
2144
|
+
let inst;
|
|
2145
|
+
try {
|
|
2146
|
+
inst = new Model(data, { persisted: true });
|
|
2147
|
+
const enforced = {};
|
|
2148
|
+
this.applyProperties(enforced, inst);
|
|
2149
|
+
inst.setAttributes(enforced);
|
|
2150
|
+
} catch (err) {
|
|
2151
|
+
process.nextTick(function() {
|
|
2152
|
+
cb(err);
|
|
2153
|
+
});
|
|
2154
|
+
return cb.promise;
|
|
2155
|
+
}
|
|
2156
|
+
Model = this.lookupModel(data);
|
|
2157
|
+
if (Model !== inst.constructor) inst = new Model(data);
|
|
2158
|
+
const strict = inst.__strict;
|
|
2159
|
+
if (isPKMissing(Model, cb)) return cb.promise;
|
|
2160
|
+
const hookState = {};
|
|
2161
|
+
if (id !== data[pkName]) {
|
|
2162
|
+
err = new Error(g.f("{{id}} property (%s) cannot be updated from %s to %s", pkName, id, data[pkName]));
|
|
2163
|
+
err.statusCode = 400;
|
|
2164
|
+
process.nextTick(function() {
|
|
2165
|
+
cb(err);
|
|
2166
|
+
});
|
|
2167
|
+
return cb.promise;
|
|
2168
|
+
} else id = inst[pkName];
|
|
2169
|
+
let context = {
|
|
2170
|
+
Model,
|
|
2171
|
+
instance: inst,
|
|
2172
|
+
isNewInstance: false,
|
|
2173
|
+
hookState,
|
|
2174
|
+
options
|
|
2175
|
+
};
|
|
2176
|
+
Model.notifyObserversOf("before save", context, function(err, ctx) {
|
|
2177
|
+
if (err) return cb(err);
|
|
2178
|
+
if (ctx.instance[pkName] !== id && !Model._warned.cannotOverwritePKInBeforeSaveHook) {
|
|
2179
|
+
Model._warned.cannotOverwritePKInBeforeSaveHook = true;
|
|
2180
|
+
g.warn("WARNING: {{id}} property cannot be changed from %s to %s for model:%s in {{'before save'}} operation hook", id, inst[pkName], Model.modelName);
|
|
2181
|
+
}
|
|
2182
|
+
data = inst.toObject(false);
|
|
2183
|
+
if (strict) applyStrictCheck(Model, strict, data, inst, validateAndCallConnector);
|
|
2184
|
+
else validateAndCallConnector(null, data);
|
|
2185
|
+
function validateAndCallConnector(err, data) {
|
|
2186
|
+
if (err) return cb(err);
|
|
2187
|
+
data = Model._sanitizeData(data, options);
|
|
2188
|
+
inst.setAttributes(data);
|
|
2189
|
+
let doValidate = true;
|
|
2190
|
+
if (options.validate === void 0) {
|
|
2191
|
+
if (Model.settings.automaticValidation !== void 0) doValidate = Model.settings.automaticValidation;
|
|
2192
|
+
} else doValidate = options.validate;
|
|
2193
|
+
if (doValidate) inst.isValid(function(valid) {
|
|
2194
|
+
if (!valid) return cb(new ValidationError(inst), inst);
|
|
2195
|
+
callConnector();
|
|
2196
|
+
}, data, options);
|
|
2197
|
+
else callConnector();
|
|
2198
|
+
function callConnector() {
|
|
2199
|
+
copyData(data, inst);
|
|
2200
|
+
const typedData = convertSubsetOfPropertiesByType(inst, data);
|
|
2201
|
+
context.data = typedData;
|
|
2202
|
+
function replaceCallback(err, dbResponse) {
|
|
2203
|
+
if (err) return cb(err);
|
|
2204
|
+
if (typeof connector.generateContextData === "function") context = connector.generateContextData(context, dbResponse);
|
|
2205
|
+
const ctx = {
|
|
2206
|
+
Model,
|
|
2207
|
+
hookState,
|
|
2208
|
+
data: context.data,
|
|
2209
|
+
isNewInstance: false,
|
|
2210
|
+
options
|
|
2211
|
+
};
|
|
2212
|
+
Model.notifyObserversOf("loaded", ctx, function(err) {
|
|
2213
|
+
if (err) return cb(err);
|
|
2214
|
+
if (ctx.data[pkName] !== id && !Model._warned.cannotOverwritePKInLoadedHook) {
|
|
2215
|
+
Model._warned.cannotOverwritePKInLoadedHook = true;
|
|
2216
|
+
g.warn("WARNING: {{id}} property cannot be changed from %s to %s for model:%s in {{'loaded'}} operation hook", id, ctx.data[pkName], Model.modelName);
|
|
2217
|
+
}
|
|
2218
|
+
inst.__persisted = true;
|
|
2219
|
+
ctx.data[pkName] = id;
|
|
2220
|
+
inst.setAttributes(ctx.data);
|
|
2221
|
+
const context = {
|
|
2222
|
+
Model,
|
|
2223
|
+
instance: inst,
|
|
2224
|
+
isNewInstance: false,
|
|
2225
|
+
hookState,
|
|
2226
|
+
options
|
|
2227
|
+
};
|
|
2228
|
+
Model.notifyObserversOf("after save", context, function(err) {
|
|
2229
|
+
cb(err, inst);
|
|
2230
|
+
});
|
|
2231
|
+
});
|
|
2232
|
+
}
|
|
2233
|
+
const ctx = {
|
|
2234
|
+
Model,
|
|
2235
|
+
where: byIdQuery(Model, id).where,
|
|
2236
|
+
data: context.data,
|
|
2237
|
+
isNewInstance: false,
|
|
2238
|
+
currentInstance: inst,
|
|
2239
|
+
hookState,
|
|
2240
|
+
options
|
|
2241
|
+
};
|
|
2242
|
+
Model.notifyObserversOf("persist", ctx, function(err) {
|
|
2243
|
+
if (err) return cb(err);
|
|
2244
|
+
context.data = ctx.data;
|
|
2245
|
+
invokeConnectorMethod(connector, "replaceById", Model, [id, Model._forDB(ctx.data)], options, replaceCallback);
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
});
|
|
2250
|
+
return cb.promise;
|
|
2251
|
+
};
|
|
2252
|
+
/**
|
|
2253
|
+
* Update set of attributes.
|
|
2254
|
+
* Performs validation before updating.
|
|
2255
|
+
* NOTE: `patchOrCreate` is an alias.
|
|
2256
|
+
*
|
|
2257
|
+
* @trigger `validation`, `save` and `update` hooks
|
|
2258
|
+
* @param {Object} data Data to update
|
|
2259
|
+
* @param {Object} [options] Options for updateAttributes
|
|
2260
|
+
* @param {Function} cb Callback function called with (err, instance)
|
|
2261
|
+
*/
|
|
2262
|
+
DataAccessObject.prototype.updateAttributes = DataAccessObject.prototype.patchAttributes = function(data, options, cb) {
|
|
2263
|
+
const self = this;
|
|
2264
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
2265
|
+
if (connectionPromise) return connectionPromise;
|
|
2266
|
+
if (options === void 0 && cb === void 0) {
|
|
2267
|
+
if (typeof data === "function") {
|
|
2268
|
+
cb = data;
|
|
2269
|
+
data = void 0;
|
|
2270
|
+
}
|
|
2271
|
+
} else if (cb === void 0) {
|
|
2272
|
+
if (typeof options === "function") {
|
|
2273
|
+
cb = options;
|
|
2274
|
+
options = {};
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
cb = cb || utils.createPromiseCallback();
|
|
2278
|
+
options = options || {};
|
|
2279
|
+
assert(typeof data === "object" && data !== null, "The data argument must be an object");
|
|
2280
|
+
assert(typeof options === "object", "The options argument must be an object");
|
|
2281
|
+
assert(typeof cb === "function", "The cb argument must be a function");
|
|
2282
|
+
const inst = this;
|
|
2283
|
+
const Model = this.constructor;
|
|
2284
|
+
const connector = inst.getConnector();
|
|
2285
|
+
assert(typeof connector.updateAttributes === "function", "updateAttributes() must be implemented by the connector");
|
|
2286
|
+
if (isPKMissing(Model, cb)) return cb.promise;
|
|
2287
|
+
const allowExtendedOperators = Model._allowExtendedOperators(options);
|
|
2288
|
+
const strict = this.__strict;
|
|
2289
|
+
const hookState = {};
|
|
2290
|
+
if (data instanceof Model) data = data.toObject(false);
|
|
2291
|
+
data = Model._sanitizeData(data, options);
|
|
2292
|
+
const idNames = Model.definition.idNames();
|
|
2293
|
+
for (let i = 0, n = idNames.length; i < n; i++) {
|
|
2294
|
+
const idName = idNames[i];
|
|
2295
|
+
if (data[idName] !== void 0 && !idEquals(data[idName], inst[idName])) {
|
|
2296
|
+
const err = new Error(g.f("{{id}} cannot be updated from %s to %s when {{forceId}} is set to true", inst[idName], data[idName]));
|
|
2297
|
+
err.statusCode = 400;
|
|
2298
|
+
process.nextTick(function() {
|
|
2299
|
+
cb(err);
|
|
2300
|
+
});
|
|
2301
|
+
return cb.promise;
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
let context = {
|
|
2305
|
+
Model,
|
|
2306
|
+
where: byIdQuery(Model, getIdValue(Model, inst)).where,
|
|
2307
|
+
data,
|
|
2308
|
+
currentInstance: inst,
|
|
2309
|
+
hookState,
|
|
2310
|
+
options
|
|
2311
|
+
};
|
|
2312
|
+
Model.notifyObserversOf("before save", context, function(err, ctx) {
|
|
2313
|
+
if (err) return cb(err);
|
|
2314
|
+
data = ctx.data;
|
|
2315
|
+
if (strict && !allowExtendedOperators) applyStrictCheck(self.constructor, strict, data, inst, validateAndSave);
|
|
2316
|
+
else validateAndSave(null, data);
|
|
2317
|
+
function validateAndSave(err, data) {
|
|
2318
|
+
if (err) return cb(err);
|
|
2319
|
+
let doValidate = true;
|
|
2320
|
+
if (options.validate === void 0) {
|
|
2321
|
+
if (Model.settings.automaticValidation !== void 0) doValidate = Model.settings.automaticValidation;
|
|
2322
|
+
} else doValidate = options.validate;
|
|
2323
|
+
try {
|
|
2324
|
+
inst.setAttributes(data);
|
|
2325
|
+
} catch (err) {
|
|
2326
|
+
return cb(err);
|
|
2327
|
+
}
|
|
2328
|
+
if (doValidate) inst.isValid(function(valid) {
|
|
2329
|
+
if (!valid) {
|
|
2330
|
+
cb(new ValidationError(inst), inst);
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
triggerSave();
|
|
2334
|
+
}, data, options);
|
|
2335
|
+
else triggerSave();
|
|
2336
|
+
function triggerSave() {
|
|
2337
|
+
inst.trigger("save", function(saveDone) {
|
|
2338
|
+
inst.trigger("update", function(done) {
|
|
2339
|
+
copyData(data, inst);
|
|
2340
|
+
const typedData = convertSubsetOfPropertiesByType(inst, data);
|
|
2341
|
+
context.data = Model._sanitizeData(typedData, options);
|
|
2342
|
+
function updateAttributesCallback(err, dbResponse) {
|
|
2343
|
+
if (err) return cb(err);
|
|
2344
|
+
if (typeof connector.generateContextData === "function") context = connector.generateContextData(context, dbResponse);
|
|
2345
|
+
const ctx = {
|
|
2346
|
+
Model,
|
|
2347
|
+
data: context.data,
|
|
2348
|
+
hookState,
|
|
2349
|
+
options,
|
|
2350
|
+
isNewInstance: false
|
|
2351
|
+
};
|
|
2352
|
+
Model.notifyObserversOf("loaded", ctx, function(err) {
|
|
2353
|
+
if (err) return cb(err);
|
|
2354
|
+
inst.__persisted = true;
|
|
2355
|
+
if (Model.settings.updateOnLoad) inst.setAttributes(ctx.data);
|
|
2356
|
+
done.call(inst, function() {
|
|
2357
|
+
saveDone.call(inst, function() {
|
|
2358
|
+
if (err) return cb(err, inst);
|
|
2359
|
+
const context = {
|
|
2360
|
+
Model,
|
|
2361
|
+
instance: inst,
|
|
2362
|
+
isNewInstance: false,
|
|
2363
|
+
hookState,
|
|
2364
|
+
options
|
|
2365
|
+
};
|
|
2366
|
+
Model.notifyObserversOf("after save", context, function(err) {
|
|
2367
|
+
cb(err, inst);
|
|
2368
|
+
});
|
|
2369
|
+
});
|
|
2370
|
+
});
|
|
2371
|
+
});
|
|
2372
|
+
}
|
|
2373
|
+
const ctx = {
|
|
2374
|
+
Model,
|
|
2375
|
+
where: byIdQuery(Model, getIdValue(Model, inst)).where,
|
|
2376
|
+
data: context.data,
|
|
2377
|
+
currentInstance: inst,
|
|
2378
|
+
isNewInstance: false,
|
|
2379
|
+
hookState,
|
|
2380
|
+
options
|
|
2381
|
+
};
|
|
2382
|
+
Model.notifyObserversOf("persist", ctx, function(err) {
|
|
2383
|
+
if (err) return cb(err);
|
|
2384
|
+
context.data = ctx.data;
|
|
2385
|
+
invokeConnectorMethod(connector, "updateAttributes", Model, [getIdValue(Model, inst), Model._forDB(ctx.data)], options, updateAttributesCallback);
|
|
2386
|
+
});
|
|
2387
|
+
}, data, cb);
|
|
2388
|
+
}, data, cb);
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
});
|
|
2392
|
+
return cb.promise;
|
|
2393
|
+
};
|
|
2394
|
+
/**
|
|
2395
|
+
* Reload object from persistence
|
|
2396
|
+
* Requires `id` member of `object` to be able to call `find`
|
|
2397
|
+
* @param {Function} cb Called with (err, instance) arguments
|
|
2398
|
+
* @private
|
|
2399
|
+
*/
|
|
2400
|
+
DataAccessObject.prototype.reload = function reload(cb) {
|
|
2401
|
+
const connectionPromise = stillConnecting(this.getDataSource(), this, arguments);
|
|
2402
|
+
if (connectionPromise) return connectionPromise;
|
|
2403
|
+
return this.constructor.findById(getIdValue(this.constructor, this), cb);
|
|
2404
|
+
};
|
|
2405
|
+
function defineReadonlyProp(obj, key, value) {
|
|
2406
|
+
Object.defineProperty(obj, key, {
|
|
2407
|
+
writable: false,
|
|
2408
|
+
enumerable: true,
|
|
2409
|
+
configurable: true,
|
|
2410
|
+
value
|
|
2411
|
+
});
|
|
2412
|
+
}
|
|
2413
|
+
const defineScope = require_lib_scope.defineScope;
|
|
2414
|
+
/**
|
|
2415
|
+
* Define a scope for the model class. Scopes enable you to specify commonly-used
|
|
2416
|
+
* queries that you can reference as method calls on a model.
|
|
2417
|
+
*
|
|
2418
|
+
* @param {String} name The scope name
|
|
2419
|
+
* @param {Object} query The query object for DataAccessObject.find()
|
|
2420
|
+
* @param {ModelClass} [targetClass] The model class for the query, default to
|
|
2421
|
+
* the declaring model
|
|
2422
|
+
*/
|
|
2423
|
+
DataAccessObject.scope = function(name, query, targetClass, methods, options) {
|
|
2424
|
+
let cls = this;
|
|
2425
|
+
if (options && options.isStatic === false) cls = cls.prototype;
|
|
2426
|
+
return defineScope(cls, targetClass || cls, name, query, methods, options);
|
|
2427
|
+
};
|
|
2428
|
+
jutil.mixin(DataAccessObject, Inclusion);
|
|
2429
|
+
jutil.mixin(DataAccessObject, Relation);
|
|
2430
|
+
jutil.mixin(DataAccessObject, require_lib_transaction);
|
|
2431
|
+
function PKMissingError(modelName) {
|
|
2432
|
+
this.name = "PKMissingError";
|
|
2433
|
+
this.message = "Primary key is missing for the " + modelName + " model";
|
|
2434
|
+
}
|
|
2435
|
+
PKMissingError.prototype = /* @__PURE__ */ new Error();
|
|
2436
|
+
function isPKMissing(modelClass, cb) {
|
|
2437
|
+
if (modelClass.definition.hasPK()) return false;
|
|
2438
|
+
process.nextTick(function() {
|
|
2439
|
+
cb(new PKMissingError(modelClass.modelName));
|
|
2440
|
+
});
|
|
2441
|
+
return true;
|
|
2442
|
+
}
|
|
2443
|
+
}));
|
|
2444
|
+
//#endregion
|
|
2445
|
+
module.exports = require_dao();
|