├── .editorconfig ├── .eslintrc ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── ARCHITECTURE.md ├── CHANGELOG.md ├── CODE-OF-CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── ROADMAP.md ├── accessible ├── allowed-validations.js └── valid-attribute-properties.js ├── appveyor.yml ├── bower.json ├── example ├── express │ └── express-example.js └── raw │ └── raw-example.js ├── lib ├── waterline.js └── waterline │ ├── MetaModel.js │ ├── methods │ ├── add-to-collection.js │ ├── archive-one.js │ ├── archive.js │ ├── avg.js │ ├── count.js │ ├── create-each.js │ ├── create.js │ ├── destroy-one.js │ ├── destroy.js │ ├── find-one.js │ ├── find-or-create.js │ ├── find.js │ ├── remove-from-collection.js │ ├── replace-collection.js │ ├── stream.js │ ├── sum.js │ ├── update-one.js │ ├── update.js │ └── validate.js │ └── utils │ ├── ontology │ ├── README.md │ ├── get-attribute.js │ ├── get-model.js │ ├── is-capable-of-optimized-populate.js │ └── is-exclusive.js │ ├── query │ ├── build-omen.js │ ├── forge-adapter-error.js │ ├── forge-stage-three-query.js │ ├── forge-stage-two-query.js │ ├── get-query-modifier-methods.js │ ├── help-find.js │ ├── private │ │ ├── GENERIC_HELP_SUFFIX.string.js │ │ ├── build-usage-error.js │ │ ├── expand-where-shorthand.js │ │ ├── is-safe-natural-number.js │ │ ├── is-valid-attribute-name.js │ │ ├── normalize-comparison-value.js │ │ ├── normalize-constraint.js │ │ ├── normalize-criteria.js │ │ ├── normalize-new-record.js │ │ ├── normalize-pk-value-or-values.js │ │ ├── normalize-pk-value.js │ │ ├── normalize-sort-clause.js │ │ ├── normalize-value-to-set.js │ │ └── normalize-where-clause.js │ ├── process-all-records.js │ └── verify-model-method-context.js │ └── system │ ├── collection-builder.js │ ├── datastore-builder.js │ ├── has-schema-check.js │ ├── lifecycle-callback-builder.js │ ├── reserved-property-names.js │ ├── reserved-validation-names.js │ ├── transformer-builder.js │ ├── types.js │ └── validate-datastore-connectivity.js ├── package.json └── test ├── .eslintrc ├── alter-migrations ├── strategy.alter.buffers.js ├── strategy.alter.schema.js └── strategy.alter.schemaless.js ├── support ├── fixtures │ ├── associations │ │ ├── customer.fixture.js │ │ └── payment.fixture.js │ ├── integrator │ │ ├── cache.js │ │ ├── finalResults.js │ │ ├── joinResults.js │ │ ├── multiple.joins.js │ │ ├── n..1.joins.js │ │ ├── n..m.joins.js │ │ ├── populateResults.js │ │ ├── schema.js │ │ └── tables.js │ └── model │ │ ├── context.belongsTo.fixture.js │ │ ├── context.fixture.js │ │ ├── context.manyToMany.fixture.js │ │ └── context.simple.fixture.js └── migrate.helper.js └── unit ├── callbacks ├── afterCreate.create.js ├── afterCreate.createEach.js ├── afterCreate.findOrCreate.js ├── afterDestroy.destroy.js ├── beforeCreate.create.js ├── beforeCreate.createEach.js ├── beforeCreate.findOrCreate.js └── beforeDestroy.destroy.js ├── collection ├── transformations │ ├── transformations.initialize.js │ ├── transformations.serialize.js │ └── transformations.unserialize.js ├── type-cast │ ├── cast.boolean.js │ ├── cast.json.js │ ├── cast.number.js │ ├── cast.ref.js │ └── cast.string.js └── validations.js └── query ├── associations ├── belongsTo.js ├── hasMany.js ├── manyToMany.js ├── populateArray.js └── transformedPopulations.js ├── query.autocreatedat.js ├── query.autoupdatedat.js ├── query.avg.js ├── query.count.js ├── query.count.transform.js ├── query.create.js ├── query.create.ref.js ├── query.create.transform.js ├── query.createEach.js ├── query.createEach.transform.js ├── query.destroy.js ├── query.destroy.transform.js ├── query.exec.js ├── query.find.js ├── query.find.transform.js ├── query.findOne.js ├── query.findOne.transform.js ├── query.findOrCreate.js ├── query.findOrCreate.transform.js ├── query.promises.js ├── query.stream.js ├── query.sum.js ├── query.update.js └── query.update.transform.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # ╔═╗╔╦╗╦╔╦╗╔═╗╦═╗┌─┐┌─┐┌┐┌┌─┐┬┌─┐ 2 | # ║╣ ║║║ ║ ║ ║╠╦╝│ │ ││││├┤ ││ ┬ 3 | # o╚═╝═╩╝╩ ╩ ╚═╝╩╚═└─┘└─┘┘└┘└ ┴└─┘ 4 | # 5 | # This file (`.editorconfig`) exists to help maintain consistent formatting 6 | # throughout this package, the Sails framework, and the Node-Machine project. 7 | # 8 | # To review what each of these options mean, see: 9 | # http://editorconfig.org/ 10 | root = true 11 | 12 | [*] 13 | indent_style = space 14 | indent_size = 2 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | // ╔═╗╔═╗╦ ╦╔╗╔╔╦╗┬─┐┌─┐ 3 | // ║╣ ╚═╗║ ║║║║ ║ ├┬┘│ 4 | // o╚═╝╚═╝╩═╝╩╝╚╝ ╩ ┴└─└─┘ 5 | // A set of basic conventions (similar to .jshintrc) for use within any 6 | // arbitrary JavaScript / Node.js package -- inside or outside Sails.js. 7 | // For the master copy of this file, see the `.eslintrc` template file in 8 | // the `sails-generate` package (https://www.npmjs.com/package/sails-generate.) 9 | // Designed for ESLint v4. 10 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 11 | // For more information about any of the rules below, check out the relevant 12 | // reference page on eslint.org. For example, to get details on "no-sequences", 13 | // you would visit `http://eslint.org/docs/rules/no-sequences`. If you're unsure 14 | // or could use some advice, come by https://sailsjs.com/support. 15 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 16 | 17 | "env": { 18 | "node": true 19 | }, 20 | 21 | "parserOptions": { 22 | "ecmaVersion": 5 23 | // ^^This can be changed to `8` if this package doesn't need to support <= Node v6. 24 | }, 25 | 26 | "globals": { 27 | "Promise": true 28 | // ^^Available since Node v4 29 | }, 30 | 31 | "rules": { 32 | "callback-return": ["error", ["done", "proceed", "next", "onwards", "callback", "cb"]], 33 | "camelcase": ["warn", {"properties": "always"}], 34 | "comma-style": ["warn", "last"], 35 | "curly": ["error"], 36 | "eqeqeq": ["error", "always"], 37 | "eol-last": ["warn"], 38 | "handle-callback-err": ["error"], 39 | "indent": ["warn", 2, { 40 | "SwitchCase": 1, 41 | "MemberExpression": "off", 42 | "FunctionDeclaration": {"body":1, "parameters": "off"}, 43 | "FunctionExpression": {"body":1, "parameters": "off"}, 44 | "CallExpression": {"arguments":"off"}, 45 | "ArrayExpression": 1, 46 | "ObjectExpression": 1, 47 | "ignoredNodes": ["ConditionalExpression"] 48 | }], 49 | "linebreak-style": ["error", "unix"], 50 | "no-dupe-keys": ["error"], 51 | "no-duplicate-case": ["error"], 52 | "no-extra-semi": ["warn"], 53 | "no-labels": ["error"], 54 | "no-mixed-spaces-and-tabs": ["error", "smart-tabs"], 55 | "no-redeclare": ["warn"], 56 | "no-return-assign": ["error", "always"], 57 | "no-sequences": ["error"], 58 | "no-trailing-spaces": ["warn"], 59 | "no-undef": ["error"], 60 | "no-unexpected-multiline": ["warn"], 61 | "no-unused-vars": ["warn", {"caughtErrors":"all", "caughtErrorsIgnorePattern": "^unused($|[A-Z].*$)", "argsIgnorePattern": "^unused($|[A-Z].*$)", "varsIgnorePattern": "^unused($|[A-Z].*$)" }], 62 | "no-use-before-define": ["error", {"functions":false}], 63 | "one-var": ["warn", "never"], 64 | "quotes": ["warn", "single", {"avoidEscape":false, "allowTemplateLiterals":true}], 65 | "semi": ["error", "always"], 66 | "semi-spacing": ["warn", {"before":false, "after":true}], 67 | "semi-style": ["warn", "last"] 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ┌─┐┬┌┬┐╦╔═╗╔╗╔╔═╗╦═╗╔═╗ 2 | # │ ┬│ │ ║║ ╦║║║║ ║╠╦╝║╣ 3 | # o└─┘┴ ┴ ╩╚═╝╝╚╝╚═╝╩╚═╚═╝ 4 | # 5 | # This file (`.gitignore`) exists to signify to `git` that certain files 6 | # and/or directories should be ignored for the purposes of version control. 7 | # 8 | # This is primarily useful for excluding temporary files of all sorts; stuff 9 | # generated by IDEs, build scripts, automated tests, package managers, or even 10 | # end-users (e.g. file uploads). `.gitignore` files like this also do a nice job 11 | # at keeping sensitive credentials and personal data out of version control systems. 12 | # 13 | 14 | ############################ 15 | # sails / node.js / npm 16 | ############################ 17 | node_modules 18 | .tmp 19 | npm-debug.log 20 | package-lock.json 21 | .waterline 22 | .node_history 23 | 24 | ############################ 25 | # editor & OS files 26 | ############################ 27 | *.swo 28 | *.swp 29 | *.swn 30 | *.swm 31 | *.seed 32 | *.log 33 | *.out 34 | *.pid 35 | lib-cov 36 | .DS_STORE 37 | *# 38 | *\# 39 | .\#* 40 | *~ 41 | .idea 42 | .netbeans 43 | nbproject 44 | 45 | ############################ 46 | # misc 47 | ############################ 48 | dump.rdb 49 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // ┬┌─┐╦ ╦╦╔╗╔╔╦╗┬─┐┌─┐ 3 | // │└─┐╠═╣║║║║ ║ ├┬┘│ 4 | // o└┘└─┘╩ ╩╩╝╚╝ ╩ ┴└─└─┘ 5 | // 6 | // This file (`.jshintrc`) exists to help with consistency of code 7 | // throughout this package, and throughout Sails and the Node-Machine project. 8 | // 9 | // To review what each of these options mean, see: 10 | // http://jshint.com/docs/options 11 | // 12 | // (or: https://github.com/jshint/jshint/blob/master/examples/.jshintrc) 13 | 14 | 15 | 16 | ////////////////////////////////////////////////////////////////////// 17 | // NOT SUPPORTED IN SOME JSHINT VERSIONS SO LEAVING COMMENTED OUT: 18 | ////////////////////////////////////////////////////////////////////// 19 | // Prevent overwriting prototypes of native classes like `Array`. 20 | // (doing this is _never_ ok in any of our packages that are intended 21 | // to be used as dependencies of other developers' modules and apps) 22 | // "freeze": true, 23 | ////////////////////////////////////////////////////////////////////// 24 | 25 | 26 | ////////////////////////////////////////////////////////////////////// 27 | // EVERYTHING ELSE: 28 | ////////////////////////////////////////////////////////////////////// 29 | 30 | // Allow the use of ES6 features. 31 | // (re ES7, see https://github.com/jshint/jshint/issues/2297) 32 | "esversion": 6, 33 | 34 | // Allow the use of `eval` and `new Function()` 35 | // (we sometimes actually need to use these things) 36 | "evil": true, 37 | 38 | // Tolerate funny-looking dashes in RegExp literals. 39 | // (see https://github.com/jshint/jshint/issues/159#issue-903547) 40 | "regexdash": true, 41 | 42 | // The potential runtime "Environments" (as defined by jshint) 43 | // that the _style_ of code written in this package should be 44 | // compatible with (not the code itself, of course). 45 | "browser": true, 46 | "node": true, 47 | "wsh": true, 48 | 49 | // Tolerate the use `[]` notation when dot notation would be possible. 50 | // (this is sometimes preferable for readability) 51 | "sub": true, 52 | 53 | // Do NOT suppress warnings about mixed tabs and spaces 54 | // (two spaces always, please; see `.editorconfig`) 55 | "smarttabs": false, 56 | 57 | // Suppress warnings about trailing whitespace 58 | // (this is already enforced by the .editorconfig, so no need to warn as well) 59 | "trailing": false, 60 | 61 | // Suppress warnings about the use of expressions where fn calls or assignments 62 | // are expected, and about using assignments where conditionals are expected. 63 | // (while generally a good idea, without this setting, JSHint needlessly lights up warnings 64 | // in existing, working code that really shouldn't be tampered with. Pandora's box and all.) 65 | "expr": true, 66 | "boss": true, 67 | 68 | // Do NOT suppress warnings about using functions inside loops 69 | // (in the general case, we should be using iteratee functions with `_.each()` 70 | // or `Array.prototype.forEach()` instead of `for` or `while` statements 71 | // anyway. This warning serves as a helpful reminder.) 72 | "loopfunc": false, 73 | 74 | // Suppress warnings about "weird constructions" 75 | // i.e. allow code like: 76 | // ``` 77 | // (new (function OneTimeUsePrototype () { } )) 78 | // ``` 79 | // 80 | // (sometimes order of operations in JavaScript can be scary. There is 81 | // nothing wrong with using an extra set of parantheses when the mood 82 | // strikes or you get "that special feeling".) 83 | "supernew": true, 84 | 85 | // Do NOT allow backwards, node-dependency-style commas. 86 | // (while this code style choice was used by the project in the past, 87 | // we have since standardized these practices to make code easier to 88 | // read, albeit a bit less exciting) 89 | "laxcomma": false, 90 | 91 | // Do NOT allow avant garde use of commas in conditional statements. 92 | // (this prevents accidentally writing code like: 93 | // ``` 94 | // if (!_.contains(['+ci', '-ci', '∆ci', '+ce', '-ce', '∆ce']), change.verb) {...} 95 | // ``` 96 | // See the problem in that code? Neither did we-- that's the problem!) 97 | "nocomma": true, 98 | 99 | // Strictly enforce the consistent use of single quotes. 100 | // (this is a convention that was established primarily to make it easier 101 | // to grep [or FIND+REPLACE in Sublime] particular string literals in 102 | // JavaScript [.js] files. Note that JSON [.json] files are, of course, 103 | // still written exclusively using double quotes around key names and 104 | // around string literals.) 105 | "quotmark": "single", 106 | 107 | // Do NOT suppress warnings about the use of `==null` comparisons. 108 | // (please be explicit-- use Lodash or `require('util')` and call 109 | // either `.isNull()` or `.isUndefined()`) 110 | "eqnull": false, 111 | 112 | // Strictly enforce the use of curly braces with `if`, `else`, and `switch` 113 | // as well as, much less commonly, `for` and `while` statements. 114 | // (this is just so that all of our code is consistent, and to avoid bugs) 115 | "curly": true, 116 | 117 | // Strictly enforce the use of `===` and `!==`. 118 | // (this is always a good idea. Check out "Truth, Equality, and JavaScript" 119 | // by Angus Croll [the author of "If Hemmingway Wrote JavaScript"] for more 120 | // explanation as to why.) 121 | "eqeqeq": true, 122 | 123 | // Allow initializing variables to `undefined`. 124 | // For more information, see: 125 | // • https://jslinterrors.com/it-is-not-necessary-to-initialize-a-to-undefined 126 | // • https://github.com/jshint/jshint/issues/1484 127 | // 128 | // (it is often very helpful to explicitly clarify the initial value of 129 | // a local variable-- especially for folks new to more advanced JavaScript 130 | // and who might not recognize the subtle, yet critically important differences between our seemingly 131 | // between `null` and `undefined`, and the impact on `typeof` checks) 132 | "-W080": true 133 | 134 | } 135 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git 2 | ./.gitignore 3 | ./.jshintrc 4 | ./.editorconfig 5 | ./.travis.yml 6 | ./appveyor.yml 7 | ./example 8 | ./examples 9 | ./test 10 | ./tests 11 | ./.github 12 | 13 | node_modules 14 | npm-debug.log 15 | .node_history 16 | *.swo 17 | *.swp 18 | *.swn 19 | *.swm 20 | *.seed 21 | *.log 22 | *.out 23 | *.pid 24 | lib-cov 25 | .DS_STORE 26 | *# 27 | *\# 28 | .\#* 29 | *~ 30 | .idea 31 | .netbeans 32 | nbproject 33 | .tmp 34 | dump.rdb 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 2 | # ╔╦╗╦═╗╔═╗╦ ╦╦╔═╗ ┬ ┬┌┬┐┬ # 3 | # ║ ╠╦╝╠═╣╚╗╔╝║╚═╗ └┬┘││││ # 4 | # o ╩ ╩╚═╩ ╩ ╚╝ ╩╚═╝o ┴ ┴ ┴┴─┘ # 5 | # # 6 | # This file configures Travis CI. # 7 | # (i.e. how we run the tests... mainly) # 8 | # # 9 | # https://docs.travis-ci.com/user/customizing-the-build # 10 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 11 | 12 | language: node_js 13 | 14 | node_js: 15 | - "10" 16 | - "12" 17 | - "14" 18 | - "16" 19 | 20 | branches: 21 | only: 22 | - master 23 | 24 | notifications: 25 | email: 26 | - ci@sailsjs.com 27 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | Waterline follows the [Sails Code of Conduct](https://github.com/balderdashy/sails/blob/master/CODE-OF-CONDUCT.md). 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Waterline 2 | 3 | Waterline follows the [Sails Contribution Guide](https://github.com/balderdashy/sails/blob/master/CONTRIBUTING.md). 4 | 5 | The Contribution Guide is designed to help you get off the ground quickly contributing to Waterline. Reading it thoroughly will help you write useful issues, make eloquent proposals, and submit top-notch code that can be merged quickly. Respecting the guidelines laid out in the guide helps make the core maintainers of Waterline more productive, and makes the experience of working with Waterline positive and enjoyable for the community at large. 6 | 7 | If you are working on a pull request, **please carefully read this file from top to bottom**. In case of doubt, open an issue in the issue tracker or contact someone from our [core team](https://github.com/balderdashy/sails#team) on Twitter. Especially do so if you plan to work on something big. Nothing is more frustrating than seeing your hard work go to waste because your vision does not align with planned or ongoing development efforts of the project's maintainers. 8 | 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | -- 3 | 4 | Copyright © 2012-present Mike McNeil & The Sails Company 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Waterline logo](http://waterlinejs.org) 2 | 3 | [![Master Branch Build Status](https://travis-ci.org/balderdashy/waterline.svg?branch=master)](https://travis-ci.org/balderdashy/waterline) 4 | [![Master Branch Build Status (Windows)](https://ci.appveyor.com/api/projects/status/tdu70ax32iymvyq3?svg=true)](https://ci.appveyor.com/project/mikermcneil/waterline) 5 | [![StackOverflow (waterline)](https://img.shields.io/badge/stackoverflow-waterline-blue.svg)]( http://stackoverflow.com/questions/tagged/waterline) 6 | [![StackOverflow (sails)](https://img.shields.io/badge/stackoverflow-sails.js-blue.svg)]( http://stackoverflow.com/questions/tagged/sails.js) 7 | 8 | Waterline is a next-generation storage and retrieval engine, and the default ORM used in the [Sails framework](https://sailsjs.com). 9 | 10 | It provides a uniform API for accessing stuff from different kinds of [databases and protocols](https://sailsjs.com/documentation/concepts/extending-sails/adapters/available-adapters). That means you write the same code to get and store things like users, whether they live in MySQL, MongoDB, neDB, or Postgres. 11 | 12 | Waterline strives to inherit the best parts of ORMs like ActiveRecord, Hibernate, and Mongoose, but with a fresh perspective and emphasis on modularity, testability, and consistency across adapters. 13 | 14 | ## No more callbacks 15 | 16 | Starting with v0.13, Waterline takes full advantage of ECMAScript & Node 8's `await` keyword. 17 | 18 | **In other words, [no more callbacks](https://gist.github.com/mikermcneil/c1028d000cc0cc8bce995a2a82b29245).** 19 | 20 | ```js 21 | var newOrg = await Organization.create({ 22 | slug: 'foo' 23 | }) 24 | .fetch(); 25 | ``` 26 | 27 | > Looking for the version of Waterline used in Sails v0.12? See the [0.11.x branch](https://github.com/balderdashy/waterline/tree/0.11.x) of this repo. If you're upgrading to v0.13 from a previous release of Waterline _standalone_, take a look at the [upgrading guide](http://sailsjs.com/documentation/upgrading/to-v-1-0). 28 | 29 | ## Installation 30 | Install from NPM. 31 | 32 | ```bash 33 | $ npm install waterline 34 | ``` 35 | 36 | ## Overview 37 | Waterline uses the concept of an adapter to translate a predefined set of methods into a query that can be understood by your data store. Adapters allow you to use various datastores such as MySQL, PostgreSQL, MongoDB, Redis, etc. and have a clear API for working with your model data. 38 | 39 | Waterline supports [a wide variety of adapters](http://sailsjs.com/documentation/concepts/extending-sails/adapters/available-adapters), both core and community maintained. 40 | 41 | ## Usage 42 | 43 | The up-to-date documentation for Waterline is maintained on the [Sails framework website](http://sailsjs.com). 44 | You can find detailed API reference docs under [Reference > Waterline ORM](http://sailsjs.com/documentation/reference/waterline-orm). For conceptual info (including Waterline standalone usage), and answers to common questions, see [Concepts > Models & ORM](https://sailsjs.com/documentation/concepts/models-and-orm). 45 | 46 | #### Help 47 | 48 | Check out the recommended [community support options](http://sailsjs.com/support) for tutorials and other resources. If you have a specific question, or just need to clarify [how something works](https://docs.google.com/drawings/d/1u7xb5jDY5i2oeVRP2-iOGGVsFbosqTMWh9wfmY3BTfw/edit), ask [for help](https://gitter.im/balderdashy/sails) or reach out to the [core team](http://sailsjs.com/about) [directly](http://sailsjs.com/flagship). 49 | 50 | You can keep up to date with security patches, the Waterline release schedule, new database adapters, and events in your area by following us ([@sailsjs](https://twitter.com/sailsjs)) on Twitter. 51 | 52 | ## Bugs   [![NPM version](https://badge.fury.io/js/waterline.svg)](http://npmjs.com/package/waterline) 53 | To report a bug, [click here](http://sailsjs.com/bugs). 54 | 55 | ## Contribute 56 | Please observe the guidelines and conventions laid out in our [contribution guide](http://sailsjs.com/documentation/contributing) when opening issues or submitting pull requests. 57 | 58 | #### Tests 59 | All tests are written with [mocha](https://mochajs.org/) and should be run with [npm](https://www.npmjs.com/): 60 | 61 | ``` bash 62 | $ npm test 63 | ``` 64 | 65 | 66 | ## License 67 | [MIT](http://sailsjs.com/license). Copyright © 2012-present Mike McNeil & The Sails Company 68 | 69 | [Waterline](http://waterlinejs.org), like the rest of the [Sails framework](https://sailsjs.com), is free and open-source under the [MIT License](https://sailsjs.com/license). 70 | 71 | ![image_squidhome@2x.png](http://sailsjs.com/images/bkgd_squiddy.png) 72 | -------------------------------------------------------------------------------- /accessible/allowed-validations.js: -------------------------------------------------------------------------------- 1 | module.exports = require('anchor/accessible/rules'); 2 | -------------------------------------------------------------------------------- /accessible/valid-attribute-properties.js: -------------------------------------------------------------------------------- 1 | module.exports = require('waterline-schema/accessible/valid-attribute-properties'); 2 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # # # # # # # # # # # # # # # # # # # # # # # # # # 2 | # ╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╦╔═╗╦═╗ ┬ ┬┌┬┐┬ # 3 | # ╠═╣╠═╝╠═╝╚╗╔╝║╣ ╚╦╝║ ║╠╦╝ └┬┘││││ # 4 | # ╩ ╩╩ ╩ ╚╝ ╚═╝ ╩ ╚═╝╩╚═o ┴ ┴ ┴┴─┘ # 5 | # # 6 | # This file configures Appveyor CI. # 7 | # (i.e. how we run the tests on Windows) # 8 | # # 9 | # https://www.appveyor.com/docs/lang/nodejs-iojs/ # 10 | # # # # # # # # # # # # # # # # # # # # # # # # # # 11 | 12 | 13 | # Test against these versions of Node.js. 14 | environment: 15 | matrix: 16 | - nodejs_version: "6" 17 | - nodejs_version: "8" 18 | - nodejs_version: "10" 19 | 20 | # Install scripts. (runs after repo cloning) 21 | install: 22 | # Get the latest stable version of Node.js 23 | # (Not sure what this is for, it's just in Appveyor's example.) 24 | - ps: Install-Product node $env:nodejs_version 25 | # Install declared dependencies 26 | - npm install 27 | 28 | 29 | # Post-install test scripts. 30 | test_script: 31 | # Output Node and NPM version info. 32 | # (Presumably just in case Appveyor decides to try any funny business? 33 | # But seriously, always good to audit this kind of stuff for debugging.) 34 | - node --version 35 | - npm --version 36 | # Run the actual tests. 37 | - npm run custom-tests 38 | 39 | 40 | # Don't actually build. 41 | # (Not sure what this is for, it's just in Appveyor's example. 42 | # I'm not sure what we're not building... but I'm OK with not 43 | # building it. I guess.) 44 | build: off 45 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "waterline", 3 | "homepage": "https://github.com/balderdashy/waterline", 4 | "authors": [ 5 | "Cody Stoltman <@particlebanana>", 6 | "Mike McNeil <@mikermcneil>" 7 | ], 8 | "description": "JavaScript ORM for the browser and Node.js", 9 | "main": ".dist/waterline.min.js", 10 | "keywords": [ 11 | "orm", 12 | "models", 13 | "collections", 14 | "mvc", 15 | "sails" 16 | ], 17 | "license": "MIT", 18 | "ignore": [ 19 | "**/.*", 20 | "example", 21 | "lib", 22 | "package.json", 23 | "node_modules", 24 | "bower_components", 25 | "test", 26 | "tests" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /lib/waterline/utils/ontology/README.md: -------------------------------------------------------------------------------- 1 | # utils/ontology/ 2 | 3 | Utilities for accessing information about the logical state of the ORM. This consists of things like accessors for live WLModels, attribute definitions, etc., and other looker-uppers (e.g. `isCapableOfOptimizedPopulate()`). 4 | -------------------------------------------------------------------------------- /lib/waterline/utils/ontology/is-exclusive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | 5 | var util = require('util'); 6 | var assert = require('assert'); 7 | var _ = require('@sailshq/lodash'); 8 | var getAttribute = require('./get-attribute'); 9 | 10 | 11 | /** 12 | * isExclusive() 13 | * 14 | * Determine whether this association is "exclusive" -- meaning that it is 15 | * a two-way, plural ("collection") association, whose `via` points at a 16 | * singular ("model") on the other side. 17 | * 18 | * > Note that "through" associations do not count. Although the "via" does 19 | * > refer to a singular ("model") association in the intermediate junction 20 | * > model, the underlying logical association is still non-exclusive. 21 | * > i.e. the same child record can be added to the "through" association 22 | * > of multiple different parent records. 23 | * 24 | * --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 25 | * @param {String} attrName [the name of the association in question] 26 | * @param {String} modelIdentity [the identity of the model this association belongs to] 27 | * @param {Ref} orm [the Waterline ORM instance] 28 | * --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 29 | * @returns {Boolean} 30 | */ 31 | 32 | module.exports = function isExclusive(attrName, modelIdentity, orm) { 33 | 34 | if (!_.isString(attrName)) { 35 | throw new Error('Consistency violation: Must specify `attrName` as a string. But instead, got: '+util.inspect(attrName, {depth:5})+''); 36 | } 37 | if (!_.isString(modelIdentity)) { 38 | throw new Error('Consistency violation: Must specify `modelIdentity` as a string. But instead, got: '+util.inspect(modelIdentity, {depth:5})+''); 39 | } 40 | if (_.isUndefined(orm)) { 41 | throw new Error('Consistency violation: Must pass in `orm` (a reference to the Waterline ORM instance). But instead, got: '+util.inspect(orm, {depth:5})+''); 42 | } 43 | 44 | 45 | // ╦ ╔═╗╔═╗╦╔═ ╦ ╦╔═╗ ┌─┐┌─┐┌─┐┌─┐┌─┐┬┌─┐┌┬┐┬┌─┐┌┐┌ ┬ ┌┬┐┌─┐┌┬┐┌─┐┬ ┌─┐ 46 | // ║ ║ ║║ ║╠╩╗ ║ ║╠═╝ ├─┤└─┐└─┐│ ││ │├─┤ │ ││ ││││ ┌┼─ ││││ │ ││├┤ │ └─┐ 47 | // ╩═╝╚═╝╚═╝╩ ╩ ╚═╝╩ ┴ ┴└─┘└─┘└─┘└─┘┴┴ ┴ ┴ ┴└─┘┘└┘ └┘ ┴ ┴└─┘─┴┘└─┘┴─┘└─┘ 48 | 49 | // Look up the containing model for this association, and the attribute definition itself. 50 | var attrDef = getAttribute(attrName, modelIdentity, orm); 51 | 52 | assert(attrDef.model || attrDef.collection, 'Attempting to check whether attribute `'+attrName+'` of model `'+modelIdentity+'` is an "exclusive" association, but it\'s not even an association in the first place!'); 53 | 54 | 55 | 56 | // ┌┐┌┌─┐┬ ┬ ╔═╗╦ ╦╔═╗╔═╗╦╔═ ╦╔╦╗ ╔═╗╦ ╦╔╦╗ 57 | // ││││ ││││ ║ ╠═╣║╣ ║ ╠╩╗ ║ ║ ║ ║║ ║ ║ 58 | // ┘└┘└─┘└┴┘┘ ╚═╝╩ ╩╚═╝╚═╝╩ ╩ ╩ ╩ ╚═╝╚═╝ ╩ 59 | 60 | // If this association is singular, then it is not exclusive. 61 | if (!attrDef.collection) { 62 | return false; 63 | }//-• 64 | 65 | // If it has no `via`, then it is not two-way, and also not exclusive. 66 | if (!attrDef.via) { 67 | return false; 68 | }//-• 69 | 70 | // If it has a "through" junction model defined, then it is not exclusive. 71 | if (attrDef.through) { 72 | return false; 73 | }//-• 74 | 75 | // If its `via` points at a plural association, then it is not exclusive. 76 | // > Note that, to do this, we look up the attribute on the OTHER model 77 | // > that is pointed at by THIS association's `via`. 78 | var viaAttrDef = getAttribute(attrDef.via, attrDef.collection, orm); 79 | if (viaAttrDef.collection) { 80 | return false; 81 | }//-• 82 | 83 | // Otherwise, its `via` must be pointing at a singular association, so it's exclusive! 84 | return true; 85 | 86 | }; 87 | 88 | 89 | // Quick test: 90 | /*``` 91 | require('./lib/waterline/utils/ontology/is-exclusive')('pets', 'user', { collections: { user: { attributes: { id: { type: 'string', required: true, unique: true }, age: { type: 'number', required: false }, foo: { type: 'string', required: true }, pets: { collection: 'pet', via: 'owner' } }, primaryKey: 'id', hasSchema: true}, pet: { attributes: { id: { type:'number', required: true, unique: true }, owner: { model: 'user' } }, primaryKey: 'id', hasSchema: true } } }); 92 | ```*/ 93 | -------------------------------------------------------------------------------- /lib/waterline/utils/query/build-omen.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | 5 | var flaverr = require('flaverr'); 6 | 7 | 8 | /** 9 | * buildOmen() 10 | * 11 | * Build an omen, an Error instance defined ahead of time in order to grab a stack trace. 12 | * (used for providing a better experience when viewing the stack trace of errors 13 | * that come from one or more asynchronous ticks down the line; e.g. uniqueness errors) 14 | * 15 | * > Note that the Error returned by this utility can only be used once. 16 | * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 17 | * @param {Function} caller 18 | * The function to use for context. 19 | * The stack trace of the omen will be snipped based on the instruction where 20 | * this "caller" function was invoked. 21 | * 22 | * @returns {Error} 23 | * The new omen (an Error instance.) 24 | */ 25 | module.exports = function buildOmen(caller){ 26 | 27 | var omen = flaverr({}, new Error('omen'), caller); 28 | return omen; 29 | 30 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 31 | // FUTURE: do something fancier here, or where this is called, to keep track of the omen so that it 32 | // can support both sorts of usages (Deferred and explicit callback.) 33 | // 34 | // This way, it could do an even better job of reporting exactly where the error came from in 35 | // userland code as the very first entry in the stack trace. e.g. 36 | // ``` 37 | // var omen = flaverr({}, new Error('omen'), Deferred.prototype.exec); 38 | // // ^^ but would need to pass through the original omen or something 39 | // ``` 40 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 41 | 42 | 43 | }; 44 | -------------------------------------------------------------------------------- /lib/waterline/utils/query/private/GENERIC_HELP_SUFFIX.string.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A generic help suffix for use in error messages. 3 | * 4 | * @type {String} 5 | */ 6 | 7 | module.exports = ' [?] See https://sailsjs.com/support for help.'; 8 | // module.exports = '--\n'+ 9 | // 'Read more (or ask for help):\n'+ 10 | // ' • https://sailsjs.com/support\n'+ 11 | // ' • https://sailsjs.com/docs/concepts/models-and-orm/query-language\n'+ 12 | // ' • https://sailsjs.com/docs/concepts/models-and-orm\n'+ 13 | // ' • https://sailsjs.com/docs/reference/waterline-orm\n'+ 14 | // ''; 15 | 16 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 17 | // FUTURE: Potentially build a more helpful landing page with the above links 18 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 19 | -------------------------------------------------------------------------------- /lib/waterline/utils/query/private/expand-where-shorthand.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | 5 | var _ = require('@sailshq/lodash'); 6 | 7 | 8 | 9 | /** 10 | * Module constants 11 | */ 12 | 13 | var RECOGNIZED_S2Q_CRITERIA_CLAUSE_NAMES = ['where', 'limit', 'skip', 'sort', 'select', 'omit']; 14 | 15 | 16 | 17 | /** 18 | * expandWhereShorthand() 19 | * 20 | * Return a new dictionary wrapping the provided `where` clause, or if the 21 | * provided dictionary already contains a criteria clause (`where`, `limit`, etc), 22 | * then just return it as-is. 23 | * 24 | * 25 | * > This handles implicit `where` clauses provided instead of criteria. 26 | * > 27 | * > If the provided criteria dictionary DOES NOT contain the names of ANY known 28 | * > criteria clauses (like `where`, `limit`, etc.) as properties, then we can 29 | * > safely assume that it is relying on shorthand: i.e. simply specifying what 30 | * > would normally be the `where` clause, but at the top level. 31 | * 32 | * 33 | * > Note that, _in addition_ to calling this utility from FS2Q, it is sometimes 34 | * > necessary to call this directly from relevant methods. That's because FS2Q 35 | * > normalization does not occur until we _actually_ execute the query, and in 36 | * > the mean time, we provide deferred methods for building criteria piece by piece. 37 | * > In other words, we need to allow for hybrid usage like: 38 | * > ``` 39 | * > User.find({ name: 'Santa' }).limit(30) 40 | * > ``` 41 | * > 42 | * > And: 43 | * > ``` 44 | * > User.find().limit(30) 45 | * > ``` 46 | * > 47 | * > ...in addition to normal usage like this: 48 | * > ``` 49 | * > User.find({ limit: 30 }).where({ name: 'Santa', age: { '>': 1000 } }) 50 | * > ``` 51 | * 52 | * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 53 | * @param {Ref?} criteria 54 | * @returns {Dictionary} 55 | * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 56 | */ 57 | 58 | module.exports = function expandWhereShorthand(criteria){ 59 | 60 | 61 | if (_.isUndefined(criteria)) { 62 | 63 | criteria = {}; 64 | 65 | } 66 | else if (!_.isObject(criteria)) { 67 | 68 | criteria = { 69 | where: criteria 70 | }; 71 | 72 | } 73 | else { 74 | 75 | var recognizedClauses = _.intersection(_.keys(criteria), RECOGNIZED_S2Q_CRITERIA_CLAUSE_NAMES); 76 | if (recognizedClauses.length === 0) { 77 | criteria = { 78 | where: criteria 79 | }; 80 | } 81 | 82 | } 83 | 84 | return criteria; 85 | 86 | }; 87 | -------------------------------------------------------------------------------- /lib/waterline/utils/query/private/is-safe-natural-number.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | 5 | var lodash4IsSafeInteger = require('lodash.issafeinteger'); 6 | 7 | 8 | /** 9 | * isSafeNaturalNumber() 10 | * 11 | * Determine whether this value is a safe, natural number: 12 | * • `safe` | `<= Number.MAX_SAFE_INTEGER` (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER) 13 | * • `natural` | `> 0 && !== Infinity && !== NaN && Math.floor(x) === x` (positive, non-zero, finite, round number. In other words, no funny business -- aka "positive, non-zero integer") 14 | * --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 15 | * @param {Ref} value 16 | * --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 17 | * @returns {Boolean} 18 | */ 19 | 20 | module.exports = function isSafeNaturalNumber(value) { 21 | 22 | // Return false for: 23 | // • NaN 24 | // • Infinity / -Infinity 25 | // • 0 / -0 26 | // • fractions 27 | // • negative integers 28 | // • and integers greater than `Number.MAX_SAFE_INTEGER` 29 | // 30 | // Otherwise, return true! 31 | // 32 | // > For more on `Number.isSafeInteger()`, check out MDN: 33 | // > https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger 34 | 35 | // Note that, eventually, we can just do: 36 | // ``` 37 | // return Number.isSafeInteger(value) && value > 0; 38 | // ``` 39 | 40 | // But for compatibility with legacy versions of Node.js, we do: 41 | // (implementation borrowed from https://github.com/lodash/lodash/blob/4.17.2/lodash.js#L12094) 42 | return lodash4IsSafeInteger(value) && value > 0; 43 | 44 | }; 45 | -------------------------------------------------------------------------------- /lib/waterline/utils/query/private/normalize-pk-value-or-values.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | 5 | var util = require('util'); 6 | var _ = require('@sailshq/lodash'); 7 | var flaverr = require('flaverr'); 8 | var normalizePkValue = require('./normalize-pk-value'); 9 | 10 | 11 | /** 12 | * normalizePkValueOrValues() 13 | * 14 | * Validate and normalize an array of pk values, OR a single pk value, into a consistent format. 15 | * 16 | * > Internally, this uses the `normalizePkValue()` utility to check/normalize each 17 | * > primary key value before returning them. If an array is provided, and if it contains 18 | * > _more than one pk value that is exactly the same_, then the duplicates will be stripped 19 | * > out. 20 | * 21 | * ------------------------------------------------------------------------------------------ 22 | * @param {Array|String|Number} pkValueOrPkValues 23 | * @param {String} expectedPkType [either "number" or "string"] 24 | * ------------------------------------------------------------------------------------------ 25 | * @returns {Array} 26 | * A valid, homogeneous array of primary key values that are guaranteed 27 | * to match the specified `expectedPkType`. 28 | * > WE should NEVER rely on this array coming back in a particular order. 29 | * > (Could change at any time.) 30 | * ------------------------------------------------------------------------------------------ 31 | * @throws {Error} if invalid 32 | * @property {String} code (=== "E_INVALID_PK_VALUE") 33 | */ 34 | 35 | module.exports = function normalizePkValueOrValues (pkValueOrPkValues, expectedPkType){ 36 | 37 | // Check usage 38 | if (expectedPkType !== 'string' && expectedPkType !== 'number') { 39 | throw new Error('Consistency violation: The internal normalizePkValueOrValues() utility must always be called with a valid second argument ("string" or "number"). But instead, got: '+util.inspect(expectedPkType, {depth:5})+''); 40 | } 41 | 42 | 43 | // Our normalized result. 44 | var pkValues; 45 | 46 | // If a singular string or number was provided, convert it into an array. 47 | if (_.isString(pkValueOrPkValues) || _.isNumber(pkValueOrPkValues)) { 48 | pkValues = [ pkValueOrPkValues ]; 49 | } 50 | // Otherwise, we'll assume it must already be an array. 51 | // (Don't worry, we'll validate it momentarily.) 52 | else { 53 | pkValues = pkValueOrPkValues; 54 | } 55 | //>- 56 | 57 | 58 | // Now, handle the case where something completely invalid was provided. 59 | if (!_.isArray(pkValues)) { 60 | throw flaverr('E_INVALID_PK_VALUE', new Error('Expecting either an individual primary key value (a '+expectedPkType+') or a homogeneous array of primary key values ('+expectedPkType+'s). But instead got a '+(typeof pkValues)+': '+util.inspect(pkValues,{depth:5})+'')); 61 | }//-• 62 | 63 | 64 | // Now that we most definitely have an array, ensure that it doesn't contain anything 65 | // strange, curious, or malevolent by looping through and calling `normalizePkValue()` 66 | // on each item. 67 | pkValues = _.map(pkValues, function (thisPkValue){ 68 | 69 | // Return this primary key value, which might have been coerced. 70 | try { 71 | return normalizePkValue(thisPkValue, expectedPkType); 72 | } catch (e) { 73 | switch (e.code) { 74 | case 'E_INVALID_PK_VALUE': 75 | throw flaverr('E_INVALID_PK_VALUE', new Error( 76 | ( 77 | _.isArray(pkValueOrPkValues) ? 78 | 'One of the values in the provided array' : 79 | 'The provided value' 80 | )+' is not valid primary key value. '+e.message 81 | )); 82 | default: throw e; 83 | } 84 | } 85 | 86 | });// 87 | 88 | 89 | // Ensure uniqueness. 90 | // (Strip out any duplicate pk values.) 91 | pkValues = _.uniq(pkValues); 92 | 93 | 94 | // Return the normalized array of pk values. 95 | return pkValues; 96 | 97 | }; 98 | 99 | -------------------------------------------------------------------------------- /lib/waterline/utils/query/verify-model-method-context.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | 5 | var flaverr = require('flaverr'); 6 | 7 | /** 8 | * verifyModelMethodContext() 9 | * 10 | * Take a look at the provided reference (presumably the `this` context of a 11 | * model method when it runs) and give it a sniff to make sure it's _probably_ 12 | * a Sails/Waterline model. 13 | * 14 | * If it's definitely NOT a Sails/Waterline model, then throw a usage error 15 | * that explains that the model method seems to have been run from an invalid 16 | * context, and throw out some ideas about what you might do about that. 17 | * 18 | * > This utility is designed exclusively for use by the model methods defined 19 | * > within Waterline core. 20 | * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 21 | * 22 | * @param {Ref} context 23 | * The context (`this`) that this Waterline model method was invoked with. 24 | * 25 | * @throws {Error} If the context is not a model. 26 | * @property {String} name :: 'UsageError' 27 | * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 28 | */ 29 | 30 | module.exports = function verifyModelMethodContext(context) { 31 | 32 | if (!context.waterline) { 33 | throw flaverr({ name: 'UsageError' }, new Error( 34 | 'Model method called from an unexpected context. Expected `this` to refer to a Sails/Waterline '+ 35 | 'model, but it doesn\'t seem to. (This sometimes occurs when passing a model method directly '+ 36 | 'through as the argument for something like `async.eachSeries()` or `.stream().eachRecord()`. '+ 37 | 'If that\'s what happened here, then just use a wrapper function.) For further help, see '+ 38 | 'http://sailsjs.com/support.' 39 | )); 40 | } 41 | 42 | }; 43 | 44 | -------------------------------------------------------------------------------- /lib/waterline/utils/system/collection-builder.js: -------------------------------------------------------------------------------- 1 | var _ = require('@sailshq/lodash'); 2 | 3 | 4 | // ██████╗ ██╗ ██╗██╗██╗ ██████╗ ██╗ ██╗██╗ ██╗███████╗ 5 | // ██╔══██╗██║ ██║██║██║ ██╔══██╗ ██║ ██║██║ ██║██╔════╝ 6 | // ██████╔╝██║ ██║██║██║ ██║ ██║ ██║ ██║██║ ██║█████╗ 7 | // ██╔══██╗██║ ██║██║██║ ██║ ██║ ██║ ██║╚██╗ ██╔╝██╔══╝ 8 | // ██████╔╝╚██████╔╝██║███████╗██████╔╝ ███████╗██║ ╚████╔╝ ███████╗ 9 | // ╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚══════╝ 10 | // 11 | // ██╗ ██╗██╗ ███╗ ███╗ ██████╗ ██████╗ ███████╗██╗ 12 | // ██║ ██║██║ ████╗ ████║██╔═══██╗██╔══██╗██╔════╝██║ 13 | // ██║ █╗ ██║██║ ██╔████╔██║██║ ██║██║ ██║█████╗ ██║ 14 | // ██║███╗██║██║ ██║╚██╔╝██║██║ ██║██║ ██║██╔══╝ ██║ 15 | // ╚███╔███╔╝███████╗ ██║ ╚═╝ ██║╚██████╔╝██████╔╝███████╗███████╗ 16 | // ╚══╝╚══╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝ 17 | // 18 | // Normalize a Waterline model instance and attaches the correct datastore, returning a "live model". 19 | module.exports = function CollectionBuilder(collection, datastores, context) { 20 | // ╦ ╦╔═╗╦ ╦╔╦╗╔═╗╔╦╗╔═╗ 21 | // ╚╗╔╝╠═╣║ ║ ║║╠═╣ ║ ║╣ 22 | // ╚╝ ╩ ╩╩═╝╩═╩╝╩ ╩ ╩ ╚═╝ 23 | 24 | // Throw Error if no Tablename/Identity is set 25 | if (!_.has(collection.prototype, 'tableName') && !_.has(collection.prototype, 'identity')) { 26 | throw new Error('A tableName or identity property must be set.'); 27 | } 28 | 29 | // Find the datastores used by this collection. If none are specified check 30 | // if a default datastores exist. 31 | // if (!_.has(collection.prototype, 'datastore')) { 32 | if (collection.prototype.datastore === undefined) { 33 | 34 | // Check if a default datastore was specified 35 | if (!_.has(datastores, 'default')) { 36 | throw new Error('No `datastore` was specified in the definition for model `' + collection.prototype.identity+'`, and there is no default datastore (i.e. defined as "default") to fall back to. (Usually, if the "default" datastore is missing, it means the ORM is not set up correctly.)'); 37 | } 38 | 39 | // Set the datastore as the default 40 | collection.prototype.datastore = 'default'; 41 | } 42 | 43 | 44 | // ╔═╗╔═╗╔╦╗ ┌─┐┌─┐┌┬┐┬┬ ┬┌─┐ ┌┬┐┌─┐┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐┌─┐┌─┐ 45 | // ╚═╗║╣ ║ ├─┤│ │ │└┐┌┘├┤ ││├─┤ │ ├─┤└─┐ │ │ │├┬┘├┤ └─┐ 46 | // ╚═╝╚═╝ ╩ ┴ ┴└─┘ ┴ ┴ └┘ └─┘ ─┴┘┴ ┴ ┴ ┴ ┴└─┘ ┴ └─┘┴└─└─┘└─┘ 47 | 48 | // Set the datastore used for the adapter 49 | var datastoreName = collection.prototype.datastore; 50 | 51 | // Ensure the named datastore exists 52 | if (!_.has(datastores, datastoreName)) { 53 | if (datastoreName !== 'default'){ 54 | throw new Error('Unrecognized datastore (`' + datastoreName + '`) specified in the definition for model `' + collection.prototype.identity + '`. Please make sure it exists. (If you\'re unsure, use "default".)'); 55 | } 56 | else { 57 | throw new Error('Unrecognized datastore (`' + datastoreName + '`) specified in the definition for model `' + collection.prototype.identity + '`. (Usually, if the "default" datastore is missing, it means the ORM is not set up correctly.)'); 58 | } 59 | } 60 | 61 | // Add the collection to the datastore listing 62 | datastores[datastoreName].collections.push(collection.prototype.identity); 63 | 64 | 65 | // ╦╔╗╔╔═╗╔╦╗╔═╗╔╗╔╔╦╗╦╔═╗╔╦╗╔═╗ 66 | // ║║║║╚═╗ ║ ╠═╣║║║ ║ ║╠═╣ ║ ║╣ 67 | // ╩╝╚╝╚═╝ ╩ ╩ ╩╝╚╝ ╩ ╩╩ ╩ ╩ ╚═╝ 68 | var liveModel = new collection(context, datastores[datastoreName]); 69 | 70 | return liveModel; 71 | }; 72 | -------------------------------------------------------------------------------- /lib/waterline/utils/system/datastore-builder.js: -------------------------------------------------------------------------------- 1 | // ██████╗ █████╗ ████████╗ █████╗ ███████╗████████╗ ██████╗ ██████╗ ███████╗ 2 | // ██╔══██╗██╔══██╗╚══██╔══╝██╔══██╗██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗██╔════╝ 3 | // ██║ ██║███████║ ██║ ███████║███████╗ ██║ ██║ ██║██████╔╝█████╗ 4 | // ██║ ██║██╔══██║ ██║ ██╔══██║╚════██║ ██║ ██║ ██║██╔══██╗██╔══╝ 5 | // ██████╔╝██║ ██║ ██║ ██║ ██║███████║ ██║ ╚██████╔╝██║ ██║███████╗ 6 | // ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ 7 | // 8 | // ██████╗ ██╗ ██╗██╗██╗ ██████╗ ███████╗██████╗ 9 | // ██╔══██╗██║ ██║██║██║ ██╔══██╗██╔════╝██╔══██╗ 10 | // ██████╔╝██║ ██║██║██║ ██║ ██║█████╗ ██████╔╝ 11 | // ██╔══██╗██║ ██║██║██║ ██║ ██║██╔══╝ ██╔══██╗ 12 | // ██████╔╝╚██████╔╝██║███████╗██████╔╝███████╗██║ ██║ 13 | // ╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝ ╚══════╝╚═╝ ╚═╝ 14 | // 15 | // Builds up the set of datastores used by the various Waterline Models. 16 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 17 | // TODO: verify that last part of the statement (not seeing how this is related to "models") 18 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 19 | 20 | var _ = require('@sailshq/lodash'); 21 | 22 | module.exports = function DatastoreBuilder(adapters, datastoreConfigs) { 23 | var datastores = {}; 24 | 25 | // For each datastore config, create a normalized, namespaced, dictionary. 26 | _.each(datastoreConfigs, function(config, datastoreName) { 27 | // Ensure that an `adapter` is specified 28 | if (!_.has(config, 'adapter')) { 29 | throw new Error('The datastore ' + datastoreName + ' is missing a required property (`adapter`). You should indicate the name of one of your adapters.'); 30 | } 31 | 32 | // Ensure that the named adapter is present in the adapters that were passed 33 | // in. 34 | if (!_.has(adapters, config.adapter)) { 35 | // Check that the adapter's name was a string 36 | if (!_.isString(config.adapter)) { 37 | throw new Error('Invalid `adapter` property in datastore ' + datastoreName + '. It should be a string (the name of one of the adapters you passed into `waterline.initialize()`).'); 38 | } 39 | 40 | // Otherwise throw an unknown error 41 | throw new Error('Unknown adapter ' + config.adapter + ' for datastore ' + datastoreName + '. You should double-check that the connection\'s `adapter` property matches the name of one of your adapters. Or perhaps you forgot to include your adapter when you called `waterline.initialize()`.)'); 42 | } 43 | 44 | // Shallow-merge the adapter defaults underneath with the user-defined config. 45 | var datastoreConfig = _.extend({}, adapters[config.adapter].defaults, config); 46 | 47 | // Build the datastore config 48 | datastores[datastoreName] = { 49 | config: datastoreConfig, 50 | adapter: adapters[config.adapter], 51 | collections: []// << TODO: fix naming 52 | }; 53 | }); 54 | 55 | return datastores; 56 | }; 57 | -------------------------------------------------------------------------------- /lib/waterline/utils/system/has-schema-check.js: -------------------------------------------------------------------------------- 1 | // ██╗ ██╗ █████╗ ███████╗ ███████╗ ██████╗██╗ ██╗███████╗███╗ ███╗ █████╗ 2 | // ██║ ██║██╔══██╗██╔════╝ ██╔════╝██╔════╝██║ ██║██╔════╝████╗ ████║██╔══██╗ 3 | // ███████║███████║███████╗ ███████╗██║ ███████║█████╗ ██╔████╔██║███████║ 4 | // ██╔══██║██╔══██║╚════██║ ╚════██║██║ ██╔══██║██╔══╝ ██║╚██╔╝██║██╔══██║ 5 | // ██║ ██║██║ ██║███████║ ███████║╚██████╗██║ ██║███████╗██║ ╚═╝ ██║██║ ██║ 6 | // ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ 7 | // 8 | // ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗ 9 | // ██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝ 10 | // ██║ ███████║█████╗ ██║ █████╔╝ 11 | // ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ 12 | // ╚██████╗██║ ██║███████╗╚██████╗██║ ██╗ 13 | // ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝ 14 | // 15 | // Returns TRUE/FALSE if a collection has it's `hasSchema` flag set. 16 | 17 | var _ = require('@sailshq/lodash'); 18 | 19 | module.exports = function hasSchemaCheck(context) { 20 | // If hasSchema is defined on the collection, return the value 21 | if (_.has(Object.getPrototypeOf(context), 'hasSchema')) { 22 | var proto = Object.getPrototypeOf(context); 23 | if (!_.isUndefined(proto.hasSchema)) { 24 | return Object.getPrototypeOf(context).hasSchema; 25 | } 26 | } 27 | 28 | // Grab the first connection used 29 | if (!context.connection || !_.isArray(context.connection)) { 30 | return true; 31 | } 32 | 33 | var connection = context.connections[_.first(context.connection)]; 34 | 35 | // Check the user defined config 36 | if (_.has(connection, 'config') && _.has(connection.config, 'schema')) { 37 | return connection.config.schema; 38 | } 39 | 40 | // Check the defaults defined in the adapter 41 | if (!_.has(connection, 'adapter')) { 42 | return true; 43 | } 44 | 45 | if (!_.has(connection.adapter, 'schema')) { 46 | return true; 47 | } 48 | 49 | return connection.adapter.schema; 50 | }; 51 | -------------------------------------------------------------------------------- /lib/waterline/utils/system/lifecycle-callback-builder.js: -------------------------------------------------------------------------------- 1 | // ██████╗ ██╗ ██╗██╗██╗ ██████╗ 2 | // ██╔══██╗██║ ██║██║██║ ██╔══██╗ 3 | // ██████╔╝██║ ██║██║██║ ██║ ██║ 4 | // ██╔══██╗██║ ██║██║██║ ██║ ██║ 5 | // ██████╔╝╚██████╔╝██║███████╗██████╔╝ 6 | // ╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝ 7 | // 8 | // ██╗ ██╗███████╗███████╗ ██████╗██╗ ██╗ ██████╗██╗ ███████╗ 9 | // ██║ ██║██╔════╝██╔════╝██╔════╝╚██╗ ██╔╝██╔════╝██║ ██╔════╝ 10 | // ██║ ██║█████╗ █████╗ ██║ ╚████╔╝ ██║ ██║ █████╗ 11 | // ██║ ██║██╔══╝ ██╔══╝ ██║ ╚██╔╝ ██║ ██║ ██╔══╝ 12 | // ███████╗██║██║ ███████╗╚██████╗ ██║ ╚██████╗███████╗███████╗ 13 | // ╚══════╝╚═╝╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═════╝╚══════╝╚══════╝ 14 | // 15 | // ██████╗ █████╗ ██╗ ██╗ ██████╗ █████╗ ██████╗██╗ ██╗███████╗ 16 | // ██╔════╝██╔══██╗██║ ██║ ██╔══██╗██╔══██╗██╔════╝██║ ██╔╝██╔════╝ 17 | // ██║ ███████║██║ ██║ ██████╔╝███████║██║ █████╔╝ ███████╗ 18 | // ██║ ██╔══██║██║ ██║ ██╔══██╗██╔══██║██║ ██╔═██╗ ╚════██║ 19 | // ╚██████╗██║ ██║███████╗███████╗██████╔╝██║ ██║╚██████╗██║ ██╗███████║ 20 | // ╚═════╝╚═╝ ╚═╝╚══════╝╚══════╝╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝ 21 | // 22 | 23 | var _ = require('@sailshq/lodash'); 24 | 25 | module.exports = function LifecycleCallbackBuilder(context) { 26 | // Build a list of accepted lifecycle callbacks 27 | var validCallbacks = [ 28 | 'beforeValidate', 29 | 'afterValidate', 30 | 'beforeUpdate', 31 | 'afterUpdate', 32 | 'beforeCreate', 33 | 'afterCreate', 34 | 'beforeDestroy', 35 | 'afterDestroy', 36 | 'beforeFind', 37 | 'afterFind', 38 | 'beforeFindOne', 39 | 'afterFindOne' 40 | ]; 41 | 42 | // Hold a mapping of functions to run at various times in the query lifecycle 43 | var callbacks = {}; 44 | 45 | // Look for each type of callback in the collection 46 | _.each(validCallbacks, function(callbackName) { 47 | // If the callback isn't defined on the model there is nothing to do 48 | if (_.isUndefined(context[callbackName])) { 49 | return; 50 | } 51 | 52 | callbacks[callbackName] = context[callbackName]; 53 | }); 54 | 55 | return callbacks; 56 | }; 57 | -------------------------------------------------------------------------------- /lib/waterline/utils/system/reserved-property-names.js: -------------------------------------------------------------------------------- 1 | // ██████╗ ███████╗███████╗███████╗██████╗ ██╗ ██╗███████╗██████╗ 2 | // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██║ ██║██╔════╝██╔══██╗ 3 | // ██████╔╝█████╗ ███████╗█████╗ ██████╔╝██║ ██║█████╗ ██║ ██║ 4 | // ██╔══██╗██╔══╝ ╚════██║██╔══╝ ██╔══██╗╚██╗ ██╔╝██╔══╝ ██║ ██║ 5 | // ██║ ██║███████╗███████║███████╗██║ ██║ ╚████╔╝ ███████╗██████╔╝ 6 | // ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚═════╝ 7 | // 8 | // ██████╗ ██████╗ ██████╗ ██████╗ ███████╗██████╗ ████████╗██╗ ██╗ 9 | // ██╔══██╗██╔══██╗██╔═══██╗██╔══██╗██╔════╝██╔══██╗╚══██╔══╝╚██╗ ██╔╝ 10 | // ██████╔╝██████╔╝██║ ██║██████╔╝█████╗ ██████╔╝ ██║ ╚████╔╝ 11 | // ██╔═══╝ ██╔══██╗██║ ██║██╔═══╝ ██╔══╝ ██╔══██╗ ██║ ╚██╔╝ 12 | // ██║ ██║ ██║╚██████╔╝██║ ███████╗██║ ██║ ██║ ██║ 13 | // ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ 14 | // 15 | // ███╗ ██╗ █████╗ ███╗ ███╗███████╗███████╗ 16 | // ████╗ ██║██╔══██╗████╗ ████║██╔════╝██╔════╝ 17 | // ██╔██╗ ██║███████║██╔████╔██║█████╗ ███████╗ 18 | // ██║╚██╗██║██╔══██║██║╚██╔╝██║██╔══╝ ╚════██║ 19 | // ██║ ╚████║██║ ██║██║ ╚═╝ ██║███████╗███████║ 20 | // ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝ 21 | // 22 | 23 | module.exports = [ 24 | 'defaultsTo', 25 | 'primaryKey', 26 | 'autoIncrement', 27 | 'unique', 28 | 'index', 29 | 'collection', 30 | 'dominant', 31 | 'through', 32 | 'columnName', 33 | 'foreignKey', 34 | 'references', 35 | 'on', 36 | 'groupKey', 37 | 'model', 38 | 'via', 39 | 'size', 40 | 'example', 41 | 'validationMessage', 42 | 'validations', 43 | 'populateSettings', 44 | 'onKey', 45 | 'protected', 46 | 'meta' 47 | ]; 48 | -------------------------------------------------------------------------------- /lib/waterline/utils/system/reserved-validation-names.js: -------------------------------------------------------------------------------- 1 | // ██████╗ ███████╗███████╗███████╗██████╗ ██╗ ██╗███████╗██████╗ 2 | // ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██║ ██║██╔════╝██╔══██╗ 3 | // ██████╔╝█████╗ ███████╗█████╗ ██████╔╝██║ ██║█████╗ ██║ ██║ 4 | // ██╔══██╗██╔══╝ ╚════██║██╔══╝ ██╔══██╗╚██╗ ██╔╝██╔══╝ ██║ ██║ 5 | // ██║ ██║███████╗███████║███████╗██║ ██║ ╚████╔╝ ███████╗██████╔╝ 6 | // ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚═════╝ 7 | // 8 | // ██╗ ██╗ █████╗ ██╗ ██╗██████╗ █████╗ ████████╗██╗ ██████╗ ███╗ ██╗ 9 | // ██║ ██║██╔══██╗██║ ██║██╔══██╗██╔══██╗╚══██╔══╝██║██╔═══██╗████╗ ██║ 10 | // ██║ ██║███████║██║ ██║██║ ██║███████║ ██║ ██║██║ ██║██╔██╗ ██║ 11 | // ╚██╗ ██╔╝██╔══██║██║ ██║██║ ██║██╔══██║ ██║ ██║██║ ██║██║╚██╗██║ 12 | // ╚████╔╝ ██║ ██║███████╗██║██████╔╝██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║ 13 | // ╚═══╝ ╚═╝ ╚═╝╚══════╝╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ 14 | // 15 | // ███╗ ██╗ █████╗ ███╗ ███╗███████╗███████╗ 16 | // ████╗ ██║██╔══██╗████╗ ████║██╔════╝██╔════╝ 17 | // ██╔██╗ ██║███████║██╔████╔██║█████╗ ███████╗ 18 | // ██║╚██╗██║██╔══██║██║╚██╔╝██║██╔══╝ ╚════██║ 19 | // ██║ ╚████║██║ ██║██║ ╚═╝ ██║███████╗███████║ 20 | // ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝ 21 | // 22 | 23 | module.exports = [ 24 | 'after', 25 | 'alpha', 26 | 'alphadashed', 27 | 'alphanumeric', 28 | 'alphanumericdashed', 29 | 'before', 30 | 'contains', 31 | 'creditcard', 32 | 'datetime', 33 | 'decimal', 34 | 'email', 35 | 'finite', 36 | 'float', 37 | 'hexadecimal', 38 | 'hexColor', 39 | 'in', 40 | 'int', 41 | 'integer', 42 | 'ip', 43 | 'ipv4', 44 | 'ipv6', 45 | 'is', 46 | 'lowercase', 47 | 'max', 48 | 'maxLength', 49 | 'min', 50 | 'minLength', 51 | 'notRegex', 52 | 'notContains', 53 | 'notIn', 54 | 'notNull', 55 | 'numeric', 56 | 'required', 57 | 'regex', 58 | 'truthy', 59 | 'uppercase', 60 | 'url', 61 | 'urlish', 62 | 'uuid', 63 | 'uuidv3', 64 | 'uuidv4' 65 | ]; 66 | -------------------------------------------------------------------------------- /lib/waterline/utils/system/types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Types Supported By Schemas 3 | */ 4 | 5 | module.exports = [ 6 | 'string', 7 | 'number', 8 | 'boolean', 9 | 'json',// << generic json (`'*'`) 10 | 'ref' // < passed straight through to adapter 11 | ]; 12 | -------------------------------------------------------------------------------- /lib/waterline/utils/system/validate-datastore-connectivity.js: -------------------------------------------------------------------------------- 1 | var _ = require('@sailshq/lodash'); 2 | 3 | /** 4 | * validateDatastoreConnectivity() 5 | * 6 | * Validates connectivity to a datastore by trying to acquire and release 7 | * connection. 8 | * 9 | * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 10 | * @param {Ref} datastore 11 | * 12 | * @param {Function} done 13 | * @param {Error?} err [if an error occured] 14 | * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 15 | */ 16 | 17 | module.exports = function validateDatastoreConnectivity(datastore, done) { 18 | var adapterDSEntry = _.get(datastore.adapter.datastores, datastore.config.identity); 19 | 20 | // skip validation if `getConnection` and `releaseConnection` methods do not exist. 21 | // aka pretend everything is OK 22 | if (!_.has(adapterDSEntry.driver, 'getConnection') || !_.has(adapterDSEntry.driver, 'releaseConnection')) { 23 | return done(); 24 | } 25 | 26 | // try to acquire connection. 27 | adapterDSEntry.driver.getConnection({ 28 | manager: adapterDSEntry.manager 29 | }, function(err, report) { 30 | if (err) { 31 | return done(err); 32 | } 33 | 34 | // release connection. 35 | adapterDSEntry.driver.releaseConnection({ 36 | connection: report.connection 37 | }, function(err) { 38 | if (err) { 39 | return done(err); 40 | } 41 | 42 | return done(); 43 | });// 44 | });// 45 | }; 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "waterline", 3 | "description": "An ORM for Node.js and the Sails framework", 4 | "version": "0.15.2", 5 | "homepage": "http://waterlinejs.org", 6 | "contributors": [ 7 | { 8 | "name": "seerepo", 9 | "github": "https://github.com/balderdashy/waterline/graphs/contributors" 10 | } 11 | ], 12 | "dependencies": { 13 | "@sailshq/lodash": "^3.10.2", 14 | "anchor": "^1.2.0", 15 | "async": "2.6.4", 16 | "encrypted-attr": "1.0.6", 17 | "flaverr": "^1.9.2", 18 | "lodash.issafeinteger": "4.0.4", 19 | "parley": "^3.3.2", 20 | "rttc": "^10.0.0-1", 21 | "waterline-schema": "^1.0.0-20", 22 | "waterline-utils": "^1.3.7" 23 | }, 24 | "devDependencies": { 25 | "eslint": "4.11.0", 26 | "mocha": "3.0.2" 27 | }, 28 | "keywords": [ 29 | "mvc", 30 | "orm", 31 | "mysql", 32 | "postgresql", 33 | "redis", 34 | "mongodb", 35 | "active-record", 36 | "waterline", 37 | "sails", 38 | "sails.js" 39 | ], 40 | "repository": "git://github.com/balderdashy/waterline.git", 41 | "main": "./lib/waterline", 42 | "scripts": { 43 | "test": "nodever=`node -e \"console.log('\\`node -v\\`'[1]);\"` && if [ $nodever != \"0\" ]; then npm run lint; fi && npm run custom-tests", 44 | "custom-tests": "node ./node_modules/mocha/bin/mocha test --recursive", 45 | "lint": "node ./node_modules/eslint/bin/eslint . --max-warnings=0 --ignore-pattern 'test/'", 46 | "browserify": "rm -rf .dist && mkdir .dist && browserify lib/waterline.js -s Waterline | uglifyjs > .dist/waterline.min.js" 47 | }, 48 | "engines": { 49 | "node": ">=4" 50 | }, 51 | "bugs": { 52 | "url": "https://sailsjs.com/bugs" 53 | }, 54 | "license": "MIT" 55 | } 56 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | // ╔═╗╔═╗╦ ╦╔╗╔╔╦╗┬─┐┌─┐ ┌─┐┬ ┬┌─┐┬─┐┬─┐┬┌┬┐┌─┐ 3 | // ║╣ ╚═╗║ ║║║║ ║ ├┬┘│ │ │└┐┌┘├┤ ├┬┘├┬┘│ ││├┤ 4 | // o╚═╝╚═╝╩═╝╩╝╚╝ ╩ ┴└─└─┘ └─┘ └┘ └─┘┴└─┴└─┴─┴┘└─┘ 5 | // ┌─ ┌─┐┌─┐┬─┐ ┌─┐┬ ┬┌┬┐┌─┐┌┬┐┌─┐┌┬┐┌─┐┌┬┐ ┌┬┐┌─┐┌─┐┌┬┐┌─┐ ─┐ 6 | // │ ├┤ │ │├┬┘ ├─┤│ │ │ │ ││││├─┤ │ ├┤ ││ │ ├┤ └─┐ │ └─┐ │ 7 | // └─ └ └─┘┴└─ ┴ ┴└─┘ ┴ └─┘┴ ┴┴ ┴ ┴ └─┘─┴┘ ┴ └─┘└─┘ ┴ └─┘ ─┘ 8 | // > An .eslintrc configuration override for use with the tests in this directory. 9 | // 10 | // (See .eslintrc in the root directory of this package for more info.) 11 | 12 | "extends": [ 13 | "../.eslintrc" 14 | ], 15 | 16 | "env": { 17 | "mocha": true 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /test/alter-migrations/strategy.alter.schema.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../lib/waterline'); 4 | var MigrateHelper = require('../support/migrate.helper'); 5 | 6 | describe.skip('Alter Mode Recovery with an enforced schema', function () { 7 | 8 | var record; 9 | 10 | // Create an adapter and set some existing data 11 | before(function (done) { 12 | 13 | // Hold data to be used in tests 14 | var persistentData = [{ 15 | id: 1, 16 | name: 'batman', 17 | car: 'batmobile', 18 | age: 50 19 | }]; 20 | 21 | var adapter = { 22 | registerDatastore: function (connection, collections, cb) { 23 | cb(null, null); 24 | }, 25 | define: function (connectionName, collectionName, definition, cb) { 26 | this.describe(connectionName, collectionName, function (err, schema) { 27 | cb(null, schema); 28 | }); 29 | }, 30 | describe: function (connectionName, collectionName, cb, connection) { 31 | var schema = { 32 | name: { type: 'string' }, 33 | age: { type: 'number' } 34 | }; 35 | cb(null, (persistentData.length === 1) ? schema : undefined); 36 | }, 37 | find: function (connectionName, collectionName, options, cb, connection) { 38 | if(!options.select && !options.where) { 39 | return cb(null, persistentData); 40 | } 41 | 42 | var results; 43 | if(!options.where) { 44 | results = persistentData; 45 | } 46 | else { 47 | results = _.filter(persistentData, options.where); 48 | } 49 | 50 | // Psuedo support for select (needed to act like a real adapter) 51 | if(options.select && _.isArray(options.select) && options.select.length) { 52 | 53 | // Force ID in query 54 | options.select.push('id'); 55 | 56 | results = _.map(results, function(result) { 57 | return _.pick(result, options.select); 58 | }); 59 | } 60 | 61 | cb(null, results); 62 | }, 63 | create: function (connectionName, collectionName, data, cb, connection) { 64 | var schemaData = _.pick(data, ['id', 'name', 'age']); 65 | persistentData.push(schemaData); 66 | cb(null, data); 67 | }, 68 | drop: function (connectionName, collectionName, relations, cb, connection) { 69 | persistentData = []; 70 | cb(null); 71 | } 72 | }; 73 | 74 | var waterline = new Waterline(); 75 | 76 | // Build up a model to test 77 | var PersonModel = { 78 | identity: 'Person', 79 | tableName: 'person_table', 80 | datastore: 'test_alter', 81 | migrate: 'alter', 82 | adapter: 'fake', 83 | schema: true, 84 | attributes: { 85 | name: 'string', 86 | age: 'integer', 87 | id: 'integer' 88 | } 89 | }; 90 | 91 | // Create a connection using the stub adapter we created above 92 | var connections = { 93 | 'test_alter': { 94 | adapter: 'fake' 95 | } 96 | }; 97 | 98 | // Build up the named adapters object 99 | var adapters = {fake: adapter}; 100 | 101 | // Build the collections and find the record 102 | var PersonCollection = Waterline.Model.extend(PersonModel); 103 | waterline.registerModel(PersonCollection); 104 | waterline.initialize({adapters: adapters, datastores: connections}, function (err, data) { 105 | if (err) return done(err); 106 | 107 | MigrateHelper(data, function(err) { 108 | data.collections.person.findOne({id: 1}, function (err, found) { 109 | if (err) return done(err); 110 | record = found; 111 | done(); 112 | }); 113 | }); 114 | }); 115 | }); 116 | 117 | 118 | it('should keep data already in the data store', function() { 119 | assert(record); 120 | }); 121 | 122 | it('should include the attributes in the schema', function() { 123 | assert.equal(record.id, 1); 124 | assert.equal(record.name, 'batman'); 125 | assert.equal(record.age, 50); 126 | }); 127 | 128 | it('should not include the attributes NOT in the schema', function() { 129 | assert(!record.car); 130 | }); 131 | 132 | }); 133 | -------------------------------------------------------------------------------- /test/alter-migrations/strategy.alter.schemaless.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../lib/waterline'); 4 | var MigrateHelper = require('../support/migrate.helper'); 5 | 6 | describe.skip('Alter Mode Recovery with schemaless data', function () { 7 | 8 | var record; 9 | 10 | // Create an adapter and set some existing data 11 | before(function (done) { 12 | 13 | // Hold data to be used in tests 14 | var persistentData = [{ 15 | id: 1, 16 | name: 'batman', 17 | car: 'batmobile', 18 | age: 50 19 | }]; 20 | 21 | var adapter = { 22 | registerDatastore: function (connection, collections, cb) { 23 | cb(null, null); 24 | }, 25 | define: function (connectionName, collectionName, definition, cb) { 26 | this.describe(connectionName, collectionName, function (err, schema) { 27 | cb(null, schema); 28 | }); 29 | }, 30 | describe: function (connectionName, collectionName, cb, connection) { 31 | var schema = { 32 | name: { type: 'string' }, 33 | age: { type: 'number' } 34 | }; 35 | cb(null, (persistentData.length === 1) ? schema : undefined); 36 | }, 37 | find: function (connectionName, collectionName, options, cb, connection) { 38 | 39 | if(!options.select && !options.where) { 40 | return cb(null, persistentData); 41 | } 42 | 43 | var results; 44 | if(!options.where) { 45 | results = persistentData; 46 | } 47 | else { 48 | results = _.filter(persistentData, options.where); 49 | } 50 | // Psuedo support for select (needed to act like a real adapter) 51 | if(options.select) { 52 | 53 | // Force ID in query 54 | options.select.push('id'); 55 | 56 | results = _.map(results, function(result) { 57 | return _.pick(result, options.select); 58 | }); 59 | } 60 | 61 | cb(null, results); 62 | }, 63 | create: function (connectionName, collectionName, data, cb, connection) { 64 | persistentData.push(data); 65 | cb(null, data); 66 | }, 67 | drop: function (connectionName, collectionName, relations, cb, connection) { 68 | persistentData = []; 69 | cb(null); 70 | } 71 | }; 72 | 73 | var waterline = new Waterline(); 74 | 75 | // Build up a model to test 76 | var PersonModel = { 77 | identity: 'Person', 78 | tableName: 'person_table', 79 | datastore: 'test_alter', 80 | migrate: 'alter', 81 | adapter: 'fake', 82 | schema: false, 83 | attributes: { 84 | name: 'string', 85 | age: 'integer', 86 | id: 'integer' 87 | } 88 | }; 89 | 90 | // Create a connection using the stub adapter we created above 91 | var connections = { 92 | 'test_alter': { 93 | adapter: 'fake' 94 | } 95 | }; 96 | 97 | // Build up the named adapters object 98 | var adapters = {fake: adapter}; 99 | 100 | // Build the collections and find the record 101 | var PersonCollection = Waterline.Model.extend(PersonModel); 102 | waterline.registerModel(PersonCollection); 103 | waterline.initialize({adapters: adapters, datastores: connections}, function (err, data) { 104 | if (err) return done(err); 105 | 106 | MigrateHelper(data, function(err) { 107 | data.collections.person.findOne({id: 1}, function (err, found) { 108 | if (err) return done(err); 109 | record = found; 110 | done(); 111 | }); 112 | }); 113 | }); 114 | }); 115 | 116 | 117 | it('should keep data already in the data store', function() { 118 | assert(record); 119 | }); 120 | 121 | it('should include the attributes in the schema', function() { 122 | assert.equal(record.id, 1); 123 | assert.equal(record.name, 'batman'); 124 | assert.equal(record.age, 50); 125 | }); 126 | 127 | it.skip('should include the attributes NOT in the schema', function() { 128 | assert.equal(record.car, 'batmobile'); 129 | }); 130 | 131 | }); 132 | -------------------------------------------------------------------------------- /test/support/fixtures/associations/customer.fixture.js: -------------------------------------------------------------------------------- 1 | var BaseMetaModel = require('../../../../lib/waterline/MetaModel'); 2 | 3 | module.exports = BaseMetaModel.extend({ 4 | identity: 'user', 5 | adapter: 'test', 6 | 7 | attributes: { 8 | 9 | // deals: { 10 | // collection: 'Deal' 11 | // }, 12 | 13 | payments: { 14 | collection: 'Payment' 15 | }, 16 | 17 | name: 'string' 18 | 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /test/support/fixtures/associations/payment.fixture.js: -------------------------------------------------------------------------------- 1 | var BaseMetaModel = require('../../../../lib/waterline/MetaModel'); 2 | 3 | module.exports = BaseMetaModel.extend({ 4 | identity: 'user', 5 | adapter: 'test', 6 | 7 | attributes: { 8 | 9 | customer: { 10 | model: 'Customer' 11 | } 12 | 13 | // deal: { 14 | // model: 'Deal' 15 | // }, 16 | 17 | // amount: { 18 | // type: 'float' 19 | // } 20 | 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /test/support/fixtures/integrator/cache.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var _ = require('@sailshq/lodash'); 5 | var fixtures = { 6 | tables: require('./tables') 7 | }; 8 | 9 | 10 | /** 11 | * Cache 12 | * 13 | * @type {Object} 14 | */ 15 | module.exports = (function() { 16 | var cache = {}; 17 | _.extend(cache, { 18 | user: fixtures.tables.user, 19 | message: fixtures.tables.message, 20 | message_to_user: fixtures.tables.message_to_user, 21 | message_cc_user: fixtures.tables.message_cc_user, 22 | message_bcc_user: fixtures.tables.message_bcc_user 23 | }); 24 | return cache; 25 | })(); 26 | -------------------------------------------------------------------------------- /test/support/fixtures/integrator/finalResults.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Final results of integrator for a use case w/ multiple joins 3 | * with the data from the various fixtures. 4 | * @type {Object} 5 | */ 6 | 7 | 8 | // NOTE 9 | // Currently unused, until the inner join feature is added 10 | // to prune keys of joined child rows. Otherwise there is a lot 11 | // of extra data that isn't worth writing short-term tests to compare against. 12 | 13 | module.exports = { 14 | 15 | // the `multiple.joins.js` fixture. 16 | 'multiple.joins': [{ 17 | id: 10, 18 | subject: 'msgA', 19 | body: 'A test message.', 20 | from: [{ 21 | email: 'sender@thatguy.com', 22 | subject: 'msgA', 23 | body: 'A test message.', 24 | from: 1, 25 | to: [ 26 | [Object], 27 | [Object] 28 | ], 29 | '??????': 10 30 | }, { 31 | email: 'sender@thatguy.com', 32 | subject: 'msgB', 33 | body: 'Another test message.', 34 | from: 1, 35 | to: [], 36 | '??????': 20 37 | }], 38 | to: [{ 39 | email: 'a@recipient.com', 40 | id: 2, 41 | subject: 'msgA', 42 | body: 'A test message.', 43 | from: 1 44 | }, { 45 | email: 'b@recipient.com', 46 | id: 3, 47 | subject: 'msgA', 48 | body: 'A test message.', 49 | from: 1 50 | }], 51 | cc: [{ 52 | email: 'c@recipient.com', 53 | id: 4, 54 | subject: 'msgA', 55 | body: 'A test message.', 56 | from: [ 57 | [Object], 58 | [Object] 59 | ], 60 | to: [ 61 | [Object], 62 | [Object] 63 | ] 64 | }, { 65 | email: 'd@recipient.com', 66 | id: 5, 67 | subject: 'msgA', 68 | body: 'A test message.', 69 | from: [ 70 | [Object], 71 | [Object] 72 | ], 73 | to: [ 74 | [Object], 75 | [Object] 76 | ] 77 | }] 78 | }, { 79 | id: 20, 80 | subject: 'msgB', 81 | body: 'Another test message.', 82 | from: [], 83 | to: [], 84 | cc: [] 85 | }, { 86 | id: 30, 87 | subject: 'msgC', 88 | body: 'Aint sent this one yet.', 89 | from: [{ 90 | subject: 'msgC', 91 | body: 'Aint sent this one yet.', 92 | from: null, 93 | to: [], 94 | '??????': 30 95 | }], 96 | to: [], 97 | cc: [] 98 | }] 99 | 100 | 101 | }; -------------------------------------------------------------------------------- /test/support/fixtures/integrator/joinResults.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Results of joins between our tables of test data. 3 | * @type {Object} 4 | */ 5 | 6 | module.exports = { 7 | 8 | 9 | // Inner join 10 | ___inner___message___message_to_user: [{ 11 | '.id': 1, 12 | '.user_id': 2, 13 | '.message_id': 10, 14 | id: 10, 15 | subject: 'msgA', 16 | body: 'A test message.', 17 | from: 1 18 | }, { 19 | '.id': 2, 20 | '.user_id': 3, 21 | '.message_id': 10, 22 | id: 10, 23 | subject: 'msgA', 24 | body: 'A test message.', 25 | from: 1 26 | }], 27 | 28 | 29 | 30 | // Left outer join: 31 | message___message_to_user: [{ 32 | id: 10, 33 | '.message_id': 10, 34 | '.id': 1, 35 | '.user_id': 2, 36 | subject: 'msgA', 37 | body: 'A test message.', 38 | from: 1 39 | }, { 40 | id: 10, 41 | '.message_id': 10, 42 | '.id': 2, 43 | '.user_id': 3, 44 | subject: 'msgA', 45 | body: 'A test message.', 46 | from: 1 47 | }, { 48 | id: 20, 49 | subject: 'msgB', 50 | body: 'Another test message.', 51 | from: 1 52 | }, { 53 | id: 30, 54 | subject: 'msgC', 55 | body: 'Aint sent this one yet.', 56 | from: null 57 | }], 58 | 59 | 60 | 61 | // Two left outer joins: 62 | message___message_to_user___user: [{ 63 | '..email': 'a@recipient.com', 64 | id: 10, 65 | '.message_id': 10, 66 | '.id': 1, 67 | '.user_id': 2, 68 | '..id': 2, 69 | subject: 'msgA', 70 | body: 'A test message.', 71 | from: 1 72 | }, { 73 | '..email': 'b@recipient.com', 74 | id: 10, 75 | '.message_id': 10, 76 | '.id': 2, 77 | '..id': 3, 78 | '.user_id': 3, 79 | subject: 'msgA', 80 | body: 'A test message.', 81 | from: 1 82 | }, { 83 | id: 20, 84 | subject: 'msgB', 85 | body: 'Another test message.', 86 | from: 1 87 | }, { 88 | id: 30, 89 | subject: 'msgC', 90 | body: 'Aint sent this one yet.', 91 | from: null 92 | }] 93 | }; 94 | -------------------------------------------------------------------------------- /test/support/fixtures/integrator/multiple.joins.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Joins 3 | * 4 | * @type {Array} 5 | */ 6 | module.exports = [ 7 | 8 | // N..M Populate 9 | // (Message has an association "to" which points to a collection of User) 10 | { 11 | parent: 'message', // left table name 12 | parentCollectionIdentity: 'message', 13 | parentKey: 'id', // left table key 14 | alias: 'to', // the `alias` -- e.g. name of association 15 | 16 | child: 'message_to_user', // right table name 17 | childKey: 'message_id', // right table key 18 | childCollectionIdentity: 'message_to_user' 19 | }, 20 | { 21 | alias: 'to', // the `alias` -- e.g. name of association 22 | 23 | parent: 'message_to_user', // left table name 24 | parentCollectionIdentity: 'message_to_user', 25 | parentKey: 'user_id', // left table key 26 | 27 | child: 'user', // right table name 28 | childKey: 'id', // right table key 29 | select: ['id', 'email'], 30 | childCollectionIdentity: 'user' 31 | }, 32 | 33 | // N..1 Populate 34 | // (Message has an association "from" which points to one User) 35 | { 36 | parent: 'message', // left table name 37 | parentCollectionIdentity: 'message', 38 | alias: 'from', // the `alias` -- e.g. name of association 39 | parentKey: 'from', // left table key 40 | 41 | child: 'user', // right table name 42 | childKey: 'id', // right table key 43 | select: ['email', 'id'], 44 | childCollectionIdentity: 'user' 45 | }, 46 | 47 | // N..M Populate 48 | // (Message has an association "cc" which points to a collection of User) 49 | { 50 | parent: 'message', // left table name 51 | parentCollectionIdentity: 'message', 52 | parentKey: 'id', // left table key 53 | alias: 'cc', // the `alias` -- e.g. name of association 54 | 55 | child: 'message_cc_user', // right table name 56 | childKey: 'message_id', // right table key 57 | childCollectionIdentity: 'message_cc_user' 58 | }, 59 | { 60 | alias: 'cc', // the `alias` -- e.g. name of association 61 | 62 | parent: 'message_cc_user', // left table name 63 | parentCollectionIdentity: 'message_cc_user', 64 | parentKey: 'user_id', // left table key 65 | 66 | child: 'user', // right table name 67 | childKey: 'id', // right table key 68 | select: ['id', 'email'], 69 | childCollectionIdentity: 'user' 70 | } 71 | ]; 72 | -------------------------------------------------------------------------------- /test/support/fixtures/integrator/n..1.joins.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Joins 3 | * 4 | * @type {Array} 5 | */ 6 | module.exports = [ 7 | { 8 | alias: 'from', // the `alias` -- e.g. name of association 9 | parent: 'message', // left table name 10 | parentCollectionIdentity: 'message', 11 | parentKey: 'from', // left table key 12 | child: 'user', // right table name 13 | childKey: 'id', // right table key 14 | childCollectionIdentity: 'user' 15 | } 16 | ]; 17 | -------------------------------------------------------------------------------- /test/support/fixtures/integrator/n..m.joins.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Joins 3 | * 4 | * @type {Array} 5 | */ 6 | module.exports = [ 7 | { 8 | alias: 'to', // the `alias` -- e.g. name of association 9 | parent: 'message', // parent/left table name 10 | parentCollectionIdentity: 'message', 11 | parentKey: 'id', // parent PK 12 | childKey: 'message_id', // intermediate FK <- parent key 13 | child: 'message_to_user', // intermediate/right table name 14 | childCollectionIdentity: 'message_to_user' 15 | }, 16 | { 17 | alias: 'to', 18 | parent: 'message_to_user', // intermediate/left table name 19 | parentCollectionIdentity: 'message_to_user', 20 | parentKey: 'user_id', // intermediate FK -> child key 21 | childKey: 'id', // child PK 22 | child: 'user', // child/right table name 23 | childCollectionIdentity: 'user' 24 | } 25 | ]; 26 | -------------------------------------------------------------------------------- /test/support/fixtures/integrator/populateResults.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Results of populate() queries using our test data. 3 | * @type {Object} 4 | */ 5 | module.exports = { 6 | 7 | // 1..N..1 (Pretty much like reverse populate) 8 | message___message_to_user: [{ 9 | id: 10, 10 | subject: 'msgA', 11 | body: 'A test message.', 12 | from: 1, 13 | to: [{ 14 | '.message_id': 10, 15 | '.user_id': 2, 16 | '.id': 1 17 | }, { 18 | '.message_id': 10, 19 | '.user_id': 3, 20 | '.id': 2 21 | }] 22 | }, { 23 | id: 20, 24 | subject: 'msgB', 25 | body: 'Another test message.', 26 | from: 1, 27 | to: [] 28 | }, { 29 | id: 30, 30 | subject: 'msgC', 31 | body: 'Aint sent this one yet.', 32 | from: null, 33 | to: [] 34 | }], 35 | 36 | 37 | 38 | // N..M Populate 39 | message___message_to_user___user: [{ 40 | id: 10, 41 | subject: 'msgA', 42 | body: 'A test message.', 43 | from: 1, 44 | to: [{ 45 | '..email': 'a@recipient.com', 46 | '..id': 2, 47 | }, { 48 | '..email': 'b@recipient.com', 49 | '..id': 3, 50 | }] 51 | }, { 52 | id: 20, 53 | subject: 'msgB', 54 | body: 'Another test message.', 55 | from: 1, 56 | to: [] 57 | }, { 58 | id: 30, 59 | subject: 'msgC', 60 | body: 'Aint sent this one yet.', 61 | from: null, 62 | to: [] 63 | }] 64 | }; 65 | -------------------------------------------------------------------------------- /test/support/fixtures/integrator/schema.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Models / Schema 3 | * (for reference) 4 | * 5 | * @type {Object} 6 | */ 7 | 8 | // Message: { 9 | // attributes: { 10 | 11 | // subject: 'string', 12 | 13 | // body: 'string', 14 | 15 | // to: { 16 | // collection: 'user' 17 | // }, 18 | 19 | // cc: { 20 | // collection: 'user' 21 | // }, 22 | 23 | // bcc: { 24 | // collection: 'user' 25 | // }, 26 | 27 | // from: { 28 | // model: 'user' 29 | // } 30 | // } 31 | // }; 32 | 33 | // User: { 34 | // attributes: { 35 | // email: 'string' 36 | // } 37 | // }; -------------------------------------------------------------------------------- /test/support/fixtures/integrator/tables.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dummy data, grouped by table. 3 | * 4 | * @type {Object} 5 | */ 6 | module.exports = { 7 | 8 | // Messages (message) 9 | message: [ 10 | { id: 10, subject: 'msgA', body: 'A test message.', from: 1 }, 11 | { id: 20, subject: 'msgB', body: 'Another test message.', from: 1 }, 12 | { id: 30, subject: 'msgC', body: 'Aint sent this one yet.', from: null } 13 | ], 14 | 15 | // Users (user) 16 | user: [ 17 | { id: 1, email: 'sender@thatguy.com' }, 18 | { id: 2, email: 'a@recipient.com' }, 19 | { id: 3, email: 'b@recipient.com' }, 20 | { id: 4, email: 'c@recipient.com' }, 21 | { id: 5, email: 'd@recipient.com' }, 22 | { id: 6, email: 'e@recipient.com' } 23 | ], 24 | 25 | 26 | // message_to_user 27 | message_to_user: [ 28 | { 29 | id: 1, 30 | message_id: 10, 31 | user_id: 2 32 | }, 33 | { 34 | id: 2, 35 | message_id: 10, 36 | user_id: 3 37 | } 38 | ], 39 | 40 | 41 | // message_cc_user 42 | message_cc_user: [ 43 | { 44 | id: 1, 45 | message_id: 10, 46 | user_id: 4 47 | }, 48 | { 49 | id: 2, 50 | message_id: 10, 51 | user_id: 5 52 | } 53 | ], 54 | 55 | 56 | 57 | // message_bcc_user 58 | message_bcc_user: [ 59 | { 60 | id: 1, 61 | message_id: 10, 62 | user_id: 6 63 | } 64 | ] 65 | }; 66 | 67 | 68 | 69 | 70 | 71 | // Note: Even 1..N relationships could potentially be represented this way as a base assumption-- 72 | // message_id would just have a unique constraint. Not right now though! 73 | // 74 | // var message_from_user = [ 75 | // { 76 | // id: 1, 77 | // message_id: 10, 78 | // user_id: 1 79 | // } 80 | // ]; 81 | -------------------------------------------------------------------------------- /test/support/fixtures/model/context.belongsTo.fixture.js: -------------------------------------------------------------------------------- 1 | var structure = require('./context.fixture'); 2 | 3 | /** 4 | * Context Fixture for a Belongs To Relationship 5 | */ 6 | 7 | module.exports = function() { 8 | var context = structure; 9 | 10 | // Name the collection 11 | context.identity = 'foo'; 12 | 13 | context.primaryKey = 'id'; 14 | 15 | // Set collection attributes 16 | context._attributes = { 17 | id: { 18 | type: 'integer', 19 | autoIncrement: true, 20 | primaryKey: true, 21 | unique: true 22 | }, 23 | 24 | name: { type: 'string' }, 25 | bars: { 26 | collection: 'bar', 27 | via: 'foo' 28 | } 29 | }; 30 | 31 | // Build a mock global schema object 32 | context.waterline.schema = { 33 | foo: { 34 | identity: 'foo', 35 | attributes: { 36 | name: 'string', 37 | bars: { 38 | collection: 'bar', 39 | references: 'bar', 40 | on: 'foo_id', 41 | onKey: 'foo' 42 | }, 43 | id: { 44 | type: 'integer', 45 | autoIncrement: true, 46 | primaryKey: true, 47 | unique: true 48 | } 49 | } 50 | }, 51 | 52 | bar: { 53 | identity: 'bar', 54 | attributes: { 55 | name: 'string', 56 | id: { 57 | type: 'integer', 58 | autoIncrement: true, 59 | primaryKey: true, 60 | unique: true 61 | }, 62 | foo: { 63 | columnName: 'foo_id', 64 | type: 'integer', 65 | foreignKey: true, 66 | references: 'foo', 67 | on: 'id', 68 | onKey: 'id' 69 | } 70 | } 71 | } 72 | }; 73 | 74 | // Build global collections 75 | context.waterline.collections.foo = { 76 | identity: 'foo', 77 | _attributes: context._attributes 78 | }; 79 | 80 | context.waterline.collections.bar = { 81 | identity: 'bar', 82 | _attributes: { 83 | name: { type: 'string' }, 84 | foo: { model: 'foo' }, 85 | id: { 86 | type: 'integer', 87 | autoIncrement: true, 88 | primaryKey: true, 89 | unique: true 90 | } 91 | } 92 | }; 93 | 94 | return context; 95 | }; 96 | -------------------------------------------------------------------------------- /test/support/fixtures/model/context.fixture.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Base Context Fixture 3 | * (for a collection) 4 | */ 5 | 6 | module.exports = { 7 | connections: {}, 8 | waterline: { 9 | _collections: [], 10 | _connections: {}, 11 | collections: {}, 12 | connections: {}, 13 | schema: {} 14 | }, 15 | attributes: {}, 16 | adapter: {}, 17 | _attributes: {}, 18 | _schema: {} 19 | }; 20 | -------------------------------------------------------------------------------- /test/support/fixtures/model/context.manyToMany.fixture.js: -------------------------------------------------------------------------------- 1 | var structure = require('./context.fixture'); 2 | 3 | /** 4 | * Context Fixture for a Many To Many Relationship 5 | */ 6 | 7 | module.exports = function() { 8 | var context = structure; 9 | 10 | context.identity = 'foo'; 11 | 12 | context.primaryKey = 'id'; 13 | 14 | context.connections = { 15 | my_foo: { 16 | config: {}, 17 | _adapter: {}, 18 | _collections: [] 19 | } 20 | }; 21 | 22 | 23 | // Build Out Model Definitions 24 | var models = { 25 | foo: { 26 | identity: 'foo', 27 | datastore: 'my_foo', 28 | attributes: { 29 | id: { 30 | type: 'integer', 31 | autoIncrement: true, 32 | primaryKey: true, 33 | unique: true 34 | }, 35 | name: { 36 | type: 'string' 37 | }, 38 | bars: { 39 | collection: 'bar', 40 | via: 'foos', 41 | dominant: true 42 | }, 43 | foobars: { 44 | collection: 'baz' , 45 | via: 'foo', 46 | dominant: true 47 | } 48 | } 49 | }, 50 | bar: { 51 | identity: 'bar', 52 | datastore: 'my_foo', 53 | attributes: { 54 | id: { 55 | type: 'integer', 56 | autoIncrement: true, 57 | primaryKey: true, 58 | unique: true 59 | }, 60 | name: { 61 | type: 'string' 62 | }, 63 | foos: { 64 | collection: 'foo', 65 | via: 'bars' 66 | } 67 | } 68 | }, 69 | baz: { 70 | identity: 'baz', 71 | datastore: 'my_foo', 72 | attributes: { 73 | id: { 74 | type: 'integer', 75 | autoIncrement: true, 76 | primaryKey: true, 77 | unique: true 78 | }, 79 | foo: { 80 | model: 'foo' 81 | } 82 | } 83 | }, 84 | bar_foos__foo_bars: { 85 | identity: 'bar_foos__foo_bars', 86 | datastore: 'my_foo', 87 | tables: ['bar', 'foo'], 88 | junctionTable: true, 89 | 90 | attributes: { 91 | id: { 92 | primaryKey: true, 93 | autoIncrement: true, 94 | type: 'integer' 95 | }, 96 | bar_foos: { 97 | columnName: 'bar_foos', 98 | type: 'integer', 99 | foreignKey: true, 100 | references: 'bar', 101 | on: 'id', 102 | via: 'foo_bars', 103 | groupBy: 'bar' 104 | }, 105 | 106 | foo_bars: { 107 | columnName: 'foo_bars', 108 | type: 'integer', 109 | foreignKey: true, 110 | references: 'foo', 111 | on: 'id', 112 | via: 'bar_foos', 113 | groupBy: 'foo' 114 | } 115 | } 116 | } 117 | }; 118 | 119 | 120 | // Set context collections 121 | context.waterline.collections = models; 122 | 123 | // Set collection attributes 124 | context._attributes = models.foo.attributes; 125 | context.attributes = context._attributes; 126 | context.waterline.connections = context.connections; 127 | 128 | // Build Up Waterline Schema 129 | context.waterline.schema.foo = { 130 | identity: 'foo', 131 | datastore: 'my_foo', 132 | attributes: { 133 | id: { 134 | type: 'integer', 135 | autoIncrement: true, 136 | primaryKey: true, 137 | unique: true 138 | }, 139 | name: { 140 | type: 'string' 141 | }, 142 | 143 | bars: { 144 | collection: 'bar_foos__foo_bars', 145 | references: 'bar_foos__foo_bars', 146 | on: 'bar_foos' 147 | }, 148 | 149 | foobars: { 150 | collection: 'baz', 151 | references: 'baz', 152 | on: 'foo_id' 153 | } 154 | } 155 | }; 156 | 157 | context.waterline.schema.bar = { 158 | identity: 'bar', 159 | datastore: 'my_foo', 160 | attributes: { 161 | id: { 162 | type: 'integer', 163 | autoIncrement: true, 164 | primaryKey: true, 165 | unique: true 166 | }, 167 | name: { 168 | type: 'string' 169 | }, 170 | foos: { 171 | collection: 'bar_foos__foo_bars', 172 | references: 'bar_foos__foo_bars', 173 | on: 'foo_bars' 174 | } 175 | } 176 | }; 177 | 178 | context.waterline.schema.baz = { 179 | identity: 'baz', 180 | datastore: 'my_foo', 181 | attributes: { 182 | id: { 183 | type: 'integer', 184 | autoIncrement: true, 185 | primaryKey: true, 186 | unique: true 187 | }, 188 | foo: { 189 | columnName: 'foo_id', 190 | type: 'integer', 191 | foreignKey: true, 192 | references: 'foo', 193 | on: 'id' 194 | } 195 | } 196 | }; 197 | 198 | context.waterline.schema.bar_foos__foo_bars = { 199 | identity: 'bar_foos__foo_bars', 200 | datastore: 'my_foo', 201 | tables: ['bar', 'foo'], 202 | junctionTable: true, 203 | 204 | attributes: { 205 | id: { 206 | primaryKey: true, 207 | autoIncrement: true, 208 | type: 'integer' 209 | }, 210 | bar_foos: { 211 | columnName: 'bar_foos', 212 | type: 'integer', 213 | foreignKey: true, 214 | references: 'bar', 215 | on: 'id', 216 | via: 'foo_bars', 217 | groupBy: 'bar' 218 | }, 219 | 220 | foo_bars: { 221 | columnName: 'foo_bars', 222 | type: 'integer', 223 | foreignKey: true, 224 | references: 'foo', 225 | on: 'id', 226 | via: 'bar_foos', 227 | groupBy: 'foo' 228 | } 229 | } 230 | }; 231 | 232 | return context; 233 | }; 234 | -------------------------------------------------------------------------------- /test/support/fixtures/model/context.simple.fixture.js: -------------------------------------------------------------------------------- 1 | var structure = require('./context.fixture'); 2 | 3 | /** 4 | * Context Fixture for a Belongs To Relationship 5 | */ 6 | 7 | module.exports = function() { 8 | var context = structure; 9 | 10 | // Name the collection 11 | context.identity = 'foo'; 12 | 13 | context.primaryKey = 'id'; 14 | 15 | // Set collection attributes 16 | context._attributes = { 17 | id: { 18 | type: 'integer', 19 | autoIncrement: true, 20 | primaryKey: true, 21 | unique: true 22 | }, 23 | 24 | name: { type: 'string' } 25 | }; 26 | 27 | // Build a mock global schema object 28 | context.waterline.schema = { 29 | foo: { 30 | identity: 'foo', 31 | attributes: { 32 | name: 'string', 33 | id: { 34 | type: 'integer', 35 | autoIncrement: true, 36 | primaryKey: true, 37 | unique: true 38 | } 39 | } 40 | }, 41 | 42 | bar: { 43 | identity: 'bar', 44 | attributes: { 45 | name: 'string', 46 | id: { 47 | type: 'integer', 48 | autoIncrement: true, 49 | primaryKey: true, 50 | unique: true 51 | } 52 | } 53 | } 54 | }; 55 | 56 | // Build global collections 57 | context.waterline.collections.foo = { 58 | identity: 'foo', 59 | _attributes: context._attributes 60 | }; 61 | 62 | context.waterline.collections.bar = { 63 | identity: 'bar', 64 | _attributes: { 65 | name: { type: 'string' }, 66 | id: { 67 | type: 'integer', 68 | autoIncrement: true, 69 | primaryKey: true, 70 | unique: true 71 | } 72 | } 73 | }; 74 | 75 | return context; 76 | }; 77 | -------------------------------------------------------------------------------- /test/support/migrate.helper.js: -------------------------------------------------------------------------------- 1 | var _ = require('@sailshq/lodash'); 2 | var async = require('async'); 3 | 4 | module.exports = function(ontology, cb) { 5 | // Run Auto-Migrations 6 | var toBeSynced = _.reduce(ontology.collections, function(resources, collection) { 7 | resources.push(collection); 8 | return resources; 9 | }, []); 10 | 11 | // Run auto-migration strategies on each collection 12 | async.eachSeries(toBeSynced, function(collection, next) { 13 | collection.sync(next); 14 | }, function(err) { 15 | if (err) { 16 | return cb(err); 17 | } 18 | 19 | // Expose Global 20 | // SomeCollection = ocean.collections.tests; 21 | cb(); 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /test/unit/callbacks/afterCreate.create.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Waterline = require('../../../lib/waterline'); 3 | 4 | describe('After Create Lifecycle Callback ::', function() { 5 | describe('Create ::', function() { 6 | var person; 7 | 8 | before(function(done) { 9 | var waterline = new Waterline(); 10 | var Model = Waterline.Model.extend({ 11 | identity: 'user', 12 | datastore: 'foo', 13 | primaryKey: 'id', 14 | fetchRecordsOnCreate: true, 15 | attributes: { 16 | id: { 17 | type: 'number' 18 | }, 19 | name: { 20 | type: 'string' 21 | } 22 | }, 23 | 24 | afterCreate: function(values, cb) { 25 | values.name = values.name + ' updated'; 26 | return cb(); 27 | } 28 | }); 29 | 30 | waterline.registerModel(Model); 31 | 32 | // Fixture Adapter Def 33 | var adapterDef = { create: function(con, query, cb) { return cb(null, query.newRecord); }}; 34 | 35 | var connections = { 36 | 'foo': { 37 | adapter: 'foobar' 38 | } 39 | }; 40 | 41 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 42 | if (err) { 43 | return done(err); 44 | } 45 | person = orm.collections.user; 46 | return done(); 47 | }); 48 | }); 49 | 50 | it('should run afterCreate and mutate values', function(done) { 51 | person.create({ name: 'test', id: 1 }, function(err, user) { 52 | if (err) { 53 | return done(err); 54 | } 55 | 56 | assert.equal(user.name, 'test updated'); 57 | return done(); 58 | }); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/unit/callbacks/afterCreate.createEach.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Waterline = require('../../../lib/waterline'); 3 | 4 | describe('After Create Lifecycle Callback ::', function() { 5 | describe('.createEach ::', function() { 6 | var person; 7 | 8 | before(function(done) { 9 | var waterline = new Waterline(); 10 | var Model = Waterline.Model.extend({ 11 | identity: 'user', 12 | datastore: 'foo', 13 | primaryKey: 'id', 14 | fetchRecordsOnCreate: true, 15 | attributes: { 16 | id: { 17 | type: 'number' 18 | }, 19 | name: { 20 | type: 'string' 21 | } 22 | }, 23 | 24 | afterCreate: function(values, cb) { 25 | values.name = values.name + ' updated'; 26 | return cb(); 27 | } 28 | }); 29 | 30 | waterline.registerModel(Model); 31 | 32 | // Fixture Adapter Def 33 | var adapterDef = { createEach: function(con, query, cb) { return cb(null, query.newRecords); }}; 34 | 35 | var connections = { 36 | 'foo': { 37 | adapter: 'foobar' 38 | } 39 | }; 40 | 41 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 42 | if (err) { 43 | return done(err); 44 | } 45 | person = orm.collections.user; 46 | return done(); 47 | }); 48 | }); 49 | 50 | it('should run afterCreate and mutate values', function(done) { 51 | person.createEach([{ name: 'test-foo', id: 1 }, { name: 'test-bar', id: 2 }], function(err, users) { 52 | if (err) { 53 | return done(err); 54 | } 55 | 56 | assert.equal(users[0].name, 'test-foo updated'); 57 | assert.equal(users[1].name, 'test-bar updated'); 58 | return done(); 59 | }, { fetch: true }); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/unit/callbacks/afterCreate.findOrCreate.js: -------------------------------------------------------------------------------- 1 | var Waterline = require('../../../lib/waterline'); 2 | var assert = require('assert'); 3 | 4 | describe('.afterCreate()', function() { 5 | describe('basic function', function() { 6 | describe('.findOrCreate()', function() { 7 | describe('without a record', function() { 8 | var person; 9 | 10 | before(function(done) { 11 | var waterline = new Waterline(); 12 | var Model = Waterline.Model.extend({ 13 | identity: 'user', 14 | datastore: 'foo', 15 | primaryKey: 'id', 16 | fetchRecordsOnCreate: true, 17 | fetchRecordsOnCreateEach: true, 18 | attributes: { 19 | id: { 20 | type: 'number' 21 | }, 22 | name: { 23 | type: 'string' 24 | } 25 | }, 26 | 27 | afterCreate: function(values, cb) { 28 | values.name = values.name + ' updated'; 29 | return cb(); 30 | } 31 | }); 32 | 33 | waterline.registerModel(Model); 34 | 35 | // Fixture Adapter Def 36 | var adapterDef = { 37 | find: function(con, query, cb) { return cb(null, null); }, 38 | create: function(con, query, cb) { return cb(null, query.newRecord); } 39 | }; 40 | 41 | var connections = { 42 | 'foo': { 43 | adapter: 'foobar' 44 | } 45 | }; 46 | 47 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 48 | if (err) { 49 | return done(err); 50 | } 51 | 52 | person = orm.collections.user; 53 | 54 | return done(); 55 | }); 56 | }); 57 | 58 | it('should run afterCreate and mutate values on create', function(done) { 59 | person.findOrCreate({ name: 'test' }, { name: 'test', id: 1 }, function(err, user) { 60 | if (err) { 61 | return done(err); 62 | } 63 | 64 | assert.equal(user.name, 'test updated'); 65 | 66 | return done(); 67 | }); 68 | }); 69 | }); 70 | 71 | describe('with a record', function() { 72 | var person; 73 | 74 | before(function(done) { 75 | var waterline = new Waterline(); 76 | var Model = Waterline.Model.extend({ 77 | identity: 'user', 78 | datastore: 'foo', 79 | primaryKey: 'id', 80 | attributes: { 81 | id: { 82 | type: 'number' 83 | }, 84 | name: { 85 | type: 'string' 86 | } 87 | }, 88 | 89 | afterCreate: function(values, cb) { 90 | values.name = values.name + ' updated'; 91 | return cb(); 92 | } 93 | }); 94 | 95 | waterline.registerModel(Model); 96 | 97 | // Fixture Adapter Def 98 | var adapterDef = { 99 | find: function(con, query, cb) { return cb(null, [{ name: 'test', id: 1 }]); }, 100 | create: function(con, query, cb) { return cb(null, query.newRecord); } 101 | }; 102 | 103 | var connections = { 104 | 'foo': { 105 | adapter: 'foobar' 106 | } 107 | }; 108 | 109 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 110 | if (err) { 111 | return done(err); 112 | } 113 | 114 | person = orm.collections.user; 115 | 116 | return done(); 117 | }); 118 | }); 119 | 120 | it('should not run afterCreate and mutate values on find', function(done) { 121 | person.findOrCreate({ name: 'test' }, { name: 'test', id: 1 }, function(err, user) { 122 | if (err) { 123 | return done(err); 124 | } 125 | 126 | assert.equal(user.name, 'test'); 127 | 128 | return done(); 129 | }); 130 | }); 131 | }); 132 | }); 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /test/unit/callbacks/afterDestroy.destroy.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Waterline = require('../../../lib/waterline'); 3 | 4 | describe('After Destroy Lifecycle Callback ::', function() { 5 | describe('Destroy ::', function() { 6 | var person; 7 | var status; 8 | 9 | before(function(done) { 10 | var waterline = new Waterline(); 11 | var Model = Waterline.Model.extend({ 12 | identity: 'user', 13 | datastore: 'foo', 14 | primaryKey: 'id', 15 | fetchRecordsOnCreate: true, 16 | fetchRecordsOnDestroy: true, 17 | attributes: { 18 | id: { 19 | type: 'number' 20 | }, 21 | name: { 22 | type: 'string' 23 | } 24 | }, 25 | 26 | afterDestroy: function(destroyedRecord, cb) { 27 | status = destroyedRecord.status; 28 | cb(); 29 | } 30 | }); 31 | 32 | waterline.registerModel(Model); 33 | 34 | // Fixture Adapter Def 35 | var adapterDef = { 36 | destroy: function(con, query, cb) { return cb(undefined, [{ status: true, id: 1 }]); }, 37 | create: function(con, query, cb) { return cb(undefined, { status: true, id: 1 }); } 38 | }; 39 | 40 | var connections = { 41 | 'foo': { 42 | adapter: 'foobar' 43 | } 44 | }; 45 | 46 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 47 | if (err) { 48 | return done(err); 49 | } 50 | person = orm.collections.user; 51 | return done(); 52 | }); 53 | }); 54 | 55 | it('should run afterDestroy', function(done) { 56 | person.destroy({ name: 'test' }, function(err) { 57 | if (err) { 58 | return done(err); 59 | } 60 | 61 | assert.equal(status, true); 62 | return done(); 63 | }); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/unit/callbacks/beforeCreate.create.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Waterline = require('../../../lib/waterline'); 3 | 4 | describe('Before Create Lifecycle Callback ::', function() { 5 | describe('Create ::', function() { 6 | var person; 7 | 8 | before(function(done) { 9 | var waterline = new Waterline(); 10 | var Model = Waterline.Model.extend({ 11 | identity: 'user', 12 | datastore: 'foo', 13 | primaryKey: 'id', 14 | fetchRecordsOnCreate: true, 15 | attributes: { 16 | id: { 17 | type: 'number' 18 | }, 19 | name: { 20 | type: 'string' 21 | } 22 | }, 23 | 24 | beforeCreate: function(values, cb) { 25 | values.name = values.name + ' updated'; 26 | cb(); 27 | } 28 | }); 29 | 30 | waterline.registerModel(Model); 31 | 32 | // Fixture Adapter Def 33 | var adapterDef = { create: function(con, query, cb) { return cb(null, query.newRecord); }}; 34 | 35 | var connections = { 36 | 'foo': { 37 | adapter: 'foobar' 38 | } 39 | }; 40 | 41 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 42 | if (err) { 43 | return done(err); 44 | } 45 | person = orm.collections.user; 46 | return done(); 47 | }); 48 | }); 49 | 50 | it('should run beforeCreate and mutate values', function(done) { 51 | person.create({ name: 'test', id: 1 }, function(err, user) { 52 | if (err) { 53 | return done(err); 54 | } 55 | 56 | assert.equal(user.name, 'test updated'); 57 | return done(); 58 | }); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/unit/callbacks/beforeCreate.createEach.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Waterline = require('../../../lib/waterline'); 3 | 4 | describe('Before Create Lifecycle Callback ::', function() { 5 | describe('.createEach() ::', function() { 6 | var person; 7 | 8 | before(function(done) { 9 | var waterline = new Waterline(); 10 | var Model = Waterline.Model.extend({ 11 | identity: 'user', 12 | datastore: 'foo', 13 | primaryKey: 'id', 14 | fetchRecordsOnCreate: true, 15 | attributes: { 16 | id: { 17 | type: 'number' 18 | }, 19 | name: { 20 | type: 'string' 21 | } 22 | }, 23 | 24 | beforeCreate: function(values, cb) { 25 | values.name = values.name + ' updated'; 26 | cb(); 27 | } 28 | }); 29 | 30 | waterline.registerModel(Model); 31 | 32 | // Fixture Adapter Def 33 | var adapterDef = { createEach: function(con, query, cb) { return cb(null, query.newRecords); }}; 34 | 35 | var connections = { 36 | 'foo': { 37 | adapter: 'foobar' 38 | } 39 | }; 40 | 41 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 42 | if (err) { 43 | return done(err); 44 | } 45 | person = orm.collections.user; 46 | return done(); 47 | }); 48 | }); 49 | 50 | it('should run beforeCreate and mutate values', function(done) { 51 | person.createEach([{ name: 'test-foo', id: 1 }, { name: 'test-bar', id: 2 }], function(err, users) { 52 | if (err) { 53 | return done(err); 54 | } 55 | 56 | assert.equal(users[0].name, 'test-foo updated'); 57 | assert.equal(users[1].name, 'test-bar updated'); 58 | return done(); 59 | }, {fetch: true}); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/unit/callbacks/beforeCreate.findOrCreate.js: -------------------------------------------------------------------------------- 1 | var Waterline = require('../../../lib/waterline'); 2 | var assert = require('assert'); 3 | 4 | describe('.beforeCreate()', function() { 5 | describe('basic function', function() { 6 | describe('.findOrCreate()', function() { 7 | describe('without a record', function() { 8 | var person; 9 | 10 | before(function(done) { 11 | var waterline = new Waterline(); 12 | var Model = Waterline.Model.extend({ 13 | identity: 'user', 14 | datastore: 'foo', 15 | primaryKey: 'id', 16 | fetchRecordsOnCreate: true, 17 | fetchRecordsOnCreateEach: true, 18 | attributes: { 19 | id: { 20 | type: 'number' 21 | }, 22 | name: { 23 | type: 'string' 24 | } 25 | }, 26 | 27 | beforeCreate: function(values, cb) { 28 | values.name = values.name + ' updated'; 29 | return cb(); 30 | } 31 | }); 32 | 33 | waterline.registerModel(Model); 34 | 35 | // Fixture Adapter Def 36 | var adapterDef = { 37 | find: function(con, query, cb) { return cb(null, null); }, 38 | create: function(con, query, cb) { return cb(null, query.newRecord); } 39 | }; 40 | 41 | var connections = { 42 | 'foo': { 43 | adapter: 'foobar' 44 | } 45 | }; 46 | 47 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 48 | if (err) { 49 | return done(err); 50 | } 51 | 52 | person = orm.collections.user; 53 | 54 | return done(); 55 | }); 56 | }); 57 | 58 | it('should run beforeCreate and mutate values on create', function(done) { 59 | person.findOrCreate({ name: 'test' }, { name: 'test', id: 1 }, function(err, user) { 60 | if (err) { 61 | return done(err); 62 | } 63 | 64 | assert.equal(user.name, 'test updated'); 65 | 66 | return done(); 67 | }); 68 | }); 69 | }); 70 | 71 | describe('without a record', function() { 72 | var person; 73 | 74 | before(function(done) { 75 | var waterline = new Waterline(); 76 | var Model = Waterline.Model.extend({ 77 | identity: 'user', 78 | datastore: 'foo', 79 | primaryKey: 'id', 80 | attributes: { 81 | id: { 82 | type: 'number' 83 | }, 84 | name: { 85 | type: 'string' 86 | } 87 | }, 88 | 89 | beforeCreate: function(values, cb) { 90 | values.name = values.name + ' updated'; 91 | return cb(); 92 | } 93 | }); 94 | 95 | waterline.registerModel(Model); 96 | 97 | // Fixture Adapter Def 98 | var adapterDef = { 99 | find: function(con, query, cb) { return cb(null, [{ name: 'test', id: 1}] ); }, 100 | create: function(con, query, cb) { return cb(null, query.newRecord); } 101 | }; 102 | 103 | var connections = { 104 | 'foo': { 105 | adapter: 'foobar' 106 | } 107 | }; 108 | 109 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 110 | if (err) { 111 | return done(err); 112 | } 113 | 114 | person = orm.collections.user; 115 | 116 | return done(); 117 | }); 118 | }); 119 | 120 | it('should not run beforeCreate and mutate values on find', function(done) { 121 | person.findOrCreate({ name: 'test' }, { name: 'test' }, function(err, user) { 122 | if (err) { 123 | return done(err); 124 | } 125 | 126 | assert(user.name === 'test'); 127 | 128 | return done(); 129 | }); 130 | }); 131 | }); 132 | }); 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /test/unit/callbacks/beforeDestroy.destroy.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Waterline = require('../../../lib/waterline'); 3 | 4 | describe('Before Destroy Lifecycle Callback ::', function() { 5 | describe('Destroy ::', function() { 6 | var person; 7 | var status = false; 8 | 9 | before(function(done) { 10 | var waterline = new Waterline(); 11 | var Model = Waterline.Model.extend({ 12 | identity: 'user', 13 | datastore: 'foo', 14 | primaryKey: 'id', 15 | attributes: { 16 | id: { 17 | type: 'number' 18 | }, 19 | name: { 20 | type: 'string' 21 | } 22 | }, 23 | 24 | beforeDestroy: function(criteria, cb) { 25 | status = true; 26 | cb(); 27 | } 28 | }); 29 | 30 | waterline.registerModel(Model); 31 | 32 | // Fixture Adapter Def 33 | var adapterDef = { destroy: function(con, query, cb) { return cb(null, query); }}; 34 | 35 | var connections = { 36 | 'foo': { 37 | adapter: 'foobar' 38 | } 39 | }; 40 | 41 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 42 | if (err) { 43 | return done(err); 44 | } 45 | person = orm.collections.user; 46 | return done(); 47 | }); 48 | }); 49 | 50 | 51 | it('should run beforeDestroy', function(done) { 52 | person.destroy({ name: 'test' }, function(err) { 53 | if (err) { 54 | return done(err); 55 | } 56 | 57 | assert.equal(status, true); 58 | return done(); 59 | }); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/unit/collection/transformations/transformations.initialize.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Transformer = require('../../../../lib/waterline/utils/system/transformer-builder'); 3 | 4 | describe('Collection Transformations ::', function() { 5 | describe('Initialize ::', function() { 6 | describe('with string columnName', function() { 7 | var transformer; 8 | 9 | before(function() { 10 | var attributes = { 11 | name: 'string', 12 | username: { 13 | columnName: 'login' 14 | } 15 | }; 16 | 17 | transformer = new Transformer(attributes, {}); 18 | }); 19 | 20 | it('should set a username transformation', function() { 21 | assert(transformer._transformations.username === 'login'); 22 | }); 23 | }); 24 | 25 | describe('with function columnName', function() { 26 | var attributes; 27 | 28 | before(function() { 29 | attributes = { 30 | name: 'string', 31 | username: { 32 | columnName: function() {} 33 | } 34 | }; 35 | }); 36 | 37 | it('should NOT set a username transformation', function() { 38 | var msg = (function() { 39 | try { 40 | new Transformer(attributes, {}); 41 | } catch(e) { 42 | return e.message; 43 | } 44 | return ''; 45 | })(); 46 | 47 | assert.strictEqual('Consistency violation: `columnName` must be a string. But for this attribute (`username`) it is not!', msg); 48 | }); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/unit/collection/transformations/transformations.serialize.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Schema = require('waterline-schema'); 4 | var Waterline = require('../../../../lib/waterline'); 5 | var Transformer = require('../../../../lib/waterline/utils/system/transformer-builder'); 6 | 7 | describe('Collection Transformations ::', function() { 8 | describe('Serialize ::', function() { 9 | describe('with normal key/value pairs', function() { 10 | var transformer; 11 | 12 | before(function() { 13 | var attributes = { 14 | name: 'string', 15 | username: { 16 | columnName: 'login' 17 | } 18 | }; 19 | 20 | transformer = new Transformer(attributes, {}); 21 | }); 22 | 23 | it('should change username key to login', function() { 24 | var values = { username: 'foo' }; 25 | transformer.serializeValues(values); 26 | assert(values.login); 27 | assert.equal(values.login, 'foo'); 28 | }); 29 | 30 | it('should work recursively', function() { 31 | var values = transformer.serializeCriteria({ where: { user: { username: 'foo' }}}); 32 | assert(values.where.user.login); 33 | assert.equal(values.where.user.login, 'foo'); 34 | }); 35 | 36 | it('should work on SELECT queries', function() { 37 | var values = transformer.serializeCriteria( 38 | { 39 | where: { 40 | username: 'foo' 41 | }, 42 | select: ['username'] 43 | } 44 | ); 45 | 46 | assert(values.where.login); 47 | assert.equal(_.indexOf(values.select, 'login'), 0); 48 | }); 49 | }); 50 | 51 | describe('with associations', function() { 52 | var transformer; 53 | 54 | /** 55 | * Build up real waterline schema for accurate testing 56 | */ 57 | 58 | before(function() { 59 | var collections = []; 60 | 61 | collections.push(Waterline.Model.extend({ 62 | identity: 'customer', 63 | tableName: 'customer', 64 | primaryKey: 'uuid', 65 | attributes: { 66 | uuid: { 67 | type: 'string' 68 | } 69 | } 70 | })); 71 | 72 | collections.push(Waterline.Model.extend({ 73 | identity: 'foo', 74 | tableName: 'foo', 75 | primaryKey: 'id', 76 | attributes: { 77 | id: { 78 | type: 'number' 79 | }, 80 | customer: { 81 | model: 'customer' 82 | } 83 | } 84 | })); 85 | 86 | var schema = new Schema(collections); 87 | transformer = new Transformer(schema.foo.attributes, schema.schema); 88 | }); 89 | 90 | it('should change customer key to customer_uuid', function() { 91 | var values = { customer: 1 }; 92 | transformer.serializeValues(values); 93 | assert(values.customer); 94 | assert.equal(values.customer, 1); 95 | }); 96 | 97 | it('should work recursively', function() { 98 | var values = transformer.serializeCriteria({ where: { user: { customer: 1 }}}); 99 | assert(values.where.user.customer); 100 | assert.equal(values.where.user.customer, 1); 101 | }); 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /test/unit/collection/transformations/transformations.unserialize.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Transformer = require('../../../../lib/waterline/utils/system/transformer-builder'); 3 | 4 | describe('Collection Transformations ::', function() { 5 | describe('Unserialize ::', function() { 6 | describe('with normal key/value pairs', function() { 7 | var transformer; 8 | 9 | before(function() { 10 | var attributes = { 11 | name: 'string', 12 | username: { 13 | columnName: 'login' 14 | } 15 | }; 16 | 17 | transformer = new Transformer(attributes, {}); 18 | }); 19 | 20 | it('should change login key to username', function() { 21 | var values = transformer.unserialize({ login: 'foo' }); 22 | assert(values.username); 23 | assert.equal(values.username, 'foo'); 24 | }); 25 | }); 26 | 27 | describe('with columnNames that conflict with other attribute names', function() { 28 | 29 | var transformer; 30 | 31 | before(function() { 32 | var attributes = { 33 | identity: { 34 | type: 'string', 35 | columnName: 'aid', 36 | }, 37 | ownerId: { 38 | type: 'string', 39 | columnName: 'identity', 40 | } 41 | }; 42 | 43 | transformer = new Transformer(attributes, {}); 44 | }); 45 | 46 | it('should change unserialize both attributes correctly', function() { 47 | var values = transformer.unserialize({ aid: 'foo', identity: 'bar' }); 48 | assert(values.identity); 49 | assert.equal(values.identity, 'foo'); 50 | assert(values.ownerId); 51 | assert.equal(values.ownerId, 'bar'); 52 | }); 53 | 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/unit/collection/type-cast/cast.boolean.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../../../lib/waterline'); 4 | 5 | describe.skip('Type Casting ::', function() { 6 | describe('with `type: \'boolean\'` ::', function() { 7 | 8 | var orm; 9 | var Person; 10 | before(function(done) { 11 | orm = new Waterline(); 12 | 13 | orm.registerModel(Waterline.Model.extend({ 14 | identity: 'person', 15 | datastore: 'foo', 16 | primaryKey: 'id', 17 | attributes: { 18 | id: { 19 | type: 'number' 20 | }, 21 | activated: { 22 | type: 'boolean' 23 | }, 24 | age: { 25 | type: 'number' 26 | }, 27 | name: { 28 | type: 'string' 29 | }, 30 | organization: { 31 | type: 'json' 32 | }, 33 | avatarBlob: { 34 | type: 'ref' 35 | } 36 | } 37 | })); 38 | 39 | orm.initialize({ 40 | adapters: { 41 | foobar: {} 42 | }, 43 | datastores: { 44 | foo: { adapter: 'foobar' } 45 | } 46 | }, function(err, orm) { 47 | if (err) { return done(err); } 48 | 49 | Person = orm.collections.person; 50 | return done(); 51 | });// 52 | 53 | });// 54 | 55 | 56 | it('should act as no-op when given a boolean', function() { 57 | assert.equal(Person.validate('activated', true), true); 58 | assert.equal(Person.validate('activated', false), false); 59 | }); 60 | 61 | it('should cast string "true" to a boolean', function() { 62 | assert.equal(Person.validate('activated', 'true'), true); 63 | }); 64 | 65 | it('should cast string "false" to a boolean', function() { 66 | // FUTURE: this may change in a future major version release of RTTC 67 | // (this test is here to help catch that when/if it happens) 68 | assert.equal(Person.validate('activated', 'false'), false); 69 | }); 70 | 71 | it('should cast number 0 to a boolean', function() { 72 | // FUTURE: this may change in a future major version release of RTTC 73 | // (this test is here to help catch that when/if it happens) 74 | assert.equal(Person.validate('activated', 0), false); 75 | }); 76 | 77 | it('should cast number 1 to a boolean', function() { 78 | assert.equal(Person.validate('activated', 1), true); 79 | }); 80 | 81 | it('should throw E_VALIDATION error when a value can\'t be cast', function() { 82 | try { 83 | Person.validate('activated', 'not yet'); 84 | } catch (e) { 85 | switch (e.code) { 86 | case 'E_VALIDATION': 87 | // FUTURE: maybe expand test to check more things 88 | return; 89 | 90 | // As of Thu Dec 22, 2016, this test is failing because 91 | // validation is not being completely rolled up yet. 92 | default: throw new Error('The actual error code was "'+e.code+'" - but it should have been "E_VALIDATION": the rolled-up validation error. This is so that errors from the public `.validate()` are consistent with errors exposed when creating or updating records (i.e. when multiple values are being set at the same time.) Here is the error that was actually received:\n```\n' +e.stack+'\n```'); 93 | } 94 | } 95 | }); 96 | 97 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 98 | // For further details on edge case handling, plus thousands more tests, see: 99 | // • http://npmjs.com/package/rttc 100 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 101 | 102 | });// 103 | });// 104 | -------------------------------------------------------------------------------- /test/unit/collection/type-cast/cast.json.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../../../lib/waterline'); 4 | 5 | describe.skip('Type Casting ::', function() { 6 | describe('with `type: \'json\'` ::', function() { 7 | 8 | var orm; 9 | var Person; 10 | before(function(done) { 11 | orm = new Waterline(); 12 | 13 | orm.registerModel(Waterline.Model.extend({ 14 | identity: 'person', 15 | datastore: 'foo', 16 | primaryKey: 'id', 17 | attributes: { 18 | id: { 19 | type: 'number' 20 | }, 21 | activated: { 22 | type: 'boolean' 23 | }, 24 | age: { 25 | type: 'number' 26 | }, 27 | name: { 28 | type: 'string' 29 | }, 30 | organization: { 31 | type: 'json' 32 | }, 33 | avatarBlob: { 34 | type: 'ref' 35 | } 36 | } 37 | })); 38 | 39 | orm.initialize({ 40 | adapters: { 41 | foobar: {} 42 | }, 43 | datastores: { 44 | foo: { adapter: 'foobar' } 45 | } 46 | }, function(err, orm) { 47 | if (err) { return done(err); } 48 | 49 | Person = orm.collections.person; 50 | return done(); 51 | });// 52 | 53 | });// 54 | 55 | 56 | it('should leave the null literal as-is', function() { 57 | assert.equal(Person.validate('organization', null), null); 58 | }); 59 | 60 | it('should leave numbers as-is', function() { 61 | assert.equal(Person.validate('organization', 4), 4); 62 | assert.equal(Person.validate('organization', 0), 0); 63 | assert.equal(Person.validate('organization', -10000.32852), -10000.32852); 64 | }); 65 | 66 | it('should leave booleans as-is', function() { 67 | assert.equal(Person.validate('organization', true), true); 68 | assert.equal(Person.validate('organization', false), false); 69 | }); 70 | 71 | describe('given a string imposter (i.e. looks like another type)', function() { 72 | 73 | it('should leave "null" imposter string as-is', function (){ 74 | assert.equal(Person.validate('organization', 'null'), 'null'); 75 | }); 76 | it('should leave number imposter strings as-is', function (){ 77 | assert.equal(Person.validate('organization', '4'), '4'); 78 | assert.equal(Person.validate('organization', '0'), '0'); 79 | assert.equal(Person.validate('organization', '-10000.32852'), '-10000.32852'); 80 | }); 81 | it('should leave boolean imposter strings as-is', function (){ 82 | assert.equal(Person.validate('organization', 'true'), 'true'); 83 | assert.equal(Person.validate('organization', 'false'), 'false'); 84 | }); 85 | it('should leave dictionary imposter strings as-is', function (){ 86 | var DICTIONARY_IMPOSTER_STR = '{ name: \'Foo Bar\', location: [-31.0123, 31.0123] }'; 87 | var result = Person.validate('organization', '{ name: \'Foo Bar\', location: [-31.0123, 31.0123] }'); 88 | assert(_.isString(result)); 89 | assert.equal(DICTIONARY_IMPOSTER_STR, result); 90 | }); 91 | 92 | }); 93 | 94 | it('should decycle circular nonsense', function(){ 95 | var cersei = {}; 96 | var jaime = {}; 97 | cersei.brother = jaime; 98 | cersei.lover = jaime; 99 | jaime.sister = cersei; 100 | jaime.lover = cersei; 101 | 102 | var dryJaime = Person.validate('organization', jaime); 103 | assert.deepEqual(dryJaime, { 104 | sister: { brother: '[Circular ~]', lover: '[Circular ~]' }, 105 | lover: { brother: '[Circular ~]', lover: '[Circular ~]' } 106 | }); 107 | 108 | var dryCersei = Person.validate('organization', cersei); 109 | assert.deepEqual(dryCersei, { 110 | brother: { sister: '[Circular ~]', lover: '[Circular ~]' }, 111 | lover: { sister: '[Circular ~]', lover: '[Circular ~]' } 112 | }); 113 | 114 | }); 115 | 116 | it('should reject Readable streams', function(){ 117 | try { 118 | Person.validate('organization', new (require('stream').Readable)()); 119 | } catch (e) { 120 | switch (e.code) { 121 | case 'E_VALIDATION': 122 | // FUTURE: maybe expand test to check more things 123 | return; 124 | 125 | // As of Thu Dec 22, 2016, this test is failing because 126 | // validation is not being completely rolled up yet. 127 | default: throw new Error('The actual error code was "'+e.code+'" - but it should have been "E_VALIDATION": the rolled-up validation error. This is so that errors from the public `.validate()` are consistent with errors exposed when creating or updating records (i.e. when multiple values are being set at the same time.) Here is the error that was actually received:\n```\n' +e.stack+'\n```'); 128 | } 129 | } 130 | }); 131 | 132 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 133 | // For further details on edge case handling, plus thousands more tests, see: 134 | // • http://npmjs.com/package/rttc 135 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 136 | 137 | 138 | }); 139 | }); 140 | -------------------------------------------------------------------------------- /test/unit/collection/type-cast/cast.number.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../../../lib/waterline'); 4 | 5 | describe.skip('Type Casting ::', function() { 6 | describe('with `type: \'number\'` ::', function() { 7 | 8 | var orm; 9 | var Person; 10 | before(function(done) { 11 | orm = new Waterline(); 12 | 13 | orm.registerModel(Waterline.Model.extend({ 14 | identity: 'person', 15 | datastore: 'foo', 16 | primaryKey: 'id', 17 | attributes: { 18 | id: { 19 | type: 'number' 20 | }, 21 | activated: { 22 | type: 'boolean' 23 | }, 24 | age: { 25 | type: 'number' 26 | }, 27 | name: { 28 | type: 'string' 29 | }, 30 | organization: { 31 | type: 'json' 32 | }, 33 | avatarBlob: { 34 | type: 'ref' 35 | } 36 | } 37 | })); 38 | 39 | orm.initialize({ 40 | adapters: { 41 | foobar: {} 42 | }, 43 | datastores: { 44 | foo: { adapter: 'foobar' } 45 | } 46 | }, function(err, orm) { 47 | if (err) { return done(err); } 48 | 49 | Person = orm.collections.person; 50 | return done(); 51 | });// 52 | 53 | });// 54 | 55 | it('should cast strings to numbers when integers', function() { 56 | assert.equal(Person.validate('age', '27'), 27); 57 | }); 58 | 59 | it('should cast strings to numbers when floats', function() { 60 | assert.equal(Person.validate('age', '27.01'), 27.01); 61 | }); 62 | 63 | it('should throw when a number can\'t be cast', function() { 64 | var values = { age: 'steve' }; 65 | assert.throws(function() { 66 | person._cast(values); 67 | }); 68 | }); 69 | 70 | it('should not try and do anything fancy with mongo ID\'s, even when it\'s really tempting', function() { 71 | try { 72 | Person.validate('age', '51f88ddc5d7967808b000002'); 73 | } catch (e) { 74 | switch (e.code) { 75 | case 'E_VALIDATION': 76 | // FUTURE: maybe expand test to check more things 77 | return; 78 | 79 | // As of Thu Dec 22, 2016, this test is failing because 80 | // validation is not being completely rolled up yet. 81 | default: throw new Error('The actual error code was "'+e.code+'" - but it should have been "E_VALIDATION": the rolled-up validation error. This is so that errors from the public `.validate()` are consistent with errors exposed when creating or updating records (i.e. when multiple values are being set at the same time.) Here is the error that was actually received:\n```\n' +e.stack+'\n```'); 82 | } 83 | } 84 | }); 85 | 86 | 87 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 88 | // For further details on edge case handling, plus thousands more tests, see: 89 | // • http://npmjs.com/package/rttc 90 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 91 | 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /test/unit/collection/type-cast/cast.ref.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../../../lib/waterline'); 4 | 5 | describe.skip('Type Casting ::', function() { 6 | describe('with `type: \'ref\'` ::', function() { 7 | var orm; 8 | var Person; 9 | before(function(done) { 10 | orm = new Waterline(); 11 | 12 | orm.registerModel(Waterline.Model.extend({ 13 | identity: 'person', 14 | datastore: 'foo', 15 | primaryKey: 'id', 16 | attributes: { 17 | id: { 18 | type: 'number' 19 | }, 20 | activated: { 21 | type: 'boolean' 22 | }, 23 | age: { 24 | type: 'number' 25 | }, 26 | name: { 27 | type: 'string' 28 | }, 29 | organization: { 30 | type: 'json' 31 | }, 32 | avatarBlob: { 33 | type: 'ref' 34 | } 35 | } 36 | })); 37 | 38 | orm.initialize({ 39 | adapters: { 40 | foobar: {} 41 | }, 42 | datastores: { 43 | foo: { adapter: 'foobar' } 44 | } 45 | }, function(err, orm) { 46 | if (err) { return done(err); } 47 | 48 | Person = orm.collections.person; 49 | return done(); 50 | });// 51 | 52 | });// 53 | 54 | it('should not modify ref types (and should return the original reference)', function() { 55 | 56 | var pretendIncomingBlobStream = new (require('stream').Readable)(); 57 | // Note that Waterline also ensures strict equality: 58 | assert(Person.validate('avatarBlob', pretendIncomingBlobStream) === pretendIncomingBlobStream); 59 | }); 60 | 61 | it('should accept EVEN the wildest nonsense, just like it is, and not change it, not even one little bit', function() { 62 | 63 | var wildNonsense = [ Waterline, assert, _ ]; 64 | wildNonsense.__proto__ = Waterline.prototype; 65 | wildNonsense.constructor = assert; 66 | wildNonsense.toJSON = _; 67 | wildNonsense.toString = Waterline; 68 | Object.defineProperty(wildNonsense, 'surprise', { 69 | enumerable: false, 70 | configurable: false, 71 | writable: false, 72 | value: wildNonsense 73 | }); 74 | Object.freeze(wildNonsense); 75 | wildNonsense.temperature = -Infinity; 76 | Object.seal(wildNonsense); 77 | wildNonsense.numSeals = NaN; 78 | wildNonsense.numSeaLions = Infinity; 79 | 80 | assert(Person.validate('avatarBlob', wildNonsense) === wildNonsense); 81 | }); 82 | 83 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 84 | // For further details on edge case handling, plus thousands more tests, see: 85 | // • http://npmjs.com/package/rttc 86 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 87 | 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /test/unit/collection/type-cast/cast.string.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../../../lib/waterline'); 4 | 5 | describe.skip('Type Casting ::', function() { 6 | describe('with `type: \'string\'` ::', function() { 7 | 8 | var orm; 9 | var Person; 10 | before(function(done) { 11 | orm = new Waterline(); 12 | 13 | orm.registerModel(Waterline.Model.extend({ 14 | identity: 'person', 15 | datastore: 'foo', 16 | primaryKey: 'id', 17 | attributes: { 18 | id: { 19 | type: 'number' 20 | }, 21 | activated: { 22 | type: 'boolean' 23 | }, 24 | age: { 25 | type: 'number' 26 | }, 27 | name: { 28 | type: 'string' 29 | }, 30 | organization: { 31 | type: 'json' 32 | }, 33 | avatarBlob: { 34 | type: 'ref' 35 | } 36 | } 37 | })); 38 | 39 | orm.initialize({ 40 | adapters: { 41 | foobar: {} 42 | }, 43 | datastores: { 44 | foo: { adapter: 'foobar' } 45 | } 46 | }, function(err, orm) { 47 | if (err) { return done(err); } 48 | 49 | Person = orm.collections.person; 50 | return done(); 51 | });// 52 | 53 | });// 54 | 55 | 56 | it('should cast numbers to strings', function() { 57 | assert.equal(Person.validate('name', 27), '27'); 58 | }); 59 | 60 | 61 | it('should throw E_VALIDATION error when a value can\'t be cast', function() { 62 | try { 63 | Person.validate('name', null); 64 | } catch (e) { 65 | switch (e.code) { 66 | case 'E_VALIDATION': 67 | // FUTURE: maybe expand test to check more things 68 | return; 69 | 70 | // As of Thu Dec 22, 2016, this test is failing because 71 | // validation is not being completely rolled up yet. 72 | default: throw new Error('The actual error code was "'+e.code+'" - but it should have been "E_VALIDATION": the rolled-up validation error. This is so that errors from the public `.validate()` are consistent with errors exposed when creating or updating records (i.e. when multiple values are being set at the same time.) Here is the error that was actually received:\n```\n' +e.stack+'\n```'); 73 | } 74 | } 75 | }); 76 | 77 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 78 | // For further details on edge case handling, plus thousands more tests, see: 79 | // • http://npmjs.com/package/rttc 80 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 81 | 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test/unit/collection/validations.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var util = require('util'); 3 | var _ = require('@sailshq/lodash'); 4 | var Waterline = require('../../../lib/waterline'); 5 | 6 | describe('Collection Validator ::', function() { 7 | describe('.validate()', function() { 8 | var person; 9 | var car; 10 | 11 | before(function(done) { 12 | var waterline = new Waterline(); 13 | 14 | var Person = Waterline.Model.extend({ 15 | identity: 'person', 16 | datastore: 'foo', 17 | primaryKey: 'id', 18 | attributes: { 19 | id: { 20 | type: 'number' 21 | }, 22 | age: { 23 | type: 'number' 24 | }, 25 | sex: { 26 | type: 'string', 27 | required: true, 28 | validations: { 29 | isIn: ['male', 'female'] 30 | } 31 | } 32 | } 33 | }); 34 | 35 | var Car = Waterline.Model.extend({ 36 | identity: 'car', 37 | datastore: 'foo', 38 | primaryKey: 'id', 39 | attributes: { 40 | id: { 41 | type: 'string', 42 | required: true, 43 | validations: { 44 | minLength: 6 45 | } 46 | } 47 | } 48 | }); 49 | 50 | waterline.registerModel(Person); 51 | waterline.registerModel(Car); 52 | 53 | var datastores = { 54 | 'foo': { 55 | adapter: 'foobar' 56 | } 57 | }; 58 | 59 | waterline.initialize({ adapters: { foobar: { update: function(con, query, cb) { return cb(); }, create: function(con, query, cb) { return cb(); } } }, datastores: datastores }, function(err, orm) { 60 | if (err) { 61 | return done(err); 62 | } 63 | person = orm.collections.person; 64 | car = orm.collections.car; 65 | done(); 66 | }); 67 | }); 68 | 69 | it('should not return any errors when no validation rules are violated', function(done) { 70 | person.create({ sex: 'male' }).exec(function(err) { 71 | assert(!err); 72 | return done(); 73 | }); 74 | }); 75 | 76 | it('should return an Error with name `UsageError` when a required field is not present in a `create`', function(done) { 77 | person.create({}).exec(function(err) { 78 | assert(err); 79 | assert.equal(err.name, 'UsageError'); 80 | assert(err.message.match(/required/)); 81 | return done(); 82 | }); 83 | }); 84 | 85 | it('should return an Error with name `UsageError` when a required string field is set to empty string in a `create`', function(done) { 86 | person.create({ sex: '' }).exec(function(err) { 87 | assert(err); 88 | assert.equal(err.name, 'UsageError'); 89 | assert(err.message.match(/required/)); 90 | return done(); 91 | }); 92 | }); 93 | 94 | it('should return an Error with name `UsageError` when a field is set to the wrong type in a `create`', function(done) { 95 | person.create({ name: 'foo', age: 'bar' }).exec(function(err) { 96 | assert(err); 97 | assert.equal(err.name, 'UsageError'); 98 | assert(err.message.match(/type/)); 99 | return done(); 100 | }); 101 | }); 102 | 103 | it('should return an Error with name `UsageError` when a field fails a validation rule in a `create`', function(done) { 104 | person.create({ name: 'foo', sex: 'bar' }).exec(function(err) { 105 | assert(err); 106 | assert.equal(err.name, 'UsageError'); 107 | assert(err.message.match(/rule/)); 108 | return done(); 109 | }); 110 | }); 111 | 112 | it('should not return an Error when a required field is not present in an `update`', function(done) { 113 | person.update({}, {}).exec(function(err) { 114 | assert(!err); 115 | return done(); 116 | }); 117 | }); 118 | 119 | it('should return an Error with name `UsageError` when a required string field is set to empty string in a `update`', function(done) { 120 | person.update({}, { sex: '' }).exec(function(err) { 121 | assert(err); 122 | assert.equal(err.name, 'UsageError'); 123 | assert(err.message.match(/required/)); 124 | return done(); 125 | }); 126 | }); 127 | 128 | it('should return an Error with name `UsageError` when a field is set to the wrong type in a `update`', function(done) { 129 | person.update({}, { age: 'bar' }).exec(function(err) { 130 | assert(err); 131 | assert.equal(err.name, 'UsageError'); 132 | assert(err.message.match(/type/)); 133 | return done(); 134 | }); 135 | }); 136 | 137 | it('should return an Error with name `UsageError` when a field fails a validation rule in a `update`', function(done) { 138 | person.update({}, { sex: 'bar' }).exec(function(err) { 139 | assert(err); 140 | assert.equal(err.name, 'UsageError'); 141 | assert(err.message.match(/rule/)); 142 | return done(); 143 | }); 144 | }); 145 | 146 | it('should not return any errors when a primary key does not violate any validations.', function(done) { 147 | 148 | car.create({ id: 'foobarbax' }).exec(function(err) { 149 | assert(!err); 150 | return done(); 151 | }); 152 | }); 153 | 154 | it('should return an Error with name `UsageError` when a primary key fails a validation rule in a `create`', function(done) { 155 | car.create({ id: 'foo' }).exec(function(err) { 156 | assert(err); 157 | assert.equal(err.name, 'UsageError'); 158 | assert(err.message.match(/rule/)); 159 | return done(); 160 | }); 161 | }); 162 | 163 | }); 164 | }); 165 | -------------------------------------------------------------------------------- /test/unit/query/associations/belongsTo.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var assert = require('assert'); 3 | var _ = require('@sailshq/lodash'); 4 | var Waterline = require('../../../../lib/waterline'); 5 | 6 | describe('Collection Query ::', function() { 7 | describe('belongs to association', function() { 8 | var Car; 9 | var generatedQuery; 10 | 11 | before(function(done) { 12 | var waterline = new Waterline(); 13 | var collections = {}; 14 | 15 | collections.user = Waterline.Model.extend({ 16 | identity: 'user', 17 | datastore: 'foo', 18 | primaryKey: 'uuid', 19 | attributes: { 20 | uuid: { 21 | type: 'string' 22 | }, 23 | name: { 24 | type: 'string', 25 | defaultsTo: 'Foo Bar' 26 | } 27 | } 28 | }); 29 | 30 | collections.car = Waterline.Model.extend({ 31 | identity: 'car', 32 | datastore: 'foo', 33 | primaryKey: 'id', 34 | attributes: { 35 | id: { 36 | type: 'number' 37 | }, 38 | driver: { 39 | model: 'user' 40 | } 41 | } 42 | }); 43 | 44 | waterline.registerModel(collections.user); 45 | waterline.registerModel(collections.car); 46 | 47 | // Fixture Adapter Def 48 | var adapterDef = { 49 | identity: 'foo', 50 | join: function(con, query, cb) { 51 | generatedQuery = query; 52 | return cb(); 53 | }, 54 | find: function(con, query, cb) { 55 | return cb(); 56 | }, 57 | findOne: function(con, query, cb) { 58 | return cb(); 59 | } 60 | }; 61 | 62 | var connections = { 63 | 'foo': { 64 | adapter: 'foobar' 65 | } 66 | }; 67 | 68 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 69 | if (err) { 70 | return done(err); 71 | } 72 | Car = orm.collections.car; 73 | return done(); 74 | }); 75 | }); 76 | 77 | it('should build a join query', function(done) { 78 | Car.find().limit(1) 79 | .populate('driver') 80 | .exec(function(err, cars) { 81 | if (err) { 82 | return done(err); 83 | } 84 | 85 | try { 86 | assert(_.isArray(cars), 'expecting array, but instead got:'+util.inspect(cars, {depth:5})); 87 | assert.equal(generatedQuery.joins[0].parent, 'car'); 88 | assert.equal(generatedQuery.joins[0].parentKey, 'driver'); 89 | assert.equal(generatedQuery.joins[0].child, 'user'); 90 | assert.equal(generatedQuery.joins[0].childKey, 'uuid'); 91 | assert.equal(generatedQuery.joins[0].removeParentKey, true); 92 | } catch (e) { return done(e); } 93 | 94 | return done(); 95 | }); 96 | }); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /test/unit/query/associations/hasMany.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../../../lib/waterline'); 4 | 5 | describe('Collection Query ::', function() { 6 | describe('has many association', function() { 7 | var User; 8 | var generatedQuery; 9 | 10 | before(function(done) { 11 | var waterline = new Waterline(); 12 | var collections = {}; 13 | 14 | collections.user = Waterline.Model.extend({ 15 | identity: 'user', 16 | datastore: 'foo', 17 | primaryKey: 'uuid', 18 | attributes: { 19 | uuid: { 20 | type: 'number' 21 | }, 22 | cars: { 23 | collection: 'car', 24 | via: 'driver' 25 | } 26 | } 27 | }); 28 | 29 | collections.car = Waterline.Model.extend({ 30 | identity: 'car', 31 | datastore: 'foo', 32 | primaryKey: 'id', 33 | attributes: { 34 | id: { 35 | type: 'number' 36 | }, 37 | driver: { 38 | model: 'user' 39 | } 40 | } 41 | }); 42 | 43 | waterline.registerModel(collections.user); 44 | waterline.registerModel(collections.car); 45 | 46 | // Fixture Adapter Def 47 | var adapterDef = { 48 | identity: 'foo', 49 | join: function(con, query, cb) { 50 | generatedQuery = query; 51 | return cb(); 52 | }, 53 | find: function(con, query, cb) { 54 | return cb(); 55 | } 56 | }; 57 | 58 | var connections = { 59 | 'foo': { 60 | adapter: 'foobar' 61 | } 62 | }; 63 | 64 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 65 | if (err) { 66 | return done(err); 67 | } 68 | User = orm.collections.user; 69 | return done(); 70 | }); 71 | }); 72 | 73 | it('should build a join query', function(done) { 74 | User.findOne(1) 75 | .populate('cars') 76 | .exec(function(err) { 77 | if(err) { 78 | return done(err); 79 | } 80 | 81 | assert.equal(generatedQuery.joins[0].parent, 'user'); 82 | assert.equal(generatedQuery.joins[0].parentKey, 'uuid'); 83 | assert.equal(generatedQuery.joins[0].child, 'car'); 84 | assert.equal(generatedQuery.joins[0].childKey, 'driver'); 85 | assert(_.isArray(generatedQuery.joins[0].criteria.select)); 86 | assert.equal(generatedQuery.joins[0].removeParentKey, false); 87 | 88 | return done(); 89 | }); 90 | }); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /test/unit/query/associations/manyToMany.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../../../lib/waterline'); 4 | 5 | describe('Collection Query ::', function() { 6 | describe('many to many association', function() { 7 | var User; 8 | var generatedQuery; 9 | 10 | before(function(done) { 11 | var waterline = new Waterline(); 12 | var collections = {}; 13 | 14 | collections.user = Waterline.Model.extend({ 15 | identity: 'user', 16 | datastore: 'foo', 17 | primaryKey: 'id', 18 | attributes: { 19 | id: { 20 | type: 'number', 21 | columnName: 'user_id' 22 | }, 23 | cars: { 24 | collection: 'car', 25 | via: 'drivers' 26 | } 27 | } 28 | }); 29 | 30 | collections.car = Waterline.Model.extend({ 31 | identity: 'car', 32 | datastore: 'foo', 33 | primaryKey: 'id', 34 | attributes: { 35 | id: { 36 | type: 'number', 37 | columnName: 'car_id' 38 | }, 39 | name: { 40 | type: 'string', 41 | columnName: 'car_name' 42 | }, 43 | drivers: { 44 | collection: 'user', 45 | via: 'cars', 46 | dominant: true 47 | } 48 | } 49 | }); 50 | 51 | waterline.registerModel(collections.user); 52 | waterline.registerModel(collections.car); 53 | 54 | // Fixture Adapter Def 55 | var adapterDef = { 56 | identity: 'foo', 57 | join: function(con, query, cb) { 58 | generatedQuery = query; 59 | return cb(); 60 | }, 61 | find: function(con, query, cb) { 62 | return cb(); 63 | } 64 | }; 65 | 66 | var connections = { 67 | 'foo': { 68 | adapter: 'foobar' 69 | } 70 | }; 71 | 72 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 73 | if (err) { 74 | return done(err); 75 | } 76 | User = orm.collections.user; 77 | return done(); 78 | }); 79 | }); 80 | 81 | it('should build a join query', function(done) { 82 | User.findOne(1) 83 | .populate('cars', { sort: [{'name': 'ASC'}]}) 84 | .exec(function(err) { 85 | if (err) { 86 | return done(err); 87 | } 88 | 89 | assert.equal(generatedQuery.joins.length, 2); 90 | assert.equal(generatedQuery.joins[0].parent, 'user'); 91 | assert.equal(generatedQuery.joins[0].parentKey, 'user_id'); 92 | assert.equal(generatedQuery.joins[0].child, 'car_drivers__user_cars'); 93 | assert.equal(generatedQuery.joins[0].childKey, 'user_cars'); 94 | assert.equal(generatedQuery.joins[0].select, false); 95 | assert.equal(generatedQuery.joins[0].removeParentKey, false); 96 | assert.equal(generatedQuery.joins[1].parent, 'car_drivers__user_cars'); 97 | assert.equal(generatedQuery.joins[1].parentKey, 'car_drivers'); 98 | assert.equal(generatedQuery.joins[1].child, 'car'); 99 | assert.equal(generatedQuery.joins[1].childKey, 'car_id'); 100 | assert(_.isArray(generatedQuery.joins[1].criteria.select)); 101 | assert.equal(generatedQuery.joins[1].criteria.select[0], 'car_id'); 102 | assert.equal(generatedQuery.joins[1].criteria.select[1], 'car_name'); 103 | assert(_.isArray(generatedQuery.joins[1].criteria.sort)); 104 | assert(generatedQuery.joins[1].criteria.sort[0].car_name); 105 | 106 | assert.equal(generatedQuery.joins[1].removeParentKey, false); 107 | 108 | return done(); 109 | }); 110 | }); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /test/unit/query/associations/populateArray.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Waterline = require('../../../../lib/waterline'); 3 | 4 | describe('Collection Query ::', function() { 5 | describe('specific populated associations ::', function() { 6 | var Car; 7 | 8 | before(function(done) { 9 | var waterline = new Waterline(); 10 | var collections = {}; 11 | 12 | collections.user = Waterline.Model.extend({ 13 | identity: 'user', 14 | datastore: 'foo', 15 | primaryKey: 'id', 16 | attributes: { 17 | id: { 18 | type: 'number' 19 | }, 20 | car: { 21 | model: 'car' 22 | }, 23 | name: { 24 | columnName: 'my_name', 25 | type: 'string' 26 | } 27 | } 28 | }); 29 | 30 | collections.ticket = Waterline.Model.extend({ 31 | identity: 'ticket', 32 | datastore: 'foo', 33 | primaryKey: 'id', 34 | attributes: { 35 | id: { 36 | type: 'number' 37 | }, 38 | reason: { 39 | columnName: 'reason', 40 | type: 'string' 41 | }, 42 | car: { 43 | model: 'car' 44 | } 45 | } 46 | }); 47 | 48 | collections.car = Waterline.Model.extend({ 49 | identity: 'car', 50 | datastore: 'foo', 51 | primaryKey: 'id', 52 | attributes: { 53 | id: { 54 | type: 'number' 55 | }, 56 | driver: { 57 | model: 'user', 58 | columnName: 'foobar' 59 | }, 60 | tickets: { 61 | collection: 'ticket', 62 | via: 'car' 63 | } 64 | } 65 | }); 66 | 67 | waterline.registerModel(collections.user); 68 | waterline.registerModel(collections.car); 69 | waterline.registerModel(collections.ticket); 70 | 71 | // Fixture Adapter Def 72 | var adapterDef = { 73 | identity: 'foo', 74 | find: function(con, query, cb) { 75 | if(query.using === 'user') { 76 | return cb(null, [{ id: 1, car: 1, name: 'John Doe' }]); 77 | } 78 | 79 | if(query.using === 'car') { 80 | return cb(null, [{ id: 1, foobar: 1, tickets: [1, 2]}]); 81 | } 82 | 83 | if(query.using === 'ticket') { 84 | return cb(null, [{ id: 1, reason: 'red light', car:1}, { id: 2, reason: 'Parking in a disabled space', car: 1 }]); 85 | } 86 | 87 | return cb(); 88 | } 89 | }; 90 | 91 | var connections = { 92 | 'foo': { 93 | adapter: 'foobar' 94 | } 95 | }; 96 | 97 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 98 | if (err) { 99 | return done(err); 100 | } 101 | 102 | Car = orm.collections.car; 103 | 104 | return done(); 105 | }); 106 | }); 107 | 108 | 109 | it('should populate all related collections', function(done) { 110 | Car.find() 111 | .populate('driver') 112 | .populate('tickets') 113 | .exec(function(err, car) { 114 | if (err) { 115 | return done(err); 116 | } 117 | 118 | assert(car[0].driver); 119 | assert(car[0].driver.name); 120 | assert(car[0].tickets); 121 | assert(car[0].tickets[0].car); 122 | assert(car[0].tickets[1].car); 123 | return done(); 124 | }); 125 | }); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /test/unit/query/associations/transformedPopulations.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Waterline = require('../../../../lib/waterline'); 3 | 4 | describe('Collection Query ::', function() { 5 | describe('populated associations ::', function() { 6 | var User; 7 | var Car; 8 | var generatedCriteria = {}; 9 | 10 | before(function(done) { 11 | var waterline = new Waterline(); 12 | var collections = {}; 13 | 14 | collections.user = Waterline.Model.extend({ 15 | identity: 'user', 16 | datastore: 'foo', 17 | primaryKey: 'id', 18 | attributes: { 19 | id: { 20 | type: 'number' 21 | }, 22 | car: { 23 | model: 'car' 24 | }, 25 | name: { 26 | columnName: 'my_name', 27 | type: 'string' 28 | } 29 | } 30 | }); 31 | 32 | collections.car = Waterline.Model.extend({ 33 | identity: 'car', 34 | datastore: 'foo', 35 | primaryKey: 'id', 36 | attributes: { 37 | id: { 38 | type: 'number' 39 | }, 40 | driver: { 41 | model: 'user', 42 | columnName: 'foobar' 43 | } 44 | } 45 | }); 46 | 47 | waterline.registerModel(collections.user); 48 | waterline.registerModel(collections.car); 49 | 50 | // Fixture Adapter Def 51 | var adapterDef = { 52 | identity: 'foo', 53 | find: function(con, query, cb) { 54 | generatedCriteria = query.criteria; 55 | if (query.using === 'user') { 56 | return cb(null, [{ id: 1, car: 1 }]); 57 | } 58 | 59 | if (query.using === 'car') { 60 | return cb(null, [{ id: 1, foobar: 1 }]); 61 | } 62 | 63 | return cb(); 64 | } 65 | }; 66 | 67 | var connections = { 68 | 'foo': { 69 | adapter: 'foobar' 70 | } 71 | }; 72 | 73 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 74 | if (err) { 75 | return done(err); 76 | } 77 | User = orm.collections.user; 78 | Car = orm.collections.car; 79 | return done(); 80 | }); 81 | }); 82 | 83 | 84 | it('should transform populated values', function(done) { 85 | User.find().populate('car').exec(function(err, users) { 86 | if (err) { 87 | return done(err); 88 | } 89 | 90 | assert(users[0].car); 91 | assert(users[0].car.driver); 92 | assert(!users[0].car.foobar); 93 | return done(); 94 | }); 95 | }); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/unit/query/query.autocreatedat.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../../lib/waterline'); 4 | 5 | describe('Collection Query ::', function() { 6 | describe('.create()', function() { 7 | describe('with autoCreatedAt', function() { 8 | var modelDef = { 9 | identity: 'user', 10 | datastore: 'foo', 11 | primaryKey: 'id', 12 | fetchRecordsOnCreate: true, 13 | attributes: { 14 | id: { 15 | type: 'number' 16 | }, 17 | stringdate: { 18 | type: 'string', 19 | autoCreatedAt: true 20 | }, 21 | numberdate: { 22 | type: 'number', 23 | autoCreatedAt: true 24 | }, 25 | refdate: { 26 | type: 'ref', 27 | autoCreatedAt: true 28 | }, 29 | } 30 | }; 31 | 32 | it('should use correct types for autoCreatedAt fields based on the attribute `type`', function(done) { 33 | var waterline = new Waterline(); 34 | waterline.registerModel(Waterline.Model.extend(_.extend({}, modelDef))); 35 | 36 | // Fixture Adapter Def 37 | var adapterDef = { 38 | create: function(con, query, cb) { 39 | assert.equal(typeof query.newRecord.numberdate, 'number'); 40 | assert.equal(typeof query.newRecord.stringdate, 'string'); 41 | assert.equal(typeof query.newRecord.refdate, 'object'); 42 | return cb(null, query.newRecord); 43 | } 44 | }; 45 | 46 | var connections = { 47 | 'foo': { 48 | adapter: 'foobar' 49 | } 50 | }; 51 | 52 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 53 | if (err) { 54 | return done(err); 55 | } 56 | orm.collections.user.create({ id: 1 }, done); 57 | }); 58 | }); 59 | 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/unit/query/query.autoupdatedat.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../../lib/waterline'); 4 | 5 | describe('Collection Query ::', function() { 6 | describe('.update()', function() { 7 | describe('with autoUpdatedAt', function() { 8 | var modelDef = { 9 | identity: 'user', 10 | datastore: 'foo', 11 | primaryKey: 'id', 12 | fetchRecordsOnCreate: true, 13 | attributes: { 14 | id: { 15 | type: 'number' 16 | }, 17 | stringdate: { 18 | type: 'string', 19 | autoUpdatedAt: true 20 | }, 21 | numberdate: { 22 | type: 'number', 23 | autoUpdatedAt: true 24 | }, 25 | refdate: { 26 | type: 'ref', 27 | autoUpdatedAt: true 28 | }, 29 | } 30 | }; 31 | 32 | it('should use correct types for autoUpdatedAt fields based on the attribute `type`', function(done) { 33 | var waterline = new Waterline(); 34 | waterline.registerModel(Waterline.Model.extend(_.extend({}, modelDef))); 35 | 36 | // Fixture Adapter Def 37 | var adapterDef = { update: function(con, query, cb) { query.valuesToSet.id = 1; return cb(null, [query.valuesToSet]); }}; 38 | 39 | var connections = { 40 | 'foo': { 41 | adapter: 'foobar' 42 | } 43 | }; 44 | 45 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 46 | if (err) { 47 | return done(err); 48 | } 49 | orm.collections.user.update({ id: 1 }, {}, function(err, records) { 50 | assert.equal(typeof records[0].numberdate, 'number'); 51 | assert.equal(typeof records[0].stringdate, 'string'); 52 | assert.equal(typeof records[0].refdate, 'object'); 53 | return done(); 54 | }, { fetch: true }); 55 | }); 56 | }); 57 | 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/unit/query/query.avg.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Waterline = require('../../../lib/waterline'); 3 | 4 | describe('Collection Query ::', function() { 5 | describe('.avg()', function() { 6 | var query; 7 | 8 | before(function(done) { 9 | // Extend for testing purposes 10 | var waterline = new Waterline(); 11 | var Model = Waterline.Model.extend({ 12 | identity: 'user', 13 | datastore: 'foo', 14 | primaryKey: 'id', 15 | attributes: { 16 | id: { 17 | type: 'number' 18 | }, 19 | age: { 20 | type: 'number' 21 | }, 22 | percent: { 23 | type: 'number' 24 | } 25 | } 26 | }); 27 | 28 | // Fixture Adapter Def 29 | var adapterDef = { 30 | avg: function(con, query, cb) { 31 | return cb(null, query); 32 | } 33 | }; 34 | 35 | waterline.registerModel(Model); 36 | 37 | var connections = { 38 | 'foo': { 39 | adapter: 'foobar' 40 | } 41 | }; 42 | 43 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 44 | if (err) { 45 | return done(err); 46 | } 47 | query = orm.collections.user; 48 | return done(); 49 | }); 50 | }); 51 | 52 | it('should return criteria with average set', function(done) { 53 | query.avg('age').exec(function(err, query) { 54 | if(err) { 55 | return done(err); 56 | } 57 | 58 | assert.equal(query.numericAttrName, 'age'); 59 | return done(); 60 | }); 61 | }); 62 | 63 | it('should NOT accept an array', function(done) { 64 | query.avg(['age', 'percent']).exec(function(err) { 65 | assert(err); 66 | return done(); 67 | }); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/unit/query/query.count.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Waterline = require('../../../lib/waterline'); 3 | 4 | describe('Collection Query ::', function() { 5 | describe('.count()', function() { 6 | 7 | var orm; 8 | before(function (done) { 9 | Waterline.start({ 10 | adapters: { 11 | 'sails-foobar': { 12 | identity: 'sails-foobar', 13 | count: function(datastoreName, s3q, cb) { 14 | return cb(undefined, 1); 15 | } 16 | } 17 | }, 18 | datastores: { 19 | default: { 20 | adapter: 'sails-foobar' 21 | } 22 | }, 23 | models: { 24 | user: { 25 | identity: 'user', 26 | datastore: 'default', 27 | primaryKey: 'id', 28 | attributes: { 29 | id: { type: 'number' }, 30 | name: { type: 'string' } 31 | } 32 | } 33 | } 34 | }, function (err, _orm) { 35 | if (err) { return done(err); } 36 | orm = _orm; 37 | return done(); 38 | }); 39 | 40 | });// 41 | 42 | after(function(done) { 43 | // Note that we don't bother attempting to stop the orm 44 | // if it doesn't even exist (i.e. because `.start()` failed). 45 | if (!orm) { return done(); } 46 | Waterline.stop(orm, done); 47 | }); 48 | 49 | it('should return a number representing the number of things', function(done) { 50 | Waterline.getModel('user', orm) 51 | .count({ name: 'foo' }, function(err, count) { 52 | if(err) { return done(err); } 53 | try { 54 | assert(typeof count === 'number'); 55 | assert(count > 0); 56 | } catch (e) { return done(e); } 57 | return done(); 58 | }); 59 | });// 60 | 61 | it('should allow a query to be built using deferreds', function(done) { 62 | Waterline.getModel('user', orm) 63 | .count() 64 | .exec(function(err, result) { 65 | if(err) { return done(err); } 66 | try { 67 | assert(result); 68 | } catch (e) { return done(e); } 69 | return done(); 70 | }); 71 | });// 72 | 73 | });// 74 | });// 75 | -------------------------------------------------------------------------------- /test/unit/query/query.count.transform.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Waterline = require('../../../lib/waterline'); 3 | 4 | describe('Collection Query ::', function() { 5 | describe('.count()', function() { 6 | describe('with transformed values', function() { 7 | var query; 8 | 9 | before(function(done) { 10 | var waterline = new Waterline(); 11 | var Model = Waterline.Model.extend({ 12 | identity: 'user', 13 | datastore: 'foo', 14 | primaryKey: 'id', 15 | attributes: { 16 | id: { 17 | type: 'number' 18 | }, 19 | name: { 20 | type: 'string', 21 | columnName: 'login' 22 | } 23 | } 24 | }); 25 | 26 | waterline.registerModel(Model); 27 | 28 | // Fixture Adapter Def 29 | var adapterDef = { 30 | count: function(con, query, cb) { 31 | assert(query.criteria.where.login); 32 | return cb(null, 1); 33 | } 34 | }; 35 | 36 | var connections = { 37 | 'foo': { 38 | adapter: 'foobar' 39 | } 40 | }; 41 | 42 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 43 | if (err) { 44 | return done(err); 45 | } 46 | query = orm.collections.user; 47 | return done(); 48 | }); 49 | }); 50 | 51 | it('should transform values before sending to adapter', function(done) { 52 | query.count({ name: 'foo' }, function(err, obj) { 53 | if(err) { 54 | return done(err); 55 | } 56 | assert.equal(obj, 1); 57 | return done(); 58 | }); 59 | }); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/unit/query/query.create.ref.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../../lib/waterline'); 4 | 5 | describe('Collection Query ::', function() { 6 | describe('.create()', function() { 7 | describe('with ref values', function() { 8 | 9 | it('should maintain object references for `ref` type attributes', function(done) { 10 | 11 | var modelDef = { 12 | identity: 'user', 13 | datastore: 'foo', 14 | primaryKey: 'id', 15 | fetchRecordsOnCreate: true, 16 | attributes: { 17 | id: { 18 | type: 'number' 19 | }, 20 | blob: { 21 | type: 'ref' 22 | } 23 | } 24 | }; 25 | 26 | var myBlob = new Buffer([1,2,3,4,5]); 27 | var waterline = new Waterline(); 28 | waterline.registerModel(Waterline.Model.extend(_.extend({}, modelDef))); 29 | 30 | // Fixture Adapter Def 31 | var adapterDef = { 32 | create: function(con, query, cb) { 33 | assert(query.newRecord.blob === myBlob); 34 | return cb(null, query.newRecord); 35 | } 36 | }; 37 | 38 | var connections = { 39 | 'foo': { 40 | adapter: 'foobar' 41 | } 42 | }; 43 | 44 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 45 | if (err) { 46 | return done(err); 47 | } 48 | orm.collections.user.create({ blob: myBlob, id: 1 }, done); 49 | }); 50 | });//it 51 | 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/unit/query/query.create.transform.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../../lib/waterline'); 4 | 5 | describe('Collection Query ::', function() { 6 | describe('.create()', function() { 7 | describe('with transformed values', function() { 8 | var modelDef = { 9 | identity: 'user', 10 | datastore: 'foo', 11 | primaryKey: 'id', 12 | fetchRecordsOnCreate: true, 13 | attributes: { 14 | id: { 15 | type: 'number' 16 | }, 17 | name: { 18 | type: 'string', 19 | columnName: 'login' 20 | } 21 | } 22 | }; 23 | 24 | it('should transform values before sending to adapter', function(done) { 25 | var waterline = new Waterline(); 26 | waterline.registerModel(Waterline.Model.extend(_.extend({}, modelDef))); 27 | 28 | // Fixture Adapter Def 29 | var adapterDef = { 30 | create: function(con, query, cb) { 31 | assert(query.newRecord.login); 32 | return cb(null, query.newRecord); 33 | } 34 | }; 35 | 36 | var connections = { 37 | 'foo': { 38 | adapter: 'foobar' 39 | } 40 | }; 41 | 42 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 43 | if (err) { 44 | return done(err); 45 | } 46 | orm.collections.user.create({ name: 'foo', id: 1 }, done); 47 | }); 48 | }); 49 | 50 | it('should transform values after receiving from adapter', function(done) { 51 | var waterline = new Waterline(); 52 | waterline.registerModel(Waterline.Model.extend(_.extend({}, modelDef))); 53 | 54 | // Fixture Adapter Def 55 | var adapterDef = { 56 | create: function(con, query, cb) { 57 | assert(query.newRecord.login); 58 | return cb(null, query.newRecord); 59 | } 60 | }; 61 | 62 | var connections = { 63 | 'foo': { 64 | adapter: 'foobar' 65 | } 66 | }; 67 | 68 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 69 | if (err) { 70 | return done(err); 71 | } 72 | 73 | orm.collections.user.create({ name: 'foo', id: 1 }, function(err, values) { 74 | if (err) { 75 | return done(err); 76 | } 77 | 78 | assert(values.name); 79 | assert(!values.login); 80 | return done(); 81 | }); 82 | }); 83 | }); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/unit/query/query.createEach.transform.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../../lib/waterline'); 4 | 5 | describe('Collection Query ::', function() { 6 | describe('.createEach()', function() { 7 | var modelDef = { 8 | identity: 'user', 9 | datastore: 'foo', 10 | primaryKey: 'id', 11 | fetchRecordsOnCreateEach: true, 12 | attributes: { 13 | id: { 14 | type: 'number' 15 | }, 16 | name: { 17 | type: 'string', 18 | defaultsTo: 'Foo Bar', 19 | columnName: 'login' 20 | } 21 | } 22 | }; 23 | 24 | 25 | it('should transform values before sending to adapter', function(done) { 26 | var waterline = new Waterline(); 27 | waterline.registerModel(Waterline.Model.extend(_.extend({}, modelDef))); 28 | 29 | // Fixture Adapter Def 30 | var adapterDef = { 31 | createEach: function(con, query, cb) { 32 | assert(_.first(query.newRecords).login); 33 | var id = 0; 34 | query.newRecords = _.map(query.newRecords, function(newRecord) { newRecord.id = ++id; return newRecord; }); 35 | return cb(null, query.newRecords); 36 | } 37 | }; 38 | 39 | var connections = { 40 | 'foo': { 41 | adapter: 'foobar' 42 | } 43 | }; 44 | 45 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 46 | if (err) { 47 | return done(err); 48 | } 49 | orm.collections.user.createEach([{ name: 'foo' }], done); 50 | }); 51 | }); 52 | 53 | it('should transform values after receiving from adapter', function(done) { 54 | var waterline = new Waterline(); 55 | waterline.registerModel(Waterline.Model.extend(_.extend({}, modelDef))); 56 | 57 | // Fixture Adapter Def 58 | var adapterDef = { 59 | createEach: function(con, query, cb) { 60 | var id = 0; 61 | query.newRecords = _.map(query.newRecords, function(newRecord) { newRecord.id = ++id; return newRecord; }); 62 | return cb(null, query.newRecords); 63 | } 64 | }; 65 | 66 | var connections = { 67 | 'foo': { 68 | adapter: 'foobar' 69 | } 70 | }; 71 | 72 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 73 | if (err) { 74 | return done(err); 75 | } 76 | orm.collections.user.createEach([{ name: 'foo' }], function(err, values) { 77 | if (err) { 78 | return done(err); 79 | } 80 | 81 | assert(values[0].name); 82 | assert(!values[0].login); 83 | 84 | return done(); 85 | }); 86 | }); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /test/unit/query/query.destroy.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Waterline = require('../../../lib/waterline'); 3 | 4 | describe('Collection Query ::', function() { 5 | describe('.destroy()', function() { 6 | 7 | describe('with Auto PK', function() { 8 | var query; 9 | 10 | before(function(done) { 11 | var waterline = new Waterline(); 12 | var Model = Waterline.Model.extend({ 13 | identity: 'user', 14 | datastore: 'foo', 15 | primaryKey: 'id', 16 | attributes: { 17 | id: { 18 | type: 'number' 19 | }, 20 | name: { 21 | type: 'string', 22 | defaultsTo: 'Foo Bar' 23 | } 24 | } 25 | }); 26 | 27 | waterline.registerModel(Model); 28 | 29 | // Fixture Adapter Def 30 | var adapterDef = { destroy: function(con, query, cb) { return cb(); }}; 31 | 32 | var connections = { 33 | 'foo': { 34 | adapter: 'foobar' 35 | } 36 | }; 37 | 38 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 39 | if (err) { 40 | return done(err); 41 | } 42 | query = orm.collections.user; 43 | return done(); 44 | }); 45 | }); 46 | 47 | it('should not return an error', function(done) { 48 | query.destroy({}, function(err) { 49 | if (err) { 50 | return done(err); 51 | } 52 | 53 | return done(); 54 | }); 55 | }); 56 | 57 | it('should allow a query to be built using deferreds', function(done) { 58 | query.destroy() 59 | .where({}) 60 | .exec(function(err) { 61 | if (err) { 62 | return done(err); 63 | } 64 | 65 | return done(); 66 | }); 67 | }); 68 | }); 69 | 70 | describe('with custom columnName set', function() { 71 | var query; 72 | 73 | before(function(done) { 74 | var waterline = new Waterline(); 75 | 76 | // Extend for testing purposes 77 | var Model = Waterline.Model.extend({ 78 | identity: 'user', 79 | datastore: 'foo', 80 | primaryKey: 'myPk', 81 | attributes: { 82 | name: { 83 | type: 'string', 84 | defaultsTo: 'Foo Bar' 85 | }, 86 | myPk: { 87 | type: 'number', 88 | columnName: 'pkColumn' 89 | } 90 | } 91 | }); 92 | 93 | waterline.registerModel(Model); 94 | 95 | // Fixture Adapter Def 96 | var adapterDef = { destroy: function(con, query, cb) { return cb(null, query.criteria); }}; 97 | 98 | var connections = { 99 | 'foo': { 100 | adapter: 'foobar' 101 | } 102 | }; 103 | 104 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 105 | if (err) { 106 | return done(err); 107 | } 108 | query = orm.collections.user; 109 | return done(); 110 | }); 111 | }); 112 | 113 | 114 | it('should use the custom primary key when a single value is passed in', function(done) { 115 | query.destroy(1, function(err) { 116 | if (err) { 117 | return done(err); 118 | } 119 | return done(); 120 | }); 121 | }); 122 | }); 123 | }); 124 | }); 125 | -------------------------------------------------------------------------------- /test/unit/query/query.destroy.transform.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Waterline = require('../../../lib/waterline'); 3 | 4 | describe('Collection Query ::', function() { 5 | describe('.destroy()', function() { 6 | describe('with transformed values', function() { 7 | var Model; 8 | 9 | before(function() { 10 | // Extend for testing purposes 11 | Model = Waterline.Model.extend({ 12 | identity: 'user', 13 | datastore: 'foo', 14 | primaryKey: 'id', 15 | attributes: { 16 | id: { 17 | type: 'number' 18 | }, 19 | name: { 20 | type: 'string', 21 | columnName: 'login' 22 | } 23 | } 24 | }); 25 | }); 26 | 27 | it('should transform values before sending to adapter', function(done) { 28 | var waterline = new Waterline(); 29 | waterline.registerModel(Model); 30 | 31 | // Fixture Adapter Def 32 | var adapterDef = { 33 | destroy: function(con, query, cb) { 34 | assert(query.criteria.where.login); 35 | return cb(); 36 | } 37 | }; 38 | 39 | var connections = { 40 | 'foo': { 41 | adapter: 'foobar' 42 | } 43 | }; 44 | 45 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 46 | if (err) { 47 | return done(err); 48 | } 49 | orm.collections.user.destroy({ name: 'foo' }, done); 50 | }); 51 | }); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/unit/query/query.exec.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var async = require('async'); 3 | var Waterline = require('../../../lib/waterline'); 4 | 5 | describe('Collection Query ::', function() { 6 | describe('.exec()', function() { 7 | var query; 8 | 9 | before(function(done) { 10 | var waterline = new Waterline(); 11 | var Model = Waterline.Model.extend({ 12 | identity: 'user', 13 | datastore: 'foo', 14 | primaryKey: 'id', 15 | attributes: { 16 | id: { 17 | type: 'number' 18 | }, 19 | name: { 20 | type: 'string', 21 | defaultsTo: 'Foo Bar' 22 | } 23 | } 24 | }); 25 | 26 | waterline.registerModel(Model); 27 | 28 | // Fixture Adapter Def 29 | var adapterDef = { 30 | find: function(con, query, cb) { 31 | return cb(null, [{id: 1}]); 32 | } 33 | }; 34 | 35 | var connections = { 36 | 'foo': { 37 | adapter: 'foobar' 38 | } 39 | }; 40 | 41 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 42 | if (err) { 43 | return done(err); 44 | } 45 | 46 | query = orm.collections.user; 47 | return done(); 48 | }); 49 | }); 50 | 51 | it('should work the same with .exec() as it does with a callback', function(done) { 52 | // .exec() usage 53 | query.find() 54 | .exec(function(err, results0) { 55 | if (err) { 56 | return done(err); 57 | } 58 | 59 | // callback usage 60 | query.find({}, {}, function(err, results1) { 61 | if (err) { 62 | return done(err); 63 | } 64 | assert.equal(results0.length, results1.length); 65 | return done(); 66 | }); 67 | }); 68 | }); 69 | 70 | describe.skip('when passed a switchback (object with multiple handlers)', function() { 71 | var _error; 72 | var _results; 73 | 74 | before(function getTheQueryResultsForTestsBelow(done) { 75 | async.auto({ 76 | objUsage: function(cb) { 77 | query.find() 78 | .exec({ 79 | success: function(results) { 80 | cb(null, results); 81 | }, 82 | error: cb 83 | }); 84 | }, 85 | 86 | cbUsage: function(cb) { 87 | query.find().exec(cb); 88 | } 89 | 90 | }, function asyncComplete(err, async_data) { 91 | // Save results for use below 92 | _error = err; 93 | _results = async_data; 94 | return done(); 95 | }); 96 | }); 97 | 98 | it('should not fail, and should work the same as it does w/ a callback', function() { 99 | assert(!_error, _error); 100 | assert.equal(_results.cbUsage.length, _results.objUsage.length); 101 | }); 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /test/unit/query/query.find.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../../lib/waterline'); 4 | 5 | describe('Collection Query ::', function() { 6 | describe('.find()', function() { 7 | var query; 8 | 9 | before(function(done) { 10 | var waterline = new Waterline(); 11 | var Model = Waterline.Model.extend({ 12 | identity: 'user', 13 | datastore: 'foo', 14 | primaryKey: 'id', 15 | schema: false, 16 | attributes: { 17 | id: { 18 | type: 'number' 19 | }, 20 | name: { 21 | type: 'string', 22 | defaultsTo: 'Foo Bar' 23 | } 24 | } 25 | }); 26 | 27 | waterline.registerModel(Model); 28 | 29 | // Fixture Adapter Def 30 | var adapterDef = { find: function(con, query, cb) { return cb(null, [{id: 1, criteria: query.criteria}]); }}; 31 | 32 | var connections = { 33 | 'foo': { 34 | adapter: 'foobar' 35 | } 36 | }; 37 | 38 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 39 | if(err) { 40 | return done(err); 41 | } 42 | query = orm.collections.user; 43 | return done(); 44 | }); 45 | }); 46 | 47 | it('should allow options to be optional', function(done) { 48 | query.find({}, function(err) { 49 | if(err) { 50 | return done(err); 51 | } 52 | 53 | return done(); 54 | }); 55 | }); 56 | 57 | it('should return an array', function(done) { 58 | query.find({}, {}, function(err, values) { 59 | if (err) { 60 | return done(err); 61 | } 62 | 63 | assert(_.isArray(values)); 64 | return done(); 65 | }); 66 | }); 67 | 68 | it('should allow a query to be built using deferreds', function(done) { 69 | query.find() 70 | .where({ name: 'Foo Bar' }) 71 | .where({ id: { '>': 1 } }) 72 | .limit(1) 73 | .skip(1) 74 | .sort([{ name: 'desc' }]) 75 | .exec(function(err, results) { 76 | if (err) { 77 | return done(err); 78 | } 79 | 80 | assert(_.isArray(results)); 81 | assert.equal(results[0].criteria.limit, 1); 82 | assert.equal(results[0].criteria.skip, 1); 83 | assert.equal(results[0].criteria.sort[0].name, 'DESC'); 84 | 85 | return done(); 86 | }); 87 | }); 88 | 89 | describe('.paginate()', function() { 90 | it('should skip to 0 and limit to 30 by default', function(done) { 91 | query.find() 92 | .paginate(0) 93 | .exec(function(err, results) { 94 | if (err) { 95 | return done(err); 96 | } 97 | 98 | assert(_.isArray(results)); 99 | assert.equal(results[0].criteria.skip, 0); 100 | assert.equal(results[0].criteria.limit, 30); 101 | 102 | return done(); 103 | }); 104 | }); 105 | 106 | it('should set skip to 0 from page 0', function(done) { 107 | query.find() 108 | .paginate(1) 109 | .exec(function(err, results) { 110 | if (err) { 111 | return done(err); 112 | } 113 | 114 | assert.equal(results[0].criteria.skip, 30); 115 | return done(); 116 | }); 117 | }); 118 | 119 | it('should set skip to 0 from page 1', function(done) { 120 | query.find() 121 | .paginate(1) 122 | .exec(function(err, results) { 123 | if (err) { 124 | return done(err); 125 | } 126 | 127 | assert.equal(results[0].criteria.skip, 30); 128 | return done(); 129 | }); 130 | }); 131 | 132 | it('should set skip to 30', function(done) { 133 | query.find() 134 | .paginate(2) 135 | .exec(function(err, results) { 136 | if (err) { 137 | return done(err); 138 | } 139 | 140 | assert.equal(results[0].criteria.skip, 60); 141 | return done(); 142 | }); 143 | }); 144 | 145 | it('should set limit to 1', function(done) { 146 | query.find() 147 | .paginate(1, 1) 148 | .exec(function(err, results) { 149 | if (err) { 150 | return done(err); 151 | } 152 | 153 | assert.equal(results[0].criteria.limit, 1); 154 | return done(); 155 | }); 156 | }); 157 | 158 | it('should set skip to 20 and limit to 10', function(done) { 159 | query.find() 160 | .paginate(2, 10) 161 | .exec(function(err, results) { 162 | if (err) { 163 | return done(err); 164 | } 165 | 166 | assert.equal(results[0].criteria.skip, 20); 167 | assert.equal(results[0].criteria.limit, 10); 168 | return done(); 169 | }); 170 | }); 171 | 172 | it('should set skip to 30 and limit to 10', function(done) { 173 | query.find() 174 | .paginate(3, 10) 175 | .exec(function(err, results) { 176 | if (err) { 177 | return done(err); 178 | } 179 | 180 | assert.equal(results[0].criteria.skip, 30); 181 | assert.equal(results[0].criteria.limit, 10); 182 | return done(); 183 | }); 184 | }); 185 | }); 186 | }); 187 | }); 188 | -------------------------------------------------------------------------------- /test/unit/query/query.findOne.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var util = require('util'); 3 | var _ = require('@sailshq/lodash'); 4 | var Waterline = require('../../../lib/waterline'); 5 | 6 | describe('Collection Query ::', function() { 7 | describe('.findOne()', function() { 8 | describe('with autoPK', function() { 9 | var query; 10 | 11 | before(function(done) { 12 | var waterline = new Waterline(); 13 | var Model = Waterline.Model.extend({ 14 | identity: 'user', 15 | datastore: 'foo', 16 | primaryKey: 'id', 17 | attributes: { 18 | id: { 19 | type: 'number' 20 | }, 21 | name: { 22 | type: 'string', 23 | defaultsTo: 'Foo Bar' 24 | } 25 | } 26 | }); 27 | 28 | waterline.registerModel(Model); 29 | 30 | // Fixture Adapter Def 31 | var adapterDef = { find: function(con, query, cb) { return cb(null, [{id: 1, criteria: query.criteria}]); }}; 32 | 33 | var connections = { 34 | 'foo': { 35 | adapter: 'foobar' 36 | } 37 | }; 38 | 39 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 40 | if (err) { 41 | return done(err); 42 | } 43 | query = orm.collections.user; 44 | return done(); 45 | }); 46 | }); 47 | 48 | it('should allow an integer to be passed in as criteria', function(done) { 49 | query.findOne(1, function(err, record) { 50 | if (err) { 51 | return done(err); 52 | } 53 | 54 | assert(_.isObject(record.criteria.where), 'Expected `record.where` to be a dictionary, but it is not. Here is `record`:\n```\n'+util.inspect(record,{depth:5})+'\n```\n'); 55 | assert.equal(record.criteria.where.id, 1); 56 | return done(); 57 | }); 58 | }); 59 | 60 | it('should allow a query to be built using deferreds', function(done) { 61 | query.findOne() 62 | .where({ 63 | name: 'Foo Bar', 64 | id: { 65 | '>': 1 66 | } 67 | }) 68 | .exec(function(err, results) { 69 | if (err) { 70 | return done(err); 71 | } 72 | 73 | assert(!_.isArray(results)); 74 | assert.equal(_.keys(results.criteria.where).length, 1); 75 | assert.equal(results.criteria.where.and[0].name, 'Foo Bar'); 76 | assert.equal(results.criteria.where.and[1].id['>'], 1); 77 | return done(); 78 | }); 79 | }); 80 | }); 81 | 82 | describe('with custom PK', function() { 83 | describe('with no columnName set', function() { 84 | var query; 85 | 86 | before(function(done) { 87 | 88 | var waterline = new Waterline(); 89 | 90 | // Extend for testing purposes 91 | var Model = Waterline.Model.extend({ 92 | identity: 'user', 93 | datastore: 'foo', 94 | primaryKey: 'myPk', 95 | attributes: { 96 | name: { 97 | type: 'string', 98 | defaultsTo: 'Foo Bar' 99 | }, 100 | myPk: { 101 | type: 'number' 102 | } 103 | } 104 | }); 105 | 106 | waterline.registerModel(Model); 107 | 108 | // Fixture Adapter Def 109 | var adapterDef = { find: function(con, query, cb) { return cb(null, [{myPk: 1, criteria: query.criteria}]); }}; 110 | 111 | var connections = { 112 | 'foo': { 113 | adapter: 'foobar' 114 | } 115 | }; 116 | 117 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 118 | if (err) { 119 | return done(err); 120 | } 121 | query = orm.collections.user; 122 | return done(); 123 | }); 124 | }); 125 | 126 | 127 | it('should use the custom primary key when a single value is passed in', function(done) { 128 | query.findOne(1, function(err, values) { 129 | if (err) { 130 | return done(err); 131 | } 132 | assert.equal(values.criteria.where.myPk, 1); 133 | return done(); 134 | }); 135 | }); 136 | }); 137 | 138 | describe('with custom columnName set', function() { 139 | var query; 140 | 141 | before(function(done) { 142 | 143 | var waterline = new Waterline(); 144 | 145 | // Extend for testing purposes 146 | var Model = Waterline.Model.extend({ 147 | identity: 'user', 148 | datastore: 'foo', 149 | primaryKey: 'myPk', 150 | attributes: { 151 | name: { 152 | type: 'string', 153 | defaultsTo: 'Foo Bar' 154 | }, 155 | myPk: { 156 | type: 'number', 157 | columnName: 'pkColumn' 158 | } 159 | } 160 | }); 161 | 162 | waterline.registerModel(Model); 163 | 164 | // Fixture Adapter Def 165 | var adapterDef = { find: function(con, query, cb) { return cb(null, [{myPk: 1, criteria: query.criteria}]); }}; 166 | 167 | var connections = { 168 | 'foo': { 169 | adapter: 'foobar' 170 | } 171 | }; 172 | 173 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 174 | if (err) { 175 | return done(err); 176 | } 177 | 178 | query = orm.collections.user; 179 | return done(); 180 | }); 181 | }); 182 | 183 | it('should use the custom primary key when a single value is passed in', function(done) { 184 | query.findOne(1, function(err, values) { 185 | if (err) { 186 | return done(err); 187 | } 188 | 189 | assert.equal(values.criteria.where.pkColumn, 1); 190 | return done(); 191 | }); 192 | }); 193 | }); 194 | }); 195 | }); 196 | }); 197 | -------------------------------------------------------------------------------- /test/unit/query/query.findOne.transform.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../../lib/waterline'); 4 | 5 | describe('Collection Query ::', function() { 6 | describe('.findOne()', function() { 7 | describe('with transformed values', function() { 8 | var modelDef = { 9 | identity: 'user', 10 | datastore: 'foo', 11 | primaryKey: 'id', 12 | attributes: { 13 | id: { 14 | type: 'number' 15 | }, 16 | name: { 17 | type: 'string', 18 | columnName: 'login' 19 | } 20 | } 21 | }; 22 | 23 | it('should transform criteria before sending to adapter', function(done) { 24 | var waterline = new Waterline(); 25 | waterline.registerModel(Waterline.Model.extend(_.extend({}, modelDef))); 26 | 27 | // Fixture Adapter Def 28 | var adapterDef = { 29 | find: function(con, query, cb) { 30 | assert(query.criteria.where.login); 31 | return cb(null, [{id: 1, criteria: query.criteria}]); 32 | } 33 | }; 34 | 35 | var connections = { 36 | 'foo': { 37 | adapter: 'foobar' 38 | } 39 | }; 40 | 41 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 42 | if (err) { 43 | return done(err); 44 | } 45 | orm.collections.user.findOne({ where: { name: 'foo' }}, done); 46 | }); 47 | }); 48 | 49 | it('should transform values after receiving from adapter', function(done) { 50 | var waterline = new Waterline(); 51 | waterline.registerModel(Waterline.Model.extend(_.extend({}, modelDef))); 52 | 53 | // Fixture Adapter Def 54 | var adapterDef = { 55 | find: function(con, query, cb) { 56 | assert(query.criteria.where.login); 57 | return cb(null, [{ id: 1, login: 'foo' }]); 58 | } 59 | }; 60 | 61 | var connections = { 62 | 'foo': { 63 | adapter: 'foobar' 64 | } 65 | }; 66 | 67 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 68 | if (err) { 69 | return done(err); 70 | } 71 | orm.collections.user.findOne({ name: 'foo' }, function(err, values) { 72 | if (err) { 73 | return done(err); 74 | } 75 | assert(values.name); 76 | assert(!values.login); 77 | return done(); 78 | }); 79 | }); 80 | }); 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test/unit/query/query.findOrCreate.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Waterline = require('../../../lib/waterline'); 3 | 4 | describe('Collection Query ::', function() { 5 | describe('.findOrCreate()', function() { 6 | describe('with proper values', function() { 7 | var query; 8 | 9 | before(function(done) { 10 | var waterline = new Waterline(); 11 | var Model = Waterline.Model.extend({ 12 | identity: 'user', 13 | datastore: 'foo', 14 | primaryKey: 'id', 15 | fetchRecordsOnCreate: true, 16 | fetchRecordsOnCreateEach: true, 17 | attributes: { 18 | id: { 19 | type: 'number' 20 | }, 21 | name: { 22 | type: 'string', 23 | defaultsTo: 'Foo Bar' 24 | } 25 | } 26 | }); 27 | 28 | waterline.registerModel(Model); 29 | 30 | // Fixture Adapter Def 31 | var adapterDef = { 32 | find: function(con, query, cb) { return cb(null, []); }, 33 | create: function(con, query, cb) { query.newRecord.id = 1; return cb(null, query.newRecord); } 34 | }; 35 | 36 | var connections = { 37 | 'foo': { 38 | adapter: 'foobar' 39 | } 40 | }; 41 | 42 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 43 | if (err) { 44 | return done(err); 45 | } 46 | query = orm.collections.user; 47 | return done(); 48 | }); 49 | }); 50 | 51 | it('should set default values', function(done) { 52 | query.findOrCreate({ name: 'Foo Bar' }, {}, function(err, status, created) { 53 | if (err) { 54 | return done(err); 55 | } 56 | 57 | assert.equal(status.name, 'Foo Bar'); 58 | assert.equal(created, true); 59 | 60 | return done(); 61 | }); 62 | }); 63 | 64 | it('should set default values with exec', function(done) { 65 | query.findOrCreate({ name: 'Foo Bar' }).exec(function(err, status, created) { 66 | if (err) { 67 | return done(err); 68 | } 69 | 70 | assert.equal(status.name, 'Foo Bar'); 71 | assert.equal(created, true); 72 | 73 | return done(); 74 | }); 75 | }); 76 | 77 | 78 | 79 | it('should set values', function(done) { 80 | query.findOrCreate({ name: 'Foo Bar' }, { name: 'Bob' }, function(err, status, created) { 81 | if (err) { 82 | return done(err); 83 | } 84 | 85 | assert.equal(status.name, 'Bob'); 86 | assert.equal(created, true); 87 | 88 | return done(); 89 | }); 90 | }); 91 | 92 | it('should strip values that don\'t belong to the schema', function(done) { 93 | query.findOrCreate({ name: 'Foo Bar'}, { foo: 'bar' }, function(err, values) { 94 | if (err) { 95 | return done(err); 96 | } 97 | 98 | assert(!values.foo); 99 | return done(); 100 | }); 101 | }); 102 | }); 103 | 104 | describe('casting values', function() { 105 | var query; 106 | 107 | before(function(done) { 108 | var waterline = new Waterline(); 109 | var Model = Waterline.Model.extend({ 110 | identity: 'user', 111 | datastore: 'foo', 112 | primaryKey: 'id', 113 | fetchRecordsOnCreate: true, 114 | fetchRecordsOnCreateEach: true, 115 | attributes: { 116 | id: { 117 | type: 'number' 118 | }, 119 | name: { 120 | type: 'string' 121 | }, 122 | age: { 123 | type: 'number' 124 | } 125 | } 126 | }); 127 | 128 | waterline.registerModel(Model); 129 | 130 | // Fixture Adapter Def 131 | var adapterDef = { 132 | find: function(con, query, cb) { return cb(null, []); }, 133 | create: function(con, query, cb) { query.newRecord.id = 1; return cb(null, query.newRecord); } 134 | }; 135 | 136 | var connections = { 137 | 'foo': { 138 | adapter: 'foobar' 139 | } 140 | }; 141 | 142 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 143 | if (err) { 144 | return done(err); 145 | } 146 | query = orm.collections.user; 147 | return done(); 148 | }); 149 | }); 150 | 151 | it('should cast values before sending to adapter', function(done) { 152 | query.findOrCreate({ name: 'Foo Bar' }, { name: 'foo', age: '27' }, function(err, values, created) { 153 | if (err) { 154 | return done(err); 155 | } 156 | assert.equal(values.name, 'foo'); 157 | assert.equal(values.age, 27); 158 | assert.equal(created, true); 159 | 160 | return done(); 161 | }); 162 | }); 163 | }); 164 | }); 165 | }); 166 | -------------------------------------------------------------------------------- /test/unit/query/query.findOrCreate.transform.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../../lib/waterline'); 4 | 5 | describe('Collection Query ::', function() { 6 | describe('.findOrCreate()', function() { 7 | describe('with transformed values', function() { 8 | var modelDef = { 9 | identity: 'user', 10 | datastore: 'foo', 11 | primaryKey: 'id', 12 | fetchRecordsOnCreate: true, 13 | fetchRecordsOnCreateEach: true, 14 | attributes: { 15 | id: { 16 | type: 'number' 17 | }, 18 | name: { 19 | type: 'string', 20 | columnName: 'login' 21 | } 22 | } 23 | }; 24 | 25 | it('should transform criteria before sending to adapter', function(done) { 26 | var waterline = new Waterline(); 27 | waterline.registerModel(Waterline.Model.extend(_.extend({}, modelDef))); 28 | 29 | // Fixture Adapter Def 30 | var adapterDef = { 31 | find: function(con, query, cb) { 32 | assert(query.criteria.where.login); 33 | return cb(null, []); 34 | }, 35 | create: function(con, query, cb) { 36 | assert(query.newRecord.login); 37 | query.newRecord.id = 1; 38 | return cb(null, query.newRecord); 39 | } 40 | }; 41 | 42 | var connections = { 43 | 'foo': { 44 | adapter: 'foobar' 45 | } 46 | }; 47 | 48 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 49 | if (err) { 50 | return done(err); 51 | } 52 | orm.collections.user.findOrCreate({ where: { name: 'foo' }}, { name: 'foo' }, done); 53 | }); 54 | }); 55 | 56 | it('should transform values before sending to adapter', function(done) { 57 | var waterline = new Waterline(); 58 | waterline.registerModel(Waterline.Model.extend(_.extend({}, modelDef))); 59 | 60 | // Fixture Adapter Def 61 | var adapterDef = { 62 | find: function(con, query, cb) { 63 | assert(query.criteria.where.login); 64 | return cb(undefined, []); 65 | }, 66 | create: function(con, query, cb) { 67 | assert(query.newRecord.login); 68 | query.newRecord.id = 1; 69 | return cb(undefined, query.newRecord); 70 | } 71 | }; 72 | 73 | var connections = { 74 | 'foo': { 75 | adapter: 'foobar' 76 | } 77 | }; 78 | 79 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 80 | if (err) { 81 | return done(err); 82 | } 83 | orm.collections.user.findOrCreate({ where: { name: 'foo' }}, { name: 'foo' }, done); 84 | }); 85 | }); 86 | 87 | it('should transform values after receiving from adapter', function(done) { 88 | var waterline = new Waterline(); 89 | waterline.registerModel(Waterline.Model.extend(_.extend({}, modelDef))); 90 | 91 | // Fixture Adapter Def 92 | var adapterDef = { 93 | find: function(con, query, cb) { 94 | assert(query.criteria.where.login); 95 | return cb(undefined, []); 96 | }, 97 | create: function(con, query, cb) { 98 | assert(query.newRecord.login); 99 | query.newRecord.id = 1; 100 | return cb(undefined, query.newRecord); 101 | } 102 | }; 103 | 104 | var connections = { 105 | 'foo': { 106 | adapter: 'foobar' 107 | } 108 | }; 109 | 110 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 111 | if (err) { 112 | return done(err); 113 | } 114 | 115 | orm.collections.user.findOrCreate({ where: { name: 'foo' }}, { name: 'foo' }, function(err, values) { 116 | if (err) { 117 | return done(err); 118 | } 119 | 120 | assert(values.name); 121 | assert(!values.login); 122 | return done(); 123 | }); 124 | }); 125 | }); 126 | }); 127 | }); 128 | }); 129 | -------------------------------------------------------------------------------- /test/unit/query/query.promises.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Waterline = require('../../../lib/waterline'); 3 | 4 | describe('Collection Promise ::', function() { 5 | describe('.then()', function() { 6 | var query; 7 | 8 | before(function(done) { 9 | var waterline = new Waterline(); 10 | var Model = Waterline.Model.extend({ 11 | identity: 'user', 12 | datastore: 'foo', 13 | primaryKey: 'id', 14 | attributes: { 15 | id: { 16 | type: 'number' 17 | }, 18 | name: { 19 | type: 'string', 20 | defaultsTo: 'Foo Bar' 21 | } 22 | } 23 | }); 24 | 25 | waterline.registerModel(Model); 26 | 27 | // Fixture Adapter Def 28 | var adapterDef = { 29 | find: function(con, query, cb) { 30 | return cb(undefined, [{id: 1, criteria: query.criteria}]); 31 | } 32 | }; 33 | 34 | var connections = { 35 | 'foo': { 36 | adapter: 'foobar' 37 | } 38 | }; 39 | 40 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 41 | if (err) { 42 | return done(err); 43 | } 44 | 45 | query = orm.collections.user; 46 | return done(); 47 | }); 48 | }); 49 | 50 | it('should return a promise object', function(done) { 51 | query.find({}).then(function(obj) { 52 | assert(obj); 53 | return 'test'; 54 | }).then(function(test) { 55 | assert.equal(test, 'test'); 56 | return done(); 57 | }).catch(function(err) { 58 | return done(err); 59 | }); 60 | }); 61 | 62 | it('should reject the promise if the then handler fails', function(done) { 63 | query.find({}).then(function() { 64 | throw new Error("Error in promise handler"); 65 | }).then(function() { 66 | return done(new Error('Unexpected success')); 67 | }).catch(function() { 68 | return done(); 69 | }); 70 | }); 71 | 72 | it('should only resolve once', function(done){ 73 | var promise = query.find({}); 74 | var prevResult; 75 | promise 76 | .then(function(result) { 77 | prevResult = result; 78 | return promise; 79 | }).then(function(result) { 80 | assert.strictEqual(result, prevResult, 'Previous and current result should be equal'); 81 | done(); 82 | }) 83 | .catch(function(err) { 84 | done(err); 85 | }); 86 | }); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /test/unit/query/query.stream.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Waterline = require('../../../lib/waterline'); 3 | 4 | describe('Collection Query ::', function() { 5 | describe('.stream()', function() { 6 | var query; 7 | 8 | var records = []; 9 | for (var i = 1; i <= 100; i++) { 10 | records.push({ 11 | id: i, 12 | name: 'user_' + i 13 | }); 14 | } 15 | 16 | before(function(done) { 17 | var waterline = new Waterline(); 18 | var Model = Waterline.Model.extend({ 19 | identity: 'user', 20 | datastore: 'foo', 21 | primaryKey: 'id', 22 | attributes: { 23 | id: { 24 | type: 'number' 25 | }, 26 | name: { 27 | type: 'string', 28 | defaultsTo: 'Foo Bar' 29 | } 30 | } 31 | }); 32 | 33 | waterline.registerModel(Model); 34 | 35 | // Fixture Adapter Def 36 | var adapterDef = { 37 | find: function(datastore, query, cb) { 38 | return cb(undefined, records.slice(query.criteria.skip, query.criteria.skip + query.criteria.limit)); 39 | } 40 | }; 41 | 42 | var connections = { 43 | 'foo': { 44 | adapter: 'foobar' 45 | } 46 | }; 47 | 48 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 49 | if (err) { 50 | return done(err); 51 | } 52 | query = orm.collections.user; 53 | return done(); 54 | }); 55 | }); 56 | 57 | it('should allow streaming a single record at a time', function(done) { 58 | 59 | var sum = 0; 60 | var stream = query.stream({}).eachRecord(function(rec, next) { 61 | sum += rec.id; 62 | return next(); 63 | }).exec(function(err) { 64 | if (err) {return done(err);} 65 | try { 66 | assert.equal(sum, 5050); 67 | } catch (e) {return done(e);} 68 | return done(); 69 | }); 70 | }); 71 | 72 | it('should allow streaming a batch of records at a time', function(done) { 73 | 74 | var batch = 0; 75 | var stream = query.stream({}).eachBatch(function(recs, next) { 76 | batch += recs.length; 77 | return next(); 78 | }).exec(function(err) { 79 | if (err) {return done(err);} 80 | try { 81 | assert.equal(batch, 100); 82 | } catch (e) {return done(e);} 83 | return done(); 84 | }); 85 | }); 86 | 87 | it('should work correctly with `.skip()` and `.limit()`', function(done) { 88 | 89 | var sum = 0; 90 | var stream = query.stream({}).skip(10).limit(50).eachRecord(function(rec, next) { 91 | sum += rec.id; 92 | return next(); 93 | }).exec(function(err) { 94 | if (err) {return done(err);} 95 | try { 96 | assert.equal(sum, 1775); 97 | } catch (e) {return done(e);} 98 | return done(); 99 | }); 100 | }); 101 | 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /test/unit/query/query.sum.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../../lib/waterline'); 4 | 5 | describe('Collection Query ::', function() { 6 | describe('.sum()', function() { 7 | var query; 8 | 9 | before(function(done) { 10 | var waterline = new Waterline(); 11 | var Model = Waterline.Model.extend({ 12 | identity: 'user', 13 | datastore: 'foo', 14 | primaryKey: 'id', 15 | attributes: { 16 | id: { 17 | type: 'number' 18 | }, 19 | age: { 20 | type: 'number' 21 | }, 22 | percent: { 23 | type: 'number' 24 | } 25 | } 26 | }); 27 | 28 | waterline.registerModel(Model); 29 | 30 | // Fixture Adapter Def 31 | var adapterDef = { 32 | sum: function(con, query, cb) { 33 | return cb(undefined, [query]); 34 | } 35 | }; 36 | 37 | var connections = { 38 | 'foo': { 39 | adapter: 'foobar' 40 | } 41 | }; 42 | 43 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 44 | if (err) { 45 | return done(err); 46 | } 47 | query = orm.collections.user; 48 | return done(); 49 | }); 50 | }); 51 | 52 | it('should return criteria with sum set', function(done) { 53 | query.sum('age') 54 | .exec(function(err, obj) { 55 | if (err) { 56 | return done(err); 57 | } 58 | 59 | assert.equal(_.first(obj).method, 'sum'); 60 | assert.equal(_.first(obj).numericAttrName, 'age'); 61 | return done(); 62 | }); 63 | }); 64 | 65 | it('should NOT accept an array', function(done) { 66 | query.sum(['age', 'percent']) 67 | .exec(function(err) { 68 | assert(err); 69 | return done(); 70 | }); 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/unit/query/query.update.transform.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | var Waterline = require('../../../lib/waterline'); 4 | 5 | describe('Collection Query ::', function() { 6 | describe('.update()', function() { 7 | describe('with transformed values', function() { 8 | var modelDef = { 9 | identity: 'user', 10 | datastore: 'foo', 11 | primaryKey: 'id', 12 | attributes: { 13 | id: { 14 | type: 'number' 15 | }, 16 | name: { 17 | type: 'string', 18 | columnName: 'login' 19 | } 20 | } 21 | }; 22 | 23 | 24 | it('should transform criteria before sending to adapter', function(done) { 25 | var waterline = new Waterline(); 26 | waterline.registerModel(Waterline.Model.extend(_.extend({}, modelDef))); 27 | 28 | // Fixture Adapter Def 29 | var adapterDef = { 30 | update: function(con, query, cb) { 31 | assert(query.criteria.where.login); 32 | return cb(undefined); 33 | } 34 | }; 35 | 36 | var connections = { 37 | 'foo': { 38 | adapter: 'foobar' 39 | } 40 | }; 41 | 42 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 43 | if (err) { 44 | return done(err); 45 | } 46 | orm.collections.user.update({ where: { name: 'foo' }}, { name: 'foo' }, done); 47 | }); 48 | }); 49 | 50 | it('should transform values before sending to adapter', function(done) { 51 | var waterline = new Waterline(); 52 | waterline.registerModel(Waterline.Model.extend(_.extend({}, modelDef))); 53 | 54 | // Fixture Adapter Def 55 | var adapterDef = { 56 | update: function(con, query, cb) { 57 | assert(query.valuesToSet.login); 58 | return cb(undefined); 59 | } 60 | }; 61 | 62 | var connections = { 63 | 'foo': { 64 | adapter: 'foobar' 65 | } 66 | }; 67 | 68 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 69 | if (err) { 70 | return done(err); 71 | } 72 | orm.collections.user.update({ where: { name: 'foo' }}, { name: 'foo' }, done); 73 | }); 74 | }); 75 | 76 | it('should transform values after receiving from adapter', function(done) { 77 | var waterline = new Waterline(); 78 | waterline.registerModel(Waterline.Model.extend(_.extend({}, modelDef))); 79 | 80 | // Fixture Adapter Def 81 | var adapterDef = { 82 | update: function(con, query, cb) { 83 | assert(query.valuesToSet.login); 84 | query.valuesToSet.id = 1; 85 | return cb(undefined, [query.valuesToSet]); 86 | } 87 | }; 88 | 89 | var connections = { 90 | 'foo': { 91 | adapter: 'foobar' 92 | } 93 | }; 94 | 95 | waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) { 96 | if (err) { 97 | return done(err); 98 | } 99 | 100 | orm.collections.user.update({}, { name: 'foo' }, function(err, values) { 101 | if (err) { 102 | return done(err); 103 | } 104 | 105 | assert(values[0].name); 106 | assert(!values[0].login); 107 | return done(); 108 | }, { fetch: true }); 109 | }); 110 | }); 111 | }); 112 | }); 113 | }); 114 | --------------------------------------------------------------------------------