waterline.js 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016
  1. // ██╗ ██╗ █████╗ ████████╗███████╗██████╗ ██╗ ██╗███╗ ██╗███████╗
  2. // ██║ ██║██╔══██╗╚══██╔══╝██╔════╝██╔══██╗██║ ██║████╗ ██║██╔════╝
  3. // ██║ █╗ ██║███████║ ██║ █████╗ ██████╔╝██║ ██║██╔██╗ ██║█████╗
  4. // ██║███╗██║██╔══██║ ██║ ██╔══╝ ██╔══██╗██║ ██║██║╚██╗██║██╔══╝
  5. // ╚███╔███╔╝██║ ██║ ██║ ███████╗██║ ██║███████╗██║██║ ╚████║███████╗
  6. // ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚═╝╚═╝ ╚═══╝╚══════╝
  7. //
  8. var assert = require('assert');
  9. var util = require('util');
  10. var _ = require('@sailshq/lodash');
  11. var async = require('async');
  12. // var EA = require('encrypted-attr'); « this is required below for node compat.
  13. var flaverr = require('flaverr');
  14. var Schema = require('waterline-schema');
  15. var buildDatastoreMap = require('./waterline/utils/system/datastore-builder');
  16. var buildLiveWLModel = require('./waterline/utils/system/collection-builder');
  17. var BaseMetaModel = require('./waterline/MetaModel');
  18. var getModel = require('./waterline/utils/ontology/get-model');
  19. /**
  20. * ORM (Waterline)
  21. *
  22. * Construct a Waterline ORM instance.
  23. *
  24. * @constructs {Waterline}
  25. */
  26. function Waterline() {
  27. // Start by setting up an array of model definitions.
  28. // (This will hold the raw model definitions that were passed in,
  29. // plus any implicitly introduced models-- but that part comes later)
  30. //
  31. // > `wmd` stands for "weird intermediate model def thing".
  32. // - - - - - - - - - - - - - - - - - - - - - - - -
  33. // FUTURE: make this whole wmd thing less weird.
  34. // - - - - - - - - - - - - - - - - - - - - - - - -
  35. var wmds = [];
  36. // Hold a map of the instantaited and active datastores and models.
  37. var modelMap = {};
  38. var datastoreMap = {};
  39. // This "context" dictionary will be passed into the BaseMetaModel constructor
  40. // later every time we instantiate a new BaseMetaModel instance (e.g. `User`
  41. // or `Pet` or generically, sometimes called "WLModel" -- sorry about the
  42. // capital letters!!)
  43. //
  44. var context = {
  45. collections: modelMap,
  46. datastores: datastoreMap
  47. };
  48. // ^^FUTURE: Level this out (This is currently just a stop gap to prevent
  49. // re-writing all the "collection query" stuff.)
  50. // Now build an ORM instance.
  51. var orm = {};
  52. // ┌─┐─┐ ┬┌─┐┌─┐┌─┐┌─┐ ┌─┐┬─┐┌┬┐ ╦═╗╔═╗╔═╗╦╔═╗╔╦╗╔═╗╦═╗╔╦╗╔═╗╔╦╗╔═╗╦
  53. // ├┤ ┌┴┬┘├─┘│ │└─┐├┤ │ │├┬┘│││ ╠╦╝║╣ ║ ╦║╚═╗ ║ ║╣ ╠╦╝║║║║ ║ ║║║╣ ║
  54. // └─┘┴ └─┴ └─┘└─┘└─┘ └─┘┴└─┴ ┴o╩╚═╚═╝╚═╝╩╚═╝ ╩ ╚═╝╩╚═╩ ╩╚═╝═╩╝╚═╝╩═╝
  55. /**
  56. * .registerModel()
  57. *
  58. * Register a "weird intermediate model definition thing". (see above)
  59. *
  60. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  61. * FUTURE: Deprecate support for this method in favor of simplified `Waterline.start()`
  62. * (see bottom of this file). In WL 1.0, remove this method altogether.
  63. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  64. *
  65. * @param {Dictionary} wmd
  66. */
  67. orm.registerModel = function registerModel(wmd) {
  68. wmds.push(wmd);
  69. };
  70. // Alias for backwards compatibility:
  71. orm.loadCollection = function heyThatsDeprecated(){
  72. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  73. // FUTURE: Change this alias method so that it throws an error in WL 0.14.
  74. // (And in WL 1.0, just remove it altogether.)
  75. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  76. console.warn('\n'+
  77. 'Warning: As of Waterline 0.13, `loadCollection()` is now `registerModel()`. Please call that instead.\n'+
  78. 'I get what you mean, so I temporarily renamed it for you this time, but here is a stack trace\n'+
  79. 'so you know where this is coming from in the code, and can change it to prevent future warnings:\n'+
  80. '```\n'+
  81. (new Error()).stack+'\n'+
  82. '```\n'
  83. );
  84. orm.registerModel.apply(orm, Array.prototype.slice.call(arguments));
  85. };
  86. // ┌─┐─┐ ┬┌─┐┌─┐┌─┐┌─┐ ┌─┐┬─┐┌┬┐ ╦╔╗╔╦╔╦╗╦╔═╗╦ ╦╔═╗╔═╗
  87. // ├┤ ┌┴┬┘├─┘│ │└─┐├┤ │ │├┬┘│││ ║║║║║ ║ ║╠═╣║ ║╔═╝║╣
  88. // └─┘┴ └─┴ └─┘└─┘└─┘ └─┘┴└─┴ ┴o╩╝╚╝╩ ╩ ╩╩ ╩╩═╝╩╚═╝╚═╝
  89. /**
  90. * .initialize()
  91. *
  92. * Start the ORM and set up active datastores.
  93. *
  94. * @param {Dictionary} options
  95. * @param {Function} done
  96. */
  97. orm.initialize = function initialize(options, done) {
  98. try {
  99. // First, verify traditional settings, check compat.:
  100. // =============================================================================================
  101. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  102. // FUTURE: In WL 0.14, deprecate support for this method in favor of the simplified
  103. // `Waterline.start()` (see bottom of this file). In WL 1.0, remove it altogether.
  104. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  105. // Ensure the ORM hasn't already been initialized.
  106. // (This prevents all sorts of issues, because model definitions are modified in-place.)
  107. if (_.keys(modelMap).length > 0) {
  108. throw new Error('A Waterline ORM instance cannot be initialized more than once. To reset the ORM, create a new instance of it by running `new Waterline()`.');
  109. }
  110. // Backwards-compatibility for `connections`:
  111. if (!_.isUndefined(options.connections)){
  112. // Sanity check
  113. assert(_.isUndefined(options.datastores), 'Attempted to provide backwards-compatibility for `connections`, but `datastores` was ALSO defined! This should never happen.');
  114. options.datastores = options.connections;
  115. console.warn('\n'+
  116. 'Warning: `connections` is no longer supported. Please use `datastores` instead.\n'+
  117. 'I get what you mean, so I temporarily renamed it for you this time, but here is a stack trace\n'+
  118. 'so you know where this is coming from in the code, and can change it to prevent future warnings:\n'+
  119. '```\n'+
  120. (new Error()).stack+'\n'+
  121. '```\n'
  122. );
  123. delete options.connections;
  124. }//>-
  125. // Usage assertions
  126. if (_.isUndefined(options) || !_.keys(options).length) {
  127. throw new Error('Usage Error: .initialize(options, callback)');
  128. }
  129. if (_.isUndefined(options.adapters) || !_.isPlainObject(options.adapters)) {
  130. throw new Error('Options must contain an `adapters` dictionary');
  131. }
  132. if (_.isUndefined(options.datastores) || !_.isPlainObject(options.datastores)) {
  133. throw new Error('Options must contain a `datastores` dictionary');
  134. }
  135. // - - - - - - - - - - - - - - - - - - - - -
  136. // FUTURE: anchor ruleset checks
  137. // - - - - - - - - - - - - - - - - - - - - -
  138. // Next, validate ORM settings related to at-rest encryption, if it is in use.
  139. // =============================================================================================
  140. var areAnyModelsUsingAtRestEncryption;
  141. _.each(wmds, function(wmd){
  142. _.each(wmd.prototype.attributes, function(attrDef){
  143. if (attrDef.encrypt !== undefined) {
  144. areAnyModelsUsingAtRestEncryption = true;
  145. }
  146. });//∞
  147. });//∞
  148. // Only allow using at-rest encryption for compatible Node versions
  149. var EA;
  150. if (areAnyModelsUsingAtRestEncryption) {
  151. var RX_NODE_MAJOR_DOT_MINOR = /^v([^.]+\.?[^.]+)\./;
  152. var parsedNodeMajorAndMinorVersion = process.version.match(RX_NODE_MAJOR_DOT_MINOR) && (+(process.version.match(RX_NODE_MAJOR_DOT_MINOR)[1]));
  153. var MIN_NODE_VERSION = 6;
  154. var isNativeCryptoFullyCapable = parsedNodeMajorAndMinorVersion >= MIN_NODE_VERSION;
  155. if (!isNativeCryptoFullyCapable) {
  156. throw new Error('Current installed node version\'s native `crypto` module is not fully capable of the necessary functionality for encrypting/decrypting data at rest with Waterline. To use this feature, please upgrade to Node v' + MIN_NODE_VERSION + ' or above, flush your node_modules, run npm install, and then try again. Otherwise, if you cannot upgrade Node.js, please remove the `encrypt` property from your models\' attributes.');
  157. }
  158. EA = require('encrypted-attr');
  159. }//fi
  160. _.each(wmds, function(wmd){
  161. var modelDef = wmd.prototype;
  162. // Verify that `encrypt` attr prop is valid, if in use.
  163. var isThisModelUsingAtRestEncryption;
  164. try {
  165. _.each(modelDef.attributes, function(attrDef, attrName){
  166. if (attrDef.encrypt !== undefined) {
  167. if (!_.isBoolean(attrDef.encrypt)){
  168. throw flaverr({
  169. code: 'E_INVALID_ENCRYPT',
  170. attrName: attrName,
  171. message: 'If set, `encrypt` must be either `true` or `false`.'
  172. });
  173. }//•
  174. if (attrDef.encrypt === true){
  175. isThisModelUsingAtRestEncryption = true;
  176. if (attrDef.type === 'ref') {
  177. throw flaverr({
  178. code: 'E_ATTR_NOT_COMPATIBLE_WITH_AT_REST_ENCRYPTION',
  179. attrName: attrName,
  180. whyNotCompatible: 'with `type: \'ref\'` attributes.'
  181. });
  182. }//•
  183. if (attrDef.autoCreatedAt || attrDef.autoUpdatedAt) {
  184. throw flaverr({
  185. code: 'E_ATTR_NOT_COMPATIBLE_WITH_AT_REST_ENCRYPTION',
  186. attrName: attrName,
  187. whyNotCompatible: 'with `'+(attrDef.autoCreatedAt?'autoCreatedAt':'autoUpdatedAt')+'` attributes.'
  188. });
  189. }//•
  190. if (attrDef.model || attrDef.collection) {
  191. throw flaverr({
  192. code: 'E_ATTR_NOT_COMPATIBLE_WITH_AT_REST_ENCRYPTION',
  193. attrName: attrName,
  194. whyNotCompatible: 'with associations.'
  195. });
  196. }//•
  197. if (attrDef.defaultsTo !== undefined) {
  198. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  199. // FUTURE: Consider adding support for this. Will require some refactoring
  200. // in order to do it right (i.e. otherwise we'll just be copying and pasting
  201. // the encryption logic.) We'll want to pull it out from normalize-value-to-set
  202. // into a new utility, then call that from the appropriate spot in
  203. // normalize-new-record in order to encrypt the initial default value.
  204. //
  205. // (See also the other note in normalize-new-record re defaultsTo + cloneDeep.)
  206. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  207. throw flaverr({
  208. code: 'E_ATTR_NOT_COMPATIBLE_WITH_AT_REST_ENCRYPTION',
  209. attrName: attrName,
  210. whyNotCompatible: 'with an attribute that also specifies a `defaultsTo`. '+
  211. 'Please remove the `defaultsTo` from this attribute definition.'
  212. });
  213. }//•
  214. }//fi
  215. }//fi
  216. });//∞
  217. } catch (err) {
  218. switch (err.code) {
  219. case 'E_INVALID_ENCRYPT':
  220. throw flaverr({
  221. message:
  222. 'Invalid usage of `encrypt` in the definition for `'+modelDef.identity+'` model\'s '+
  223. '`'+err.attrName+'` attribute. '+err.message
  224. }, err);
  225. case 'E_ATTR_NOT_COMPATIBLE_WITH_AT_REST_ENCRYPTION':
  226. throw flaverr({
  227. message:
  228. 'Invalid usage of `encrypt` in the definition for `'+modelDef.identity+'` model\'s '+
  229. '`'+err.attrName+'` attribute. At-rest encryption (`encrypt: true`) cannot be used '+
  230. err.whyNotCompatible
  231. }, err);
  232. default: throw err;
  233. }
  234. }
  235. // Verify `dataEncryptionKeys`.
  236. // (Remember, if there is a secondary key system in use, these DEKs should have
  237. // already been "unwrapped" before they were passed in to Waterline as model settings.)
  238. if (modelDef.dataEncryptionKeys !== undefined) {
  239. if (!_.isObject(modelDef.dataEncryptionKeys) || _.isArray(modelDef.dataEncryptionKeys) || _.isFunction(modelDef.dataEncryptionKeys)) {
  240. throw flaverr({
  241. message: 'In the definition for the `'+modelDef.identity+'` model, the `dataEncryptionKeys` model setting '+
  242. 'is invalid. If specified, `dataEncryptionKeys` must be a dictionary (plain JavaScript object).'
  243. });
  244. }//•
  245. // Check all DEKs for validity.
  246. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  247. // (FUTURE: maybe extend EA to support a `validateKeys()` method instead of this--
  248. // or at least to have error code)
  249. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  250. try {
  251. _.each(modelDef.dataEncryptionKeys, function(dek, dekId){
  252. if (!dek || !_.isString(dek)) {
  253. throw flaverr({
  254. code: 'E_INVALID_DATA_ENCRYPTION_KEYS',
  255. dekId: dekId,
  256. message: 'Must be a cryptographically random, 32 byte string.'
  257. });
  258. }//•
  259. if (!dekId.match(/^[a-z\$]([a-z0-9])*$/i)){
  260. throw flaverr({
  261. code: 'E_INVALID_DATA_ENCRYPTION_KEYS',
  262. dekId: dekId,
  263. message: 'Please make sure the ids of all of your data encryption keys begin with a letter and do not contain any special characters.'
  264. });
  265. }//•
  266. if (areAnyModelsUsingAtRestEncryption) {
  267. try {
  268. EA(undefined, { keys: modelDef.dataEncryptionKeys, keyId: dekId }).encryptAttribute(undefined, 'test-value-purely-for-validation');
  269. } catch (err) {
  270. throw flaverr({
  271. code: 'E_INVALID_DATA_ENCRYPTION_KEYS',
  272. dekId: dekId
  273. }, err);
  274. }
  275. }
  276. });//∞
  277. } catch (err) {
  278. switch (err.code) {
  279. case 'E_INVALID_DATA_ENCRYPTION_KEYS':
  280. throw flaverr({
  281. message: 'In the definition for the `'+modelDef.identity+'` model, one of the data encryption keys (`dataEncryptionKeys.'+err.dekId+'`) is invalid.\n'+
  282. 'Details:\n'+
  283. ' '+err.message
  284. }, err);
  285. default:
  286. throw err;
  287. }
  288. }
  289. }//fi
  290. // If any attrs have `encrypt: true`, verify that there is both a valid
  291. // `dataEncryptionKeys` dictionary and a valid `dataEncryptionKeys.default` DEK set.
  292. if (isThisModelUsingAtRestEncryption) {
  293. if (!modelDef.dataEncryptionKeys || !modelDef.dataEncryptionKeys.default) {
  294. throw flaverr({
  295. message:
  296. 'DEKs should be 32 bytes long, and cryptographically random. A random, default DEK is included '+
  297. 'in new Sails apps, so one easy way to generate a new DEK is to generate a new Sails app. '+
  298. 'Alternatively, you could run:\n'+
  299. ' require(\'crypto\').randomBytes(32).toString(\'base64\')\n'+
  300. '\n'+
  301. 'Remember: once in production, you should manage your DEKs like you would any other sensitive credential. '+
  302. 'For example, one common best practice is to configure them using environment variables.\n'+
  303. 'In a Sails app:\n'+
  304. ' sails_models__dataEncryptionKeys__default=vpB2EhXaTi+wYKUE0ojI5cVQX/VRGP++Fa0bBW/NFSs=\n'+
  305. '\n'+
  306. ' [?] If you\'re unsure or want advice, head over to https://sailsjs.com/support'
  307. });
  308. }//•
  309. }//fi
  310. });//∞
  311. // Next, set up support for the default archive, and validate related settings:
  312. // =============================================================================================
  313. var DEFAULT_ARCHIVE_MODEL_IDENTITY = 'archive';
  314. // Notes for use in docs:
  315. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  316. // • To choose which datastore the Archive model will live in:
  317. //
  318. // …in top-level orm settings:
  319. // archiveModelIdentity: 'myarchive',
  320. //
  321. // …in 'MyArchive' model:
  322. // datastore: 'foo'
  323. //
  324. //
  325. // • To choose the `tableName` and `columnName`s for your Archive model:
  326. // …in top-level orm settings:
  327. // archiveModelIdentity: 'archive',
  328. //
  329. // …in 'archive' model:
  330. // tableName: 'foo',
  331. // attributes: {
  332. // originalRecord: { type: 'json', columnName: 'barbaz' },
  333. // fromModel: { type: 'string', columnName: 'bingbong' }
  334. // }
  335. //
  336. //
  337. // • To disable support for the `.archive()` model method:
  338. //
  339. // …in top-level orm settings:
  340. // archiveModelIdentity: false
  341. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  342. var archiversInfoByArchiveIdentity = {};
  343. _.each(wmds, function(wmd){
  344. var modelDef = wmd.prototype;
  345. // console.log('· checking `'+util.inspect(wmd,{depth:null})+'`…');
  346. // console.log('· checking `'+modelDef.identity+'`…');
  347. // Check the `archiveModelIdentity` model setting.
  348. if (modelDef.archiveModelIdentity === undefined) {
  349. if (modelDef.archiveModelIdentity !== modelDef.identity) {
  350. // console.log('setting default archiveModelIdentity for model `'+modelDef.identity+'`…');
  351. modelDef.archiveModelIdentity = DEFAULT_ARCHIVE_MODEL_IDENTITY;
  352. }
  353. else {
  354. // A model can't be its own archive model!
  355. modelDef.archiveModelIdentity = false;
  356. }
  357. }//fi
  358. if (modelDef.archiveModelIdentity === false) {
  359. // This will cause the .archive() method for this model to error out and explain
  360. // that the feature was explicitly disabled.
  361. }
  362. else if (modelDef.archiveModelIdentity === modelDef.identity) {
  363. return done(new Error('Invalid `archiveModelIdentity` setting. A model cannot be its own archive! But model `'+modelDef.identity+'` has `archiveModelIdentity: \''+modelDef.archiveModelIdentity+'\'`.'));
  364. }
  365. else if (!modelDef.archiveModelIdentity || !_.isString(modelDef.archiveModelIdentity)){
  366. return done(new Error('Invalid `archiveModelIdentity` setting. If set, expecting either `false` (to disable .archive() altogether) or the identity of a registered model (e.g. "archive"), but instead got: '+util.inspect(options.defaults.archiveModelIdentity,{depth:null})));
  367. }//fi
  368. // Keep track of the model identities of all archive models, as well as info about the models using them.
  369. if (modelDef.archiveModelIdentity !== false) {
  370. if (!_.contains(Object.keys(archiversInfoByArchiveIdentity), modelDef.archiveModelIdentity)) {
  371. // Save an initial info dictionary:
  372. archiversInfoByArchiveIdentity[modelDef.archiveModelIdentity] = {
  373. archivers: []
  374. };
  375. }//fi
  376. archiversInfoByArchiveIdentity[modelDef.archiveModelIdentity].archivers.push(modelDef);
  377. }//fi
  378. });//∞
  379. // If any models are using the default archive, then register the default archive model
  380. // if it isn't already registered.
  381. if (_.contains(Object.keys(archiversInfoByArchiveIdentity), DEFAULT_ARCHIVE_MODEL_IDENTITY)) {
  382. // Inject the built-in Archive model into the ORM's ontology:
  383. // • id (pk-- string or number, depending on where the Archive model is being stored)
  384. // • createdAt (timestamp-- this is effectively ≈ "archivedAt")
  385. // • originalRecord (json-- the original record, completely unpopulated)
  386. // • originalRecordId (pk-- string or number, the pk of the original record)
  387. // • fromModel (string-- the original model identity)
  388. //
  389. // > Note there's no updatedAt!
  390. var existingDefaultArchiveWmd = _.find(wmds, function(wmd){ return wmd.prototype.identity === DEFAULT_ARCHIVE_MODEL_IDENTITY; });
  391. if (!existingDefaultArchiveWmd) {
  392. var defaultArchiversInfo = archiversInfoByArchiveIdentity[DEFAULT_ARCHIVE_MODEL_IDENTITY];
  393. // Arbitrarily pick the first archiver.
  394. // (we'll use this to derive a datastore and pk style so that they both match)
  395. var arbitraryArchiver = defaultArchiversInfo.archivers[0];
  396. // console.log('arbitraryArchiver', arbitraryArchiver);
  397. var newWmd = Waterline.Model.extend({
  398. identity: DEFAULT_ARCHIVE_MODEL_IDENTITY,
  399. // > Note that we inject a "globalId" for potential use in higher-level frameworks (e.g. Sails)
  400. // > that might want to globalize this model. This way, it'd show up as "Archive" instead of "archive".
  401. // > Remember: Waterline is NOT responsible for any globalization itself, this is just advisory.
  402. globalId: _.capitalize(DEFAULT_ARCHIVE_MODEL_IDENTITY),
  403. primaryKey: 'id',
  404. datastore: arbitraryArchiver.datastore,
  405. attributes: {
  406. id: arbitraryArchiver.attributes[arbitraryArchiver.primaryKey],
  407. createdAt: { type: 'number', autoCreatedAt: true, autoMigrations: { columnType: '_numbertimestamp' } },
  408. fromModel: { type: 'string', required: true, autoMigrations: { columnType: '_string' } },
  409. originalRecord: { type: 'json', required: true, autoMigrations: { columnType: '_json' } },
  410. // Use `type:'json'` for this:
  411. // (since it might contain pks for records from different datastores)
  412. originalRecordId: { type: 'json', autoMigrations: { columnType: '_json' } },
  413. }
  414. });
  415. wmds.push(newWmd);
  416. }//fi
  417. }//fi
  418. // Now make sure all archive models actually exist, and that they're valid.
  419. _.each(archiversInfoByArchiveIdentity, function(archiversInfo, archiveIdentity) {
  420. var archiveWmd = _.find(wmds, function(wmd){ return wmd.prototype.identity === archiveIdentity; });
  421. if (!archiveWmd) {
  422. throw new Error('Invalid `archiveModelIdentity` setting. A model declares `archiveModelIdentity: \''+archiveIdentity+'\'`, but there\'s no other model actually registered with that identity to use as an archive!');
  423. }
  424. // Validate that this archive model can be used for the purpose of Waterline's .archive()
  425. // > (note that the error messages here should be considerate of the case where someone is
  426. // > upgrading their app from an older version of Sails/Waterline and might happen to have
  427. // > a model named "Archive".)
  428. var EXPECTED_ATTR_NAMES = ['id', 'createdAt', 'fromModel', 'originalRecord', 'originalRecordId'];
  429. var actualAttrNames = _.keys(archiveWmd.prototype.attributes);
  430. var namesOfMissingAttrs = _.difference(EXPECTED_ATTR_NAMES, actualAttrNames);
  431. try {
  432. if (namesOfMissingAttrs.length > 0) {
  433. throw flaverr({
  434. code: 'E_INVALID_ARCHIVE_MODEL',
  435. because: 'it is missing '+ namesOfMissingAttrs.length+' mandatory attribute'+(namesOfMissingAttrs.length===1?'':'s')+': '+namesOfMissingAttrs+'.'
  436. });
  437. }//•
  438. if (archiveWmd.prototype.primaryKey !== 'id') {
  439. throw flaverr({
  440. code: 'E_INVALID_ARCHIVE_MODEL',
  441. because: 'it is using an attribute other than `id` as its logical primary key attribute.'
  442. });
  443. }//•
  444. if (_.any(EXPECTED_ATTR_NAMES, { encrypt: true })) {
  445. throw flaverr({
  446. code: 'E_INVALID_ARCHIVE_MODEL',
  447. because: 'it is using at-rest encryption on one of its mandatory attributes, when it shouldn\'t be.'
  448. });
  449. }//•
  450. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  451. // FUTURE: do more checks (there's a lot of things we should probably check-- e.g. the `type` of each
  452. // mandatory attribute, that no crazy defaultsTo is provided, that the auto-timestamp is correct, etc.)
  453. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  454. } catch (err) {
  455. switch (err.code) {
  456. case 'E_INVALID_ARCHIVE_MODEL':
  457. throw new Error(
  458. 'The `'+archiveIdentity+'` model cannot be used as a custom archive, because '+err.because+'\n'+
  459. 'Please adjust this custom archive model accordingly, or otherwise switch to a different '+
  460. 'model as your custom archive. (For reference, this `'+archiveIdentity+'` model this is currently '+
  461. 'configured as the custom archive model for '+archiversInfo.archivers.length+' other '+
  462. 'model'+(archiversInfo.archivers.length===1?'':'s')+': '+_.pluck(archiversInfo.archivers, 'identity')+'.'
  463. );
  464. default:
  465. throw err;
  466. }
  467. }
  468. });//∞
  469. // Build up a dictionary of datastores (used by our models?)
  470. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  471. // TODO: verify the last part of that statement ^^ (not seeing how this is related to "used by our models")
  472. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  473. // =================================================================
  474. try {
  475. datastoreMap = buildDatastoreMap(options.adapters, options.datastores);
  476. } catch (err) { throw err; }
  477. // Now check out the models and build a schema map (using wl-schema)
  478. // =================================================================
  479. var internalSchema;
  480. try {
  481. internalSchema = new Schema(wmds, options.defaults);
  482. } catch (err) { throw err; }
  483. // Check the internal "schema map" for any junction models that were
  484. // implicitly introduced above and handle them.
  485. _.each(_.keys(internalSchema), function(table) {
  486. if (internalSchema[table].junctionTable) {
  487. // Whenever one is found, flag it as `_private: true` and generate
  488. // a custom constructor for it (based on a clone of the `BaseMetaModel`
  489. // constructor), then push it on to our set of wmds.
  490. internalSchema[table]._private = true;
  491. wmds.push(BaseMetaModel.extend(internalSchema[table]));
  492. }//fi
  493. });//∞
  494. // Now build live models
  495. // =================================================================
  496. // Hydrate each model definition (in-place), and also set up a
  497. // reference to it in the model map.
  498. _.each(wmds, function (wmd) {
  499. // Set the attributes and schema values using the normalized versions from
  500. // Waterline-Schema where everything has already been processed.
  501. var schemaVersion = internalSchema[wmd.prototype.identity];
  502. // Set normalized values from the schema version on the model definition.
  503. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  504. // FUTURE: no need to use a prototype here, so let's avoid it to minimize future boggling
  505. // (or if we determine it significantly improves the performance of ORM initialization, then
  506. // let's keep it, but document that here and leave a link to the benchmark as a comment)
  507. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  508. wmd.prototype.identity = schemaVersion.identity;
  509. wmd.prototype.tableName = schemaVersion.tableName;
  510. wmd.prototype.datastore = schemaVersion.datastore;
  511. wmd.prototype.primaryKey = schemaVersion.primaryKey;
  512. wmd.prototype.meta = schemaVersion.meta;
  513. wmd.prototype.attributes = schemaVersion.attributes;
  514. wmd.prototype.schema = schemaVersion.schema;
  515. wmd.prototype.hasSchema = schemaVersion.hasSchema;
  516. // Mixin junctionTable or throughTable if available
  517. if (_.has(schemaVersion, 'junctionTable')) {
  518. wmd.prototype.junctionTable = schemaVersion.junctionTable;
  519. }
  520. if (_.has(schemaVersion, 'throughTable')) {
  521. wmd.prototype.throughTable = schemaVersion.throughTable;
  522. }
  523. var WLModel = buildLiveWLModel(wmd, datastoreMap, context);
  524. // Store the live Waterline model so it can be used
  525. // internally to create other records
  526. modelMap[WLModel.identity] = WLModel;
  527. });
  528. } catch (err) { return done(err); }
  529. // Finally, register datastores.
  530. // =================================================================
  531. // Simultaneously register each datastore with the correct adapter.
  532. // (This is async because the `registerDatastore` method in adapters
  533. // is async. But since they're not interdependent, we run them all in parallel.)
  534. async.each(_.keys(datastoreMap), function(datastoreName, next) {
  535. var datastore = datastoreMap[datastoreName];
  536. if (_.isFunction(datastore.adapter.registerConnection)) {
  537. return next(new Error('The adapter for datastore `' + datastoreName + '` is invalid: the `registerConnection` method must be renamed to `registerDatastore`.'));
  538. }
  539. try {
  540. // Note: at this point, the datastore should always have a usable adapter
  541. // set as its `adapter` property.
  542. // Check if the datastore's adapter has a `registerDatastore` method
  543. if (!_.has(datastore.adapter, 'registerDatastore')) {
  544. // FUTURE: get rid of this `setImmediate` (or if it's serving a purpose, document what that is)
  545. setImmediate(function() { next(); });//_∏_
  546. return;
  547. }//-•
  548. // Add the datastore name as the `identity` property in its config.
  549. datastore.config.identity = datastoreName;
  550. // Get the identities of all the models which use this datastore, and then build up
  551. // a simple mapping that can be passed down to the adapter.
  552. var usedSchemas = {};
  553. var modelIdentities = _.uniq(datastore.collections);
  554. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  555. // TODO: figure out if we still need this `uniq` or not. If so, document why.
  556. // If not, remove it. (hopefully the latter)
  557. //
  558. // e.g.
  559. // ```
  560. // assert(modelIdentities.length === datastore.collections.length);
  561. // ```
  562. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  563. _.each(modelIdentities, function(modelIdentity) {
  564. var WLModel = modelMap[modelIdentity];
  565. // Track info about this model by table name (for use in the adapter)
  566. var tableName;
  567. if (_.has(Object.getPrototypeOf(WLModel), 'tableName')) {
  568. tableName = Object.getPrototypeOf(WLModel).tableName;
  569. }
  570. else {
  571. tableName = modelIdentity;
  572. }
  573. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  574. // FUTURE: Suck the `getPrototypeOf()` poison out of this stuff. Mike is too dumb for this.
  575. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  576. assert(WLModel.tableName === tableName, 'Expecting `WLModel.tableName === tableName`. (Please open an issue: http://sailsjs.com/bugs)');
  577. assert(WLModel.identity === modelIdentity, 'Expecting `WLModel.identity === modelIdentity`. (Please open an issue: http://sailsjs.com/bugs)');
  578. assert(WLModel.primaryKey && _.isString(WLModel.primaryKey), 'How flabbergasting! Expecting truthy string in `WLModel.primaryKey`, but got something else. (If you\'re seeing this, there\'s probably a bug in Waterline. Please open an issue: http://sailsjs.com/bugs)');
  579. assert(WLModel.schema && _.isObject(WLModel.schema), 'Expecting truthy string in `WLModel.schema`, but got something else. (Please open an issue: http://sailsjs.com/bugs)');
  580. usedSchemas[tableName] = {
  581. primaryKey: WLModel.primaryKey,
  582. definition: WLModel.schema,
  583. tableName: tableName,
  584. identity: modelIdentity
  585. };
  586. });//</ each model identity >
  587. // Call the `registerDatastore` adapter method.
  588. datastore.adapter.registerDatastore(datastore.config, usedSchemas, next);
  589. } catch (err) { return next(err); }
  590. }, function(err) {
  591. if (err) { return done(err); }
  592. // Build up and return the ontology.
  593. return done(undefined, {
  594. collections: modelMap,
  595. datastores: datastoreMap
  596. });
  597. });//</async.each>
  598. };//</ definition of `orm.initialize` >
  599. // ┌─┐─┐ ┬┌─┐┌─┐┌─┐┌─┐ ┌─┐┬─┐┌┬┐╔╦╗╔═╗╔═╗╦═╗╔╦╗╔═╗╦ ╦╔╗╔
  600. // ├┤ ┌┴┬┘├─┘│ │└─┐├┤ │ │├┬┘│││ ║ ║╣ ╠═╣╠╦╝ ║║║ ║║║║║║║
  601. // └─┘┴ └─┴ └─┘└─┘└─┘ └─┘┴└─┴ ┴o╩ ╚═╝╩ ╩╩╚══╩╝╚═╝╚╩╝╝╚╝
  602. orm.teardown = function teardown(done) {
  603. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  604. // FUTURE: In WL 0.14, deprecate support for this method in favor of the simplified
  605. // `Waterline.start()` (see bottom of this file). In WL 1.0, remove it altogether.
  606. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  607. async.each(_.keys(datastoreMap), function(datastoreName, next) {
  608. var datastore = datastoreMap[datastoreName];
  609. // Check if the adapter has a teardown method implemented.
  610. // If not, then just skip this datastore.
  611. if (!_.has(datastore.adapter, 'teardown')) {
  612. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  613. // FUTURE: get rid of this `setImmediate` (or if it's serving a purpose, document what that is)
  614. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  615. setImmediate(function() { next(); });//_∏_
  616. return;
  617. }//-•
  618. // But otherwise, call its teardown method.
  619. try {
  620. datastore.adapter.teardown(datastoreName, next);
  621. } catch (err) { return next(err); }
  622. }, done);
  623. };
  624. // ╦═╗╔═╗╔╦╗╦ ╦╦═╗╔╗╔ ┌┐┌┌─┐┬ ┬ ┌─┐┬─┐┌┬┐ ┬┌┐┌┌─┐┌┬┐┌─┐┌┐┌┌─┐┌─┐
  625. // ╠╦╝║╣ ║ ║ ║╠╦╝║║║ │││├┤ │││ │ │├┬┘│││ ││││└─┐ │ ├─┤││││ ├┤
  626. // ╩╚═╚═╝ ╩ ╚═╝╩╚═╝╚╝ ┘└┘└─┘└┴┘ └─┘┴└─┴ ┴ ┴┘└┘└─┘ ┴ ┴ ┴┘└┘└─┘└─┘
  627. return orm;
  628. }
  629. // Export the Waterline ORM constructor.
  630. module.exports = Waterline;
  631. // ╔═╗═╗ ╦╔╦╗╔═╗╔╗╔╔═╗╦╔═╗╔╗╔╔═╗
  632. // ║╣ ╔╩╦╝ ║ ║╣ ║║║╚═╗║║ ║║║║╚═╗
  633. // ╚═╝╩ ╚═ ╩ ╚═╝╝╚╝╚═╝╩╚═╝╝╚╝╚═╝
  634. // Expose the generic, stateless BaseMetaModel constructor for direct access from
  635. // vanilla Waterline applications (available as `Waterline.Model`)
  636. //
  637. // > Note that this is technically a "MetaModel", because it will be "newed up"
  638. // > into a Waterline model instance (WLModel) like `User`, `Pet`, etc.
  639. // > But since, from a userland perspective, there is no real distinction, we
  640. // > still expose this as `Model` for the sake of simplicity.
  641. module.exports.Model = BaseMetaModel;
  642. // Expose `Collection` as an alias for `Model`, but only for backwards compatibility.
  643. module.exports.Collection = BaseMetaModel;
  644. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  645. // ^^FUTURE: In WL 1.0, remove this alias.
  646. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  647. /**
  648. * Waterline.start()
  649. *
  650. * Build and initialize a new Waterline ORM instance using the specified
  651. * userland ontology, including model definitions, datastore configurations,
  652. * and adapters.
  653. *
  654. * --EXPERIMENTAL--
  655. *
  656. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  657. * FUTURE: Have this return a Deferred using parley (so it supports `await`)
  658. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  659. *
  660. * @param {Dictionary} options
  661. * @property {Dictionary} models
  662. * @property {Dictionary} datastores
  663. * @property {Dictionary} adapters
  664. * @property {Dictionary?} defaultModelSettings
  665. *
  666. * @param {Function} done
  667. * @param {Error?} err
  668. * @param {Ref} orm
  669. */
  670. module.exports.start = function (options, done){
  671. // Verify usage & apply defaults:
  672. if (!_.isFunction(done)) {
  673. throw new Error('Please provide a valid callback function as the 2nd argument to `Waterline.start()`. (Instead, got: `'+done+'`)');
  674. }
  675. try {
  676. if (!_.isObject(options) || _.isArray(options) || _.isFunction(options)) {
  677. throw new Error('Please provide a valid dictionary (plain JS object) as the 1st argument to `Waterline.start()`. (Instead, got: `'+options+'`)');
  678. }
  679. if (!_.isObject(options.adapters) || _.isArray(options.adapters) || _.isFunction(options.adapters)) {
  680. throw new Error('`adapters` must be provided as a valid dictionary (plain JS object) of adapter definitions, keyed by adapter identity. (Instead, got: `'+options.adapters+'`)');
  681. }
  682. if (!_.isObject(options.datastores) || _.isArray(options.datastores) || _.isFunction(options.datastores)) {
  683. throw new Error('`datastores` must be provided as a valid dictionary (plain JS object) of datastore configurations, keyed by datastore name. (Instead, got: `'+options.datastores+'`)');
  684. }
  685. if (!_.isObject(options.models) || _.isArray(options.models) || _.isFunction(options.models)) {
  686. throw new Error('`models` must be provided as a valid dictionary (plain JS object) of model definitions, keyed by model identity. (Instead, got: `'+options.models+'`)');
  687. }
  688. if (_.isUndefined(options.defaultModelSettings)) {
  689. options.defaultModelSettings = {};
  690. } else if (!_.isObject(options.defaultModelSettings) || _.isArray(options.defaultModelSettings) || _.isFunction(options.defaultModelSettings)) {
  691. throw new Error('If specified, `defaultModelSettings` must be a dictionary (plain JavaScript object). (Instead, got: `'+options.defaultModelSettings+'`)');
  692. }
  693. var VALID_OPTIONS = ['adapters', 'datastores', 'models', 'defaultModelSettings'];
  694. var unrecognizedOptions = _.difference(_.keys(options), VALID_OPTIONS);
  695. if (unrecognizedOptions.length > 0) {
  696. throw new Error('Unrecognized option(s):\n '+unrecognizedOptions+'\n\nValid options are:\n '+VALID_OPTIONS+'\n');
  697. }
  698. // Check adapter identities.
  699. _.each(options.adapters, function (adapter, key){
  700. if (_.isUndefined(adapter.identity)) {
  701. adapter.identity = key;
  702. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  703. // Note: We removed the following purely for convenience.
  704. // If this comes up again, we should consider bringing it back instead of the more
  705. // friendly behavior above. But in the mean time, erring on the side of less typing
  706. // in userland by gracefully adjusting the provided adapter def.
  707. // ```
  708. // throw new Error('All adapters should declare an `identity`. But the adapter passed in under `'+key+'` has no identity! (Keep in mind that this adapter could get require()-d from somewhere else.)');
  709. // ```
  710. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  711. }
  712. else if (adapter.identity !== key) {
  713. throw new Error('The `identity` explicitly defined on an adapter should exactly match the key under which it is passed in to `Waterline.start()`. But the adapter passed in for key `'+key+'` has an identity that does not match: `'+adapter.identity+'`');
  714. }
  715. });//</_.each>
  716. // Now go ahead: start building & initializing the ORM.
  717. var orm = new Waterline();
  718. // Register models (checking model identities along the way).
  719. //
  720. // > In addition: Unfortunately, passing in `defaults` in `initialize()`
  721. // > below doesn't _ACTUALLY_ apply the specified model settings as
  722. // > defaults right now -- it only does so for implicit junction models.
  723. // > So we have to do that ourselves for the rest of the models out here
  724. // > first in this iteratee. Also note that we handle `attributes` as a
  725. // > special case.
  726. _.each(options.models, function (userlandModelDef, key){
  727. if (_.isUndefined(userlandModelDef.identity)) {
  728. userlandModelDef.identity = key;
  729. }
  730. else if (userlandModelDef.identity !== key) {
  731. throw new Error('If `identity` is explicitly defined on a model definition, it should exactly match the key under which it is passed in to `Waterline.start()`. But the model definition passed in for key `'+key+'` has an identity that does not match: `'+userlandModelDef.identity+'`');
  732. }
  733. _.defaults(userlandModelDef, _.omit(options.defaultModelSettings, 'attributes'));
  734. if (options.defaultModelSettings.attributes) {
  735. userlandModelDef.attributes = userlandModelDef.attributes || {};
  736. _.defaults(userlandModelDef.attributes, options.defaultModelSettings.attributes);
  737. }
  738. orm.registerModel(Waterline.Model.extend(userlandModelDef));
  739. });//</_.each>
  740. // Fire 'er up
  741. orm.initialize({
  742. adapters: options.adapters,
  743. datastores: options.datastores,
  744. defaults: options.defaultModelSettings
  745. }, function (err, _classicOntology) {
  746. if (err) { return done(err); }
  747. // Attach two private properties for compatibility's sake.
  748. // (These are necessary for utilities that accept `orm` to work.)
  749. // > But note that we do this as non-enumerable properties
  750. // > to make it less tempting to rely on them in userland code.
  751. // > (Instead, use `getModel()`!)
  752. Object.defineProperty(orm, 'collections', {
  753. value: _classicOntology.collections
  754. });
  755. Object.defineProperty(orm, 'datastores', {
  756. value: _classicOntology.datastores
  757. });
  758. return done(undefined, orm);
  759. });
  760. } catch (err) { return done(err); }
  761. };//</Waterline.start()>
  762. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  763. // To test quickly:
  764. // ```
  765. // require('./').start({adapters: { 'sails-foo': { identity: 'sails-foo' } }, datastores: { default: { adapter: 'sails-foo' } }, models: { user: { attributes: {id: {type: 'number'}}, primaryKey: 'id', datastore: 'default'} }}, function(err, _orm){ if(err){throw err;} console.log(_orm); /* and expose as `orm`: */ orm = _orm; });
  766. // ```
  767. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  768. /**
  769. * Waterline.stop()
  770. *
  771. * Tear down the specified Waterline ORM instance.
  772. *
  773. * --EXPERIMENTAL--
  774. *
  775. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  776. * FUTURE: Have this return a Deferred using parley (so it supports `await`)
  777. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  778. *
  779. * @param {Ref} orm
  780. *
  781. * @param {Function} done
  782. * @param {Error?} err
  783. */
  784. module.exports.stop = function (orm, done){
  785. // Verify usage & apply defaults:
  786. if (!_.isFunction(done)) {
  787. throw new Error('Please provide a valid callback function as the 2nd argument to `Waterline.stop()`. (Instead, got: `'+done+'`)');
  788. }
  789. try {
  790. if (!_.isObject(orm)) {
  791. throw new Error('Please provide a Waterline ORM instance (obtained from `Waterline.start()`) as the first argument to `Waterline.stop()`. (Instead, got: `'+orm+'`)');
  792. }
  793. orm.teardown(function (err){
  794. if (err) { return done(err); }
  795. return done();
  796. });//_∏_
  797. } catch (err) { return done(err); }
  798. };
  799. /**
  800. * Waterline.getModel()
  801. *
  802. * Look up one of an ORM's models by identity.
  803. * (If no matching model is found, this throws an error.)
  804. *
  805. * --EXPERIMENTAL--
  806. *
  807. * ------------------------------------------------------------------------------------------
  808. * @param {String} modelIdentity
  809. * The identity of the model this is referring to (e.g. "pet" or "user")
  810. *
  811. * @param {Ref} orm
  812. * The ORM instance to look for the model in.
  813. * ------------------------------------------------------------------------------------------
  814. * @returns {Ref} [the Waterline model]
  815. * ------------------------------------------------------------------------------------------
  816. * @throws {Error} If no such model exists.
  817. * E_MODEL_NOT_REGISTERED
  818. *
  819. * @throws {Error} If anything else goes wrong.
  820. * ------------------------------------------------------------------------------------------
  821. */
  822. module.exports.getModel = function (modelIdentity, orm){
  823. return getModel(modelIdentity, orm);
  824. };