@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.
Files changed (63) hide show
  1. package/LICENSE +25 -0
  2. package/NOTICE +23 -0
  3. package/README.md +74 -0
  4. package/dist/_virtual/_rolldown/runtime.js +4 -0
  5. package/dist/index.js +26 -0
  6. package/dist/lib/browser.depd.js +13 -0
  7. package/dist/lib/case-utils.js +21 -0
  8. package/dist/lib/connectors/kv-memory.js +158 -0
  9. package/dist/lib/connectors/memory.js +810 -0
  10. package/dist/lib/connectors/transient.js +126 -0
  11. package/dist/lib/dao.js +2445 -0
  12. package/dist/lib/datasource.js +2215 -0
  13. package/dist/lib/date-string.js +87 -0
  14. package/dist/lib/geo.js +244 -0
  15. package/dist/lib/globalize.js +33 -0
  16. package/dist/lib/hooks.js +79 -0
  17. package/dist/lib/id-utils.js +66 -0
  18. package/dist/lib/include.js +795 -0
  19. package/dist/lib/include_utils.js +104 -0
  20. package/dist/lib/introspection.js +37 -0
  21. package/dist/lib/jutil.js +65 -0
  22. package/dist/lib/kvao/delete-all.js +57 -0
  23. package/dist/lib/kvao/delete.js +43 -0
  24. package/dist/lib/kvao/expire.js +35 -0
  25. package/dist/lib/kvao/get.js +34 -0
  26. package/dist/lib/kvao/index.js +28 -0
  27. package/dist/lib/kvao/iterate-keys.js +38 -0
  28. package/dist/lib/kvao/keys.js +55 -0
  29. package/dist/lib/kvao/set.js +39 -0
  30. package/dist/lib/kvao/ttl.js +35 -0
  31. package/dist/lib/list.js +101 -0
  32. package/dist/lib/mixins.js +58 -0
  33. package/dist/lib/model-builder.js +608 -0
  34. package/dist/lib/model-definition.js +231 -0
  35. package/dist/lib/model-utils.js +368 -0
  36. package/dist/lib/model.js +586 -0
  37. package/dist/lib/observer.js +235 -0
  38. package/dist/lib/relation-definition.js +2604 -0
  39. package/dist/lib/relations.js +587 -0
  40. package/dist/lib/scope.js +392 -0
  41. package/dist/lib/transaction.js +183 -0
  42. package/dist/lib/types.js +58 -0
  43. package/dist/lib/utils.js +625 -0
  44. package/dist/lib/validations.js +742 -0
  45. package/dist/package.js +93 -0
  46. package/package.json +85 -0
  47. package/types/common.d.ts +28 -0
  48. package/types/connector.d.ts +52 -0
  49. package/types/datasource.d.ts +324 -0
  50. package/types/date-string.d.ts +21 -0
  51. package/types/inclusion-mixin.d.ts +44 -0
  52. package/types/index.d.ts +36 -0
  53. package/types/kv-model.d.ts +201 -0
  54. package/types/model.d.ts +368 -0
  55. package/types/observer-mixin.d.ts +174 -0
  56. package/types/persisted-model.d.ts +505 -0
  57. package/types/query.d.ts +108 -0
  58. package/types/relation-mixin.d.ts +577 -0
  59. package/types/relation.d.ts +301 -0
  60. package/types/scope.d.ts +92 -0
  61. package/types/transaction-mixin.d.ts +47 -0
  62. package/types/types.d.ts +65 -0
  63. 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();