@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.
- package/LICENSE +25 -0
- package/README.md +91 -0
- package/common/models/README.md +109 -0
- package/common/models/access-token.json +37 -0
- package/common/models/acl.json +17 -0
- package/common/models/application.json +130 -0
- package/common/models/change.json +25 -0
- package/common/models/checkpoint.json +14 -0
- package/common/models/email.json +11 -0
- package/common/models/key-value-model.json +4 -0
- package/common/models/role-mapping.json +26 -0
- package/common/models/role.json +30 -0
- package/common/models/scope.json +14 -0
- package/common/models/user.json +118 -0
- package/dist/_virtual/_rolldown/runtime.cjs +32 -0
- package/dist/common/models/access-token.cjs +144 -0
- package/dist/common/models/access-token2.cjs +43 -0
- package/dist/common/models/acl.cjs +428 -0
- package/dist/common/models/acl2.cjs +27 -0
- package/dist/common/models/application.cjs +100 -0
- package/dist/common/models/application2.cjs +118 -0
- package/dist/common/models/change.cjs +404 -0
- package/dist/common/models/change2.cjs +25 -0
- package/dist/common/models/checkpoint.cjs +43 -0
- package/dist/common/models/checkpoint2.cjs +18 -0
- package/dist/common/models/email.cjs +18 -0
- package/dist/common/models/email2.cjs +30 -0
- package/dist/common/models/key-value-model.cjs +140 -0
- package/dist/common/models/key-value-model2.cjs +14 -0
- package/dist/common/models/role-mapping.cjs +57 -0
- package/dist/common/models/role-mapping2.cjs +34 -0
- package/dist/common/models/role.cjs +396 -0
- package/dist/common/models/role2.cjs +38 -0
- package/dist/common/models/scope.cjs +30 -0
- package/dist/common/models/scope2.cjs +21 -0
- package/dist/common/models/user.cjs +810 -0
- package/dist/common/models/user2.cjs +118 -0
- package/dist/index.cjs +16 -0
- package/dist/lib/access-context.cjs +228 -0
- package/dist/lib/application.cjs +450 -0
- package/dist/lib/builtin-models.cjs +60 -0
- package/dist/lib/configure-shared-methods.cjs +41 -0
- package/dist/lib/connectors/base-connector.cjs +23 -0
- package/dist/lib/connectors/mail-direct-transport.cjs +375 -0
- package/dist/lib/connectors/mail-stub-transport.cjs +86 -0
- package/dist/lib/connectors/mail.cjs +128 -0
- package/dist/lib/connectors/memory.cjs +19 -0
- package/dist/lib/current-context.cjs +22 -0
- package/dist/lib/globalize.cjs +29 -0
- package/dist/lib/loopback.cjs +313 -0
- package/dist/lib/model.cjs +1009 -0
- package/dist/lib/persisted-model.cjs +1835 -0
- package/dist/lib/registry.cjs +291 -0
- package/dist/lib/runtime.cjs +25 -0
- package/dist/lib/server-app.cjs +231 -0
- package/dist/lib/utils.cjs +154 -0
- package/dist/package.cjs +124 -0
- package/dist/server/middleware/context.cjs +7 -0
- package/dist/server/middleware/error-handler.cjs +6 -0
- package/dist/server/middleware/favicon.cjs +13 -0
- package/dist/server/middleware/rest.cjs +44 -0
- package/dist/server/middleware/static.cjs +14 -0
- package/dist/server/middleware/status.cjs +28 -0
- package/dist/server/middleware/token.cjs +66 -0
- package/dist/server/middleware/url-not-found.cjs +20 -0
- package/favicon.ico +0 -0
- package/package.json +121 -0
- package/templates/reset-form.ejs +3 -0
- package/templates/verify.ejs +9 -0
|
@@ -0,0 +1,1009 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
|
|
3
|
+
const require_lib_globalize = require("./globalize.cjs");
|
|
4
|
+
const require_lib_utils = require("./utils.cjs");
|
|
5
|
+
//#region src/lib/model.ts
|
|
6
|
+
/*!
|
|
7
|
+
* Module Dependencies.
|
|
8
|
+
*/
|
|
9
|
+
var require_model = /* @__PURE__ */ require_runtime.__commonJSMin(((exports, module) => {
|
|
10
|
+
const g = require_lib_globalize;
|
|
11
|
+
const assert = require("assert");
|
|
12
|
+
const debug = require("debug")("loopback:model");
|
|
13
|
+
require("@vsaas/remoting");
|
|
14
|
+
const SharedClass = require("@vsaas/remoting").SharedClass;
|
|
15
|
+
const utils = require_lib_utils;
|
|
16
|
+
const deprecated = require("depd")("loopback");
|
|
17
|
+
module.exports = function(registry) {
|
|
18
|
+
/**
|
|
19
|
+
* The base class for **all models**.
|
|
20
|
+
*
|
|
21
|
+
* **Inheriting from `Model`**
|
|
22
|
+
*
|
|
23
|
+
* ```js
|
|
24
|
+
* var properties = {...};
|
|
25
|
+
* var options = {...};
|
|
26
|
+
* var MyModel = loopback.Model.extend('MyModel', properties, options);
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* **Options**
|
|
30
|
+
*
|
|
31
|
+
* - `trackChanges` - If true, changes to the model will be tracked. **Required
|
|
32
|
+
* for replication.**
|
|
33
|
+
*
|
|
34
|
+
* **Events**
|
|
35
|
+
*
|
|
36
|
+
* #### Event: `changed`
|
|
37
|
+
*
|
|
38
|
+
* Emitted after a model has been successfully created, saved, or updated.
|
|
39
|
+
* Argument: `inst`, model instance, object
|
|
40
|
+
*
|
|
41
|
+
* ```js
|
|
42
|
+
* MyModel.on('changed', function(inst) {
|
|
43
|
+
* console.log('model with id %s has been changed', inst.id);
|
|
44
|
+
* // => model with id 1 has been changed
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* #### Event: `deleted`
|
|
49
|
+
*
|
|
50
|
+
* Emitted after an individual model has been deleted.
|
|
51
|
+
* Argument: `id`, model ID (number).
|
|
52
|
+
*
|
|
53
|
+
* ```js
|
|
54
|
+
* MyModel.on('deleted', function(id) {
|
|
55
|
+
* console.log('model with id %s has been deleted', id);
|
|
56
|
+
* // => model with id 1 has been deleted
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* #### Event: `deletedAll`
|
|
61
|
+
*
|
|
62
|
+
* Emitted after all models have been deleted.
|
|
63
|
+
* Argument: `where` (optional), where filter, JSON object.
|
|
64
|
+
*
|
|
65
|
+
* ```js
|
|
66
|
+
* MyModel.on('deletedAll', function(where) {
|
|
67
|
+
* if (where) {
|
|
68
|
+
* console.log('all models where ', where, ' have been deleted');
|
|
69
|
+
* // => all models where
|
|
70
|
+
* // => {price: {gt: 100}}
|
|
71
|
+
* // => have been deleted
|
|
72
|
+
* }
|
|
73
|
+
* });
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* #### Event: `attached`
|
|
77
|
+
*
|
|
78
|
+
* Emitted after a `Model` has been attached to an `app`.
|
|
79
|
+
*
|
|
80
|
+
* #### Event: `dataSourceAttached`
|
|
81
|
+
*
|
|
82
|
+
* Emitted after a `Model` has been attached to a `DataSource`.
|
|
83
|
+
*
|
|
84
|
+
* #### Event: set
|
|
85
|
+
*
|
|
86
|
+
* Emitted when model property is set.
|
|
87
|
+
* Argument: `inst`, model instance, object
|
|
88
|
+
*
|
|
89
|
+
* ```js
|
|
90
|
+
* MyModel.on('set', function(inst) {
|
|
91
|
+
* console.log('model with id %s has been changed', inst.id);
|
|
92
|
+
* // => model with id 1 has been changed
|
|
93
|
+
* });
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* @param {Object} data
|
|
97
|
+
* @property {String} Model.modelName The name of the model. Static property.
|
|
98
|
+
* @property {DataSource} Model.dataSource Data source to which the model is connected, if any. Static property.
|
|
99
|
+
* @property {SharedClass} Model.sharedMethod The `strong-remoting` [SharedClass](http://apidocs.strongloop.com/strong-remoting/#sharedclass) that contains remoting (and http) metadata. Static property.
|
|
100
|
+
* @property {Object} settings Contains additional model settings.
|
|
101
|
+
* @property {string} settings.http.path Base URL of the model HTTP route.
|
|
102
|
+
* @property {Array.<Object>} settings.acls Array of ACLs for the model.
|
|
103
|
+
* @class
|
|
104
|
+
*/
|
|
105
|
+
const Model = registry.modelBuilder.define("Model");
|
|
106
|
+
Model.registry = registry;
|
|
107
|
+
/**
|
|
108
|
+
* The `loopback.Model.extend()` method calls this when you create a model that extends another model.
|
|
109
|
+
* Add any setup or configuration code you want executed when the model is created.
|
|
110
|
+
* See [Setting up a custom model](http://loopback.io/doc/en/lb2/Extending-built-in-models.html#setting-up-a-custom-model).
|
|
111
|
+
*/
|
|
112
|
+
Model.setup = function() {
|
|
113
|
+
const ModelCtor = this;
|
|
114
|
+
const Parent = this.super_;
|
|
115
|
+
if (!ModelCtor.registry && Parent && Parent.registry) ModelCtor.registry = Parent.registry;
|
|
116
|
+
const options = this.settings;
|
|
117
|
+
const typeName = this.modelName;
|
|
118
|
+
ModelCtor.sharedCtor = function(data, id, options, fn) {
|
|
119
|
+
const ModelCtor = this;
|
|
120
|
+
if (typeof data !== "object" && typeof id === "object" && typeof options === "function") {
|
|
121
|
+
fn = options;
|
|
122
|
+
options = id;
|
|
123
|
+
id = data;
|
|
124
|
+
data = null;
|
|
125
|
+
} else if (typeof data === "function") {
|
|
126
|
+
fn = data;
|
|
127
|
+
data = null;
|
|
128
|
+
id = null;
|
|
129
|
+
options = null;
|
|
130
|
+
} else if (typeof id === "function") {
|
|
131
|
+
fn = id;
|
|
132
|
+
options = null;
|
|
133
|
+
if (typeof data !== "object") {
|
|
134
|
+
id = data;
|
|
135
|
+
data = null;
|
|
136
|
+
} else id = null;
|
|
137
|
+
}
|
|
138
|
+
fn = fn || utils.createPromiseCallback();
|
|
139
|
+
(async function() {
|
|
140
|
+
if (id != null && data) {
|
|
141
|
+
const model = new ModelCtor(data);
|
|
142
|
+
model.id = id;
|
|
143
|
+
return model;
|
|
144
|
+
}
|
|
145
|
+
if (data) return new ModelCtor(data);
|
|
146
|
+
if (id != null) {
|
|
147
|
+
const model = await utils.invokeWithCallback(ModelCtor.findById, ModelCtor, [
|
|
148
|
+
id,
|
|
149
|
+
{},
|
|
150
|
+
options
|
|
151
|
+
]);
|
|
152
|
+
if (model) return model;
|
|
153
|
+
const err = new Error(g.f("could not find a model with {{id}} %s", id));
|
|
154
|
+
err.statusCode = 404;
|
|
155
|
+
err.code = "MODEL_NOT_FOUND";
|
|
156
|
+
throw err;
|
|
157
|
+
}
|
|
158
|
+
throw new Error(g.f("must specify an {{id}} or {{data}}"));
|
|
159
|
+
})().then(function(model) {
|
|
160
|
+
fn(null, model);
|
|
161
|
+
}, function(err) {
|
|
162
|
+
fn(err);
|
|
163
|
+
});
|
|
164
|
+
return fn.promise;
|
|
165
|
+
};
|
|
166
|
+
const idDesc = ModelCtor.modelName + " id";
|
|
167
|
+
ModelCtor.sharedCtor.accepts = [{
|
|
168
|
+
arg: "id",
|
|
169
|
+
type: "any",
|
|
170
|
+
required: true,
|
|
171
|
+
http: { source: "path" },
|
|
172
|
+
description: idDesc
|
|
173
|
+
}, {
|
|
174
|
+
arg: "options",
|
|
175
|
+
type: "object",
|
|
176
|
+
http: createOptionsViaModelMethod
|
|
177
|
+
}];
|
|
178
|
+
ModelCtor.sharedCtor.http = [{ path: "/:id" }];
|
|
179
|
+
ModelCtor.sharedCtor.returns = { root: true };
|
|
180
|
+
const remotingOptions = {};
|
|
181
|
+
Object.assign(remotingOptions, options.remoting || {});
|
|
182
|
+
const sharedClass = ModelCtor.sharedClass = new SharedClass(ModelCtor.modelName, ModelCtor, remotingOptions);
|
|
183
|
+
ModelCtor.beforeRemote = function(name, fn) {
|
|
184
|
+
const className = this.modelName;
|
|
185
|
+
this._runWhenAttachedToApp(function(app) {
|
|
186
|
+
app.remotes().before(className + "." + name, function(ctx, next) {
|
|
187
|
+
return fn(ctx, ctx.result, next);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
ModelCtor.afterRemote = function(name, fn) {
|
|
192
|
+
const className = this.modelName;
|
|
193
|
+
this._runWhenAttachedToApp(function(app) {
|
|
194
|
+
app.remotes().after(className + "." + name, function(ctx, next) {
|
|
195
|
+
return fn(ctx, ctx.result, next);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
ModelCtor.afterRemoteError = function(name, fn) {
|
|
200
|
+
const className = this.modelName;
|
|
201
|
+
this._runWhenAttachedToApp(function(app) {
|
|
202
|
+
app.remotes().afterError(className + "." + name, fn);
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
ModelCtor._runWhenAttachedToApp = function(fn) {
|
|
206
|
+
if (typeof fn === "function") {
|
|
207
|
+
if (this.app) return fn(this.app);
|
|
208
|
+
const self = this;
|
|
209
|
+
self.once("attached", function() {
|
|
210
|
+
fn(self.app);
|
|
211
|
+
});
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (this.app) return Promise.resolve(this.app);
|
|
215
|
+
const self = this;
|
|
216
|
+
return new Promise(function(resolve) {
|
|
217
|
+
self.once("attached", function() {
|
|
218
|
+
resolve(self.app);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
if ("injectOptionsFromRemoteContext" in options) {
|
|
223
|
+
console.warn(g.f("%s is using model setting %s which is no longer available.", typeName, "injectOptionsFromRemoteContext"));
|
|
224
|
+
console.warn(g.f("Please rework your app to use the offical solution for injecting \"options\" argument from request context,\nsee %s", "http://loopback.io/doc/en/lb3/Using-current-context.html"));
|
|
225
|
+
}
|
|
226
|
+
sharedClass.resolve(function resolver(define) {
|
|
227
|
+
const relations = ModelCtor.relations || {};
|
|
228
|
+
const defineRaw = define;
|
|
229
|
+
define = function(name, options, fn) {
|
|
230
|
+
if (options.accepts) {
|
|
231
|
+
options = Object.assign({}, options);
|
|
232
|
+
options.accepts = setupOptionsArgs(options.accepts);
|
|
233
|
+
}
|
|
234
|
+
defineRaw(name, options, fn);
|
|
235
|
+
};
|
|
236
|
+
for (const relationName in relations) {
|
|
237
|
+
const relation = relations[relationName];
|
|
238
|
+
if (relation.type === "belongsTo") ModelCtor.belongsToRemoting(relationName, relation, define);
|
|
239
|
+
else if (relation.type === "hasOne" || relation.type === "embedsOne") ModelCtor.hasOneRemoting(relationName, relation, define);
|
|
240
|
+
else if (relation.type === "hasMany" || relation.type === "embedsMany" || relation.type === "referencesMany") ModelCtor.hasManyRemoting(relationName, relation, define);
|
|
241
|
+
if (relation.options && relation.options.nestRemoting) ModelCtor.nestRemoting(relationName);
|
|
242
|
+
}
|
|
243
|
+
const scopes = ModelCtor.scopes || {};
|
|
244
|
+
for (const scopeName in scopes) ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
|
|
245
|
+
});
|
|
246
|
+
return ModelCtor;
|
|
247
|
+
};
|
|
248
|
+
/*!
|
|
249
|
+
* Get the reference to ACL in a lazy fashion to avoid race condition in require
|
|
250
|
+
*/
|
|
251
|
+
let _aclModel = null;
|
|
252
|
+
Model._ACL = function getACL(ACL) {
|
|
253
|
+
const registry = this.registry;
|
|
254
|
+
if (ACL !== void 0) _aclModel = ACL;
|
|
255
|
+
if (_aclModel) return _aclModel;
|
|
256
|
+
const aclModel = registry.getModel("ACL");
|
|
257
|
+
_aclModel = registry.getModelByType(aclModel);
|
|
258
|
+
return _aclModel;
|
|
259
|
+
};
|
|
260
|
+
/**
|
|
261
|
+
* Check if the given access token can invoke the specified method.
|
|
262
|
+
*
|
|
263
|
+
* @param {AccessToken} token The access token.
|
|
264
|
+
* @param {*} modelId The model ID.
|
|
265
|
+
* @param {SharedMethod} sharedMethod The method in question.
|
|
266
|
+
* @param {Object} ctx The remote invocation context.
|
|
267
|
+
* @callback {Function} callback The callback function.
|
|
268
|
+
* @param {String|Error} err The error object.
|
|
269
|
+
* @param {Boolean} allowed True if the request is allowed; false otherwise.
|
|
270
|
+
*/
|
|
271
|
+
Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) {
|
|
272
|
+
const ANONYMOUS = registry.getModel("AccessToken").ANONYMOUS;
|
|
273
|
+
token = token || ANONYMOUS;
|
|
274
|
+
const aclModel = Model._ACL();
|
|
275
|
+
ctx = ctx || {};
|
|
276
|
+
if (typeof ctx === "function" && callback === void 0) {
|
|
277
|
+
callback = ctx;
|
|
278
|
+
ctx = {};
|
|
279
|
+
}
|
|
280
|
+
callback = callback || utils.createPromiseCallback();
|
|
281
|
+
utils.invokeWithCallback(aclModel.checkAccessForContext, aclModel, [{
|
|
282
|
+
accessToken: token,
|
|
283
|
+
model: this,
|
|
284
|
+
property: sharedMethod.name,
|
|
285
|
+
method: sharedMethod.name,
|
|
286
|
+
sharedMethod,
|
|
287
|
+
modelId,
|
|
288
|
+
accessType: this._getAccessTypeForMethod(sharedMethod),
|
|
289
|
+
remotingContext: ctx
|
|
290
|
+
}]).then(function(accessRequest) {
|
|
291
|
+
callback(null, accessRequest.isAllowed());
|
|
292
|
+
}, callback);
|
|
293
|
+
return callback.promise;
|
|
294
|
+
};
|
|
295
|
+
/*!
|
|
296
|
+
* Determine the access type for the given `RemoteMethod`.
|
|
297
|
+
*
|
|
298
|
+
* @api private
|
|
299
|
+
* @param {RemoteMethod} method
|
|
300
|
+
*/
|
|
301
|
+
Model._getAccessTypeForMethod = function(method) {
|
|
302
|
+
if (typeof method === "string") method = { name: method };
|
|
303
|
+
assert(typeof method === "object", "method is a required argument and must be a RemoteMethod object");
|
|
304
|
+
const ACL = Model._ACL();
|
|
305
|
+
if (method.accessType) {
|
|
306
|
+
assert(method.accessType === ACL.READ || method.accessType === ACL.REPLICATE || method.accessType === ACL.WRITE || method.accessType === ACL.EXECUTE, "invalid accessType " + method.accessType + ". It must be \"READ\", \"REPLICATE\", \"WRITE\", or \"EXECUTE\"");
|
|
307
|
+
return method.accessType;
|
|
308
|
+
}
|
|
309
|
+
let verb = method.http && method.http.verb;
|
|
310
|
+
if (typeof verb === "string") verb = verb.toUpperCase();
|
|
311
|
+
if (verb === "GET" || verb === "HEAD") return ACL.READ;
|
|
312
|
+
switch (method.name) {
|
|
313
|
+
case "create": return ACL.WRITE;
|
|
314
|
+
case "updateOrCreate": return ACL.WRITE;
|
|
315
|
+
case "upsertWithWhere": return ACL.WRITE;
|
|
316
|
+
case "upsert": return ACL.WRITE;
|
|
317
|
+
case "exists": return ACL.READ;
|
|
318
|
+
case "findById": return ACL.READ;
|
|
319
|
+
case "find": return ACL.READ;
|
|
320
|
+
case "findOne": return ACL.READ;
|
|
321
|
+
case "destroyById": return ACL.WRITE;
|
|
322
|
+
case "deleteById": return ACL.WRITE;
|
|
323
|
+
case "removeById": return ACL.WRITE;
|
|
324
|
+
case "count": return ACL.READ;
|
|
325
|
+
default: return ACL.EXECUTE;
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
/**
|
|
329
|
+
* Get the `Application` object to which the Model is attached.
|
|
330
|
+
*
|
|
331
|
+
* @callback {Function} callback Callback function called with `(err, app)` arguments.
|
|
332
|
+
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
|
|
333
|
+
* @param {Application} app Attached application object.
|
|
334
|
+
* @end
|
|
335
|
+
*/
|
|
336
|
+
Model.getApp = function(callback) {
|
|
337
|
+
const self = this;
|
|
338
|
+
callback = callback || utils.createPromiseCallback();
|
|
339
|
+
self._runWhenAttachedToApp().then(function(app) {
|
|
340
|
+
assert(self.app);
|
|
341
|
+
assert.equal(app, self.app);
|
|
342
|
+
callback(null, app);
|
|
343
|
+
}, callback);
|
|
344
|
+
return callback.promise;
|
|
345
|
+
};
|
|
346
|
+
/**
|
|
347
|
+
* Enable remote invocation for the specified method.
|
|
348
|
+
* See [Remote methods](http://loopback.io/doc/en/lb2/Remote-methods.html) for more information.
|
|
349
|
+
*
|
|
350
|
+
* Static method example:
|
|
351
|
+
* ```js
|
|
352
|
+
* Model.myMethod();
|
|
353
|
+
* Model.remoteMethod('myMethod');
|
|
354
|
+
* ```
|
|
355
|
+
*
|
|
356
|
+
* @param {String} name The name of the method.
|
|
357
|
+
* @param {Object} options The remoting options.
|
|
358
|
+
* See [Remote methods - Options](http://loopback.io/doc/en/lb2/Remote-methods.html#options).
|
|
359
|
+
*/
|
|
360
|
+
Model.remoteMethod = function(name, options) {
|
|
361
|
+
if (options.isStatic === void 0) {
|
|
362
|
+
const m = name.match(/^prototype\.(.*)$/);
|
|
363
|
+
options.isStatic = !m;
|
|
364
|
+
name = options.isStatic ? name : m[1];
|
|
365
|
+
}
|
|
366
|
+
if (options.accepts) {
|
|
367
|
+
options = Object.assign({}, options);
|
|
368
|
+
options.accepts = setupOptionsArgs(options.accepts, this);
|
|
369
|
+
}
|
|
370
|
+
this.sharedClass.defineMethod(name, options);
|
|
371
|
+
this.emit("remoteMethodAdded", this.sharedClass);
|
|
372
|
+
};
|
|
373
|
+
function setupOptionsArgs(accepts, modelClass) {
|
|
374
|
+
if (!Array.isArray(accepts)) accepts = [accepts];
|
|
375
|
+
return accepts.map(function(arg) {
|
|
376
|
+
if (arg.http && arg.http === "optionsFromRequest") {
|
|
377
|
+
arg = Object.assign({}, arg);
|
|
378
|
+
arg.http = createOptionsViaModelMethod.bind(modelClass);
|
|
379
|
+
}
|
|
380
|
+
return arg;
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
function createOptionsViaModelMethod(ctx) {
|
|
384
|
+
const ModelCtor = ctx.method && ctx.method.ctor || this;
|
|
385
|
+
/**
|
|
386
|
+
* Configure default values for juggler settings to protect user-supplied
|
|
387
|
+
* input from attacking juggler
|
|
388
|
+
*/
|
|
389
|
+
const DEFAULT_OPTIONS = {
|
|
390
|
+
prohibitHiddenPropertiesInQuery: ModelCtor._getProhibitHiddenPropertiesInQuery({}, true),
|
|
391
|
+
maxDepthOfQuery: ModelCtor._getMaxDepthOfQuery({}, 12),
|
|
392
|
+
maxDepthOfData: ModelCtor._getMaxDepthOfData({}, 32)
|
|
393
|
+
};
|
|
394
|
+
if (typeof ModelCtor.createOptionsFromRemotingContext !== "function") return DEFAULT_OPTIONS;
|
|
395
|
+
debug("createOptionsFromRemotingContext for %s", ctx.method.stringName);
|
|
396
|
+
return Object.assign(DEFAULT_OPTIONS, ModelCtor.createOptionsFromRemotingContext(ctx));
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Disable remote invocation for the method with the given name.
|
|
400
|
+
*
|
|
401
|
+
* @param {String} name The name of the method.
|
|
402
|
+
* @param {Boolean} isStatic Is the method static (eg. `MyModel.myMethod`)? Pass
|
|
403
|
+
* `false` if the method defined on the prototype (eg.
|
|
404
|
+
* `MyModel.prototype.myMethod`).
|
|
405
|
+
*/
|
|
406
|
+
Model.disableRemoteMethod = function(name, isStatic) {
|
|
407
|
+
deprecated("Model.disableRemoteMethod is deprecated. Use Model.disableRemoteMethodByName instead.");
|
|
408
|
+
const key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic);
|
|
409
|
+
this.sharedClass.disableMethodByName(key);
|
|
410
|
+
this.emit("remoteMethodDisabled", this.sharedClass, key);
|
|
411
|
+
};
|
|
412
|
+
/**
|
|
413
|
+
* Disable remote invocation for the method with the given name.
|
|
414
|
+
*
|
|
415
|
+
* @param {String} name The name of the method (include "prototype." if the method is defined on the prototype).
|
|
416
|
+
*
|
|
417
|
+
*/
|
|
418
|
+
Model.disableRemoteMethodByName = function(name) {
|
|
419
|
+
this.sharedClass.disableMethodByName(name);
|
|
420
|
+
this.emit("remoteMethodDisabled", this.sharedClass, name);
|
|
421
|
+
};
|
|
422
|
+
Model.belongsToRemoting = function(relationName, relation, define) {
|
|
423
|
+
let modelName = relation.modelTo && relation.modelTo.modelName;
|
|
424
|
+
modelName = modelName || "PersistedModel";
|
|
425
|
+
const fn = this.prototype[relationName];
|
|
426
|
+
const pathName = relation.options.http && relation.options.http.path || relationName;
|
|
427
|
+
define("__get__" + relationName, {
|
|
428
|
+
isStatic: false,
|
|
429
|
+
http: {
|
|
430
|
+
verb: "get",
|
|
431
|
+
path: "/" + pathName
|
|
432
|
+
},
|
|
433
|
+
accepts: [{
|
|
434
|
+
arg: "refresh",
|
|
435
|
+
type: "boolean",
|
|
436
|
+
http: { source: "query" }
|
|
437
|
+
}, {
|
|
438
|
+
arg: "options",
|
|
439
|
+
type: "object",
|
|
440
|
+
http: "optionsFromRequest"
|
|
441
|
+
}],
|
|
442
|
+
accessType: "READ",
|
|
443
|
+
description: g.f("Fetches belongsTo relation %s.", relationName),
|
|
444
|
+
returns: {
|
|
445
|
+
arg: relationName,
|
|
446
|
+
type: modelName,
|
|
447
|
+
root: true
|
|
448
|
+
}
|
|
449
|
+
}, fn);
|
|
450
|
+
};
|
|
451
|
+
function convertNullToNotFoundError(toModelName, ctx, cb) {
|
|
452
|
+
if (ctx.result !== null) return cb();
|
|
453
|
+
const fk = ctx.getArgByName("fk");
|
|
454
|
+
const msg = fk ? g.f("Unknown \"%s\" id \"%s\".", toModelName, fk) : g.f("No \"%s\" instance(s) found", toModelName);
|
|
455
|
+
const error = new Error(msg);
|
|
456
|
+
error.statusCode = error.status = 404;
|
|
457
|
+
error.code = "MODEL_NOT_FOUND";
|
|
458
|
+
cb(error);
|
|
459
|
+
}
|
|
460
|
+
Model.hasOneRemoting = function(relationName, relation, define) {
|
|
461
|
+
const pathName = relation.options.http && relation.options.http.path || relationName;
|
|
462
|
+
const toModelName = relation.modelTo.modelName;
|
|
463
|
+
define("__get__" + relationName, {
|
|
464
|
+
isStatic: false,
|
|
465
|
+
http: {
|
|
466
|
+
verb: "get",
|
|
467
|
+
path: "/" + pathName
|
|
468
|
+
},
|
|
469
|
+
accepts: [{
|
|
470
|
+
arg: "refresh",
|
|
471
|
+
type: "boolean",
|
|
472
|
+
http: { source: "query" }
|
|
473
|
+
}, {
|
|
474
|
+
arg: "options",
|
|
475
|
+
type: "object",
|
|
476
|
+
http: "optionsFromRequest"
|
|
477
|
+
}],
|
|
478
|
+
description: g.f("Fetches hasOne relation %s.", relationName),
|
|
479
|
+
accessType: "READ",
|
|
480
|
+
returns: {
|
|
481
|
+
arg: relationName,
|
|
482
|
+
type: relation.modelTo.modelName,
|
|
483
|
+
root: true
|
|
484
|
+
},
|
|
485
|
+
rest: { after: convertNullToNotFoundError.bind(null, toModelName) }
|
|
486
|
+
});
|
|
487
|
+
define("__create__" + relationName, {
|
|
488
|
+
isStatic: false,
|
|
489
|
+
http: {
|
|
490
|
+
verb: "post",
|
|
491
|
+
path: "/" + pathName
|
|
492
|
+
},
|
|
493
|
+
accepts: [{
|
|
494
|
+
arg: "data",
|
|
495
|
+
type: "object",
|
|
496
|
+
model: toModelName,
|
|
497
|
+
http: { source: "body" }
|
|
498
|
+
}, {
|
|
499
|
+
arg: "options",
|
|
500
|
+
type: "object",
|
|
501
|
+
http: "optionsFromRequest"
|
|
502
|
+
}],
|
|
503
|
+
description: g.f("Creates a new instance in %s of this model.", relationName),
|
|
504
|
+
accessType: "WRITE",
|
|
505
|
+
returns: {
|
|
506
|
+
arg: "data",
|
|
507
|
+
type: toModelName,
|
|
508
|
+
root: true
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
define("__update__" + relationName, {
|
|
512
|
+
isStatic: false,
|
|
513
|
+
http: {
|
|
514
|
+
verb: "put",
|
|
515
|
+
path: "/" + pathName
|
|
516
|
+
},
|
|
517
|
+
accepts: [{
|
|
518
|
+
arg: "data",
|
|
519
|
+
type: "object",
|
|
520
|
+
model: toModelName,
|
|
521
|
+
http: { source: "body" }
|
|
522
|
+
}, {
|
|
523
|
+
arg: "options",
|
|
524
|
+
type: "object",
|
|
525
|
+
http: "optionsFromRequest"
|
|
526
|
+
}],
|
|
527
|
+
description: g.f("Update %s of this model.", relationName),
|
|
528
|
+
accessType: "WRITE",
|
|
529
|
+
returns: {
|
|
530
|
+
arg: "data",
|
|
531
|
+
type: toModelName,
|
|
532
|
+
root: true
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
define("__destroy__" + relationName, {
|
|
536
|
+
isStatic: false,
|
|
537
|
+
http: {
|
|
538
|
+
verb: "delete",
|
|
539
|
+
path: "/" + pathName
|
|
540
|
+
},
|
|
541
|
+
accepts: [{
|
|
542
|
+
arg: "options",
|
|
543
|
+
type: "object",
|
|
544
|
+
http: "optionsFromRequest"
|
|
545
|
+
}],
|
|
546
|
+
description: g.f("Deletes %s of this model.", relationName),
|
|
547
|
+
accessType: "WRITE"
|
|
548
|
+
});
|
|
549
|
+
};
|
|
550
|
+
Model.hasManyRemoting = function(relationName, relation, define) {
|
|
551
|
+
const pathName = relation.options.http && relation.options.http.path || relationName;
|
|
552
|
+
const toModelName = relation.modelTo.modelName;
|
|
553
|
+
const findByIdFunc = this.prototype["__findById__" + relationName];
|
|
554
|
+
define("__findById__" + relationName, {
|
|
555
|
+
isStatic: false,
|
|
556
|
+
http: {
|
|
557
|
+
verb: "get",
|
|
558
|
+
path: "/" + pathName + "/:fk"
|
|
559
|
+
},
|
|
560
|
+
accepts: [{
|
|
561
|
+
arg: "fk",
|
|
562
|
+
type: "any",
|
|
563
|
+
description: g.f("Foreign key for %s", relationName),
|
|
564
|
+
required: true,
|
|
565
|
+
http: { source: "path" }
|
|
566
|
+
}, {
|
|
567
|
+
arg: "options",
|
|
568
|
+
type: "object",
|
|
569
|
+
http: "optionsFromRequest"
|
|
570
|
+
}],
|
|
571
|
+
description: g.f("Find a related item by id for %s.", relationName),
|
|
572
|
+
accessType: "READ",
|
|
573
|
+
returns: {
|
|
574
|
+
arg: "result",
|
|
575
|
+
type: toModelName,
|
|
576
|
+
root: true
|
|
577
|
+
},
|
|
578
|
+
rest: { after: convertNullToNotFoundError.bind(null, toModelName) }
|
|
579
|
+
}, findByIdFunc);
|
|
580
|
+
const destroyByIdFunc = this.prototype["__destroyById__" + relationName];
|
|
581
|
+
define("__destroyById__" + relationName, {
|
|
582
|
+
isStatic: false,
|
|
583
|
+
http: {
|
|
584
|
+
verb: "delete",
|
|
585
|
+
path: "/" + pathName + "/:fk"
|
|
586
|
+
},
|
|
587
|
+
accepts: [{
|
|
588
|
+
arg: "fk",
|
|
589
|
+
type: "any",
|
|
590
|
+
description: g.f("Foreign key for %s", relationName),
|
|
591
|
+
required: true,
|
|
592
|
+
http: { source: "path" }
|
|
593
|
+
}, {
|
|
594
|
+
arg: "options",
|
|
595
|
+
type: "object",
|
|
596
|
+
http: "optionsFromRequest"
|
|
597
|
+
}],
|
|
598
|
+
description: g.f("Delete a related item by id for %s.", relationName),
|
|
599
|
+
accessType: "WRITE",
|
|
600
|
+
returns: []
|
|
601
|
+
}, destroyByIdFunc);
|
|
602
|
+
const updateByIdFunc = this.prototype["__updateById__" + relationName];
|
|
603
|
+
define("__updateById__" + relationName, {
|
|
604
|
+
isStatic: false,
|
|
605
|
+
http: {
|
|
606
|
+
verb: "put",
|
|
607
|
+
path: "/" + pathName + "/:fk"
|
|
608
|
+
},
|
|
609
|
+
accepts: [
|
|
610
|
+
{
|
|
611
|
+
arg: "fk",
|
|
612
|
+
type: "any",
|
|
613
|
+
description: g.f("Foreign key for %s", relationName),
|
|
614
|
+
required: true,
|
|
615
|
+
http: { source: "path" }
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
arg: "data",
|
|
619
|
+
type: "object",
|
|
620
|
+
model: toModelName,
|
|
621
|
+
http: { source: "body" }
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
arg: "options",
|
|
625
|
+
type: "object",
|
|
626
|
+
http: "optionsFromRequest"
|
|
627
|
+
}
|
|
628
|
+
],
|
|
629
|
+
description: g.f("Update a related item by id for %s.", relationName),
|
|
630
|
+
accessType: "WRITE",
|
|
631
|
+
returns: {
|
|
632
|
+
arg: "result",
|
|
633
|
+
type: toModelName,
|
|
634
|
+
root: true
|
|
635
|
+
}
|
|
636
|
+
}, updateByIdFunc);
|
|
637
|
+
if (relation.modelThrough || relation.type === "referencesMany") {
|
|
638
|
+
const modelThrough = relation.modelThrough || relation.modelTo;
|
|
639
|
+
const accepts = [];
|
|
640
|
+
if (relation.type === "hasMany" && relation.modelThrough) accepts.push({
|
|
641
|
+
arg: "data",
|
|
642
|
+
type: "object",
|
|
643
|
+
model: modelThrough.modelName,
|
|
644
|
+
http: { source: "body" }
|
|
645
|
+
});
|
|
646
|
+
const addFunc = this.prototype["__link__" + relationName];
|
|
647
|
+
define("__link__" + relationName, {
|
|
648
|
+
isStatic: false,
|
|
649
|
+
http: {
|
|
650
|
+
verb: "put",
|
|
651
|
+
path: "/" + pathName + "/rel/:fk"
|
|
652
|
+
},
|
|
653
|
+
accepts: [{
|
|
654
|
+
arg: "fk",
|
|
655
|
+
type: "any",
|
|
656
|
+
description: g.f("Foreign key for %s", relationName),
|
|
657
|
+
required: true,
|
|
658
|
+
http: { source: "path" }
|
|
659
|
+
}].concat(accepts).concat([{
|
|
660
|
+
arg: "options",
|
|
661
|
+
type: "object",
|
|
662
|
+
http: "optionsFromRequest"
|
|
663
|
+
}]),
|
|
664
|
+
description: g.f("Add a related item by id for %s.", relationName),
|
|
665
|
+
accessType: "WRITE",
|
|
666
|
+
returns: {
|
|
667
|
+
arg: relationName,
|
|
668
|
+
type: modelThrough.modelName,
|
|
669
|
+
root: true
|
|
670
|
+
}
|
|
671
|
+
}, addFunc);
|
|
672
|
+
const removeFunc = this.prototype["__unlink__" + relationName];
|
|
673
|
+
define("__unlink__" + relationName, {
|
|
674
|
+
isStatic: false,
|
|
675
|
+
http: {
|
|
676
|
+
verb: "delete",
|
|
677
|
+
path: "/" + pathName + "/rel/:fk"
|
|
678
|
+
},
|
|
679
|
+
accepts: [{
|
|
680
|
+
arg: "fk",
|
|
681
|
+
type: "any",
|
|
682
|
+
description: g.f("Foreign key for %s", relationName),
|
|
683
|
+
required: true,
|
|
684
|
+
http: { source: "path" }
|
|
685
|
+
}, {
|
|
686
|
+
arg: "options",
|
|
687
|
+
type: "object",
|
|
688
|
+
http: "optionsFromRequest"
|
|
689
|
+
}],
|
|
690
|
+
description: g.f("Remove the %s relation to an item by id.", relationName),
|
|
691
|
+
accessType: "WRITE",
|
|
692
|
+
returns: []
|
|
693
|
+
}, removeFunc);
|
|
694
|
+
const existsFunc = this.prototype["__exists__" + relationName];
|
|
695
|
+
define("__exists__" + relationName, {
|
|
696
|
+
isStatic: false,
|
|
697
|
+
http: {
|
|
698
|
+
verb: "head",
|
|
699
|
+
path: "/" + pathName + "/rel/:fk"
|
|
700
|
+
},
|
|
701
|
+
accepts: [{
|
|
702
|
+
arg: "fk",
|
|
703
|
+
type: "any",
|
|
704
|
+
description: g.f("Foreign key for %s", relationName),
|
|
705
|
+
required: true,
|
|
706
|
+
http: { source: "path" }
|
|
707
|
+
}, {
|
|
708
|
+
arg: "options",
|
|
709
|
+
type: "object",
|
|
710
|
+
http: "optionsFromRequest"
|
|
711
|
+
}],
|
|
712
|
+
description: g.f("Check the existence of %s relation to an item by id.", relationName),
|
|
713
|
+
accessType: "READ",
|
|
714
|
+
returns: {
|
|
715
|
+
arg: "exists",
|
|
716
|
+
type: "boolean",
|
|
717
|
+
root: true
|
|
718
|
+
},
|
|
719
|
+
rest: { after: function(ctx, cb) {
|
|
720
|
+
if (ctx.result === false) {
|
|
721
|
+
const modelName = ctx.method.sharedClass.name;
|
|
722
|
+
const id = ctx.getArgByName("id");
|
|
723
|
+
const msg = g.f("Unknown \"%s\" {{id}} \"%s\".", modelName, id);
|
|
724
|
+
const error = new Error(msg);
|
|
725
|
+
error.statusCode = error.status = 404;
|
|
726
|
+
error.code = "MODEL_NOT_FOUND";
|
|
727
|
+
cb(error);
|
|
728
|
+
} else cb();
|
|
729
|
+
} }
|
|
730
|
+
}, existsFunc);
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
Model.scopeRemoting = function(scopeName, scope, define) {
|
|
734
|
+
const pathName = scope.options && scope.options.http && scope.options.http.path || scopeName;
|
|
735
|
+
let modelTo = scope.modelTo;
|
|
736
|
+
const isStatic = scope.isStatic;
|
|
737
|
+
let toModelName = scope.modelTo.modelName;
|
|
738
|
+
const relation = this.relations[scopeName];
|
|
739
|
+
if (relation && relation.modelTo) {
|
|
740
|
+
toModelName = relation.modelTo.modelName;
|
|
741
|
+
modelTo = relation.modelTo;
|
|
742
|
+
}
|
|
743
|
+
const updateOnlyProps = modelTo.getUpdateOnlyProperties ? modelTo.getUpdateOnlyProperties() : false;
|
|
744
|
+
const hasUpdateOnlyProps = updateOnlyProps && updateOnlyProps.length > 0;
|
|
745
|
+
define("__get__" + scopeName, {
|
|
746
|
+
isStatic,
|
|
747
|
+
http: {
|
|
748
|
+
verb: "get",
|
|
749
|
+
path: "/" + pathName
|
|
750
|
+
},
|
|
751
|
+
accepts: [{
|
|
752
|
+
arg: "filter",
|
|
753
|
+
type: "object"
|
|
754
|
+
}, {
|
|
755
|
+
arg: "options",
|
|
756
|
+
type: "object",
|
|
757
|
+
http: "optionsFromRequest"
|
|
758
|
+
}],
|
|
759
|
+
description: g.f("Queries %s of %s.", scopeName, this.modelName),
|
|
760
|
+
accessType: "READ",
|
|
761
|
+
returns: {
|
|
762
|
+
arg: scopeName,
|
|
763
|
+
type: [toModelName],
|
|
764
|
+
root: true
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
define("__create__" + scopeName, {
|
|
768
|
+
isStatic,
|
|
769
|
+
http: {
|
|
770
|
+
verb: "post",
|
|
771
|
+
path: "/" + pathName
|
|
772
|
+
},
|
|
773
|
+
accepts: [{
|
|
774
|
+
arg: "data",
|
|
775
|
+
type: "object",
|
|
776
|
+
allowArray: true,
|
|
777
|
+
model: toModelName,
|
|
778
|
+
createOnlyInstance: hasUpdateOnlyProps,
|
|
779
|
+
http: { source: "body" }
|
|
780
|
+
}, {
|
|
781
|
+
arg: "options",
|
|
782
|
+
type: "object",
|
|
783
|
+
http: "optionsFromRequest"
|
|
784
|
+
}],
|
|
785
|
+
description: g.f("Creates a new instance in %s of this model.", scopeName),
|
|
786
|
+
accessType: "WRITE",
|
|
787
|
+
returns: {
|
|
788
|
+
arg: "data",
|
|
789
|
+
type: toModelName,
|
|
790
|
+
root: true
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
define("__delete__" + scopeName, {
|
|
794
|
+
isStatic,
|
|
795
|
+
http: {
|
|
796
|
+
verb: "delete",
|
|
797
|
+
path: "/" + pathName
|
|
798
|
+
},
|
|
799
|
+
accepts: [{
|
|
800
|
+
arg: "where",
|
|
801
|
+
type: "object",
|
|
802
|
+
http: function(ctx) {}
|
|
803
|
+
}, {
|
|
804
|
+
arg: "options",
|
|
805
|
+
type: "object",
|
|
806
|
+
http: "optionsFromRequest"
|
|
807
|
+
}],
|
|
808
|
+
description: g.f("Deletes all %s of this model.", scopeName),
|
|
809
|
+
accessType: "WRITE"
|
|
810
|
+
});
|
|
811
|
+
define("__count__" + scopeName, {
|
|
812
|
+
isStatic,
|
|
813
|
+
http: {
|
|
814
|
+
verb: "get",
|
|
815
|
+
path: "/" + pathName + "/count"
|
|
816
|
+
},
|
|
817
|
+
accepts: [{
|
|
818
|
+
arg: "where",
|
|
819
|
+
type: "object",
|
|
820
|
+
description: "Criteria to match model instances"
|
|
821
|
+
}, {
|
|
822
|
+
arg: "options",
|
|
823
|
+
type: "object",
|
|
824
|
+
http: "optionsFromRequest"
|
|
825
|
+
}],
|
|
826
|
+
description: g.f("Counts %s of %s.", scopeName, this.modelName),
|
|
827
|
+
accessType: "READ",
|
|
828
|
+
returns: {
|
|
829
|
+
arg: "count",
|
|
830
|
+
type: "number"
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
};
|
|
834
|
+
/**
|
|
835
|
+
* Enabled deeply-nested queries of related models via REST API.
|
|
836
|
+
*
|
|
837
|
+
* @param {String} relationName Name of the nested relation.
|
|
838
|
+
* @options {Object} [options] It is optional. See below.
|
|
839
|
+
* @param {String} pathName The HTTP path (relative to the model) at which your remote method is exposed.
|
|
840
|
+
* @param {String} filterMethod The filter name.
|
|
841
|
+
* @param {String} paramName The argument name that the remote method accepts.
|
|
842
|
+
* @param {String} getterName The getter name.
|
|
843
|
+
* @param {Boolean} hooks Whether to inherit before/after hooks.
|
|
844
|
+
* @callback {Function} filterCallback The Optional filter function.
|
|
845
|
+
* @param {Object} SharedMethod object. See [here](https://apidocs.strongloop.com/strong-remoting/#sharedmethod).
|
|
846
|
+
* @param {Object} RelationDefinition object which includes relation `type`, `ModelConstructor` of `modelFrom`, `modelTo`, `keyFrom`, `keyTo` and more relation definitions.
|
|
847
|
+
*/
|
|
848
|
+
Model.nestRemoting = function(relationName, options, filterCallback) {
|
|
849
|
+
if (typeof options === "function" && !filterCallback) {
|
|
850
|
+
filterCallback = options;
|
|
851
|
+
options = {};
|
|
852
|
+
}
|
|
853
|
+
options = options || {};
|
|
854
|
+
const regExp = /^__([^_]+)__([^_]+)$/;
|
|
855
|
+
const relation = this.relations[relationName];
|
|
856
|
+
if (relation && relation._nestRemotingProcessed) return;
|
|
857
|
+
else if (relation && relation.modelTo && relation.modelTo.sharedClass) {
|
|
858
|
+
relation._nestRemotingProcessed = true;
|
|
859
|
+
const self = this;
|
|
860
|
+
const sharedClass = this.sharedClass;
|
|
861
|
+
const sharedToClass = relation.modelTo.sharedClass;
|
|
862
|
+
const toModelName = relation.modelTo.modelName;
|
|
863
|
+
const pathName = options.pathName || relation.options.path || relationName;
|
|
864
|
+
const paramName = options.paramName || "nk";
|
|
865
|
+
[].concat(sharedToClass.http || [])[0];
|
|
866
|
+
let httpPath, acceptArgs;
|
|
867
|
+
if (relation.multiple) {
|
|
868
|
+
httpPath = pathName + "/:" + paramName;
|
|
869
|
+
acceptArgs = [{
|
|
870
|
+
arg: paramName,
|
|
871
|
+
type: "any",
|
|
872
|
+
http: { source: "path" },
|
|
873
|
+
description: g.f("Foreign key for %s.", relation.name),
|
|
874
|
+
required: true
|
|
875
|
+
}];
|
|
876
|
+
} else {
|
|
877
|
+
httpPath = pathName;
|
|
878
|
+
acceptArgs = [];
|
|
879
|
+
}
|
|
880
|
+
if (httpPath[0] !== "/") httpPath = "/" + httpPath;
|
|
881
|
+
const filter = filterCallback || options.filterMethod || function(method, relation) {
|
|
882
|
+
const matches = method.name.match(regExp);
|
|
883
|
+
if (matches) return "__" + matches[1] + "__" + relation.name + "__" + matches[2];
|
|
884
|
+
};
|
|
885
|
+
sharedToClass.methods().forEach(function(method) {
|
|
886
|
+
let methodName;
|
|
887
|
+
if (!method.isStatic && (methodName = filter(method, relation))) {
|
|
888
|
+
const prefix = relation.multiple ? "__findById__" : "__get__";
|
|
889
|
+
const getterName = options.getterName || prefix + relationName;
|
|
890
|
+
if (typeof relation.modelFrom.prototype[getterName] !== "function") throw new Error(g.f("Invalid remote method: `%s`", getterName));
|
|
891
|
+
const nestedFn = relation.modelTo.prototype[method.name];
|
|
892
|
+
if (typeof nestedFn !== "function") throw new Error(g.f("Invalid remote method: `%s`", method.name));
|
|
893
|
+
const opts = {};
|
|
894
|
+
opts.accepts = acceptArgs.concat(method.accepts || []);
|
|
895
|
+
opts.returns = [].concat(method.returns || []);
|
|
896
|
+
opts.description = method.description;
|
|
897
|
+
opts.accessType = method.accessType;
|
|
898
|
+
opts.rest = Object.assign({}, method.rest || {});
|
|
899
|
+
opts.rest.delegateTo = method;
|
|
900
|
+
opts.http = [];
|
|
901
|
+
[].concat(method.http || []).forEach(function(route) {
|
|
902
|
+
if (route.path) {
|
|
903
|
+
const copy = Object.assign({}, route);
|
|
904
|
+
copy.path = httpPath + route.path;
|
|
905
|
+
opts.http.push(copy);
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
const lastArg = opts.accepts[opts.accepts.length - 1] || {};
|
|
909
|
+
const hasOptionsFromContext = (lastArg.arg || lastArg.name) === "options" && lastArg.type === "object" && lastArg.http;
|
|
910
|
+
if (relation.multiple) sharedClass.defineMethod(methodName, opts, function(fkId) {
|
|
911
|
+
const args = Array.prototype.slice.call(arguments, 1);
|
|
912
|
+
const cb = args[args.length - 1];
|
|
913
|
+
const contextOptions = hasOptionsFromContext && args[args.length - 2] || {};
|
|
914
|
+
utils.invokeWithCallback(this[getterName], this, [fkId, contextOptions]).then(function(inst) {
|
|
915
|
+
if (inst instanceof relation.modelTo) try {
|
|
916
|
+
nestedFn.apply(inst, args);
|
|
917
|
+
} catch (err) {
|
|
918
|
+
return cb(err);
|
|
919
|
+
}
|
|
920
|
+
else cb(null, null);
|
|
921
|
+
}, cb);
|
|
922
|
+
}, method.isStatic);
|
|
923
|
+
else sharedClass.defineMethod(methodName, opts, function() {
|
|
924
|
+
const args = Array.prototype.slice.call(arguments);
|
|
925
|
+
const cb = args[args.length - 1];
|
|
926
|
+
const contextOptions = hasOptionsFromContext && args[args.length - 2] || {};
|
|
927
|
+
utils.invokeWithCallback(this[getterName], this, [contextOptions]).then(function(inst) {
|
|
928
|
+
if (inst instanceof relation.modelTo) try {
|
|
929
|
+
nestedFn.apply(inst, args);
|
|
930
|
+
} catch (err) {
|
|
931
|
+
return cb(err);
|
|
932
|
+
}
|
|
933
|
+
else cb(null, null);
|
|
934
|
+
}, cb);
|
|
935
|
+
}, method.isStatic);
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
this.emit("remoteMethodAdded", this.sharedClass);
|
|
939
|
+
if (options.hooks === false) return;
|
|
940
|
+
self.once("mounted", function(app, sc, remotes) {
|
|
941
|
+
const listenerTree = Object.assign({}, remotes.listenerTree || {});
|
|
942
|
+
listenerTree.before = listenerTree.before || {};
|
|
943
|
+
listenerTree.after = listenerTree.after || {};
|
|
944
|
+
const beforeListeners = listenerTree.before[toModelName] || {};
|
|
945
|
+
const afterListeners = listenerTree.after[toModelName] || {};
|
|
946
|
+
sharedClass.methods().forEach(function(method) {
|
|
947
|
+
const delegateTo = method.rest && method.rest.delegateTo;
|
|
948
|
+
if (delegateTo && delegateTo.ctor == relation.modelTo) {
|
|
949
|
+
const before = method.isStatic ? beforeListeners : beforeListeners["prototype"];
|
|
950
|
+
const after = method.isStatic ? afterListeners : afterListeners["prototype"];
|
|
951
|
+
const m = method.isStatic ? method.name : "prototype." + method.name;
|
|
952
|
+
if (before && before[delegateTo.name]) self.beforeRemote(m, function(ctx, result, next) {
|
|
953
|
+
before[delegateTo.name]._listeners.call(null, ctx, next);
|
|
954
|
+
});
|
|
955
|
+
if (after && after[delegateTo.name]) self.afterRemote(m, function(ctx, result, next) {
|
|
956
|
+
after[delegateTo.name]._listeners.call(null, ctx, next);
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
});
|
|
960
|
+
});
|
|
961
|
+
} else {
|
|
962
|
+
const msg = g.f("Relation `%s` does not exist for model `%s`", relationName, this.modelName);
|
|
963
|
+
throw new Error(msg);
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
Model.ValidationError = require("@vsaas/loopback-datasource-juggler").ValidationError;
|
|
967
|
+
/**
|
|
968
|
+
* Create "options" value to use when invoking model methods
|
|
969
|
+
* via strong-remoting (e.g. REST).
|
|
970
|
+
*
|
|
971
|
+
* Example
|
|
972
|
+
*
|
|
973
|
+
* ```js
|
|
974
|
+
* MyModel.myMethod = function(options, cb) {
|
|
975
|
+
* // by default, options contains only one property "accessToken"
|
|
976
|
+
* var accessToken = options && options.accessToken;
|
|
977
|
+
* var userId = accessToken && accessToken.userId;
|
|
978
|
+
* var message = 'Hello ' + (userId ? 'user #' + userId : 'anonymous');
|
|
979
|
+
* cb(null, message);
|
|
980
|
+
* });
|
|
981
|
+
*
|
|
982
|
+
* MyModel.remoteMethod('myMethod', {
|
|
983
|
+
* accepts: {
|
|
984
|
+
* arg: 'options',
|
|
985
|
+
* type: 'object',
|
|
986
|
+
* // "optionsFromRequest" is a loopback-specific HTTP mapping that
|
|
987
|
+
* // calls Model's createOptionsFromRemotingContext
|
|
988
|
+
* // to build the argument value
|
|
989
|
+
* http: 'optionsFromRequest'
|
|
990
|
+
* },
|
|
991
|
+
* returns: {
|
|
992
|
+
* arg: 'message',
|
|
993
|
+
* type: 'string'
|
|
994
|
+
* }
|
|
995
|
+
* });
|
|
996
|
+
* ```
|
|
997
|
+
*
|
|
998
|
+
* @param {Object} ctx A strong-remoting Context instance
|
|
999
|
+
* @returns {Object} The value to pass to "options" argument.
|
|
1000
|
+
*/
|
|
1001
|
+
Model.createOptionsFromRemotingContext = function(ctx) {
|
|
1002
|
+
return { accessToken: ctx.req ? ctx.req.accessToken : null };
|
|
1003
|
+
};
|
|
1004
|
+
Model.setup();
|
|
1005
|
+
return Model;
|
|
1006
|
+
};
|
|
1007
|
+
}));
|
|
1008
|
+
//#endregion
|
|
1009
|
+
module.exports = require_model();
|