@vsaas/loopback-datasource-juggler 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +25 -0
- package/NOTICE +23 -0
- package/README.md +74 -0
- package/dist/_virtual/_rolldown/runtime.js +4 -0
- package/dist/index.js +26 -0
- package/dist/lib/browser.depd.js +13 -0
- package/dist/lib/case-utils.js +21 -0
- package/dist/lib/connectors/kv-memory.js +158 -0
- package/dist/lib/connectors/memory.js +810 -0
- package/dist/lib/connectors/transient.js +126 -0
- package/dist/lib/dao.js +2445 -0
- package/dist/lib/datasource.js +2215 -0
- package/dist/lib/date-string.js +87 -0
- package/dist/lib/geo.js +244 -0
- package/dist/lib/globalize.js +33 -0
- package/dist/lib/hooks.js +79 -0
- package/dist/lib/id-utils.js +66 -0
- package/dist/lib/include.js +795 -0
- package/dist/lib/include_utils.js +104 -0
- package/dist/lib/introspection.js +37 -0
- package/dist/lib/jutil.js +65 -0
- package/dist/lib/kvao/delete-all.js +57 -0
- package/dist/lib/kvao/delete.js +43 -0
- package/dist/lib/kvao/expire.js +35 -0
- package/dist/lib/kvao/get.js +34 -0
- package/dist/lib/kvao/index.js +28 -0
- package/dist/lib/kvao/iterate-keys.js +38 -0
- package/dist/lib/kvao/keys.js +55 -0
- package/dist/lib/kvao/set.js +39 -0
- package/dist/lib/kvao/ttl.js +35 -0
- package/dist/lib/list.js +101 -0
- package/dist/lib/mixins.js +58 -0
- package/dist/lib/model-builder.js +608 -0
- package/dist/lib/model-definition.js +231 -0
- package/dist/lib/model-utils.js +368 -0
- package/dist/lib/model.js +586 -0
- package/dist/lib/observer.js +235 -0
- package/dist/lib/relation-definition.js +2604 -0
- package/dist/lib/relations.js +587 -0
- package/dist/lib/scope.js +392 -0
- package/dist/lib/transaction.js +183 -0
- package/dist/lib/types.js +58 -0
- package/dist/lib/utils.js +625 -0
- package/dist/lib/validations.js +742 -0
- package/dist/package.js +93 -0
- package/package.json +85 -0
- package/types/common.d.ts +28 -0
- package/types/connector.d.ts +52 -0
- package/types/datasource.d.ts +324 -0
- package/types/date-string.d.ts +21 -0
- package/types/inclusion-mixin.d.ts +44 -0
- package/types/index.d.ts +36 -0
- package/types/kv-model.d.ts +201 -0
- package/types/model.d.ts +368 -0
- package/types/observer-mixin.d.ts +174 -0
- package/types/persisted-model.d.ts +505 -0
- package/types/query.d.ts +108 -0
- package/types/relation-mixin.d.ts +577 -0
- package/types/relation.d.ts +301 -0
- package/types/scope.d.ts +92 -0
- package/types/transaction-mixin.d.ts +47 -0
- package/types/types.d.ts +65 -0
- package/types/validation-mixin.d.ts +287 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const require_runtime = require("../_virtual/_rolldown/runtime.js");
|
|
3
|
+
const require_lib_model = require("./model.js");
|
|
4
|
+
//#region src/lib/mixins.ts
|
|
5
|
+
var require_mixins = /* @__PURE__ */ require_runtime.__commonJSMin(((exports, module) => {
|
|
6
|
+
const debug = require("debug")("loopback:mixin");
|
|
7
|
+
const assert = require("assert");
|
|
8
|
+
const DefaultModelBaseClass = require_lib_model;
|
|
9
|
+
function isModelClass(cls) {
|
|
10
|
+
if (!cls) return false;
|
|
11
|
+
return cls.prototype instanceof DefaultModelBaseClass;
|
|
12
|
+
}
|
|
13
|
+
module.exports = MixinProvider;
|
|
14
|
+
function MixinProvider(modelBuilder) {
|
|
15
|
+
this.modelBuilder = modelBuilder;
|
|
16
|
+
this.mixins = {};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Apply named mixin to the model class
|
|
20
|
+
* @param {Model} modelClass
|
|
21
|
+
* @param {String} name
|
|
22
|
+
* @param {Object} options
|
|
23
|
+
*/
|
|
24
|
+
MixinProvider.prototype.applyMixin = function applyMixin(modelClass, name, options) {
|
|
25
|
+
const fn = this.mixins[name];
|
|
26
|
+
if (typeof fn === "function") if (modelClass.dataSource) fn(modelClass, options || {});
|
|
27
|
+
else modelClass.once("dataSourceAttached", function() {
|
|
28
|
+
fn(modelClass, options || {});
|
|
29
|
+
});
|
|
30
|
+
else {
|
|
31
|
+
const model = this.modelBuilder.getModel(name);
|
|
32
|
+
if (model) {
|
|
33
|
+
debug("Mixin is resolved to a model: %s", name);
|
|
34
|
+
modelClass.mixin(model, options);
|
|
35
|
+
} else {
|
|
36
|
+
const errMsg = "Model \"" + modelClass.modelName + "\" uses unknown mixin: " + name;
|
|
37
|
+
debug(errMsg);
|
|
38
|
+
throw new Error(errMsg);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Define a mixin with name
|
|
44
|
+
* @param {String} name Name of the mixin
|
|
45
|
+
* @param {*) mixin The mixin function or a model
|
|
46
|
+
*/
|
|
47
|
+
MixinProvider.prototype.define = function defineMixin(name, mixin) {
|
|
48
|
+
assert(typeof mixin === "function", "The mixin must be a function or model class");
|
|
49
|
+
if (this.mixins[name]) debug("Duplicate mixin: %s", name);
|
|
50
|
+
else debug("Defining mixin: %s", name);
|
|
51
|
+
if (isModelClass(mixin)) this.mixins[name] = function(Model, options) {
|
|
52
|
+
Model.mixin(mixin, options);
|
|
53
|
+
};
|
|
54
|
+
else if (typeof mixin === "function") this.mixins[name] = mixin;
|
|
55
|
+
};
|
|
56
|
+
}));
|
|
57
|
+
//#endregion
|
|
58
|
+
module.exports = require_mixins();
|
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const require_runtime = require("../_virtual/_rolldown/runtime.js");
|
|
3
|
+
const require_lib_globalize = require("./globalize.js");
|
|
4
|
+
const require_lib_types = require("./types.js");
|
|
5
|
+
const require_lib_utils = require("./utils.js");
|
|
6
|
+
const require_lib_list = require("./list.js");
|
|
7
|
+
const require_lib_model = require("./model.js");
|
|
8
|
+
const require_lib_model_definition = require("./model-definition.js");
|
|
9
|
+
const require_lib_mixins = require("./mixins.js");
|
|
10
|
+
const require_lib_introspection = require("./introspection.js");
|
|
11
|
+
//#region src/lib/model-builder.ts
|
|
12
|
+
var require_model_builder = /* @__PURE__ */ require_runtime.__commonJSMin(((exports) => {
|
|
13
|
+
/*!
|
|
14
|
+
* Module dependencies
|
|
15
|
+
*/
|
|
16
|
+
const g = require_lib_globalize();
|
|
17
|
+
const inflection = require("inflection");
|
|
18
|
+
const EventEmitter = require("events").EventEmitter;
|
|
19
|
+
const util = require("util");
|
|
20
|
+
const assert = require("assert");
|
|
21
|
+
const deprecated = require("depd")("loopback-datasource-juggler");
|
|
22
|
+
const DefaultModelBaseClass = require_lib_model;
|
|
23
|
+
const List = require_lib_list;
|
|
24
|
+
const ModelDefinition = require_lib_model_definition;
|
|
25
|
+
const MixinProvider = require_lib_mixins;
|
|
26
|
+
const { deepMerge, deepMergeProperty, rankArrayElements, isClass, applyParentProperty } = require_lib_utils;
|
|
27
|
+
require_lib_types(ModelBuilder);
|
|
28
|
+
const introspect = require_lib_introspection(ModelBuilder);
|
|
29
|
+
/*!
|
|
30
|
+
* Export public API
|
|
31
|
+
*/
|
|
32
|
+
exports.ModelBuilder = exports.Schema = ModelBuilder;
|
|
33
|
+
/*!
|
|
34
|
+
* Helpers
|
|
35
|
+
*/
|
|
36
|
+
const slice = Array.prototype.slice;
|
|
37
|
+
/**
|
|
38
|
+
* ModelBuilder - A builder to define data models.
|
|
39
|
+
*
|
|
40
|
+
* @property {Object} definitions Definitions of the models.
|
|
41
|
+
* @property {Object} models Model constructors
|
|
42
|
+
* @class
|
|
43
|
+
*/
|
|
44
|
+
function ModelBuilder() {
|
|
45
|
+
this.models = {};
|
|
46
|
+
this.definitions = {};
|
|
47
|
+
this.settings = {};
|
|
48
|
+
this.mixins = new MixinProvider(this);
|
|
49
|
+
this.defaultModelBaseClass = DefaultModelBaseClass;
|
|
50
|
+
}
|
|
51
|
+
util.inherits(ModelBuilder, EventEmitter);
|
|
52
|
+
ModelBuilder.defaultInstance = new ModelBuilder();
|
|
53
|
+
function isModelClass(cls) {
|
|
54
|
+
if (!cls) return false;
|
|
55
|
+
return cls.prototype instanceof DefaultModelBaseClass;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get a model by name.
|
|
59
|
+
*
|
|
60
|
+
* @param {String} name The model name
|
|
61
|
+
* @param {Boolean} forceCreate Whether the create a stub for the given name if a model doesn't exist.
|
|
62
|
+
* @returns {ModelClass} The model class
|
|
63
|
+
*/
|
|
64
|
+
ModelBuilder.prototype.getModel = function(name, forceCreate) {
|
|
65
|
+
let model = this.models[name];
|
|
66
|
+
if (!model && forceCreate) model = this.define(name, {}, { unresolved: true });
|
|
67
|
+
return model;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Get the model definition by name
|
|
71
|
+
* @param {String} name The model name
|
|
72
|
+
* @returns {ModelDefinition} The model definition
|
|
73
|
+
*/
|
|
74
|
+
ModelBuilder.prototype.getModelDefinition = function(name) {
|
|
75
|
+
return this.definitions[name];
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Define a model class.
|
|
79
|
+
* Simple example:
|
|
80
|
+
* ```
|
|
81
|
+
* var User = modelBuilder.define('User', {
|
|
82
|
+
* email: String,
|
|
83
|
+
* password: String,
|
|
84
|
+
* birthDate: Date,
|
|
85
|
+
* activated: Boolean
|
|
86
|
+
* });
|
|
87
|
+
* ```
|
|
88
|
+
* More advanced example:
|
|
89
|
+
* ```
|
|
90
|
+
* var User = modelBuilder.define('User', {
|
|
91
|
+
* email: { type: String, limit: 150, index: true },
|
|
92
|
+
* password: { type: String, limit: 50 },
|
|
93
|
+
* birthDate: Date,
|
|
94
|
+
* registrationDate: {type: Date, default: function () { return new Date }},
|
|
95
|
+
* activated: { type: Boolean, default: false }
|
|
96
|
+
* });
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* @param {String} className Name of class
|
|
100
|
+
* @param {Object} properties Hash of class properties in format `{property: Type, property2: Type2, ...}` or `{property: {type: Type}, property2: {type: Type2}, ...}`
|
|
101
|
+
* @param {Object} settings Other configuration of class
|
|
102
|
+
* @param {Function} parent Parent model
|
|
103
|
+
* @return {ModelClass} The class constructor.
|
|
104
|
+
*
|
|
105
|
+
*/
|
|
106
|
+
ModelBuilder.prototype.define = function defineClass(className, properties, settings, parent) {
|
|
107
|
+
const modelBuilder = this;
|
|
108
|
+
const args = slice.call(arguments);
|
|
109
|
+
const pluralName = settings && settings.plural || inflection.pluralize(className);
|
|
110
|
+
let pathName = (settings && settings.http || {}).path || pluralName;
|
|
111
|
+
if (!className) throw new Error(g.f("Class name required"));
|
|
112
|
+
if (args.length === 1) {
|
|
113
|
+
properties = {};
|
|
114
|
+
args.push(properties);
|
|
115
|
+
}
|
|
116
|
+
if (args.length === 2) {
|
|
117
|
+
settings = {};
|
|
118
|
+
args.push(settings);
|
|
119
|
+
}
|
|
120
|
+
properties = properties || {};
|
|
121
|
+
settings = settings || {};
|
|
122
|
+
if (settings.strict === void 0 || settings.strict === null) settings.strict = false;
|
|
123
|
+
let ModelBaseClass = parent || this.defaultModelBaseClass;
|
|
124
|
+
const baseClass = settings.base || settings["super"];
|
|
125
|
+
if (baseClass) {
|
|
126
|
+
settings.base = baseClass;
|
|
127
|
+
delete settings["super"];
|
|
128
|
+
if (isModelClass(baseClass)) ModelBaseClass = baseClass;
|
|
129
|
+
else {
|
|
130
|
+
ModelBaseClass = this.models[baseClass];
|
|
131
|
+
assert(ModelBaseClass, "Base model is not found: " + baseClass);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
assert(ModelBaseClass.getMergePolicy, `Base class ${ModelBaseClass.modelName}
|
|
135
|
+
does not provide method getMergePolicy(). Most likely it is not inheriting
|
|
136
|
+
from datasource-juggler's built-in default ModelBaseClass, which is an
|
|
137
|
+
incorrect usage of the framework.`);
|
|
138
|
+
ModelBaseClass.__rank = ModelBaseClass.__rank || 1;
|
|
139
|
+
if (parent && !settings.base || !parent && settings.base) return ModelBaseClass.extend(className, properties, settings);
|
|
140
|
+
let ModelClass = this.models[className];
|
|
141
|
+
if (!ModelClass || !ModelClass.settings.unresolved) {
|
|
142
|
+
ModelClass = createModelClassCtor(className, ModelBaseClass);
|
|
143
|
+
const events = new EventEmitter();
|
|
144
|
+
events.setMaxListeners(32);
|
|
145
|
+
for (const f in EventEmitter.prototype) if (typeof EventEmitter.prototype[f] === "function") ModelClass[f] = EventEmitter.prototype[f].bind(events);
|
|
146
|
+
hiddenProperty(ModelClass, "modelName", className);
|
|
147
|
+
}
|
|
148
|
+
ModelClass.__rank = ModelBaseClass.__rank + 1;
|
|
149
|
+
util.inherits(ModelClass, ModelBaseClass);
|
|
150
|
+
this.models[className] = ModelClass;
|
|
151
|
+
if (settings.unresolved) {
|
|
152
|
+
ModelClass.settings = { unresolved: true };
|
|
153
|
+
return ModelClass;
|
|
154
|
+
}
|
|
155
|
+
hiddenProperty(ModelClass, "modelBuilder", modelBuilder);
|
|
156
|
+
hiddenProperty(ModelClass, "dataSource", null);
|
|
157
|
+
hiddenProperty(ModelClass, "pluralModelName", pluralName);
|
|
158
|
+
hiddenProperty(ModelClass, "relations", {});
|
|
159
|
+
if (pathName[0] !== "/") pathName = "/" + pathName;
|
|
160
|
+
hiddenProperty(ModelClass, "http", { path: pathName });
|
|
161
|
+
hiddenProperty(ModelClass, "base", ModelBaseClass);
|
|
162
|
+
hiddenProperty(ModelClass, "_observers", {});
|
|
163
|
+
hiddenProperty(ModelClass, "_warned", {});
|
|
164
|
+
for (const i in ModelBaseClass) if (i !== "_mixins" && !(i in ModelClass)) ModelClass[i] = ModelBaseClass[i];
|
|
165
|
+
if (settings.models) Object.keys(settings.models).forEach(function(m) {
|
|
166
|
+
const model = settings.models[m];
|
|
167
|
+
ModelClass[m] = typeof model === "string" ? modelBuilder.getModel(model, true) : model;
|
|
168
|
+
});
|
|
169
|
+
ModelClass.getter = {};
|
|
170
|
+
ModelClass.setter = {};
|
|
171
|
+
for (const p in properties) {
|
|
172
|
+
const excludePropertyList = settings["excludeBaseProperties"];
|
|
173
|
+
if (properties[p] === null || properties[p] === false || excludePropertyList != null && excludePropertyList.indexOf(p) != -1) delete properties[p];
|
|
174
|
+
if (/\./.test(p)) throw new Error(g.f("Property names containing dot(s) are not supported. Model: %s, property: %s", className, p));
|
|
175
|
+
if (p === "constructor") deprecated(g.f("Property name should not be \"{{constructor}}\" in Model: %s", className));
|
|
176
|
+
}
|
|
177
|
+
const modelDefinition = new ModelDefinition(this, className, properties, settings);
|
|
178
|
+
this.definitions[className] = modelDefinition;
|
|
179
|
+
ModelClass.definition = modelDefinition;
|
|
180
|
+
ModelClass.settings = modelDefinition.settings;
|
|
181
|
+
let idInjection = settings.idInjection;
|
|
182
|
+
if (idInjection !== false) idInjection = true;
|
|
183
|
+
let idNames = modelDefinition.idNames();
|
|
184
|
+
if (idNames.length > 0) idInjection = false;
|
|
185
|
+
if (idInjection) ModelClass.definition.defineProperty("id", {
|
|
186
|
+
type: Number,
|
|
187
|
+
id: 1,
|
|
188
|
+
generated: true
|
|
189
|
+
});
|
|
190
|
+
idNames = modelDefinition.idNames();
|
|
191
|
+
if (idNames.length === 1) {
|
|
192
|
+
if (idNames[0] !== "id") Object.defineProperty(ModelClass.prototype, "id", {
|
|
193
|
+
get: function() {
|
|
194
|
+
const idProp = ModelClass.definition.idNames()[0];
|
|
195
|
+
return this.__data[idProp];
|
|
196
|
+
},
|
|
197
|
+
configurable: true,
|
|
198
|
+
enumerable: false
|
|
199
|
+
});
|
|
200
|
+
} else Object.defineProperty(ModelClass.prototype, "id", {
|
|
201
|
+
get: function() {
|
|
202
|
+
const compositeId = {};
|
|
203
|
+
const idNames = ModelClass.definition.idNames();
|
|
204
|
+
for (let i = 0, p; i < idNames.length; i++) {
|
|
205
|
+
p = idNames[i];
|
|
206
|
+
compositeId[p] = this.__data[p];
|
|
207
|
+
}
|
|
208
|
+
return compositeId;
|
|
209
|
+
},
|
|
210
|
+
configurable: true,
|
|
211
|
+
enumerable: false
|
|
212
|
+
});
|
|
213
|
+
let forceId = ModelClass.settings.forceId;
|
|
214
|
+
if (idNames.length > 0) {
|
|
215
|
+
const idName = modelDefinition.idName();
|
|
216
|
+
const idProp = ModelClass.definition.rawProperties[idName];
|
|
217
|
+
if (idProp.generated && forceId !== false) forceId = "auto";
|
|
218
|
+
else if (!idProp.generated && forceId === "auto") forceId = false;
|
|
219
|
+
if (forceId) ModelClass.validatesAbsenceOf(idName, { if: "isNewRecord" });
|
|
220
|
+
ModelClass.definition.properties[idName].updateOnly = !!forceId;
|
|
221
|
+
ModelClass.definition.rawProperties[idName].updateOnly = !!forceId;
|
|
222
|
+
ModelClass.settings.forceId = forceId;
|
|
223
|
+
}
|
|
224
|
+
ModelClass.forEachProperty = function(cb) {
|
|
225
|
+
const props = ModelClass.definition.properties;
|
|
226
|
+
const keys = Object.keys(props);
|
|
227
|
+
for (let i = 0, n = keys.length; i < n; i++) cb(keys[i], props[keys[i]]);
|
|
228
|
+
};
|
|
229
|
+
ModelClass.attachTo = function(dataSource) {
|
|
230
|
+
dataSource.attach(this);
|
|
231
|
+
};
|
|
232
|
+
/** Extend the model with the specified model, properties, and other settings.
|
|
233
|
+
* For example, to extend an existing model, for example, a built-in model:
|
|
234
|
+
*
|
|
235
|
+
* ```js
|
|
236
|
+
* var Customer = User.extend('customer', {
|
|
237
|
+
* accountId: String,
|
|
238
|
+
* vip: Boolean
|
|
239
|
+
* });
|
|
240
|
+
* ```
|
|
241
|
+
*
|
|
242
|
+
* To extend the base model, essentially creating a new model:
|
|
243
|
+
* ```js
|
|
244
|
+
* var user = loopback.Model.extend('user', properties, options);
|
|
245
|
+
* ```
|
|
246
|
+
*
|
|
247
|
+
* @param {String} className Name of the new model being defined.
|
|
248
|
+
* @options {Object} subClassProperties child model properties, added to base model
|
|
249
|
+
* properties.
|
|
250
|
+
* @options {Object} subClassSettings child model settings such as relations and acls,
|
|
251
|
+
* merged with base model settings.
|
|
252
|
+
*/
|
|
253
|
+
ModelClass.extend = function(className, subClassProperties, subClassSettings) {
|
|
254
|
+
const baseClassProperties = ModelClass.definition.properties;
|
|
255
|
+
const baseClassSettings = ModelClass.definition.settings;
|
|
256
|
+
subClassProperties = subClassProperties || {};
|
|
257
|
+
subClassSettings = subClassSettings || {};
|
|
258
|
+
let idFound = false;
|
|
259
|
+
for (const k in subClassProperties) if (subClassProperties[k] && subClassProperties[k].id) {
|
|
260
|
+
idFound = true;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
const keys = Object.keys(baseClassProperties);
|
|
264
|
+
for (let i = 0, n = keys.length; i < n; i++) {
|
|
265
|
+
const key = keys[i];
|
|
266
|
+
if (idFound && baseClassProperties[key].id) continue;
|
|
267
|
+
if (subClassProperties[key] === void 0) {
|
|
268
|
+
const baseProp = baseClassProperties[key];
|
|
269
|
+
let basePropCopy = baseProp;
|
|
270
|
+
if (baseProp && typeof baseProp === "object") basePropCopy = deepMerge(baseProp);
|
|
271
|
+
subClassProperties[key] = basePropCopy;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const originalSubclassSettings = subClassSettings;
|
|
275
|
+
const mergePolicy = ModelClass.getMergePolicy(subClassSettings);
|
|
276
|
+
subClassSettings = mergeSettings(baseClassSettings, subClassSettings, mergePolicy);
|
|
277
|
+
if (!originalSubclassSettings.base) subClassSettings.base = ModelClass;
|
|
278
|
+
const subClass = modelBuilder.define(className, subClassProperties, subClassSettings, ModelClass);
|
|
279
|
+
if (typeof subClass.setup === "function") subClass.setup.call(subClass);
|
|
280
|
+
return subClass;
|
|
281
|
+
};
|
|
282
|
+
function mergeSettings(baseClassSettings, subClassSettings, mergePolicy) {
|
|
283
|
+
const mergedSettings = deepMerge(baseClassSettings);
|
|
284
|
+
Object.keys(baseClassSettings).forEach(function(key) {
|
|
285
|
+
if (mergePolicy[key] && mergePolicy[key].rank) baseClassSettings[key] = rankArrayElements(baseClassSettings[key], ModelBaseClass.__rank);
|
|
286
|
+
});
|
|
287
|
+
Object.keys(subClassSettings).forEach(function(key) {
|
|
288
|
+
if (mergePolicy[key] == null) mergePolicy[key] = mergePolicy.__default || {};
|
|
289
|
+
if (subClassSettings[key] === mergePolicy.__delete) {
|
|
290
|
+
delete mergedSettings[key];
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (mergePolicy[key].rank) subClassSettings[key] = rankArrayElements(subClassSettings[key], ModelBaseClass.__rank + 1);
|
|
294
|
+
if (mergePolicy[key].replace) {
|
|
295
|
+
mergedSettings[key] = subClassSettings[key];
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (mergePolicy[key].patch) {
|
|
299
|
+
mergedSettings[key] = mergedSettings[key] || {};
|
|
300
|
+
Object.keys(subClassSettings[key]).forEach(function(innerKey) {
|
|
301
|
+
mergedSettings[key][innerKey] = subClassSettings[key][innerKey];
|
|
302
|
+
});
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
mergedSettings[key] = deepMergeProperty(baseClassSettings[key], subClassSettings[key]);
|
|
306
|
+
});
|
|
307
|
+
return mergedSettings;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Register a property for the model class
|
|
311
|
+
* @param {String} propertyName Name of the property.
|
|
312
|
+
*/
|
|
313
|
+
ModelClass.registerProperty = function(propertyName) {
|
|
314
|
+
const properties = modelDefinition.build();
|
|
315
|
+
const prop = properties[propertyName];
|
|
316
|
+
const DataType = prop.type;
|
|
317
|
+
if (!DataType) throw new Error(g.f("Invalid type for property %s", propertyName));
|
|
318
|
+
if (prop.required) {
|
|
319
|
+
const requiredOptions = typeof prop.required === "object" ? prop.required : void 0;
|
|
320
|
+
ModelClass.validatesPresenceOf(propertyName, requiredOptions);
|
|
321
|
+
}
|
|
322
|
+
if (DataType === Date) ModelClass.validatesDateOf(propertyName);
|
|
323
|
+
Object.defineProperty(ModelClass.prototype, propertyName, {
|
|
324
|
+
get: function() {
|
|
325
|
+
if (ModelClass.getter[propertyName]) return ModelClass.getter[propertyName].call(this);
|
|
326
|
+
else return this.__data && this.__data[propertyName];
|
|
327
|
+
},
|
|
328
|
+
set: function(value) {
|
|
329
|
+
let DataType = ModelClass.definition.properties[propertyName].type;
|
|
330
|
+
if (Array.isArray(DataType) || DataType === Array) DataType = List;
|
|
331
|
+
else if (DataType === Date) DataType = DateType;
|
|
332
|
+
else if (DataType === Boolean) DataType = BooleanType;
|
|
333
|
+
else if (typeof DataType === "string") DataType = modelBuilder.resolveType(DataType);
|
|
334
|
+
const persistUndefinedAsNull = ModelClass.definition.settings.persistUndefinedAsNull;
|
|
335
|
+
if (value === void 0 && persistUndefinedAsNull) value = null;
|
|
336
|
+
if (ModelClass.setter[propertyName]) ModelClass.setter[propertyName].call(this, value);
|
|
337
|
+
else {
|
|
338
|
+
this.__data = this.__data || {};
|
|
339
|
+
if (value === null || value === void 0) this.__data[propertyName] = value;
|
|
340
|
+
else if (DataType === List) this.__data[propertyName] = isClass(DataType) ? new DataType(value, properties[propertyName].type, this) : DataType(value, properties[propertyName].type, this);
|
|
341
|
+
else {
|
|
342
|
+
this.__data[propertyName] = value instanceof DataType ? value : isClass(DataType) ? new DataType(value) : DataType(value);
|
|
343
|
+
if (value && this.__data[propertyName] instanceof DefaultModelBaseClass) applyParentProperty(this.__data[propertyName], this);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
configurable: true,
|
|
348
|
+
enumerable: true
|
|
349
|
+
});
|
|
350
|
+
Object.defineProperty(ModelClass.prototype, "$" + propertyName, {
|
|
351
|
+
get: function() {
|
|
352
|
+
return this.__data && this.__data[propertyName];
|
|
353
|
+
},
|
|
354
|
+
set: function(value) {
|
|
355
|
+
if (!this.__data) this.__data = {};
|
|
356
|
+
this.__data[propertyName] = value;
|
|
357
|
+
},
|
|
358
|
+
configurable: true,
|
|
359
|
+
enumerable: false
|
|
360
|
+
});
|
|
361
|
+
};
|
|
362
|
+
const props = ModelClass.definition.properties;
|
|
363
|
+
let keys = Object.keys(props);
|
|
364
|
+
let size = keys.length;
|
|
365
|
+
for (let i = 0; i < size; i++) {
|
|
366
|
+
const propertyName = keys[i];
|
|
367
|
+
ModelClass.registerProperty(propertyName);
|
|
368
|
+
}
|
|
369
|
+
const mixinSettings = settings.mixins || {};
|
|
370
|
+
keys = Object.keys(mixinSettings);
|
|
371
|
+
size = keys.length;
|
|
372
|
+
for (let i = 0; i < size; i++) {
|
|
373
|
+
const name = keys[i];
|
|
374
|
+
let mixin = mixinSettings[name];
|
|
375
|
+
if (mixin === true) mixin = {};
|
|
376
|
+
if (Array.isArray(mixin)) mixin.forEach(function(m) {
|
|
377
|
+
if (m === true) m = {};
|
|
378
|
+
if (typeof m === "object") modelBuilder.mixins.applyMixin(ModelClass, name, m);
|
|
379
|
+
});
|
|
380
|
+
else if (typeof mixin === "object") modelBuilder.mixins.applyMixin(ModelClass, name, mixin);
|
|
381
|
+
}
|
|
382
|
+
ModelClass.emit("defined", ModelClass);
|
|
383
|
+
return ModelClass;
|
|
384
|
+
};
|
|
385
|
+
function createModelClassCtor(name, ModelBaseClass) {
|
|
386
|
+
name = name.replace(/[-.:]/g, "_");
|
|
387
|
+
try {
|
|
388
|
+
return new Function("ModelBaseClass", `
|
|
389
|
+
// every class can receive hash of data as optional param
|
|
390
|
+
return function ${name}(data, options) {
|
|
391
|
+
if (!(this instanceof ${name})) {
|
|
392
|
+
return new ${name}(data, options);
|
|
393
|
+
}
|
|
394
|
+
if (${name}.settings.unresolved) {
|
|
395
|
+
throw new Error(g.f('Model %s is not defined.', ${JSON.stringify(name)}));
|
|
396
|
+
}
|
|
397
|
+
ModelBaseClass.apply(this, arguments);
|
|
398
|
+
};`)(ModelBaseClass);
|
|
399
|
+
} catch (err) {
|
|
400
|
+
if (err.name === "SyntaxError") return createModelClassCtor("ModelConstructor", ModelBaseClass);
|
|
401
|
+
else throw err;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function DateType(arg) {
|
|
405
|
+
if (arg === null) return null;
|
|
406
|
+
return new Date(arg);
|
|
407
|
+
}
|
|
408
|
+
function BooleanType(arg) {
|
|
409
|
+
if (typeof arg === "string") switch (arg) {
|
|
410
|
+
case "true":
|
|
411
|
+
case "1": return true;
|
|
412
|
+
case "false":
|
|
413
|
+
case "0": return false;
|
|
414
|
+
}
|
|
415
|
+
if (arg == null) return null;
|
|
416
|
+
return Boolean(arg);
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Define single property named `propertyName` on `model`
|
|
420
|
+
*
|
|
421
|
+
* @param {String} model Name of model
|
|
422
|
+
* @param {String} propertyName Name of property
|
|
423
|
+
* @param {Object} propertyDefinition Property settings
|
|
424
|
+
*/
|
|
425
|
+
ModelBuilder.prototype.defineProperty = function(model, propertyName, propertyDefinition) {
|
|
426
|
+
this.definitions[model].defineProperty(propertyName, propertyDefinition);
|
|
427
|
+
this.models[model].registerProperty(propertyName);
|
|
428
|
+
};
|
|
429
|
+
/**
|
|
430
|
+
* Define a new value type that can be used in model schemas as a property type.
|
|
431
|
+
* @param {function()} type Type constructor.
|
|
432
|
+
* @param {string[]=} aliases Optional list of alternative names for this type.
|
|
433
|
+
*/
|
|
434
|
+
ModelBuilder.prototype.defineValueType = function(type, aliases) {
|
|
435
|
+
ModelBuilder.registerType(type, aliases);
|
|
436
|
+
};
|
|
437
|
+
/**
|
|
438
|
+
* Extend existing model with specified properties
|
|
439
|
+
*
|
|
440
|
+
* Example:
|
|
441
|
+
* Instead of extending a model with attributes like this (for example):
|
|
442
|
+
*
|
|
443
|
+
* ```js
|
|
444
|
+
* db.defineProperty('Content', 'competitionType',
|
|
445
|
+
* { type: String });
|
|
446
|
+
* db.defineProperty('Content', 'expiryDate',
|
|
447
|
+
* { type: Date, index: true });
|
|
448
|
+
* db.defineProperty('Content', 'isExpired',
|
|
449
|
+
* { type: Boolean, index: true });
|
|
450
|
+
*```
|
|
451
|
+
* This method enables you to extend a model as follows (for example):
|
|
452
|
+
* ```js
|
|
453
|
+
* db.extendModel('Content', {
|
|
454
|
+
* competitionType: String,
|
|
455
|
+
* expiryDate: { type: Date, index: true },
|
|
456
|
+
* isExpired: { type: Boolean, index: true }
|
|
457
|
+
* });
|
|
458
|
+
*```
|
|
459
|
+
*
|
|
460
|
+
* @param {String} model Name of model
|
|
461
|
+
* @options {Object} properties JSON object specifying properties. Each property is a key whos value is
|
|
462
|
+
* either the [type](http://docs.strongloop.com/display/LB/LoopBack+types) or `propertyName: {options}`
|
|
463
|
+
* where the options are described below.
|
|
464
|
+
* @property {String} type Datatype of property: Must be an [LDL type](http://docs.strongloop.com/display/LB/LoopBack+types).
|
|
465
|
+
* @property {Boolean} index True if the property is an index; false otherwise.
|
|
466
|
+
*/
|
|
467
|
+
ModelBuilder.prototype.extendModel = function(model, props) {
|
|
468
|
+
const t = this;
|
|
469
|
+
const keys = Object.keys(props);
|
|
470
|
+
for (let i = 0; i < keys.length; i++) {
|
|
471
|
+
const definition = props[keys[i]];
|
|
472
|
+
t.defineProperty(model, keys[i], definition);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
ModelBuilder.prototype.copyModel = function copyModel(Master) {
|
|
476
|
+
const modelBuilder = this;
|
|
477
|
+
const className = Master.modelName;
|
|
478
|
+
const md = Master.modelBuilder.definitions[className];
|
|
479
|
+
const Slave = function SlaveModel() {
|
|
480
|
+
Master.apply(this, [].slice.call(arguments));
|
|
481
|
+
};
|
|
482
|
+
util.inherits(Slave, Master);
|
|
483
|
+
Slave.__proto__ = Master;
|
|
484
|
+
hiddenProperty(Slave, "modelBuilder", modelBuilder);
|
|
485
|
+
hiddenProperty(Slave, "modelName", className);
|
|
486
|
+
hiddenProperty(Slave, "relations", Master.relations);
|
|
487
|
+
if (!(className in modelBuilder.models)) {
|
|
488
|
+
modelBuilder.models[className] = Slave;
|
|
489
|
+
modelBuilder.definitions[className] = {
|
|
490
|
+
properties: md.properties,
|
|
491
|
+
settings: md.settings
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
return Slave;
|
|
495
|
+
};
|
|
496
|
+
/**
|
|
497
|
+
* Remove a model from the registry.
|
|
498
|
+
*
|
|
499
|
+
* @param {String} modelName
|
|
500
|
+
*/
|
|
501
|
+
ModelBuilder.prototype.deleteModelByName = function(modelName) {
|
|
502
|
+
delete this.models[modelName];
|
|
503
|
+
delete this.definitions[modelName];
|
|
504
|
+
};
|
|
505
|
+
/*!
|
|
506
|
+
* Define hidden property
|
|
507
|
+
*/
|
|
508
|
+
function hiddenProperty(where, property, value) {
|
|
509
|
+
Object.defineProperty(where, property, {
|
|
510
|
+
writable: true,
|
|
511
|
+
enumerable: false,
|
|
512
|
+
configurable: true,
|
|
513
|
+
value
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Get the schema name. If no parameter is given, then an anonymous model name
|
|
518
|
+
* is generated and returned.
|
|
519
|
+
* @param {string=} name The optional name parameter.
|
|
520
|
+
* @returns {string} The schema name.
|
|
521
|
+
*/
|
|
522
|
+
ModelBuilder.prototype.getSchemaName = function(name) {
|
|
523
|
+
if (name) return name;
|
|
524
|
+
if (typeof this._nameCount !== "number") this._nameCount = 0;
|
|
525
|
+
else this._nameCount++;
|
|
526
|
+
return "AnonymousModel_" + this._nameCount;
|
|
527
|
+
};
|
|
528
|
+
/**
|
|
529
|
+
* Resolve the type string to be a function, for example, 'String' to String.
|
|
530
|
+
* Returns {Function} if the type is resolved
|
|
531
|
+
* @param {String | Object | Array} prop The object whose type is to be resolved
|
|
532
|
+
*/
|
|
533
|
+
ModelBuilder.prototype.resolveType = function(prop, isSubProperty) {
|
|
534
|
+
if (!prop) return prop;
|
|
535
|
+
if (Array.isArray(prop) && prop.length > 0) {
|
|
536
|
+
const itemType = this.resolveType(prop[0]);
|
|
537
|
+
if (typeof itemType === "function") return [itemType];
|
|
538
|
+
else return itemType;
|
|
539
|
+
}
|
|
540
|
+
if (typeof prop === "string") {
|
|
541
|
+
const schemaType = ModelBuilder.schemaTypes[prop.toLowerCase()] || this.models[prop];
|
|
542
|
+
if (schemaType) return schemaType;
|
|
543
|
+
else {
|
|
544
|
+
prop = this.define(prop, {}, { unresolved: true });
|
|
545
|
+
return prop;
|
|
546
|
+
}
|
|
547
|
+
} else if (prop.constructor.name === "Object") if (!isSubProperty && prop.type) return this.resolveType(prop.type, true);
|
|
548
|
+
else return this.define(this.getSchemaName(null), prop, {
|
|
549
|
+
anonymous: true,
|
|
550
|
+
idInjection: false,
|
|
551
|
+
strict: this.settings.strictEmbeddedModels || false
|
|
552
|
+
});
|
|
553
|
+
else if ("function" === typeof prop) return prop;
|
|
554
|
+
return prop;
|
|
555
|
+
};
|
|
556
|
+
/**
|
|
557
|
+
* Build models from schema definitions
|
|
558
|
+
*
|
|
559
|
+
* `schemas` can be one of the following:
|
|
560
|
+
*
|
|
561
|
+
* 1. An array of named schema definition JSON objects
|
|
562
|
+
* 2. A schema definition JSON object
|
|
563
|
+
* 3. A list of property definitions (anonymous)
|
|
564
|
+
*
|
|
565
|
+
* @param {*} schemas The schemas
|
|
566
|
+
* @returns {Object.<string, ModelClass>} A map of model constructors keyed by
|
|
567
|
+
* model name.
|
|
568
|
+
*/
|
|
569
|
+
ModelBuilder.prototype.buildModels = function(schemas, createModel) {
|
|
570
|
+
const models = {};
|
|
571
|
+
if (!Array.isArray(schemas)) if (schemas.properties && schemas.name) schemas = [schemas];
|
|
572
|
+
else schemas = [{
|
|
573
|
+
name: this.getSchemaName(),
|
|
574
|
+
properties: schemas,
|
|
575
|
+
options: { anonymous: true }
|
|
576
|
+
}];
|
|
577
|
+
let relations = [];
|
|
578
|
+
for (let s = 0, n = schemas.length; s < n; s++) {
|
|
579
|
+
const name = this.getSchemaName(schemas[s].name);
|
|
580
|
+
schemas[s].name = name;
|
|
581
|
+
const model = typeof createModel === "function" ? createModel(schemas[s].name, schemas[s].properties, schemas[s].options) : this.define(schemas[s].name, schemas[s].properties, schemas[s].options);
|
|
582
|
+
models[name] = model;
|
|
583
|
+
relations = relations.concat(model.definition.relations);
|
|
584
|
+
}
|
|
585
|
+
for (let i = 0; i < relations.length; i++) {
|
|
586
|
+
const relation = relations[i];
|
|
587
|
+
const sourceModel = models[relation.source];
|
|
588
|
+
const targetModel = models[relation.target];
|
|
589
|
+
if (sourceModel && targetModel) {
|
|
590
|
+
if (typeof sourceModel[relation.type] === "function") sourceModel[relation.type](targetModel, { as: relation.as });
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return models;
|
|
594
|
+
};
|
|
595
|
+
/**
|
|
596
|
+
* Introspect the JSON document to build a corresponding model.
|
|
597
|
+
* @param {String} name The model name
|
|
598
|
+
* @param {Object} json The JSON object
|
|
599
|
+
* @param {Object} options The options
|
|
600
|
+
* @returns {ModelClass} The generated model class constructor.
|
|
601
|
+
*/
|
|
602
|
+
ModelBuilder.prototype.buildModelFromInstance = function(name, json, options) {
|
|
603
|
+
const schema = introspect(json);
|
|
604
|
+
return this.define(name, schema, options);
|
|
605
|
+
};
|
|
606
|
+
}));
|
|
607
|
+
//#endregion
|
|
608
|
+
module.exports = require_model_builder();
|