@vsaas/loopback 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +91 -0
  3. package/common/models/README.md +109 -0
  4. package/common/models/access-token.json +37 -0
  5. package/common/models/acl.json +17 -0
  6. package/common/models/application.json +130 -0
  7. package/common/models/change.json +25 -0
  8. package/common/models/checkpoint.json +14 -0
  9. package/common/models/email.json +11 -0
  10. package/common/models/key-value-model.json +4 -0
  11. package/common/models/role-mapping.json +26 -0
  12. package/common/models/role.json +30 -0
  13. package/common/models/scope.json +14 -0
  14. package/common/models/user.json +118 -0
  15. package/dist/_virtual/_rolldown/runtime.cjs +32 -0
  16. package/dist/common/models/access-token.cjs +144 -0
  17. package/dist/common/models/access-token2.cjs +43 -0
  18. package/dist/common/models/acl.cjs +428 -0
  19. package/dist/common/models/acl2.cjs +27 -0
  20. package/dist/common/models/application.cjs +100 -0
  21. package/dist/common/models/application2.cjs +118 -0
  22. package/dist/common/models/change.cjs +404 -0
  23. package/dist/common/models/change2.cjs +25 -0
  24. package/dist/common/models/checkpoint.cjs +43 -0
  25. package/dist/common/models/checkpoint2.cjs +18 -0
  26. package/dist/common/models/email.cjs +18 -0
  27. package/dist/common/models/email2.cjs +30 -0
  28. package/dist/common/models/key-value-model.cjs +140 -0
  29. package/dist/common/models/key-value-model2.cjs +14 -0
  30. package/dist/common/models/role-mapping.cjs +57 -0
  31. package/dist/common/models/role-mapping2.cjs +34 -0
  32. package/dist/common/models/role.cjs +396 -0
  33. package/dist/common/models/role2.cjs +38 -0
  34. package/dist/common/models/scope.cjs +30 -0
  35. package/dist/common/models/scope2.cjs +21 -0
  36. package/dist/common/models/user.cjs +810 -0
  37. package/dist/common/models/user2.cjs +118 -0
  38. package/dist/index.cjs +16 -0
  39. package/dist/lib/access-context.cjs +228 -0
  40. package/dist/lib/application.cjs +450 -0
  41. package/dist/lib/builtin-models.cjs +60 -0
  42. package/dist/lib/configure-shared-methods.cjs +41 -0
  43. package/dist/lib/connectors/base-connector.cjs +23 -0
  44. package/dist/lib/connectors/mail-direct-transport.cjs +375 -0
  45. package/dist/lib/connectors/mail-stub-transport.cjs +86 -0
  46. package/dist/lib/connectors/mail.cjs +128 -0
  47. package/dist/lib/connectors/memory.cjs +19 -0
  48. package/dist/lib/current-context.cjs +22 -0
  49. package/dist/lib/globalize.cjs +29 -0
  50. package/dist/lib/loopback.cjs +313 -0
  51. package/dist/lib/model.cjs +1009 -0
  52. package/dist/lib/persisted-model.cjs +1835 -0
  53. package/dist/lib/registry.cjs +291 -0
  54. package/dist/lib/runtime.cjs +25 -0
  55. package/dist/lib/server-app.cjs +231 -0
  56. package/dist/lib/utils.cjs +154 -0
  57. package/dist/package.cjs +124 -0
  58. package/dist/server/middleware/context.cjs +7 -0
  59. package/dist/server/middleware/error-handler.cjs +6 -0
  60. package/dist/server/middleware/favicon.cjs +13 -0
  61. package/dist/server/middleware/rest.cjs +44 -0
  62. package/dist/server/middleware/static.cjs +14 -0
  63. package/dist/server/middleware/status.cjs +28 -0
  64. package/dist/server/middleware/token.cjs +66 -0
  65. package/dist/server/middleware/url-not-found.cjs +20 -0
  66. package/favicon.ico +0 -0
  67. package/package.json +121 -0
  68. package/templates/reset-form.ejs +3 -0
  69. package/templates/verify.ejs +9 -0
@@ -0,0 +1,1835 @@
1
+ "use strict";
2
+ const require_runtime$1 = require("../_virtual/_rolldown/runtime.cjs");
3
+ const require_lib_globalize = require("./globalize.cjs");
4
+ const require_lib_runtime = require("./runtime.cjs");
5
+ const require_lib_utils = require("./utils.cjs");
6
+ //#region src/lib/persisted-model.ts
7
+ /*!
8
+ * Module Dependencies.
9
+ */
10
+ var require_persisted_model = /* @__PURE__ */ require_runtime$1.__commonJSMin(((exports, module) => {
11
+ const g = require_lib_globalize;
12
+ const runtime = require_lib_runtime;
13
+ const assert = require("assert");
14
+ const deprecated = require("depd")("loopback");
15
+ const debug = require("debug")("loopback:persisted-model");
16
+ const PassThrough = require("stream").PassThrough;
17
+ const utils = require_lib_utils;
18
+ const filterNodes = require("loopback-filters");
19
+ const REPLICATION_CHUNK_SIZE = -1;
20
+ module.exports = function(registry) {
21
+ const Model = registry.getModel("Model");
22
+ /**
23
+ * Extends Model with basic query and CRUD support.
24
+ *
25
+ * **Change Event**
26
+ *
27
+ * Listen for model changes using the `change` event.
28
+ *
29
+ * ```js
30
+ * MyPersistedModel.on('changed', function(obj) {
31
+ * console.log(obj) // => the changed model
32
+ * });
33
+ * ```
34
+ *
35
+ * @class PersistedModel
36
+ */
37
+ const PersistedModel = Model.extend("PersistedModel");
38
+ /*!
39
+ * Setup the `PersistedModel` constructor.
40
+ */
41
+ PersistedModel.setup = function setupPersistedModel() {
42
+ Model.setup.call(this);
43
+ const PersistedModel = this;
44
+ if (this.settings.trackChanges) {
45
+ PersistedModel._defineChangeModel();
46
+ PersistedModel.once("dataSourceAttached", function() {
47
+ PersistedModel.enableChangeTracking();
48
+ });
49
+ } else if (this.settings.enableRemoteReplication) PersistedModel._defineChangeModel();
50
+ PersistedModel.setupRemoting();
51
+ };
52
+ /*!
53
+ * Throw an error telling the user that the method is not available and why.
54
+ */
55
+ function throwNotAttached(modelName, methodName) {
56
+ throw new Error(g.f("Cannot call %s.%s(). The %s method has not been setup. The {{PersistedModel}} has not been correctly attached to a {{DataSource}}!", modelName, methodName, methodName));
57
+ }
58
+ /*!
59
+ * Convert null callbacks to 404 error objects.
60
+ * @param {HttpContext} ctx
61
+ * @param {Function} cb
62
+ */
63
+ function convertNullToNotFoundError(ctx, cb) {
64
+ if (ctx.result !== null) return cb();
65
+ const modelName = ctx.method.sharedClass.name;
66
+ const id = ctx.getArgByName("id");
67
+ const msg = g.f("Unknown \"%s\" {{id}} \"%s\".", modelName, id);
68
+ const error = new Error(msg);
69
+ error.statusCode = error.status = 404;
70
+ error.code = "MODEL_NOT_FOUND";
71
+ cb(error);
72
+ }
73
+ /**
74
+ * Create new instance of Model, and save to database.
75
+ *
76
+ * @param {Object|Object[]} [data] Optional data argument. Can be either a single model instance or an array of instances.
77
+ *
78
+ * @callback {Function} callback Callback function called with `cb(err, obj)` signature.
79
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
80
+ * @param {Object} models Model instances or null.
81
+ */
82
+ PersistedModel.create = function(data, callback) {
83
+ throwNotAttached(this.modelName, "create");
84
+ };
85
+ /**
86
+ * Update or insert a model instance
87
+ * @param {Object} data The model instance data to insert.
88
+ * @callback {Function} callback Callback function called with `cb(err, obj)` signature.
89
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
90
+ * @param {Object} model Updated model instance.
91
+ */
92
+ PersistedModel.upsert = PersistedModel.updateOrCreate = PersistedModel.patchOrCreate = function upsert(data, callback) {
93
+ throwNotAttached(this.modelName, "upsert");
94
+ };
95
+ /**
96
+ * Update or insert a model instance based on the search criteria.
97
+ * If there is a single instance retrieved, update the retrieved model.
98
+ * Creates a new model if no model instances were found.
99
+ * Returns an error if multiple instances are found.
100
+ * @param {Object} [where] `where` filter, like
101
+ * ```
102
+ * { key: val, key2: {gt: 'val2'}, ...}
103
+ * ```
104
+ * <br/>see
105
+ * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-other-methods).
106
+ * @param {Object} data The model instance data to insert.
107
+ * @callback {Function} callback Callback function called with `cb(err, obj)` signature.
108
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
109
+ * @param {Object} model Updated model instance.
110
+ */
111
+ PersistedModel.upsertWithWhere = PersistedModel.patchOrCreateWithWhere = function upsertWithWhere(where, data, callback) {
112
+ throwNotAttached(this.modelName, "upsertWithWhere");
113
+ };
114
+ /**
115
+ * Replace or insert a model instance; replace existing record if one is found,
116
+ * such that parameter `data.id` matches `id` of model instance; otherwise,
117
+ * insert a new record.
118
+ * @param {Object} data The model instance data.
119
+ * @options {Object} [options] Options for replaceOrCreate
120
+ * @property {Boolean} validate Perform validation before saving. Default is true.
121
+ * @callback {Function} callback Callback function called with `cb(err, obj)` signature.
122
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
123
+ * @param {Object} model Replaced model instance.
124
+ */
125
+ PersistedModel.replaceOrCreate = function replaceOrCreate(data, callback) {
126
+ throwNotAttached(this.modelName, "replaceOrCreate");
127
+ };
128
+ /**
129
+ * Finds one record matching the optional filter object. If not found, creates
130
+ * the object using the data provided as second argument. In this sense it is
131
+ * the same as `find`, but limited to one object. Returns an object, not
132
+ * collection. If you don't provide the filter object argument, it tries to
133
+ * locate an existing object that matches the `data` argument.
134
+ *
135
+ * @options {Object} [filter] Optional Filter object; see below.
136
+ * @property {String|Object|Array} fields Identify fields to include in return result.
137
+ * <br/>See [Fields filter](http://loopback.io/doc/en/lb2/Fields-filter.html).
138
+ * @property {String|Object|Array} include See PersistedModel.include documentation.
139
+ * <br/>See [Include filter](http://loopback.io/doc/en/lb2/Include-filter.html).
140
+ * @property {Number} limit Maximum number of instances to return.
141
+ * <br/>See [Limit filter](http://loopback.io/doc/en/lb2/Limit-filter.html).
142
+ * @property {String} order Sort order: either "ASC" for ascending or "DESC" for descending.
143
+ * <br/>See [Order filter](http://loopback.io/doc/en/lb2/Order-filter.html).
144
+ * @property {Number} skip Number of results to skip.
145
+ * <br/>See [Skip filter](http://loopback.io/doc/en/lb2/Skip-filter.html).
146
+ * @property {Object} where Where clause, like
147
+ * ```
148
+ * {where: {key: val, key2: {gt: val2}, ...}}
149
+ * ```
150
+ * <br/>See
151
+ * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-queries).
152
+ * @param {Object} data Data to insert if object matching the `where` filter is not found.
153
+ * @callback {Function} callback Callback function called with `cb(err, instance, created)` arguments. Required.
154
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
155
+ * @param {Object} instance Model instance matching the `where` filter, if found.
156
+ * @param {Boolean} created True if the instance does not exist and gets created.
157
+ */
158
+ PersistedModel.findOrCreate = function findOrCreate(query, data, callback) {
159
+ throwNotAttached(this.modelName, "findOrCreate");
160
+ };
161
+ PersistedModel.findOrCreate._delegate = true;
162
+ /**
163
+ * Check whether a model instance exists in database.
164
+ *
165
+ * @param {id} id Identifier of object (primary key value).
166
+ *
167
+ * @callback {Function} callback Callback function called with `(err, exists)` arguments. Required.
168
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
169
+ * @param {Boolean} exists True if the instance with the specified ID exists; false otherwise.
170
+ */
171
+ PersistedModel.exists = function exists(id, cb) {
172
+ throwNotAttached(this.modelName, "exists");
173
+ };
174
+ /**
175
+ * Find object by ID with an optional filter for include/fields.
176
+ *
177
+ * @param {*} id Primary key value
178
+ * @options {Object} [filter] Optional Filter JSON object; see below.
179
+ * @property {String|Object|Array} fields Identify fields to include in return result.
180
+ * <br/>See [Fields filter](http://loopback.io/doc/en/lb2/Fields-filter.html).
181
+ * @property {String|Object|Array} include See PersistedModel.include documentation.
182
+ * <br/>See [Include filter](http://loopback.io/doc/en/lb2/Include-filter.html).
183
+ * @callback {Function} callback Callback function called with `(err, instance)` arguments. Required.
184
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
185
+ * @param {Object} instance Model instance matching the specified ID or null if no instance matches.
186
+ */
187
+ PersistedModel.findById = function findById(id, filter, cb) {
188
+ throwNotAttached(this.modelName, "findById");
189
+ };
190
+ /**
191
+ * Find all model instances that match `filter` specification.
192
+ * See [Querying models](http://loopback.io/doc/en/lb2/Querying-data.html).
193
+ *
194
+ * @options {Object} [filter] Optional Filter JSON object; see below.
195
+ * @property {String|Object|Array} fields Identify fields to include in return result.
196
+ * <br/>See [Fields filter](http://loopback.io/doc/en/lb2/Fields-filter.html).
197
+ * @property {String|Object|Array} include See PersistedModel.include documentation.
198
+ * <br/>See [Include filter](http://loopback.io/doc/en/lb2/Include-filter.html).
199
+ * @property {Number} limit Maximum number of instances to return.
200
+ * <br/>See [Limit filter](http://loopback.io/doc/en/lb2/Limit-filter.html).
201
+ * @property {String} order Sort order: either "ASC" for ascending or "DESC" for descending.
202
+ * <br/>See [Order filter](http://loopback.io/doc/en/lb2/Order-filter.html).
203
+ * @property {Number} skip Number of results to skip.
204
+ * <br/>See [Skip filter](http://loopback.io/doc/en/lb2/Skip-filter.html).
205
+ * @property {Object} where Where clause, like
206
+ * ```
207
+ * { where: { key: val, key2: {gt: 'val2'}, ...} }
208
+ * ```
209
+ * <br/>See
210
+ * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-queries).
211
+ *
212
+ * @callback {Function} callback Callback function called with `(err, returned-instances)` arguments. Required.
213
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
214
+ * @param {Array} models Model instances matching the filter, or null if none found.
215
+ */
216
+ PersistedModel.find = function find(filter, cb) {
217
+ throwNotAttached(this.modelName, "find");
218
+ };
219
+ /**
220
+ * Find one model instance that matches `filter` specification.
221
+ * Same as `find`, but limited to one result;
222
+ * Returns object, not collection.
223
+ *
224
+ * @options {Object} [filter] Optional Filter JSON object; see below.
225
+ * @property {String|Object|Array} fields Identify fields to include in return result.
226
+ * <br/>See [Fields filter](http://loopback.io/doc/en/lb2/Fields-filter.html).
227
+ * @property {String|Object|Array} include See PersistedModel.include documentation.
228
+ * <br/>See [Include filter](http://loopback.io/doc/en/lb2/Include-filter.html).
229
+ * @property {String} order Sort order: either "ASC" for ascending or "DESC" for descending.
230
+ * <br/>See [Order filter](http://loopback.io/doc/en/lb2/Order-filter.html).
231
+ * @property {Number} skip Number of results to skip.
232
+ * <br/>See [Skip filter](http://loopback.io/doc/en/lb2/Skip-filter.html).
233
+ * @property {Object} where Where clause, like
234
+ * ```
235
+ * {where: { key: val, key2: {gt: 'val2'}, ...} }
236
+ * ```
237
+ * <br/>See
238
+ * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-queries).
239
+ *
240
+ * @callback {Function} callback Callback function called with `(err, returned-instance)` arguments. Required.
241
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
242
+ * @param {Array} model First model instance that matches the filter or null if none found.
243
+ */
244
+ PersistedModel.findOne = function findOne(filter, cb) {
245
+ throwNotAttached(this.modelName, "findOne");
246
+ };
247
+ /**
248
+ * Destroy all model instances that match the optional `where` specification.
249
+ *
250
+ * @param {Object} [where] Optional where filter, like:
251
+ * ```
252
+ * {key: val, key2: {gt: 'val2'}, ...}
253
+ * ```
254
+ * <br/>See
255
+ * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-other-methods).
256
+ *
257
+ * @callback {Function} callback Optional callback function called with `(err, info)` arguments.
258
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
259
+ * @param {Object} info Additional information about the command outcome.
260
+ * @param {Number} info.count Number of instances (rows, documents) destroyed.
261
+ */
262
+ PersistedModel.destroyAll = function destroyAll(where, cb) {
263
+ throwNotAttached(this.modelName, "destroyAll");
264
+ };
265
+ /**
266
+ * Alias for `destroyAll`
267
+ */
268
+ PersistedModel.remove = PersistedModel.destroyAll;
269
+ /**
270
+ * Alias for `destroyAll`
271
+ */
272
+ PersistedModel.deleteAll = PersistedModel.destroyAll;
273
+ /**
274
+ * Update multiple instances that match the where clause.
275
+ *
276
+ * Example:
277
+ *
278
+ *```js
279
+ * Employee.updateAll({managerId: 'x001'}, {managerId: 'x002'}, function(err, info) {
280
+ * ...
281
+ * });
282
+ * ```
283
+ *
284
+ * @param {Object} [where] Optional `where` filter, like
285
+ * ```
286
+ * { key: val, key2: {gt: 'val2'}, ...}
287
+ * ```
288
+ * <br/>see
289
+ * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-other-methods).
290
+ * @param {Object} data Object containing data to replace matching instances, if any.
291
+ *
292
+ * @callback {Function} callback Callback function called with `(err, info)` arguments. Required.
293
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
294
+ * @param {Object} info Additional information about the command outcome.
295
+ * @param {Number} info.count Number of instances (rows, documents) updated.
296
+ *
297
+ */
298
+ PersistedModel.updateAll = function updateAll(where, data, cb) {
299
+ throwNotAttached(this.modelName, "updateAll");
300
+ };
301
+ /**
302
+ * Alias for updateAll.
303
+ */
304
+ PersistedModel.update = PersistedModel.updateAll;
305
+ /**
306
+ * Destroy model instance with the specified ID.
307
+ * @param {*} id The ID value of model instance to delete.
308
+ * @callback {Function} callback Callback function called with `(err)` arguments. Required.
309
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
310
+ */
311
+ PersistedModel.destroyById = function deleteById(id, cb) {
312
+ throwNotAttached(this.modelName, "deleteById");
313
+ };
314
+ /**
315
+ * Alias for destroyById.
316
+ */
317
+ PersistedModel.removeById = PersistedModel.destroyById;
318
+ /**
319
+ * Alias for destroyById.
320
+ */
321
+ PersistedModel.deleteById = PersistedModel.destroyById;
322
+ /**
323
+ * Return the number of records that match the optional "where" filter.
324
+ * @param {Object} [where] Optional where filter, like
325
+ * ```
326
+ * { key: val, key2: {gt: 'val2'}, ...}
327
+ * ```
328
+ * <br/>See
329
+ * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-other-methods).
330
+ * @callback {Function} callback Callback function called with `(err, count)` arguments. Required.
331
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
332
+ * @param {Number} count Number of instances.
333
+ */
334
+ PersistedModel.count = function(where, cb) {
335
+ throwNotAttached(this.modelName, "count");
336
+ };
337
+ /**
338
+ * Save model instance. If the instance doesn't have an ID, then calls [create](#persistedmodelcreatedata-cb) instead.
339
+ * Triggers: validate, save, update, or create.
340
+ * @options {Object} [options] See below.
341
+ * @property {Boolean} validate Perform validation before saving. Default is true.
342
+ * @property {Boolean} throws If true, throw a validation error; WARNING: This can crash Node.
343
+ * If false, report the error via callback. Default is false.
344
+ * @callback {Function} callback Optional callback function called with `(err, obj)` arguments.
345
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
346
+ * @param {Object} instance Model instance saved or created.
347
+ */
348
+ PersistedModel.prototype.save = function(options, callback) {
349
+ const Model = this.constructor;
350
+ if (typeof options == "function") {
351
+ callback = options;
352
+ options = {};
353
+ }
354
+ callback = callback || function() {};
355
+ options = options || {};
356
+ if (!("validate" in options)) options.validate = true;
357
+ if (!("throws" in options)) options.throws = false;
358
+ const inst = this;
359
+ const data = inst.toObject(true);
360
+ if (!this.getId()) return Model.create(this, callback);
361
+ if (!options.validate) return save();
362
+ inst.isValid(function(valid) {
363
+ if (valid) save();
364
+ else {
365
+ const err = new Model.ValidationError(inst);
366
+ if (options.throws) throw err;
367
+ callback(err, inst);
368
+ }
369
+ });
370
+ function save() {
371
+ inst.trigger("save", function(saveDone) {
372
+ inst.trigger("update", function(updateDone) {
373
+ Model.upsert(inst, function(err) {
374
+ inst._initProperties(data);
375
+ updateDone.call(inst, function() {
376
+ saveDone.call(inst, function() {
377
+ callback(err, inst);
378
+ });
379
+ });
380
+ });
381
+ }, data);
382
+ }, data);
383
+ }
384
+ };
385
+ /**
386
+ * Determine if the data model is new.
387
+ * @returns {Boolean} Returns true if the data model is new; false otherwise.
388
+ */
389
+ PersistedModel.prototype.isNewRecord = function() {
390
+ throwNotAttached(this.constructor.modelName, "isNewRecord");
391
+ };
392
+ /**
393
+ * Deletes the model from persistence.
394
+ * Triggers `destroy` hook (async) before and after destroying object.
395
+ * @param {Function} callback Callback function.
396
+ */
397
+ PersistedModel.prototype.destroy = function(cb) {
398
+ throwNotAttached(this.constructor.modelName, "destroy");
399
+ };
400
+ /**
401
+ * Alias for destroy.
402
+ * @header PersistedModel.remove
403
+ */
404
+ PersistedModel.prototype.remove = PersistedModel.prototype.destroy;
405
+ /**
406
+ * Alias for destroy.
407
+ * @header PersistedModel.delete
408
+ */
409
+ PersistedModel.prototype.delete = PersistedModel.prototype.destroy;
410
+ PersistedModel.prototype.destroy._delegate = true;
411
+ /**
412
+ * Update a single attribute.
413
+ * Equivalent to `updateAttributes({name: 'value'}, cb)`
414
+ *
415
+ * @param {String} name Name of property.
416
+ * @param {Mixed} value Value of property.
417
+ * @callback {Function} callback Callback function called with `(err, instance)` arguments. Required.
418
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
419
+ * @param {Object} instance Updated instance.
420
+ */
421
+ PersistedModel.prototype.updateAttribute = function updateAttribute(name, value, callback) {
422
+ throwNotAttached(this.constructor.modelName, "updateAttribute");
423
+ };
424
+ /**
425
+ * Update set of attributes. Performs validation before updating.
426
+ *
427
+ * Triggers: `validation`, `save` and `update` hooks
428
+ * @param {Object} data Data to update.
429
+ * @callback {Function} callback Callback function called with `(err, instance)` arguments. Required.
430
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
431
+ * @param {Object} instance Updated instance.
432
+ */
433
+ PersistedModel.prototype.updateAttributes = PersistedModel.prototype.patchAttributes = function updateAttributes(data, cb) {
434
+ throwNotAttached(this.modelName, "updateAttributes");
435
+ };
436
+ /**
437
+ * Replace attributes for a model instance and persist it into the datasource.
438
+ * Performs validation before replacing.
439
+ *
440
+ * @param {Object} data Data to replace.
441
+ * @options {Object} [options] Options for replace
442
+ * @property {Boolean} validate Perform validation before saving. Default is true.
443
+ * @callback {Function} callback Callback function called with `(err, instance)` arguments.
444
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
445
+ * @param {Object} instance Replaced instance.
446
+ */
447
+ PersistedModel.prototype.replaceAttributes = function replaceAttributes(data, cb) {
448
+ throwNotAttached(this.modelName, "replaceAttributes");
449
+ };
450
+ /**
451
+ * Replace attributes for a model instance whose id is the first input
452
+ * argument and persist it into the datasource.
453
+ * Performs validation before replacing.
454
+ *
455
+ * @param {*} id The ID value of model instance to replace.
456
+ * @param {Object} data Data to replace.
457
+ * @options {Object} [options] Options for replace
458
+ * @property {Boolean} validate Perform validation before saving. Default is true.
459
+ * @callback {Function} callback Callback function called with `(err, instance)` arguments.
460
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
461
+ * @param {Object} instance Replaced instance.
462
+ */
463
+ PersistedModel.replaceById = function replaceById(id, data, cb) {
464
+ throwNotAttached(this.modelName, "replaceById");
465
+ };
466
+ /**
467
+ * Reload object from persistence. Requires `id` member of `object` to be able to call `find`.
468
+ * @callback {Function} callback Callback function called with `(err, instance)` arguments. Required.
469
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
470
+ * @param {Object} instance Model instance.
471
+ */
472
+ PersistedModel.prototype.reload = function reload(callback) {
473
+ throwNotAttached(this.constructor.modelName, "reload");
474
+ };
475
+ /**
476
+ * Set the correct `id` property for the `PersistedModel`. Uses the `setId` method if the model is attached to
477
+ * connector that defines it. Otherwise, uses the default lookup.
478
+ * Override this method to handle complex IDs.
479
+ *
480
+ * @param {*} val The `id` value. Will be converted to the type that the `id` property specifies.
481
+ */
482
+ PersistedModel.prototype.setId = function(val) {
483
+ this.getDataSource();
484
+ this[this.getIdName()] = val;
485
+ };
486
+ /**
487
+ * Get the `id` value for the `PersistedModel`.
488
+ *
489
+ * @returns {*} The `id` value
490
+ */
491
+ PersistedModel.prototype.getId = function() {
492
+ const data = this.toObject();
493
+ if (!data) return;
494
+ return data[this.getIdName()];
495
+ };
496
+ /**
497
+ * Get the `id` property name of the constructor.
498
+ *
499
+ * @returns {String} The `id` property name
500
+ */
501
+ PersistedModel.prototype.getIdName = function() {
502
+ return this.constructor.getIdName();
503
+ };
504
+ /**
505
+ * Get the `id` property name of the constructor.
506
+ *
507
+ * @returns {String} The `id` property name
508
+ */
509
+ PersistedModel.getIdName = function() {
510
+ const Model = this;
511
+ const ds = Model.getDataSource();
512
+ if (ds.idName) return ds.idName(Model.modelName);
513
+ else return "id";
514
+ };
515
+ PersistedModel.setupRemoting = function() {
516
+ const PersistedModel = this;
517
+ const typeName = PersistedModel.modelName;
518
+ const options = PersistedModel.settings;
519
+ const updateOnlyProps = this.getUpdateOnlyProperties ? this.getUpdateOnlyProperties() : false;
520
+ const hasUpdateOnlyProps = updateOnlyProps && updateOnlyProps.length > 0;
521
+ options.replaceOnPUT = options.replaceOnPUT !== false;
522
+ function setRemoting(scope, name, options) {
523
+ const fn = scope[name];
524
+ fn._delegate = true;
525
+ options.isStatic = scope === PersistedModel;
526
+ PersistedModel.remoteMethod(name, options);
527
+ }
528
+ setRemoting(PersistedModel, "create", {
529
+ description: "Create a new instance of the model and persist it into the data source.",
530
+ accessType: "WRITE",
531
+ accepts: [{
532
+ arg: "data",
533
+ type: "object",
534
+ model: typeName,
535
+ allowArray: true,
536
+ createOnlyInstance: hasUpdateOnlyProps,
537
+ description: "Model instance data",
538
+ http: { source: "body" }
539
+ }, {
540
+ arg: "options",
541
+ type: "object",
542
+ http: "optionsFromRequest"
543
+ }],
544
+ returns: {
545
+ arg: "data",
546
+ type: typeName,
547
+ root: true
548
+ },
549
+ http: {
550
+ verb: "post",
551
+ path: "/"
552
+ }
553
+ });
554
+ const upsertOptions = {
555
+ aliases: ["upsert", "updateOrCreate"],
556
+ description: "Patch an existing model instance or insert a new one into the data source.",
557
+ accessType: "WRITE",
558
+ accepts: [{
559
+ arg: "data",
560
+ type: "object",
561
+ model: typeName,
562
+ http: { source: "body" },
563
+ description: "Model instance data"
564
+ }, {
565
+ arg: "options",
566
+ type: "object",
567
+ http: "optionsFromRequest"
568
+ }],
569
+ returns: {
570
+ arg: "data",
571
+ type: typeName,
572
+ root: true
573
+ },
574
+ http: [{
575
+ verb: "patch",
576
+ path: "/"
577
+ }]
578
+ };
579
+ if (!options.replaceOnPUT) upsertOptions.http.unshift({
580
+ verb: "put",
581
+ path: "/"
582
+ });
583
+ setRemoting(PersistedModel, "patchOrCreate", upsertOptions);
584
+ const replaceOrCreateOptions = {
585
+ description: "Replace an existing model instance or insert a new one into the data source.",
586
+ accessType: "WRITE",
587
+ accepts: [{
588
+ arg: "data",
589
+ type: "object",
590
+ model: typeName,
591
+ http: { source: "body" },
592
+ description: "Model instance data"
593
+ }, {
594
+ arg: "options",
595
+ type: "object",
596
+ http: "optionsFromRequest"
597
+ }],
598
+ returns: {
599
+ arg: "data",
600
+ type: typeName,
601
+ root: true
602
+ },
603
+ http: [{
604
+ verb: "post",
605
+ path: "/replaceOrCreate"
606
+ }]
607
+ };
608
+ if (options.replaceOnPUT) replaceOrCreateOptions.http.push({
609
+ verb: "put",
610
+ path: "/"
611
+ });
612
+ setRemoting(PersistedModel, "replaceOrCreate", replaceOrCreateOptions);
613
+ setRemoting(PersistedModel, "upsertWithWhere", {
614
+ aliases: ["patchOrCreateWithWhere"],
615
+ description: "Update an existing model instance or insert a new one into the data source based on the where criteria.",
616
+ accessType: "WRITE",
617
+ accepts: [
618
+ {
619
+ arg: "where",
620
+ type: "object",
621
+ http: { source: "query" },
622
+ description: "Criteria to match model instances"
623
+ },
624
+ {
625
+ arg: "data",
626
+ type: "object",
627
+ model: typeName,
628
+ http: { source: "body" },
629
+ description: "An object of model property name/value pairs"
630
+ },
631
+ {
632
+ arg: "options",
633
+ type: "object",
634
+ http: "optionsFromRequest"
635
+ }
636
+ ],
637
+ returns: {
638
+ arg: "data",
639
+ type: typeName,
640
+ root: true
641
+ },
642
+ http: {
643
+ verb: "post",
644
+ path: "/upsertWithWhere"
645
+ }
646
+ });
647
+ setRemoting(PersistedModel, "exists", {
648
+ description: "Check whether a model instance exists in the data source.",
649
+ accessType: "READ",
650
+ accepts: [{
651
+ arg: "id",
652
+ type: "any",
653
+ description: "Model id",
654
+ required: true,
655
+ http: { source: "path" }
656
+ }, {
657
+ arg: "options",
658
+ type: "object",
659
+ http: "optionsFromRequest"
660
+ }],
661
+ returns: {
662
+ arg: "exists",
663
+ type: "boolean"
664
+ },
665
+ http: [{
666
+ verb: "get",
667
+ path: "/:id/exists"
668
+ }, {
669
+ verb: "head",
670
+ path: "/:id"
671
+ }],
672
+ rest: { after: function(ctx, cb) {
673
+ if (ctx.req.method === "GET") return cb();
674
+ if (!ctx.result.exists) {
675
+ const modelName = ctx.method.sharedClass.name;
676
+ const id = ctx.getArgByName("id");
677
+ const msg = "Unknown \"" + modelName + "\" id \"" + id + "\".";
678
+ const error = new Error(msg);
679
+ error.statusCode = error.status = 404;
680
+ error.code = "MODEL_NOT_FOUND";
681
+ cb(error);
682
+ } else cb();
683
+ } }
684
+ });
685
+ setRemoting(PersistedModel, "findById", {
686
+ description: "Find a model instance by {{id}} from the data source.",
687
+ accessType: "READ",
688
+ accepts: [
689
+ {
690
+ arg: "id",
691
+ type: "any",
692
+ description: "Model id",
693
+ required: true,
694
+ http: { source: "path" }
695
+ },
696
+ {
697
+ arg: "filter",
698
+ type: "object",
699
+ description: "Filter defining fields and include - must be a JSON-encoded string ({\"something\":\"value\"})"
700
+ },
701
+ {
702
+ arg: "options",
703
+ type: "object",
704
+ http: "optionsFromRequest"
705
+ }
706
+ ],
707
+ returns: {
708
+ arg: "data",
709
+ type: typeName,
710
+ root: true
711
+ },
712
+ http: {
713
+ verb: "get",
714
+ path: "/:id"
715
+ },
716
+ rest: { after: convertNullToNotFoundError }
717
+ });
718
+ const replaceByIdOptions = {
719
+ description: "Replace attributes for a model instance and persist it into the data source.",
720
+ accessType: "WRITE",
721
+ accepts: [
722
+ {
723
+ arg: "id",
724
+ type: "any",
725
+ description: "Model id",
726
+ required: true,
727
+ http: { source: "path" }
728
+ },
729
+ {
730
+ arg: "data",
731
+ type: "object",
732
+ model: typeName,
733
+ http: { source: "body" },
734
+ description: "Model instance data"
735
+ },
736
+ {
737
+ arg: "options",
738
+ type: "object",
739
+ http: "optionsFromRequest"
740
+ }
741
+ ],
742
+ returns: {
743
+ arg: "data",
744
+ type: typeName,
745
+ root: true
746
+ },
747
+ http: [{
748
+ verb: "post",
749
+ path: "/:id/replace"
750
+ }]
751
+ };
752
+ if (options.replaceOnPUT) replaceByIdOptions.http.push({
753
+ verb: "put",
754
+ path: "/:id"
755
+ });
756
+ setRemoting(PersistedModel, "replaceById", replaceByIdOptions);
757
+ setRemoting(PersistedModel, "find", {
758
+ description: "Find all instances of the model matched by filter from the data source.",
759
+ accessType: "READ",
760
+ accepts: [{
761
+ arg: "filter",
762
+ type: "object",
763
+ description: "Filter defining fields, where, include, order, offset, and limit - must be a JSON-encoded string (`{\"where\":{\"something\":\"value\"}}`). See https://loopback.io/doc/en/lb3/Querying-data.html#using-stringified-json-in-rest-queries for more details."
764
+ }, {
765
+ arg: "options",
766
+ type: "object",
767
+ http: "optionsFromRequest"
768
+ }],
769
+ returns: {
770
+ arg: "data",
771
+ type: [typeName],
772
+ root: true
773
+ },
774
+ http: {
775
+ verb: "get",
776
+ path: "/"
777
+ }
778
+ });
779
+ setRemoting(PersistedModel, "findOne", {
780
+ description: "Find first instance of the model matched by filter from the data source.",
781
+ accessType: "READ",
782
+ accepts: [{
783
+ arg: "filter",
784
+ type: "object",
785
+ description: "Filter defining fields, where, include, order, offset, and limit - must be a JSON-encoded string (`{\"where\":{\"something\":\"value\"}}`). See https://loopback.io/doc/en/lb3/Querying-data.html#using-stringified-json-in-rest-queries for more details."
786
+ }, {
787
+ arg: "options",
788
+ type: "object",
789
+ http: "optionsFromRequest"
790
+ }],
791
+ returns: {
792
+ arg: "data",
793
+ type: typeName,
794
+ root: true
795
+ },
796
+ http: {
797
+ verb: "get",
798
+ path: "/findOne"
799
+ },
800
+ rest: { after: convertNullToNotFoundError }
801
+ });
802
+ setRemoting(PersistedModel, "destroyAll", {
803
+ description: "Delete all matching records.",
804
+ accessType: "WRITE",
805
+ accepts: [{
806
+ arg: "where",
807
+ type: "object",
808
+ description: "filter.where object"
809
+ }, {
810
+ arg: "options",
811
+ type: "object",
812
+ http: "optionsFromRequest"
813
+ }],
814
+ returns: {
815
+ arg: "count",
816
+ type: "object",
817
+ description: "The number of instances deleted",
818
+ root: true
819
+ },
820
+ http: {
821
+ verb: "del",
822
+ path: "/"
823
+ },
824
+ shared: false
825
+ });
826
+ setRemoting(PersistedModel, "updateAll", {
827
+ aliases: ["update"],
828
+ description: "Update instances of the model matched by {{where}} from the data source.",
829
+ accessType: "WRITE",
830
+ accepts: [
831
+ {
832
+ arg: "where",
833
+ type: "object",
834
+ http: { source: "query" },
835
+ description: "Criteria to match model instances"
836
+ },
837
+ {
838
+ arg: "data",
839
+ type: "object",
840
+ model: typeName,
841
+ http: { source: "body" },
842
+ description: "An object of model property name/value pairs"
843
+ },
844
+ {
845
+ arg: "options",
846
+ type: "object",
847
+ http: "optionsFromRequest"
848
+ }
849
+ ],
850
+ returns: {
851
+ arg: "info",
852
+ description: "Information related to the outcome of the operation",
853
+ type: { count: {
854
+ type: "number",
855
+ description: "The number of instances updated"
856
+ } },
857
+ root: true
858
+ },
859
+ http: {
860
+ verb: "post",
861
+ path: "/update"
862
+ }
863
+ });
864
+ setRemoting(PersistedModel, "deleteById", {
865
+ aliases: ["destroyById", "removeById"],
866
+ description: "Delete a model instance by {{id}} from the data source.",
867
+ accessType: "WRITE",
868
+ accepts: [{
869
+ arg: "id",
870
+ type: "any",
871
+ description: "Model id",
872
+ required: true,
873
+ http: { source: "path" }
874
+ }, {
875
+ arg: "options",
876
+ type: "object",
877
+ http: "optionsFromRequest"
878
+ }],
879
+ http: {
880
+ verb: "del",
881
+ path: "/:id"
882
+ },
883
+ returns: {
884
+ arg: "count",
885
+ type: "object",
886
+ root: true
887
+ }
888
+ });
889
+ setRemoting(PersistedModel, "count", {
890
+ description: "Count instances of the model matched by where from the data source.",
891
+ accessType: "READ",
892
+ accepts: [{
893
+ arg: "where",
894
+ type: "object",
895
+ description: "Criteria to match model instances"
896
+ }, {
897
+ arg: "options",
898
+ type: "object",
899
+ http: "optionsFromRequest"
900
+ }],
901
+ returns: {
902
+ arg: "count",
903
+ type: "number"
904
+ },
905
+ http: {
906
+ verb: "get",
907
+ path: "/count"
908
+ }
909
+ });
910
+ const updateAttributesOptions = {
911
+ aliases: ["updateAttributes"],
912
+ description: "Patch attributes for a model instance and persist it into the data source.",
913
+ accessType: "WRITE",
914
+ accepts: [{
915
+ arg: "data",
916
+ type: "object",
917
+ model: typeName,
918
+ http: { source: "body" },
919
+ description: "An object of model property name/value pairs"
920
+ }, {
921
+ arg: "options",
922
+ type: "object",
923
+ http: "optionsFromRequest"
924
+ }],
925
+ returns: {
926
+ arg: "data",
927
+ type: typeName,
928
+ root: true
929
+ },
930
+ http: [{
931
+ verb: "patch",
932
+ path: "/"
933
+ }]
934
+ };
935
+ setRemoting(PersistedModel.prototype, "patchAttributes", updateAttributesOptions);
936
+ if (!options.replaceOnPUT) updateAttributesOptions.http.unshift({
937
+ verb: "put",
938
+ path: "/"
939
+ });
940
+ if (options.trackChanges || options.enableRemoteReplication) {
941
+ setRemoting(PersistedModel, "diff", {
942
+ description: "Get a set of deltas and conflicts since the given checkpoint.",
943
+ accessType: "READ",
944
+ accepts: [{
945
+ arg: "since",
946
+ type: "number",
947
+ description: "Find deltas since this checkpoint"
948
+ }, {
949
+ arg: "remoteChanges",
950
+ type: "array",
951
+ description: "an array of change objects",
952
+ http: { source: "body" }
953
+ }],
954
+ returns: {
955
+ arg: "result",
956
+ type: "object",
957
+ root: true
958
+ },
959
+ http: {
960
+ verb: "post",
961
+ path: "/diff"
962
+ }
963
+ });
964
+ setRemoting(PersistedModel, "changes", {
965
+ description: "Get the changes to a model since a given checkpoint.Provide a filter object to reduce the number of results returned.",
966
+ accessType: "READ",
967
+ accepts: [{
968
+ arg: "since",
969
+ type: "number",
970
+ description: "Only return changes since this checkpoint"
971
+ }, {
972
+ arg: "filter",
973
+ type: "object",
974
+ description: "Only include changes that match this filter"
975
+ }],
976
+ returns: {
977
+ arg: "changes",
978
+ type: "array",
979
+ root: true
980
+ },
981
+ http: {
982
+ verb: "get",
983
+ path: "/changes"
984
+ }
985
+ });
986
+ setRemoting(PersistedModel, "checkpoint", {
987
+ description: "Create a checkpoint.",
988
+ accessType: "REPLICATE",
989
+ returns: {
990
+ arg: "checkpoint",
991
+ type: "object",
992
+ root: true
993
+ },
994
+ http: {
995
+ verb: "post",
996
+ path: "/checkpoint"
997
+ }
998
+ });
999
+ setRemoting(PersistedModel, "currentCheckpoint", {
1000
+ description: "Get the current checkpoint.",
1001
+ accessType: "READ",
1002
+ returns: {
1003
+ arg: "checkpoint",
1004
+ type: "object",
1005
+ root: true
1006
+ },
1007
+ http: {
1008
+ verb: "get",
1009
+ path: "/checkpoint"
1010
+ }
1011
+ });
1012
+ setRemoting(PersistedModel, "createUpdates", {
1013
+ description: "Create an update list from a delta list.",
1014
+ accessType: "READ",
1015
+ accepts: {
1016
+ arg: "deltas",
1017
+ type: "array",
1018
+ http: { source: "body" }
1019
+ },
1020
+ returns: {
1021
+ arg: "updates",
1022
+ type: "array",
1023
+ root: true
1024
+ },
1025
+ http: {
1026
+ verb: "post",
1027
+ path: "/create-updates"
1028
+ }
1029
+ });
1030
+ setRemoting(PersistedModel, "bulkUpdate", {
1031
+ description: "Run multiple updates at once. Note: this is not atomic.",
1032
+ accessType: "WRITE",
1033
+ accepts: {
1034
+ arg: "updates",
1035
+ type: "array"
1036
+ },
1037
+ http: {
1038
+ verb: "post",
1039
+ path: "/bulk-update"
1040
+ }
1041
+ });
1042
+ setRemoting(PersistedModel, "findLastChange", {
1043
+ description: "Get the most recent change record for this instance.",
1044
+ accessType: "READ",
1045
+ accepts: {
1046
+ arg: "id",
1047
+ type: "any",
1048
+ required: true,
1049
+ http: { source: "path" },
1050
+ description: "Model id"
1051
+ },
1052
+ returns: {
1053
+ arg: "result",
1054
+ type: this.Change.modelName,
1055
+ root: true
1056
+ },
1057
+ http: {
1058
+ verb: "get",
1059
+ path: "/:id/changes/last"
1060
+ }
1061
+ });
1062
+ setRemoting(PersistedModel, "updateLastChange", {
1063
+ description: "Update the properties of the most recent change record kept for this instance.",
1064
+ accessType: "WRITE",
1065
+ accepts: [{
1066
+ arg: "id",
1067
+ type: "any",
1068
+ required: true,
1069
+ http: { source: "path" },
1070
+ description: "Model id"
1071
+ }, {
1072
+ arg: "data",
1073
+ type: "object",
1074
+ model: typeName,
1075
+ http: { source: "body" },
1076
+ description: "An object of Change property name/value pairs"
1077
+ }],
1078
+ returns: {
1079
+ arg: "result",
1080
+ type: this.Change.modelName,
1081
+ root: true
1082
+ },
1083
+ http: {
1084
+ verb: "put",
1085
+ path: "/:id/changes/last"
1086
+ }
1087
+ });
1088
+ }
1089
+ setRemoting(PersistedModel, "createChangeStream", {
1090
+ description: "Create a change stream.",
1091
+ accessType: "READ",
1092
+ http: [{
1093
+ verb: "post",
1094
+ path: "/change-stream"
1095
+ }, {
1096
+ verb: "get",
1097
+ path: "/change-stream"
1098
+ }],
1099
+ accepts: {
1100
+ arg: "options",
1101
+ type: "object"
1102
+ },
1103
+ returns: {
1104
+ arg: "changes",
1105
+ type: "ReadableStream",
1106
+ json: true
1107
+ }
1108
+ });
1109
+ };
1110
+ /**
1111
+ * Get a set of deltas and conflicts since the given checkpoint.
1112
+ *
1113
+ * See [Change.diff()](#change-diff) for details.
1114
+ *
1115
+ * @param {Number} since Find deltas since this checkpoint.
1116
+ * @param {Array} remoteChanges An array of change objects.
1117
+ * @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
1118
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
1119
+ * @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
1120
+ */
1121
+ PersistedModel.diff = function(since, remoteChanges, callback) {
1122
+ this.getChangeModel().diff(this.modelName, since, remoteChanges, callback);
1123
+ };
1124
+ /**
1125
+ * Get the changes to a model since the specified checkpoint. Provide a filter object
1126
+ * to reduce the number of results returned.
1127
+ * @param {Number} since Return only changes since this checkpoint.
1128
+ * @param {Object} filter Include only changes that match this filter, the same as for [#persistedmodel-find](find()).
1129
+ * @callback {Function} callback Callback function called with `(err, changes)` arguments. Required.
1130
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
1131
+ * @param {Array} changes An array of [Change](#change) objects.
1132
+ */
1133
+ PersistedModel.changes = function(since, filter, callback) {
1134
+ if (typeof since === "function") {
1135
+ filter = {};
1136
+ callback = since;
1137
+ since = -1;
1138
+ }
1139
+ if (typeof filter === "function") {
1140
+ callback = filter;
1141
+ since = -1;
1142
+ filter = {};
1143
+ }
1144
+ const idName = this.dataSource.idName(this.modelName);
1145
+ const Change = this.getChangeModel();
1146
+ const model = this;
1147
+ const changeFilter = this.createChangeFilter(since, filter);
1148
+ filter = filter || {};
1149
+ const modelFilter = Object.assign({}, filter);
1150
+ const modelFilterWhere = Object.assign({}, filter.where);
1151
+ modelFilter.fields = {};
1152
+ modelFilter.where = modelFilterWhere;
1153
+ modelFilter.fields[idName] = true;
1154
+ Change.find(changeFilter, function(err, changes) {
1155
+ if (err) return callback(err);
1156
+ if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);
1157
+ const ids = [];
1158
+ const changeIdLookup = Object.create(null);
1159
+ for (let i = 0; i < changes.length; i++) {
1160
+ const id = changes[i].getModelId();
1161
+ const lookupKey = String(id);
1162
+ if (changeIdLookup[lookupKey] !== true) {
1163
+ changeIdLookup[lookupKey] = true;
1164
+ ids.push(id);
1165
+ }
1166
+ }
1167
+ modelFilter.where[idName] = { inq: ids };
1168
+ model.find(modelFilter, function(err, models) {
1169
+ if (err) return callback(err);
1170
+ const modelIdLookup = Object.create(null);
1171
+ for (let i = 0; i < models.length; i++) modelIdLookup[String(models[i][idName])] = true;
1172
+ const filteredChanges = [];
1173
+ for (let i = 0; i < changes.length; i++) {
1174
+ const change = changes[i];
1175
+ if (change.type() === Change.DELETE || modelIdLookup[String(change.modelId)] === true) filteredChanges.push(change);
1176
+ }
1177
+ callback(null, filteredChanges);
1178
+ });
1179
+ });
1180
+ };
1181
+ /**
1182
+ * Create a checkpoint.
1183
+ *
1184
+ * @param {Function} callback
1185
+ */
1186
+ PersistedModel.checkpoint = function(cb) {
1187
+ const Checkpoint = this.getChangeModel().getCheckpointModel();
1188
+ if (!cb) return Checkpoint.bumpLastSeq();
1189
+ Checkpoint.bumpLastSeq(cb);
1190
+ };
1191
+ /**
1192
+ * Get the current checkpoint ID.
1193
+ *
1194
+ * @callback {Function} callback Callback function called with `(err, currentCheckpointId)` arguments. Required.
1195
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
1196
+ * @param {Number} currentCheckpointId Current checkpoint ID.
1197
+ */
1198
+ PersistedModel.currentCheckpoint = function(cb) {
1199
+ const Checkpoint = this.getChangeModel().getCheckpointModel();
1200
+ if (!cb) return Checkpoint.current();
1201
+ Checkpoint.current(cb);
1202
+ };
1203
+ /**
1204
+ * Replicate changes since the given checkpoint to the given target model.
1205
+ *
1206
+ * @param {Number} [since] Since this checkpoint
1207
+ * @param {Model} targetModel Target this model class
1208
+ * @param {Object} [options] An optional options object to pass to underlying data-access calls.
1209
+ * @param {Object} [options.filter] Replicate models that match this filter
1210
+ * @callback {Function} [callback] Callback function called with `(err, conflicts)` arguments.
1211
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
1212
+ * @param {Conflict[]} conflicts A list of changes that could not be replicated due to conflicts.
1213
+ * @param {Object} checkpoints The new checkpoints to use as the "since"
1214
+ * argument for the next replication.
1215
+ *
1216
+ * @promise
1217
+ */
1218
+ PersistedModel.replicate = function(since, targetModel, options, callback) {
1219
+ const lastArg = arguments[arguments.length - 1];
1220
+ if (typeof lastArg === "function" && arguments.length > 1) callback = lastArg;
1221
+ if (typeof since === "function" && since.modelName) {
1222
+ targetModel = since;
1223
+ since = -1;
1224
+ }
1225
+ if (typeof since !== "object") since = {
1226
+ source: since,
1227
+ target: since
1228
+ };
1229
+ if (typeof options === "function") options = {};
1230
+ options = options || {};
1231
+ const sourceModel = this;
1232
+ callback = callback || utils.createPromiseCallback();
1233
+ debug("replicating %s since %s to %s since %s", sourceModel.modelName, since.source, targetModel.modelName, since.target);
1234
+ if (options.filter) debug(" with filter %j", options.filter);
1235
+ const MAX_ATTEMPTS = 3;
1236
+ run(1, since);
1237
+ return callback.promise;
1238
+ function run(attempt, since) {
1239
+ debug(" iteration #%s", attempt);
1240
+ tryReplicate(sourceModel, targetModel, since, options, next);
1241
+ function next(err, conflicts, cps, updates) {
1242
+ if (err || conflicts.length || !updates || updates.length === 0 || attempt >= MAX_ATTEMPTS) return callback(err, conflicts, cps);
1243
+ run(attempt + 1, cps);
1244
+ }
1245
+ }
1246
+ };
1247
+ function tryReplicate(sourceModel, targetModel, since, options, callback) {
1248
+ const Change = sourceModel.getChangeModel();
1249
+ const TargetChange = targetModel.getChangeModel();
1250
+ const changeTrackingEnabled = Change && TargetChange;
1251
+ let replicationChunkSize = REPLICATION_CHUNK_SIZE;
1252
+ if (sourceModel.settings && sourceModel.settings.replicationChunkSize) replicationChunkSize = sourceModel.settings.replicationChunkSize;
1253
+ assert(changeTrackingEnabled, "You must enable change tracking before replicating");
1254
+ let diff, updates, newSourceCp, newTargetCp;
1255
+ run().then(function() {
1256
+ done();
1257
+ }, done);
1258
+ async function run() {
1259
+ await checkpoints();
1260
+ const createdUpdates = await createSourceUpdates(await getDiffFromTarget(await getSourceChanges()));
1261
+ if (!createdUpdates) return;
1262
+ await bulkUpdate(createdUpdates);
1263
+ }
1264
+ function getSourceChanges() {
1265
+ const chunkPromise = utils.downloadInChunks(options.filter, replicationChunkSize, function(filter, pagingCallback) {
1266
+ sourceModel.changes(since.source, filter, pagingCallback);
1267
+ });
1268
+ if (!debug.enabled) return chunkPromise;
1269
+ return chunkPromise.then(function(result) {
1270
+ debug(" using source changes");
1271
+ result.forEach(function(it) {
1272
+ debug(" %j", it);
1273
+ });
1274
+ return result;
1275
+ });
1276
+ }
1277
+ function getDiffFromTarget(sourceChanges) {
1278
+ const diffPromise = utils.uploadInChunks(sourceChanges, replicationChunkSize, function(smallArray, chunkCallback) {
1279
+ return targetModel.diff(since.target, smallArray, chunkCallback);
1280
+ });
1281
+ if (!debug.enabled) return diffPromise;
1282
+ return diffPromise.then(function(result) {
1283
+ if (result.conflicts && result.conflicts.length) {
1284
+ debug(" diff conflicts");
1285
+ result.conflicts.forEach(function(d) {
1286
+ debug(" %j", d);
1287
+ });
1288
+ }
1289
+ if (result.deltas && result.deltas.length) {
1290
+ debug(" diff deltas");
1291
+ result.deltas.forEach(function(it) {
1292
+ debug(" %j", it);
1293
+ });
1294
+ }
1295
+ return result;
1296
+ });
1297
+ }
1298
+ function createSourceUpdates(_diff) {
1299
+ diff = _diff;
1300
+ diff.conflicts = diff.conflicts || [];
1301
+ if (diff && diff.deltas && diff.deltas.length) {
1302
+ debug(" building a list of updates");
1303
+ return utils.uploadInChunks(diff.deltas, replicationChunkSize, function(smallArray, chunkCallback) {
1304
+ return sourceModel.createUpdates(smallArray, chunkCallback);
1305
+ });
1306
+ }
1307
+ }
1308
+ function bulkUpdate(_updates) {
1309
+ debug(" starting bulk update");
1310
+ updates = _updates;
1311
+ if (!updates || !updates.length) return Promise.resolve();
1312
+ return utils.uploadInChunks(updates, replicationChunkSize, function(smallArray, chunkCallback) {
1313
+ return targetModel.bulkUpdate(smallArray, options, function(err) {
1314
+ chunkCallback(null, err);
1315
+ });
1316
+ }).then(function(err) {
1317
+ const conflicts = err && err.details && err.details.conflicts;
1318
+ if (conflicts && err.statusCode == 409) {
1319
+ diff.conflicts = conflicts;
1320
+ const conflictedIds = Object.create(null);
1321
+ for (let i = 0; i < conflicts.length; i++) conflictedIds[String(conflicts[i].modelId)] = true;
1322
+ updates = updates.filter(function(u) {
1323
+ return conflictedIds[String(u.change.modelId)] !== true;
1324
+ });
1325
+ return;
1326
+ }
1327
+ if (err) throw err;
1328
+ });
1329
+ }
1330
+ function checkpoints() {
1331
+ return utils.invokeWithCallback(sourceModel.checkpoint, sourceModel, []).then(function(source) {
1332
+ newSourceCp = source.seq;
1333
+ return utils.invokeWithCallback(targetModel.checkpoint, targetModel, []);
1334
+ }).then(function(target) {
1335
+ newTargetCp = target.seq;
1336
+ debug(" created checkpoints");
1337
+ debug(" %s for source model %s", newSourceCp, sourceModel.modelName);
1338
+ debug(" %s for target model %s", newTargetCp, targetModel.modelName);
1339
+ });
1340
+ }
1341
+ function done(err) {
1342
+ if (err) return callback(err);
1343
+ debug(" replication finished");
1344
+ debug(" %s conflict(s) detected", diff.conflicts.length);
1345
+ debug(" %s change(s) applied", updates ? updates.length : 0);
1346
+ debug(" new checkpoints: { source: %j, target: %j }", newSourceCp, newTargetCp);
1347
+ const conflicts = new Array(diff.conflicts.length);
1348
+ for (let i = 0; i < diff.conflicts.length; i++) {
1349
+ const change = diff.conflicts[i];
1350
+ conflicts[i] = new Change.Conflict(change.modelId, sourceModel, targetModel);
1351
+ }
1352
+ if (conflicts.length) sourceModel.emit("conflicts", conflicts);
1353
+ if (callback) callback(null, conflicts, {
1354
+ source: newSourceCp,
1355
+ target: newTargetCp
1356
+ }, updates);
1357
+ }
1358
+ }
1359
+ /**
1360
+ * Create an update list (for `Model.bulkUpdate()`) from a delta list
1361
+ * (result of `Change.diff()`).
1362
+ *
1363
+ * @param {Array} deltas
1364
+ * @param {Function} callback
1365
+ */
1366
+ PersistedModel.createUpdates = function(deltas, cb) {
1367
+ const Change = this.getChangeModel();
1368
+ const updates = [];
1369
+ const Model = this;
1370
+ const tasks = [];
1371
+ deltas.forEach(function(change) {
1372
+ change = new Change(change);
1373
+ const type = change.type();
1374
+ const update = {
1375
+ type,
1376
+ change
1377
+ };
1378
+ switch (type) {
1379
+ case Change.CREATE:
1380
+ case Change.UPDATE:
1381
+ tasks.push(function(cb) {
1382
+ Model.findById(change.modelId, function(err, inst) {
1383
+ if (err) return cb(err);
1384
+ if (!inst) return cb && cb(new Error(g.f("Missing data for change: %s", change.modelId)));
1385
+ if (inst.toObject) update.data = inst.toObject();
1386
+ else update.data = inst;
1387
+ updates.push(update);
1388
+ cb();
1389
+ });
1390
+ });
1391
+ break;
1392
+ case Change.DELETE:
1393
+ updates.push(update);
1394
+ break;
1395
+ }
1396
+ });
1397
+ Promise.all(tasks.map(function(task) {
1398
+ return utils.invokeWithCallback(task, null, []);
1399
+ })).then(function() {
1400
+ cb(null, updates);
1401
+ }, cb);
1402
+ };
1403
+ /**
1404
+ * Apply an update list.
1405
+ *
1406
+ * **Note: this is not atomic**
1407
+ *
1408
+ * @param {Array} updates An updates list, usually from [createUpdates()](#persistedmodel-createupdates).
1409
+ * @param {Object} [options] An optional options object to pass to underlying data-access calls.
1410
+ * @param {Function} callback Callback function.
1411
+ */
1412
+ PersistedModel.bulkUpdate = function(updates, options, callback) {
1413
+ const tasks = [];
1414
+ const Model = this;
1415
+ const Change = this.getChangeModel();
1416
+ const conflicts = [];
1417
+ const lastArg = arguments[arguments.length - 1];
1418
+ if (typeof lastArg === "function" && arguments.length > 1) callback = lastArg;
1419
+ if (typeof options === "function") options = {};
1420
+ options = options || {};
1421
+ buildLookupOfAffectedModelData(Model, updates, function(err, currentMap) {
1422
+ if (err) return callback(err);
1423
+ updates.forEach(function(update) {
1424
+ const id = update.change.modelId;
1425
+ const current = currentMap[id];
1426
+ switch (update.type) {
1427
+ case Change.UPDATE:
1428
+ tasks.push(function(cb) {
1429
+ applyUpdate(Model, id, current, update.data, update.change, conflicts, options, cb);
1430
+ });
1431
+ break;
1432
+ case Change.CREATE:
1433
+ tasks.push(function(cb) {
1434
+ applyCreate(Model, id, current, update.data, update.change, conflicts, options, cb);
1435
+ });
1436
+ break;
1437
+ case Change.DELETE:
1438
+ tasks.push(function(cb) {
1439
+ applyDelete(Model, id, current, update.change, conflicts, options, cb);
1440
+ });
1441
+ break;
1442
+ }
1443
+ });
1444
+ Promise.all(tasks.map(function(task) {
1445
+ return utils.invokeWithCallback(task, null, []);
1446
+ })).then(function() {
1447
+ if (conflicts.length) {
1448
+ err = new Error(g.f("Conflict"));
1449
+ err.statusCode = 409;
1450
+ err.details = { conflicts };
1451
+ return callback(err);
1452
+ }
1453
+ callback();
1454
+ }, callback);
1455
+ });
1456
+ };
1457
+ function buildLookupOfAffectedModelData(Model, updates, callback) {
1458
+ const idName = Model.dataSource.idName(Model.modelName);
1459
+ const affectedIds = [];
1460
+ const affectedIdLookup = Object.create(null);
1461
+ for (let i = 0; i < updates.length; i++) {
1462
+ const modelId = updates[i].change.modelId;
1463
+ const lookupKey = String(modelId);
1464
+ if (affectedIdLookup[lookupKey] !== true) {
1465
+ affectedIdLookup[lookupKey] = true;
1466
+ affectedIds.push(modelId);
1467
+ }
1468
+ }
1469
+ const whereAffected = {};
1470
+ whereAffected[idName] = { inq: affectedIds };
1471
+ Model.find({ where: whereAffected }, function(err, affectedList) {
1472
+ if (err) return callback(err);
1473
+ const dataLookup = {};
1474
+ for (let i = 0; i < affectedList.length; i++) {
1475
+ const it = affectedList[i];
1476
+ dataLookup[it[idName]] = it;
1477
+ }
1478
+ callback(null, dataLookup);
1479
+ });
1480
+ }
1481
+ function applyUpdate(Model, id, current, data, change, conflicts, options, cb) {
1482
+ const Change = Model.getChangeModel();
1483
+ const rev = current ? Change.revisionForInst(current) : null;
1484
+ if (rev !== change.prev) {
1485
+ debug("Detected non-rectified change of %s %j", Model.modelName, id);
1486
+ debug(" Expected revision: %s", change.rev);
1487
+ debug(" Actual revision: %s", rev);
1488
+ conflicts.push(change);
1489
+ return Change.rectifyModelChanges(Model.modelName, [id], cb);
1490
+ }
1491
+ Model.updateAll(current.toObject(), data, options, function(err, result) {
1492
+ if (err) return cb(err);
1493
+ const count = result && result.count;
1494
+ switch (count) {
1495
+ case 1: return cb();
1496
+ case 0:
1497
+ debug("UpdateAll detected non-rectified change of %s %j", Model.modelName, id);
1498
+ conflicts.push(change);
1499
+ return cb();
1500
+ case void 0:
1501
+ case null: return cb(new Error(g.f("Cannot apply bulk updates, the connector does not correctly report the number of updated records.")));
1502
+ default:
1503
+ debug("%s.updateAll modified unexpected number of instances: %j", Model.modelName, count);
1504
+ return cb(new Error(g.f("Bulk update failed, the connector has modified unexpected number of records: %s", JSON.stringify(count))));
1505
+ }
1506
+ });
1507
+ }
1508
+ function applyCreate(Model, id, current, data, change, conflicts, options, cb) {
1509
+ Model.create(data, options, function(createErr) {
1510
+ if (!createErr) return cb();
1511
+ Model.findById(id, function(findErr, inst) {
1512
+ if (findErr || !inst) return cb(createErr);
1513
+ return conflict();
1514
+ });
1515
+ });
1516
+ function conflict() {
1517
+ debug("Detected non-rectified new instance of %s %j", Model.modelName, id);
1518
+ conflicts.push(change);
1519
+ return Model.getChangeModel().rectifyModelChanges(Model.modelName, [id], cb);
1520
+ }
1521
+ }
1522
+ function applyDelete(Model, id, current, change, conflicts, options, cb) {
1523
+ if (!current) return cb();
1524
+ const Change = Model.getChangeModel();
1525
+ const rev = Change.revisionForInst(current);
1526
+ if (rev !== change.prev) {
1527
+ debug("Detected non-rectified change of %s %j", Model.modelName, id);
1528
+ debug(" Expected revision: %s", change.rev);
1529
+ debug(" Actual revision: %s", rev);
1530
+ conflicts.push(change);
1531
+ return Change.rectifyModelChanges(Model.modelName, [id], cb);
1532
+ }
1533
+ Model.deleteAll(current.toObject(), options, function(err, result) {
1534
+ if (err) return cb(err);
1535
+ const count = result && result.count;
1536
+ switch (count) {
1537
+ case 1: return cb();
1538
+ case 0:
1539
+ debug("DeleteAll detected non-rectified change of %s %j", Model.modelName, id);
1540
+ conflicts.push(change);
1541
+ return cb();
1542
+ case void 0:
1543
+ case null: return cb(new Error(g.f("Cannot apply bulk updates, the connector does not correctly report the number of deleted records.")));
1544
+ default:
1545
+ debug("%s.deleteAll modified unexpected number of instances: %j", Model.modelName, count);
1546
+ return cb(new Error(g.f("Bulk update failed, the connector has deleted unexpected number of records: %s", JSON.stringify(count))));
1547
+ }
1548
+ });
1549
+ }
1550
+ /**
1551
+ * Get the `Change` model.
1552
+ * Throws an error if the change model is not correctly setup.
1553
+ * @return {Change}
1554
+ */
1555
+ PersistedModel.getChangeModel = function() {
1556
+ const changeModel = this.Change;
1557
+ assert(changeModel && changeModel.dataSource, "Cannot get a setup Change model for " + this.modelName);
1558
+ return changeModel;
1559
+ };
1560
+ /**
1561
+ * Get the source identifier for this model or dataSource.
1562
+ *
1563
+ * @callback {Function} callback Callback function called with `(err, id)` arguments.
1564
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
1565
+ * @param {String} sourceId Source identifier for the model or dataSource.
1566
+ */
1567
+ PersistedModel.getSourceId = function(cb) {
1568
+ const dataSource = this.dataSource;
1569
+ if (!dataSource) this.once("dataSourceAttached", this.getSourceId.bind(this, cb));
1570
+ assert(dataSource.connector.name, "Model.getSourceId: cannot get id without dataSource.connector.name");
1571
+ cb(null, [dataSource.connector.name, this.modelName].join("-"));
1572
+ };
1573
+ /**
1574
+ * Enable the tracking of changes made to the model. Usually for replication.
1575
+ */
1576
+ PersistedModel.enableChangeTracking = function() {
1577
+ const Model = this;
1578
+ this.Change || this._defineChangeModel();
1579
+ const cleanupInterval = Model.settings.changeCleanupInterval || 3e4;
1580
+ assert(this.dataSource, "Cannot enableChangeTracking(): " + this.modelName + " is not attached to a dataSource");
1581
+ const idName = this.getIdName();
1582
+ const idProp = this.definition.properties[idName];
1583
+ const idType = idProp && idProp.type;
1584
+ const idDefn = idProp && idProp.defaultFn;
1585
+ if (idType !== String || !(idDefn === "uuid" || idDefn === "guid")) deprecated("The model " + this.modelName + " is tracking changes, which requires a string id with GUID/UUID default value.");
1586
+ Model.observe("after save", rectifyOnSave);
1587
+ Model.observe("after delete", rectifyOnDelete);
1588
+ if (runtime.isServer && cleanupInterval > 0) {
1589
+ cleanup();
1590
+ const cleanupTimer = setInterval(cleanup, cleanupInterval);
1591
+ if (cleanupTimer && typeof cleanupTimer.unref === "function") cleanupTimer.unref();
1592
+ }
1593
+ function cleanup() {
1594
+ Model.rectifyAllChanges(function(err) {
1595
+ if (err) Model.handleChangeError(err, "cleanup");
1596
+ });
1597
+ }
1598
+ };
1599
+ function rectifyOnSave(ctx, next) {
1600
+ const instance = ctx.instance || ctx.currentInstance;
1601
+ const id = instance ? instance.getId() : getIdFromWhereByModelId(ctx.Model, ctx.where);
1602
+ if (debug.enabled) {
1603
+ debug("rectifyOnSave %s -> " + (id ? "id %j" : "%s"), ctx.Model.modelName, id ? id : "ALL");
1604
+ debug("context instance:%j currentInstance:%j where:%j data %j", ctx.instance, ctx.currentInstance, ctx.where, ctx.data);
1605
+ }
1606
+ if (id != null) ctx.Model.rectifyChange(id, reportErrorAndNext);
1607
+ else ctx.Model.rectifyAllChanges(reportErrorAndNext);
1608
+ function reportErrorAndNext(err) {
1609
+ if (err) ctx.Model.handleChangeError(err, "after save");
1610
+ next();
1611
+ }
1612
+ }
1613
+ function rectifyOnDelete(ctx, next) {
1614
+ const id = ctx.instance ? ctx.instance.getId() : getIdFromWhereByModelId(ctx.Model, ctx.where);
1615
+ if (debug.enabled) {
1616
+ debug("rectifyOnDelete %s -> " + (id ? "id %j" : "%s"), ctx.Model.modelName, id ? id : "ALL");
1617
+ debug("context instance:%j where:%j", ctx.instance, ctx.where);
1618
+ }
1619
+ if (id != null) ctx.Model.rectifyChange(id, reportErrorAndNext);
1620
+ else ctx.Model.rectifyAllChanges(reportErrorAndNext);
1621
+ function reportErrorAndNext(err) {
1622
+ if (err) ctx.Model.handleChangeError(err, "after delete");
1623
+ next();
1624
+ }
1625
+ }
1626
+ function getIdFromWhereByModelId(Model, where) {
1627
+ const idName = Model.getIdName();
1628
+ if (!(idName in where)) return void 0;
1629
+ const id = where[idName];
1630
+ if (typeof id === "string" || typeof id === "number") return id;
1631
+ }
1632
+ PersistedModel._defineChangeModel = function() {
1633
+ const BaseChangeModel = this.registry.getModel("Change");
1634
+ assert(BaseChangeModel, "Change model must be defined before enabling change replication");
1635
+ const additionalChangeModelProperties = this.settings.additionalChangeModelProperties || {};
1636
+ this.Change = BaseChangeModel.extend(this.modelName + "-change", additionalChangeModelProperties, { trackModel: this });
1637
+ if (this.dataSource) attachRelatedModels(this);
1638
+ const self = this;
1639
+ this.on("dataSourceAttached", function() {
1640
+ attachRelatedModels(self);
1641
+ });
1642
+ return this.Change;
1643
+ function attachRelatedModels(self) {
1644
+ self.Change.attachTo(self.dataSource);
1645
+ self.Change.getCheckpointModel().attachTo(self.dataSource);
1646
+ }
1647
+ };
1648
+ PersistedModel.rectifyAllChanges = function(callback) {
1649
+ this.getChangeModel().rectifyAll(callback);
1650
+ };
1651
+ /**
1652
+ * Handle a change error. Override this method in a subclassing model to customize
1653
+ * change error handling.
1654
+ *
1655
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
1656
+ */
1657
+ PersistedModel.handleChangeError = function(err, operationName) {
1658
+ if (!err) return;
1659
+ this.emit("error", err, operationName);
1660
+ };
1661
+ /**
1662
+ * Specify that a change to the model with the given ID has occurred.
1663
+ *
1664
+ * @param {*} id The ID of the model that has changed.
1665
+ * @callback {Function} callback
1666
+ * @param {Error} err
1667
+ */
1668
+ PersistedModel.rectifyChange = function(id, callback) {
1669
+ this.getChangeModel().rectifyModelChanges(this.modelName, [id], callback);
1670
+ };
1671
+ PersistedModel.findLastChange = function(id, cb) {
1672
+ this.getChangeModel().findOne({ where: { modelId: id } }, cb);
1673
+ };
1674
+ PersistedModel.updateLastChange = function(id, data, cb) {
1675
+ const self = this;
1676
+ this.findLastChange(id, function(err, inst) {
1677
+ if (err) return cb(err);
1678
+ if (!inst) {
1679
+ err = new Error(g.f("No change record found for %s with id %s", self.modelName, id));
1680
+ err.statusCode = 404;
1681
+ return cb(err);
1682
+ }
1683
+ inst.updateAttributes(data, cb);
1684
+ });
1685
+ };
1686
+ /**
1687
+ * Create a change stream. [See here for more info](http://loopback.io/doc/en/lb2/Realtime-server-sent-events.html)
1688
+ *
1689
+ * @param {Object} options
1690
+ * @param {Object} options.where Only changes to models matching this where filter will be included in the `ChangeStream`.
1691
+ * @callback {Function} callback
1692
+ * @param {Error} err
1693
+ * @param {ChangeStream} changes
1694
+ */
1695
+ PersistedModel.createChangeStream = function(options, cb) {
1696
+ if (typeof options === "function") {
1697
+ cb = options;
1698
+ options = void 0;
1699
+ }
1700
+ cb = cb || utils.createPromiseCallback();
1701
+ const idName = this.getIdName();
1702
+ const Model = this;
1703
+ const changes = new PassThrough({ objectMode: true });
1704
+ changes._destroy = function() {
1705
+ changes.end();
1706
+ changes.emit("end");
1707
+ changes.emit("close");
1708
+ };
1709
+ changes.destroy = changes.destroy || changes._destroy;
1710
+ changes.on("error", removeHandlers);
1711
+ changes.on("close", removeHandlers);
1712
+ changes.on("finish", removeHandlers);
1713
+ changes.on("end", removeHandlers);
1714
+ process.nextTick(function() {
1715
+ cb(null, changes);
1716
+ });
1717
+ Model.observe("after save", changeHandler);
1718
+ Model.observe("after delete", deleteHandler);
1719
+ return cb.promise;
1720
+ function changeHandler(ctx, next) {
1721
+ const change = createChangeObject(ctx, "save");
1722
+ if (change) changes.write(change);
1723
+ next();
1724
+ }
1725
+ function deleteHandler(ctx, next) {
1726
+ const change = createChangeObject(ctx, "delete");
1727
+ if (change) changes.write(change);
1728
+ next();
1729
+ }
1730
+ function createChangeObject(ctx, type) {
1731
+ const where = ctx.where;
1732
+ let data = ctx.instance || ctx.data;
1733
+ where && where[idName];
1734
+ let target;
1735
+ if (data && (data[idName] || data[idName] === 0)) target = data[idName];
1736
+ else if (where && (where[idName] || where[idName] === 0)) target = where[idName];
1737
+ const hasTarget = target === 0 || !!target;
1738
+ if (options) {
1739
+ const filtered = filterNodes([data], options);
1740
+ if (filtered.length !== 1) return null;
1741
+ data = filtered[0];
1742
+ }
1743
+ const change = {
1744
+ target,
1745
+ where,
1746
+ data
1747
+ };
1748
+ switch (type) {
1749
+ case "save":
1750
+ if (ctx.isNewInstance === void 0) change.type = hasTarget ? "update" : "create";
1751
+ else change.type = ctx.isNewInstance ? "create" : "update";
1752
+ break;
1753
+ case "delete":
1754
+ change.type = "remove";
1755
+ break;
1756
+ }
1757
+ return change;
1758
+ }
1759
+ function removeHandlers() {
1760
+ Model.removeObserver("after save", changeHandler);
1761
+ Model.removeObserver("after delete", deleteHandler);
1762
+ }
1763
+ };
1764
+ /**
1765
+ * Get the filter for searching related changes.
1766
+ *
1767
+ * Models should override this function to copy properties
1768
+ * from the model instance filter into the change search filter.
1769
+ *
1770
+ * ```js
1771
+ * module.exports = (TargetModel, config) => {
1772
+ * TargetModel.createChangeFilter = function(since, modelFilter) {
1773
+ * const filter = this.base.createChangeFilter.apply(this, arguments);
1774
+ * if (modelFilter && modelFilter.where && modelFilter.where.tenantId) {
1775
+ * filter.where.tenantId = modelFilter.where.tenantId;
1776
+ * }
1777
+ * return filter;
1778
+ * };
1779
+ * };
1780
+ * ```
1781
+ *
1782
+ * @param {Number} since Return only changes since this checkpoint.
1783
+ * @param {Object} modelFilter Filter describing which model instances to
1784
+ * include in the list of changes.
1785
+ * @returns {Object} The filter object to pass to `Change.find()`. Default:
1786
+ * ```
1787
+ * {where: {checkpoint: {gte: since}, modelName: this.modelName}}
1788
+ * ```
1789
+ */
1790
+ PersistedModel.createChangeFilter = function(since, modelFilter) {
1791
+ return { where: {
1792
+ checkpoint: { gte: since },
1793
+ modelName: this.modelName
1794
+ } };
1795
+ };
1796
+ /**
1797
+ * Add custom data to the Change instance.
1798
+ *
1799
+ * Models should override this function to duplicate model instance properties
1800
+ * to the Change instance properties, typically to allow the changes() method
1801
+ * to filter the changes using these duplicated properties directly while
1802
+ * querying the Change model.
1803
+ *
1804
+ * ```js
1805
+ * module.exports = (TargetModel, config) => {
1806
+ * TargetModel.prototype.fillCustomChangeProperties = function(change, cb) {
1807
+ * var inst = this;
1808
+ * const base = this.constructor.base;
1809
+ * base.prototype.fillCustomChangeProperties.call(this, change, err => {
1810
+ * if (err) return cb(err);
1811
+ *
1812
+ * if (inst && inst.tenantId) {
1813
+ * change.tenantId = inst.tenantId;
1814
+ * } else {
1815
+ * change.tenantId = null;
1816
+ * }
1817
+ *
1818
+ * cb();
1819
+ * });
1820
+ * };
1821
+ * };
1822
+ * ```
1823
+ *
1824
+ * @callback {Function} callback
1825
+ * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb3/Error-object.html).
1826
+ */
1827
+ PersistedModel.prototype.fillCustomChangeProperties = function(change, cb) {
1828
+ cb();
1829
+ };
1830
+ PersistedModel.setup();
1831
+ return PersistedModel;
1832
+ };
1833
+ }));
1834
+ //#endregion
1835
+ module.exports = require_persisted_model();