@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,2215 @@
|
|
|
1
|
+
const require_runtime = require("../_virtual/_rolldown/runtime.js");
|
|
2
|
+
const require_lib_globalize = require("./globalize.js");
|
|
3
|
+
const require_lib_jutil = require("./jutil.js");
|
|
4
|
+
const require_lib_geo = require("./geo.js");
|
|
5
|
+
const require_lib_utils = require("./utils.js");
|
|
6
|
+
const require_lib_observer = require("./observer.js");
|
|
7
|
+
const require_lib_validations = require("./validations.js");
|
|
8
|
+
const require_lib_model = require("./model.js");
|
|
9
|
+
const require_lib_model_definition = require("./model-definition.js");
|
|
10
|
+
const require_lib_model_builder = require("./model-builder.js");
|
|
11
|
+
const require_lib_scope = require("./scope.js");
|
|
12
|
+
const require_lib_relation_definition = require("./relation-definition.js");
|
|
13
|
+
const require_lib_dao = require("./dao.js");
|
|
14
|
+
const require_lib_case_utils = require("./case-utils.js");
|
|
15
|
+
const require_lib_kvao_index = require("./kvao/index.js");
|
|
16
|
+
//#region src/lib/datasource.ts
|
|
17
|
+
var require_datasource = /* @__PURE__ */ require_runtime.__commonJSMin(((exports) => {
|
|
18
|
+
/*!
|
|
19
|
+
* Module dependencies
|
|
20
|
+
*/
|
|
21
|
+
const ModelBuilder = require_lib_model_builder.ModelBuilder;
|
|
22
|
+
const ModelDefinition = require_lib_model_definition;
|
|
23
|
+
const RelationDefinition = require_lib_relation_definition;
|
|
24
|
+
const OberserverMixin = require_lib_observer;
|
|
25
|
+
const jutil = require_lib_jutil;
|
|
26
|
+
const utils = require_lib_utils;
|
|
27
|
+
const ModelBaseClass = require_lib_model;
|
|
28
|
+
const DataAccessObject = require_lib_dao;
|
|
29
|
+
const defineScope = require_lib_scope.defineScope;
|
|
30
|
+
const EventEmitter = require("events").EventEmitter;
|
|
31
|
+
const util = require("util");
|
|
32
|
+
const assert = require("assert");
|
|
33
|
+
const fs = require("fs");
|
|
34
|
+
const path = require("path");
|
|
35
|
+
const traverse = require("neotraverse/legacy");
|
|
36
|
+
const g = require_lib_globalize();
|
|
37
|
+
const deprecated = require("depd")("loopback-datasource-juggler");
|
|
38
|
+
const Transaction = require("@vsaas/loopback-connector").Transaction;
|
|
39
|
+
const { pascalCase, camelCase } = require_lib_case_utils;
|
|
40
|
+
const GeoPoint = require_lib_geo.GeoPoint;
|
|
41
|
+
const ValidationError = require_lib_validations.ValidationError;
|
|
42
|
+
const KeyValueAccessObject = require_lib_kvao_index;
|
|
43
|
+
const createRequire = require("module").createRequire;
|
|
44
|
+
if (process.env.DEBUG === "loopback") process.env.DEBUG = "loopback:*";
|
|
45
|
+
const debug = require("debug")("loopback:datasource");
|
|
46
|
+
/*!
|
|
47
|
+
* Export public API
|
|
48
|
+
*/
|
|
49
|
+
exports.DataSource = DataSource;
|
|
50
|
+
/*!
|
|
51
|
+
* Helpers
|
|
52
|
+
*/
|
|
53
|
+
const slice = Array.prototype.slice;
|
|
54
|
+
let jugglerExports;
|
|
55
|
+
function getJugglerExports() {
|
|
56
|
+
if (!jugglerExports) jugglerExports = {
|
|
57
|
+
ModelBuilder,
|
|
58
|
+
LDL: ModelBuilder,
|
|
59
|
+
DataSource,
|
|
60
|
+
Schema: DataSource,
|
|
61
|
+
ModelBaseClass,
|
|
62
|
+
GeoPoint,
|
|
63
|
+
ValidationError,
|
|
64
|
+
Transaction,
|
|
65
|
+
KeyValueAccessObject
|
|
66
|
+
};
|
|
67
|
+
return jugglerExports;
|
|
68
|
+
}
|
|
69
|
+
function once(fn) {
|
|
70
|
+
let called = false;
|
|
71
|
+
return function() {
|
|
72
|
+
if (called) return;
|
|
73
|
+
called = true;
|
|
74
|
+
fn.apply(this, arguments);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function runParallelTasks(tasks, callback) {
|
|
78
|
+
if (!tasks || tasks.length === 0) return callback(null, []);
|
|
79
|
+
const results = Array.from({ length: tasks.length });
|
|
80
|
+
let pending = tasks.length;
|
|
81
|
+
let finished = false;
|
|
82
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
83
|
+
const task = tasks[i];
|
|
84
|
+
const done = once(function(err, result) {
|
|
85
|
+
if (finished) return;
|
|
86
|
+
if (err) {
|
|
87
|
+
finished = true;
|
|
88
|
+
return callback(err);
|
|
89
|
+
}
|
|
90
|
+
results[i] = result;
|
|
91
|
+
pending--;
|
|
92
|
+
if (pending === 0) callback(null, results);
|
|
93
|
+
});
|
|
94
|
+
try {
|
|
95
|
+
task(done);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
done(err);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function invokeCallbacks(callbacks, err) {
|
|
102
|
+
for (let i = 0; i < callbacks.length; i++) try {
|
|
103
|
+
callbacks[i](err);
|
|
104
|
+
} catch (e) {
|
|
105
|
+
debug("Uncaught error raised by connect callback function: ", e);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* LoopBack models can manipulate data via the DataSource object.
|
|
110
|
+
* Attaching a `DataSource` to a `Model` adds instance methods and static methods to the `Model`.
|
|
111
|
+
*
|
|
112
|
+
* Define a data source to persist model data.
|
|
113
|
+
* To create a DataSource programmatically, call `createDataSource()` on the LoopBack object; for example:
|
|
114
|
+
* ```js
|
|
115
|
+
* var oracle = loopback.createDataSource({
|
|
116
|
+
* connector: 'oracle',
|
|
117
|
+
* host: '111.22.333.44',
|
|
118
|
+
* database: 'MYDB',
|
|
119
|
+
* username: 'username',
|
|
120
|
+
* password: 'password'
|
|
121
|
+
* });
|
|
122
|
+
* ```
|
|
123
|
+
*
|
|
124
|
+
* All classes in single dataSource share same the connector type and
|
|
125
|
+
* one database connection.
|
|
126
|
+
*
|
|
127
|
+
* For example, the following creates a DataSource, and waits for a connection callback.
|
|
128
|
+
*
|
|
129
|
+
* ```
|
|
130
|
+
* var dataSource = new DataSource('mysql', { database: 'myapp_test' });
|
|
131
|
+
* dataSource.define(...);
|
|
132
|
+
* dataSource.on('connected', function () {
|
|
133
|
+
* // work with database
|
|
134
|
+
* });
|
|
135
|
+
* ```
|
|
136
|
+
* @class DataSource
|
|
137
|
+
* @param {String} [name] Optional name for datasource.
|
|
138
|
+
* @options {Object} settings Database-specific settings to establish connection (settings depend on specific connector).
|
|
139
|
+
* The table below lists a typical set for a relational database.
|
|
140
|
+
* @property {String} connector Database connector to use. For any supported connector, can be any of:
|
|
141
|
+
*
|
|
142
|
+
* - The connector module from `require(connectorName)`.
|
|
143
|
+
* - The full name of the connector module, such as 'loopback-connector-oracle'.
|
|
144
|
+
* - The short name of the connector module, such as 'oracle'.
|
|
145
|
+
* - A local module under `./connectors/` folder.
|
|
146
|
+
* @property {String} host Database server host name.
|
|
147
|
+
* @property {String} port Database server port number.
|
|
148
|
+
* @property {String} username Database user name.
|
|
149
|
+
* @property {String} password Database password.
|
|
150
|
+
* @property {String} database Name of the database to use.
|
|
151
|
+
* @property {Boolean} debug Display debugging information. Default is false.
|
|
152
|
+
*
|
|
153
|
+
* The constructor allows the following styles:
|
|
154
|
+
*
|
|
155
|
+
* 1. new DataSource(dataSourceName, settings). For example:
|
|
156
|
+
* - new DataSource('myDataSource', {connector: 'memory'});
|
|
157
|
+
* - new DataSource('myDataSource', {name: 'myDataSource', connector: 'memory'});
|
|
158
|
+
* - new DataSource('myDataSource', {name: 'anotherDataSource', connector: 'memory'});
|
|
159
|
+
*
|
|
160
|
+
* 2. new DataSource(settings). For example:
|
|
161
|
+
* - new DataSource({name: 'myDataSource', connector: 'memory'});
|
|
162
|
+
* - new DataSource({connector: 'memory'});
|
|
163
|
+
*
|
|
164
|
+
* 3. new DataSource(connectorModule, settings). For example:
|
|
165
|
+
* - new DataSource(connectorModule, {name: 'myDataSource})
|
|
166
|
+
* - new DataSource(connectorModule)
|
|
167
|
+
*/
|
|
168
|
+
function DataSource(name, settings, modelBuilder) {
|
|
169
|
+
if (!(this instanceof DataSource)) return new DataSource(name, settings);
|
|
170
|
+
if (typeof name === "object" && settings === void 0) {
|
|
171
|
+
settings = name;
|
|
172
|
+
name = void 0;
|
|
173
|
+
}
|
|
174
|
+
if (typeof name === "string" && name.indexOf("://") !== -1) name = utils.parseSettings(name);
|
|
175
|
+
if (typeof settings === "string" && settings.indexOf("://") !== -1) settings = utils.parseSettings(settings);
|
|
176
|
+
if (settings) settings = Object.assign({}, settings);
|
|
177
|
+
if (typeof name === "object") name = Object.assign({}, name);
|
|
178
|
+
this.modelBuilder = modelBuilder || new ModelBuilder();
|
|
179
|
+
this.models = this.modelBuilder.models;
|
|
180
|
+
this.definitions = this.modelBuilder.definitions;
|
|
181
|
+
this.juggler = getJugglerExports();
|
|
182
|
+
this._queuedInvocations = 0;
|
|
183
|
+
this._operations = {};
|
|
184
|
+
this.setup(name, settings);
|
|
185
|
+
this._setupConnector();
|
|
186
|
+
const connector = this.connector;
|
|
187
|
+
const dao = connector && connector.DataAccessObject || this.constructor.DataAccessObject;
|
|
188
|
+
this.DataAccessObject = function() {};
|
|
189
|
+
Object.keys(dao).forEach(function(name) {
|
|
190
|
+
const fn = dao[name];
|
|
191
|
+
this.DataAccessObject[name] = fn;
|
|
192
|
+
if (typeof fn === "function") this.defineOperation(name, {
|
|
193
|
+
accepts: fn.accepts,
|
|
194
|
+
returns: fn.returns,
|
|
195
|
+
http: fn.http,
|
|
196
|
+
remoteEnabled: fn.shared ? true : false,
|
|
197
|
+
scope: this.DataAccessObject,
|
|
198
|
+
fnName: name
|
|
199
|
+
});
|
|
200
|
+
}.bind(this));
|
|
201
|
+
Object.keys(dao.prototype || []).forEach(function(name) {
|
|
202
|
+
const fn = dao.prototype[name];
|
|
203
|
+
this.DataAccessObject.prototype[name] = fn;
|
|
204
|
+
if (typeof fn === "function") this.defineOperation(name, {
|
|
205
|
+
prototype: true,
|
|
206
|
+
accepts: fn.accepts,
|
|
207
|
+
returns: fn.returns,
|
|
208
|
+
http: fn.http,
|
|
209
|
+
remoteEnabled: fn.shared ? true : false,
|
|
210
|
+
scope: this.DataAccessObject.prototype,
|
|
211
|
+
fnName: name
|
|
212
|
+
});
|
|
213
|
+
}.bind(this));
|
|
214
|
+
}
|
|
215
|
+
util.inherits(DataSource, EventEmitter);
|
|
216
|
+
DataSource.DataAccessObject = DataAccessObject;
|
|
217
|
+
/**
|
|
218
|
+
* Global maximum number of event listeners
|
|
219
|
+
*/
|
|
220
|
+
DataSource.DEFAULT_MAX_OFFLINE_REQUESTS = 16;
|
|
221
|
+
/**
|
|
222
|
+
* Set up the connector instance for backward compatibility with JugglingDB schema/adapter
|
|
223
|
+
* @private
|
|
224
|
+
*/
|
|
225
|
+
DataSource.prototype._setupConnector = function() {
|
|
226
|
+
this.connector = this.connector || this.adapter;
|
|
227
|
+
this.adapter = this.connector;
|
|
228
|
+
if (this.connector) {
|
|
229
|
+
if (!this.connector.dataSource) this.connector.dataSource = this;
|
|
230
|
+
const dataSource = this;
|
|
231
|
+
dataSource.setMaxListeners(dataSource.getMaxOfflineRequests());
|
|
232
|
+
this.connector.log = function(query, start) {
|
|
233
|
+
dataSource.log(query, start);
|
|
234
|
+
};
|
|
235
|
+
this.connector.logger = function(query) {
|
|
236
|
+
const t1 = Date.now();
|
|
237
|
+
const log = this.log;
|
|
238
|
+
return function(q) {
|
|
239
|
+
log(q || query, t1);
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
jutil.mixin(this.connector, OberserverMixin);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
function connectorModuleNames(name) {
|
|
246
|
+
const names = [];
|
|
247
|
+
if (!name.match(/^\//)) {
|
|
248
|
+
names.push("./connectors/" + name);
|
|
249
|
+
if (name.indexOf("loopback-connector-") !== 0) names.push("loopback-connector-" + name);
|
|
250
|
+
}
|
|
251
|
+
if ([
|
|
252
|
+
"mongodb",
|
|
253
|
+
"oracle",
|
|
254
|
+
"mysql",
|
|
255
|
+
"postgresql",
|
|
256
|
+
"mssql",
|
|
257
|
+
"rest",
|
|
258
|
+
"soap",
|
|
259
|
+
"db2",
|
|
260
|
+
"cloudant"
|
|
261
|
+
].indexOf(name) === -1) names.push(name);
|
|
262
|
+
return names;
|
|
263
|
+
}
|
|
264
|
+
function dedupe(list) {
|
|
265
|
+
return list.filter(function(item, index) {
|
|
266
|
+
return list.indexOf(item) === index;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
function findPackageFile(startDir) {
|
|
270
|
+
let current = path.resolve(startDir || process.cwd());
|
|
271
|
+
while (true) {
|
|
272
|
+
const packageFile = path.join(current, "package.json");
|
|
273
|
+
if (fs.existsSync(packageFile)) return packageFile;
|
|
274
|
+
const parent = path.dirname(current);
|
|
275
|
+
if (parent === current) return null;
|
|
276
|
+
current = parent;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function appConnectorModuleNames(name, startDir) {
|
|
280
|
+
if (!name || name.match(/^\//) || name.indexOf("./") === 0 || name.indexOf("../") === 0) return [];
|
|
281
|
+
const packageFile = findPackageFile(startDir);
|
|
282
|
+
if (!packageFile) return [];
|
|
283
|
+
let pkg;
|
|
284
|
+
try {
|
|
285
|
+
pkg = JSON.parse(fs.readFileSync(packageFile, "utf8"));
|
|
286
|
+
} catch (error) {
|
|
287
|
+
debug("Cannot read package.json from %s: %s", packageFile, error.stack || error);
|
|
288
|
+
return [];
|
|
289
|
+
}
|
|
290
|
+
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies, pkg.optionalDependencies, pkg.peerDependencies);
|
|
291
|
+
const fullName = name.indexOf("loopback-connector-") === 0 ? name : "loopback-connector-" + name;
|
|
292
|
+
return Object.keys(deps).filter(function(dep) {
|
|
293
|
+
return dep === fullName || dep.indexOf("/" + fullName) !== -1;
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
function connectorLoaders(startDir) {
|
|
297
|
+
const packageFile = findPackageFile(startDir);
|
|
298
|
+
if (!packageFile) return [require];
|
|
299
|
+
let appLoader;
|
|
300
|
+
try {
|
|
301
|
+
appLoader = createRequire(packageFile);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
debug("Cannot create loader for %s: %s", packageFile, error.stack || error);
|
|
304
|
+
}
|
|
305
|
+
const loaders = [];
|
|
306
|
+
if (appLoader) loaders.push(appLoader);
|
|
307
|
+
loaders.push(require);
|
|
308
|
+
return dedupe(loaders);
|
|
309
|
+
}
|
|
310
|
+
function tryModules(names, loader) {
|
|
311
|
+
let mod;
|
|
312
|
+
const loaders = Array.isArray(loader) ? loader : [loader || require];
|
|
313
|
+
for (let m = 0; m < names.length; m++) {
|
|
314
|
+
const candidate = names[m];
|
|
315
|
+
const candidateLoaders = candidate.match(/^(\.\/|\.\.\/|\/)/) ? [require] : loaders;
|
|
316
|
+
for (let l = 0; l < candidateLoaders.length; l++) {
|
|
317
|
+
try {
|
|
318
|
+
mod = candidateLoaders[l](candidate);
|
|
319
|
+
} catch (e) {
|
|
320
|
+
if (e.code === "MODULE_NOT_FOUND" && e.message && e.message.indexOf(candidate) > 0) {
|
|
321
|
+
debug("Module %s not found, will try another candidate.", candidate);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
debug("Cannot load connector %s: %s", candidate, e.stack || e);
|
|
325
|
+
throw e;
|
|
326
|
+
}
|
|
327
|
+
if (mod) break;
|
|
328
|
+
}
|
|
329
|
+
if (mod) break;
|
|
330
|
+
}
|
|
331
|
+
return mod;
|
|
332
|
+
}
|
|
333
|
+
/*!
|
|
334
|
+
* Resolve a connector by name
|
|
335
|
+
* @param name The connector name
|
|
336
|
+
* @returns {*}
|
|
337
|
+
* @private
|
|
338
|
+
*/
|
|
339
|
+
DataSource._resolveConnector = function(name, loader) {
|
|
340
|
+
const names = dedupe(connectorModuleNames(name).concat(appConnectorModuleNames(name, process.cwd())));
|
|
341
|
+
const connector = tryModules(names, loader || connectorLoaders(process.cwd()));
|
|
342
|
+
let error = null;
|
|
343
|
+
if (!connector) error = g.f("\nWARNING: {{LoopBack}} connector \"%s\" is not installed as any of the following modules:\n\n %s\n\nTo fix, run:\n\n {{npm install %s --save}}\n", name, names.join("\n"), names[names.length - 1]);
|
|
344
|
+
return {
|
|
345
|
+
connector,
|
|
346
|
+
error
|
|
347
|
+
};
|
|
348
|
+
};
|
|
349
|
+
/**
|
|
350
|
+
* Connect to the data source.
|
|
351
|
+
* If no callback is provided, it will return a Promise.
|
|
352
|
+
* Emits the 'connect' event.
|
|
353
|
+
* @param callback
|
|
354
|
+
* @returns {Promise}
|
|
355
|
+
* @emits connected
|
|
356
|
+
*/
|
|
357
|
+
DataSource.prototype.connect = function(callback) {
|
|
358
|
+
callback = callback || utils.createPromiseCallback();
|
|
359
|
+
const self = this;
|
|
360
|
+
if (this.connected) {
|
|
361
|
+
process.nextTick(callback);
|
|
362
|
+
return callback.promise;
|
|
363
|
+
}
|
|
364
|
+
if (typeof this.connector.connect !== "function") {
|
|
365
|
+
self.connected = true;
|
|
366
|
+
self.connecting = false;
|
|
367
|
+
process.nextTick(function() {
|
|
368
|
+
self.emit("connected");
|
|
369
|
+
callback();
|
|
370
|
+
});
|
|
371
|
+
return callback.promise;
|
|
372
|
+
}
|
|
373
|
+
this.pendingConnectCallbacks = this.pendingConnectCallbacks || [];
|
|
374
|
+
this.pendingConnectCallbacks.push(callback);
|
|
375
|
+
if (this.connecting) return callback.promise;
|
|
376
|
+
this.connector.connect(function(err, _result) {
|
|
377
|
+
self.connecting = false;
|
|
378
|
+
if (!err) self.connected = true;
|
|
379
|
+
const cbs = self.pendingConnectCallbacks;
|
|
380
|
+
self.pendingConnectCallbacks = [];
|
|
381
|
+
if (!err) self.emit("connected");
|
|
382
|
+
else self.emit("error", err);
|
|
383
|
+
invokeCallbacks(cbs, err);
|
|
384
|
+
});
|
|
385
|
+
this.connecting = true;
|
|
386
|
+
return callback.promise;
|
|
387
|
+
};
|
|
388
|
+
/**
|
|
389
|
+
* Set up the data source. The following styles are supported:
|
|
390
|
+
* ```js
|
|
391
|
+
* ds.setup('myDataSource', {connector: 'memory'}); // ds.name -> 'myDataSource'
|
|
392
|
+
* ds.setup('myDataSource', {name: 'myDataSource', connector: 'memory'}); // ds.name -> 'myDataSource'
|
|
393
|
+
* ds.setup('myDataSource', {name: 'anotherDataSource', connector: 'memory'}); // ds.name -> 'myDataSource' and a warning will be issued
|
|
394
|
+
* ds.setup({name: 'myDataSource', connector: 'memory'}); // ds.name -> 'myDataSource'
|
|
395
|
+
* ds.setup({connector: 'memory'}); // ds.name -> 'memory'
|
|
396
|
+
* ```
|
|
397
|
+
* @param {String} dsName The name of the datasource. If not set, use
|
|
398
|
+
* `settings.name`
|
|
399
|
+
* @param {Object} settings The settings
|
|
400
|
+
* @returns {*}
|
|
401
|
+
* @private
|
|
402
|
+
*/
|
|
403
|
+
DataSource.prototype.setup = function(dsName, settings) {
|
|
404
|
+
let connector;
|
|
405
|
+
if (dsName && typeof dsName === "object") if (settings === void 0) {
|
|
406
|
+
settings = dsName;
|
|
407
|
+
dsName = void 0;
|
|
408
|
+
} else {
|
|
409
|
+
connector = dsName;
|
|
410
|
+
dsName = void 0;
|
|
411
|
+
}
|
|
412
|
+
if (typeof dsName !== "string") dsName = void 0;
|
|
413
|
+
if (typeof settings === "object") {
|
|
414
|
+
if (settings.initialize) {
|
|
415
|
+
connector = settings;
|
|
416
|
+
settings = void 0;
|
|
417
|
+
} else if (settings.connector) connector = settings.connector;
|
|
418
|
+
else if (settings.adapter) connector = settings.adapter;
|
|
419
|
+
}
|
|
420
|
+
this.settings = settings || {};
|
|
421
|
+
this.settings.debug = this.settings.debug || debug.enabled;
|
|
422
|
+
if (this.settings.debug) debug("Settings: %j", this.settings);
|
|
423
|
+
if (typeof settings === "object" && typeof settings.name === "string" && typeof dsName === "string" && dsName !== settings.name) console.warn("A datasource is created with name %j, which is different from the name in settings (%j). Please adjust your configuration to ensure these names match.", dsName, settings.name);
|
|
424
|
+
this.connected = false;
|
|
425
|
+
this.connecting = false;
|
|
426
|
+
this.initialized = false;
|
|
427
|
+
this.name = dsName || typeof this.settings.name === "string" && this.settings.name;
|
|
428
|
+
let connectorName;
|
|
429
|
+
if (typeof connector === "string") {
|
|
430
|
+
connectorName = connector;
|
|
431
|
+
connector = void 0;
|
|
432
|
+
} else if (typeof connector === "object" && connector) connectorName = connector.name;
|
|
433
|
+
else connectorName = dsName;
|
|
434
|
+
if (!this.name) this.name = connectorName;
|
|
435
|
+
if (!connector && connectorName) {
|
|
436
|
+
const result = DataSource._resolveConnector(connectorName);
|
|
437
|
+
connector = result.connector;
|
|
438
|
+
if (!connector) {
|
|
439
|
+
console.error(result.error);
|
|
440
|
+
this.emit("error", new Error(result.error));
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (connector) {
|
|
445
|
+
const postInit = function postInit(err, result) {
|
|
446
|
+
this._setupConnector();
|
|
447
|
+
if (!this.connector) throw new Error(g.f("Connector is not defined correctly: it should create `{{connector}}` member of dataSource"));
|
|
448
|
+
if (!err) {
|
|
449
|
+
this.initialized = true;
|
|
450
|
+
this.emit("initialized");
|
|
451
|
+
}
|
|
452
|
+
debug("Connector is initialized for dataSource %s", this.name);
|
|
453
|
+
if (!this.settings.lazyConnect) this.connected = !err && result !== false;
|
|
454
|
+
if (this.connected) {
|
|
455
|
+
debug("DataSource %s is now connected to %s", this.name, this.connector.name);
|
|
456
|
+
this.emit("connected");
|
|
457
|
+
} else if (err) {
|
|
458
|
+
this.connecting = false;
|
|
459
|
+
if (this._queuedInvocations) debug("Connection fails: %s\nIt will be retried for the next request.", err);
|
|
460
|
+
else {
|
|
461
|
+
g.error("Connection fails: %s\nIt will be retried for the next request.", err);
|
|
462
|
+
if (settings.catchFailure) try {
|
|
463
|
+
this.emit("error", err);
|
|
464
|
+
} catch (error) {
|
|
465
|
+
console.log(error);
|
|
466
|
+
}
|
|
467
|
+
else this.emit("error", err);
|
|
468
|
+
}
|
|
469
|
+
} else debug("DataSource %s will be connected to connector %s", this.name, this.connector.name);
|
|
470
|
+
}.bind(this);
|
|
471
|
+
try {
|
|
472
|
+
if ("function" === typeof connector.initialize) {
|
|
473
|
+
debug("Initializing connector %s", connector.name);
|
|
474
|
+
connector.initialize(this, postInit);
|
|
475
|
+
} else if ("function" === typeof connector) {
|
|
476
|
+
this.connector = new connector(this.settings);
|
|
477
|
+
postInit();
|
|
478
|
+
}
|
|
479
|
+
} catch (err) {
|
|
480
|
+
if (err.message) err.message = "Cannot initialize connector " + JSON.stringify(connectorName) + ": " + err.message;
|
|
481
|
+
throw err;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
function isModelClass(cls) {
|
|
486
|
+
if (!cls) return false;
|
|
487
|
+
return cls.prototype instanceof ModelBaseClass;
|
|
488
|
+
}
|
|
489
|
+
DataSource.relationTypes = Object.keys(RelationDefinition.RelationTypes);
|
|
490
|
+
function isModelDataSourceAttached(model) {
|
|
491
|
+
return model && !model.settings.unresolved && model.dataSource instanceof DataSource;
|
|
492
|
+
}
|
|
493
|
+
/*!
|
|
494
|
+
* Define scopes for the model class from the scopes object. See
|
|
495
|
+
* [scopes](./Model-definition-JSON-file.html#scopes) for more information on
|
|
496
|
+
* scopes and valid options objects.
|
|
497
|
+
* @param {Object} modelClass - The model class that corresponds to the model
|
|
498
|
+
* definition that will be enhanced by the provided scopes.
|
|
499
|
+
* @param {Object} scopes A key-value collection of names and their object
|
|
500
|
+
* definitions
|
|
501
|
+
* @property options The options defined on the scope object.
|
|
502
|
+
*/
|
|
503
|
+
DataSource.prototype.defineScopes = function(modelClass, scopes) {
|
|
504
|
+
if (scopes) for (const s in scopes) defineScope(modelClass, modelClass, s, scopes[s], {}, scopes[s].options);
|
|
505
|
+
};
|
|
506
|
+
/*!
|
|
507
|
+
* Define relations for the model class from the relations object. See
|
|
508
|
+
* [relations](./Model-definition-JSON-file.html#relations) for more information.
|
|
509
|
+
* @param {Object} modelClass - The model class that corresponds to the model
|
|
510
|
+
* definition that will be enhanced by the provided relations.
|
|
511
|
+
* @param {Object} relations A key-value collection of relation names and their
|
|
512
|
+
* object definitions.
|
|
513
|
+
*/
|
|
514
|
+
DataSource.prototype.defineRelations = function(modelClass, relations) {
|
|
515
|
+
const self = this;
|
|
516
|
+
const deferRelationSetup = function(relationName, relation, targetModel, throughModel) {
|
|
517
|
+
if (!isModelDataSourceAttached(targetModel)) targetModel.once("dataAccessConfigured", function(targetModel) {
|
|
518
|
+
if (!throughModel || isModelDataSourceAttached(throughModel)) {
|
|
519
|
+
const params = traverse(relation).clone();
|
|
520
|
+
params.as = relationName;
|
|
521
|
+
params.model = targetModel;
|
|
522
|
+
if (throughModel) params.through = throughModel;
|
|
523
|
+
modelClass[relation.type].call(modelClass, relationName, params);
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
if (throughModel && !isModelDataSourceAttached(throughModel)) throughModel.once("dataAccessConfigured", function(throughModel) {
|
|
527
|
+
if (isModelDataSourceAttached(targetModel)) {
|
|
528
|
+
const params = traverse(relation).clone();
|
|
529
|
+
params.as = relationName;
|
|
530
|
+
params.model = targetModel;
|
|
531
|
+
params.through = throughModel;
|
|
532
|
+
modelClass[relation.type].call(modelClass, relationName, params);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
};
|
|
536
|
+
if (relations) Object.keys(relations).forEach(function(relationName) {
|
|
537
|
+
let targetModel;
|
|
538
|
+
const r = relations[relationName];
|
|
539
|
+
validateRelation(relationName, r);
|
|
540
|
+
if (r.model) targetModel = isModelClass(r.model) ? r.model : self.getModel(r.model, true);
|
|
541
|
+
let throughModel = null;
|
|
542
|
+
if (r.through) throughModel = isModelClass(r.through) ? r.through : self.getModel(r.through, true);
|
|
543
|
+
if (targetModel && !isModelDataSourceAttached(targetModel) || throughModel && !isModelDataSourceAttached(throughModel)) deferRelationSetup(relationName, r, targetModel, throughModel);
|
|
544
|
+
else {
|
|
545
|
+
const params = traverse(r).clone();
|
|
546
|
+
params.as = relationName;
|
|
547
|
+
params.model = targetModel;
|
|
548
|
+
if (throughModel) params.through = throughModel;
|
|
549
|
+
modelClass[r.type].call(modelClass, relationName, params);
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
};
|
|
553
|
+
function validateRelation(relationName, relation) {
|
|
554
|
+
const rn = relationName;
|
|
555
|
+
const r = relation;
|
|
556
|
+
let msg, code;
|
|
557
|
+
assert(DataSource.relationTypes.indexOf(r.type) !== -1, "Invalid relation type: " + r.type);
|
|
558
|
+
assert(isValidRelationName(rn), "Invalid relation name: " + rn);
|
|
559
|
+
if (!r.polymorphic && r.type === "belongsTo" && !r.model) {
|
|
560
|
+
msg = g.f("%s relation: %s requires param `model`", r.type, rn);
|
|
561
|
+
code = "BELONGS_TO_MISSING_MODEL";
|
|
562
|
+
}
|
|
563
|
+
if (r.polymorphic && r.type === "belongsTo" && r.model) {
|
|
564
|
+
msg = g.f("{{polymorphic}} %s relation: %s does not expect param `model`", r.type, rn);
|
|
565
|
+
code = "POLYMORPHIC_BELONGS_TO_MODEL";
|
|
566
|
+
}
|
|
567
|
+
if (r.polymorphic && r.type !== "belongsTo" && !r.model) {
|
|
568
|
+
msg = g.f("{{polymorphic}} %s relation: %s requires param `model`", r.type, rn);
|
|
569
|
+
code = "POLYMORPHIC_NOT_BELONGS_TO_MISSING_MODEL";
|
|
570
|
+
}
|
|
571
|
+
if (r.polymorphic && r.polymorphic.foreignKey && !r.polymorphic.discriminator) {
|
|
572
|
+
msg = g.f("{{polymorphic}} %s relation: %s requires param `polymorphic.discriminator` when param `polymorphic.foreignKey` is provided", r.type, rn);
|
|
573
|
+
code = "POLYMORPHIC_MISSING_DISCRIMINATOR";
|
|
574
|
+
}
|
|
575
|
+
if (r.polymorphic && r.polymorphic.discriminator && !r.polymorphic.foreignKey) {
|
|
576
|
+
msg = g.f("{{polymorphic}} %s relation: %s requires param `polymorphic.foreignKey` when param `polymorphic.discriminator` is provided", r.type, rn);
|
|
577
|
+
code = "POLYMORPHIC_MISSING_FOREIGN_KEY";
|
|
578
|
+
}
|
|
579
|
+
if (r.polymorphic && r.polymorphic.as && r.polymorphic.foreignKey) {
|
|
580
|
+
msg = g.f("{{polymorphic}} %s relation: %s does not expect param `polymorphic.as` when defing custom `foreignKey`/`discriminator` ", r.type, rn);
|
|
581
|
+
code = "POLYMORPHIC_EXTRANEOUS_AS";
|
|
582
|
+
}
|
|
583
|
+
if (r.polymorphic && r.polymorphic.selector && r.polymorphic.foreignKey) {
|
|
584
|
+
msg = g.f("{{polymorphic}} %s relation: %s does not expect param `polymorphic.selector` when defing custom `foreignKey`/`discriminator` ", r.type, rn);
|
|
585
|
+
code = "POLYMORPHIC_EXTRANEOUS_SELECTOR";
|
|
586
|
+
}
|
|
587
|
+
if (msg) {
|
|
588
|
+
const error = new Error(msg);
|
|
589
|
+
error.details = {
|
|
590
|
+
code,
|
|
591
|
+
rType: r.type,
|
|
592
|
+
rName: rn
|
|
593
|
+
};
|
|
594
|
+
throw error;
|
|
595
|
+
}
|
|
596
|
+
if (r.polymorphic && r.polymorphic.as) deprecated(g.f("WARNING: {{polymorphic}} %s relation: %s uses keyword `polymorphic.as` which will be DEPRECATED in LoopBack.next, refer to this doc for replacement solutions (https://loopback.io/doc/en/lb3/Polymorphic-relations.html#deprecated-polymorphic-as)", r.type, rn), r.type);
|
|
597
|
+
}
|
|
598
|
+
function isValidRelationName(relationName) {
|
|
599
|
+
return ["trigger"].indexOf(relationName) === -1;
|
|
600
|
+
}
|
|
601
|
+
/*!
|
|
602
|
+
* Set up the data access functions from the data source. Each data source will
|
|
603
|
+
* expose a data access object (DAO), which will be mixed into the modelClass.
|
|
604
|
+
* @param {Model} modelClass The model class that will receive DAO mixins.
|
|
605
|
+
* @param {Object} settings The settings object; typically allows any settings
|
|
606
|
+
* that would be valid for a typical Model object.
|
|
607
|
+
*/
|
|
608
|
+
DataSource.prototype.setupDataAccess = function(modelClass, settings) {
|
|
609
|
+
if (this.connector) {
|
|
610
|
+
const idName = modelClass.definition.idName();
|
|
611
|
+
const idProp = modelClass.definition.rawProperties[idName];
|
|
612
|
+
if (idProp && idProp.generated && idProp.useDefaultIdType !== false && this.connector.getDefaultIdType) {
|
|
613
|
+
const idType = this.connector.getDefaultIdType() || String;
|
|
614
|
+
idProp.type = idType;
|
|
615
|
+
modelClass.definition.rawProperties[idName].type = idType;
|
|
616
|
+
modelClass.definition.properties[idName].type = idType;
|
|
617
|
+
}
|
|
618
|
+
if (this.connector.define) this.connector.define({
|
|
619
|
+
model: modelClass,
|
|
620
|
+
properties: modelClass.definition.properties,
|
|
621
|
+
settings
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
this.mixin(modelClass);
|
|
625
|
+
const relations = settings.relationships || settings.relations;
|
|
626
|
+
this.defineRelations(modelClass, relations);
|
|
627
|
+
modelClass.emit("dataAccessConfigured", modelClass);
|
|
628
|
+
const scopes = settings.scopes || {};
|
|
629
|
+
this.defineScopes(modelClass, scopes);
|
|
630
|
+
};
|
|
631
|
+
/**
|
|
632
|
+
* Define a model class. Returns newly created model object.
|
|
633
|
+
* The first (String) argument specifying the model name is required.
|
|
634
|
+
* You can provide one or two JSON object arguments, to provide configuration options.
|
|
635
|
+
* See [Model definition reference](http://docs.strongloop.com/display/DOC/Model+definition+reference) for details.
|
|
636
|
+
*
|
|
637
|
+
* Simple example:
|
|
638
|
+
* ```
|
|
639
|
+
* var User = dataSource.createModel('User', {
|
|
640
|
+
* email: String,
|
|
641
|
+
* password: String,
|
|
642
|
+
* birthDate: Date,
|
|
643
|
+
* activated: Boolean
|
|
644
|
+
* });
|
|
645
|
+
* ```
|
|
646
|
+
* More advanced example
|
|
647
|
+
* ```
|
|
648
|
+
* var User = dataSource.createModel('User', {
|
|
649
|
+
* email: { type: String, limit: 150, index: true },
|
|
650
|
+
* password: { type: String, limit: 50 },
|
|
651
|
+
* birthDate: Date,
|
|
652
|
+
* registrationDate: {type: Date, default: function () { return new Date }},
|
|
653
|
+
* activated: { type: Boolean, default: false }
|
|
654
|
+
* });
|
|
655
|
+
* ```
|
|
656
|
+
* You can also define an ACL when you create a new data source with the `DataSource.create()` method. For example:
|
|
657
|
+
*
|
|
658
|
+
* ```js
|
|
659
|
+
* var Customer = ds.createModel('Customer', {
|
|
660
|
+
* name: {
|
|
661
|
+
* type: String,
|
|
662
|
+
* acls: [
|
|
663
|
+
* {principalType: ACL.USER, principalId: 'u001', accessType: ACL.WRITE, permission: ACL.DENY},
|
|
664
|
+
* {principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
|
|
665
|
+
* ]
|
|
666
|
+
* }
|
|
667
|
+
* }, {
|
|
668
|
+
* acls: [
|
|
669
|
+
* {principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
|
|
670
|
+
* ]
|
|
671
|
+
* });
|
|
672
|
+
* ```
|
|
673
|
+
*
|
|
674
|
+
* @param {String} className Name of the model to create.
|
|
675
|
+
* @param {Object} properties Hash of model properties in format `{property: Type, property2: Type2, ...}` or `{property: {type: Type}, property2: {type: Type2}, ...}`
|
|
676
|
+
* @options {Object} properties Other configuration options. This corresponds to the options key in the config object.
|
|
677
|
+
* @param {Object} settings A settings object that would typically be used for Model objects.
|
|
678
|
+
*/
|
|
679
|
+
DataSource.prototype.createModel = DataSource.prototype.define = function defineClass(className, properties, settings) {
|
|
680
|
+
const args = slice.call(arguments);
|
|
681
|
+
if (!className) throw new Error(g.f("Class name required"));
|
|
682
|
+
if (args.length === 1) {
|
|
683
|
+
properties = {};
|
|
684
|
+
args.push(properties);
|
|
685
|
+
}
|
|
686
|
+
if (args.length === 2) {
|
|
687
|
+
settings = {};
|
|
688
|
+
args.push(settings);
|
|
689
|
+
}
|
|
690
|
+
properties = properties || {};
|
|
691
|
+
settings = settings || {};
|
|
692
|
+
if (this.isRelational()) {
|
|
693
|
+
if (settings.strict === void 0 || settings.strict === null) settings.strict = true;
|
|
694
|
+
if (settings.strict === false) {
|
|
695
|
+
console.warn(`WARNING: relational database doesn't support {strict: false} mode. {strict: true} mode will be set for model ${className} instead.`);
|
|
696
|
+
settings.strict = true;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
const modelClass = this.modelBuilder.define(className, properties, settings);
|
|
700
|
+
modelClass.dataSource = this;
|
|
701
|
+
if (settings.unresolved) return modelClass;
|
|
702
|
+
this.setupDataAccess(modelClass, settings);
|
|
703
|
+
modelClass.emit("dataSourceAttached", modelClass);
|
|
704
|
+
return modelClass;
|
|
705
|
+
};
|
|
706
|
+
/**
|
|
707
|
+
* Remove a model from the registry.
|
|
708
|
+
*
|
|
709
|
+
* @param {String} modelName
|
|
710
|
+
*/
|
|
711
|
+
DataSource.prototype.deleteModelByName = function(modelName) {
|
|
712
|
+
this.modelBuilder.deleteModelByName(modelName);
|
|
713
|
+
delete this.connector._models[modelName];
|
|
714
|
+
};
|
|
715
|
+
/**
|
|
716
|
+
* Remove all models from the registry, but keep the connector instance
|
|
717
|
+
* (including the pool of database connections).
|
|
718
|
+
*/
|
|
719
|
+
DataSource.prototype.deleteAllModels = function() {
|
|
720
|
+
for (const m in this.modelBuilder.models) this.deleteModelByName(m);
|
|
721
|
+
};
|
|
722
|
+
/**
|
|
723
|
+
* Mixin DataAccessObject methods.
|
|
724
|
+
*
|
|
725
|
+
* @param {Function} ModelCtor The model constructor
|
|
726
|
+
* @private
|
|
727
|
+
*/
|
|
728
|
+
DataSource.prototype.mixin = function(ModelCtor) {
|
|
729
|
+
const ops = this.operations();
|
|
730
|
+
const DAO = this.DataAccessObject;
|
|
731
|
+
jutil.mixin(ModelCtor, DAO, {
|
|
732
|
+
proxyFunctions: true,
|
|
733
|
+
override: true
|
|
734
|
+
});
|
|
735
|
+
Object.keys(ops).forEach(function(name) {
|
|
736
|
+
const op = ops[name];
|
|
737
|
+
if (op.enabled) {
|
|
738
|
+
op.prototype && ModelCtor.prototype;
|
|
739
|
+
Object.keys(op).filter(function(key) {
|
|
740
|
+
return ~[
|
|
741
|
+
"scope",
|
|
742
|
+
"fnName",
|
|
743
|
+
"prototype"
|
|
744
|
+
].indexOf(key);
|
|
745
|
+
}).forEach(function(key) {
|
|
746
|
+
if (typeof op[key] !== "undefined") op.scope[op.fnName][key] = op[key];
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
};
|
|
751
|
+
/*! Method will be deprecated in LoopBack.next
|
|
752
|
+
*/
|
|
753
|
+
/**
|
|
754
|
+
* See [ModelBuilder.getModel](http://apidocs.strongloop.com/loopback-datasource-juggler/#modelbuilder-prototype-getmodel)
|
|
755
|
+
* for details.
|
|
756
|
+
*/
|
|
757
|
+
DataSource.prototype.getModel = function(name, forceCreate) {
|
|
758
|
+
return this.modelBuilder.getModel(name, forceCreate);
|
|
759
|
+
};
|
|
760
|
+
/*! Method will be deprecated in LoopBack.next
|
|
761
|
+
*/
|
|
762
|
+
/**
|
|
763
|
+
* See ModelBuilder.getModelDefinition
|
|
764
|
+
* See [ModelBuilder.getModelDefinition](http://apidocs.strongloop.com/loopback-datasource-juggler/#modelbuilder-prototype-getmodeldefinition)
|
|
765
|
+
* for details.
|
|
766
|
+
*/
|
|
767
|
+
DataSource.prototype.getModelDefinition = function(name) {
|
|
768
|
+
return this.modelBuilder.getModelDefinition(name);
|
|
769
|
+
};
|
|
770
|
+
/*! Method will be deprecated in LoopBack.next
|
|
771
|
+
*/
|
|
772
|
+
/**
|
|
773
|
+
* Get the data source types collection.
|
|
774
|
+
* @returns {String[]} The data source type array.
|
|
775
|
+
* For example, ['db', 'nosql', 'mongodb'] would be represent a datasource of
|
|
776
|
+
* type 'db', with a subtype of 'nosql', and would use the 'mongodb' connector.
|
|
777
|
+
*
|
|
778
|
+
* Alternatively, ['rest'] would be a different type altogether, and would have
|
|
779
|
+
* no subtype.
|
|
780
|
+
*/
|
|
781
|
+
DataSource.prototype.getTypes = function() {
|
|
782
|
+
const getTypes = this.connector && this.connector.getTypes;
|
|
783
|
+
let types = getTypes && getTypes() || [];
|
|
784
|
+
if (typeof types === "string") types = types.split(/[\s,/]+/);
|
|
785
|
+
return types;
|
|
786
|
+
};
|
|
787
|
+
/**
|
|
788
|
+
* Check the data source supports the specified types.
|
|
789
|
+
* @param {String|String[]} types Type name or an array of type names.
|
|
790
|
+
* @returns {Boolean} true if all types are supported by the data source
|
|
791
|
+
*/
|
|
792
|
+
DataSource.prototype.supportTypes = function(types) {
|
|
793
|
+
const supportedTypes = this.getTypes();
|
|
794
|
+
if (Array.isArray(types)) {
|
|
795
|
+
for (let i = 0; i < types.length; i++) if (supportedTypes.indexOf(types[i]) === -1) return false;
|
|
796
|
+
return true;
|
|
797
|
+
} else return supportedTypes.indexOf(types) !== -1;
|
|
798
|
+
};
|
|
799
|
+
/*! In future versions, this will not maintain a strict 1:1 relationship between datasources and model classes
|
|
800
|
+
* Moving forward, we will allow a model to be attached to a datasource. The model itself becomes a template.
|
|
801
|
+
*/
|
|
802
|
+
/**
|
|
803
|
+
* Attach an existing model to a data source.
|
|
804
|
+
* This will mixin all of the data access object functions (DAO) into your
|
|
805
|
+
* modelClass definition.
|
|
806
|
+
* @param {Function} modelClass The model constructor that will be enhanced by
|
|
807
|
+
* DAO mixins.
|
|
808
|
+
*/
|
|
809
|
+
DataSource.prototype.attach = function(modelClass) {
|
|
810
|
+
if (modelClass.dataSource === this) return modelClass;
|
|
811
|
+
if (modelClass.modelBuilder !== this.modelBuilder) {
|
|
812
|
+
this.modelBuilder.definitions[modelClass.modelName] = modelClass.definition;
|
|
813
|
+
this.modelBuilder.models[modelClass.modelName] = modelClass;
|
|
814
|
+
modelClass.modelBuilder = this.modelBuilder;
|
|
815
|
+
}
|
|
816
|
+
modelClass.dataSource = this;
|
|
817
|
+
this.setupDataAccess(modelClass, modelClass.settings);
|
|
818
|
+
modelClass.emit("dataSourceAttached", modelClass);
|
|
819
|
+
return modelClass;
|
|
820
|
+
};
|
|
821
|
+
/*! Method will be deprecated in LoopBack.next
|
|
822
|
+
*/
|
|
823
|
+
/**
|
|
824
|
+
* Define a property with name `prop` on a target `model`. See
|
|
825
|
+
* [Properties](./Model-definition-JSON-file.html#properties) for more information
|
|
826
|
+
* regarding valid options for `params`.
|
|
827
|
+
* @param {String} model Name of model
|
|
828
|
+
* @param {String} prop Name of property
|
|
829
|
+
* @param {Property} params Property settings
|
|
830
|
+
*/
|
|
831
|
+
DataSource.prototype.defineProperty = function(model, prop, params) {
|
|
832
|
+
this.modelBuilder.defineProperty(model, prop, params);
|
|
833
|
+
const resolvedProp = this.getModelDefinition(model).properties[prop];
|
|
834
|
+
if (this.connector && this.connector.defineProperty) this.connector.defineProperty(model, prop, resolvedProp);
|
|
835
|
+
};
|
|
836
|
+
/**
|
|
837
|
+
* Drop schema objects such as tables, indexes, views, triggers, etc that correspond
|
|
838
|
+
* to model definitions attached to this DataSource instance, specified by the `models` parameter.
|
|
839
|
+
*
|
|
840
|
+
* **WARNING**: In many situations, this will destroy data! `autoupdate()` will attempt to preserve
|
|
841
|
+
* data while updating the schema on your target DataSource, but this is not guaranteed to be safe.
|
|
842
|
+
*
|
|
843
|
+
* Please check the documentation for your specific connector(s) for a detailed breakdown of
|
|
844
|
+
* behaviors for automigrate!
|
|
845
|
+
*
|
|
846
|
+
* @param {String|String[]} [models] Model(s) to migrate. If not present, apply to all models.
|
|
847
|
+
* @param {Function} [callback] Callback function. Optional.
|
|
848
|
+
*
|
|
849
|
+
*/
|
|
850
|
+
DataSource.prototype.automigrate = function(models, cb) {
|
|
851
|
+
this.freeze();
|
|
852
|
+
if (!cb && "function" === typeof models) {
|
|
853
|
+
cb = models;
|
|
854
|
+
models = void 0;
|
|
855
|
+
}
|
|
856
|
+
cb = cb || utils.createPromiseCallback();
|
|
857
|
+
if (!this.connector.automigrate) {
|
|
858
|
+
process.nextTick(cb);
|
|
859
|
+
return cb.promise;
|
|
860
|
+
}
|
|
861
|
+
if ("string" === typeof models) models = [models];
|
|
862
|
+
const attachedModels = this.connector._models;
|
|
863
|
+
if (attachedModels && typeof attachedModels === "object") {
|
|
864
|
+
models = models || Object.keys(attachedModels);
|
|
865
|
+
if (models.length === 0) {
|
|
866
|
+
process.nextTick(cb);
|
|
867
|
+
return cb.promise;
|
|
868
|
+
}
|
|
869
|
+
const invalidModels = models.filter(function(m) {
|
|
870
|
+
return !(m in attachedModels);
|
|
871
|
+
});
|
|
872
|
+
if (invalidModels.length) {
|
|
873
|
+
process.nextTick(function() {
|
|
874
|
+
cb(new Error(g.f("Cannot migrate models not attached to this datasource: %s", invalidModels.join(" "))));
|
|
875
|
+
});
|
|
876
|
+
return cb.promise;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
const args = [models, cb];
|
|
880
|
+
args.callee = this.automigrate;
|
|
881
|
+
if (this.ready(this, args)) return cb.promise;
|
|
882
|
+
this.connector.automigrate(models, cb);
|
|
883
|
+
return cb.promise;
|
|
884
|
+
};
|
|
885
|
+
/**
|
|
886
|
+
* Update existing database tables.
|
|
887
|
+
* This method applies only to database connectors.
|
|
888
|
+
*
|
|
889
|
+
* **WARNING**: `autoupdate()` will attempt to preserve data while updating the
|
|
890
|
+
* schema on your target DataSource, but this is not guaranteed to be safe.
|
|
891
|
+
*
|
|
892
|
+
* Please check the documentation for your specific connector(s) for a detailed breakdown of
|
|
893
|
+
* behaviors for automigrate!*
|
|
894
|
+
*
|
|
895
|
+
* @param {String|String[]} [models] Model(s) to migrate. If not present, apply to all models.
|
|
896
|
+
* @param {Function} [cb] The callback function
|
|
897
|
+
*/
|
|
898
|
+
DataSource.prototype.autoupdate = function(models, cb) {
|
|
899
|
+
this.freeze();
|
|
900
|
+
if (!cb && "function" === typeof models) {
|
|
901
|
+
cb = models;
|
|
902
|
+
models = void 0;
|
|
903
|
+
}
|
|
904
|
+
cb = cb || utils.createPromiseCallback();
|
|
905
|
+
if (!this.connector.autoupdate) {
|
|
906
|
+
process.nextTick(cb);
|
|
907
|
+
return cb.promise;
|
|
908
|
+
}
|
|
909
|
+
if ("string" === typeof models) models = [models];
|
|
910
|
+
const attachedModels = this.connector._models;
|
|
911
|
+
if (attachedModels && typeof attachedModels === "object") {
|
|
912
|
+
models = models || Object.keys(attachedModels);
|
|
913
|
+
if (models.length === 0) {
|
|
914
|
+
process.nextTick(cb);
|
|
915
|
+
return cb.promise;
|
|
916
|
+
}
|
|
917
|
+
const invalidModels = models.filter(function(m) {
|
|
918
|
+
return !(m in attachedModels);
|
|
919
|
+
});
|
|
920
|
+
if (invalidModels.length) {
|
|
921
|
+
process.nextTick(function() {
|
|
922
|
+
cb(new Error(g.f("Cannot migrate models not attached to this datasource: %s", invalidModels.join(" "))));
|
|
923
|
+
});
|
|
924
|
+
return cb.promise;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
const args = [models, cb];
|
|
928
|
+
args.callee = this.autoupdate;
|
|
929
|
+
if (this.ready(this, args)) return cb.promise;
|
|
930
|
+
this.connector.autoupdate(models, cb);
|
|
931
|
+
return cb.promise;
|
|
932
|
+
};
|
|
933
|
+
/**
|
|
934
|
+
* Discover existing database tables.
|
|
935
|
+
* This method returns an array of model objects, including {type, name, onwer}
|
|
936
|
+
*
|
|
937
|
+
* @options {Object} options Discovery options. See below.
|
|
938
|
+
* @param {Function} Callback function. Optional.
|
|
939
|
+
* @property {String} owner/schema The owner or schema to discover from.
|
|
940
|
+
* @property {Boolean} all If true, discover all models; if false, discover only models owned by the current user.
|
|
941
|
+
* @property {Boolean} views If true, include views; if false, only tables.
|
|
942
|
+
* @property {Number} limit Page size
|
|
943
|
+
* @property {Number} offset Starting index
|
|
944
|
+
* @returns {ModelDefinition[]}
|
|
945
|
+
*/
|
|
946
|
+
DataSource.prototype.discoverModelDefinitions = function(options, cb) {
|
|
947
|
+
this.freeze();
|
|
948
|
+
if (cb === void 0 && typeof options === "function") {
|
|
949
|
+
cb = options;
|
|
950
|
+
options = {};
|
|
951
|
+
}
|
|
952
|
+
options = options || {};
|
|
953
|
+
cb = cb || utils.createPromiseCallback();
|
|
954
|
+
if (this.connector.discoverModelDefinitions) this.connector.discoverModelDefinitions(options, cb);
|
|
955
|
+
else if (cb) process.nextTick(cb);
|
|
956
|
+
return cb.promise;
|
|
957
|
+
};
|
|
958
|
+
/*! Method will be completely removed in LoopBack.next
|
|
959
|
+
*/
|
|
960
|
+
/**
|
|
961
|
+
* The synchronous version of discoverModelDefinitions.
|
|
962
|
+
* @options {Object} options The options
|
|
963
|
+
* @property {Boolean} all If true, discover all models; if false, discover only models owned by the current user.
|
|
964
|
+
* @property {Boolean} views If true, nclude views; if false, only tables.
|
|
965
|
+
* @property {Number} limit Page size
|
|
966
|
+
* @property {Number} offset Starting index
|
|
967
|
+
* @returns {ModelDefinition[]}
|
|
968
|
+
*/
|
|
969
|
+
DataSource.prototype.discoverModelDefinitionsSync = function(options) {
|
|
970
|
+
this.freeze();
|
|
971
|
+
if (this.connector.discoverModelDefinitionsSync) return this.connector.discoverModelDefinitionsSync(options);
|
|
972
|
+
return null;
|
|
973
|
+
};
|
|
974
|
+
/**
|
|
975
|
+
* Discover properties for a given model.
|
|
976
|
+
*
|
|
977
|
+
* Callback function return value is an object that can have the following properties:
|
|
978
|
+
*
|
|
979
|
+
*| Key | Type | Description |
|
|
980
|
+
*|-----|------|-------------|
|
|
981
|
+
*|owner | String | Database owner or schema|
|
|
982
|
+
*|tableName | String | Table/view name|
|
|
983
|
+
*|columnName | String | Column name|
|
|
984
|
+
*|dataType | String | Data type|
|
|
985
|
+
*|dataLength | Number | Data length|
|
|
986
|
+
*|dataPrecision | Number | Numeric data precision|
|
|
987
|
+
*|dataScale |Number | Numeric data scale|
|
|
988
|
+
*|nullable |Boolean | If true, then the data can be null|
|
|
989
|
+
* See [Properties](./Model-definition-JSON-file.html#properties) for more
|
|
990
|
+
* details on the Property return type.
|
|
991
|
+
* @param {String} modelName The table/view name
|
|
992
|
+
* @options {Object} options The options
|
|
993
|
+
* @property {String} owner|schema The database owner or schema
|
|
994
|
+
* @param {Function} cb Callback function. Optional
|
|
995
|
+
* @callback cb
|
|
996
|
+
* @returns {Promise} A promise that returns an array of Properties (Property[])
|
|
997
|
+
*
|
|
998
|
+
*/
|
|
999
|
+
DataSource.prototype.discoverModelProperties = function(modelName, options, cb) {
|
|
1000
|
+
this.freeze();
|
|
1001
|
+
if (cb === void 0 && typeof options === "function") {
|
|
1002
|
+
cb = options;
|
|
1003
|
+
options = {};
|
|
1004
|
+
}
|
|
1005
|
+
options = options || {};
|
|
1006
|
+
cb = cb || utils.createPromiseCallback();
|
|
1007
|
+
if (this.connector.discoverModelProperties) this.connector.discoverModelProperties(modelName, options, cb);
|
|
1008
|
+
else if (cb) process.nextTick(cb);
|
|
1009
|
+
return cb.promise;
|
|
1010
|
+
};
|
|
1011
|
+
/*! Method will be completely removed in LoopBack.next
|
|
1012
|
+
*/
|
|
1013
|
+
/**
|
|
1014
|
+
* The synchronous version of discoverModelProperties
|
|
1015
|
+
* @param {String} modelName The table/view name
|
|
1016
|
+
* @param {Object} options The options
|
|
1017
|
+
* @returns {*}
|
|
1018
|
+
*/
|
|
1019
|
+
DataSource.prototype.discoverModelPropertiesSync = function(modelName, options) {
|
|
1020
|
+
this.freeze();
|
|
1021
|
+
if (this.connector.discoverModelPropertiesSync) return this.connector.discoverModelPropertiesSync(modelName, options);
|
|
1022
|
+
return null;
|
|
1023
|
+
};
|
|
1024
|
+
/**
|
|
1025
|
+
* Discover primary keys for a given owner/modelName.
|
|
1026
|
+
* Callback function return value is an object that can have the following properties:
|
|
1027
|
+
*
|
|
1028
|
+
*| Key | Type | Description |
|
|
1029
|
+
*|-----|------|-------------|
|
|
1030
|
+
*| owner |String | Table schema or owner (may be null). Owner defaults to current user.
|
|
1031
|
+
*| tableName |String| Table name
|
|
1032
|
+
*| columnName |String| Column name
|
|
1033
|
+
*| keySeq |Number| Sequence number within primary key (1 indicates the first column in the primary key; 2 indicates the second column in the primary key).
|
|
1034
|
+
*| pkName |String| Primary key name (may be null)
|
|
1035
|
+
* See [ID Properties](./Model-definition-JSON-file.html#id-properties) for more
|
|
1036
|
+
* information.
|
|
1037
|
+
* @param {String} modelName The model name
|
|
1038
|
+
* @options {Object} options The options
|
|
1039
|
+
* @property {String} owner|schema The database owner or schema
|
|
1040
|
+
* @param {Function} [cb] The callback function
|
|
1041
|
+
* @returns {Promise} A promise with an array of Primary Keys (Property[])
|
|
1042
|
+
*/
|
|
1043
|
+
DataSource.prototype.discoverPrimaryKeys = function(modelName, options, cb) {
|
|
1044
|
+
this.freeze();
|
|
1045
|
+
if (cb === void 0 && typeof options === "function") {
|
|
1046
|
+
cb = options;
|
|
1047
|
+
options = {};
|
|
1048
|
+
}
|
|
1049
|
+
options = options || {};
|
|
1050
|
+
cb = cb || utils.createPromiseCallback();
|
|
1051
|
+
if (this.connector.discoverPrimaryKeys) this.connector.discoverPrimaryKeys(modelName, options, cb);
|
|
1052
|
+
else if (cb) process.nextTick(cb);
|
|
1053
|
+
return cb.promise;
|
|
1054
|
+
};
|
|
1055
|
+
/**
|
|
1056
|
+
* Discover unique keys for a given modelName.
|
|
1057
|
+
* Callback function return value is an object that can have the following properties:
|
|
1058
|
+
*
|
|
1059
|
+
*| Key | Type | Description |
|
|
1060
|
+
*|-----|------|-------------|
|
|
1061
|
+
*| owner |String | Table schema or owner (may be null). Owner defaults to current user.
|
|
1062
|
+
*| tableName |String| Table name
|
|
1063
|
+
*| columnName |String| Column name
|
|
1064
|
+
* See [ID Properties](./Model-definition-JSON-file.html#id-properties) for more
|
|
1065
|
+
* information.
|
|
1066
|
+
* @param {String} modelName The model name
|
|
1067
|
+
* @options {Object} options The options
|
|
1068
|
+
* @param {Function} [cb] The callback function
|
|
1069
|
+
* @returns {Promise} A promise with an array of Primary Keys (Property[])
|
|
1070
|
+
*/
|
|
1071
|
+
DataSource.prototype.discoverUniqueKeys = function(modelName, options, cb) {
|
|
1072
|
+
this.freeze();
|
|
1073
|
+
if (cb === void 0 && typeof options === "function") {
|
|
1074
|
+
cb = options;
|
|
1075
|
+
options = {};
|
|
1076
|
+
}
|
|
1077
|
+
options = options || {};
|
|
1078
|
+
cb = cb || utils.createPromiseCallback();
|
|
1079
|
+
if (this.connector.discoverUniqueKeys) this.connector.discoverUniqueKeys(modelName, options, cb);
|
|
1080
|
+
else if (cb) process.nextTick(cb);
|
|
1081
|
+
return cb.promise;
|
|
1082
|
+
};
|
|
1083
|
+
/*! Method will be completely removed in LoopBack.next
|
|
1084
|
+
*/
|
|
1085
|
+
/**
|
|
1086
|
+
* The synchronous version of discoverPrimaryKeys
|
|
1087
|
+
* @param {String} modelName The model name
|
|
1088
|
+
* @options {Object} options The options
|
|
1089
|
+
* @property {String} owner|schema The database owner or schema
|
|
1090
|
+
* @returns {*}
|
|
1091
|
+
*/
|
|
1092
|
+
DataSource.prototype.discoverPrimaryKeysSync = function(modelName, options) {
|
|
1093
|
+
this.freeze();
|
|
1094
|
+
if (this.connector.discoverPrimaryKeysSync) return this.connector.discoverPrimaryKeysSync(modelName, options);
|
|
1095
|
+
return null;
|
|
1096
|
+
};
|
|
1097
|
+
/**
|
|
1098
|
+
* Discover foreign keys for a given owner/modelName
|
|
1099
|
+
*
|
|
1100
|
+
* Callback function return value is an object that can have the following properties:
|
|
1101
|
+
*
|
|
1102
|
+
*| Key | Type | Description |
|
|
1103
|
+
*|-----|------|-------------|
|
|
1104
|
+
*|fkOwner |String | Foreign key table schema (may be null)
|
|
1105
|
+
*|fkName |String | Foreign key name (may be null)
|
|
1106
|
+
*|fkTableName |String | Foreign key table name
|
|
1107
|
+
*|fkColumnName |String | Foreign key column name
|
|
1108
|
+
*|keySeq |Number | Sequence number within a foreign key( a value of 1 represents the first column of the foreign key, a value of 2 would represent the second column within the foreign key).
|
|
1109
|
+
*|pkOwner |String | Primary key table schema being imported (may be null)
|
|
1110
|
+
*|pkName |String | Primary key name (may be null)
|
|
1111
|
+
*|pkTableName |String | Primary key table name being imported
|
|
1112
|
+
*|pkColumnName |String | Primary key column name being imported
|
|
1113
|
+
* See [Relations](/Model-definition-JSON-file.html#relations) for more
|
|
1114
|
+
* information.
|
|
1115
|
+
* @param {String} modelName The model name
|
|
1116
|
+
* @options {Object} options The options
|
|
1117
|
+
* @property {String} owner|schema The database owner or schema
|
|
1118
|
+
* @param {Function} [cb] The callback function
|
|
1119
|
+
* @returns {Promise} A Promise with an array of foreign key relations.
|
|
1120
|
+
*
|
|
1121
|
+
*/
|
|
1122
|
+
DataSource.prototype.discoverForeignKeys = function(modelName, options, cb) {
|
|
1123
|
+
this.freeze();
|
|
1124
|
+
if (cb === void 0 && typeof options === "function") {
|
|
1125
|
+
cb = options;
|
|
1126
|
+
options = {};
|
|
1127
|
+
}
|
|
1128
|
+
options = options || {};
|
|
1129
|
+
cb = cb || utils.createPromiseCallback();
|
|
1130
|
+
if (this.connector.discoverForeignKeys) this.connector.discoverForeignKeys(modelName, options, cb);
|
|
1131
|
+
else if (cb) process.nextTick(cb);
|
|
1132
|
+
return cb.promise;
|
|
1133
|
+
};
|
|
1134
|
+
/*! Method will be completely removed in LoopBack.next
|
|
1135
|
+
*/
|
|
1136
|
+
/**
|
|
1137
|
+
* The synchronous version of discoverForeignKeys
|
|
1138
|
+
*
|
|
1139
|
+
* @param {String} modelName The model name
|
|
1140
|
+
* @param {Object} options The options
|
|
1141
|
+
* @returns {*}
|
|
1142
|
+
*/
|
|
1143
|
+
DataSource.prototype.discoverForeignKeysSync = function(modelName, options) {
|
|
1144
|
+
this.freeze();
|
|
1145
|
+
if (this.connector.discoverForeignKeysSync) return this.connector.discoverForeignKeysSync(modelName, options);
|
|
1146
|
+
return null;
|
|
1147
|
+
};
|
|
1148
|
+
/**
|
|
1149
|
+
* Retrieves a description of the foreign key columns that reference the given table's primary key columns
|
|
1150
|
+
* (the foreign keys exported by a table), ordered by fkTableOwner, fkTableName, and keySeq.
|
|
1151
|
+
*
|
|
1152
|
+
* Callback function return value is an object that can have the following properties:
|
|
1153
|
+
*
|
|
1154
|
+
*| Key | Type | Description |
|
|
1155
|
+
*|-----|------|-------------|
|
|
1156
|
+
*|fkOwner |String | Foreign key table schema (may be null)
|
|
1157
|
+
*|fkName |String | Foreign key name (may be null)
|
|
1158
|
+
*|fkTableName |String | Foreign key table name
|
|
1159
|
+
*|fkColumnName |String | Foreign key column name
|
|
1160
|
+
*|keySeq |Number | Sequence number within a foreign key( a value of 1 represents the first column of the foreign key, a value of 2 would represent the second column within the foreign key).
|
|
1161
|
+
*|pkOwner |String | Primary key table schema being imported (may be null)
|
|
1162
|
+
*|pkName |String | Primary key name (may be null)
|
|
1163
|
+
*|pkTableName |String | Primary key table name being imported
|
|
1164
|
+
*|pkColumnName |String | Primary key column name being imported
|
|
1165
|
+
* See [Relations](/Model-definition-JSON-file.html#relations) for more
|
|
1166
|
+
* information.
|
|
1167
|
+
*
|
|
1168
|
+
* @param {String} modelName The model name
|
|
1169
|
+
* @options {Object} options The options
|
|
1170
|
+
* @property {String} owner|schema The database owner or schema
|
|
1171
|
+
* @param {Function} [cb] The callback function
|
|
1172
|
+
* @returns {Promise} A Promise with an array of exported foreign key relations.
|
|
1173
|
+
*/
|
|
1174
|
+
DataSource.prototype.discoverExportedForeignKeys = function(modelName, options, cb) {
|
|
1175
|
+
this.freeze();
|
|
1176
|
+
if (cb === void 0 && typeof options === "function") {
|
|
1177
|
+
cb = options;
|
|
1178
|
+
options = {};
|
|
1179
|
+
}
|
|
1180
|
+
options = options || {};
|
|
1181
|
+
cb = cb || utils.createPromiseCallback();
|
|
1182
|
+
if (this.connector.discoverExportedForeignKeys) this.connector.discoverExportedForeignKeys(modelName, options, cb);
|
|
1183
|
+
else if (cb) process.nextTick(cb);
|
|
1184
|
+
return cb.promise;
|
|
1185
|
+
};
|
|
1186
|
+
/*! Method will be completely removed in LoopBack.next
|
|
1187
|
+
*/
|
|
1188
|
+
/**
|
|
1189
|
+
* The synchronous version of discoverExportedForeignKeys
|
|
1190
|
+
* @param {String} modelName The model name
|
|
1191
|
+
* @param {Object} options The options
|
|
1192
|
+
* @returns {*}
|
|
1193
|
+
*/
|
|
1194
|
+
DataSource.prototype.discoverExportedForeignKeysSync = function(modelName, options) {
|
|
1195
|
+
this.freeze();
|
|
1196
|
+
if (this.connector.discoverExportedForeignKeysSync) return this.connector.discoverExportedForeignKeysSync(modelName, options);
|
|
1197
|
+
return null;
|
|
1198
|
+
};
|
|
1199
|
+
/**
|
|
1200
|
+
* Renames db column names with different naming conventions:
|
|
1201
|
+
* camelCase for property names as it's LB default naming convention for properties,
|
|
1202
|
+
* or keep the name the same if needed.
|
|
1203
|
+
*
|
|
1204
|
+
* @param {*} name name defined in database
|
|
1205
|
+
* @param {*} caseFunction optional. A function to convert the name into different case.
|
|
1206
|
+
*/
|
|
1207
|
+
function fromDBName(name, caseFunction) {
|
|
1208
|
+
if (!name) return name;
|
|
1209
|
+
if (typeof caseFunction === "function") return caseFunction(name);
|
|
1210
|
+
return name;
|
|
1211
|
+
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Discover one schema from the given model without following the relations.
|
|
1214
|
+
**Example schema from oracle connector:**
|
|
1215
|
+
*
|
|
1216
|
+
* ```js
|
|
1217
|
+
* {
|
|
1218
|
+
* "name": "Product",
|
|
1219
|
+
* "options": {
|
|
1220
|
+
* "idInjection": false,
|
|
1221
|
+
* "oracle": {
|
|
1222
|
+
* "schema": "BLACKPOOL",
|
|
1223
|
+
* "table": "PRODUCT"
|
|
1224
|
+
* }
|
|
1225
|
+
* },
|
|
1226
|
+
* "properties": {
|
|
1227
|
+
* "id": {
|
|
1228
|
+
* "type": "String",
|
|
1229
|
+
* "required": true,
|
|
1230
|
+
* "length": 20,
|
|
1231
|
+
* "id": 1,
|
|
1232
|
+
* "oracle": {
|
|
1233
|
+
* "columnName": "ID",
|
|
1234
|
+
* "dataType": "VARCHAR2",
|
|
1235
|
+
* "dataLength": 20,
|
|
1236
|
+
* "nullable": "N"
|
|
1237
|
+
* }
|
|
1238
|
+
* },
|
|
1239
|
+
* "name": {
|
|
1240
|
+
* "type": "String",
|
|
1241
|
+
* "required": false,
|
|
1242
|
+
* "length": 64,
|
|
1243
|
+
* "oracle": {
|
|
1244
|
+
* "columnName": "NAME",
|
|
1245
|
+
* "dataType": "VARCHAR2",
|
|
1246
|
+
* "dataLength": 64,
|
|
1247
|
+
* "nullable": "Y"
|
|
1248
|
+
* }
|
|
1249
|
+
* },
|
|
1250
|
+
* ...
|
|
1251
|
+
* "fireModes": {
|
|
1252
|
+
* "type": "String",
|
|
1253
|
+
* "required": false,
|
|
1254
|
+
* "length": 64,
|
|
1255
|
+
* "oracle": {
|
|
1256
|
+
* "columnName": "FIRE_MODES",
|
|
1257
|
+
* "dataType": "VARCHAR2",
|
|
1258
|
+
* "dataLength": 64,
|
|
1259
|
+
* "nullable": "Y"
|
|
1260
|
+
* }
|
|
1261
|
+
* }
|
|
1262
|
+
* }
|
|
1263
|
+
* }
|
|
1264
|
+
* ```
|
|
1265
|
+
*
|
|
1266
|
+
* @param {String} tableName The name of the table to discover.
|
|
1267
|
+
* @options {Object} [options] An options object typically used for Relations.
|
|
1268
|
+
* See [Relations](./Model-definition-JSON-file.html#relations) for more information.
|
|
1269
|
+
* @param {Function} [cb] The callback function
|
|
1270
|
+
* @returns {Promise} A promise object that resolves to a single schema.
|
|
1271
|
+
*/
|
|
1272
|
+
DataSource.prototype.discoverSchema = function(tableName, options, cb) {
|
|
1273
|
+
options = options || {};
|
|
1274
|
+
if (!cb && "function" === typeof options) {
|
|
1275
|
+
cb = options;
|
|
1276
|
+
options = {};
|
|
1277
|
+
}
|
|
1278
|
+
options.visited = {};
|
|
1279
|
+
options.relations = false;
|
|
1280
|
+
cb = cb || utils.createPromiseCallback();
|
|
1281
|
+
this.discoverSchemas(tableName, options, function(err, schemas) {
|
|
1282
|
+
if (err || !schemas) {
|
|
1283
|
+
if (cb) cb(err, schemas);
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
for (const s in schemas) {
|
|
1287
|
+
if (cb) cb(null, schemas[s]);
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1291
|
+
return cb.promise;
|
|
1292
|
+
};
|
|
1293
|
+
/**
|
|
1294
|
+
* Discover schema from a given tableName/viewName.
|
|
1295
|
+
*
|
|
1296
|
+
* @param {String} tableName The table name.
|
|
1297
|
+
* @options {Object} [options] Options; see below.
|
|
1298
|
+
* @property {String} owner|schema Database owner or schema name.
|
|
1299
|
+
* @property {Boolean} relations True if relations (primary key/foreign key) are navigated; false otherwise.
|
|
1300
|
+
* @property {Boolean} all True if all owners are included; false otherwise.
|
|
1301
|
+
* @property {Boolean} views True if views are included; false otherwise.
|
|
1302
|
+
* @param {Function} [cb] The callback function
|
|
1303
|
+
* @returns {Promise} A promise object that resolves to an array of schemas.
|
|
1304
|
+
*/
|
|
1305
|
+
DataSource.prototype.discoverSchemas = function(tableName, options, cb) {
|
|
1306
|
+
options = options || {};
|
|
1307
|
+
if (!cb && "function" === typeof options) {
|
|
1308
|
+
cb = options;
|
|
1309
|
+
options = {};
|
|
1310
|
+
}
|
|
1311
|
+
cb = cb || utils.createPromiseCallback();
|
|
1312
|
+
const self = this;
|
|
1313
|
+
const dbType = this.connector.name;
|
|
1314
|
+
let nameMapper;
|
|
1315
|
+
const disableCamelCase = !!options.disableCamelCase;
|
|
1316
|
+
if (options.nameMapper === null) nameMapper = function(type, name) {
|
|
1317
|
+
return name;
|
|
1318
|
+
};
|
|
1319
|
+
else if (typeof options.nameMapper === "function") nameMapper = options.nameMapper;
|
|
1320
|
+
else nameMapper = function mapName(type, name) {
|
|
1321
|
+
if (type === "table" || type === "model") return fromDBName(name, pascalCase);
|
|
1322
|
+
else if (type == "fk") if (disableCamelCase) return fromDBName(name + "Rel");
|
|
1323
|
+
else return fromDBName(name + "Rel", camelCase);
|
|
1324
|
+
else if (disableCamelCase) return fromDBName(name);
|
|
1325
|
+
else return fromDBName(name, camelCase);
|
|
1326
|
+
};
|
|
1327
|
+
if (this.connector.discoverSchemas) {
|
|
1328
|
+
this.connector.discoverSchemas(tableName, options, cb);
|
|
1329
|
+
return cb.promise;
|
|
1330
|
+
}
|
|
1331
|
+
const tasks = [
|
|
1332
|
+
this.discoverModelProperties.bind(this, tableName, options),
|
|
1333
|
+
this.discoverPrimaryKeys.bind(this, tableName, options),
|
|
1334
|
+
this.discoverUniqueKeys.bind(this, tableName, options)
|
|
1335
|
+
];
|
|
1336
|
+
const followingRelations = options.associations || options.relations;
|
|
1337
|
+
if (followingRelations) tasks.push(this.discoverForeignKeys.bind(this, tableName, options));
|
|
1338
|
+
runParallelTasks(tasks, function(err, results) {
|
|
1339
|
+
if (err) {
|
|
1340
|
+
cb(err);
|
|
1341
|
+
return cb.promise;
|
|
1342
|
+
}
|
|
1343
|
+
const columns = results[0];
|
|
1344
|
+
const uniqueKeyArray = results[2] || [];
|
|
1345
|
+
let uniqueKeys = [];
|
|
1346
|
+
uniqueKeyArray.forEach((key) => {
|
|
1347
|
+
uniqueKeys.push(key["columnName"]);
|
|
1348
|
+
});
|
|
1349
|
+
if (!columns || columns.length === 0) {
|
|
1350
|
+
cb(new Error(g.f("Table '%s' does not exist.", tableName)));
|
|
1351
|
+
return cb.promise;
|
|
1352
|
+
}
|
|
1353
|
+
const primaryKeys = results[1] || [];
|
|
1354
|
+
const pks = {};
|
|
1355
|
+
primaryKeys.forEach(function(pk) {
|
|
1356
|
+
pks[pk.columnName] = pk.keySeq;
|
|
1357
|
+
});
|
|
1358
|
+
if (self.settings.debug) debug("Primary keys: ", pks);
|
|
1359
|
+
uniqueKeys = uniqueKeys.filter((item) => !pks.hasOwnProperty(item));
|
|
1360
|
+
const schema = {
|
|
1361
|
+
name: nameMapper("table", tableName),
|
|
1362
|
+
options: { idInjection: false },
|
|
1363
|
+
properties: {}
|
|
1364
|
+
};
|
|
1365
|
+
schema.options[dbType] = {
|
|
1366
|
+
schema: columns[0].owner,
|
|
1367
|
+
table: tableName
|
|
1368
|
+
};
|
|
1369
|
+
columns.forEach(function(item) {
|
|
1370
|
+
const propName = nameMapper("column", item.columnName);
|
|
1371
|
+
schema.properties[propName] = {
|
|
1372
|
+
type: item.type,
|
|
1373
|
+
required: !item.generated && (item.nullable === "N" || item.nullable === "NO" || item.nullable === 0 || item.nullable === false),
|
|
1374
|
+
jsonSchema: { nullable: item.nullable === "Y" || item.nullable === "YES" || item.nullable === 1 || item.nullable === true },
|
|
1375
|
+
length: item.dataLength,
|
|
1376
|
+
precision: item.dataPrecision,
|
|
1377
|
+
scale: item.dataScale,
|
|
1378
|
+
generated: item.generated || false
|
|
1379
|
+
};
|
|
1380
|
+
if (pks[item.columnName]) schema.properties[propName].id = pks[item.columnName];
|
|
1381
|
+
if (uniqueKeys.includes(propName)) schema.properties[propName]["index"] = { unique: true };
|
|
1382
|
+
if (schema.properties[propName]["id"] && schema.properties[propName]["generated"] && schema.properties[propName]["type"].toLowerCase() === "string") schema.properties[propName]["useDefaultIdType"] = false;
|
|
1383
|
+
const dbSpecific = schema.properties[propName][dbType] = {
|
|
1384
|
+
columnName: item.columnName,
|
|
1385
|
+
dataType: item.dataType,
|
|
1386
|
+
dataLength: item.dataLength,
|
|
1387
|
+
dataPrecision: item.dataPrecision,
|
|
1388
|
+
dataScale: item.dataScale,
|
|
1389
|
+
nullable: item.nullable,
|
|
1390
|
+
generated: item.generated || false
|
|
1391
|
+
};
|
|
1392
|
+
if (item.dataType && item.dataType.toLowerCase().includes("enum")) {
|
|
1393
|
+
let enumItems = "";
|
|
1394
|
+
item.dataType.toLowerCase().split("enum")[1].slice(1, -1).replaceAll("'", "").split(",").forEach((enumValue) => {
|
|
1395
|
+
enumItems += `'${enumValue.trimStart()}',`;
|
|
1396
|
+
});
|
|
1397
|
+
dbSpecific.value = enumItems;
|
|
1398
|
+
}
|
|
1399
|
+
schema.properties[propName][dbType] = dbSpecific;
|
|
1400
|
+
if (item[dbType]) for (const k in item[dbType]) dbSpecific[k] = item[dbType][k];
|
|
1401
|
+
});
|
|
1402
|
+
options.visited = options.visited || {};
|
|
1403
|
+
const schemaKey = columns[0].owner + "." + tableName;
|
|
1404
|
+
if (!options.visited.hasOwnProperty(schemaKey)) {
|
|
1405
|
+
if (self.settings.debug) debug("Adding schema for " + schemaKey);
|
|
1406
|
+
options.visited[schemaKey] = schema;
|
|
1407
|
+
}
|
|
1408
|
+
const otherTables = {};
|
|
1409
|
+
if (followingRelations) {
|
|
1410
|
+
const fks = {};
|
|
1411
|
+
const foreignKeys = results[3] || [];
|
|
1412
|
+
foreignKeys.forEach(function(fk) {
|
|
1413
|
+
const fkInfo = {
|
|
1414
|
+
keySeq: fk.keySeq,
|
|
1415
|
+
owner: fk.pkOwner,
|
|
1416
|
+
tableName: fk.pkTableName,
|
|
1417
|
+
columnName: fk.pkColumnName
|
|
1418
|
+
};
|
|
1419
|
+
if (fks[fk.fkName]) fks[fk.fkName].push(fkInfo);
|
|
1420
|
+
else fks[fk.fkName] = [fkInfo];
|
|
1421
|
+
});
|
|
1422
|
+
if (self.settings.debug) debug("Foreign keys: ", fks);
|
|
1423
|
+
schema.options.relations = {};
|
|
1424
|
+
foreignKeys.forEach(function(fk) {
|
|
1425
|
+
const propName = nameMapper("fk", fk.fkName || fk.pkTableName);
|
|
1426
|
+
schema.options.relations[propName] = {
|
|
1427
|
+
model: nameMapper("table", fk.pkTableName),
|
|
1428
|
+
type: "belongsTo",
|
|
1429
|
+
foreignKey: nameMapper("column", fk.fkColumnName)
|
|
1430
|
+
};
|
|
1431
|
+
const key = fk.pkOwner + "." + fk.pkTableName;
|
|
1432
|
+
if (!options.visited.hasOwnProperty(key) && !otherTables.hasOwnProperty(key)) otherTables[key] = {
|
|
1433
|
+
owner: fk.pkOwner,
|
|
1434
|
+
tableName: fk.pkTableName
|
|
1435
|
+
};
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
if (Object.keys(otherTables).length === 0) cb(null, options.visited);
|
|
1439
|
+
else {
|
|
1440
|
+
const moreTasks = [];
|
|
1441
|
+
for (const t in otherTables) {
|
|
1442
|
+
if (self.settings.debug) debug("Discovering related schema for " + schemaKey);
|
|
1443
|
+
const newOptions = {};
|
|
1444
|
+
for (const key in options) newOptions[key] = options[key];
|
|
1445
|
+
newOptions.owner = otherTables[t].owner;
|
|
1446
|
+
moreTasks.push(DataSource.prototype.discoverSchemas.bind(self, otherTables[t].tableName, newOptions));
|
|
1447
|
+
}
|
|
1448
|
+
runParallelTasks(moreTasks, function(err, results) {
|
|
1449
|
+
const result = results && results[0];
|
|
1450
|
+
cb(err, result);
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
});
|
|
1454
|
+
return cb.promise;
|
|
1455
|
+
};
|
|
1456
|
+
/*! Method will be completely removed in LoopBack.next
|
|
1457
|
+
*/
|
|
1458
|
+
/**
|
|
1459
|
+
* Discover schema from a given table/view synchronously
|
|
1460
|
+
* @param {String} modelName The model name
|
|
1461
|
+
* @options {Object} [options] Options; see below.
|
|
1462
|
+
* @property {String} owner|schema Database owner or schema name.
|
|
1463
|
+
* @property {Boolean} relations True if relations (primary key/foreign key) are navigated; false otherwise.
|
|
1464
|
+
* @property {Boolean} all True if all owners are included; false otherwise.
|
|
1465
|
+
* @property {Boolean} views True if views are included; false otherwise.
|
|
1466
|
+
* @returns {Array<Object>} An array of schema definition objects.
|
|
1467
|
+
*/
|
|
1468
|
+
DataSource.prototype.discoverSchemasSync = function(modelName, options) {
|
|
1469
|
+
const self = this;
|
|
1470
|
+
const dbType = this.connector.name;
|
|
1471
|
+
const columns = this.discoverModelPropertiesSync(modelName, options);
|
|
1472
|
+
if (!columns || columns.length === 0) return [];
|
|
1473
|
+
const disableCamelCase = !!options.disableCamelCase;
|
|
1474
|
+
const nameMapper = options.nameMapper || function mapName(type, name) {
|
|
1475
|
+
if (type === "table" || type === "model") return fromDBName(name, pascalCase);
|
|
1476
|
+
else if (disableCamelCase) return fromDBName(name);
|
|
1477
|
+
else return fromDBName(name, camelCase);
|
|
1478
|
+
};
|
|
1479
|
+
const primaryKeys = this.discoverPrimaryKeysSync(modelName, options);
|
|
1480
|
+
const pks = {};
|
|
1481
|
+
primaryKeys.forEach(function(pk) {
|
|
1482
|
+
pks[pk.columnName] = pk.keySeq;
|
|
1483
|
+
});
|
|
1484
|
+
if (self.settings.debug) debug("Primary keys: ", pks);
|
|
1485
|
+
const schema = {
|
|
1486
|
+
name: nameMapper("table", modelName),
|
|
1487
|
+
options: { idInjection: false },
|
|
1488
|
+
properties: {}
|
|
1489
|
+
};
|
|
1490
|
+
schema.options[dbType] = {
|
|
1491
|
+
schema: columns.length > 0 && columns[0].owner,
|
|
1492
|
+
table: modelName
|
|
1493
|
+
};
|
|
1494
|
+
columns.forEach(function(item) {
|
|
1495
|
+
const i = item;
|
|
1496
|
+
const propName = nameMapper("column", item.columnName);
|
|
1497
|
+
schema.properties[propName] = {
|
|
1498
|
+
type: item.type,
|
|
1499
|
+
required: !item.generated && item.nullable === "N",
|
|
1500
|
+
length: item.dataLength,
|
|
1501
|
+
precision: item.dataPrecision,
|
|
1502
|
+
scale: item.dataScale,
|
|
1503
|
+
generated: item.generated || false
|
|
1504
|
+
};
|
|
1505
|
+
if (pks[item.columnName]) schema.properties[propName].id = pks[item.columnName];
|
|
1506
|
+
schema.properties[propName][dbType] = {
|
|
1507
|
+
columnName: i.columnName,
|
|
1508
|
+
dataType: i.dataType,
|
|
1509
|
+
dataLength: i.dataLength,
|
|
1510
|
+
dataPrecision: item.dataPrecision,
|
|
1511
|
+
dataScale: item.dataScale,
|
|
1512
|
+
nullable: i.nullable,
|
|
1513
|
+
generated: i.generated || false
|
|
1514
|
+
};
|
|
1515
|
+
});
|
|
1516
|
+
options.visited = options.visited || {};
|
|
1517
|
+
const schemaKey = columns[0].owner + "." + modelName;
|
|
1518
|
+
if (!options.visited.hasOwnProperty(schemaKey)) {
|
|
1519
|
+
if (self.settings.debug) debug("Adding schema for " + schemaKey);
|
|
1520
|
+
options.visited[schemaKey] = schema;
|
|
1521
|
+
}
|
|
1522
|
+
const otherTables = {};
|
|
1523
|
+
if (options.associations || options.relations) {
|
|
1524
|
+
const fks = {};
|
|
1525
|
+
const foreignKeys = this.discoverForeignKeysSync(modelName, options);
|
|
1526
|
+
foreignKeys.forEach(function(fk) {
|
|
1527
|
+
const fkInfo = {
|
|
1528
|
+
keySeq: fk.keySeq,
|
|
1529
|
+
owner: fk.pkOwner,
|
|
1530
|
+
tableName: fk.pkTableName,
|
|
1531
|
+
columnName: fk.pkColumnName
|
|
1532
|
+
};
|
|
1533
|
+
if (fks[fk.fkName]) fks[fk.fkName].push(fkInfo);
|
|
1534
|
+
else fks[fk.fkName] = [fkInfo];
|
|
1535
|
+
});
|
|
1536
|
+
if (self.settings.debug) debug("Foreign keys: ", fks);
|
|
1537
|
+
schema.options.relations = {};
|
|
1538
|
+
foreignKeys.forEach(function(fk) {
|
|
1539
|
+
const propName = nameMapper("column", fk.pkTableName);
|
|
1540
|
+
schema.options.relations[propName] = {
|
|
1541
|
+
model: nameMapper("table", fk.pkTableName),
|
|
1542
|
+
type: "belongsTo",
|
|
1543
|
+
foreignKey: nameMapper("column", fk.fkColumnName)
|
|
1544
|
+
};
|
|
1545
|
+
const key = fk.pkOwner + "." + fk.pkTableName;
|
|
1546
|
+
if (!options.visited.hasOwnProperty(key) && !otherTables.hasOwnProperty(key)) otherTables[key] = {
|
|
1547
|
+
owner: fk.pkOwner,
|
|
1548
|
+
tableName: fk.pkTableName
|
|
1549
|
+
};
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
if (Object.keys(otherTables).length === 0) return options.visited;
|
|
1553
|
+
else {
|
|
1554
|
+
for (const t in otherTables) {
|
|
1555
|
+
if (self.settings.debug) debug("Discovering related schema for " + schemaKey);
|
|
1556
|
+
const newOptions = {};
|
|
1557
|
+
for (const key in options) newOptions[key] = options[key];
|
|
1558
|
+
newOptions.owner = otherTables[t].owner;
|
|
1559
|
+
self.discoverSchemasSync(otherTables[t].tableName, newOptions);
|
|
1560
|
+
}
|
|
1561
|
+
return options.visited;
|
|
1562
|
+
}
|
|
1563
|
+
};
|
|
1564
|
+
/**
|
|
1565
|
+
* Discover and build models from the specified owner/modelName.
|
|
1566
|
+
*
|
|
1567
|
+
* @param {String} modelName The model name.
|
|
1568
|
+
* @options {Object} [options] Options; see below.
|
|
1569
|
+
* @property {String} owner|schema Database owner or schema name.
|
|
1570
|
+
* @property {Boolean} relations True if relations (primary key/foreign key) are navigated; false otherwise.
|
|
1571
|
+
* @property {Boolean} all True if all owners are included; false otherwise.
|
|
1572
|
+
* @property {Boolean} views True if views are included; false otherwise.
|
|
1573
|
+
* @param {Function} [cb] The callback function
|
|
1574
|
+
* @returns {Promise} A Promise object that resolves with a map of model
|
|
1575
|
+
* constructors, keyed by model name
|
|
1576
|
+
*/
|
|
1577
|
+
DataSource.prototype.discoverAndBuildModels = function(modelName, options, cb) {
|
|
1578
|
+
const self = this;
|
|
1579
|
+
options = options || {};
|
|
1580
|
+
this.discoverSchemas(modelName, options, function(err, schemas) {
|
|
1581
|
+
if (err) {
|
|
1582
|
+
if (cb) cb(err, schemas);
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
const schemaList = [];
|
|
1586
|
+
for (const s in schemas) {
|
|
1587
|
+
const schema = schemas[s];
|
|
1588
|
+
if (options.base) {
|
|
1589
|
+
schema.options = schema.options || {};
|
|
1590
|
+
schema.options.base = options.base;
|
|
1591
|
+
}
|
|
1592
|
+
schemaList.push(schema);
|
|
1593
|
+
}
|
|
1594
|
+
const models = self.modelBuilder.buildModels(schemaList, self.createModel.bind(self));
|
|
1595
|
+
if (cb) cb(err, models);
|
|
1596
|
+
});
|
|
1597
|
+
};
|
|
1598
|
+
/*! Method will be completely removed in LoopBack.next
|
|
1599
|
+
*/
|
|
1600
|
+
/**
|
|
1601
|
+
* Discover and build models from the given owner/modelName synchronously.
|
|
1602
|
+
* @param {String} modelName The model name.
|
|
1603
|
+
* @options {Object} [options] Options; see below.
|
|
1604
|
+
* @property {String} owner|schema Database owner or schema name.
|
|
1605
|
+
* @property {Boolean} relations True if relations (primary key/foreign key) are navigated; false otherwise.
|
|
1606
|
+
* @property {Boolean} all True if all owners are included; false otherwise.
|
|
1607
|
+
* @property {Boolean} views True if views are included; false otherwise.
|
|
1608
|
+
|
|
1609
|
+
* @param {String} modelName The model name
|
|
1610
|
+
* @param {Object} [options] The options
|
|
1611
|
+
* @returns {Object} A map of model constructors, keyed by model name
|
|
1612
|
+
*/
|
|
1613
|
+
DataSource.prototype.discoverAndBuildModelsSync = function(modelName, options) {
|
|
1614
|
+
options = options || {};
|
|
1615
|
+
const schemas = this.discoverSchemasSync(modelName, options);
|
|
1616
|
+
const schemaList = [];
|
|
1617
|
+
for (const s in schemas) {
|
|
1618
|
+
const schema = schemas[s];
|
|
1619
|
+
if (options.base) {
|
|
1620
|
+
schema.options = schema.options || {};
|
|
1621
|
+
schema.options.base = options.base;
|
|
1622
|
+
}
|
|
1623
|
+
schemaList.push(schema);
|
|
1624
|
+
}
|
|
1625
|
+
return this.modelBuilder.buildModels(schemaList, this.createModel.bind(this));
|
|
1626
|
+
};
|
|
1627
|
+
/**
|
|
1628
|
+
* Introspect a JSON object and build a model class
|
|
1629
|
+
* @param {String} name Name of the model
|
|
1630
|
+
* @param {Object} json The json object representing a model instance
|
|
1631
|
+
* @param {Object} options Options
|
|
1632
|
+
* @returns {Model} A Model class
|
|
1633
|
+
*/
|
|
1634
|
+
DataSource.prototype.buildModelFromInstance = function(name, json, options) {
|
|
1635
|
+
const schema = ModelBuilder.introspect(json);
|
|
1636
|
+
return this.createModel(name, schema, options);
|
|
1637
|
+
};
|
|
1638
|
+
/**
|
|
1639
|
+
* Check whether or not migrations are required for the database schema to match
|
|
1640
|
+
* the Model definitions attached to the DataSource.
|
|
1641
|
+
* Note: This method applies only to SQL connectors.
|
|
1642
|
+
* @param {String|String[]} [models] A model name or an array of model names. If not present, apply to all models.
|
|
1643
|
+
* @returns {Boolean} Whether or not migrations are required.
|
|
1644
|
+
*/
|
|
1645
|
+
DataSource.prototype.isActual = function(models, cb) {
|
|
1646
|
+
this.freeze();
|
|
1647
|
+
if (this.connector.isActual) this.connector.isActual(models, cb);
|
|
1648
|
+
else {
|
|
1649
|
+
if (!cb && "function" === typeof models) {
|
|
1650
|
+
cb = models;
|
|
1651
|
+
models = void 0;
|
|
1652
|
+
}
|
|
1653
|
+
if (cb) process.nextTick(function() {
|
|
1654
|
+
cb(null, true);
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
};
|
|
1658
|
+
/**
|
|
1659
|
+
* Log benchmarked message. Do not redefine this method, if you need to grab
|
|
1660
|
+
* schema logs, use `dataSource.on('log', ...)` emitter event
|
|
1661
|
+
*
|
|
1662
|
+
* @private used by connectors
|
|
1663
|
+
*/
|
|
1664
|
+
DataSource.prototype.log = function(sql, t) {
|
|
1665
|
+
debug(sql, t);
|
|
1666
|
+
this.emit("log", sql, t);
|
|
1667
|
+
};
|
|
1668
|
+
/**
|
|
1669
|
+
* Freeze dataSource. Behavior depends on connector
|
|
1670
|
+
* To continuously add artifacts to datasource until it is frozen, but it is not really used in loopback.
|
|
1671
|
+
*/
|
|
1672
|
+
DataSource.prototype.freeze = function freeze() {
|
|
1673
|
+
if (!this.connector) throw new Error(g.f("The connector has not been initialized."));
|
|
1674
|
+
if (this.connector.freezeDataSource) this.connector.freezeDataSource();
|
|
1675
|
+
if (this.connector.freezeSchema) this.connector.freezeSchema();
|
|
1676
|
+
};
|
|
1677
|
+
/**
|
|
1678
|
+
* Return table name for specified `modelName`.
|
|
1679
|
+
* @param {String} modelName The model name.
|
|
1680
|
+
* @returns {String} The table name.
|
|
1681
|
+
*/
|
|
1682
|
+
DataSource.prototype.tableName = function(modelName) {
|
|
1683
|
+
return this.getModelDefinition(modelName).tableName(this.connector.name);
|
|
1684
|
+
};
|
|
1685
|
+
/**
|
|
1686
|
+
* Return column name for specified modelName and propertyName
|
|
1687
|
+
* @param {String} modelName The model name
|
|
1688
|
+
* @param {String} propertyName The property name
|
|
1689
|
+
* @returns {String} columnName The column name.
|
|
1690
|
+
*/
|
|
1691
|
+
DataSource.prototype.columnName = function(modelName, propertyName) {
|
|
1692
|
+
return this.getModelDefinition(modelName).columnName(this.connector.name, propertyName);
|
|
1693
|
+
};
|
|
1694
|
+
/**
|
|
1695
|
+
* Return column metadata for specified modelName and propertyName
|
|
1696
|
+
* @param {String} modelName The model name
|
|
1697
|
+
* @param {String} propertyName The property name
|
|
1698
|
+
* @returns {Object} column metadata
|
|
1699
|
+
*/
|
|
1700
|
+
DataSource.prototype.columnMetadata = function(modelName, propertyName) {
|
|
1701
|
+
return this.getModelDefinition(modelName).columnMetadata(this.connector.name, propertyName);
|
|
1702
|
+
};
|
|
1703
|
+
/**
|
|
1704
|
+
* Return column names for specified modelName
|
|
1705
|
+
* @param {String} modelName The model name
|
|
1706
|
+
* @returns {String[]} column names
|
|
1707
|
+
*/
|
|
1708
|
+
DataSource.prototype.columnNames = function(modelName) {
|
|
1709
|
+
return this.getModelDefinition(modelName).columnNames(this.connector.name);
|
|
1710
|
+
};
|
|
1711
|
+
/**
|
|
1712
|
+
* @deprecated
|
|
1713
|
+
* Function to get the id column name of the target model
|
|
1714
|
+
*
|
|
1715
|
+
* For SQL connectors, use `SqlConnector.prototype.column(ModelDefinition.prototype.idName())`.
|
|
1716
|
+
* For NoSQL connectors, use `Connector.prototype.idMapping(ModelDefinition.prototype.idName())`
|
|
1717
|
+
* instead.
|
|
1718
|
+
*
|
|
1719
|
+
* @param {String} modelName The model name
|
|
1720
|
+
* @returns {String} columnName for ID
|
|
1721
|
+
*/
|
|
1722
|
+
DataSource.prototype.idColumnName = function(modelName) {
|
|
1723
|
+
return this.getModelDefinition(modelName).idColumnName(this.connector.name);
|
|
1724
|
+
};
|
|
1725
|
+
/**
|
|
1726
|
+
* Find the ID property name
|
|
1727
|
+
* @param {String} modelName The model name
|
|
1728
|
+
* @returns {String} property name for ID
|
|
1729
|
+
*/
|
|
1730
|
+
DataSource.prototype.idName = function(modelName) {
|
|
1731
|
+
if (!this.getModelDefinition(modelName).idName) g.error("No {{id}} name %s", this.getModelDefinition(modelName));
|
|
1732
|
+
return this.getModelDefinition(modelName).idName();
|
|
1733
|
+
};
|
|
1734
|
+
/**
|
|
1735
|
+
* Find the ID property names sorted by the index
|
|
1736
|
+
* @param {String} modelName The model name
|
|
1737
|
+
* @returns {String[]} property names for IDs
|
|
1738
|
+
*/
|
|
1739
|
+
DataSource.prototype.idNames = function(modelName) {
|
|
1740
|
+
return this.getModelDefinition(modelName).idNames();
|
|
1741
|
+
};
|
|
1742
|
+
/**
|
|
1743
|
+
* Find the id property definition
|
|
1744
|
+
* @param {String} modelName The model name
|
|
1745
|
+
* @returns {Object} The id property definition
|
|
1746
|
+
*/
|
|
1747
|
+
DataSource.prototype.idProperty = function(modelName) {
|
|
1748
|
+
const def = this.getModelDefinition(modelName);
|
|
1749
|
+
const idProps = def && def.ids();
|
|
1750
|
+
return idProps && idProps[0] && idProps[0].property;
|
|
1751
|
+
};
|
|
1752
|
+
/**
|
|
1753
|
+
* Define foreign key to another model
|
|
1754
|
+
* @param {String} className The model name that owns the key
|
|
1755
|
+
* @param {String} key Name of key field
|
|
1756
|
+
* @param {String} foreignClassName The foreign model name
|
|
1757
|
+
* @param {String} pkName (optional) primary key used for foreignKey
|
|
1758
|
+
*/
|
|
1759
|
+
DataSource.prototype.defineForeignKey = function defineForeignKey(className, key, foreignClassName, pkName) {
|
|
1760
|
+
let pkType = null;
|
|
1761
|
+
const foreignModel = this.getModelDefinition(foreignClassName);
|
|
1762
|
+
pkName = pkName || foreignModel && foreignModel.idName();
|
|
1763
|
+
if (pkName) pkType = foreignModel.properties[pkName].type;
|
|
1764
|
+
const model = this.getModelDefinition(className);
|
|
1765
|
+
if (model.properties[key]) {
|
|
1766
|
+
if (pkType) model.rawProperties[key].type = model.properties[key].type = pkType;
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
const fkDef = { type: pkType };
|
|
1770
|
+
const foreignMeta = this.columnMetadata(foreignClassName, pkName);
|
|
1771
|
+
if (foreignMeta && (foreignMeta.dataType || foreignMeta.dataLength)) {
|
|
1772
|
+
fkDef[this.connector.name] = {};
|
|
1773
|
+
if (foreignMeta.dataType) fkDef[this.connector.name].dataType = foreignMeta.dataType;
|
|
1774
|
+
if (foreignMeta.dataLength) fkDef[this.connector.name].dataLength = foreignMeta.dataLength;
|
|
1775
|
+
}
|
|
1776
|
+
if (this.connector.defineForeignKey) {
|
|
1777
|
+
const cb = function(err, keyType) {
|
|
1778
|
+
if (err) throw err;
|
|
1779
|
+
fkDef.type = keyType || pkType;
|
|
1780
|
+
this.defineProperty(className, key, fkDef);
|
|
1781
|
+
}.bind(this);
|
|
1782
|
+
switch (this.connector.defineForeignKey.length) {
|
|
1783
|
+
case 4:
|
|
1784
|
+
this.connector.defineForeignKey(className, key, foreignClassName, cb);
|
|
1785
|
+
break;
|
|
1786
|
+
default:
|
|
1787
|
+
case 3:
|
|
1788
|
+
this.connector.defineForeignKey(className, key, cb);
|
|
1789
|
+
break;
|
|
1790
|
+
}
|
|
1791
|
+
} else this.defineProperty(className, key, fkDef);
|
|
1792
|
+
};
|
|
1793
|
+
/**
|
|
1794
|
+
* Close connection to the DataSource.
|
|
1795
|
+
* @param {Function} [cb] The callback function. Optional.
|
|
1796
|
+
*/
|
|
1797
|
+
DataSource.prototype.disconnect = function disconnect(cb) {
|
|
1798
|
+
cb = cb || utils.createPromiseCallback();
|
|
1799
|
+
const self = this;
|
|
1800
|
+
if (this.connected && typeof this.connector.disconnect === "function") this.connector.disconnect(function(err, result) {
|
|
1801
|
+
self.connected = false;
|
|
1802
|
+
if (cb) cb(err, result);
|
|
1803
|
+
});
|
|
1804
|
+
else process.nextTick(function() {
|
|
1805
|
+
self.connected = false;
|
|
1806
|
+
if (cb) cb();
|
|
1807
|
+
});
|
|
1808
|
+
return cb.promise;
|
|
1809
|
+
};
|
|
1810
|
+
/**
|
|
1811
|
+
* An alias for `disconnect` to make the datasource a LB4 life-cycle observer.
|
|
1812
|
+
* Please note that we are intentionally not providing a `start` method,
|
|
1813
|
+
* because the logic for establishing connection(s) is more complex
|
|
1814
|
+
* and usually started immediately from the datasoure constructor.
|
|
1815
|
+
*/
|
|
1816
|
+
DataSource.prototype.stop = DataSource.prototype.disconnect;
|
|
1817
|
+
/**
|
|
1818
|
+
* Copy the model from Master.
|
|
1819
|
+
* @param {Function} Master The model constructor
|
|
1820
|
+
* @returns {Model} A copy of the Model object constructor
|
|
1821
|
+
*
|
|
1822
|
+
* @private
|
|
1823
|
+
*/
|
|
1824
|
+
DataSource.prototype.copyModel = function copyModel(Master) {
|
|
1825
|
+
const dataSource = this;
|
|
1826
|
+
const className = Master.modelName;
|
|
1827
|
+
const md = Master.modelBuilder.getModelDefinition(className);
|
|
1828
|
+
const Slave = function SlaveModel() {
|
|
1829
|
+
Master.apply(this, [].slice.call(arguments));
|
|
1830
|
+
};
|
|
1831
|
+
util.inherits(Slave, Master);
|
|
1832
|
+
Slave.__proto__ = Master;
|
|
1833
|
+
hiddenProperty(Slave, "dataSource", dataSource);
|
|
1834
|
+
hiddenProperty(Slave, "modelName", className);
|
|
1835
|
+
hiddenProperty(Slave, "relations", Master.relations);
|
|
1836
|
+
if (!(className in dataSource.modelBuilder.models)) {
|
|
1837
|
+
dataSource.modelBuilder.models[className] = Slave;
|
|
1838
|
+
dataSource.modelBuilder.definitions[className] = new ModelDefinition(dataSource.modelBuilder, md.name, md.properties, md.settings);
|
|
1839
|
+
if (!dataSource.isTransaction && dataSource.connector && dataSource.connector.define) dataSource.connector.define({
|
|
1840
|
+
model: Slave,
|
|
1841
|
+
properties: md.properties,
|
|
1842
|
+
settings: md.settings
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
return Slave;
|
|
1846
|
+
};
|
|
1847
|
+
/**
|
|
1848
|
+
* Run a transaction against the DataSource.
|
|
1849
|
+
*
|
|
1850
|
+
* This method can be used in different ways based on the passed arguments and
|
|
1851
|
+
* type of underlying data source:
|
|
1852
|
+
*
|
|
1853
|
+
* If no `execute()` function is provided and the underlying DataSource is a
|
|
1854
|
+
* database that supports transactions, a Promise is returned that resolves to
|
|
1855
|
+
* an EventEmitter representing the transaction once it is ready.
|
|
1856
|
+
* `transaction.models` can be used to receive versions of the DataSource's
|
|
1857
|
+
* model classes which are bound to the created transaction, so that all their
|
|
1858
|
+
* database methods automatically use the transaction. At the end of all
|
|
1859
|
+
* database transactions, `transaction.commit()` can be called to commit the
|
|
1860
|
+
* transactions, or `transaction.rollback()` to roll them back.
|
|
1861
|
+
*
|
|
1862
|
+
* If no `execute()` function is provided on a transient or memory DataSource,
|
|
1863
|
+
* the EventEmitter representing the transaction is returned immediately. For
|
|
1864
|
+
* backward compatibility, this object also supports `transaction.exec()`
|
|
1865
|
+
* instead of `transaction.commit()`, and calling `transaction.rollback()` is
|
|
1866
|
+
* not required on such transient and memory DataSource instances.
|
|
1867
|
+
*
|
|
1868
|
+
* If an `execute()` function is provided, then it is called as soon as the
|
|
1869
|
+
* transaction is ready, receiving `transaction.models` as its first
|
|
1870
|
+
* argument. `transaction.commit()` and `transaction.rollback()` are then
|
|
1871
|
+
* automatically called at the end of `execute()`, based on whether exceptions
|
|
1872
|
+
* happen during execution or not. If no callback is provided to be called at
|
|
1873
|
+
* the end of the execution, a Promise object is returned that is resolved or
|
|
1874
|
+
* rejected as soon as the execution is completed, and the transaction is
|
|
1875
|
+
* committed or rolled back.
|
|
1876
|
+
*
|
|
1877
|
+
* @param {Function} execute The execute function, called with (models). Note
|
|
1878
|
+
* that the instances in `models` are bound to the created transaction, and
|
|
1879
|
+
* are therefore not identical with the models in `app.models`, but slaves
|
|
1880
|
+
* thereof (optional).
|
|
1881
|
+
* @options {Object} options The options to be passed to `beginTransaction()`
|
|
1882
|
+
* when creating the transaction on database sources (optional).
|
|
1883
|
+
* @param {Function} cb Callback called with (err)
|
|
1884
|
+
* @returns {Promise | EventEmitter}
|
|
1885
|
+
*/
|
|
1886
|
+
DataSource.prototype.transaction = function(execute, options, cb) {
|
|
1887
|
+
if (cb === void 0 && typeof options === "function") {
|
|
1888
|
+
cb = options;
|
|
1889
|
+
options = {};
|
|
1890
|
+
} else options = options || {};
|
|
1891
|
+
const dataSource = this;
|
|
1892
|
+
const transaction = new EventEmitter();
|
|
1893
|
+
for (const p in dataSource) transaction[p] = dataSource[p];
|
|
1894
|
+
transaction.isTransaction = true;
|
|
1895
|
+
transaction.origin = dataSource;
|
|
1896
|
+
transaction.connected = false;
|
|
1897
|
+
transaction.connecting = false;
|
|
1898
|
+
transaction.transaction = function() {
|
|
1899
|
+
throw new Error(g.f("Nesting transactions is not supported"));
|
|
1900
|
+
};
|
|
1901
|
+
const modelBuilder = new ModelBuilder();
|
|
1902
|
+
const slaveModels = modelBuilder.models;
|
|
1903
|
+
transaction.modelBuilder = modelBuilder;
|
|
1904
|
+
transaction.models = slaveModels;
|
|
1905
|
+
transaction.definitions = modelBuilder.definitions;
|
|
1906
|
+
const masterModels = dataSource.modelBuilder.models;
|
|
1907
|
+
Object.keys(masterModels).forEach(function(name) {
|
|
1908
|
+
Object.defineProperty(slaveModels, name, {
|
|
1909
|
+
enumerable: true,
|
|
1910
|
+
configurable: true,
|
|
1911
|
+
get: function() {
|
|
1912
|
+
delete slaveModels[name];
|
|
1913
|
+
return dataSource.copyModel.call(transaction, masterModels[name]);
|
|
1914
|
+
}
|
|
1915
|
+
});
|
|
1916
|
+
});
|
|
1917
|
+
let done = function(err) {
|
|
1918
|
+
if (err) transaction.rollback(function(error) {
|
|
1919
|
+
cb(err || error);
|
|
1920
|
+
});
|
|
1921
|
+
else transaction.commit(cb);
|
|
1922
|
+
done = function() {};
|
|
1923
|
+
};
|
|
1924
|
+
function handleExecute() {
|
|
1925
|
+
if (execute) {
|
|
1926
|
+
cb = cb || utils.createPromiseCallback();
|
|
1927
|
+
try {
|
|
1928
|
+
const result = execute(slaveModels, done);
|
|
1929
|
+
if (result && typeof result.then === "function") result.then(function() {
|
|
1930
|
+
done();
|
|
1931
|
+
}, done);
|
|
1932
|
+
} catch (err) {
|
|
1933
|
+
done(err);
|
|
1934
|
+
}
|
|
1935
|
+
return cb.promise;
|
|
1936
|
+
} else if (cb) cb(null, transaction);
|
|
1937
|
+
else return transaction;
|
|
1938
|
+
}
|
|
1939
|
+
function transactionCreated(err, tx) {
|
|
1940
|
+
if (err) cb(err);
|
|
1941
|
+
else {
|
|
1942
|
+
transaction.currentTransaction = tx;
|
|
1943
|
+
if (!tx.observe && tx.connector) tx = tx.connector;
|
|
1944
|
+
tx.observe("timeout", function(context, next) {
|
|
1945
|
+
const err = new Error(g.f("Transaction is rolled back due to timeout"));
|
|
1946
|
+
err.code = "TRANSACTION_TIMEOUT";
|
|
1947
|
+
next(err);
|
|
1948
|
+
done(err);
|
|
1949
|
+
});
|
|
1950
|
+
handleExecute();
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
function ensureTransaction(transaction, cb) {
|
|
1954
|
+
if (!transaction) process.nextTick(function() {
|
|
1955
|
+
cb(new Error(g.f("Transaction is not ready, wait for the returned promise to resolve")));
|
|
1956
|
+
});
|
|
1957
|
+
return transaction;
|
|
1958
|
+
}
|
|
1959
|
+
const connector = dataSource.connector;
|
|
1960
|
+
if (connector.transaction) {
|
|
1961
|
+
transaction.connector = connector.transaction();
|
|
1962
|
+
transaction.commit = transaction.exec = function(cb) {
|
|
1963
|
+
this.connector.exec(cb);
|
|
1964
|
+
};
|
|
1965
|
+
transaction.rollback = function(cb) {
|
|
1966
|
+
cb();
|
|
1967
|
+
};
|
|
1968
|
+
return handleExecute();
|
|
1969
|
+
} else if (connector.beginTransaction) {
|
|
1970
|
+
transaction.exec = transaction.commit = function(cb) {
|
|
1971
|
+
ensureTransaction(this.currentTransaction, cb).commit(cb);
|
|
1972
|
+
};
|
|
1973
|
+
transaction.rollback = function(cb) {
|
|
1974
|
+
ensureTransaction(this.currentTransaction, cb).rollback(cb);
|
|
1975
|
+
};
|
|
1976
|
+
cb = cb || utils.createPromiseCallback();
|
|
1977
|
+
Transaction.begin(connector, options, transactionCreated);
|
|
1978
|
+
return cb.promise;
|
|
1979
|
+
} else throw new Error(g.f("DataSource does not support transactions"));
|
|
1980
|
+
};
|
|
1981
|
+
/**
|
|
1982
|
+
* Enable remote access to a data source operation. Each [connector](#connector) has its own set of set
|
|
1983
|
+
* remotely enabled and disabled operations. To list the operations, call `dataSource.operations()`.
|
|
1984
|
+
* @param {String} operation The operation name
|
|
1985
|
+
*/
|
|
1986
|
+
DataSource.prototype.enableRemote = function(operation) {
|
|
1987
|
+
const op = this.getOperation(operation);
|
|
1988
|
+
if (op) op.remoteEnabled = true;
|
|
1989
|
+
else throw new Error(g.f("%s is not provided by the attached connector", operation));
|
|
1990
|
+
};
|
|
1991
|
+
/**
|
|
1992
|
+
* Disable remote access to a data source operation. Each [connector](#connector) has its own set of set enabled
|
|
1993
|
+
* and disabled operations. To list the operations, call `dataSource.operations()`.
|
|
1994
|
+
*
|
|
1995
|
+
*```js
|
|
1996
|
+
* var oracle = loopback.createDataSource({
|
|
1997
|
+
* connector: require('loopback-connector-oracle'),
|
|
1998
|
+
* host: '...',
|
|
1999
|
+
* ...
|
|
2000
|
+
* });
|
|
2001
|
+
* oracle.disableRemote('destroyAll');
|
|
2002
|
+
* ```
|
|
2003
|
+
* **Notes:**
|
|
2004
|
+
*
|
|
2005
|
+
* - Disabled operations will not be added to attached models.
|
|
2006
|
+
* - Disabling the remoting for a method only affects client access (it will still be available from server models).
|
|
2007
|
+
* - Data sources must enable / disable operations before attaching or creating models.
|
|
2008
|
+
* @param {String} operation The operation name
|
|
2009
|
+
*/
|
|
2010
|
+
DataSource.prototype.disableRemote = function(operation) {
|
|
2011
|
+
const op = this.getOperation(operation);
|
|
2012
|
+
if (op) op.remoteEnabled = false;
|
|
2013
|
+
else throw new Error(g.f("%s is not provided by the attached connector", operation));
|
|
2014
|
+
};
|
|
2015
|
+
/**
|
|
2016
|
+
* Get an operation's metadata.
|
|
2017
|
+
* @param {String} operation The operation name
|
|
2018
|
+
*/
|
|
2019
|
+
DataSource.prototype.getOperation = function(operation) {
|
|
2020
|
+
const ops = this.operations();
|
|
2021
|
+
const opKeys = Object.keys(ops);
|
|
2022
|
+
for (let i = 0; i < opKeys.length; i++) {
|
|
2023
|
+
const op = ops[opKeys[i]];
|
|
2024
|
+
if (op.name === operation) return op;
|
|
2025
|
+
}
|
|
2026
|
+
};
|
|
2027
|
+
/**
|
|
2028
|
+
* Return JSON object describing all operations.
|
|
2029
|
+
*
|
|
2030
|
+
* Example return value:
|
|
2031
|
+
* ```js
|
|
2032
|
+
* {
|
|
2033
|
+
* find: {
|
|
2034
|
+
* remoteEnabled: true,
|
|
2035
|
+
* accepts: [...],
|
|
2036
|
+
* returns: [...]
|
|
2037
|
+
* enabled: true
|
|
2038
|
+
* },
|
|
2039
|
+
* save: {
|
|
2040
|
+
* remoteEnabled: true,
|
|
2041
|
+
* prototype: true,
|
|
2042
|
+
* accepts: [...],
|
|
2043
|
+
* returns: [...],
|
|
2044
|
+
* enabled: true
|
|
2045
|
+
* },
|
|
2046
|
+
* ...
|
|
2047
|
+
* }
|
|
2048
|
+
* ```
|
|
2049
|
+
*/
|
|
2050
|
+
DataSource.prototype.operations = function() {
|
|
2051
|
+
return this._operations;
|
|
2052
|
+
};
|
|
2053
|
+
/**
|
|
2054
|
+
* Define an operation to the data source
|
|
2055
|
+
* @param {String} name The operation name
|
|
2056
|
+
* @param {Object} options The options
|
|
2057
|
+
* @param {Function} fn The function
|
|
2058
|
+
*/
|
|
2059
|
+
DataSource.prototype.defineOperation = function(name, options, fn) {
|
|
2060
|
+
options.fn = fn;
|
|
2061
|
+
options.name = name;
|
|
2062
|
+
this._operations[name] = options;
|
|
2063
|
+
};
|
|
2064
|
+
/**
|
|
2065
|
+
* Check if the backend is a relational DB
|
|
2066
|
+
* @returns {Boolean}
|
|
2067
|
+
*/
|
|
2068
|
+
DataSource.prototype.isRelational = function() {
|
|
2069
|
+
return this.connector && this.connector.relational;
|
|
2070
|
+
};
|
|
2071
|
+
/**
|
|
2072
|
+
* Check if the data source is connected. If not, the method invocation will be
|
|
2073
|
+
* deferred and queued.
|
|
2074
|
+
*
|
|
2075
|
+
*
|
|
2076
|
+
* @param {Object} obj Receiver for the method call
|
|
2077
|
+
* @param {Object} args Arguments passing to the method call
|
|
2078
|
+
* @returns - a Boolean value to indicate if the method invocation is deferred.
|
|
2079
|
+
* false: The datasource is already connected
|
|
2080
|
+
* - true: The datasource is yet to be connected
|
|
2081
|
+
*/
|
|
2082
|
+
DataSource.prototype.queueInvocation = DataSource.prototype.ready = function(obj, args) {
|
|
2083
|
+
const self = this;
|
|
2084
|
+
debug("Datasource %s: connected=%s connecting=%s", this.name, this.connected, this.connecting);
|
|
2085
|
+
if (this.connected) return false;
|
|
2086
|
+
this._queuedInvocations++;
|
|
2087
|
+
const method = args.callee;
|
|
2088
|
+
let onConnected = null, onError = null, timeoutHandle = null;
|
|
2089
|
+
onConnected = function() {
|
|
2090
|
+
debug("Datasource %s is now connected - executing method %s", self.name, method.name);
|
|
2091
|
+
this._queuedInvocations--;
|
|
2092
|
+
self.removeListener("error", onError);
|
|
2093
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
2094
|
+
const params = [].slice.call(args);
|
|
2095
|
+
try {
|
|
2096
|
+
method.apply(obj, params);
|
|
2097
|
+
} catch (err) {
|
|
2098
|
+
const cb = params.pop();
|
|
2099
|
+
if (typeof cb === "function") process.nextTick(function() {
|
|
2100
|
+
cb(err);
|
|
2101
|
+
});
|
|
2102
|
+
else throw err;
|
|
2103
|
+
}
|
|
2104
|
+
};
|
|
2105
|
+
onError = function(err) {
|
|
2106
|
+
debug("Datasource %s fails to connect - aborting method %s", self.name, method.name);
|
|
2107
|
+
this._queuedInvocations--;
|
|
2108
|
+
self.removeListener("connected", onConnected);
|
|
2109
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
2110
|
+
const cb = [].slice.call(args).pop();
|
|
2111
|
+
if (typeof cb === "function") process.nextTick(function() {
|
|
2112
|
+
cb(err);
|
|
2113
|
+
});
|
|
2114
|
+
};
|
|
2115
|
+
this.once("connected", onConnected);
|
|
2116
|
+
this.once("error", onError);
|
|
2117
|
+
const timeout = this.settings.connectionTimeout || 5e3;
|
|
2118
|
+
timeoutHandle = setTimeout(function() {
|
|
2119
|
+
debug("Datasource %s fails to connect due to timeout - aborting method %s", self.name, method.name);
|
|
2120
|
+
this._queuedInvocations--;
|
|
2121
|
+
self.connecting = false;
|
|
2122
|
+
self.removeListener("error", onError);
|
|
2123
|
+
self.removeListener("connected", onConnected);
|
|
2124
|
+
const cb = [].slice.call(args).pop();
|
|
2125
|
+
if (typeof cb === "function") cb(new Error(g.f("Timeout in connecting after %s ms", timeout)));
|
|
2126
|
+
}, timeout);
|
|
2127
|
+
if (!this.connecting) {
|
|
2128
|
+
debug("Connecting datasource %s to connector %s", this.name, this.connector.name);
|
|
2129
|
+
this.connect(() => {});
|
|
2130
|
+
}
|
|
2131
|
+
return true;
|
|
2132
|
+
};
|
|
2133
|
+
/**
|
|
2134
|
+
* Ping the underlying connector to test the connections
|
|
2135
|
+
* @param {Function} [cb] Callback function
|
|
2136
|
+
*/
|
|
2137
|
+
DataSource.prototype.ping = function(cb) {
|
|
2138
|
+
cb = cb || utils.createPromiseCallback();
|
|
2139
|
+
const self = this;
|
|
2140
|
+
if (self.connector.ping) this.connector.ping(cb);
|
|
2141
|
+
else if (self.connector.discoverModelProperties) self.discoverModelProperties("dummy", {}, cb);
|
|
2142
|
+
else process.nextTick(function() {
|
|
2143
|
+
const err = self.connected ? null : new Error(g.f("Not connected"));
|
|
2144
|
+
cb(err);
|
|
2145
|
+
});
|
|
2146
|
+
return cb.promise;
|
|
2147
|
+
};
|
|
2148
|
+
/**
|
|
2149
|
+
* Execute an arbitrary command. The commands are connector specific,
|
|
2150
|
+
* please refer to the documentation of your connector for more details.
|
|
2151
|
+
*
|
|
2152
|
+
* @param [...params] Array The command and its arguments, e.g. an SQL query.
|
|
2153
|
+
* @returns Promise A promise of the result
|
|
2154
|
+
*/
|
|
2155
|
+
DataSource.prototype.execute = function(...params) {
|
|
2156
|
+
if (!this.connector) return Promise.reject(errorNotImplemented(`DataSource "${this.name}" is missing a connector to execute the command.`));
|
|
2157
|
+
if (!this.connector.execute) return Promise.reject(new errorNotImplemented(`The connector "${this.connector.name}" used by dataSource "${this.name}" does not implement "execute()" API.`));
|
|
2158
|
+
return new Promise((resolve, reject) => {
|
|
2159
|
+
this.connector.execute(...params, onExecuted);
|
|
2160
|
+
function onExecuted(err, result) {
|
|
2161
|
+
if (err) return reject(err);
|
|
2162
|
+
if (arguments.length > 2) result = Array.prototype.slice.call(arguments, 1);
|
|
2163
|
+
resolve(result);
|
|
2164
|
+
}
|
|
2165
|
+
});
|
|
2166
|
+
function errorNotImplemented(msg) {
|
|
2167
|
+
const err = new Error(msg);
|
|
2168
|
+
err.code = "NOT_IMPLEMENTED";
|
|
2169
|
+
return err;
|
|
2170
|
+
}
|
|
2171
|
+
};
|
|
2172
|
+
/**
|
|
2173
|
+
* Begin a new Transaction.
|
|
2174
|
+
*
|
|
2175
|
+
*
|
|
2176
|
+
* @param [options] Options {isolationLevel: '...', timeout: 1000}
|
|
2177
|
+
* @returns Promise A promise which resolves to a Transaction object
|
|
2178
|
+
*/
|
|
2179
|
+
DataSource.prototype.beginTransaction = function(options) {
|
|
2180
|
+
return Transaction.begin(this.connector, options);
|
|
2181
|
+
};
|
|
2182
|
+
/**
|
|
2183
|
+
* Get the maximum number of event listeners
|
|
2184
|
+
*/
|
|
2185
|
+
DataSource.prototype.getMaxOfflineRequests = function() {
|
|
2186
|
+
let maxOfflineRequests = DataSource.DEFAULT_MAX_OFFLINE_REQUESTS;
|
|
2187
|
+
if (this.settings && this.settings.maxOfflineRequests) {
|
|
2188
|
+
if (typeof this.settings.maxOfflineRequests !== "number") throw new Error("maxOfflineRequests must be a number");
|
|
2189
|
+
maxOfflineRequests = this.settings.maxOfflineRequests;
|
|
2190
|
+
}
|
|
2191
|
+
return maxOfflineRequests;
|
|
2192
|
+
};
|
|
2193
|
+
/*! The hidden property call is too expensive so it is not used that much
|
|
2194
|
+
*/
|
|
2195
|
+
/**
|
|
2196
|
+
* Define a hidden property
|
|
2197
|
+
* It is an utility to define a property to the Object with info flags
|
|
2198
|
+
* @param {Object} obj The property owner
|
|
2199
|
+
* @param {String} key The property name
|
|
2200
|
+
* @param {Mixed} value The default value
|
|
2201
|
+
*/
|
|
2202
|
+
function hiddenProperty(obj, key, value) {
|
|
2203
|
+
Object.defineProperty(obj, key, {
|
|
2204
|
+
writable: false,
|
|
2205
|
+
enumerable: false,
|
|
2206
|
+
configurable: false,
|
|
2207
|
+
value
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
DataSource.Text = ModelBuilder.Text;
|
|
2211
|
+
DataSource.JSON = ModelBuilder.JSON;
|
|
2212
|
+
DataSource.Any = ModelBuilder.Any;
|
|
2213
|
+
}));
|
|
2214
|
+
//#endregion
|
|
2215
|
+
module.exports = require_datasource();
|