[
  {
    "path": ".editorconfig",
    "content": "#   ╔═╗╔╦╗╦╔╦╗╔═╗╦═╗┌─┐┌─┐┌┐┌┌─┐┬┌─┐\n#   ║╣  ║║║ ║ ║ ║╠╦╝│  │ ││││├┤ ││ ┬\n#  o╚═╝═╩╝╩ ╩ ╚═╝╩╚═└─┘└─┘┘└┘└  ┴└─┘\n#\n# This file (`.editorconfig`) exists to help maintain consistent formatting\n# throughout this package, the Sails framework, and the Node-Machine project.\n#\n# To review what each of these options mean, see:\n# http://editorconfig.org/\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  //   ╔═╗╔═╗╦  ╦╔╗╔╔╦╗┬─┐┌─┐\n  //   ║╣ ╚═╗║  ║║║║ ║ ├┬┘│\n  //  o╚═╝╚═╝╩═╝╩╝╚╝ ╩ ┴└─└─┘\n  // A set of basic conventions (similar to .jshintrc) for use within any\n  // arbitrary JavaScript / Node.js package -- inside or outside Sails.js.\n  // For the master copy of this file, see the `.eslintrc` template file in\n  // the `sails-generate` package (https://www.npmjs.com/package/sails-generate.)\n  // Designed for ESLint v4.\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  // For more information about any of the rules below, check out the relevant\n  // reference page on eslint.org.  For example, to get details on \"no-sequences\",\n  // you would visit `http://eslint.org/docs/rules/no-sequences`.  If you're unsure\n  // or could use some advice, come by https://sailsjs.com/support.\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n  \"env\": {\n    \"node\": true\n  },\n\n  \"parserOptions\": {\n    \"ecmaVersion\": 8\n  },\n\n  \"globals\": {\n    \"Promise\": true,\n    \"Symbol\": true,\n    // ^^Available since Node v4\n  },\n\n  \"rules\": {\n    \"callback-return\":              [\"error\", [\"done\", \"proceed\", \"next\", \"onwards\", \"callback\", \"cb\"]],\n    \"camelcase\":                    [\"warn\", {\"properties\": \"always\"}],\n    \"comma-style\":                  [\"warn\", \"last\"],\n    \"curly\":                        [\"error\"],\n    \"eqeqeq\":                       [\"error\", \"always\"],\n    \"eol-last\":                     [\"warn\"],\n    \"handle-callback-err\":          [\"error\"],\n    \"indent\":                       [\"warn\", 2, {\n      \"SwitchCase\": 1,\n      \"MemberExpression\": \"off\",\n      \"FunctionDeclaration\": {\"body\":1, \"parameters\": \"off\"},\n      \"FunctionExpression\": {\"body\":1, \"parameters\": \"off\"},\n      \"CallExpression\": {\"arguments\":\"off\"},\n      \"ArrayExpression\": 1,\n      \"ObjectExpression\": 1,\n      \"ignoredNodes\": [\"ConditionalExpression\"]\n    }],\n    \"linebreak-style\":              [\"error\", \"unix\"],\n    \"no-dupe-keys\":                 [\"error\"],\n    \"no-duplicate-case\":            [\"error\"],\n    \"no-extra-semi\":                [\"warn\"],\n    \"no-labels\":                    [\"error\"],\n    \"no-mixed-spaces-and-tabs\":     [\"error\", \"smart-tabs\"],\n    \"no-redeclare\":                 [\"warn\"],\n    \"no-return-assign\":             [\"error\", \"always\"],\n    \"no-sequences\":                 [\"error\"],\n    \"no-trailing-spaces\":           [\"warn\"],\n    \"no-undef\":                     [\"error\"],\n    \"no-unexpected-multiline\":      [\"warn\"],\n    \"no-unreachable\":               [\"warn\"],\n    \"no-unused-vars\":               [\"warn\", {\"caughtErrors\":\"all\", \"caughtErrorsIgnorePattern\": \"^unused($|[A-Z].*$)\", \"argsIgnorePattern\": \"^unused($|[A-Z].*$)\", \"varsIgnorePattern\": \"^unused($|[A-Z].*$)\" }],\n    \"no-use-before-define\":         [\"error\", {\"functions\":false}],\n    \"one-var\":                      [\"warn\", \"never\"],\n    \"quotes\":                       [\"warn\", \"single\", {\"avoidEscape\":false, \"allowTemplateLiterals\":true}],\n    \"semi\":                         [\"error\", \"always\"],\n    \"semi-spacing\":                 [\"warn\", {\"before\":false, \"after\":true}],\n    \"semi-style\":                   [\"warn\", \"last\"]\n  }\n\n}\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE",
    "content": "**Node version**: \n**Sails version** _(sails)_: \n**ORM hook version** _(sails-hook-orm)_: \n**Sockets hook version** _(sails-hook-sockets)_: \n**Organics hook version** _(sails-hook-organics)_: \n**Grunt hook version** _(sails-hook-grunt)_: \n**Uploads hook version** _(sails-hook-uploads)_: \n**DB adapter & version** _(e.g. sails-mysql@5.55.5)_: \n**Skipper adapter & version** _(e.g. skipper-s3@5.55.5)_: \n<hr/>\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE",
    "content": "\n"
  },
  {
    "path": ".gitignore",
    "content": "#   ┌─┐┬┌┬┐╦╔═╗╔╗╔╔═╗╦═╗╔═╗\n#   │ ┬│ │ ║║ ╦║║║║ ║╠╦╝║╣\n#  o└─┘┴ ┴ ╩╚═╝╝╚╝╚═╝╩╚═╚═╝\n#\n# This file (`.gitignore`) exists to signify to `git` that certain files\n# and/or directories should be ignored for the purposes of version control.\n#\n# This is primarily useful for excluding temporary files of all sorts; stuff\n# generated by IDEs, build scripts, automated tests, package managers, or even\n# end-users (e.g. file uploads). `.gitignore` files like this also do a nice job\n# at keeping sensitive credentials and personal data out of version control systems.\n#\n\n############################\n# sails / node.js / npm\n############################\nnode_modules\n.tmp\nnpm-debug.log\npackage-lock.json\npackage-lock.*\n.waterline\n.node_history\n\n############################\n# editor & OS files\n############################\n*.swo\n*.swp\n*.swn\n*.swm\n*.seed\n*.log\n*.out\n*.pid\nlib-cov\n.DS_STORE\n*#\n*\\#\n.\\#*\n*~\n.idea\n.netbeans\nnbproject\n\n############################\n# misc\n############################\ndump.rdb\n"
  },
  {
    "path": ".npmignore",
    "content": ".git\n./.gitignore\n./.jshintrc\n./.editorconfig\n./.travis.yml\n./appveyor.yml\n./example\n./examples\n./test\n./tests\n./sails-docs\n./.github\n\nnode_modules\nnpm-debug.log\n.node_history\n*.swo\n*.swp\n*.swn\n*.swm\n*.seed\n*.log\n*.out\n*.pid\nlib-cov\n.DS_STORE\n*#\n*\\#\n.\\#*\n*~\n.idea\n.netbeans\nnbproject\n.tmp\ndump.rdb\n"
  },
  {
    "path": ".travis.yml",
    "content": " # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n#   ╔╦╗╦═╗╔═╗╦  ╦╦╔═╗ ┬ ┬┌┬┐┬                           #\n#    ║ ╠╦╝╠═╣╚╗╔╝║╚═╗ └┬┘││││                           #\n#  o ╩ ╩╚═╩ ╩ ╚╝ ╩╚═╝o ┴ ┴ ┴┴─┘                         #\n#                                                       #\n# This file configures Travis CI.                       #\n# (i.e. how we run the tests... mainly)                 #\n#                                                       #\n# https://docs.travis-ci.com/user/customizing-the-build #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n\nlanguage: node_js\n\nnode_js:\n  - \"12\"\n  - \"14\"\n  - \"16\"\n\nbranches:\n  only:\n    - master\n\nnotifications:\n  email:\n    - ci@sailsjs.com\n\nenv:\n  TEST_REDIS_SESSION=true\n\nbefore_script: sudo redis-server /etc/redis/redis.conf --daemonize yes --port 6380 --requirepass 'secret'\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Sails Changelog\n\n## 1.2.0\n\n- Added `sails migrate` for quickly running auto-migrations by hand\n- The output of `sails inspect` no longer includes controller information\n- When loading user hooks, if `sails.config.loadHooks` is specified, skip hooks whose names aren't explicitly included\n- Increased time to display warning message in `config/bootstrap.js` from 5 seconds to 30 seconds\n- Switched to using `updateOne` in the \"update\" blueprint\n- Blueprint queries no longer include `fetch: true` by default, to avoid warnings from `updateOne`\n- Update error mesage in default `serverError` response to use flaverr\n- In `lib/router/res.js`, instead of always setting 'content-type' to 'application/json', only set it if `res.get('content-type')` is falsy\n- Update flaverr dependency\n- Update i18n-2 dependency to resolve deprecation warning\n- Update rc dependency to address potential vulnerabilities\n- Update machinepack-process dependency to address potential vulnerabilities\n- Update machinepack-redis dependency to address potential vulnerabilities\n\n## 1.1.0\n\n> As always, we owe a debt of gratitude to our contributors-- we mentioned you below next to the features/enhancements/fixes you contributed to.  (Apologies to anyone we missed, there was a lot in this release!  Let us know and we'll add you to the list.)\n>\n> We especially want to thank those contributors who helped out with the documentation:\n> - [Rachael Shaw](https://github.com/rachaelshaw)\n> - [Mike McNeil](https://github.com/mikermcneil)\n> - Ali Norouzi\n> - [Ronny Medina](https://github.com/ronnymedinave)\n> - [Alex Schwarz](https://github.com/alexschwarz89)\n> - [Xavier Spriet](https://github.com/loginx)\n> - [Ian Harris](https://github.com/harrisi)\n> - [Vladyslav Piskunov](https://github.com/vpiskunov)\n> - [Michael Frederick](https://github.com/mdfrederick)\n> - [Mark Strelecky](https://github.com/streleck)\n> - [Freddy](https://github.com/mdfrederick)\n> - [pavan](https://github.com/pavanmehta91)\n> - [Scott Reed](https://github.com/AdJesumPerMariam)\n> - [Mario Colque](https://github.com/colkito)\n> - [Okoli Lemuel](https://github.com/okolilemuel)\n> - [ultimate-tester](https://github.com/ultimate-tester)\n> - [@snidell](https://github.com/snidell)\n> - [Pika](https://github.com/ThatNerdyPikachu)\n> - [AYEDOUN Fiacre](https://github.com/afidosstar)\n> - [Tim Wisniewski](https://github.com/timwis)\n> - [Tom Saleeba](https://github.com/tomsaleeba)\n> - [s-slavchev](https://github.com/s-slavchev)\n> - [Daniel Harvey](https://github.com/danielsharvey)\n> - [Julien Le Coupanec](https://github.com/LeCoupa)\n> - [floriancummings](https://github.com/floriancummings)\n> - [Bernardo Gomes](https://github.com/Bernardoow)\n> - [Eli Peters](https://github.com/elipeters)\n> - [Dennis Cheung](https://github.com/oaksofmamre)\n> - [Minh Tri Nguyen](https://github.com/kevinvn1709)\n>\n> Finally, big thanks to [Dennis Cheung](https://github.com/oaksofmamre) who did the painstaking work of compiling this changelog!\n>\n>   ~[mike](https://twitter.com/mikermcneil)\n\n\n#### Framework:\n- **New model methods: `.updateOne()`, `.destroyOne()`, and `.archiveOne()`**\n- **`exits` argument may now be excluded from your `fn` function in all helpers, actions2 definitions, and shell scripts.**\n- **Cleaner usage for `.stream()`,  `.transaction()`, and `.leaseConnection()`.  Functions provided as procedural parameters (i.e. `during` and iteratees) no longer expect a callback to be invoked, as long as you omit their 2nd callabck argument from the function signature.**\n- **The bootstrap function (`config/bootstrap.js`) and the `initialize` function of hooks no longer expect a callback to be invoked, as long as the callback argument is excluded from the function signature.**\n- **New chainable methods available on helper invocations: `.timeout()` and `.retry()`.**\n- Use vuejs VueRouter (and a virtual page example) by default, and make vuejs <router-view> more apparent\n- Update helper, action, and hook generators, plus a few other updates to boilerplate output from generators.\n- Update boilerplate bootstrap and hook initialize functions.\n- Disable 'async' dependency when generated the expanded starter app, to avoid confusion.  (Can always still be installed, it just won't be bundled by default anymore, and auto-globalization is also, of course, disabled.)\n- Include FontAwesome 4 files, and update instructions for removal if desired\n- Update check for production/staging in vuejs to assume we're in production if no SAILS_LOCALS are present\n- Provide documentation tip on how to configure Mongo as default datastore w/ link to tutorial\n- Take advantage of next-gen whelk.\n- Improve Windows compatibility (for 'sails generate page', sails generate action, etc)\n- Exclude 'async' dep by default in all new apps\n- Update boilerplate to take advantage of optional 'exits' argument in helpers, actions, and shell scripts.\n- Use updated bootstrap for web app template\n- Implement Google fonts/typekit/google tag manager/google analytics\n- Include (hard-code) Bowser inline now\n- Improve <ajax-form> to also include 'is' rule\n- Redirect away ALL unrecognized base subdomains (not just webhooks and click), but only do it in staging/production.\n- Fix \"sails g page\" with nested folders on Windows\n- Add FileList and FormData to reserved list to protect them from mangling.  (Should be fine anyway, but this protects against potential issues from changing uglify versions.)\n- Take advantage of patch in Parasails 0.7.7\n- Leverage .updateOne()\n- Add 5 second timeout for all four of those calls, to protect user experience from any 3rd party API call issues.  (For context, this is rare, but it happens.  I've noticed Stripe go all 500 on a customer app twice in the last 6 months: once for about 28 minutes earlier this summer, and once last month for a split second.  I've never seen it hang, but this is such a common thing with other 3rd party apis better to be safe than sorry.  An error message after a reasonable wait time is a far better user experience than having to wait 2 whole minutes for the TCP timeout.  And this way, any error handling logic to unroll stuff etc that gets added here will still be able to run.)\n- Integrate Cloud SDK into Parasails\n- Use top-level implementationSniffingTactic and pass through to whelk and MaA\n- Force homogeneous sniffing tactic for helpers+actions.\n- Force minimum SVRs (re implementationType: 'classical').\n- Finish up support for smarter .bootstrap and .initialize. (Continued from 21bc1f1ab88dfa1064f2e312a4b1135b92763d2a)\n- Add faux callbacks for easier debugging\n- Add `create` method to datastore fixture, and test \"no validation errors\" case. [(Scott Gress)](https://github.com/sgress454)\n- Fix test descriptions for `update` [(Scott Gress)](https://github.com/sgress454)\n- Add test cases for validations on primary key [(Scott Gress)](https://github.com/sgress454)\n- Hoist flag for determining whether to apply validation rules, and use it for both PKs and generic attributes. [(Scott Gress)](https://github.com/sgress454)\n- Add implementation-sniffing to .stream()\n- Setup updateOne() method\n- Set up destroyOne() and archiveOne(), and use an omen in the 'found too many' error in findOne() to improve the stack trace.\n- Implement implementation sniffing in .transaction() and .leaseConnection().\n- Enable ORM to accept attributes named 'length'. Replace `_.each()` with `_.forIn()` to enable ORM to accept attributes named 'length'. [(Daniel Harvey)](https://github.com/danielsharvey)\n- Add an isError check when handling errors thrown from validation rules to prevent potential issues in extreme edge cases (where something weird gets thrown)\n- Clean up tests for greater clarity and usefulness\n- Finish restructuring things to match latest conventions in parley, etc.  Leave standalone/ alias for backwards-compatibility\n- Update docs about other adapter methods\n- Improve error messaging to assist in debugging\n- Add E_TOO_MANY_FILES error code.\n- Add Cloud.on() and Cloud.off(), improve error msgs, and remove idempotency guarantee\n- Add typeof check to ensure reasonable behavior even with strange input.\n- Enhance built-in catchall error msg for Cloud.on()\n- Refactor UMD approach to make it easier to see what's going on at a glance.\n- Add experimental support for FileList instances (multi-file upload via Cloud SDK).\n- Improve performance of +500K ops/sec by collapsing IIFE\n- Setup for being able to use AsyncFunctions with .intercept() and .tolerate().\n- Add new method: getInvocationinfo() -- a public API for accessing private info such as _timeout. Add .interruptions to getInvocationInfo().\n- Introduce working impl of .retry()\n- Clone (shallow) metadata on the way in during .retry(), just to avoid any potential edge cases with a fn attaching additional properties and that actually mattering for subsequent invocations with the same arguments.\n- Take advantage of new getInvocationInfo() method in parley related to .retry()\n- Implement partial support for blended tolerate/intercept with retry (Note: works only for completely parallel error conditions).\n- Optimize AsyncFunction function constructor check.\n- Allow implementationSniffingTactic to be customized.\n- Add failsafe to ensure app lifts inside of Mocha tests. Related to https://github.com/balderdashy/sails/issues/4395 and https://github.com/balderdashy/captains-log/pull/22\n- Also in new Sails apps: Force min 3.10.3 of lodash in new projects\n- Also in new Sails apps: Update eslintrc templates to tolerate unused args and vars that begin with 'unused' (or are just named 'unused')\n- Also in new Sails apps: Add 'custom' validation rule.\n- Also in new Sails apps: Replace autofocus with focus-first, and ensure this feature is opt-in for <ajax-form> and <modal>. Also add typeof check for bowser.\n- Also in new Sails apps: Tolerate required: false, etc in <ajax-form>\n- Also in new Sails apps: Improve error msg in weird edge case\n- Also in new Sails apps: Allow completely numeric passwords by default when using 'isHalfwayDecentPassword' validation rule for <ajax-form>\n- Also in new Sails apps: Update email templates\n- Also in new Sails apps: Add a couple opinionated-yet-reasonable global styles to the 'bootstrap-overrides' file\n- Also in new Sails apps: Bump appveyor+travis files to always include Node 10\n- Also in new Sails apps: Standardize redis url format to be consistent with site documentation for db (numerical database names only). [Scott Reed](https://github.com/AdJesumPerMariam)\n- Also in new Sails apps: Ignore files generated by 'npm link' in recent NPM releases\n- Also in new Sails apps: Add note about conditional requiredness and validation rules.\n- Also in new Sails apps: Add autocomplete attributes to form fields where applicable\n- Also in new Sails apps: Disable lesshint rule for border-boxing everything in bootstrap-overrides\n- Also in new Sails apps: Fix mismatched tags in email templates\n- Also in new Sails apps: Fix that silly checkbox alignment on login/signup templates\n- Also in new Sails apps: Fix camelCasing issue in intermediate subdirectories for newly created view actions.\n- Also in new Sails apps: Fix fullName validation on edit profile page [(S.Slavchev)](https://github.com/s-slavchev)\n- Also in new Sails apps: Don't include inputs in generated shell script.\n- Also in new Sails apps: Make 'semi' and 'curly' lint violations burn yellow instead of burning red\n- Also in new Sails apps: Tweak generated comment about 'no lodash'\n- Also in new Sails apps: Add note about the function signature of 'leave'\n- Also in new Sails apps: Simplify readme links\n- Also in new Sails apps: Tweak inline docs for <ajax-form>\n- Also in new Sails apps: Bring in self-updating timestamp component for web app template\n- Also in new Sails apps: Update model examples\n- Also in new Sails apps: Use kebab-cased status to avoid database-dependent behavior in edge cases (b/c case sensitivity)\n- Also in new Sails apps: Rename var and kebab-case desired effect strings for consistency\n- Also in new Sails apps: Use _.extend for consistency\n- Also in new Sails apps: Include moment in eslintrc-override\n- Also in new Sails apps: Tweak <js-timestamp>, and then include a demo in the welcome page.\n- Also in new Sails apps: Normalize indentation in \"send template email\" helper\n- Also in new Sails apps: Make sure the HTML email contents are actually being logged properly\n- Also in new Sails apps: Change method names on welcome page for consistency, and also use `this.modal`, since there can only be one modal on the page at a time anyway\n- Also in new Sails apps: Rearrange virtual page example\n- Also in new Sails apps: Bring in unsupported browser overlay\n- Also in new Sails apps: Change href so it makes more sense\n- Also in new Sails apps: Expand comment re virtual pages.\n- Also in new Sails apps: Expand comments in js-timestamp a bit.\n- Also in new Sails apps: Rearrange note about filtering argins now that handleSubmitting exists.\n- Also in new Sails apps: Don't specify testMode when calling out to Mailgun, to avoid confusion.\n- Also in new Sails apps: Never indicate that the email was logged instead of sent if it wasn't.\n- Also in new Sails apps: Add retries w/ exponentional back-off for idempotent / low-risk 3rd party API calls.\n- Also in new Sails apps: Add 'ensureAck' flag to sendTemplateEmail(), and change behavior to background the sending by default.\n- Also in new Sails apps: Simplify rebuild-cloud-sdk script a bit.\n- Also in new Sails apps: Use for instead of _.each()\n- Also in new Sails apps: Fix typo introduced in 22df26e832aeef817a6e3c01561041eda83830cd\n- Also in new Sails apps: Tweak verbiage in boilerplate FAQ for clarity. (action not controller)\n- Also in new Sails apps: Normalize capitalization in generated footer\n- Also in new Sails apps: Include a placeholder logo image for the branding in the masthead\n- Also in new Sails apps: On homepage for web app template, add more space between \"feature\" sections on small screens\n- Also in new Sails apps: Use consistent link styles all across the web app template, and also add some special blockquote styles\n- Also in new Sails apps: Verbiage tweaks / capitalization normalizaish\n- Also in new Sails apps: 'No matter how legal it is, capitalization should never look this ugly.'  -mike, amateur lawyer\n- Also in new Sails apps: Add FAQ item with info about placeholders, and give the questions some more breathing room\n- Also in new Sails apps: Cleanup and friendlify boilerplate legal documents\n- Also in new Sails apps: Provide better example config\n- Also in new Sails apps: Tweak constant name to match\n- Also in new Sails apps: Fix inconsistent constants\n- Also in new Sails apps: Avoid find/replace conflicts with constants. Also, add the link we mention in the FAQ.\n- Also in new Sails apps: Correct the boilerplate language\n- Also in new Sails apps: Add clarifications to boilerplate stylesheet\n- Also in new Sails apps: Remove custom `a:not(.btn)` styles from homepage, because those are set elsewhere now\n- Parasails: Tweak varname for clarity.\n- Parasails: Rename private method for clarity.\n- Parasails: Leave better error sniffability, but rip out attempt to actually use it from 173685357364baaf9e2e2b3c16307c3fa7ec5d44\n- Parasails: An additional layer to Cloud.on() and Cloud.off()  (intermediate commit to track concept of wildcard key -- ends up being too confusing for custom error handling, but might be worth revisiting as a kind of lifecycle callback in the future)\n- Parasails: Rename \"*\" catchall handler to \"error\" and finish implementation\n- Parasails: Rename to Cloud.listen() and Cloud.ignore(), add aliases, and improve error msgs.\n- Parasails: Intermediate commit: Tracks how we'd approach making .listen() idempotent-- but actually this probably isn't the right granularity to do that (save that for parasails proper on a per-Vue-component basis)\n- Parasails: Change .listen() and .ignore() back to .on() and .off(), and remove idempotency guarantee (that'll need to live at the component/Vue instance level in parasails proper -- see also https://github.com/mikermcneil/parasails/commit/36f87501cd174104e6b75a18b7c16d83ec74\nedaa and https://github.com/mikermcneil/parasails/commit/b54258a9f244fabbbd3c89e35c7be5095c5d8dfa)\n- Parasails: Add back conveniences for multiple bindings.\n- Parasails: 'error' => '*'\n- Parasails: Add uncaught error handler\n- Parasails: Bring in most recent updates\n- Parasails: Remove double-binding\n- Parasails: Update parasails.js [(Mark Strelecky)](https://github.com/streleck)\n- Parasails: Throw error when upload request is no good because it contains structured data as text parameter values.\n- Parasails: Add tip about uglify\n- Parasails: Follow-up to 4941181912646ec27132afb82c2be32bb3cf6203 to make $.ajax() transport work with FileList instances (on browsers where this is supported).\n- Parasails: Fix hard-coded version number in parasails.js file.\n- Parasails: Allow sparser shorthand for setting up virtual pages, while also implementing more error-checking and edge-case handling for various configurations of virtualPages, virtualPagesRegExp, and html5HistoryMode.\n- Parasails: Improve 'did you mean?' intelligence and add two FUTURE nice-to-haves.\n- Force sails-generate@1.15.19\n- Fix linting and global var issues for globals tests [(Scott Gress)](https://github.com/sgress454)\n- Fix paths to Lodash for globals tests [(Scott Gress)](https://github.com/sgress454)\n- Fix lint and global var issues in www test [(Scott Gress)](https://github.com/sgress454)\n- Use `tmp` in www tests to attempt to fix Appveyor issues [(Scott Gress)](https://github.com/sgress454)\n- Address https://github.com/balderdashy/sails/commit/8168dffb8052c0a2df3de923a18c60dd27875915#diff-78ca47cd74fd541e85ff2100564371ddR656\n- Use `tmp` in remaining www test to try and address random Appveyor failures [(Scott Gress)](https://github.com/sgress454)\n- Use more accurate verb in secure cookie info msg\n- Contextualize the 'yes secure cookies, no trustProxy' debug mesage.\n- Clarify message and change it to be verbose\n- Closes #4392 (by removing coffee-script, checksum, and istanbul devDependencies)\n- Ignore 'npm link' collateral\n- Stub implementation opts for helpers re upcoming opt-ins\n- Add note about implementationSniffingTactic\n- Implicitly set implementationSniffingTactic\n- Add bookends about 'customize' for whelk and machine-as-action\n- Don't use explicit implementationSniffingTactic yet.\n- Bump machine runner SVR and remove unnecessary code\n- Note optimization\n- Setup for smarter .bootstrap and .initialize\n- Correctly apply implementationSniffingTactic for helpers\n- Default value for implementationSniffingTactic\n- Add note in 'sails run'\n- Expand explanations in changelog.\n- For compatibility, tolerate action identities that contain '$'.\n- Change variable name to prevent confusion with built-in 'module'.\n- Follow-up to a22598eb26f108768be91a31f4baa8fc92a9c5c7 which covers the rest of the cases\n- Change var name for clarity (same idea as in 2eaaf968daae8f00ebc0007210baa32b5d50f7f0)\n- Log warnings if special actions-only props are in use in helper defs.\n- Update changelog to reflect what's happening w/ the responses hook\n- Bump skipper dep\n- Clarify optional-ness of bootstrap callback in comment.\n- Update error messages for bootstrap/initialize to reflect new reality.\n- 2nd follow up to 97082b8e067a490531758f6bcbc471e08874c209 to allow for \"$\" in route bindings\n- Use prerelease of sails-generate\n- Fix sails-generate SVR\n- Pin merge-dictionaries\n- Fix test now that async is no longer included as a dep. in newly generated sails apps (thanks to await and sails.helpers.flow via organics)\n- Fix expectations of test to match correct child process output (follow up to c694f2d2b1db4b9f397c52f540b19d7d4a4125f4)\n- Fix one more test related to c694f2d2b1db4b9f397c52f540b19d7d4a4125f4\n- Waterline: [PATCH] Fix typo and swap unit test description misplaced in fix #1554 to issue #4360 [(Luis Lobo Borobia)](https://github.com/luislobo)\n- Waterline: Add link to drawing demonstrating how Waterline works underneath the hood\n- Waterline: Stub out the spots where implementationSniffingTactic needs to apply\n- Waterline: Improve error msg when attempting to use a too-generic WHERE clause with updateOne/destroyOne/archiveOne\n- Waterline: Don't use skipEncryption for archiveOne() and destroyOne() -- doesn't make sense.\n- sails-hook-orm: Add missing comma and link to lifecycle hooks in docs [(Tom Saleeba)](https://github.com/tomsaleeba)\n- sails-hook-orm: Update error message about adapter compatibility\n- sails-hook-orm: Stub out the spots where implementationSniffingTactic needs to apply\n- waterline-utils: Update boilerplate\n- waterline-utils: Resolve lint issues\n- waterline-utils: Ignore fun new files generated by 'npm link'\n- waterline-utils: Add normalize-datastore-config from sails-mongo (needs more love)\n- waterline-utils: Set 'protocol' property automatically.\n- waterline-utils: Expose .normalizeDatastoreConfig()\n- waterline-utils: Change .protocol -> protocolPrefix  (and get rid of trailing colon)\n- waterline-utils: Add missing change from prev. commit, and also set .protocolPrefix when appropriate, even if no url was specified.\n- waterline-utils: Grab dictionary of models (w/ backwards compatibility)\n- waterline-utils: Fix backwards conditional\n- anchor: Fix build status urls\n- anchor: Bump validator version re https://snyk.io/test/npm/anchor?severity=high&severity=medium&severity=low#npm:validator:20160218 (without applying any of the necessary changes yet, if there are any). Also upgrade to latest eslintrc file, etc, and bump eslint dep.\n- anchor: Fix out of date test label\n- anchor: Tweak checkConfig error msgs for isBefore+isAfter\n- anchor: Clean up checkConfig for isBefore and isAfter, and clarify another comment.\n- anchor: Reroll isBefore and isAfter the dumb, explicit way.\n- anchor: Added a few more tests.\n- anchor: Add validator upgrade\n- sails-hook-sockets: Fix typo in function being checked when preparing driver in case of unexpected failure [(Luis Lobo Borobia)](https://github.com/luislobo)\n- sails-hook-sockets: Modify deprecation message to recommend install Sails owned socket.io-redis, to match up documentation https://sailsjs.com/documentation/concepts/deployment/scaling [(Luis Lobo Borobia)](https://github.com/luislobo)\n- skipper: Update boilerplate.\n- skipper: Add eslint dev dep\n- skipper: Set up lint script and add docs for quota-related error codes in README\n- skipper: Some lint fixes plus remove stringfile and 2 unnecessary deps.\n- skipper: sailshq/lodash\n- skipper: Improve linter\n- skipper: Consolidate roadmap w/ sails core\n- skipper: Consolidate contributor info\n- skipper: Remove old logger in favor of consistently using 'debug'\n- skipper: Documentation\n- skipper: Conslidate into lib/ (part 1)\n- skipper: Move index.js to lib/skipper.js\n- skipper: Remove standalone/ alias\n- skipper: Latest SVR for skipper-adapter-tests\n- Backport https://github.com/lodash/lodash/commit/d8e069cc3410082e44eb18fcf8e7f3d08ebe1d4a\n- Flaverr: Fix bad error message\n- Parley: Shorten error message and pull out exec countdown secs into a constant.\n- Parley: Tweak verbiage in warning msg one more time for further clarity.\n- Parley: Implement .timeout() and initial pass at .retry().\n- Parley: Set up more of .retry(), and add relevant TODO about what needs to happen now\n- Parley: Avoid 1MM ops/sec perf. loss by using .slice() alternative\n- Parley: 200k ops/sec to general-case performance  (and take care of annoying arguments keyword usage)\n- Parley: Implement IIFE but prepare for about-face since it takes away 1MM ops/sec\n- Parley: Regain 1MM ops/sec by moving IIFE out\n- Parley: Cleanup, deduplication, and docs\n- Parley: +350K ops/sec\n- Parley: Change where bindUserlandAfterExecLC lives, for consistency\n- Parley: Rearrange for clarity, and update comments\n- Parley: Boilerplate update\n- Parley: Setup first pass at async function support for userland afterExec LCs\n- Parley: Remove usage assertion now that AsyncFunctions are supported\n- Parley: Added _hasStartedButNotFinishedAfterExecLC\n- Parley: Eliminate todos now that \"one tick\" flag takes afterexec LCs into account\n- Parley: Rough .retry() impl\n- Parley: Rip out retry from parley (realized it needs to live a level higher up in the stack)\n- Parley: Remove two now-unused reserved keys from blacklist\n- Parley: Update examples\n- Parley: Add 'thenable' option for backwards-compatibility for older node releases (specifically important for .tolerate())\n- Parley: Fix backwards conditional\n- Parley: Revert \"3.6.0\"\n- Parley: Prevent throwing when no afterExec LC handler is specified\n- Parley: Don't throw a special error when handler provided to .tolerate() throws -- instead just allow it through unscathed.\n- Machine (runner for actions2, helpers, & shell scripts): Upgrade eslint\n- Machine: Obey new 'instructionTimeout' meta key\n- Machine: Reverse 8af993b01c1480aca45d7e7e2952cfd8d288a293 because all metadata isn't known at build time.  (This will need more shuffling to accomplish)\n- Machine: Update boilerplate and bump min parley version.\n- Machine: Finish \"Too many retries\" error, and make note of situation with [Circular]\n- Machine: Disable old tests, remove now-irrelevant future note\n- Machine: More cleanup, after verifying it works w/ defaultArgins and defaultMeta.\n- Machine: Add note explaining why we don't pass in a build-time omen (or any omen) to .retry()'s extra .build() call.\n- Machine: Respect original invocation timeout\n- Machine: Remove strict idempotency check.\n- Machine: Eliminate support for variadic usage (because it's potentially confusing/imprecise -- negotation rules and retry series can BOTH be arrays)\n- Machine: Fix an oversight/edge case: an error message would have itself thrown an error.\n- Machine: Fix typo in .retry().\n- Machine: Setup for implementationSniffingTacticgls\n- Machine: Catch attempt to use AsyncFunction in sync:true method at build time instead of waiting until exec-time.\n- Machine: Cursory setup of exits arg detection\n- Machine: Don't freak out about new customization option\n- Machine: Validate implementationSniffingTactic\n- Machine: Eliminate old comments, and one trivial optimization\n- Machine: Tweak comments and add clarification\n- Machine: Handle implementationType: 'classical' for 'exits'-less functions.\n- Machine: Skip 'await' tests for Node versions that dont support it.\n- Machine: Avoid returning from .catch()  (this is not tied to any known issue, just cleanup)\n- Machine: Fix to properly simulate chaining: promise = promise.catch()\n- Machine: Swap around 71eb66400f73a52baf0a78a38c1b8d44061737ac so that .then() gets handled first.  Otherwise, you get either an unhandled promise rejection OR a called-it-twice warning.\n- whelk (shell scripts): Add note about customizations\n- whelk (shell scripts): Add support for new `customize` option\n- whelk (shell scripts): Ensure at least machine@15.2.2\n- Defined a req.path for socket requests.\n- Ensured req.path is good and stringy.\n\n#### Documentation:\n- Add documentation for new model methods in Sails 1.1.0.\n- Sails 1.1.0 updates for .transaction() and .leaseConnection() -- also some follow up from b126f7b66a1c46be0208c2a81235e0584cc50225 for .stream()\n- Update examples for .stream() in advance of sails 1.1.0\n- Update Blueprint API docs for clarity\n- Update Platzi course links\n- Update adapterList.md [(Ronny Medina)](https://github.com/ronnymedinave)\n- Update policies.md [(Alex Schwarz)](https://github.com/alexschwarz89)\n- Update waterline.md\n- Update dependencies.md\n- Normalize capittalization\n- Update upgrading.md\n- Update routes.md\n- Add link and clarify a bit further\n- Update res.status.md\n- Updated link [(Simon)](https://github.com/svict4)\n- Fix event documentation for `lower` [(Xavier Spriet)](https://github.com/loginx)\n- Add note to upgrading guide re: i18n (see https://gitter.im/balderdashy/sails?at=5ac46d9cc574b1aa3e6533f6)\n- Fix commented-out code block\n- Fix getLocale link\n- Update locales.md\n- Update req.setLocale.md\n- Fix broken links in old 0.12 upgrading guide\n- Fix RESTful route examples for `add` and `remove`\n- Update content for req.acceptsLanguages()\n- Update content for req.acceptsCharsets(), and rename both files\n- Clean up req.accepts() reference page.\n- Update upgrading guide\n- Update logging.md\n- Fixed typo in routing actions description [(Scott Reed)](https://github.com/AdJesumPerMariam)\n- Fix action name in docs [(Ian Harris)](https://github.com/harrisi)\n- Update views.md\n- Fixed broken links to req.wantsJSON & req.allParams() [(Vladyslav Piskunov)](https://github.com/vpiskunov)\n- Update Lifecyclecallbacks.md\n- Update cors.md link from cors.js to security.js [(Michael Frederick)](https://github.com/mdfrederick)\n- Update cors.md - Adjusting links and updating examples [(Michael Frederick)](https://github.com/mdfrederick)\n- Remove link to guide that doesn't exist anymore (fixes #1000)\n- Update faq.md\n- Fix some links, and create a page in tutorials linking to the course/demo app\n- Create req.hostname [(Mark Strelecky)](https://github.com/streleck)\n- Update req.host.md and change to deprecated [(Mark Strelecky)](https://github.com/streleck)\n- Update req.isSocket.md [(Mark Strelecky)](https://github.com/streleck)\n- Update req.wantsJSON.md [(Mark Strelecky)](https://github.com/streleck)\n- Update req.hostname\n- Fix docmeta tag\n- Update req.isSocket.md\n- Update req.wantsJSON.md\n- Update res.send.md [(Mark Strelecky)](https://github.com/streleck)\n- Add res headers to examples\n- Add fragments to cors links. Update another link to use /documentation [(Freddy)](https://github.com/mdfrederick)\n- Update low-level-mysql-access.md\n- Update mongo.md\n- Update cors.md since there's no config/cors.js in 1.0 and the settings are in config/security.js [(pavan)](https://github.com/pavanmehta91)\n- Fix broken link [(Scott Reed)](https://github.com/AdJesumPerMariam)\n- Add the ) and } forgotten, to close the queries in the right way. [(Mario Colque)](https://github.com/colkito)\n- Add hooks + a few tweaks\n- Update hooks.md\n- Update services.md\n- Update sendNativeQuery.md\n- Update findOrCreate.md\n- Clarify sentence, and add link\n- Add file extension\n- Update req.host.md\n- Update ExampleHelper.md [(Okoli Lemuel)](https://github.com/okolilemuel)\n- Update standalone-usage.md to 1.0 [(ultimate-tester)](https://github.com/ultimate-tester)\n- Correct syntax typo for log messages [(Alex Schwarz)](https://github.com/alexschwarz89)\n- Update Validations.md [(Okoli Lemuel)](https://github.com/okolilemuel)\n- Take first pass at e-commerce page\n- Remove irrelevant example\n- Add note re: customToJSON does not support async functions [(Scott)](https://github.com/snidell)\n- Add sails-hook-organics to the Hooks page [(Pika)](https://github.com/ThatNerdyPikachu)\n- Fix missing comma [(Scott Reed)](https://github.com/AdJesumPerMariam)\n- Waterline initialize function must be async [(Scott Reed)](https://github.com/AdJesumPerMariam)\n- Update sails.sockets.md [(AYEDOUN Fiacre)](https://github.com/afidosstar)\n- Fix custom model methods example [(Tim Wisniewski)](https://github.com/timwis)\n- Add quotes to key in headers example re: dictionary keys [(Tom Saleeba)](https://github.com/tomsaleeba)\n- Fix typo in ActionsAndControllers.md [(s-slavchev)](https://github.com/s-slavchev)\n- Fix typo in URL link [(Daniel Harvey)](https://github.com/danielsharvey)\n- Update GeneratingActions.md to fix typo manging => managing [(Julien Le Coupanec)](https://github.com/LeCoupa)\n- Update extending-sails.md to remove remove broken link [(Julien Le Coupanec)](https://github.com/LeCoupa)\n- Update Testing.md to add dd Semaphore [(Julien Le Coupanec)](https://github.com/LeCoupa)\n- Update To1.0.md [(floriancummings)](https://github.com/floriancummings)\n- Add information about optional param. [(Bernardo Gomes)](https://github.com/Bernardoow)\n- Update sort.md to fix code example typo [(Eli Peters)](https://github.com/elipeters)\n- Update standalone-usage.md\n- Add back and correct link\n- Update Models.md\n- Update To1.0.md\n- Edit Sails versioning for consistency (v1.0 and JavaScript) [(Dennis Cheung)](https://github.com/oaksofmamre)\n- Update globals.md to fix broken link for Services and Models [(Minh Tri Nguyen)](https://github.com/kevinvn1709)\n- Update res.redirect.md [(Julien Le Coupanec)](https://github.com/LeCoupa)\n- Update res.status.md [(Julien Le Coupanec)](https://github.com/LeCoupa)\n- Add link to info about negotiating errrors\n- Update errors.md\n- Simplify examples.\n- Fix typo in .update() usage example.\n- Update sails.config.bootstrap.md\n- Update decrypt.md\n- Update attributes.md\n\n#### Organics:\n- Improve security\n- Add note for future about new exit from .saveBillingInfo()\n- Update package.json [(Pika)](https://github.com/ThatNerdyPikachu)\n- Use `encodeURIComponent` instead of `encoreURI` for `url-friendly` style.\n- Replace all special characters and skip encoding entirely. [(Scott Gress)](https://github.com/sgress454)\n- Swap open with opn to remove critical error on audit ([Peter Barrett)](https://github.com/Peter-Barrett)\n- Pin opn version number and move back to json5 [(Peter Barrett)](https://github.com/Peter-Barrett)\n- Remove unused dep\n\n#### Adapters:\n- Check `exits` against raw instead of built machine as it seems as though the built machine doesn’t expose the exits  [(Yuki von Kanel)](https://github.com/Rua-Yuki)\n- Update debug to fix ReDoS vulnerability [(Alec Fenichel)](https://github.com/fenichelar)\n- Remove auto setting JSON to LONGTEXT [(Betanu701)](https://github.com/Betanu701)\n- sails-disk: Update eslint\n- sails-disk: Don't test node 0.10 and 0.12 because eslint doesn't even run on them anymore\n- MySQL: Pin debug version [(Alec Fenichel)](https://github.com/fenichelar)\n- MySQL: machine@15 adjustment\n- MySQL: Follow up to d41eb10e1804ccc884cf6e290fd5a340f8f12c0b\n- MySQL: Test on Node 10\n- MySQL: eslint for tests\n- MySQL: Use the right port\n- MySQL: Get rid of ajv keywords dev install warning and clean up machine SVR\n- MySQL: Update skipped test\n- MySQL: Rename test files for easier quick-switching and add back linting for tests\n- MySQL: Move the files (still need to update require paths-- see next commit)\n- MySQL: Fix require paths (follow up to d9db8cda4fcd9ffb37b738402d855991991c892b)\n- MySQL: Update travis.yml and remove Dockerfile\n- MySQL: Normalize the license stuff\n- MySQL: Fix those badge things\n- PostgreSQL: Correctly raises error when you have bad model attributes [(Tony Buser)](https://github.com/tbuser)\n- PostgreSQL: Allow for other auto increment scenarios other than numeric [(Andrew Greenstreet)](https://github.com/gstreetmedia)\n- PostgreSQL: Single quotes (eslint) and a couple of other minor adjustments to comments and varnames\n- PostgreSQL: Improve linter\n- PostgreSQL: Change it's => its\n- PostgreSQL: Fix failing test (https://travis-ci.org/balderdashy/sails-postgresql/jobs/368925613).  This is because https://github.com/balderdashy/sails-postgresql/pull/278 actually breaks normal usage (because logical types are not made accessible to the adapter in the per-table DDL spec passed into the define() adapter method- instead, another approach must be used)\n- PostgreSQL: Update node versions tested to include all LTS releases\n- PostgreSQL: Improve error message\n- PostgreSQL: Adjust tests to stop using 'integer' columnType for auto-incrementing pk\n- PostgreSQL: Close https://github.com/balderdashy/sails-postgresql/pull/279\n- PostgreSQL: Include additional number type property to model. This will cause the registerDatastore method to throw an error due to autoMigrations being undefined for anotherGood property on the model. [(Andrew Salib)](https://github.com/andezzat)\n- PostgreSQL: Check that property autoMigrations exists before checking its child property columnType resolving undefined errors [(Andrew Salib)](https://github.com/andezzat)\n- MongoDB: Upgrade 'machine' dependency to ^15.0.0 [(Yuki von Kanel)](https://github.com/Rua-Yuki)\n- MongoDB: Use `.switch(...)` where needed [(Yuki von Kanel)](https://github.com/Rua-Yuki)\n- MongoDB: Allow tests to run on PRs (hopefully) - see https://docs.travis-ci.com/user/environment-variables/\n- MongoDB: Bump devdep SVRs\n\n#### Tools:\n- sails-hook-dev: Recommend simple solution to setting up staging environment in readme.md\n\n#### Raw diffs:\n\n##### Framework:\n- https://github.com/balderdashy/sails-generate/compare/master@%7B2018-03-29%7D...master\n- https://github.com/balderdashy/sails/compare/master@%7B2018-03-29%7D...master\n- https://github.com/balderdashy/waterline/compare/master@%7B2018-03-29%7D...master\n- https://github.com/balderdashy/sails-hook-orm/compare/master@%7B2018-03-29%7D...master\n- https://github.com/sailshq/waterline-utils/compare/master@%7B2018-03-29%7D...master\n- https://github.com/balderdashy/waterline-schema/compare/master@%7B2018-03-29%7D...master\n- https://github.com/sailshq/anchor/compare/master@%7B2018-03-29%7D...master\n- https://github.com/balderdashy/sails-hook-sockets/compare/master@%7B2018-03-29%7D...master\n- https://github.com/balderdashy/sails.io.js/compare/master@%7B2018-03-29%7D...master\n- https://github.com/mikermcneil/socket.io-redis/compare/master@%7B2018-03-29%7D...master\n- https://github.com/balderdashy/skipper/compare/master@%7B2018-03-29%7D...master\n- https://github.com/rachaelshaw/sails-hook-uploads/compare/master@%7B2018-03-29%7D...master\n- https://github.com/sailshq/sails-hook-grunt/compare/master@%7B2018-03-29%7D...master\n- https://github.com/sailshq/lodash/compare/master@%7B2018-03-29%7D...master\n- https://github.com/mikermcneil/connect-redis/compare/master@%7B2018-03-29%7D...master\n- https://github.com/mikermcneil/flaverr/compare/master@%7B2018-03-29%7D...master\n- https://github.com/mikermcneil/parley/compare/master@%7B2018-03-29%7D...master\n- https://github.com/node-machine/machine/compare/master@%7B2018-03-29%7D...master\n- https://github.com/node-machine/rttc/compare/master@%7B2018-03-29%7D...master\n- https://github.com/sailshq/whelk/compare/master@%7B2018-03-29%7D...master\n- https://github.com/sailshq/whelk/compare/master@%7B2018-03-29%7D...master\n- https://github.com/balderdashy/captains-log/compare/master@%7B2018-03-29%7D...master\n- https://github.com/mikermcneil/reportback/compare/master@%7B2018-03-29%7D...master\n- https://github.com/balderdashy/include-all/compare/master@%7B2018-03-29%7D...master\n- https://github.com/sailshq/machinepack-redis/compare/master@%7B2018-03-29%7D...master\n- https://github.com/sailshq/sort-route-addresses/compare/master@%7B2018-03-29%7D...master\n- https://github.com/balderdashy/sails-stringfile/compare/master@%7B2018-03-29%7D...master\n\n##### Documentation:\n- https://github.com/balderdashy/sails-docs/compare/master@%7B2018-03-29%7D...master\n\n##### Organics:\n- https://github.com/mikermcneil/parasails/compare/master@%7B2018-03-29%7D...master\n- https://github.com/sailshq/sails-hook-organics/compare/master@%7B2018-03-29%7D...master\n- https://github.com/mikermcneil/machinepack-http/compare/master@%7B2018-03-29%7D...master\n- https://github.com/sailshq/machinepack-strings/compare/master@%7B2018-03-29%7D...master\n- https://github.com/mikermcneil/machinepack-fs/compare/master@%7B2018-03-29%7D...master\n- https://github.com/sailshq/machinepack-process/compare/master@%7B2018-03-29%7D...master\n\n##### Adapters:\n- https://github.com/balderdashy/sails-disk/compare/master@%7B2018-03-29%7D...master\n- https://github.com/balderdashy/sails-mysql/compare/master@%7B2018-03-29%7D...master\n- https://github.com/sailshq/machinepack-mysql/compare/master@%7B2018-03-29%7D...master\n- https://github.com/sailshq/waterline-sql-builder/compare/master@%7B2018-03-29%7D...master\n- https://github.com/balderdashy/sails-postgresql/compare/master@%7B2018-03-29%7D...master\n- https://github.com/sailshq/machinepack-postgresql/compare/master@%7B2018-03-29%7D...master\n- https://github.com/balderdashy/sails-mongo/compare/master@%7B2018-03-29%7D...master\n- https://github.com/balderdashy/sails-redis/compare/master@%7B2018-03-29%7D...master\n- https://github.com/balderdashy/skipper-disk/compare/master@%7B2018-03-29%7D...master\n- https://github.com/balderdashy/skipper-s3/compare/master@%7B2018-03-29%7D...master\n\n##### Tools:\n- https://github.com/mikermcneil/sails-hook-apianalytics/compare/master@%7B2018-03-29%7D...master\n- https://github.com/balderdashy/sails-hook-dev/compare/master@%7B2018-03-29%7D...master\n- https://github.com/mikermcneil/eslint/compare/master@%7B2018-03-29%7D...master\n- https://github.com/mikermcneil/htmlhint/compare/master@%7B2018-03-29%7D...master\n- https://github.com/mikermcneil/lesshint/compare/master@%7B2018-03-29%7D...master\n\n\n\n## 1.0.0\n\nSails v1.0 comes with a host of new features and improvements as well as some breaking changes to previous versions.  Please see the [migration guide](http://sailsjs.com/upgrading) if you're upgrading from a previous version of Sails!\n\n* [ENHANCEMENT] Introduce Actions 2 -- ability to declare actions as [Machines](http://node-machine.org) in individual files.  See the [Actions and controllers](http://sailsjs.com/documentation/concepts/actions-and-controllers) and [Action target syntax](http://sailsjs.com/documentation/concepts/routes/custom-routes#?action-target-syntax) docs for more info.\n* [ENHANCEMENT] Introduce Helpers -- the successor to services.  See the [Helpers](http://sailsjs.com/documentation/concepts/helpers) docs for more info.\n* [BUGFIX] Improve path resolution in moduleloader for Windows [f13bb77](https://github.com/balderdashy/sails/commit/f13bb77eb49b9b61aa225c43cc2aacaadd4f07be)\n* [BUGFIX] Fix output for virtual requests that have non 2xx/3xx status codes and no body [525c7c5](https://github.com/balderdashy/sails/commit/525c7c503347ef586ebf26730af77e2aa8626060)\n* [REMOVAL] Remove support for (undocumented) \"action/model\" route syntax for binding routes to blueprint actions\n* [BUGFIX] Fix issue causing Sails to sometimes crash when using Redis sessions if it receives a request after the Redis server has unexpectedly disconnected [3f29dce](https://github.com/balderdashy/sails/commit/3f29dce22b5a36403108d8d1aab7a903aa4488a5)\n* [ENHANCEMENT] Add `sails.getActions()` method to return a dictionary of registered Sails actions [5598179](https://github.com/balderdashy/sails/commit/55981792ea52febfa5c343bbae4517c6a38f20c9)\n* [ENHANCEMENT] Add `sails.registerAction()` method to programmatically register a Sails action [dd9af88](https://github.com/balderdashy/sails/commit/dd9af88b114db696dde2fbeee79a1dc745f2d748)\n* [ENHANCEMENT] Add `exposeLocalsToBrowser` local in all views\n* [ENHANCEMENT] Add `sails.registerActionMiddleware()` to programmatically register middleware to be applied to one or more actions [2c281d5](https://github.com/balderdashy/sails/commit/2c281d51301e684f00c91da5cc9d8fa37814094c)\n* [ENHANCEMENT] Allow explicitly defined actions in Sails config via `sails.config.controllers.moduleDefinitions` [3b264fa](https://github.com/balderdashy/sails/commit/3b264facc2e0a72bd2aa5366271c588c903e6f5c), [4ad23dd](https://github.com/balderdashy/sails/commit/4ad23dd353ff1d7e607c0a75377a0ba6d9dd2f3d)\n* [BUGFIX] Default `req.options.populate` to the value of `sails.config.blueprints.populate [d8f4df8](https://github.com/balderdashy/sails/commit/d8f4df8e19cfb7eaefa00bbcbbc6cab870483194)\n* [ENHANCEMENT] Add `sails.reloadActions()` method to reload actions from disk / config while an app is running [df2ee46](https://github.com/balderdashy/sails/commit/df2ee4657774a092fb0bd25b3013448c9f155700)\n* [ENHANCEMENT] Allow loaded modules (actions, controllers, policies, models, etc.) to have _any_ file extension besides `.md` and `.txt` (which are ignored).  Direct support for anything besides plain Javascript has been removed; variants like CoffeeScript and TypeScript can be used by registering them in the `app.js` file.  See the [Using CoffeeScript tutorial](http://sailsjs.com/documentation/tutorials/using-coffee-script) for an example.\n* [REMOVAL] Remove support for custom blueprints.  These can easily be replaced by regular actions.  See the [migration guide](http://sailsjs.com/upgrading#custom-blueprints) for more info. [0fd4362](https://github.com/balderdashy/sails/commit/0fd4362795ee338ea5b18c3ee42dbdae4b08fd43)\n* [REMOVAL] Remove support for blueprint route target syntax [0fd4362](https://github.com/balderdashy/sails/commit/0fd4362795ee338ea5b18c3ee42dbdae4b08fd43)\n* [REMOVAL] Remove deprecated `dontFlattenConfig` option [56c9b5b](https://github.com/balderdashy/sails/commit/56c9b5bc4b4f876778653a3995981516bb0767a7)\n* [ENHANCEMENT] Humanize config vars that are loaded from the environment [7eb6af6](https://github.com/balderdashy/sails/commit/7eb6af659b67eb856719c8c0d08c7e7043c9e3e8)\n* [REMOVAL] Remove most Resourceful PubSub methods, leaving just `.subscribe()`, `.unsubscribe()` and `.publish()`. [c981c6e](https://github.com/balderdashy/sails/commit/c981c6edf8573fea0d3a13451d51292ba9038b3b)\n* [ENHANCEMENT] Expose Sails generator for programmatic use [e008a6b](https://github.com/balderdashy/sails/commit/e008a6b7e76cf0c272ec556f0ab48b67499269f5)\n* [UPGRADE] Upgrade to Express 4.14.0.  Thanks to [josebaseba](https://github.com/josebaseba) for his help in the transition to Express 4!\n* [ENHANCEMENT] Improved CORS support.  See the [migration guide](http://sailsjs.com/upgrading#security) and [CORS docs](http://sailsjs.com/documentation/concepts/security/cors) for more info.\n* [REMOVAL] Removed `req.validate()` functionality.  Use Actions 2 instead, which automatically validate parameters. [68fa8ff](https://github.com/balderdashy/sails/commit/68fa8ff0327530973237dfd602b3155e0386cad5)\n* [ENHANCEMENT] Updated syntax for policies.  See the [migration guide](http://sailsjs.com/upgrading#policies) and the [policies documentation](http://sailsjs.com/documentation/concepts/policies) for more details. [66d2b2d](https://github.com/balderdashy/sails/commit/66d2b2d8d9d7f23b35cad7693eab79a58e888d77)\n* [ENHANCEMENT] Automatically sort routes to avoid wildcards swallowing static routes.  See the [sort-route-addresses](https://github.com/treelinehq/sort-route-addresses) module for more info on route sorting.\n* [ENHANCEMENT] Combine CORS and CSRF hooks into new \"Security\" hook.  See the [migration guide](http://sailsjs.com/upgrading#security) for more details.\n* [ENHANCEMENT] Update CSRF configuration and replace `/csrfToken` route with an action that can be bound to any route.  See the [migration guide](http://sailsjs.com/upgrading#security) and [CSRF docs](http://sailsjs.com/documentation/concepts/security/csrf) for more info. [7328c05](https://github.com/balderdashy/sails/commit/7328c0527de4d6ae7bfd48f0c6959ff90c7e3d30)\n* [REMOVAL] `res.created()` will no longer be included by default. [7988866](https://github.com/balderdashy/sails/commit/7988866f68f3cb9926d750265acf1fa9e1cb25c6)\n* [BUGFIX] Ignore default installed hooks when searching `node_modules` for hooks.  [#3855](https://github.com/balderdashy/sails/issues/3855).\n* [SECURITY] Don't serve CSRF token via websockets [50b0684](https://github.com/balderdashy/sails/commit/50b06845a0075f4a45f5e2b5d66c05ea3ed62c4b)\n* [INTERNAL] Bring EJS layout code into Sails rather than relying on the unmaintained `ejs-locals` package [d3ba9bd](https://github.com/balderdashy/sails/commit/d3ba9bdd3c7301b6ac438d2b4591c74c40e2b06c)\n* [REMOVAL] Remove handlebars dependency and support for layouts in view engines other than EJS. See the [migration guide](http://sailsjs.com/upgrading#layouts) for more details. [ae7e656](https://github.com/balderdashy/sails/commit/ae7e656cf815480d135a8d124db6c3269bad1b92), [9f1f2fb](https://github.com/balderdashy/sails/commit/9f1f2fb91b69a8f6a6e29e9a171a981e7a109f51)\n* [ENHANCEMENT] Don't set NODE_ENV based on Sails environment, except in special circumstances. See the [migration guide](http://sailsjs.com/upgrading#node-env) for more details. [cf20d07](https://github.com/balderdashy/sails/commit/cf20d070700230f73b158bd37fb7109739f16519), [abbf1f7](https://github.com/balderdashy/sails/commit/abbf1f724f4b45176d9c8cd46db1a94bef7c37f9)\n* [ENHANCEMENT] Make session hook `routesDisabled` use Sails route address syntax (including regular expression syntax) [aba8f2f](https://github.com/balderdashy/sails/commit/aba8f2fe9856e43168b423d813e17f1b3067746a)\n* [BUGFIX] Supply `res.locals._csrf` to _all_ routes when CSRF protection is enabled.  Fixes [#3865](https://github.com/balderdashy/sails/issues/3865)\n* [REMOVAL] Remove Consolidate dependency -- view template engines are now configurable via the `sails.config.views.getRenderFn` setting.  See the [migration guide](http://sailsjs.com/upgrading#views) for more details. [6316452](https://github.com/balderdashy/sails/commit/63164527b2e0b7c268488539aa3a0e011ff332f9)\n* [REMOVAL] Remove deprecated RPS \"firehose\" [86b88](https://github.com/balderdashy/sails/commit/86b8884f15b647ebc963cadff8502f2449ce04a2)\n* [REMOVAL] Remove deprecated 0.9.x socket support [6464d8f](https://github.com/balderdashy/sails/commit/6464d8f89bd74aaaed380cee15143e6ad0aad614)\n* [BUGFIX] Update and standardize precedence of route param, query and body in `req.param()` and `req.allParams()` [820d1eb](https://github.com/balderdashy/sails/commit/820d1eb53e12774cd644f516d5952c8da8f02da8)\n* [REMOVAL] Remove JSONP support from blueprints.  CORS is fairly ubiquitous now. [effd6c3](https://github.com/balderdashy/sails/commit/effd6c315f2ef9fe55cf5dafdf65b59b8c5cbe1e)\n* [REMOVAL] Remove deprecated `sails.getBaseUrl` method. [d0fe4ff](https://github.com/balderdashy/sails/commit/d0fe4ff169ae9e01a799d4fd32da45700daba21e)\n* [INTERNAL] Remove `sails-util` [1fee468](https://github.com/balderdashy/sails/commit/1fee468bacf98b847d2455d3c40f92bae8b33233)\n* [INTERNAL] Remove grunt, sockets and ORM hooks [48750d7](https://github.com/balderdashy/sails/commit/48750d7010218bcb224623c532474430063dbdb3), [07c59ce](https://github.com/balderdashy/sails/commit/07c59ceaccf3e5a7258962b01f9eaae03fd888d9)\n* [REMOVAL] Remove deprecated `req.params.all()` method [9c6b217](https://github.com/balderdashy/sails/commit/9c6b21773b07ab816fea28f67d3e614e5eb2210b)\n* [BUGFIX] Correctly load custom adapters (stored in either `FooAdapter.js` files or in subfolders) [#3884](https://github.com/balderdashy/sails/issues/3884)\n* [INTERNAL] Add benchmarks [6b4ba32](https://github.com/balderdashy/sails/commit/6b4ba32d1d5219976be81fe7a5bf57a38635d681)\n* [ENHANCEMENT] Requests for assets will skip running session middleware by default [3c5ddf7](https://github.com/balderdashy/sails/commit/3c5ddf719e78acbe66d0896b9d0d013d9e02279b)\n* [ENHANCEMENT] Support `routesDisabled` for sessions in virtual requests [a00cf78](https://github.com/balderdashy/sails/commit/a00cf78895836f3ab982fab00c52fdcb668c4eef)\n* [INTERNAL] Standardize log level for warnings to always use \"debug\" except for warning related to security.\n* [ENHANCEMENT] Add 404, 500 and `startRequestTimer` to middleware order automatically (and remove them from default sails.config.http.middleware.order) [a22e4e7](https://github.com/balderdashy/sails/commit/a22e4e730f42e9868f604bc62eaf9dcc4d2abd6b), [f788e39](https://github.com/balderdashy/sails/commit/f788e3956cf2daf521dbe36fdb8d64a2a8dbb5c8)\n* [REMOVAL] Remove the `handleBodyParserError` middleware from the stack, use the `onBodyParserError` option in Skipper instead.  [f788e39](https://github.com/balderdashy/sails/commit/f788e3956cf2daf521dbe36fdb8d64a2a8dbb5c8)\n* [BUGFIX] Take locale into account in views.render() [#3833](https://github.com/balderdashy/sails/issues/3833)\n* [ENHANCEMENT] Allow session adapter to be required directly [039d245](https://github.com/balderdashy/sails/commit/039d24580f55c4e03f187de87339f88dd31afbc6)\n* [REMOVAL] Remove support for Express 3 session adapters [628a55b](https://github.com/balderdashy/sails/commit/628a55b687ed95adc7b6f7667023ddb96bd939fd)\n* [INTERNAL] Switch from i18n to i18n-2. See the [migration guide](http://sailsjs.com/upgrading#i18n) for more details. [4a90de2](https://github.com/balderdashy/sails/commit/4a90de293883545fb0418851826fa84c5331dad9)\n* [BUGFIX] Fail to lift Sails if `connect-redis` can't initialize. [#3590](https://github.com/balderdashy/sails/issues/3590)\n* [REMOVAL] Remove `method-override` from default middleware list. [575c4a3](https://github.com/balderdashy/sails/commit/575c4a37613992af92a3e40338fe5b470ec2531b)\n* [BUGFIX] Use _.clone() instead of _.cloneDeep() for config overrides, to preserve things like Redis clients passed into config. [1b970b6](https://github.com/balderdashy/sails/commit/1b970b6c445659a731967550ec6132c582613bea)\n* [ENHANCEMENT] Update `sails.config.globals` functionality.  See the [migration guide](http://sailsjs.com/upgrading#globals) for more details. [44c6d9b](https://github.com/balderdashy/sails/commit/44c6d9b41e1b1ffd38150788085890c8c2cbe286)\n* [ENHANCEMENT] Add `sails.config.session.onDisconnect` and `sails.config.session.onReconnect` functions [1e55c63](https://github.com/balderdashy/sails/commit/1e55c639b617e13199db7ee4c3a17f62db87b8ea)\n* [ENHANCEMENT] Add `sails.config.sockets.adapterOptions.onDisconnect` and `sails.config.sockets.adapterOptions.onReconnect` functions [3a25971](https://github.com/balderdashy/sails-hook-sockets/commit/3a2597155eefa086bba54b70b1f3274aaa97a1f5)\n* [DEPRECATION] Deprecate `.jsonx()` [cdcc3c0](https://github.com/balderdashy/sails/commit/cdcc3c09ad864329474ae50b5888571cd6dc2654)\n* [INTERNAL] Update default responses [510504e](https://github.com/balderdashy/sails/commit/510504e210047a2ae8aced98a3a17ced5b499140)\n* [REMOVAL] Remove update-via-POST blueprint route [5c3814d](https://github.com/balderdashy/sails/commit/5c3814d0162535d8f5b9e907ed9a25aa263c2eff)\n* [INTERNAL] Disconnect Redis session client when Sails is lowering [80fb71b](https://github.com/balderdashy/sails/commit/80fb71b45fa7dab94f4c6054b6d365a9319150f7)\n* [UPGRADE] Upgrade EJS dependency to v2.5.3 [6bfad70](https://github.com/balderdashy/sails/commit/6bfad700445038533486affb1637b52b73ed2eac)\n* [REMOVAL] Removed deprecated `connect-flash` middleware [c5c4900](https://github.com/balderdashy/sails/commit/c5c49007f9d55ed74b3ca3062b465c470a15e82b)\n* [REMOVAL] Removed 16 unused dependencies (see full list [here](https://gist.github.com/sgress454/4a4930fec3520b24bdf0df552f70a45c))\n\n\n\n> Also see the [Waterline changelog](https://github.com/balderdashy/waterline/blob/a7cad817e831af5367be2d2c89c021d12a674b86/CHANGELOG.md#edge).\n\n\n\n## 0.12.11\n\n* [BUGFIX] fix typo in error message (see https://github.com/balderdashy/sails/pull/3902)  Thanks [Johnny](https://github.com/Hiro-Nakamura) and [@appdevdesigns](https://github.com/appdevdesigns)!\n* [BUGFIX] backport fix for `_.isFunction()` from Lodash 4 (see https://github.com/lodash/lodash/issues/2768 and https://github.com/balderdashy/sails/issues/3863)  Thanks [@adnan-kamili](https://github.com/adnan-kamili) and [@jdalton](https://github.com/jdalton)!\n* [INTERNAL] rebase changelog updates from master [223567c](https://github.com/balderdashy/sails/commit/223567cf986dd62317d958cb29a6683a4cf1e140)\n\n\n## 0.12.10\n\n* [BUGFIX] Fix issue where incorrect file size was computed for incoming (multi-)file uploads on skipper-disk and skipper-s3 [#3847](https://github.com/balderdashy/sails/issues/3847)  (thanks [@crobinson42](https://github.com/crobinson42), [@NAlexandrov](https://github.com/NAlexandrov) and [@vbogdanov](https://github.com/vbogdanov)!)  See also https://github.com/balderdashy/skipper/issues/109.\n* [BUGFIX] Internationalization fix ([#3833](https://github.com/balderdashy/sails/issues/3833) fixes [#3889](https://github.com/balderdashy/sails/pull/3889) (thanks [@josebaseba](https://github.com/Josebaseba)!)\n* [INTERNAL] Added automated request latency benchmarks -- primarily in advance of 1.0 for the purpose of comparison (thanks [@sgress454](http://github.com/sgress454)!)\n* [BUGFIX] Fixed issue with defined-in-app adapters being improperly loaded [#3884](https://github.com/balderdashy/sails/issues/3884) (thanks [@richdunajewski](https://github.com/richdunajewski)!)\n\n## 0.12.9\n\n* [INTERNAL] Fix deprecation warning from express-session [3872](https://github.com/balderdashy/sails/issues/3872)  (thanks [@Boycce](https://github.com/Boycce) and [@dougwilson](https://github.com/dougwilson)!)\n\n## 0.12.8\n\n* [BUGFIX] Fix issue with multiple config files that have the same filename [3850](https://github.com/balderdashy/sails/issues/3850)\n* [ENHANCEMENT] Add criteria validation for the `find` and `findOne` blueprint actions. [ab9c2c3...c6e8ad0](https://github.com/balderdashy/sails/compare/ab9c2c3431a5298e8fd140e5c1e2ed2c7260526c...c6e8ad0940e1034222b958b97e8f28287fae32b6)\n* [INTERNAL] Fix Gitter link in README so it displays properly on NPM [284c660](https://github.com/balderdashy/sails/commit/284c66008632d906524b2238447a0b79715855e7)\n\n## 0.12.7\n\n* [BUGFIX] Fix issue with multiple config files that have the same filename [3846](https://github.com/balderdashy/sails/issues/3846)\n* [ENHANCEMENT] Warn about overly permissive CORS settings when lifting in production [ca43e05](https://github.com/balderdashy/sails/commit/ca43e0507af79f15361789a3489013b01c8e1825)\n\n## 0.12.6\n\n* [BUGFIX] Revert inadvertent breaking change to CORS config in 0.12.5, see [f80252f](https://github.com/balderdashy/sails/commit/f80252f66edc0bf00cf6ed317d9a3e68b4e8d948) for details)\n\n## 0.12.5\n\n* [INTERNAL] Upgrade version of `include-all` to ^1.0.0 [f6e8d32](https://github.com/balderdashy/sails/commit/f6e8d3243d7d695983a3816e6cf7c43ca4237948)\n* [ENHANCEMENT] Add experimental `sails console --dontLift` option [029fe06](https://github.com/balderdashy/sails/commit/029fe0683ea4f01a962b91381b948136f5c18f63)\n* [UPGRADE] Dependencies in captains-log\n* [UPGRADE] Moduleloader now uses include-all@1, and sails-build-dictionary is deprecated (all of its methods were folded into include-all)\n* [BUGFIX] In moduleloader: Improve path resolution on windows\n* [BUGFIX] Fix property name for ('status' => 'statusCode') in virtual request header\n\n\n## 0.12.4\n\n* [INTERNAL] Upgrade Mocha to 3.0.0 to remove more of the deprecation notices when installing dependencies (see [mocha:#2200](https://github.com/mochajs/mocha/issues/2200))\n* [INTERNAL] Simplify config-merging code in captains-log.  This also gets rid of more deprecation notices during install (see [captains-log:49f433eff348c05115a2caf292b4da0db9499887](https://github.com/balderdashy/captains-log/commit/49f433eff348c05115a2caf292b4da0db9499887))\n* [BUGFIX] Fix long-standing, low-priority (but super annoying) issue with logged dictionaries/arrays getting extra quote marks (due to a pecularity in the usage of util.format()) [d67e9c8e6775](https://github.com/balderdashy/captains-log/commit/d67e9c8e67759e8dda3a2d664c3607e9127d209c)\n* [ENHANCEMENT] Add `sails.config.session.routesDisabled` config option to specify routes that should not use session middleware [c712acf](https://github.com/balderdashy/sails/commit/c712acf29de257d438b422b2c47e67a4d5126ddc)\n* [ENHANCEMENT] Use `res.forbidden()` when denying access to a route via a policy.  Thanks [@wulfsolter](https://github.com/wulfsolter)!  [3764](https://github.com/balderdashy/sails/pull/3764)\n* [ENHANCEMENT] Allow use of Express style path and RegExp in `sails.config.csrf.routesDisabled`.  Thanks [@bolasblack](https://github.com/bolasblack)!\n* [BUGFIX] Fix for query / body params called `length` [3738](https://github.com/balderdashy/sails/issues/3738)\n* [BUGFIX] Fix view rendering when i18n hook is disabled.  Thanks [@mordred](https://github.com/Mordred)! [3741](https://github.com/balderdashy/sails/pull/3741)\n* [BUGFIX] Allow `sails.renderView` to work with globals turned off [3753](https://github.com/balderdashy/sails/issues/3753)\n[d3f634c](https://github.com/balderdashy/sails/commit/d3f634c9ac0c5e2172710fe27ab3f61f8303d840)\n* [BUGFIX] Fix typo which could cause crashing when attempting to serialize non-json-compatible output in the response to a socket request.\n* [UPGRADE] Update all Grunt dependencies [5f6be05](https://github.com/balderdashy/sails/commit/5f6be059823aeb235ef3b4cf53a8d40a341c5873)\n* [UPGRADE] Update \"connect\" dependency to 3.4.1 [1d3c9e6](https://github.com/balderdashy/sails/commit/1d3c9e6459253261e0f763d133c559641bcbfa33)\n* [UPGRADE] Update \"compression\" dependency to version 1.6.2\n* [UPGRADE] Upgraded version of Consolidate to `0.14.1` [a70623c](https://github.com/balderdashy/sails/commit/a70623ce2809d497b3581268354f06904d862268)\n* [UPGRADE] Upgraded version of grunt-contrib-watch to `1.0.0` [3678](https://github.com/balderdashy/sails/issues/3678)\n* [INTERNAL] Use standalone CSRF package instead of using the one (formerly) bundled with Connect.  Sails should be using all standalone middleware now. [1d3c9e6](https://github.com/balderdashy/sails/commit/1d3c9e6459253261e0f763d133c559641bcbfa33)\n[98861ef](https://github.com/balderdashy/sails/commit/98861ef12ddca0ff6d57cf7ea6d4bb9f8bca9656)\n* [INTERNAL] Add some assertions to ensure custom hooks don't use reserved properties [2e76dac](https://github.com/balderdashy/sails/commit/2e76dac2f961a1f20c591fb0a5d7ea6556d2ab70)\n* [INTERNAL] Update code that virtual response uses to read buffer to work with all Node versions [a5ab134](https://github.com/balderdashy/sails/commit/a5ab134c4bafa40db6b2b2133145f8a5462e4abc)\n* [INTERNAL] Remove un-maintained \"wrench\" module from tests; use \"fs-extra\" instead.  Thanks [@Ignigena](https://github.com/Ignigena)! [4f90f78](https://github.com/balderdashy/sails/commit/4f90f78fbfb1b2edf088c5e57d5e4cab56e3cf47)\n\n## 0.12.3\n\n* [BUGFIX] Allow `skipAssets` and `skipRegex` to be used with direct/static view route target syntax [3682](https://github.com/balderdashy/sails/issues/3682).  Thanks [@dottodot](https://github.com/dottodot), [@nikhilbedi](https://github.com/nikhilbedi), and [@AlexanderKozhevin](https://github.com/AlexanderKozhevin)!\n* [BUGFIX] Automatically route to `index/` in deeply nested views when using direct/static view route target syntax\n* [BUGFIX] Add assertion about views which contain extra dots (`.`) in their paths when using direct/static view route target syntax\n* [INTERNAL] Use `chalk` instead of `colors` for console output. Thanks [@markelog](https://github.com/markelog)! [3680](https://github.com/balderdashy/sails/pull/3680)\n\n## 0.12.2\n\n* [ENHANCEMENT] Allow use of `fn` in expanded route targets [e1790b7](https://github.com/balderdashy/sails/commit/e1790b70b35cd7dc50743a63bb169585f8a927f2)\n* [BUGFIX] Add blacklist to \"update\" blueprint action so that it can be used with primary keys that are not \"id\" [3625](https://github.com/balderdashy/sails/issues/3625)\n* [ENHANCEMENT] Allow hooks to be turned off by setting their environment var to the string \"false\" [3618](https://github.com/balderdashy/sails/issues/3618)\n* [BUGFIX] Allow view target syntax for routes to specify deeply-nested views [3604](https://github.com/balderdashy/sails/issues/3604)\n* [BUGFIX] Allow custom bodyParser middleware config [3592](https://github.com/balderdashy/sails/issues/3592)\n* [BUGFIX] When lifting with unknown validation rule, exit gracefully instead of throwing.\n* [BUGFIX] Update validation rules from anchor [3649](https://github.com/balderdashy/sails/issues/3649)\n* [BUGFIX] Respond with an error if attempting to use `req.file()` from a virtual request (i.e. when Skipper is not available).  And don't pass in `res` when building the mock request, since it is not available yet. [3656](https://github.com/balderdashy/sails/issues/3656)\n* [BUGFIX] Fix incorrect handling of errors in responses hook.  Thanks [@tapuzzo-fsi](https://github.com/tapuzzo-fsi)! [3645](https://github.com/balderdashy/sails/pull/3645)\n* [BUGFIX] Fix error from `routeCorsConfig` sometimes being undefined [3662](https://github.com/balderdashy/sails/issues/3662)\n* [INTERNAL] Replace `ready` event with an async `handleLift` lifecycle callback in order to simplify the behavior of `sails lift` and ensure the timing of the \"done\" callback is correct when using it programmatically.\n* [INTERNAL] Massive overhaul of tests.  See [b033f2d thru e85810a](https://github.com/balderdashy/sails/compare/71aa56db59129da58825b22f32030234a4f5ae2c...b033f2d9af4953fd65c3e1bbb44ed4df15da1f68).\n* [INTERNAL] Extrapolate ORM hook into sails-hook-orm.\n* [INTERNAL] Force asynchronicity in the optional third argument of `res.view()`/`res.render()` to pave the way for better, request-agnostic view rendering methods.  This prevents double-calling of the callback if userland code throws an error.  Thanks [@lennym](https://github.com/lennym)! [cd413e15435947aa855e27aab16d9cd9e65ad493](https://github.com/balderdashy/sails/commit/cd413e15435947aa855e27aab16d9cd9e65ad493)\n* [ENHANCEMENT] Update version of i18n to `0.8.1` [3631](https://github.com/balderdashy/sails/pull/3631).\n* [ENHANCEMENT] Improve auto-migrate prompt, and skip the prompt and log an info message instead if `sails.config.models.migrate` is being automatically set to production anyways.  [sails-hook-orm/commit/3161c34edbe0aa07055f8665493734dda1688c2a](https://github.com/balderdashy/sails-hook-orm/commit/3161c34edbe0aa07055f8665493734dda1688c2a)\n* [ENHANCEMENT] Add production check in case sails-disk is being used, and experimental `sails.config.orm.skipProductionWarnings` flag for preventing the warning. [sails-hook-orm/commit/9a0d46e135dadf00bc4576341624a31e50b12838](https://github.com/balderdashy/sails-hook-orm/commit/9a0d46e135dadf00bc4576341624a31e50b12838)\n* [INTERNAL] Don't clone target function in expanded route syntax [6cfb2de](https://github.com/balderdashy/sails/commit/6cfb2de17ccafd789d4af001934b286bc189d1a4)\n* [BUGFIX] Replace naughty code in implicit default res.forbidden() response; relevant when api/responses/ is deleted. See #3667 for more info. Thanks [@Biktop](https://github.com/biktop)!  [4767585994c45e7a7040402a057f0e41660d3419](https://github.com/balderdashy/sails/commit/4767585994c45e7a7040402a057f0e41660d34)19\n* [INCONSISTENCY] Fix embarassing old link that was being shown when you `console.log` the `sails` app instance.  Thanks [@wulfsolter](https://github.com/wulfsolter) [52d45688fcfb6c4437348115f3e9c91595a8d379](https://github.com/balderdashy/sails/commit/52d45688fcfb6c4437348115f3e9c91595a8d379)!\n* [INTERNAL] Get rid of a whimsical little `--require` in mocha.opts that must have gotten lost.  Don't ask us how it ended up there. Thanks [@markelog](https://github.com/markelog)! [06837a53b48352de7c46a1be84e87e28a084ffe2](https://github.com/balderdashy/sails/commit/06837a53b48352de7c46a1be84e87e28a084ffe2)\n* [INTERNAL] Remove needless require from mocha opts. Thanks [@markelog](https://github.com/markelog)! [3681](https://github.com/balderdashy/sails/pull/3681)\n\n\n## 0.12.1\n\n* [INTERNAL] Expose private `loadAndRegisterControllers` method for now, since certain apps are relying on it [0ba7829](https://github.com/balderdashy/sails/commit/0ba78296047874debd33ce62588e97c371b7138c)\n* [BUGFIX] Updated default HTTP cache config property to match what's documented [750d434](https://github.com/balderdashy/sails/commit/750d434a5592b422686ef0217ecab6cc2abcce7a)\n* [BUGFIX] Check for `sails.io` before checking for `sails.io.httpServer` when lowering [92c4b19](https://github.com/balderdashy/sails/commit/92c4b1907073336b879c7c6abf57d5d34b3fca46)\n* [ENHANCEMENT] Keep cookie middleware even if session middleware is deactivated [d21ae2d](https://github.com/balderdashy/sails/commit/d21ae2d8cf16df1169187392c9f522f99d556a85)\n* [BUGFIX] Reset process.env.NODE_ENV after Sails lowers to whatever it was originally (to make it non-sticky when lifting/lowering multiple apps) [f9db888](https://github.com/balderdashy/sails/commit/f9db888a4fd39d43138bad4b279ad86046c27482)\n* [BUGFIX] Use correct extension config for Handlebars [3559](https://github.com/balderdashy/sails/issues/3559)\n* [BUGFIX] Update usage of `sails.sockets.id()` in pubsub hook to `sails.sockets.getId()` to avoid deprecation warning [3552](https://github.com/balderdashy/sails/issues/3552)\n* [INTERNAL] Replace usage of Express middleware (e.g. `require('express').favicon`) with equivalent standalone packages (e.g. `require('serve-favicon')`)\n* [BUGFIX] Allow passing in non-model instances to `publishCreate` [3558](https://github.com/balderdashy/sails/issues/3558)\n\n## 0.12.0\n\n* [UPGRADE] Bump Waterline dependency to `0.11.0` and Sails-Disk to `0.10.9`\n* [ENHANCEMENT] More core hooks are now fully documented ([controllers](https://github.com/balderdashy/sails/tree/master/lib/hooks/controllers)|[grunt](https://github.com/balderdashy/sails/tree/master/lib/hooks/grunt)|[logger](https://github.com/balderdashy/sails/tree/master/lib/hooks/logger)|[cors](https://github.com/balderdashy/sails/tree/master/lib/hooks/cors)|[responses](https://github.com/balderdashy/sails/tree/master/lib/hooks/responses)|[orm](https://github.com/balderdashy/sails/tree/master/lib/hooks/orm))\n* [ENHANCEMENT] Improve `sails --help` output (note that this removes support for common misspellings) [#3539](https://github.com/balderdashy/sails/issues/3539)\n* [ENHANCEMENT] Detect EMFILE warnings from grunt-contrib-watch and treat them as fatal (this is the too many open files / `ulimit -n 1024` thing)  [#3523](https://github.com/balderdashy/sails/issues/3523)\n* [BUGFIX] Downgrade default grunt-contrib-watch dependency installed in new Sails apps to use v0.5.3 [#3526](https://github.com/balderdashy/sails/issues/3526)\n* [BUGFIX] Use locally-installed Sails (when available) with `sails console` instead of always using global [093ec01](https://github.com/balderdashy/sails/commit/093ec01754f1caa54333e97cfb9a095f1697a2f1)\n* [UPGRADE] Update `express-handlebars` to `3.0.0` [1760604](https://github.com/balderdashy/sails/commit/1760604b5a78eacc2d5a1facd4db2de3ea930972)\n* [BUGFIX] Don't attempt to run CSRF protection methods if session is not available\n* [BUGFIX] Properly remove process listeners on sails.lower() to avoid EventEmitter leaks when lifting/lowering multiple apps (e.g. in tests) [#2693](https://github.com/balderdashy/sails/issues/2693)\n* [UPGRADE] Updated versions of Lodash (v3.10.1) and Async (v1.5.0) used in Sails (and globalized in Sails apps by default)\n* [ENHANCEMENT] Support for newer versions of connect-redis session adapter (and other session adapters using express-session)\n* [ENHANCEMENT] Set the useGlobal config option for REPL while using sails console, allows autoreload hook to reflect changes on global models and services\n* [ENHANCEMENT] Support JSON sorting syntax in blueprints [#2449](https://github.com/balderdashy/sails/issues/2449)\n* [ENHANCEMENT] Support namespaced modules as hooks [#3022](https://github.com/balderdashy/sails/issues/3022), [#3514](https://github.com/balderdashy/sails/pull/3514)\n* [ENHANCEMENT] Allow installable hooks to override their default names [#3168](https://github.com/balderdashy/sails/pull/3168)\n* [BUGFIX] Fixed issues with subscribing sockets to new model instances in a clustered environment [#2990](https://github.com/balderdashy/sails/issues/2990), [#3008](https://github.com/balderdashy/sails/issues/3008)\n* [UPGRADE] Update `consolidate` to `^0.12.1`\n* [BUGFIX] Don't allow changing a model's primary key via blueprints\n* [ENHANCEMENT] Added sails.config.keepResponseErrors option to keep response errors in production mode [#2853](https://github.com/balderdashy/sails/pull/2853)\n* [ENHANCEMENT] Added Livescript support [#2662](https://github.com/balderdashy/sails/pull/2662), [#2599](https://github.com/balderdashy/sails/pull/2599)\n* [ENHANCEMENT] Added IcedCoffeeScript support (brrr) [#2599](https://github.com/balderdashy/sails/pull/2599)\n* [BUGFIX] Fix req.param() to work correctly with falsy params [#2756](https://github.com/balderdashy/sails/pull/2756)\n* [ENHANCEMENT] Support \"exposeHeaders\" option in CSRF config [#2712](https://github.com/balderdashy/sails/pull/2712)\n* [BUGFIX] Honor all route options when using policy target syntax (https://github.com/balderdashy/sails/issues/2609#issuecomment-77527609)\n* [ENHANCEMENT] New `sails deploy` CLI command.  See https://github.com/mikermcneil/sails-deploy-azure for an example deployment strategy.\n* [ENHANCEMENT] Support CSRF hook route configuration [#2366](https://github.com/balderdashy/sails/issues/2366)\n* [BUGFIX] Fix [RangeError: Maximum call stack size exceeded] error in PubSub hook\n* [ENHANCEMENT] Support layout for Ractive template engine\n* [ENHANCEMENT] Body parser error logs no longer outputted in production, unless `sails.config.keepResponseErrors` is set [#3347](https://github.com/balderdashy/sails/pull/3347)\n* [BUGFIX] Pluralize option works correctly for all routes [#3223](https://github.com/balderdashy/sails/pull/3223)\n* [BUGFIX] Blueprint create now works when POSTing arrays [#3228](https://github.com/balderdashy/sails/pull/3228)\n* [UPGRADE] Updated `sails-hook-sockets` to `^0.13.0`, which uses an updated socket.io-client module and has some bugfixes\n* [BUGFIX] Default responses now work correctly when views hook is disabled [#2770](https://github.com/balderdashy/sails/pull/2770)\n* [BUGFIX] Restored troubleshooting messages in console when Sails server fails to lift\n* [BUGFIX] app-wide locals (sails.config.views.locals) are combined using a shallow merge (`_.extend()` instead of `_.merge()`) [#3500](https://github.com/balderdashy/sails/issues/3500)\n* [ENHANCEMENT] Added `sails.getRouteFor()` and `sails.getUrlFor()`, utility methods for reverse routing  [#3402](https://github.com/balderdashy/sails/issues/3402#issuecomment-167137610)\n* [BUGFIX] Improve interoperability of virtual requests to provide a more consistent API to Socket.io and `sails.request()` (e.g. for tests)  [121f3feb8702d44420e86707ef05e3282461d136](https://github.com/balderdashy/sails/commit/121f3feb8702d44420e86707ef05e3282461d136)\n* [INTERNAL] Use shallow merge in services hook when loading modules (37eceee9b0ff0a20a285ac2889f4a5e96f3f5b30)\n* [INTERNAL] Don't expose sails.services until `loadModules` is called in the services hook (37eceee9b0ff0a20a285ac2889f4a5e96f3f5b30)\n\n## 0.11.5\n\n* [BUGFIX] Allow disabling of installed hooks [#3550](https://github.com/balderdashy/sails/pull/3550)\n* [ENHANCEMENT] Support namespaced modules as hooks (hotfix from [#3022](https://github.com/balderdashy/sails/issues/3022), [#3514](https://github.com/balderdashy/sails/pull/3514))\n* [ENHANCEMENT] Allow installable hooks to override their default names (hotfix from [#3168](https://github.com/balderdashy/sails/pull/3168))\n\n## 0.11.4\n\n* [SECURITY] Updated several dependencies due to security vulnerabilities (https://github.com/balderdashy/sails/issues/3464#issuecomment-169255559)\n\n## 0.11.3\n\n* [BUGFIX] Fix [RangeError: Maximum call stack size exceeded] error in PubSub hook (https://github.com/balderdashy/sails/issues/2636)\n* [ENHANCEMENT] Allow custom route options in policy target syntax (https://github.com/balderdashy/sails/commit/0990fc10709520a9f6c55923b991708d5eaf8aa0)\n* [ENHANCEMENT] Support CSRF hook route configuration [#2366](https://github.com/balderdashy/sails/issues/2366)\n* [ENHANCEMENT] Added \"exposeHeaders\" option in CORS configuration (https://github.com/balderdashy/sails/pull/2712)\n\n## 0.11.2\n\n* [BUGFIX] Fixes to allow proper installation / execution in environments using Node 4 and/or NPM 3.\n\n## 0.11.1\n\n* Shhhh nothing to see here (version skipped)\n\n## 0.11.0\n\n* [ENHANCEMENT] Allow hooks to be installed in node_modules and dynamic changing of hook name\n* [ENHANCEMENT] Pull out the `sockets` hook to its own repository\n* [ENHANCEMENT] Allow hooks to have individual timeouts, and a global `sails.config.hookTimeout`\n* [ENHANCEMENT] Pull out `sails.io`.js to its own generator\n* [UPGRADE] Update `sails.io.js` for the latest version of the sockets hook\n* [UPGRADE] Upgrade from Socket.IO 0.9.17 to 1.2.1\n* [FEATURE] Add `restPrefix` setting in addition to `prefix` setting for blueprints for finer control\n* [ENHANCEMENT] Support partials and layout with Handlebars for the `backend` generator\n* [BUGFIX] Blueprint creation returns 201 status code instead of 200\n* [BUGFIX] `ractive.toHTML()` replaces `ractive.renderHTML()` for Ractive template engine\n* [BUGFIX] Fix arguments for publishAdd, publishRemove and publishUpdate\n* [ENHANCEMENT] Enable views hook for all methods\n* [BUGFIX] Resolve depreciation warnings\n* [BUGFIX] Fix dependency for npm 2.0.0\n* [BUGFIX] Fix Grunt launching when it's a peer dep\n* [ENHANCEMENT] Upgrade express and skipper because of security vulnerabilities\n* [BUGFIX] Fix Sails crashes if Redis goes down [#2277](https://github.com/balderdashy/sails/pull/2277)\n* [BUGFIX] Fix crash when using sessionless requests over WebSockets [#2107](https://github.com/balderdashy/sails/pull/2107)\n* [ENHANCEMENT] Checking npm-version on install\n* [ENHANCEMENT] Updated \"skipAssets\" regex to ignore query string\n\n\n## 0.10.5\n\n* [ENHANCEMENT] Updated `waterline` to `~0.10.9`\n* [ENHANCEMENT] Added new `routesDisabled` option for CSRF [#2121](https://github.com/balderdashy/sails/pull/2121)\n* [ENHANCEMENT] Refactoring and cleanup.\n* [ENHANCEMENT] Switched from `express3-handlebars` to `express-handlebars`\n* [BUGFIX] Add missing require for async module [#2101](https://github.com/balderdashy/sails/pull/2101)\n\n## 0.10.4 and earlier?\n\nSee https://github.com/balderdashy/sails/commits/eea3b43b4e79d6b9f1b03b318a3ccab80704f22a.\n"
  },
  {
    "path": "CODE-OF-CONDUCT.md",
    "content": "> The Code of Conduct now lives in the 'Contributing' section of the documentation: [http://sailsjs.com/documentation/contributing/code-of-conduct](http://sailsjs.com/documentation/contributing/code-of-conduct)\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "> The Contribution guide now lives in the 'Contributing' section of the documentation: [sailsjs.com/documentation/contributing](http://sailsjs.com/documentation/contributing)\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n--\n\nCopyright © 2012-present, Mike McNeil\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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.\n"
  },
  {
    "path": "MODULES.md",
    "content": "# Modules\n\nSails is a large project, with many modular components.  Each module is located in its own repository, and in most cases is tested individually.\n\nBelow, you'll find an overview of the modules maintained by the core team and community members.\n\n\n## Sails core\n\nThe modules comprising the Sails framework, as well as the other plugins maintained by our core team, are spread across a number of different code repositories.  Some modules can be used outside of the context of Sails, while others are not intended for external use.\n\n#### Framework and ORM\n\n> For more information on the available releases of the Sails framework as a whole, check out the [contribution guide](https://github.com/balderdashy/sails/blob/master/CONTRIBUTING.md).\n\n| Package          |  Latest Stable Release   |  Build Status (edge)                  |\n|------------------|--------------------------|---------------------------------------|\n| <a href=\"http://github.com/balderdashy/sails\" target=\"_blank\" title=\"Github repo for Sails core\"><img src=\"http://sailsjs.com/images/logos/sails-logo_ltBg_dkBlue.png\" width=60 alt=\"Sails.js logo (small)\"/></a>     | [![NPM version](https://badge.fury.io/js/sails.png)](http://badge.fury.io/js/sails) | [![Build Status](https://travis-ci.org/balderdashy/sails.png?branch=master)](https://travis-ci.org/balderdashy/sails)\n| <a href=\"http://github.com/balderdashy/waterline\" target=\"_blank\" title=\"Github repo for Waterline ORM\"><img src=\"https://camo.githubusercontent.com/fda800f7fab38baffcf951761d8c1e97f3af6533/687474703a2f2f692e696d6775722e636f6d2f33587168364d7a2e706e67\" width=100 alt=\"Waterline logo (small)\"/></a> | [![NPM version](https://badge.fury.io/js/waterline.png)](http://badge.fury.io/js/waterline) | [![Build Status](https://travis-ci.org/balderdashy/waterline.png?branch=master)](https://travis-ci.org/balderdashy/waterline)\n\n\n\n#### Core hooks\n\nAs of Sails v1, some hooks are no longer included in Sails core.  Instead, they're published as standalone packages:\n\n| Hook           | Package                                                             |  Latest Stable Release                                                                                             | Build Status (edge)                                                                                                                              | Purpose                                                  |\n|:---------------|---------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------|\n| `orm`          | [sails-hook-orm](https://npmjs.com/package/sails-hook-orm)          | [![NPM version](https://badge.fury.io/js/sails-hook-orm.png)](http://badge.fury.io/js/sails-hook-orm)         | [![Build Status](https://travis-ci.org/balderdashy/sails-hook-orm.png?branch=master)](https://travis-ci.org/balderdashy/sails-hook-orm)          | Implements support for Waterline ORM in Sails.  |\n| `sockets`      | [sails-hook-sockets](https://npmjs.com/package/sails-hook-sockets)  | [![NPM version](https://badge.fury.io/js/sails-hook-sockets.png)](http://badge.fury.io/js/sails-hook-sockets) | [![Build Status](https://travis-ci.org/balderdashy/sails-hook-sockets.png?branch=master)](https://travis-ci.org/balderdashy/sails-hook-sockets)  | Implements Socket.io support in Sails.  |\n\n> These are not _all_ the core hooks in Sails.  There are other core hooks built in to the `sails` package itself (see [`lib/hooks/`](https://github.com/balderdashy/sails/tree/master/lib/hooks)).  These other, _built-in hooks_ can still be disabled or overridden using the same configuration.\n\n\n#### Bundled hooks\n\nCertain additional hooks are bundled as dependencies of a new Sails app, especially when using the \"Web app\" template:\n\n\n| Hook           | Package                                                             |  Latest Stable Release                                                                                             | Build Status (edge)                                                                                                                              | Purpose                                                  |\n|:---------------|---------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------|\n| `grunt`        | [sails-hook-grunt](https://npmjs.com/package/sails-hook-grunt)      | [![NPM version](https://badge.fury.io/js/sails-hook-grunt.png)](http://badge.fury.io/js/sails-hook-grunt)     | [![Build Status](https://travis-ci.org/balderdashy/sails-hook-grunt.png?branch=master)](https://travis-ci.org/balderdashy/sails-hook-grunt)      | Governs the built-in asset pipeline in Sails.  |\n| `organics`     | [sails-hook-organics](https://npmjs.com/package/sails-hook-organics)                | [![NPM version](https://badge.fury.io/js/sails-hook-organics.png)](http://badge.fury.io/js/sails-hook-organics) | [![Build Status](https://travis-ci.org/sailshq/sails-hook-organics.png?branch=master)](https://travis-ci.org/sailshq/sails-hook-organics)             | Evolving library of well-tested, well-documented, and officially supported modules for the most common everyday tasks in apps (e.g. password hashing, emails, billing, etc.)\n| `apianalytics` | [sails-hook-apianalytics](https://npmjs.com/package/sails-hook-apianalytics)                | [![NPM version](https://badge.fury.io/js/sails-hook-apianalytics.png)](http://badge.fury.io/js/sails-hook-apianalytics) | [![Build Status](https://travis-ci.org/sailshq/sails-hook-apianalytics.png?branch=master)](https://travis-ci.org/sailshq/sails-hook-apianalytics)             | A Sails hook for logging detailed request metadata and monitoring your API.\n| `dev` | [sails-hook-dev](https://npmjs.com/package/sails-hook-dev)                | [![NPM version](https://badge.fury.io/js/sails-hook-dev.png)](http://badge.fury.io/js/sails-hook-dev) | [![Build Status](https://travis-ci.org/sailshq/sails-hook-dev.png?branch=master)](https://travis-ci.org/sailshq/sails-hook-dev)             | A Sails hook that provides diagnostic / debugging information and levers during development.\n\n\n\n#### Core socket client SDKs\n\n| Platform     | Package             |  Latest Stable Release           | Build Status (edge)          |\n|--------------|---------------------|----------------------------------|------------------------------|\n| Browser      | [sails.io.js-dist](https://npmjs.com/package/sails.io.js-dist)  | [![NPM version](https://badge.fury.io/js/sails.io.js-dist.png)](http://badge.fury.io/js/sails.io.js-dist) | [![Build Status](https://travis-ci.org/balderdashy/sails.io.js.png?branch=master)](https://travis-ci.org/balderdashy/sails.io.js)  |\n| Node.js      | [sails.io.js](https://npmjs.com/package/sails.io.js)  | [![NPM version](https://badge.fury.io/js/sails.io.js.png)](http://badge.fury.io/js/sails.io.js) | [![Build Status](https://travis-ci.org/balderdashy/sails.io.js.png?branch=master)](https://travis-ci.org/balderdashy/sails.io.js)  |\n\n\n#### Other browser libraries\n\nThe \"Web App\" template in Sails comes with a lightweight client-side JavaScript wrapper for Vue.js called `parasails`:\n\n[![NPM version](https://badge.fury.io/js/parasails.png)](https://npmjs.com/package/parasails)\n\n\n#### Core database adapters\n\n| Package                                                          |  Latest Stable Release                                                                                       | Build Status (edge)                                                                                                                           | Platform                                                          |\n|:-----------------------------------------------------------------| -------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------|\n| [sails-disk](https://npmjs.com/package/sails-disk)               | [![NPM version](https://badge.fury.io/js/sails-disk.png)](http://badge.fury.io/js/sails-disk)                | [![Build Status](https://travis-ci.org/balderdashy/sails-disk.png?branch=master)](https://travis-ci.org/balderdashy/sails-disk)               | Local disk (`.tmp/`)                                              |\n| [sails-mysql](https://npmjs.com/package/sails-mysql)             | [![NPM version](https://badge.fury.io/js/sails-mysql.png)](http://badge.fury.io/js/sails-mysql)              | [![Build Status](https://travis-ci.org/balderdashy/sails-mysql.png?branch=master)](https://travis-ci.org/balderdashy/sails-mysql)             | [MySQL](http://dev.mysql.com/)                                    |\n| [sails-postgresql](https://npmjs.com/package/sails-postgresql)   | [![NPM version](https://badge.fury.io/js/sails-postgresql.png)](http://badge.fury.io/js/sails-postgresql)    | [![Build Status](https://travis-ci.org/balderdashy/sails-postgresql.png?branch=master)](https://travis-ci.org/balderdashy/sails-postgresql)   | [PostgreSQL](https://www.postgresql.org/)                         |\n| [sails-mongo](https://npmjs.com/package/sails-mongo)             | [![NPM version](https://badge.fury.io/js/sails-mongo.png)](http://badge.fury.io/js/sails-mongo)              | [![Build Status](https://travis-ci.org/balderdashy/sails-mongo.png?branch=master)](https://travis-ci.org/balderdashy/sails-mongo)             | [MongoDB](https://www.mongodb.com/)                               |\n| [sails-redis](https://npmjs.com/package/sails-redis)             | [![NPM version](https://badge.fury.io/js/sails-redis.png)](http://badge.fury.io/js/sails-redis)              | [![Build Status](https://travis-ci.org/balderdashy/sails-redis.png?branch=master)](https://travis-ci.org/balderdashy/sails-redis)             | [Redis](http://redis.io)                                          |\n\n\n#### Core filesystem adapters\n\n| Package                                                          |  Latest Stable Release                                                                                       | Build Status (edge)                                                                                                                           | Platform                                                          |\n|:-----------------------------------------------------------------| -------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------|\n| [skipper-disk](https://npmjs.com/package/skipper-disk)           | [![NPM version](https://badge.fury.io/js/skipper-disk.png)](http://badge.fury.io/js/skipper-disk)            | [![Build Status](https://travis-ci.org/balderdashy/skipper-disk.png?branch=master)](https://travis-ci.org/balderdashy/skipper-disk)           | Local disk (`.tmp/uploads/`)                                      |\n| [skipper-s3](https://npmjs.com/package/skipper-s3)           | [![NPM version](https://badge.fury.io/js/skipper-s3.png)](http://badge.fury.io/js/skipper-s3)            | [![Build Status](https://travis-ci.org/balderdashy/skipper-s3.png?branch=master)](https://travis-ci.org/balderdashy/skipper-s3)           | [Amazon S3 (AWS)](https://aws.amazon.com/s3)                                      |\n\n\n\n#### Core generators\n\n_As of Sails v1.0, core generators are now bundled in [sails-generate](https://github.com/balderdashy/sails-generate).  All generators can still be overridden the same way.  For examples, see below._\n\n\n\n#### Core framework utilities\n\n| Package                                                               | Latest Stable Release   | Build Status (edge)         |\n|-----------------------------------------------------------------------|--------------------------|----------------------------|\n| [**skipper**](http://npmjs.com/package/skipper)                       | [![NPM version](https://badge.fury.io/js/skipper.png)](http://badge.fury.io/js/skipper)                           | [![Build Status](https://travis-ci.org/balderdashy/skipper.png?branch=master)](https://travis-ci.org/balderdashy/skipper) |\n| [**machine**](http://npmjs.com/package/machine)                       | [![NPM version](https://badge.fury.io/js/machine.png)](http://badge.fury.io/js/machine)                           | [![Build Status](https://travis-ci.org/node-machine/machine.png?branch=master)](https://travis-ci.org/node-machine/machine) |\n| [**machine-as-action**](http://npmjs.com/package/machine-as-action)   | [![NPM version](https://badge.fury.io/js/machine-as-action.png)](http://badge.fury.io/js/machine-as-action)       | [![Build Status](https://travis-ci.org/sailshq/machine-as-action.png?branch=master)](https://travis-ci.org/treelinehq/machine-as-action) |\n| [**whelk**](http://npmjs.com/package/whelk)   | [![NPM version](https://badge.fury.io/js/whelk.png)](http://badge.fury.io/js/whelk)       | [![Build Status](https://travis-ci.org/sailshq/whelk.png?branch=master)](https://travis-ci.org/treelinehq/whelk) |\n| [**captains-log**](http://npmjs.com/package/captains-log)             | [![NPM version](https://badge.fury.io/js/captains-log.png)](http://badge.fury.io/js/captains-log)                 | [![Build Status](https://travis-ci.org/balderdashy/captains-log.png?branch=master)](https://travis-ci.org/balderdashy/captains-log) |\n| [**anchor**](http://npmjs.com/package/anchor)                         | [![NPM version](https://badge.fury.io/js/anchor.png)](http://badge.fury.io/js/anchor)                             | [![Build Status](https://travis-ci.org/sailsjs/anchor.png?branch=master)](https://travis-ci.org/sailsjs/anchor) |\n| [**sails-generate**](http://npmjs.com/package/sails-generate)         | [![NPM version](https://badge.fury.io/js/sails-generate.png)](http://badge.fury.io/js/sails-generate)             | [![Build Status](https://travis-ci.org/balderdashy/sails-generate.png?branch=master)](https://travis-ci.org/balderdashy/sails-generate) |\n| [**waterline-schema**](http://npmjs.com/package/waterline-schema)     | [![NPM version](https://badge.fury.io/js/waterline-schema.png)](http://badge.fury.io/js/waterline-schema)         | [![Build Status](https://travis-ci.org/balderdashy/waterline-schema.svg?branch=master)](https://travis-ci.org/balderdashy/waterline-schema) |\n| [**waterline-utils**](http://npmjs.com/package/waterline-utils)       | [![NPM version](https://badge.fury.io/js/waterline-utils.png)](http://badge.fury.io/js/waterline-utils)           | [![Build Status](https://travis-ci.org/sailshq/waterline-utils.svg?branch=master)](https://travis-ci.org/balderdashy/waterline-utils)\n| [**include-all**](http://npmjs.com/package/include-all)               | [![NPM version](https://badge.fury.io/js/include-all.png)](http://badge.fury.io/js/include-all)                   | [![Build Status](https://travis-ci.org/balderdashy/include-all.png?branch=master)](https://travis-ci.org/balderdashy/include-all) |\n| [**reportback**](http://npmjs.com/package/reportback)                 | [![NPM version](https://badge.fury.io/js/reportback.png)](http://badge.fury.io/js/reportback)                     | _n/a_\n| [**switchback**](http://npmjs.com/package/switchback)                 | [![NPM version](https://badge.fury.io/js/switchback.png)](http://badge.fury.io/js/switchback)                     | [![Build Status](https://travis-ci.org/node-machine/switchback.png?branch=master)](https://travis-ci.org/node-machine/switchback) |\n| [**rttc**](http://npmjs.com/package/rttc)                             | [![NPM version](https://badge.fury.io/js/rttc.png)](http://badge.fury.io/js/rttc)                                 | [![Build Status](https://travis-ci.org/node-machine/rttc.png?branch=master)](https://travis-ci.org/node-machine/rttc) |\n| [**@sailshq/lodash**](http://npmjs.com/package/@sailshq/lodash)       | [![npm version](https://badge.fury.io/js/%40sailshq%2Flodash.svg)](https://badge.fury.io/js/%40sailshq%2Flodash)                        | _n/a_\n\n\n#### Forks\n\n- [@sailshq/lodash](https://npmjs.com/package/@sailshq/lodash) · _(A fork of Lodash 3.10.x that fixes security issues.  Ongoing maintenance provided by the Sails core team.)_\n- [@sailshq/connect-redis](https://npmjs.com/package/@sailshq/connect-redis)\n- [@sailshq/socket.io-redis](https://npmjs.com/package/@sailshq/socket.io-redis)\n- [@sailshq/eslint](https://npmjs.com/package/@sailshq/eslint)\n- [@sailshq/htmlhint](https://npmjs.com/package/@sailshq/htmlhint)\n- [@sailshq/lesshint](https://npmjs.com/package/@sailshq/lesshint)\n\n\n## Official documentation\n\nThe official documentation for the Sails framework is written in Markdown, and is automatically compiled for the [Sails website](http://sailsjs.com).\n\n| Repo       | Purpose                           |\n|------------|:----------------------------------|\n| [sails-docs](https://github.com/balderdashy/sails-docs)  | Raw content for reference, conceptual, anatomical, and other documentation on the Sails website (in Markdown).\n| [www.sailsjs.com](https://sailsjs.com) | The Sails app that powers [sailsjs.com](http://sailsjs.com).  HTML content is automatically compiled from [`sails-docs`](https://github.com/balderdashy/sails-docs).\n| [doc-templater](https://github.com/uncletammy/doc-templater) | The module we use to pre-process, compile, and format Markdown documentation files into the HTML markup and tree menus at [`sailsjs.com/documentation`](http://sailsjs.com/documentation).\n\n\n_All known translation projects for the Sails documentation are listed in the README [**sails-docs**](https://github.com/balderdashy/sails-docs)._\n\n\n\n\n\n\n\n## Community projects\n\nIn addition to the official code repositories that are supported by the Sails.js core team, there are countless other plugins created by members of the Sails.js community.\n\n\n#### Hooks\n\nThere are at least 200 community hooks for Sails.js [available on NPM](https://www.npmjs.com/search?q=sails+hook).\n\n> [Learn about custom hooks in Sails](http://sailsjs.com/documentation/concepts/extending-sails/hooks).\n\n\n#### Asset pipeline\n\nNeed to customize your build?  Want automatically-generated spritesheets?  Source maps?  Sails.js uses Grunt for its asset pipeline, which means it supports any Grunt plugin. out of the box.  There are thousands of Grunt plugins [available on NPM](http://gruntjs.com/plugins).\n\n> [Learn how to customize your app's asset pipeline](http://sailsjs.com/documentation/concepts/assets).\n\n\n\n#### Generators\n\nDon't like Grunt?  Want to use WebPack or Gulp instead?  Prefer your generated backend files to be written in CoffeeScript?  There are at least 100 community generators for Sails.js [available on NPM](https://www.npmjs.com/search?q=sails%20generate).\n\n> [Learn how to use community generators, and how to build your own](http://sailsjs.com/documentation/concepts/extending-sails/generators).\n\n<!-- Looking for the list that used to be here?  See https://github.com/balderdashy/sails-docs/blob/323477613b6b9ab0cfd7dfb38e53cdff6f46f5d8/concepts/extending-sails/Generators/generatorList.md -->\n\n\n#### Database adapters\n\nIs your database not supported by one of the core adapters?  Good news!  There are many different community database adapters for Sails.js and Waterline [available on NPM](https://www.npmjs.com/search?q=sails+adapter).\n\n> [Learn how to install and configure community adapters](http://sailsjs.com/documentation/concepts/extending-sails/adapters).\n\n\n\n#### Filesystem adapters\n\nNeed to upload files to a cloud file store like S3, GridFS, or Azure Cloud Files?  Check out the community filesystem adapters for Sails.js and Skipper [available on NPM](https://www.npmjs.com/search?q=skipper+adapter).\n\n> [Learn how to wire up one or more custom filesystem adapters for your application](https://github.com/balderdashy/skipper#use-cases).\n\n\n\n#### 3rd party integrations\n\nNeed to process payments with Stripe?  Fetch video metadata from YouTube?  Process user email data via Google APIs?  Choose from hundreds of community machinepacks for Sails.js/Node [available on NPM](http://node-machine.org/machinepacks).\n\n> [Learn how to install and use machinepacks in your controller actions and helpers.](http://node-machine.org/)\n\n\n#### Database drivers\n\nWant to work with your database at a low level?  Need to get extra performance out of your database queries?  Dynamic database connections?\n\n> [Learn about Waterline drivers](https://github.com/node-machine/driver-interface).\n\n\n#### View engines\n\nIs EJS bumming you out?  Prefer to use a different templating language like pug (/jade), handlebars, or dust?  Sails.js supports almost any Consolidate/Express-compatible view engine-- meaning you can use just about any imaginable markup language for your Sails.js views.  Check out the community view engines for Sails.js and Express [available on NPM](http://sailsjs.com/documentation/concepts/views/view-engines).\n\n> [Learn how to set up a custom view engine for your app](http://sailsjs.com/documentation/reference/configuration/sails-config-views).\n\n\n#### Session stores\n\nThe recommended production session store for Sails.js is Redis... but we realize that, for some apps, that isn't an option.  Fortunately, Sails.js supports almost any Connect/Express-compatible session store-- meaning you can store your sessions almost anywhere, whether that's Mongo, on the local filesystem, or even in a relational database.  Check out the community session stores for Sails.js, Express, and Connect [available on NPM](https://www.npmjs.com/search?q=connect%20session-).\n\n> [Learn how to install and configure a custom session store in your Sails app](http://sailsjs.com/documentation/reference/configuration/sails-config-session#?production-config).\n\n\n\n#### Community socket client SDKs & examples\n\nNeed to connect to Sails from a native iPhone or Android app?\n\n| Platform     | Repo       |  Build Status (edge)             |\n|--------------|------------|----------------------------------|\n| iOS          | [sails.ios](https://github.com/ChrisChares/sails.ios)  | [![CI Status](http://img.shields.io/travis/ChrisChares/sails.ios.svg?style=flat)](https://travis-ci.org/ChrisChares/sails.ios) |\n| Objective C  | [sails.io.objective-c](https://github.com/fishrod-interactive/sails-io.objective-c) | _N/A_ |\n| Android      | [Sails Messenger](https://github.com/TheFinestArtist/Sails-Messenger)  | _N/A_  |\n| React Native | [React Native example](https://github.com/mikermcneil/chatkin/tree/master/mobileapp)  | _N/A_  |\n| Cordova      | [Phonegap tips](https://stackoverflow.com/questions/33378104/how-to-implement-sailsjs-phonegap-cordova-application)  | _N/A_  |\n\n\n#### Misc. projects\n\n| Package                                                                             | Latest Stable Release           | Purpose\n|-------------------------------------------------------------------------------------|---------------------------------|:------------|\n| [sails-migrations](https://github.com/BlueHotDog/sails-migrations)                  | [![NPM version](https://badge.fury.io/js/sails-migrations.png)](http://badge.fury.io/js/sails-migrations) | Manual migration tool for Sails, built on Knex.\n| [sails-mysql-transactions](https://github.com/postmanlabs/sails-mysql-transactions) | [![NPM version](https://badge.fury.io/js/sails-mysql-transactions.png)](http://badge.fury.io/js/sails-mysql-transactions) | Augmented database adapter for mySQL with transaction and replication support.\n| [sails-inverse-model](https://www.npmjs.com/package/sails-inverse-model) | Generate Sails/Waterline model definitions from a pre-existing database.\n\n\n\n## FAQ\n\n#### What happened to the core generators?\n\nFor easier maintainence, they were pulled into [`sails-generate`](https://github.com/balderdashy/sails-generate).\n\n#### What release of XYZ should I install?\n\nYou can read about naming conventions for plugins and core modules [here](https://gist.github.com/mikermcneil/baa3eed1030e67f1b0670fb05a2b1f53).  Covers NPM dist tags, git tags, and version strings, as well as recommendations for hotfix branches.\n"
  },
  {
    "path": "README.md",
    "content": "# [![Sails.js](http://balderdashy.github.io/sails/images/logo.png \"Sails.js\")](http://sailsjs.com)\n\n### [Website](https://sailsjs.com/)  &nbsp; [Get Started](https://sailsjs.com/get-started) &nbsp;  [Docs](http://sailsjs.com/documentation)  &nbsp; [News](http://twitter.com/sailsjs) &nbsp; [Submit Issue](http://sailsjs.com/bugs)\n\n[![NPM version](https://badge.fury.io/js/sails.svg)](http://badge.fury.io/js/sails) &nbsp; [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/balderdashy/sails)  &nbsp; [![Twitter Follow](https://img.shields.io/twitter/follow/sailsjs.svg?style=social&maxAge=3600)](https://twitter.com/sailsjs)\n\nSails.js is a [web framework](http://sailsjs.com/whats-that) that makes it easy to build custom, enterprise-grade Node.js apps. It is designed to resemble the MVC architecture from frameworks like Ruby on Rails, but with support for the more modern, data-oriented style of web app & API development. It's especially good for building realtime features like chat.\n\nSince version 1.0, Sails supports `await` out of the box.  This replaces nested callbacks (and the commensurate error handling) with simple, familiar usage:\n\n```javascript\nvar orgs = await Organization.find();\n```\n\n\n## Installation &nbsp;\n**With [node](http://nodejs.org) [installed](http://nodejs.org/en/download):**\n```sh\n# Get the latest stable release of Sails\n$ npm install sails -g\n```\n\n> ##### Upgrading from an earlier version of Sails?\n> Upgrade guides for all major releases since 2013 are available on the Sails website under [**Upgrading**](http://sailsjs.com/upgrading).\n\n\n## Your First Sails Project\n\n**Create a new app:**\n```sh\n# Create the app\nsails new my-app\n```\n\n**Lift sails:**\n```sh\n# cd into the new folder\ncd my-app\n\n# fire up the server\nsails lift\n```\n\n[![Screenshot from the original Sails video](http://i.imgur.com/Ii88jlhl.png)](https://sailsjs.com/get-started)\n\nFor the most up-to-date introduction to Sails, [get started here](https://sailsjs.com/get-started).\n\n## Compatibility\n\nSails is built on [Node.js](http://nodejs.org/), [Express](http://expressjs.com/), and [Socket.io](http://socket.io/).\n\nSails [actions](http://sailsjs.com/documentation/concepts/actions-and-controllers) are compatible with Connect middleware, so in most cases, you can paste code into Sails from an existing Express project and everything will work-- plus you'll be able to use WebSockets to talk to your API, and vice versa.\n\nThe ORM, [Waterline](https://github.com/balderdashy/waterline), has a well-defined adapter system for supporting all kinds of datastores.  Officially supported databases include [MySQL](https://npmjs.com/package/sails-mysql), [PostgreSQL](https://npmjs.com/package/sails-postgresql), [MongoDB](https://npmjs.com/package/sails-mongo), [Redis](https://npmjs.com/package/sails-redis), and [local disk / memory](https://npmjs.com/package/sails-disk).\nCommunity adapters exist for [CouchDB](https://github.com/search?q=sails+couch&nwo=codeswarm%2Fsails-couchdb-orm&search_target=global&ref=cmdform), [neDB](https://github.com/adityamukho/sails-nedb), [SQLite](https://github.com/AndrewJo/sails-sqlite3/tree/0.10), [Oracle](https://github.com/search?utf8=%E2%9C%93&q=%22sails+oracle%22+OR+%22waterline+oracle%22&type=Repositories&ref=searchresults), [MSSQL](https://github.com/misterGF/sails-mssqlserver), [DB2](https://github.com/search?q=sails+db2&type=Repositories&ref=searchresults), [ElasticSearch](https://github.com/search?q=%28elasticsearch+AND+sails%29+OR+%28elasticsearch+AND+waterline%29+&type=Repositories&ref=searchresults), [Riak](https://github.com/search?q=sails+riak&type=Repositories&ref=searchresults),\n[neo4j](https://www.npmjs.org/package/sails-neo4j), [OrientDB](https://github.com/appscot/sails-orientdb),\n[Amazon RDS](https://github.com/TakenPilot/sails-rds), [DynamoDB](https://github.com/TakenPilot/sails-dynamodb), [Azure Tables](https://github.com/azuqua/sails-azuretables), [RethinkDB](https://github.com/gutenye/sails-rethinkdb) and [Solr](https://github.com/sajov/sails-solr); for various 3rd-party REST APIs like Quickbooks, Yelp, and Twitter, including a configurable generic [REST API adapter](https://github.com/zohararad/sails-rest); plus some [eclectic projects](https://www.youtube.com/watch?v=OmcQZD_LIAE).\n\n<!-- Core adapter logos -->\n<a target=\"_blank\" href=\"http://www.mysql.com\">\n  <img width=\"75\" src=\"http://www.mysql.com/common/logos/powered-by-mysql-125x64.png\" alt=\"Powered by MySQL\" title=\"sails-mysql: MySQL adapter for Sails\"/>\n</a>&nbsp; &nbsp; &nbsp; &nbsp;\n<a target=\"_blank\" href=\"http://www.postgresql.org/\"><img width=\"50\" title=\"PostgreSQL\" src=\"http://i.imgur.com/OSlDDKv.png\"/></a>&nbsp; &nbsp; &nbsp; &nbsp;\n<a target=\"_blank\" href=\"http://www.mongodb.org/\"><img width=\"100\" title=\"MongoDB\" src=\"http://i.imgur.com/bC2j13z.png\"/></a>&nbsp; &nbsp; &nbsp; &nbsp;\n<a target=\"_blank\" href=\"http://redis.io/\"><img width=\"75\" title=\"Redis\" src=\"http://i.imgur.com/dozv0ub.jpg\"/></a>&nbsp; &nbsp; &nbsp; &nbsp;\n<!-- /core adapter logos -->\n\n> For the latest core adapters and notable community adapters, see [Available Adapters](http://sailsjs.com/documentation/concepts/extending-sails/adapters/available-adapters).\n\n## Tutorial Course\n- [Sailscasts](https://sailscasts.com/), taught by [Kelvin Omereshone](https://twitter.com/Dominus_Kelvin) _(English)_\n- [Full-Stack JavaScript with Sails.js and Vue.js](https://platzi.com/cursos/javascript-pro/), taught by [Mike McNeil](https://twitter.com/mikermcneil) _(in English, with optional Spanish subtitles)_\n\n\n## Books\n- [Sails.js in Action](https://www.manning.com/books/sails-js-in-action) by Mike McNeil and Irl Nathan (Manning Publications).\n- [Sails.js Essentials](https://www.packtpub.com/web-development/sailsjs-essentials) by Shaikh Shahid (Packt)\n- [Pro Express.js: Part 3](http://link.springer.com/chapter/10.1007%2F978-1-4842-0037-7_18) by Azat Mardan (Apress).\n\n## Support\nNeed help or have a question?\n- [Frequently Asked Questions (FAQ)](http://sailsjs.com/faq)\n- [Tutorials](http://sailsjs.com/faq#?what-are-some-good-community-tutorials)\n- [Community support](http://sailsjs.com/support)\n- [Professional/Enterprise options](http://sailsjs.com/faq#?are-there-professional-support-options)\n\n\n## Issue submission\nPlease read the [submission guidelines](http://sailsjs.com/documentation/contributing/issue-contributions) and [code of conduct](http://sailsjs.com/documentation/contributing/code-of-conduct) before opening a new issue.  Click [here](https://github.com/balderdashy/sails/search?q=&type=Issues) to search/post issues in this repository.\n\n## Contribute\nThere are many different ways you can contribute to Sails:\n- answering questions on [StackOverflow](http://stackoverflow.com/questions/tagged/sails.js), [Gitter](https://gitter.im/balderdashy/sails), [Facebook](https://www.facebook.com/sailsjs), or [Twitter](https://twitter.com/search?f=tweets&vertical=default&q=%40sailsjs%20OR%20%23sailsjs%20OR%20sails.js%20OR%20sailsjs&src=typd)\n- improving the [documentation](https://github.com/balderdashy/sails-docs#contributing-to-the-docs)\n- translating the [documentation](https://github.com/balderdashy/sails-docs/issues/580) to your native language\n- writing [tests](https://github.com/balderdashy/sails/blob/master/test/README.md)\n- writing a [tutorial](https://github.com/sails101/contribute-to-sails101), giving a [talk](https://speakerdeck.com/mikermcneil), or supporting [your local Sails meetup](https://www.meetup.com/find/?allMeetups=false&keywords=node.js&radius=Infinity&sort=default)\n- troubleshooting [reported issues](http://sailsjs.com/bugs)\n- and [submitting patches](http://sailsjs.com/documentation/contributing/code-submission-guidelines).\n\n_Please carefully read our [contribution guide](http://sailsjs.com/documentation/contributing) and check the [build status](http://sailsjs.com/architecture) for the relevant branch before submitting a pull request with code changes._\n\n\n## Links\n- [Website](http://sailsjs.com/)\n- [Documentation](http://sailsjs.com/documentation)\n- [Ask a question](http://sailsjs.com/support)\n- [Tutorial](https://platzi.com/cursos/javascript-pro/)\n- [Roadmap](https://trello.com/b/s9zEnyG7/sails-v1)\n- [Twitter (@sailsjs)](https://twitter.com/sailsjs)\n- [Facebook](https://www.facebook.com/sailsjs)\n\n## Team\nSails is actively maintained with the help of many amazing [contributors](https://github.com/balderdashy/sails/graphs/contributors).  Our core team consists of:\n\n[![Mike McNeil](https://www.gravatar.com/avatar/4b02a9d5780bdd282151f7f9b8a4d8de?s=144&d=identicon&rating=g)](https://twitter.com/mikermcneil) |  [![Kelvin Omereshone](https://avatars.githubusercontent.com/u/24433274?s=144&v=3)](https://twitter.com/dominus_kelvin) |  [![Eric Shaw](https://avatars2.githubusercontent.com/u/7445991?s=144&v=3)](https://github.com/eashaw)\n|:---:|:---:|:---:|\n[Mike McNeil](http://github.com/mikermcneil) | [Kelvin Omereshone](https://github.com/DominusKelvin) | [Eric Shaw](https://github.com/eashaw)\n\n\n[Our company](https://sailsjs.com/about) designs/builds Node.js websites and apps for startups and enterprise customers. After building a few applications and taking them into production, we realized that the Node.js development landscape was very much still the Wild West. Over time, after trying lots of different methodologies, we decided to crystallize all of our best practices into this framework.  Six years later, Sails is now one of the most widely-used web application frameworks in the world. I hope it saves you some time! :)\n\n## License\n\n[MIT License](https://opensource.org/licenses/MIT)  Copyright © 2012-present, Mike McNeil\n\n> Sails is built around so many great open-source technologies that it would never have crossed our minds to keep it proprietary.  We owe huge gratitude and props to Ryan Dahl ([@ry](https://github.com/ry)), TJ Holowaychuk ([@tj](https://github.com/tj)), Doug Wilson ([@dougwilson](https://github.com/dougwilson)) and Guillermo Rauch ([@rauchg](https://github.com/rauchg)) for the work they've done, as well as the stewards of all the other open-source modules we use.  Sails could never have been developed without your tremendous contributions to the JavaScript community.\n\n![A squid peering inside a book, halation and cosmic Sails.js knowledge emanating from the pages of the substantial tome](https://sailsjs.com/images/get_started_hero.png)\n"
  },
  {
    "path": "ROADMAP.md",
    "content": "# Sails Roadmap\n\nAs of November 2017, the Sails project roadmap is now managed [on Trello](https://trello.com/b/s9zEnyG7) to allow for simpler feedback and collaboration.\n\nThe \"Bugs/Priority\" and \"Frontlog\" columns on Trello consist of relatively hashed-out proposals for useful features or patches which would be excellent places to contribute code to the Sails framework. We would exuberantly accept a pull request implementing any of the Trello cards in these columns, so long as that pull request was accompanied with reasonable tests that prove it, all code changes adhere to the style guide laid out in the `.eslintrc` file, and it doesn't cause breaking changes to any other core functionality.\n\n> Community proposals can still be made as pull requests against this file-- but instead of managing status updates here, once approved, they are now managed on the [Trello board](https://trello.com/b/s9zEnyG7).  See \"Pending Proposals\" below for more on that.\n\n\n> ##### What's up with Sails v1.0?\n>\n> For the latest news on Sails v1.0 and beyond, and to check out specific changes and new features, see https://trello.com/b/s9zEnyG7.  (Please feel free to contribute by leaving comments on cards!  It helps the core team to verify that the new release is working as expected.)\n>\n> You can find more information about installing v1.0 here: http://sailsjs.com/documentation/upgrading/to-v-1-0\n\n\n\n## Pending Proposals\n\nThe table below consists of pending proposals for useful features which are not currently in the official roadmap on Trello.  To submit a proposal, send a pull request adding a row to this column.  Please see the Sails [contribution guide](https://github.com/balderdashy/sails/blob/master/CONTRIBUTING.md) to get started.\n\n> - If you would like to see a new feature or an enhancement to an existing feature in Sails, please review the [Sails contribution guide](https://github.com/balderdashy/sails/blob/master/CONTRIBUTING.md). When you are ready, submit a pull request adding a new row to the bottom of this table.\n> - Check [the official Sails roadmap on Trello](https://trello.com/b/s9zEnyG7) to make sure there isn't already something similar feature already planned, in discussion, or under active development.\n> - In your pull request, please include a detailed proposal with a short summary of your use case, the reason why you cannot implement the feature as a hook, adapter, or generator, and a well-reasoned explanation of how you think that feature could be implemented.  Your proposal should include changes or additions to usage, expected return values, and any errors or exit conditions.\n> - Once your pull request has been created, add an additional commit which links to it from your new row in the table below.\n> - If there is sufficient interest in the proposal from other contributors, a core team member will close your PR and add a new card for the proposal to the appropriate column [on Trello](https://trello.com/b/s9zEnyG7).\n\n\nFeature                                          | Proposal                                                                              | Summary\n :---------------------------------------------- | :------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------\n Allow select/omit clauses when populating a singular association | https://trello.com/c/yM9WPxzr/107-waterline-fs2q-tolerate-a-subcriteria-being-provided-to-populate-for-a-singular-associations-but-only-if-it-exclusively-contains | Don't throw an error if these clauses are included in a `populate` for a singular association (but still error if actual \"where\" criteria are used)\n Generate `test/` folder in new Sails apps       | [#2499](https://github.com/balderdashy/sails/pull/2499#issuecomment-171556544)        | Generate a generic setup for mocha tests in all new Sails apps.  Originally suggested by [@jedd-ahyoung](https://github.com/jedd-ahyoung).\n\n\n\n<!--\n\nTODO: Double check that all items from here are covered in Trello:\n\n\n## 1.1.0 and beyond\n\n+ **Blueprint API: Support transactions, when possible.**\n  + See \"FUTURE\" comments throughout the code for the blueprints hook in this repo.\n+ **Sessions: Expand `express-session`/Connect session store interface**\n  + Expose a method in session stores which can be used to do an initial, asynchronous ping in order to check configuration.\n  + Worst case, we should also be able to use [`.get()`](https://github.com/expressjs/session/blob/2667028d39b3655a45eb1f9579d7f66f26a6937f/README.md#storegetsid-callback) with a nonsense session id to do this-- the errors just won't be as nice, or as easy to negotiate.\n  + The best middle-of-the-road solution is probably to get a couple of standardized error codes in the spec for `.get()`\n    + Most likely, that's stuff like `ECONNREFUSED`\n    + But would be a lot better if we could swing more specific error codes-- e.g. `E_BAD_SESSION_STORE_CONFIG` and `E_COULD_NOT_CONNECT_TO_SESSION_STORE`-- since that would eliminate the possibility of false positives due to throwing / `cb(err)`-ing.\n\n\n## 2.0.0 and beyond\n\n+ **Custom responses: Deprecate res.ok() in favor of res.success(); as well as some other breaking changes to custom responses.**\n  + See first half of https://github.com/balderdashy/sails/commit/518bae84f01d17eac84c96977e5ed0c3b6a98083#commitcomment-20917978 for details.\n+ **Blueprint API: Make the behavior of certain error conditions in blueprint actions customizable via `sails.config.blueprints.handle*`**\n  + See second half of https://github.com/balderdashy/sails/commit/518bae84f01d17eac84c96977e5ed0c3b6a98083#commitcomment-20917978 for details.\n+ **Federate sails-hook-blueprints**\n  + In the process, pull the implementation of the three public RPS methods into sails-hook-sockets (and take the rest of the private methods out and drop them into the blueprints hook)\n+ **Federate sails-hook-session**\n  + Remember: This will involve a few delicate tweaks to the boilerplate config generated by `sails new foo --without=session`\n+ **Federate sails-hook-i18n**\n  + ~~(Will need to publish the backwards-compatible i18n hook as a separate package at that point)~~\n+ **Switch to Lodash view engine by default?**\n  + This is really just to normalize the confusing backwardsness of `<%=` vs. `<%-` in EJS/Lodash/Underscore\n  + Would need to figure out partials/layouts though\n\n-->\n\n\n<!--\n\nTODO: go through these lingering pending proposals:\n\nAtomic `update`                                 | See [this issue](https://github.com/balderdashy/sails-mysql/issues/253) for details.  Originally suggested by [@leedm777](https://github.com/leedm777).\nLog key configuration info on lift              | For example, if `config/local.js` is present, log a message explaining that it will be used.  See also https://github.com/dominictarr/rc/issues/23#issuecomment-33875197. Originally suggested by [@mikermcneil](https://github.com/mikermcneil).\nLock + unlock app in dev env                    | Capability for a hook to \"lock\" and/or \"unlock\" the app (in a development env only).  When \"locked\" all requests are intercepted by an endpoint which responds with either a page or JSON payload communicating a custom message.  e.g. so the grunt hook can let us know as it syncs.  e.g. `sails.emit('lock')`. Originally suggested by [@mikermcneil](https://github.com/mikermcneil).\nHook dependency/load order mgmt                 | Rebase the hook dependency+optional depenency system.  A detailed spec was originally proposed by @ragulka, but since then, custom hooks have complicated the equation.\n~~Standalone router~~                               | ~~replace express dependency in `lib/router` with standalone router- either routification or @dougwilson's new project.  See https://github.com/balderdashy/sails/pull/2351#issuecomment-71855236 for more information.~~\nStandalone view renderer                        | Use @fishrock123's standalone views module (enables views over sockets).  See https://github.com/balderdashy/sails/pull/2351#issuecomment-71855236 for more information.\nStandalone static middleware                    | use static middleware directly in `lib/router` (enables static files over sockets)  See https://github.com/balderdashy/sails/pull/2351#issuecomment-71855236 for more information.\nBreak out core hooks into separate modules      | Makes Sails more composable, and removes most of its dependencies in core. Also allows for easier sharing of responsibility w/ the community, controls issue flow.  Started with github.com/balderdashy/sails-hook-sockets\n~~Allow disabling session mw for static assets~~    | ~~Allow session handling to be turned off for static assets. In certain situations, a request for a static asset concurrent to a request to a controller action can have undesirable consequences; specifically, a race condition can occur wherein the static asset response ends up overwriting changes that were made to the session in the controller action.  Luckily, this is a very rare issue, and only occurs when there are race conditions from two different simultaneous requests sent from the same browser with the same cookies.  If you encounter this issue today, first think about whether you actually need/want to do things this way.  If you absolutely need this functionality, a workaround is to change the order of middleware or override the `session` middleware implementation in `config/http.js`.  However, for the long-term, we need a better solution.  It would be good to improve the default behavior of our dependency, `express-session` so that it uses a smarter heuristics.  For more information, see the implementation of session persistence in [express-session](https://github.com/expressjs/session/blob/master/index.js#L207).  However, the single cleanest solution to the general case of this issue would be the ability to turn off session handling features for all static assets (or on a per-route basis).  This is easier said than done.  If you'd like to have this feature, and have the cycles/chops to implement it, please tweet @sgress454 or @mikermcneil and we can dive in and work out a plan.  Summary of what we could merge:  We could remove the default session middleware from our http middleware configuration, and instead add it as a manual step in the virtual router that runs before the route action is triggered.  Good news it that we're actually already doing this in order to support sessions [in the virtual router](https://github.com/balderdashy/sails/blob/master/lib/router/index.js#L101) (e.g. for use w/ socket.io).  So the actual implementation isn't a lot of work-- just needs some new automated tests written, as well as a lot of manual testing (including w/ redis sessions).  We also need to update our HTTP docs to explain that requests for static assets no longer create a session by default, and that default HTTP session support is no longer configured via Express's middleware chain (handled by the virtual router instead.)  Finally we'd also need to document how to enable sessions for assets (i.e. attaching the express-session middleware in `config/http.js`, but doing so directly _before_ the static middleware runs so that other routes don't try to retrieve/save the session twice).  [@sgress454](https://github.com/sgress454)~~\nManual migrations in Sails CLI                  | For production environments it would be nice to have a save/secure command that creates the db automatically for you; e.g. a `sails migrate` or `sails create-db` command.  See [sails-migrations](https://github.com/BlueHotDog/sails-migrations) and [sails-db-migrate](https://github.com/building5/sails-db-migrate) for inspiration.  We should begin by contributing and using one or both of these modules in production in order to refine them further into a full fledged proposal (the Sails core team is using sails-migrations currently).  Originally suggested by [@globegitter](https://github.com/Globegitter).\nWildcard action policies                        | Instead of only having one global action policy `'*'` it would be nice if we could define policies for a specific action in all controllers: `'*/destroy': ['isOwner']` or something similar.  Originally suggested by [@ProLoser](https://github.com/ProLoser).\nSPDY/HTTP2 protocol support                     | See https://github.com/balderdashy/sails/issues/80 for background.\n\n\n-->\n\n"
  },
  {
    "path": "accessible/generate.js",
    "content": "/**\n * Module dependencies\n */\n\nvar sailsgen = require('sails-generate');\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n// TODO: Remove this at next opportunity to simplify maintenance.\n// (Check docs, but I don't think it's documented, and it's not being used\n// anywhere anymore.  Now that NPM is faster than it used to be, there's no\n// reason to work towards separating the core generators from the main\n// framework's NPM package anymore.  So this doesn't really need to exist,\n// unless there are a lot of really good use cases for why generators need to be\n// easily expoed for programmatic usage.  If you have such a use case, let us\n// know at https://sailsjs.com/bugs)\n//\n// But note that this is a breaking change.\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n\n/**\n * require('sails/accessible/generate')\n *\n * Generate files or folders.\n *\n * > This is an exposed version of sails-generate for programmatic use.\n * > (available on `require('sails').Sails.generate()`)\n *\n * @param {Dictionary} scope\n * @param {Function|Dictionary} cbOrHandlers\n */\nmodule.exports = function generate (){\n\n  return sailsgen.apply(this, Array.prototype.slice.call(arguments));\n\n};\n\n"
  },
  {
    "path": "accessible/rc.js",
    "content": "/**\n * Module dependencies\n */\n\nvar rc = require('../lib/app/configuration/rc');\n\n\n/**\n * require('sails/accessible/rc')\n *\n * A direct reference to Sails' built-in `rc` dependency.\n *\n * > This should not be modified.\n * > It's job is to eliminate the need for an extra `rc` dep. in userland\n * > just to load cmdline config in app.js.\n *\n * @type {Ref}\n */\nmodule.exports = rc;\n"
  },
  {
    "path": "appveyor.yml",
    "content": "# # # # # # # # # # # # # # # # # # # # # # # # # #\n#  ╔═╗╔═╗╔═╗╦  ╦╔═╗╦ ╦╔═╗╦═╗ ┬ ┬┌┬┐┬              #\n#  ╠═╣╠═╝╠═╝╚╗╔╝║╣ ╚╦╝║ ║╠╦╝ └┬┘││││              #\n#  ╩ ╩╩  ╩   ╚╝ ╚═╝ ╩ ╚═╝╩╚═o ┴ ┴ ┴┴─┘            #\n#                                                 #\n# This file configures Appveyor CI.               #\n# (i.e. how we run the tests on Windows)          #\n#                                                 #\n# https://www.appveyor.com/docs/lang/nodejs-iojs/ #\n# # # # # # # # # # # # # # # # # # # # # # # # # #\n\n\n# Test against these versions of Node.js.\nenvironment:\n  matrix:\n    - nodejs_version: \"12\"\n    - nodejs_version: \"14\"\n    - nodejs_version: \"16\"\n\n# Install scripts. (runs after repo cloning)\ninstall:\n  # Get the latest stable version of Node.js\n  # (Not sure what this is for, it's just in Appveyor's example.)\n  - ps: Install-Product node $env:nodejs_version\n  # Install declared dependencies\n  - npm install\n\n\n# Post-install test scripts.\ntest_script:\n  # Output Node and NPM version info.\n  # (Presumably just in case Appveyor decides to try any funny business?\n  #  But seriously, always good to audit this kind of stuff for debugging.)\n  - node --version\n  - npm --version\n  # Run the actual tests (but note that we skip linting.)\n  - npm run custom-tests\n\n\n# Don't actually build.\n# (Not sure what this is for, it's just in Appveyor's example.\n#  I'm not sure what we're not building... but I'm OK with not\n#  building it.  I guess.)\nbuild: off\n"
  },
  {
    "path": "bin/private/patched-commander.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar program = require('commander');\n\n\n//\n//\n// Monkey-patch commander\n//\n//\n\n// Override the `usage` method to always strip out the `*` command,\n// which we added so that `sails someunknowncommand` will output\n// the Sails help message instead of nothing.\nvar usage = program.Command.prototype.usage;\nprogram.Command.prototype.usage = program.usage = function( /* str */ ) {\n  program.commands = _.reject(program.commands, {\n    _name: '*'\n  });\n  return usage.apply(this, Array.prototype.slice.call(arguments));\n};\n\n// Force commander to display version information.\nprogram.Command.prototype.versionInformation = program.versionInformation = function() {\n  program.emit('version');\n};\n\nmodule.exports = program;\n"
  },
  {
    "path": "bin/private/read-repl-history-and-start-transcribing.js",
    "content": "/**\n * Module dependencies\n */\n\nvar fs = require('fs');\n\n\n/**\n * readReplHistoryAndBeginTranscribing()\n *\n * Load from a REPL history file, then bind notifier functions to\n * track history-making events as changes occur in the future.\n *\n * > Originally based on https://github.com/tmpvar/repl.history\n *\n * @param {Ref} repl\n *        An already-started REPL instance.\n *\n * @param {String} file\n *        The absolute path to the `.node_history` file to use.\n */\n\nmodule.exports = function readReplHistoryAndBeginTranscribing(repl, file) {\n\n  // Check that the REPL history file exists.\n  var historyFileExists = fs.existsSync(file);\n  if (historyFileExists) {\n\n    // If so, then read it, and set the initial REPL history.\n    repl.history = fs.readFileSync(file, 'utf-8').split('\\n').reverse();\n    repl.history.shift();\n    repl.historyIndex = -1;\n\n  }//>-\n\n  // Attempt to open the history file.\n  var fd = fs.openSync(file, 'a');\n\n  // Track whether we've logged a warning about writing the REPL history yet.\n  // (Just to avoid making everybody tear their hair out.)\n  var alreadyLoggedWarningAboutREPLHistory;\n\n  // Bind alistener that will fire each time a newline is entered on the REPL.\n  repl.addListener('line', function (code) {\n\n    // Update the REPL history file accordingly.\n    if (code && code !== '.history') {\n      var buffer = Buffer.from(code + '\\n');\n      // Send all arguments to fs.write to support Node v0.10.x.\n      fs.write(fd, buffer, 0, buffer.length, null, function (err /*, written */){\n        if (!err) {\n          // If everything worked, then there's nothing to worry about.  We're done.\n          return;\n        }\n\n        // Otherwise, log a warning about the REPL history.\n        // (Unless the spinlock has already been spun.)\n        if (alreadyLoggedWarningAboutREPLHistory) { return; }\n        alreadyLoggedWarningAboutREPLHistory = true;\n        console.warn('WARNING: Could not write REPL history.  Details: '+err.stack);\n\n      });// _∏_\n    }\n    else {\n      repl.historyIndex++;\n      repl.history.pop();\n    }\n\n  });//</every time repl emits a \"line\" event>\n\n  // Bind a one-time-use listener that will fire when the process exits.\n  process.once('exit', function () {\n\n    // Close the history file.\n    fs.closeSync(fd);\n\n  });//</when process emits \"exit\">\n\n};\n"
  },
  {
    "path": "bin/sails-console.js",
    "content": "/**\n * Module dependencies\n */\n\nvar nodepath = require('path');\nvar REPL = require('repl');\nvar stream = require('stream');\nvar _ = require('@sailshq/lodash');\nvar chalk = require('chalk');\nvar CaptainsLog = require('captains-log');\n\nvar rconf = require('../lib/app/configuration/rc')();\nvar Sails = require('../lib/app');\nvar SharedErrorHelpers = require('../errors');\nvar readReplHistoryAndStartTranscribing = require('./private/read-repl-history-and-start-transcribing');\n\n\n\n/**\n * `sails console`\n *\n * Enter the interactive console (aka REPL) for the app\n * in our working directory.  This is just like the default\n * Node REPL except that it starts with the Sails app in the\n * current directory lifted, and with console history enabled\n * (i.e. so you can press up arrow to browse and potentially\n *  replay commands from past runs)\n *\n * @stability 3\n * @see http://sailsjs.com/documentation/reference/command-line-interface/sails-console\n * ------------------------------------------------------------------------\n * This lifts the Sails app in the current working directory, then uses\n * the core `repl` package to spin up an interactive console.\n *\n * Note that, if `--dontLift` was set, then `sails.load()` will be used\n * instead. (By default, the `sails console` cmd runs `sails.lift()`.)\n * ------------------------------------------------------------------------\n */\n\nmodule.exports = function() {\n\n  // Get a temporary logger just for use in `sails console`.\n  // > This is so that logging levels are configurable, even when a\n  // > Sails app hasn't been loaded yet.\n  var cliLogger = CaptainsLog(rconf.log);\n\n  // Now grab our dictionary of configuration overrides to pass in\n  // momentarily when we lift (or load) our Sails app.  This is the\n  // dictionary of configuration settings built from `.sailsrc` file(s),\n  // command-line options, and environment variables.\n  // (No need to clone, since, even through we're modifying it below,\n  //  it's not being used anywhere else.)\n  var configOverrides = rconf;\n\n  // Then tweak this configuration to make sure we always disable\n  // the ASCII ship.  It just doesn't look good in the REPL.\n  if (!_.isObject(configOverrides.log)) {\n    configOverrides.log = {};\n  }\n  configOverrides.log.noShip = true;\n\n  // Determine whether to use the local or global Sails install.\n  var sailsApp = (function _determineAppropriateSailsAppInstance(){\n\n    // Use the app's locally-installed Sails dependency (in `node_modules/sails`),\n    // assuming it's extant and valid.\n    // > Note that we always assume the current working directory to be the\n    // > root directory of the app.\n    var appPath = process.cwd();\n    var localSailsPath = nodepath.resolve(appPath, 'node_modules/sails');\n    if (Sails.isLocalSailsValid(localSailsPath, appPath)) {\n      cliLogger.verbose('Using locally-installed Sails.');\n      cliLogger.silly('(which is located at `'+localSailsPath+'`)');\n      return require(localSailsPath);\n    }// --•\n\n    // Otherwise, since no workable locally-installed Sails exists,\n    // run the app using the currently running version of Sails.\n    // > This is probably always the global install.\n    cliLogger.info('No local Sails install detected; using globally-installed Sails.');\n\n    return Sails();\n\n  })();\n\n  console.log();\n  if (configOverrides.dontLift) {\n    cliLogger.info(chalk.blue('Loading app in interactive mode...'));\n    cliLogger.info(chalk.gray('Sails is not listening for requests (since `dontLift` was enabled).'));\n    cliLogger.info(chalk.gray('You still have access to your models, helpers, and `sails`.'));\n  }\n  else {\n    cliLogger.info(chalk.blue('Starting app in interactive mode...'));\n  }\n  console.log();\n\n  // Lift (or load) Sails\n  (function _loadOrLift(proceed){\n\n    // If `--dontLift` was set, then use `.load()` instead.\n    if (configOverrides.dontLift) {\n      sailsApp.load(configOverrides, proceed);\n    }\n    // Otherwise, go with the default behavior (`.lift()`)\n    else {\n      sailsApp.lift(configOverrides, proceed);\n    }\n\n  })(function afterwards(err){// ~∞%°\n    if (err) {\n      return SharedErrorHelpers.fatal.failedToLoadSails(err);\n    }\n\n    // Get the current global _ value, if any.\n    var underscore = global._;\n\n    cliLogger.info('Welcome to the Sails console.');\n    cliLogger.info(chalk.grey('( to exit, type ' + '<CTRL>+<C>' + ' )'));\n    console.log();\n\n    // Define a custom output stream that will replace global._ after every command.\n    // This works around the issue where the Node REPL uses the underscore to hold\n    // the result of the last command.\n    var outputStream = (function() {\n      // Create a new writable stream.\n      var writableStream = new stream.Writable();\n      // Add the `_write` method to it (can't do this in the constructor b/c that's not supported in older Node versions).\n      writableStream._write = function(chunk, encoding, callback) {\n        // Ignore the output generated the first time the global _ is set in Node 6+.\n        if (chunk.toString('utf8').indexOf('Expression assignment to _ now disabled.') !== -1) {\n          return callback();\n        }\n        // Set the global underscore again (for Node < 6).\n        // See code after `REPL.start` for more info.\n        if (typeof underscore !== 'undefined') {\n          global._ = underscore;\n        }\n        // Forward the chunk on to stdout.\n        process.stdout.write(chunk, encoding, callback);\n      };\n      // Return the new writable stream.\n      return writableStream;\n    })();\n\n    // Start a REPL.\n    var repl = REPL.start({\n      // Set the REPL prompt.\n      prompt: 'sails> ',\n      // Allow the REPL to use the same global space as the Sails app, giving it access\n      // to things like globalized models.\n      useGlobal: true,\n      // Specify the custom output stream we created above.\n      output: outputStream,\n      // When an output stream is specified, an input stream must be specified as well\n      // or else the REPL crashes.\n      input: process.stdin,\n      // Set `terminal` to true to allow arrow keys to work correctly,\n      // even when we're using a custom output stream.  Otherwise pressing\n      // the up arrow just outputs ^[[A instead of accessing history.\n      terminal: true,\n      preview: false,\n      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n      // FUTURE: Potentially use custom `eval` as stopgap for `await` support in Node <v9\n      // https://nodejs.org/api/repl.html#repl_repl_start_options\n      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    });\n\n    // Replace the global _ value, if any.  This will deactivate the underscore behavior\n    // in the Node REPL for Node 6+, which conflicts with the global _ in Sails apps.\n    // Note that in Node 6+ this typically causes the REPL to output \"Expression assignment to _ now disabled.\",\n    // which unfortunately gets put right next to the `sails>` prompt, making for a very confusing\n    // introduction to the Sails console.  In the custom output stream we defined above, we filter out\n    // that message.\n    if (typeof underscore !== 'undefined') {\n      global._ = underscore;\n    }\n\n    // Now attempt to read the existing REPL history file, if there is one.\n    var pathToReplHistoryFile = nodepath.join(sailsApp.config.paths.tmp, '.node_history');\n    try {\n\n      // Read the REPL history file, and bind notifier functions that will listen\n      // for history-making events, and keep track of them for future generations.\n      readReplHistoryAndStartTranscribing(repl, pathToReplHistoryFile);\n\n    }\n    catch (e) {\n\n      cliLogger.verbose('Encountered an error attempting to access/interpret a `.node_history` file at `'+pathToReplHistoryFile+'`.');\n      cliLogger.verbose('(This session of `sails console` will still work, it just won\\'t support REPL history.)');\n      cliLogger.verbose('Error details:\\n',e);\n\n    }//>-\n\n    // Bind a one-time-use handler that will run when the REPL instance emits its \"exit\" event.\n    repl.once('exit', function(err) {\n\n      // If an error occurred, log it, then terminate the process with an exit code of 1.\n      if (err) {\n        cliLogger.error(err);\n        return process.exit(1);\n      }// --•\n\n      // Otherwise, everything is cool.\n      // Call the core 'lower' function and terminate the process with an exit code of 0.\n      sailsApp.lower(function () {\n        return process.exit(0);\n      });\n\n    });//</when 'exit' event it emitted by repl instance>\n\n  });//</after lifting or loading Sails app>\n\n};\n"
  },
  {
    "path": "bin/sails-debug-console.js",
    "content": "#!/usr/bin/env node\n\n\n/**\n * Module dependencies\n */\n\nvar path = require('path');\nvar Womb = require('child_process');\nvar CaptainsLog = require('captains-log');\nvar chalk = require('chalk');\nvar Sails = require('../lib/app');\n\n\n/**\n * `sails debug-console`\n *\n * Attach the Node debugger and enter the interactive console\n * (aka REPL) for the app in our working directory by calling\n * `sails-console.js`. You can then use the console to invoke\n * methods and Node inspector, or your favorite IDE, to debug\n * your app as it runs.\n *\n * @stability 2\n * @see http://sailsjs.org/documentation/reference/command-line-interface/sails-debug-console\n */\nmodule.exports = function(cmd) {\n\n  var extraArgs = cmd.parent.rawArgs.slice(3);\n\n  var log = CaptainsLog();\n\n  // Use the app's local Sails in `node_modules` if one exists\n  // But first make sure it'll work...\n  var appPath = process.cwd();\n  var pathToSails = path.resolve(appPath, '/node_modules/sails');\n  if (!Sails.isLocalSailsValid(pathToSails, appPath)) {\n    // otherwise, use the currently-running instance of Sails\n    pathToSails = path.resolve(__dirname, './sails.js');\n  }\n\n  console.log();\n  log.info('Running console in debug mode...');\n\n  log.info(chalk.grey('( to exit, type ' + '<CTRL>+<C>' + ' )'));\n  console.log();\n\n  // Spin up child process for the Sails console\n  Womb.spawn('node', ['--debug', pathToSails, 'console'].concat(extraArgs), {\n    stdio: 'inherit'\n  });\n\n};\n"
  },
  {
    "path": "bin/sails-debug.js",
    "content": "/**\n * Module dependencies\n */\n\nvar path = require('path');\nvar Womb = require('child_process');\nvar CaptainsLog = require('captains-log');\nvar chalk = require('chalk');\nvar Sails = require('../lib/app');\n\n\n/**\n * `sails debug`\n *\n * Attach the Node debugger and lift a Sails app.\n * You can then use Node inspector to debug your app as it runs.\n *\n * @stability 2\n * @see http://sailsjs.com/documentation/reference/command-line-interface/sails-debug\n */\nmodule.exports = function(cmd) {\n\n  var extraArgs = cmd.parent.rawArgs.slice(3);\n\n  var log = CaptainsLog();\n\n  // Use the app's local Sails in `node_modules` if one exists\n  // But first make sure it'll work...\n  var appPath = process.cwd();\n  var pathToSails = path.resolve(appPath, '/node_modules/sails');\n  if (!Sails.isLocalSailsValid(pathToSails, appPath)) {\n    // otherwise, use the currently-running instance of Sails\n    pathToSails = path.resolve(__dirname, './sails.js');\n  }\n\n  console.log();\n  log.info('Running app in debug mode...');\n\n  log.info(chalk.grey('( to exit, type ' + '<CTRL>+<C>' + ' )'));\n  console.log();\n\n  // Spin up child process for Sails\n  Womb.spawn('node', ['--debug', pathToSails, 'lift'].concat(extraArgs), {\n    stdio: 'inherit'\n  });\n\n};\n"
  },
  {
    "path": "bin/sails-deploy.js",
    "content": "/**\n * Module dependencies\n */\n\nvar path = require('path');\nvar rconf = require('../lib/app/configuration/rc')();\n\n/**\n * `sails deploy`\n *\n * Deploy the Sails app in the current directory to a hosting provider.\n *\n * @stability 1\n */\n\nmodule.exports = function() {\n\n  var commands = rconf.commands;\n  var deploy = commands && commands.deploy;\n  var modulePath = deploy && deploy.module;\n  var module;\n\n  // If no module path was specified, bail out\n  if (!modulePath) {\n    console.error('No module specified for the `deploy` command.');\n    console.error('To use `sails deploy`, set a `commands.deploy.module` setting in your .sailsrc file');\n    return;\n  }\n\n  // Attempt to require the specified module from the project node_modules folder\n  try {\n    module = require(path.resolve(process.cwd(), 'node_modules', modulePath));\n  } catch (unusedErr) { // FUTURE: provide access to error details instead of swallowing\n    // If the module couldn't be required, bail out\n    console.error('Could not require module at path: ' + modulePath + '.  Please check the path and try again.');\n    return;\n  }//•\n\n  try {\n    // Attempt to run the deploy command\n    module({config: rconf}, function(err) {\n      // If there were any issues, log them to the console.\n      if (err) {\n        console.error('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-');\n        console.error('Deployment failed!  Details below:');\n        console.error('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-');\n        console.error(err);\n      }\n    });\n  }\n  // Chances are we won't catch any errors internal to the deploy command here;\n  // this would probably be an error at the top level of the deploy script.\n  catch(e) {\n    console.error('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-');\n    console.error('Could not run deploy!  Details below:');\n    console.error('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-');\n    console.error(e);\n  }\n\n\n};\n"
  },
  {
    "path": "bin/sails-generate.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar path = require('path');\nvar assert = require('assert');\nvar _ = require('@sailshq/lodash');\nvar CaptainsLog = require('captains-log');\nvar sailsGen = require('sails-generate');\nvar package = require('../package.json');\nvar rconf = require('../lib/app/configuration/rc')();\n\n\n/**\n * `sails generate`\n *\n * Generate one or more file(s) in our working directory.\n * This runs an appropriate generator.\n *\n * @see http://sailsjs.com/docs/reference/command-line-interface/sails-generate\n */\n\nmodule.exports = function () {\n\n  // Build initial scope for our call to sails-generate.\n  var scope = {\n    rootPath: process.cwd(),\n    sailsRoot: path.resolve(__dirname, '..'),\n    modules: {},\n    sailsPackageJSON: package,\n  };\n\n  // Mix-in rc config\n  // (note that we mix in everything namespaced under `generators` at the top level-\n  //  but also that anything at the top level takes precedence)\n  _.merge(scope, rconf.generators);\n  _.merge(scope, rconf);\n\n\n  // Get a temporary logger just for use in `sails generate`.\n  // > This is so that logging levels are configurable, even when a\n  // > Sails app hasn't been loaded yet.\n  var log = CaptainsLog(rconf.log);\n\n\n  // Pass down the original serial args from the CLI.\n  // > Note that (A) first, we remove the last arg from commander using `_.initial`,\n  // > and then (B) second, we remove ANOTHER arg -- the one representing the\n  // > generator type -- in favor of just setting `scope.generatorType`.\n  var cliArguments = _.initial(arguments);\n  scope.generatorType = cliArguments.shift();\n  scope.args = cliArguments;\n\n\n  // If no generator type was defined, then log the expected usage.\n  if (!scope.generatorType) {\n    console.log('Usage: sails generate [something]');\n    return;\n  }\n  assert(arguments.length === (scope.args.length + 2), new Error('Consistency violation: Should have trimmed exactly two args.'));\n\n  // Call out to `sails-generate`.\n  return sailsGen(scope, {\n\n    // Handle unexpected errors.\n    error: function (err) {\n\n      log.error(err);\n      return process.exit(1);\n\n    },//</on error :: sailsGen()>\n\n    // Attend to invalid usage.\n    invalid: function (err) {\n\n      // If this is an Error, don't bother logging the stack, just log the `.message`.\n      // (This is purely for readability.)\n      if (_.isError(err)) {\n        log.error(err.message);\n      }\n      else {\n        log.error(err);\n      }\n\n      return process.exit(1);\n\n    },//</on invalid :: sailsGen()>\n\n    // Enjoy success.\n    success: function (){\n\n      // Infer the `outputPath` if necessary/possible.\n      if (!scope.outputPath && scope.filename && scope.destDir) {\n        scope.outputPath = scope.destDir + scope.filename;\n      }\n\n      // Humanize the output path\n      var humanizedPath;\n      if (scope.outputPath) {\n        humanizedPath = ' at ' + scope.outputPath;\n      }\n      else if (scope.destDir) {\n        humanizedPath = ' in ' + scope.destDir;\n      }\n      else {\n        humanizedPath = '';\n      }\n\n      // Humanize the module identity\n      var humanizedId;\n      if (scope.id) {\n        humanizedId = util.format(' (\"%s\")',scope.id);\n      }\n      else {\n        humanizedId = '';\n      }\n\n      // If this isn't the \"new\" generator, and we're not explicitly\n      // asked not to, output a final success message.\n      if (scope.generatorType !== 'new' && !scope.suppressFinalLog) {\n\n        log.info(util.format(\n          'Created a new %s%s%s!',\n          scope.generatorType, humanizedId, humanizedPath\n        ));\n\n      }\n\n    }//</on success :: sailsGen()>\n\n  });//</sailsGen()>\n\n};\n"
  },
  {
    "path": "bin/sails-inspect.js",
    "content": "/**\n * Module dependencies\n */\n\nvar path = require('path');\nvar Womb = require('child_process');\nvar CaptainsLog = require('captains-log');\nvar chalk = require('chalk');\nvar Sails = require('../lib/app');\n\n\n/**\n * `sails inspect`\n *\n * Attach the Node inspector and lift a Sails app.\n * You can then use Node inspector to debug your app as it runs.\n *\n */\nmodule.exports = function(cmd) {\n\n  var extraArgs = cmd.parent.rawArgs.slice(3);\n\n  var log = CaptainsLog();\n\n  // Use the app's local Sails in `node_modules` if one exists\n  // But first make sure it'll work...\n  var appPath = process.cwd();\n  var pathToSails = path.resolve(appPath, '/node_modules/sails');\n  if (!Sails.isLocalSailsValid(pathToSails, appPath)) {\n    // otherwise, use the currently-running instance of Sails\n    pathToSails = path.resolve(__dirname, './sails.js');\n  }\n\n  console.log();\n  log.info('Running app in inspect mode...');\n  if (process.version[1] >= 8) {\n    log.info('In Google Chrome, go to chrome://inspect for interactive debugging.');\n    log.info('For other options, see the link below.');\n  }\n\n  log.info(chalk.grey('( to exit, type ' + '<CTRL>+<C>' + ' )'));\n  console.log();\n\n  // Spin up child process for Sails\n  Womb.spawn('node', ['--inspect', pathToSails, 'lift'].concat(extraArgs), {\n    stdio: 'inherit'\n  });\n\n};\n"
  },
  {
    "path": "bin/sails-lift.js",
    "content": "/**\n * Module dependencies\n */\n\nvar nodepath = require('path');\nvar _ = require('@sailshq/lodash');\nvar chalk = require('chalk');\nvar captains = require('captains-log');\n\nvar rconf = require('../lib/app/configuration/rc')();\nvar Sails = require('../lib/app');\nvar SharedErrorHelpers = require('../errors');\n\n\n\n/**\n * `sails lift`\n *\n * Fire up the Sails app in our working directory, using the\n * appropriate version of Sails.\n *\n * > This uses the locally-installed Sails, if available.\n * > Otherwise, it uses the currently-running Sails (which,\n * > 99.9% of the time, is the globally-installed version.)\n *\n * @stability 3\n * @see http://sailsjs.com/documentation/reference/command-line-interface/sails-lift\n */\n\nmodule.exports = function() {\n\n  // Get a temporary logger just for use in `sails lift`.\n  // > This is so that logging levels are configurable, even when a\n  // > Sails app hasn't been loaded yet.\n  var cliLogger = captains(rconf.log);\n\n  console.log();\n  cliLogger.info(chalk.grey('Starting app...'));\n  console.log();\n\n  // Now grab our dictionary of configuration overrides to pass in\n  // momentarily when we lift (or load) our Sails app.  This is the\n  // dictionary of configuration settings built from `.sailsrc` file(s),\n  // command-line options, and environment variables.\n  // (No need to clone, since it's not being used anywhere else)\n  var configOverrides = rconf;\n\n  // Determine whether to use the local or global Sails install.\n  var sailsApp = (function _determineAppropriateSailsAppInstance(){\n\n    // Use the app's locally-installed Sails dependency (in `node_modules/sails`),\n    // assuming it's extant and valid.\n    // > Note that we always assume the current working directory to be the\n    // > root directory of the app.\n    var appPath = process.cwd();\n    var localSailsPath = nodepath.resolve(appPath, 'node_modules/sails');\n    if (Sails.isLocalSailsValid(localSailsPath, appPath)) {\n      cliLogger.verbose('Using locally-installed Sails.');\n      cliLogger.silly('(which is located at `'+localSailsPath+'`)');\n      return require(localSailsPath);\n    }// --•\n\n    // Otherwise, since no workable locally-installed Sails exists,\n    // run the app using the currently running version of Sails.\n    // > This is probably always the global install.\n    cliLogger.info('No local Sails install detected; using globally-installed Sails.');\n\n    return Sails();\n\n  })();\n\n  // Lift (or load) Sails\n  (function _loadOrLift(proceed){\n\n    // If `--dontLift` was set, then use `.load()` instead.\n    if (!_.isUndefined(configOverrides.dontLift)) {\n      sailsApp.load(configOverrides, proceed);\n    }\n    // Otherwise, go with the default behavior (`.lift()`)\n    else {\n      sailsApp.lift(configOverrides, proceed);\n    }\n\n  })(function afterwards(err){// ~∞%°\n    if (err) {\n      return SharedErrorHelpers.fatal.failedToLoadSails(err);\n    }// --•\n\n    // If we made it here, the app is all lifted and ready to go.\n    // The server will lower when the process is terminated-- either by a signal,\n    // or via an uncaught fatal error.\n\n  });//</after lifting or loading Sails app>\n\n};\n"
  },
  {
    "path": "bin/sails-migrate.js",
    "content": "/**\n * Module dependencies\n */\n\nvar nodepath = require('path');\nvar _ = require('@sailshq/lodash');\nvar captains = require('captains-log');\nvar rconf = require('../lib/app/configuration/rc')();\nvar Sails = require('../lib/app');\nvar SharedErrorHelpers = require('../errors');\n\n\n\n/**\n * `sails migrate`\n *\n * Load (but don't lift) the Sails app in our working directory, using the\n * appropriate version of Sails, and skipping the Grunt hook.  Then run the\n * app's bootstrap function, and simply exit.\n *\n * (Useful for quickly running auto-migrations by hand.)\n *\n * > This uses the locally-installed Sails, if available.\n * > Otherwise, it uses the currently-running Sails (which,\n * > 99.9% of the time, is the globally-installed version.)\n *\n * Example usage:\n * ```\n * # Run \"alter\" auto-migrations to attempt to adjust all data\n * # (but possibly delete it)\n * sails migrate\n *\n * # Run \"drop\" auto-migrations to wipe all data\n * sails migrate --drop\n * ```\n *\n * @stability EXPERIMENTAL\n * @see http://sailsjs.com/documentation/reference/command-line-interface/sails-migrate\n */\n\nmodule.exports = function() {\n\n  // Get a temporary logger just for use in this file.\n  // > This is so that logging levels are configurable, even when a\n  // > Sails app hasn't been loaded yet.\n  var cliLogger = captains(rconf.log);\n\n  cliLogger.warn('`sails migrate` is currently experimental.');\n\n  // Now grab our dictionary of configuration overrides to pass in\n  // momentarily when we lift (or load) our Sails app.  This is the\n  // dictionary of configuration settings built from `.sailsrc` file(s),\n  // command-line options, and environment variables.\n  // (No need to clone, since it's not being used anywhere else)\n  var configOverrides = rconf;\n\n  // Determine whether to use the local or global Sails install.\n  var sailsApp = (function _determineAppropriateSailsAppInstance(){\n\n    // Use the app's locally-installed Sails dependency (in `node_modules/sails`),\n    // assuming it's extant and valid.\n    // > Note that we always assume the current working directory to be the\n    // > root directory of the app.\n    var appPath = process.cwd();\n    var localSailsPath = nodepath.resolve(appPath, 'node_modules/sails');\n    if (Sails.isLocalSailsValid(localSailsPath, appPath)) {\n      cliLogger.verbose('Using locally-installed Sails.');\n      cliLogger.silly('(which is located at `'+localSailsPath+'`)');\n      return require(localSailsPath);\n    }// --•\n\n    // Otherwise, since no workable locally-installed Sails exists,\n    // run the app using the currently running version of Sails.\n    // > This is probably always the global install.\n    cliLogger.info('No local Sails install detected; using globally-installed Sails.');\n\n    return Sails();\n\n  })();//†\n\n\n  // Skip the grunt hook.\n  // (Note that we can't really use `sails.config.loadHooks` because we don't\n  // know what kinds of stuff you might be relying on in your bootstrap function.)\n  //\n  // > FUTURE: if no orm hook actually installed, then fail with an error\n  // > explaining you can't really run auto-migrations without that.\n  configOverrides = _.extend(_.clone(configOverrides), {\n    hooks: _.extend(configOverrides.hooks||{}, {\n      grunt: false\n    }),\n  });\n\n  // Load the Sails app\n  sailsApp.load(configOverrides, function(err) {\n    if (err) {\n      return SharedErrorHelpers.fatal.failedToLoadSails(err);\n    }// --•\n\n    // Run the app bootstrap\n    sailsApp.runBootstrap(function afterBootstrap(err) {\n      if (err) {\n        sailsApp.log.error('Bootstrap function encountered an error during `sails migrate`: (see below)');\n        sailsApp.log.error(err);\n        return;\n      }// --•\n\n      // Tear down the Sails app\n      sailsApp.lower();\n\n    });//_∏_. </after running bootstrap>\n\n  });//_∏_  </after loading Sails app>\n\n};\n"
  },
  {
    "path": "bin/sails-new.js",
    "content": "/**\n * Module dependencies\n */\n\nvar nodepath = require('path');\nvar _ = require('@sailshq/lodash');\nvar sailsgen = require('sails-generate');\nvar CaptainsLog = require('captains-log');\nvar package = require('../package.json');\nvar rconf = require('../lib/app/configuration/rc')();\n\n\n/**\n * `sails new`\n *\n * Generate a new Sails app.\n *\n * ```\n * # In the current directory:\n * sails new\n * ```\n *\n * ```\n * # As a new directory or within an existing directory:\n * sails new foo\n * ```\n *\n * @stability 3\n * @see http://sailsjs.com/documentation/reference/command-line-interface/sails-new\n * ------------------------------------------------------------------------\n * This command builds `scope` for the generator by scooping up any available\n * configuration using `rc` (merging config from env vars, CLI opts, and\n * relevant `.sailsrc` files).  Then it runs the `sails-generate-new`\n * generator (https://github.com/balderdashy/sails-generate-new).\n */\n\nmodule.exports = function () {\n\n  // Build initial scope\n  var scope = {\n    rootPath: process.cwd(),\n    modules: {},\n    sailsRoot: nodepath.resolve(__dirname, '..'),\n    sailsPackageJSON: package,\n    viewEngine: rconf.viewEngine\n  };\n\n  // Support --template option for backwards-compat.\n  if (!scope.viewEngine && rconf.template) {\n    scope.viewEngine = rconf.template;\n  }\n\n  // Mix-in rconf\n  _.merge(scope, rconf.generators);\n\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  // FUTURE: Verify that we can just do a top-level merge here,\n  // and then reference `scope.generators.modules` as needed\n  // (would be simpler- but would be a breaking change, though\n  // unlikely to affect most people.  The same issue exists in\n  // other places where we read rconf and then call out to\n  // sails-generate)\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  _.merge(scope, rconf);\n\n  // Get a temporary logger just for use in `sails new`.\n  // > This is so that logging levels are configurable, even when a\n  // > Sails app hasn't been loaded yet.\n  var log = CaptainsLog(rconf.log);\n\n  // Pass the original CLI arguments down to the generator\n  // (but first, remove commander's extra argument)\n  var cliArguments = Array.prototype.slice.call(arguments);\n  cliArguments.pop();\n  scope.args = cliArguments;\n\n  scope.generatorType = 'new';\n\n  return sailsgen(scope, {\n    // Handle unexpected errors.\n    error: function (err) {\n\n      log.error(err);\n      return process.exit(1);\n\n    },//</on error :: sailsGen()>\n\n    // Attend to invalid usage.\n    invalid: function (err) {\n\n      // If this is an Error, don't bother logging the stack, just log the `.message`.\n      // (This is purely for readability.)\n      if (_.isError(err)) {\n        log.error(err.message);\n      }\n      else {\n        log.error(err);\n      }\n\n      return process.exit(1);\n\n    },//</on invalid :: sailsGen()>\n    success: function() {\n      // Good to go.\n    }\n  });\n};\n"
  },
  {
    "path": "bin/sails-run.js",
    "content": "/**\n * Module dependencies\n */\n\nvar path = require('path');\nvar fs = require('fs');\nvar _ = require('@sailshq/lodash');\nvar chalk = require('chalk');\nvar COMMON_JS_FILE_EXTENSIONS = require('common-js-file-extensions');\nvar flaverr = require('flaverr');\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n// Note that `whelk`, `machinepack-process`, and `../lib/app` are\n// conditionally required below, only in the cases where they are actually used.\n// (That way you don't have to wait for them to load if you're not using them.)\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n\n/**\n * Module constants\n */\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n// Supported file extensions for imperative code files such as hooks:\n//  • 'js' (.js)\n//  • 'ts' (.ts)\n//  • 'es6' (.es6)\n//  • ...etc.\n//\n// > For full list, see:\n// > https://github.com/luislobo/common-js-file-extensions/blob/210fd15d89690c7aaa35dba35478cb91c693dfa8/README.md#code-file-extensions\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\nvar BASIC_SUPPORTED_FILE_EXTENSIONS = COMMON_JS_FILE_EXTENSIONS.code;\n\n\n/**\n * `sails run`\n *\n * Run a script for the Sails app in the current working directory.\n *\n * This matches either a JavaScript file in the `scripts/` directory, or one of the command-line scripts\n * declared within the `scripts: {}` dictionary in the package.json file.\n *\n * @see https://sailsjs.com/documentation/reference/command-line-interface/sails-run\n */\n\nmodule.exports = function(scriptName) {\n\n  // If there is only one argument, it means there is actually no scriptName at all.\n  // (A detail of how commander works.)\n  if (arguments.length === 1) {\n    scriptName = undefined;\n  }\n\n  // Sanitize the script name, for comfort.\n  if (!scriptName) {\n    console.error('Which one?  (To run a script, provide its name.)');\n    console.error('For example:');\n    console.error('    sails run rebuild-cloud-sdk');\n    console.error();\n    console.error('^ runs `scripts/rebuild-cloud-sdk.js`.');\n    console.error();\n    console.error('(For more help, visit '+chalk.underline('https://sailsjs.com/support')+'.)');\n    return process.exit(1);\n  }\n\n  // Remove `scripts/` prefix if it exists (allows users to do sails run scripts/foo, so they can use tab autocomplete).\n  scriptName = _.trim(scriptName);\n  scriptName = scriptName.replace(/^scripts\\//, '');\n\n  // Unless the script name is under a \"scope\" (as in `@sailshq/some-package`), don't allow slashes in the name.\n  if (scriptName.match(/\\//) && scriptName[0] !== '@') {\n    // FUTURE: Do allow this so scripts can be nested in subdirectories  (doesn't work for package.json scripts obviously)\n    console.error('Cannot run `'+scriptName+'`.  Script name should never contain any slashes.');\n    return process.exit(1);\n  }//-•\n\n  // Examine the script name and determine if it has a file extension included.\n  // If so, we'll rip it out of the script name, but keep a reference to it.\n  // Otherwise, we'll always assume that we're looking for a normal `.js` file.\n  var X_BASIC_SUPPORTED_FILE_EXTENSION = new RegExp('^([^.]+)\\\\.(' + BASIC_SUPPORTED_FILE_EXTENSIONS.join('|') + ')$');\n  var matchedFileExtension = scriptName.match(X_BASIC_SUPPORTED_FILE_EXTENSION);\n  var fileExtension;\n  if (matchedFileExtension) {\n    fileExtension = matchedFileExtension[2];\n    scriptName = scriptName.replace(X_BASIC_SUPPORTED_FILE_EXTENSION, '$1');\n  }\n  else {\n    fileExtension = 'js';\n  }\n\n\n  // First, we need to determine the appropriate script to run.\n  // (either a terminal command or a specially-formatted Node.js/Sails.js module)\n\n  // We begin by figuring out whether this is a script from the package.json\n  // or a definition in the `scripts/` folder.\n  var pjCommandToRun;\n\n  // Check the package.json file.\n  try {\n    var pathToLocalPj = path.resolve(process.cwd(), 'package.json');\n    var packageJson;\n    try {\n      packageJson = require(pathToLocalPj);\n    } catch (e) {\n      switch (e.code) {\n        case 'MODULE_NOT_FOUND': throw flaverr('E_NO_PACKAGE_JSON', new Error('No package.json file.  Are you sure you\\'re in the root directory of a Node.js/Sails.js app?'));\n        default: throw e;\n      }\n    }\n\n    if (!_.isUndefined(packageJson.scripts) && (!_.isObject(packageJson.scripts) || _.isArray(packageJson.scripts))) {\n      throw flaverr('E_MALFORMED_PACKAGE_JSON', new Error('This package.json file has an invalid `scripts` property -- should be a dictionary (plain JS object).'));\n    }\n\n    pjCommandToRun = packageJson.scripts[scriptName];\n\n  } catch (e) {\n    switch (e.code) {\n      case 'E_NO_PACKAGE_JSON':\n      case 'E_MALFORMED_PACKAGE_JSON':\n        console.error('--');\n        console.error(chalk.red(e.message));\n        return process.exit(1);\n\n      default:\n        console.error('--');\n        console.error(chalk.bold('Oops, something unexpected happened:'));\n        console.error(chalk.red(e.stack));\n        console.error('--');\n        console.error('Please read the error message above and troubleshoot accordingly.');\n        console.error('(You can report suspected bugs at '+chalk.underline('http://sailsjs.com/bugs')+'.)');\n        return process.exit(1);\n    }\n  }\n\n\n  // Now check both the `scripts/` directory and node_modules to see if a matching script exists.\n  var relativePathToAppScript = 'scripts/'+scriptName+'.'+fileExtension;\n  var relativePathToInstalledScript = (function(){\n    // Handle scripts organized under org subdirectories in node_modules.\n    var installedScriptName = scriptName;\n    var org = '';\n    if (scriptName[0] === '@') {\n      org = scriptName.split('/')[0] + '/';\n      installedScriptName = scriptName.split('/')[1];\n    }\n    installedScriptName = installedScriptName.replace(/^sails-run-/,'');\n    return 'node_modules/' + org + 'sails-run-'+installedScriptName;\n  })();\n\n  var installedScriptExists = fs.existsSync(path.resolve(relativePathToInstalledScript));\n  var appScriptExists = fs.existsSync(path.resolve(relativePathToAppScript));\n  var doesScriptFileExist = appScriptExists || installedScriptExists;\n\n  // Ensure that this script is not defined in BOTH places.\n  if (pjCommandToRun && doesScriptFileExist) {\n    console.error('Cannot run `'+scriptName+'` because it is too ambiguous.');\n    console.error('A script should only be defined once, but that script is defined in both the package.json file');\n    console.error('AND as a file in the `scripts/` directory.');\n    return process.exit(1);\n  }\n\n  // Ensure that this script exists one place or the other.\n  if (!pjCommandToRun && !doesScriptFileExist) {\n    console.error('Unknown script: `'+scriptName+'`');\n    console.error('No matching script is defined at `'+relativePathToAppScript+'`.');\n    console.error('(And there is no matching NPM script in the package.json file.)');\n    return process.exit(1);\n  }\n\n\n  // If this is a Node.js/Sails.js script (machine def), then require the script file\n  // to get the module definition, then run it using MaS.\n  if (!pjCommandToRun) {\n    try {\n      var pathToScriptDef = path.resolve(process.cwd(), appScriptExists ? relativePathToAppScript : relativePathToInstalledScript);\n      var scriptDef;\n      try {\n        scriptDef = require(pathToScriptDef);\n      } catch (e) {\n        switch (e.code) {\n          case 'MODULE_NOT_FOUND': throw flaverr('E_FAILED_TO_REQUIRE_SCRIPT_DEF', new Error('Encountered an error while loading the script definition.  Are you sure this is a well-formed Node.js/Sails.js script definition?  Error details:\\n'+e.stack));\n          default: throw e;\n        }\n      }\n\n      // Make sure the script is at least basically valid.\n      // (MaS will check it more later -- this is just preliminary -- and also to make sure that it's not `{}`,\n      // the special indicator that the script definition didn't export _ANYTHING_ at all.)\n      if (!_.isObject(scriptDef) || _.isArray(scriptDef) || _.isEqual(scriptDef, {})) {\n        console.error('');\n        console.error('');\n        console.error('Invalid script: `'+scriptName+'`');\n        console.error('');\n        console.error('A well-formed Node.js/Sails.js script should export a script definition.');\n        console.error('In other words, it should be defined more or less like this:');\n        console.error('');\n        console.error('    ```````````````````````````````````````````````````````````');\n        console.error('    module.exports = {');\n        console.error('      description: \\'Do a thing given some stuff.\\',');\n        console.error('      inputs: {');\n        console.error('        someStuff: { type: \\'string\\', required: true }');\n        console.error('      },');\n        console.error('      fn: async function (inputs, exits) {');\n        console.error('        // ...');\n        console.error('        sails.log(\\'Hello world!\\');');\n        console.error('        return exits.success();');\n        console.error('      }');\n        console.error('    };');\n        console.error('    ```````````````````````````````````````````````````````````');\n        console.error('');\n        console.error(' [?] Visit https://sailsjs.com/support for assistance.');\n        console.error('');\n        return process.exit(1);\n      }\n\n      // Modify the script definition to add `sails: require('sails')` and `habitat: 'sails'`\n      // (unless it explicitly disables this behavior with `sails: false` or by explicitly\n      // declaring some other habitat)\n      var isLifecycleMgmtExplicitlyDisabled = (\n        scriptDef.sails === false ||\n        (scriptDef.habitat !== undefined && scriptDef.habitat !== 'sails')\n      );\n      if (!isLifecycleMgmtExplicitlyDisabled) {\n        // (Only require the rest of the Sails framework if it's needed.)\n        var Sails = require('../lib/app');\n        scriptDef.habitat = 'sails';\n        scriptDef.sails = Sails();\n      }\n\n      // (Only require whelk if it's needed.)\n      var whelk = require('whelk');\n\n      // console.log('process.argv ->', require('util').inspect(process.argv,{depth:null}));\n      // console.log('arguments ->', require('util').inspect(arguments,{depth:null}));\n      // console.log('scriptName ->', scriptName);\n      // console.log('Array.prototype.slice.call(arguments, 1, -1) ->', Array.prototype.slice.call(arguments, 1, -1));\n\n      // Pass in override for runtime array of serial command-line arguments\n      // (we rely on commander having parsed them for us so that we don't include `sails`, `run`, `node`, etc)\n      scriptDef.rawSerialCommandLineArgs = Array.prototype.slice.call(arguments, 1, -1);\n\n      // Now actually run the script.\n      whelk(scriptDef);\n\n    } catch (err) {\n      console.error(err);\n      return process.exit(1);\n    }\n  }\n  // Otherwise, this is an NPM script of some kind, from the package.json file.\n  else {\n\n\n    // So execute the command like you would on the terminal.\n\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    // FUTURE: Consider pulling this boilerplate setup code into spawnChildProcess() machine\n    // as a way of leveraging a subshell to remove the need to pass in CLI args directly.\n    // Maybe as an option at least.\n    //\n    // > Also, we should also consider adding a notifier function to optionally provide\n    // > special instructions of what to do when the current (parent) process receives a SIGINT.\n    // > (Otherwise, by default, the SIGINT behavior implemented below could be used instead.)\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    //\n    // -AND/OR-\n    //\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    // FUTURE: Consider exposing an optional `onData` input to the more basic `executeCommand()`\n    // machine.  That way, you can just pass in a notifier function that handles the child's\n    // writes to its stdout and stderr streams without having to dig into all of these\n    // annoying complexities.  Also, it'd then be possible to add another input: a flag that\n    // allows you to choose whether or not to store output and pass it to the callback\n    // (e.g. `bufferOutput`).\n    //\n    // > Finally, we should also consider adding the same SIGINT notifier function mentioned above.\n    //\n    // Here's an example of how we might put it all together:\n    // ```\n    // Process.executeCommand({\n    //   command: pjCommandToRun,\n    //   bufferOutput: false,\n    //   killOnParentSigint: false,\n    //   onData: function (data, stdStreamName){\n    //     process[stdStreamName].write(data);\n    //   }\n    // }).exec(function (err) {\n    //   if (err) {\n    //     console.error('Error occurred running `'+ pjCommandToRun+ '`');\n    //     console.error('Please resolve any issues and try `sails run '+scriptName+'` again.');\n    //     console.error('Details:');\n    //     console.error(err);\n    //     return process.exit(1);\n    //   }//-•\n    //\n    //   return process.exit(0);\n    // });//< Process.executeCommand().exec() > _∏_\n    // ```\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n    // (Only require machinepack-process if it's needed.)\n    var Process = require('machinepack-process');\n\n    // Determine an appropriate name for our shell.\n    // > This is mainly just so we don't have to try and do any fancy parsing of the command,\n    // > allowing for more platform-specific customization.  (Mirroring the NPM CLI here)\n    var shProcName;\n    var shFlag;\n    if (process.platform === 'win32') {\n      shProcName = process.env.comspec || 'cmd';\n      shFlag = '/d /s /c';\n    }\n    else {\n      shProcName = 'sh';\n      shFlag = '-c';\n    }\n\n    var childProcess = Process.spawnChildProcess({\n      command: shProcName,\n      cliArgs: [ shFlag, pjCommandToRun ]\n    }).now();\n\n    // Pipe output from the child process to the current (parent) process.\n    childProcess.stdout.pipe(process.stdout);\n    childProcess.stderr.pipe(process.stderr);\n\n    // Set up CTRL+C listener on the parent process that will force-kill this child process.\n    // (Note that we define the event listener as a named function so we can unbind it below.)\n    var onSigTerm = function (){\n      Process.killChildProcess({ childProcess: childProcess, force: true }).exec(function (_forceKillErr){\n        if (_forceKillErr) {\n          console.error('There was a problem terminating this script:\\n'+_forceKillErr.stack+'\\nHere are some details which might be helpful:\\n' + _forceKillErr.stack);\n        }\n      });\n    };\n    process.once('SIGTERM', onSigTerm);\n\n\n    var spinlocked;\n    (function (proceed){\n\n      childProcess.on('error', function (err) { return proceed(err); });\n      childProcess.stderr.on('error', function (err) { return proceed(err); });\n      childProcess.stdout.on('error', function (err) { return proceed(err); });\n      childProcess.on('close', function (code, signal) {\n        // log.silly('lifecycle', logid(pkg, stage), 'Returned: code:', code, ' signal:', signal)\n        // If a signal was received, terminate the current parent process (i.e. `sails run`).\n        if (signal) {\n          // Note that, in this case, `proceed()` is never called.\n          // (But it doesn't actually matter, because we'll have killed the process.)\n          return process.kill(process.pid, signal);\n        }\n\n        // Otherwise if we got a non-zero exit code, then consider this an error.\n        if (code !== 0) {\n          return proceed(new Error('Exit status '+code));\n        }\n\n        // Otherwise, consider it a success.\n        return proceed();\n      });\n\n    })(function(err){\n      if (err) {\n\n        if (spinlocked) {\n          console.error(err);\n          return;\n        }\n        spinlocked = true;\n\n        console.error('- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ');\n        console.error('Error occurred running `'+ pjCommandToRun+ '`');\n        console.error('Please resolve any issues and try `sails run '+scriptName+'` again.');\n        console.error('Details:');\n        console.error(err);\n\n        process.removeListener('SIGTERM', onSigTerm);\n        return process.exit(1);\n      }//-•\n\n      return process.exit(0);\n\n    });//_∏_  (†)\n\n  }//</ else >\n\n};\n\n"
  },
  {
    "path": "bin/sails-upgrade.js",
    "content": "/**\n * Module dependencies\n */\n\nvar path = require('path');\nvar chalk = require('chalk');\nvar _ = require('@sailshq/lodash');\nvar sailsgen = require('sails-generate');\nvar flaverr = require('flaverr');\nvar semver = require('semver');\nvar rconf = require('../lib/app/configuration/rc')();\n\n\n/**\n * `sails upgrade`\n *\n * Upgrade a pre v1.0.x app to Sails v1.0.x.\n *\n * ```\n * # In the root directory of your Sails app:\n * sails upgrade\n * ```\n */\n\nmodule.exports = function () {\n\n  if (!rconf.reportOnly) {\n\n    console.log(chalk.gray('Checking compatibility for Sails v1.0 upgrade...'));\n\n    try {\n      var packageJson;\n      try {\n        var pathToLocalPackageJson = path.resolve(process.cwd(), 'package.json');\n        packageJson = require(pathToLocalPackageJson);\n      } catch (e) {\n        switch (e.code) {\n          case 'MODULE_NOT_FOUND': throw flaverr('E_NO_PACKAGE_JSON', new Error('No package.json file.  Are you sure you\\'re in the root directory of a Sails app?'));\n          default: throw e;\n        }\n      }\n\n      if (_.isUndefined(packageJson.dependencies)) {\n        throw flaverr('E_NO_SAILS_DEP', new Error('This package.json file does not declare any dependencies.  Are you sure you\\'re in the root directory of a Sails app?'));\n      }\n\n      if (!_.isObject(packageJson.dependencies) || _.isArray(packageJson.dependencies)) {\n        throw flaverr('E_NO_SAILS_DEP', new Error('This package.json file has an invalid `dependencies` property -- should be a dictionary (plain JS object).'));\n      }\n\n      var sailsDepSVR = packageJson.dependencies.sails;\n      if (!sailsDepSVR) {\n        throw flaverr('E_NO_SAILS_DEP', new Error('This package.json file does not declare `sails` as a dependency.  Are you sure you\\'re in the root directory of a Sails app?'));\n      }\n\n      if (!semver.ltr('0.9.9999', sailsDepSVR)) {\n        throw flaverr('E_SAILS_DEP_DEFINITELY_TOO_OLD', new Error('this app depends on sails@'+sailsDepSVR+'.'));\n      }\n\n      if (!semver.ltr('0.11.9999', sailsDepSVR)) {\n        throw flaverr('E_SAILS_DEP_MIGHT_BE_TOO_OLD', new Error('this app depends on sails@'+sailsDepSVR+'.'));\n      }\n\n      // if (semver.ltr('0.12.9999', sailsDepSVR)) {\n      //   throw flaverr('E_SAILS_DEP_IS_ALREADY_V1', new Error('this app already depends on sails@'+sailsDepSVR+'...'));\n      // }\n\n      console.log();\n      console.log('----------------------------------------------------');\n      console.log('This utility will kickstart the process of migrating');\n      console.log('this Sails v0.12.x app to Sails v1.');\n      console.log('----------------------------------------------------');\n      console.log();\n\n    } catch (e) {\n      switch (e.code) {\n        case 'E_SAILS_DEP_IS_ALREADY_V1':\n          console.log();\n          console.log('----------------------------------------------------');\n          console.log('This utility is designed to kickstart the process of');\n          console.log('migrating a '+chalk.bold('v0.12.x')+' app to Sails v1.');\n          console.log();\n          console.log(chalk.yellow.bold('But '+e.message));\n          console.log(chalk.reset('Maybe you already started upgrading it?'));\n          console.log(chalk.reset('If so, then please press CTRL+C to cancel now, or'));\n          console.log(chalk.reset('otherwise feel free to proceed with care-- this'));\n          console.log(chalk.reset('upgrade tool may still work partially as-is.'));\n          console.log(chalk.gray('For more help, visit '+chalk.underline('http://sailsjs.com/support')+'.'));\n          console.log('----------------------------------------------------');\n          console.log();\n          break;\n\n        case 'E_SAILS_DEP_MIGHT_BE_TOO_OLD':\n          console.log();\n          console.log('----------------------------------------------------');\n          console.log('This utility is designed to kickstart the process of');\n          console.log('migrating a '+chalk.bold('v0.12.x')+' app to Sails v1.');\n          console.log();\n          console.log(chalk.yellow.bold('But '+e.message));\n          console.log(chalk.reset('This upgrade tool may partially work as-is, but we recommend'));\n          console.log(chalk.reset('using the appropriate guide(s) to upgrade to Sails v0.12 first.'));\n          console.log(chalk.reset('See '+chalk.underline('http://sailsjs.com/upgrading')+' for details.'));\n          console.log(chalk.gray('(Press CTRL+C to cancel -- or proceed at your own risk!)'));\n          console.log('----------------------------------------------------');\n          console.log();\n          break;\n\n        case 'E_SAILS_DEP_DEFINITELY_TOO_OLD':\n          console.log('--');\n          console.log(chalk.red.bold('Well, '+e.message));\n          console.log(chalk.reset('It looks to be built for a version of Sails that is probably too'));\n          console.log(chalk.reset('old to work with this upgrade tool as-is.  We recommend using'));\n          console.log(chalk.reset('the appropriate guide(s) to upgrade to Sails v0.12 first.'));\n          console.log(chalk.gray('For more assistance, visit '+chalk.underline('http://sailsjs.com/support')+' or, if'));\n          console.log(chalk.gray('you\\'re using Sails Flagship, '+chalk.underline('https://flagship.sailsjs.com')+'.'));\n          return process.exit(1);\n\n        case 'E_NO_PACKAGE_JSON':\n        case 'E_NO_SAILS_DEP':\n          console.log('--');\n          console.log(chalk.red(e.message));\n          return process.exit(1);\n\n        default:\n          console.log('--');\n          console.log(chalk.bold('Oops, something unexpected happened:'));\n          console.log(chalk.red(e.stack));\n          console.log('--');\n          console.log('Please read the error message above and troubleshoot accordingly.');\n          console.log('(You can report suspected bugs at '+chalk.underline('http://sailsjs.com/bugs')+'.)');\n          return process.exit(1);\n      }\n    }\n\n  }\n\n\n  // Attempt to require the upgrade tool.\n  var generator;\n  try {\n    var requirePath = path.resolve(process.cwd(), 'node_modules/@sailshq/upgrade');\n    generator = require(requirePath);\n  } catch (e) {\n\n    if (e.code === 'MODULE_NOT_FOUND') {\n      console.log(chalk.blue.bold('Could not find the `@sailshq/upgrade` package in your local app folder.'));\n      console.log('Please run `npm install @sailshq/upgrade` and try again.');\n      console.log(chalk.gray('(Or just use the Sails v1.0.x upgrade guide on sailsjs.com.)'));\n      console.log();\n      return process.exit(1);\n    }//-•\n\n    // Some other unexpected error from within this package:\n    console.log(chalk.bold('Oops, something unexpected happened:'));\n    console.log(chalk.red(e.stack));\n    console.log('--');\n    console.log('Please report this bug at '+chalk.underline('https://flagship.sailsjs.com')+'.');\n    process.exit(1);\n\n  }//</catch>\n\n\n  // Build initial scope\n  var scope = {\n    rootPath: process.cwd(),\n    sailsRoot: path.resolve(__dirname, '..'),\n    generatorType: 'upgrade',\n    modules: { upgrade: generator },\n  };\n\n  // Mix-in rconf\n  _.merge(scope, rconf.generators);\n\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  // FUTURE: Verify that we can just do a top-level merge here,\n  // and then reference `scope.generators.modules` as needed\n  // (would be simpler- but would be a breaking change, though\n  // unlikely to affect most people.  The same issue exists in\n  // other places where we read rconf and then call out to\n  // sails-generate)\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  _.merge(scope, rconf);\n\n  // Pass the original CLI arguments down to the generator\n  // (but first, remove commander's extra argument)\n  var cliArguments = Array.prototype.slice.call(arguments);\n  cliArguments.pop();\n  scope.args = cliArguments;\n\n  return sailsgen(scope, {\n    // Handle unexpected errors.\n    error: function (err) {\n      console.log(chalk.bold('Oops, something unexpected happened:'));\n      console.log(chalk.red(err.stack));\n      console.log('--');\n      console.log('Please report this bug at '+chalk.underline('https://flagship.sailsjs.com')+'.');\n      return process.exit(1);\n\n    },//</on error :: sailsGen()>\n\n    // Attend to invalid usage.\n    invalid: function (err) {\n\n      // If this is an Error, don't bother logging the other details, just log the `.message`.\n      // (This is purely for readability.)\n      if (_.isError(err)) {\n        console.log(err.message);\n      }\n      else {\n        console.log(err);\n      }\n\n      console.log('--');\n      console.log('For assistance, visit '+chalk.underline('https://flagship.sailsjs.com')+'.');\n      return process.exit(1);\n\n    },//</on invalid :: sailsGen()>\n    success: function() {\n      // Good to go.\n    }\n  });\n};\n"
  },
  {
    "path": "bin/sails-www.js",
    "content": "/**\n * Module dependencies\n */\n\nvar path = require('path');\nvar _ = require('@sailshq/lodash');\nvar CaptainsLog = require('captains-log');\nvar Process = require('machinepack-process');\nvar chalk = require('chalk');\nvar flaverr = require('flaverr');\nvar rconf = require('../lib/app/configuration/rc')();\n\n\n/**\n * `sails www`\n *\n * Run the `build` or `buildProd` Grunt task (depending on whether this is the production environment)\n * for the Sails app in the current working directory.\n *\n * @see http://sailsjs.com/documentation/reference/command-line-interface/sails-www\n */\n\nmodule.exports = function() {\n\n  // Check compatibility\n  try {\n    var pathToLocalPackageJson = path.resolve(process.cwd(), 'package.json');\n    var packageJson;\n    try {\n      packageJson = require(pathToLocalPackageJson);\n    } catch (e) {\n      switch (e.code) {\n        case 'MODULE_NOT_FOUND': throw flaverr('E_NO_PACKAGE_JSON', new Error('No package.json file.  Are you sure you\\'re in the root directory of a Sails app?'));\n        default: throw e;\n      }\n    }\n\n    if (_.isUndefined(packageJson.dependencies)) {\n      throw flaverr('E_NO_SAILS_DEP', new Error('This package.json file does not declare any dependencies.  Are you sure you\\'re in the root directory of a Sails app?'));\n    }\n\n    if (!_.isObject(packageJson.dependencies) || _.isArray(packageJson.dependencies)) {\n      throw flaverr('E_NO_SAILS_DEP', new Error('This package.json file has an invalid `dependencies` property -- should be a dictionary (plain JS object).'));\n    }\n\n    var sailsDepSVR = packageJson.dependencies.sails;\n    if (!sailsDepSVR) {\n      throw flaverr('E_NO_SAILS_DEP', new Error('This package.json file does not declare `sails` as a dependency.\\nAre you sure you\\'re in the root directory of a Sails app?'));\n    }\n\n    var shGruntDepSVR = packageJson.dependencies['sails-hook-grunt'] || packageJson.devDependencies['sails-hook-grunt'];\n    if (!shGruntDepSVR) {\n      throw flaverr('E_NO_SH_GRUNT_DEP', new Error('This app\\'s package.json file does not declare `sails-hook-grunt` in \"dependencies\" or \"devDependencies\".\\nAre you sure this is a Sails v1.0 app that is using Grunt?'));\n    }\n\n  } catch (e) {\n    switch (e.code) {\n      case 'E_NO_PACKAGE_JSON':\n      case 'E_NO_SAILS_DEP':\n        console.log('--');\n        console.log(chalk.red(e.message));\n        return process.exit(1);\n      case 'E_NO_SH_GRUNT_DEP':\n        console.log('--');\n        console.log(chalk.red(e.message));\n        console.log(chalk.gray('(Maybe try running `npm install sails-hook-grunt --save`?)'));\n        return process.exit(1);\n\n      default:\n        console.log('--');\n        console.log(chalk.bold('Oops, something unexpected happened:'));\n        console.log(chalk.red(e.stack));\n        console.log('--');\n        console.log('Please read the error message above and troubleshoot accordingly.');\n        console.log('(You can report suspected bugs at '+chalk.underline('http://sailsjs.com/bugs')+'.)');\n        return process.exit(1);\n    }\n  }\n\n\n\n  var log = CaptainsLog(rconf.log);\n\n  // The destination path.\n  var wwwPath = path.resolve(process.cwd(), 'www');\n\n  // Determine the appropriate Grunt task to run based on `process.env.NODE_ENV`, `rconf.prod`, and `rconf.environment`.\n  var overrideGruntTask;\n  if (rconf.prod || rconf.environment === 'production' || process.env.NODE_ENV === 'production') {\n    overrideGruntTask = 'buildProd';\n  }\n  else {\n    overrideGruntTask = 'build';\n  }\n  log.info('Compiling assets into standalone directory with `grunt ' + overrideGruntTask + '`...');\n\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  // Execute a command like you would on the terminal.\n  Process.executeCommand({\n    command: path.join('node_modules', '.bin', 'grunt')+' '+overrideGruntTask,\n  }).exec(function (err) {\n    if (err) {\n      log.error('Error occured running `grunt ' + overrideGruntTask + '`');\n      log.error('Please resolve any issues and try running `sails www` again.');\n      log.error('Hint: you must have the Grunt CLI installed!  Try `npm install grunt -g`.');\n      log.error();\n      log.error('Error details:');\n      log.error(err);\n      return process.exit(1);\n    }\n\n    log.info();\n    log.info('Created directory of compiled assets at:');\n    log.info(wwwPath);\n    return process.exit(0);\n\n  });//</ Process.executeCommand() >\n\n};\n\n"
  },
  {
    "path": "bin/sails.js",
    "content": "#!/usr/bin/env node\n\n\n/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar program = require('./private/patched-commander');\nvar sailsPackageJson = require('../package.json');\nvar NOOP = function() {};\n\n\n\nprogram\n  .version(sailsPackageJson.version, '-v, --version');\n\n\n//\n// Normalize version argument, i.e.\n//\n// $ sails -v\n// $ sails -V\n// $ sails --version\n// $ sails version\n//\n\n\n// make `-v` option case-insensitive\nprocess.argv = _.map(process.argv, function(arg) {\n  return (arg === '-V') ? '-v' : arg;\n});\n\n\n// $ sails version (--version synonym)\nprogram\n  .command('version')\n  .description('')\n  .action(program.versionInformation);\n\n\n\nprogram\n  .unknownOption = NOOP;\nprogram.usage('[command]');\n\n\n// $ sails lift\nvar cmd;\ncmd = program.command('lift');\ncmd.option('--prod', 'Lift in \"production\" environment.');\ncmd.option('--staging', 'Lift in \"staging\" environment.');\ncmd.option('--port [port]', 'Listen on the specified port (defaults to 1337).');\ncmd.option('--silent', 'Set log level to \"silent\".');\ncmd.option('--verbose', 'Set log level to \"verbose\".');\ncmd.option('--silly', 'Set log level to \"silly\".');\ncmd.unknownOption = NOOP;\ncmd.description('');\ncmd.alias('l');\ncmd.action(require('./sails-lift'));\n\n\n// $ sails new <appname>\ncmd = program.command('new [path_to_new_app]');\n// cmd.option('--dry');\ncmd.option('--no-frontend', 'Don\\'t generate \"assets\", \"views\" or \"task\" folders.');\ncmd.option('--fast', 'Don\\'t install node modules after generating app.');\ncmd.usage('[path_to_new_app]');\ncmd.unknownOption = NOOP;\ncmd.description('');\ncmd.action(require('./sails-new'));\n\n\n// $ sails generate <module>\ncmd = program.command('generate');\n// cmd.option('--dry');\ncmd.unknownOption = NOOP;\ncmd.description('');\ncmd.usage('[something]');\ncmd.action(require('./sails-generate'));\n\n// $ sails upgrade\ncmd = program.command('upgrade');\ncmd.unknownOption = NOOP;\ncmd.description('');\ncmd.action(require('./sails-upgrade'));\n\n// $ sails migrate\ncmd = program.command('migrate');\ncmd.unknownOption = NOOP;\ncmd.description('');\ncmd.action(require('./sails-migrate'));\n\n\n// $ sails console\ncmd = program.command('console');\ncmd.option('--silent', 'Set log level to \"silent\".');\ncmd.option('--verbose', 'Set log level to \"verbose\".');\ncmd.option('--silly', 'Set log level to \"silly\".');\ncmd.option('--dontLift', 'Start console session without lifting an HTTP server.');\ncmd.unknownOption = NOOP;\ncmd.description('');\ncmd.alias('c');\ncmd.action(require('./sails-console'));\n\n\n// $ sails www\n// Compile `assets` directory into a standalone `www` folder.\ncmd = program.command('www');\ncmd.unknownOption = NOOP;\ncmd.description('');\ncmd.action(require('./sails-www'));\n\n\n\n// $ sails debug\ncmd = program.command('debug');\ncmd.unknownOption = NOOP;\ncmd.description('(for Node v5 and below)');\ncmd.action(require('./sails-debug'));\n\n// $ sails inspect\ncmd = program.command('inspect');\ncmd.unknownOption = NOOP;\ncmd.description('(for Node v6 and above)');\ncmd.action(require('./sails-inspect'));\n\n// $ sails run\ncmd = program.command('run');\ncmd.usage('[name-of-script]');\ncmd.unknownOption = NOOP;\ncmd.description('');\ncmd.action(require('./sails-run'));\n\n\n// $ sails test\ncmd = program.command('test');\ncmd.unknownOption = NOOP;\ncmd.description('');\ncmd.action(function(){\n  require('./sails-run')('test', _.last(arguments));\n});\n\n// $ sails lint\ncmd = program.command('lint');\ncmd.unknownOption = NOOP;\ncmd.description('');\ncmd.action(function(){\n  require('./sails-run')('lint', _.last(arguments));\n});\n\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - -\n// $ sails deploy\ncmd = program.command('deploy');\n// cmd.option('--dry');\ncmd.unknownOption = NOOP;\ncmd.description('');\ncmd.usage('');\ncmd.action(require('./sails-deploy'));\n// FUTURE: ^^ Consider simplifying this into a script.\n// - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n// $ sails debug-console\ncmd = program.command('debug-console');\ncmd.unknownOption = NOOP;\ncmd.description('');\ncmd.alias('dc');\ncmd.action(require('./sails-debug-console'));\n\n\n//\n// Normalize help argument, i.e.\n//\n// $ sails --help\n// $ sails help\n// $ sails\n// $ sails <unrecognized_cmd>\n//\n\n\n// $ sails help (--help synonym)\ncmd = program.command('help [command]');\ncmd.description('');\ncmd.action(function(){\n  if (program.args.length > 1 && _.isString(program.args[0])) {\n    var helpCmd = _.find(program.commands, {_name: program.args[0]});\n    if (helpCmd) {\n      helpCmd.help();\n      return;\n    }\n  }\n  program.help();\n});\n\n\n\n// $ sails <unrecognized_cmd>\n// Output Sails help when an unrecognized command is used.\nprogram\n  .command('*')\n  .action(function(cmd){\n    console.log('\\n  ** Unrecognized command:', cmd, '**');\n    program.help();\n  });\n\n\n\n// Don't balk at unknown options\nprogram.unknownOption = NOOP;\n\n\n\n// $ sails\n//\nprogram.parse(process.argv);\nvar NO_COMMAND_SPECIFIED = program.args.length === 0;\nif (NO_COMMAND_SPECIFIED) {\n  program.help();\n}\n"
  },
  {
    "path": "docs/PAGE_NEEDED.md",
    "content": "# Page Needed\n\nIf you&rsquo;re seeing this page, it means you've clicked on a link to a Sails doc that has yet to be written.  Help make Sails better by contributing to the docs!\n\nPlease send a pull request to master with corrections/additions and they'll be double-checked and merged as soon as possible.\n\nSecondly, we are open to suggestions about the process we're using to manage our documentation, and to work with the community in general. Please post to the Google Group with your ideas, or if you're interested in helping directly, contact @fancydoilies, @aaaaanxiety, or @mikermcneil on Twitter.\n\nLove,\n\nThe Sails Team\n"
  },
  {
    "path": "docs/README.md",
    "content": "![Squiddy reads the docs](https://sailsjs.com/images/squidford_swimming.png)\n\n# Sails.js Documentation\n\nThe official documentation for the current stable release of Sails is on the master branch of this repository.  Content for most sections on the [official Sails website](https://sailsjs.com) is compiled from here.\n\n\n## In other languages\n\nThe documentation for Sails has been translated to a number of different languages.  The list below is a reference of the translation projects we are aware of.\n\n| Language                     | [IETF Language Tag](https://en.wikipedia.org/wiki/IETF_language_tag)  | Version |  Maintainer(s)        | Repo                               |\n| ---------------------------- | ------- | ------- | ------------------ | ---------------------------------- |\n| Brazilian Portuguese         | `pt-BR` | **v1.0.x**  | [@Avlye](https://github.com/Avlye) | [sails-docs-pt-BR](https://github.com/Avlye/sails-docs-pt-BR)\n| Chinese                      | `zh-cn`    | v0.12.x | [@linxiaowu66](https://github.com/linxiaowu66)   | [sails-docs-zh-cn](https://github.com/linxiaowu66/sails-docs-zh-cn)\n| French                       | `fr`    | v0.12.x | [@marrouchi](https://github.com/marrouchi)   | [sails-docs-fr](https://github.com/marrouchi/sails-docs-fr)\n| Spanish                      | `es`    | v0.12.x | [@eduartua](https://github.com/eduartua/) & [@alejandronanez](https://github.com/alejandronanez)   | [sails-docs-es](https://github.com/eduartua/sails-docs-es)\n| Japanese                     | `ja`    | v0.11.x | [@kory-yhg](https://github.com/kory-yhg)      | [sails-docs-ja](https://github.com/balderdashy/sails-docs/tree/ja)\n| Brazilian Portuguese         |         | v0.10.x | [@marceloboeira](https://github.com/marceloboeira)   | [sails-docs-pt-BR](https://github.com/balderdashy/sails-docs/tree/pt-BR)\n| Korean                       | `ko`    | v0.10.x | [@sapsaldog](https://github.com/sapsaldog)   | [sails-docs-ko](https://github.com/balderdashy/sails-docs/tree/ko)\n| Taiwanese Mandarin           | `zh-TW` | v0.10.x | [@CalvertYang](https://github.com/CalvertYang)   | [sails-docs-zh-TW](https://github.com/balderdashy/sails-docs/tree/zh-TW)\n\n> Since we are now using branches to keep track of different versions of the Sails documentation, we are moving away from the original approach of using branches for different languages.  Before embarking on a new translation project, we ask that you review the [updated information below](#how-can-i-help-translate-the-documentation)-- the process has changed a little bit.\n\n## Contributing to the Sails docs\n\nWe welcome your help!  Please send a pull request with corrections/additions and they'll be double-checked and merged as soon as possible.\n\n\n#### How are these docs compiled and pushed to the website?\n\nWe use a module called `doc-templater` to convert the .md files to the html for the website. You can learn more about how it works in [the doc-templater repo](https://github.com/uncletammy/doc-templater).\n\nEach .md file has its own page on the website (i.e. all reference, concepts, and anatomy files), and should include a special `<docmeta name=\"displayName\">` tag with a `value` property specifying the title for the page.  This will impact how the doc page appears in search engine results, and it will also be used as its display name in the navigation menu on sailsjs.com.  For example:\n\n```markdown\n<docmeta name=\"displayName\" value=\"Building Custom Homemade Puddings\">\n```\n\n#### When will my change appear on the Sails website?\n\nOnce your change to the documentation is merged, you can see how it will appear on sailsjs.com by visiting [next.sailsjs.com](https://next.sailsjs.com). The preview site updates itself automatically as changes are merged.\n\n\n#### How can I help translate the documentation?\n\nA great way to help the Sails project, especially if you speak a language other than English natively, is to volunteer to translate the Sails documentation.  If you are interested in collaborating with any of the translation projects listed in the table above, contact the maintainer of the translation project using the instructions in the README of that fork.\n\nIf your language is not represented in the table above, and you are interested in beginning a translation project, follow these steps:\n\n+ Bring the documentation folder (`balderdashy/sails/docs`) into a new repo named `sails-docs-{{IETF}}` where {{IETF}} is the [IETF language tag](https://en.wikipedia.org/wiki/IETF_language_tag) for your language.\n+ Edit the README to summarize your progress so far, provide any other information you think would be helpful for others reading your translation, and let interested contributors know how to contact you.\n+ Send a pull request editing the table above to add a link to your fork.\n+ When you are satisfied with the first complete version of your translation, open an issue and someone from our docs team will be happy to help you get preview it in the context of the Sails website, get it live on a domain (yours, or a subdomain of sailsjs.com, whichever makes the most sense), and share it with the rest of the Sails community.\n\n\n#### How else can I help?\n\nFor more information on contributing to Sails in general, see the [Contribution Guide](sailsjs.com/contributing).\n\n\n## License\n\n[MIT](https://sailsjs.com/license)\n\nThe [Sails framework](https://sailsjs.com) is free and open-source under the [MIT License](https://sailsjs.com/license).\n\n"
  },
  {
    "path": "docs/anatomy/.editorconfig.md",
    "content": "# .editorconfig\n\nThis file exists to help maintain consistent formatting throughout the files in your Sails app.\n\nFor more information, see [editorconfig.org](http://editorconfig.org/).\n\n\n<docmeta name=\"displayName\" value=\".editorconfig\">\n"
  },
  {
    "path": "docs/anatomy/.eslintignore.md",
    "content": "# .eslintignore\n\nThis file exists to signify to [ESLint](https://eslint.org/) that certain files and/or directories should be ignored for the purposes of linting.\n\n<docmeta name=\"displayName\" value=\".eslintignore\">\n"
  },
  {
    "path": "docs/anatomy/.eslintrc.md",
    "content": "# .eslintrc\n\nThis file defines a set of basic code conventions designed to encourage quality and consistency across your Sails app's code base.\n\nFor more information, see [eslint.org](https://eslint.org/).\n\n<docmeta name=\"displayName\" value=\".eslintrc\">\n"
  },
  {
    "path": "docs/anatomy/.htmlhintrc.md",
    "content": "# .htmlhintrc\n\nThis file defines the rules for your app's [HTMLHint](http://htmlhint.com/), to encourage quality and consistency in your views and templates.\n\n<docmeta name=\"displayName\" value=\".htmlhintrc\">\n"
  },
  {
    "path": "docs/anatomy/Gruntfile.js.md",
    "content": "# Gruntfile.js\n\n\nSails uses [Grunt](http://gruntjs.com) for asset management. This file contains the entry point for the default asset pipeline in Sails; that is, the code that does stuff like compiling LESS stylesheets, minifying scripts for production, and precompiling and injecting client-side templates.\n\nSails' integration with Grunt is fully customizable, but for most use cases, this file (`Gruntfile.js`) should remain unchanged.  Instead, you can install Grunt plugins or add your own custom logic as new files in the [`tasks/`](./tasks) folder.\n\n> + To learn more about working with static assets in Sails, check out the [conceptual documentation on assets](https://sailsjs.com/documentation/concepts/assets).\n> + For a broader introduction to Grunt tasks in general, see [Grunt's docs on configuring tasks](http://gruntjs.com/configuring-tasks).\n\n\n<docmeta name=\"displayName\" value=\"Gruntfile.js\">\n"
  },
  {
    "path": "docs/anatomy/README.md",
    "content": "# docs/anatomy\n\nThis section contains the \"Anatomy\" documentation which is eventually available at https://sailsjs.com/documentation/anatomy.\n\n### Notes\n> - This README file **is not compiled to HTML** for the website.  It is just here to explain what you're looking at.\n> - Depending on what branch of `sails` you are currently viewing, the domain may vary. See the top-level documentation README file for information about working with the markdown files in this repo, and to understand the branching/versioning strategy.\n\n<docmeta name=\"notShownOnWebsite\" value=\"true\">\n"
  },
  {
    "path": "docs/anatomy/README.md.md",
    "content": "# README.md\n\nThis is a generic README that you can edit to describe your app.\n\n\n<docmeta name=\"displayName\" value=\"README.md\">\n"
  },
  {
    "path": "docs/anatomy/anatomy.md",
    "content": "# Anatomy of a Sails app\n\nAn interactive guide to the structure of the Sails app generated by default with `sails new`.\n\nChoose from any of the files or folders in the list to learn more about its purpose.\n\n<docmeta name=\"displayName\" value=\"Anatomy of a Sails app\">\n<docmeta name=\"isOverviewPage\" value=\"true\">\n"
  },
  {
    "path": "docs/anatomy/api/api.md",
    "content": "# api/\n\nThis folder contains the vast majority of your app's back-end logic.  It is home to the 'M' and 'C' in <a href=\"http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller\" target=\"_blank\">MVC Framework</a>.\n\nIn it you will find the following:\n\n- Controllers: [Actions](https://sailsjs.com/documentation/concepts/actions-and-controllers) contain back-end logic that handle incoming requests (like handling a form submission or responding with personalized, server-rendered HTML).\n- Helpers: [Helpers](https://sailsjs.com/documentation/concepts/helpers) are shared functions that can be called from anywhere in your app.\n- Models: [Models](https://sailsjs.com/documentation/concepts/models-and-orm) are the structures that contain data for your Sails App.\n- Policies: [Policies](https://sailsjs.com/documentation/concepts/policies) are middleware that restrict access to certain actions in your app.\n\nYou may also find these folders, which are not always generated by default in new Sails apps:\n\n- Hooks: [Hooks](https://sailsjs.com/documentation/concepts/extending-sails/hooks) are modules that add functionality to Sails core.  You can use hooks to run custom code when your app lifts and before handling every incoming request.  Hooks can also be installed as plugins, but the hooks in this folder are always custom for your application.\n- Responses: [Custom responses](https://sailsjs.com/documentation/concepts/extending-sails/custom-responses) can help maintain consistent HTTP status codes and behavior across your app.  (Since not every Sails application needs to define its own custom responses, this folder is sometimes excluded.)\n- Services: [Services](https://sailsjs.com/documentation/concepts/services) are shared utilities common in Sails apps written before version 1.0.  They can be _just about anything_, so for new apps, it's recommended that you use [helpers](https://sailsjs.com/documentation/concepts/helpers) instead.\n\n\n<docmeta name=\"displayName\" value=\"api\">\n\n"
  },
  {
    "path": "docs/anatomy/api/controllers/controllers.md",
    "content": "# api/controllers/\n\nThis is the directory that holds your controllers.  In Sails, controllers are JavaScript files that contain logic for interacting with models and rendering appropriate views to the client.\n\nWhen you call `sails generate api cats` via the command line from inside your project's root directory, Sails will generate the file `api/controllers/CatsController.js` along with a matching model.\n\nThe `api/controllers` directory can also contain _standalone actions_, which are JavaScript files containing a _single_ controller action, rather than a dictionary of actions.\n\nSee the [main actions and controllers documentation](https://sailsjs.com/documentation/concepts/actions-and-controllers) for more info.\n\n<docmeta name=\"displayName\" value=\"controllers\">\n\n\n"
  },
  {
    "path": "docs/anatomy/api/controllers/gitkeep.md",
    "content": "# api/controllers/.gitkeep\n\nIgnore this file.  It only exists because `git` refuses to push empty directories to a remote server.  `.gitkeep` is an unofficial convention that has emerged as a workaround for people who don't discriminate against empty directories.\n\n\n<docmeta name=\"displayName\" value=\".gitkeep\">\n"
  },
  {
    "path": "docs/anatomy/api/helpers/.gitkeep.md",
    "content": "# api/helpers/.gitkeep\n\nIgnore this file.  It only exists because `git` refuses to push empty directories to a remote server.  `.gitkeep` is an unofficial convention that has emerged as a workaround for people who don't discriminate against empty directories.\n\n\n<docmeta name=\"displayName\" value=\".gitkeep\">\n"
  },
  {
    "path": "docs/anatomy/api/helpers/helpers.md",
    "content": "# api/helpers/\n\nThis is the directory that holds your helpers.  In Sails, helpers are shared functions that can be called from anywhere in your app.\n\nWhen you call `sails generate helper tickle-user` via the command line from inside your project's root directory, Sails will generate the file `api/helpers/tickle-user.js`, with a skeleton helper file to get you started.\n\nSee the [main helpers documentation](https://sailsjs.com/documentation/concepts/helpers) for more info.\n\n\n<docmeta name=\"displayName\" value=\"helpers\">\n"
  },
  {
    "path": "docs/anatomy/api/models/.gitkeep.md",
    "content": "# api/models/.gitkeep\n\nIgnore this file.  It only exists because `git` refuses to push empty directories to a remote server.  `.gitkeep` is an unofficial convention that has emerged as a workaround for people who don't discriminate against empty directories.\n\n\n\n<docmeta name=\"displayName\" value=\".gitkeep\">\n"
  },
  {
    "path": "docs/anatomy/api/models/models.md",
    "content": "# api/models/\n\nThis is the directory that holds your models.  In Sails, models are the structures that contain data for your Sails App.\n\nYou can learn more about how to define and use models in [Concepts > Models and ORM > Models](https://sailsjs.com/documentation/concepts/models-and-orm/models), and about how to generate them [here](https://sailsjs.com/documentation/reference/command-line-interface/sails-generate#?core-generators).\n\n<docmeta name=\"displayName\" value=\"models\">\n\n"
  },
  {
    "path": "docs/anatomy/api/policies/.gitkeep.md",
    "content": "# api/policies/.gitkeep\n\nIgnore this file.  It only exists because `git` refuses to push empty directories to a remote server.  `.gitkeep` is an unofficial convention that has emerged as a workaround for people who don't discriminate against empty directories.\n\n\n\n<docmeta name=\"displayName\" value=\".gitkeep\">\n"
  },
  {
    "path": "docs/anatomy/api/policies/policies.md",
    "content": "# api/policies/\n\nThis is the folder you will store your &ldquo;policy&rdquo; files in.  A policy file is a JavaScript file that contains what is essentially Express middleware for authenticating access to controller actions in your app.\n\nFor example, if you want to make sure only authenticated admin users can access `http://yourapp.com/admin/dashboard`, this is the folder you would put that logic in.\n\nFor more information about policies and how to use them in your app, see [Concepts > Policies](https://sailsjs.com/documentation/concepts/policies).\n\n<docmeta name=\"displayName\" value=\"policies\">\n\n"
  },
  {
    "path": "docs/anatomy/app.js.md",
    "content": "# app.js\n\nThis file is the conventional entry point for a _production_ Sails/Node.js app.\n\nWhen developing on your local computer, and you run `sails lift`, the code in `app.js` is not executed.  Instead, this file exists to provide an easy, out-of-the-box way to run your app _without_ typing `sails lift`.  This is most likely how you'll start your app in production (i.e. `node app`, or `npm start`).\n\nFor example, when you deploy to most PaaS vendors like [Heroku](http://heroku.com), they will automatically detect that you're running a Sails/Node.js app and execute this file with the `NODE_ENV` environment variable set to production.\n\n> Whatever stage of the development lifecycle you're at, you can safely ignore `app.js`.  It's good to go out of the box for most apps.  But the code in `app.js` also serves as an easy-to-reference example of how to use Sails programmatically.  So you might want to take a look at it if you plan on writing automated tests, scheduled jobs, manual database migrations, or administration scripts.\n\n\n<docmeta name=\"displayName\" value=\"app.js\">\n"
  },
  {
    "path": "docs/anatomy/assets/.eslintrc.md",
    "content": "# assets/.eslintrc\n\nThis file is for [ESLint](https://eslint.org/) configuration overrides for  the `assets/` directory.\n\nThese override the code conventions defined in the top-level [`.eslintrc`](https://sailsjs.com/documentation/anatomy/.eslintrc), to allow for variations between front-end JavaScript code vs. backend code designed to run in a Node.js/Sails process.\n\n<docmeta name=\"displayName\" value=\".eslintrc\">\n"
  },
  {
    "path": "docs/anatomy/assets/assets.md",
    "content": "# assets/\n\nThis is your assets folder.  It houses all of the static files that your app will need to host.  Feel free to create your own files and folders in here.  Upon lifting, a file called `assets/newFolder/data.txt` could be accessed at `http://localhost:1337/newFolder/data.txt`.\n\n\n\n<docmeta name=\"displayName\" value=\"assets\">\n\n"
  },
  {
    "path": "docs/anatomy/assets/dependencies/dependencies.md",
    "content": "# assets/dependencies/\n\nAs a rule of thumb, if it's code written by you or someone on your team, it _does not belong in this folder._  Instead, `assets/dependencies/` is for your client-side dependencies such as Vue.js, Bootstrap, or jQuery.  This folder can include client-side JavaScript files, stylesheets, and even images.  (See the \"Web App\" template for an example.)\n\nJavaScript files and stylesheets in the `assets/dependencies/` folder are loaded first, before your other assets.  This conventional behavior is orchestrated by [tasks/pipeline.js](https://sailsjs.com/documentation/anatomy/tasks/pipeline.js), so head over there if you need to tweak this behavior (for example, if some of your client-side dependencies need to load before others.)\n\n<docmeta name=\"displayName\" value=\"dependencies\">\n\n"
  },
  {
    "path": "docs/anatomy/assets/dependencies/sails.io.js.md",
    "content": "# assets/dependencies/sails.io.js\n\n\nThis file adds a few custom methods to socket.io which provide the \"built-in\" websockets functionality for Sails.\n\nSpecifically, those methods allow you to send and receive Socket.IO messages to and from Sails by simulating a REST client interface on top of Socket.IO. It models its API after the $.ajax pattern from jQuery which you might be familiar with.\n\nSee the [Socket client reference](https://sailsjs.com/documentation/reference/web-sockets/socket-client) for more info about using the methods that this file provides.\n\n<docmeta name=\"displayName\" value=\"sails.io.js\">\n\n"
  },
  {
    "path": "docs/anatomy/assets/favicon.ico.md",
    "content": "# assets/favicon.ico\n\nThis file is the [Favicon](http://en.wikipedia.org/wiki/Favicon) for your app.\n\n\n<docmeta name=\"displayName\" value=\"favicon.ico\">\n\n"
  },
  {
    "path": "docs/anatomy/assets/images/gitkeep.md",
    "content": "# assets/images/.gitkeep\n\n\nIgnore this file.  It only exists because `git` refuses to push empty directories to a remote server.  `.gitkeep` is an unofficial convention that has emerged as a workaround for people who don't discriminate against empty directories.\n\n\n<docmeta name=\"displayName\" value=\".gitkeep\">\n"
  },
  {
    "path": "docs/anatomy/assets/images/images.md",
    "content": "# assets/images/\n\nThis is where you should put image files that need to be statically hosted by your app.\n\nUpon lifting your app, an image called `omgCat.jpg` could be found at `http://localhost:1337/images/omgCat.jpg`\n\n\n\n<docmeta name=\"displayName\" value=\"images\">\n\n"
  },
  {
    "path": "docs/anatomy/assets/js/gitkeep.md",
    "content": "# assets/js/.gitkeep\n\nIgnore this file.  It only exists because `git` refuses to push empty directories to a remote server.  `.gitkeep` is an unofficial convention that has emerged as a workaround for people who don't discriminate against empty directories.\n\n\n<docmeta name=\"displayName\" value=\".gitkeep\">\n"
  },
  {
    "path": "docs/anatomy/assets/js/js.md",
    "content": "# assets/js/\n\nThis is where you put client-side JavaScript files that you want to be statically hosted by your app.  Sails puts a few in there for making communication via socket.io easier.\n\n\n<docmeta name=\"displayName\" value=\"js\">\n\n"
  },
  {
    "path": "docs/anatomy/assets/styles/importer.less.md",
    "content": "# assets/styles/importer.less\n\nBy default, new Sails projects are configured to compile this file from LESS to CSS.  Unlike CSS files, LESS files are not compiled and included automatically unless they are imported here.\n\nThe LESS files imported in this file are compiled and included in the order they are listed.  Mixins, variables, etc. should be imported first so that they can be accessed by subsequent LESS stylesheets.\n\n(Just like the rest of the asset pipeline bundled in Sails, you can always omit, customize, or replace this behavior with SASS, SCSS, or any other Grunt tasks you like.)\n\n\n<docmeta name=\"displayName\" value=\"importer.less\">\n"
  },
  {
    "path": "docs/anatomy/assets/styles/styles.md",
    "content": "# assets/styles/\n\nThis is where you will put all of the .css files that you would like to be statically hosted by your app.\n\n\n<docmeta name=\"displayName\" value=\"styles\">\n\n"
  },
  {
    "path": "docs/anatomy/assets/templates/gitkeep.md",
    "content": "# assets/templates/.gitkeep\n\nIgnore this file.  It only exists because `git` refuses to push empty directories to a remote server.  `.gitkeep` is an unofficial convention that has emerged as a workaround for people who don't discriminate against empty directories.\n\n\n<docmeta name=\"displayName\" value=\".gitkeep\">\n"
  },
  {
    "path": "docs/anatomy/assets/templates/templates.md",
    "content": "# assets/templates/\n\nClient-side HTML templates are important prerequisites for certain types of modern, rich client applications built for browsers; particularly [SPAs](https://en.wikipedia.org/wiki/Single-page_application). To work their magic, frameworks like Backbone, Angular, Ember, and Knockout require that you load templates client-side; completely separate from your traditional [server-side views](https://sailsjs.com/documentation/concepts/views).  Out of the box, new Sails apps support the best of both worlds.\n\nWhether or not you use client-side templates in your app and where you put them is, of course, completely up to you.  But for the sake of convention, new apps generated with Sails include a `templates/` folder for you by default.\n\n\n### How do I use these templates?\n\nBy default, your Gruntfile is configured to automatically load and precompile\nclient-side JST templates in your `assets/templates` folder, then\ninclude them in your `layout.ejs` view automatically (between TEMPLATES and TEMPLATES END).\n\n    <!--TEMPLATES-->\n\n    <!--TEMPLATES END-->\n\nThis exposes your HTML templates as precompiled functions on `window.JST` for use from your client-side JavaScript.\n\nTo customize this behavior to fit your needs, just edit your Gruntfile.\nFor example, here are a few things you could do:\n\n- Import templates from other directories\n- Use a different template engine (handlebars, jade, dust, etc)\n- Internationalize your client-side templates using a server-side stringfile before they're served.\n\n\nFor more information, check out the conceptual documentation on the [default Grunt tasks](https://sailsjs.com/documentation/concepts/assets/default-tasks) that make up Sails' asset pipeline.\n\n<docmeta name=\"displayName\" value=\"templates\">\n\n"
  },
  {
    "path": "docs/anatomy/config/blueprints.js.md",
    "content": "# config/blueprints.js\n\nThis file is for the configuration of blueprint routes and actions.\n\nFor an overview of blueprints, see the [main Blueprints API concepts docs](https://sailsjs.com/documentation/concepts/blueprints).  For more information on configuring the blueprint API, check out the [reference documentation on blueprints](https://sailsjs.com/documentation/reference/configuration/sails-config-blueprints).\n\n### Usage\n\nSee [`sails.config.blueprints`](https://sailsjs.com/documentation/reference/configuration/sails-config-blueprints) for all available options.\n\n\n\n<docmeta name=\"displayName\" value=\"blueprints.js\">\n"
  },
  {
    "path": "docs/anatomy/config/bootstrap.js.md",
    "content": "# config/bootstrap.js\n\nThis is a server-side JavaScript file that is executed by Sails just before your app is lifted.\n\nThis gives you an opportunity to set up your data model, run jobs, or perform some special logic.\n\n### Usage\n\nSee [`sails.config.bootstrap`](https://sailsjs.com/documentation/reference/configuration/sails-config-bootstrap) for more info.\n\n<docmeta name=\"displayName\" value=\"bootstrap.js\">\n"
  },
  {
    "path": "docs/anatomy/config/config.md",
    "content": "# config/\n\nThis folder contains various files that will allow you to customize and configure your Sails app.\n\n\n\n<docmeta name=\"displayName\" value=\"config\">\n\n"
  },
  {
    "path": "docs/anatomy/config/custom.js.md",
    "content": "# config/custom\n\nThis is your custom configuration file. It is useful for one-off settings specific to your application-- like your base URL for linkbacks, the no-reply \"From\" address to use when sending automated emails, or 3rd party API keys for Stripe, Mailgun, Twilio, etc.\n\n> Use [`sails.config.custom`](https://sailsjs.com/documentation/reference/application/sails-config-custom) to access these values from your actions and helpers.\n\nYou can learn more about custom configuration [here](https://sailsjs.com/documentation/reference/configuration/sails-config-custom).\n\n\n<docmeta name=\"displayName\" value=\"custom.js\">\n"
  },
  {
    "path": "docs/anatomy/config/datastores.js.md",
    "content": "# config/datastores\n\nA set of datastore configurations which tell Sails where to fetch or save data when you execute built-in model methods like `.find()` and `.create()`.\n\n> This file is mainly useful for configuring your development database, as well as any additional one-off databases used by individual models.\n\n### Usage\n\nSee [`sails.config.datastores`](https://sailsjs.com/documentation/reference/configuration/sails-config-datastores) for all available options.\n\n<docmeta name=\"displayName\" value=\"datastores.js\">\n"
  },
  {
    "path": "docs/anatomy/config/env/env.md",
    "content": "# config/env/\n\nThis folder contains various environment-specific settings such as API keys or remote database passwords. Depending on the environment Sails is lifted in, the appropriate configuration file in this folder will load.  To read more about environent-specific config in Sails, see [**Concepts > Configuration**](https://sailsjs.com/documentation/concepts/configuration#?environmentspecific-files-config-env).\n\n\n<docmeta name=\"displayName\" value=\"env\">\n"
  },
  {
    "path": "docs/anatomy/config/env/production.js.md",
    "content": "# config/env/production.js\n\nThis file will be loaded when Sails is running in `production` mode. If using the CLI command `sails lift --prod`, these settings will be loaded.\n\n\n<docmeta name=\"displayName\" value=\"production.js\">\n"
  },
  {
    "path": "docs/anatomy/config/globals.js.md",
    "content": "# config/globals.js\n\nConfiguration for the global variables Sails exposes to its Node process.\n\n### Usage\n\nSee [`sails.config.globals`](https://sailsjs.com/documentation/reference/configuration/sails-config-globals) for all available options.\n\n\n<docmeta name=\"displayName\" value=\"globals.js\">\n"
  },
  {
    "path": "docs/anatomy/config/http.js.md",
    "content": "# config/http.js\n\nThis file is for configuring the underlying HTTP server in Sails, as well as any HTTP middleware your app may need.\n\n### Usage\n\nSee [`sails.config.http`](https://sailsjs.com/documentation/reference/configuration/sails-config-http) for all available options.\n\n<docmeta name=\"displayName\" value=\"http.js\">\n\n"
  },
  {
    "path": "docs/anatomy/config/i18n.js.md",
    "content": "# config/i18n.js\n\nThis file contains your Sails app's internationalization settings.\n\n### Usage\n\nSee [`sails.config.i18n`](https://sailsjs.com/documentation/reference/configuration/sails-config-i-18-n) for all available options.\n\n<docmeta name=\"displayName\" value=\"i18n.js\">\n"
  },
  {
    "path": "docs/anatomy/config/local.js.md",
    "content": "# config/local.js\n\nThis file is used to specify configuration settings for use while developing the app on your personal system.\n\nFor more information, check out [Concepts > Configuration > The local.js file](https://sailsjs.com/docs/concepts/configuration/the-local-js-file)\n\n> Since `config/local.js` is usually used to store sensitive credentials, it is included in your app's [.gitignore](https://sailsjs.com/documentation/anatomy/.gitignore), and isn't pushed to the remote server. If you click the link to this file below, you should see a 404 page; in this case, that's a _good_ thing!\n\n<docmeta name=\"displayName\" value=\"local.js\">\n"
  },
  {
    "path": "docs/anatomy/config/locales/de.json.md",
    "content": "# config/locales/de.json\n\nThis file is where German locale information is stored.\n\n<docmeta name=\"displayName\" value=\"de.json\">\n\n"
  },
  {
    "path": "docs/anatomy/config/locales/en.json.md",
    "content": "# config/locales/en.json\n\nThis file is where English locale settings are stored.\n\n<docmeta name=\"displayName\" value=\"en.json\">\n"
  },
  {
    "path": "docs/anatomy/config/locales/es.json.md",
    "content": "# config/locales/es.json\n\nThis file is where Spanish locale settings are stored.\n\n<docmeta name=\"displayName\" value=\"es.json\">\n"
  },
  {
    "path": "docs/anatomy/config/locales/fr.json.md",
    "content": "# config/locales/fr.json\n\nThis file is where French locale settings are stored.\n\n<docmeta name=\"displayName\" value=\"fr.json\">\n"
  },
  {
    "path": "docs/anatomy/config/locales/locales.md",
    "content": "# config/locales\n\nThis folder contains the information that is used by your app in supporting visiting client's different [locales](http://en.wikipedia.org/wiki/Locale).\n\n### Usage\n\nSee **[Concepts > Internationalization](https://sailsjs.com/documentation/concepts/internationalization)**.\n\n<docmeta name=\"displayName\" value=\"locales\">\n\n"
  },
  {
    "path": "docs/anatomy/config/log.js.md",
    "content": "# config/log.js\n\nThis file contains the logger configuration for your Sails app.\n\nConfigure the log level for your app, as well as the transport.\n\nUnderneath the covers, Sails uses Winston for logging, which allows for some pretty neat custom transports/adapters for log messages.\n\n### Usage\n\nSee [`sails.config.log`](https://sailsjs.com/documentation/reference/configuration/sails-config-log) for all available options.\n\n<docmeta name=\"displayName\" value=\"log.js\">\n"
  },
  {
    "path": "docs/anatomy/config/models.js.md",
    "content": "# config/models.js\n\nUnless you override them, the properties contained in this file will be included in each of your models.\n\n### Usage\n\nSee [`sails.config.models`](https://sailsjs.com/documentation/reference/configuration/sails-config-models) for all available options.\n\n<docmeta name=\"displayName\" value=\"models.js\">\n\n"
  },
  {
    "path": "docs/anatomy/config/policies.js.md",
    "content": "# config/policies.js\n\nThis file contains the default policies for your app.\n\nPolicies are simply Express middleware functions which run before your controllers. You can apply one or more policies to a given controller, or protect just one of it's actions. Any policy file (e.g. `api/policies/isLoggedIn.js`) can be dropped into the `api/policies/` folder, at which point it can be accessed by it's filename, minus the extension, (e.g. `isLoggedIn`).\n\n### Usage\n\nSee [`sails.config.policies`](https://sailsjs.com/documentation/reference/configuration/sails-config-policies) for all available options.\n\n<docmeta name=\"displayName\" value=\"policies.js\">\n"
  },
  {
    "path": "docs/anatomy/config/routes.js.md",
    "content": "# config/routes.js\n\nThis file contains custom routes.  Sails uses these routes to determine what to do each time it receives a request.\n\nIf Sails receives a URL that doesn't match any of the [custom routes](https://sailsjs.com/documentation/concepts/routes/custom-routes) in this file, it will check for matching [assets](https://sailsjs.com/documentation/concepts/assets) (images, scripts, stylesheets, etc.). Finally, if those don't match either, the [default 404 handler](https://sailsjs.com/documentation/reference/response-res/res-not-found) is triggered.\n\nWhen you first generate your Sails app, there is only one route in this file.  Its job is to serve the home page.\n\nYou'll probably want to add some more.\n\n> Sails also injects _shadow routes_, or implicit routes that handle certain kinds of requests behind the scenes.  For more information about these kinds of routes, see **[Concepts > Blueprints](https://sailsjs.com/documentation/concepts/blueprints)**.\n\n### Usage\n\nSee [`sails.config.routes`](https://sailsjs.com/documentation/reference/configuration/sails-config-routes) for all available options.\n\n<docmeta name=\"displayName\" value=\"routes.js\">\n"
  },
  {
    "path": "docs/anatomy/config/security.js.md",
    "content": "# config/security.js\n\nThis file is the conventional home of your Sails app's global security settings.  For a complete reference of available security configuration in Sails, see:\n\n* CORS settings reference: [sails.config.security.cors](https://sailsjs.com/documentation/reference/configuration/sails-config-security-cors)\n* CSRF settings reference: [sails.config.security.csrf](https://sailsjs.com/documentation/reference/configuration/sails-config-security-csrf)\n\nFor a conceptual explanation of CORS in Sails, see [Security > CORS](https://sailsjs.com/documentation/concepts/security/cors).\n\nFor a conceptual explanation of CSRF in Sails, see [Security > CSRF](https://sailsjs.com/documentation/concepts/security/csrf).\n\n### Usage\n\nSee [`sails.config.security`](https://sailsjs.com/documentation/reference/configuration/sails-config-security) for all available options.\n\n\n<docmeta name=\"displayName\" value=\"security.js\">\n"
  },
  {
    "path": "docs/anatomy/config/session.js.md",
    "content": "# config/session.js\n\nThis file contains information that tells Sails where to store your sessions.\n\nSails session integration leans heavily on the great work already done by Express, but also unifies socket.io with the Connect session store. It uses Connect's cookie parser to normalize configuration differences between Express and socket.io and hooks into Sails' middleware interpreter to allow you to access and auto-save to `req.session` with Socket.io the same way you would with Express.\n\nThis is where you would go to configure a different session store like Redis or Mongo.  In this file you will find commented examples of what that configuration should look like.\n\nThis file also contains your 'Session Secret' that is generated by Sails when you create your app.  Do not change or remove this unless you really know what you are doing.\n\n### Usage\n\nSee [`sails.config.session`](https://sailsjs.com/documentation/reference/configuration/sails-config-session) for all available options.\n\n\n<docmeta name=\"displayName\" value=\"session.js\">\n"
  },
  {
    "path": "docs/anatomy/config/sockets.js.md",
    "content": "# config/sockets.js\n\nThis is a configuration file that allows you to customize the way your app talks to clients over Socket.IO.\n\nIt provides transparent access to Sails' encapsulated pubsub/socket server for complete customizability. In it you can do things on the list below (and more!).\n\n- Override afterDisconnect function (server side)\n- Define custom authorization logic for client socket connections\n- Set transport method\n- Change Heartbeat Interval\n- Change socket store\n\n### More Info\n> Socket.IO configuration options can be found [here](https://github.com/LearnBoost/Socket.IO/wiki/Configuring-Socket.IO).\n\n### Usage\n\nSee [`sails.config.sockets`](https://sailsjs.com/documentation/reference/configuration/sails-config-sockets) for all available options.\n\n\n<docmeta name=\"displayName\" value=\"sockets.js\">\n"
  },
  {
    "path": "docs/anatomy/config/views.js.md",
    "content": "# config/views.js\n\nThis file is where Sails looks to find out which templating engine to use when rendering server side HTML templates.  By default Sails uses ejs, but any view engine can be used by changing the `extension` and supplying a `getRenderFn` value (see the [view engine documentation](https://sailsjs.com/documentation/concepts/views/view-engines) for more info).\n\n### Usage\n\nSee [`sails.config.views`](https://sailsjs.com/documentation/reference/configuration/sails-config-views) for all available options.\n\n<docmeta name=\"displayName\" value=\"views.js\">\n"
  },
  {
    "path": "docs/anatomy/gitignore.md",
    "content": "# .gitignore\n\nThis file is only relevant if you are using git.\n\nIt informs `git` of any files that you don't want `pushed` to the remote server.\n\nFiles which match the splat patterns seen in the code below will be ignored by git.  This keeps random crap and and sensitive credentials from being uploaded to  your repository.  It allows you to configure your app for your machine without accidentally committing settings which will smash the local settings of other developers on your team.\n\nSome reasonable defaults are included, but, of course, you should modify/extend/prune to fit your needs!\n\n[Read more about .gitignore](https://help.github.com/articles/ignoring-files)\n\n\n\n<docmeta name=\"displayName\" value=\".gitignore\">\n"
  },
  {
    "path": "docs/anatomy/package.json.md",
    "content": "# package.json\n\nThis is a standard configuration file for [npm](https://npmjs.org/doc/json.html). Among other things, this file contains the name and version of all of the Node Modules that your app depends on to run.  You can change this manually but be careful to adhere to their rules or things might break.\n\n<docmeta name=\"displayName\" value=\"package.json\">\n"
  },
  {
    "path": "docs/anatomy/sailsrc.md",
    "content": "# .sailsrc\n\nThis file is useful for setting configuration that you want to generate programmatically.  You can also use it to extend the functionality of the Sails CLI tool, for example to add a generator.\n\nYou can learn more about using sailsrc files [here](https://sailsjs.com/documentation/concepts/configuration/using-sailsrc-files).\n\n\n<docmeta name=\"displayName\" value=\".sailsrc\">\n"
  },
  {
    "path": "docs/anatomy/tasks/config/babel.js.md",
    "content": "# tasks/config/babel.js\n\nThis file configures a Grunt task called \"babel\".\n\nThis task is used to <a href=\"https://en.wiktionary.org/wiki/transcompile\" target=\"_blank\">transpile</a> any <a href=\"http://es6-features.org\" target=\"_blank\">ES8, ES7, and ES6 syntax</a> in your front-end JavaScript files for compatibility with older browsers.\n\n> (By default, only `.js` files in the `assets/js/` folder and subfolders will be transpiled.  If you need other things transpiled, such as `assets/dependencies/`, you'll need to modify the configuration of this task accordingly.)\n\nFor additional usage documentation, see [`grunt-babel`](https://npmjs.com/package/grunt-babel).\n\n\n\n<docmeta name=\"displayName\" value=\"babel.js\">\n"
  },
  {
    "path": "docs/anatomy/tasks/config/clean.js.md",
    "content": "# tasks/config/clean.js\n\nThis file configures a Grunt task called \"clean\".\n\nThis task is used when preparing for a new pass through the asset pipeline. Its job is to remove any existing temporary files and folders in your Sails app's web root.\n\n> (By default, the [web root in a Sails app](https://sailsjs.com/documentation/concepts/assets) is a hidden directory called `.tmp/public`.)\n\nFor additional usage documentation, see [`grunt-contrib-clean`](https://npmjs.com/package/grunt-contrib-clean).\n\n\n\n<docmeta name=\"displayName\" value=\"clean.js\">\n"
  },
  {
    "path": "docs/anatomy/tasks/config/coffee.js.md",
    "content": "# tasks/config/coffee.js\n\nThis file configures a Grunt task called \"coffee\".\n\nBy default, this compiles CoffeeScript files located in [`assets/js/`](https://sailsjs.com/anatomy/assets/js/) into JavaScript, then generates new `.js` files in `.tmp/public/js/`.\n\n\n### But I'm not using CoffeeScript...\n\nNo problem!\n\nIf you aren't using any kind of pre-processing for your client-side JavaScript, then just ignore this file.\n\nIf you want to use a _different_ pre-processor like [TypeScript](https://www.typescriptlang.org/) or [Babel](https://babeljs.io/), and you want Sails to process your client-side JavaScript assets automatically as you work, then you're in luck.  In most cases, this is as easy as installing the appropriate Grunt plugin as a dependency of your Sails app, and then configuring it to output compiled JavaScript to the same path as in this default task.\n\nHere are a couple of popular examples:\n\n+ [grunt-ts](https://www.npmjs.com/package/grunt-ts)\n+ [grunt-babel](https://www.npmjs.com/package/grunt-babel)\n\n\n### Usage\n\nFor additional usage documentation, see [`grunt-contrib-coffee`](https://npmjs.com/package/grunt-contrib-coffee).\n\n\n<docmeta name=\"displayName\" value=\"coffee.js\">\n"
  },
  {
    "path": "docs/anatomy/tasks/config/concat.js.md",
    "content": "# tasks/config/concat.js\n\nThis file configures a Grunt task called \"concat\".\n\nIt concatenates the contents of multiple JavaScript and/or CSS files into two new files, each located at `concat/production.js` and `concat/production.css` respectively in `.tmp/public/concat`.\n\nThis is used as an intermediate step to generate monolithic files that can then be passed in to `uglify` and/or `cssmin` for [minification](https://en.wikipedia.org/wiki/Minification_(programming)).\n\n\n### Usage\n\nFor additional usage documentation, see [`grunt-contrib-concat`](https://npmjs.com/package/grunt-contrib-concat).\n\n\n<docmeta name=\"displayName\" value=\"concat.js\">\n"
  },
  {
    "path": "docs/anatomy/tasks/config/config.md",
    "content": "# tasks/config/\n\nThis folder contains the default Grunt task configuration used by the main entry points in [`tasks/register/`](https://sailsjs.com/anatomy/tasks/register).\n\nFor more about the files in this folder, see [Assets > Default Tasks](https://sailsjs.com/documentation/concepts/assets/default-tasks).\n\n\n<docmeta name=\"displayName\" value=\"config\">\n\n"
  },
  {
    "path": "docs/anatomy/tasks/config/copy.js.md",
    "content": "# tasks/config/copy.js\n\nThis file configures a Grunt task called \"copy\".\n\nCopy files and/or folders from your `assets/` directory into the web root (`.tmp/public`) so they can be served via HTTP, and also for further pre-processing by other Grunt tasks.\n\n##### Normal usage (`sails lift`)\nCopies all directories and files (except CoffeeScript and LESS) from the `assets/` folder into the web root -- conventionally a hidden directory located `.tmp/public`.\n\n##### Via the `build` tasklist (`sails www`)\nCopies all directories and files from the .tmp/public directory into a www directory.\n\n### Usage\n\nFor additional usage documentation, see [`grunt-contrib-copy`](https://npmjs.com/package/grunt-contrib-copy).\n\n\n<docmeta name=\"displayName\" value=\"copy.js\">\n"
  },
  {
    "path": "docs/anatomy/tasks/config/cssmin.js.md",
    "content": "# tasks/config/cssmin.js\n\nThis file configures a Grunt task called \"cssmin\".\n\nIt minifies the intermediate, concatenated CSS stylesheet which was prepared by the `concat` task at `.tmp/public/concat/production.css`.  Together with the `concat` task, this is the final step that minifies all CSS files from `assets/styles/` (and potentially your LESS importer file from `assets/styles/importer.less`).\n\n### Usage\n\nFor additional usage documentation, see [`grunt-contrib-cssmin`](https://npmjs.com/package/grunt-contrib-cssmin).\n\n\n<docmeta name=\"displayName\" value=\"cssmin.js\">\n"
  },
  {
    "path": "docs/anatomy/tasks/config/hash.js.md",
    "content": "# tasks/config/hash.js\n\n\nThis file configures a Grunt task called \"hash\".\n\nThis task implements cache-busting for minified CSS and JavaScript files.\n\nSpecifically, its job is to append a unique hash to the end of a filename.\n\n> For example: bar/foo.css => bar/dist/foo.f8494f81.css\n\n\n### Usage\n\nFor additional usage documentation, see [`grunt-hash`](https://github.com/jgallen23/grunt-hash/tree/0.5.0#grunt-hash).\n\n\n<docmeta name=\"displayName\" value=\"hash.js\">\n"
  },
  {
    "path": "docs/anatomy/tasks/config/jst.js.md",
    "content": "# tasks/config/jst.js\n\n\nThis file configures a Grunt task called \"jst\".\n\nIt precompiles HTML templates using Underscore/Lodash notation into functions, creating a `.jst` file.  This can be brought into your HTML via a `<script>` tag in order to expose your templates as `window.JST` for use in your client-side JavaScript.\n\nIn other words, this takes HTML files in `assets/templates/` and turns them into tiny little JavaScript functions that return HTML strings when you pass a data dictionary into them.  This approach is called \"precompiling\", and it can considerably speed up template rendering on the client, and even reduce bandwidth usage and related expenses.)\n\n> Note that, by default, Underscore/Lodash/JST notation is _opposite_ from EJS (`<%=` is `<%-`, and vice versa).\n> If this bothers you, it can be easily configured in this file. (See inline comments for details.)\n\n### But I'm not using Lodash/Underscore/JST templates...\n\nNo problem!\n\nIf you aren't using any kind of precompiled client-side templates, then just ignore this file.\n\nIf you are using a front-end framework like [Vue.js](https://vuejs.org), Ember, React, or Angular, see the starter app for examples, or come by https://sailsjs.com/support for assistance.\n\nIf you want to use a _completely different_ pre-processor like [Handlebars](http://handlebarsjs.com/) or [Dust](http://www.dustjs.com/), and you want Sails to process your client-side templates automatically as you work, then you're in luck.  In most cases, this is as easy as installing the appropriate Grunt plugin as a dependency of your Sails app, and then configuring it to output the precompiled templates (condensed into a single JavaScript file) to the same path as in this default task.\n\nHere are a couple of popular examples:\n\n+ [grunt-contrib-handlebars](https://www.npmjs.com/package/grunt-contrib-handlebars)\n+ [grunt-dust](https://www.npmjs.com/package/grunt-dust)\n\n\n### Usage\n\nFor additional usage documentation, see [`grunt-contrib-jst`](https://www.npmjs.com/package/grunt-contrib-jst).\n\n\n\n<docmeta name=\"displayName\" value=\"jst.js\">\n\n"
  },
  {
    "path": "docs/anatomy/tasks/config/less.js.md",
    "content": "# tasks/config/less.js\n\n\nThis file configures a Grunt task called \"less\".\n\nIts job is to compile your LESS files into a CSS stylesheet.\n\nBy default, only the `assets/styles/importer.less` file is compiled.  This allows you to control the ordering yourself, i.e. import your dependencies, mixins, variables, resets, etc. before your other more application-specific styles.  This is entirely up to you, and based on the order with which write your `@import`s in your LESS file.\n\n### But I'm not using LESS...\n\nNo problem!\n\nIf you aren't using _any_ preprocessor for your stylesheets, then just ignore this file.\n\nIf you want to use a different pre-processor like [SASS](http://sass-lang.com/) or [Stylus](http://stylus-lang.com/), and you want Sails to process your stylesheets automatically as you work, then you're in luck.  In most cases, this is as easy as installing the appropriate Grunt plugin as a dependency of your Sails app, and then configuring it to output compiled CSS to the same path as in this default task.\n\nHere are a couple of popular examples:\n\n+ [grunt-sass](http://npmjs.com/package/grunt-sass)\n+ [grunt-contrib-stylus](https://npmjs.com/package/grunt-contrib-stylus)\n\n### Usage\n\nFor additional usage documentation, see [`grunt-contrib-less`](https://npmjs.com/package/grunt-contrib-less).\n\n\n<docmeta name=\"displayName\" value=\"less.js\">\n"
  },
  {
    "path": "docs/anatomy/tasks/config/sails-linker.js.md",
    "content": "# tasks/config/sails-linker.js\n\nThis file configures a Grunt task called \"sails-linker\".\n\nAutomatically inject `<script>` tags and `<link>` tags into the specified \nHTML and/or EJS files.  The specified delimiters (`startTag`\nand `endTag`) determine the insertion points.\n\n##### Development (default)\n\nBy default, tags will be injected for your app's client-side JavaScript files,\nCSS stylesheets, and precompiled client-side HTML templates in the `templates/`\ndirectory (see the `jst` task for more info on that).  In addition, if a LESS\nstylesheet exists at `assets/styles/importer.less`, it will be compiled to CSS\nand a `<link>` tag will be inserted for it.  Similarly, if any Coffeescript\nfiles exist in `assets/js/`, they will be compiled into JavaScript and injected\nas well.\n\n##### Production (`NODE_ENV=production`)\n\nIn production, all stylesheets (including all .css files and `assets/styles/importer.less`) are\nminified into a single `.css` file (see `tasks/config/cssmin.js` task) and\nall client-side scripts (including `.js` and `.coffee` files) are minified\ninto a single `.js` file (see `tasks/config/uglify.js` task).  Any precompiled,\nclient-side HTML templates (JST) can also be minified alongside the other\nscripts when `sails-linker:prodJs` runs-- but since this could change the\nbehavior of your front-end code, it is not included by default.\n\n> If you're using JST templates and you'd like them to be included in the\n> minified bundle, remove `clientSideTemplates` from the tasklist array in\n> `tasks/register/prod.js`, and then modify `tasks/config/uglify.js` to include\n> the compiled `jst.js` file from `.tmp/public/` in its `src` array.\n\n### Usage\n\nFor additional usage documentation, see [`grunt-sails-linker`](https://www.npmjs.com/package/grunt-sails-linker).\n\n<docmeta name=\"displayName\" value=\"sails-linker.js\">\n\n"
  },
  {
    "path": "docs/anatomy/tasks/config/sync.js.md",
    "content": "# tasks/config/sync.js\n\n\nThis file configures a Grunt task called \"sync\".\n\nThis task synchronizes one directory with another (like rsync).  In the default Sails asset pipeline, it plays a very similar role to `tasks/config/copy.js`, but copies only those files that have actually changed since the last time the task was run.\n\nSpecifically, its job is to synchronize files from the `assets/` folder to `.tmp/public`, smashing anything that's already there.\n\n\n### Usage\n\nFor additional usage documentation, see [`grunt-sync`](https://www.npmjs.com/package/grunt-sync).\n\n\n<docmeta name=\"displayName\" value=\"sync.js\">\n"
  },
  {
    "path": "docs/anatomy/tasks/config/uglify.js.md",
    "content": "# tasks/config/uglify.js\n\nThis file configures a Grunt task called \"uglify\".\n\nIts job is to <a target=\"_blank\" href=\"https://en.wikipedia.org/wiki/Minification_(programming)\">minify</a> client-side JavaScript files.  Internally, it uses [UglifyES](https://www.npmjs.com/package/uglifyes).\n\n### Usage\n\nFor additional usage documentation, see [`grunt-contrib-uglify`](https://github.com/gruntjs/grunt-contrib-uglify/tree/harmony).\n\n### ES8 and beyond\n\nThe default package is capable of minifying JavaScript written using ES6, ES7, and ES8 syntax and features, even without [transpiling](https://sailsjs.com/documentation/concepts/assets/default-tasks#?babel).  However, if you're planning on supporting older browsers that don't support ES6, it's recommended that you still transpile your code (by leaving the default [`babel`](https://sailsjs.com/documentation/anatomy/tasks/config/babel.js) and [`polyfill`](https://sailsjs.com/documentation/anatomy/tasks/register/polyfill.js) tasks in place).\n\n<docmeta name=\"displayName\" value=\"uglify.js\">\n"
  },
  {
    "path": "docs/anatomy/tasks/config/watch.js.md",
    "content": "# tasks/config/watch.js\n\nThis file configures a Grunt task called \"watch\".\n\nIt runs predefined tasks whenever watched file patterns are added, changed or deleted.\n\nSpecifically, this watches for changes to:\n- files in the `assets` folder\n- the `tasks/pipeline.js` file\n\n...and then re-runs the appropriate tasks.\n\n### Usage\n\nFor additional usage documentation, see [`grunt-contrib-watch`](https://npmjs.com/package/grunt-contrib-watch).\n\n\n<docmeta name=\"displayName\" value=\"watch.js\">\n"
  },
  {
    "path": "docs/anatomy/tasks/pipeline.js.md",
    "content": "# tasks/pipeline.js\n\nThe `pipeline.js` file in your Sails app determines the order in which your stylesheets,\nJavaScript, and client-side template files should be compiled and linked as `<script>`\nor `<link>` tags.\n\nIf you are not relying on [automatic asset linking](https://sailsjs.com/documentation/concepts/assets/task-automation#?asset-pipeline), then you can safely ignore this file.\n\n> Note that you can take advantage of Grunt-style wildcard/glob/splat expressions for matching multiple files, and use `!` in front of an expression to ignore files.\n\n\n<docmeta name=\"displayName\" value=\"pipeline.js\">\n"
  },
  {
    "path": "docs/anatomy/tasks/register/build.js.md",
    "content": "# tasks/register/build.js\n\nThis Grunt tasklist will be executed if you run `sails www` or `grunt build` in a development environment.  It generates a folder containing your compiled assets, e.g. for troubleshooting issues with other Grunt plugins, bundling assets for an Electron or PhoneGap app, or deploying your app's flat files to a CDN.\n\n> Note that when running `sails www` in a production environment (with the `NODE_ENV` environment variable set to 'production') the `buildProd` task will be run instead of this one.\n\n<docmeta name=\"displayName\" value=\"build.js\">\n"
  },
  {
    "path": "docs/anatomy/tasks/register/buildProd.js.md",
    "content": "# tasks/register/buildProd.js\n\nThis Grunt tasklist will be executed instead of `build` if you run `sails www` in a production environment, e.g.:\n\n```bash\nNODE_ENV=production sails www\n```\n\nThis generates a folder containing your compiled (and usually minified)\nassets.  The most common use case for this is bundling up files to\ndeploy to a CDN.\n\n> This is also useful for building standalone applications with tools like PhoneGap or Electron.\n\n<docmeta name=\"displayName\" value=\"buildProd.js\">\n"
  },
  {
    "path": "docs/anatomy/tasks/register/compileAssets.js.md",
    "content": "# tasks/register/compileAssets.js\n\nThis Grunt tasklist is not designed to be used directly-- rather it is a supporting module used by the `default`, `prod`, `build`, and `buildProd` tasklists.\n\n<docmeta name=\"displayName\" value=\"compileAssets.js\">\n"
  },
  {
    "path": "docs/anatomy/tasks/register/default.js.md",
    "content": "# tasks/register/default.js\n\nThis is the default Grunt tasklist that will be executed if you\n run `grunt` in the top level directory of your app.  It is also\n called automatically when you start Sails in development mode using\n `sails lift` or `node app`.\n\n Note that when lifting your app with a custom environment setting\n (i.e. `sails.config.environment`), Sails will look for a tasklist file\n with the same name and run that instead of this one.\n\n > Note that as a special case for compatibility/historial reasons, if\n > your environment is \"production\" (i.e. because you lifted with NODE_ENV=production),\n > and Sails cannot find a tasklist named `production.js`, it will attempt to run\n > the `prod.js` tasklist as well before defaulting to `default.js`.\n\n<docmeta name=\"displayName\" value=\"default.js\">\n\n"
  },
  {
    "path": "docs/anatomy/tasks/register/linkAssets.js.md",
    "content": "# tasks/register/linkAssets.js\n\nThis Grunt tasklist is not designed to be used directly-- rather it is a supporting module used by the `default` tasklist and the `watch` task (but only if the `grunt-sails-linker` package is in use).\n\n<docmeta name=\"displayName\" value=\"linkAssets.js\">\n\n"
  },
  {
    "path": "docs/anatomy/tasks/register/linkAssetsBuild.js.md",
    "content": "# tasks/register/linkAssetsBuild.js\n\nThis Grunt tasklist is not designed to be used directly-- rather it is a supporting module used by the `build` tasklist.\n\n<docmeta name=\"displayName\" value=\"linkAssetsBuild.js\">\n\n"
  },
  {
    "path": "docs/anatomy/tasks/register/linkAssetsBuildProd.js.md",
    "content": "# tasks/register/linkAssetsBuildProd.js\n\nThis Grunt tasklist is not designed to be used directly-- rather it is a supporting module used by the `buildProd` tasklist.\n\n<docmeta name=\"displayName\" value=\"linkAssetsBuildProd.js\">\n"
  },
  {
    "path": "docs/anatomy/tasks/register/polyfill.js.md",
    "content": "# tasks/register/polyfill.js\n\nThis file configures a Grunt task called \"polyfill\".\n\nAdd a polyfill.js file to the public assets (in dev mode) or minified JavaScript file (in production) to fill in features missing in older browsers, such as `Promise`.  This task is meant to work in conjunction with the [babel task](https://sailsjs.com/documentation/anatomy/tasks/config/babel.js).\n\n##### Development (`polyfill:dev`)\n\nThe development version of this task copies the polyfill file to `.tmp/public/polyfill/polyfill.min.js`, and ensures that the file will be included (via the [`linkAssets` task](https://sailsjs.com/documentation/anatomy/tasks/register/linkassets.js)) as a `<script>` tag in any HTML files with the `<!--SCRIPTS-->` template tag.\n\n> By default, the `polyfill:dev` and `babel` tasks are commented out in development Grunt tasks, to make it easier to debug your code in the browser.\n\n##### Production (`polyfill:prod`)\n\nIn production, (i.e. when the `NODE_ENV` environment variable is set to `production`), this task adds the contents of the polyfill file to the very top of the concatenated and minified `production.min.js` file.\n\n<docmeta name=\"displayName\" value=\"polyfill.js\">\n\n"
  },
  {
    "path": "docs/anatomy/tasks/register/prod.js.md",
    "content": "# tasks/register/prod.js\n\nThis Grunt tasklist will be executed instead of `default` when your Sails app is lifted in a production environment (e.g. using `NODE_ENV=production node app`).\n\n<docmeta name=\"displayName\" value=\"prod.js\">\n"
  },
  {
    "path": "docs/anatomy/tasks/register/register.md",
    "content": "# tasks/register/\n\nThis folder contains the Grunt tasks that Sails runs by default.\n\nFor more information, see [Assets > Task Automation > Task Triggers](https://sailsjs.com/documentation/concepts/assets/task-automation#?task-triggers).\n\n> To run a custom task list, create a file in this directory and set [`sails.config.environment`](https://sailsjs.com/documentation/reference/configuration/sails-config#?sailsconfigenvironment) to match this file name.  For example, if the Sails `environment` config is set to \"qa\", then when you lift, instead of `tasks/register/default.js` or `tasks/register/prod.js`, Sails will _instead_ run `tasks/register/qa.js`. (If it does not exist, then `default.js` will be run instead.)\n\n<docmeta name=\"displayName\" value=\"register\">\n\n"
  },
  {
    "path": "docs/anatomy/tasks/register/syncAssets.js.md",
    "content": "# tasks/register/syncAssets.js\n\nThis Grunt tasklist is not designed to be used directly-- rather it is a supporting module used by the `watch` task (`tasks/config/watch.js`).\n\n<docmeta name=\"displayName\" value=\"syncAssets.js\">\n\n"
  },
  {
    "path": "docs/anatomy/tasks/tasks.md",
    "content": "# tasks/\n\nThe `tasks/` directory is a suite of Grunt tasks and their configurations, bundled for your convenience.  The Grunt integration is mainly useful for bundling front-end assets (like stylesheets, scripts and markup templates), but it can also be used to run all kinds of development tasks, from browserify compilation to database migrations.\n\nIf you haven't used [Grunt](http://gruntjs.com/) before, that's OK!  For many common use cases, you can get by without customizing or even looking at the files in this folder.  If you do need to customize something, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains basic concepts like the [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as how to install and use Grunt plugins. Once you're familiar with that process, read on!\n\n\n### How does this work?\n\nThe asset pipeline bundled in Sails is a set of Grunt tasks configured with conventional defaults designed to make your project more consistent and productive.\n\nThe entire front-end asset workflow in Sails is completely customizable-- while it provides some suggestions out of the box, Sails makes no pretense that it can anticipate all of the needs you'll encounter building the browser-based/front-end portion of your application.  Who's to say you're even building an app for a browser?\n\n\n### What tasks does Sails run automatically?\n\nSails runs some of these tasks (certain ones in the `tasks/register/` folder) automatically when you run certain commands.\n\n###### `sails lift`\n\nRuns the `default` task (`tasks/register/default.js`).\n\n###### `sails lift --prod`\n\nRuns the `prod` task (`tasks/register/prod.js`).\n\n###### `sails www`\n\nRuns the `build` task (`tasks/register/build.js`).\n\n###### `sails www --prod` (production)\n\nRuns the `buildProd` task (`tasks/register/buildProd.js`).\n\n\n### Can I customize this for SASS, Angular, client-side Jade templates, etc?\n\nYou can modify, omit, or replace any of these Grunt tasks to fit your requirements. You can also add your own Grunt tasks- just add a `someTask.js` file in the `grunt/config` directory to configure the new task, then register it with the appropriate parent task(s) (see files in `grunt/register/*.js`).\n\n\n### Do I have to use Grunt?\n\nNope!  The Sails core team has used Grunt on real-world projects for upwards of 4 years now, and overall it's been a fantastic tool.  But we realize it's not for everyone.  To disable Grunt integration in Sails, just delete your Gruntfile or [disable the Grunt hook](https://sailsjs.com/documentation/concepts/assets/disabling-grunt).\n\n> You can also [generate a new Sails app `--without=grunt`](https://sailsjs.com/documentation/reference/command-line-interface/sails-new).\n\n\n### What if I'm not building a web frontend?\n\nThat's ok! A core tenant of Sails is client-agnosticism-- it's especially designed for building APIs used by all sorts of clients; native Android/iOS/Cordova, serverside SDKs, etc.\n\nYou can completely disable Grunt by following the instructions [here](https://sailsjs.com/documentation/concepts/assets/disabling-grunt).\n\nIf you still want to use Grunt for other purposes, but don't want any of the default web front-end stuff, just delete your project's `assets` folder and remove the front-end oriented tasks from the `grunt/register` and `grunt/config` folders.  You can also run `sails new myCoolApi --no-frontend` to omit the `assets` folder and front-end-oriented Grunt tasks for future projects.  You can also replace your `sails-generate-frontend` module with alternative community generators, or create your own.  This allows `sails new` to create the boilerplate for native iOS apps, Android apps, Cordova apps, SteroidsJS apps, etc.\n\n> If you know you'll _never_ need any kind of web frontend, you can also [generate a new Sails app with `--no-frontend` at all](https://sailsjs.com/documentation/reference/command-line-interface/sails-new).\n\n\n### More info\n\n> More information on using Grunt to work with static assets: http://gruntjs.com/configuring-tasks\n\n\n\n<docmeta name=\"displayName\" value=\"tasks\">\n\n"
  },
  {
    "path": "docs/anatomy/views/.eslintrc.md",
    "content": "# views/.eslintrc\n\nThis file overrides your Sails app's top-level [ESLint](https://eslint.org/) configuration, specifically for the `views/` directory.  It simply extends [`assets/.eslintrc`](https://sailsjs.com/documentation/anatomy/assets/.eslintrc), but omits the `eol-last` rule.\n\nCertain CI environments, build systems, IDEs, and syntax-highlighting plugins for text editors run ESLint checks on inline `<script>` tags in HTML files to help catch common mistakes.  Which is great-- except that not all of these tools are quite smart enough to understand that each `<script>` tag isn't a separate JavaScript file.  And unfortunately, that can cause some weird issues when `eol-last` is enabled.\n\nLuckily, all it takes to disable the `eol-last` rule for these inline `<script>` tags is to include this `.eslintrc` file in the `views/` directory (which Sails does by default).\n\n<docmeta name=\"displayName\" value=\".eslintrc\">\n"
  },
  {
    "path": "docs/anatomy/views/404.ejs.md",
    "content": "# views/404.ejs\n\nThis is the default \"404: Not Found\" page. User agents that don't \"Accept\" HTML will see a JSON version instead. You can customize the control logic for your needs in `config/404.js`.\n\nSails considers a request to be in a \"404: Not Found\" state when a user requests a URL which doesn't match any of your app's routes or blueprints. You can also trigger this response from one of your controllers or policies with: `return res.notFound();`\n\n<docmeta name=\"displayName\" value=\"404.ejs\">\n"
  },
  {
    "path": "docs/anatomy/views/500.ejs.md",
    "content": "# views/500.ejs\n\nThis is the default \"500: Server Error\" page. User agents that don't \"Accept\" HTML will see a JSON version instead.\n\nYou can customize the control logic for your needs in `config/500.js` Sails considers a request in a \"500: Server Error\" state when your app throws a catchable error (not inside of an asynchronous callback).\n\nYou can also trigger this response from one of your controllers or policies with: `return res.serverError( e );` (where `e` is an optional message, error, or array of errors to include in the response).\n\n<docmeta name=\"displayName\" value=\"500.ejs\">\n"
  },
  {
    "path": "docs/anatomy/views/layouts/layout.ejs.md",
    "content": "# views/layouts/layout.ejs\n\nThis [Embedded JavaScript file](http://ejs.co/) acts as the default layout for all server side views rendered by your app.\n\nBefore one of your custom views is sent to the client, it is injected into this file.  It is this file that is actually sent to the client.\n\nFeel free to change this as you see fit.  Its also a great place to include JavaScript and CSS that you plan on using in every view.  This keeps you from having to include them in all your custom .ejs files.\n\n\n\n<docmeta name=\"displayName\" value=\"layout.ejs\">\n"
  },
  {
    "path": "docs/anatomy/views/layouts/layouts.md",
    "content": "# views/layouts/\n\nThis directory initially contains the default layout for your app, `layout.ejs`, but you can add any other layout files that you want to include in your app.\n\nFor more information on how to configure layouts, see [Concepts > Views > Layouts](https://sailsjs.com/documentation/concepts/views/layouts).\n\n\n<docmeta name=\"displayName\" value=\"layouts\">\n\n"
  },
  {
    "path": "docs/anatomy/views/pages/homepage.ejs.md",
    "content": "# views/pages/homepage.ejs\n\nThis is the actual template that is rendered by default when a user visits the base URL of your lifted app.  Notice the file extension?  It stands for [Embedded JavaScript](http://ejs.co/).  EJS is what Sails uses by default to render server side HTML views.  This can be changed in `config/views.js`.\n\nIf a new view you've created isn't rendering, make sure you've hooked it up in your `config/routes.js`.\n\nIf you're used to putting all your HTML in a single file, this might look funny.  You might be thinking \"Where are the head and body tags\"?  The answer is, `views/layouts/layout.ejs`.\n\n\n<docmeta name=\"displayName\" value=\"homepage.ejs\">\n"
  },
  {
    "path": "docs/anatomy/views/pages/pages.md",
    "content": "# views/pages/\n\nThis is the directory that holds the homepage, and any other page files you add to it.\n\nThis folder is mainly here for organizational purposes, to separate the page templates from layouts, error pages, etc.\n\n\n<docmeta name=\"displayName\" value=\"pages\">\n\n"
  },
  {
    "path": "docs/anatomy/views/views.md",
    "content": "# views/\n\nThis is the directory that holds all of your custom views.\n\nTo create a custom view, create a new directory inside of this then create a new .ejs file.  In order for it to be rendered by a client, you must either set up a route in `config/routes.js` or use the `res.view()` method inside of a custom controller action.\n\n\n\n<docmeta name=\"displayName\" value=\"views\">\n\n"
  },
  {
    "path": "docs/concepts/ActionsAndControllers/ActionsAndControllers.md",
    "content": "# Actions and controllers\n\n### Overview\n\n_Actions_ are responsible for responding to *requests* from a web browser, mobile application or any other system capable of communicating with a server.  They often act as a middleman between your [models](https://sailsjs.com/documentation/concepts/models-and-orm) and [views](https://sailsjs.com/documentation/concepts/views), and orchestrate the bulk of your project&rsquo;s [business logic](http://en.wikipedia.org/wiki/Business_logic): you can use actions to serve web pages, handle form submissions, manage 3rd party API requests, and everything in between.\n\nActions are bound to [routes](https://sailsjs.com/documentation/concepts/Routes) in your application. When a user agent requests a particular URL, the action bound to that route performs the business logic within and sends back a response.  For example, the `GET /hello` route in your application could be bound to an action like:\n\n```javascript\nasync function (req, res) {\n  return res.send('Hi there!');\n}\n```\n\nAny time a web browser navigates to the `/hello` URL on your app's server, the page will display the message: &ldquo;Hi there!&rdquo;.\n\n### Defining your action\nActions are defined in the `api/controllers/` folder and subfolders (we&rsquo;ll talk more about _controllers_ in a bit). In order for Sails to recognize a file as an action, the filename must be _kebab-cased_ (containing only lowercase letters, numbers and dashes).  When referencing an action in Sails (in most cases, when [binding it to a route](https://sailsjs.com/documentation/concepts/routes/custom-routes#?action-target-syntax)), use its path relative to `api/controllers`, without any file extension.  For example, to bind a route to an action located at `api/controllers/user/find.js`, you would point its URL to `user/find`.\n\n##### File extensions for actions\n\nBy default, Sails only knows how to interpret `.js` files, but you can customize your app to use things like [CoffeeScript](https://sailsjs.com/documentation/tutorials/using-coffee-script) or [TypeScript](https://sailsjs.com/documentation/tutorials/using-type-script) as well. An action can have any file extension that isn't `.md` (Markdown) and `.txt` (text).\n\n### Creating an action\n\nAction files can use one of two formats: _actions2_ (recommended) or _classic_.\n\n##### actions2\n\nSince the release of Sails v1.0, we recommend writing your actions in the more modern \"actions2\" syntax, which works much the same way as Sails [helpers](https://sailsjs.com/documentation/concepts/helpers). By defining your actions in this way, they are essentially self-documenting and self-validating.\n\nUsing actions2 provides several advantages:\n\n+ You can use [`sails generate action`](https://sailsjs.com/documentation/reference/command-line-interface/sails-generate) to quickly create an actions2 file\n+ You can clearly define the names and types of the request parameters the action expects, and those parameters will be automatically validated before the action is run\n+ All of the possible outcomes of running the action (`exits`) are clearly visible, without the need to dissect the code\n+ The code you write is not directly dependent on `req` and `res`, making it easier to re-use or abstract into a [helper](https://sailsjs.com/documentation/concepts/helpers)\n> Note that when using actions2, you can access the [request object](https://sailsjs.com/documentation/reference/request-req) as `this.req`.</br>Alternatively, you can pass `env` into the function with `inputs` and `exits` to get access to `req` without using `this.req`.\n\nIn a nutshell, your code will be standardized in a way that makes it easier to re-use and modify later.  And since you'll declare the action's parameters ahead of time, you'll be much less likely to expose edge cases and security holes.\n\nHere's an example of the actions2 format:\n\n```javascript\nmodule.exports = {\n\n  friendlyName: 'Welcome user',\n\n  description: 'Look up the specified user and welcome them, or redirect to a signup page if no user was found.',\n\n  inputs: {\n    userId: {\n      description: 'The ID of the user to look up.',\n      // By declaring a numeric example, Sails will automatically respond with `res.badRequest`\n      // if the `userId` parameter is not a number.\n      type: 'number',\n      // By making the `userId` parameter required, Sails will automatically respond with\n      // `res.badRequest` if it's left out.\n      required: true\n    }\n  },\n\n  exits: {\n    success: {\n      responseType: 'view',\n      viewTemplatePath: 'pages/welcome'\n    },\n    notFound: {\n      description: 'No user with the specified ID was found in the database.',\n      responseType: 'notFound'\n    }\n  },\n\n  fn: async function ({userId}) {\n\n    // Look up the user whose ID was specified in the request.\n    // Note that we don't have to validate that `userId` is a number;\n    // the machine runner does this for us and returns `badRequest`\n    // if validation fails.\n    var user = await User.findOne({ id: userId });\n\n    // If no user was found, respond \"notFound\" (like calling `res.notFound()`)\n    if (!user) { throw 'notFound'; }\n\n    // Display a personalized welcome view.\n    return {\n      name: user.name\n    };\n  }\n};\n```\n\n> Sails uses the [machine-as-action](https://github.com/treelinehq/machine-as-action) module to automatically create route-handling functions out of actions formatted like the example above.  See the [machine-as-action docs](https://github.com/treelinehq/machine-as-action#customizing-the-response) for more information.\n\n###### Exit signals\n\nIn an action, helper, or script, throwing anything will trigger the `error` exit by default. If you want to trigger any other exit, you can do so by throwing a \"special exit signal\". This will either be a string (the name of the exit), or an object with the name of the exit as the key and the output data as the value.\nFor example, instead of the usual syntax:\n\n```javascript\nreturn exits.hasConflictingCourses();\n```\n\nYou could use the shorthand:\n\n```javascript\nthrow 'hasConflictingCourses';\n```\n\nOr, to include output data:\n\n```javascript\nthrow { hasConflictingCourses: ['CS 301', 'M 402'] };\n```\n\nAside from being an easy-to-read shorthand, exit signals are especially useful if you're inside of a `for` loop, `forEach`, etc., but still want to exit through a particular exit.\n\n\n##### Classic actions\n\nIf you're working with an existing codebase or an app that was upgraded from v0.12, you may be more used to the classic action format. Classic actions are declared as functions with `req` and `res` arguments. When a client requests a route bound to this type of action, the function runs using the [incoming request object](https://sailsjs.com/documentation/reference/request-req) as the first argument (`req`), and the [outgoing response object](https://sailsjs.com/documentation/reference/response-res) as the second argument (`res`).\n\nHere's a sample action that looks up a user by ID, then either displays a \"welcome\" view or redirects to a signup page if the user can't be found:\n\n```javascript\nmodule.exports = async function welcomeUser (req, res) {\n\n  // Get the `userId` parameter from the request.\n  // This could have been set on the querystring, in\n  // the request body, or as part of the URL used to\n  // make the request.\n  var userId = req.param('userId');\n\n   // If no `userId` was specified, or it wasn't a number, return an error.\n  if (!_.isNumeric(userId)) {\n    return res.badRequest(new Error('No user ID specified!'));\n  }\n\n  // Look up the user whose ID was specified in the request.\n  var user = await User.findOne({ id: userId });\n\n  // If no user was found, redirect to signup.\n  if (!user) {\n    return res.redirect('/signup' );\n  }\n\n  // Display the welcome view, setting the view variable\n  // named \"name\" to the value of the user's name.\n  return res.view('welcome', {name: user.name});\n\n}\n```\n\n> You can use [`sails generate action`](https://sailsjs.com/documentation/reference/command-line-interface/sails-generate) with `--no-actions2` to quickly create a classic action.\n\n\n### Controllers\n\nFor simpler projects and prototypes, often the quickest way to get started writing Sails apps is to organize your actions into _controller files_.  A controller file is a [_PascalCased_](https://en.wikipedia.org/wiki/PascalCase) file whose name must end in `Controller`, containing a dictionary of actions.  For example, a  \"User Controller\" could be created at `api/controllers/UserController.js` file containing:\n\n```javascript\nmodule.exports = {\n  login: function (req, res) { ... },\n  logout: function (req, res) { ... },\n  signup: function (req, res) { ... },\n};\n```\n\nYou can use [`sails generate controller`](https://sailsjs.com/documentation/reference/command-line-interface/sails-generate#?sails-generate-controller-foo-action-1-action-2) to quickly create a controller file.\n\n##### File extensions for controllers\n\nJust like with action files, you can customize your app to use things like [CoffeeScript](https://sailsjs.com/documentation/tutorials/using-coffee-script) or [TypeScript](https://sailsjs.com/documentation/tutorials/using-type-script), although Sails only knows how to interpret `.js` files by default. A controller can have any file extension besides `.md` (Markdown) and `.txt` (text).\n\n\n### Standalone actions\n\nFor larger, more mature apps, _standalone actions_ may be a better approach than controller files.  In this scheme, rather than having multiple actions living in a single file, each action is in its own file in an appropriate subfolder of `api/controllers`.  For example, the following file structure would be equivalent to the  `UserController.js` file:\n\n```\napi/\n controllers/\n  user/\n   login.js\n   logout.js\n   signup.js\n```\n\nUsing standalone actions has several advantages over controller files:\n\n+ It's easier to see a clear overview of the actions in your app, because you can reference your project's file structure instead of scanning through individual controller files\n+ Each action file is smaller and easy to maintain, whereas controller files tend to grow as your app grows\n+ [Routing to standalone actions](https://sailsjs.com/documentation/concepts/routes/custom-routes#?action-target-syntax) in nested subfolders is more intuitive than routing to actions in controller files (`foo/bar/baz.js` vs. `foo/BarController.baz`)\n+ Blueprint index routes apply to top-level standalone actions, so you can create an `api/controllers/index.js` file and have it automatically bound to your app&rsquo;s `/` route (as opposed to creating an arbitrary controller file to hold the root action)\n\n\n### Keeping it lean\n\nIn the tradition of most MVC frameworks, mature Sails apps usually have \"thin\" controllers&mdash;that is, your action code ends up lean because reusable code has been moved into [helpers](https://sailsjs.com/documentation/concepts/helpers) or occasionally even extracted into separate node modules.  This approach can definitely make your app easier to maintain as it grows in complexity.\n\nBut at the same time, extrapolating code into reusable helpers _too_ early can cause maintenance issues that waste time and productivity.  The right answer lies somewhere in the middle.\n\nSails recommends this general rule of thumb:  **wait until you're about to use the same piece of code for the _third_ time before you extrapolate it into a separate helper.**  But, as with any dogma, use your judgement!  If the code in question is very long or complex, then it might make sense to pull it out into a helper much sooner.  Conversely, if you know what you're building is a quick, throwaway prototype, you might just copy and paste the code to save time.\n\n> Whether you're developing for passion or profit, at the end of the day, the goal is to make the best possible use of your time as an engineer.  Some days that means getting more code written, and other days it means looking out for the long-term maintainability of the project.  If you're not sure which of these goals is more important at your current stage of development, you might take a step back and give it some thought (better yet, have a chat with the rest of your team or [other folks building apps on Node.js/Sails](https://sailsjs.com/support)).\n\n<docmeta name=\"displayName\" value=\"Actions and controllers\">\n<docmeta name=\"nextUpLink\" value=\"/documentation/concepts/views\">\n<docmeta name=\"nextUpName\" value=\"Views\">\n"
  },
  {
    "path": "docs/concepts/ActionsAndControllers/GeneratingActions.md",
    "content": "# Generating controllers or standalone actions\n\nYou can use [`sails-generate`](https://sailsjs.com/documentation/reference/command-line-interface/sails-generate) from the Sails command line tool to quickly generate a controller, or even just an individual action.\n\n\n### Generating controllers\n\nFor example, to generate a controller:\n\n```sh\n$ sails generate controller user\n```\n\nSails will generate `api/controllers/UserController.js`:\n\n```javascript\n/**\n * UserController.js\n *\n * @description :: Server-side controller action for managing users.\n * @help        :: See https://sailsjs.com/documentation/concepts/controllers\n */\nmodule.exports = {\n\n}\n```\n\n### Generating standalone actions\n\nRun the following command to generate a standalone action:\n\n```sh\n$ sails generate action user/signup\ninfo: Created an action!\nUsing \"actions2\"...\n[?] https://sailsjs.com/docs/concepts/actions\n```\n\nSails will create `api/controllers/user/sign-up.js`:\n\n```javascript\n/**\n * user/sign-up.js\n *\n * @description :: Server-side controller action for handling incoming requests.\n * @help        :: See https://sailsjs.com/documentation/concepts/controllers\n */\nmodule.exports = {\n\n\n  friendlyName: 'Sign up',\n\n\n  description: '',\n\n\n  inputs: {\n\n  },\n\n\n  exits: {\n\n  },\n\n\n  fn: function (inputs, exits) {\n\n    return exits.success();\n\n  }\n\n\n};\n\n```\n\n\nOr, using the [classic actions](https://sailsjs.com/documentation/concepts/actions-and-controllers#?classic-actions) interface:\n\n\n```sh\n$ sails generate action user/signup --no-actions2\ninfo: Created a traditional (req,res) controller action, but as a standalone file\n```\n\nSails will create `api/controllers/user/sign-up.js`:\n\n```javascript\n/**\n * Module dependencies\n */\n\n// ...\n\n\n/**\n * user/signup.js\n *\n * Signup user.\n */\nmodule.exports = function signup(req, res) {\n\n  sails.log.debug('TODO: implement');\n  return res.ok();\n\n};\n```\n\n\n\n\n<docmeta name=\"displayName\" value=\"Generating actions and controllers\">\n"
  },
  {
    "path": "docs/concepts/ActionsAndControllers/RoutingToActions.md",
    "content": "# Routing to actions\n\n### Manual routing\n\nBy default, controller actions in your Sails app will be inaccessible to users until you _bind_ them to a route in your [`config/routes.js` file](https://sailsjs.com/documentation/reference/configuration/sails-config-routes).  When you bind a route, you specify a URL that users can access the action at, along with options like [CORS security settings](https://sailsjs.com/documentation/concepts/security/cors#?configuring-cors-for-individual-routes).\n\nTo bind a route to an action in the `config/routes.js` file, you can use the HTTP verb and path (i.e. the **route address**) as the key, and the action identity as the value (i.e. the **route target**).\n\nFor example, the following manual route will cause your app to trigger the `make` action in `api/controllers/SandwichController.js` whenever it receives a POST request to `/make/a/sandwich`:\n\n```js\n  'POST /make/a/sandwich': 'SandwichController.make'\n```\n\nIf you&rsquo;re using standalone actions, so that you had an `api/controllers/sandwich/make.js` file, a more intuitive syntax exists which uses the path to the action (relative to `api/controllers`):\n\n```js\n  'POST /make/a/sandwich': 'sandwich/make'\n```\n\nFor a full discussion of routing, please see the [routes documentation](https://sailsjs.com/documentation/concepts/Routes).\n\n### Automatic routing\n\nSails can also automatically bind routes to your controller actions so that a `GET` request to `/:actionIdentity` will trigger the action.  This is called _blueprint action routing_, and it can be activated by setting `actions` to `true` in the [`config/blueprints.js`](https://sailsjs.com/documentation/reference/configuration/sails-config-blueprints) file.  For example, with blueprint action routing turned on, a `signup` action saved in `api/controllers/UserController.js` or `api/controllers/user/signup.js` would be bound to a `/user/signup` route.  See the [blueprints documentation](https://sailsjs.com/documentation/reference/blueprint-api) for more information about Sails&rsquo; automatic route binding.\n\n\n<docmeta name=\"displayName\" value=\"Routing to actions\">\n"
  },
  {
    "path": "docs/concepts/Assets/Assets.md",
    "content": "# Assets\n\n### Overview\n\nAssets refer to [static files](http://en.wikipedia.org/wiki/Static_web_page) (js, css, images, etc.) on your server that you want to make accessible to the outside world.  In Sails, these files are placed in the [`assets/`](https://sailsjs.com/documentation/anatomy/assets) folder.  When you lift your app, add files to your `assets/` folder, or change existing assets, Sails' built-in asset pipeline processes and syncs those files to a hidden folder (`.tmp/public/`).\n\n> This intermediate step (moving files from `assets/` into `.tmp/public/`) allows Sails to pre-process assets for use on the client - things like LESS, CoffeeScript, SASS, spritesheets, Jade templates, etc.\n\nThe contents of this `.tmp/public` folder are what Sails actually serves at runtime.  This is roughly equivalent to the \"public\" folder in [express](https://github.com/expressjs), or the `www/` folder you might be familiar with from other web servers like Apache.\n\n\n### Static middleware\n\nBehind the scenes, Sails uses the [serve-static middleware](https://www.npmjs.com/package/serve-static) from Express to serve your assets. You can configure this middleware (e.g. to change cache settings) in [`/config/http.js`](https://sailsjs.com/documentation/reference/configuration/sails-config-http).\n\n##### `index.html`\nLike most web servers, Sails honors the `index.html` convention.  For instance, if you create `assets/foo.html` in a new Sails project, it will be accessible at `http://localhost:1337/foo.html`.  But if you create `assets/foo/index.html`, it will be available at both `http://localhost:1337/foo/index.html` and `http://localhost:1337/foo`.\n\n##### Precedence\nIt is important to note that the static [middleware](http://stephensugden.com/middleware_guide/) is installed **after** the Sails router.  So if you define a [custom route](https://sailsjs.com/documentation/concepts/Routes?q=custom-routes), but also have a file in your assets directory with a conflicting path, the custom route will intercept the request before it reaches the static middleware. For example, if you create `assets/index.html`, with no routes defined in your [`config/routes.js`](https://sailsjs.com/documentation/reference/configuration/sails-config-routes) file, it will be served as your home page.  But if you define a custom route, `'/': 'FooController.bar'`, that route will take precedence.\n\n\n\n<docmeta name=\"displayName\" value=\"Assets\">\n<docmeta name=\"nextUpLink\" value=\"/documentation/concepts/shell-scripts\">\n<docmeta name=\"nextUpName\" value=\"Shell Scripts\">\n\n"
  },
  {
    "path": "docs/concepts/Assets/DefaultTasks.md",
    "content": "# Default tasks\n\n### Overview\n\nThe asset pipeline bundled in Sails is a set of Grunt tasks configured with conventional defaults designed to make your project more consistent and productive. The entire frontend asset workflow is completely customizable, while providing some default tasks out of the box. Sails makes it easy to [configure new tasks](https://sailsjs.com/documentation/concepts/assets/task-automation#?task-configuration) to fit your needs.\n\n\nHere are a few things that the default Grunt configuration in Sails does to help you out:\n- Automatic LESS compilation\n- Cache busting\n- Optional automatic asset injection, minification, and concatenation\n- Creation of a web ready public directory\n- File watching and syncing\n- Transpilation of client-side JavaScript in production to allow use of >=ES6 syntax while maintaining broad browser compatibility\n- Optimization of assets in production\n\n\n### Default Grunt tasks\n\nBelow is a list of the Grunt tasks that are included by default in new Sails projects:\n\n##### clean\n\n> This grunt task is configured to clean out the contents in the `.tmp/public/` of your Sails project.\n\n> [usage docs](https://github.com/gruntjs/grunt-contrib-clean)\n\n##### hash\n\n> Creates and adds an unique hash to the end of a filename for cache busting.\n\n> [usage docs](https://github.com/jgallen23/grunt-hash/tree/0.5.0#grunt-hash)\n\n##### concat\n\n> Concatenates JavaScript and CSS files, and saves concatenated files in `.tmp/public/concat/` directory.\n\n> [usage docs](https://github.com/gruntjs/grunt-contrib-concat)\n\n##### copy\n\n> **dev task config**\n>\n> Copies all directories and files, except coffeescript and less files, from the sails assets folder into the `.tmp/public/` directory.\n\n> **build task config**\n>\n> Copies all directories and files from the .tmp/public directory into a www directory.\n\n> [usage docs](https://github.com/gruntjs/grunt-contrib-copy)\n\n##### cssmin\n\n> Minifies CSS files and places them into `.tmp/public/min/` directory.\n\n> [usage docs](https://github.com/gruntjs/grunt-contrib-cssmin)\n\n##### less\n\n> Compiles LESS files into CSS. Only the `assets/styles/importer.less` is compiled. This allows you to control the ordering yourself (i.e. import your dependencies, mixins, variables, resets, etc. before other stylesheets).\n\n> [usage docs](https://github.com/gruntjs/grunt-contrib-less)\n\n##### sails-linker\n\n> Automatically inject `<script>` tags for JavaScript files and `<link>` tags for CSS files.  Also automatically links an output file containing precompiled templates using a `<script>` tag. A much more detailed description of this task can be found [here](https://github.com/balderdashy/sails-generate-frontend/blob/master/docs/overview.md#a-litte-bit-more-about-sails-linking), but the big takeaway is that script and stylesheet injection is *only* done in files containing `<!--SCRIPTS--><!--SCRIPTS END-->` and/or `<!--STYLES--><!--STYLES END-->` tags.  These are included in the default **views/layouts/layout.ejs** file in a new Sails project.  If you don't want to use the linker for your project, you can simply remove those tags.\n\n> [usage docs](https://github.com/Zolmeister/grunt-sails-linker)\n\n##### sync\n\n> A grunt task to keep directories in sync. It is very similar to grunt-contrib-copy but tries to copy only those files that have actually changed. It specifically synchronizes files from the `assets/` folder to `.tmp/public/`, overwriting anything that's already there.\n\n> [usage docs](https://github.com/tomusdrw/grunt-sync)\n\n##### babel\n\n> This grunt task is configured to transpile any >=ES6 syntax in your front-end Javascript files into code compatible with older browsers.\n\n> [usage docs](https://github.com/babel/grunt-babel)\n\n##### uglify\n\n> Minifies client-side JavaScript assets.  Note that by default, this task will \"mangle\" all of your function and variable names (either by changing them to a much shorter name, or stripping them entirely).  This is usually desirable as it makes your code significantly smaller, but in some cases can lead to unexpected results (particularly when you expect an object's constructor to have a certain name).  To turn off or modify this behavior, [use the `mangle` option](https://www.npmjs.com/package/uglify-es#mangle-properties-options) when setting up this task.\n\n> [usage docs](https://github.com/gruntjs/grunt-contrib-uglify/tree/harmony)\n\n##### watch\n\n> Runs predefined tasks whenever watched file patterns are added, changed, or deleted. Watches for changes on files in the `assets/` folder, and re-runs the appropriate tasks (e.g. LESS compilation).  This allows you to see changes to your assets reflected in your app without having to restart the Sails server.\n\n> [usage docs](https://github.com/gruntjs/grunt-contrib-watch)\n\n\n<docmeta name=\"displayName\" value=\"Default tasks\">\n"
  },
  {
    "path": "docs/concepts/Assets/DisablingGrunt.md",
    "content": "# Disabling Grunt\n\nTo disable Grunt integration in Sails, simply delete your Gruntfile (and/or [`tasks/`](https://sailsjs.com/documentation/anatomy/tasks) folder). You can also disable the Grunt hook. Just set the `grunt` property to `false` in `.sailsrc` hooks, like this:\n\n```json\n{\n    \"hooks\": {\n        \"grunt\": false\n    }\n}\n```\n\n### Can I customize this for SASS, Angular, client-side Jade templates, etc.?\n\nYep! Just replace the relevant grunt task in your `tasks/` directory, or add a new one.  Like [SASS](https://github.com/sails101/using-sass), for example.\n\nIf you still want to use Grunt for other purposes, but don't want any of the default web front-end stuff, just delete your project's assets folder and remove the front-end oriented tasks from the `tasks/register/` and `tasks/config/` folders.  You can also run `sails new myCoolApi --no-frontend` to omit the assets folder and front-end-oriented Grunt tasks for future projects.  You can also replace your `sails-generate-frontend` module with alternative community generators, or [create your own](https://github.com/balderdashy/sails-generate-generator).  This allows `sails new` to create the boilerplate for native iOS apps, Android apps, Cordova apps, SteroidsJS apps, etc.\n\n\n\n<docmeta name=\"displayName\" value=\"Disabling Grunt\">\n\n### NOTE:\n\nWhen removing the Grunt hook above you must also specify the following in `.sailsrc` in order for your assets to be served, otherwise all assets will return a `404`.\n\n```json\n{\n    \"paths\": {\n        \"public\": \"assets\"\n    }\n}\n```\n"
  },
  {
    "path": "docs/concepts/Assets/TaskAutomation.md",
    "content": "# Task automation\n\n### Overview\n\nThe [`tasks/`](https://sailsjs.com/documentation/anatomy/tasks) directory contains a suite of [Grunt tasks](http://gruntjs.com/creating-tasks) and their [configurations](http://gruntjs.com/configuring-tasks).\n\nTasks are mainly useful for bundling front-end assets, (like stylesheets, scripts, & client-side markup templates) but they can also be used to automate all kinds of repetitive development chores, from [browserify](https://github.com/jmreidy/grunt-browserify) compilation to [database migrations](https://www.npmjs.org/package/grunt-db-migrate).\n\nSails bundles some [default tasks](https://sailsjs.com/documentation/grunt/default-tasks) for convenience, but with [literally hundreds of plugins](http://gruntjs.com/plugins) to choose from, you can use tasks to automate just about anything with minimal effort.  If someone hasn't already built what you need, you can always [author](http://gruntjs.com/creating-tasks) and [publish your own Grunt plugin](http://gruntjs.com/creating-plugins) to [npm](http://npmjs.org)!\n\n> If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins.\n\n\n### Asset pipeline\n\nThe asset pipeline is the place where you will organize the assets that will be injected into your views, and it can be found in the `tasks/pipeline.js` file. Configuring these assets is simple and uses Grunt [task file configuration](http://gruntjs.com/configuring-tasks#files) and [wildcard/glob/splat patterns](http://gruntjs.com/configuring-tasks#globbing-patterns). These are broken down into three sections:\n\n##### CSS Files to Inject\nThis is an array of CSS files to be injected into your HTML as `<link>` tags.  These tags will be injected between the `<!--STYLES--><!--STYLES END-->` comments in any view in which they appear.\n\n##### JavaScript Files to Inject\nThis is an array of JavaScript files that gets injected into your HTML as `<script>` tags.  These tags will be injected between the `<!--SCRIPTS--><!--SCRIPTS END-->` comments in any view in which they appear. The files get injected in the order in which they appear in the array, meaning you should place the path of dependencies before the file that depends on them.\n\n##### Template Files to Inject\nThis is an array of HTML files that will compiled to a JST function and placed in a jst.js file. This file then gets injected as a `<script>` tag in between the `<!--TEMPLATES--><!--TEMPLATES END-->` comments in your HTML.\n\n> The same Grunt wildcard/glob/splat patterns and task file configuration are used in some of the task configuration JS files themselves if you would like to change those too.\n\n### Task configuration\n\nConfigured tasks are the set of rules your Gruntfile will follow when run. They are completely customizable and are located in the [`tasks/config/`](https://sailsjs.com/documentation/anatomy/my-app/tasks/config) directory. You can modify, omit, or replace any of these Grunt tasks to fit your requirements. You can also add your own Grunt tasks&mdash;just add a `someTask.js` file in this directory to configure the new task, then register it with the appropriate parent task(s) (see files in `tasks/register/*.js`). Remember, Sails comes with a set of useful default tasks that are designed to get you up and running with no configuration required.\n\n##### Configuring a custom task.\n\nConfiguring a custom task into your project is very simple and uses Grunt&rsquo;s [config](http://gruntjs.com/api/grunt.config) and [task](http://gruntjs.com/api/grunt.task) APIs to allow you to make your task modular. Let&rsquo;s go through a quick example of creating a new task that replaces an existing task. Suppose we want to use the [Handlebars](http://handlebarsjs.com/) templating engine instead of the underscore templating engine that comes configured by default:\n\n* The first step is to install the Handlebars Grunt plugin using the following command in your terminal:\n\n```bash\nnpm install grunt-contrib-handlebars --save-dev\n```\n\n* Next, create a configuration file at `tasks/config/handlebars.js`. This is where we&rsquo;ll put our Handlebars configuration.\n\n```javascript\n// tasks/config/handlebars.js\n// --------------------------------\n// handlebar task configuration.\n\nmodule.exports = function(grunt) {\n\n  // We use the grunt.config api's set method to configure an\n  // object to the defined string. In this case the task\n  // 'handlebars' will be configured based on the object below.\n  grunt.config.set('handlebars', {\n    dev: {\n      // We will define which template files to inject\n      // in tasks/pipeline.js\n      files: {\n        '.tmp/public/templates.js': require('../pipeline').templateFilesToInject\n      }\n    }\n  });\n\n  // load npm module for handlebars.\n  grunt.loadNpmTasks('grunt-contrib-handlebars');\n};\n```\n\n* Replace the path to source files in asset pipeline. The only change here will be that Handlebars looks for files with the extension .hbs while underscore templates can be in simple HTML files.\n\n```javascript\n// tasks/pipeline.js\n// --------------------------------\n// asset pipeline\n\nvar cssFilesToInject = [\n  'styles/**/*.css'\n];\n\nvar jsFilesToInject = [\n  'js/socket.io.js',\n  'js/sails.io.js',\n  'js/connection.example.js',\n  'js/**/*.js'\n];\n\n// We change this glob pattern to include all files in\n// the templates/ direcotry that end in the extension .hbs\nvar templateFilesToInject = [\n  'templates/**/*.hbs'\n];\n\nmodule.exports = {\n  cssFilesToInject: cssFilesToInject.map(function(path) {\n    return '.tmp/public/' + path;\n  }),\n  jsFilesToInject: jsFilesToInject.map(function(path) {\n    return '.tmp/public/' + path;\n  }),\n  templateFilesToInject: templateFilesToInject.map(function(path) {\n    return 'assets/' + path;\n  })\n};\n```\n\n* Include the Handlebars task into the compileAssets and syncAssets registered tasks. This is where the JST task was being used; we will now replace it with the newly configured Handlebars task.\n\n```javascript\n// tasks/register/compileAssets.js\n// --------------------------------\n// compile assets registered grunt task\n\nmodule.exports = function (grunt) {\n  grunt.registerTask('compileAssets', [\n    'clean:dev',\n    'handlebars:dev',       // changed jst task to handlebars task\n    'less:dev',\n    'copy:dev',\n    'coffee:dev'\n  ]);\n};\n\n// tasks/register/syncAssets.js\n// --------------------------------\n// synce assets registered grunt task\n\nmodule.exports = function (grunt) {\n  grunt.registerTask('syncAssets', [\n    'handlebars:dev',      // changed jst task to handlebars task\n    'less:dev',\n    'sync:dev',\n    'coffee:dev'\n  ]);\n};\n```\n\n* Remove JST task config file. We are no longer using it so we can get rid of `tasks/config/jst.js`. Simply delete it from your project.\n\n> Ideally you should delete it from your project and your project's Node dependencies. This can be done by running this command in your terminal:\n```bash\nnpm uninstall grunt-contrib-jst --save-dev\n```\n\n### Task triggers\n\nIn [development mode](https://next.sailsjs.com/documentation/reference/configuration/sails-config#?sailsconfigenvironment), Sails runs the `default` task ([`tasks/register/default.js`](https://sailsjs.com/documentation/anatomy/tasks/register/default.js)).  This compiles LESS, CoffeeScript, and client-side JST templates, then links to them automatically from your app's dynamic views and static HTML pages.\n\nIn production, Sails runs the `prod` task ([`tasks/register/prod.js`](https://sailsjs.com/documentation/anatomy/tasks/register/prod.js)) which shares the same duties as `default`, but also minifies your app's scripts and stylesheets.  This reduces your application's load time and bandwidth usage.\n\nThese task triggers are [\"basic\" Grunt tasks](http://gruntjs.com/creating-tasks#basic-tasks) located in the [`tasks/register/`](https://sailsjs.com/documentation/anatomy/tasks/register) folder.  Below, you'll find the complete reference of all task triggers in Sails, and the command which kicks them off:\n\n##### `sails lift`\n\nRuns the **default** task (`tasks/register/default.js`).\n\n##### `sails lift --prod`\n\nRuns the **prod** task (`tasks/register/prod.js`).\n\n##### `sails www`\n\nRuns the **build** task (`tasks/register/build.js`) that compiles all the assets to `www` subfolder instead of `.tmp/public` using relative paths in references. This allows serving static content with Apache or Nginx instead of relying on ['www middleware'](https://sailsjs.com/documentation/concepts/Middleware).\n\n##### `sails www --prod` (production)\n\nRuns the **buildProd** task (`tasks/register/buildProd.js`) that does the same as **build** task but also optimizes assets.\n\nYou may run other tasks by specifying setting NODE_ENV and creating a task list in tasks/register/ with the same name.  For example, if NODE_ENV is QA, sails will run tasks/register/QA.js if it exists.\n\n\n<docmeta name=\"displayName\" value=\"Task automation\">\n"
  },
  {
    "path": "docs/concepts/Blueprints/Blueprint Actions.md",
    "content": "# Blueprint actions\n\nBlueprint actions (not to be confused with implicit [blueprint \"action\" _routes_](https://sailsjs.com/documentation/concepts/blueprints/blueprint-routes#?action-routes)) are generic actions designed to work with your models.  Think of them as the default behavior for your application.  For instance, if you have a `User.js` model then `find`, `create`, `update`, `destroy`, `populate`, `add` and `remove` actions exist implicitly, without you having to write them.\n\nBy default, the blueprint [RESTful routes](https://sailsjs.com/documentation/concepts/blueprints/blueprint-routes#?restful-routes) and [shortcut routes](https://sailsjs.com/documentation/concepts/blueprints/blueprint-routes#?shortcut-routes) are bound to their corresponding blueprint actions.  However, any blueprint action can be overridden for a particular controller by creating a custom action in that controller file (e.g. `ParrotController.find`).\n\nThe current version of Sails ships with the following blueprint actions:\n\n+ [find](https://sailsjs.com/documentation/reference/blueprint-api/find-where)\n+ [findOne](https://sailsjs.com/documentation/reference/blueprint-api/find-one)\n+ [create](https://sailsjs.com/documentation/reference/blueprint-api/create)\n+ [update](https://sailsjs.com/documentation/reference/blueprint-api/update)\n+ [destroy](https://sailsjs.com/documentation/reference/blueprint-api/destroy)\n+ [populate](https://sailsjs.com/documentation/reference/blueprint-api/populate)\n+ [add](https://sailsjs.com/documentation/reference/blueprint-api/add-to)\n+ [remove](https://sailsjs.com/documentation/reference/blueprint-api/remove-from)\n+ [replace](https://sailsjs.com/documentation/reference/blueprint-api/replace)\n\n### Socket notifications\n\nMost blueprint actions have realtime features that take effect if your app has WebSockets enabled.  For example, if the **find** blueprint action receives a request from a socket client, it will [subscribe](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/subscribe) that socket to future notifications.  Then, any time records are changed using blueprint actions like **update**, Sails will [publish](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/publish) certain notifications.\n\nThe best way to understand the behavior of a particular blueprint action is to read its [reference page](https://sailsjs.com/documentation/reference/blueprint-api) (or see the list above).  But if you're looking for more of a birds-eye view of how realtime features work in Sails's blueprint API, see [**Concepts > Realtime**](https://sailsjs.com/documentation/concepts/realtime).  (If you're OK with some details being out of date, you might even want to check out the [original \"Intro to Sails.js\" video from 2013](https://www.youtube.com/watch?v=GK-tFvpIR7c).)\n\n> For a more advanced breakdown of all notifications published by blueprint actions in Sails, see:\n> + [Chart A (scenarios vs. notification types)](https://docs.google.com/spreadsheets/d/10FV9plyHR4gE9xIomIZlF-YS1S54oHEdvH8ZmTC1Fnc/edit#gid=0)\n> + [Chart B (actions vs. recipients)](https://docs.google.com/spreadsheets/d/1B6i8aOoLNLtxJ4aeiA8GQ2lUQSvLOrP89RSLr7IAImw/edit#gid=0)\n\n### Overriding blueprint actions\n\nYou may also override any of the blueprint actions for a controller by defining a [custom action](https://sailsjs.com/documentation/concepts/actions-and-controllers) with the same name.\n\n```javascript\n// api/controllers/user/UserController.js\nmodule.exports = {\n\n  /**\n   * A custom action that overrides the built-in \"findOne\" blueprint action.\n   * As a dummy example of customization, imagine we were working on something in our app\n   * that demanded we tweak the format of the response data, and that we only populate two\n   * associations: \"company\" and \"friends\".\n   */\n  findOne: function (req, res) {\n\n    sails.log.debug('Running custom `findOne` action.  (Will look up user #'+req.param(\\'id\\')...');\n\n    User.findOne({ id: req.param('id') }).omit(['password'])\n    .populate('company', { select: ['profileImageUrl'] })\n    .populate('top8', { omit: ['password'] })\n    .exec(function(err, userRecord) {\n      if (err) {\n        switch (err.name) {\n          case 'UsageError': return res.badRequest(err);\n          default: return res.serverError(err);\n        }\n      }\n\n      if (!userRecord) { return res.notFound(); }\n\n      if (req.isSocket) {\n        User.subscribe(req, [user.id]);\n      }\n\n      return res.ok({\n        model: 'user',\n        luckyCoolNumber: Math.ceil(10*Math.random()),\n        record: userRecord\n      });\n    });\n  }\n\n}\n```\n\n> Alternatively, we could have created this as a standalone action at `api/controllers/user/findone.js` or used [actions2](https://sailsjs.com/documentation/concepts/actions-and-controllers#?actions-2).\n\n<docmeta name=\"displayName\" value=\"Blueprint actions\">\n"
  },
  {
    "path": "docs/concepts/Blueprints/Blueprint Routes.md",
    "content": "# Blueprint routes\n\nWhen you run `sails lift` with blueprints enabled, the framework inspects your models and configuration in order to [bind certain routes](https://sailsjs.com/documentation/concepts/Routes) automatically. These implicit blueprint routes (sometimes called \"shadow routes\", or even just \"shadows\") allow your app to respond to certain requests without you having to bind those routes manually in your `config/routes.js` file.  When enabled, the blueprint routes point to their corresponding blueprint *actions* (see \"Action routes\" below), any of which can be overridden with custom code.\n\nThere are four types of blueprint routes in Sails:\n\n### RESTful blueprint routes\nREST blueprints are the automatically generated routes Sails uses to expose a conventional REST API for a model, including `find`, `create`, `update`, and `destroy` actions. The path for RESTful routes is always `/:modelIdentity` or `/:modelIdentity/:id`.  These routes use the HTTP \"verb\" to determine the action to take.\n\nFor example, with [`rest`](https://sailsjs.com/documentation/reference/configuration/sails-config-blueprints#?routerelated-settings) enabled, having a `Boat` model in your app generates the following routes:\n\n+ **GET /boat** -> find boats matching criteria provided on the query string, using the [`find` blueprint](https://sailsjs.com/documentation/reference/blueprint-api/find-where).\n+ **GET /boat/:id** -> find a single boat with the given unique ID (i.e. primary key) value, using the [`findOne` blueprint](https://sailsjs.com/documentation/reference/blueprint-api/find-one).\n+ **POST /boat** -> create a new boat with the attributes provided in the request body, using the [`create` blueprint](https://sailsjs.com/documentation/reference/blueprint-api/create).\n+ **PATCH /boat/:id** -> update the boat with the given unique ID with the attributes provided in the request body, using the [`update` blueprint](https://sailsjs.com/documentation/reference/blueprint-api/update).\n+ **DELETE /boat/:id** -> destroy the boat with the given unique ID, using the [`destroy` blueprint](https://sailsjs.com/documentation/reference/blueprint-api/destroy).\n\nIf the `Boat` model has a &ldquo;to-many&rdquo; relationship with a `Driver` model through an attribute called `drivers`, then the following additional routes would be available:\n\n+ **GET /boat/:id/drivers** -> Finds the drivers' records associated to the boat record with the ID given as `:id` using the [`populate` blueprint](https://sailsjs.com/documentation/reference/blueprint-api/populate-where).\n+ **PUT /boat/:id/drivers/:fk** -> add the driver with the unique ID equal to the `:fk` value to the `drivers` collection of the boat with the ID given as `:id`, using the [`add` blueprint](https://sailsjs.com/documentation/reference/blueprint-api/add-to).\n+ **DELETE /boat/:id/drivers/:fk** -> remove the driver with the unique ID equal to the `:fk` value to the `drivers` collection of the boat with the ID given as `:id`, using the [`remove` blueprint](https://sailsjs.com/documentation/reference/blueprint-api/remove-from)\n+ **PUT /boat/:id/drivers** -> replace the entire `drivers` collection with the drivers whose unique IDs are contained in an array provided as the body of the request, using the [`replace` blueprint](https://sailsjs.com/documentation/reference/blueprint-api/replace).\n\nDepending on the style of app you generated, `rest` blueprint routes may be enabled by default, and could be suitable for use in a production scenario, as long as they are protected by [policies](https://sailsjs.com/documentation/concepts/Policies) to avoid unauthorized access. If you choose the \"Web app\" template, `rest` blueprint routes will not be enabled by default.\n\n> Be forewarned: Most web apps, microservices, and even REST APIs eventually need custom features that aren't really as simple as \"create\", \"update\", or \"destroy\".  If/when the time comes, don't be afraid to write your own custom actions.  Custom actions and routes can, and in many cases _should_, still be organized as a RESTful API, and they can be mixed and matched with blueprints when necessary.  Best of all, thanks to the introduction of [async/await in Node.js](https://gist.github.com/mikermcneil/c1028d000cc0cc8bce995a2a82b29245), writing custom actions no longer requires the use of callbacks.\n\n<!--\nIf we keep this, we should find a way to word it better:\nIn fact, unless you're already familiar with how to customize blueprints in Sails, it's usually a good idea to lean towards using custom actions any time you find yourself unsure whether to continue with REST blueprints or switch to a custom action for a particular feature, it's usually a good idea to lean towards custom actions.\n-->\n\n##### Notes\n\n> + If CSRF protection is enabled, you'll need to provide or disable a [CSRF token](https://sailsjs.com/documentation/concepts/security/csrf) for POST/PUT/DELETE actions, otherwise you will get a 403 Forbidden response.\n> + If your app contains a controller whose name matches that of your model, then you can override the default actions pointed to by the RESTful routes by providing your own controller actions.  For example, if you have an `api/controllers/BoatController.js` controller file containing a custom `find` action, then the `GET /boat` route will point at that action.\n> + Also, as usual, the same logic applies whether you're using controllers or standalone actions.  (As far as Sails is concerned, once an app has been loaded into memory and normalized in `sails lift`, all of its actions look the same no matter where they came from.)\n> + If your app contains a route in `config/routes.js` that matches one of the above RESTful routes, it will be used instead of the default route.\n\n### Shortcut blueprint routes\nShortcut routes are a simple (development-mode only) hack that provides access to your models from your browser's URL bar.\n\nThe shortcut routes are as follows:\n\n| Route | Blueprint Action | Example URL |\n| ----- | ----------------------- | ------- |\n| GET /:modelIdentity/find | [find](https://sailsjs.com/documentation/reference/blueprint-api/find-where) | `http://localhost:1337/user/find?name=bob`\n| GET /:modelIdentity/find/:id | [findOne](https://sailsjs.com/documentation/reference/blueprint-api/find-one) | `http://localhost:1337/user/find/123`\n| GET /:modelIdentity/create | [create](https://sailsjs.com/documentation/reference/blueprint-api/create) | `http://localhost:1337/user/create?name=bob&age=18`\n| GET /:modelIdentity/update/:id | [update](https://sailsjs.com/documentation/reference/blueprint-api/update) | `http://localhost:1337/user/update/123?name=joe`\n| GET /:modelIdentity/destroy/:id | [destroy](https://sailsjs.com/documentation/reference/blueprint-api/destroy) | `http://localhost:1337/user/destroy/123`\n| GET /:modelIdentity/:id/:association/add/:fk | [add](https://sailsjs.com/documentation/reference/blueprint-api/add-to) | `http://localhost:1337/user/123/pets/add/3`\n| GET /:modelIdentity/:id/:association/remove/:fk | [remove](https://sailsjs.com/documentation/reference/blueprint-api/remove-from) | `http://localhost:1337/user/123/pets/remove/3`\n| GET /:modelIdentity/:id/:association/replace?association=[1,2...] | [replace](https://sailsjs.com/documentation/reference/blueprint-api/replace) | `http://localhost:1337/user/123/pets/replace?pets=[3,4]`\n\n**Shortcut routes should always be disabled when Sails lifts in a production environment.  But they can be very handy during development, especially if you prefer not to use [the terminal](https://sailsjs.com/documentation/reference/command-line-interface/sails-console).**\n\n##### Notes\n\n> + Like RESTful routes, shortcut routes can be overridden by providing an action in a matching controller, or by providing a route in `config/routes.js`.\n> + the same _action_ is executed for similar RESTful/shortcut routes.  For example, the `POST /user` and `GET /user/create` routes that Sails creates when it loads `api/models/User.js` will respond by running the same code (even if you [override the blueprint action](https://sailsjs.com/documentation/reference/blueprint-api#?overriding-blueprints))\n> + When using a <a href=\"https://en.wikipedia.org/wiki/NoSQL\" target=\"_blank\">NoSQL</a> database (like <a href=\"https://docs.mongodb.com/\" target=\"_blank\">MongoDB</a>) with your model&rsquo;s [`schema` configuration](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?schema) set to `false`, shortcut routes will interpret any parameter value for an unknown attribute as a _string_.  Be careful doing `http://localhost:1337/game/create?players=2` if you don&rsquo;t have a `players` attribute with a `number` type!\n\n### Action shadow routes\n\nWhen action shadow routes (or \"action shadows\") are enabled, Sails will automatically create routes for your custom controller actions.  This is sometimes useful (especially early on in the development process) for speeding up backend development by eliminating the need to manually bind routes.  When enabled, GET, POST, PUT, and DELETE routes will be generated for every one of a controller's actions.\n\nFor example, if you have a `FooController.js` file with a `bar` method, then a `/foo/bar` route will automatically be created for you as long as `sails.config.blueprints.actions` is enabled.  Unlike RESTful and shortcut shadows, implicit, per-action shadow routes do *not* require that a controller has a corresponding model file.\n\nIf an `index` action exists, additional naked routes will be created for it. Finally, all `actions` blueprints support an optional path parameter, `id`, for convenience.\n\nSince Sails v1.0, action shadows are **disabled by default**. They can be OK for production-- however, if you'd like to continue to use controller/action autorouting in a production deployment, you must take great care not to inadvertently expose unsafe/unintentional controller logic to GET requests. You can easily turn off a particular method or path in your [`/config/routes.js`](https://sailsjs.com/documentation/anatomy/my-app/config/routes-js) file using the [response target syntax](https://sailsjs.com/documentation/concepts/routes/custom-routes#?response-target-syntax), for example:\n\n```javascript\n'POST /user': {response: 'notFound'}\n```\n\n##### Notes\n> + Action routes respond to _all_ HTTP verbs (GET, PUT, POST, etc.).  You can use `req.method` inside an action to determine which method was used.\n\n##### \"Index\" actions\n\nWhen action shadows (`sails.config.blueprints.actions`) are enabled, an additional, root shadow route is automatically exposed for any actions that happen to be named `index`.  For example, if you have a `FooController.js` file with an `index` action in it, a `/foo` shadow route will automatically be bound for that action.  Similarly, if you have a [standalone action](https://sailsjs.com/documentation/concepts/actions-and-controllers#?standalone-actions) at `api/controllers/foo/index.js`, a `/foo` route will be exposed automatically on its behalf.\n\n<!--\nTODO: check on this (it's unclear what point it was trying to get across):\n\n> Note:  Action shadows come with a special exception for top-level standalone actions.  For example, if you have a standalone action at `api/controllers/index.js`, it will be bound to a `/` shadow route automatically.\n\n-->\n\nRead more about [configuring blueprints in Sails](https://sailsjs.com/documentation/reference/configuration/sails-config-blueprints), including how to enable / disable different categories of blueprint routes.\n\n\n<docmeta name=\"displayName\" value=\"Blueprint routes\">\n"
  },
  {
    "path": "docs/concepts/Blueprints/Blueprints.md",
    "content": "# Blueprints\n\n### Overview\n\nLike any good web framework, Sails aims to reduce both the amount of code you write and the time it takes to get a functional app up and running.  _Blueprints_ are Sails&rsquo;s way of quickly generating API [routes](https://sailsjs.com/documentation/concepts/routes) and [actions](https://sailsjs.com/documentation/concepts/controllers#?actions) based on your application design.\n\nTogether, [blueprint routes](https://sailsjs.com/documentation/concepts/blueprints/blueprint-routes) and [blueprint actions](https://sailsjs.com/documentation/concepts/blueprints/blueprint-actions) constitute the **blueprint API**, the built-in logic that powers the [RESTful JSON API](http://en.wikipedia.org/wiki/Representational_state_transfer) you get every time you create a model and controller.\n\nFor example, if you create a `User.js` model file in your project, then with blueprints enabled you will be able to immediately visit `/user/create?name=joe` to create a user, and visit `/user` to see an array of your app's users.  All without writing a single line of code!\n\nBlueprints are a powerful tool for prototyping, but in many cases can be used in production as well, since they can be overridden, protected, extended or disabled entirely.\n\n### Up next\n\n+ [Read more](https://sailsjs.com/documentation/concepts/blueprints/blueprint-actions) about built-in blueprint actions\n+ [Read more](https://sailsjs.com/documentation/concepts/blueprints/blueprint-routes) about implicit \"shadow\" routes and how to configure or override them\n\n<docmeta name=\"displayName\" value=\"Blueprints\">\n"
  },
  {
    "path": "docs/concepts/Configuration/Configuration.md",
    "content": "# Configuration\n\n### Overview\n\nWhile Sails dutifully adheres to the philosophy of [convention-over-configuration](http://en.wikipedia.org/wiki/Convention_over_configuration), it is important to understand how to customize those handy defaults from time to time.  For almost every convention in Sails, there is an accompanying set of configuration options that allow you to adjust or override things to fit your needs.\n\n> Here looking for a particular setting?  Head over to [Reference > Configuration](https://sailsjs.com/documentation/reference/configuration) to see a complete guide to all configuration options available in Sails.\n\nSails apps can be [configured programmatically](https://github.com/mikermcneil/sails-generate-new-but-like-express/blob/master/templates/app.js#L15), by specifying [environment variables](http://en.wikipedia.org/wiki/Environment_variable) or command-line arguments, by changing the local or global [`.sailsrc` files](https://sailsjs.com/documentation/anatomy/.sailsrc), or (most commonly) using the boilerplate configuration files conventionally located in the [`config/`](https://sailsjs.com/documentation/anatomy/config) folder of new projects. The authoritative, merged-together configuration used in your app is available at runtime on the `sails` global as `sails.config`.\n\n\n### Standard configuration files (`config/*`)\n\nA number of configuration files are generated in new Sails apps by default.  These boilerplate files include a number of inline comments, which are designed to provide a quick, on-the-fly reference without having to jump back and forth between the docs and your text editor.\n\nIn most cases, the top-level keys on the `sails.config` object (e.g. `sails.config.views`) correspond to a particular configuration file (e.g. `config/views.js`) in your app; however configuration settings may be arranged however you like across the files in your `config/` directory.  The important part is the name (i.e. key) of the setting&mdash;not the file it came from.\n\nFor instance, let's say you add a new file, `config/foo.js`:\n\n```js\n// config/foo.js\n// The object below will be merged into `sails.config.blueprints`:\nmodule.exports.blueprints = {\n  shortcuts: false\n};\n```\n\nFor an exhaustive reference of individual configuration options, and the file they live in by default, check out the reference pages in this section, or take a look at [\"`config/`\"](https://sailsjs.com/documentation/anatomy/config) in [The Anatomy of a Sails App](https://sailsjs.com/documentation/anatomy) for a higher-level overview.\n\n### Environment-specific files (`config/env/*`)\n\nSettings specified in the standard configuration files will generally be available in all environments (i.e. development, production, test, etc.).  If you'd like to have some settings take effect only in certain environments, you can use the special environment-specific files and folders:\n\n* Any files saved under the `/config/env/<environment-name>` folder will be loaded *only* when Sails is lifted in the `<environment-name>` environment.  For example, files saved under `config/env/production` will only be loaded when Sails is lifted in production mode.\n* Any files saved as `config/env/<environment-name>.js` will be loaded *only* when Sails is lifted in the `<environment-name>` environment, and will be merged on top of any settings loaded from the environment-specific subfolder.  For example, settings in `config/env/production.js` will take precedence over those in the files in the  `config/env/production` folder.\n\nBy default, your app runs in the \"development\" environment.  The recommended approach for changing your app's environment is by using the `NODE_ENV` environment variable:\n```\nNODE_ENV=production node app.js\n```\n\n> The `production` environment is special: depending on your configuration, it enables compression, caching, minification, etc.\n>\n> Also note that if you are using `config/local.js`, the configuration exported in that file takes precedence over environment-specific configuration files.\n\n\n### The `config/local.js` file\n\nYou may use the `config/local.js` file to configure a Sails app for your local environment (your laptop, for example).  The settings in this file take precedence over all other config files except [.sailsrc](https://sailsjs.com/documentation/concepts/configuration/using-sailsrc-files).  Since they're intended only for local use, they should not be put under version control (and are included in the default `.gitignore` file for that reason).  Use `local.js` to store local database settings, change the port used when lifting an app on your computer, etc.\n\nSee [Concepts > Configuration > The local.js file](https://sailsjs.com/documentation/concepts/configuration/the-local-js-file) for more information.\n\n\n### Accessing `sails.config` in your app\n\nThe `config` object is available on the Sails app instance (`sails`).  By default, this is exposed on the [global scope](https://sailsjs.com/documentation/concepts/globals) during lift, and therefore available from anywhere in your app.\n\n##### Example\n```javascript\n// This example checks that, if we are in production mode, csrf is enabled.\n// It throws an error and crashes the app otherwise.\nif (sails.config.environment === 'production' && !sails.config.security.csrf) {\n  throw new Error('STOP IMMEDIATELY ! CSRF should always be enabled in a production deployment!');\n}\n```\n\n### Setting `sails.config` values directly using environment variables\n\nIn addition to using configuration _files_, you can set individual configuration values on the command line when you lift Sails by prefixing the config key names with `sails_`, and separating nested key names with double underscores (`__`).  Any environment variable formatted this way will be parsed as JSON (if possible). For example, you could do the following to set the [allowed CORS origins](https://sailsjs.com/documentation/concepts/security/cors) (`sails.config.security.cors.allowOrigins`) to `[\"http://somedomain.com\",\"https://anotherdomain.com:1337\"]` on the command line:\n\n```javascript\nsails_security__cors__allowOrigins='[\"http://somedomain.com\",\"https://anotherdomain.com:1337\"]' sails console\n```\n\n> Note the use of double quotes to indicate strings within the JSON-encoded value, and the single quotes surrounding the whole value so that it is passed correctly to Sails from the console.\n\nThis value will be in effect _only_ for the lifetime of this particular Sails instance, and will override any values in the configuration files.\n\nAlso note that configuration specified using environment variables does _not_ automatically apply to Sails instances that are started [programmatically](https://sailsjs.com/documentation/concepts/programmatic-usage).\n\n> There are a couple of special exceptions to the above rule: `NODE_ENV` and `PORT`.\n> + `NODE_ENV` is a convention for any Node.js app.  When set to `'production'`, it sets [`sails.config.environment`](https://sailsjs.com/documentation/reference/configuration/sails-config#?sailsconfigenvironment).\n> + Similarly, `PORT` is just another way to set [`sails.config.port`](https://sailsjs.com/documentation/reference/configuration/sails-config#?sailsconfigport).  This is strictly for convenience and backwards compatibility.\n>\n> Here's a relatively common example where you might use both of these environment variables at the same time:\n>\n> ```bash\n> PORT=443 NODE_ENV=production sails lift\n> ```\n>\n> When present in the current process environment, `NODE_ENV` and `PORT` will apply to any Sails app that is started via the command line or programmatically, unless explicitly overridden.\n\nEnvironment variables are one of the most powerful ways to configure your Sails app.  Since you can customize just about any setting (as long as it's JSON-serializable), this approach solves a number of problems, and is our core team's recommended strategy for production deployments.  Here are a few:\n\n+ Using environment variables means you don't have to worry about checking in your production database credentials, API tokens, etc.\n+ This makes changing Postgresql hosts, Mailgun accounts, S3 credentials, and other maintenance straightforward, fast, and easy; plus you don't need to change any code or worry about merging in downstream commits from other people on your team\n+ Depending on your hosting situation, you may be able to manage your production configuration through a UI (most PaaS providers like [Heroku](http://heroku.com) or [Modulus](https://modulus.io) support this, as does [Azure Cloud](https://azure.microsoft.com/en-us/).)\n\n\n### Setting `sails.config` values using command line arguments\n\nFor situations where setting an environment variable on the command line may not be practical (such as some Windows systems), you can use regular command line arguments to set configuration options.  To do so, specify the name of the option prefixed by two dashes (`--`), with nested key names separated by dots.  Command line arguments are parsed using [minimist](https://github.com/substack/minimist/tree/0.0.10), which does _not_ parse JSON values like arrays or dictionaries, but will handle strings, numbers and booleans (using a special syntax).  Some examples:\n\n```javascript\n// Set the port to 1338\nsails lift --port=1338\n\n// Set a custom \"email\" value to \"foo@bar.com\":\nsails lift --custom.email='foo@bar.com'\n\n// Turn on CSRF support\nsails lift --security.csrf\n\n// Turn off CSRF support\nsails lift --no-security.csrf\n\n// This won't work; it'll just try to set the value to the string \"[1,2,3]\"\nsails lift --custom.array='[1,2,3]'\n```\n\n### Custom configuration\n\nYou can also leverage Sails's configuration loader to manage your own custom settings.  See [sails.config.custom](https://sailsjs.com/documentation/reference/configuration/sails-config-custom) for more information.\n\n\n\n### Configuring the command line interface\n\nWhen it comes to configuration, most of the time you'll be focused on managing the runtime settings for a particular app: the port, database setup, and so forth.  However it can also be useful to customize the Sails CLI itself; to simplify your workflow, reduce repetitive tasks, perform custom build automation, etc.  Thankfully, Sails v0.10 added a powerful new tool to do just that.\n\nThe [`.sailsrc` file](https://sailsjs.com/documentation/anatomy/.sailsrc) is unique from other configuration sources in Sails in that it may also be used to configure the Sails CLI&mdash;either system-wide, for a group of directories, or only when you are `cd`'ed into a particular folder.  The main reason to do this is to customize the [generators](https://sailsjs.com/documentation/concepts/extending-sails/Generators) that are used when `sails generate` and `sails new` are run, but it can also be useful to install your own custom generators or apply hard-coded config overrides.\n\nAnd since Sails will look for the \"nearest\" `.sailsrc` in the ancestor directories of the current working directory, you can safely use this file to configure sensitive settings you can't check in to your cloud-hosted code repository (_like your **database password**_.)  Just include a `.sailsrc` file in your \"$HOME\" directory.  See [the docs on `.sailsrc`](https://sailsjs.com/documentation/anatomy/.sailsrc) files for more information.\n\n\n### Order of precedence for configuration\n\nDepending on whether you're starting a Sails app from the command line using `sails lift` or `node app.js`, or programmatically using [`sails.lift()`](https://sailsjs.com/documentation/reference/application/advanced-usage/sails-lift) or [`sails.load()`](https://sailsjs.com/documentation/reference/application/advanced-usage/sails-load), Sails will draw its configuration from a number of sources, in a certain order.\n\n##### Order of precedence when starting via `sails lift` or `node app.js` (in order from highest to lowest priority):\n\n+ command line options parsed by [minimist](https://github.com/substack/minimist/tree/0.0.10); e.g. `sails lift --custom.mailgun.apiToken='foo'` becomes `sails.config.custom.mailgun.apiToken`\n+ [environment variables](https://en.wikipedia.org/wiki/Environment_variable) prefixed with `sails_`, and using double underlines to indicate dots; e.g.: `sails_port=1492 sails lift` ([A few more examples](https://gist.github.com/mikermcneil/92769de1e6c10f0159f97d575e18c6cf))\n+ a [`.sailsrc` file](https://sailsjs.com/documentation/concepts/configuration/using-sailsrc-files) in your app's directory, or the first found looking in `../`, `../../` etc.\n+ a global `.sailsrc` file in your home folder (e.g. `~/.sailsrc`).\n+ any existing `config/local.js` file in your app\n+ any existing `config/env/*` files in your app that match the name of your current NODE_ENV environment (defaulting to `development`)\n+ any other files in your app's `config/` directory (if one exists)\n\n##### Order of precedence when starting programmatically (in order from highest to lowest priority):\n\n+ an optional dictionary (`{}`) of configuration overrides passed in as the first argument to `.lift()` or `.load()`\n+ any existing `config/local.js` file in your app\n+ any existing `config/env/*` files in your app that match the name of your current NODE_ENV environment (defaulting to `development`)\n+ any other files in your app's `config/` directory (if one exists)\n\n\n### Notes\n> The built-in meaning of the settings in `sails.config` are, in some cases, only interpreted by Sails during the \"lift\" process.  In other words, changing some options at runtime will have no effect.  To change the port your app is running on, for instance, you can't just change `sails.config.port`&mdash;you'll need to change or override the setting in a configuration file or as a command line argument, etc., then restart the server.\n\n\n\n<docmeta name=\"displayName\" value=\"Configuration\">\n<docmeta name=\"nextUpLink\" value=\"/documentation/concepts/policies\">\n<docmeta name=\"nextUpName\" value=\"Policies\">\n"
  },
  {
    "path": "docs/concepts/Configuration/localjsfile.md",
    "content": "# The `config/local.js` file\n\nThe config/local.js file is useful for configuring a Sails app for your local environment (your laptop, for example). This would be a good place to store settings like database or email passwords that apply only to you, and shouldn't be shared with others in your organization.\n\nThese settings take precedence over all other files in `config/`, including those in the `env/` subfolder.\n \nNote:\n> By default, `config/local.js` is included in your `.gitignore`, so if you're using git as a version control solution for your Sails app, keep in mind that this file won't be committed to your repository!\n>\n> Good news is, that means you can specify configuration for your local machine in this file without inadvertently committing personal information (like database passwords) to the repo.  Plus, this prevents other members of your team from commiting their local configuration changes on top of yours.\n>\n> In a production environment, you'll probably want to leave this file out entirely and configure all of your production overrides using `env/production.js`, or environment variables, or a combination of both.\n\n<docmeta name=\"displayName\" value=\"The local.js file\">\n"
  },
  {
    "path": "docs/concepts/Configuration/usingsailsrcfiles.md",
    "content": "# Using .sailsrc files\n\nIn addition to the other methods of configuring your app, you can also specify configuration for one or more apps in `.sailsrc` file(s).  These files are useful for configuring the Sails command-line, and especially for generators.  They also allow you to apply _global_ configuration settings for generators in ANY of the Sails apps you run on your computer, if desired.\n\nWhen the Sails CLI runs a command, it first looks for  `.sailsrc` files (in either JSON or [.ini](http://en.wikipedia.org/wiki/INI_file) format) in the current directory and in your home folder (i.e. `~/.sailsrc`) (every newly generated Sails app comes with a boilerplate `.sailsrc` file).  Then it merges them in to its existing configuration.\n\n> Actually, Sails looks for `.sailsrc` files in a few other places (following [rc conventions](https://github.com/dominictarr/rc#standards)).  You can put a `.sailsrc` file at any of those paths, if you want it to apply globally to all Sails apps.  That said, stick to convention when you can- the best place to put a global `.sailsrc` file is in your home directory (i.e. `~/.sailsrc`).\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"Using `.sailsrc` files\">\n\n"
  },
  {
    "path": "docs/concepts/Deployment/Deployment.md",
    "content": "# Deployment\n\n### Before you deploy\n\nBefore you launch any web application, you should ask yourself a few questions:\n\n+ What is your expected traffic?\n+ Are you contractually required to meet any uptime guarantees, e.g. a Service Level Agreement (SLA)?\n+ What sorts of user agents will be \"hitting\" your infrastructure? These might be:\n  + desktop web browsers\n  + mobile web browsers (What form factors?  Tablet? Handset? Both?)\n  + embedded browsers from smart TVs or gaming consoles\n  + Android/iOS/Windows Phone apps\n  + PhoneGap/Electron apps\n  + Developers (cURL, Postman, AJAX requests, WebSocket front-end apps)\n  + other devices (TVs, watches, toasters...)\n+ What kinds of things will they be requesting (e.g. HTML, JSON, XML)?\n+ Will you be taking advantage of realtime features with Socket.io (e.g. chat, realtime analytics, in-app notifications/messages)?\n+ How are you tracking crashes and errors? Are you using `sails.log()` in combination with a hosted service like [Papertrail](https://papertrailapp.com/)?  <!--Or are you using a custom logger from NPM like [Winston](https://github.com/winstonjs/winston)?  Or even easier, sticking with built-in logging from `sails.log()` in combination with a hosted service like [Papertrail](https://papertrailapp.com/)?-->\n+ Have you tried lifting locally with the `NODE_ENV` environment variable set to \"production\"? (A quick way to test this out is to run `NODE_ENV=production node app` (or, as a shortcut: `sails lift --prod`).)\n\n\n### Configuring your app for production\n\nYou can provide configuration which only applies in production in a [few different ways](https://sailsjs.com/documentation/reference/configuration).  Most apps find themselves using a mix of environment variables and `config/env/production.js`.  Regardless of how you go about it, this section and the [Scaling section](https://sailsjs.com/documentation/concepts/deployment/scaling) of the documentation cover the configuration settings you should review before going to production.\n\n\n\n### Deploying on a single server\n\nNode.js is pretty darn fast.  For many apps, one server is enough to handle the expected traffic&mdash;initailly, at least.\n\n> This section focuses on _single-server Sails deployment_.  This kind of deployment is inherently limited in scale.  See [Scaling](https://sailsjs.com/documentation/concepts/deployment/scaling) for information about deploying your Sails/Node app behind a load balancer.\n\nMany teams decide to deploy their production app behind a load balancer or proxy (in a PaaS like Heroku or Now, maybe, or behind an nginx server).  This is often the right approach since it helps future-proof your app in case your scalability needs change and you need to add more servers.  If you are using a load balancer or proxy, there are a few things in the list below that you can ignore:\n\n+ Don't worry about configuring Sails to use an SSL certificate.  SSL will almost always be resolved at your load balancer/proxy server or by your PaaS provider.\n+ You _probably_ don't need to worry about setting your app to run on port 80 (if not behind a proxy like nginx). Most PaaS providers automatically figure out the port for you.  If you are using a proxy server, please refer to its documentation (whether or not you need to configure the port for your Sails app depends on how you set things up and can vary widely based on your needs).\n\n> If your app uses sockets and you're using nginx, be sure to configure it to relay websocket messages to your server. You can find guidance on proxying WebSockets in [nginx's docs on the subject](http://nginx.org/en/docs/http/websocket.html).\n\n\n##### Set the `NODE_ENV` environment variable to `'production'`\n\nSetting your app's environment config to `'production'` tells Sails to get its game face on&mdash;i.e. that your app is running in a production environment.  This is, hands down, the most important step. If you only have the time to change _one setting_ before deploying your Sails app, _this should be that setting_!\n\nWhen your app is running in a production environment:\n  + middleware and other dependencies baked into Sails switch to using more efficient code.\n  + all of your [models' migration settings](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings) are forced to `migrate: 'safe'`.  This is a failsafe to protect against inadvertently damaging your production data during deployment.\n  + your asset pipeline runs in production mode (if relevant).  Out of the box, that means your Sails app will compile all stylesheets, client-side scripts, and precompiled JST templates into minified `.css` and `.js` files to decrease page load times and reduce bandwidth consumption.\n  + error messages and stack traces from `res.serverError()` will still be logged, but will not be sent in the response (this is to prevent a would-be attacker from accessing any sensitive information, such as encrypted passwords or the path where your Sails app is located on the server's file system).\n\n\n>**Note:**\n>If you set [`sails.config.environment`](https://sailsjs.com/documentation/reference/configuration/sails-config#?sailsconfigenvironment) to `'production'` some other way, that's totally cool.  Just note that Sails will either set the `NODE_ENV` environment variable to `'production'` for you automatically, or it will log a warning&mdash;so keep an eye on the console! The reason this environment variable is so important is that it is a universal convention in Node.js, regardless of the framework you are using.  Built-in middleware and dependencies in Sails _expect_ `NODE_ENV` to be set in production, otherwise they use their less efficient code paths that were designed for development use only.\n\n##### Set a `sails.config.sockets.onlyAllowOrigins` value\n\nIf you have sockets enabled for your app (that is, you have the `sails-hook-sockets` module installed), then for security reasons you'll need to set `sails.config.sockets.onlyAllowOrigins` to the array of origins that should be allowed to connect to your app via websockets.  You&rsquo;ll likely set this in your app&rsquo;s `config/env/production.js` file.  See the [socket configuration documentation](https://sailsjs.com/documentation/reference/configuration/sails-config-sockets) for more info on `onlyAllowOrigins`.\n\n##### Configure your app to run on port 80\n\nWhether it's by using the `sails_port` environment variable, setting the `--port` command-line option, or changing your production config file(s), add the following to the top level of your Sails config:\n\n```javascript\nport: 80\n```\n\n> As mentioned above, ignore this step if your app will be running behind a load balancer or proxy.\n\n\n\n##### Set up production database(s) for your models\n\nIf all of your app's models use the default datastore, then setting up your production database is as simple as configuring `sails.config.datastores.default` in the [config/env/production.js](https://sailsjs.com/documentation/concepts/configuration#?environmentspecific-files-config-env) file with the correct settings.\n\nIf your app is using more than one database, your process will be similar.  For every datastore used by the app, add an item to the `sails.config.datastores` dictionary in [config/env/production.js](https://sailsjs.com/documentation/concepts/configuration#?environmentspecific-files-config-env).\n\nKeep in mind that if you are using version control (git, for example), then any sensitive credentials (such as database passwords) will be checked in to the repo if you include them in your app's configuration files.  A common solution to this problem is to provide certain sensitive configuration settings as environment variables.  See [Configuration](https://sailsjs.com/documentation/concepts/configuration) for more information.\n\nIf you are using a relational database such as MySQL, there is an additional step.  Remember how Sails sets all your models to `migrate:safe` when run in production?  That means no auto-migrations are run when lifting the app... which means that by default your tables won't exist.  A common approach in dealing with this during the first-time setup of a relational database for your Sails app is as follows:\n  + Create the database on the production database server (e.g. `frenchfryparty`).\n  + Configure your app locally to use this production database, but _don't set the environment to `'production'`, and leave your models' configuration set to `migrate: 'alter'`_.  Now run `sails lift` **once**-- and when the local server finishes lifting, kill it.\n    + **Be careful!**  You should only do this when there is _no data_ in the production database.\n\nIf this makes you nervous or if you can't connect to the production database remotely, you can skip the steps above.  Instead, simply dump your local schema and import it into the production database.\n\n\n##### Enable CSRF protection\n\nProtecting against CSRF is an important security measure for Sails apps.  If you haven't already been developing with CSRF protection enabled (see [`sails.config.security.csrf`](https://sailsjs.com/documentation/reference/configuration/sails-config-security#?sailsconfigsecuritycsrf)), be sure to [enable CSRF protection](https://sailsjs.com/documentation/concepts/security/csrf#?enabling-csrf-protection) before going to production.\n\n\n\n##### Enable SSL\n\nIf your API or website does anything that requires authentication, you should use SSL in production.  To configure your Sails app to use an SSL certificate, use [`sails.config.ssl`](https://sailsjs.com/documentation/reference/configuration/sails-config).\n\n> As mentioned above, ignore this step if your app will be running behind a load balancer or proxy.\n\n\n\n##### Lift your app\n\nThe last step of deployment is actually starting the server. For example:\n\n```bash\nNODE_ENV=production node app.js\n```\n\nOr if you're more comfortable with command-line options you can use `--prod`:\n\n```bash\nnode app.js --prod\n# (Sails will set `NODE_ENV` automatically)\n```\n\nAs you can see, instead of `sails lift` you should start your Sails app with `node app.js` in production.  This way, instead of relying on having access to the `sails` command-line tool, your app just runs the `app.js` file bundled in your Sails app (which does the same thing).\n\n\n##### ...And keep it lifted\n\nUnless you're deploying to a PaaS like Heroku, you will want to use a tool like [`pm2`](http://pm2.keymetrics.io/) or [`forever`](https://github.com/foreverjs/forever) to make sure your app server will start back up if it crashes.  Regardless of the daemon you choose, you'll want to make sure that it starts the server as described above.\n\n\n\n### Next steps\n+ [Security](https://sailsjs.com/documentation/concepts/security)\n+ [Hosting options](https://sailsjs.com/documentation/concepts/deployment/hosting)\n+ [Scaling your Sails/Node.js app](https://sailsjs.com/documentation/concepts/deployment/scaling)\n+ [Complete API reference](https://sailsjs.com/documentation/reference)\n\n\n<docmeta name=\"displayName\" value=\"Deployment\">\n"
  },
  {
    "path": "docs/concepts/Deployment/FAQ.md",
    "content": "# FAQ\n\n\n### Can I use environment variables?\n\nYes! Like any Node app, your environment variables are available as `process.env`.\n\nSails also comes with built-in support for creating your own custom configuration settings that will be exposed directly on `sails.config`.  And whether custom or built-in, any of the configuration properties in `sails.config` can be overridden using environment variables.  See the conceptual documentation on [Configuration](https://sailsjs.com/documentation/concepts/configuration) for details.\n\n\n### Where do I put my production database credentials?  Other settings?\n\nThe easiest way to add configuration to your Sails app is by modifying the files in `config/` or adding new ones. Sails supports environment-specific configuration loading out of the box, so you can use `config/env/production.js`.  Again, see the conceptual documentation on [Configuration](https://sailsjs.com/documentation/concepts/configuration) for details.\n\nBut sometimes you don't want to put certain configuration information into your repository.  **The best place to put this kind of configuration is in environment variables.**\n\nThat said, for development (e.g. on your laptop) using environment variables can sometimes be cumbersome.  So for your other deployment/machine-specific settings, namely any kind of credentials you want to keep private, you can also use your `config/local.js` file.  This file is included in your `.gitignore` file by default, which helps prevent you from inadvertently commiting your credentials to your code repository.\n\n**config/local.js**\n```javascript\n// Local configuration\n// \n// Included in the .gitignore by default,\n// this is where you include configuration overrides for your local system\n// or for a production deployment.\n//\n// For example, to use port 80 on the local machine, override the `port` config\nmodule.exports = {\n    port: 80,\n    environment: 'production',\n    adapters: {\n        mysql: {\n            user: 'root',\n            password: '12345'\n        }\n    }\n}\n```\n\n\n\n### How do I get my Sails app on the server?\n\nIf you are using a Paas like Heroku or Modulus, it's easy: just follow their instructions!\n\nOtherwise, get the IP address of your server and `ssh` onto it.  Then `npm install -g sails` and `npm install -g forever` to install Sails and `forever` globally from NPM for the first time on the server. Finally `git clone` your project (or `scp` it onto the server if it's not in a git repo) into a new folder on the server, `cd` into it, and then run `forever start app.js`.\n\n\n### What should I expect as far as performance?\n\nBaseline performance in Sails is comparable to what you'd expect from a standard Node.js/Express application.  In other words, it's fast!  We've done some optimizations ourselves in Sails core, but our primary focus is not messing up what we get for free from our dependencies.  For a quick and dirty benchmark, see [http://serdardogruyol.com/sails-vs-rails-a-quick-and-dirty-benchmark](http://serdardogruyol.com/sails-vs-rails-a-quick-and-dirty-benchmark).\n\nThe most common performance bottleneck in production Sails applications is the database.  Over the lifetime of an application with a growing user base, it becomes increasingly important to set up good indexes on your tables/collections and use queries which return paginated results.  Eventually, as your production database grows to contain tens of millions of records, you will start to locate and optimize slow queries by hand (either by calling [`.query()`](https://sailsjs.com/documentation/reference/waterline-orm/models/query) or [`.native()`](https://sailsjs.com/documentation/reference/waterline-orm/models/native), or by using the underlying database driver from NPM).  \n\n\n### What's this warning about the connect session memory store?\n\nIf you are using sessions in your Sails app, you should not use the built-in memory store in production.  The memory session store is a development-only tool that does not scale to multiple servers; even if you only have one server it is not particularly performant (see [#3099](https://github.com/balderdashy/sails/issues/3099) and [#2779](https://github.com/balderdashy/sails/issues/2779)).\n\nFor instructions on configuring a production session store, see [sails.config.session](https://sailsjs.com/documentation/reference/configuration/sails-config-session).  If you want to disable session support altogether, turn off the `session` hook in your app's `.sailsrc` file:\n```javascript\n\"hooks\": {\n  \"session\": false\n}\n```\n\n\n<docmeta name=\"displayName\" value=\"FAQ\">\n\n"
  },
  {
    "path": "docs/concepts/Deployment/Hosting.md",
    "content": "# Hosting\n\nHere is a non-comprehensive list of Node/Sails hosting providers and a few available community tutorials.  Keep in mind that, most of the time, the process for deploying your Sails app is exactly the same as it would be for any other Node.js app.  Just be sure to take a look at the [other pages](https://sailsjs.com/documentation/concepts/deployment) in this section of the docs (as well as your app's [`config/env/production.js` file](https://sailsjs.com/documentation/anatomy/config/env/production-js)) and make any necessary adjustments before you actually deploy to production.\n\n\n### Heroku\n\n<a title=\"Deploy your Sails/Node.js app on Heroku\" href=\"http://heroku.com\"><img style=\"width:285px;\" src=\"https://sailsjs.com/images/deployment_heroku.png\" alt=\"Heroku logo\"/></a>\n\nHeroku offers easy, free deployment for any Sails project generated using the Web App template:\n\n1. Create a GitHub repo and push your code up to the `master` branch.\n2. Create a Heroku pipeline and a staging app within that pipeline (e.g. `my-cool-site-staging`).\n3. Using the point-and-click interface, set up that staging app to auto-deploy from the `deploy` branch of your GitHub repo.\n4. Under \"Add-ons\", set up Papertrail for logging, Redis2Go as your production session store (and for delivering socket messages, if relevant), Heroku Scheduler for scheduled jobs (if relevant), and a database host of your choosing (e.g. MySQL, PostgreSQL, MongoDB).\n5. Run through `config/production.js` and `config/staging.js` in your project and set it up.  Any information you feel is too sensitive to hard-code into those files (like database credentials) can be stored in Heroku's config vars (see bundled config files for examples).\n6. In the terminal, make sure you've got everything pulled/pushed and are fully in sync with the remote master branch on GitHub.\n7. Deploy by typing `sails run deploy`.\n\nYou can see a demonstration of this in action [here](https://courses.platzi.com/courses/sails-js/).\n\n##### More resources for using Heroku with Node.js/Sails.js:\n\n+ [Platzi: Full Stack JavaScript: Pt 5 (2018)](https://courses.platzi.com/courses/sails-js/)\n+ [Hello Sails.js: Hosting your Sails.js application on Heroku (2016-2017)](https://hellosails.com/hosting-your-sails-js-application-heroku/)\n+ [Platzi: Develop Apps with Sails.js: Pt 2 (2015)](https://courses.platzi.com/classes/develop-apps-sails-js/)  _(see part 2)_\n+ [Sails.js on Heroku (2015)](http://vort3x.me/sailsjs-heroku/)\n+ [SailsCasts: Deploying a Sails App to Heroku (2013)](http://irlnathan.github.io/sailscasts/blog/2013/11/05/building-a-sails-application-ep26-deploying-a-sails-app-to-heroku/)\n\n<!--\nMore 2013:\n+ [StackOverflow: Sails.js + Heroku (2013)](http://stackoverflow.com/a/20184907/486547)\n+ https://groups.google.com/forum/#!topic/sailsjs/vgqJFr7maSY\n+ https://github.com/chadn/heroku-sails\n+ http://dennisrongo.com/deploying-sails-js-to-heroku\n-->\n\n### Microsoft Azure\n\n<a title=\"Deploy a Sails.js web app to Azure App Service\" href=\"https://docs.microsoft.com/en-us/azure/app-service-web/app-service-web-nodejs-sails\"><img style=\"width:350px;\" src=\"https://sailsjs.com/images/deployment_azure.png\" alt=\"Azure logo\"/></a>\n\n+ [Deploy a Sails.js web app to Azure App Service (2017)](https://docs.microsoft.com/en-us/azure/app-service-web/app-service-web-nodejs-sails)\n\n<!--\n+ [Deploying Sails.js to Azure Web Apps (2015)](https://blogs.msdn.microsoft.com/partnercatalystteam/2015/07/16/y-combinator-collaboration-deploying-sailsjs-to-azure-web-apps/)\nPAGE NOT FOUND\n-->\n\n### Google Cloud Platform\n\n<a title=\"Deploy your Sails/Node.js app to Google Cloud Platform\" href=\"https://cloud.google.com/nodejs/resources/frameworks/sails\"><img style=\"width:350px;\" src=\"https://sailsjs.com/images/deployment_googlecloud.png\" alt=\"Google Cloud Platform logo\"/></a>\n\nUsing Google Cloud Platform means that your apps run on the same infrastructure that powers all of Google's products, so you can be confident that they'll scale seamlessly&mdash;no matter how many users you have.\n\n+ [Run Sails.js on Google Cloud Platform (2016)](https://cloud.google.com/nodejs/resources/frameworks/sails)\n<!--\n+ [Deploying Sails.js to Google Cloud (2016)](http://www.mot.la/2016-06-04-deploying-sails-js-to-google-cloud.html)\nPAGE NOT FOUND\n-->\n+ [A couple of Googlers demonstrate and deploy their app built on Sails.js and GO in a talk called `runtime:yours` at Google Cloud Platform Live (2014)](https://www.facebook.com/sailsjs/posts/721341477911963)\n\n\n### DigitalOcean\n\n<a title=\"DigitalOcean\" href=\"https://aws.amazon.com/\"><img style=\"width:225px;\" src=\"https://sailsjs.com/images/deployment_digitalocean.png\" alt=\"DigitalOcean logo\"/></a>\n\n+ [Troubleshooting: Can't install Sails.js on DigitalOcean (2017)](https://www.digitalocean.com/community/questions/can-t-install-sails-js)\n+ [How to use PM2 to set up a Node.js production environment on an Ubuntu VPS (2014)](https://www.digitalocean.com/community/articles/how-to-use-pm2-to-setup-a-node-js-production-environment-on-an-ubuntu-vps)\n+ [How to create a Node.js app using Sails.js on an Ubuntu VBS (2013)](https://www.digitalocean.com/community/articles/how-to-create-an-node-js-app-using-sails-js-on-an-ubuntu-vps)\n\n<!--\nMore 2013:\n+ https://www.digitalocean.com/community/articles/how-to-host-multiple-node-js-applications-on-a-single-vps-with-nginx-forever-and-crontab\n-->\n\n\n### Amazon Web Services (AWS)\n\n<a title=\"Amazon Web Services (AWS)\" href=\"https://aws.amazon.com/\"><img style=\"width:275px;\" src=\"https://sailsjs.com/images/deployment_aws.png\" alt=\"AWS logo\"/></a>\n\n\n+ [Creating a Sails.js application on AWS (2017)](http://bussing-dharaharsh.blogspot.com/2013/08/creating-sailsjs-application-on-aws-ami.html) _(see also [this question on ServerFault](http://serverfault.com/questions/531560/creating-an-sails-js-application-on-aws-ami-instance))_\n+ [Your own mini-Heroku on AWS (2014)](http://blog.grio.com/2014/01/your-own-mini-heroku-on-aws.html)\n\n\n\n### PM2 (KeyMetrics)\n\n<a title=\"About PM2\" href=\"http://pm2.keymetrics.io/\"><img style=\"width:285px;\" src=\"https://sailsjs.com/images/deployment_pm2.png\" alt=\"PM2 logo\"/></a>\n\n+ [Deploying with PM2](http://devo.ps/blog/goodbye-node-forever-hello-pm2/)\n\n> Note: PM2 isn't really a hosting platform, but it's worth mentioning in this section just so you're aware of it.\n\n\n### OpenShift (Red Hat)\n\n<a href=\"https://www.openshift.com/\"><img style=\"width:350px;\" alt=\"Red Hat™ OpenShift logo\" src=\"https://sailsjs.com/images/deployment_openshift.png\"/></a>\n\n+ [Deploying a Sails / Node.js application to OpenShift (2017)](https://gist.github.com/mikermcneil/b6136aa219f6d15b01a05b14cc681fcb)\n+ [Listening to a different IP address on OpenShift (2017-2018)](https://coderwall.com/p/dhhfcw/sailsjs-listening-on-a-different-ip-address) _(courtesy [@otupman](https://github.com/otupman))_\n+ [Get Sails/Node.js running on OpenShift (2017)](https://gist.github.com/mdunisch/4a56bdf972c2f708ccc6) _(Warning: quite out of date, but still useful for context.  Courtesy [@mdunisch](https://github.com/mdunisch).)_\n\n<!--\n### Xervo (formerly Modulus)\n\n<a href=\"https://xervo.io\"><img alt=\"Xervo logo\" style=\"display: inline-block; width: 85px;\" src=\"https://sailsjs.com/images/deployment_xervo.png\"/>&nbsp; &nbsp;<img alt=\"Modulus logo\" style=\"display: inline-block; width: 85px;\" src=\"https://sailsjs.com/images/deployment_modulus.png\"/></a>\n\n+ [Customer Spotlight: Sails.js](https://blog.xervo.io/sails-js)\n-->\n\n### Nanobox\n\n+ [Getting Started: A Simple Sails.js App (2017)](https://content.nanobox.io/a-simple-sails-js-example-app/) on Nanobox\n+ [Quickstart: nanobox-sails](https://github.com/nanobox-quickstarts/nanobox-sails)\n+ [Official Sails.js Guides](https://guides.nanobox.io/nodejs/sails/)\n+ [Official Nanobox Docs](https://docs.nanobox.io)\n+ [Nanobox Slack](https://slack.nanoapp.io)\n\n\n### exoscale / CloudControl\n\n+ [Deploying a Sails.js application to exoscale / CloudControl](https://github.com/exoscale/apps-documentation/blob/88d9f157093f0690f139337ff934c027482d4727/Guides/NodeJS/Sailsjs.md) _([rendered version of tutorial](https://webcache.googleusercontent.com/search?q=cache:gq8UZXarNq8J:https://community.exoscale.ch/documentation/apps/nodejs-app-sailsjs/+&cd=1&hl=en&ct=clnk&gl=us))_\n\n\n### RoseHosting\n\n + [Install Sails.js with Apache as a reverse proxy on CentOS 7 (2016)](https://www.rosehosting.com/blog/install-sails-js-with-apache-as-a-reverse-proxy-on-centos-7/)\n + [Install Sails.js on Ubuntu (2014)](https://www.rosehosting.com/blog/install-the-sails-js-framework-on-an-ubuntu-vps/)\n\n\n### More options\n\n+ Like [Heroku](https://stackshare.io/heroku), there are many [other Platform as a Service (PaaS) solutions that support Node.js/Sails.js](https://stackshare.io/heroku/alternatives).\n+ Like [Microsoft Azure](https://stackshare.io/microsoft-azure) and [EC2](https://stackshare.io/amazon-ec2), there are many [other Node.js/Sails.js-compatible \"bare-metal\"/IaaS cloud servers](https://stackshare.io/amazon-ec2/alternatives).\n+ Like [Cloudflare](https://stackshare.io/cloudflare), there are [other great CDNs for optimized hosting of your static assets](https://stackshare.io/cloudflare/alternatives).\n\n<docmeta name=\"displayName\" value=\"Hosting\">\n"
  },
  {
    "path": "docs/concepts/Deployment/Scaling.md",
    "content": "# Scaling\n\nIf you have the immediate expectation of lots of traffic to your application (or better yet, you already have the traffic),\nyou'll want to set up a scalable architecture that will allow you to add servers as more and more requests hit your app.\n\n### Performance\n\nIn production, Sails performs like any Connect, Express or Socket.io app ([example](http://serdardogruyol.com/?p=111)).  If you have your own benchmark you'd like to share, please write a blog post or article and tweet [@sailsjs](http://twitter.com/sailsjs).  But benchmarks aside, keep in mind that most performance and scalability metrics are application-specific.  The actual performance of your app will have a lot more to do with the way you implement your business logic and model calls than it will about the underlying framework you are using.\n\n\n\n### Example architecture\n\n```\n                             ....\n                    /  Sails.js server  \\      /  Database (e.g. Mongo, Postgres, etc)\nLoad Balancer  <-->    Sails.js server    <-->    Socket.io message queue (Redis)\n                    \\  Sails.js server  /      \\  Session store (Redis, Mongo, etc.)\n                             ....\n```\n\n\n### Preparing your app for a clustered deployment\n\nNode.js (and consequently Sails.js) apps scale horizontally. It's a powerful, efficient approach, but it involves a tiny bit of planning. At scale, you'll want to be able to copy your app onto multiple Sails.js servers and throw them behind a load balancer.\n\nOne of the big challenges of scaling an application is that these sorts of clustered deployments cannot share memory, since they are on physically different machines. On top of that, there is no guarantee that a user will \"stick\" with the same server between requests (whether HTTP or sockets), since the load balancer will route each request to the Sails server with the most available resources. The most important thing to remember about scaling a server-side application is that it should be **stateless**.  That means you should be able to deploy the same code to _n_ different servers, expecting any given incoming request handled by any given server, and everything should still work.  Luckily, Sails apps come ready for this kind of deployment almost right out of the box.  But before deploying your app to multiple servers, there are a few things you need to do:\n\n+ ensure none of the other dependencies you might be using in your app rely on shared memory\n+ make sure the database(s) for your models (e.g. MySQL, Postgres, Mongo) are scalable (e.g. sharding/cluster)\n+ **If your app uses sessions:**\n  + Configure your app to use a shared session store such as Redis (simply uncomment the `adapter` option in `config/session.js`) and install the \"@sailshq/connect-redis\" session adapter as a dependency of your app (e.g. `npm install @sailshq/connect-redis --save`).\n  + For more information about configuring your session store for production, see the [sails.config.session](https://sailsjs.com/documentation/reference/configuration/sails-config-session#?production-config) docs.\n+ **If your app uses sockets:**\n  + Configure your app to use Redis as a shared message queue for delivering socket.io messages. Socket.io (and consequently Sails.js) apps support Redis for sockets by default, so to enable a remote redis pubsub server, uncomment the relevant lines in `config/env/production.js`.\n  + Install the \"@sailshq/socket.io-redis\" adapter as a dependency of your app (e.g. `npm install @sailshq/socket.io-redis`)\n+ **If your cluster is on a single server (for instance, using [pm2 cluster mode](http://pm2.keymetrics.io/docs/usage/cluster-mode/))**:\n  + To avoid file conflict issues due to Grunt tasks, always start your apps in `production` environment and/or consider [turning Grunt off completely](https://sailsjs.com/documentation/concepts/assets/disabling-grunt).  See [here](https://github.com/balderdashy/sails/issues/3577#issuecomment-184786535) for more details on Grunt issues in single-server clusters.\n  + Be careful with [`config/bootstrap.js`](https://sailsjs.com/documentation/reference/configuration/sails-config-bootstrap) code that persists data in a database, to avoid conflicts when the bootstrap runs multiple times (once per node in the cluster).\n\n\n### Deploying a Node/Sails app to a PaaS\n\nDeploying your app to a PaaS like Heroku or Modulus is simple! Take a look at [Hosting](https://sailsjs.com/documentation/concepts/deployment/Hosting) for platform-specific information.\n\n\n### Deploying your own cluster\n\n+ Deploy multiple instances (servers running a copy of your app) behind a [load balancer](https://en.wikipedia.org/wiki/Load_balancing_(computing) (e.g. nginx).\n+ Configure your load balancer to terminate SSL requests.\n+ Remember that you won't need to use the SSL configuration in Sails, since the traffic will already be decrypted by the time it reaches Sails.\n+ Lift your app on each instance using a daemon like `forever` or `pm2` (see https://sailsjs.com/documentation/concepts/deployment for more about daemonology).\n\n\n### Optimization\n\nOptimizing an endpoint in your Node/Sails app is exactly like optimizing an endpoint in any other server-side application; e.g. identifying and manually optimizing slow queries, reducing the number of queries, etc.  For Node apps, if you find you have a heavily trafficked endpoint that is eating up CPU, look for synchronous (blocking) model methods, services, or machines that might be getting called over and over again in a loop or recursive dive.\n\nBut remember:\n\n> Premature optimization is the root of all evil.\n> &mdash;[Donald Knuth](http://c2.com/cgi/wiki?PrematureOptimization)\n\nNo matter what tool you're using, it is important to spend your time and energy writing high quality, well documented, readable code.  That way, if/when you are forced to optimize a code path in your application, you'll find it much easier to do.\n\n\n\n### Notes\n> + You don't have to use Redis for your sessions; you can actually use any Connect or Express-compatible session store.  See [sails.config.session](sailsjs.com/documentation/reference/configuration/sails-config-session) for more information.\n> + Some hosted Redis providers (such as <a href=\"https://elements.heroku.com/addons/redistogo\" target=\"_blank\">Redis To Go</a>) set a <a href=\"https://redis.io/topics/clients#client-timeouts\" target=\"_blank\">timeout for idle connections</a>.  In most cases you&rsquo;ll want to turn this off to avoid unexpected behavior in your app.  Details on how to turn off the timeout vary depending on provider, so you may have to contact their support team.\n\n<docmeta name=\"displayName\" value=\"Scaling\">\n"
  },
  {
    "path": "docs/concepts/E-commerce/E-commerce.md",
    "content": "# E-commerce\n\nLike any web application framework, Sails can be used for e-commerce apps. Depending on your project's specific needs, you can use Sails as the base for your own custom solution, or integrate with an existing e-commerce platform.\n\nWhen building a custom e-commerce solution on Sails, there are a number of possibilities for how to structure your data. A good place to start is with four [models](https://sailsjs.com/documentation/concepts/models-and-orm): `User` (already included in the \"Web app\" template for new Sails apps), `CartItem`, `Product`, and `Order`. By including [associations](https://sailsjs.com/documentation/concepts/models-and-orm/associations), you can track things like shopping carts and a user's individual order history.\n\n> If the prospect of rolling custom e-commerce features from scratch is rather daunting, you may consider building your Sails app on top of an existing Sails-based platform (e.g. [Ymple](https://www.ymple.com/en/)).\n\n<docmeta name=\"displayName\" value=\"E-commerce\">\n"
  },
  {
    "path": "docs/concepts/File Uploads/File Uploads.md",
    "content": "# File uploads\n\nUploading files in Sails is similar to uploading files for a vanilla Node.js or Express application. However, the process may be unfamiliar if you're coming from a different server-side platform like PHP, .NET, Python, Ruby, or Java.  But fear not: the core team has gone to great lengths to make file uploads easier without sacrificing scalability or security.\n\nSails comes with a powerful \"body parser\", [Skipper](https://github.com/balderdashy/skipper), which makes it easy to implement streaming file uploads&mdash;not only to the server's filesystem (i.e. hard disk), but also to Amazon S3, MongoDB's gridfs, or any other supported file adapter.\n\nSails does not automatically virus scan file uploads, or do any other attempt to detect whether uploaded files might be infected, broken, or unusual.  If you allow users to upload and share files with each other, it is your responsibility to protect your users from each other.  Always assume any request coming into your server could be malicious or misrepresent itself.\n\n\n### Uploading a file\n\nFiles are uploaded to HTTP web servers as _file parameters_.  In the same way that you might send a form POST to a URL with text parameters like \"name\", \"email\", and \"password\", you send files as file parameters like \"avatar\" or \"newSong\".\n\nTake this simple example:\n\n```javascript\nreq.file('avatar').upload(function (err, uploadedFiles) {\n  // ...\n});\n```\n\nFiles should be uploaded inside of an [action](https://sailsjs.com/documentation/concepts/actions-and-controllers).  Below is a more in-depth example that demonstrates how you could allow users to upload an avatar image and link it to an account.  This example assumes that you've already taken care of access control in a policy, and that you're storing the id of the logged-in user in `req.session.userId`.\n\n```javascript\n// api/controllers/UserController.js\n//\n// ...\n\n\n/**\n * Upload avatar for currently logged-in user\n *\n * (POST /user/avatar)\n */\nuploadAvatar: function (req, res) {\n\n  req.file('avatar').upload({\n    // don't allow the total upload size to exceed ~10MB\n    maxBytes: 10000000\n  },function whenDone(err, uploadedFiles) {\n    if (err) {\n      return res.serverError(err);\n    }\n\n    // If no files were uploaded, respond with an error.\n    if (uploadedFiles.length === 0){\n      return res.badRequest('No file was uploaded');\n    }\n\n    // Get the base URL for our deployed application from our custom config\n    // (e.g. this might be \"http://foobar.example.com:1339\" or \"https://example.com\")\n    var baseUrl = sails.config.custom.baseUrl;\n\n    // Save the \"fd\" and the url where the avatar for a user can be accessed\n    User.update(req.session.userId, {\n\n      // Generate a unique URL where the avatar can be downloaded.\n      avatarUrl: require('util').format('%s/user/avatar/%s', baseUrl, req.session.userId),\n\n      // Grab the first file and use it's `fd` (file descriptor)\n      avatarFd: uploadedFiles[0].fd\n    })\n    .exec(function (err){\n      if (err) return res.serverError(err);\n      return res.ok();\n    });\n  });\n},\n\n\n/**\n * Download avatar of the user with the specified id\n *\n * (GET /user/avatar/:id)\n */\navatar: function (req, res){\n\n  User.findOne(req.param('id')).exec(function (err, user){\n    if (err) return res.serverError(err);\n    if (!user) return res.notFound();\n\n    // User has no avatar image uploaded.\n    // (should have never have hit this endpoint and used the default image)\n    if (!user.avatarFd) {\n      return res.notFound();\n    }\n\n    var SkipperDisk = require('skipper-disk');\n    var fileAdapter = SkipperDisk(/* optional opts */);\n\n    // set the filename to the same file as the user uploaded\n    res.set(\"Content-disposition\", \"attachment; filename='\" + file.name + \"'\");\n\n    // Stream the file down\n    fileAdapter.read(user.avatarFd)\n    .on('error', function (err){\n      return res.serverError(err);\n    })\n    .pipe(res);\n  });\n}\n\n//\n// ...\n```\n\n\n\n\n#### Where do the files go?\nWhen using the default `receiver`, file uploads go to the `myApp/.tmp/uploads/` directory.  This can be overridden using the `dirname` option.  Note that you'll need to specify this option both when you call the `.upload()` function and when you invoke the skipper-disk adapter (so that you are uploading to and downloading from the same place).\n\n> Any Node.js app (or other server-side app) that receives untrusted file uploads and stores them on disk should never upload those files into paths within a Java server web root or any directory that a legacy web server might automatically dive into recursively to execute arbitrary code files that it finds.  For best results, upload files to S3 or a safe directory on disk.  Always assume any request coming into your server could be malicious or misrepresent itself.\n\n#### Uploading to a custom folder\nIn the example above we upload the file to .tmp/uploads, but how can we configure it with a custom folder, say `assets/images`? We can achieve this by adding options to the upload function as shown below.\n\n```javascript\nreq.file('avatar').upload({\n  dirname: require('path').resolve(sails.config.appPath, 'assets/images')\n},function (err, uploadedFiles) {\n  if (err) return res.serverError(err);\n\n  return res.json({\n    message: uploadedFiles.length + ' file(s) uploaded successfully!'\n  });\n});\n```\n\n### Sending text parameters in the same form as a file upload\n\nIf you need to send text parameters along with your file upload, the simplest way is by including them in the URL.\n\nIf you must send text parameters in the body of your request, the easiest way to handle this is by using the built in Cloud SDK that comes with the \"Web app\" template. (This also makes JSON parameters sent alongside file uploads \"just work\" when they wouldn't without extra work.)\n\n> As of Parasails v0.9.x, [the bundled Cloud SDK](https://github.com/mikermcneil/parasails/compare/v0.8.4...v0.9.0-4) properly handles additional parameters for you, so if you've generated your Sails app with the \"Web app\" template, you might want to make sure you're using the latest version of [`dist/parasails.js` and `dist/cloud.js`](https://github.com/mikermcneil/parasails/releases) in your project.\n\nRegardless of what you're using on the client side, you'll need to do things a little differently than usual in your Sails action on the back end. Because we're dealing with a multipart upload, any text parameters in your request body _must be sent before any files_.  This allows Sails to run your action code while files are still uploading, rather than having to wait for them to finish (avoiding a [famous DDoS vulnerability in Express-based Node.js apps](https://andrewkelley.me/post/do-not-use-bodyparser-with-express-js.html)). See the [Skipper docs](https://github.com/balderdashy/skipper#text-parameters) for advanced information on how this works behind the scenes.\n\n### Example\n\n#### Generate an `api`\nFirst we need to generate a new `api` for serving/storing files.  Do this using the sails command line tool.\n\n```sh\n$ sails generate api file\n\ndebug: Generated a new controller `file` at api/controllers/FileController.js!\ndebug: Generated a new model `File` at api/models/File.js!\n\ninfo: REST API generated @ http://localhost:1337/file\ninfo: and will be available the next time you run `sails lift`.\n```\n\n#### Write Controller Actions\n\nLets make an `index` action to initiate the file upload and an `upload` action to receive the file.\n\n```javascript\n\n// myApp/api/controllers/FileController.js\n\nmodule.exports = {\n\n  index: function (req,res){\n\n    res.writeHead(200, {'content-type': 'text/html'});\n    res.end(\n    '<form action=\"http://localhost:1337/file/upload\" enctype=\"multipart/form-data\" method=\"post\">'+\n    '<input type=\"text\" name=\"title\"><br>'+\n    '<input type=\"file\" name=\"avatar\" multiple=\"multiple\"><br>'+\n    '<input type=\"submit\" value=\"Upload\">'+\n    '</form>'\n    )\n  },\n  upload: function  (req, res) {\n    req.file('avatar').upload(function (err, files) {\n      if (err)\n        return res.serverError(err);\n\n      return res.json({\n        message: files.length + ' file(s) uploaded successfully!',\n        files: files\n      });\n    });\n  }\n\n};\n```\n\n### Notes\n> While loading untrusted JavaScript as an `<img src=\"…\">` [is not an XSS vulnerability in modern browsers](https://stackoverflow.com/a/46041031), the MIME type in the request headers of file uploads should never be relied upon.  Always assume any request coming into your server could be malicious or misrepresent itself.\n\n\n## Read more\n\n+ [Skipper docs](https://github.com/balderdashy/skipper)\n+ [Uploading to Amazon S3](https://sailsjs.com/documentation/concepts/file-uploads/uploading-to-s-3)\n+ [Uploading to Mongo GridFS](https://sailsjs.com/documentation/concepts/file-uploads/uploading-to-grid-fs)\n\n\n\n<docmeta name=\"displayName\" value=\"File uploads\">\n"
  },
  {
    "path": "docs/concepts/File Uploads/uploading-to-amazon-s3.md",
    "content": "# Uploading to Amazon S3\n\n> Please note that your Amazon S3 bucket must be created in the 'US East (N. Virginia)' region.\n> If you fail to do so, the uploads will not work and you'll see an 'InvalidRequest' error from AWS.\n\nWith Sails, you can stream file uploads to Amazon S3 with very little additional configuration.\n\n\nFirst install the [S3 Skipper adapter](https://github.com/balderdashy/skipper-s3):\n```sh\nnpm install skipper-s3 --save\n```\n\nThen use it in one of your controllers:\n\n```javascript\n  uploadFile: function (req, res) {\n    req.file('avatar').upload({\n      adapter: require('skipper-s3'),\n      key: 'S3 Key',\n      secret: 'S3 Secret',\n      bucket: 'Bucket Name'\n    }, function (err, filesUploaded) {\n      if (err) return res.serverError(err);\n      return res.ok({\n        files: filesUploaded,\n        textParams: req.allParams()\n      });\n    });\n  }\n```\n\n<docmeta name=\"displayName\" value=\"Uploading to S3\">\n"
  },
  {
    "path": "docs/concepts/File Uploads/uploading-to-mongo-gridfs.md",
    "content": "# Uploading to Mongo GridFS\n\nUploading files to MongoDB is possible thanks to Mongo's GridFS filesystem.  With Sails, you can accomplish this with very little additional configuration using the Skipper adapter for [MongoDB's GridFS](https://github.com/willhuang85/skipper-gridfs).\n\nInstall it with:\n\n```sh\n$ npm install skipper-gridfs --save\n```\n\nThen use it in one of your controllers:\n\n```javascript\n  uploadFile: function (req, res) {\n    req.file('avatar').upload({\n      adapter: require('skipper-gridfs'),\n      uri: 'mongodb://[username:password@]host1[:port1][/[database[.bucket]]'\n    }, function (err, filesUploaded) {\n      if (err) return res.serverError(err);\n      return res.ok();\n    });\n  }\n```\n\n<docmeta name=\"displayName\" value=\"Uploading to GridFS\">\n"
  },
  {
    "path": "docs/concepts/Globals/DisablingGlobals.md",
    "content": "# Disabling globals\n\nSails determines which globals to expose by looking at [`sails.config.globals`](https://sailsjs.com/documentation/reference/configuration/sails-config-globals), which is conventionallly configured in [`config/globals.js`](https://sailsjs.com/documentation/anatomy/config/globals.js).\n\nTo disable all global variables, just set the setting to `false`:\n\n```js\n// config/globals.js\nmodule.exports.globals = false;\n```\n\nTo disable _some_ global variables, specify an object instead, e.g.:\n\n```js\n// config/globals.js\nmodule.exports.globals = {\n  _: false,\n  async: false,\n  models: false,\n  services: false\n};\n```\n\n### Notes\n\n> + Bear in mind that none of the globals, including `sails`, are accessible until _after_ sails has loaded.  In other words, you won't be able to use `sails.models.user` or `User` outside of a function (since `sails` will not have finished loading yet.)\n\n<!-- not true anymore:\nMost of this section of the docs focuses on the methods and properties of `sails`, the singleton object representing your app.\n-->\n\n<docmeta name=\"displayName\" value=\"Disabling globals\">\n"
  },
  {
    "path": "docs/concepts/Globals/Globals.md",
    "content": "# Globals\n### Overview\n\nFor convenience, Sails exposes a handful of global variables.  By default, your app's [models](https://sailsjs.com/documentation/concepts/models-and-orm), [services](https://sailsjs.com/documentation/concepts/services), and the global `sails` object are all available on the global scope, meaning you can refer to them by name anywhere in your backend code (as long as Sails [has been loaded](https://github.com/balderdashy/sails/tree/master/lib/app)).\n\nNothing in Sails core relies on these global variables&mdash;each and every global exposed in Sails may be disabled in `sails.config.globals` (conventionally configured in `config/globals.js`.)\n\n\n### The App Object (`sails`)\nIn most cases, you will want to keep the `sails` object globally accessible, as it makes your app code much cleaner.  However, if you _do_ need to disable _all_ globals, including `sails`, you can get access to `sails` on the request object (`req`).\n\n### Models and Services\nYour app's [models](https://sailsjs.com/documentation/concepts/models-and-orm) and [services](https://sailsjs.com/documentation/concepts/services) are exposed as global variables using their `globalId`.  For instance, the model defined in the file `api/models/Foo.js` will be globally accessible as `Foo`, and the service defined in `api/services/Baz.js` will be available as `Baz`.\n\n### Async (`async`) and Lodash (`_`)\nSails also exposes an instance of [lodash](http://lodash.com) as `_`, and an instance of [async](https://github.com/caolan/async) as `async`.  These commonly-used utilities are provided by default so that you don't have to `npm install` them in every new project.  Like any of the other globals in sails, they can be disabled.\n\n\n\n<docmeta name=\"displayName\" value=\"Globals\">\n"
  },
  {
    "path": "docs/concepts/Helpers/ExampleHelper.md",
    "content": "# An example helper\n\nA common use of helpers is to encapsulate some repeated database queries.  For example, suppose our app had a `User` model which included a field `lastActiveAt` which tracked the time of their last login.  A common task in such an app might be to retrieve the list of users most recently online.  Rather than hard-coding this query into multiple locations, we could write a helper instead:\n\n```javascript\n// api/helpers/get-recent-users.js\nmodule.exports = {\n\n\n  friendlyName: 'Get recent users',\n\n\n  description: 'Retrieve a list of users who were online most recently.',\n\n\n  extendedDescription: 'Use `activeSince` to only retrieve users who logged in since a certain date/time.',\n\n\n  inputs: {\n\n    numUsers: {\n      friendlyName: 'Number of users',\n      description: 'The maximum number of users to retrieve.',\n      type: 'number',\n      defaultsTo: 5\n    },\n\n    activeSince: {\n      description: 'Cut-off time to look for logins after, expressed as a JS timestamp.',\n      extendedDescription: 'Remember: A _JS timestamp_ is the number of **milliseconds** since [that fateful night in 1970](https://en.wikipedia.org/wiki/Unix_time).',\n      type: 'number',\n      defaultsTo: 0\n    }\n\n  },\n\n\n  exits: {\n\n    success: {\n      outputFriendlyName: 'Recent users',\n      outputDescription: 'An array of users who recently logged in.',\n    },\n\n    noUsersFound: {\n      description: 'Could not find any users who logged in during the specified time frame.'\n    }\n\n  },\n\n\n  fn: async function (inputs, exits) {\n\n    // Run the query\n    var users = await User.find({\n      active: true,\n      lastLogin: { '>': inputs.activeSince }\n    })\n    .sort('lastLogin DESC')\n    .limit(inputs.numUsers);\n\n    // If no users were found, trigger the `noUsersFound` exit.\n    if (users.length === 0) {\n      throw 'noUsersFound';\n    }\n\n    // Otherwise return the records through the `success` exit.\n    return exits.success(users);\n\n  }\n\n};\n```\n\n### Usage\n\nTo call this helper from app code using the default options (in an action, for example), we would use:\n\n```javascript\nvar users = await sails.helpers.getRecentUsers();\n```\n\nTo alter the criteria for the returned users, we could pass in some values:\n\n```javascript\nvar users = await sails.helpers.getRecentUsers(50);\n```\n\nOr, to get the 10 most recent users who have logged in since St. Patrick's Day, 2017:\n\n```javascript\nawait sails.helpers.getRecentUsers(10, (new Date('2017-03-17')).getTime());\n```\n\n> Note: These values passed into a helper at runtime are sometimes called **argins**, or options, and they correspond with the key order of the helper's declared input definitions (e.g. `numUsers` and `activeSince`).\n\nAgain, chaining `.with()` in order to use named parameters:\n\n```javascript\nawait sails.helpers.getRecentUsers.with({\n  numUsers: 10,\n  activeSince: (new Date('2017-03-17')).getTime()\n});\n```\n\n\n##### Exceptions\n\nFinally, to handle the `noUsersFound` exit explicitly rather than simply treating it like any other error, we can use [`.intercept()`](https://sailsjs.com/documentation/reference/waterline-orm/queries/intercept) or [`.tolerate()`](https://sailsjs.com/documentation/reference/waterline-orm/queries/tolerate):\n\n```javascript\nvar users = await sails.helpers.getRecentUsers(10)\n.tolerate('noUsersFound', ()=>{\n  // ... handle the case where no users were found. For example:\n  sails.log.verbose(\n    'Worth noting: Just handled a request for active users during a time frame '+\n    'where no users were found.  Anyway, I didn\\'t think this was possible, because '+\n    'our app is so cool and popular.  But there you have it.'\n  );\n});\n```\n\n```javascript\nvar users = await sails.helpers.getRecentUsers(10)\n.intercept('noUsersFound', ()=>{\n  return new Error('Inconceivably, no active users were found for that timeframe.');\n});\n```\n\nThe main advantage of using helpers is the ability to update functionality in many parts of an app by changing code in a single place.  For example, by changing the default value of `numUsers` from `5` to `15`, we update the size of the default list returned in _any_ place that uses the helper.  Also, by using well-defined inputs like `numUsers` and `activeSince`, we guarantee we&rsquo;ll get helpful errors if we accidentally use an invalid (i.e. non-numeric) value.\n\n\n### Notes\n\nA few more notes about the example `getRecentUsers()` helper above:\n\n> * Many of the fields such as `description` and `friendlyName` are not strictly required but are immensely helpful in keeping the code maintainable, especially when sharing the helper across multiple apps.\n> * The `noUsersFound` exit may or may not be helpful, depending on your app.  If you always want to perform a specific action when no users are returned (for example, redirecting to a different page), this exit would be a good idea.  On the other hand, if you simply want to tweak some text in a view based on whether or not users were returned, it might be better to just have the `success` exit and check the `length` of the returned array in your action or view code.\n\n<docmeta name=\"displayName\" value=\"Example helper\">\n"
  },
  {
    "path": "docs/concepts/Helpers/Helpers.md",
    "content": "# Helpers\n\nAs of v1.0, all Sails apps come with built-in support for **helpers**, simple utilities that let you share Node.js code in more than one place.  This helps you avoid repeating yourself, and makes development more efficient by reducing bugs and minimizing rewrites.  Like actions2, this also makes it much easier to create documentation for your app.\n\n### Overview\n\nIn Sails, helpers are the recommended approach for pulling repeated code into a separate file, then reusing that code in various [actions](https://sailsjs.com/documentation/concepts/actions-and-controllers), [custom responses](https://sailsjs.com/documentation/concepts/extending-sails/custom-responses), [command-line scripts](https://www.npmjs.com/package/machine-as-script), [unit tests](https://sailsjs.com/documentation/concepts/testing), or even other helpers. You don't _have_ to use helpers&mdash;in fact you might not even need them right away.  But as your code base grows, helpers will become more and more important for your app's maintainability (plus, they're really convenient).\n\nFor example, in the course of creating the actions that your Node.js/Sails app uses to respond to client requests, you will sometimes find yourself repeating code in several places.  That can be pretty bug-prone, of course, not to mention annoying.  Fortunately, there's a neat solution: replace the duplicate code with a call to a custom helper:\n\n```javascript\nconst greeting = await sails.helpers.formatWelcomeMessage('Bubba');\nsails.log(greeting);\n// => \"Hello, Bubba!\"\n```\n\n> Helpers can be called from almost anywhere in your code, as long as that place has access to the [`sails` app instance](https://sailsjs.com/documentation/reference/application).\n\n\n### How helpers are defined\n\nHere's an example of a simple, well-defined helper:\n\n```javascript\n// api/helpers/format-welcome-message.js\nmodule.exports = {\n\n  friendlyName: 'Format welcome message',\n\n\n  description: 'Return a personalized greeting based on the provided name.',\n\n\n  inputs: {\n\n    name: {\n      type: 'string',\n      example: 'Ami',\n      description: 'The name of the person to greet.',\n      required: true\n    }\n\n  },\n\n\n  fn: async function (inputs, exits) {\n    const result = `Hello, ${inputs.name}!`;\n    return exits.success(result);\n  }\n\n};\n```\n\nThough simple, this file displays several characteristics of a good helper: it starts with a friendly name and description that make it immediately clear what the utility does, it describes its inputs so that it&rsquo;s easy to see how the utility is used, and it accomplishes a discrete task in the simplest way possible.\n\n> Look familiar?  Helpers follow the same specification as [shell scripts](https://sailsjs.com/documentation/concepts/shell-scripts) and [actions2](https://sailsjs.com/documentation/concepts/actions-and-controllers#?actions-2).\n\n##### The `fn` function\n\nThe core of the helper is the `fn` function, which contains the actual code that the helper will run.  The function takes two arguments: `inputs` (a dictionary of input values, or \"argins\") and `exits` (a dictionary of callback functions).  The job of `fn` is to utilize and process the argins, and then trigger one of the provided exits to return control back to whatever code called the helper.  Note that, as opposed to a typical JavaScript function that uses `return` to provide output to the caller, helpers provide that result value by passing it in to `exits.success()`.\n\n##### Inputs\n\nA helper&rsquo;s declared _inputs_ are analogous to the parameters of a typical JavaScript function: they define the values that the code has to work with.  However, unlike standard JavaScript function parameters, inputs are validated automatically.  If a helper is called using argins of the wrong type for their corresponding inputs or missing a value for a required input, it will trigger an error.  Thus, helpers are _self-validating_.\n\nInput for a helper are defined in the `inputs` dictionary.  Each input definition is composed of, at minimum, a `type` property.  Helper inputs support types like:\n\n* `string` - a string value\n* `number` - a number value (both integers and floats are valid)\n* `boolean` - the value `true` or `false`\n* `ref` - a JavaScript variable reference (can be _any_ value, including dictionaries, arrays, functions, streams, etc.)\n\nThese are the same data types (and related semantics) that you might already be accustomed to from [defining model attributes](https://sailsjs.com/documentation/concepts/models-and-orm/attributes).\nSo, as you might expect, you can provide a default value for an input by setting its `defaultsTo` property.  Or you can make it required by setting `required: true`.  You can even use `allowNull` and almost any of the higher-level validation rules like `isEmail`.\n\n\nThe arguments you pass in when calling a helper correspond with the order of keys in that helper's declared `inputs`.  Alternatively, if you'd rather pass in argins by name, use `.with()`:\n\n```javascript\nconst greeting = await sails.helpers.formatWelcomeMessage.with({ name: 'Bubba' });\n```\n\n##### Exits\n\nExits describe all the different possible outcomes a helper can have, good or bad.  Every helper automatically supports the `error` and `success` exits.\nWhen calling a helper, if its `fn` triggers `success`, then it will return normally.  But if its `fn` triggers some exit _other than_ `success`, then it will throw an Error (unless [`.tolerate()`](https://sailsjs.com/documentation/reference/waterline-orm/queries/tolerate) was used).\n\nWhen necessary, you can also expose other custom exits (known as \"exceptions\"), allowing the userland code that calls your helper to handle specific, exceptional cases.\nThis helps guarantee your code&rsquo;s transparency and maintainability by making it painless and easy to declare and negotiate errors.\n\n> Exceptions (custom exits) for a helper are defined in the `exits` dictionary.  It is a good practice to provide all custom exceptions with an explicit `description` property.\n\n\nImagine a helper called &ldquo;inviteNewUser&rdquo; which exposes a custom `emailAddressInUse` exit.  The helper's `fn` might trigger this custom exit if the provided email already exists, allowing your userland code to handle this specific scenario-- without muddying up your result values or resorting to extra `try/catch` blocks.\n\nFor example, if this helper was called from within an action that has its own \"badRequest\" exit:\n\n```javascript\nconst newUserId = sails.helpers.inviteNewUser('bubba@hawtmail.com')\n.intercept('emailAddressInUse', 'badRequest');\n```\n\n> The fancy-looking shorthand above is just a quicker way to write:\n>\n> ```javascript\n> .intercept('emailAddressInUse', (err)=>{\n>   return 'badRequest';\n> });\n> ```\n>\n> As for [.intercept()](https://sailsjs.com/documentation/reference/waterline-orm/queries/intercept),  it's just another shortcut so you're not forced to write custom try/catch blocks to negotiate these errors by hand all the time.\n\nInternally, your helper's `fn` is responsible for triggering one of its exits, either by throwing a [special exit signal](https://sailsjs.com/documentation/concepts/actions-and-controllers#?exit-signals) or by invoking an exit callback (e.g. `exits.success('foo')`).  If your helper sends back a result through the success exit (e.g. `'foo'`), then that will be the return value of the helper.\n\n> Note: For non-success exits, Sails will use the exit's predefined description to create an appropriate JavaScript Error instance automatically, if needed.\n\n##### Synchronous helpers\n\nBy default, all helpers are considered _asynchronous_.  While this is a safe default assumption, it's not always true. When you know for certain that your helper is _synchronous_, you can optimize performance by telling Sails using the `sync: true` property. This allows userland code to [call the helper without `await`](https://sailsjs.com/documentation/concepts/helpers#?synchronous-usage). But if you set `sync` to `true`, don't forget to change `fn: async function` to `fn: function`!\n\n> Note: Calling an asynchronous helper without `await` _will not work_.\n\n\n##### Accessing `req` in a helper\n\nIf you&rsquo;re designing a helper that parses request headers specifically for use from within actions, then you'll want to take advantage of pre-existing methods and/or properties of the [request object](https://sailsjs.com/documentation/reference/request-req).  The simplest way to allow the code in your action to pass along `req` to your helper is to define a `type: 'ref'` input:\n\n```javascript\ninputs: {\n\n  req: {\n    type: 'ref',\n    description: 'The current incoming request (req).',\n    required: true\n  }\n\n}\n```\n\n\nThen, to use your helper in your actions, you might write code like this:\n\n```javascript\nconst headers = await sails.helpers.parseMyHeaders(req);\n```\n\n### Generating a helper\n\nSails provides a built-in generator that you can use to create a new helper automatically:\n\n```bash\nsails generate helper foo-bar\n```\n\nThis will create a file `api/helpers/foo-bar.js` that can be accessed in your code as `sails.helpers.fooBar`.  The file that is initially created will be a generic helper with no inputs and just the default exits (`success` and `error`), which immediately triggers its `success` exit when executed.\n\n### Calling a helper\n\nWhenever a Sails app loads, it finds all of the files in `api/helpers/`, compiles them into functions, and stores them in the `sails.helpers` dictionary using the camel-cased version of the filename.  Any helper can then be invoked from your code, simply by calling it with `await`, and providing some argin values:\n\n```javascript\nconst result = await sails.helpers.formatWelcomeMessage('Dolly');\nsails.log('Ok it worked!  The result is:', result);\n```\n\n> This is roughly the same usage you might already be familiar with from [model methods](sailsjs.com/documentation/concepts/models-and-orm/models) like `.create()`.\n\n##### `.timeout(ms)`\n\nThe `.timeout()` method sets a maximum number of milliseconds to wait for the helper to complete. If execution takes longer than the specified time, it will throw a `TimeoutError`.\n\n```javascript\n// Throws TimeoutError if the helper takes longer than 5 seconds\nvar result = await sails.helpers.someLongRunningTask()\n  .timeout(5000);\n```\n\nYou can use `0` to disable any timeout that may have been set by the helper's implementation.\n\n##### `.retry(negotiationRule, retryDelaySeries)`\n\nThe `.retry()` method attaches an exponential backoff and retry strategy to the helper invocation. When the helper fails, it will automatically retry after a delay.\n\n| Argument | Type | Description |\n|----------|------|-------------|\n| `negotiationRule` | String, Object, or Array | _(Optional)_ Specifies which errors should trigger a retry. Can be an error code string (e.g. `'TimeoutError'`), a dictionary (e.g. `{code: 'E_TIMEOUT'}`), or an array of rules. If omitted, retries on any error. |\n| `retryDelaySeries` | Array of Numbers | _(Optional)_ An array of milliseconds to wait between each retry attempt. The length of the array determines the number of retries. Defaults to `[250, 500, 1000]` (3 retries with increasing delays). |\n\n```javascript\n// Retry up to 3 times with default delays (250ms, 500ms, 1000ms)\nvar result = await sails.helpers.riskyOperation()\n  .retry();\n\n// Retry only on TimeoutError\nvar result = await sails.helpers.externalApiCall()\n  .retry('TimeoutError');\n\n// Retry with custom exponential backoff (4 retries: 1s, 2s, 4s, 8s)\nvar result = await sails.helpers.flakyService()\n  .retry('TimeoutError', [1000, 2000, 4000, 8000]);\n```\n\n##### Chaining `.timeout()` and `.retry()`\n\nThese methods are fully chainable with each other and with other helper modifiers like `.intercept()` and `.tolerate()`:\n\n```javascript\nvar data = await sails.helpers.externalApiCall(apiPayload)\n  .timeout(10000)\n  .retry('TimeoutError', [1000, 2000, 5000])\n  .intercept('serviceUnavailable', 'backboneError')\n  .intercept((err) => {\n    sails.log.error('API call failed:', err);\n    return err;\n  });\n```\n\nWhen combining `.timeout()` with `.retry()`, the timeout applies to each individual attempt, not the total elapsed time across all retries.\n\n##### Synchronous usage\n\nIf a helper declares the `sync` property, you can also call it without `await`:\n\n```javascript\nconst greeting = sails.helpers.formatWelcomeMessage('Timothy');\n```\n\nBut before you remove `await`, make sure the helper is actually synchronous. Without `await` an asynchronous helper will never execute!\n\n##### Organizing helpers\nIf your application uses many helpers, you might find it helpful to group related helpers into subdirectories. For example, imagine you had a number of `user` helpers and several `item` helpers, organized in the following directory structure\n\n```\napi/\n helpers/\n  user/\n   find-by-username.js\n   toggle-admin-role.js\n   validate-username.js\n  item/\n   set-price.js\n   apply-coupon.js\n```\nWhen calling these helpers, each subfolder name (e.g. `user` and `item`) becomes an additional property layer in the `sails.helpers` object, so you can call `find-by-username.js` using `sails.helpers.user.findByUsername()` and you can call `set-price.js` with `sails.helpers.item.setPrice()`.\n\n> For more information, you can read a [conversation between Ryan Emberling and Mike McNeil](https://www.linkedin.com/feed/update/urn:li:activity:6998946887701565440?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A6998946887701565440%2C7000154787505668096%29) which goes into more detail about this use case, including some general tips and tricks for working with custom helpers and organics.\n\n### Handling exceptions\n\nFor more granular error handling (and even for those exceptional cases that aren't _quite_ errors) you may be used to setting some kind of error code, then sniffing out the error.  This approach works fine, but it can be time-consuming and hard to track.\n\nFortunately, there are a few different ways to conveniently handle errors in Sails helpers.  See the pages on [.tolerate()](https://sailsjs.com/documentation/reference/waterline-orm/queries/tolerate), [.intercept()](https://sailsjs.com/documentation/reference/waterline-orm/queries/intercept), and [special exit signals](https://sailsjs.com/documentation/concepts/actions-and-controllers#?exit-signals) for more information.\n\n\n<!--\nFor future reference, see https://github.com/balderdashy/sails-docs/commit/61f0039d26021c8abf4873aa675c409372dc2f8f\nfor the original content of these docs.\n-->\n\n##### As much or as little as you need\n\nWhile the usage in this example is excessive, it's easy to imagine a scenario in which it would be helpful to rely on custom exits like `notUnique`.  Still, you don't want to have to handle _every_ custom exit _every_ time.  Ideally, you'd only have to handle a custom exit in your userland code when necessary: whether to implement a feature of some kind or even to improve user experience or provide a better internal error message.\n\nLuckily, Sails helpers support \"automatic exit forwarding\".  That means userland code can choose to integrate with _as few or as many custom exits as you like_, on a case by case basis.  In other words, when calling a helper it's OK to completely ignore its custom `notUnique` exit if you don't need it.  That way, your code remains as concise and intuitive as possible.  And if things change, you can always revise your code to handle the custom exit later.\n\n### Next steps\n\n+ [Explore a practical example](https://sailsjs.com/documentation/concepts/helpers/example-helper) of a helper in a Node.js/Sails app.\n+ `sails-hook-organics` (which is bundled in the \"Web App\" template) comes with several free, open-source, and MIT-licensed helpers for many common use cases.  [Have a look!](https://npmjs.com/package/sails-hook-organics)\n+ [Click here](https://sailsjs.com/support) if you're unsure about helpers, or if you want to see more tutorials and examples.\n\n<docmeta name=\"displayName\" value=\"Helpers\">\n<docmeta name=\"nextUpLink\" value=\"/documentation/concepts/deployment\">\n<docmeta name=\"nextUpName\" value=\"Deployment\">\n"
  },
  {
    "path": "docs/concepts/Internationalization/Internationalization.md",
    "content": "# Internationalization\n\n### Overview\n\nIf your app will touch people or systems from all over the world, internationalization and localization (also known as \"i18n\") may be an important part of your international strategy.  This is particularly important for applications whose main user base is split across different languages: for example a tutorial site providing both Spanish and English content, or an online store with customers all over Quebec and British Columbia.\n\nFortunately, Sails provides built-in support for detecting user language preferences and translating static words/sentences.  As of Sails v1, this is implemented using the lightweight [`i18n-node-2` package](https://www.npmjs.com/package/i18n-2).  This package provides several additional options beyond what is covered here, which you can read about in its README file.  But for many Node.js/Sails.js apps with basic internationalization requirements, the simple usage below is all you'll need.\n\n### Usage\n\nIn Sails, it's easy to translate words and phrases using the locale specified in the request header:\n\nFrom a view:\n```ejs\n<h1> <%= __('Hello') %> </h1>\n<h1> <%= __('Hello %s, how are you today?', 'Mike') %> </h1>\n<p> <%= i18n('That\\'s right-- you can use either i18n() or __()') %> </p>\n```\n\n\n##### Overriding language headers\n\nSometimes, it is useful to override browser/device language headers -- for example, if you want to allow a user to set their own language preference.  Whether such a preference is session-based or associated with their account in the database, this is pretty straightforward to accomplish using [`req.setLocale()`](https://sailsjs.com/documentation/reference/request-req/req-set-locale).\n\n\n##### Internationalizing a shell script\n\nFinally, if you are building a [command-line script](https://sailsjs.com/documentation/concepts/shell-scripts) using Sails, or pursuing some other advanced use case, you can also translate abritrary strings to the [configured default locale](https://sailsjs.com/documentation/reference/configuration/sails-config-i-18-n) from almost anywhere in your application using `sails.__`:\n\n```javascript\nsails.__('Welcome');\n// => 'Bienvenido'\n\nsails.__('Welcome, %s', 'Mary');\n// => 'Bienvenido, Mary'\n```\n\n<!--\n\n  FUTURE: See https://trello.com/c/7GusjTTX\n\n-->\n\n### Locales\n\nSee [**Concepts > Internationalization > Locales**](https://sailsjs.com/documentation/concepts/internationalization/locales) for more information about creating your locale files (aka \"stringfiles\").\n\n\n### Additional options\n\nSettings for localization/internationalization may be configured in [`config/i18n.js`](https://sailsjs.com/documentation/reference/configuration/sails-config-i-18-n).  The most common reason you'll need to modify these settings is to edit the list of your app's supported locales.\n\nFor more information on configuring your Node.js/Sails.js app's internationalization settings, see [sails.config.i18n](https://sailsjs.com/documentation/reference/configuration/sails-config-i-18-n).\n\n\n### Disabling or customizing Sails' default internationalization support\n\nOf course you can always `require()` any Node modules you like, anywhere in your project, and use any internationalization strategy you want.\n\nBut worth noting is that since Sails implements [node-i18n-2](https://github.com/jeresig/i18n-node-2) integration in the [i18n hook](https://sailsjs.com/documentation/concepts/Internationalization), you can completely disable or override it using the [`loadHooks`](https://github.com/balderdashy/sails/blob/master/docs/PAGE_NEEDED.md) and/or [`hooks`](https://github.com/balderdashy/sails/blob/master/docs/PAGE_NEEDED.md) configuration options.\n\n\n### Translating dynamic content\n\nSee [**Concepts > Internationalization > Translating dynamic content**](https://sailsjs.com/documentation/concepts/internationalization/translating-dynamic-content).\n\n\n### What about i18n on the client?\n\nThe above technique works great out of the box for server-side views. But what about rich client apps that serve static HTML templates from a CDN or static host? <!-- (e.g. performance-sensitive SPAs, Chrome extensions, or webview apps built with tools like Ionic, PhoneGap, etc.) -->\n\nWell, the easiest option is just to keep internationalizing from your server-rendered views.  But if you'd rather not do that, there are [lots of different options available](https://web.archive.org/web/20160505184006/https://stackoverflow.com/questions/9640630/javascript-i18n-internationalization-frameworks-libraries-for-client-side-use) for client-side internationalization.  Like other client-side technologies, you should have no problem integrating any of them with Sails.\n\n> If you'd prefer not to use an external internationalization library, you can actually reuse Sails' i18n support to help you get your translated templates to the browser.  If you want to use Sails to internationalize your _client-side templates_, put your front-end templates in a subdirectory of your app's `/views` folder.\n> + In development mode, you should retranslate and precompile your templates each time the relevant stringfile or template changes using grunt-contrib-watch, which is already installed by default in new Sails projects.\n> + In production mode, you'll want to translate and precompile all templates on lift(). In loadtime-critical scenarios (e.g. mobile web apps) you can even upload your translated, precompiled, minified templates to a CDN like Cloudfront for further performance gains.\n\n\n<docmeta name=\"displayName\" value=\"Internationalization\">\n"
  },
  {
    "path": "docs/concepts/Internationalization/Locales.md",
    "content": "# Locales\n\n### Overview\n\nThe i18n hook reads JSON-formatted translation files from your project's \"locales\" directory (`config/locales` by default).  Each file corresponds with a [locale](http://en.wikipedia.org/wiki/Locale) (usually a language) that your Sails backend will support.\n\nThese files contain locale-specific strings (as JSON key-value pairs) that you can use in your views, controllers, etc.  The name of the file should match the language that you are supporting. This allows for automatic language detection based on request headers.\n\nHere is an example locale file (`config/locales/es.json`):\n```json\n{\n    \"Hello!\": \"Hola!\",\n    \"Hello %s, how are you today?\": \"¿Hola %s, como estas?\"\n}\n```\n\nLocales can be accessed in controller actions and policies through `req.i18n()`, or in views through the `__(key)` or `i18n(key)` functions.\n\n```ejs\n<h1> <%= __('Welcome to PencilPals!') %> </h1>\n<h2> <%= i18n('Hello %s, how are you today?', 'Pencil Maven') %> </h2>\n<p> <%= i18n('That\\'s right-- you can use either i18n() or __()') %> </p>\n```\n\nNote that the keys in your stringfiles (e.g. \"Hello %s, how are you today?\") are **case sensitive** and require exact matches.  There are a few different schools of thought on the best approach here; it really depends on who is editing the stringfiles and how often.  Especially if you'll be editing the translations by hand, simpler, all-lowercase key names may be preferable for maintainability.\n\nFor example, here's another way you could approach `config/locales/es.json`:\n\n```json\n{\n    \"hello\": \"hola\",\n    \"howAreYouToday\": \"cómo estás\"\n}\n```\n\nAnd here's `config/locales/en.json`:\n\n```json\n{\n    \"hello\": \"hello\",\n    \"howAreYouToday\": \"how are you today\"\n}\n```\n\nTo represent nested strings, use `.` in keys.  For example, here are some of the strings for an app's \"Edit profile\" page:\n\n``` json\n{\n  \"editProfile.heading\": \"Edit your profile\",\n  \"editProfile.username.label\": \"Username\",\n  \"editProfile.username.description\": \"Choose a new unique username.\",\n  \"editProfile.username.placeholder\": \"callmethep4rtysquid\"\n}\n```\n\n\n### Detecting and/or overriding the desired locale for a request\n\nTo determine the current locale used by the request, use [`req.getLocale()`](https://github.com/jeresig/i18n-node-2/tree/9c77e01a772bfa0b86fab8716619860098d90d6f#getlocale).\n\nTo override the auto-detected language/localization preference for a request, use [`req.setLocale()`](https://sailsjs.com/documentation/reference/request-req/req-set-locale), calling it with the unique code for the new locale, e.g.:\n\n```js\n// Force the language to German for the remainder of the request:\nreq.setLocale('de');\n// (this will use the strings located in `config/locales/de.json` for translation)\n```\n\nBy default, node-i18n will detect the desired language of a request by examining its language headers.  Language headers are set in your users' browser settings, and while they're correct most of the time, you may need the flexibility to override this detected locale and provide your own.  For a deeper dive into one way you might go about implementing this, check out [this gist](https://gist.github.com/mikermcneil/0af155ed546f3ddf164b4885fb67830c).\n\n\n<docmeta name=\"displayName\" value=\"Locales\">\n"
  },
  {
    "path": "docs/concepts/Internationalization/TranslatingDynamicContent.md",
    "content": "### Translating dynamic content\n\nIf your backend is storing interlingual data (e.g. product data is entered in multiple languages via a CMS), you shouldn't rely on simple JSON locale files unless you're somehow planning on editing your locale translations dynamically.  One option is to edit the locale translations programatically, either with a custom implementation or through a translation service.  Sails/node-i18n JSON stringfiles are compatible with the format used by [webtranslateit.com](https://webtranslateit.com/en).\n\nOn the other hand, you might opt to store these types of dynamic translated strings in a database.  If so, just make sure to build your data model accordingly so you can store and retrieve the relevant dynamic data by locale id (e.g. \"en\", \"es\", \"de\", etc.).  That way, you can leverage the [`req.getLocale()`](https://github.com/jeresig/i18n-node-2/tree/9c77e01a772bfa0b86fab8716619860098d90d6f#getlocale) method to help you figure out which translated content to use in any given response, and keep consistent with the conventions used elsewhere in your app.\n<docmeta name=\"displayName\" value=\"Translating dynamic content\">\n"
  },
  {
    "path": "docs/concepts/Logging/Custom log messages.md",
    "content": "# Custom log messages\n\nIt is often useful to emit custom log messages or events from your application code; whether you are tracking the status of outbound emails sent in the background, or just looking for a configurable alternative to calling [`console.log()`](https://nodejs.org/api/console.html#console_console_log_data) in your application code.\n\nFor convenience, Sails exposes its internal logging interface as `sails.log`.  Its usage is purposely very similar to Node's `console.log()`, but with a handful of extra features; namely support for multiple log levels with colorized, prefixed console output.\n\nSee [sails.log()](https://sailsjs.com/documentation/reference/application/sails-log) for more information and examples, or  [sails.config.log](https://sailsjs.com/documentation/reference/configuration/sails-config-log) for configuration options.\n\n\n## Available methods\n\nEach of the log methods below accepts an infinite number of arguments of any data type, seperated by commas.  Like `console.log`, data passed as arguments to the Sails logger are automatically prettified for readability using Node's [`util.inspect()`](http://nodejs.org/api/util.html#util_util_inspect_object_options). Consequently, standard Node.js conventions apply; _any_ dictionaries, errors, dates, arrays, or other data types are pretty-printed using the built-in logic in [`util.inspect()`](https://nodejs.org/api/util.html#util_util_inspect_object_options) (e.g. you see `{ pet: { name: 'Hamlet' } }` instead of `[object Object]`.)  Also, if you log an object that has a custom `inspect()` method, the logger will run that method automatically and write the string it returns to the console.\n\n\n### sails.log.error()\n\nWrites log output to `stderr` at the \"error\" log level.\nUseful for tracking major errors.\n\n```js\nsails.log.error('Sending 500 (\"Server Error\") response.');\n// -> error: Sending 500 (\"Server Error\") response.\n```\n\n### sails.log.warn()\n\nWrites log output to `stderr` at the \"warn\" log level.\nUseful for tracking information about operations that failed silently.\n\n```js\nsails.log.warn('File upload quota exceeded for user #%d.  Request aborted.', user.id);\n// -> warn: File upload quota exceeded for user #94271.  Request aborted.\n```\n\n\n### sails.log()\n\n_aka sails.log.debug()_\n\nThe default log function, which writes console output to `stderr` at the \"debug\" log level.\nUseful for passing around important technical information amongst your team; or as a general alternative to `console.log()`.\n\n```js\nsails.log('This endpoint (`POST /accounts`) will be deprecated in the next few days.  Please use `POST /signup` instead. ');\n// -> debug: This endpoint (`POST /accounts`) will be deprecated in the next few days.  Please use `POST /signup` instead.\n```\n\n\n\n### sails.log.info()\n\nWrites log output to `stdout` at the \"info\" log level.\nUseful for capturing information about your app's business logic.\n\n```js\nsails.log.info('A new user (', newUser.emailAddress, ') just signed up!');\n// -> info: A new user ( irl@foobar.com ) just signed up!\n```\n\n\n### sails.log.verbose()\n\nWrites log output to `stdout` at the \"verbose\" log level.\nUseful for capturing detailed information about your app that you only need on rare occasions.\n\n```js\nsails.log.verbose('A user (IP adddress: `%s`) initiated an account transfer...', req.ip);\n// -> verbose: A user (IP adddress: `10.48.1.191`) initiated an account transfer...\n```\n\n\n### sails.log.silly()\n\nWrites log output to `stdout` at the \"silly\" log level.\nUseful for capturing technical details about your app that are only useful for diagnostics and/or troubleshooting.\n\n```js\nsails.log.silly(\n'Successfully fetched Account record for requesting authenticated user (`%d`).',\n'Took %dms.', req.param('id'), msElapsed);\n// -> silly: Successfully fetched Account record for authenticated user (`49722`). Took 41ms.\n```\n\n\n\n\n<docmeta name=\"displayName\" value=\"Custom log messages\">\n\n"
  },
  {
    "path": "docs/concepts/Logging/Logging.md",
    "content": "# Logging\n\nSails comes with a simple, built-in logger called [`captains-log`](https://github.com/balderdashy/captains-log).  Its usage is functionally very similar to Node's [`console.log`](https://nodejs.org/api/console.html#console_console_log_data), but with a handful of extra features, namely support for multiple log levels with colorized, prefixed console output. The logger serves two purposes:\n+ it emits warnings, errors, and other console output from inside the Sails framework\n+ it can be used to emit [custom events/messages](https://sailsjs.com/documentation/concepts/logging/custom-log-messages) from within your application code\n\n\n### Configuration\nSails' log configuration is determined by [`sails.config.log`](https://sailsjs.com/documentation/reference/configuration/sails-config-log), which is conventionally set by a generated configuration file ([`config/log.js`](https://sailsjs.com/documentation/anatomy/my-app/config/log-js)) in new Sails projects out of the box.\n\n### Usage\n\n```javascript\nsails.log.info('I am an info-level message.');\nsails.log('I am a debug-level message');\nsails.log.warn('I am a warn-level message');\n```\n\n### Log levels\n\nUsing the built-in logger, Sails will write output (to stdout/stderr) for log function calls that are _at_ or _above_ the priority of the currently-configured log level.  This log level is normalized and also applied to generated output from Grunt, Socket.io, Waterline, Express, and other dependencies. The hierarchy of log levels and their relative priorities is summarized by the chart below:\n\n| Priority | Level     | Log fns that produce visible output   |\n|----------|-----------|:--------------------------------------|\n| 0        | silent    | _N/A_\n| 1        | error     | `.error()`            |\n| 2        | warn      | `.warn()`, `.error()` |\n| 3        | debug     | `.debug()`, `.warn()`, `.error()` |\n| 4        | info      | `.info()`, `.debug()`, `.warn()`, `.error()` |\n| 5        | verbose   | `.verbose()`, `.info()`, `.debug()`, `.warn()`, `.error()` |\n| 6        | silly     | `.silly()`, `.verbose()`, `.info()`, `.debug()`, `.warn()`, `.error()` |\n\n\n#### Notes\n + The [default log level](https://sailsjs.com/documentation/reference/configuration/sails-config-log) is **info**.  When your app's log level is set to \"info\", Sails logs limited information about the server/app's status.\n + When running automated tests for your app, it is often helpful to set the log level to **error**.\n + When the log level is set to **verbose**, Sails logs Grunt output, as well as much more detailed information on the routes, models, hooks, etc. that were loaded.\n + When the log level is set to **silly**, Sails outputs everything from **verbose** as well as internal information on which routes are being bound and other detailed framework lifecycle information, diagnostics, and implementation details.\n\n\n\n<docmeta name=\"displayName\" value=\"Logging\">\n"
  },
  {
    "path": "docs/concepts/Middleware/ConventionalDefaults.md",
    "content": "# Conventional defaults\n\nSails comes bundled with a suite of conventional HTTP middleware, ready to use.  Naturally, you may choose to disable, override, append to, or rearrange it, but the pre-installed stack is perfectly acceptable for most apps in development or production.  Below is a list of the standard HTTP middleware functions that come bundled in Sails, in the order they execute every time the server receives an incoming HTTP request:\n\n HTTP Middleware Key       | Purpose\n :------------------------ |:------------\n _cookieParser_ *          | Parses the cookie header into a clean object for use in subsequent middleware and your application code.\n _session_ *               | Creates or loads a unique session object (`req.session`) for the requesting user agent based on their cookies and your [session configuration](https://sailsjs.com/documentation/reference/configuration/sails-config-session).\n **bodyParser**            | Parses parameters and binary upstreams (for streaming file uploads) from the HTTP request body using [Skipper](https://github.com/balderdashy/skipper).\n **compress**              | Compresses response data using gzip/deflate. See [`compression`](https://github.com/expressjs/compression) for details.\n **poweredBy**             | Attaches an `X-Powered-By` header to outgoing responses.\n _router_ *                | This is where the bulk of your app logic gets applied to any given request.  In addition to running `\"before\"` handlers in hooks (e.g. csrf token enforcement) and some internal Sails logic, this routes requests using your app's explicit routes (in [`sails.config.routes`](https://sailsjs.com/documentation/reference/configuration/sails-config-routes)) and/or route blueprints.\n _www_ *                   | Serves static files&mdash;usually images, stylesheets, scripts&mdash;in your app's \"public\" folder (configured in [`sails.config.paths`](https://github.com/balderdashy/sails/blob/master/docs/PAGE_NEEDED.md), conventionally [`.tmp/public/`](https://github.com/balderdashy/sails/blob/master/docs/PAGE_NEEDED.md)) using Connect's [static middleware](http://www.senchalabs.org/connect/static.html).\n **favicon**               | Serves the [browser favicon](http://en.wikipedia.org/wiki/Favicon) for your app if one is provided as `/assets/favicon.ico`.\n\n\n###### Legend:\n\n+ `*` : The middleware with an asterisk (*) should _almost never_ need to be modified or removed. Please only do so if you really understand what you're doing.\n\n\n<docmeta name=\"displayName\" value=\"Conventional defaults\">\n"
  },
  {
    "path": "docs/concepts/Middleware/Middleware.md",
    "content": "# Middleware\n\nTechnically, much of the code you&rsquo;ll write in a Sails app is _middleware_, in that runs in between the incoming request and the outgoing response&mdash;that is, in the \"middle\" of the request/response stack.  In an MVC framework, the term &ldquo;middleware&rdquo; typically refers more specifically to code that runs _before_ or _after_ your route-handling code (i.e. your [controller actions](https://sailsjs.com/documentation/concepts/Controllers?q=actions)), making it possible to apply the same piece of code to multiple routes or actions.  Sails has robust support for the middleware design pattern.  Depending on your needs, you may choose to implement:\n\n* HTTP middleware&mdash;to apply code before _every_ HTTP request (see below for more details)\n* [Policies](https://sailsjs.com/documentation/concepts/policies)&mdash;to apply code before one or more controller actions\n* [Hooks with the `routes` feature implemented](https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification/routes)&mdash;to apply code before one or more route handlers\n* [Custom responses](https://sailsjs.com/documentation/concepts/custom-responses)&mdash;to apply code after one or more controller actions\n\n### HTTP middleware\n\nSails is fully compatible with Express / Connect middleware, which are functions that accept `req`, `res` and `next` as arguments.  Every app utilizes a configurable _middleware stack_ for handling HTTP requests.  Each time the app receives an HTTP request, its configured HTTP middleware stack runs in order.\n\n> Note that this HTTP middleware stack is only used for \"true\" HTTP requests; it is ignored for **virtual requests** (e.g. requests from a live Socket.io connection).\n\n##### Built-in HTTP middleware\n\nBy default, Sails uses a few different middleware functions to handle low-level HTTP-related tasks.  These are things like interpreting cookies, parsing HTTP request bodies, serving assets, and even attaching your app's routes.  You can read more about the default middleware stack [here](https://sailsjs.com/documentation/concepts/middleware/conventional-defaults).\n\n\n### Configuring the HTTP middleware stack\n\nSince the middleware stack comes with reasonable defaults, many Sails apps won't need to modify this configuration at all.  But for situations where you need more flexibility, Sails makes it simple to add, reorder, override, and disable the functions in your app's HTTP middleware stack.\n\n##### Adding middleware\nTo configure a new custom HTTP middleware function, add a middleware function as a new key in `middleware` (e.g. \"foobar\"), then add the name of its key (\"foobar\") in the `middleware.order` array, wherever you'd like it to run in the middleware chain.\n\nWith the exception of \"order\", which is reserved for configuring the order of the middleware stack, any value assigned to a key of `sails.config.middleware` should be a function which takes three arguments: `req`, `res` and `next`.  This function works almost exactly like a [policy](https://sailsjs.com/documentation/concepts/policies), the only visible difference is when it's executed.\n\n##### Initializing middleware\nIf you need to run some one-time set up code for a custom middleware function, you'll need to do so _before_ passing it in.  The recommended way of doing this is with a self-calling (i.e. [\"immediately-invoked\"](https://en.wikipedia.org/wiki/Immediately-invoked_function_expression)) wrapper function.  In the example below, note that rather than setting the value to a \"req, res, next\" function directly, a self-calling function is used to \"wrap\" some initial setup code.  That self-calling wrapper function then returns the final middleware (req,res,next) function, so it gets set on the key just the same was as if it had been passed in directly.\n\n##### Example: using custom middleware\nThe following example shows how you might set up three different custom HTTP middleware functions:\n\n```js\n// config/http.js\nmodule.exports.http = {\n\n  middleware: {\n\n    order: [\n      'cookieParser',\n      'session',\n      'passportInit',            // <==== If you're using \"passport\", you'll want to have its two\n      'passportSession',         // <==== middleware functions run after \"session\".\n      'bodyParser',\n      'compress',\n      'foobar',                  // <==== We can put other, custom HTTP middleware like this wherever we want.\n      'poweredBy',\n      'router',\n      'www',\n      'favicon',\n    ],\n\n\n    // An example of a custom HTTP middleware function:\n    foobar: (function (){\n      console.log('Initializing `foobar` (HTTP middleware)...');\n      return function (req,res,next) {\n        console.log('Received HTTP request: '+req.method+' '+req.path);\n        return next();\n      };\n    })(),\n\n    // An example of a couple of 3rd-party HTTP middleware functions:\n    // (notice that this time we're using an existing middleware library from npm)\n    passportInit    : (function (){\n      var passport = require('passport');\n      var reqResNextFn = passport.initialize();\n      return reqResNextFn;\n    })(),\n\n    passportSession : (function (){\n      var passport = require('passport');\n      var reqResNextFn = passport.session();\n      return reqResNextFn;\n    })()\n\n  },\n}\n```\n\n##### Overriding or disabling built-in HTTP middleware\n\nYou can also use the strategy described above to _override_ built-in middleware like the body parser (see [Customizing the body parser](https://sailsjs.com/documentation/reference/configuration/sails-config-http#?customizing-the-body-parser)).\n\n> While this is not recommended, you can even _disable_ a built-in HTTP middleware function entirely&mdash;just remove it from the `middleware.order` array.  This allows for complete flexibility, but it should be used with care.  If you choose to disable a piece of built-in middleware, make sure you fully understand the consequences. Disabling built-in HTTP middleware may dramatically change the way your app works.\n\n\n### Express middleware in Sails\n\nOne of the really nice things about Sails apps is that they can take advantage of the wealth of existing Express/Connect middleware,  but a common question arises when people _actually_ try to do this:\n\n> _\"Where do I `app.use()` this thing?\"_.\n\nIn most cases, the answer is to install the Express middleware as a custom HTTP middleware in [`sails.config.http.middleware`](https://sailsjs.com/documentation/reference/configuration/sails-config-http).  This will trigger it for all HTTP requests to your Sails app, and allow you to configure the order in which it runs in relation to other HTTP middleware.\n\n> You should never override or remove the `router` HTTP middleware.  It is built-in to Sails; without it, your app's explicit routes and blueprint routes will not work.\n\n\n##### Express middleware as policies\n\nTo make Express middleware apply to only a particular action, you can also include Express middleware as a policy&mdash;just be sure that you actually want it to run for both HTTP _and_ virtual socket requests.\n\nTo do this, edit [`config/policies.js`](https://sailsjs.com/documentation/reference/configuration/sails-config-policies) to either require and setup the middleware in an actual wrapper policy (usually a good idea) or to require it directly in your policies.js file.  The following example uses the latter strategy for brevity:\n\n```js\nvar auth = require('http-auth');\nvar basic = auth.basic({\n  realm: 'admin area'\n}, function (username, password, onwards) {\n  return onwards(username === 'Tina' && password === 'Bullock');\n});\n\n//...\nmodule.exports.policies = {\n  '*': [true],\n\n  // Prevent end users from doing CRUD operations on products reserved for admins\n  // (uses HTTP basic auth)\n  'product/*': [auth.connect(basic)],\n\n  // Everyone can view product pages\n  'product/show': [true]\n}\n```\n\n\n\n<!--\n\n  FUTURE:\n\n### Advanced Express Middleware In Sails\n\nYou can actually do this in a few different ways, depending on your needs.\n\n\n\nGenerally, the following best-practices apply:\n\nIf you want a middleware function\n\n+ If you want a piece of middleware to run only when your app's explicit or blueprint routes are matched, you should include it as a policy.\n+ this will run passport for all incoming http requests, including images, css, etc.\n\nIf you want a middleware function to run for all you should include it at the top of your `config/routes.js` as a wildcard route.  for your controller (both HTTP and virtual) requests\n-->\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"Middleware\">\n"
  },
  {
    "path": "docs/concepts/ORM/Associations/Associations.md",
    "content": "# Associations\n\n### Overview\n\nIn addition to being literal types like `string` and `number`, attributes in a Sails model can represent links to other records in a datastore.  Attributes of this type are called _associations_.  For example, given a `User` model and a `Pet` model, the `User` may contain a `pets` attribute that links a given user to one or more pets.\n\n##### Setting values for associations\n\nDepending on the type of link, an association attribute can be set in a [`.create()`](https://sailsjs.com/documentation/reference/waterline-orm/models/create) or [`.update()`](https://sailsjs.com/documentation/reference/waterline-orm/models/update) call by giving it the value of another record&rsquo;s primary key, or by using special model methods like [`.addToCollection`](https://sailsjs.com/documentation/reference/waterline-orm/models/add-to-collection), [`.removeFromCollection()`](https://sailsjs.com/documentation/reference/waterline-orm/models/remove-from-collection), or [`.replaceCollection()`](https://sailsjs.com/documentation/reference/waterline-orm/models/replace-collection).\n\n##### Associations in retrieved records\n\nUnlike normal attributes, association attribute values are not always returned when retrieving a record with [`.find()`](https://sailsjs.com/documentation/reference/waterline-orm/models/find) or [`.findOne()`](https://sailsjs.com/documentation/reference/waterline-orm/models/find-one).  Instead, you declare which associations to retrieve by using the [`.populate()`](https://sailsjs.com/documentation/reference/waterline-orm/queries/populate) method:\n\n```js\n// Find a single user, including its pets\nvar userWithPets = await User.findOne(123).populate('pets');\n```\n\nHow an association attribute is represented in a returned record depends on the type of association, whether there are actual records linked, and whether `.populate()` is chained to the query.  See [this table](https://sailsjs.com/documentation/concepts/models-and-orm/records#?expected-types-values-for-association-attributes) for a full description of what to expect in a returned record with association attributes.\n\n### Cross-adapter associations\n\nWith Sails and Waterline, you can associate models across multiple data stores. This means that even if your users live in [PostgreSQL](http://www.postgresql.org/) and their comments live in [MongoDB](http://www.mongodb.com/), you can interact with the data as if they lived together in the same database. You can also have associations that span different [datastores](https://sailsjs.com/documentation/reference/configuration/sails-config-datastores) using the same adapter.  This comes in handy if, for example, your app needs to access/update legacy recipe data stored in a [MySQL](http://www.mysql.com/) database somewhere in your company's data center, but also store/retrieve ingredient data from a brand new MySQL database in the cloud.\n\n> **IMPORTANT NOTE**\n>\n> In tutorials and example code, you might sometimes see associations' `collection`, `model`, or `through` properties reference models in either lowercase (the _identity_) or capitalized (the _global ID_).  For example, in the following association, the `collection` property is set to `product`&mdash;the identity of the Sails model called `Product`:\n>\n>```javascript\n>wishlist: {\n>  collection: 'product',\n>  via: 'wishlistedBy'\n>}\n>```\n>\n> In the Sails docs, we always use the global ID approach for consistency's sake.  But realize that either approach will work.\n\n<docmeta name=\"displayName\" value=\"Associations\">\n"
  },
  {
    "path": "docs/concepts/ORM/Associations/ManytoMany.md",
    "content": "# Many-to-many\n\n**AKA \"Has and Belongs To Many\"**\n\n### Overview\n\nA many-to-many association states that one record can be associated with many other records and vice-versa.  This type of relationship involves the creation of a _join table_ to keep track of the many links between records.  When Waterline detects that two models have collection attributes that point to each other through their `via` keys (see below), it will automatically build up a join table for you.\n\n### The `via` key\n\nBecause you may want a model to have multiple many-to-many associations on another model a `via` key is needed on the `collection` attribute. The `via` key indicates the related attribute on the other side of a many-to-many association.\n\nUsing the `User` and `Pet` example, let&rsquo;s look at how to build a schema where a `User` may have many `Pet` records and a `Pet` may have multiple owners.\n\n```javascript\n// myApp/api/models/User.js\n// A user may have many pets\nmodule.exports = {\n  attributes: {\n    firstName: {\n      type: 'string'\n    },\n    lastName: {\n      type: 'string'\n    },\n\n    // Add a reference to Pet\n    pets: {\n      collection: 'pet',\n      via: 'owners'\n    }\n  }\n};\n```\n```javascript\n// myApp/api/models/Pet.js\n// A pet may have many owners\nmodule.exports = {\n  attributes: {\n    breed: {\n      type: 'string'\n    },\n    type: {\n      type: 'string'\n    },\n    name: {\n      type: 'string'\n    },\n\n    // Add a reference to User\n    owners: {\n      collection: 'user',\n      via: 'pets'\n    }\n  }\n};\n```\n\nTo associate records together, the Model method [.addToCollection()](https://sailsjs.com/documentation/reference/waterline-orm/models/add-to-collection) is used. This allows you to set the primary keys of the records that will be associated.\n\n```javascript\n// To add a Pet to a user's `pets` collection where the User has an id of\n// 10 and the Pet has an id of 300.\nawait User.addToCollection(10, 'pets', 300);\n```\n\nYou can also add multiple pets at once:\n\n```javascript\nawait User.addToCollection(10, 'pets', [300, 301]);\n```\n\nRemoving associations is just as easy using the [.removeFromCollection()](https://sailsjs.com/documentation/reference/waterline-orm/models/remove-from-collection) method. It works the same way as  `addToCollection`:\n\n```javascript\n// To remove a User from a pet's collection of owners where the User has an id of\n// 10 and the Pet has an id of 300.\nawait Pet.removeFromCollection(300, 'owners', 10);\n```\n\nAnd you can remove multiple owners at once:\n\n```javascript\nawait Pet.removeFromCollection(300, 'owners', [10, 12]);\n```\n\nNote that adding or removing associated records from one side of a many-to-many relationship will automatically affect the other side.  For example, adding records to the `pets` attribute of a `User` model record with `.addToCollection()` will immediately affect the `owners` attributes of the linked `Pet` records.\n\nTo return associated collections along with a record retrieved by [`.find()`](https://sailsjs.com/documentation/reference/waterline-orm/models/find) or [`.findOne()`](https://sailsjs.com/documentation/reference/waterline-orm/models/find-one), use the [`.populate()`](https://sailsjs.com/documentation/reference/waterline-orm/queries/populate) method.\n\n### Dominance\n\nIn most cases, Sails will be able to create the join table for a many-to-many association without any input from you.  However, if the two models in the association use different datastores, you may want to choose which one should contain the join table.  You can do this by setting `dominant: true` on one of the associations in the relationship.\n\nConsider the following models:\n\n\n```javascript\n// User.js\nmodule.exports = {\n  datastore: 'ourMySQL',\n  attributes: {\n    email: 'string',\n    wishlist: {\n      collection: 'product',\n      via: 'wishlistedBy'\n    }\n  }\n};\n```\n\n\n```javascript\n// Product.js\nmodule.exports = {\n  datastore: 'ourRedis',\n  attributes: {\n    name: 'string',\n    wishlistedBy: {\n      collection: 'user',\n      via: 'wishlist'\n    }\n  }\n};\n```\n\nIn this case, `User` and `Product` records exist in different databases.  By default, Sails will arbitrarily choose one of the datastores (either `ourMySQL` or `ourRedis`) to contain the join table linking the `wishlist` attribute of `User` to the `wishlistedBy` attribut of `Product`.  In order to force the join table to exist in the `ourMySQL` datastore, you would add `dominant: true` to the `wishlist` attribute definition.  Conversely, adding `dominant: true` to the `wishlistedBy` attribute would cause the join table to be created in the `ourRedis` datastore.\n\n\n##### Choosing a \"dominant\"\n\nSeveral factors may influence your decision of where to create the join table:\n\n+ If one side is a SQL database, placing the join table on that side will allow your queries to be more efficient, since the relationship table can be joined before the other side is communicated with.  This reduces the number of total queries required from 3 to 2.\n+ If one datastore is much faster than the other, all other things being equal, it probably makes sense to put the join table on that side.\n+ If you know that it is much easier to migrate one of the datastores, you may choose to set that side as `dominant`.  Similarly, regulations or compliance issues may affect your decision as well.  If the relationship contains sensitive patient information (for instance, a relationship between `Patient` and `Medicine`) you want to be sure that all relevant data is saved in one particular database over the other (in this case, `Patient` is likely to be `dominant`).\n+ Along the same lines, if one of your datastores is read-only (perhaps `Medicine` in the previous example is connected to a read-only vendor database), you won't be able to write to it, so you'll want to make sure your relationship data can be persisted safely on the other side.\n\n<docmeta name=\"displayName\" value=\"Many-to-many\">\n\n"
  },
  {
    "path": "docs/concepts/ORM/Associations/OneWayAssociation.md",
    "content": "# One way association\n\n**AKA \"Belongs To\"**\n\n### Overview\n\nA one way association is where a model is associated with another model.  You could query that model and [populate](https://sailsjs.com/documentation/reference/waterline-orm/queries/populate) to get the associated model.  You can't however query the associated model and populate to get the associating model.\n\n### One Way Example\n\nIn this example, we are associating a `User` with a `Pet` but not a `Pet` with a `User`.\n\n```javascript\n// myApp/api/models/Pet.js\nmodule.exports = {\n  attributes: {\n    name: {\n      type: 'string'\n    },\n    color: {\n      type: 'string'\n    }\n  }\n}\n```\n\n```javascript\n// myApp/api/models/User.js\nmodule.exports = {\n  attributes: {\n    name: {\n      type: 'string'\n    },\n    age: {\n      type: 'number'\n    },\n    pony:{\n      model: 'Pet'\n    }\n  }\n}\n```\n\nNow that the association is setup, you can [populate](https://sailsjs.com/documentation/reference/waterline-orm/queries/populate) the pony association.\n\n```javascript\nvar usersWithPonies = await User.find({ name:'Mike' }).populate('pony');\n  // The users object would look something like:\n  // [{\n  //  name: 'Mike',\n  //  age: 21,\n  //  pony: {\n  //    name: 'Pinkie Pie',\n  //    color: 'pink',\n  //    id: 5,\n  //    createdAt: Tue Feb 11 2014 15:45:33 GMT-0600 (CST),\n  //    updatedAt: Tue Feb 11 2014 15:45:33 GMT-0600 (CST)\n  //  },\n  //  createdAt: Tue Feb 11 2014 15:48:53 GMT-0600 (CST),\n  //  updatedAt: Tue Feb 11 2014 15:48:53 GMT-0600 (CST),\n  //  id: 1\n  // }]\n```\n\n### Notes\n> Because we have only formed an association on one of the models, a `Pet` has no restrictions on the number of `User` models it can belong to. If we wanted to, we could change this and associate the `Pet` with exactly one `User` and the `User` with exactly one `Pet`.\n\n\n<docmeta name=\"displayName\" value=\"One way association\">\n\n"
  },
  {
    "path": "docs/concepts/ORM/Associations/OnetoMany.md",
    "content": "# One-to-many\n\n**AKA \"Has Many\"**\n\n### Overview\n\nA one-to-many association states that a model can be associated with many other models. To build this\nassociation a virtual attribute is added to a model using the `collection` property. In a one-to-many\nassociation, the \"one\" side must have a `collection` attribute, and the \"many\" side must contain a `model` attribute. This allows the \"many\" side to know which records it needs to get when `populate` is used.\n\nBecause you may want a model to have multiple one-to-many associations on another model, a `via` key\nis needed on the `collection` attribute. This states which `model` attribute on the one side of the\nassociation is used to populate the records.\n\n```javascript\n// myApp/api/models/User.js\n// A user may have many pets\nmodule.exports = {\n  attributes: {\n    firstName: {\n      type: 'string'\n    },\n    lastName: {\n      type: 'string'\n    },\n\n    // Add a reference to Pets\n    pets: {\n      collection: 'pet',\n      via: 'owner'\n    }\n  }\n};\n```\n```javascript\n// myApp/api/models/Pet.js\n// A pet may only belong to a single user\nmodule.exports = {\n  attributes: {\n    breed: {\n      type: 'string'\n    },\n    type: {\n      type: 'string'\n    },\n    name: {\n      type: 'string'\n    },\n\n    // Add a reference to User\n    owner: {\n      model: 'user'\n    }\n  }\n};\n```\n\nNow that the pets and users know about each other, they can be associated. To do this we can create\nor update a pet with the user's primary key for the `owner` value.\n\n```javascript\nawait Pet.create({\n  breed: 'labrador',\n  type: 'dog',\n  name: 'fido',\n\n  // Set the User's Primary Key to associate the Pet with the User.\n  owner: 123\n});\n```\n\nNow that the `Pet` is associated with the `User`, all the pets belonging to a specific user can\nbe populated by using the [`.populate()`](https://sailsjs.com/documentation/reference/waterline-orm/queries/populate) method.\n\n```javascript\nvar users = await User.find().populate('pets');\n  // The users object would look something like the following\n  // [{\n  //   id: 123,\n  //   firstName: 'Foo',\n  //   lastName: 'Bar',\n  //   pets: [{\n  //     id: 1,\n  //     breed: 'labrador',\n  //     type: 'dog',\n  //     name: 'fido',\n  //     user: 123\n  //   }]\n  // }]\n```\n\n\n<docmeta name=\"displayName\" value=\"One-to-many\">\n\n"
  },
  {
    "path": "docs/concepts/ORM/Associations/OnetoOne.md",
    "content": "# One-to-one\n\n**AKA \"has one\"**\n\n### Overview\n\nA one-to-one association states that a model may only be associated with one other model. In order\nfor the model to know which other model it is associated with, a foreign key must be included on one of the\nrecords along with a `unique` database constraint on it.\n\nThere are currently two ways of handling this association in Waterline.\n\n### Has one using a collection\n\nIn this example, we are associating a `Pet` with a `User`. The `User` may only have one `Pet`, and a `Pet` can only have one `User`. However, in order to query from both sides in this example, we must add a `collection` attribute to the `User` model. This allows us to call both `User.find().populate('pet')` along with `Pet.find().populate('owner')`.\n\nThe two models will stay in sync by updating the `Pet` model's `owner` attribute. Adding the `unique` property ensures that only one value for each `owner` will exist in the database. The downside is that when populating from the `User` side, you will always get an array back.\n\n```javascript\n// myApp/api/models/Pet.js\nmodule.exports = {\n  attributes: {\n    name: {\n      type: 'string'\n    },\n    color: {\n      type: 'string'\n    },\n    owner:{\n      model:'user',\n      unique: true\n    }\n  }\n}\n```\n\n```javascript\n// myApp/api/models/User.js\nmodule.exports = {\n  attributes: {\n    name: {\n      type: 'string'\n    },\n    age: {\n      type: 'number'\n    },\n    pet: {\n      collection:'pet',\n      via: 'owner'\n    }\n  }\n}\n```\n\n### Has one manual sync\n\nIn this example, we are associating a `Pet` with a `User`. The `User` may only have one `Pet` and a `Pet` can only have one `User`. However, in order to query from both sides, a `model` property is added to the `User` model. This allows us to call both `User.find().populate('pet')` along with `Pet.find().populate('owner')`.\n\nNote that the two models will not stay in sync, so when updating one side you must remember to update the other side as well.\n\n```javascript\n// myApp/api/models/Pet.js\nmodule.exports = {\n  attributes: {\n    name: {\n      type: 'string'\n    },\n    color: {\n      type: 'string'\n    },\n    owner:{\n      model:'user'\n    }\n  }\n}\n```\n\n```javascript\n// myApp/api/models/User.js\nmodule.exports = {\n  attributes: {\n    name: {\n      type: 'string'\n    },\n    age: {\n      type: 'number'\n    },\n    pet: {\n      model:'pet'\n    }\n  }\n}\n```\n\n<docmeta name=\"displayName\" value=\"One-to-one\">\n\n"
  },
  {
    "path": "docs/concepts/ORM/Associations/Reflexive.md",
    "content": "# Reflexive associations\n\n### Overview\n\nIn most cases, an association will be between attributes of two different models&mdash;for example, a relationship between a `User` model and a `Pet` model.  However, it is also possible to have a relationship between two attributes in the _same_ model.  This is called a _reflexive association_.\n\nConsider the following `User` model:\n\n```javascript\n// myApp/api/models/User.js\nmodule.exports = {\n  attributes: {\n    firstName: {\n      type: 'string'\n    },\n    lastName: {\n      type: 'string'\n    },\n\n    // Add a singular reflexive association\n    bestFriend: {\n      model: 'user',\n    },\n\n    // Add one side of a plural reflexive association\n    parents: {\n      collection: 'user',\n      via: 'children'\n    },\n\n    // Add the other side of a plural reflexive association\n    children: {\n      collection: 'user',\n      via: 'parents'\n    },\n\n    // Add another plural reflexive association, this one via-less\n    bookmarkedUsers: {\n      collection: 'user'\n    }\n\n  }\n};\n```\n\nThe reflexive associations in the example `User` model above operate just like any other associations.  The singular `bestFriend` attribute can be set to the primary key of another user (or for the narcissistic, to the same user!).  The `parents` and `children` attributes can be modified using `.addToCollection()`, `.removeFromCollection()` and `.replaceCollection()`.  Note that as with all plural associations, adding to one side will allow the relationship to be accessed by either side, so running:\n\n```javascript\n// Add User #12 as a parent of User #23\nawait User.addToCollection(23, 'parents', 12);\n// Find User #12 and populate its children\nvar userTwelve = await User.findOne(12).populate('children');\n```\n\nwould return something like:\n\n```javascript\n{\n  id: 12,\n  firstName: 'John',\n  lastName: 'Doe',\n  bestFriend: null,\n  children: [\n    {\n      id: 23,\n      firstName: 'Jane',\n      lastName: 'Doe',\n      bestFriend: null\n    }\n  ]\n}\n```\n\n\n### Notes\n> As with all &ldquo;via-less&rdquo; plural associations, reflexive via-less associations are only accessible from the side on which they are declared.  In the above `User` model, you can do `User.findOne(55).populate('bookmarkedUsers')` to find all of the users that User #55 bookmarked, but there&rsquo;s no way to get a list of all of the users that have bookmarked User #55.  To do so would require an additional attribute (e.g. `bookmarkedBy`) that would be joined to `bookmarkedUsers` using the `via` property.\n\n\n<docmeta name=\"displayName\" value=\"Reflexive associations\">\n\n"
  },
  {
    "path": "docs/concepts/ORM/Associations/ThroughAssociations.md",
    "content": "# Through associations\n\n**AKA \"has many through\"**\n\n### Overview\n\nMany-to-many through associations behave in the same way as many-to-many associations, except that in a many-to-many through association, the join table is created automatically. In a many-to-many through assocation, you define a model containing two fields that correspond to the two models you will be joining together. When defining an association, you will add the `through` key to show that the model should be used instead of the automatic join table.\n\n### Has many through example\n\n```javascript\n// myApp/api/models/User.js\nmodule.exports = {\n  attributes: {\n    name: {\n      type: 'string'\n    },\n    pets:{\n      collection: 'pet',\n      via: 'owner',\n      through: 'petuser'\n    }\n  }\n}\n```\n\n```javascript\n// myApp/api/models/Pet.js\nmodule.exports = {\n  attributes: {\n    name: {\n      type: 'string'\n    },\n    color: {\n      type: 'string'\n    },\n    owners:{\n      collection: 'user',\n      via: 'pet',\n      through: 'petuser'\n    }\n  }\n}\n```\n\n```javascript\n// myApp/api/models/PetUser.js\nmodule.exports = {\n  attributes: {\n    owner:{\n      model:'user'\n    },\n    pet: {\n      model: 'pet'\n    }\n  }\n}\n```\n\nBy using the `PetUser` model, we can use [`.populate()`](https://sailsjs.com/documentation/reference/waterline-orm/queries/populate) on both the `User` model and `Pet` model just as we do in a normal [many-to-many](https://sailsjs.com/documentation/concepts/models-and-orm/associations/many-to-many) association.\n\n> Currently, if you would like to add additional information to the `through` table, it will not be available when calling `.populate`. To do this you will need to query the `through` model manually.\n\n\n\n<docmeta name=\"displayName\" value=\"Through associations\">\n\n"
  },
  {
    "path": "docs/concepts/ORM/Attributes.md",
    "content": "# Attributes\n### Overview\n\nModel attributes are basic pieces of information about a model.  For example, a model called `Person` might have attributes named `firstName`, `lastName`, `phoneNumber`, `age`, `birthDate` and `emailAddress`.\n\n<!---\nFUTURE: address sql vs. no sql and stuff like:\n\"\"\"\nIn most cases, this data is _homogenous_, meaning each record has the same attributes,\n\"\"\"\n-->\n\n\n\n### Defining attributes\n\nA model's `attributes` [setting](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings) allows you to provide a set of attributes, each defined as a dictionary (a plain JavaScript object):\n\n```javascript\n// api/models/User.js\n{\n  attributes: {\n    emailAddress: { type: 'string', required: true, },\n    karma: { type: 'number', },\n    isSubscribedToNewsletter: { type: 'boolean', defaultsTo: true, },\n  },\n}\n```\n\nWithin each attribute, there are one or more keys&mdash;or options&mdash;which are used to provide additional direction to Sails and Waterline.  These attribute keys tell the model how to go about ensuring type safety, enforcing high-level validation rules, and (if you have automigrations enabled) how it should go about setting up tables or collections in your database.\n\n\n##### Default attributes\n\nYou can also define default attributes that will appear in _all_ of your models, by defining `attributes` as a [default model setting](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings) (e.g. in `config/models.js`).  For example, new Sails apps come with three default attributes out of the box: `id`, `createdAt`, and `updatedAt`.\n\nThese attributes will be available in all models unless they are overridden or disabled.  To override a default attribute, define an attribute with the same name in the model definition.  To _disable_ a default attribute, define it as `false`.  For instance, to disable the default `updatedAt` attribute for a particular model:\n\n```javascript\n// api/models/ProductCategory.js\nmodule.exports = {\n  attributes: {\n    updatedAt: false,\n    label: { type: 'string', required: true },\n  }\n}\n```\n\n\n\n### Type safety\n\n\n##### Type\n\nExcept for [associations](https://sailsjs.com/documentation/concepts/models-and-orm/associations), every attribute must declare a `type`.\n\nThis is the type of data that will be stored for this attribute and used for logical type safety checks of queries and results.  Here is a list of the data types supported by Sails and Waterline:\n\n- string\n- number\n- boolean\n- json\n- ref\n\n\n\n##### Required\n\nIf an attribute is `required: true`, then a value must always be specified for it when calling `.create()`.  This prevents the attribute value from being created as or updated to `null` or empty string (\"\").\n\n\n##### Default values\n\nIn addition to the five data types, there are a couple of other basic guarantees that you can define for an attribute; one of these is the ability to assign it a default value.\n\nThe default value (`defaultsTo`) of an attribute only applies on `.create()`, and only when the key is omitted entirely.\n\n\n```javascript\nattributes: {\n  phoneNumber: {\n    type: 'string',\n    defaultsTo: '111-222-3333'\n  }\n}\n```\n\n##### Allow Null\n\nThe `string`, `number`, and `boolean` data types do _not_ accept `null` as a value when creating or updating records.  In order to allow a `null` value to be set, you can toggle the `allowNull` flag on the attribute. Note that the `allowNull` flag is only valid on the data types listed above. It is _not_ valid on attributes with types `json` or `ref`, any associations, or any primary key attributes.\n\n\n```javascript\nattributes: {\n  phoneNumber: {\n    type: 'string',\n    allowNull: true\n  }\n}\n```\n\n### Validations\n\nIn addition to basic type safety checks, Sails offers several different high-level validation rules.  For example, the `isIn` rule verifies that any new value stored for this attribute must _exactly match_ one of a few different hard-coded constants:\n\n```javascript\nunsubscribeReason: {\n  type: 'string',\n  isIn: ['boring', 'too many emails', 'recipes too difficult', 'other'],\n  required: true\n}\n```\n\nFor a complete list of high-level validation rules, see [Validations](https://sailsjs.com/documentation/concepts/models-and-orm/validations).\n\n\n\n\n<!--\n\nFUTURE: need ot move primary key out to the top-level (it's a model setting now)\n\ncommented-out content at: https://gist.github.com/rachaelshaw/f10d70c73780d5087d4c936cdefd5648#1\n-->\n\n\n### columnName\n\nInside an attribute definition, you can specify a `columnName` to force Sails/Waterline to store data for that attribute in a specific column in the configured datastore (i.e. database).  Be aware that this is not necessarily SQL-specific&mdash;it will also work for MongoDB fields, etc.\n\nWhile the `columnName` property is primarily designed for working with existing/legacy databases, it can also be useful in situations where your database is being shared by other applications, or those in which you don't have access permissions to change the schema.\n\nTo store/fetch your model's `numberOfWheels` attribute into/from the `number_of_round_rotating_things` column:\n```javascript\n  // An attribute in one of your models:\n  // ...\n  numberOfWheels: {\n    type: 'number',\n    columnName: 'number_of_round_rotating_things'\n  }\n  // ...\n```\n\n\nNow for a more comprehensive example.\n\nLet's say you have a `User` model in your Sails app that looks like this:\n\n```javascript\n// api/models/User.js\nmodule.exports = {\n  datastore: 'shinyNewMySQLDatabase',\n  attributes: {\n    name: {\n      type: 'string'\n    },\n    password: {\n      type: 'string'\n    },\n    email: {\n      type: 'string',\n      unique: true\n    }\n  }\n};\n```\n\n\nEverything works great, but instead of using an existing MySQL database sitting on a server somewhere that happens to house your app's intended users...\n\n```javascript\n// config/datastores.js\nmodule.exports = {\n  // ...\n\n  // Existing users are in here!\n  rustyOldMySQLDatabase: {\n    adapter: 'sails-mysql',\n    url: 'mysql://ofh:Gh19R!?@db.eleven.sameness.foo/jonas'\n  },\n  // ...\n};\n```\n\n... let's say there's a table called `our_users` in the old MySQL database that looks like this:\n\n| the_primary_key | email_address | full_name | seriously_hashed_password|\n|------|---|----|---|\n| 7 | mike@sameness.foo | Mike McNeil | ranchdressing |\n| 14 | nick@sameness.foo | Nick Crumrine | thousandisland |\n\n\nIn order to use this from Sails, you'd change your `User` model to look like this:\n\n```javascript\n// api/models/User.js\nmodule.exports = {\n  datastore: 'rustyOldMySQLDatabase',\n  tableName: 'our_users',\n  attributes: {\n    id: {\n      type: 'number',\n      unique: true,\n      columnName: 'the_primary_key'\n    },\n    name: {\n      type: 'string',\n      columnName: 'full_name'\n    },\n    password: {\n      type: 'string',\n      columnName: 'seriously_hashed_password'\n    },\n    email: {\n      type: 'string',\n      unique: true,\n      columnName: 'email_address'\n    }\n  }\n};\n```\n\n> You might have noticed that we also used the [`tableName`](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?tablename) property in this example.  This allows us to control the name of the table that will be used to house our data.\n\n\n### Encryption at rest\n\n##### encrypt\n\nSetting `encrypt` allows you to decide whether this attribute should be automatically encrypted. If set to `true`, when a record is retrieved, it will still contain the encrypted value for this attribute unless [`.decrypt()`](https://sailsjs.com/documentation/reference/waterline-orm/queries/decrypt) is used.\n\n```javascript\nattributes: {\n  ssn: {\n    type: 'string',\n    encrypt: true\n  }\n}\n```\n> If you're using `encrypt: true` for an attribute, you won't be able to look up records by the unencrypted value.\n\n\n\n\n### Automigrations\n\nThese settings are used to indicate how Sails should create the physical-level (e.g. PostgreSQL, MySQL or MongoDB) database field for an attribute when an app is lifted.\n\n> When a model&rsquo;s `migrate` property is set to `safe`, these settings will be ignored and the database columns will remain unchanged.\n\n##### columnType\n\nIndicates the type of physical-level column data type to use for an attribute when Sails creates the database table. This allows you to specify types that are tied directly to how your underlying database will create them. For example, you may have an attribute that sets its `type` property to `number` and to store that in the database you want to use the column type `float`. Your attribute definition would look like:\n\n```javascript\nattributes: {\n  placeInLine: {\n    type: 'number',\n    columnType: 'float'\n  }\n}\n```\n\n> * Column types are entirely database-dependent.  Be sure that the `columnType` you select corresponds to a data type that is valid for your database!  If you don&rsquo;t specify a `columnType`, the adapter will choose one for you based on the attribute&rsquo;s `type`.\n> * The `columnType` value is used verbatim in the statement that creates the database column, so you can use it to specify additional options, e.g. `varchar(255) CHARACTER SET utf8mb4`.\n> * If you intend to store binary data in a Sails model, you&rsquo;ll want to set the `type` of the attribute to `ref`, and then use the appropriate `columnType` for your chosen database (e.g. `mediumblob` for MySQL or `bytea` for PostgreSQL).  Keep in mind that whatever you attempt to store will have to fit in memory before being transferred to the database, as there is currently no mechanism in Sails for streaming binary data to a datastore adapter.  As an alternative to storing blobs in a database, you might consider streaming them to disk or to a remote filesystem like S3, using the [`.upload()` method](https://sailsjs.com/documentation/concepts/file-uploads).\n> * Keep in mind that custom column options like `CHARACTER SET utf8mb4` in MySQL can affect a column&rsquo;s storage size. This is especially relevant when used in conjunction with the `unique` property, where you may have to specify a smaller column size to avoid errors.  See the [`unique` property](https://sailsjs.com/documentation/concepts/models-and-orm/attributes#?unique) docs below for more info.\n\n##### autoIncrement\n\nSets up the attribute as an auto-increment key.  When a new record is added to the model, if a value for this attribute is not specified, it will be generated by incrementing the most recent record's value by one. Note: attributes that specify `autoIncrement` should always be of `type: 'number'`. Also bear in mind that the level of support varies across different datastores. For instance, MySQL will not allow more than one auto-incrementing column per table.\n\n```javascript\nattributes: {\n  placeInLine: {\n    type: 'number',\n    autoIncrement: true\n  }\n}\n```\n\n##### unique\n\nEnsures no two records will be allowed with the same value for the target attribute. This is an adapter-level constraint, so in most cases this will result in a unique index on the attribute being created in the underlying datastore.\n\n```javascript\nattributes: {\n  username: {\n    type: 'string',\n    unique: true\n  }\n}\n```\n\nDepending on your database, when using `unique: true`, you may also need set `required: true`. \n\n> When using `unique: true` on an attribute with the `utf8mb4` character set in a MySQL database, you will need to set the column size manually via the [`columnType` property](https://sailsjs.com/documentation/concepts/models-and-orm/attributes#?columntype) to avoid a possible 'index too long' error.  For example: `columnType: varchar(100) CHARACTER SET utf8mb4`.\n\n\n<!--\n\ncommented-out content at: https://gist.github.com/rachaelshaw/f10d70c73780d5087d4c936cdefd5648#2\n\n\ncommented-out content at: https://gist.github.com/rachaelshaw/f10d70c73780d5087d4c936cdefd5648#3\n\n\nFUTURE: move enum to validations page\n\n\ncommented-out content at: https://gist.github.com/rachaelshaw/f10d70c73780d5087d4c936cdefd5648#4\n\n\ncommented-out content at: https://gist.github.com/rachaelshaw/f10d70c73780d5087d4c936cdefd5648#5\n\n\n-->\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"Attributes\">\n"
  },
  {
    "path": "docs/concepts/ORM/Lifecyclecallbacks.md",
    "content": "# Lifecycle callbacks\n\n### Overview\n\nLifecycle callbacks are functions that are called before or after certain model methods.  For example, you might use lifecycle callbacks to automatically compute the value of a `fullName` attribute before creating or updating a `User` record.\n\nSails exposes a handful of lifecycle callbacks by default:\n\n##### Lifecycle callbacks on `.create()`\n\nThe `afterCreate` lifecycle callback will only be run on queries that have the `fetch` meta flag set to `true`. For more information on using the `meta` flags, see [Waterline Queries](https://sailsjs.com/documentation/reference/waterline-orm/queries/meta).\n\n  - beforeCreate: fn(recordToCreate, proceed)\n  - afterCreate: fn(newlyCreatedRecord, proceed)\n\n> `beforeCreate` is also run on bulk inserts of data when you call `.createEach()`. However, `afterCreate` is **not**.\n\n##### Lifecycle callbacks on `.update()`\n\nThe `afterUpdate` lifecycle callback will only be run on `.update()` queries that have the `fetch` meta flag set to `true`. For more information on using the `meta` flags, see [Waterline Queries](https://sailsjs.com/documentation/reference/waterline-orm/queries/meta).\n\n  - beforeUpdate: fn(valuesToSet, proceed)\n  - afterUpdate: fn(updatedRecord, proceed)\n\n##### Lifecycle callbacks on `.destroy()`\n\nThe `afterDestroy` lifecycle callback will only be run on `.destroy()` queries that have the `fetch` meta flag set to `true`. For more information on using the `meta` flags, see [Waterline Queries](https://sailsjs.com/documentation/reference/waterline-orm/queries/meta).\n\n  - beforeDestroy: fn(criteria, proceed)\n  - afterDestroy: fn(destroyedRecord, proceed)\n\n\n### Example\n\nIf you want to hash a password before saving in the database, you might use the `beforeCreate` lifecycle callback.\n\n```javascript\n// User.js\nmodule.exports = {\n\n  attributes: {\n\n    username: {\n      type: 'string',\n      required: true\n    },\n\n    password: {\n      type: 'string',\n      minLength: 6,\n      required: true\n    }\n\n  },\n\n\n  beforeCreate: function (valuesToSet, proceed) {\n    // Hash password\n    sails.helpers.passwords.hashPassword(valuesToSet.password).exec((err, hashedPassword)=>{\n      if (err) { return proceed(err); }\n      valuesToSet.password = hashedPassword;\n      return proceed();\n    });//_∏_\n  }\n  \n};\n```\n\n\n<docmeta name=\"displayName\" value=\"Lifecycle callbacks\">\n"
  },
  {
    "path": "docs/concepts/ORM/Models.md",
    "content": "# Models\n\nA model represents a set of structured data, called records.  Models usually correspond to a table/collection in a database, attributes correspond to columns/fields, and records correspond to rows/documents.\n\n### Defining models\n\nBy convention, models are defined by creating a file in a Sails app's `api/models/` folder:\n\n```javascript\n// api/models/Product.js\nmodule.exports = {\n  attributes: {\n    nameOnMenu: { type: 'string', required: true },\n    price: { type: 'string', required: true },\n    percentRealMeat: { type: 'number', defaultsTo: 20, columnType: 'FLOAT' },\n    numCalories: { type: 'number' },\n  },\n};\n```\n\nFor a complete walkthrough of available options when setting up a model definition, see [Model Settings](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings), [Attributes](https://sailsjs.com/documentation/concepts/models-and-orm/attributes), and [Associations](https://sailsjs.com/documentation/concepts/models-and-orm/associations).\n\n<!--\ncommented-out content at: https://gist.github.com/rachaelshaw/1d7a989f6685f11134de3a5c47b2ebb8#1\n\n\ncommented-out content at: https://gist.github.com/rachaelshaw/1d7a989f6685f11134de3a5c47b2ebb8#2\n-->\n\n\n\n### Using models\n\nOnce a Sails app is running, its models may be accessed from within controller actions, helpers, tests, and just about anywhere else you normally write backend code.  This allows your code's call model methods to communicate with your database (or even with multiple databases).\n\nThere are many built-in methods available on models, the most important of which are the model methods like [.find()](https://sailsjs.com/documentation/reference/waterline/models/find) and [.create()](https://sailsjs.com/documentation/reference/waterline/models/create).  You can find detailed usage documentation for methods like these in [Reference > Waterline (ORM) > Models](https://sailsjs.com/documentation/reference/waterline-orm/models).\n\n\n### Query methods\n\nEvery model in Sails has a set of methods exposed on it to allow you to interact with the database in a normalized fashion. This is the primary way of interacting with your app's data.\n\nSince they usually have to send a query to the database and wait for a response, most model methods are **asynchronous**.  That is, they don't come back with an answer right away.  Like other asynchronous logic in JavaScript (`setTimeout()` for example), that means we need some other way of determining when they've finished executing, whether they were successful, and, if not, what kind of error (or other exceptional circumstance) occurred.\n\nIn Node.js, Sails, and JavaScript in general, the recommended way to handle this is by using [`async/await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await).\n\nFor more information about working with queries, see [Reference > Waterline (ORM) > Queries](https://sailsjs.com/documentation/reference/waterline-orm/queries).\n\n### Resourceful pubsub methods\n\nSails also provides a few other \"resourceful pubsub\" (or RPS) methods specifically designed for performing simple realtime operations using dynamic rooms.  For more information about those methods, see [Reference > WebSockets > Resourceful PubSub](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub).\n\n\n### Custom model methods\n\nIn addition to the built-in functionality provided by Sails, you can also define your own custom model methods.  Custom model methods are most useful for extrapolating controller code that relates to a particular model. They allow code to be pulled out of controllers and inserted into reusuable functions that can be called from anywhere (independent of `req` or `res`).\n\n> This feature takes advantage of the fact that models ignore unrecognized settings, so you do need to be careful about inadvertently overriding built-in methods (don't define methods named \"create\", for example).\n>\n> If you're at all unsure, write a [helper](https://sailsjs.com/documentation/concepts/helpers) instead.\n\nCustom model methods can be synchronous or asynchronous functions, but more often than not, they're _asynchronous_.  By convention, asynchronous model methods should be `async` functions, which accept a dictionary of `options` as their argument.\n\nFor example:\n\n```js\n// in api/models/Monkey.js...\n\n// Find monkeys with the same name as the specified person\nfindWithSameNameAsPerson: async function (opts) {\n\tvar person = await Person.findOne({ id: opts.id });\n\t\n\tif (!person) {\n\t\tthrow require('flaverr')({\n      message: `Cannot find monkeys with the same name as the person w/ id=${opts.id} because that person does not exist.`,\n      code: 'E_UNKNOWN_PERSON'\n    });\n\t}\n\t\n\treturn await Monkey.find({ name: person.name });\n}\n```\n> Notice we didn't `try/catch` any of the code within that function. That's because we intend to leave that responsibility to whoever calls our function.\n\nThen you can do:\n\n```js\nvar monkeys = await Monkey.findWithSameNameAsPerson({id:37});\n```\n\n> For more tips, read about the incident involving [Timothy the Monkey]().\n\n##### What about instance methods?\n\nAs of Sails v1.0, instance methods have been removed from Sails and Waterline.  While instance methods like `.save()` and `.destroy()` were sometimes convenient in app code, in Node.js at least, many users found that they led to unintended consequences and design pitfalls.\n\nFor example, consider an app that manages wedding records.  It might seem like a good idea to write an instance method on the Person model to update the `spouse` attribute on both individuals in the database.  This would allow you to write controller code like:\n\n```js\npersonA.marry(personB, function (err) {\n  if (err) { return res.serverError(err); }\n  return res.ok();\n})\n```\n\nWhich looks great...until it comes time to implement a slightly different action with roughly the same logic, but where the only available data is the id of \"personA\" (not the entire record).  In that case, you're stuck rewriting your instance method as a static method anyway!\n\nA better strategy is to write a custom (static) model method from the get-go.  This makes your function more reusable/versatile, since it will be accessible whether or not you have an actual record instance on hand.  You might refactor the code from the previous example to look like:\n\n```js\nPerson.marry(personA.id, personB.id, function (err) {\n  if (err) { return res.serverError(err); }\n  return res.ok();\n})\n```\n\n### Case sensitivity\n\nQueries in Sails v1.0 are no longer forced to be case *insensitive* regardless of how the database processes the query. This leads to much-improved query performance and better index utilization. Most databases are case *sensitive* by default, but in the rare cases where they aren't and you would like to change that behavior you must modify the database to do so.\n\nFor example, MySQL will use a database collation that is case *insensitive* by default. This is different from sails-disk, so you may experience different results from development to production. In order to fix this, you can set the tables in your MySQL database to a case *sensitive* collation such as `utf8_bin`.\n\n\n<!--\ncommented-out content at: https://gist.github.com/rachaelshaw/1d7a989f6685f11134de3a5c47b2ebb8#3\n\n\ncommented-out content at: https://gist.github.com/rachaelshaw/1d7a989f6685f11134de3a5c47b2ebb8#4\n\ncommented-out content at: https://gist.github.com/rachaelshaw/1d7a989f6685f11134de3a5c47b2ebb8#5\n\ncommented-out content at: https://gist.github.com/rachaelshaw/1d7a989f6685f11134de3a5c47b2ebb8#6\n-->\n\n<docmeta name=\"displayName\" value=\"Models\">\n<docmeta name=\"nextUpLink\" value=\"/documentation/concepts/configuration\">\n<docmeta name=\"nextUpName\" value=\"Configuration\">\n"
  },
  {
    "path": "docs/concepts/ORM/ORM.md",
    "content": "# Waterline: SQL/noSQL Data Mapper (ORM/ODM)\n\n\nSails comes installed with a powerful [ORM/ODM](http://stackoverflow.com/questions/12261866/what-is-the-difference-between-an-orm-and-an-odm) called [Waterline](https://github.com/balderdashy/waterline), a datastore-agnostic tool that dramatically simplifies interaction with one or more databases. It provides an abstraction layer on top of the underlying database, allowing you to easily query and manipulate your data _without_ writing vendor-specific integration code.\n\n### Database Agnosticism\n\nIn schemaful databases like [Postgres](http://www.postgresql.org/), [Oracle](https://www.oracle.com/database), and [MySQL](http://www.mysql.com), models are represented by tables.  In [MongoDB](http://www.mongodb.org), they're represented by Mongo \"collections\".  In [Redis](http://redis.io), they're represented using key/value pairs.  Each database has its own distinct query dialect, and in some cases even requires installing and compiling a specific native module to connect to the server.  This involves a fair amount of overhead, and garners an unsettling level of [vendor lock-in](https://en.wikipedia.org/wiki/Vendor_lock-in) to a specific database; for example, if your app uses a bunch of SQL queries, it will be very hard to switch to Mongo later, or Redis, and vice versa.\n\nWaterline query syntax floats above all that, focusing on business logic like creating new records, fetching/searching existing records, updating records, or destroying records.  No matter what database you're contacting, the usage is _exactly the same_.  Furthermore, Waterline allows you to [`.populate()`](https://sailsjs.com/documentation/reference/waterline-orm/queries/populate) associations between models, _even if_ the data for each model lives in a different database.  That means you can switch your app's models from Mongo, to PostgreSQL, to MySQL and back again&mdash; with minimal code changes.  For the times when you need low-level, database-specific functionality, Waterline provides a query interface that allows you to talk directly to your models' underlying database driver (see [.query()](https://sailsjs.com/documentation/reference/waterline-orm/models/query) and [.native()](https://sailsjs.com/documentation/reference/waterline-orm/models/native)).\n\n\n\n### Scenario\n\nLet's imagine you're building an e-commerce website, with an accompanying mobile app.  Users browse products by category or search for products by keyword, then they buy them.  That's it!  Some parts of your app are quite ordinary: you have an API-driven flow for logging in, signing up, order/payment processing, resetting passwords, etc. However, you know there are a few mundane features lurking in your roadmap that will likely become more involved.  Sure enough:\n\n##### Flexibility\n\n_You ask the business what database they would like to use:_\n\n> \"Datab... what?  Let's not be hasty, wouldn't want to make the wrong choice.  I'll get ops/IT on it.  Go ahead and get started though.\"\n\nThe traditional methodology of choosing one single database for a web application/API is actually prohibitive for some production use cases. While most apps can get away with just one type of database, some applications need to maintain compatibility with existing data sets, or (if you're working on a high-volume production app) use more than one type of database for performance reasons.\n\nSince Sails uses `sails-disk` by default, you can start building your app with zero configuration, using a local temporary file as storage.  When you're ready to switch to the real thing (and when everyone knows what that is), just change your app's [datastore configuration](https://sailsjs.com/documentation/reference/configuration/sails-config-datastores).\n\n\n\n##### Compatibility\n\n_The product owner/stakeholder walks up to you and says:_\n\n> \"Oh hey by the way, the product's actually already live in our point of sale system. It's some ERP thing I guess, something like \"DB2\"?  Anyway, I'm sure you'll figure it out. Sounds easy right?\"\n\nMany enterprise applications must integrate with an existing database.  If you're lucky, a one-time data migration may be all that's necessary, but more commonly, the existing dataset is still being modified by other applications.  In order to build your app, you might need to marry data from multiple legacy systems, or with a separate dataset stored elsewhere.  These datasets could live on five different servers scattered across the world! One colocated database server might house a SQL database with relational data, while another cloud server might hold a handful of Mongo or Redis collections.\n\nSails/Waterline lets you hook up different models to different datastores, locally or anywhere on the internet.  You can build a User model that maps to a custom MySQL table in a legacy database (with weird crazy column names).  Likewise for a Product model that maps to a table in DB2, or an Order model that maps to a MongoDB collection.  Best of all, you can `.populate()` across these different datastores and adapters, so if you configure a model to live in a different database, your controller/model code doesn't need to change (note that you _will_ need to migrate any important production data manually).\n\n##### Performance\n\n_You're sitting in front of your laptop late at night, and you realize:_\n> \"How can I do keyword search?  The product data doesn't have any keywords, and the business wants search results ranked based on n-gram word sequences.  Also I have no idea how this recommendation engine is going to work.  Also if I hear the words `big data` one more time tonight I'm quitting and going back to work at the coffee shop.\"\n\nSo what about the \"big data\"?  Normally when you hear bloggers and analyst use that buzzword, you think of data mining and business intelligence.  You might imagine a process that pulls data from multiple sources, processes/indexes/analyzes it, then writes that extracted information somewhere else and either keeps the original data or throws it away.\n\nHowever, there are some much more common challenges that lend themselves to the same sort of indexing/analysis needs: for example, features like \"driving-direction-proximity\" search, or a recommendation engine for related products.  Fortunately, a number of databases simplify specific big-data use cases. MongoDB, for instance, provides geospatial indexing, while ElasticSearch provides excellent support for indexing data for full-text search.\n\nUsing databases in the way they're intended affords tremendous performance benefits, particularly when it comes to complex report queries, searching (which is really just customized sorting), and NLP/machine learning.  Certain databases are very good at answering traditional relational business queries, while others are better suited for map/reduce-style processing of data, with both optimizations and trade-offs for blazing-fast read/writes.  This consideration is especially important as your app's user-base scales.\n\n### Adapters\n\nLike most MVC frameworks, Sails supports [multiple databases](https://sailsjs.com/features).  That means the syntax to query and manipulate our data is always the same, whether we're using MongoDB, MySQL, or any other supported database.\n\nWaterline builds on this flexibility with its concept of adapters.  An adapter is a bit of code that maps methods like `find()` and `create()` to a lower-level syntax like `SELECT * FROM` and `INSERT INTO`.  The Sails core team maintains open-source adapters for a handful of the [most popular databases](https://sailsjs.com/features), and a wealth of [community adapters](https://sailsjs.com/documentation/concepts/extending-sails/adapters/available-adapters#?communitysupported-database-adapters) are also available.\n\nCustom Waterline adapters are actually [pretty simple to build](https://github.com/balderdashy/sails-generate-adapter), and can make for more maintainable integrations: anything from a proprietary enterprise account system, to a cache, to a traditional database.\n\n\n### Datastores\n\nA **datastore** represents a particular database configuration.  This configuration object includes an adapter to use, plus information like the host, port, username, password, and so forth.  Datastores are defined in the Sails config [`config/datastores.js`](https://sailsjs.com/documentation/reference/configuration/sails-config-datastores).\n\n```javascript\n// in config/datastores.js\n// ...\n{\n  adapter: 'sails-mysql',\n  host: 'localhost',\n  port: 3306,\n  user: 'root',\n  password: 'g3tInCr4zee&stUfF',\n  database: 'database-name'\n}\n// ...\n```\n\n\n### Analogy\n\nImagine a file cabinet full of completed pen-and-ink forms. All of the forms have the same fields (e.g. \"name\", \"birthdate\", \"maritalStatus\"), but for each form, the _values_ written in the fields vary.  For example, one form might contain \"Lara\", \"2000-03-16T21:16:15.127Z\", \"single\", while another form contains \"Larry\", \"1974-01-16T21:16:15.127Z\", \"married\".\n\nNow imagine you're running a hot dog business.  If you were _very_ organized, you might set up your file cabinets as follows:\n\n+ **Employee** (contains your employee records)\n  + `fullName`\n  + `hourlyWage`\n  + `phoneNumber`\n+ **Location** (contains a record for each location you operate)\n  + `streetAddress`\n  + `city`\n  + `state`\n  + `zipcode`\n  + `purchases`\n    + a list of all the purchases made at this location\n  + `manager`\n    + the employee who manages this location\n+ **Purchase** (contains a record for each purchase made by one of your customers)\n  + `madeAtLocation`\n  + `productsPurchased`\n  + `createdAt`\n+ **Product** (contains a record for each of your various product offerings)\n  + `nameOnMenu`\n  + `price`\n  + `numCalories`\n  + `percentRealMeat`\n  + `availableAt`\n    + a list of the locations where this product offering is available.\n\n\nIn your Sails app, a **model** is like one of the file cabinets.  It contains **records**, which are like the forms.  `Attributes` are like the fields in each form.\n\n\n\n### Notes\n+ This documentation on models is not applicable if you are overriding the built-in ORM, [Waterline](https://github.com/balderdashy/waterline).  In that case, your models will follow whatever convention you set up, on top of whatever ORM library you're using (e.g. Mongoose).\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"Models and ORM\">\n<docmeta name=\"nextUpLink\" value=\"/documentation/concepts/models-and-orm/models\">\n<docmeta name=\"nextUpName\" value=\"Models\">\n"
  },
  {
    "path": "docs/concepts/ORM/Querylanguage.md",
    "content": "# Waterline query language\n\nThe syntax supported by Sails' model methods is called Waterline Query Language.  Waterline knows how to interpret this syntax to retrieve or mutate records from any supported database.  Under the covers, Waterline uses the database adapter(s) installed in your project to translate this language into native queries and send those queries to the appropriate database.  This means that you can use the same query with MySQL as you do with Redis or MongoDB. It also means that you can change your database with minimal (if any) changes to your application code.\n\n\n### Query language basics\n\nThe criteria objects are formed using one of four types of object keys. These are the top level\nkeys used in a query object. They are loosely based on the criteria used in MongoDB, with a few slight variations.\n\nQueries can be built using either a `where` key to specify attributes, or excluding it.\n\nUsing the `where` key allows you to also use [query options](https://sailsjs.com/documentation/concepts/models-and-orm/query-language#query-options), such as `limit`, `skip`, and `sort`.\n\n\n```javascript\nvar thirdPageOfRecentPeopleNamedMary = await Model.find({\n  where: { name: 'mary' },\n  skip: 20,\n  limit: 10,\n  sort: 'createdAt DESC'\n});\n```\nConstraints can be further joined together in a more complex example.\n\n```javascript\nvar teachersNamedMaryInMaine = await Model.find({\n  where: { name: 'mary', state: 'me', occupation: { contains: 'teacher' } },\n  sort: [{ firstName: 'ASC'}, { lastName: 'ASC'}]\n});\n```\n\nIf `where` is excluded, the entire object will be treated as a `where` criteria.\n\n```javascript\nvar peopleNamedMary = await Model.find({\n  name: 'mary'\n});\n```\n\n#### Key pairs\n\nA key pair can be used to search records for values matching exactly what is specified. This is the base of a criteria object where the key represents an attribute on a model and the value is a strict equality check of the records for matching values.\n\n```javascript\nvar peopleNamedLyra = await Model.find({\n  name: 'lyra'\n});\n```\n\nThey can be used together to search multiple attributes.\n\n```javascript\nvar waltersFromNewMexico = await Model.find({\n  name: 'walter',\n  state: 'new mexico'\n});\n```\n\n#### Complex constraints\n\nComplex constraints also have model attributes for keys but they also use any of the supported criteria modifiers to perform queries where a strict equality check wouldn't work.\n\n```javascript\nvar peoplePossiblyNamedLyra = await Model.find({\n  name : {\n    'contains' : 'yra'\n  }\n});\n```\n\n#### In modifier\n\nProvide an array to find records whose value for this attribute exactly matches _any_ of the specified search terms.\n\n> This is more or less equivalent to \"IN\" queries in SQL, and the `$in` operator in MongoDB.\n\n```javascript\nvar waltersAndSkylers = await Model.find({\n  name : ['walter', 'skyler']\n});\n```\n\n#### Not-in modifier\n\nProvide an array wrapped in a dictionary under a `!=` key (like `{ '!=': [...] }`) to find records whose value for this attribute _ARE NOT_ exact matches for any of the specified search terms.\n\n> This is more or less equivalent to \"NOT IN\" queries in SQL, and the `$nin` operator in MongoDB.\n\n```javascript\nvar everyoneExceptWaltersAndSkylers = await Model.find({\n  name: { '!=' : ['walter', 'skyler'] }\n});\n```\n\n#### Or predicate\n\nUse the `or` modifier to match _any_ of the nested rulesets you specify as an array of query pairs.  For records to match an `or` query, they must match at least one of the specified query modifiers in the `or` array.\n\n```javascript\nvar waltersAndTeachers = await Model.find({\n  or : [\n    { name: 'walter' },\n    { occupation: 'teacher' }\n  ]\n});\n```\n\n### Criteria modifiers\n\nThe following modifiers are available to use when building queries.\n\n* `'<'`\n* `'<='`\n* `'>'`\n* `'>='`\n* `'!='`\n* `nin`\n* `in`\n* `contains`\n* `startsWith`\n* `endsWith`\n\n> Note that the availability and behavior of the criteria modifiers when matching against attributes with [JSON attributes](https://sailsjs.com/documentation/concepts/models-and-orm/validations#?builtin-data-types) may vary according to the database adapter you&rsquo;re using.  For instance, while `sails-postgresql` will map your JSON attributes to the <a href=\"https://www.postgresql.org/docs/9.4/static/datatype-json.html\" target=\"_blank\">JSON column type</a>, you&rsquo;ll need to [send a native query](https://sailsjs.com/documentation/reference/waterline-orm/datastores/send-native-query) in order to query those attributes directly.  On the other hand, `sails-mongo` supports queries against JSON-type attributes, but you should be aware that if a field contains an array, the query criteria will be run against every _item_ in the array, rather than the array itself (this is based on the behavior of MongoDB itself).\n\n#### '<'\n\nSearches for records where the value is less than the value specified.\n\n```usage\nModel.find({\n  age: { '<': 30 }\n});\n```\n\n#### '<='\n\nSearches for records where the value is less or equal to the value specified.\n\n```usage\nModel.find({\n  age: { '<=': 20 }\n});\n```\n\n#### '>'\n\nSearches for records where the value is greater than the value specified.\n\n```usage\nModel.find({\n  age: { '>': 18 }\n});\n```\n\n#### '>='\n\nSearches for records where the value is greater than or equal to the value specified.\n\n```usage\nModel.find({\n  age: { '>=': 21 }\n});\n```\n\n#### '!='\n\nSearches for records where the value is not equal to the value specified.\n\n```usage\nModel.find({\n  name: { '!=': 'foo' }\n});\n```\n\n#### in\n\nSearches for records where the value is in the list of values.\n\n```usage\nModel.find({\n  name: { in: ['foo', 'bar'] }\n});\n```\n\n#### nin\n\nSearches for records where the value is NOT in the list of values.\n\n```usage\nModel.find({\n  name: { nin: ['foo', 'bar'] }\n});\n```\n\n#### contains\n\nSearches for records where the value for this attribute _contains_ the given string.\n\n```usage\nvar musicCourses = await Course.find({\n  subject: { contains: 'music' }\n});\n```\n\n_For performance reasons, case-sensitivity of `contains` depends on the database adapter._\n\n#### startsWith\n\nSearches for records where the value for this attribute _starts with_ the given string.\n\n```usage\nvar coursesAboutAmerica = await Course.find({\n  subject: { startsWith: 'american' }\n});\n```\n\n_For performance reasons, case-sensitivity of `startsWith` depends on the database adapter._\n\n#### endsWith\n\nSearches for records where the value for this attribute _ends with_ the given string.\n\n```usage\nvar historyCourses = await Course.find({\n  subject: { endsWith: 'history' }\n});\n```\n\n_For performance reasons, case-sensitivity of `endsWith` depends on the database adapter._\n\n\n### Query options\n\nQuery options allow you refine the results that are returned from a query. They are used\nin conjunction with a `where` key. The current options available are:\n\n* `limit`\n* `skip`\n* `sort`\n\n#### Limit\n\nLimits the number of results returned from a query.\n\n```usage\nModel.find({ where: { name: 'foo' }, limit: 20 });\n```\n\n> Note: if you set `limit` to 0, the query will always return an empty array.\n\n#### Skip\n\nReturns all the results excluding the number of items to skip.\n\n```usage\nModel.find({ where: { name: 'foo' }, skip: 10 });\n```\n\n##### Pagination\n\n`skip` and `limit` can be used together to build up a pagination system.\n\n```usage\nModel.find({ where: { name: 'foo' }, limit: 10, skip: 10 });\n```\n\n> **Waterline**\n>\n> You can find out more about the Waterline API below:\n> * [Sails.js Documentation](https://sailsjs.com/documentation/reference/waterline-orm/queries)\n> * [Waterline README](https://github.com/balderdashy/waterline/blob/master/README.md)\n> * [Waterline Reference Docs](https://sailsjs.com/documentation/reference/waterline-orm)\n> * [Waterline Github Repository](https://github.com/balderdashy/waterline)\n\n\n#### Sort\n\nResults can be sorted by attribute name. Simply specify an attribute name for natural (ascending)\nsort, or specify an `ASC` or `DESC` flag for ascending or descending orders respectively.\n\n```usage\n// Sort by name in ascending order\nModel.find({ where: { name: 'foo' }, sort: 'name' });\n\n// Sort by name in descending order\nModel.find({ where: { name: 'foo' }, sort: 'name DESC' });\n\n// Sort by name in ascending order\nModel.find({ where: { name: 'foo' }, sort: 'name ASC' });\n\n// Sort by object notation\nModel.find({ where: { name: 'foo' }, sort: [{ 'name': 'ASC' }] });\n\n// Sort by multiple attributes\nModel.find({ where: { name: 'foo' }, sort: [{ name:  'ASC'}, { age: 'DESC' }] });\n```\n\n\n<docmeta name=\"displayName\" value=\"Query language\">\n"
  },
  {
    "path": "docs/concepts/ORM/Records.md",
    "content": "# Records\n\nA _record_ is what you get back from `.find()` or `.findOne()`.  Each record is a uniquely identifiable object that corresponds one-to-one with a physical database entry; e.g. a row in Oracle/MSSQL/PostgreSQL/MySQL, a document in MongoDB, or a hash in Redis.\n\n```js\nvar records = await Order.find();\n\nconsole.log('Found %d records', records.length);\nif (records.length > 0) {\n  console.log('Found at least one record, and its `id` is:',records[0].id);\n}\n```\n\n\n### JSON serialization\n\nIn Sails, records are just dictionaries (plain JavaScript objects), which means they can easily be represented as JSON. But you can also customize the way that records from a particular model are _stringified_ using the [`customToJSON` model setting](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?customtojson).\n\n\n### Populated values\n\nIn addition to basic attribute data like email addresses, phone numbers, and birthdates, Waterline can dynamically store and retrieve linked sets of records using [associations](https://sailsjs.com/documentation/concepts/models-and-orm/associations).  When [`.populate()`](https://sailsjs.com/documentation/reference/waterline-orm/queries/populate) is called on a query, each of the resulting records will contain one or more populated values.  Each one of those populated values is a snapshot of the record (or array of records) linked to that particular association at the time of the query.\n\nThe type of a populated value depends on what kind of association it is:\n\n+ `null`, or a plain JavaScript object (if it corresponds to a \"model\" association)\n+ an empty array, or an array of plain JavaScript objects (if it corresponds to a \"collection\" association)\n\n\n\nFor example, assuming we're dealing with orders of adorable wolf puppies:\n\n```js\nvar orders = await Order.find()\n.populate('buyers')  // a \"collection\" association\n.populate('seller');  // a \"model\" association\n\n// this array is a snapshot of the Customers who are associated with the first Order as \"buyers\"\nconsole.log(orders[0].buyers);\n// => [ {id: 1, name: 'Robb Stark'}, {id: 6, name: 'Arya Stark'} ]\n\n// this object is a snapshot of the Company that is associated with the first Order as the \"seller\"\nconsole.log(orders[0].seller);\n// => { id: 42941, corporateName: 'WolvesRUs Inc.' }\n\n// this array is empty because the second Order doesn't have any \"buyers\"\nconsole.log(orders[1].buyers);\n// => []\n\n// this is `null` because there is no \"seller\" associated with the second Order\nconsole.log(orders[1].seller);\n// => null\n```\n\n##### Expected types / values for association attributes\n\nThe table below shows what values you can expect in records returned from a `.find()` or `.findOne()` call under different circumstances.  \n\n| &nbsp; |  without a `.populate()` added for the association | with `.populate()`, but no associated records found | with `.populate()`, with associated records found\n|:--- |:--- | --- |:--- |\n| Singular association (e.g. `seller`) | Whatever is in the database record for this attribute (typically `null` or a foreign key value) | `null` | A POJO representing a child record |\n| Plural association (e.g. `buyers`) |  `undefined` (the key will not be present) | `[]` (an empty array) | An array of POJOs representing child records\n\n\n##### Modifying populated values\n\nTo modify the populated values of a particular record or set of records, call the [.addToCollection()](https://sailsjs.com/documentation/reference/waterline-orm/models/add-to-collection), [.removeFromCollection()](https://sailsjs.com/documentation/reference/waterline-orm/models/remove-from-collection), or [.replaceCollection()](https://sailsjs.com/documentation/reference/waterline-orm/models/replace-collection) model methods.\n\n\n\n<docmeta name=\"displayName\" value=\"Records\">\n"
  },
  {
    "path": "docs/concepts/ORM/Validations.md",
    "content": "# Validations\n\nSails bundles support for automatic validations of your models' attributes. Any time a record is updated, or a new record is created, the data for each attribute will be checked against all of your predefined validation rules. This provides a convenient failsafe to ensure that invalid entries don't make their way into your app's database(s).\n\nExcept for `unique` (which is implemented as a database-level constraint; [see \"Unique\"](https://sailsjs.com/documentation/concepts/models-and-orm/validations#?unique)), all validations below are implemented in JavaScript and run in the same Node.js server process as Sails.  Also keep in mind that, no matter what validations are used, an attribute must _always_ specify one of the built-in data types (`string`, `number`, `json`, etc).\n\n```javascript\n// User\nmodule.exports = {\n  attributes: {\n    emailAddress: {\n      type: 'string',\n      unique: true,\n      required: true\n    }\n  }\n};\n```\n\n### Built-in data types\n\nIn Sails/Waterline, model attributes always have some kind of data type guarantee.  This is above and beyond any physical-layer constraints which might exist in your underlying database&mdash;it's more about providing a way for developers to maintain reasonable assumptions about the data that goes in or comes out of a particular model.\n\nThis data type guarantee is used for logical validation and coercion of results and criteria.  Here is a list of the data types supported by Sails and Waterline:\n\n| Data Type        | Usage                         | Description                                                  |\n|:----------------:|:----------------------------- |:------------------------------------------------------------ |\n| ((string))       | `type: 'string'`              | Any string.\n| ((number))       | `type: 'number'`              | Any number.\n| ((boolean))      | `type: 'boolean'`             | `true` or `false`.\n| ((json))         | `type: 'json'`                | Any JSON-serializable value, including numbers, booleans, strings, arrays, dictionaries (plain JavaScript objects), and `null`.\n| ((ref))          | `type: 'ref'`                 | Any JavaScript value except `undefined`. (Should only be used when taking advantage of adapter-specific behavior.)    |\n\nSails' ORM (Waterline) and its adapters perform loose validation to ensure that the values provided in criteria dictionaries and as values to `.create()` or `.update()` match the expected data type.\n\n**NOTE:** In adapters that don't support the ((json)) type natively, the adapter must support it in other ways. For example, in MySQL the data being written to a ((json)) attribute gets `JSON.stringify()` called on it and then is stored in a column with a type set to `text`. Each time the record is returned, the data has `JSON.parse()` called on it. This is something to be aware of when considering performance and compatibility with other applications or existing data in the database. The official PostgreSQL and mongoDB adapters can read and write ((json)) data natively.\n\n\n##### Null and empty string\n\nThe `string`, `number` and `boolean` data types do _not_ accept `null` as a value when creating or updating records.  In order to allow a `null` value to be set, toggle the `allowNull` flag on the attribute. The `allowNull` flag is only valid on the above data types; it is _not_ valid on attributes with types `json` or `ref`, any associations, or any primary key attributes.\n\nSince empty string (\"\") is a string, it is normally supported by `type: 'string'` attributes; but there are a couple of exceptions:  primary keys (because primary keys never support empty string) and any attribute which has `required: true`.\n\n\n##### Required\n\nIf an attribute is `required: true`, then a value must always be specified for it when calling `.create()`.  This also prevents a value from being set to `null` or empty string (\"\") when created or updated.\n\n### Validation rules\n\n_None_ of the following validation rules impose any additional restrictions against `null`.  That is, if `null` would be allowed normally, then enabling the `isEmail` validation rule will not cause `null` to be rejected as invalid.\n\nSimilarly, _most_ of the following validation rules don't impose any additional restrictions against empty string (\"\").  There are a few exceptions (`isNotEmptyString` and non-string-related rules like `isBoolean`, `isNumber`, `max`, and `min`), but otherwise, for any attribute where empty string (\"\") would normally be allowed, adding a validation rule will not cause it to be rejected.\n\nIn the table below, the \"Compatible Attribute Type(s)\" column shows what data type(s) (i.e. for the attribute definition's `type` property) are appropriate for each validation rule.  In many cases, a validation rule can be used with more than one type.  Note that the table below takes a shortcut:  if compatible with ((string)), ((number)), or ((boolean)), then the validation rule is also compatible with ((json)) and ((ref)), even if it doesn't explicitly say so.\n\n\n| Name of Rule      | What It Checks For                                                                                                  | Notes On Usage                                         | Compatible Attribute Type(s) |\n|:------------------|:--------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------|:----------------------------:|\n| custom            | A value such that when it is provided as the first argument to the custom function, the function returns `true`.                          | [Example](https://sailsjs.com/documentation/concepts/models-and-orm/validations#?custom-validation-rules)            |  _Any_   |\n| isAfter           | A value that, when parsed as a date, refers to a moment _after_ the configured JavaScript `Date` instance.          | `isAfter: new Date('Sat Nov 05 1605 00:00:00 GMT-0000')`  | ((string)), ((number))       |\n| isBefore          | A value that, when parsed as a date, refers to a moment _before_ the configured JavaScript `Date` instance.         | `isBefore: new Date('Sat Nov 05 1605 00:00:00 GMT-0000')` | ((string)), ((number))       |\n| isBoolean         | A value that is `true` or `false` | isBoolean: true | ((json)), ((ref)) |\n| isCreditCard      | A value that is a credit card number.                                                                               | **Do not store credit card numbers in your database unless your app is PCI compliant!**  If you want to allow users to store credit card information, a safe alternative is to use a payment API like [Stripe](https://stripe.com). | ((string)) |\n| isEmail           | A value that looks like an email address.                                                                           | `isEmail: true`                                         | ((string)) |\n| isHexColor        | A string that is a hexadecimal color.                                                                               | `isHexColor: true`                                      | ((string)) |\n| isIn              | A value that is in the specified array of allowed strings.                                                          | `isIn: ['paid', 'delinquent']`                          | ((string)) |\n| isInteger         | A number that is an integer (a whole number)                                                                        | `isInteger: true`                                       | ((number)) |\n| isIP              | A value that is a valid IP address (v4 or v6)                                                                       | `isIP: true`                                              | ((string)) |\n| isNotEmptyString  | A value that is _not_ an empty string | `isNotEmptyString: true` | ((json)), ((ref))\n| isNotIn           | A value that **is not in** the configured array.                                                                    | `isNotIn: ['profanity1', 'profanity2']`                   | ((string)) |\n| isNumber          | A value that is a Javascript number | `isNumber: true` | ((json)), ((ref))\n| isString          | A value that is a string (i.e. `typeof(value) === 'string'`) | `isString: true` | ((json)), ((ref))\n| isURL             | A value that looks like a URL. | `isURL: true` | ((string)) |\n| isUUID            | A value that looks like a UUID (v3, v4 or v5) | `isUUID: true` | ((string))\n| max               | A number that is less than or equal to the configured number. | `max: 10000` | ((number)) |\n| min               | A number that is greater than or equal to the configured number. | `min: 0` | ((number)) |\n| maxLength         | A string that has no more than the configured number of characters. |  `maxLength: 144` | ((string)) |\n| minLength         | A string that has at least the configured number of characters. | `minLength: 8` | ((string)) |\n| regex             | A string that matches the configured regular expression. | `regex: /^[a-z0-9]$/i` | ((string)) |\n\n\n##### Example: optional email address\n\nImagine that you have an attribute defined as follows:\n\n```javascript\nworkEmail: {\n  type: 'string',\n  isEmail: true,\n}\n```\n\nWhen you call `.create()` _or_ `.update()`, this value can be set to any valid email address (like \"santa@clause.com\") OR to an empty string (\"\").  You would _not_ be able to set it to `null`, though, because that would violate the type safety restriction imposed by `type: 'string'`.\n\n> To make this attribute accept `null` (e.g. if you are working with a pre-existing database), change it to `type: 'json'`.  You'd normally also want to add `isString: true`, but since we already enforce `isEmail: true` in this example, there's no need to do so.\n>\n> A more advanced feature to keep in mind is that, depending on your database, you can choose to take advantage of [`columnType`](https://sailsjs.com/documentation/concepts/models-and-orm/attributes#?columntype) to inform Sails / Waterline which column type to define during auto-migrations (if relevant).\n\n\n##### Example: required star rating\n\nIf we want to indicate that an attribute supports certain numbers, like a star rating, we might do something like this:\n\n```javascript\nstarRating: {\n  type: 'number',\n  min: 1,\n  max: 5,\n  required: true,\n}\n```\n\n\n##### Example: optional star rating\n\nIf we want to make our star rating optional, it's easiest to just remove the `required: true` flag.  If omitted, the starRating will default to zero.\n\n\n##### Example: optional star rating (with `null`)\n\nBut what if the star rating can't _always_ be a number? Imagine we need to integrate with a legacy database in which star ratings could be either a number or the special null literal. In this scenario, we would like to define the `starRating` attribute to support both certain numbers and `null`.\n\nTo accomplish this, just use `allowNull`:\n\n```javascript\nstarRating: {\n  type: 'number',\n  allowNull: true,\n  min: 1,\n  max: 5,\n}\n```\n\n> Sails and Waterline attributes support `allowNull` for convenience, but another viable solution is to change `starRating` from `type: 'number'` to `type: 'json'`.  Remember, though, that the `json` type allows other data, like booleans, arrays, etc. If we want to explicitly protect against those data types being supported by `starRating`, we could add the `isNumber: true` validation rule:\n>\n>\n> ```javascript\n> starRating: {\n>   type: 'json',\n>   isNumber: true,\n>   min: 1,\n>   max: 5,\n> }\n> ```\n\n\n\n### Unique\n\n`unique` is different from all of the validation rules listed above.  In fact, it isn't really a validation rule at all: it is a **database-level constraint**.  More on that in a second.\n\nIf an attribute declares itself `unique: true`, then Sails ensures that no two records will be allowed with the same value.  The canonical example is an `emailAddress` attribute on a `User` model:\n\n```javascript\n// api/models/User.js\nmodule.exports = {\n\n  attributes: {\n    emailAddress: {\n      type: 'string',\n      unique: true,\n      required: true\n    }\n  }\n\n};\n```\n\n##### Why is `unique` different from other validations?\n\nImagine you have 1,000,000 user records in your database.  If `unique` was implemented like other validations, every time a new user signed up for your app, Sails would need to search through _one million_ existing records to ensure that no one else was already using the email address provided by the new user.  That would be so slow that by the time we finished searching through all those records, someone else could have signed up!\n\nFortunately, this type of uniqueness check is perhaps the most universal feature of _any_ database.  To take advantage of that, Sails relies on the [database adapter](https://sailsjs.com/documentation/concepts/models-and-orm#?adapters) to implement support for `unique`&mdash;specifically, it adds a **uniqueness constraint** to the relevant field/column/attribute in the database itself during [auto-migration](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?migrate).  That is, while your app is set to `migrate:'alter'`, Sails will automatically generate tables/collections in the underlying database with uniqueness constraints built right in.  Once you switch to `migrate:'safe'`, updating your database constraints is up to you.\n\n##### What about indexes?\n\nWhen you start using your production database, it is always a good idea to set up indexes to boost your database's performance.  The exact process and best practices for setting up indexes varies between databases and is beyond the scope of this documentation.  That said, if you've never done this before, don't worry: it's [easier than you think](http://stackoverflow.com/a/1130/486547).\n\nJust like everything else related to your production schema, once you set your app to use `migrate: 'safe'`, Sails leaves database indexes entirely up to you.\n\n> Note that this means you should be sure to update your indexes alongside your uniqueness constraints when performing [manual migrations](https://github.com/BlueHotDog/sails-migrations).\n\n\n### When to use validations\n\nValidations can save you from writing many hundreds of lines of repetitive code,  but keep in mind that model validations are run for _every create or update_ in your application.  Before using a validation rule in one of your attribute definitions, make sure you are okay with it being applied _every time_ your application calls `.create()` or `.update()` to specify a new value for that attribute.  If that is _not_ the case, write code that validates the incoming values inline in your controller, or call a custom function in one of your [services](https://sailsjs.com/documentation/concepts/services) or a [model class method](https://sailsjs.com/documentation/concepts/models-and-orm/models#?model-methods-aka-static-or-class-methods).\n\nSuppose that your Sails app allows users to sign up for an account by either (A) entering an email address and password and then confirming that email address or (B) signing up with LinkedIn.  Your `User` model might have one attribute called `manuallyEnteredEmail` and another called `linkedInEmail`.  While one of those email address attributes is required, _which_ one that is depends on how a user signs up.  In this case, your `User` model cannot use the `required: true` validation. In order to confirm that one of the two emails has been provided&mdash;and that the provided email is valid&mdash;you'll instead have to manually check these values before the relevant `.create()` and `.update()` calls in your code:\n\n```javascript\nif ( !_.isString( req.param('email') ) ) {\n  return res.badRequest();\n}\n```\n\nTaking this one step further, let's say your application accepts payments.  During the sign-up flow, if the user signs up with a paid plan, they must provide an email address for billing purposes (`billingEmail`), while if the user signs up with a free account, they skip that step.  On the account settings page, users on the paid plan see a \"Billing Email\" form field where they can customize their billing email. Users with the free plan, on the other hand, see a call to action which links to the \"Upgrade Plan\" page.\n\nWhile these requirements seem specific, there are still unanswered questions:\n\n- Do we update the billing email automatically when the other email address from which it was defaulted changes?\n- What if the billing email had been changed at least once?\n- What happens to the billing email after a user downgrades to the free plan? If that user upgrades to a paid plan again, do we request their billing email address anew or use the old one?\n- What happens to the billing email when an existing user connects their LinkedIn account and a new `linkedInEmail` is saved?\n- What happens to the billing email if a monthly invoice email cannot be delivered?\n- What happens to the billing email if a member of your support team logs into the admin interface and changes it manually?\n- What happens to the billing email if a POST request is received on the callback URL we provided to the LinkedIn API to notify our app that the user changed their email address on http://linkedin.com, saving a new `linkedInEmail`?\n- What happens to the billing email when an existing user disconnects their LinkedIn account?\n- Are two user accounts in the database allowed to have the same billing email?  What about the email from LinkedIn?  Or the one they entered manually?\n\nDepending on the answers to questions like these, we might end up keeping the `required` validation on `billingEmail`, adding new attributes (like `hasBillingEmailBeenChangedManually`), or even rethinking whether we use a `unique` constraint.\n\n### Best practices\n\nFinally, here are a few tips:\n\n+ Your initial decision about whether or not to use validations for a particular attribute should depend on your app's requirements and how you are calling `.update()` and `.create()`. Don't be afraid to forgo built-in validation support in favor of checking values by hand in your controllers or in a helper function.  Oftentimes this is the cleanest and most maintainable approach.\n+ There's nothing wrong with adding or removing validations from your models as your app evolves, but once in production, there is one **very important exception**: `unique`.  During development, when your app is configured to use [`migrate: 'alter'`](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?migrate), you can add or remove `unique` validations at will.  However, if you are using `migrate: safe` (e.g. with your production database), you will want to update constraints/indices in your database, as well as [migrate your data by hand](https://github.com/BlueHotDog/sails-migrations).\n+ It is a very good idea to take the time to fully understand your application's user interface _first_, before setting up complex validations on your model attributes.\n\n> As much as possible, it is best to obtain or flesh out your own wireframes of your app's user interface _before_ you spend any serious amount of time implementing _any_ backend code.  Of course, this isn't always possible, and that's what the [blueprint API](https://sailsjs.com/documentation/concepts/blueprints) is for.  Applications built with a UI-centric, or \"front-end first\", philosophy are easier to maintain, tend to have fewer bugs, and&mdash;since mindfulness of the user experience is at their core&mdash;often have more elegant APIs.\n\n\n\n### Custom validation rules\n\nYou can define your own custom validation rules by specifying a `custom` function in your attributes.\n\n```javascript\n// api/models/User.js\nmodule.exports = {\n\n  // Values passed for creates or updates of the User model must obey the following rules:\n  attributes: {\n\n    firstName: {\n      // Note that a base type (in this case \"string\") still has to be defined, even though validation rules are in use.\n      type: 'string',\n      required: true,\n      minLength: 5,\n      maxLength: 15\n    },\n\n    location: {\n      type: 'json',\n      custom: function(value) {\n        return _.isObject(value) &&\n        _.isNumber(value.x) && _.isNumber(value.y) &&\n        value.x !== Infinity && value.x !== -Infinity &&\n        value.y !== Infinity && value.y !== -Infinity;\n      }\n    },\n\n    password: {\n      type: 'string',\n      custom: function(value) {\n        // • be a string\n        // • be at least 6 characters long\n        // • contain at least one number\n        // • contain at least one letter\n        return _.isString(value) && value.length >= 6 && value.match(/[a-z]/i) && value.match(/[0-9]/);\n      }\n    }\n\n  }\n\n}\n```\n\nCustom validation functions receive the incoming value to be validated as their first argument. They are expected to return `true` if valid and `false` otherwise.\n\n\n\n##### Custom validation messages\n\nOut of the box, Sails.js does not support custom validation messages.  Instead, your code should [look at (or \"negotiate\") validation errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors#?usage-errors) thrown by `.create()` or `.update()` calls and take the appropriate action, whether that's sending a particular error code in your JSON response or rendering the appropriate message in an HTML error page.\n\n\n<docmeta name=\"displayName\" value=\"Validations\">\n"
  },
  {
    "path": "docs/concepts/ORM/errors.md",
    "content": "# Errors\n\nWhen a call to any model method or helper fails, Sails throws a [JavaScript Error instance](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) whose properties can be useful in diagnosing what went wrong.\n\nWaterline normalizes these Error instances, classifying them with consistent `err.name` values and, when applicable, `err.code`:\n\n```js\ntry {\n  await Something.create({…});\n} catch (err) {\n  // err.name\n  // err.code\n  // …\n}\n```\n\n\n### Negotiating errors\n\nCatch-all error handling, while better than nothing, often just isn't enough. (There's a big difference between \"that is not a valid username\" and \"we aren't able to create new users at all right now\".)  In order to negotiate the different kinds of errors appropriately, you'll need to be able to examine them in a granular way.\n\nFortunately, Sails provides some syntactic sugar for doing this out of the box, without resorting to try… catch: [.intercept()](https://sailsjs.com/documentation/reference/waterline-orm/queries/intercept) and [.tolerate()](https://sailsjs.com/documentation/reference/waterline-orm/queries/tolerate).\n\n```javascript\nawait Something.create({…})\n.intercept((err)=>{\n // Return a modified error here (or a special exit signal)\n // and .create() will throw that instead\n err.message = 'Uh oh: '+err.message;\n return err;\n});\n```\n\n\n| Property       | Type          | Details            |\n|:---------------|---------------|:-------------------|\n| name           | ((string))    | The broad classification of the error. <br/><br/> e.g.`'UsageError'`     |\n| message        | ((string))    | <em>See [.message](https://nodejs.org/dist/latest/docs/api/errors.html#errors_error_message).</em> |\n| stack          | ((string))    | <em>See [.stack](https://nodejs.org/dist/latest/docs/api/errors.html#errors_error_stack).<em>     |\n| _code_         | ((string?))   | A narrower classification of the error that is sometimes included.<br/><br/>e.g. `'E_UNIQUE'`       |\n\nWhen using code that interacts with Waterline (usually through model methods) there are a few different kinds of error you may encounter.\n\n\n### Usage errors\n\nWhen an error has `name: 'UsageError'`, this indicates that a Waterline method was used incorrectly, or executed with invalid options (for example, attempting to create a new record that would violate one of your model's [high-level validation rules](https://sailsjs.com/documentation/concepts/models-and-orm/validations#?validation-rules).)\n\nThis sort of error can come from any model method.\n\n```\nerr.name === 'UsageError'\n```\n\n### Adapter errors\n\nAdapter errors usually indicate a problem in the underlying adapter, and not in the request itself. This can happen when a database goes offline, when there is a permission issue, because of some database-specific edge case, or (more rarely) a bug in the adapter. This kind of error will have `name: 'AdapterError'`.\n\nThis sort of error can come from any model method.\n\n```\nerr.name === 'AdapterError'\n```\n\n\n##### E_UNIQUE\n\nA uniqueness error occurs when a value that _should_ be unique matches that of another record in the database. While this is considered an adapter error, it has its own `code` to differentiate it from a normal adapter error: `code: 'E_UNIQUE'`.\n\nThis sort of error can only come from the `.create()`, `.update()`, `.addToCollection()`, and `.replaceCollection()` model methods.\n\n```\nerr.code === 'E_UNIQUE'\n```\n\n### Examples\n\nThe exact strategy you use to do this in your Sails app depends on whether you're using `await`, promises, or callbacks.\n\n##### Negotiating errors with `await`\n\nTo handle the different errors that may occur when attempting to create a new user from within an action:\n\n```javascript\nawait User.create({ emailAddress: inputs.emailAddress })\n// Uniqueness constraint violation\n.intercept('E_UNIQUE', (err)=> {\n  return 'emailAlreadyInUse';\n})\n// Some other kind of usage / validation error\n.intercept({name:'UsageError'}, (err)=> {\n  return 'invalid';\n});\n// If something completely unexpected happened, the error will be thrown as-is.\n\nreturn exits.success();\n```\n\n##### Negotiating errors with callbacks or promise chaining\n\nIf you're not able to use `await` because you're using Node.js <= v7.9, then prepare yourself: error handling works a bit differently when [using callbacks or promise chaining](https://github.com/mikermcneil/parley/tree/49c06ee9ed32d9c55c24e8a0e767666a6b60b7e8#flow-control) instead of `await`.\n\n> Please use `await` if at all possible!  It is much safer for your app, your code will be cleaner, and you will be happier.\n\nFor example, if you're using promise chaining, here's how you might handle the different errors that could occur when attempting to create a new user:\n\n```javascript\nUser.create({\n  emailAddress: req.param('emailAddress')\n})\n.then(function (){\n  res.ok();\n})\n// Uniqueness constraint violation\n.catch({ code: 'E_UNIQUE' }, function (err) {\n  res.sendStatus(409);\n})\n// Some other kind of usage / validation error\n.catch({ name: 'UsageError' }, function (err) {\n  res.badRequest();\n})\n// If something completely unexpected happened.\n.catch(function (err) {\n  res.serverError(err);\n});\n```\n\nHere's the same example, but written with traditional Node.js callbacks instead of promise chaining:\n\n```javascript\nUser.create({\n  emailAddress: req.param('emailAddress')\n})\n.exec(function (err){\n  if (err && err.code === 'E_UNIQUE') {\n    return res.sendStatus(409);\n  } else if (err && err.name === 'UsageError') {\n    return res.badRequest();\n  } else if (err) {\n    return res.serverError(err);\n  }\n\n  return res.ok();\n});\n```\n\n> But beware [uncaught exceptions](https://github.com/mikermcneil/parley/tree/49c06ee9ed32d9c55c24e8a0e767666a6b60b7e8#handling-uncaught-exceptions)!\n\n\n<docmeta name=\"displayName\" value=\"Errors\">\n"
  },
  {
    "path": "docs/concepts/ORM/model-settings.md",
    "content": "# Model settings\n\nIn Sails, the top-level properties of model definitions are called **model settings**.  This includes everything from [attribute definitions](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?attributes), to the [database settings](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?datastore) the model will use, as well as a few other options.\n\nThe majority of this page is devoted to a complete tour of the model settings supported by Sails.  But before we begin, let's look at how to actually apply these settings in a Sails app.\n\n\n### Overview\n\nModel settings allow you to customize the behavior of the models in your Sails app.  They can be specified on a per-model basis by setting top-level properties in a [model definition](https://sailsjs.com/documentation/concepts/models-and-orm/models), or as app-wide defaults in [`sails.config.models`](https://sailsjs.com/documentation/reference/configuration/sails-config-models).\n\n##### Changing default model settings\n\nTo modify the [default model settings](https://sailsjs.com/documentation/reference/configuration/sails-config-models) shared by all of the models in your app, edit [`config/models.js`](https://sailsjs.com/documentation/anatomy/my-app/config/models-js).\n\nFor example, when you generate a new app, Sails automatically includes three different default attributes in your `config/models.js` file:  `id`, `createdAt`, and `updatedAt`.  Let's say that, for all of your models, you wanted to use a slightly different, customized `id` attribute. To do so, you could just override `attributes: {  id: {...}  }` in your `config/models.js` definition.\n\n\n##### Overriding settings for a particular model\n\nTo further customize these settings for a particular model, you can specify them as top-level properties in that model's definition file (e.g. `api/models/User.js`).  This will override default model settings with the same name.\n\nFor example, if you add `fetchRecordsOnUpdate: true` to one of your model definitions (`api/models/UploadedFile.js`), then that model will now return the records that were updated.  But the rest of your models will be unaffected: they will still use the default setting (which is `fetchRecordsOnUpdate: false`, unless you've changed it).\n\n\n##### Choosing an approach\n\nIn your day to day development, the model setting you'll interact with most often is `attributes`. Attributes are used in almost every model definition, and some default attributes are included in `config/models.js`.  For future reference, here are a few additional tips:\n\n+ If you are specifying a `tableName`, you should always do so on a per-model basis.  (An app-wide table name wouldn't make sense!)\n+ There is no reason to specify an app-wide datastore, since you already have one out of the box (named \"default\"). Even so, you might want to override `datastore` for a particular model in certain situations&mdash;if, for example, your default datastore is PostgreSQL but you have an `CachedBloodworkReport` model that you want to live in Redis.\n+ For the sake of clarity, it is best to only specify `migrate` and `schema` settings as app-wide defaults, never on a per-model basis.\n\n\nNow that you have an idea of what model settings are and how to configure them, let's run through and have a look at each one.\n\n--------------------\n\n\n\n\n### attributes\n\nThe set of attribute definitions for a model.\n\n```\nattributes: { /* ... */ }\n```\n\n| Type           | Example                 | Default       |\n| -------------- |:------------------------|:--------------|\n| ((dictionary)) | _See below._            | `{}`          |\n\nMost of the time, you'll define attributes in your individual model definitions (in `api/models/`), but you can also specify **default attributes** in `config/models.js`.  This allows you to define a set of global attributes in one place, then rely on Sails to make them available to all of your models implicitly and without repeating yourself.  Default attributes can also be overridden on a per-model basis by defining a replacement attribute with the same name in the relevant model definition.\n\n```js\nattributes: {\n  id: { type: 'number', autoIncrement: true },\n  createdAt: { type: 'number', autoCreatedAt: true },\n  updatedAt: { type: 'number', autoUpdatedAt: true },\n}\n```\n\nFor a complete introduction to model attributes, including how to define and use them in your Sails app, see [Concepts > ORM > Attributes](https://sailsjs.com/documentation/concepts/orm/attributes).\n\n### customToJSON\n\nA function that allows you to customize the way a model's records are serialized to JSON.\n\n```\ncustomToJSON: function() { /*...*/ }\n```\n\n| Type         | Example                 | Default       |\n| ------------ |:------------------------|:--------------|\n| ((function)) | _See below._            | _n/a_         |\n\nAdding the `customToJSON` setting to a model changes the way that the model&rsquo;s records are _stringified_.  In other words, it allows you to inject custom logic that runs any time one of these records are passed into `JSON.stringify()`.  This is most commonly used to implement a failsafe, making sure sensitive data like user passwords aren't accidentally included in a response (since [`res.send()`](https://sailsjs.com/documentation/reference/response-res/res-send) and actions2 may stringify data before sending).\n\nThe `customToJSON` function takes no arguments, but provides access to the record as the `this` variable.  This allows you to omit sensitive data and return the sanitized result, which is what `JSON.stringify()` will actually use when generating a JSON string.  For example:\n\n```js\ncustomToJSON: function() {\n  // Return a shallow copy of this record with the password and ssn removed.\n  return _.omit(this, ['password', 'ssn'])\n}\n```\n\nThe customToJSON function is deisgned not to support async capabilities. This allows synchronous bits in core to stay synchronous and provide better stability to the system as a whole.\n\n> Note that the `this` variable available in `customToJSON` is a _direct reference to the actual record object_, so be careful not to modify it.  In other words, avoid writing code like `delete this.password`.  Instead, use methods like `_.omit()` or `_.pick()` to get a _copy_ of the record.  Or just construct a new dictionary and return that (e.g. `return { foo: this.foo }`).\n\n### tableName\n\nThe name of the SQL table (/MongoDB collection) where a model will store and retrieve its records as rows (/MongoDB documents).\n\n```\ntableName: 'some_preexisting_table'\n```\n\n| Type        | Example                    | Default       |\n| ----------- |:---------------------------|:--------------|\n| ((string))  | `'some_preexisting_table'` | _Same as model's identity._\n\nThe **tableName** setting gives you the ability to customize the name of the underlying _physical model_ that a particular model should use.  In other words, it lets you control where a model stores and retrieves records within the database, _without_ affecting the code in your controller actions / helpers.\n\nBy default, Sails uses the model's [identity](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings?identity) to determine its table name:\n\n```js\nawait User.find();\n// => SELECT * FROM user;\n```\n\nThis is a recommended convention, and shouldn't need to be changed in most cases.  But, if you are sharing a legacy database with an existing application written for a different platform like Python or C#, or if your team prefers a different naming convention for their database tables, then it may be useful to customize this mapping.\n\nReturning to the example above, if you modified your model definition in `api/models/User.js`, and set `tableName: 'foo_bar'`, then you'd see slightly different results:\n\n```js\nawait User.find();\n// => SELECT * FROM foo_bar;\n```\n\n> What's in a `tableName`?  In databases like MySQL and PostgreSQL, the setting refers to a literal \"table\".  In MongoDB, it refers to a \"collection\".  It's really just about familiarity: That which we call a \"table\", by any other word would query as well.\n\n\n\n### migrate\n\nThe **auto-migration strategy** that Sails will run every time your app loads.\n\n```\nmigrate: 'alter'\n```\n\n| Type        | Example                 | Default       |\n| ----------- |:------------------------|:--------------|\n| ((string))  | `'alter'`               | _You'll be prompted._<br/><br/>_**Note**: In production, this is always `'safe'`._\n\nThe `migrate` setting controls your app's auto-migration strategy.  In short, this tells Sails whether or not you'd like it to attempt to automatically rebuild the tables/collections/sets/etc. in your database(s).\n\n##### Database migrations\n\nIn the course of developing an app, you will almost always need to make at least one or two **breaking changes** to the structure of your database.  Exactly _what_ constitutes a \"breaking change\" depends on the database you're using:  For example, imagine you add a new attribute to one of your model definitions.  If that model is configured to use MongoDB, then this is no big deal; you can keep developing as if nothing happened.  But if that model is configured to use MySQL, then there is an extra step: a column must be added to the corresponding table (otherwise model methods like `.create()` will stop working).  So for a model using MySQL, adding an attribute is a breaking change to the database schema.\n\n> Even if all of your models use MongoDB, there are still some breaking schema changes to watch out for.  For example, if you add `unique: true` to one of your attributes, a [unique index](https://docs.mongodb.com/manual/core/index-unique/) must be created in MongoDB.\n\n\nIn Sails, there are two different modes of operation when it comes to [database migrations](https://en.wikipedia.org/wiki/Schema_migration):\n\n1. **Manual migrations**: The art of updating your database tables/collections/sets/etc. by hand.  For example, writing a SQL query to [add a new column](http://dev.mysql.com/doc/refman/5.7/en/alter-table.html), or sending a [Mongo command to create a unique index](https://docs.mongodb.com/manual/core/index-unique/).  If the database contains data you care about (in production, for example), you must carefully consider whether that data needs to change to fit the new schema, and, if necessary, write scripts to migrate it.  While a [number of](https://www.npmjs.com/package/sails-migrations) great [open-source tools](http://knexjs.org/#Migrations-CLI) exist for managing manual migration scripts, as well as hosted products like the [database migration service on AWS](https://aws.amazon.com/blogs/aws/aws-database-migration-service/), we recommend doing all database migrations by hand, using [`sails run`](https://sailsjs.com/documentation/concepts/shell-scripts).\n2. **Auto-migrations**: A convenient, built-in feature in Sails that allows you to make iterative changes to your model definitions during development, without worrying about the reprecussions.  Auto-migrations should _never_ be enabled when connecting to a database with data you care about.  Instead use auto-migrations with fake data, or with cached data that you can easily recreate.\n\n\nWhenever you need to apply breaking changes to your _production database_, you should use manual database migrations. Otherwise, when you're developing on your laptop or running your automated tests, auto-migrations can save you tons of time.\n\n\n##### How auto-migrations work\n\nWhen you lift your Sails app in a development environment (e.g. running `sails lift` in a brand new Sails app), the configured auto-migration strategy will run.  If you are using `migrate: 'safe'`, then nothing additional will happen,  but if you are using `drop` or `alter`, Sails will load every record in your development database into memory, then drop and recreate the physical layer representation of the data (i.e. tables/collections/sets/etc.).  This allows any breaking changes you've made in your model definitions, like removing a uniqueness constraint, to be automatically applied to your development database.  Finally, if you are using `alter`, Sails will then attempt to re-seed the freshly generated tables/collections/sets with the records it saved earlier.\n\n\n| Auto-migration strategy  | Description |\n|:-------------------------|:---------------------------------------------|\n| `safe`                    | never auto-migrate my database(s). I will do it myself, by hand.\n| `alter`                   | auto-migrate columns/fields, but attempt to keep my existing data (experimental)\n| `drop`                    | wipe/drop ALL my data and rebuild models every time I lift Sails\n\n\n> Keep in mind that when using the `alter` or `drop` strategies, any manual changes you have made to your database since the last time you lifted your app may be lost.  This includes things like custom indexes, foreign key constraints, column order and comments.  In general, tables created by auto-migrations are not guaranteed to be consistent regarding any details of your physical database columns besides setting the column name, type (including character set / encoding if specified) and uniqueness.\n\n##### Can I use auto-migrations in production?\n\nThe `drop` and `alter` auto-migration strategies in Sails exist as a feature for your convenience during development, and when running automated tests.  **They are not designed to be used with data you care about.**  Please take care to never use `drop` or `alter` with a production dataset.  In fact, as a failsafe to help protect you from doing this inadvertently, any time you lift your app [in a production environment](https://sailsjs.com/documentation/reference/configuration/sails-config#?sailsconfigenvironment), Sails _always_ uses `migrate: 'safe'`, no matter what you have configured.\n\nIn many cases, hosting providers automatically set the `NODE_ENV` environment variable to \"production\" when they detect a Node.js app.  Even so, please don't rely only on that failsafe, and take the usual precautions to keep your users' data safe.  Any time you connect Sails (or any other tool or framework) to a database with pre-existing production data, **do a dry run**,  especially the very first time.  Production data is sensitive, valuable, and in many cases irreplaceable.  Customers, users, and their lawyers are not cool with it getting flushed.\n\nAs a best practice, make sure to never lift or [deploy](https://sailsjs.com/documentation/concepts/deployment) your app with production database credentials unless you are 100% sure you are running in a production environment.  A popular approach for solving this at an organization-wide scale is simply to _never_ push up production database credentials to your source code repository in the first place, instead relying on [environment variables](https://sailsjs.com/documentation/reference/configuration) for all sensitive credentials.  (This is an especially good idea if your app is subject to regulatory requirements, or if a large number of people have access to your code base.)\n\n\n##### Are auto-migrations slow?\n\nIf you are working with a relatively large amount of development/test data, the `alter` auto-migration strategy may take a long time to complete at startup.  If you notice that a command like `npm test`, `sails console`, or `sails lift` appears to hang, consider decreasing the size of your development dataset.  (Remember: Sails auto-migrations should only be used on your local laptop/desktop computer, and only with small, development datasets.)\n\n\n\n### schema\n\nWhether or not a model expects records to conform to a specific set of attributes.\n\n```\nschema: true\n```\n\n| Type        | Example                 | Default       |\n| ----------- |:------------------------|:--------------|\n| ((boolean)) | `true`                  | _Depends on the adapter._\n\n\nThe `schema` setting allows you to toggle a model between \"schemaless\" or \"schemaful\" mode.  More specifically, it governs the behavior of methods like `.create()` and `.update()`.  Normally you are allowed to store arbitrary data in a record, as long as the adapter you're using supports it.  But if you enable `schema:true`, only properties that correspond with the model's `attributes` will actually be stored.\n\n> This setting is only relevant for models using schemaless databases like MongoDB.  When hooked up to a relational database like MySQL or PostgreSQL, a model is always effectively `schema:true`, since the underlying database can only store data in tables and columns that have been set up ahead of time.\n\n\n\n### datastore\n\nThe name of the [datastore configuration](https://sailsjs.com/documentation/reference/configuration/sails-config-datastores) that a model will use to find records, create records, etc.\n\n```\ndatastore: 'legacyECommerceDb'\n```\n\n| Type       | Example                 | Default       |\n| ---------- |:------------------------|:--------------|\n| ((string)) | `'legacyECommerceDb'`   | `'default'`   |\n\nThis allows you to indicate the database where this model will fetch and save its data.  Unless otherwise specified, every model in your app uses a built-in datastore named \"default\", which is included in every new Sails app out of the box.  This makes it easy to configure your app's primary database while still allowing you to override the `datastore` setting for any particular model.\n\nFor more about configuring your app's datastores, see [Reference > Configuration > Datastores](https://sailsjs.com/documentation/reference/configuration/sails-config-datastores).\n\n\n### dataEncryptionKeys\n\nA set of keys to use when decrypting data.  The `default` data encryption key (or \"DEK\") is always used for encryption unless configured otherwise.\n\n\n```javascript\ndataEncryptionKeys: {\n  default: 'tVdQbq2JptoPp4oXGT94kKqF72iV0VKY/cnp7SjL7Ik='\n}\n```\n\n> Unless your use case requires key rotation, the `default` key is all you need.  Any other data encryption keys besides `default` are just there to allow for decrypting older data that was encrypted with them.\n\n##### Key rotation\n\nTo retire a data encryption key, you'll need to give it a new key id (like `2028`) and then create a new `default` key for use in any new encryption. For example, if you release a Sails app in year 2028 and your keys are rotated out yearly, then the following year your `dataEncryptionKeys` may look like this:\n\n```javascript\ndataEncryptionKeys: {\n  default: 'DZ7MslaooGub3pS/0O734yeyPTAeZtd0Lrgeswwlt0s=',\n  '2028': 'C5QAkA46HD9pK0m7293V2CzEVlJeSUXgwmxBAQVj+xU='\n}\n```\n\nAfter changing out the default key _the year after that_ in January 2030, you might have:\n\n```javascript\ndataEncryptionKeys: {\n  default: 'tVdQbq2JptoPp4oXGT94kKqF72iV0VKY/cnp7SjL7Ik=',\n  '2029': 'DZ7MslaooGub3pS/0O734yeyPTAeZtd0Lrgeswwlt0s=',\n  '2028': 'C5QAkA46HD9pK0m7293V2CzEVlJeSUXgwmxBAQVj+xU='\n}\n```\n\n\n### cascadeOnDestroy\n\nWhether or not to _always_ act like you set `cascade: true` any time you call `.destroy()` using this model.\n\n```\ncascadeOnDestroy: true\n```\n\n| Type        | Example                 | Default       |\n| ----------- |:------------------------|:--------------|\n| ((boolean)) | `true`                  | `false`\n\nThis is disabled by default, for performance reasons.  You can enable it with this model setting, or on a per-query basis using [`.meta({cascade: true})`](https://sailsjs.com/documentation/reference/waterline-orm/queries/meta).\n\n\n\n### dontUseObjectIds\n\n> ##### _**This feature is for use with the [`sails-mongo` adapter](https://sailsjs.com/documentation/concepts/extending-sails/adapters/available-adapters#?sailsmongo) only.**_\n\nIf set to `true`, the model will _not_ use an auto-generated MongoDB ObjectID object as its primary key.  This allows you to create models using the `sails-mongo` adapter with primary keys that are arbitrary strings or numbers, not just big long UUID-looking things.  Note that setting this to `true` means that you will have to provide a value for `id` in every call to [`.create()`](https://sailsjs.com/documentation/reference/waterline-orm/models/create) or [`.createEach()`](https://sailsjs.com/documentation/reference/waterline-orm/models/create-each).\n\n\n| Type        | Example                 | Default       |\n| ----------- |:------------------------|:--------------|\n| ((boolean)) | `true`                  | `false`\n\nThis is disabled by default, for performance reasons.  You can enable it with this model setting, or on a per-query basis using [`.meta({dontUseObjectIds: true})`](https://sailsjs.com/documentation/reference/waterline-orm/queries/meta).\n\n\n\n### Seldom-used settings\n\nThe following low-level settings are included in the spirit of completeness, but in practice, they should rarely (if ever) be changed.\n\n\n##### primaryKey\n\nThe name of a model's primary key attribute.\n\n> **You should never need to change this setting.  Instead, if you need to use a custom primary key, set a custom `columnName` on the \"id\" attribute.**\n\n```javascript\nprimaryKey: 'id'\n```\n\n| Type       | Example       | Default       |\n| ---------- |:--------------|:--------------|\n| ((string)) | `'id'`        | `'id'`        |\n\nConventionally this is \"id\", a default attribute that is included for you automatically in the `config/models.js` file of new apps generated as of Sails v1.0.  The best way to change the primary key for your model is simply to customize the `columnName` of that default attribute.\n\nFor example, imagine you have a User model that needs to integrate with a table in a pre-existing MySQL database.  That table might have a column named something other than \"id\" (like \"email_address\") as its primary key.  To make your model respect that primary key, you'd specify an override for your `id` attribute in the model definition; like this:\n\n```js\nid: {\n  type: 'string',\n  columnName: 'email_address',\n  required: true\n}\n```\n\nThen, in your app's code, you'll be able to look up users by primary key, while the mapping to `email_address` in all generated SQL queries is taken care of for you automatically:\n\n```js\nawait User.find({ id: req.param('emailAddress' });\n```\n\n> All caveats aside, lets say you're an avid user of MongoDB.  In your new Sails app, you'll start off by setting `columnName: '_id'` on your default \"id\" attribute in `config/models.js`.  Then you can use Sails and Waterline just like normal, and everything will work just fine.\n>\n> But what if you find yourself wishing that you could change the actual name of the \"id\" attribute itself for the sake of familiarity?  That way, when you call built-in model methods in your code, instead of the usual \"id\", you would use syntax like `.destroy({ _id: 'ba8319abd-13810-ab31815' })`.\n>\n> That's where this model setting might become useful.  All you'd have to do is edit `config/models.js` so that it contains `primaryKey: '_id'`, and then rename the default \"id\" attribute to \"_id\".  But there are some [good reasons to reconsider](https://gist.github.com/mikermcneil/9247a420488d86f09be342038e114a08) this course of action.\n\n##### identity\n\nThe lowercase, unique identifier for a model.\n\n> **A model's `identity` is read-only.  It is automatically derived, and should never be set by hand.**\n\n```\nSomething.identity;\n```\n\n| Type       | Example       |\n| ---------- |:--------------|\n| ((string)) | `'purchase'`  |\n\n\nIn Sails, a model's `identity` is inferred automatically by lowercasing its filename and stripping off the file extension.  For example, the identity of `api/models/Purchase.js` would be `purchase`.  It would be accessible as `sails.models.purchase`, and if blueprint routes were enabled, you'd be able to reach it with requests like `GET /purchase` and `PATCH /purchase/1`.\n\n```javascript\nassert(Purchase.identity === 'purchase');\nassert(sails.models.purchase.identity === 'purchase');\nassert(Purchase === sails.models.purchase);\n```\n\n\n\n##### globalId\n\nThe unique global identifier for a model, which also determines the name of its corresponding global variable (if relevant).\n\n> **A model's `globalId` is read-only.  It is automatically derived, and should never be set by hand.**\n\n```\nSomething.globalId;\n```\n\n| Type       | Example       |\n| ---------- |:--------------|\n| ((string)) | `'Purchase'`  |\n\nThe primary purpose of a model's globalId is to determine the name of the global variable that Sails automatically exposes on its behalf&mdash;that is, unless globalization of models has been [disabled](https://sailsjs.com/documentation/concepts/globals?q=disabling-globals).  In Sails, a model's `globalId` is inferred automatically from its filename.  For example, the globalId of `api/models/Purchase.js` would be `Purchase`.\n\n```javascript\nassert(Purchase.globalId === 'Purchase');\nassert(sails.models.purchase.globalId === 'Purchase');\nif (sails.config.globals.models) {\n  assert(sails.models.purchase === Purchase);\n}\nelse {\n  assert(typeof Purchase === 'undefined');\n}\n```\n\n\n<docmeta name=\"displayName\" value=\"Model settings\">\n"
  },
  {
    "path": "docs/concepts/ORM/standalone-usage.md",
    "content": "# Standalone Waterline usage\n\nIn addition to built-in usage with the Sails framework, Waterline can be used as a standalone module.\n\n> **Warning:** This section of the documentation is for fairly advanced Node.js users.  If you aren't planning to use Waterline outside of your Sails app (e.g. to build your own framework), you might want to skip this page and head back to [Models and ORM](https://sailsjs.com/documentation/concepts/models-and-orm) instead.\n\n### Installation\n\nWaterline is available via NPM.\n\n```sh\n$ npm install --save waterline\n```\nWaterline ships without any adapters, so you will need to install these separately. For example:\n\n```sh\n$ npm install --save sails-mysql\n$ npm install --save-dev sails-disk\n```\n\nYou can install any number of adapters into your application.\n\nThe `sails-disk` adapter is a common choice for development and testing.\n\n> If you are new to Node, hop on over to [Getting Started](https://sailsjs.com/get-started) to learn about installing Node on your preferred platform.\n\n\n### Getting Started\n\nTo get started with Waterline as a standalone module, we need two ingredients: adapters and model definitions.\n\nThe simplest adapter is the `sails-disk` adapter. Let's install that and Waterline in an empty directory.\n\n```sh\nmkdir my-tool\ncd my-tool\nnpm init\n# ...\nnpm install waterline sails-disk\n```\n\nNow we want some sample code. Copy the [example code demonstrating raw Waterline usage from here](https://github.com/balderdashy/waterline-docs/blob/master/examples/src/getting-started.js) into a file in the same directory where the `waterline` and `sails-disk` packages were installed.\n\nBefore we run it, let's explore how it works.\n\n```js\nvar Waterline = require('waterline');\nvar sailsDiskAdapter = require('sails-disk');\nvar waterline = new Waterline();\n```\n\nHere we are simply bootstrapping our main objects. We are setting up the `Waterline` factory object, an instance of an adapter, and an instance of `waterline` itself.\n\nNext we define the specification for the user model, like so:\n\n```js\nvar userCollection = Waterline.Collection.extend({\n  identity: 'user',\n  datastore: 'default',\n  primaryKey: 'id',\n  \n  attributes: {\n    id: {\n        type: 'number',\n        autoMigrations: {autoIncrement: true}\n    },\n    firstName: {type:'string'},\n    lastName: {type:'string'},\n\n    // Add a reference to Pets\n    pets: {\n      collection: 'pet',\n      via: 'owner'\n    }\n  }\n});\n```\n\nWhat's important here is the object that we are passing into that factory method.\n\nWe need to give our model an `identity` by which it can be referred to later, and also declare which datastore we are going to use.\n\n> A datastore is an instance of an adapter. For example, you could have one datastore for each type of storage you are using (file, MySQL, etc). You might even have more than one datastore for the same type of adapter.\n\nThe `attributes` define the properties of the model. In a traditional database, these attributes would align with columns in a table. Our example, `pets`, is a little different because it's defining an association that allows a user to own multiple pets.\n\n> In a relational database, the `pets` attribute won't appear as a column. Rather, it establishes a virtual one-to-many association with the pets model that we are about to define.\n\nWe must now define what a pet is:\n\n```js\nvar petCollection = Waterline.Collection.extend({\n  identity: 'pet',\n  datastore: 'default',\n  primaryKey: 'id'\n  \n  attributes: {\n    id: {\n        type: 'number',\n        autoMigrations: {autoIncrement: true}\n    },\n    breed: {type:'string'},\n    type: {type:'string'},\n    name: {type:'string'},\n\n    // Add a reference to User\n    owner: {\n      model: 'user'\n    }\n  }\n});\n```\n\nMost of the structure is the same as for the user, except there's an additional `owner` field which specifies the owner of this pet.\n\n> In our example, a pet can only have one owner, and we provide the associated model (in this case, `user`) within the `owner` field. Notice that the name of the model needs to match the `identity` given to the model. See, too, that a relational database will, in this example, create a column called `owner` containing a foreign key back to the `user` table.\n\nNext we have some more boring setup chores:\n\n```js\nwaterline.registerModel(userCollection);\nwaterline.registerModel(petCollection);\n```\n\nHere we are adding the model specifications into the `waterline` instance itself.\n\nLast, but not least, we have to configure the datastores:\n\n```js\nvar config = {\n  adapters: {\n    'disk': sailsDiskAdapter\n  },\n\n  datastores: {\n    default: {\n      adapter: 'disk'\n    }\n  }\n};\n```\n\nHere we specify the `adapters` that will be used&mdash;one for each type of storage we intend to employ&mdash;and our `datastores`, which will usually contain datastore details for the target storage system (login details, file paths, etc.). Each datastore can be named; in this case we've named our datastore \"default\" for simplicity.  Depending on the adapter, further configuration may be available for items within `datastores`.  For instance, the `sails-disk` adapter allows the `dir` and `inMemoryOnly` settings to be configured.  See the [sails-disk adapter reference](https://sailsjs.com/documentation/concepts/extending-sails/adapters/available-adapters#?optional-datastore-settings-for-sailsdisk) for more information.\n\n\nOk, it's time to crank things up and work with the datastore. First we'll initialize the `waterline` instance, and then we can go to work:\n\n```js\nwaterline.initialize(config, (err, ontology)=>{\n  if (err) {\n    console.error(err);\n    return;\n  }\n\n  // Tease out fully initialized models.\n  var User = ontology.collections.user;\n  var Pet = ontology.collections.pet;\n\n  // Since we're using `await`, we'll scope our selves an async IIFE:\n  (async ()=>{\n    // First we create a user\n    var user = await User.create({\n      firstName: 'Neil',\n      lastName: 'Armstrong'\n    });\n\n    // Then we create the pet\n    var pet = await Pet.create({\n      breed: 'beagle',\n      type: 'dog',\n      name: 'Astro',\n      owner: user.id\n    });\n\n    // Then we grab all users and their pets\n    var users = await User.find().populate('pets');\n    console.log(users);\n  })()\n  .then(()=>{\n    // All done.\n  })\n  .catch((err)=>{\n    console.error(err);\n  });//_∏_\n  \n});\n```\n\nThat's a fair chunk of code, so let's unpack it piece by piece.\n\nFirst we `initialize` the Waterline instance. This wires up the datastores (maybe logs into a database server or two), parses any models looking for associations, and does a heap of other whizbangery. When all that's done, it defers to the callback we passed in the second argument.\n\nAfter checking for an error, the `ontology` variable gathers the collection objects for our users and our pets. In the next lines, we add some shortcut variables to those collection objects in the form of `User` and `Pet`.\n\n> We typically name models in the singular form; that is, for the _type_ of _object_ you'd get back from a query.\n\nNext, we use some `await` goodness to create a user and a pet and see what we can get back out of the datastore.\n\nWe first use the `create` method to create a new user. We just need to supply the attributes for our user to get a copy of the record that was created.\n\n> Note: unless you specify otherwise, Waterline adds an `id` primary key by default.\n\nWe then create a new pet. Notice that we can associate the `id` of the user that was created in the previous step with that pet. This is done by setting the `owner` field directly.\n\nOnce the pet is created, both sides of the association are ready. To join them, we simply add the pet to a `pets` array in our new user. Then we just save the record using the `save` method on the model.\n\n> Note that `save` is only available on model objects returned by the query. Our `User` collection object does not have access to this.\n\nFinally, we want to see what actually got stuffed into the database, so we use `User.find` to get all the `User` records out of the datastore. We also want the query to resolve the pet association, so we add the `populate` method to tell the query to retrieve the pet records for each user.\n\nRunning that simple application gives us:\n\n```sh\n$ node getting-started.js\n[ { pets:\n     [ { breed: 'beagle',\n         type: 'dog',\n         name: 'Astro',\n         owner: 1,\n         createdAt: Thu May 07 2015 20:44:37 GMT+1000 (AEST),\n         updatedAt: Thu May 07 2015 20:44:37 GMT+1000 (AEST),\n         id: 1 } ],\n    firstName: 'Neil',\n    lastName: 'Armstrong',\n    createdAt: Thu May 07 2015 20:44:37 GMT+1000 (AEST),\n    updatedAt: Thu May 07 2015 20:44:37 GMT+1000 (AEST),\n    id: 1 } ]\n```\n\nThere are the attributes given to the models, and we can see the primary keys that were automatically generated for us. We can also see that Waterline has thrown in some default `createdAt` and `updatedAt` timestamps. Cool!\n\n> You can turn off the timestamps with other global or per-model configuration options.\n\n\n### Testing\n\nThis section will walk you through running integration tests for Waterline models. For documentation on testing in Sails apps, see [Concepts > Testing](https://sailsjs.com/documentation/concepts/testing).\n\n##### The testing framework\n\nTo run the tests, we need a testing framework. There are several out there, but for our examples we will be using [Mocha](mochajs.org). It's best to install this on the command line like so:\n\n```js\n$ npm install -g mocha\n```\n\nIf you are interested in code coverage, you might want to check out a tool called [Istanbul](https://www.npmjs.com/package/istanbul). For spying, stubbing, and mocking, [Sinon](http://sinonjs.org) is a good choice. For simulating HTTP requests, [nock](https://www.npmjs.com/package/nock) is worth a look.\n\n##### Testing a Waterline model\n\nThe following example shows how you might test a Waterline model. It assumes the following extremely simple application structure:\n\n```none\nroot\n|- models\n|  |- Pet.js\n|  `- User.js\n`- test\n   |- mocha.opts\n   `- UserModelTest.js\n```\n\n##### `Pet.js`\n\nHere's our standard example Pet model:\n\n```js\nmodule.exports = {\n\n  identity: 'pet',\n  datastore: 'default',\n\n  attributes: {\n    breed: 'string',\n    type: 'string',\n    name: 'string',\n\n    // Add a reference to User\n    owner: {\n      model: 'user'\n    }\n  }\n};\n```\n\n##### `User.js`\n\nAnd our standard example User model:\n\n```js\nmodule.exports = {\n\n  identity: 'user',\n  datastore: 'default',\n\n  attributes: {\n    firstName: 'string',\n    lastName: 'string',\n\n    // Add a reference to Pets\n    pets: {\n      collection: 'pet',\n      via: 'owner'\n    }\n  }\n};\n```\n\n##### `UserModelTest.js`\n\nHere's how to test our `User` model.\n\nThe `setup` function wires up the Waterline instance with our models, then initializes it. The models are using the `default` adapter, but here the test is overriding that configuration to use the disk adapter. We do this because it's fast, and because it may detect where we're trying to use \"magic\" in our models that might not be portable across database storages.\n\nThe `teardown` function clears the adapters so that future tests can start with a clean slate (it allows you to safely use the `-w` option with Mocha). Note that `teardown` assumes you are using Node 0.12; if you aren't, you'll either need to use a promise library, like Bluebird, or to convert the method to use `async` or similar.\n\nFinally, we get to our test method that tries to create a user and make some basic assertions:\n\n```js\nvar assert = require('assert');\nvar Waterline = require('waterline');\nvar sailsDiskAdapter = require('sails-disk');\n\nsuite('UserModel', function () {\n  var waterline = new Waterline();\n  var config = {\n    adapters: {\n      'sails-disk': sailsDiskAdapter\n    },\n    datastores: {\n      default: {\n        adapter: 'sails-disk'\n      }\n    }\n  }\n\n  setup(function (done) {\n    waterline.loadCollection(\n      Waterline.Collection.extend(require('../models/User.js'))\n    );\n    waterline.loadCollection(\n      Waterline.Collection.extend(require('../models/Pet.js'))\n    );\n    waterline.initialize(config, function  (err, ontology) {\n      if (err) {\n        return done(err);\n      }\n      done();\n    });\n  });\n\n  teardown(function () {\n    var adapters = config.adapters || {};\n    var promises = [];\n\n    Object.keys(adapters)\n      .forEach(function (adapter) {\n        if (adapters[adapter].teardown) {\n          var promise = new Promise(function (resolve) {\n            adapters[adapter].teardown(null, resolve);\n          });\n          promises.push(promise);\n        }\n      });\n\n    return Promise.all(promises);\n  });\n\n  test('should be able to create a user', function () {\n    var User = waterline.collections.user;\n\n    return User.create({\n        firstName: 'Neil',\n        lastName: 'Armstrong'\n      })\n      .then(function (user) {\n        assert.equal(user.firstName, 'Neil', 'should have set the first name');\n        assert.equal(user.lastName, 'Armstrong', 'should have set the last name');\n        assert.equal(user.pets.length, 0, 'should have no pets');\n      });\n  });\n});\n```\n> Obviously there is a lot of scope to refactoring the code into a utility library as you add more test files for your models.\n\nNow all we have to to is run the tests:\n\n```sh\n$ mocha\n\n\n  UserModel\n    ✓ should be able to create a user\n\n\n  1 passing (83ms)\n```\n\n\n\n<docmeta name=\"displayName\" value=\"Standalone Waterline usage\">\n"
  },
  {
    "path": "docs/concepts/Policies/Permissions.md",
    "content": "# Access control and permissions\n\nPolicies in Sails are designed for controlling binary (\"yes or no\") access to particular actions.  They work great for checking whether a user is logged in or for other simple \"yes or no\" checks, like whether the logged in user is a \"super admin\".\n\nTo see an example of access control in action&mdash;as well as login, authentication, and password recovery&mdash;generate the starter web app:\n\n```bash\nsails new foo\n\n# Then choose \"Web App\"\n```\n\n### Dynamic permissions\n\nFor more complex permission schemes, like those in which a requesting user agent's access rights depend on both _who they are_ and _what they're trying to do_, you'll want to involve the database.  While you can use policies to accomplish this, it's usually more straightforward and maintainable to use a [helper](https://sailsjs.com/documentation/concepts/helpers).\n\nFor example, you might create `api/helpers/check-permissions.js`:\n\n```javascript\nmodule.exports = {\n\n\n  friendlyName: 'Check permissions',\n\n\n  description: 'Look up a user\\'s \"rights\" within a particular organization.',\n\n\n  inputs: {\n    userId: { type: 'number', required: true },\n    orgId: { type: 'number', required: true }\n  },\n\n  exits: {\n    success: {\n      outputFriendlyName: 'Rights',\n      outputDescription: `A user's \"rights\" within an org.`,\n      outputType: ['string']\n    },\n    orgNotFound: {\n      description: 'No such organization exists.'\n    }\n  },\n\n  fn: async function(inputs, exits) {\n    var org = await Organization.findOne(inputs.orgId)\n    .populate('adminUsers', { id: inputs.userId })\n    .populate('regularUsers', { id: inputs.userId });\n\n    if (!org) { throw 'orgNotFound'; }\n\n    var rights = [];\n    if (org.regularUsers.length !== 0) {\n      rights = ['basicAccess', 'inviteRegularUsers'];\n    } else if (org.adminUsers.length !== 0) {\n      rights = ['basicAccess', 'inviteRegularUsers', 'removeRegularUsers', 'inviteOrgAdmins'];\n    } else if (org.owner === inputs.userId) {\n      rights = ['basicAccess', 'inviteRegularUsers', 'removeRegularUsers', 'inviteOrgAdmins', 'removeOrDemoteOrgAdmins'];\n    }\n    // ^^This could be as simple or as granular as you need, e.g.\n    // ['basicAccess', 'inviteRegularUsers', 'inviteOrgAdmins', 'removeRegularUsers', 'removeOrDemoteOrgAdmins']\n\n    return exits.success(rights);\n  }\n\n};\n```\n\n\nYour action&mdash;`api/controllers/demote-org-admin.js`, for example&mdash;might look like this:\n\n```javascript\n//…\nvar rights = await checkPermissions(this.req.session.userId, inputs.orgId)\n.intercept('orgNotFound', 'notFound');\n\nif (!_.contains(rights, 'removeOrDemoteOrgAdmins')) {\n  throw 'forbidden';\n}\n\nawait Organization.removeFromCollection(inputs.orgId, 'adminUsers', inputs.targetUserId);\nawait Organization.addToCollection(inputs.orgId, 'regularUsers', inputs.targetUserId);\n\nreturn exits.success();\n```\n\n\n> ### Note\n> Remember that, while we used `checkPermissions(…,…)` here, we could have\n> also used `.with()` and switched to named parameters:\n>\n> ```js\n> await checkPermissions.with({\n>   userId: this.req.session.userId,\n>   orgId: inputs.orgId\n> });\n> ```\n>\n> You may choose to use different ways of calling a helper in order to enhance code readability in different situations.        When in doubt, a good best practice is to optimize first for explicitness, then for readability, and last for conciseness.  Still, these priorities may shift as you implement a helper more frequently and become more familiar with its usage.\n\n\n<docmeta name=\"displayName\" value=\"Access Control and Permissions\">\n"
  },
  {
    "path": "docs/concepts/Policies/Policies.md",
    "content": "# Policies\n### Overview\n\nPolicies in Sails are versatile tools for authorization and access control: they let you execute some logic _before_ an action is run in order to determine whether or not to continue processing the request.  The most common use-case for policies is to restrict certain actions to _logged-in users only_.\n\n> NOTE: policies apply **only** to controllers and actions, not to views.  If you define a route in your [routes.js config file](https://sailsjs.com/documentation/reference/configuration/sails-config-routes) that points directly to a view, no policies will be applied to it.  To make sure policies are applied, you can instead define an action which displays your view and then point your route to that action. &nbsp;\n\n### When to use policies\n\nIt's best to avoid implementing numerous or complex policies in your app.  Instead, when implementing features like granular, role-based permissions, rely on your [actions](https://sailsjs.com/documentation/concepts/actions-and-controllers) to reject unwanted access.  Your actions should also be responsible for any necessary personalization of the view locals and JSON response data you send in the response.\n\nFor example, if you need to implement user-level or role-based permissions in your application, the most straightforward solution is to take care of the relevant checks at the top of your controller action&mdash;either inline or by calling out to a helper.  Following this best practice will significantly improve the maintainability of your code.\n\n### Protecting actions and controllers with policies\n\nSails has a built in ACL (access control list) located in `config/policies.js`.  This file is used to map policies to actions and controllers.\n\nThis file is  *declarative*, meaning it describes *what* the permissions for your app should look like rather than *how* they should work.  This makes it easier for new developers to understand what's going on, and it makes your app more flexible as requirements inevitably change over time.\n\nThe `config/policies.js` file is a dictionary whose properties and values differ depending on whether you are applying policies to [controllers](https://sailsjs.com/documentation/concepts/actions-and-controllers#?controllers) or [standalone actions](https://sailsjs.com/documentation/concepts/actions-and-controllers#?standalone-actions).\n\n##### Applying policies to a controller\n\nTo apply policies to a controller, use the controller name as the name of a property in the  `config/policies.js` dictionary, and set its value to a dictionary mapping actions in that controller to policies that should be applied to them.  Use `*` to represent &ldquo;all unmapped actions&rdquo;.  A policy's _name_ is the same as its filename, minus the file extension.\n\n```js\nmodule.exports.policies = {\n  UserController: {\n    // By default, require requests to come from a logged-in user\n    // (runs the policy in api/policies/isLoggedIn.js)\n    '*': 'isLoggedIn',\n\n    // Only allow admin users to delete other users\n    // (runs the policy in api/policies/isAdmin.js)\n    'delete': 'isAdmin',\n\n    // Allow anyone to access the login action, even if they're not logged in.\n    'login': true\n  }\n};\n```\n\n##### Applying policies to standalone actions\n\nTo apply policies to one or more standalone actions, use the action path (relative to `api/controllers`) as a property name in the `config/policies.js` dictionary, and set the value to the policy or policies that should apply to those actions.  By using a wildcard `*` at the end of the action path, you can apply policies to all actions that begin with that path.  Here's the same set of policies as above, rewritten to apply to standalone actions:\n\n```js\nmodule.exports.policies = {\n  'user/*': 'isLoggedIn',\n  'user/delete': 'isAdmin',\n  'user/login': true\n}\n```\n\n> Note that this example differs slightly from that of the controller-based policies in that the `isLoggedIn` policy will apply to all actions in the `api/controllers/user` folder _and subfolders_ (except for `user/delete` and `user/login`, as is explained in the next section).\n\n##### Policy ordering and precedence\n\nIt is important to note that policies do _not_ cascade.  In the examples above, the `isLoggedIn` policy will be applied to all actions in the `UserController.js` file (or standalone actions living under `api/controllers/user` ) _except for `delete` and `login`_.  If you wish to apply multiple policies to an action, list the policies in an array. For example:\n\n```javascript\n'getEncryptedData': ['isLoggedIn', 'isInValidRegion']\n```\n\n##### Using policies with blueprint actions\n\nSails' built-in [blueprint API](https://sailsjs.com/documentation/concepts/blueprints) is implemented using regular Sails actions.  The only difference is that blueprint actions are implicit.\n\nTo apply your policies to blueprint actions, set up your policy mappings just like we did in the example above, but pointed at the name of the relevant implicit [blueprint action](https://sailsjs.com/documentation/concepts/blueprints/blueprint-actions) in your controller (or as a standalone action).  For example:\n```js\nmodule.exports.policies = {\n  UserController: {\n    // Apply the 'isLoggedIn' policy to the 'update' action of 'UserController'\n    update: 'isLoggedIn'\n  }\n};\n```\nor\n```js\nmodule.exports.policies = {\n  'user/update': 'isLoggedIn'\n};\n```\n\n##### Global policies\n\nYou can apply a policy to _all_ actions that are not otherwise explicitly mapped by using the `*` property.  For example:\n\n```js\nmodule.exports.policies = {\n  '*': 'isLoggedIn',\n  'user/login': true\n};\n```\nThis would apply the `isLoggedIn` policy to every action except the `login` action in `api/controllers/user/login.js` (or in `api/controllers/UserController.js`).\n\n### Built-in policies\nSails provides two built-in policies that can be applied globally or to a specific controller or action:\n  + `true`: public access  (allows anyone to get to the mapped controller/action)\n  + `false`: **NO** access (allows **no-one** to access the mapped controller/action)\n\n `'*': true` is the default policy for all controllers and actions.  In production, it's good practice to set this to `false` to prevent access to any logic you might have inadvertently exposed.\n\n\n### Writing Your First Policy\n\nHere is a simple `isLoggedIn` policy to prevent access for unauthenticated users. It checks the session for a `userId` property, and if it doesn&rsquo;t find one, sends the default [`forbidden` response](https://sailsjs.com/documentation/concepts/extending-sails/custom-responses/default-responses#?resforbidden. For many apps, this will likely be the only policy needed. The following example assumes that, in the controller actions for authenticating a user, you set `req.session.userId` to a [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) value.\n\n```javascript\n// policies/isLoggedIn.js\nmodule.exports = async function (req, res, proceed) {\n\n  // If `req.me` is set, then we know that this request originated\n  // from a logged-in user.  So we can safely proceed to the next policy--\n  // or, if this is the last policy, the relevant action.\n  // > For more about where `req.me` comes from, check out this app's\n  // > custom hook (`api/hooks/custom/index.js`).\n  if (req.me) {\n    return proceed();\n  }\n\n  //--•\n  // Otherwise, this request did not come from a logged-in user.\n  return res.forbidden();\n\n};\n```\n\n\n\n\n<docmeta name=\"displayName\" value=\"Policies\">\n<docmeta name=\"nextUpLink\" value=\"/documentation/concepts/helpers\">\n<docmeta name=\"nextUpName\" value=\"Helpers\">\n"
  },
  {
    "path": "docs/concepts/Programmatic Usage/Programmatic Usage.md",
    "content": "# Using Sails programmatically\n\n### Overview\n\nUsually you will interact with Sails through its [command-line interface](https://sailsjs.com/documentation/reference/command-line-interface), starting servers with [`sails lift`](https://sailsjs.com/documentation/reference/command-line-interface/sails-lift), but Sails apps can also be started and manipulated from within other Node apps by using the [programmatic interface](https://sailsjs.com/documentation/reference/application).  One of the main uses of this interface is to run Sails apps inside of automated test suites.\n\n### Creating a Sails app programmatically\n\nTo create a new Sails app from within a Node.js script, use the Sails _constructor_.  The same constructor can be used to create as many distinct Sails apps as you like:\n\n```javascript\nvar Sails = require('sails').constructor;\nvar mySailsApp = new Sails();\nvar myOtherSailsApp = new Sails();\n```\n\n### Configuring, starting and stopping Sails apps programmatically\n\nOnce you have a reference to a new Sails app, you can use [`.load()`](https://sailsjs.com/documentation/reference/application/sails-load) or [`.lift()`](https://sailsjs.com/documentation/reference/application/sails-lift) to start it.  Both methods take two arguments: a dictionary of configuration options, and a callback function that will be run after the Sails app starts.\n\n> When Sails is started programmatically, it will still use the `api`, `config` and other folders underneath the current working directory to load controllers, models, and configuration options.  One notable exception is that `.sailsrc` files will _not_ be loaded when starting apps this way.\n\n> Any configuration options sent as arguments to `.load()` or `.lift()` will take precedence over options loaded from anywhere else.\n\n> Configuration options set via environment variables will _not_ automatically be applied to Sails app started programmatically, with the exception of `NODE_ENV` and `PORT`.\n\n> To load configuration options from `.sailsrc` files and environment variables, use the `rc` module that Sails makes available via `require('sails/accessible/rc')`.\n\nThe difference between `.load()` and `.lift()` is that `.lift()` takes the additional steps of (1) running the app's [bootstrap](https://sailsjs.com/documentation/reference/configuration/sails-config-bootstrap), if any, and (2) starting an HTTP server on the port configured via `sails.config.port` (1337 by default).  This allows you to make HTTP requests to the lifted app.  To make requests to an app started with `.load()`, you can use the [`.request()`](https://sailsjs.com/documentation/reference/application/sails-request) method of the loaded app.\n\n\n##### .lift()\n\nStarting an app with `.lift()` on port 1338 and sending a POST request via HTTP:\n\n```javascript\nvar request = require('request');\nvar Sails = require('sails').constructor;\n\nvar mySailsApp = new Sails();\nmySailsApp.lift({\n  port: 1338\n  // Optionally pass in any other programmatic config overrides you like here.\n}, function(err) {\n  if (err) {\n    console.error('Failed to lift app.  Details:', err);\n    return;\n  }\n\n  // --•\n  // Make a request using the \"request\" library and display the response.\n  // Note that you still must have an `api/controllers/FooController.js` file\n  // under the current working directory, with an `index` action,\n  // or a `/foo` or `POST /foo` route set up in `config/routes.js`.\n  request.post('/foo', function (err, response) {\n    if (err) {\n      console.log('Could not send HTTP request.  Details:', err);\n    }\n    else {\n      console.log('Got response:', response);\n    }\n\n    // >--\n    // In any case, whether the request worked or not, now we need to call `.lower()`.\n    mySailsApp.lower(function (err) {\n      if (err) {\n        console.log('Could not lower Sails app.  Details:',err);\n        return;\n      }\n\n      // --•\n      console.log('Successfully lowered Sails app.');\n\n    });//</lower sails app>\n  });//</request.post() :: send http request>\n});//</lift sails app>\n```\n\nStarting an app with `.lift()` using the current environment and .sailsrc settings:\n\n```javascript\nvar Sails = require('sails').constructor;\n\nvar rc = require('sails/accessible/rc');\n\nvar mySailsApp = new Sails();\nmySailsApp.lift(rc('sails'), function(err) {\n\n});\n```\n\n##### .load()\n\nHere's an alternative to the previous example:  starting a Sails app with `.load()` and sending what is _semantically_ the same POST request, but this time we'll use a virtual request instead of HTTP:\n\n```javascript\nmySailsApp.load({\n  // Optionally pass in any programmatic config overrides you like here.\n}, function(err) {\n  if (err) {\n    console.error('Failed to load app.  Details:', err);\n    return;\n  }\n\n  // --•\n  // Make a request using the \"request\" method and display the response.\n  // Note that you still must have an `api/controllers/FooController.js` file\n  // under the current working directory, with an `index` action,\n  // or a `/foo` or `POST /foo` route set up in `config/routes.js`.\n  mySailsApp.request({url:'/foo', method: 'post'}, function (err, response) {\n    if (err) {\n      console.log('Could not send virtual request.  Details:', err);\n    }\n    else {\n      console.log('Got response:', response);\n    }\n\n    // >--\n    // In any case, whether the request worked or not, now we need to call `.lower()`.\n    mySailsApp.lower(function (err) {\n      if (err) {\n        console.log('Could not lower Sails app.  Details:',err);\n        return;\n      }\n\n      // --•\n      console.log('Successfully lowered Sails app.');\n\n    });//</lower sails app>\n  });//</send virtual request to sails app>\n});//</load sails app (but not lift!)>\n```\n\n##### .lower()\n\nTo stop an app programmatically, use `.lower()`:\n\n```javascript\nmySailsApp.lower(function(err) {\n  if (err) {\n     console.log('An error occured when attempting to stop app:', err);\n     return;\n  }\n\n  // --•\n  console.log('Lowered app successfully.');\n\n});\n```\n\n##### Using `moduleDefinitions` to add actions, models and more\n\n> **Warning:**  declarative loading of modules with the `moduleDefinitions` setting is **currently experimental**, and may undergo breaking changes _even between major version releases_.  Before using this setting, be sure your project's Sails dependency is pinned to an exact version (i.e. no `^`).\n\nWhenever a Sails app starts, it typically loads and initializes all modules stored in `api/*` (e.g. models from `api/models`, policies from `api/policies`, etc.).  You can add _additional_ modules by specifying them in the runtime configuration passed in as the first argument to `.load()` or `.lift()`, using the `moduleDefinitions` key.  This is mainly useful when running tests.\n\nThe following Sails modules can be added programmatically:\n\n  Module type          | Config key        | Details\n :------------------   |:----------        |:-------\n Actions | `controllers.moduleDefinitions` | A dictionary mapping [standalone action](https://sailsjs.com/documentation/concepts/actions-and-controllers#?standalone-actions) paths to action definitions ([classic](https://sailsjs.com/documentation/concepts/actions-and-controllers#?classic-actions) or [Actions2](https://sailsjs.com/documentation/concepts/actions-and-controllers#?actions-2)).\n Helpers | `helpers.moduleDefinitions` | A dictionary mapping helper names to helper definitions.\n Models  | `orm.moduleDefinitions.models` | A dictionary mapping model identities (lower-cased model names) to model definitions.\n Policies | `policies.moduleDefinitions` | A dictionary mapping policy names (e.g. `isAdmin`) to policy functions.\n\n\n### Reference\n\nThe full reference for Sails' programmatic interface is available in [**Reference > Application**](https://sailsjs.com/documentation/reference/application).\n\n<docmeta name=\"displayName\" value=\"Programmatic usage\">\n"
  },
  {
    "path": "docs/concepts/Programmatic Usage/Tips and Tricks.md",
    "content": "# Tips and tricks for programmatic usage\n\nWhen loading a Sails app programmatically, you will usually want to turn off hooks that are not being used, both for optimization and to ensure minimal interference between the Sails app and the Node script enclosing it.  To turn off a hook, set it to `false` in the `hooks` dictionary that is sent as part of the first argument to `.load()` or `.lift()`.\n\nYou may also want to turn off Sails [globals](https://sailsjs.com/documentation/concepts/globals), _especially when loading more than one Sails app simultaneously_.  Since all Node apps in the same process share the same globals, starting more than one Sails app with globals turned on is a surefire way to end up with collisions between models, controllers, and other app-wide entities.\n\n\n```javascript\n// Turn off globala and commonly unused hooks in programmatic apps\nmySailsApp.load({\n  hooks: {\n     grunt: false,\n     sockets: false,\n     pubsub: false\n  },\n  globals: false\n})\n```\n\nFinally, note that while you can use the Sails constructor to programmatically create and start as many Sails apps as you like, each app can only be started once.  Once you call `.lower()` on an app, it cannot be started again.\n\n<docmeta name=\"displayName\" value=\"Tips and tricks\">\n"
  },
  {
    "path": "docs/concepts/README.md",
    "content": "# docs/concepts\n\nThis section contains the markdown files which are compiled to HTML and eventually made available as conceptual documentation for the Sails framework at https://sailsjs.com/documentation/concepts.\n\n### Notes\n> - This README file **is not compiled to HTML** for the website.  It is just here to explain what you're looking at.\n> - Depending on what branch of `sails` you are currently viewing, the domain may vary. See the top-level documentation README file for information about working with the markdown files in this repo, and to understand the branching/versioning strategy.\n\n<docmeta name=\"notShownOnWebsite\" value=\"true\">\n"
  },
  {
    "path": "docs/concepts/Realtime/Multi-server environments.md",
    "content": "# Realtime communication in a multi-server (aka \"clustered\") environment\n\nWith the default configuration, Sails allows realtime communication between a single server and all of its connected clients.  When [scaling your Sails app to multiple servers](https://sailsjs.com/documentation/concepts/deployment/scaling), some extra setup is necessary in order for realtime messages to be reliably delivered to clients regardless of which server they&rsquo;re connected to.  This setup typically involves:\n\n1. Setting up a [hosted](https://www.google.com/search?q=hosted+redis) instance of [Redis](http://redis.io/).\n2. Installing [@sailshq/socket.io-redis](https://npmjs.com/package/@sailshq/socket.io-redis) as a dependency of your Sails app.\n1. Updating your [sails.config.sockets.adapter](https://sailsjs.com/documentation/reference/configuration/sails-config-sockets#?commonlyused-options) setting to `@sailshq/socket.io-redis` and setting the appropriate `host`, `password`, etc. fields to point to your hosted Redis instance.\n\nNo special setup is necessary in your hosted Redis install; just plug the appropriate host address and credentials into your `/config/sockets.js` file and the `@sailshq/socket.io-redis` adapter will take care of everything for you.\n\n> Note: When operating in a multi-server environment, some socket methods without callbacks are _volatile_, meaning that they take an indeterminate amount of time to complete, even if the code appears to execute immediately.  It's good to keep this in mind when considering code that would, for example, follow a call to [`.addRoomMembersToRoom()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/add-room-members-to-room) immediately with a call to [`.broadcast()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/sails-sockets-broadcast).  In such cases, the new room member probably won't receive the newly broadcasted message, since it is unlikely that the updated room membership had already been propagated to the other servers in the cluster when `.broadcast()` was called.\n\n### Reference\n\n* See the full reference for the [sails.io.js library](https://sailsjs.com/documentation/reference/web-sockets/socket-client) to learn how to use sockets on the client side to communicate with your Sails app.\n* See the [sails.sockets](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets) reference to learn how to send messages from the server to connected sockets\n* See the [resourceful pub-sub](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub) reference to learn how to use Sails blueprints to automatically send realtime messages about changes to your [models](https://sailsjs.com/documentation/concepts/models-and-orm/models).\n* Visit the [Socket.io](http://socket.io) website to learn more about the underlying library Sails uses for realtime communication\n\n<docmeta name=\"displayName\" value=\"Multi-server environments\">\n"
  },
  {
    "path": "docs/concepts/Realtime/On the client.md",
    "content": "# Realtime communication between the client and the server\n\nThe easiest way to send a realtime message from a client to a Sails app is by using the [sails.io.js](https://sailsjs.com/documentation/reference/web-sockets/sails-io-js) library.  This library allows you to easily connect sockets to a running Sails app, and provides methods for making requests to [Sails routes](https://sailsjs.com/documentation/concepts/routes) that are handled in the same manner as a \"regular\" HTTP request.\n\nThe sails.io.js library is automatically added to the default [layout template](https://sailsjs.com/documentation/concepts/views/layouts) of new Sails apps using a `<script>` tag.  When a web page loads the `sails.io.js` script, it attempts to create a new [client socket](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket) and connect it to the Sails app, exposing it as the global variable `io.socket`.\n\n### Examples\n\nInclude the `sails.io.js` library, and make a request to the `/hello` route of a Sails app using the automatically-connected socket:\n\n```html\n<script type=\"text/javascript\" src=\"/js/dependencies/sails.io.js\"></script>\n<script type=\"text/javascript\">\nio.socket.get('/hello', function responseFromServer (body, response) {\n  console.log(\"The server responded with status \" + response.statusCode + \" and said: \", body);\n});\n</script>\n```\n\nNow consider this more advanced (and less common) use case: let's disable the eager (auto-connecting) socket, and instead create a new client socket manually.  When it successfully connects to the server, we'll make it log a message:\n```html\n<script type=\"text/javascript\" src=\"/js/dependencies/sails.io.js\" autoConnect=\"false\"></script>\n<script type=\"text/javascript\">\nvar mySocket = io.sails.connect();\nmySocket.on('connect', function onConnect () {\n  console.log(\"Socket connected!\");\n});\n</script>\n```\n\n### Socket requests vs traditional AJAX requests\n\nYou may have noticed that a client socket `.get()` is very similar to making an AJAX request, for example by using jQuery's `$.get()` method.  This is intentional&mdash;the goal is for you to be able to get the same response from Sails no matter where the request originated from.  The benefit to making the request using a client socket is that the [controller action](https://sailsjs.com/documentation/concepts/controllers#?actions) in your Sails app will have access to the socket which made the request, allowing it to _subscribe_ that socket to realtime notifications (see [sending realtime messages from the server](https://sailsjs.com/documentation/concepts/realtime/on-the-server)).\n\n### Reference\n\n* View the full [sails.io.js library](https://sailsjs.com/documentation/reference/web-sockets/socket-client) reference.\n* See the [sails.sockets](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets) reference to learn how to send messages from the server to connected sockets\n* See the [resourceful pub-sub](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub) reference to learn how to use Sails blueprints to automatically send realtime messages about changes to your [models](https://sailsjs.com/documentation/concepts/models-and-orm/models).\n* Visit the [Socket.io](http://socket.io) website to learn more about the underlying library Sails uses for realtime communication\n\n<docmeta name=\"displayName\" value=\"On the client\">\n"
  },
  {
    "path": "docs/concepts/Realtime/On the server.md",
    "content": "# Sending realtime messages from the server to one or more clients\n\n### Overview\n\nSails exposes two APIs for communicating with connected socket clients: the higher-level [resourceful pubsub API](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub), and the lower-level [sails.sockets API](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets).\n\n### Resourceful PubSub\n\nThe Resourceful PubSub (Published/Subscribe) API provides a high-level way to subscribe sockets to Sails model classes and instances.  It is entirely possible to create a rich realtime experience (for example, a chat app) using just this API.  Sails blueprints use Resourceful PubSub to automatically send out notifications about new model instances and changes to existing instances, but you can use them in your custom controller actions as well.\n\n##### Example\n\nCreate a new User model instance and notify all interested clients\n\n```javascript\n// Create the new user\nUser.create({\n  name: 'johnny five'\n}).exec(function(err, newUser) {\n  if (err) {\n    // Handle errors here!\n    return;\n  }\n  // Tell any socket watching the User model class\n  // that a new User has been created!\n  User.publishCreate(newUser);\n});\n```\n\n### `sails.sockets`\n\nThe `sails.sockets` API allows for lower-level communication directly with sockets, using methods like [`sails.sockets.join()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/sails-sockets-join) (subscribe a socket to all messages sent to a particular \"room\"), [`sails.sockets.leave()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/sails-sockets-leave) (unsubscribe a socket from a room), and [`sails.sockets.broadcast()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/sails-sockets-broadcast) (broadcast a message to all subscribers in one or more rooms).\n\n##### Example\n\nAdd a socket to the room \"funSockets\"\n\n```javascript\nsails.sockets.join(someSocket, \"funSockets\");\n```\n\nBroadcast a \"hello\" message to the \"funSockets\" room.  This message will be received by all client sockets that have (1) been added to the \"funSockets\" room on the server with `sails.sockets.join()` and (2) added a listener for the \"hello\" event on the client with [`socket.on('hello', ...)`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-on).\n\n```javascript\nsails.sockets.broadcast(\"funSockets\", \"hello\", \"Hello to all my fun sockets!\");\n```\n\n### Reference\n\n* View the full [sails.sockets](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets) API reference\n* See the reference for the [sails.io.js library](https://sailsjs.com/documentation/reference/web-sockets/socket-client) to learn how to use sockets on the client side to communicate with your Sails app.\n* See the [resourceful pub-sub](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub) reference to learn how to use Sails blueprints to automatically send realtime messages about changes to your [models](https://sailsjs.com/documentation/concepts/models-and-orm/models).\n* Visit the [Socket.io](http://socket.io) website to learn more about the underlying library Sails uses for realtime communication\n\n<docmeta name=\"displayName\" value=\"On the server\">\n"
  },
  {
    "path": "docs/concepts/Realtime/Realtime.md",
    "content": "# Realtime communication (aka Sockets)\n\n### Overview\n\nSails apps are capable of full-duplex, realtime communication between the client and server.  This means that a client (e.g. browser tab, Raspberry Pi, etc.) can maintain a persistent connection to a Sails backend, and messages can be sent from client to server (e.g. AJAX) or from server to client (e.g. \"comet\") at any time.  Two common uses of realtime communication are live chat implementations and multiplayer games.  Sails implements realtime on the server using the [socket.io](http://socket.io) library, and on the client using the [sails.io.js](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-on) library.  Throughout the Sails documentation, the terms **socket** and **websocket** are commonly used to refer to a two-way, persistent communication channel between a Sails app and a client.\n\nCommunicating with a Sails app via sockets is similar to using AJAX, in that both methods allow a web page to interact with the server without refreshing.  However, sockets differ from AJAX in two important ways: first, a socket can stay connected to the server for as long as the web page is open, allowing it to maintain _state_ (AJAX requests, like all HTTP requests, are _stateless_).  Second, because of the always-on nature of the connection, a Sails app can send data down to a socket at any time (hence the \"realtime\" moniker), whereas AJAX only allows the server to respond when a request is made.\n\n\n### Realtime model updates with resourceful pub-sub\n\nSockets making requests to Sails' [blueprint actions](https://sailsjs.com/documentation/reference/blueprint-api) are automatically subscribed to realtime messages about the models they retrieve via the [resourceful pub-sub API](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub).  You can also use this API in your custom controller actions to send out messages to clients interested in certain models.\n\n##### Example\n\nConnect a client-side socket to the server, subscribe to the `user` event, and request `/user` to subscribe to current and future User model instances.\n\n```html\n<!-- Simply include the sails.io.js script, and a client socket will be created for you -->\n<script type=\"text/javascript\" src=\"/js/dependencies/sails.io.js\"></script>\n<script type=\"text/javascript\">\n// The automatically-created socket is exposed as io.socket.\n// Use .on() to subscribe to the 'user' event on the client.\n// This event is sent by the Sails \"create\", \"update\",\n// \"delete\", \"add\" and \"remove\" blueprints to any socket that\n// is subscribed to one or more User model instances.\nio.socket.on('user', function gotHelloMessage (data) {\n  console.log('User alert!', data);\n});\n// Using .get('/user') will retrieve a list of current User models,\n// subscribe this socket to those models, AND subscribe this socket\n// to notifications about new User models when they are created.\nio.socket.get('/user', function gotResponse(body, response) {\n  console.log('Current users: ', body);\n})\n</script>\n```\n\n### Custom realtime communication with `sails.sockets`\n\nSails exposes a rich API on both the client and the server for sending custom realtime messages.\n\n##### Example\n\nHere's the client-side code to connect a socket to the Sails/Node.js server and listen for an socket event named \"hello\":\n\n```html\n<!-- Simply include the sails.io.js script, and a client socket will be created and auto-connected for you -->\n<script type=\"text/javascript\" src=\"/js/dependencies/sails.io.js\"></script>\n<script type=\"text/javascript\">\n\n// The auto-connecting socket is exposed as `io.socket`.\n\n// Use `io.socket.on()` to listen for the 'hello' event:\nio.socket.on('hello', function (data) {\n  console.log('Socket `' + data.id + '` joined the party!');\n});\n</script>\n```\n\nThen, also on the client, we can send a _socket request_.  In this case, we'll wire up the browser to send a socket request when a particular button is clicked:\n\n```js\n$('button#say-hello').click(function (){\n\n  // And use `io.socket.get()` to send a request to the server:\n  io.socket.get('/say/hello', function gotResponse(data, jwRes) {\n    console.log('Server responded with status code ' + jwRes.statusCode + ' and data: ', data);\n  });\n\n});\n\n```\n\n\nMeanwhile, on the server...\n\nTo respond to requests to `GET /say/hello`, we use an action.  In our action, we'll subscribe the requesting socket to the \"funSockets\" room, then broadcast a \"hello\" message to all sockets in that room (excluding the new one).\n\n```javascript\n// In /api/controllers/SayController.js\nmodule.exports = {\n\n  hello: function(req, res) {\n\n    // Make sure this is a socket request (not traditional HTTP)\n    if (!req.isSocket) {\n      return res.badRequest();\n    }\n\n    // Have the socket which made the request join the \"funSockets\" room.\n    sails.sockets.join(req, 'funSockets');\n\n    // Broadcast a notification to all the sockets who have joined\n    // the \"funSockets\" room, excluding our newly added socket:\n    sails.sockets.broadcast('funSockets', 'hello', { howdy: 'hi there!'}, req);\n\n    // ^^^\n    // At this point, we've blasted out a socket message to all sockets who have\n    // joined the \"funSockets\" room.  But that doesn't necessarily mean they\n    // are _listening_.  In other words, to actually handle the socket message,\n    // connected sockets need to be listening for this particular event (in this\n    // case, we broadcasted our message with an event name of \"hello\").  The\n    // client-side code you'd need to write looks like this:\n    // \n    //   io.socket.on('hello', function (broadcastedData){\n    //       console.log(data.howdy);\n    //       // => 'hi there!'\n    //   }\n    // \n\n    // Now that we've broadcasted our socket message, we still have to continue on\n    // with any other logic we need to take care of in our action, and then send a\n    // response.  In this case, we're just about wrapped up, so we'll continue on\n\n    // Respond to the request with a 200 OK.\n    // The data returned here is what we received back on the client as `data` in:\n    // `io.socket.get('/say/hello', function gotResponse(data, jwRes) { /* ... */ });`\n    return res.json({\n      anyData: 'we want to send back'\n    });\n\n  }\n}\n```\n\n### Reference\n\n* See the full reference for the [sails.io.js library](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-on) to learn how to use sockets on the client side to communicate with your Sails app.\n* See the [sails.sockets](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets) reference to learn how to send custom messages from the server to connected sockets.\n* See the [resourceful pub-sub](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub) reference to learn how Sails' blueprint API automatically sends realtime messages about changes to your [models](https://sailsjs.com/documentation/concepts/models-and-orm/models).\n* Visit the [Socket.io](http://socket.io) website to learn more about the underlying library Sails uses for realtime communication\n\n<docmeta name=\"displayName\" value=\"Realtime\">\n"
  },
  {
    "path": "docs/concepts/Routes/RouteTargetSyntax.md",
    "content": "# Custom routes\n\n### Overview\n\nSails allows you to explicitly route URLs in several different ways in your **config/routes.js** file.  Every route configuration consists of an **address** and a **target**, for example:\n\n```js\n'GET /foo/bar': 'UserController.subscribe'\n^^^address^^^  ^^^^^^^^^^target^^^^^^^^^^\n```\n\n### Route address\n\nThe route address indicates what URL should be matched in order to apply the handler and options defined by the target.  A route consists of an optional verb and a mandatory path:\n\n```js\n'POST  /foo/bar'\n^verb^ ^^path^^\n```\n\nIf no verb is specified, the route will match any CRUD method (**GET**, **PUT**, **POST**, **DELETE** or **PATCH**).  If `ALL` is specified as the verb, the route will match _any_ method.\n\nNote the initial `/` in the path--all paths should start with one in order to work properly.\n\n\n### Wildcards and dynamic parameters\n\nIn addition to specifying a static path like **foo/bar**, you can use `*` as a wildcard:\n\n```js\n'/*'\n```\n\nwill match all paths, where as:\n\n```js\n'/user/foo/*'\n```\n\nwill match all paths that *start* with **/user/foo**.\n\n> **Note:** When using a route with a wildcard, such as `'/*'`, be aware that this will also match requests to static assets (i.e. `/js/dependencies/sails.io.js`) and override them. To prevent this, consider using the `skipAssets` option [described below](https://sailsjs.com/documentation/concepts/routes/custom-routes#?route-target-options).\n\nTo receive the runtime value corresponding with this wildcard (`*`) in a [modern Sails action](https://sailsjs.com/documentation/concepts/actions-and-controllers#?what-does-an-action-file-look-like), use `urlWildcardSuffix` at the top level of your action definition to indicate the name of the input you would like to use to represent the dynamic value:\n\n\n```javascript\nurlWildcardSuffix: 'template',\ninputs: {\n  template: {\n    description: 'The relative path to an EJS template within our `views/emails/` folder -- WITHOUT the file extension.',\n    extendedDescription: 'Use strings like \"foo\" or \"foo/bar\", but NEVER \"foo/bar.ejs\" or \"/foo/bar\".  For example, '+\n      '\"internal/email-contact-form\" would send an email using the \"views/emails/internal/email-contact-form.ejs\" template.',\n    example: 'email-reset-password',\n    type: 'string',\n    required: true\n  },\n},\nfn: async function({ template }) {\n  // …\n}\n```\n\n\n### Notes\n> - Alternatively, in a classic (req,res) action, you can use `req.param('0')` to access the dynamic value of a route's URL wildcard suffix (`*`).\n> - For more background, see https://www.npmjs.com/package/machine-as-action\n\n\n\nAnother way to capture parts of the address is to use **pattern variables**.  This lets a route match special named parameters which _never contain any `/` characters_ by using the `:paramName` pattern variable syntax instead of the `*`:\n\n```js\n'/user/foo/bar/:name'\n```\n\nOr for an optional path parameter, add `?` to the end of the pattern variable:\n\n```js\n'/user/foo/bar/:name?'\n```\n\nThis will match _almost_ the same requests as `/user/foo/bar/*`, but will provide the value of the dynamic portions of the route URL to the route handler as parameter values (e.g. `req.param('name')`).\n\n> Note that the wildcard (`*`) syntax matches slashes, where the URL pattern variable (`:`) syntax does not.  So in the example above, given the route address `GET /user/foo/bar/*`, incoming requests with URLs like `/user/foo/bar/baz/bing/bong/bang` would match (whereas if you used the `:name` syntax, the same URL would not match.)\n\n### URL slugs\nA common use case for pattern variables is the design of slugs or [vanity URLs](http://en.wikipedia.org/wiki/Clean_URL#Slug).  For example, consider the URL of a repository on Github, [`http://www.github.com/balderdashy/sails`](http://www.github.com/balderdashy/sails).  In Sails, we might define this route at the **bottom of our `config/routes.js` file** like so:\n\n```javascript\n'get /:account/:repo': {\n  controller: 'RepoController',\n  action: 'show',\n  skipAssets: true\n}\n```\n\nIn your `RepoController`'s `show` action, we'd use `req.param('account')` and `req.param('repo')` to look up the data for the appropriate repository, then pass it in to the appropriate [view](https://sailsjs.com/documentation/concepts/Views) as [locals](https://sailsjs.com/documentation/concepts/views/locals).  The [`skipAssets` option](https://sailsjs.com/documentation/concepts/routes/custom-routes#?route-target-options) ensures that the vanity route doesn't accidentally match any of our [assets](https://sailsjs.com/documentation/concepts/assets) (e.g. `/images/logo.png`), so they are still accessible.\n\n### Regular expressions in addresses\n\nIn addition to the wildcard address syntax, you may also use regular expressions to define the URLs that a route should match.  The syntax for defining an address with a regular expression is:\n\n`'r|<regular expression string>|<comma-delimited list of param names>'`\n\nThat's the letter \"**r**\", followed by a pipe character `|`, a regular expression string *without delimiters*, another pipe, and a list of parameter names that should be mapped to parenthesized groups in the regular expression.  For example:\n\n`'r|^/\\\\d+/(\\\\w+)/(\\\\w+)$|foo,bar\": \"message/my-action'`\n\nWill match `/123/abc/def`, running the action in **api/controllers/message/my-action.js**, and supplying the values `abc` and `def` as `req.param('foo')` and `req.param('bar')`, respectively.\n\nNote the double-backslash in `\\\\d` and `\\\\w`; this escaping is necessary for the regular expression to work correctly!\n\n##### About route ordering\n\nWhile you are free to add items to your **config/routes.js** file in any order, be aware that Sails will internally sort your routes by _inclusiveness_, a measure of how many potential requests an address can handle.  In general, routes with addresses containing no dynamic components will be matched first, followed by routes with dynamic parameters, followed by those with wildcards.  This prevents routes from blocking each other (for example, a `/*` route, if left at the top of the list, would respond to all requests and no other routes would ever be matched).\n\nIf you have any [regular expression addresses](https://sailsjs.com/documentation/concepts/routes/custom-routes#?regular-expressions-in-addresses), they will be left in the order you specify.  For example, if your **config/routes.js** file contains a `GET /foo/bar` route followed by a `GET r|^/foo/\\\\d+$|` route, the second route will always be sorted to appear immediately after `GET /foo/bar`.  This is due to the extreme difficulty of determining the inclusiveness of a regular expression route.  Take care when specifying these routes that you order them so that they won't match more requests than intended.\n\n### Route target\n\nThe address portion of a custom route specifies which URLs the route should match.  The *target* portion specifies what Sails should do after the match is made.  A target can take one of several different forms.  In some cases you may want to chain multiple targets to a single address by placing them in an array, but in most cases each address will have only one target.  The different types of targets are discussed below, followed by a discussion of the various options that can be applied to them.\n\n##### Controller / action target syntax\n\nThis syntax binds a route to an action in a [controller file](https://sailsjs.com/documentation/concepts/actions-and-controllers#?controllers).  The following four routes are equivalent:\n\n```js\n'GET /foo/go': 'FooController.myGoAction',\n'GET /foo/go': 'foo.myGoAction',\n'GET /foo/go': { controller: 'foo', action: 'myGoAction' },\n'GET /foo/go': { controller: 'FooController', action:'myGoAction' },\n```\n\nEach one maps `GET /foo/go` to the `myGoAction` action of the controller in **api/controllers/FooController.js**, or to the action in **api/controllers/foo/mygoaction.js**.  If no such controller or action exists, Sails will output an error message and ignore the route.  Otherwise, whenever a **GET** request to **/foo/go** is made, the code in that action will be run.\n\nThe controller and action names in this syntax are case-insensitive.\n\n##### Standalone action target syntax\n\nThis syntax binds an address to a [standalone Sails action](https://sailsjs.com/documentation/concepts/actions-and-controllers#?standalone-actions).  Simply specify the path of the action (relative to `api/controllers`):\n\n```js\n'GET /': { action: 'index' },   // Use the action in api/controllers/index.js\n\n'GET /foo/go': { action: 'foo/go-action' } // Use the action in api/controllers/foo/go-action.js OR\n                                           // the \"go-action\" action in api/controllers/FooController.js\n\n'GET /bar/go': 'foo/go-action' // Binds to the same action as above, using shorthand notation\n```\n\n##### Routing to blueprint actions\n\nThe [blueprint API](https://sailsjs.com/documentation/reference/blueprint-api) adds several actions for each of your models, all of which are available for routing.  For example, if you have a model defined in `api/models/User.js`, you&rsquo;ll automatically be able to do:\n\n```js\n'GET /foo/go': 'user/find'              // Return a list of users\n```\nor\n```js\n'GET /foo/go': 'UserController.find'    // Same as above\n```\n\nIf you have a custom action in `api/controllers/user/find.js` or `api/controllers/UserController.js`, that action will be run instead of the default blueprint `find`.  For a full list of the actions provided for your models, see the [blueprint API reference](https://sailsjs.com/documentation/reference/blueprint-api).\n\n##### View target syntax\n\nAnother common target is one that binds a route to a [view](https://sailsjs.com/documentation/concepts/Views).  This is particularly useful for binding static views to a custom URL, and it's how the default homepage for new projects is set up out of the box.\n\nThe syntax for view targets is simple: it is just the path to the view file, without the file extension (e.g. `.ejs`) and relative to the **views/** folder :\n\n```js\n'GET /team': { view: 'brochure/about' }\n```\n\nThis tells Sails to handle `GET` requests to `/team` by serving the view template located at `views/brochure/about.ejs` (assuming the default EJS [template engine](https://sailsjs.com/documentation/concepts/views/view-engines) is used).  As long as that view file exists, a **GET** request to  **/home** will display it. For consistency with Express/consolidate, if the specified relative path does not match a view file, then Sails will look for a sub-folder with the same name (e.g. `pages/brochure`) and serve the \"index\" view in that sub-folder (e.g. `pages/brochure/index.ejs`) if one exists.\n\n> Note that since this route is bound directly to the view, none of your configured policies will be applied.  If you need to configure a policy, use `res.view()` from a controller action.  See [this StackOverflow question](http://stackoverflow.com/questions/21303217/sailsjs-policy-based-route-with-a-view/21340313#21340313) for more background information.\n\n\n\n##### Redirect target syntax\nYou can have one address redirect to another, either within your Sails app or on another server entirely. This can be done just by specifying the redirect URL as a string:\n\n```js\n'/alias' : '/some/other/route/url',\n'GET /google': 'http://www.google.com'\n```\n\nBe careful to avoid redirect loops when redirecting within your Sails app!\n\nNote that when redirecting, the HTTP method of the original request (and any extra headers / parameters) will likely be lost, and the request will be transformed to a simple **GET** request.  In the above example, a **POST** request to **/alias** will result in a **GET** request to **/some/other/route**.  This is somewhat browser-dependent behavior, but it is recommended that you don't expect request methods and other data to survive a redirect.\n\n##### Response target syntax\nYou can map an address directly to a default or custom [response](https://sailsjs.com/documentation/concepts/extending-sails/custom-responses) using this syntax:\n\n```js\n'/foo': { response: 'notFound' }\n```\n\nSimply specify the name of the response file in your **api/responses** folder, without the **.js** extension.  The response name in this syntax is case-sensitive.  If you attempt to bind a route to a non-existent response, Sails will output an error and ignore the route.\n\n##### Policy target syntax\n\nIn most cases, you will want to apply [policies](https://sailsjs.com/documentation/concepts/policies) to your controller actions using the [**config/policies.js**](https://sailsjs.com/documentation/reference/configuration/sails-config-policies) config file.  However, there are some instances when you'll want to apply a policy directly to a custom route, particularly when you are using the [view](https://sailsjs.com/documentation/concepts/routes/custom-routes#?view-target-syntax) target syntax.  The policy target syntax is:\n\n```js\n'/foo': { policy: 'my-policy' }\n```\n\nNote that you will always want to chain the policy to at least one other type of target using an array:\n\n```js\n'/foo': [\n  { policy: 'my-policy' },\n  { view: 'dashboard' }\n]\n```\n\nThis will apply the **my-policy** policy to the route and, if it passes, continue by displaying the **views/dashboard.ejs** view.\n\n##### Function target syntax\n\nFor one-off jobs (quick tests, for example), you can assign a route directly to a function:\n```js\n'/foo': function(req, res) {\n  return res.send('hello!');\n},\n```\n\nYou can also combine this syntax with others using an array. This allows you to define quick, inline middleware:\n\n```js\n'/foo': [\n  function(req, res, next) {\n    sails.log('Quick and dirty test:', req.allParams());\n    return next();\n  },\n  { controller: 'user', action: 'find' }\n],\n```\n\nYou can also use a dictionary with an `fn` key to assign a function.  This allows you to also specify [other route target  options](https://sailsjs.com/documentation/concepts/routes/custom-routes#?route-target-options) at the same time:\n```js\n'GET /*': {\n  skipAssets: true,\n  fn: function(req, res) {\n    return res.send('hello!');\n  }\n},\n```\n\n> Best practice is to use the function syntax only for temporary routes, since doing so goes against the structural conventions that make Sails useful!  (Plus, the less cluttered your routes.js file, the better.)\n\n### Route target options\n\nIn addition to the options discussed in the various route target syntaxes above, any other property added to a route target object will be passed through to the route handler in the `req.options` object.  There are several reserved properties that can be used to affect the behavior of the route handlers.  These are listed in the table below.\n\n| Property    | Applicable Target Types       | Data Type | Details |\n|-------------|:----------:|-----------|-----------|\n|`skipAssets`|all|((boolean))|Set to `true` if you *don't* want the route to match URLs with dots in them (e.g. **myImage.jpg**).  This will keep your routes with [wildcard notation](https://sailsjs.com/documentation/concepts/routes/custom-routes#?wildcards-and-dynamic-parameters) from matching URLs of static assets.  Useful when creating [URL slugs](https://sailsjs.com/documentation/concepts/routes#url-slugs).|\n|`skipRegex`|all|((regexp))|If skipping every URL containing a dot is too permissive, or you need a route's handler to be skipped based on different criteria entirely, you can use `skipRegex`.  This option allows you to specify a regular expression or array of regular expressions to match the request URL against; if any of the matches are successful, the handler is skipped.  Note that unlike the syntax for binding a handler with a regular expression, `skipRegex` expects *actual [RegExp objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp)*, not strings.|\n|`locals`|[controller](https://sailsjs.com/documentation/concepts/routes/custom-routes#?controller-action-target-syntax), [view](https://sailsjs.com/documentation/concepts/routes/custom-routes#?view-target-syntax), [blueprint](https://sailsjs.com/documentation/concepts/routes/custom-routes#?routing-to-blueprint-actions), [response](https://sailsjs.com/documentation/concepts/routes/custom-routes#?response-target-syntax)|((dictionary))|Sets default [local variables](https://sailsjs.com/documentation/reference/response-res/res-view?q=arguments) to pass to any view that is rendered while handling the request.|\n|`cors`|all|((dictionary)) or ((boolean)) or ((string))|Specifies how to handle requests for this route from a different origin.  See the [main CORS documentation](https://sailsjs.com/documentation/concepts/security/cors) for more info.|\n|`csrf`|all|((boolean))|Indicate whether the route should be protected by requiring a CSRF token to be passed with the request.  See the [main CSRF documentation](https://sailsjs.com/documentation/concepts/security/csrf) for more info.\n|`parseBlueprintOptions`|[blueprint](https://sailsjs.com/documentation/concepts/routes/custom-routes#?routing-to-blueprint-actions)|((function))|Provide this function in order to override the default behavior for a blueprint action (including search criteria, skip, limit, sort and population).  See the [blueprints configuration reference](https://sailsjs.com/documentation/reference/configuration/sails-config-blueprints#?using-parseblueprintoptions) for more info.\n<docmeta name=\"displayName\" value=\"Custom routes\">\n"
  },
  {
    "path": "docs/concepts/Routes/Routes.md",
    "content": "# Routes\n\n### Overview\n\nThe most basic feature of any web application is the ability to interpret a request sent to a URL, then send back a response.  In order to do this, your application has to be able to distinguish one URL from another.\n\nLike most web frameworks, Sails provides a router: a mechanism for mapping URLs to actions and views.  **Routes** are rules that tell Sails what to do when faced with an incoming request.  There are two main types of routes in Sails: **custom** (or \"explicit\") and **automatic** (or \"implicit\").\n\n\n### Custom routes\n\nSails lets you design your app's URLs in any way you like&mdash;there are no framework restrictions.\n\nEvery Sails project comes with [`config/routes.js`](https://sailsjs.com/documentation/reference/configuration/sails-config-routes), a simple [Node.js module](http://nodejs.org/api/modules.html) that exports an object of custom, or \"explicit\" **routes**. For example, this `routes.js` file defines six routes; some of them point to actions, while others route directly to views:\n\n```javascript\n// config/routes.js\nmodule.exports.routes = {\n  'GET /signup': { view: 'conversion/signup' },\n  'POST /signup': { action: 'entrance/signup' },\n  'GET /login': { view: 'portal/login' },\n  'POST /login': { action: 'entrance/login' },\n  '/logout': { action: 'account/logout' },\n  'GET /me': { action: 'account/profile' }\n```\n\n\nEach **route** consists of an **address** on the left (e.g. `'GET /me'`) and a **target** on the right (e.g. `{ action: 'account/profile' }`)  The **address** is a URL path and (optionally) a specific [HTTP method](http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods). The **target** can be defined in a number of different ways ([see the expanded concepts section on the subject](https://sailsjs.com/documentation/concepts/routes/custom-routes#?route-target)), but the syntax above is the most common.  When Sails receives an incoming request, it checks the **address** of all custom routes for matches.  If a matching route is found, the request is then passed to its **target**.\n\nFor example, we might read `'GET /me': { action: 'account/profile' }` as:\n\n> \"Hey Sails, when you receive a GET request to `http://mydomain.com/me`, run the `account/profile` action, would'ya?\"\n\nYou can also specify the view layout within the route itself:\n\n```javascript\n'GET /privacy': {\n    view: 'legal/privacy',\n    locals: {\n      layout: 'users'\n    }\n  },\n```\n\n#### Notes\n+ That a request matches a route address doesn't necessarily mean it will be passed to that route's target _directly_. HTTP requests will usually pass through some [middleware](https://sailsjs.com/documentation/concepts/Middleware) before being passed to a route's target, and if the route points to a controller [action](https://sailsjs.com/documentation/concepts/Controllers?q=actions), the request will first need to pass through any configured [policies](https://sailsjs.com/documentation/concepts/Policies). There are also a few special [route options](https://sailsjs.com/documentation/concepts/routes/custom-routes#?route-target-options) which allow a route to be \"skipped\" for certain kinds of requests.\n+ The router can also programmatically **bind** a **route** to any valid route target, including canonical Node middleware functions (i.e. `function (req, res, next) {}`).  However, you should always use the conventional [route target syntax](https://sailsjs.com/documentation/concepts/routes/custom-routes#?route-target) when possible&mdash;it streamlines development, simplifies training, and makes your app more maintainable.\n\n\n\n### Automatic routes\n\nIn addition to your custom routes, Sails binds many routes for you automatically.  If a URL doesn't match a custom route, it may match one of the automatic routes and still generate a response.  The main types of automatic routes in Sails are:\n\n* [blueprint routes](https://sailsjs.com/documentation/reference/blueprint-api?q=blueprint-routes), which provide your [controllers](https://sailsjs.com/documentation/concepts/controllers) and [models](https://sailsjs.com/documentation/concepts//models-and-orm/models) with a full REST API.\n* [assets](https://sailsjs.com/documentation/concepts/assets), such as images, Javascript and stylesheet files.\n\n\n##### Unhandled requests\n\nIf no custom or automatic route matches a request URL, Sails will send back a default 404 response.  This response can be customized by adding a `api/responses/notFound.js` file to your app.  See [custom responses](https://sailsjs.com/documentation/concepts/extending-sails/custom-responses) for more info.\n\n##### Unhandled errors in request handlers\n\nIf an unhandled error is thrown during the processing of a request (for instance, in some [action code](https://sailsjs.com/documentation/concepts/actions-and-controllers)), Sails will send back a default 500 response. This response can be customized by adding an `api/responses/serverError.js` file to your app.  See [custom responses](https://sailsjs.com/documentation/concepts/extending-sails/custom-responses) for more info.\n\n### Supported protocols\n\nThe Sails router is \"protocol-agnostic\"&mdash;it knows how to handle both [HTTP requests](http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol) and messages sent via [WebSockets](http://en.wikipedia.org/wiki/Websockets). It accomplishes this by listening for Socket.io messages sent to reserved event handlers in a simple format, called JWR (JSON-WebSocket Request/Response).  This specification is implemented and available out of the box in the [client-side socket SDK](https://sailsjs.com/documentation/reference/web-sockets/socket-client).\n\n\n\n#### Notes\nAdvanced users may opt to circumvent the router entirely and send low-level, completely customizable WebSocket messages directly to the underlying Socket.io server.  You can bind socket events directly in your app's [`onConnect`](https://sailsjs.com/documentation/reference/configuration/sails-config-sockets#?commonlyused-options) function (located in [`config/sockets.js`](https://sailsjs.com/documentation/anatomy/config/sockets.js)),  but bear in mind that in most cases you are better off leveraging the request interpreter for socket communication. Maintaining consistent routes across HTTP and WebSockets helps keep your app maintainable.\n\n\n\n\n<docmeta name=\"displayName\" value=\"Routes\">\n<docmeta name=\"nextUpLink\" value=\"/documentation/concepts/actions-and-controllers\">\n<docmeta name=\"nextUpName\" value=\"Actions\">\n"
  },
  {
    "path": "docs/concepts/Security/CORS.md",
    "content": "# Cross-Origin Resource Sharing (CORS)\n\n<!--\nEvery Sails app comes ready to handle AJAX requests from a web page on the same domain.  But what if you need to handle AJAX requests\noriginating from other domains?\n-->\n\n[CORS](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing) is a mechanism that allows browser scripts on pages served from other domains (e.g. myothersite.com) to talk to your server (e.g. api.mysite.com).  Like [JSONP](https://en.wikipedia.org/wiki/JSONP), the goal of CORS is to circumvent the [same-origin policy](http://en.wikipedia.org/wiki/Same-origin_policy), allowing your Sails server to successfully respond to requests from client-side JavaScript code running on a page hosted from some other domain.  Unlike JSONP, it works with more than just GET requests, and it allows you to whitelist particular origins (`staging.yoursite.com` or `yourothersite.net`) and prevent requests from others (`evil.com`).\n\nSails can be configured to allow cross-origin requests from a list of domains you specify, or from every domain.  This can be done on a per-route basis, or globally for every route in your app.\n\n### Enabling CORS\n\nFor security reasons, CORS is disabled by default in Sails.  But enabling it is simple.\n\nTo allow cross-origin requests from a whitelist of trusted domains to _any_ route in your app, simply enable `allRoutes` and provide an `origin` setting in [`config/security.js`](https://sailsjs.com/documentation/reference/configuration/sails-config-security#?sailsconfigsecuritycors):\n\n```javascript\ncors: {\n  allRoutes: true,\n  allowOrigins: ['http://example.com','https://api.example.com','http://blog.example.com:1337','https://foo.com:8888']\n}\n```\n\nTo allow cross-origin requests from _any_ domain to _any_ route in your app, use `allowOrigins: '*'`:\n\n```javascript\ncors: {\n  allRoutes: true,\n  allowOrigins: '*',\n  allowCredentials: false\n}\n```\n\nNote that when using `allowOrigins: '*'`, the `allowCredentials` setting _must_ be `false`, which means that requests containing cookies will be blocked.  This restriction exists to prevent third-party sites from being able to trick your logged-in users into making unauthorized requests to your app.  You can lift this restriction (at your own risk!) using the [`allowAnyOriginWithCredentialsUnsafe`](https://sailsjs.com/documentation/reference/configuration/sails-config-security#?sailsconfigsecuritycors) setting.\n\n\nSee [`sails.config.security.cors`](https://sailsjs.com/documentation/reference/configuration/sails-config-security#?sailsconfigsecuritycors) for a comprehensive reference of all available options.\n\n\n### Configuring CORS for individual routes\nIn addition to the global CORS configuration in `config/security.js`, these settings can be configured on a per-route basis in [`config/routes.js`](https://sailsjs.com/documentation/anatomy/config/routes-js).\n\nIf you set `allRoutes: true` in `config/security.js` but want to exempt a specific route, set `cors: false` in the route's target:\n\n```javascript\n'POST /signup': {\n   action: 'user/signup',\n   cors: false\n}\n```\n\nTo enable or override global CORS configuration for a particular route, provide `cors` as a dictionary:\n\n```javascript\n'GET /videos': {\n   action: 'video/find',\n   cors: {\n     allowOrigins: ['http://example.com','https://api.example.com','http://blog.example.com:1337','https://foo.com:8888'],\n     allowCredentials: false\n   }\n}\n```\n\n### Notes\n\n> + CORS support is only relevant for HTTP requests.  Requests made via sockets are not subject to cross-origin restrictions.  To ensure that your app is secure via sockets, configure the [`onlyAllowOrigins`](https://sailsjs.com/documentation/reference/configuration/sails-config-sockets) setting (typically in [`config/env/production.js`](https://sailsjs.com/documentation/anatomy/config/env/production-js)).\n> + CORS is not supported in Internet Explorer 7.  Fortunately, it is supported in IE8 and up, as well as in all other modern browsers.\n> + Read [more about CORS from MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS).\n> + Read the [CORS spec](https://www.w3.org/TR/cors/).\n\n<docmeta name=\"displayName\" value=\"CORS\">\n"
  },
  {
    "path": "docs/concepts/Security/CSRF.md",
    "content": "# CSRF\n\nCross-site request forgery ([CSRF](https://www.owasp.org/index.php/Cross-Site_Request_Forgery)) is a type of attack which forces an end user to execute unwanted actions on a web application backend with which he/she is currently authenticated.  In other words, without protection, cookies stored in a browser like Google Chrome can be used to send requests to Chase.com from a user's computer whether that user is currently visiting Chase.com or Horrible-Hacker-Site.com.\n\n### About CSRF tokens\n\nCSRF tokens are like limited-edition swag.  While a session tells the server that a user \"is who they say they are\", a csrf token tells the server they \"were where they say they were\".  When CSRF protection is enabled in your Sails app, all non-GET requests to the server must be accompanied by a special \"CSRF token\", which can be included as either the '_csrf' parameter or the 'X-CSRF-Token' header.\n\nUsing tokens protects your Sails app against cross-site request forgery (or CSRF) attacks. A would-be attacker needs not only a user's session cookie, but also this timestamped, secret CSRF token, which is refreshed/granted when the user visits a URL on your app's domain.  This allows you to have certainty that your users' requests haven't been hijacked, and that the requests they're making are intentional and legitimate.\n\nEnabling CSRF protection requires managing the token in your front-end app.  In traditional form submissions, this can be easily accomplished by sending along the CSRF token as a hidden input in your `<form>`.  Or better yet, include the CSRF token as a request param or header when you send AJAX requests.  To do that, you can either fetch the token by sending a request to the route where you mounted `security/grant-csrf-token`, or better yet, harvest the token from view locals using the `exposeLocalsToBrowser` partial.\n\nHere are some examples:\n\n#### (a) For modern, view-driven hybrid apps that submit forms with AJAX:\nUse the `exposeLocalsToBrowser` partial to provide access to the token from\nyour client-side JavaScript, e.g.:\n```html\n<%- exposeLocalsToBrowser() %>\n<script>\n  $.post({\n    foo: 'bar',\n    _csrf: window.SAILS_LOCALS._csrf\n  })\n</script>\n```\n\n#### (b) For single-page apps with static HTML:\nFetch the token by sending a GET request to the route where you mounted\nthe `security/grant-csrf-token`.  It will respond with JSON, e.g.:\n```js\n{ _csrf: 'ajg4JD(JGdajhLJALHDa' }\n```\n\n#### (c) For traditional HTML form submissions:\nRender the token directly into a hidden form input element in your HTML, e.g.:\n```html\n<form>\n  <input type=\"hidden\" name=\"_csrf\" value=\"<%= _csrf %>\" />\n</form>\n```\n\n### Enabling CSRF protection\n\nSails bundles optional CSRF protection out of the box. To enable the built-in enforcement, just make the following adjustment to [sails.config.security.csrf](https://sailsjs.com/docs/reference/configuration/sails-config-security-csrf) (conventionally located in your project's [`config/security.js`](https://sailsjs.com/anatomy/config/security-js) file):\n\n```js\ncsrf: true\n```\n\nYou can also turn CSRF protection on or off on a per-route basis by adding `csrf: true` or `csrf: false` to any route in your [`config/routes.js`](https://sailsjs.com/anatomy/config/routes-js) file.\n\nNote that if you have existing code that communicates with your Sails backend via POST, PUT, or DELETE requests, you'll need to acquire a CSRF token and include it as a parameter or header in those requests.  More on that in a sec.\n\n\n\n### CSRF tokens\n\nLike most Node applications, Sails and Express are compatibile with Connect's [CSRF protection middleware](http://www.senchalabs.org/connect/csrf.html) for guarding against such attacks.  This middleware implements the [Synchronizer Token Pattern](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29_Prevention_Cheat_Sheet#General_Recommendation:_Synchronizer_Token_Pattern).  When CSRF protection is enabled, all non-GET requests to the Sails server must be accompanied by a special token, identified by either a header or a parameter in the query string or HTTP body.\n\nCSRF tokens are temporary and session-specific; e.g. Imagine Mary and Muhammad are both shoppers accessing our e-commerce site running on Sails, and CSRF protection is enabled.  Let's say that on Monday, Mary and Muhammad both make purchases.  In order to do so, our site needed to dispense at least two different CSRF tokens- one for Mary and one for Muhammad.  From then on, if our web backend received a request with a missing or incorrect token, that request will be rejected. So now we can rest assured that when Mary navigates away to play online poker, the 3rd party website cannot trick the browser into sending malicious requests to our site using her cookies.\n\n### Dispensing CSRF tokens\n\nTo get a CSRF token, you should either bootstrap it in your view using [locals](https://sailsjs.com/documentation/concepts/views/locals) (good for traditional multi-page web applications) or fetch it using AJAX from a special protected JSON endpoint (handy for single-page-applications (SPAs).)\n\n\n##### Using view locals:\n\nFor old-school form submissions, it's as easy as passing the data from a view into a form action.  You can grab hold of the token in your view, where it may be accessed as a view local: `<%= _csrf %>`\n\ne.g.:\n```html\n<form action=\"/signup\" method=\"POST\">\n <input type=\"text\" name=\"emailaddress\"/>\n <input type='hidden' name='_csrf' value='<%= _csrf %>'>\n <input type='submit'>\n</form>\n```\nIf you are doing a `multipart/form-data` upload with the form, be sure to place the `_csrf` field before the `file` input, otherwise you run the risk of a timeout and a 403 firing before the file finishes uploading.\n\n\n\n\n\n##### Using AJAX/WebSockets\n\nIn AJAX/Socket-heavy apps, you might prefer to get the CSRF token dynamically rather than having it bootstrapped on the page.  You can do so by setting up a route in your [`config/routes.js`](https://sailsjs.com/anatomy/config/routes-js) file pointing to the `security/grant-csrf-token` action:\n\n```json\n{\n  'GET /csrfToken': { action: 'security/grant-csrf-token' }\n}\n```\n\nThen send a GET request to the route you defined, and you'll get CSRF token returned as JSON, e.g.:\n\n```json\n{\n  _csrf: 'ajg4JD(JGdajhLJALHDa'\n}\n```\n\n> For security reasons, you can&rsquo;t retrieve a CSRF token via a socket request.  You can however _spend_ CSRF tokens (see below) via socket requests.\n> The `security/grant-csrf-token` action is not intended to be used in cross-origin requests, since some browsers block third-party cookies by default.  See the [CORS documentation](https://sailsjs.com/documentation/concepts/security/cors) for more info about cross-origin requests.\n\n\n\n### Spending CSRF tokens\n\nOnce you've enabled CSRF protection, any POST, PUT, or DELETE requests (**including** virtual requests, e.g. from Socket.io) made to your Sails app will need to send an accompanying CSRF token as a header or parameter.  Otherwise, they'll be rejected with a 403 (Forbidden) response.\n\nFor example, if you're sending an AJAX request from a webpage with jQuery:\n```js\n$.post('/checkout', {\n  order: '8abfe13491afe',\n  electronicReceiptOK: true,\n  _csrf: 'USER_CSRF_TOKEN'\n}, function andThen(){ ... });\n```\n\nWith some client-side modules, you may not have access to the AJAX request itself. In this case, you can consider sending the CSRF token directly in the URL of your query. However, if you do so, remember to URL-encode the token before spending it:\n```js\n..., {\n  checkoutAction: '/checkout?_csrf='+encodeURIComponent('USER_CSRF_TOKEN')\n}\n```\n\n\n\n### Notes\n\n> + You can choose to send the CSRF token as the `X-CSRF-Token` header instead of the `_csrf` parameter.\n> + For most developers and organizations, CSRF attacks need only be a concern if you allow users to log into/securely access your Sails backend _from the browser_ (i.e. from your HTML/CSS/JavaScript front-end code). If you _don't_ (e.g. users only access the secured sections from your native iOS or Android app), it is possible you don't need to enable CSRF protection.  Why?  Because technically, the common CSRF attack discussed on this page is only _possible_ in scenarios where users use the _same client application_ (e.g. Chrome) to access different web services (e.g. Chase.com, Horrible-Hacker-Site.com.)\n> + For more information on CSRF, check out [Wikipedia](http://en.wikipedia.org/wiki/Cross-site_request_forgery)\n> + For \"spending\" CSRF tokens in a traditional form submission, refer to the example above (under \"Using view locals\".)\n\n\n<docmeta name=\"displayName\" value=\"CSRF\">\n"
  },
  {
    "path": "docs/concepts/Security/Clickjacking.md",
    "content": "# Clickjacking\n\n\n[Clickjacking](https://www.owasp.org/index.php/Clickjacking) (aka \"UI redress attacks\") happens when an attacker manages to trick your users into triggering \"unintended\" UI events (e.g. DOM events).\n\n\n\n### X-FRAME-OPTIONS\n\nOne simple way to help prevent clickjacking attacks is to enable the X-FRAME-OPTIONS header.\n\n##### Using [lusca](https://github.com/krakenjs/lusca#luscaxframevalue)\n\n> `lusca` is open-source under the [Apache license](https://github.com/krakenjs/lusca/blob/master/LICENSE.txt)\n\nFirst: \n\n```sh\n# In your sails app\nnpm install lusca --save\n```\n\nThen, in the `middleware` config object in `config/http.js`:\n\n```js\n  // ...\n  // maxAge ==> Number of seconds strict transport security will stay in effect.\n  xframe: require('lusca').xframe('SAMEORIGIN'),\n  // ...\n  order: [\n    // ...\n    'xframe'\n    // ...\n  ]\n```\n\n\n\n### Additional Resources\n+ [Clickjacking (OWasp)](https://www.owasp.org/index.php/Clickjacking)\n\n\n\n\n<docmeta name=\"displayName\" value=\"Clickjacking\">\n<docmeta name=\"tags\" value=\"clickjacking,ui redress attack\">\n"
  },
  {
    "path": "docs/concepts/Security/ContentSecurityPolicy.md",
    "content": "# Content security policy\n\n[Content Security Policy (CSP)](https://www.owasp.org/index.php/Clickjacking) is a [W3C specification](https://w3c.github.io/webappsec/specs/content-security-policy) for instructing the client browser as to which location and/or which type of resources are allowed to be loaded.  This spec uses \"directives\" to define loading behaviors for target resource types. Directives can be specified using HTTP response headers or HTML `<meta>` tags.\n\n\n### Enabling CSP\n\n##### Using [lusca](https://github.com/krakenjs/lusca#luscacspoptions)\n\n> `lusca` is open-source under the [Apache license](https://github.com/krakenjs/lusca/blob/master/LICENSE.txt)\n\nFirst:\n\n```sh\n# In your sails app\nnpm install lusca --save --save-exact\n```\n\nThen add `csp` in [`config/http.js`](https://sailsjs.com/anatomy/config/http-js):\n\n```js\n\n  // ...\n\n  csp: require('lusca').csp({\n    policy: {\n      'default-src': '*'\n    }\n  }),\n\n  // ...\n\n  order: [\n    // ...\n    'csp',\n    // ...\n  ]\n\n```\n\n\n\n##### Supported directives\n\nTo give you an idea how this works, here's a snapshot of supported CSP directives, as of 2017:\n\n| Directive       | |\n|:--------------- |:-------------------------- |\n| default-src     | Loading policy for all resources type in case a resource type dedicated directive is not defined (fallback) |\n| script-src      | Defines which scripts the protected resource can execute |\n| object-src      | Defines from where the protected resource can load plugins |\n| style-src       | Defines which styles (CSS) the user applies to the protected resource |\n| img-src         | Defines from where the protected resource can load images |\n| media-src       | Defines from where the protected resource can load video and audio |\n| frame-src       | Defines from where the protected resource can embed frames |\n| font-src        | Defines from where the protected resource can load fonts |\n| connect-src     | Defines which URIs the protected resource can load using script interfaces |\n| form-action     | Defines which URIs can be used as the action of HTML form elements |\n| sandbox         | Specifies an HTML sandbox policy that the user agent applies to the protected resource |\n| script-nonce    | Defines script execution by requiring the presence of the specified nonce on script elements |\n| plugin-types    | Defines the set of plugins that can be invoked by the protected resource by limiting the types of resources that can be embedded |\n| reflected-xss   | Instructs a user agent to activate or deactivate any heuristics used to filter or block reflected cross-site scripting attacks, equivalent to the effects of the non-standard X-XSS-Protection header |\n| report-uri      | Specifies a URI to which the user agent sends reports about policy violation |\n\n> For more information, see the [W3C CSP Spec](https://w3c.github.io/webappsec/specs/content-security-policy/).\n\n\n\n##### Browser compatibility\n\nDifferent CSP response headers are supported by different browsers.  For example, `Content-Security-Policy` is the W3C standard, but various versions of Chrome, Firefox, and IE use `X-Content-Security-Policy` or `X-WebKit-CSP`.  For the latest information on browser support, see [OWasp](https://www.owasp.org/index.php/Content_Security_Policy).\n\n\n\n### Additional Resources\n+ [Content Security Policy (OWasp)](https://www.owasp.org/index.php/Content_Security_Policy)\n+ Learn more about installing HTTP middleware in [Concepts > Middleware](https://sailsjs.com/documentation/concepts/middleware).\n\n<docmeta name=\"displayName\" value=\"Content security policy\">\n<docmeta name=\"tags\" value=\"csp,content security policy\">\n"
  },
  {
    "path": "docs/concepts/Security/DDOS.md",
    "content": "# DDOS\n\nThe prevention of [denial of service attacks](https://www.owasp.org/index.php/Application_Denial_of_Service) is a [complex problem](http://en.wikipedia.org/wiki/Denial-of-service_attack#Handling) which involves multiple layers of protection, up and down the networking stack.\nThis type of attack has achieved [notoriety](http://www.darkreading.com/vulnerabilities-and-threats/10-strategies-to-fight-anonymous-ddos-attacks/d/d-id/1102699) in recent years due to widespread media coverage of groups like Anonymous.\n\nAt the API layer, there isn't much that can be done in the way of prevention.  However, Sails offers a few settings to mitigate certain types of DDOS attacks:\n\n+ The session in Sails can be [configured](https://sailsjs.com/documentation/reference/configuration/sails-config-session) to use a separate session store (e.g. [Redis](http://redis.io/)), allowing your application to run without relying on the memory state of any one API server.  This means that multiple copies of your Sails app may be deployed to as many servers as is necessary to handle traffic.  This is achieved by using a [load balancer](https://en.wikipedia.org/wiki/Load_balancing_(computing) ), which directs each incoming request to a free server with the resources to handle it, eliminating any one app server as a single point of failure.\n+ Socket.io connections may be [configured](https://sailsjs.com/docs/reference/configuration/sails-config-sockets) to use a separate [socket store](sailsjs.com/docs/concepts/deployment/scaling) (e.g. Redis) for managing pub/sub state and message queueing. This eliminates the need for sticky sessions at the load balancer, preventing would-be attackers from directing their attacks against the same server again and again.\n\n> Note that, if you have the long-polling transport enabled in [sails.config.sockets](https://sailsjs.com/documentation/reference/configuration/sails-config-sockets), you'll still want to make sure TCP sticky sessions are enabled at your load balancer.  For more on that, check out this writeup about [sockets on Deis and Kubernetes](https://deis.com/blog/2016/socket.io-applications-kubernetes/).\n\n\n### Additional Resources\n\n+ [Backpressure and Unbounded Concurrency in Node.js](http://engineering.voxer.com/2013/09/16/backpressure-in-nodejs/) ([Voxer](http://voxer.com/))\n+ [Building a Node.js Server That Won't Melt](https://hacks.mozilla.org/2013/01/building-a-node-js-server-that-wont-melt-a-node-js-holiday-season-part-5/) ([Mozilla](https://hacks.mozilla.org/))\n+ [Security in Node.js](https://www.harrytorry.co.uk/node-js/security-flaws-in-node-js/) - see the \"Denial of Service\" section ([Harry Torry](https://www.harrytorry.co.uk))\n+ [Slowloris DDoSAttacks](http://www.ddosattacks.biz/attacks/slowloris-ddos-attack-aka-slow-and-low/)\n\n\n\n<docmeta name=\"displayName\" value=\"DDOS\">\n"
  },
  {
    "path": "docs/concepts/Security/P3P.md",
    "content": "# P3P\n\n### Background\n\nP3P stands for the \"Platform for Privacy Preferences\" and is a browser/web standard designed to facilitate better consumer web privacy control.  Currently (as of 2014), out of all the major browsers, only Internet Explorer supports it.  P3P most often comes into play when dealing with legacy applications.\n\nMany modern organizations are willfully ignoring P3P. Here's what [Facebook has to say](https://www.facebook.com/help/327993273962160/) on the subject:\n\n> The organization that established P3P, the World Wide Web Consortium, suspended its work on this standard several years ago because most modern web browsers don't fully support P3P. As a result, the P3P standard is now out of date and doesn't reflect technologies that are currently in use on the web, so most websites currently don't have P3P policies.\n> \n> See also: http://www.zdnet.com/blog/facebook/facebook-to-microsoft-p3p-is-outdated-what-else-ya-got/9332\n\n\n### Supporting P3P with Sails\n\nAll of that aside, sometimes you have to support P3P anyways.\n\nFortunately, a few different modules exist that bring P3P support to Express and Sails by enabling the relevant P3P headers.  To use one of these modules for handling P3P headers, install it from npm using the directions below, then open `config/http.js` in your project and configure it as a custom middleware.  To do that, define your P3P middleware as \"p3p\", and add the string \"p3p\" to your `middleware.order` array wherever you'd like it to run in the middleware chain (a good place to put it might be right before `cookieParser`):\n\nE.g. in `config/http.js`:\n\n```js\n// .....\nmodule.exports.http = {\n\n  middleware: {\n  \n    p3p: require('p3p')(p3p.recommmended), // <==== set up the custom middleware here and named it \"p3p\"\n\n    order: [\n      'startRequestTimer',\n      'p3p', // <============ configured the order of our \"p3p\" custom middleware here\n      'cookieParser',\n      'session',\n      'bodyParser',\n      'handleBodyParserError',\n      'compress',\n      'methodOverride',\n      'poweredBy',\n      '$custom',\n      'router',\n      'www',\n      'favicon',\n      '404',\n      '500'\n    ],\n    // .....\n  }\n};\n```\n\n\nCheck out the examples below for more guidance, and be sure and follow the links to see the docs for the module you're using for the latest information, comparative analysis of its features, any recent bug fixes, and advanced usage details.\n\n\n##### Using [node-p3p](https://github.com/troygoode/node-p3p)\n\n> `node-p3p` is open-source under the [MIT license](https://github.com/troygoode/node-p3p/blob/master/LICENSE).\n\n```sh\n# In your sails app\nnpm install p3p --save\n```\n\nThen in the `middleware` config object in `config/http.js`:\n\n```js\n  // ...\n  // node-p3p provides a recommended compact privacy policy out of the box\n  p3p: require('p3p')(require('p3p').recommended)\n  // ...\n```\n\n\n##### Using [lusca](https://github.com/krakenjs/lusca#luscap3pvalue)\n\n> `lusca` is open-source under the [Apache license](https://github.com/krakenjs/lusca/blob/master/LICENSE.txt)\n\n```sh\n# In your sails app\nnpm install lusca --save\n```\n\nThen in the `middleware` config object in `config/http.js`:\n\n```js\n  // ...\n  // \"ABCDEF\" ==> The compact privacy policy to use.\n  p3p: require('lusca').p3p('ABCDEF')\n  // ...\n```\n\n\n### Additional Resources: \n\n+ [Description of the P3P Project (Microsoft)](http://support.microsoft.com/kb/290333)\n+ [\"P3P Work suspended\" (W3C)](http://www.w3.org/P3P/)\n+ [P3P Compact Specification (CompactPrivacyPolicy.org)](http://compactprivacypolicy.org/compact_specification.htm)\n\n\n<docmeta name=\"displayName\" value=\"P3P\">\n\n"
  },
  {
    "path": "docs/concepts/Security/Security.md",
    "content": "# Security\n\n### Overview\n\nSails and Express provide built-in, easily configurable protection against most known types of web-application-level attacks.\n\n> **Note**: If you believe you have found a security vulnerability in Sails, please refer to our [security policy](https://sailsjs.com/security) for instructions for reporting it.\n\n\n### Security topics\n\nLearn about several different types of attacks that Node.js/Sails helps prevent out of the box, and how to enable and configure security settings in your app:\n\n+ [CORS](https://sailsjs.com/documentation/concepts/security/cors)\n+ [DDOS](https://sailsjs.com/documentation/concepts/security/ddos)\n+ [CSRF](https://sailsjs.com/documentation/concepts/security/csrf)\n+ [Clickjacking](https://sailsjs.com/documentation/concepts/security/clickjacking)\n+ [P3P](https://sailsjs.com/documentation/concepts/security/p3p)\n+ [Content Security Policy](https://sailsjs.com/documentation/concepts/security/content-security-policy)\n+ [Socket hijacking](https://sailsjs.com/documentation/concepts/security/socket-hijacking)\n+ [XSS](https://sailsjs.com/documentation/concepts/security/xss)\n+ [Strict Transport Security](https://sailsjs.com/documentation/concepts/security/strict-transport-security)\n\n\n<docmeta name=\"displayName\" value=\"Security\">\n\n"
  },
  {
    "path": "docs/concepts/Security/SocketHijacking.md",
    "content": "# Socket hijacking\n\nUnfortunately, cross-site request forgery attacks are not limited to the HTTP protocol.  WebSocket hijacking (sometimes known as [CSWSH](http://www.christian-schneider.net/CrossSiteWebSocketHijacking.html)) is a commonly overlooked vulnerability in most realtime applications.  Fortunately, since Sails treats both HTTP and WebSocket requests as first-class citizens, its built-in [CSRF protection](https://sailsjs.com/documentation/concepts/security/csrf) and [configurable CORS rulesets](https://sailsjs.com/documentation/concepts/security/cors) apply to both protocols.\n\nYou can prepare your Sails app against CSWSH attacks by enabling the built-in protection in [`config/security.js`](https://sailsjs.com/documentation/anatomy/config/security.js) and ensuring that a `_csrf` token is sent with all relevant incoming socket requests.  Additionally, if you're planning on allowing sockets to connect to your Sails app cross-origin (i.e. from a different domain, subdomain, or port) you'll want to configure your CORS settings accordingly.  You can also define the `authorization` setting in [`config/sockets.js`](https://sailsjs.com/documentation/anatomy/config/sockets.js) as a custom function which allows or denies the initial socket connection based on your needs.\n\n#### Notes\n+ CSWSH prevention is only a concern in scenarios where people use the same client application to connect sockets to multiple web services (e.g. cookies in a browser like Google Chrome can be used to connect a socket to Chase.com from both Chase.com and Horrible-Hacker-Site.com.)\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"Socket hijacking\">\n"
  },
  {
    "path": "docs/concepts/Security/StrictTransportSecurity.md",
    "content": "# HTTP Strict Transport Security\n\nStrict Transport Security (STS) is an opt-in security enhancement that forces usage of `HTTPS` instead of `HTTP` (in modern browsers, at least).\n\n### Enabling STS\n\nImplementing STS is actually very simple and [only takes a few lines of code](https://github.com/krakenjs/lusca/blob/master/lib/hsts.js).  Better yet, a few different open-source modules exist that bring support for this feature to Express and Sails.  To use one of these modules, install it from npm using the directions below, then open `config/http.js` in your project and [configure it as a custom middleware](https://sailsjs.com/documentation/concepts/Middleware).  The example below covers basic usage and configuration.  For more guidance and advanced usage details, be sure and follow the link to the docs.\n\n\n##### Using [lusca](https://github.com/krakenjs/lusca#luscahstsoptions)\n\n> `lusca` is open-source under the [Apache license](https://github.com/krakenjs/lusca/blob/master/LICENSE.txt)\n\n\n```sh\n# In your sails app\nnpm install lusca --save\n```\n\nThen in the `middleware` config object in `config/http.js`:\n\n```js\n  // ...\n  // maxAge ==> Number of seconds strict transport security will stay in effect.\n  strictTransportSecurity: require('lusca').hsts({ maxAge: 31536000 })\n  // ...\n```\n\n\n\n### Additional Resources\n+ [HTTP Strict Transport Security (OWasp)](https://www.owasp.org/index.php/HTTP_Strict_Transport_Security)\n\n\n\n<docmeta name=\"displayName\" value=\"Strict Transport Security\">\n"
  },
  {
    "path": "docs/concepts/Security/XSS.md",
    "content": "# XSS\n\nCross-site scripting (XSS) is a type of attack in which a malicious agent manages to inject client-side JavaScript into your website, so that it runs in the trusted environment of your users' browsers.\n\n\n### Protecting against XSS attacks\n\nThe cleanest way to prevent XSS attacks is to escape untrusted data _at the point of injection_.  That means at the point where it's actually being injected into the HTML.\n\n\n#### On the server\n\n##### When injecting data into a server-side view...\n\nUse `<%= %>` to HTML-encode data:\n\n```html\n<h3 is=\"welcome-msg\">Hello <%= me.username %>!</h3>\n\n<h4><%= owner.username %>'s projects:</h4>\n<ul><% _.each(projects, function (project) { %>\n  <li>\n    <a href=\"/<%= owner.username %>/<%= project.slug %>\"><%= project.friendlyName %></a>\n  </li>\n<% }); %></ul>\n```\n\n##### When exposing view locals to client-side JavaScript...\n\nUse the `exposeLocalsToBrowser` partial to safely expose some or all of your view locals to client-side JavaScript:\n\n```html\n<%- exposeLocalsToBrowser(); %>\n\n<script>\nconsole.log(window.SAILS_LOCALS);\n// {\n//   me: {\n//     username: 'eleven',\n//     memberSince: '1982-08-01T05:00:00.000Z'\n//   },\n//   owner: {\n//     username: 'joyce',\n//     memberSince: '1987-11-03T05:00:00.000Z'\n//   },\n//   projects: [\n//     {\n//       slug: 'my-neat-stuff-n-things',\n//       friendlyName: 'My neat stuff & things',\n//       description: 'Yet another project.'\n//     },\n//     {\n//       slug: 'kind-of-neat-stuff-but-not-that-great',\n//       friendlyName: 'Kind of neat stuff, but not that great...',\n//       description: 'I am so sick and tired of these project. <script>alert(\\'attack\\');</script>'\n//     }\n//   ],\n//   _csrf: 'oon95Uac-wKfWQKC5pHx1rP3HsiN9tjqGMyE'\n// }\n</script>\n```\n\n> Note that when you use this strategy, the strings in your view locals are no longer HTML unescaped after being exposed to client-side JavaScript.\n> That's because you'll want to escape them _again_ when you stick them in the DOM.  If you always escape at the point of injection, this stuff is a\n> lot easier to keep track of.  This way, you know you can safely escape _any_ string you inject into the DOM from your client-side JavaScript.\n> (More on that below.)\n\n\n#### On the client\n\nA lot of XSS prevention is about what you do in your client-side code.  Here are a few examples:\n\n##### When injecting data into a client-side JST template...\n\nUse `<%- %>` to HTML-encode data:\n\n```html\n<div data-template-id=\"welcome-box\">\n  <h3 is=\"welcome-msg\">Hello <%- me.username %>!</h3>\n</div>\n```\n\n\n##### When modifying the DOM with client-side JavaScript...\n\nUse something like `$(...).text()` to HTML-encode data:\n\n```js\nvar $welcomeMsg = $('#signup').find('[is=\"welcome-msg\"]');\nwelcomeMsg.text('Hello, '+window.SAILS_LOCALS.me.username+'!');\n\n// Avoid using `$(...).html()` to inject untrusted data.\n// Even if you know an XSS is not possible under particular circumstances,\n// accidental escaping issues can cause really, really annoying client-side bugs.\n```\n\n> As you've probably figured out, the example above assumes you are using jQuery, but the same concepts apply regardless of what front-end library you are using.\n\n\n### Additional Resources\n+ [XSS (OWasp)](https://www.owasp.org/index.php/XSS)\n+ [XSS Prevention Cheatsheet](https://www.owasp.org/index.php/XSS_Prevention_Cheat_Sheet)\n\n\n### Notes\n\n> + The examples above assume you are using the default view engine (EJS) and client-side JST/Lodash templates from the default asset pipeline.\n\n\n<docmeta name=\"displayName\" value=\"XSS\">\n"
  },
  {
    "path": "docs/concepts/Services/Services.md",
    "content": "# Services\n\n> _**Note**_: Although Services are still fully supported in Sails 1.0, it is recommended that you use [helpers](https://sailsjs.com/documentation/concepts/helpers) instead.\n\n**Services** are stateless libraries of functions that you can use from anywhere in your Sails app.  For example, you might have an `EmailService` which tidily wraps up one or more utility functions so you can use them in more than one place within your application.\n\nAnother benefit of using services in Sails is that they are *globalized*, which means that you don't have to use `require()` to access them, although you can if you prefer (you can also disable the automatic exposure of global variables in your app's configuration). By default, you can access a service and call its functions (e.g. `EmailService.sendHtmlEmail()` or `EmailService.sendPasswordRecoveryEmail()`) from anywhere: within controller actions, from inside other services, in custom model methods, or even from command-line scripts.\n\nHypothetically, one could create a service for:\n\n- Sending an email\n- Blasting tweets to celebrities\n- Retrieving data from a third party API\n\nBut [helpers](https://sailsjs.com/documentation/concepts/helpers) are a better bet.\n\n<docmeta name=\"displayName\" value=\"Services\">\n"
  },
  {
    "path": "docs/concepts/Sessions/sessions.md",
    "content": "# How sessions work in Sails (advanced)\n\nFor our purposes, **sessions** are defined to be a few components that together allow you to store information about a user agent between requests.\n\n> A **user agent** is the software (browser or native application) that represents you on a device (e.g. a browser tab on your computer, a smartphone application, or your refrigerator).  It is associated one-to-one with a cookie or access token.\n\nSessions can be very useful because the request/response cycle is **stateless**. The request/response cycle is considered stateless because neither the client nor the server inherently stores any information between different requests about a particular request.  Therefore, the lifecycle of a request/response ends when a response is made to the requesting user agent (e.g. `res.send()`).\n\nNote: we’re going to discuss sessions in the context of a browser user agent. While you can use sessions in Sails for whatever you like, it is generally a best practice to use them purely for storing the state of user agent authentication. Authentication is a process that allows a user agent to prove that they have a certain identity.  For example, in order to access some protected functionality, I might need to prove that my browser tab actually corresponds with a particular user record in a database.  If I provide you with a unique name and a password, you can look up the name and compare my password with a stored (hopefully [encrypted](http://node-machine.org/machinepack-passwords/encrypt-password)) password.  If there's a match, I'm authenticated. But how do you store that \"authenticated-ness\" between requests? That's where sessions come in.\n\n### What sessions are made of\nThere are three main components to the implementation of sessions in Sails:\n1. the **session store** where information is retained\n2. the middleware that manages the session\n3. a cookie that is sent along with every request and stores a session id (by default, `sails.sid`)\n\nThe **session store** can either be in memory (this is the default Sails session store) or in a database (Sails has built-in support for using Redis for this purpose).  Sails builds on top of Connect middleware to manage the session, which includes using a **cookie** to store a session id (`sid`) on the user agent.\n\n### A day in the life of a request, a response, and a session\nWhen a request is sent to Sails, the request header is parsed by the session middleware.\n\n##### Scenario 1: The request header has no cookie\n\nIf the header does not contain a cookie, a `sid` is created in the session and a default session dictionary is added to `req` (e.g. `req.session`).  At this point you can make changes to the session property (usually in a controller/action).  For example, let's look at the following login action:\n\n```javascript\nmodule.exports = {\n\n  login: function(req, res) {\n\n    // Authentication code here\n\n    // If successfully authenticated\n\n    req.session.userId = foundUser.id;   // returned from a database\n\n    return res.json(foundUser);\n\n  }\n}\n```\n\nHere we added a `userId` property to `req.session`.\n\n> **Note:** the property will not be stored in the session store, nor will it be available to other requests until the response is sent.\n\nOnce the response is sent, any new requests will have access to `req.session.userId`. Since we didn't have a cookie in the request header, a cookie will be established for us.\n\n##### Scenario 2: The request header has a cookie with a `Sails.sid`\n\nNow when the user agent makes the next request, the `Sails.sid` stored on the cookie is checked for authenticity. If it matches an existing `sid` in the session store, the contents of the session store are added as a property on the `req` dictionary (`req.session`).  We can access properties on `req.session` (e.g. `req.session.userId`) or set properties on it (e.g. `req.session.userId == someValue`).  The values in the session store might change, but the `Sails.sid` and `sid` generally do not.\n\n### When does the `Sails.sid` change?\nDuring development, the Sails session store is in memory.  Therefore, when you close the Sails server, the current session store disappears.  When Sails is restarted, although a user agent request contains a `Sails.sid` in the cookie, the `sid` is no longer in the session store.  Therefore, a new `sid` will be generated and replaced in the cookie.  The `Sails.sid` will also change if the user agent cookie expires or is removed.\n\n>The lifespan of a Sails cookie can be changed from its default setting (never expires) to a new setting by accessing the `cookie.maxAge` property in `projectName/config/session.js`.\n\n\n### Using Redis as the session store\n\nRedis is a key-value database package that can be used as a session store that is separate from the Sails instance.  This configuration for sessions has two benefits.  The first is that the session store will remain viable between Sails restarts.  The second is that if you have multiple Sails instances behind a load balancer, all of the instances can point to a single consolidated session store.\n\n#### Enabling Redis session store in development\n\nTo enable Redis as your session store in development, first make sure you have a local Redis instance running on your machine (`redis-server`). Then, lift your app with `sails lift --redis`.\n\nThis is just a shortcut for `sails lift --session.adapter=@sailshq/connect-redis --sockets.adapter=@sailshq/socket.io-redis`. These packages are included as dependencies of new Sails apps by default, but if you're working with an upgraded app you'll need to `npm install @sailshq/connect-redis` and `npm install @sailshq/socket.io-redis`.\n\n> Note that this built-in configuration uses your local Redis instance. For advanced session configuration options, see [Reference > Configuration > sails.config.session](https://sailsjs.com/documentation/reference/configuration/sails-config-session).\n\n#### Nerdy details of how the session cookie is created\nThe value for the cookie is created by first hashing the `sid` with a configurable *secret* which is just a long string.\n\n> You can change the session `secret` property in `projectName/config/session.js`.\n\nThe Sails `sid` (e.g. `Sails.sid`) then becomes a combination of the plain `sid` followed by a hash of the `sid` plus the `secret`.  To take this out of the world of abstraction, let's use an example.  Sails creates a `sid` of `234lj232hg234jluy32UUYUHH` and a `session secret` of `9238cca11a83d473e10981c49c4f`. These values are simply two strings that Sails combines and hashes to create a `signature` of `AuSosBAbL9t3Ev44EofZtIpiMuV7fB2oi`.  So the `Sails.sid` becomes `234lj232hg234jluy32UUYUHH.AuSosBAbL9t3Ev44EofZtIpiMuV7fB2oi` and is stored in the user agent cookie by sending a `set-cookie` property in the response header.\n\n**What does this prevent?** This prevents a user from guessing the `sid`. It also prevents a evildoer from spoofing a user into making an authetication request with a `sid` that the evildoer knows.  This could allow the evildoer to use the `sid` to do bad things while the user is authenticated via the session.\n\n### Disabling sessions\n\nEven if your Sails app is designed to be accessed by non-browser clients, such as toasters, you are strongly encouraged to use sessions for authentication.  While it can sometimes be complex to understand, the built-in session mechanism in Sails (session store + HTTP-only cookies) is a tried and true solution that is generally [less brittle, easier to use, and lower-risk than rolling out something yourself](http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/).\n\nThat said, sessions may not always be an option (for example, if you must [integrate with a different authentication scheme](https://github.com/sails101/jwt-login) like JWT).  In these cases, you can disable sessions on an app-wide or per-request basis.\n\n##### Disabling sessions for your entire app\n\nTo entirely turn off session support for your app, add the following to your `.sailsrc` file:\n\n```javascript\n\"hooks\": {\n  \"session\": false\n}\n```\n\nThis disables the core Sails session hook.  You can also accomplish this by setting the `sails_hooks__session` environment variable to `false`.\n\n##### Disabling sessions for certain requests\n\nTo turn off session support on a per-route (or per-request) basis, use the [`sails.config.session.isSessionDisabled` setting](https://sailsjs.com/documentation/reference/configuration/sails-config-session#?properties).  By default, Sails enables session support for all requests except those that [look like](https://sailsjs.com/documentation/reference/application/advanced-usage/sails-looks-like-asset-rx) they're pointed at static assets like images, stylesheets, etc.\n\n<docmeta name=\"displayName\" value=\"Sessions\">\n"
  },
  {
    "path": "docs/concepts/Testing/Testing.md",
    "content": "# Testing your code\n\nThis section of the documentation runs through how you might go about testing your Sails application.  There are countless test frameworks and assertion libraries for Sails and Node.js; pick one that fits your needs.\n\n> There is no official strategy for testing in the Sails framework, and this page is a collaborative, community-driven guide that has not been thoroughly vetted by Sails core team members.  If you run across something that seems confusing or incorrect, feel free to submit a pull request.\n\n### Preparation\n\nFor our example test suite, we'll use [mocha](http://mochajs.org/).\n\n```bash\nnpm install mocha --save-dev\n```\n\nBefore you start building your test cases, organize your `test/` directory structure.  Once again, when it comes to automated testing, there are several different organizational approaches you might choose.  For this example, we'll go about it as follows:\n\n```bash\n./myApp\n├── api/\n├── assets/\n├── ...\n├── test/\n│  ├── integration/\n│  │  ├── controllers/\n│  │  │  └── UserController.test.js\n│  │  ├── models/\n│  │  │  └── User.test.js\n│  │  └── helpers/\n|  |     └── ...\n│  ├── fixtures/\n|  │  └── ...\n│  ├── lifecycle.test.js\n│  └── mocha.opts\n├── ...\n└── views/\n\n```\n\n##### lifecycle.test.js\n\nThis file is useful when you want to execute some code before and after running your tests (e.g. lifting and lowering your Sails application). Since your models are converted to Waterline collections on lift, it is necessary to lift your Sails app before trying to test them (this applies controllers and other parts of your app, too, so be sure to call this file first).\n\n```javascript\nvar sails = require('sails');\n\n// Before running any tests...\nbefore(function(done) {\n\n  // Increase the Mocha timeout so that Sails has enough time to lift, even if you have a bunch of assets.\n  this.timeout(5000);\n\n  sails.lift({\n    // Your Sails app's configuration files will be loaded automatically,\n    // but you can also specify any other special overrides here for testing purposes.\n\n    // For example, we might want to skip the Grunt hook,\n    // and disable all logs except errors and warnings:\n    hooks: { grunt: false },\n    log: { level: 'warn' },\n\n  }, function(err) {\n    if (err) { return done(err); }\n\n    // here you can load fixtures, etc.\n    // (for example, you might want to create some records in the database)\n\n    return done();\n  });\n});\n\n// After all tests have finished...\nafter(function(done) {\n\n  // here you can clear fixtures, etc.\n  // (e.g. you might want to destroy the records you created above)\n\n  sails.lower(done);\n\n});\n```\n\n##### mocha.opts\n\nThis file is optional.  You can use it as an alternative to command-line options for specifying [custom Mocha configuration](https://mochajs.org/#mochaopts).\n\nOne notable customization option is timeout. The default timeout in Mocha is 2 seconds, which is sufficient for most test cases but may be too short depending on how often your tests are lifting and lowering Sails. To ensure that Sails lifts in time to finish your first test, you may need to increase the timeout value in mocha.opts:\n\n```bash\n--timeout 10000\n```\n\n> **Note**: If you are writing your tests in a transpiled language such as CoffeeScript (`.coffee` files instead of `.js` files), you'll need to take an extra step to configure Mocha accordingly.  For example, you might add these lines to your `mocha.opts`:\n>\n> ```bash\n> --require coffee-script/register\n> --compilers coffee:coffee-script/register\n> ```\n>\n> _If you prefer Typescript, the approach is basically the same, except you'll want to use `--require ts-node/register`.\n\n\n### Writing tests\n\nOnce you have prepared your directory, you can start writing your integration tests:\n\n```js\n// ./test/integration/models/User.test.js\n\nvar util = require('util');\n\ndescribe('User (model)', function() {\n\n  describe('#findBestStudents()', function() {\n    it('should return 5 users', function (done) {\n      User.findBestStudents()\n      .then(function(bestStudents) {\n\n        if (bestStudents.length !== 5) {\n          return done(new Error(\n            'Should return exactly 5 students -- the students '+\n            'from our test fixtures who are considered the \"best\".  '+\n            'But instead, got: '+util.inspect(bestStudents, {depth:null})+''\n          ));\n        }//-•\n\n        return done();\n\n      })\n      .catch(done);\n    });\n  });\n\n});\n```\n\n\n\n### Testing actions & controllers\n\nThe most fundamental tests for your backend code involve sending an HTTP request and checking the response.  There are numerous ways to go about this, whether it's a full-fledged testing tool, like Supertest, or a pure utility like [`request`](https://npmjs.com/package/request) or [`mp-http`](https://npmjs.com/package/machinepack-http), combined with [`assert`](https://nodejs.org/dist/latest/docs/api/assert.html).\n\n##### Using Supertest\n\nLet's take [Supertest](https://github.com/visionmedia/supertest) for a spin:\n\n```bash\nnpm install supertest --save-dev\n```\n\nThe idea behind Supertest is to provide a high-level tool that helps build a specific type of test&mdash;specifically, the type of test that send an HTTP request to your Sails app and checks the response.\n\n```js\n// test/integration/controllers/UserController.test.js\nvar supertest = require('supertest');\n\ndescribe('UserController.login', function() {\n\n  describe('#login()', function() {\n    it('should redirect to /my/page', function (done) {\n      supertest(sails.hooks.http.app)\n      .post('/users/login')\n      .send({ name: 'test', password: 'test' })\n      .expect(302)\n      .expect('location','/my/page', done);\n    });\n  });\n\n});\n```\n\n\n### Running tests\n\nIn order to run your test using Mocha, you'll have to use `mocha` in the command line then pass as arguments any test you want to run. Be sure to call lifecycle.test.js before the rest of your tests, like this: `mocha test/lifecycle.test.js test/integration/**/*.test.js`\n\n##### Using `npm test` to run your test\n\nYou can modify your package.json file to use `npm test` instead of Mocha, and thus avoid typing out the Mocha command described above. This is particularly useful when calling lifecycle.test.js. \n\nOn the scripts dictionary, add a `test` key and use the following as its value: `mocha test/lifecycle.test.js test/integration/**/*.test.js`. This looks like:\n\n```json\n  \"scripts\": {\n    \"start\": \"node app.js\",\n    \"debug\": \"node debug app.js\",\n    \"test\": \"node ./node_modules/mocha/bin/mocha test/lifecycle.test.js test/integration/**/*.test.js\"\n  }\n```\nThe `*` is a wildcard used to match any file inside the `integration/` folder that ends in `.test.js`. If it suits you, you can modify it to search for `*.spec.js` instead. In the same way, you can use wildcards for your folders by using two `*` instead of one.\n\n> As of Sails v1, Sails apps are generated with a `test` script already in their package.json file, but you'll still want to make some modifications to it for this example.  If you're upgrading an existing app, you may have to add a `test` key by hand.\n\n\n### Continuous integration\n\nIf you'd like to have a system automatically run your tests every time you push to your source code repository, you're in luck!  Many different continuous integration systems support Sails/Node.js, so you can have your pick.  Here are a few popular choices to get you started:\n\n+ [Circle CI](https://circleci.com/)\n+ [Travis CI](http://travis-ci.com)\n+ [Semaphore CI](https://semaphoreci.com/)\n+ [Appveyor](http://appveyor.com)  _(useful if you'll be deploying to a Windows server)_\n\n> All of the options above charge a monthly fee for proprietary apps but are free for open source.  Circle CI is free for proprietary apps as well, but throttled to two builds at a time. Semaphore is also free and and allows you 4x parallel CI/CD jobs.\n\n\n### Load testing\n\nA [number of commercial options](http://www.bing.com/search?q=load+testing) exist for load testing web applications.  You can also get a reasonable idea of how your app will perform using tools like [`ab`](http://httpd.apache.org/docs/2.4/programs/ab.html) or [JMeter](http://jmeter.apache.org/).  Just remember, the goal is to simulate real traffic.  For more help setting up your Sails app to be production-ready and scalable, see [Scalability](https://sailsjs.com/documentation/concepts/deployment/scaling).  For additional help or more specific questions, click [here](https://sailsjs.com/support).\n\n\n### Optimizing performance\n\nUsually, the scalability and overall performance of your app is more important than the performance and latency of any given individual request to a particular endpoint.  So rather than focusing on one piece of code in isolation, we recommend starting with [the basics](https://sailsjs.com/documentation/concepts/deployment/scaling); for most apps, that's good enough.  For some use cases (e.g. serving ads, or apps with very computationally-intensive functionality), though, individual request latency may be important from the get-go.\n\nFor testing the performance of particular chunks of code, or for benchmarking the latency of individual requests to particular endpoints, a great option is [benchmark.js](https://www.npmjs.com/package/benchmark).  Not only is it a robust library that supports high-resolution timers and returns statistically significant results, it also works great with Mocha out of the box.\n\n\n<docmeta name=\"displayName\" value=\"Testing\">\n"
  },
  {
    "path": "docs/concepts/Views/Layouts.md",
    "content": "# Layouts\n\nWhen building an app with many different pages, it can be helpful to extrapolate markup shared by several HTML files into a layout.  This [reduces the total amount of code](http://en.wikipedia.org/wiki/Don't_repeat_yourself) in your project and helps you avoid making the same changes in multiple files down the road.\n\nIn Sails and Express, layouts are implemented by the view engines themselves.  For instance, `jade` has its own layout system, with its own syntax.\n\nFor convenience, Sails bundles special support for layouts **when using the default view engine, EJS**. If you'd like to use layouts with a different view engine, check out [that view engine's documentation](https://sailsjs.com/documentation/concepts/views/view-engines) to find the appropriate syntax.\n\n\n### Creating layouts\n\nSails layouts are special `.ejs` files in your app's `views/` folder you can use to \"wrap\" or \"sandwich\" other views. Layouts usually contain the preamble (e.g. `<!DOCTYPE html><html><head>....</head><body>`) and conclusion (`</body></html>`).  The original view file is included using `<%- body %>`.  Layouts are never used without a view: that would be like serving someone a bread sandwich.\n\nLayout support for your app can be configured or disabled in [`config/views.js`](https://sailsjs.com/documentation/anatomy/config/views.js), and it can be overridden for a particular route or action by setting a special [local](https://sailsjs.com/documentation/concepts/views/locals) called `layout`. By default, Sails will compile all views using the layout located at `views/layouts/layout.ejs`.\n\nTo specify what layout a view uses, see the example below. There is more information in the docs at [routes](https://sailsjs.com/documentation/concepts/routes).\n\nThe example route below will use the view located at `./views/users/privacy.ejs` within the layout located at `./views/users.ejs`\n\n```javascript\n'get /privacy': {\n    view: 'users/privacy',\n    locals: {\n      layout: 'users'\n    }\n  },\n```\n\nThe example controller action below will use the view located at `./views/users/privacy.ejs` within the layout located at `./views/users.ejs`\n\n```javascript\nprivacy: function (req, res) {\n  res.view('users/privacy', {layout: 'users'})\n}\n```\n\n### Notes\n\n> #### Why do layouts only work for EJS?\n> A couple of years ago, built-in support for layouts/partials was deprecated in Express. Instead, developers were expected to rely on the view engines themselves to implement this feature. (See https://github.com/balderdashy/sails/issues/494 for more information.)\n>\n> Sails supports the legacy `layouts` feature for convenience, backwards compatibility with Express 2.x and Sails 0.8.x apps, and in particular, familiarity for new community members coming from other MVC frameworks. As a result, layouts have only been tested with the default view engine (ejs).\n>\n> If layouts aren&rsquo;t your thing, or (for now) if you&rsquo;re using a server-side view engine other than ejs, (e.g. Jade, handlebars, haml, dust) you&rsquo;ll want to set `layout:false` in [`sails.config.views`](https://sailsjs.com/documentation/reference/configuration/sails-config-views) and rely on your view engine&rsquo;s custom layout/partial support.\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"Layouts\">\n"
  },
  {
    "path": "docs/concepts/Views/Locals.md",
    "content": "# Locals\n\nThe variables accessible in a particular view are called `locals`.  Locals represent server-side data that is _accessible_ to your view&mdash;locals are not actually _included_ in the compiled HTML unless you explicitly reference them using special syntax provided by your view engine.\n\n```ejs\n<div>Logged in as <a><%= user.fullName %></a>.</div>\n```\n\n### Using locals in your views\n\nThe notation for accessing locals varies between view engines.  In EJS, you use special template markup (e.g. `<%= someValue %>`) to include locals in your views.\n\nThere are three kinds of template tags in EJS:\n+ `<%= someValue %>`\n  + HTML-escapes the `someValue` local, and then includes it as a string.\n+ `<%- someRawHTML %>`\n  + Includes the `someRawHTML` local verbatim, without escaping it.\n  + Be careful!  This tag can make you vulnerable to XSS attacks if you don't know what you're doing.\n+ `<% if (!loggedIn) { %>  <a>Logout</a>  <% } %>`\n  + Runs the JavaScript inside the `<% ... %>` when the view is compiled.\n  + Useful for conditionals (`if`/`else`), and looping over data (`for`/`each`).\n\n\nHere's an example of a view (`views/backOffice/profile.ejs`) using two locals, `user` and `corndogs`:\n\n```ejs\n<div>\n  <h1><%= user.fullName %>'s first view</h1>\n  <h2>My corndog collection:</h2>\n  <ul>\n    <% for (let corndog of corndogs) { %>\n    <li><%= _.capitalize(corndog.name) %></li>\n    <% } %>\n  </ul>\n</div>\n```\n\n> You might have noticed another local: `_`.  By default, Sails passes down a few locals to your views automatically, one of which is lodash (`_`).\n\nIf the data you wanted to pass down to this view was completely static, you wouldn't necessarily need a controller. Instead, you could hard-code the view and its locals in your `config/routes.js` file, like so:\n\n```javascript\n  // ...\n  'get /profile': {\n    view: 'backOffice/profile',\n    locals: {\n      user: {\n        fullName: 'Frank',\n        emailAddress: 'frank@enfurter.com'\n      },\n      corndogs: [\n        { name: 'beef corndog' },\n        { name: 'chicken corndog' },\n        { name: 'soy corndog' }\n      ]\n    }\n  },\n  // ...\n```\n\nMore likely, though, this data will be dynamic. In this scenario, we'd need to use a controller action to load the data from our models, then pass it to the view using the [res.view()](https://sailsjs.com/documentation/reference/response-res/res-view) method.\n\nAssuming we hooked up our route to one of our controller's actions (and our models were set up), we might send down our view like this:\n\n```javascript\n// in api/controllers/UserController.js...\n\n  profile: function (req, res) {\n    // ...\n    return res.view('backOffice/profile', {\n      user: theUser,\n      corndogs: theUser.corndogCollection\n    });\n  },\n  // ...\n```\n\n### Escaping untrusted data using `exposeLocalsToBrowser`\n\nIt is often desirable to &ldquo;bootstrap&rdquo; data onto a page so that it&rsquo;s available via Javascript as soon as the page loads, rather than having to fetch the data in a separate AJAX or socket request.  Sites like [Twitter and GitHub](https://blog.twitter.com/2012/improving-performance-on-twittercom) rely heavily on this approach in order to optimize page load times and provide an improved user experience.\n\nHistorically, this problem was commonly solved using hidden form fields or by hand-rolling code that injected server-side locals directly into a client-side script tag.  While effective, these techniques can present challenges when some of the data to be bootstrapped is from an _untrusted_ source that might contain HTML tags and Javascript code meant to compromise your app with an <a href=\"https://en.wikipedia.org/wiki/Cross-site_scripting\" target=\"_blank\">XSS attack</a>.  To prevent situations like this, Sails provides a built-in view partial called `exposeLocalsToBrowser` that you can use to securely inject data from your view locals for access from client-side JavaScript.\n\nTo use `exposeLocalsToBrowser`, simply call it from within your view using the _non-escaping syntax_ for your template language.  For example, using the default EJS view engine:\n\n```ejs\n<%- exposeLocalsToBrowser() %>\n```\n\nBy default, this exposes _all_ of your view locals as the `window.SAILS_LOCALS` global variable.  For example, if your action code contained:\n\n```javascript\nres.view('myView', {\n  someString: 'hello',\n  someNumber: 123,\n  someObject: { owl: 'hoot' },\n  someArray: [1, 'boot', true],\n  someBool: false\n  someXSS: '<script>alert(\"all your credit cards belong to me!!\");</script>'\n});\n```\n\nthen using `exposeLocalsToBrowser` as shown above would cause the locals to be safely bootstrapped in such a way that `window.SAILS_LOCALS.someArray` would contain the array `[1, 'boot', true]`, and  `window.SAILS_LOCALS.someXSS` would contain the _string_ `<script>alert(\"all your credit cards belong to me!!\");</script>` without causing that code to actually be executed on the page.\n\nThe `exposeLocalsToBrowser` function has a single `options` parameter that can be used to configure what data is outputted, and how.  The `options` parameter is a dictionary that can contain the following properties:\n\n|&nbsp;   |     Property        | Type                                         | Default| Details                            |\n|---|:--------------------|----------------------------------------------|:-----------------------------------|-----|\n| 1 | _keys_     | ((array?))                              | `undefined` | A &ldquo;whitelist&rdquo; of locals to expose.  If left undefined, _all_ locals will be exposed.  If specified, this should be an array of property names from the locals dictionary.  For example, given the `res.view()` statement shown above, setting `keys: ['someString', 'someBool']` would cause `windows.SAILS_LOCALS` to be set to `{someString: 'hello', someBool: false}`.\n| 2 | _namespace_ | ((string?)) | `SAILS_LOCALS` | The name of the global variable to which the bootstrapped data should be assigned.\n| 3| _dontUnescapeOnClient_ | ((boolean?)) | false | **Advanced. Not recommended for most apps.** If set to `true`, any string values that were escaped to avoid XSS attacks will _still be escaped_ when accessed from client-side JS, instead of being transformed back into the original value.  For example, given the `res.view()` statement from the example above, using `exposeLocalsToBrowser({dontUnescapeOnClient: true})` would cause `window.SAILS_LOCALS.someXSS` to be set to `&lt;script&gt;alert(&#39;hello!&#39;);`.\n\n\n<docmeta name=\"displayName\" value=\"Locals\">\n"
  },
  {
    "path": "docs/concepts/Views/Partials.md",
    "content": "# Partials\n\nWhen using the default view engine (`ejs`), Sails supports the use of _partials_ (i.e. \"view partials\").  Partials are basically just views that are designed to be used from within other views.\n\nThey are particularly useful for reusing the same markup between different views, layouts, and even other partials.\n\n```ejs\n<%- partial('./partials/navbar.ejs') %>\n```\n\nThis should render the partial located at `views/partials/navbar.ejs`, which might look something like this:\n\n```ejs\n<%\n/**\n * views/partials/navbar.ejs\n *\n * > Note: This EJS comment won't show up in the ejs served to the browser.\n * > So you can be as verbose as you like.  Just be careful not to inadvertently\n * > type a percent sign followed by a greater-than sign (it'll bust you out of\n * > the EJS block).\n *\n */%>\n<nav class=\"navbar\">\n  <a href=\"/\">Dashboard</a>\n  <a href=\"/inbox\">Inbox</a>\n</nav>\n```\n\n\nThe target path that you pass in as the first argument to `partial()` should be relative from the view, layout, or partial where you call it.  So if you are calling `partial()` from within a view file located at `views/pages/dashboard/user-profile.ejs`, and want to load `views/partials/widget.ejs` then you would use:\n\n```ejs\n<%- partial('../../partials/navbar.ejs') %>\n```\n\n### Partials and view locals\n\nPartials automatically inherit the view locals that are available wherever they are used.  For example, if you call `partial()` within a view where a variable named `currentUser` is available, then `currentUser` will also be available within the partial:\n\n```ejs\n<%\n/**\n * views/partials/navbar.ejs\n *\n * The navbar at the top of the page.\n *\n * @needs {Dictionary} currentUser\n *   @property {Boolean} isLoggedIn\n *   @property {String} username\n */%>\n<nav class=\"navbar\">\n  <div class=\"links\">\n    <a href=\"/\">Dashboard</a>\n    <a href=\"/inbox\">Inbox</a>\n  </div>\n  <span class=\"login-or-signup\"><%\n  // If the user accessing this page is logged in...\n  if (currentUser.isLoggedIn) {\n  %>\n    You are signed in as <a href=\"/<%= currentUser.username %>\"><%= currentUser.username %></a>.\n  <%\n  }\n  // Otherwise the user accessing this page must be a visitor:\n  else {\n  %>\n    <a href=\"/login\">Log in</a>\n  <%\n  }\n  %>\n  </span>\n</nav>\n```\n\n\n### Overriding locals in a partial\n\nAutomatic inheritance of view locals takes care of most use cases for partials, but sometimes you might want to pass in additional, dynamic data.  For example, imagine your app has duplicate copies of the following code in a few different views:\n\n```ejs\n<%\n// A list representing the currently-logged in user's inbox.\n%><ul class=\"message-list\"><%\n  // Display each message, with a button to delete it.\n  _.each(messages, function (message) {\n  %><li class=\"inbox-message\" data-id=\"<%= message.id %>\">\n    <a href=\"/messages/<%= message.id %>\"><%= message.subject %></a>\n    <button class=\"fa fa-trash\" is=\"delete-btn\"></button>\n  </li><% });\n %></ul>\n```\n\nTo refactor this, you might extrapolate the `<li>` into a partial to avoid duplicating code.  But if we do that, _we cannot rely on automatic inheritance_.  Partials only inherit locals that are available to the view, partial, or layout where they're called as a whole, but this `<li>` relies on a variable called `message`, which comes from the call to [`_.each()`](https://lodash.com/docs/3.10.1#forEach).\n\nFortunately, Sails also allows you to pass in an optional dictionary (i.e. a plain JavaScript object) of overrides as the second argument to `partial()`:\n\n```\n<%- partial(relPathToPartial, optionalOverrides) %>\n```\n\nThese overrides will be accessible in the partial as local variables, where they will take precedence over any automatically inherited locals with the same variable name.\n\nHere's our example from above, refactored to take advantage of this:\n\n```ejs\n<%\n// A list representing the currently-logged in user's inbox.\n%><ul class=\"message-list\"><%\n  // Display each message, with a button to delete it.\n  _.each(messages, function (message) { %>\n  <%- partial ('../partials/inbox-message.ejs', { message: message }) %>\n  <% });\n%></ul>\n```\n\n\nAnd finally, here is our new partial representing an individual inbox message:\n\n```ejs\n/**\n * views/partials/inbox-message.ejs\n *\n * An individual inbox message.\n *\n * @needs {Dictionary} message\n *   @property {Number} id\n *   @property {String} subject\n *\n */%>\n<li class=\"inbox-message\" data-id=\"<%= message.id %>\">\n  <a href=\"/messages/<%= message.id %>\"><%= message.subject %></a>\n  <button class=\"fa fa-trash\" is=\"delete-btn\" aria-label=\"Delete\"></button>\n</li>\n```\n\n\n\n\n\n\n\n### Notes\n\n> + Partials are rendered synchronously, so they will block Sails from serving more requests until they're done loading.  It's something to keep in mind while developing your app, especially if you anticipate a large number of connections.\n> + Built-in support for partials in Sails is only for the default view engine, `ejs`.  If you decide to customize your Sails install and use a view engine other than `ejs`, then please be aware that support for partials (sometimes known as \"blocks\", \"includes\", etc.) may or may not be included, and that the usage will vary.  Refer to the documentation for your view engine of choice for more information on its syntax and conventions.\n\n\n<docmeta name=\"displayName\" value=\"Partials\">\n\n"
  },
  {
    "path": "docs/concepts/Views/ViewEngines.md",
    "content": "# View engines\n\nThe default view engine in Sails is [EJS](https://github.com/mde/ejs).\n\n##### Swapping out the view engine\n\nTo use a different view engine, you should use npm to install it in your project, then in [`config/views.js`](https://sailsjs.com/documentation/anatomy/config/views.js) set `sails.config.views.extension` to your desired file extension and `sails.config.views.getRenderFn` to a function that returns your view engine's rendering function.\n\nIf your view engine is supported by [Consolidate](https://github.com/tj/consolidate.js/blob/master/Readme.md#api), you can use that in `getRenderFn` to easily access the rendering function. First, you'll need to use npm to install `consolidate` into your project, if it is not already present:\n\n```bash\nnpm install consolidate --save\n```\n\nAfter the install has completed and you have installed your view engine package, you can then set the view configuration.  For example, to use [Swig](https://github.com/paularmstrong/swig) templates you would `npm install swig --save` and then add the following into [`config/views.js`](https://sailsjs.com/documentation/anatomy/config/views.js):\n\n```javascript\nextension: 'swig',\ngetRenderFn: ()=>{\n  // Import `consolidate`.\n  var cons = require('consolidate');\n  // Return the rendering function for Swig.\n  return cons.swig;\n}\n```\n\nThe `getRenderFn` allows you to configure your view engine before plugging it into Sails:\n\n```javascript\nextension: 'swig',\ngetRenderFn: ()=>{\n  // Import `consolidate`.\n  var cons = require('consolidate');\n  // Import `swig`.\n  var swig = require('swig');\n  // Configure `swig`.\n  swig.setDefaults({tagControls: ['{?', '?}']});\n  // Set the module that Consolidate uses for Swig.\n  cons.requires.swig = swig;\n  // Return the rendering function for Swig.\n  return cons.swig;\n}\n```\n\n<docmeta name=\"displayName\" value=\"View engines\">\n"
  },
  {
    "path": "docs/concepts/Views/Views.md",
    "content": "# Views\n### Overview\n\nIn Sails, views are markup templates that are compiled _on the server_ into HTML pages.  In most cases, views are used as the response to an incoming HTTP request, e.g. to serve your home page.\n\n> Much more rarely, you can also compile a view directly into an HTML string for use in your backend code (see [`sails.renderView()`](https://github.com/balderdashy/sails/blob/master/docs/PAGE_NEEDED.md)).  For instance, you might use this approach to send HTML emails, or to build big XML strings for use with a legacy API.\n\n\n##### Creating a view\n\nBy default, Sails is configured to use EJS ([Embedded Javascript](http://ejs.co/)) as its view engine.  The syntax for EJS is highly conventional; if you've worked with php, asp, erb, gsp, jsp, etc., you'll immediately know what you're doing.\n\nIf you prefer to use a different view engine, there are a multitude of options.  Sails supports all of the view engines compatible with [Express](http://expressjs.com/en/guide/using-template-engines.html) via [Consolidate](https://github.com/visionmedia/consolidate.js).\n\nViews are defined in your app's [`views/`](https://sailsjs.com/documentation/anatomy/views) folder by default, but like all of the default paths in Sails, they are [configurable](https://sailsjs.com/documentation/reference/configuration/sails-config-views).  If you don't need to serve dynamic HTML pages at all (say, if you're building an API for a mobile app), you can remove the directory from your app.\n\n##### Compiling a view\n\nAnywhere you can access the `res` object (e.g. a controller action, custom response, or policy), you can use [`res.view`](https://sailsjs.com/documentation/reference/response-res/res-view) to compile one of your views, then send the resulting HTML down to the user.\n\nYou can also hook up a view directly to a route in your `routes.js` file.  Just indicate the relative path to the view from your app's `views/` directory.  For example:\n\n```javascript\n{\n  'get /': {\n    view: 'pages/homepage'\n  },\n  'get /signup': {\n    view: 'pages/signup/basic-info'\n  },\n  'get /signup/password': {\n    view: 'pages/signup/choose-password'\n  },\n  // and so on.\n}\n```\n\n##### What about single-page apps?\n\nIf you are building a web application for the browser, part (or all) of your navigation may take place on the client; i.e. instead of the browser fetching a new HTML page each time the user navigates around, the client-side code preloads some markup templates which are then rendered in the user's browser without needing to hit the server again directly.\n\nIn this case, you have a couple of options for bootstrapping the single-page app:\n\n+ Use a single view, e.g. `views/publicSite.ejs`.  The advantage of this option is that you can use the view engine in Sails to pass data from the server directly into the HTML that will be rendered on the client.  This is an easy way to get stuff like user data to your client-side JavaScript, without having to send AJAX/WebSocket requests from the client.\n+ Use a single HTML page in your assets folder , e.g. `assets/index.html`.  Although you can't pass server-side data directly to the client this way, the advantage of this approach is that it allows you to further decouple the client and server-side parts of your application.\n\nNote that anything in your assets folder can be moved to a static CDN (like Cloudfront or CloudFlare), allowing you to take advantage of that provider's geographically-distributed data centers to get your content closer to your users.\n\n\n\n<docmeta name=\"displayName\" value=\"Views\">\n<docmeta name=\"nextUpLink\" value=\"/documentation/concepts/assets\">\n<docmeta name=\"nextUpName\" value=\"Assets\">\n"
  },
  {
    "path": "docs/concepts/concepts.md",
    "content": "# Sails.js Documentation > Core Concepts\n\n> The contents of this file are overridden automatically during compilation (please do not edit manually!)\n\n<docmeta name=\"displayName\" value=\"Core Concepts: Table of Contents\">\n<docmeta name=\"isTableOfContents\" value=\"true\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Adapters/Adapters.md",
    "content": "# Adapters\n\n### What is an adapter?\n\nIn Sails and Waterline, database adapters (often simply called \"adapters\", for short) allow the models in your Sails app to communicate with your database(s). In other words, when your code in a controller action or helper calls a model method like `User.find()`, what happens next is determined by the [configured adapter](https://sailsjs.com/documentation/reference/configuration/sails-config-datastores).\n\nAn adapter is defined as a dictionary (aka JavaScript object, like `{}`) with methods like `find`, `create`, etc.  Based on which methods it implements, and the completeness with which they are implemented, adapters are said to implement one or more **interface layers**.  Each interface layer implies a contract to implement certain functionality.  This allows Sails and Waterline to guarantee conventional usage patterns across multiple models, developers, apps, and even companies, making app code more maintainable, efficient, and reliable.\n\n> In previous versions of Sails, adapters were sometimes used for other purposes, like communicating with certain kinds of RESTful web APIs, internal/proprietary web services, or even hardware.  But _truly_ RESTful APIs are very rare, and so, in most cases, writing a database adapter to integrate with a _non-database API_ can be limiting.  Luckily, there is now a [more straightforward way](https://sailsjs.com/documentation/concepts/helpers) to build these types of integrations.\n\n\n### What kind of things can I do in an adapter?\n\nAdapters are mainly focused on providing model-contextualized CRUD methods.  CRUD stands for create, read, update, and delete.  In Sails/Waterline, we call these methods `create()`, `find()`, `update()`, and `destroy()`.\n\nFor example, a `MySQLAdapter` implements a `create()` method which, internally, calls out to a MySQL database using the specified table name and connection information and runs an `INSERT ...` SQL query.\n\n\n### Next steps\n\nRead about [available adapters](https://sailsjs.com/documentation/concepts/extending-sails/adapters/available-adapters), or how to make your own [custom adapter](https://sailsjs.com/documentation/concepts/extending-sails/adapters/custom-adapters).\n\n\n<docmeta name=\"displayName\" value=\"Adapters\">\n<docmeta name=\"stabilityIndex\" value=\"3\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Adapters/adapterList.md",
    "content": "# Available database adapters\n\nThis page is meant to be an up-to-date, comprehensive list of all of the core adapters available for the Sails.js framework, and a reference of a few of the most robust community adapters out there.\n\nAll supported adapters can be configured in roughly the same way: by passing in a Sails/Waterline adapter (`adapter`), as well as a connection URL (`url`).  For more information on configuring datastores, see [sails.config.datastores](https://sailsjs.com/documentation/reference/configuration/sails-config-datastores).\n\n> Having trouble connecting?  Be sure to check your connection URL for typos.  If that doesn't work, review the documentation for your database provider, or [get help](https://sailsjs.com/support).\n\n### Officially-supported database adapters\n\nThe following core adapters are maintained, tested, and used by the Sails.js core team.\n\n> Want to help out with a core adapter?  Get started by reading [the Sails project contribution guide](https://sailsjs.com/contributing).\n\n|  Database technology    | Adapter                                                        | Connection URL structure                      | For production?     |\n|:------------------------|:---------------------------------------------------------------|:----------------------------------------------|:--------------------|\n|  MySQL                  | [require('sails-mysql')](http://npmjs.com/package/sails-mysql)            | `mysql://user:password@host:port/database`      | Yes\n|  PostgreSQL             | [require('sails-postgresql')](http://npmjs.com/package/sails-postgresql)  | `postgresql://user:password@host:port/database` | Yes\n|  MongoDB                | [require('sails-mongo')](http://npmjs.com/package/sails-mongo)            | `mongodb://user:password@host:port/database`      | Yes\n|  Local disk / memory           | _(built-in, see [sails-disk](http://npmjs.com/package/sails-disk))_          | _n/a_                                         | **No!**\n\n\n\n### sails-mysql\n\n[MySQL](http://en.wikipedia.org/wiki/MySQL) is the world's most popular relational database.\n\n\n[![NPM package info for sails-mysql](https://img.shields.io/npm/dm/sails-mysql.svg?style=plastic)](http://npmjs.com/package/sails-mysql) &nbsp; [![License info](https://img.shields.io/npm/l/sails-mysql.svg?style=plastic)](http://npmjs.com/package/sails-mysql)\n\n```bash\nnpm install sails-mysql --save\n```\n\n```javascript\nadapter: 'sails-mysql',\nurl: 'mysql://user:password@host:port/database',\n```\n\n> + The default port for MySQL is `3306`.\n> + If you plan on saving special characters&mdash;like emojis&mdash;in your data, you may need to set the [`charset`](https://dev.mysql.com/doc/refman/5.7/en/charset-charsets.html) configuration option for your datastore.  To allow emojis, use `charset: 'utf8mb4'`.  You may use the [`columnType` setting](https://sailsjs.com/documentation/concepts/models-and-orm/attributes#?columntype) in a model attribute to set the character set.\n> + For relational database servers like MySQL and PostgreSQL, you may have to create a \"database\" first using a free tool like [SequelPro](https://www.sequelpro.com/) or in the MySQL REPL on the command-line (if you're an experience SQL user). It's customary to make a database specifically for your app to use.\n> + The sails-mysql adapter is also 100% compatible with [Amazon Aurora](https://aws.amazon.com/rds/aurora/) databases.\n\n##### Handshake inactivity timeout errors\nIf you find yourself encountering a \"Handshake inactivity timeout\" error when your Sails app interacts with MySQL, you can increase the timeout using the `connectTimeout` option.  This is [usually only necessary](https://github.com/mysqljs/mysql/issues/1434) when queries are running side-by-side with computationally expensive operations (for example, compiling client-side typescript files or running webpack during development).\n\nFor example, you might extend the timeout to 20 seconds:\n\n```javascript\nadapter: 'sails-mysql',\nurl: 'mysql://user:password@host:port/database',\nconnectTimeout: 20000\n```\n\n\n### sails-postgresql\n\n[PostgreSQL](http://en.wikipedia.org/wiki/postgresql) is a modern relational database with powerful features.\n\n[![NPM package info for sails-postgresql](https://img.shields.io/npm/dm/sails-postgresql.svg?style=plastic)](http://npmjs.com/package/sails-postgresql) &nbsp; [![License info](https://img.shields.io/npm/l/sails-postgresql.svg?style=plastic)](http://npmjs.com/package/sails-postgresql)\n\n```bash\nnpm install sails-postgresql --save\n```\n\n```javascript\nadapter: 'sails-postgresql',\nurl: 'postgresql://user:password@host:port/database',\n```\n\n> + The default port for PostgreSQL is `5432`.\n> + In addition to `adapter` and `url`, you might also need to set `ssl: true`.  This depends on where your PostgreSQL database server is hosted.  For example, `ssl: true` is required when connecting to Heroku's hosted PostgreSQL service.\n> + Note that in `pg` version 8.0, the syntax was updated to `ssl: { rejectUnauthorized: false }`.\n> + Compatible with most versions of Postgres. See [this issue](https://github.com/balderdashy/sails/issues/6957) to learn more about compatability with Postgres >12\n\n### sails-mongo\n\n[MongoDB](http://en.wikipedia.org/wiki/MongoDB) is the leading NoSQL database.\n\n[![NPM package info for sails-mongo](https://img.shields.io/npm/dm/sails-mongo.svg?style=plastic)](http://npmjs.com/package/sails-mongo) &nbsp; [![License info](https://img.shields.io/npm/l/sails-mongo.svg?style=plastic)](http://npmjs.com/package/sails-mongo)\n\n```bash\nnpm install sails-mongo --save\n```\n\n```javascript\nadapter: 'sails-mongo',\nurl: 'mongodb://user:password@host:port/database',\n```\n\n> + The default port for MongoDB is `27017`.\n> + If your Mongo deployment keeps track of its internal credentials in a separate database, then you may need to name that database by tacking on [`?authSource=theotherdb`](https://stackoverflow.com/a/40608735/486547) to the end of the connection URL.\n> + Other [Mongo configuration settings](https://github.com/balderdashy/sails-mongo/blob/master/lib/private/constants/config-whitelist.constant.js) provided via querystring in the connection URL are passed through to the underlying Mongo driver.\n\n\n\n### sails-disk\n\nWrite to your computer's hard disk, or a mounted network drive.  Not suitable for at-scale production deployments, but great for a small project, and essential for developing in environments where you may not always have a database set up.  This adapter is bundled with Sails and works out of the box with zero configuration.\n\nYou can also operate `sails-disk` in _memory-only mode_.  See the settings table below for details.\n\n[![NPM package info for sails-disk](https://img.shields.io/npm/dm/sails-disk.svg?style=plastic)](http://npmjs.com/package/sails-disk) &nbsp; [![License info](https://img.shields.io/npm/l/sails-disk.svg?style=plastic)](http://npmjs.com/package/sails-disk)\n\n_Available out of the box in every Sails app._\n\n_Configured as the default database, by default._\n\n##### Optional datastore settings for `sails-disk`\n\n| Setting | Description | Type  | Default |\n|:--------|:------------|:------|:--------|\n| `dir`   | The directory to place database files in.  The adapter creates one file per model. | ((string)) | `.tmp/localDiskDb` |\n| `inMemoryOnly` | If `true`, no database files will be written to disk.  Instead, all data will be stored in memory (and will be lost when the app stops running). | ((boolean)) | `false` |\n\n> + You can configure the default `sails-disk` adapter by adding settings to the `default` datastore in `config/datastores.js`.\n\n\n### Community-supported database adapters\n\nIs your database not supported by one of the core adapters?  Good news!  There are many different community database adapters for Sails.js and Waterline [available on NPM](https://www.npmjs.com/search?q=sails+adapter).\n\nHere are a few highlights:\n\n\n| Database technology             | Adapter                | Maintainer | Interfaces implemented | Stable release |\n|:--------------------------------|:-----------------------|:-----------|:-----------------------|-----------------------|\n| **Redis**                       | [sails-redis](https://npmjs.com/package/sails-redis) | [Ryan Clough / Solnet Solutions](https://github.com/Ryanc1256) | Semantic, Queryable                                               | [![NPM package info for sails-redis](https://img.shields.io/npm/dm/sails-redis.svg?style=plastic)](http://npmjs.com/package/sails-redis) |\n| **MS SQL Server**               | [sails-MSSQLserver](https://github.com/misterGF/sails-mssqlserver) | [misterGF](https://github.com/misterGF) | Semantic, Queryable                  | [![NPM package info for sails-sqlserver](https://img.shields.io/npm/dm/sails-sqlserver.svg?style=plastic)](http://npmjs.com/package/sails-sqlserver)\n| **OrientDB**                    | [sails-orientDB](https://github.com/appscot/sails-orientdb) | [appscot](https://github.com/appscot) | Semantic, Queryable, Associations, Migratable | [![NPM package info for sails-orientdb](https://img.shields.io/npm/dm/sails-orientdb.svg?style=plastic)](http://npmjs.com/package/sails-orientdb)\n| **Oracle**                      | [sails-oracleDB](https://npmjs.com/package/sails-oracledb) | [atiertant](https://github.com/atiertant) | Semantic, Queryable | [![NPM package info for sails-oracledb](https://img.shields.io/npm/dm/sails-oracledb.svg?style=plastic)](http://npmjs.com/package/sails-oracledb) |\n| **Oracle (AnyPresence)**        | [waterline-oracle-adapter](https://github.com/AnyPresence/waterline-oracle-adapter) | [AnyPresence](https://github.com/AnyPresence) | Semantic, Queryable     | [![Release info for AnyPresence/waterline-oracle-adapter](https://img.shields.io/github/tag/AnyPresence/waterline-oracle-adapter.svg?style=plastic)](https://github.com/AnyPresence/waterline-oracle-adapter)\n| **Oracle (stored procedures)**  | [sails-oracle-SP](https://npmjs.com/sails-oracle-sp) | [Buto](http://github.com/buto) and [nethoncho](http://github.com/nethoncho) | Semantic, Queryable     | [![NPM package info for sails-oracle-sp](https://img.shields.io/npm/dm/sails-oracle-sp.svg?style=plastic)](http://npmjs.com/package/sails-oracle-sp)\n| **SAP HANA DB**                 | [sails-HANA](https://npmjs.com/sails-hana) | [Enrico Battistella](https://github.com/battistaar) | Semantic, Queryable     | [![NPM package info for sails-hana](https://img.shields.io/npm/dm/sails-hana.svg?style=plastic)](http://npmjs.com/package/sails-hana)\n| **SAP HANA (AnyPresence)**      | [waterline-SAP-HANA-adapter](https://github.com/AnyPresence/waterline-sap-hana-adapter) | [AnyPresence](https://github.com/AnyPresence) | Semantic, Queryable     | [![Release info for AnyPresence/waterline-sap-hana-adapter](https://img.shields.io/github/tag/AnyPresence/waterline-sap-hana-adapter.svg?style=plastic)](https://github.com/AnyPresence/waterline-sap-hana-adapter)\n| **IBM DB2**                     | [sails-DB2](https://npmjs.com/sails-db2) | [ibuildings Italia](https://github.com/IbuildingsItaly) &amp; [Vincenzo Ferrari](https://github.com/wilk) | Semantic, Queryable     | [![NPM package info for sails-db2](https://img.shields.io/npm/dm/sails-db2.svg?style=plastic)](http://npmjs.com/package/sails-db2)\n| **ServiceNow SOAP**             | [waterline-ServiceNow-SOAP](https://npmjs.com/waterline-servicenow-soap) | [Sungard Availability Services](http://www.sungardas.com/) | Semantic, Queryable     | [![NPM package info for waterline-servicenow-soap](https://img.shields.io/npm/dm/waterline-servicenow-soap.svg?style=plastic)](http://npmjs.com/package/waterline-servicenow-soap)\n| **Cassandra**                   | [sails-cassandra](https://github.com/dtoubelis/sails-cassandra) | [dtoubelis](https://github.com/dtoubelis) | Semantic, Migratable, Iterable | [![NPM package info for sails-cassandra](https://img.shields.io/npm/dm/sails-cassandra.svg?style=plastic)](http://npmjs.com/package/sails-cassandra)\n| **Solr**                        | [sails-solr](https://github.com/sajov/sails-solr) | [sajov](https://github.com/sajov) | Semantic, Migratable, Queryable | [![NPM package info for sails-solr](https://img.shields.io/npm/dm/sails-solr.svg?style=plastic)](http://npmjs.com/package/sails-solr)\n| **FileMaker Database**          | [sails-FileMaker](https://github.com/geistinteractive/sails-filemaker) | [Geist Interactive](https://www.geistinteractive.com/) | Semantic | [![NPM package info for sails-filemaker](https://img.shields.io/npm/dm/sails-filemaker.svg?style=plastic)](http://npmjs.com/package/sails-filemaker)\n| **Apache Derby**                | [sails-derby](https://github.com/dash-/node-sails-derby) | [dash-](https://github.com/dash-) | Semantic, Queryable, Associations, SQL | [![NPM package info for sails-derby](https://img.shields.io/npm/dm/sails-derby.svg?style=plastic)](http://npmjs.com/package/sails-derby)\n| **REST API (Generic)**          | [sails-REST](https://github.com/zohararad/sails-rest) | [zohararad](https://github.com/zohararad) | Semantic                                        | [![NPM package info for sails-rest](https://img.shields.io/npm/dm/sails-rest.svg?style=plastic)](http://npmjs.com/package/sails-rest)\n\n\n\n##### Add your custom adapter to this list\n\nIf you see out of date information on this page, or if you want to add an adapter you made, please submit a pull request to this file updating the table of community adapters above.\n\nNote that, to be listed on this page, an adapter must:\n\n1. Be free and open source (_libre_ and _gratis_), preferably under the MIT license.\n2. Pass all of the Waterline adapter tests for the interface layers declared in its package.json file.\n3. Support configuration via a connection URL, as `url` (if applicable).\n\n\nIf you find that any of these conventions are not true for any of the community adapters above (i.e. for latest stable release published on NPM, not for the code on GitHub), then please reach out to the maintainer of the adapter.  If you can't reach them or need further assistance, then please [get in touch](https://sailsjs.com/contact) with a member of the Sails core team.\n\n\n\n<docmeta name=\"displayName\" value=\"Available adapters\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Adapters/customAdapters.md",
    "content": "# Custom adapters\n\nSails makes it fairly easy to write your own database adapter.  Custom adapters can be built directly in your app (`api/adapters/`) or published as NPM packages.  Check out [Intro to Custom Adapters](https://github.com/balderdashy/sails/blob/master/docs/contributing/intro-to-custom-adapters.md), the [Adapter Interface Reference](https://github.com/balderdashy/sails/blob/master/docs/contributing/adapter-specification.md), and [sails-adapter-boilerplate](https://github.com/balderdashy/sails-adapter-boilerplate) for more information about creating your own adapter.\n\n\n### Where does my adapter go?\n\nThere are two different places you can build an adapter:\n\n##### In your app's `api/adapters/` folder\n\nIf an adapter is only going to be used in one app (e.g. a short-term fork of an existing adapter) you can put it in `api/adapters/`.  This is what you get out of the box when you run `sails generate adapter`.  In this case, the name of the adapter is determined by the name of the folder inside `api/adapters/` (by convention, the entry point for your adapter should be `index.js`).\n\n##### In a separate repo\n\nGo with this option if you plan to share your adapter between multiple Sails apps, whether that's within your organization or as an open-source package for other members of the Sails/Node.js community at large.  To use an externalized adapter like this, you'll need to do `npm install your-adapter-package-name` or `npm link your-adapter-package-name`.\n\n> Before you start on an open-source adapter, we recommend you search GitHub for `sails-databasename` and `waterline-databasename` to check if a project already exists. If it does, it's generally a good idea to approach the author of an existing adapter and offer to contribute instead of starting a new project. Most developers will welcome your help, and the combined efforts will likely result in a better quality adapter. If one doesn't exist, we recommend you create a new project and name it following the convention: `sails-databasename`.\n\n\n### What goes in a custom adapter?\n\nIn Sails, database adapters expose **interfaces**, which imply a contract to implement certain functionality.  This allows us to guarantee conventional usage patterns across multiple models, developers, apps, and even companies, making app code more maintainable, efficient, and reliable.  Adapters are primarily useful for integrating with databases, but they can also be used to support any open API or internal/proprietary web service that is _purely_ RESTful.\n\n> Not everything fits perfectly into a RESTful/CRUD mold.  Sometimes the service you're integrating with has an RPC-style interface with one-off methods.  For example, consider an API request to send an email, or to read a remote sensor on a piece of connected hardware.  For that, you'll want to write or extend a machinepack.  [Learn more about machinepacks here](http://node-machine.org).\n\n\n### What kind of things can I do in an adapter?\n\nAdapters are mainly focused on providing model-contextualized CRUD methods.  CRUD stands for create, read, update, and delete.  In Sails/Waterline, we call these methods `create()`, `find()`, `update()`, and `destroy()`.\n\nFor example, a `MySQLAdapter` implements a `create()` method which, internally, calls out to a MySQL database using the specified table name and connection information and runs an `INSERT ...` SQL query.\n\nIn practice, your adapter can really do anything it likes&mdash;any method you write will be exposed on the raw datastore objects and any models which use them.\n\n### Building a custom adapter\n\nCheck out the [Sails docs](https://sailsjs.com/documentation), or see [`config/datastores.js`](https://sailsjs.com/anatomy/config/datastores.js) in a new Sails project for information on setting up this adapter in a Sails app.\n\n\n#### Running the tests\n\nConfigure the interfaces you plan to support (and the targeted version of Sails) in the adapter's `package.json` file:\n\n```javascript\n{\n  //...\n  \"sails\": {\n  \t\"adapter\": {\n\t    \"sailsVersion\": \"^1.0.0\",\n\t    \"implements\": [\n\t      \"semantic\",\n\t      \"queryable\"\n\t    ]\n\t  }\n  }\n}\n```\n\nIn your adapter's directory, run:\n\n```sh\n$ npm test\n```\n\n\n#### Publish your adapter\n\n> You're welcome to write proprietary adapters and use them any way you wish&mdash;\n> these instructions are for releasing an open-source adapter.\n\n1. Create a [new public repo](https://github.com/new) and add it as a remote (`git remote add origin git@github.com:yourusername/sails-youradaptername.git).\n2. Make sure you attribute yourself as the author and set the license in the package.json to \"MIT\".\n3. Run the tests one last time.\n4. Do a [pull request to the docs](https://github.com/balderdashy/sails/edit/master/docs/concepts/extending-sails/Adapters/adapterList.md), adding your adapter's repo.\n5. We'll update the documentation with information about your new adapter.\n6. Let the people of the world adore you with lavish praise.\n7. Run `npm version patch`.\n8. Run `git push && git push --tags`.\n9. Run `npm publish`.\n\n\n\n### Why would I need a custom adapter?\n\nWhen building a Sails app, the sending or receiving of any asynchronous communication with another piece of hardware can _technically_ be normalized into an adapter (viz. API integrations).\n\n> **From Wikipedia:**\n> *http://en.wikipedia.org/wiki/Create,_read,_update_and_delete*\n\n> Although a relational database provides a common persistence layer in software applications, numerous other persistence layers exist. CRUD functionality can be implemented with an object database, an XML database, flat text files, custom file formats, tape, or card, for example.\n\nIn other words, Waterline is not _necessarily_ just an ORM for your database.  It is a purpose-agnostic open standard and toolset for integrating with all kinds of RESTful services, datasources, and devices&mdash;whether it's LDAP, Neo4J, or [a lamp](https://www.youtube.com/watch?v=OmcQZD_LIAE).\n\n> **But remember:** only use Waterline adapters for communicating with databases and APIs that support a \"create\", \"read\", \"update\", and \"destroy\" interface.  Not everything fits into that mold, and there are [better, more generic ways](http://node-machine.org) to address those other use cases.\n\n\n### Why should I build a custom adapter?\n\nTo recap, writing your API integrations as adapters is **easier**, takes **less time**, and **absorbs a considerable amount of risk**, since you get the advantage of a **standardized set of conventions**, a **documented API**, and a **built-in community** of other developers who have gone through the same process.  Best of all, you (and your team) can **reuse the adapter** in other projects, **speeding up development** and **saving time and money**.\n\nFinally, if you choose to release your adapter as open source, you provide a tremendous boon to our little framework and our budding Sails.js ecosystem.  Even if it's not via Sails, I encourage you to give back to the OSS community, even if you've never forked a repo before&mdash;don't be intimidated, it's not that bad!\n\nThe more high-quality adapters the Sails community collectively releases as open source, the less repetitive work we all have to do when we integrate with various databases and services.  Our vision is to make building server-side apps more fun and less repetitive for everyone, and that happens one community adapter (or machinepack/driver/generator/view engine/etc.) at a time.\n\n\n### What is an adapter interface?\n\nThe functionality of database adapters is as varied as the services they connect.  That said, there is a standard library of methods, and a support matrix you should be aware of.  Adapters may implement some, all, or none of the interfaces below, but rest assured that **if an adapter implements one method in an interface, it should implement *all* of them**.  This is not always the case due to limitations and/or incomplete implementations, but at the very least, a descriptive error message should be used to keep developers informed of what's supported and what's not.\n\n> For more information, check out the Sails docs, and specifically the [adapter interface reference](https://github.com/balderdashy/sails/blob/master/docs/contributing/adapter-specification.md).\n\n### Are there examples I can look at?\n\nIf you're looking for some inspiration, a good place to start is with the core adapters.  Take a look at **[MySQL](https://github.com/balderdashy/sails-mysql)**, **[PostgreSQL](https://github.com/balderdashy/sails-postgresql)**, **[MongoDB](https://github.com/balderdashy/sails-mongo)**, **[Redis](https://github.com/balderdashy/sails-redis)**, or local [disk](https://github.com/balderdashy/sails-disk).\n\n\n### Where do I get help?\n\nAn active community of Sails and Waterline users exists on GitHub, Stack Overflow, Google groups, IRC, Gitter, and more.  See the [Support page](https://sailsjs.com/support) for a list of recommendations.\n\n> If you have an unanswered question that isn't covered here, and that you feel would add value for the community, please feel free to send a PR adding it to this section of the docs.\n\n\n\n\n<docmeta name=\"displayName\" value=\"Custom adapters\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Custom Responses/AddingCustomResponse.md",
    "content": "# Adding a custom response\n\nTo add your own custom response method, simply add a file to `/api/responses` with the same name as the method you would like to create.  The file should export a function, which can take any parameters you like.\n\n```javascript\n/**\n * api/responses/myResponse.js\n *\n * This will be available in controllers as res.myResponse('foo');\n */\n\nmodule.exports = function(message) {\n\n  var req = this.req;\n  var res = this.res;\n\n  var viewFilePath = 'mySpecialView';\n  var statusCode = 200;\n\n  var result = {\n    status: statusCode\n  };\n\n  // Optional message\n  if (message) {\n    result.message = message;\n  }\n\n  // If the user-agent wants a JSON response, send json\n  if (req.wantsJSON) {\n    return res.json(result, result.status);\n  }\n\n  // Set status code and view locals\n  res.status(result.status);\n  for (var key in result) {\n    res.locals[key] = result[key];\n  }\n  // And render view\n  res.render(viewFilePath, result, function(err) {\n    // If the view doesn't exist, or an error occured, send json\n    if (err) {\n      return res.json(result, result.status);\n    }\n\n    // Otherwise, serve the `views/mySpecialView.*` page\n    res.render(viewFilePath);\n  });\n}\n```\n<docmeta name=\"displayName\" value=\"Adding a custom response\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Custom Responses/Custom Responses.md",
    "content": "# Custom responses\n\n### Overview\n\nSails apps come bundled with several pre-configured _responses_ that can be called from [action code](https://sailsjs.com/documentation/concepts/actions-and-controllers).  These default responses can handle situations like &ldquo;resource not found&rdquo; (the [`notFound` response](https://sailsjs.com/documentation/reference/response-res/res-not-found)) and &ldquo;internal server error&rdquo; (the [`serverError` response](https://sailsjs.com/documentation/reference/response-res/res-server-error)).  If your app needs to modify the way that the default responses work, or create new responses altogether, you can do so by adding files to the `api/responses` folder.\n\n> Note: `api/responses` is not generated by default in new Sails apps, so you&rsquo;ll have to add it yourself if you want to add / customize responses.\n\n### Using responses\n\nAs a quick example, consider the following action:\n\n```javascript\ngetProfile: function(req, res) {\n\n  // Look up the currently logged-in user's record from the database.\n  User.findOne({ id: req.session.userId }).exec(function(err, user) {\n    if (err) {\n      res.status(500);\n      return res.view('500', {data: err});\n    }\n    \n    return res.json(user);\n  });\n}\n```\n\nThis code handles a database error by sending a 500 error status and sending the error data to a view to be displayed.  However, this code has several drawbacks, primarily:\n\n*  The response isn't *content-negotiated*: if the client is expecting a JSON response, they're out of luck\n*  The response *reveals too much* about the error: in production, it'd be best to just log the error to the terminal\n*  It isn't *normalized*: even if we dealt with the other bullet points above, the code is specific to this action, and we'd have to work hard to keep the exact same format for error handling everywhere\n*  It isn't *abstracted*: if we wanted to use a similar approach elsewhere, we'd have to copy / paste the code\n\n\nNow, consider this replacement:\n\n```javascript\ngetProfile: function(req, res) {\n\n  // Look up the currently logged-in user's record from the database.\n  User.findOne({ id: req.session.userId }).exec(function(err, user) {\n    if (err) { return res.serverError(err); }\n    return res.json(user);\n  });\n}\n```\n\n\nThis approach has many advantages:\n\n - More concise\n - Error payloads are normalized\n - Production vs. development logging is taken into account\n - Error codes are consistent\n - Content negotiation (JSON vs HTML) is taken care of\n - API tweaks can be done in one quick edit to the appropriate generic response file\n\n\n### Response methods and files\n\nAny `.js` file saved in the `api/responses/` folder can be executed by calling `res.thatFileName()`.  For example, `api/responses/insufficientFunds.js` can be executed with a call to `res.insufficientFunds()`.\n\n##### Accessing `req`, `res`, and `sails`\n\nThe request and response objects are available inside of a custom response as `this.req` and `this.res`.  This allows the actual response function to take arbitrary parameters.  For example: \n\n```javascript\nreturn res.insufficientFunds(err, { happenedDuring: 'signup' });\n```\n\nAnd the implementation of the custom response might look something like this:\n\n```javascript\nmodule.exports = function insufficientFunds(err, extraInfo){\n  \n  var req = this.req;\n  var res = this.res;\n  var sails = req._sails;\n  \n  var newError = new Error('Insufficient funds');\n  newError.raw = err;\n  _.extend(newError, extraInfo);\n  \n  sails.log.verbose('Sent \"Insufficient funds\" response.');\n\n  return res.badRequest(newError);\n\n}\n```\n\n\n\n### Built-in responses\n\nAll Sails apps have several pre-configured responses like [`res.serverError()`](https://sailsjs.com/documentation/reference/response-res/res-server-error) and [`res.notFound()`](https://sailsjs.com/documentation/reference/response-res/res-not-found) that can be used even if they don&rsquo;t have corresponding files in `api/responses/`.\n\nAny of the default responses may be overridden by adding a file with the same name to `api/responses/` in your app (e.g. `api/responses/serverError.js`).\n\n> You can use the [Sails command-line tool](https://sailsjs.com/documentation/reference/command-line-interface/sails-generate) as a shortcut for doing this.\n>\n> For example:\n>\n>```bash\n>sails generate response serverError\n>```\n>\n\n\n\n<docmeta name=\"displayName\" value=\"Custom responses\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Generators/Generators.md",
    "content": "# Generators\n\nA big part of Sails, like any framework, is automating repetitive tasks.  **Generators** are no exception: they're what power the Sails command-line interface any time it generates new files for your Sails projects.  In fact, you or someone on your team probably used a _generator_ to create your latest Sails project.\n\nWhen you type\n\n```sh\nsails new my-project\n```\n\nsails uses its built-in \"new\" generator to prompt you for your app template of choice, then spits out the initial folder structure for a Sails app:\n\n```javascript\nmy-project\n  ├── api/\n  │   ├─ controllers/\n  │   ├─ helpers/\n  │   └─ models/\n  ├── assets/\n  │   └─ …\n  ├── config/\n  │   └─ …\n  ├── views/\n  │   └─ …\n  ├── .gitignore\n  …\n  ├── package.json\n  └── README.md\n```\n\n\nThis conventional folder structure is one of the big advantages of using a framework.  But it's usually also one of the trade-offs (what if your team or organization has made firm commitments to a different set of conventions?).\n\nFortunately since Sails v0.11, generators are extensible and easy to check in to a project repository or publish on NPM for re-use.\n\nSails' generators allow you to completely customize what happens when you run `sails new` and `sails generate` from the command-line.  By augmenting new apps and newly-generated modules, custom generators can be used to do all sorts of cool things:\n- to standardize conventions and boilerplate logic for all new apps across your organization\n- to swap out rules in the default .eslintrc file\n- to customize how the asset pipeline works in new projects\n- to use a different asset pipeline altogether (like [Gulp](http://gulpjs.com/) or [webpack](https://webpack.github.io/))\n- to use a [different default view engine](https://sailsjs.com/documentation/concepts/views/view-engines)\n- to automate custom deployments (e.g. white label apps with one server per customer)\n- to include a different set of dependencies in the package.json file\n- to generate files in a transpiled language like TypeScript or CoffeeScript\n- to start off with all documentation and comments in a language other than English\n- to include ASCII pictures of cats at the top of every code file (or license headers, whatever)\n- to standardize around a particular version of a front-end dependency (for example, `sails generate jquery`)\n- to include a particular front-end framework in your new Sails apps\n- to make it easy to include new Vue / React components or Angular modules from your favorite templates (for example, `sails generate component` or `sails generate ng-module`)\n\n\n> If you are interested in making custom generators, the best place to start is by checking out the [introduction to custom generators](https://sailsjs.com/documentation/concepts/extending-sails/generators/custom-generators).  You also might check out [open-source generators from the community](https://sailsjs.com/documentation/concepts/extending-sails/generators/available-generators), in case something already out there will save you some time.\n\n\n<docmeta name=\"displayName\" value=\"Generators\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Generators/customGenerators.md",
    "content": "# Custom generators\n\n<!-- TODO: update this tutorial to reflect how generator names are spat out.  Also update it to explain that you can just delete the package.json file in the newly generated generator if you're not planning on publishing it to npm.  Also bring back in the information that was deleted because the examples were quite out of date (the other content is still good though- see commit history of this file on GitHub  -->\n\n### Overview\n\nCustom [generators](https://sailsjs.com/documentation/concepts/extending-sails/generators) are a type of plugin for the Sails command line.  Through templates, they control which files get generated in your Sails projects when you run `sails new` or `sails generate`, and also what those files look like.\n\n### Creating a generator\n\nTo make this easier to play with, let's first make a Sails project.  If you haven't already created one, go to your terminal and type:\n\n```sh\nsails new my-project\n```\n\nThen `cd` into `my-project` and ask Sails to spit out the template for a new generator:\n\n```sh\nsails generate generator awesome\n```\n\n### Configuring a generator\n\nTo enable the generator you need to tell Sails about it via your test project's [`.sailsrc` file](https://sailsjs.com/documentation/concepts/configuration/using-sailsrc-files).\n\nIf we were using an existing generator, we could just install it from NPM, then specify the name of the package in `.sailsrc`.  But since we're developing this generator locally, we'll just connect it to the folder directly:\n\n```javascript\n{\n  \"generators\": {\n    \"modules\": {\n    \t\"awesome\": \"./my-project/awesome\"\n    }\n  }\n}\n```\n\n> **Note:** For now, we'll stick with \"awesome\", but you can mount the generator under any name you want.  Whatever you choose for the name of the key in the `.sailsrc` file will be the name you'll use to run this generator from the terminal (e.g. `sails generate awesome`).\n\n\n### Running a custom generator\n\nTo run your generator, just tack its name on to `sails generate`, followed by any desired arguments or command-line options.  For example:\n\n```js\nsails generate awesome\n```\n\n\n### Publishing to NPM\n\nIf your generator is useful across different projects, you might consider publishing it as an NPM package (note that this doesn't mean that your generator must be open-source: NPM also supports [private packages](https://docs.npmjs.com/private-modules/intro).\n\nFirst, pop open the `package.json` file and verify the package name (e.g. \"@my-npm-name/sails-generate-awesome\"), author (\"My Name\"), license, and other information are correct.  If you're unsure, a good open source license to use is \"MIT\".  If you're publishing a private generator and want it to remain proprietary to your organization, use \"UNLICENSED\".\n\n> **Note:**  If you don't already have an NPM account, go to [npmjs.com](https://www.npmjs.com/) and create one.  Then use `npm login` to get set up.\n\nWhen you're ready to pull the trigger and publish your generator on NPM, cd into the generator's folder in the terminal and type:\n\n```sh\nnpm publish\n```\n\n\n### Installing a generator\n\nTo take your newly-published generator for a spin, cd back into your example Sails project (`my-project`), delete the inline generator, and run:\n\n```js\nnpm install @my-npm-name/sails-generate-awesome\n```\n\nthen change the `.sailsrc` in your example Sails project (`my-project/.sailsrc`):\n\n```javascript\n{\n  \"generators\": {\n    \"modules\": {\n      \"awesome\": \"@my-npm-name/sails-generate-awesome\"\n    }\n  }\n}\n```\n\nAnd, last but not least:\n\n```sh\nsails generate awesome\n```\n\n\n<docmeta name=\"displayName\" value=\"Custom generators\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Generators/generatorList.md",
    "content": "# Available generators\n\nThe Sails framework's built-in [generators](https://sailsjs.com/documentation/concepts/extending-sails/generators) can be customized using command-line options and overridden by [mounting custom generators in the `.sailsrc` file](https://sailsjs.com/documentation/concepts/extending-sails/generators/custom-generators).  Other generators that add completely new sub-commands to [`sails generate`](https://sailsjs.com/documentation/reference/command-line-interface/sails-generate) can be mounted in the same way.\n\n### Core generators\n\nCertain generators are built in to Sails by default.\n\n| Commands that generate a new Sails app\n|:-----------------------------------|\n| sails new _name_\n| sails new _name_ --fast\n| sails new _name_ --caviar\n| sails new _name_ --without=grunt\n| sails new _name_ --without=lodash,async,grunt,blueprints,i18n\n| sails new _name_ --no-frontend --without=sockets,lodash\n| sails new _name_ --minimal\n\n\n| Generators for spitting out new files in an existing Sails app\n|:-----------------------------------|\n| sails generate model _identity_\n| sails generate action _name_\n| sails generate action view-_name_\n| sails generate action _some/path/_view-_name_\n| sails generate page _name_\n| sails generate helper _name_\n| sails generate helper view-_name_\n| sails generate script _name_\n| sails generate script get-_name_\n| sails generate controller _name_\n| sails generate api _name_\n| sails generate hook _name_\n| sails generate response _name_\n\n\n| Commands for generating plugins\n|:-----------------------------------|\n| sails generate generator _name_\n| sails generate adapter _name_\n\n\n| Commands for (re)generating client-side dependencies\n|:-----------------------------------|\n| sails generate sails.io.js\n| sails generate parasails\n\n| Utils for building your own 3rd party packages\n|:-----------------------------------|\n| sails generate etc\n\n\n_Since Sails v1.0, built-in generators are now [bundled](https://npmjs.com/package/sails-generate) in Sails core, rather than in separate NPM packages.  All generators can still be overridden the same way.  For advice setting up overrides for core generators in your environment, [click here](https://sailsjs.com/support)._\n\n\n### Community generators\n\nThere are over 100 community-supported generators [available on NPM](https://www.npmjs.com/search?q=sails+generate):\n\n+ [sails-inverse-model](https://github.com/juliandavidmr/sails-inverse-model)\n+ [sails-generate-new-gulp](https://github.com/Karnith/sails-generate-new-gulp)\n+ [sails-generate-archive](https://github.com/jaumard/sails-generate-archive)\n+ [sails-generate-scaffold](https://github.com/irlnathan/sails-generate-scaffold)\n+ [sails-generate-directive](https://github.com/balderdashy/sails-generate-directive)\n+ [sails-generate-bower](https://github.com/smies/sails-generate-bower)\n+ [sails-generate-angular-gulp](https://github.com/Karnith/sails-generate-angular-gulp)\n+ [sails-generate-ember-blueprints](https://github.com/mphasize/sails-generate-ember-blueprints)\n+ And [many more](https://www.npmjs.com/search?q=sails+generate)...\n\n\n<docmeta name=\"displayName\" value=\"Available generators\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Hooks/Hooks.md",
    "content": "# Hooks\n\n### What is a hook?\n\nA hook is a Node module that adds functionality to the Sails core.  The [hook specification](https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification) defines the requirements a module must meet for Sails to be able to import its code and make the new functionality available.  Because they can be saved separately from the core, hooks allow Sails code to be shared between apps and developers without having to modify the framework.\n\n### Types of hooks\n\nThere are three types of hooks available in Sails:\n\n1. **Core hooks** are built in and provide many of the common features essential to a Sails app, such as request handling, blueprint route creation, and database integration via [Waterline](https://sailsjs.com/documentation/concepts/models-and-orm).  Core hooks are bundled with the Sails core and are thus available to every app.  You will rarely need to call core hook methods in your code.\n2. **App-level hooks** live in the `api/hooks/` folder of a Sails app.  Project hooks let you take advantage of the features of the hook system for code that doesn&rsquo;t need to be shared between apps.\n3. **Installable hooks** are plugins, installed into an app&rsquo;s `node_modules` folder using `npm install`.  Installable hooks allow developers in the Sails community to create &ldquo;plug-in&rdquo;-like modules for use in Sails apps.\n\n### Read more\n\n* [Using hooks in your app](https://sailsjs.com/documentation/concepts/extending-sails/Hooks/using-hooks)\n* [The hook specification](https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification)\n* [Creating a project hook](https://sailsjs.com/documentation/concepts/extending-sails/hooks/project-hooks)\n* [Creating an installable hook](https://sailsjs.com/documentation/concepts/extending-sails/Hooks/installable-hooks)\n\n\n\n<docmeta name=\"displayName\" value=\"Hooks\">\n<docmeta name=\"stabilityIndex\" value=\"3\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Hooks/available-hooks.md",
    "content": "# Available hooks\n\n\nThis page is meant to be an up to date, comprehensive list of all of the core hooks in the Sails.js framework, and a reference of a few of the most popular community-made hooks.\n\n### Core hooks\n\nThe following hooks are maintained by the Sails.js core team and are included in your Sails app by default. You can override or disable them using your [sailsrc file](https://sailsjs.com/documentation/concepts/configuration/using-sailsrc-files) or [environment variables](https://sailsjs.com/documentation/concepts/configuration#?setting-sailsconfig-values-directly-using-environment-variables).\n\n| Hook           | Package       | Latest stable release   | Purpose     |\n|:---------------|---------------|-------------------------|:------------|\n| `grunt`        | [sails-hook-grunt](https://npmjs.com/package/sails-hook-grunt)      | [![NPM version](https://badge.fury.io/js/sails-hook-grunt.png)](http://badge.fury.io/js/sails-hook-grunt)     | Governs the built-in asset pipeline in Sails.\n| `orm`          | [sails-hook-orm](https://npmjs.com/package/sails-hook-orm)          | [![NPM version](https://badge.fury.io/js/sails-hook-orm.png)](http://badge.fury.io/js/sails-hook-orm)         | Implements support for Waterline ORM in Sails.\n| `sockets`      | [sails-hook-sockets](https://npmjs.com/package/sails-hook-sockets)  | [![NPM version](https://badge.fury.io/js/sails-hook-sockets.png)](http://badge.fury.io/js/sails-hook-sockets) | Implements Socket.io support in Sails.\n\n### sails-hook-orm\n\nImplements support for the Waterline ORM in Sails.\n\n[![Release info for sails-hook-orm](https://img.shields.io/npm/dm/sails-hook-orm.svg?style=plastic)](http://npmjs.com/package/sails-hook-orm) &nbsp; [![License info](https://img.shields.io/npm/l/sails-hook-orm.svg?style=plastic)](http://npmjs.com/package/sails-hook-orm)\n\n> + The default configuration set by this hook can be found [here](https://www.npmjs.com/package/sails-hook-orm#implicit-defaults).\n> + You can find futher details about this hook's purpose [here](https://www.npmjs.com/package/sails-hook-orm#purpose).\n> + You can disable this hook by following [these instructions](https://www.npmjs.com/package/sails-hook-orm#can-i-disable-this-hook).\n\n\n### sails-hook-sockets\n\nImplements socket.io support in Sails.\n\n[![Release info for sails-hook-sockets](https://img.shields.io/npm/dm/sails-hook-sockets.svg?style=plastic)](http://npmjs.com/package/sails-hook-sockets) &nbsp; [![License info](https://img.shields.io/npm/l/sails-hook-sockets.svg?style=plastic)](http://npmjs.com/package/sails-hook-sockets)\n\n> + You can find futher details about this hook's purpose [here](https://www.npmjs.com/package/sails-hook-sockets#purpose).\n\n### sails-hook-grunt\n\nImplements support for the built-in asset pipeline and task runner in Sails.\n\n[![Release info for sails-hook-grunt](https://img.shields.io/npm/dm/sails-hook-grunt.svg?style=plastic)](http://npmjs.com/package/sails-hook-grunt) &nbsp; [![License info](https://img.shields.io/npm/l/sails-hook-grunt.svg?style=plastic)](http://npmjs.com/package/sails-hook-grunt)\n\n> + You can find futher details about this hook's purpose [here](https://www.npmjs.com/package/sails-hook-grunt#purpose).\n> + You can disable this hook by following [these instructions](https://www.npmjs.com/package/sails-hook-grunt#can-i-disable-this-hook).\n\n\n### Community-made hooks\n\nThere are more than 200 community hooks for Sails.js [available on NPM](https://www.npmjs.com/search?q=sails+hook). Here are a few highlights:\n\n| Hook        | Maintainer  | Purpose        | Stable release |\n|-------------|-------------|:---------------|----------------|\n| [sails-hook-webpack](https://www.npmjs.com/package/sails-hook-webpack) | [Michael Diarmid](https://github.com/Salakar) | Use Webpack for your Sails app's asset pipeline instead of Grunt. | [![Release info for sails-hook-webpack](https://img.shields.io/npm/dm/sails-hook-webpack.svg?style=plastic)](http://npmjs.com/package/sails-hook-webpack)\n| [sails-hook-postcss](https://www.npmjs.com/package/sails-hook-postcss) | [Jeff Jewiss](https://github.com/jeffjewiss)| Process your Sails application’s CSS with Postcss. | [![Release info for sails-hook-postcss](https://img.shields.io/npm/dm/sails-hook-postcss.svg?style=plastic)](http://npmjs.com/package/sails-hook-postcss)\n| [sails-hook-babel](https://www.npmjs.com/package/sails-hook-babel) |  [Onoshko Dan](https://github.com/dangreen), [Markus Padourek](https://github.com/globegitter) &amp; [SANE](http://sanestack.com/) | Process your Sails application’s CSS with Postcss. | [![Release info for sails-hook-babel](https://img.shields.io/npm/dm/sails-hook-babel.svg?style=plastic)](http://npmjs.com/package/sails-hook-babel)\n| [sails-hook-responsetime](https://www.npmjs.com/package/sails-hook-responsetime) | [Luis Lobo Borobia](https://github.com/luislobo)| Add X-Response-Time to both HTTP and Socket request headers. | [![Release info for sails-hook-responsetime](https://img.shields.io/npm/dm/sails-hook-responsetime.svg?style=plastic)](http://npmjs.com/package/sails-hook-responsetime)\n| [sails-hook-winston](https://www.npmjs.com/package/sails-hook-winston) | [Kikobeats](https://github.com/Kikobeats) | Integrate the Winston logging system with your Sails application. | [![Release info for sails-hook-winston](https://img.shields.io/npm/dm/sails-hook-winston.svg?style=plastic)](http://npmjs.com/package/sails-hook-winston)\n| [sails-hook-allowed-hosts](https://www.npmjs.com/package/sails-hook-allowed-hosts) | [Akshay Bist](https://github.com/elssar) | Ensure that only requests made from authorized hosts/IP addresses are allowed. | [![Release info for sails-hook-allowed-hosts](https://img.shields.io/npm/dm/sails-hook-allowed-hosts.svg?style=plastic)](http://npmjs.com/package/sails-hook-allowed-hosts)\n| [sails-hook-cron](https://www.npmjs.com/package/sails-hook-cron) | [Eugene Obrezkov](https://github.com/ghaiklor) | Run cron tasks for your Sails app. | [![Release info for sails-hook-cron](https://img.shields.io/npm/dm/sails-hook-cron.svg?style=plastic)](http://npmjs.com/package/sails-hook-cron)\n| [sails-hook-organics](https://www.npmjs.com/package/sails-hook-organics) | [Mike McNeil](https://github.com/mikermcneil) | Exposes a set of commonly-used functions (\"organics\") as built-in helpers in your Sails app. | [![Release info for sails-hook-organics](https://img.shields.io/npm/dm/sails-hook-organics.svg?style=plastic)](http://npmjs.com/package/sails-hook-organics)\n\n\n##### Add your hook to this list\n\nIf you see out of date information on this page, or if you want to add a hook you made, please submit a pull request to this file updating the table of community hooks above.\n\nNote: to be listed on this page, an adapter must be free and open-source (_libre_ and _gratis_), preferably under the MIT license.\n\n\n<docmeta name=\"displayName\" value=\"Available hooks\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Hooks/events.md",
    "content": "# Application Events\n\n### Overview\n\nSails app instances inherit Node's [`EventEmitter` interface](https://nodejs.org/api/events.html#events_class_eventemitter), meaning that they can both emit and listen for custom events.  While it is not recommended that you utilize Sails events directly in app code (since your apps should strive to be as stateless as possible to facilitate scalability), events can be very useful when extending Sails (via [hooks](https://sailsjs.com/documentation/concepts/extending-sails/hooks) or [adapters](https://sailsjs.com/documentation/concepts/extending-sails/adapters)) and in a testing environment.\n\n### Should I use events?\n\nMost Sails developers never have a use case for working with application events. Events emitted by the Sails app instance are designed to be used when building your own custom hooks, and while you _could_ technically use them anywhere, in most cases you _should not_.  Never use events in your controllers, models, services, configuration, or anywhere else in the userland code in your Sails app (unless you are building a custom app-level hook in `api/hooks/`).\n\n### Events emitted by Sails\n\nThe following are the most commonly used built-in events emitted by Sails instances.  Like any EventEmitter in Node, you can listen for these events with `sails.on()`:\n\n```javascript\nsails.on(eventName, eventHandlerFn);\n```\n\nNone of the events are emitted with extra information, so your `eventHandlerFn` should not have any arguments.\n\n| Event name | Emitted when... |\n|:-----------|:----------------|\n| `ready`    | The app has been loaded and the bootstrap has run, but it is not yet listening for requests |\n| `lifted`   | The app has been lifted and is listening for requests. |\n| `lower`  | The app has is lowering and will stop listening for requests. |\n| `hook:<hook identity>:loaded` | The hook with the specified identity loaded and ran its `initialize()` method successfully.  |\n\n\n> In addition to `.on()`, Sails also exposes a useful utility function called `sails.after()`.  See the [inline documentation](https://github.com/balderdashy/sails/blob/fd2f9b6866637143eda8e908775365ca52fab27c/lib/EVENTS.md#usage) in Sails core for more information.\n\n<docmeta name=\"displayName\" value=\"Events\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Hooks/hookspec/configure.md",
    "content": "# `.configure`\n\nThe `configure` feature provides a way to configure a hook after the [`defaults` objects](https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification/defaults) have been applied to all hooks.  By the time a custom hook&rsquo;s `configure()` function runs, all user-level configuration and core hook settings will have been merged into `sails.config`.  However, you should *not* depend on the configuration of other custom hooks at this point, as the load order of custom hooks is not guaranteed.\n\n`configure` should be implemented as a function with no arguments, and should not return any value.  For example, the following `configure` function could be used for a hook that communicates with a remote API, to change the API endpoint based on whether the user set the hook&rsquo;s `ssl` property to `true`.  Note that the hook&rsquo;s configuration key is available in `configure` as `this.configKey`:\n\n```\nconfigure: function() {\n\n   // If SSL is on, use the HTTPS endpoint\n   if (sails.config[this.configKey].ssl == true) {\n      sails.config[this.configKey].url = \"https://\" + sails.config[this.configKey].domain;\n   }\n   // Otherwise use HTTP\n   else {\n      sails.config[this.configKey].url = \"http://\" + sails.config[this.configKey].domain;\n   }\n}\n```\n\nThe main benefit of `configure` is that all hook `configure` functions are guaranteed to run before any [`initialize` functions](https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification/initialize) run; therefore, a hook&rsquo;s `initialize` function can examine the configuration settings of other hooks.\n\n\n<docmeta name=\"displayName\" value=\".configure\">\n<docmeta name=\"stabilityIndex\" value=\"3\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Hooks/hookspec/defaults.md",
    "content": "# `.defaults`\n\nThe `defaults` feature can be implemented either as an object or a function which takes a single argument (see &ldquo;using `defaults` as a function&rdquo; below) and returns an object.  The object you specify will be used to provide default configuration values for Sails.  You should use this feature to specify default settings for your hook.  For example, if you were creating a hook that communicates with a remote service, you may want to provide a default domain and timeout length:\n\n```\n{\n   myapihook: {\n      timeout: 5000,\n      domain: \"www.myapi.com\"\n   }\n}\n```\n\nIf a `myapihook.timeout` value is provided via a Sails configuration file, that value will be used; otherwise it will default to `5000`.\n\n##### Namespacing your hook configuration\nFor [project hooks](https://sailsjs.com/documentation/concepts/extending-sails/Hooks?q=types-of-hooks), you should namespace your hook&rsquo;s configuration under a key that uniquely identifies that hook (e.g. `myapihook` above).  For [installable hooks](https://sailsjs.com/documentation/concepts/extending-sails/Hooks?q=types-of-hooks), you should use the special `__configKey__` key to allow end-users of your hook to [change the configuration key](https://sailsjs.com/documentation/concepts/extending-sails/hooks/using-hooks?q=changing-the-way-sails-loads-an-installable-hook) if necessary.  The default key for a hook using `__configKey__` is the hook name.  For example, if you create a hook called `sails-hooks-myawesomehook` which includes the following `defaults` object:\n\n```\n{\n   __configKey__: {\n      name: \"Super Bob\"\n   }\n}\n```\n\nthen it will, by default, provide default settings for the `sails.config.myawesomehook.name` value.  If the end-user of the hook overrides the hook name to be `foo`, then the `defaults` object will provide a default value for `sails.config.foo.name`.\n\n##### Using `defaults` as a function\n\nIf you specify a function for the `defaults` feature instead of a plain object, it takes a single argument (`config`) which receives any Sails configuration overrides.  Configuration overrides can be made by passing settings to the command line when lifting Sails (e.g. `sails lift --prod`), by passing an object as the first argument when programmatically lifting or loading Sails (e.g. `Sails.lift({port: 1338}, ...)`) or by using a [`.sailsrc`](https://sailsjs.com/documentation/anatomy/.sailsrc) file.  The `defaults` function should return a plain object representing configuration defaults for your hook.\n\n\n<docmeta name=\"displayName\" value=\".defaults\">\n<docmeta name=\"stabilityIndex\" value=\"3\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Hooks/hookspec/hookspec.md",
    "content": "# The hook specification\n\n### Overview\n\nEach Sails hook is implemeted as a Javascript function that takes a single argument&mdash;a reference to the running `sails` instance&mdash;and returns an object with one or more of the keys described later in this document.  The most basic hook would look like this:\n\n```javascript\nmodule.exports = function myBasicHook(sails) {\n   return {};\n}\n```\n\nIt wouldn't do much, but it would work!\n\nEach hook should be saved in its own folder with the filename `index.js`.  The folder name should uniquely identify the hook, and the folder can contain any number of additional files and subfolders.  Extending the previous example, if you saved the file containing `myBasicHook` in a Sails project as `index.js` in the folder `api/hooks/my-basic-hook` and then lifted your app with `sails lift --verbose`, you would see the following in the output:\n\n`verbose: my-basic-hook hook loaded successfully.`\n\n### Hook features\nThe following features are available to implement in your hook.  All features are optional, and can be implemented by adding them to the object returned by your hook function.\n\n* [.defaults](https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification/defaults)\n* [.configure()](https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification/configure)\n* [.initialize()](https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification/initialize)\n* [.routes](https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification/routes)\n* [.registerActions()](https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification/register-actions)\n\n### Custom hook data and functions\n\nAny other keys added to the object returned from the main hook function will be provided in the `sails.hooks[<hook name>]` object.  This is how custom hook functionality is provided to end-users.  Any data and functions that you wish to remain private to the hook can be added *outside* the returned object:\n\n```javascript\n// File api/hooks/myhook/index.js\nmodule.exports = function (sails) {\n\n   // This var will be private\n   var foo = 'bar';\n\n   return {\n\n     // This var will be public\n     abc: 123,\n\n     // This function will be public\n     sayHi: function (name) {\n       console.log(greet(name));\n     }\n\n   };\n\n   // This function will be private\n   function greet (name) {\n      return 'Hi, ' + name + '!';\n   }\n\n};\n```\n\nThe public var and function above would be available as `sails.hooks.myhook.abc` and `sails.hooks.myhook.sayHi`, respectively.\n\n\n<docmeta name=\"displayName\" value=\"Hook specification\">\n<docmeta name=\"stabilityIndex\" value=\"3\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Hooks/hookspec/initialize.md",
    "content": "# `.initialize`\n\nThe `initialize` feature allows a hook to perform startup tasks that may be asynchronous or rely on other hooks.  All Sails configuration is guaranteed to be completed before a hook&rsquo;s `initialize` function runs.  Examples of tasks that you may want to put in `initialize` include:\n\n* logging in to a remote API\n* reading from a database that will be used by hook methods\n* loading support files from a user-configured directory\n* waiting for another hook to load first\n\nLike all hook features, `initialize` is optional and can be left out of your hook definition.  If implemented, `initialize` should be an `async function` which must be resolved (i.e. not throw or hang forever) in order for Sails to finish loading:\n\n```javascript\ninitialize: async function() {\n\n   // Do some stuff here to initialize hook\n\n}\n```\n\n##### Hook timeout settings\n\nBy default, hooks have ten seconds to complete their `initialize` function and resolve before Sails throws an error.  That timeout can be configured by setting the `_hookTimeout` key to the number of milliseconds that Sails should wait.  This can be done in the hook&rsquo;s [`defaults`](https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification/defaults):\n\n```\ndefaults: {\n   __configKey__: {\n      _hookTimeout: 20000 // wait 20 seconds before timing out\n   }\n}\n```\n\n##### Hook events and dependencies\n\nWhen a hook successfully initializes, it emits an event with the following name:\n\n`hook:<hook name>:loaded`\n\nFor example:\n\n* the core `orm` hook emits `hook:orm:loaded` after its initialization is complete\n* a hook installed into `node_modules/sails-hook-foo` emits `hook:foo:loaded` by default\n* the same `sails-hook-foo` hook, with `sails.config.installedHooks['sails-hook-foo'].name` set to `bar` would emit `hook:bar:loaded`\n* a hook installed into `node_modules/mygreathook` would emit `hook:mygreathook:loaded`\n* a hook installed into `api/hooks/mygreathook` would also emit `hook:mygreathook:loaded`\n\nYou can use the \"hook loaded\" events to make one hook dependent on another.  To do so, simply wrap your hook&rsquo;s `initialize` logic in a call to `sails.on()`.  For example, to make your hook wait for the `orm` hook to load, you could make your `initialize` similar to the following:\n\n```javascript\ninitialize: async function() {\n  return new Promise((resolve)=>{\n    sails.on('hook:orm:loaded', ()=>{\n      // Finish initializing custom hook\n      // Then resolve.\n      resolve();\n    });\n  });\n}\n```\n\nTo make a hook dependent on several others, gather the event names to wait for into an array and call `sails.after`:\n\n```javascript\ninitialize: async function() {\n  return new Promise((resolve)=>{\n    var eventsToWaitFor = ['hook:orm:loaded', 'hook:mygreathook:loaded'];\n    sails.after(eventsToWaitFor, ()=>{\n      resolve();\n    });\n  });\n}\n```\n\n\n<docmeta name=\"displayName\" value=\".initialize()\">\n<docmeta name=\"stabilityIndex\" value=\"3\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Hooks/hookspec/register-actions.md",
    "content": "# `.registerActions()`\n\nIf your hook adds new actions to an app, and you want to guarantee that those actions will be maintained even after a call to [`sails.reloadActions()`](https://sailsjs.com/documentation/reference/application/sails-reload-actions), you should register the actions from within a `registerActions` method.\n\nFor example, the core Sails security hook registers the [`grant-csrf-token` action](https://sailsjs.com/documentation/concepts/security/csrf#?using-ajax-websockets) from within a `registerActions()` method.\n\n`registerActions` should be implemented as a function with a single argument (a callback) to be called after the hook is done adding actions.  In the interest of avoiding duplicate code, you may want to call this method yourself from within the hook&rsquo;s [`initialize()` method]((https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification/initialize)).\n\n```\nregisterActions: function(cb) {\n\n  // Register an action as `myhook/greet` that an app can bind to any route they like.\n  sails.registerAction(function greet(req, res) {\n    var name = req.param('name') || 'stranger';\n    return res.status(200).send('Hey there, ' + name + '!');\n  }, 'myhook/greet');\n\n  return cb();\n\n}\n```\n\n<docmeta name=\"displayName\" value=\".registerActions()\">\n<docmeta name=\"stabilityIndex\" value=\"3\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Hooks/hookspec/routes.md",
    "content": "# `.routes`\n\nThe `routes` feature allows a custom hook to easily bind new routes to a Sails app at load time.  If implemented, `routes` should be an object with either a `before` key, an `after` key, or both.  The values of those keys should in turn be objects whose keys are [route addresses](https://sailsjs.com/documentation/concepts/routes/custom-routes#?route-address), and whose values are route-handling functions with the standard `(req, res, next)` parameters.  Any routes specified in the `before` object will be bound *before* custom user routes (as defined in [sails.config.routes](https://sailsjs.com/documentation/reference/configuration/sails-config-routes)) and [blueprint routes](https://next.sailsjs.com/documentation/reference/blueprint-api#?restful-shortcut-routes-and-actions).  Conversely, routes specified in the `after` object will be bound *after* custom and blueprint routes.  For example, consider the following `count-requests` hook:\n\n```javascript\nmodule.exports = function (sails) {\n\n  // Declare a var that will act as a reference to this hook.\n  var hook;\n\n  return {\n\n    initialize: function(cb) {\n      // Assign this hook object to the `hook` var.\n      // This allows us to add/modify values that users of the hook can retrieve.\n      hook = this;\n      // Initialize a couple of values on the hook.\n      hook.numRequestsSeen = 0;\n      hook.numUnhandledRequestsSeen = 0;\n      // Signal that initialization of this hook is complete\n      // by calling the callback.\n      return cb();\n    },\n\n    routes: {\n      before: {\n        'GET /*': function (req, res, next) {\n          hook.numRequestsSeen++;\n          return next();\n        }\n      },\n      after: {\n        'GET /*': function (req, res, next) {\n          hook.numUnhandledRequestsSeen++;\n          return next();\n        }\n      }\n    }\n  };\n};\n```\n\nThis hook will process all requests via the function provided in the `before` object, and increment its `numRequestsSeen` variable.  It will also process any *unhandled* requests via the function provided in the `after` object&mdash;that is, any routes that aren't bound in the app via a custom route configuration or a blueprint.\n\n> The two variables set up in the hook will be available to other modules in the Sails app as `sails.hooks[\"count-requests\"].numRequestsSeen` and `sails.hooks[\"count-requests\"].numUnhandledRequestsSeen`\n\n\n<docmeta name=\"displayName\" value=\".routes\">\n<docmeta name=\"stabilityIndex\" value=\"3\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Hooks/installablehooks.md",
    "content": "# Creating an installable hook\n\nInstallable hooks are custom Sails hooks that reside in an application&rsquo;s `node_modules` folder.  They are useful when you want to share functionality between Sails apps, or publish your hook to [NPM](http://npmjs.org) to share it with the Sails community.  If you wish to create a hook for use in  *just one* Sails app, see [creating a project hook](https://sailsjs.com/documentation/concepts/extending-sails/hooks/project-hooks) instead.\n\nTo create a new installable hook:\n\n1. Choose a name for your new hook.  It must not conflict with any of the [core hook names](https://github.com/balderdashy/sails/blob/master/lib/app/configuration/default-hooks.js).\n1. Create a new folder on your system with the name `sails-hook-<your hook name>`.  The `sails-hook-` prefix is optional but recommended for consistency; it is stripped off by Sails when the hook is loaded.\n1. Create a `package.json` file in the folder.  If you have `npm` installed on your system, you can do this easily by running `npm init` and following the prompts.  Otherwise, you can create the file manually, and ensure that it contains at minimum the following:\n```json\n{\n    \"name\": \"sails-hook-your-hook-name\",\n    \"version\": \"0.0.0\",\n    \"description\": \"a brief description of your hook\",\n    \"main\": \"index.js\",\n    \"sails\": {\n      \"isHook\": true\n    }\n}\n```\nIf you use `npm init` to create your `package.json`, be sure to open the file afterwards and manually insert the `sails` key containing `isHook: true`.\n1. Write your hook code in `index.js` in accordance with the [hook specification](https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification).\n\nYour new folder may contain other files as well, which can be loaded in your hook via `require`; only `index.js` will be read automatically by Sails.  Use the `dependencies` key of your `package.json` to refer to any dependencies that need to be installed in order for your hook to work (you may also use `npm install <dependency> --save` to easily save dependency information to `package.json`).\n\n### Specifying the internal name Sails uses for your hook (advanced)\n\nIn certain cases, especially when using a [scoped NPM package](https://docs.npmjs.com/misc/scope) to override a core Sails hook, you will want to change the name that Sails uses internally when it loads your hook.  You can use the `sails.hookName` configuration option in your `package.json` file for this.  The value should be the name you want to be loaded into the `sails.hooks` dictionary, so you generally will _not_ want a `sails-hooks-` prefix.  For example, if you have a module `@mycoolhooks/sails-hook-sockets` that you wish to use to override the core `sails-hook-sockets` module, the `package.json` might look like:\n\n```json\n{\n    \"name\": \"@mycoolhooks/sails-hook-sockets\",\n    \"version\": \"0.0.0\",\n    \"description\": \"my own sockets hook\",\n    \"main\": \"index.js\",\n    \"sails\": {\n      \"isHook\": true,\n      \"hookName\": \"sockets\"\n    }\n}\n```\n\n### Testing your new hook\n\nBefore you distribute your installable hook to others, you&rsquo;ll want to write some tests for it.  This will help ensure compatibility with future Sails versions and significantly reduce hair-pulling and destruction of nearby objects in fits of rage.  While a full guide to writing tests is outside the scope of this doc, the following steps should help get you started:\n\n1. Add Sails as a `devDependency` in your hook&rsquo;s `package.json` file:\n```json\n\"devDependencies\": {\n      \"sails\": \"~0.11.0\"\n}\n```\n1. Install Sails as a dependency of your hook with `npm install sails` or `npm link sails` (if you have Sails installed globally on your system).\n1. Install [Mocha](http://mochajs.org/) on your system with `npm install -g mocha`, if you haven&rsquo;t already.\n1. Add a `test` folder inside your hook&rsquo;s main folder.\n2. Add a `basic.js` file with the following basic test:\n```javascript\n    var Sails = require('sails').Sails;\n\n    describe('Basic tests ::', function() {\n\n        // Var to hold a running sails app instance\n        var sails;\n\n        // Before running any tests, attempt to lift Sails\n        before(function (done) {\n\n            // Hook will timeout in 10 seconds\n            this.timeout(11000);\n\n            // Attempt to lift sails\n            Sails().lift({\n              hooks: {\n                // Load the hook\n                \"your-hook-name\": require('../'),\n                // Skip grunt (unless your hook uses it)\n                \"grunt\": false\n              },\n              log: {level: \"error\"}\n            },function (err, _sails) {\n              if (err) return done(err);\n              sails = _sails;\n              return done();\n            });\n        });\n\n        // After tests are complete, lower Sails\n        after(function (done) {\n\n            // Lower Sails (if it successfully lifted)\n            if (sails) {\n                return sails.lower(done);\n            }\n            // Otherwise just return\n            return done();\n        });\n\n        // Test that Sails can lift with the hook in place\n        it ('sails does not crash', function() {\n            return true;\n        });\n\n    });\n```\n1. Run the test with `mocha -R spec` to see full results.\n1. See the [Mocha](http://mochajs.org/) docs for a full reference.\n\n### Publishing your hook\n\nAssuming your hook is tested and looks good, and assuming that the hook name isn&rsquo;t already in use by another [NPM](http://npmjs.org) module, you can share it with world by running `npm publish`.  Go you!\n\n* [Hooks overview](https://sailsjs.com/documentation/concepts/extending-sails/hooks)\n* [Using hooks in your app](https://sailsjs.com/documentation/concepts/extending-sails/hooks/using-hooks)\n* [The hook specification](https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification)\n* [Creating a project hook](https://sailsjs.com/documentation/concepts/extending-sails/hooks/project-hooks)\n\n\n\n<docmeta name=\"displayName\" value=\"Installable hooks\">\n<docmeta name=\"stabilityIndex\" value=\"3\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Hooks/projecthooks.md",
    "content": "# Creating a project hook\n\nProject hooks are custom Sails hooks that reside in an application&rsquo;s `api/hooks` folder.  They are most useful when you want to take advantage of hook features like [defaults](https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification/defaults) and [routes](https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification/routes) for code that is used by multiple components in a single app.  If you wish to re-use a hook in *more than one* Sails app, see [creating an installable hook](https://sailsjs.com/documentation/concepts/extending-sails/hooks/installable-hooks) instead.\n\nTo create a new project hook:\n\n1. Choose a name for your new hook.  It must not conflict with any of the [core hook names](https://github.com/balderdashy/sails/blob/master/lib/app/configuration/default-hooks.js).\n2. Create a folder with that name in your app&rsquo;s `api/hooks` folder.\n3. Add an `index.js` file to that folder.\n4. Write your hook code in `index.js` in accordance with the [hook specification](https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification).\n\nYour new folder may contain other files as well, which can be loaded in your hook via `require`; only `index.js` will be read automatically by Sails.\n\nAs an alternative to a folder, you may create a file in your app&rsquo;s `api/hooks` folder like `api/hooks/myProjectHook.js`.\n\n#### Testing that your hook loads properly\n\nTo test that your hook is being loaded by Sails, lift your app with `sails lift --verbose`.  If your hook is loaded, you will see a message like:\n\n`verbose: your-hook-name hook loaded successfully.`\n\nin the logs.\n\n* [Hooks overview](https://sailsjs.com/documentation/concepts/extending-sails/hooks)\n* [Using hooks in your app](https://sailsjs.com/documentation/concepts/extending-sails/hooks/using-hooks)\n* [The hook specification](https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification)\n* [Creating an installable hook](https://sailsjs.com/documentation/concepts/extending-sails/hooks/installable-hooks)\n\n\n<docmeta name=\"displayName\" value=\"Project hooks\">\n<docmeta name=\"stabilityIndex\" value=\"3\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/Hooks/usinghooks.md",
    "content": "# Using hooks in a Sails app\n\n## Using a project hook\nTo use a project hook in your app, first create the `api/hooks` folder if it doesn&rsquo;t already exist.  Then [create the project hook](https://sailsjs.com/documentation/concepts/extending-sails/hooks/project-hooks) or copy the folder for the hook you want to use into `api/hooks`.\n\n## Using an installable hook\nTo use an installable hook in your app, simply run `npm install` with the package name of the hook you wish to install (e.g. `npm install sails-hook-autoreload`).  You may also manually copy or link an [installable hook folder that you've created](https://sailsjs.com/documentation/concepts/extending-sails/hooks/installable-hooks) directly into your app&rsquo;s `node_modules` folder.\n\n## Calling hook methods\nAny methods that a hook exposes are available in the `sails.hooks[<hook-name>]` object.  For example, the `sails-hook-email` hook provides a `sails.hooks.email.send()` method (note that the `sails-hook-` prefix is stripped off).  Consult a hook&rsquo;s documentation to determine which methods it provides.\n\n## Configuring a hook\nOnce you&rsquo;ve added an installable hook to your app, you can configure it using the regular Sails config files like `config/local.js`, `config/env/development.js`, or a custom config file you create yourself.  Hook settings are typically namespaced under the hook&rsquo;s name, with any `sails-hook-` prefix stripped off.  For example, the `from` setting for `sails-hook-email` is available as `sails.config.email.from`.  The documentation for the installable hook should describe the available configuration options.\n\n## Changing the way Sails loads an installable hook\nOn rare occassions, you may need to change the name that Sails uses for an installable hook, or change the configuration key that the hook uses.  This may be the case if you already have a project hook with the same name as an installable hook, or if you&rsquo;re already using a configuration key for something else.  To avoid these conflicts, Sails provides the `sails.config.installedHooks.<hook-identity>` configuration option.  The hook identity is *always* the name of the folder that the hook is installed in.\n\n```javascript\n// config/installedHooks.js\nmodule.exports.installedHooks = {\n   \"sails-hook-email\": {\n      // load the hook into sails.hooks.emailHook instead of sails.hooks.email\n      \"name\": \"emailHook\",\n      // configure the hook using sails.config.emailSettings instead of sails.config.email\n      \"configKey\": \"emailSettings\"\n   }\n};\n```\n\n> Note: you may have to create the `config/installedHooks.js` file yourself.\n\n* [Hooks overview](https://sailsjs.com/documentation/concepts/extending-sails/hooks)\n* [The hook specification](https://sailsjs.com/documentation/concepts/extending-sails/hooks/hook-specification)\n* [Creating a project hook](https://sailsjs.com/documentation/concepts/extending-sails/hooks/project-hooks)\n* [Creating an installable hook](https://sailsjs.com/documentation/concepts/extending-sails/hooks/installable-hooks)\n\n\n\n<docmeta name=\"displayName\" value=\"Using hooks\">\n<docmeta name=\"stabilityIndex\" value=\"3\">\n"
  },
  {
    "path": "docs/concepts/extending-sails/extending-sails.md",
    "content": "# Extending Sails\n\nIn keeping with the Node philosophy, Sails aims to keep its core as small as possible, delegating all but the most critical functions to separate modules.  There are currently three types of extensions that you can add to Sails:\n\n+ [**Generators**](https://sailsjs.com/documentation/concepts/extending-sails/Generators): for adding and overriding functionality in the Sails CLI.  *Example*: [sails-generate-model](https://www.npmjs.com/package/sails-generate-model), which allows you to create models on the command line with `sails generate model foo`.\n+ [**Adapters**](https://sailsjs.com/documentation/concepts/extending-sails/Adapters): for integrating Waterline (Sails' ORM) with new data sources, including databases, APIs, or even hardware. *Example*: [sails-postgresql](https://www.npmjs.com/package/sails-postgresql), the official [PostgreSQL](http://www.postgresql.org/) adapter for Sails.\n+ [**Hooks**](https://sailsjs.com/documentation/concepts/extending-sails/Hooks): for overriding or injecting new functionality in the Sails runtime.  *Example*: [sails-hook-autoreload](https://www.npmjs.com/package/sails-hook-autoreload), which adds auto-refreshing for a Sails project's API without having to manually restart the server.\n\nIf you&rsquo;re interested in developing a plugin for Sails, you will most often want to make a [hook](https://sailsjs.com/documentation/concepts/extending-sails/Hooks).\n\n<sub><a name=\"foot1\">*</a> _Core hooks_, like `http`, `request`, etc. are hooks which are bundled with Sails out of the box.  They can be disabled by specifying a `hooks` configuration in your `.sailsrc` file, or when lifting Sails programatically.</sub>\n\n\n<docmeta name=\"displayName\" value=\"Extending Sails\">\n"
  },
  {
    "path": "docs/concepts/shell-scripts/shell-scripts.md",
    "content": "# Shell scripts\n\nSails comes bundled with [Whelk](https://github.com/sailshq/whelk), which lets you run JavaScript functions as shell scripts. This can be useful for running scheduled jobs (cron, Heroku scheduler), worker processes, and any other custom, one-off scripts that need access to your Sails app's models, configuration, and helpers.\n\n\n### Your first script\n\nTo add a new script, just create a file in the `scripts/` folder of your app.\n\n```bash\nsails generate script hello\n```\n\nThen, to run it, use:\n\n```bash\nsails run hello\n```\n\n> If you need to run a script without global access to the `sails` command-line interface (in a Procfile, for example), use `node ./node_modules/sails/bin/sails run hello`.\n\n### Example\n\nHere's a more complex example that you might see in a real-world app:\n\n```js\n// scripts/send-email-proof-reminders.js\nmodule.exports = {\n\n  description: 'Send a reminder to any recent users who haven\\'t confirmed their email address yet.',\n\n  inputs: {\n    template: {\n      description: 'The name of another email template to use as an optional override.',\n      type: 'string',\n      defaultsTo: 'reminder-to-confirm-email'\n    }\n  },\n\n  fn: async function (inputs, exits) {\n\n    await User.stream({\n      emailStatus: 'pending',\n      emailConfirmationReminderAlreadySent: false,\n      createdAt: { '>': Date.now() - 1000*60*60*24*3 }\n    })\n    .eachRecord(async (user, proceed)=>{\n      await sails.helpers.sendTemplateEmail.with({\n        template: inputs.template,\n        templateData: {\n          user: user\n        },\n        to: user.emailAddress\n      });\n      return proceed();\n    });//∞\n\n    return exits.success();\n\n  }\n};\n```\n\nThen you can run:\n\n```bash\nsails run send-email-proof-reminders\n```\n\nFor more detailed information on usage, see the [`whelk` README](https://github.com/sailshq/whelk/blob/master/README.md).\n\n<docmeta name=\"displayName\" value=\"Shell scripts\">\n<docmeta name=\"nextUpLink\" value=\"/documentation/concepts/models-and-orm\">\n<docmeta name=\"nextUpName\" value=\"Models and ORM\">\n"
  },
  {
    "path": "docs/contributing/adapter-specification.md",
    "content": "# Adapter interface reference\n\n> The adapter interface specification is currently under active development and may change.\n\n\n## Semantic (interface)\n> e.g. `RestAPI` or `MySQL`\n\n> ##### Stability: [3](http://nodejs.org/api/documentation.html#documentation_stability_index) - Stable\n\nImplementing the basic semantic interface (CRUD) is really a step towards a complete implementation of the Queryable interface, but with some services/datasources, about as far as you'll be able to get using native methods.\n\nBy supporting the Semantic interface, you also get the following:\n+ if you write a `find()` function, developers can also use all of its synonyms, including dynamic finders and `findOne()`.  When they're called, they'll automatically be converted into the appropriate criteria object for the basic `find()` definition in your adapter.\n+ as long as you implement basic `where` functionality (see `Queryable` below), Waterline can derive a simplistic version of associations support for you.  To optimize the default assumptions with native methods, override the appropriate methods in your adapter.\n\n<!--\n\nDeprecated-- should be moved to the pubsub hook docs:\n\n+ When a socket subscribes to one or more \"instance room(s)\" (e.g. `Foo.subscribe(req, [3,2]`), it will receive `Foo.publishUpdate()` and `Foo.publishDestroy()` notifications for the relevant instances.\n+ If a socket is subscribed to an \"instance room\", it will also be subscribed for \"updates\" and \"destroys\" to all instances of other models with a 1:* association with `Foo`.  The socket will also be notified of and subscribed to new matching instances of the associated model.\n\n+ automatic socket.io pubsub support is provided by Sails-- it manages \"rooms\" for every class (collection) and each instance (model)\n  + As soon as a socket subscribes to the \"class room\" using `Foo.subscribe()`, it starts receiving `Foo.publishCreate()` notifications any time they're fired for `Foo`.\n-->\n\n\n> All officially supported Sails.js database adapters implement the `Semantic` interface.\n\n###### Class methods\n+ `Model.create()`\n+ `Model.find()`\n+ `Model.update()`\n+ `Model.destroy()`\n+ Optimizations:\n  + `findOrCreate()`\n  + `createEach()`\n  + Not yet available:\n    + `destroyEach()`\n    + `updateEach()`\n    + `findOrCreateEach()`\n    + `findAndUpdateOrCreate()`\n    + `findAndUpdateOrCreateEach()`\n\n<!--\n+ `henry.destroy()`\n-->\n\n\n## Queryable (interface)\n\n> ##### Stability: [3](http://nodejs.org/api/documentation.html#documentation_stability_index) - Stable\n\nQuery building features are common in traditional ORMs, but not at all a guarantee when working with Waterline.  Since Waterline adapters can support services as varied as Twitter, SMTP, and Skype, traditional assumptions around structured data don't always apply.\n\nIf query modifiers are enabled, the adapter must support `Model.find()`, as well as the **complete** query interface, or, where it is impossible to do so, at least provide good error messages.  If coverage of the interface is unfinished, it's still not a bad idea to make the adapter available, but it's important to clearly state the unifinished parts, and consequent limitations, up front.  This helps prevent the creation of off-topic issues in Sails/Waterline core, protects developers from unexpected consequences, and perhaps most importantly, helps focus contributors on high-value tasks.\n\n> All officially supported Sails.js database adapters implement this interface.\n\n###### Query modifiers\nQuery modifiers include normalized syntax:\n+ `where`\n+ `limit`\n+ `skip`\n+ `sort`\n+ `select`\n\nAnd WHERE supports:\n\nBoolean logic:\n+ `and`\n+ `or`\n+ `not`\n\n\n`IN` queries:\nAdapters which implement `where` should recognize a list of values (e.g. `name: ['Gandalf', 'Merlin']`) as an `IN` query.  In other words, if `name` is either of those values, a match occured.\n\nSub-attribute modifiers:\nYou are also responsible for sub-attribute modifiers, (e.g. `{ age: { '>=' : 65 } }`) with the notable exception of `contains`, `startsWith`, and `endsWith`, since support for those modifiers can be derived programatically by leveraging your definition of  `like`.\n+ `like`    (SQL-style, with % wildcards)\n+ `'>' `    (you can also opt to use the more verbose `.greaterThan()`, etc.)\n+ `'<' `\n+ `'>='`\n+ `'<='`\n\n\n## Migratable (interface)\n\n> ##### Stability: [1](http://nodejs.org/api/documentation.html#documentation_stability_index) - Experimental\n\nAdapters which implement the Migratable interface are usually interacting with SQL databases.  This interface enables the `migrate` configuration option on a per-model or adapter-global basis, as well as access to the prototypal/class-level CRUD operations for working with tables.\n\n###### Adapter methods\n\n> This is not how it actually works, but how it could work soon:\n\n+ `Adapter.define()`\n+ `Adapter.describe()`\n+ `Adapter.drop()`\n+ `Adapter.alter()` (change table name, other table metadata)\n+ `Adapter.addAttribute()` (add column)\n+ `Adapter.removeAttribute()` (remove column)\n+ `Adapter.alterAttribute()` (rename column, add or remove uniquness constraint to column)\n+ `Adapter.addIndex()`\n+ `Adapter.removeIndex()`\n\n\n###### Auto-migration strategies\n+ `\"safe\"` (default in production env)\n  + do nothing\n+ `\"drop\"` (default in development env)\n  + drop all tables and recreate them each time the server starts-- useful for development\n+ `\"alter\"`\n  + experimental automigrations\n+ `\"create\"`\n  + create all missing tables/columns without modifying existing data\n\n\n\n## SQL (interface)\n\n> ##### Stability: [1](http://nodejs.org/api/documentation.html#documentation_stability_index) - Experimental\n\nAdapters which implement the SQL interface interact with databases supporting the SQL language. This interface exposes the method `.query()` allowing the user to run *raw* SQL queries against the database.\n\n###### Adapter methods\n\n+ `Adapter.query(query,[ data,] cb)`\n\n\n<!--\n## Iterable (interface)\n\n> ##### Stability: [1](http://nodejs.org/api/documentation.html#documentation_stability_index) - Experimental\n\n#### Background\n\n> Communicating with another server via messages/packets is the gold standard of performance--\n> network latency is the slowest I/O operation computers deal with, yet ironically, the standard methodology\n> used by most developers/frameworks/libraries outside of Node.js is detrimental to performance.\n>\n> In the Node community, you might say we're in the midst of a bit of an I/O renaissance.\n>\n> The standard approach to communicating with another server (or a disk) involves loading a message into memory\n> from the source, and then sending the entire object to the destination at once.\n>\n> This is like trying to transport a heavy bag of gold over a river by wading across with it on your back.\n> Even if you're very strong, with enough gold, you will drown.  This is analogous to your server\n> running out of RAM as it buffers data in memory, and the resulting scalability problem.\n>\n> Using Node streams is a different ball game.  It's like splitting up the big bag into smaller containers, then\n> floating them across one by one.  This way, no matter how much gold you end up with, you never drown.\n\nA huge advantage of using Node.js is the ease with which you can parse and manipulate streams of data.  Instead of pulling an entire dataset into RAM, you can inspect it a little at a time.  This unlocks a level of performance that is unachievable using conventional approaches.\n\nThe most common use case is taking advantage of the available HTTP response stream to pipe the output byte stream from the database directly back to the user.  i.e. to generate a dynamic sitemap, you might need to respond with a huge set of data (far too large to fit in memory on a commodity server) and simultaneously transform it into XML.\n\n#### Implementation\n\nImplementing the Streaming CRUD interface is actually pretty simple-- you just need to get comfortable with Node.js streams.  You can mutate streams as they come in-- you just need to find or design a mapping function designed for streams, where you don't have all the data at once.\n\n\n\n## Blob / Readable / Writable (interface)\n\n> ##### Stability: [1](http://nodejs.org/api/documentation.html#documentation_stability_index) - Experimental\n\ne.g. `sails-local-fs`, `sails-s3`\n\nImplementing the Blob interface allows you to upload and download binary data (aka files) to the service/database.  These \"blobs\" might be MP3 music files (~5MB) but they could also be data-center backups (~50TB).  Because of this, it's crucial that adapters which implement this interface use streams for uploads (incoming, into data source from Sails) and downloads (outgoing, from data source to Sails).\n\n###### Class methods\n+ `write( id, options )` or `upload()`\n+ `read( id, options )` or `download()`\n\n\n\n## Mesageable (interface)\n\n> ##### Stability: [1](http://nodejs.org/api/documentation.html#documentation_stability_index) - Experimental\n\nAdapters which implement one-way messages.  This lets user know two important facts about your adapter:\n\n1. that it's not safe to assume that its operations are reversible or atomic.\n2. that it has a `send` or one or more `send*()` methods with a custom suffix.\n\nAn example of one such adapter is SMTP, for sending email, or APNS for sending Apple push notifications.\n\nIf `send` is passed an array of target ids, it will broadcast its data to each of them.\n\n###### Class methods\n+ `send( targetId, data, onComplete )`\n+ Optimizations:\n  + `broadcast( targetIds, data, onComplete )`\n\n\n\n## Subscribable (interface)\n\n> ##### Stability: [1](http://nodejs.org/api/documentation.html#documentation_stability_index) - Experimental\n\nAdapters implementing the pubsub interface report changes from the service/database back up to the app.\n\nWhen a subscriber needs to be informed of an incoming notifiation, the subscribable adapters currently do one of the following:\n\n1. emit a declaratively configurable event on the `sails` object.\n2. send an HTTP request to a declaratively configurable endpoint.\n3. call a function which is part of their declarative config, leveraging the generic `req/res` interpreter in Sails\n\n(#3 is where I'd like this head in the future, since it provides the most normalized, extensible interface)\n\n-->\n\n<!--\ndeprecated:\n\nThey should call Sails' `Model.publishUpdate()`, `Model.publishCreate()`, and `Model.publishDestroy()` to publish changes and take advantage of automatic room management functionality.\n`Model.subscribe()` should still be called at the app layer, not in our adapter.\nWe don't want to force users to handle realtime events-- we don't know the specific goals and requiements of their app, and since the broadcasts are volatile, pubsub notifications is a feature that should be opt-in anyway.\n-->\n<!--\nExamples:\n+ Twitter streaming API (see new tweets as they come in)\n+ IRC (see new chats as they come in)\n+ Stock prices (visualize the latest market data as soon as it is available)\n+ Hardware scanners (see new data as it comes in)\n\n-->\n\n<docmeta name=\"notShownOnWebsite\" value=\"true\">\n\n"
  },
  {
    "path": "docs/contributing/code-of-conduct.md",
    "content": "# Code of conduct\n\n> The Code of Conduct explains the *bare minimum* behavior expectations the Sails project requires of its contributors.  This Code of Conduct is adapted from the version used by the [Node.js core team](https://github.com/nodejs/node/blob/master/CODE_OF_CONDUCT.md).  Their version was originally borrowed from [Rust lang's excellent CoC](http://www.rust-lang.org/conduct.html).\n\n- We are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, disability, ethnicity, religion, or similar personal characteristic.\n- Please avoid using overtly sexual, racial, or political nicknames, or any other nicknames that might detract from a friendly, safe and welcoming environment for all.\n- Please be kind and courteous. There's no need to be mean or rude.\n- Avoid the use of personal pronouns in any code comments or documentation where such use could be perceived in a negative light. There is no need to address persons when explaining code (e.g. \"When the developer\").\n- Respect that some individuals and cultures consider the casual use of profanity offensive and off-putting.\n- Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer.\n- Please keep unstructured critique to a minimum. If you have ideas you want to experiment with, make a fork and see how it works.\n- We will exclude you from interaction if you insult, demean or harass\n  anyone. That is not welcome behavior. We interpret the term\n  \"harassment\" as including the definition in the [Citizen Code of\n  Conduct](http://citizencodeofconduct.org/); if you have any lack of\n  clarity about what might be included in that concept, please read\n  their definition, or ask one of the project maintainers first.\n  In particular, we don't tolerate defamatory remarks or behavior that\n  excludes people in socially marginalized groups, or for whom English\n  is not a native language.\n- Private harassment is also unacceptable. No matter who you are, if\n  you feel you have been or are being harassed or made uncomfortable\n  by a community member, please contact one of the core maintainers immediately\n  via private message on Twitter or by emailing [inquiries@sailsjs.com](inquiries@sailsjs.com).\n  In either case, include a capture (screenshot, log, photo, email) of\n  the harassment if possible. Whether you're a regular contributor or\n  a newcomer, we care about making this community a safe, comfortable\n  place for you and we've got your back.\n- Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome,\n  and will result in your exclusion.\n\n\n<docmeta name=\"displayName\" value=\"Code of conduct\">\n"
  },
  {
    "path": "docs/contributing/code-submission-guidelines/best-practices.md",
    "content": "# Best practices\n\nThere are many undocumented best practices and workflow improvements for developing in Sails that contributors have established over the years.  This section is an attempt to document some of the basics, but be sure and pop into [Gitter](https://gitter.im/balderdashy/sails) if you ever have a question about how to set things up or want to share your own tool chain.\n\nThe best way to work with Sails core is to fork the repository, `git clone` it to your filesystem, and then run `npm link`.  In addition to writing tests, you'll often want to use a sample project as a harness; to do that, `cd` into the sample app and run `npm link sails`.  This will create a symbolic link in the `node_modules` directory of your sample app that points to your local cloned version of Sails.  This keeps you from having to copy the framework over every time you make a change.  You can force your sample app to use the local Sails dependency by running `node app` instead of `sails lift` (although `sails lift` **should** use the local dependency, if one exists).  If you need to test the command line tool this way, you can access it from your sample app as `node node_modules/sails/bin/sails`.  For example, if you were working on `sails new`, and you wanted to test it manually, you could run `node node_modules/sails/bin/sails new testProj`.\n\n\n#### Installing different versions of Sails\n\n| Release               | Install Command          | Build Status      |\n|-----------------------|--------------------------|-------------------|\n| [latest](https://npmjs.com/package/sails)                | `npm install sails`      | Stable |\n| [edge](https://github.com/balderdashy/sails/tree/master)                  | `npm install sails@git://github.com/balderdashy/sails.git` | [![Build Status](https://travis-ci.org/balderdashy/sails.png?branch=master)](https://travis-ci.org/balderdashy/sails/branches) |\n\n<!-- | [beta](https://github.com/balderdashy/sails/tree/beta)                  | `npm install sails@beta` | [![Build Status](https://travis-ci.org/balderdashy/sails.png?branch=beta)](https://travis-ci.org/balderdashy/sails/branches) | -->\n\n\n#### Installing an unreleased branch for testing\n\nIn general, you can `npm install` Sails directly from Github as follows:\n\n```sh\n# Install an unreleased branch of Sails in the current directory's `node_modules`\n$ npm install sails@git://github.com/balderdashy/sails.git#nameOfDesiredBranch\n```\n\nThis is useful for testing/installing hot-fixes and just a good thing to know how to do in general.\n\n#### Submitting Pull Requests\n\n0. If this is your first time forking and submitting a PR, [follow our instructions here](https://sailsjs.com/documentation/contributing/code-submission-guidelines/sending-pull-requests).\n1. Fork the repo.\n2. Add a test for your change. Only refactoring and documentation changes require no new tests. If you are adding functionality or fixing a bug, we need a test!\n4. Make the tests pass and make sure you follow [our syntax guidelines](https://github.com/balderdashy/sails/blob/master/.jshintrc).\n5. Add a line of what you did to CHANGELOG.md (right under `master`).\n6. Push to your fork and submit a pull request to the appropriate branch:\n  + [Master](https://github.com/balderdashy/sails/tree/master)\n    + Corresponds with the \"edge\" version&mdash;the latest, not-yet-released version of Sails. Most pull requests should be sent here.\n  + [Latest (or \"stable\")](https://npmjs.com/package/sails)\n    + Corresponds with the latest stable release on npm (if you have a high-priority hotfix, send the PR explaining that).\n\n<docmeta name=\"displayName\" value=\"Best Practices\">\n"
  },
  {
    "path": "docs/contributing/code-submission-guidelines/code-submission-guidelines.md",
    "content": "# Code submission guidelines\n\nThere are two types of code contributions we can accept in Sails core:  patches and new features.\n\n**Patches** are small fixes and represent everything from typos to timing issues.  Removing an unused `require()` from the top of a file, or fixing a typo that is crashing the master branch tests on Travis are two great examples of patches.  Major refactoring projects that change whitespace and variable names across multiple files are **not** patches.  Also, keep in mind that even a seemingly trivial change is not a patch if it affects the usage of a documented feature of Sails, or if it adds an undocumented public function.\n\n**New features** are TODOs summarized in the [Sails Roadmap](https://github.com/balderdashy/sails/blob/master/ROADMAP.md) file, with more information in an accompanying pull request.  Anything that is not specifically in the ROADMAP.md file should not be submitted as a new feature.\n\nIf in doubt about whether a change you would like to make would be considered a \"patch\", please open an issue in the [issue tracker](https://github.com/balderdashy/sails/issues/new) or contact someone from our [core team](https://sailsjs.com/about) on Twitter _before_ you begin work on the pull request. 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.\n\n#### General rules\n\n- **Javascript supported by [maintained LTS](https://github.com/nodejs/Release/blob/0e0b592273104d1cca9154588092654b932659b1/README.md) only, please**.  For consistency, all imperative code in Sails core, including core hooks and core generators, must be written in JavaScript&mdash;not CoffeeScript, TypeScript, or any other pre-compiled or transpiled language.  Don't get us wrong: we think it's great to use ES6, TypeScript, and/or CoffeeScript syntax in userland code if it boosts your productivity!  But for compatibility and consistency reasons, we cannot merge a pull request unless it is written in maintained LTS-supported JavaScript.\n- Do not auto-format code or attempt to fix perceived style problems in existing files in core.\n- Keep each pull request narrowly focused on a single goal, and change as few LoC/files as possible.\n- Do not submit pull requests that implement new features or enhance existing features unless you are working from a very clearly-defined proposal.  As stated above, nothing is more frustrating than seeing your hard work go unmerged because your vision does not align with a project's maintainers.\n- Before beginning work on a feature, be sure to leave a comment telling other contributors that you are working on that feature.  Note that if you do not actively keep other contributors informed about your progress, your silence may be taken as inactivity, and someone else may start their own work on that feature.\n\n\n#### Contributing to core\n\nSub-modules within the Sails core are at varying levels of API stability. Bug fixes (patches) are always welcome, but API or behavioral changes cannot be merged without serious planning, as documented in the process for feature proposals above.\n\nSails has several dependencies referenced in the `package.json` file that are not part of the project proper. Any proposed changes to those dependencies or _their_ dependencies should be sent to their respective projects (e.g. Express, Socket.io, etc.) Please do not send your patch or feature request to the Sails repository&mdash;we cannot accept or fulfill it.  (Though if you reach out via chat, we'll try to help if we can.)\n\n\n#### Contributing to an adapter\n\nIf the adapter is part of core (code base located in the Sails repo), please follow the general best practices for contributing to Sails core.  If it is located in a different repo, please send feature requests and patches there.\n\n#### Authoring a new adapter\n\nSails adapters translate Waterline query syntax into the lower-level language of the integrated database, and they take the results from the database and map them to the response expected by Waterline, the Sails framework's ORM.  While creating a new adapter should not be taken lightly, in many cases, writing an adapter is not as hard as it sounds (since you usually end up wrapping around an existing npm package), and it's a great way to get your feet wet with contributing to the ORM hook in Sails and to the Waterline code base.\n\nBefore starting work on a new adapter, just make sure and do a thorough search on npm, Google and Github to check that someone else hasn't already started working on the same thing.  Read more about adapters in [Concepts > Extending Sails > Adapters](https://sailsjs.com/documentation/concepts/extending-sails/adapters).\n\n\n#### Contributing to a hook\n\nIf the hook is part of core (code base is located in the Sails repo), please follow the general best practices for contributing to Sails core.  If the hook is located in a different repo, please send feature requests, patches, and issues there.  Many core hooks have README.md files with extensive documentation of their purpose, the methods they attach, the events they trigger, and any other relevant information about their implementation.\n\n#### Authoring a new hook\n\nCreating a hook is a great way to accomplish _almost anything_ in Sails core.  Before starting work on a new custom hook, just make sure and do a thorough search on npm, Google, and Github to make sure someone else hasn't already started working on the same thing.  Read more about custom hooks in [Concepts > Extending Sails > Hooks](https://sailsjs.com/documentation/concepts/extending-sails/hooks).\n\n\n#### Contributing to a generator\n\nIf the generator is part of core (code base is located in the Sails repo), please follow the general best practices for contributing to Sails core.  If it is located in a different repo, please send feature requests, patches, and issues there.\n\n\n#### Authoring a new generator\n\nThe custom generator API is not 100% stable yet, but it is settling.  Feel free to start work on a new custom generator, but first make sure and do a thorough search on npm, Google and Github to make sure someone else hasn't already started working on the same thing.  A custom generator is a great way to get your feet wet with contributing to the Sails code base.\n\n<docmeta name=\"displayName\" value=\"Code submission guidelines\">\n"
  },
  {
    "path": "docs/contributing/code-submission-guidelines/sending-pull-requests.md",
    "content": "# Sending pull requests\n\n<!--\n> **NOTE**\n> This is really just a support document for the official contribution guide [here](https://github.com/balderdashy/sails/blob/master/CONTRIBUTING.md) and is mainly focused on helping guide you through the mechanics of submiting a pull request.  If this document contradicts the official contribution guide in any way, particularly re: rules/guidelines, or if you're otherwise in doubt, go w/ the offical guide :)\n>\n> Thanks!\n> ~mm\n-->\n\nThis guide is designed to get you started contributing to the Sails framework. It assumes basic familiarity with Github, but it should be useful for contributors of all levels.\n\n\n## Contribution guidelines\nLike any open-source project, we must have guidelines for contributions&mdash;it helps protect the quality of the code and ensures that our framework stays robust and dependable.\nFor these reasons, it's important that contribution protocols are followed for *all* contributions to Sails, whether they be bug fixes or whole sets of new features.\n\nBefore submitting a pull request, please make sure:\n - Any bug fixes have accompanying tests where possible.  We use [Mocha](http://visionmedia.github.io/mocha/) for testing.\n - Code follows our style guide, to maintain consistency (see `.jshint` and/or `.editorconfig` files in repo).\n\nIf you have a high-priority hot-fix for the currently deployed version, please [post an issue on Github](https://github.com/balderdashy/sails/issues?milestone=none&state=open) and mention @mikermcneil.  Also, for emergencies, please feel free to tweet @sailsjs.\n\nNow that we are all on the same page, lets get to coding some awesomeness of our own :D\n\n## Fork\nStart by forking the repository:\n\n![Screen Shot 2013-02-12 at 2.37.04 PM.png](http://i.imgur.com/h0CCcAu.png)\n\n## Clone\nThen clone your fork into your local filesystem:\ngit clone `git@github.com:YOUR_USER_NAME/sails.git`\n\n## Update\nTo merge recent changes into your fork, inside your project dir:\n```\ngit remote add core https://github.com/balderdashy/sails.git\ngit fetch core\ngit merge core/master\n```\nFor additional details, see [Github](https://help.github.com/articles/fork-a-repo).\n\n## Code\nMake your enhancements, fix bugs, do your thang.\n\n\n## Test\nPlease write a test for your addition/fix.  I know it kind of sucks if you're not used to it, but it's how we maintain great code.\nFor our test suite, we use [Mocha](http://visionmedia.github.com/mocha/).  You can run the tests with `npm test`.  See the \"Testing\" section in the contribution guide for more information.\n\n![Screen Shot 2013-02-12 at 2.56.59 PM.png](http://i.imgur.com/dalbOdZ.png)\n\n## Pull request\nWhen you're done, you can commit your fix, push up your changes, and then go into Github and submit a pull request.  We'll look it over and get back to you ASAP.\n\n![Screen Shot 2013-02-12 at 2.55.40 PM.png](http://i.imgur.com/GBg0AOi.png)\n\n\n## Running your fork with your application\nIf you forked Sails and you want to test your Sails app against your fork, here's how you do it:\n\nIn your local copy of your fork of Sails:\n`sudo npm link`\n\nIn your Sails app's repo:\n`npm link sails`\n\nThis creates a symbolic link as a local dependency (in your app's `node_modules` folder).  This has the effect of letting you run your app with the version Sails you `linked`.\n```bash\n$ sails lift\n```\n\n### *Thanks for your contributions!*\n\n<docmeta name=\"displayName\" value=\"Sending pull requests\">\n"
  },
  {
    "path": "docs/contributing/code-submission-guidelines/writing-tests.md",
    "content": "# Writing tests\n\n### What to test\nIn an ideal world, any possible action you could perform as a Sails user&mdash;whether programatically or via the command-line tool&mdash;would have a test. However, the number of configuration variations in Sails, along with the fact that userland code can override just about any key piece of core, means we'll never _quite_ get to this point.  And that's okay.\n\nInstead, the Sails project's goal is for any _feature of Sails_ you might use&mdash;programatically or via the command-line tool&mdash;to have a test.  In cases where these features are implemented within a dependency, the only tests for that feature exist within that dependency (e.g. [Waterline](https://github.com/balderdashy/waterline/tree/master/test), [Skipper](https://github.com/balderdashy/skipper/tree/master/test), and [Captains Log](https://github.com/balderdashy/captains-log/tree/master/test)).  Even in these cases, though, tests in Sails inevitably end up retesting certain features that are already verified by Sails' dependencies, and there's nothing wrong with that.\n\n### What _not_ to test\nWe should strive to avoid tests which verify exclusivity: it cripples our ability to develop quickly.  In other words, tests should not fail with the introduction of additive features.\n\nFor instance, if you're writing a test to check that the appropriate files have been created with `sails new`, it would make sense to check for those files, but it would _not_ make sense to ensure that ONLY those files were created (i.e. adding a new file should not break the tests).\n\nAnother example is a test which verifies the correctness of blueprint configuration, e.g. `sails.config.blueprints.rest`.  The test should check that blueprints behave properly with the `rest` config enabled and disabled.  We could change the configuration, add more controller-specific options, etc., and we'd only need to write new tests.\n\nIf, on the other hand, our strategy for testing the behavior of the blueprints involved evaluating the behavior and *then* making a judgement on what the config \"_should_\" look like, we'd have to modify the tests when we added new options.  This may not sound like a big deal, but it can grow out of proportion quickly!\n\n\n\n<!--\n### Structural Conventions\n\nSails's tests are broken up into three distinct types- `unit`, `integration`, and `benchmark` tests.  See the README.md file in each directory for more information about the distinction and purpose of each type of test, as well as a shortlist of ways you can get involved.\n\nThe following conventions are true for all three types of tests:\n\n+ Instead of partitioning tests for various components into subdirectories, the test files are located in the top level of the directory for their test type (i.e. `/test/TEST_TYPE/*.test.js`).\n+ All test filenames have the `*.test.js` suffix.\n+ Each test file for a particular component is namespaced with a prefix describing the relevant component (e.g. `router.specifiedRoutes.test.js`, `router.APIScaffold.test.js`, etc.).\n+ Tests for core hooks are namespaced according to the hook that they test, e.g. `hook.policies.test.js`.\n+ If tests for a core hook need to span multiple files, maintain the namespacing, e.g. `hook.policies.load.test.js` and `hook.policies.teardown.test.js`.\n\n> **Reasoning**\n>\n> Filenames like these make it easy to differentiate tests from core files when performing a flat search on the repository (i.e. CMD/CTRL+P in Sublime).  Likewise, this makes the process easier to automate-- you can quickly grab all the test files with a simple recursive find on the command-line, for instance.\n\n#### `fixtures` directory\nContains sample data/files/templates used for testing (e.g. a dummy Sails app or simple middleware functions)\n\n#### `helpers` directory\nLogic to help setup or teardown Sails, read fixtures, and otherwise simplify the logic in our tests.\n-->\n\n<docmeta name=\"displayName\" value=\"Writing tests\">\n"
  },
  {
    "path": "docs/contributing/contributing-to-the-documentation.md",
    "content": "# Contributing to the documentation\nThe official documentation on the Sails website is compiled from markdown files in the [sails](https://github.com/balderdashy/sails/sails-docs) repo. Please send a pull request to the **master** branch with amendments and they'll be double-checked and merged as soon as possible.\n\nWe are open to suggestions about the process we're using to manage our documentation, and to working with the community in general.  Please post to the [Gitter](https://gitter.im/balderdashy/sails) with your ideas; or, if you're interested in helping directly, contact @fancydoilies or @mikermcneil on Twitter.\n\n#### What branch should I edit?\n\nThat depends on what kind of edit you are making.  Most often, you'll be making an edit that is relevant for the latest stable version of Sails (i.e. the version on [NPM](npmjs.org/package/sails)) and so you'll want to edit the `master` branch of _this_ repo (what you see in the sails repo by default).  The docs team merges master into the appropriate branch for the latest stable release of Sails, and then deploys that to sailsjs.com about once per week.\n\nOn the other hand, if you are making an edit related to an unreleased feature in an upcoming version&mdash;usually as an accompaniment a feature proposal or open pull request to Sails or a related project&mdash;then you will want to edit the branch for the next, unreleased version of Sails (sometimes called \"edge\").\n\n\n| Branch (in `sails` or `sails-docs`)                    | Documentation for Sails Version...                                   | Preview At...      |\n|-------------------------------------------------------------------------------------|------------------------|:-------------------|\n| [`master`](https://github.com/balderdashy/sails/tree/master/docs) | [![NPM version](https://badge.fury.io/js/sails.png)](http://badge.fury.io/js/sails) | [preview.sailsjs.com](http://preview.sailsjs.com)\n| [`0.12`](https://github.com/balderdashy/sails-docs/tree/0.12) | Sails v0.12.x | [sailsjs.com](https://sailsjs.com)\n| [`0.11`](https://github.com/balderdashy/sails-docs/tree/0.11) | Sails v0.11.x           | [0.11.sailsjs.com](http://0.11.sailsjs.com)\n\n\n#### How are these docs compiled and pushed to the website?\n\nWe use a module called `doc-templater` to convert the .md files to the HTML for the website. You can learn more about how it works in [the doc-templater repo](https://github.com/uncletammy/doc-templater).\n\nEach .md file has its own page on the website (e.g. all reference, concepts, and anatomy files), and should include a special `<docmeta name=\"displayName\">` tag with a `value` property specifying the title for the page.  This will impact how the doc page appears in search engine results, and it will also be used as its display name in the navigation menu on sailsjs.com.  For example:\n\n```markdown\n<docmeta name=\"displayName\" value=\"Building Custom Homemade Puddings\">\n```\n\n#### When will my change appear on the Sails website?\n\nDocumentation changes go live when they are merged onto a special branch corresponding with the current stable version of Sails (e.g. 0.12). We cannot merge pull requests sent directly to this branch&mdash;its sole purpose is to reflect the content currently hosted on sailsjs.com, and content is only merged just before redeploying the Sails website.\n\nIf you want to see how documentation changes will appear on sailsjs.com, you can visit [preview.sailsjs.com](http://preview.sailsjs.com). The preview site updates itself automatically as changes are merged into the master branch of sails.\n\n\n#### How can I help translate the documentation?\n\nA great way to help the Sails project, especially if you're a native speaker of a language other than English, is to volunteer to translate the Sails documentation.\n\nIf you are interested in beginning a translation project, follow these steps:\n\n+ Bring the documentation folder from the [sails repo](https://github.com/balderdashy/sails/tree/master/docs) (`balderdashy/sails/docs`) into a new repo named `sails-docs-{{IETF}}` where {{IETF}} is the [IETF language tag](https://en.wikipedia.org/wiki/IETF_language_tag) for your language.\n+ Edit [the documentation README](https://github.com/balderdashy/sails/tree/master/docs) to summarize your progress so far, provide any other information you think would be helpful for others reading your translation, and let interested contributors know how to contact you.\n+ When you are satisfied with the first complete version of your translation, open an issue and someone from our docs team will be happy to help you preview it in the context of the Sails website, get it live on a domain (yours, or a subdomain of sailsjs.com, whichever makes the most sense), and share it with the rest of the Sails community.\n\n\n<docmeta name=\"displayName\" value=\"Contributing to the docs\">\n"
  },
  {
    "path": "docs/contributing/contributors-pledge.md",
    "content": "# Contributor's pledge\n\nBy making a contribution to this project, I certify that:\n\n* (a) The contribution was created in whole or in part by me and I\n  have the right to submit it under the MIT license; or\n* (b) The contribution is based upon previous work that, to the best\n  of my knowledge, is covered under an appropriate open source license\n  and I have the right under that license to submit that work with\n  modifications, whether created in whole or in part by me, under the\n  same open source license (unless I am permitted to submit under a\n  different license), as indicated in the file; or\n* (c) The contribution was provided directly to me by some other\n  person who certified (a), (b) or (c) and I have not modified it.\n\n> The certificate of origin above is based on the \"[Developer's Certificate of Origin 1.0](https://github.com/nodejs/node/blob/master/CONTRIBUTING.md#developers-certificate-of-origin-10)\" used by Node.js core.\n\n<docmeta name=\"displayName\" value=\"Contributor's pledge\">\n"
  },
  {
    "path": "docs/contributing/core-maintainers.md",
    "content": "# Core maintainers\n\nThe Sails.js core maintainers constitute a small team of individuals located in Austin, TX who are passionate about making it easier for everyone to develop scalable, secure, custom web applications.  We fell in love with Node.js at first sight and are firm believers in the continued, unprecedented dominance of JavaScript as a unifying force for good.  We see Node.js as the logical continuation of the web standards movement into the world of server-side development.\n\nThe Sails core team maintains the framework and its related sub-projects, including the Waterline ORM, the Node-Machine project, the Skipper body parser, and all officially-supported generators, adapters, and hooks.  We rely heavily on the help of a network of contributors and users all over the world, but we make all final decisions about our releases and roadmap.\n\n\n#### History\n\nSails.js was originally developed by [Mike McNeil](http://twitter.com/mikermcneil) with the help of his company [Balderdash](http://www.bizjournals.com/sanantonio/blog/socialmadness/2013/03/sxsw-2013-Balderdash-startup-web-app.html), a small development and design studio in Austin, TX.  The first stable version of Sails was released as open source in 2012.  Today, it is still actively maintained by the same [core team members](https://sailsjs.com/about), along with the help of many amazing [contributors](https://github.com/balderdashy/sails/network/members).\n\n\n#### Financial Support\n\nToday, Sails.js is financially supported by [The Sails Company](https://sailsjs.com/about) ([YC W15](http://techcrunch.com/2015/03/11/treeline-wants-to-take-the-coding-out-of-building-a-backend/)).  Please feel free to [contact us directly](https://sailsjs.com/contact) with questions about the company, our [team](https://sailsjs.com/about), or our mission.\n\n<docmeta name=\"displayName\" value=\"Core maintainers\">\n"
  },
  {
    "path": "docs/contributing/intro-to-custom-adapters.md",
    "content": "# Introduction to custom adapters for Sails/Waterline\n\n> ##### Stability: Varies\n\n## Reference\n\nPlease see the [adapter interface specification](https://github.com/balderdashy/sails/blob/master/docs/contributing/adapter-specification.md).\n\n\n\n### What is an adapter?\n\n Adapters expose **interfaces**, which imply a conract to implemnt certain functionality.  This allows us to guarantee conventional usage patterns across multiple models, developers, apps, and even companies, making app code more maintainable, efficient, and reliable.  Adapters are useful for integrating with databases, open APIs, internal/proprietary web services, or even hardware.\n\n\n### What kind of things can I do in an adapter?\n\nAdapters are mainly focused on providing model-contextualized CRUD methods.  CRUD stands for create, read, update, and delete.  In Sails/Waterline, we call these methods `create()`, `find()`, `update()`, and `destroy()`.\n\nFor example, a `MySQLAdapter` implements a `create()` method which, internally, calls out to a MySQL database using the specified table name and connection information and runs an `INSERT ...` SQL query.\n\nIn practice, your adapter can really do anything it likes-- any method you write will be exposed on the raw datastore objects and any models which use them.\n\n\n\n\n## Why would I need a custom adapter?\n\nWhen building a Sails app, the sending or receiving of any asynchronous communication with another piece of hardware can be normalized into an adapter.  (viz. API integrations)\n\n> **From Wikipedia:**\n> *http://en.wikipedia.org/wiki/Create,_read,_update_and_delete*\n\n> Although a relational database provides a common persistence layer in software applications, numerous other persistence layers exist. CRUD functionality can be implemented with an object database, an XML database, flat text files, custom file formats, tape, or card, for example.\n\nIn other words, Waterline is not just an ORM for your database.  It is a purpose-agnostic, open standard and toolset for integrating with all kinds of RESTful services, datasources, and devices, whether it's LDAP, Neo4J, or [a lamp](https://www.youtube.com/watch?v=OmcQZD_LIAE).\nI know, I know... Not everything fits perfectly into a RESTful/CRUD mold!  Sometimes the service you're integrating with has more of an RPC-style interface, with one-off method names.  That's ok-- you can define any adapter methods you like! You still get all of the trickle-down config and connection-management goodness of Waterline core.\n\n\n\n## Why should I build a custom adapter?\n\nTo recap, writing your API integrations as adapters is **easier**, takes **less time**, and **absorbs a considerable amount of risk**, since you get the advantage of a **standardized set of conventions**, a **documented API**, and a **built-in community** of other developers who have gone through the same process.  Best of all, you (and your team) can **reuse the adapter** in other projects, **speeding up development** and **saving time and money**.\n\nFinally, if you choose to release your adapter as open-source, you provide a tremendous boon to our little framework and our budding Sails.js ecosystem.  Even if it's not via Sails, I encourage you to give back to the OSS community, even if you've never forked a repo before-- don't be intimidated, it's not that bad!\n\nThe more high-quality adapters we collectively release as open-source, the less repetitive work we all have to do when we integrate with various databases and services.  My vision is to make building server-side apps more fun and less repetitive for everyone, and that happens one community adapter at a time.\n\nI tip my hat to you in advance :)\n\n\n\n\n## What is an Adapter Interface?\n\nThe functionality of adapters is as varied as the services they connect.  That said, there is a standard library of methods, and a support matrix you should be aware of.  Adapters may implement some, all, or none of the interfaces below, but rest assured that **if an adapter implements one method in an interface, it should implement *all* of them**.  This is not always the case due to limitations and/or incomplete implementations, but at the very least, a descriptive error message should be used to keep developers informed of what's supported and what's not.\n\n\n##### Class methods\nBelow, `class methods` refer to the static, or collection-oriented, functions available on the model itself, e.g. `User.create()` or `Menu.update()`.  To add custom class methods to your model (beyond what is provided in the adapters it implements), define them as top-level key/function pairs in the model object.\n\n##### Instance methods\n`instance methods` on the other hand, (also known as object, or model, methods) refer to methods available on the individual result models themselves, e.g. `User.findOne(7).done(function (err, user) { user.someInstanceMethod(); });`.  To add custom instance methods to your model (beyond what is provided in the adapters it implements), define them as key/function pairs in the `attributes` object of the model's definition.\n\n##### DDL and auto-migrations\n`DDL` stands for data-definition language, and is a common fixture of schema-oriented databases.  In Sails, auto-migrations are supported out of the box.  Since adapters for the most common SQL databases support `alter()`, they also support automatic schema migration!  In your own adapter, if you write the `alter()` method, the same behavior will take effect.  The feature is configurable using the `migrate` property, which can be set to `safe` (don't touch the schema, period), `drop` (recreate the tables every time the app starts), or `alter` (the default-- merge the schema in the apps' models with what is currently in the database).\n\n\n\n\n\n\n## Offcially supported adapters\n\nCody, Mike, and the team behind Sails.js at Balderdash support a handful of commonly used adapters.\n\n### Disk\n\n\nWrite to your computer's hard disk, or a mounted network drive.  Not suitable for at-scale production deployments, but great for a small project, and essential for developing in environments where you may not always have a database set up. This adapter is bundled with Sails and works out of the box with zero configuration.\n\n###### Interfaces implemented:\n+ Semantic\n+ Queryable\n+ Streaming\n\n\n### Memory\n\nPretty much like Disk, but doesn't actually write to disk, so it's not persistent.  Not suitable for at-scale production deployments, but useful when developing on systems with little or no disk space.\n\n###### Interfaces implemented:\n+ Semantic\n+ Queryable\n+ Streaming\n\n\n### MySQL\n\nMySQL is the world's most popular relational database.\nhttp://en.wikipedia.org/wiki/MySQL\n\n###### Interfaces implemented:\n+ Semantic\n+ Queryable\n+ Streaming\n+ Migratable\n\n\n### PostgreSQL\n\n[PostgreSQL](http://en.wikipedia.org/wiki/PostgreSQL) is another popular relational database.\n\n###### Interfaces implemented:\n+ Semantic\n+ Queryable\n+ Streaming\n+ Migratable\n\n\n### MongoDB\n\n[MongoDB](http://en.wikipedia.org/wiki/MongoDB) is the leading NoSQL database.\n\n###### Interfaces implemented:\n+ Semantic\n+ Queryable\n+ Streaming\n\n### Redis\n\n[Redis](http://redis.io/) is an open source, BSD licensed, advanced key-value store.\n\n###### Interfaces implemented:\n+ Semantic\n+ Queryable\n\n\n\n> Under active development:\n>\n> + sails-s3\n> + sails-local-fs\n\n\n\n\n\n\n## Notable Community Adapters\n\n> ##### Stability: Varies\n> in various states of completion\n\n\nCommunity adapters are crucial to the success and central to the philosophy of an open ecosystem for API integrations.  The more high-quality adapters you release as open-source, the less repetitive work we all have to do when we integrate with various databases and services.  My vision is to make building server-side apps more fun and less repetitive for everyone, and that happens one community adapter at a time.  We welcome your support!\n\n\n### [Mandrill (email-sending service by MailChimp)](https://github.com/mikermcneil/sails-mandrill)\n+ One-Way\n\n### Heroku\n> Not currently available as open-source.\n\n### Git\n> Not currently available.\n\n### [CouchDB](https://github.com/craveprogramminginc/sails-couchdb)\n+ Semantic\n\n### [Riak](https://npmjs.org/package/sails-riak)\n+ Semantic\n\n### [REST](https://github.com/zohararad/sails-rest)\n+ Semantic\n\n### [IRC](https://github.com/balderdashy/sails-irc)\n+ Pubsub\n\n### [Twitter](https://github.com/balderdashy/sails-twitter)\n\n### [ElasticSearch](https://github.com/UsabilityDynamics/waterline-elasticsearch)\n+ Semantic\n\n### [JSDom](https://github.com/mikermcneil/sails-jsdom)\n\n### [Yelp](https://github.com/balderdashy/sails-adapter-boilerplate/pull/2)\n\n### [OrientDB](https://github.com/appscot/sails-orientdb)\n\n[OrientDB](http://en.wikipedia.org/wiki/OrientDB) is an Open Source NoSQL DBMS with the features of both Document and Graph DBMSs.\n\n###### Interfaces implemented:\n+ Semantic\n+ Queryable\n+ Associations\n+ Migratable\n\n\n> Search google and NPM for more-- there are new adapters being written all the time.\n\n\n\n> Check out the docs to learn how to write your own custom adapter (whether it's a private, internal project for a proprietary API or something you can share as open-source)\n\n\n> Want to see your adapter listed here?  Send a pull request with a link and we'll merge it!\n<docmeta name=\"notShownOnWebsite\" value=\"true\">\n"
  },
  {
    "path": "docs/contributing/issue-contributions.md",
    "content": "# Issue contributions\n\nWhen opening new issues or commenting on existing issues in any of the repositories in this GitHub organization, please make sure discussions are related to concrete technical issues of the Sails.js software.  Feature requests and ideas are always welcome, but they should not be submitted as GitHub issues.  See [Requesting Features](https://sailsjs.com/documentation/contributing/proposing-features-enhancements) below for submission guidelines.\n\nFor general help using Sails, please refer to the [official Sails documentation](https://sailsjs.com/documentation).  For additional help, ask a question on [StackOverflow](http://stackoverflow.com/questions/ask) or refer to any of the [other recommended avenues of support](https://sailsjs.com/support).\n\nIf you have found a security vulnerability in Sails or any of its dependencies, _do not report it in a public issue_.  Instead, alert the core maintainers immediately using the instructions detailed in the [Sails Security Policy](https://sailsjs.com/security).  Please observe this request _even for  external dependencies not directly maintained by the core Sails.js team_ (e.g. Socket.io, Express, Node.js, or openssl).  Whether or not you believe the core team can do anything to fix an issue, please follow the instructions in our security policy to privately disclose the vulnerability as quickly as possible.\n\nFinally, discussion of a non-technical nature, including subjects like team membership, trademark, code of conduct, and high-level questions or concerns about the project should be sent directly to the core maintainers by emailing [inquiries@sailsjs.com](inquiries@sailsjs.com).\n\n#### Opening an issue\n\n> Sails is composed of a number of different sub-projects, many of which have their [own dedicated repository](https://sailsjs.com/architecture).  Even so, the best place to submit a suspected issue with a module maintained by the Sails core team is in the main Sails repo.  This helps us stay on top of issues and keep organized.\n\nBefore submitting an issue, please follow these simple instructions:\n\n<a name=\"issue-instructions\"></a>\n\nFirst, search for issues similar to yours in [GitHub search](https://github.com/balderdashy/sails/search?type=Issues) within the main Sails repo.\n  - If your original bug report is covered by an existing open issue, then add a comment to that issue instead of opening a new one.\n  - If all clearly related issues are closed, then open a new issue and paste links to the URLs of the already closed issue(s) at the bottom.\n  - If you cannot find any related issues, try using different search keywords, if appropriate (in case this affects how you search, at the time of this writing, GitHub uses ElasticSearch, which is based on Lucene, to index content).  If you still cannot find any relevant existing issues, then create a new one.\n  - Please consider the importance of backlinks.  A contributor responding to your issue will almost always need to search for similar existing issues theirself, so having the URLs all in one place is a huge time-saver.  Also keep in mind that backlinking from new issues causes GitHub to insert a link to the URL of the new issue in referenced original issues automatically.  This is very helpful, since many visitors to our GitHub issues arrive from search engines.\n\nOnce you've determined that a new issue should be created,\n+ Make sure your new issue does not report multiple unrelated problems.\n  - If you are experiencing more than one problem&mdash;and the problems are clearly distinct&mdash;create a separate issue for each one, but start with the most urgent.\n  - If you are experiencing multiple related problems (problems that you have only been able to reproduce in tandem), then please create only a single issue. Be sure to describe both problems thoroughly, though, as well as the steps necessary to cause them both to appear.\n\n+ Check that your issue has a concise, on-topic title that uses polite, neutral language to explain the problem as best you can in the available space. The ideal title for your issue is one that communicates the problem at a glance.\n  - For example, _\"jst.js being removed from layout.ejs on lift\"_ is a **very helpful** title for an issue.\n  - Here are some **non-examples**&mdash;that is, examples of issue titles which are **not helpful**:\n    - _\"templates dont work\"_ : This title is too vague. Even if more information cannot be gleaned, wording like _\"unexpected behavior with templates\"_ is a little more specific and would likely generate a quicker response.\n    - _\"app broken cannot access templates on filesystem because it is broken in the asset pipeline please help\"_ : This title is repetative and contains unnecessary content (\"_please help_\"). Remember that a useful title is both desciptive and concise.\n    - _\"jst.js is being REMOVED!!!!!!!!!\"_: This title contains unnecessary capitalization and punctuation, which is distracting at best, and may be perceived as impolite. In either case, it's unlikely to speed the response to your issue.\n    - _\"How does this dumb, useless framework remove jst.js from my app?\"_: This title contains unnecessary negativity, which doesn't encourage participant review. Try keeping titles as objective as possible for the best possible issue resolution experience.\n    - _\"Thousands of files being corrupted in our currently deployed production app every time the server crashes.\"_: Language like this might be perceived as hyperbolic and could lessen the credibility of your claim. In this instance, it may even confuse the issue (e.g. \"Is this only happening when NODE_ENV===production?\").\n\n+ Before putting together steps to reproduce your issue, normalize as many of the variables on your personal development environment as possible:\n  - Make sure you have the right app lifted.\n  - Make sure you've killed the Sails server with CTRL+C and started it again.\n  - Make sure you do not have any open browser tabs pointed at localhost.\n  - Make sure you do not have any other Sails apps running in other terminal windows.\n  - Make sure the app you are using to reproduce the issue has a clean `node_modules/` directory, meaning:\n    - no dependencies are linked (e.g. you haven't run `npm link foo`)\n    - you haven't made any inline changes to files in the `node_modules/` folder\n    - you don't have any weird global dependency loops\n    The easiest way to double-check any of the above, if you aren't sure, is to run: `rm -rf node_modules && npm cache clear && npm install`.\n\n+ Remember to provide the version of Sails that your app is using (`sails -v`).\n  - Note that this could be different than the version of Sails you have globally installed.\n\n+ Provide your currently-installed version of Node.js (`node -v`), your version of NPM (`npm -v`), and the operating system that you are running (OS X, Windows, Ubuntu, etc.)\n  - If you are using `nvm` or another Node version manager like `n`, please be sure to mention that in the issue.\n\n+ Provide detailed steps to reproduce the problem from a clean Sails app (i.e. an app created with `sails new` on a computer with no special environment variables or `.sailsrc` files)\n\n+ Finally, take a moment to think about what you are about to post and how it will be interpreted by the rest of the Sails userbase.  Make sure it is aligned with our Code of Conduct, and make sure you are not endangering other Sails users by posting a [security vulnerability](https://sailsjs.com/security) publicly.\n\nIssues which do not meet these guidelines will usually be closed without being read, with a response asking that the submitter review this contribution guide.  If this happens to you, _realize that it's nothing personal_, and that it may even happen again.  Please understand that Sails is a large project that receives hundreds of new issue submissions every month, and that we truly appreciate the time you donate to post detailed issues.  The more familiar you become with the conventions and ground rules laid out in this contribution guide, the more helpful your future contributions will be for the community.  You will also earn the respect of core team members and set a good example for future contributors.\n\n> You might think of these rules as guardrails on a beautiful mountain road: they may not always be pretty, and if you run into them you may get banged up a little bit, but, collectively, they keep us all from sliding off a turn and into the abyss.\n\n<docmeta name=\"displayName\" value=\"Issue contributions\">\n"
  },
  {
    "path": "docs/contributing/preface.md",
    "content": "# Contributing to Sails\n\nThis guide is designed to help you get off the ground quickly contributing to Sails.  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 here helps make the core maintainers of Sails more productive, and makes the experience of working with Sails positive and enjoyable for the community at large.\n\nIf you are working on a pull request, **please carefully read the this guide in its entirety**. In case of doubt, [open an issue on GitHub](https://github.com/balderdashy/sails/issues/new) or contact someone from our [core team](https://sailsjs.com/about) 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.\n\n> Note that unless otherwise specified, the content in this section is either straight from the hearts of the Sails.js core team, or based on the [Node.js contribution guide](https://github.com/joyent/node/blob/master/CONTRIBUTING.md#contributing).\n\n<docmeta name=\"displayName\" value=\"Contributing to Sails\">\n<docmeta name=\"isOverviewPage\" value=\"true\">\n"
  },
  {
    "path": "docs/contributing/proposing-features/proposing-features.md",
    "content": "# Proposing features and enhancements\n\nSails contributors have learned over the years that keeping track of feature requests in the same bucket as potentially-critical issues leads to a dizzying number of open issues on GitHub, and makes it harder for the community as a whole to respond to bug reports.  It also introduces a categorization burden: Imagine a GitHub issue that is 2 parts feature request, 3 parts question, but also has a _teensie pinch_ of immediately-relevant-and-critical-issue-with-the-latest-stable-version-of-Sails-that-needs-immediate-attention.\n\nIf suggestions, requests, or pleas for features or enhancements are submitted as GitHub issues, they will be closed by [sailsbot](http://asksailsbot.tumblr.com/) or one of her lackeys in the Sails core team.  This doesn't mean the core team does not appreciate your willingness to share your experience and ideas with us; we just ask that you use our new process.  Instead of creating a GitHub issue, please submit your proposal for a new feature or an extension to an existing feature using the process outlined under [Submitting a Proposal](https://sailsjs.com/documentation/contributing/proposing-features-enhancements/submitting-a-proposal).\n\nPlease **do not propose _changes to the established conventions or default settings_ of Sails**. These types of discussions tend to start \"religious wars\" about topics like EJS vs. Jade, Grunt vs. Gulp, Express vs. Hapi, etc., and managing those arguments creates rifts and consumes an inordinate amount of contributors' time.  Instead, if you have concerns about the opinions, conventions or default configuration in Sails, please [contact the core maintainers directly](mailto:inquiries@sailsjs.com).\n\n<docmeta name=\"displayName\" value=\"Proposing features/enhancements\">\n"
  },
  {
    "path": "docs/contributing/proposing-features/submitting-a-proposal.md",
    "content": "# Submitting a proposal\n\nBefore submitting a new proposal, please consider the following:\n\nMany individuals and companies (large and small) are happily using Sails in production projects (both greenfield and mature) with the currently-released feature set today, as-is.  A lot of the reason for this is that Sails was built while the core team was running a development shop, where it was used to take many different kinds of applications from concept to production, and then to serve as the backend for those applications as they were maintained over the next few years.\n\nMuch like the canonical case of Ruby on Rails, this means that Sails was designed from the beginning to be both developer-friendly and enterprise-friendly using a convention over configuration methodology.  **Conventions** make it quick and easy to build new Sails apps and switch between different existing Sails apps, while **configurability** allows Sails developers to be flexible and customize those apps as they mature using the full power of the underlying tool chain (configuration, plugins/overrides, Express, Socket.io, Node.js, and JavaScript).\n\nOver the first year of Sails's life, the **configurability** requirement became even more important.  As the user base grew and Sails started to be used on all sorts of different projects, and by developers with all sorts of different preferences, the number of feature requests skyrocketed.  Sails solved this in 2013 by rewriting its core and becoming innately interoperable:\n\n+ Since Sails apps are just Node apps, you can take advantage of any of the [millions](bit.ly/npm-numbers) of NPM packages on http://npmjs.org.  (And more recently, you can also take advantage of any of the hundreds of automatically-documented machine functions curated from NPM at http://node-machine.org)\n+ Since Sails uses the same req/res/next pattern as Express and Connect, you can take advantage of any middleware written for those middleware frameworks in your app, such as Lusca (security middleware from Paypal) or morgan (HTTP logging util).\n+ Since Sails uses [Consolidate](https://github.com/tj/consolidate.js/), you can use any of the view engines compatible with Express such as Jade, Dust or Handlebars.\n+ Since Sails uses a familiar MVC project structure, you and/or other developers on your team can quickly get up to speed with how the app works, the database schema, and even have a general notion of where common configuration options live.\n+ Since Sails uses Grunt, you can install and use any of the thousands of available Grunt plugins on http://gruntjs.com/plugins in your app.\n+ Sails's hook system allows you to disable, replace, or customize large swaths of functionality in your app, including pieces of Sails core, such as replacing Grunt with Gulp.\n+ Waterline's adapter interface allows you to plug your models into any database such as Oracle, MSSQL, or Orient DB.\n+ Skipper's adapter interface allows you to plug your incoming streaming file uploads into any blob storage container such as S3, GridFS, or Azure.\n+ Sails's generator system allow you to completely control all files and folders that the Sails command-line tool generates when you run `sails new` or `sails generate *`.\n\nIt is important to realize that today, most (but certainly not all) new features in Sails can be implemented using one or more of the existing plugin interfaces, rather than making a change to core.  If the feature you are requesting is an exception to that rule, then please proceed-- but realize that perhaps the most important part of your proposal is a clear explanation of why what you're suggesting is not possible today.\n\nThe core maintainers of Sails review all feature proposals, and we do our best to participate in the discussion in these PRs.  However, many of these proposals can sometimes involve back and forth discussion that could require them to be open for months at a time.  So it is important to understand going in that if you are proposing a feature, the onus is on you to fully specify how that feature would work; i.e. how it would be used, how it would be configured, and in particular its implementation-- that is, which modules would need to change to make it a reality, how it would be tested, whether it would be a major or minor-version breaking change, and the additions and/or modifications that would be necessary to the official Sails documentation.\n\nWith that in mind, to submit a proposal for a new feature, or an extension to an existing feature, please take the following steps:\n\n\n0. First, look at the `backlog` table in [ROADMAP.MD](https://github.com/balderdashy/sails/blob/master/ROADMAP.md) and also search open pull requests in that file to make sure your change hasn't already been proposed.\n  - If the PR (pull request) has been merged, it means that a core maintainer has (A) looked over the proposal and discussion in the pull request, (B) personally agreed to him or herself that the feature would be a good fit for Sails core, and (C) confirmed the decision with [@mikermcneil](https://github.com/mikermcneil).  It also means that the proposal is now in the backlog in ROADMAP.md, which means that the core team would be willing to merge a pull request with code changes adding the feature to Sails core (assuming that pull request follows our coding style conventions and the guidelines in this section).\n  - If the PR has been closed without being merged, it means that the core team has decided that the feature request should not be a part of Sails core.  Just because the proposal is closed does not mean the feature will never be achievable in Sails, it just means that (A) it would need to be specced differently to be merged or (B) it would need to be implemented as a plugin (i.e. a hook, adapter, generator, view engine, grunt/gulp task, etc.)\n  - If the PR is _open_, it means that either (A) it was recently posted, (B) there is still an active discussion in progress, (C) that a core maintainer has not had time to look into it yet, or most commonly (D) that one or more core maintainers have looked at and potentially even responded to the proposal, but the team decided there wasn't enough information to make a firm \"yes\" or \"no\" judgement call.  This fourth scenario is quite common, since it sometimes takes a great deal of time to develop a specification that is thorough enough to merge into the backlog.  The core maintainers review and contribute to proposals as much as time allows, but ultimately it is the responsibility of the developers requesting a feature to do the work of fully speccing it out.\n  - While some of Sails's core maintainers carefully filter email from GitHub (because they also like to get other email sometimes), many contributors receive GitHub notifications every time a new comment is posted.  Out of respect for them, please do not `*bump*` or `:+1:` feature proposals.   Instead, write a concise (3-5 sentences) explanation of your real-world use case for the feature.\n1. If it doesn't already exist, create a pull request editing [ROADMAP.MD](https://github.com/balderdashy/sails/blob/master/ROADMAP.md) (the easiest way to do this is opening ROADMAP.md while logged in to GitHub and clicking the \"Edit\" button).\n2. Add a new row to the **Backlog** table with a very short description of the feature, then submit the change as a pull request (the easiest way to do this is to use the GitHub UI as discussed above, make your changes, then follow the on-screen instructions).\n3. In the description for your pull request:\n  - First, write out a high-level summary of the feature you are proposing as a concise description (3-5 sentences) focused around a convincing real-world use case where the Sails app you are building or maintaining for your job, your clients, your company, your non-profit work, or your independent hobby project would be made easier by this feature or change.\n  - Next, describe in clear prose with relevant links to code files exactly why it would be difficult or impossible to implement the feature without changing Sails core (i.e. using one or more of the existing plugin mechanisms).  If this is not the case, and this feature could be implemented as a plugin, then please reconsider writing your proposal (it is unlikely the core team will be able to accept it).  If you are the author of one or more plugins, and feel that you or other users would benefit from having your work in Sails core, please contact the core team directly (see the instructions for submitting \"high-level questions or concerns about the project\" above).\n  - Finally, if you have time, take a first pass at proposing a spec for this feature (its configuration, usage, and how it would be implemented).  If you do not have time to write out a first draft of a thorough specification, please make that point in your feature request, and clarify that it would be up to other contributors with the same or a similar use case to finish this proposal.\n\n\nProposals which do not meet these guidelines will be closed with a response asking that the submitter review this contribution guide.  If this happens to you, _realize it is nothing personal_ and that it may even happen again.  Please consider that a tremendous amount of effort has been put into the existing plugin systems in Sails, and so any proposed change to core must be carefully considered in relation to how it would affect existing plugins, existing apps, and future development of the framework.  Many Sails contributors have become intimately familiar with how the various systems in Sails interact and will be willing to help you out; but in order for that process to be efficient, it is important that all new features and enhancements follow a common set of ground rules.\n\n> ###### If your feature proposal is merged...\n> Having your proposal merged does not necessarily mean that you are responsible for _implementing_ the feature; and you certainly won't be responsible for _maintaining_ future changes which might affect that feature for all eternity.  _That_ privilege is reserved for Mike and the rest of the core team; which is why it is so important to spec out the vision for the usage, configuration, and implementation of your proposed feature from day 1.  Working out this sort of a detailed proposal is not an easy task, and often involves more effort than the actual implementation.  But if a proposal is accepted, it becomes part of the project's mission: which means once it is implemented and merged, the core team is committed to maintaining it as a part of Sails.\n\n<docmeta name=\"displayName\" value=\"Submitting a proposal\">\n"
  },
  {
    "path": "docs/contributing/stability-index.md",
    "content": "# Stability index\n\nThroughout the documentation and in README files in Sails, you will see indications of a section's stability. The Sails framework is still somewhat changing, and as it matures, certain parts are more reliable than others. Some are so proven, and so relied upon, that they are unlikely to ever change at all. Others are brand new and experimental, or known to be hazardous and in the process of being redesigned.\n\nStability indices are used to describe individual methods, events, and configuration settings _as well_ as sub-modules of Sails core such as core hooks.  The latter affordance is a soft science-- the core team labels hooks with stability indices in order to provide a better experience for developers building plugins for Sails and/or contributing to Sails core.\n\nWhen a stability index refers to a module like a core hook, note that that index refers to the **features of that hook which are _explicitly public_**.  For example, if the documentation for a hook mentions that it \"exposes\" a property called `foo` on the `sails` app object, then you can _only rely on that property_ to respect the hook's the stability level if it is also clearly marked as \"public\" elsewhere in the hook documentation.  If in doubt, submit a pull request to the relevant hook's README file in the [GitHub repository for Sails core](https://github.com/balderdashy/sails) and add a question to the FAQ section.\n\nThe stability indices are as follows:\n\n##### Stability: 0 - Deprecated\nThis feature is known to be problematic, and changes are planned.  Do not rely on it in new code, and be sure to change existing code before upgrading.  Use of the feature may cause warnings.  Backwards compatibility should not be expected.\n\n##### Stability: 1 - Experimental\nThis feature is subject to change or removal in future major releases of Sails.\n\n##### Stability: 2 - Stable\nThis feature has proven satisfactory. Compatibility with existing Sails apps and the plugin ecosystem is a high priority, and so stable hooks/features/etc. will not be broken or removed in future major releases unless absolutely necessary.\n\n##### Stability: 3 - Locked\nThis hook/feature/etc. will not undergo any future API changes, except as demanded by critical fixes related to security or performance.  Please do not propose usage/philosophical changes for features/hooks/etc. at this stability index; they will be refused.\n\n\n\n### Notes\n> - Sails' stability index, and much of the verbiage of this file, is based on [the Stability Index used by Node.js core](https://nodejs.org/api/documentation.html#documentation_stability_index).\n\n<docmeta name=\"notShownOnWebsite\" value=\"true\">\n"
  },
  {
    "path": "docs/faq/README.md",
    "content": "# docs/faq\n\nThis section contains the contents that will live on sailsjs.com/faq.\n\n\n### Notes\n> - This README file **is not compiled to HTML** for the website.  It is just here to explain what you're looking at.\n"
  },
  {
    "path": "docs/faq/faq.md",
    "content": "# Frequently Asked Questions\n\n### Table of Contents\n1. [I'm having trouble installing Sails. What should I do?](https://sailsjs.com/faq#?im-having-trouble-installing-sails-what-should-i-do)\n2. [What are the dependencies of Sails?](https://sailsjs.com/faq#?what-are-the-dependencies-of-sails)\n3. [Who else is using Sails.js?](https://sailsjs.com/faq#?who-else-is-using-sailsjs)\n4. [Are there professional support options?](https://sailsjs.com/faq#?are-there-professional-support-options)\n5. [Where do I get help?](https://sailsjs.com/faq#?where-do-i-get-help)\n6. [What are some good community tutorials?](https://sailsjs.com/faq#?what-are-some-good-community-tutorials)\n7. [How can I convince the other girls/guys on my team?](https://sailsjs.com/faq#?how-can-i-convince-the-other-girls-guys-on-my-team)\n8. [Where do I submit ideas?  Report bugs?](https://sailsjs.com/faq#?where-do-i-submit-ideas-report-bugs)\n9. [What version of Sails should I use?](https://sailsjs.com/faq#?what-version-of-sails-should-i-use)\n10. [How do I get involved?](https://sailsjs.com/faq#?how-do-i-get-involved)\n11. [How does the documentation end up on the Sails website?](https://sailsjs.com/faq#?how-does-the-documentation-end-up-on-the-sails-website)\n12. [Where is the documentation for the different releases of Sails?](https://sailsjs.com/faq#?where-is-the-documentation-for-the-different-releases-of-sails)\n\n### I'm having trouble installing Sails. What should I do?\n\nStart with NPM's helpful [troubleshooting guide](https://github.com/npm/npm/wiki/Troubleshooting).  If you continue to have problems, and you've tried Google searching but you're still stumped, please carefully review the updated Sails [contribution guide](https://sailsjs.com/documentation/contributing) and then create a GitHub issue in the Sails repo.\n\n\n### What are the dependencies of Sails?\n\n[![Dependency Status](https://david-dm.org/balderdashy/sails.png)](https://david-dm.org/balderdashy/sails)\n\nWe have learned again and again over the years to take versioning of dependencies very seriously.  We lock Sails's dependency versions and only bump those versions if the associated updates fix a security issue or present other substantive advantages to Sails users (improved compatibility, performance, etc.)  In addition, the core maintainers of Sails are committed to fixing any major security, performance, or stability bugs that arise in any of our core dependencies-- regardless of whether those modules are [officially maintained by another entity or not](https://github.com/balderdashy/sails/pull/3235#issuecomment-170417122).\n\nSails is tested with [node](http://nodejs.org/) versions 0.10.x and up, though, we recommend using The latest LTS version of Node.  The framework is built on the rock-solid foundations of [Express](https://github.com/expressjs/) and [Socket.io](http://socket.io/).  Out of the box, it also depends on other great modules, like `grunt`, `waterline`, and `fs-extra`.  Click the badge above for the full list of dependencies in the latest stable release of Sails core.\n\n> **Sails Flagship users:** We manually verify every dependency of Sails and other officially-maintained modules by hand, every single week.  This includes core hooks, adapters, generators, client SDKs, and Flagship packages.   We regularly send security/compatibility reports about dependencies to the primary email address associated with your account.  If you'd like additional people on your team to receive these reports, no problem!  Just [let us know](https://flagship.sailsjs.com/ask) their email addresses and we'll get them set up.  _(These email addresses will also receive communications about patches, shrinkwrap updates, and compatibility notices.)_\n\nIf you have questions or concerns about our dependencies, [talk to a core team member](https://sailsjs.com/contact).  _Please do not submit a pull request changing the version of a dependency without first (1) checking that dependency's changelog, (2) verifying compatibility, and (3) [submitting an accompanying PR to update **roadstead**](https://github.com/treelinehq/roadstead/edit/master/constants/verified-releases.type.js), our dependency wallah._\n\n\n### Who else is using Sails.js?\n\nSails is used in production by individuals and companies, non-profits, and government entities all over the world, for all sorts of projects (greenfield and mature).  You can see some examples [here](https://sailsjs.com/#?using-sails) of companies that have used Sails for their projects. (This small list is definitely not authoritative, so if you're using Sails in your app/product/service, [we'd love to hear about it](https://sailsjs.com/contact)!\n\n### Are there professional support options?\n\n[The Sails Company](https://sailsjs.com/about) offers custom development, services, training, enterprise-class products, and support for teams building applications on Sails.\n\n##### Partner with us\nOur studio provides development services for startups, SMBs, and the Fortune 500. As you might expect, the Sails core team has done a lot of custom Sails/Node.js development, but we also have experience across the full stack, including: advanced interaction design, practical/scalable JavaScript development practices for huge applications, and building rich user experiences across many different devices and screen resolutions.\n\nWe can build your app and API from scratch, modernize your legacy web platform, or catalyze the development efforts of your established team.  If you're interested in working with us on your next project, [drop us a line](https://sailsjs.com/studio#?contact).\n\n##### Sails Flagship for Enterprise\nSails Flagship is a platform on top of Sails which provides a suite of additional services, production-quality accoutrements, and support for enterprise use cases.  This includes early access to new features and enhancements, a license for our internal tools, as well as exclusive reports and best-practice guides created by core maintainers.  To learn more, [set up a call](https://sailsjs.com/contact) _(or [purchase online now](https://sailsjs.com/flagship/plans))_.\n\n> We are actively expanding this product offering with new additions and official re-releases of some formerly-experimental modules.  If you have specific suggestions/requests for new Flagship packages, please [let us know](http://flagship.sailsjs.com/contact).\n\n##### Professional support / SLAs\nThe Sails Company also provides a lifeline for organizations using Sails to build their products. If you need guaranteed support in the event of a critical production issue, or just want an extra pair of eyes looking out for your code base during development, take a look at our [basic subscriptions](https://sailsjs.com/flagship/plans), or [contact us](https://flagship.sailsjs.com/contact) and we'll give you a call.\n\n\n\n### Where do I get help?\n\nAside from the [official documentation](https://sailsjs.com/documentation), be sure and check out the [recommended support options on the Sails website](https://sailsjs.com/support), and pop in to our [Gitter chat room](https://gitter.im/balderdashy/sails).  If you're stumped, make sure and [ask a question on StackOverflow](http://stackoverflow.com/questions/ask), where there's an [active Sails community](http://stackoverflow.com/questions/tagged/sailsjs?sort=newest&days=30).  Members of our core team recently taught a [free video course](https://courses.platzi.com/courses/develop-apps-sails-js/) on [Platzi](http://platzi.com) and wrote [a book](https://www.manning.com/books/sails-js-in-action).\n\n> If you're using [Sails Flagship](https://sailsjs.com/faq#?are-there-professional-support-options), you can contact the core team [here](http://flagship.sailsjs.com/ask).\n\n\n\n### What are some good community tutorials?\n\n> If you are the author of a tutorial or guide about Sails, please send us a pull request [here](https://github.com/balderdashy/sails/edit/master/docs/faq/faq.md) and we'll check it out. (Be sure to add your tutorial to the top of the applicable list, as we try to order these from newest to oldest.)\n\n<!--\nA quick note for anyone contributing to this file:\n\nFirst of all, thanks for making a tutorial! That was pretty cool of you.\n\nSecondly, when you add the tutorial to one of the lists below, please follow it with a comment that has the date your tutorial was last updated. (We try to keep the most recent ones toward the top of the list.) If you are linking to an ongoing series that you continually update, just add the date of your most recent post + the phrase '(ongoing series)' so we know to keep checking back.\n\nThanks!\n-@rachaelshaw\n-->\n\n##### Multi-part guides:\n+ [The busy JavaScript developer's guide to Sails.js](https://www.ibm.com/developerworks/library/wa-build-deploy-web-app-sailsjs-1-bluemix/index.html) -- 4-part series from IBM developerWorks. (Also available in [Chinese](http://www.ibm.com/developerworks/cn/web/wa-build-deploy-web-app-sailsjs-1-bluemix/) and [Japanese](http://www.ibm.com/developerworks/jp/web/library/wa-build-deploy-web-app-sailsjs-1-bluemix/).)\n<!-- 7-12-2016 -->\n+ [SailsCasts](http://irlnathan.github.io/sailscasts/) - Short screencasts that take you through the basics of building traditional websites, single-page/mobile apps, and APIs using Sails.  Perfect for both novice and tenured developers, but does assume some background on MVC.\n<!-- 4-4-2015 -->\n+ [Sails.js Development channel on Medium](https://medium.com/sails-js-development/)\n<!-- 3-19-2015 -->\n+ [Sails.js Course on Pluralsight](https://www.pluralsight.com/courses/two-tier-enterprise-app-api-development-angular-sails)\n<!-- 2-10-2015 -->\n+ Sails API Development\n  + [Datalayer -models, connections, waterline](http://www.codeproject.com/Articles/898221/Sails-API-development-Datalayer-models-connections)\n  + [Custom methods, overriding default actions, and related](http://www.codeproject.com/Articles/985730/Sails-API-development-2-2-Custom-methods-overriding-default)\n<!-- 5-5-2015 -->\n+ Desarrollar Webapps Realtime:\n  + [Creación](http://jorgecasar.github.io/blog/desarrollar-webapps-realtime-creacion/)\n  + [Usuarios](http://jorgecasar.github.io/blog/desarrollar-webapps-realtime-usuarios/)\n  + [Auth](http://jorgecasar.github.io/blog/desarrollar-webapps-realtime-auth/)\n  + [Auth con Passport](http://jorgecasar.github.io/blog/desarrollar-webapps-realtime-auth-con-passport/)\n<!-- 1-19-2014 -->\n\n\n##### Articles & blog posts:\n+ [Nanobox Blog: Getting Started - A Simple Sails.js App](https://content.nanobox.io/a-simple-sails-js-example-app/)\n<!-- 6-13-2017 -->\n+ [Twitter Dev Blog: Guest Post: Twitter Sign-In with Sails.js](https://blog.twitter.com/2015/guest-post-twitter-sign-in-with-treelineio)\n<!-- 3-25-2015 -->\n+ [Guest Post on Segment.io Blog: Webhooks with Slack, Segment, and Sails.js/Treeline](https://segment.com/blog/segment-webhooks-slack/)\n<!-- 3-15-2015 -->\n+ [Postman Blog: Manage your Sails.js server bootstrap code](http://blog.getpostman.com/2015/08/28/manage-your-sailsjs-server-bootstrap-code/)\n<!-- 8-28-2015 -->\n+ [Sails.js on Heroku](https://vort3x.me/sailsjs-heroku/)\n<!-- 5-19-2015 -->\n+ [Angular + Sails.js (0.10.0-rc5) with angular-sails socket.io](https://github.com/maartendb/angular-sails-scrum-tutorial/blob/master/README.md)\n<!-- 4-14-2014 -->\n+ [Angular + Sails!  Help!](https://github.com/xdissent/spinnaker) - Sails Resources Service for AngularJS\n<!-- 8-19-2013 -->\n+ [How to Create a Node.js App using Sails.js on an Ubuntu VPS](https://www.digitalocean.com/community/articles/how-to-create-an-node-js-app-using-sails-js-on-an-ubuntu-vps)\n<!-- 7-16-2013 -->\n+ [Working With Data in Sails.js](http://net.tutsplus.com/tutorials/javascript-ajax/working-with-data-in-sails-js/) tutorial on NetTuts\n<!-- 6-12-2013 -->\n\n##### Video tutorials:\n+ [Develop Web Apps in Node.js and Sails.js](https://courses.platzi.com/courses/sails-js/)\n+ [Jorge Casar: Introduccion a Sails.js](https://www.youtube.com/watch?v=7_zUNTtXtcg)\n<!-- 12-17-2014 -->\n+ [Sails.js - How to render node views via Ajax, single page application, SPA](http://www.youtube.com/watch?v=Di50_eHqI7I&feature=youtu.be)\n<!-- 8-29-2013 -->\n+ [Intro to Sails.js](https://www.youtube.com/watch?v=GK-tFvpIR7c) [@mikermcneil](https://github.com/mikermcneil)'s original screencast\n<!-- 2-25-2013 -->\n\n\n### How can I convince the other girls/guys on my team?\n\n##### Articles / interviews / press releases / whitepapers / talks\n\n> + If you are the author of an article about Sails, please send us a pull request [here](https://github.com/balderdashy/sails/edit/master/docs/faq/faq.md).  We'll check it out!\n> + If you are a company interested in doing a press release about Sails, please contact [@mikermcneil](https://twitter.com/mikermcneil) on Twitter.  We'll do what we can to help.\n\n+ [InfoWorld: Why Node.js beats Java and .Net for web, mobile, and IoT apps](http://www.infoworld.com/article/2975233/javascript/why-node-js-beats-java-net-for-web-mobile-iot-apps.html) _(Speed, scalability, productivity, and developer politics all played a role in [AnyPresence](http://anypresence.com)’s selection of Sails.js/Node.js for its enterprise development platform)_\n+ [TechRepublic: Build Robust Applications with the Node.js MVC framework](http://www.techrepublic.com/article/build-robust-node-applications-with-the-sails-js-mvc-framework/)\n+ [Microsoft Case Study: Deploying Sails.js to Azure Web Apps](https://blogs.msdn.microsoft.com/partnercatalystteam/2015/07/16/y-combinator-collaboration-deploying-sailsjs-to-azure-web-apps/)\n+ [Mike's interview w/ @freddier and @cvander from Platzi](https://www.youtube.com/watch?v=WN0YgPdPbRE)\n+ [Smashing Magazine: Sailing with Sails.js](https://www.smashingmagazine.com/2015/11/sailing-sails-js-mvc-style-framework-node-js/)\n+ [Presentation at Smart City Conference & Expo 2015](http://www.goodxense.com/blog/post/our-presentation-at-smart-city-conference-expo-2015/) (George Lu & YJ Yang)\n+ [Radio interview with Mike McNeil w/ ComputerAmerica's Craig Crossman](https://www.youtube.com/watch?v=ERIvf2iUj5U&feature=youtu.be)\n+ Sails.js, Treeline and the future of programming  ([Article](https://courses.platzi.com/blog/sails-js-creator-mike-mcneil-on-treeline-and-frameworks/) | [Video](https://www.youtube.com/watch?v=nZKG7hLhbRs) | [Deck](https://speakerdeck.com/mikermcneil/what-even-is-software))\n+ [UI-First API Design & Development: Apigee's I &hearts; APIs, San Francisco, 2015](https://speakerdeck.com/mikermcneil/i-love-apis)\n+ [Choosing the right framework for Node.js development](https://jaxenter.com/choosing-the-right-framework-for-node-js-development-126432.html)\n+ [TechCrunch: Our 10 Favorite Companies From Y Combinator Demo Day](https://techcrunch.com/gallery/our-10-favorite-companies-from-y-combinator-demo-day-day-1/slide/11/)\n+ [Sails.js used on the website for the city of Paris](https://twitter.com/parisnumerique/status/617999231182176256)\n+ [18f Open Source Hack Series: Midas](https://18f.gsa.gov/2014/10/01/open-source-hack-series-midas/)\n+ [From Rags to Open Source](https://speakerdeck.com/mikermcneil/all-things-open) (All Things Open, Raleigh, 2014)\n+ SxSW Conference, Austin, TX: ([2014](https://speakerdeck.com/mikermcneil/2014-intro-to-sails-v0-dot-10-dot-x) | [2015](https://speakerdeck.com/mikermcneil/sxsw-2015))\n+ [More talks by Mike and the Sails.js core team](http://lanyrd.com/profile/mikermcneil/)\n+ [Dessarolo Web: Interview w/ Mike McNeil](https://www.youtube.com/watch?v=XMpf44oV2Og) (Spanish & English--English starts at 1:30)\n+ [CapitalOne blog: Contrasting Enterprise Node.js Frameworks](http://www.capitalone.io/blog/contrasting-enterprise-nodejs-frameworks/) (by [Azat Mardan](https://www.linkedin.com/in/azatm), author of the book \"Pro Express.js\")\n+ [Alternatives to MongoDB (Chinese article)](http://www.infoq.com/cn/news/2015/07/never-ever-mongodb)\n+ [Introducción a Sails.js, un framework para crear aplicaciones realtime](https://abalozz.es/introduccion-a-sails-js-un-framework-para-crear-aplicaciones-realtime/)\n+ [Austin startup finds success in responsive design](http://www.bizjournals.com/sanantonio/blog/socialmadness/2013/03/sxsw-2013-Balderdash-startup-web-app.html?ana=twt)\n+ [Interact ATX](http://www.siliconhillsnews.com/2013/03/10/flying-high-with-interact-atx-adventures-in-austin-part-3-2-1/)\n+ [Intro to Sails.js :: Node.js Conf: Italy, 2014](http://2014.nodejsconf.it/)\n+ [Startup America](http://www.prlog.org/12038372-engine-pitches-startup-america-board-of-directors.html)\n+ [Recent tweets about Sails.js](https://twitter.com/search?q=treelinehq%20OR%20%40treelinehq%20OR%20%23treelinehq%20OR%20%40waterlineorm%20OR%20treeline.io%20OR%20sailsjs.com%20OR%20github.com%2Fbalderdashy%2Fsails%20OR%20sailsjs%20OR%20sails.js%20OR%20%23sailsjs%20OR%20%40sailsjs&src=typd)\n+ [How to use more open source](https://18f.gsa.gov/2014/11/26/how-to-use-more-open-source/) _(18F is an office inside the U.s. General Services Administration that helps other federal agencies build, buy, and share efficient and easy-to-use digital services.)_\n+ [Express Web Server Advances in Node.js Ecosystem](https://adtmag.com/articles/2016/02/11/express-joins-node.aspx) ([auch auf Deutsch](http://www.heise.de/developer/meldung/IBM-uebergibt-JavaScript-Webframework-Express-an-Node-js-Foundation-3099223.html))\n+ Interview w/ Tim Heckel [on InfoQ](http://www.infoq.com/news/2013/04/Sails-0.8.9-Released)\n+ [Sails.js - Une Architecture MVC pour applications real-time Node.js](http://www.lafermeduweb.net/billet/sails-js-une-architecture-mvc-pour-applications-real-time-node-js-1528.html)\n+ [Hacker News](https://news.ycombinator.com/item?id=5373342)\n+ [Pulling the Plug: dotJS (Paris, 2014)](http://www.thedotpost.com/2014/11/mike-mcneil-pulling-the-plug)\n+ [Intro to Sails.js :: Node PDX, Portland, 2013 (Slides)](http://www.slideshare.net/michaelrmcneil/node-pdx))\n+ [Sail.js : un framework MVC pour Node.js](http://javascript.developpez.com/actu/52729/Sail-js-un-framework-MVC-pour-Node-js/)\n+ [Build Custom & Enterprise Node.js Apps with Sails.js](http://www.webappers.com/2013/03/29/build-custom-enterprise-node-js-apps-with-sails-js/)\n+ [New tools for web design and development: March 2013](http://www.creativebloq.com/design-tools/new-tools-web-design-and-development-march-2013-4132972)\n+ [Sails 0.8.9: A Rails-Inspired Real-Time Node MVC Framework](http://www.infoq.com/news/2013/04/Sails-0.8.9-Released)\n+ [Node.js の MVCフレームワーク Sails.js が良さげなので少し試してみた](http://nantokaworks.com/?p=1101)\n+ [InfoWorld: 13 fabulous frameworks for Node.js](http://www.infoworld.com/article/3064653/application-development/13-fabulous-frameworks-for-nodejs.html#slide9)\n+ [New web design tools that you need to check out](http://www.designyourway.net/blog/resources/new-web-design-tools-that-you-need-to-check-out/)\n+ [Live code Sails.js avec Mike McNeil](http://www.weezevent.com/live-code-sailsjs-avec-mike-mcneil)\n+ [#hack4good adds cities and welcomes Sails.js creator to speak and hack in Paris!](http://us2.campaign-archive1.com/?u=cf9af451f2674767755b02b35&id=fb98713f48&e=b2d87b15fe)\n+ [TechCrunch: Sails.js Funded by Y-Combinator](http://techcrunch.com/2015/03/11/treeline-wants-to-take-the-coding-out-of-building-a-backend/)\n\n\n### Where do I submit ideas?  Report bugs?\n\nThe Sails project tracks bug reports in GitHub issues and uses pull requests for feature proposals.  Please read the [contribution guide](https://sailsjs.com/documentation/contributing) before you create an issue, submit a proposal, or begin working on pull request.\n\n\n### What version of Sails should I use?\n\n[![NPM version](https://badge.fury.io/js/sails.png)](http://badge.fury.io/js/sails)\n\nUnless you are a contributor running a pre-release version of the framework in order to do some testing or work on core, you should use the latest stable version of Sails from NPM (click the badge above).  Installing is easy- just follow [these instructions](https://sailsjs.com/get-started).\n\n> Note: to install/upgrade to the latest version of Sails locally in an existing project, run `npm install sails@latest --save`.  If you are having trouble and are looking for a bazooka, you might also want to run `rm -rf node_modules && npm cache clear && npm install sails@latest --force --save && npm install`.\n\nIf you are looking to install a pre-release version of Sails, you can install from the `beta` tag on npm (i.e. `npm install sails@beta`). This is a great way to try out a coming release ahead of time and start upgrading before the release becomes official.  The beta npm release candidate corresponds with the `beta` branch in the Sails repo.  (Just be sure to also use the right version of your favorite adapters and other plugins.  If in doubt, [feel free to ask](https://sailsjs.com/support).)\n\nFinally, if you like living on the edge, or you're working on adding a feature or fixing a bug in Sails, install the edge version from the `master` branch on github.  The edge version is not published on the registry since it's constantly under development, but you can _still use npm to install it_ (e.g. `npm install sails@git://github.com/balderdashy/sails.git`)\n\nFor more instructions on installing the beta and edge versions of Sails, check out the [contribution guide](https://sailsjs.com/documentation/contributing).\n\n\n### How do I get involved?\n\nThere are many different ways to contribute to Sails; for example you could help us improve the [official documentation](https://github.com/balderdashy/sails/tree/master/docs), write a [plugin](https://sailsjs.com/documentation/concepts/extending-sails), answer [StackOverflow questions](http://stackoverflow.com/questions/tagged/sails.js), start a Sails meetup, help troubleshoot GitHub issues, write some tests, or submit a patch to Sails core or one of its dependencies.  Please look through the [contribution guide](https://sailsjs.com/documentation/contributing) before you get started. It's a short read that covers guidelines and best practices that ensure your hard work will have the maximum impact.\n\n### How does the documentation end up on the Sails website?\n\nThe documentation is compiled from the markdown files in the [`sails` repo on github](https://github.com/balderdashy/sails/tree/master/docs). A number of Sails users have expressed interest in emulating the process we use to generate the pages on the Sails website.  Good news is it's pretty simple:  The compilation process for the Sails docs involves generating HTML from Markdown files in the sails repo, then performing some additional transformations such as adding data type bubbles, tagging permalinks for individual sections of pages, building JSON data to power the side navigation menu and setting HTML `<title>` attributes for better search engine discoverability of individual doc pages.  See the [doc-templater](https://github.com/uncletammy/doc-templater) module for more information.\n\n\n### Where is the documentation for the different releases of Sails?\nThe [documentation on the main website](https://sailsjs.com/documentation) is for the latest stable npm release of Sails, and is mirrored by the docs in the [master branch of the `sails` repo on github](https://github.com/balderdashy/sails/tree/master/docs) (Master is sometimes a few commits ahead, but any critical documentation updates make it onto the website within a day or two.)\n\nFor older releases of Sails that are still widely used, the documentation is compiled from the relevant `sails-docs` branches and hosted on the following subdomains:\n+ [0.12.sailsjs.com](http://0.12.sailsjs.com/)\n+ [0.11.sailsjs.com](http://0.11.sailsjs.com/)\n"
  },
  {
    "path": "docs/irc/irc.md",
    "content": "## Grab An IRC Client\nBelow you'll find some of the more popular IRC Clients.\n\n### Linux\n - [xChat](http://xchat.org)\n - [irssi](http://irssi.org)\n - [weeChat](http://www.weechat.org)\n\n#### Using apt package manager for Ubuntu/Debian\n```\nsudo apt-get install weechat\n\n```\n\n### OSX\n- [irssi](http://irssi.org)\n\n```\nsudo steveJobsPM --prettyPlease install -m 'is this okay?' irssi\n\n```\n### Windows\n - [xChat](http://xchat.org)\n - [hydra IRC](http://www.hydrairc.com/content/downloads)\n \n\n## Setting Up Your Client\n### Registering On Freenode\nOur chat room is on the Freenode network.  Freenode does not require that you register your `nick` name.  You do have the option to though.  If you want to do this, read about how to do it [on the freenode website](https://freenode.net/faq.shtml#registering)\n\n### Getting on Freenode\n\nEach IRC Client is a little different to configure.  All of the ones we have recommended have very straight forward configuration process.  If your client provides a list of available servers, look for the one called Freenode.\n\nMake sure to put in a `nick` to go by.\n\nUpon connecting to the Freenode network, join us by typing `/join #sailsjs`.\n\nIf you registered a nick, you can identify yourself with `/msg nickserv identify <password>`\n\n\n## Getting help on IRC\n\n### `#sailsjs` on irc.freenode.net\nIf you are looking for a quick answer and you can't find what you're looking for in the docs, come ask in our IRC chat room.  While there is typically somebody there who can answer your question, please remember that #sailsjs is 100% community maintained. That means that help is given at the discretion of the community.  For best results, be polite and to the point.\n\nIf you've never been on IRC, now is the perfect time.  Getting started is easy.\n\n\n## Sails Troll\nSails Troll is our resident IRC Bot.  His job is to write down what people say in case someone wants to find it later.\n\nHe also informs the room whenever someone pushes up a change to any of the repos in the Sails.js ecosystem.\n\n\n<docmeta name=\"displayName\" value=\"#sailsjs on IRC\">\n"
  },
  {
    "path": "docs/reference/README.md",
    "content": "# docs/reference\n\nThis section contains the official reference documentation for Sails.  It is made available at https://sailsjs.com/documentation/reference.\n\n\n### Notes\n> - This README file **is not compiled to HTML** for the website.  It is just here to explain what you're looking at.\n> - Depending on what branch of `sails` you are currently viewing, the domain may vary. See the top-level documentation README file for information about working with the markdown files in this repo, and to understand the branching/versioning strategy.\n\n<docmeta name=\"notShownOnWebsite\" value=\"true\">\n"
  },
  {
    "path": "docs/reference/application/advanced-usage/advanced-usage.md",
    "content": "# Advanced usage\n\nMost users of the Sails framework will never need to access more than a few basic methods of the `sails` application object. However, if you have an advanced use case or are considering [contributing to Sails](https://sailsjs.com/documentation/contributing), you may need to delve into some of these lesser-used methods or reference the [loading order of Sails core](https://sailsjs.com/documentation/reference/application/advanced-usage/lifecycle).\n\n### Disabling the `sails` global\n\nWe recommended using the `sails` global with Sails.\n\nHowever, the auto-globalization of `sails` [can be disabled](https://sailsjs.com/documentation/reference/configuration/sails-config-globals). Disabling the `sails` global might be a good idea for use cases where multiple Sails app instances need to exist at once, or where globals are not an option.\n\nIf the `sails` global is disabled, then you'll need another way to reference the application instance.  Luckily, this is possible from almost anywhere in your app:\n\n+ in the `fn` of an [action](https://sailsjs.com/documentation/concepts/actions-and-controllers) (`this.sails`)\n+ in the `fn` of a [helper](https://sailsjs.com/documentation/concepts/helpers) (`this.sails`).\n+ on an incoming request (`req._sails`)\n\n\n### Properties (advanced)\n\n##### sails.hooks\n\nA dictionary of all loaded [Sails hooks](https://sailsjs.com/documentation/concepts/extending-sails/hooks), indexed by their _identity_.  Use `sails.hooks` to access properties and methods of hooks you've installed to extend Sails&mdash;for example, by calling `sails.hooks.email.send()`.  You can also use this dictionary to access the Sails [core hooks](https://sailsjs.com/documentation/concepts/extending-sails/hooks#?types-of-hooks), for advanced usage.\n\nBy default, a hook's identity is the lowercased version of its folder name, with any `sails-hook-` prefix removed.  For example, the default identity for a hook loaded from `node_modules/sails-hook-email` would be `email`, and the hook would be accessible via `sails.hooks.email`.  An installed hook's identity can be changed via the [`installedHooks` config property](https://sailsjs.com/documentation/concepts/extending-sails/hooks/using-hooks#?changing-the-way-sails-loads-an-installable-hook).\n\nSee the [hooks concept documentation](https://sailsjs.com/documentation/concepts/extending-sails/hooks) for more information about hooks.\n\n##### `sails.io`\n\nThe API exposed by the [`sails.sockets.*` methods](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets) is flexible enough out of the box to cover the requirements of most applications, and using them will future-proof your app against possible changes in the underlying implementation.  However, if you are working on bringing some legacy code from a vanilla Socket.io app into your Sails app, it can be useful to talk to Socket.io directly.  To accomplish this, Sails provides raw access to the underlying [socket.io](http://socket.io/) server instance (`io`) as `sails.io`. See the [Socket.io docs](http://socket.io/docs/) for more information.  If you decide to use Socket.io directly, please proceed with care.\n\n> Sails bundles `socket.io` as a dependency of [sails-hook-sockets](github.com/balderdashy/sails-hook-sockets), a core hook.\n\n\n### Where does the application object come from?\n\nAn application instance automatically created _the first time_ you `require('sails')`.\n\nThis is what is happening in the generated `app.js` file:\n\n```javascript\nvar sails = require('sails');\n```\n\nNote that any subsequent calls to `require('sails')` return the same app instance.  (This is why you might sometimes hear the Sails app instance referred to as a \"singleton\".)\n\n\n\n### Creating a new application object (advanced)\n\nIf you are implementing something unconventional (e.g. writing tests for Sails core)\nwhere you need to create more than one Sails application instance in a process, you _should not_ use\nthe instance returned by `require('sails')`, as this can cause unexpected behavior.  Instead, you should\nobtain application instances by using the Sails constructor:\n\n```javascript\nvar Sails = require('sails').constructor;\nvar sails0 = new Sails();\nvar sails1 = new Sails();\nvar sails2 = new Sails();\n```\n\nEach app instance (`sails0`, `sails1`, `sails2`) can be loaded/lifted separately,\nusing different configuration.\n\nFor more on using Sails programatically, see the conceptual overview on [programmatic usage in Sails](https://sailsjs.com/documentation/concepts/programmatic-usage).\n\n\n<docmeta name=\"displayName\" value=\"Advanced usage\">\n"
  },
  {
    "path": "docs/reference/application/advanced-usage/lifecycle.md",
    "content": "# The Sails app lifecycle\n\nThe Sails core has been iterated upon several times to make it easier to maintain and extend. As a result, it has a very particular loading order, which its hooks depend on heavily. This process is summarized below.\n\n### (1) Load configuration \"overrides\"\n\nGather the set of configuration values passed in on the command line, in environment variables, and in programmatic configuration (i.e. options passed to [`sails.load`](https://sailsjs.com/documentation/reference/application/sails-load) or [`sails.lift`](https://sailsjs.com/documentation/reference/application/sails-lift)).  When an app is started via the command-line interface (by typing `sails lift` or `sails console`), the values of any `.sailsrc` files will also be merged into the config overrides.  These override values will take precedence over any user configuration encountered in the next step.\n\n### (2) Load user configuration\n\nUnless the `userconfiguration` hook is explicitly disabled, Sails will next load the configuration files in the `config` folder (and subfolders) underneath the current working directory.  See [**Concepts > Configuration**](https://sailsjs.com/documentation/concepts/configuration) for more details about user configuration.  Configuration settings from step 1 will be merged on top of these values to form the `sails.config` object.\n\n### (3) Load hooks\n\nNext, Sails will load the other hooks.  [Core hooks](https://sailsjs.com/documentation/concepts/extending-sails/hooks#?types-of-hooks) will load first, followed by user hooks and installable hooks.  Note that hooks typically include configuration of their own which will be used as _default values_ in `sails.config`.  For example, if no `port` setting is configured by this point, the `http` hook's default value of 1337 will be used.\n\n### (4) Assemble router\n\nSails prepares the core Router, then emits multiple events on the `sails` object informing hooks that they can safely bind routes.\n\n### (5) Expose global variables\nAfter all hooks have initialized, Sails exposes global variables (by default: `sails` object, models, services, `_`, and `async`).\n\n### (6) Initialize app runtime\n\n> This step does not run when `sails.load()` is used programmatically.\n> To run the initialization step, use `sails.lift()` instead.\n\n+ Run the bootstrap function (`sails.config.bootstrap`)\n+ Start attached servers (by default, Express and Socket.io)\n\n### FAQ\n\n\n+ What is the difference between `sails.lift()` and `sails.load()`?\n  + `lift()` === `load()` + `initialize()`.  It does everything `load()` does, plus it starts any attached servers (e.g. HTTP) and logs a picture of a boat.\n\n\n<docmeta name=\"displayName\" value=\"Lifecycle\">\n"
  },
  {
    "path": "docs/reference/application/advanced-usage/sails.LOOKS_LIKE_ASSET_RX.md",
    "content": "# sails.LOOKS_LIKE_ASSET_RX\n\nA regular expression designed for use in identifying URL paths that seem like they are _probably_ for a static asset of some kind (e.g. image, stylesheet, `favicon.ico`, `robots.txt`, etc.).\n\n### Usage\n```usage\nsails.LOOKS_LIKE_ASSET_RX;\n```\n\n**Type:** ((RegExp))\n\n> This regex is **by no means foolproof**, and may match URLs too aggressively for some applications.  It is just a reasonable approximation made available for convenience.\n\n### Example\n\nTo avoid disabling built-in session support for any request to a URL path that ends in `.json`, but still disable sessions for other requests for static assets, you might use the following configuration:\n\n```javascript\n// In `config/session.js`\nisSessionDisabled: function (req){\n\n  if (req.path.match(/\\.json$/)) {\n    // Don't disable sessions.\n    return;\n  }\n\n  var seemsToWantSomeOtherStaticAsset = !!req.path.match(sails.LOOKS_LIKE_ASSET_RX);\n  if (seemsToWantSomeOtherStaticAsset) {\n    // Disable sessions.\n    return true;\n  }\n  \n  // Otherwise, don't disable sessions.\n  return;\n\n}\n```\n\n<docmeta name=\"displayName\" value=\"sails.LOOKS_LIKE_ASSET_RX\">\n<docmeta name=\"pageType\" value=\"constant\">\n"
  },
  {
    "path": "docs/reference/application/advanced-usage/sails.getActions.md",
    "content": "# sails.getActions()\n\nReturn a dictionary of Sails [actions](https://sailsjs.com/documentation/concepts/actions-and-controllers).\n\n```usage\nsails.getActions();\n```\n\nThe result is a flat (i.e. one-level) dictionary where the keys are the kebab-cased, dash-delimited action identities, and the values are the action functions.  All actions in the dictionary will have been converted to `req, res` functions at this point, even if they were defined using [actions2 syntax](https://sailsjs.com/documentation/concepts/actions-and-controllers#?actions-2).\n\n\n<docmeta name=\"displayName\" value=\"sails.getActions()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/application/advanced-usage/sails.getBaseUrl.md",
    "content": "# sails.getBaseUrl()\n\n> ##### _**This method is deprecated and will likely be removed or changed in an upcoming release.**_\n> There is no reliable, cross-platform way to automatically detect the external URL of a running Sails app (or any other Node app).  Instead, configure your base URL explicitly and save it in [custom configuration](https://sailsjs.com/documentation/reference/configuration/sails-config-custom) (e.g. `sails.config.custom.baseUrl`) that you can reference throughout the app.  (This can then be overridden in production, staging, etc. as needed using [environment-dependent configuration](https://sailsjs.com/documentation/concepts/configuration#?environmentspecific-files-config-env).)\n\nReturn a (possibly incorrect) best guess of the base URL for this app, based on a combination of user-supplied and default configuration values.\n\n\n```usage\nsails.getBaseUrl();\n```\n\n`getBaseUrl()` constructs a URL string by inspecting various configuration values and defaults.  For example, if `sails.config.ssl.key` and `sails.config.ssl.cert` both have values, the URL will start with `https://` instead of `http://`.  If `sails.config.explicitHost` is not undefined, its value will be used as the domain name, otherwise it will be `localhost`.  If `sails.config.port` is not 80 or 443, its value will be appended to the URL as well.\n\n\n### Usage\n\n_This function does not accept any arguments._\n\n\n#### Returns\n\n**Type:** ((string))\n\n```javascript\nhttp://localhost:1337\n```\n\n\n\n### Example\n\nIn an email template...\n```html\nFor more information, visit <a href=\"<%=sails.getBaseUrl()%>\">our web site</a>.\n```\n\n<docmeta name=\"displayName\" value=\"sails.getBaseUrl()\">\n<docmeta name=\"pageType\" value=\"method\">\n<docmeta name=\"isDeprecated\" value=\"true\">\n"
  },
  {
    "path": "docs/reference/application/advanced-usage/sails.getRouteFor.md",
    "content": "# sails.getRouteFor()\n\nLook up the first route pointing at the specified target (e.g. `MeController.login`) and return a dictionary containing its method and URL.\n\n\n\n```usage\nsails.getRouteFor(target);\n```\n\n\n### Usage\n\n\n|   |       Argument             | Type                | Details\n|---|--------------------------- | ------------------- |:-----------\n| 1 |      target                | ((string))          | The route target string; e.g. `MeController.login`\n\n\n#### Returns\n\n**Type:** ((dictionary))\n\n```javascript\n{\n  method: 'post',\n  url: '/auth/login'\n}\n```\n\n\n\n### Example\n\nIn a controller action...\n```javascript\nreturn res.view('pages/some-page-with-a-form-on-it', {\n  formEndpoint: sails.getRouteFor('SomeotherController.someAction'),\n  // ...\n});\n```\n\nSo that in the rendered view...\n```ejs\n<form action=\"<%=formEndpoint.url%>\" method=\"<%=formEndpoint.method%>\">\n  <!-- ... -->\n</form>\n```\n\n### Notes\n> - This function searches the Sails app's explicitly configured routes; [`sails.config.routes`](https://sailsjs.com/documentation/reference/configuration/sails-config-routes).  Shadow routes bound by hooks (including [blueprint routes](https://sailsjs.com/documentation/reference/blueprint-api#?blueprint-routes)) will not be matched.\n> - If a matching target cannot be found, this function throws an `E_NOT_FOUND` error (i.e. if you catch the error and check its `code` property, it will be the string `E_NOT_FOUND`).\n> - If more than one route matches the specified target, the first match is returned.\n> - If you only need the URL for a route (e.g. to use as an `href` from within one of your views), you may want to use [`sails.getUrlFor()`](https://sailsjs.com/documentation/reference/application/sails-get-url-for) instead of this function.\n\n<docmeta name=\"displayName\" value=\"sails.getRouteFor()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/application/advanced-usage/sails.lift.md",
    "content": "# sails.lift()\n\nLift a Sails app programmatically.\n\n> This does exactly what you might be used to seeing by now when you run `sails lift`.  It [loads](https://sailsjs.com/documentation/reference/application/sails-load) the app, runs its bootstrap, then starts listening for HTTP requests and WebSocket connections.  Useful for building top-to-bottom integration tests that rely on HTTP requests, and for building higher-level tooling on top of Sails.\n\n```usage\nsailsApp.lift(configOverrides, function (err) {\n\n});\n```\n\n_Or:_\n+ `sailsApp.lift(function (err) {...});`\n\n\n### Usage\n\n|   |     Argument        | Type                                         | Details                            |\n|---|:--------------------|----------------------------------------------|:-----------------------------------|\n| 1 | _configOverrides_   | ((dictionary?))                              | A dictionary of config that will override any conflicting options present in configuration files.  If provided, this will be merged on top of [`sails.config`](https://sailsjs.com/documentation/reference/configuration).\n\n##### Callback\n\n|   |     Argument        | Type                | Details |\n|---|:--------------------|---------------------|:---------------------------------------------------------------------------------|\n| 1 |    _err_            | ((Error?))          | An error encountered while lifting, or `undefined` if there were no errors.\n\n\n\n\n### Example\n\n```javascript\nvar Sails = require('sails').constructor;\nvar sailsApp = new Sails();\n\nsailsApp.lift({\n  log: { level: 'warn' }\n}, function (err) {\n  if (err) {\n    console.log('Error occurred lifting Sails app:', err);\n    return;\n  }\n\n  // --•\n  console.log('Sails app lifted successfully!');\n\n});\n```\n\n\n### Notes\n> - The difference between [`.lift()`](https://sailsjs.com/documentation/reference/application/sails-lift) and [`.load()`](https://sailsjs.com/documentation/reference/application/sails-load) is that `.lift()` takes the additional steps of (1) running the app's [bootstrap](https://sailsjs.com/documentation/reference/configuration/sails-config-bootstrap) (if any), and (2) emitting the `ready` event.  The core `http` hook will typically respond to the `ready` event by starting an HTTP server on the port configured via `sails.config.port` (1337 by default).\n> - When a Sails app is fully lifted, it also emits the [`lifted` event](https://sailsjs.com/documentation/concepts/extending-sails/hooks/events).\n> - With the exception of `NODE_ENV` and `PORT`, [configuration set via environment variables](https://sailsjs.com/documentation/concepts/configuration#?setting-sailsconfig-values-directly-using-environment-variables) will not automatically apply to apps started using `.lift()`, nor will options set in [`.sailsrc` files](https://sailsjs.com/documentation/concepts/configuration/using-sailsrc-files).  If you wish to use those configuration values, you can retrieve them via `require('sails/accessible/rc')('sails')` and pass them in as the first argument to `.lift()`.\n\n<docmeta name=\"displayName\" value=\"sails.lift()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/application/advanced-usage/sails.load.md",
    "content": "# sails.load()\n\nLoad a Sails app into memory, but without lifting an HTTP server.\n\n_Useful for writing tests, command-line scripts, and scheduled jobs._\n\n```usage\nsailsApp.load(configOverrides, function (err) {\n\n});\n```\n\n_Or:_\n+ `sailsApp.load(function (err) {...});`\n\n\n\n\n#### Usage\n\n|   |     Argument        | Type                                         | Details                            |\n|---|:--------------------|----------------------------------------------|:-----------------------------------|\n| 1 |    _configOverrides_| ((dictionary?))                              | A dictionary of config that will override any conflicting options present in configuration files.  If provided, this will be merged on top of [`sails.config`](https://sailsjs.com/documentation/reference/configuration).\n\n##### Callback\n\n|   |     Argument        | Type                | Details |\n|---|:--------------------|---------------------|:---------------------------------------------------------------------------------|\n| 1 |    _err_            | ((Error?))          | An error encountered while loading, or `undefined` if there were no errors.\n\n\n\n\n### Example\n\n```javascript\nvar Sails = require('sails').constructor;\nvar sailsApp = new Sails();\n\nsailsApp.load({\n  log: {\n    level: 'error'\n  }\n}, function (err) {\n  if (err) {\n    console.log('Error occurred loading Sails app:', err);\n    return;\n  }\n\n  // --•\n  console.log('Sails app loaded successfully!');\n\n});\n```\n\n### Notes\n> - This takes care of loading configuration files, initializing hooks (including the ORM), and binding routes.  It **does not** run the bootstrap, and it **does not** start listening for HTTP requests and WebSocket connections.\n> - More specifically, the difference between [`.lift()`](https://sailsjs.com/documentation/reference/application/sails-lift) and [`.load()`](https://sailsjs.com/documentation/reference/application/sails-load) is that `.lift()` takes the additional steps of (1) running the app's [bootstrap](https://sailsjs.com/documentation/reference/configuration/sails-config-bootstrap) (if any), and (2) emitting the `ready` event.  The core `http` hook will typically respond to the `ready` event by starting an HTTP server on the port configured via `sails.config.port` (1337 by default).\n> - Even though a \"loaded-but-not-lifted\" Sails app does not listen for requests on an HTTP port, you can make \"virtual\" requests to it using [`sails.request`](https://sailsjs.com/documentation/reference/application/sails-request)\n> - For an example of this in practice, see [machine-as-script](https://github.com/treelinehq/machine-as-script/blob/ec8972137489afd24562bdf0b6a10ada11e540cc/index.js#L778-L791).\n> - With the exception of `NODE_ENV` and `PORT`, [configuration set via environment variables](https://sailsjs.com/documentation/concepts/configuration#?setting-sailsconfig-values-directly-using-environment-variables) will not automatically apply to apps started using `.load()`, nor will options set in [`.sailsrc` files](https://sailsjs.com/documentation/concepts/configuration/using-sailsrc-files).  If you wish to use those configuration values, you can retrieve them via `require('sails/accessible/rc')('sails')` and pass them in as the first argument to `.load()`.\n\n\n<docmeta name=\"displayName\" value=\"sails.load()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/application/advanced-usage/sails.lower.md",
    "content": "# sails.lower()\n\nShut down a lifted Sails app and have it cease listening for or responding to any future requests.\n\n\n\n```usage\nsails.lower(callback);\n```\n\n\n### Usage\n\n|   |          Argument           | Type                | Details\n|---| --------------------------- | ------------------- | -----------\n| 1 |        _`callback`_         | ((function?))       | Optional. A function to call when lowering is complete (or if an error occurs)\n\n##### Callback\n\n|   | Argument  | Type         | Details |\n|---|-----------|:------------:|---------|\n| 1 | _`err`_     | ((Error?))   | An error instance will be sent as the first argument of the callback if any fatal errors occurred while lowering.\n\n\n### Example\n\n```javascript\nsailsApp.lower(\n  function (err) {\n    if (err) {\n      return console.log(\"Error occurred lowering Sails app: \", err);\n    }\n    console.log(\"Sails app lowered successfully!\");\n  }\n)\n```\n\n### Notes\n> + The app will emit the `lower` event before shutting down the HTTP and WebSocket services.\n> + Lowered apps cannot be lifted again.\n\n<docmeta name=\"displayName\" value=\"sails.lower()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/application/advanced-usage/sails.registerAction.md",
    "content": "# sails.registerAction()\n\nRegister a new Sails [action](https://sailsjs.com/documentation/concepts/actions-and-controllers) that can then be bound to a route.\n\n```usage\nsails.registerAction(action, name);\n```\n\nWhile actions are mainly registered automatically when the files in an app&rsquo;s `api/controllers` folder are loaded, you can use the `registerAction()` method to add a new action programmatically.  This is especially useful in custom [hooks](https://sailsjs.com/documentation/concepts/extending-sails/hooks), in situations where you want to provide a new action but let the app developer determine the route to bind the action to, or when you want to ensure that policies and other [action middleware](https://sailsjs.com/documentation/reference/application/sails-register-action-middleware) apply to your action.\n\n\n### Usage\n\n| &nbsp;  |       Argument             | Type                | Details\n|---|--------------------------- | ------------------- |:-----------\n| 1 |      action                | ((function)) or ((dictionary))    | Either a [classic action](https://sailsjs.com/documentation/concepts/actions-and-controllers#?classic-actions) (aka `(req, res)`) function or an [actions2](https://sailsjs.com/documentation/concepts/actions-and-controllers#?actions-2) definition.\n| 2 |     identity               | ((string)) | The identifier for the action.   This is the string that will be used to reference the action elsewhere in an app, for instance when [binding the action to a route](http://sailsjs.com/documentation/concepts/routes/custom-routes#?standalone-action-target-syntax).\n\n\n<docmeta name=\"displayName\" value=\"sails.registerAction()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/application/advanced-usage/sails.registerActionMiddleware.md",
    "content": "# sails.registerActionMiddleware()\n\n> ##### _**This feature is still experimental.**_\n> This method is still under development, and its interface and/or behavior could change at any time.\n\nRegister a new action middleware function that will be applied to actions with the specified identities.\n\n```usage\nsails.registerActionMiddleware(actionMiddlewareFns, actionIdentities);\n```\n\nAction middleware functions are essentially [policies](https://sailsjs.com/documentation/concepts/policies#?writing-your-first-policy) that you declare programmatically (rather than via [sails.config.policies](https://sailsjs.com/documentation/reference/configuration/sails-config-policies)).  In fact, policies are implemented under-the-hood using action middleware.  The `registerActionMiddleware()` method is mainly useful in [custom hooks](https://sailsjs.com/documentation/concepts/extending-sails/hooks) as a way of adding new policies to an app.\n\n### Usage\n\n| &nbsp;  |       Argument             | Type                | Details\n|---|--------------------------- | ------------------- |:-----------\n| 1 |      actionMiddlewareFns                | ((function)) or ((array))  | One or more middleware functions to register.  Action middleware (like policies) must be functions which accept `req`, `res` and `next` arguments.\n| 2 |     actionIdentities               | ((string)) | An expression that indicates the action or actions that the action middleware should apply to.  Use `*` at the end for a wildcard; e.g. `user/*` will apply to any actions whose identities begin with `user/`. Use a ! at the beginning to indicate that the action middleware should NOT apply to the actions specified by the expression, e.g. `!user/foo` or `!user/*`.  Multiple identity expressions can be specified by separating with a comma, e.g. `pets/count,user/*,!user/tickle`\n\n> The `actionIdentities` argument expects the identities to be expressed as if they were [standalone actions](https://sailsjs.com/documentation/concepts/actions-and-controllers#?standalone-actions).  To apply action middleware to actions inside of a controller file (e.g. `UserController.js`), simply refer to the lower-cased version of the filename _without \"Controller\"_ (e.g. `user`).\n\n### Example\n\nAs an example of action middleware that might be applied in a custom hook, imagine a page view counter (this code might be added to the `initialize` method of the hook):\n\n```javascript\n// Declare a local var to hold the number of views for each URL.\nvar pageViews = {};\n\n// Register middleware to record each page view.\nsails.registerActionMiddleware(\n\n  // First argument is the middleware to run\n  function countPage (req, res, next) {\n\n    // Initialize the page counter to zero if this is the first time we've seen this URL.\n    pageViews[req.url] = pageViews[req.url] || 0;\n\n    // Increment the page counter.\n    pageViews[req.url]++;\n\n    // Add the current page count to the request, so that it can be used in other middleware / actions.\n    req.currentPageCount = pageViews[req.url];\n\n    // Continue to the next matching middleware / action\n    next();\n  },\n\n  // Second argument is the actions to apply the middleware to.  In this case, we want the\n  // hook to apply to all actions EXCEPT the `show-page-views` action supplied by this hook.\n  '*, !page-view-hook/show-page-views'\n\n);\n```\n\n<docmeta name=\"displayName\" value=\"sails.registerActionMiddleware()\">\n<docmeta name=\"pageType\" value=\"method\">\n<docmeta name=\"isExperimental\" value=\"true\">\n"
  },
  {
    "path": "docs/reference/application/advanced-usage/sails.reloadActions.md",
    "content": "# sails.reloadActions()\n\n> ##### _**This feature is still experimental.**_\n> This method is still under development, and its interface and/or behavior could change at any time.\n\nFlush and reload all Sails [actions](https://sailsjs.com/documentation/concepts/actions-and-controllers)\n\n```usage\nsails.reloadActions(cb);\n```\n\n_Or:_\n\n+ `sails.reloadActions(options, cb)`\n\nThis method causes hooks to run their `registerActions()` methods if they have them.  After the hooks are finished reloading / re-registering their actions, actions in the `api/controllers` folder (including those stored in [controller files](https://sailsjs.com/documentation/concepts/actions-and-controllers#?controllers)) are reloaded and merged on top of those loaded via hooks.\n\nThis method is useful primarily in development scenarios.\n\n\n### Usage\n\n| &nbsp;  |       Argument             | Type                | Details\n|---|--------------------------- | ------------------- |:-----------\n| 1 |      _options_      | ((dictionary?))          | Currently accepts one key, `hooksToSkip`, which if given should be an array of names of hooks that should _not_ call their `reloadActions` method.\n| 2 |      _callback_              | ((function)) | A callback to be called with the virtual response.\n\n### Notes\n> - Never dynamically replace your Sails.js controller or action files on disk with untrusted code at runtime, regardless of whether you are using `.reloadActions()` in your app or not.  Since `reloadActions()` runs the code in your Sails.js app's files, if the files are not safe to run, then using `reloadActions()` would be [a security risk](https://github.com/balderdashy/sails/issues/7209).  This risk is only present if your Sails app is deliberately overwriting its own files to replace them with unsafe code.\n\n\n<docmeta name=\"displayName\" value=\"sails.reloadActions()\">\n<docmeta name=\"pageType\" value=\"method\">\n<docmeta name=\"isExperimental\" value=\"true\">\n"
  },
  {
    "path": "docs/reference/application/advanced-usage/sails.renderView.md",
    "content": "# sails.renderView()\n\n> ##### _**This feature is still experimental.**_\n> This method is still under development, and its interface and/or behavior could change at any time.\n\nCompile a view into an HTML template.\n\n```usage\nsails.renderView(pathToView, templateData);\n```\n\n### Usage\n\n| &nbsp; |       Argument        | Type                | Details\n|---|--------------------------- | ------------------- |:-----------\n| 1 |      pathToView            | ((string))          | The path to the view that will be compiled into HTML.\n| 2 |     _templateData_         | ((dictionary?))     | The dynamic data to pass into the view.\n\n\n### Example\n\nTo compile an HTML template with a customized greeting for the recipient:\n\n```javascript\nvar htmlEmailContents = await sails.renderView('emails/signup-welcome', {\n  fullName: inputs.fullName,\n  // Don't include the Sails app's default layout in the rendered template.\n  layout: false\n});\n```\n\n<docmeta name=\"displayName\" value=\"sails.renderView()\">\n<docmeta name=\"pageType\" value=\"method\">\n<docmeta name=\"isExperimental\" value=\"true\">\n"
  },
  {
    "path": "docs/reference/application/advanced-usage/sails.request.md",
    "content": "# sails.request()\n\n> ##### _**This feature is still experimental.**_\n> This method is still under development, and its interface and/or behavior could change at any time.\n\nMake a virtual request to a running Sails instance.\n\n```usage\nsails.request(request);\n```\n\n_Or:_\n\n+ `sails.request(url, body)`\n+ `sails.request(url, callback)`\n+ `sails.request(url, body, callback)`\n\nThis method can be used on instances that have been started with [`sails.load()`](https://sailsjs.com/documentation/reference/application/sails-load) and that are not actively listening for HTTP requests on a server port.  This makes `sails.request()` useful for testing scenarios where running [`sails.lift()`](https://sailsjs.com/documentation/reference/application/sails-lift) is not necessary.  However, it should be noted that the data may not be processed in exactly the same way as an HTTP request; in particular, a much simpler body parser will be employed, and Express middleware such as the static asset server will not be used.\n\n\n### Usage\n\n|   |       Argument             | Type                | Details\n|---|--------------------------- | ------------------- |:-----------:\n| 1 |      request (or url)      | ((string)) -or- ((dictionary))          | The virtual request to make.  If specified as a string, this should be an address containing an optional method and a path, e.g. `/foo` or `PUT /user/friend`.  If specified as an object, it should have one or more of the properties described in the \"request argument\" section below.\n| 2 |      _body_                  | ((json?)) | (optional) A JSON-serializable value to use as the request body.  This argument will override the `data` property of the `request` argument, if provided.\n| 3 |      _callback_              | ((function?)) | (optional) A callback to be called with the virtual response.\n\n#### Request object\n\nIf the `request` argument is specified as an object, it can have the following properties:\n\n|       Property             | Type                | Example | Details\n|--------------------------- | ------------------- | ------- | :-----------:\n| url                        | ((string))          | `\"/foo\"`, `\"PUT /user/friend\"`    | (required) The route in the Sails app to make a request to, with an optional HTTP method prefix\n| method                     | ((string))          | `\"GET\"`, `\"POST\"`    | (optional) The HTTP method to use in the request.  This will override any method supplied as part of the `url` property.\n| headers                    | ((dictionary))          | `{'content-type': 'application/json'}`    | (optional) Dictionary of headers to use in the virtual request.\n| data                       | ((json))            | `{foo:'bar'}`, `12345` | ((optional)) Data to send along with the request.  For `GET`, `HEAD` and `DELETE` requests, the data will be serialized into a querystring and added to the URL.  Otherwise, it will be sent as-is as the request body.\n\n#### Callback\n\n|   |       Argument             | Type                | Details\n|---|--------------------------- | ------------------- |:-----------\n| 1 |       _err_                | ((Error?))           | If the response was unsuccessful (status code was not in the 200-399 range) this will be an object containing `status` and `body` properties.  If the response was successful, this will be `null`.\n| 2 |       response             | ((dictionary))          | If the response was successful, this will be an object containing the full server response.\n| 3 |       body                 | ((json))            | If the response was successful, this will be the value of `response.body`.\n\n\n#### Returns\n\n**Type:** ((stream))\n\nThe full virtual request stream object.  This is a readable stream.\n\n<docmeta name=\"displayName\" value=\"sails.request()\">\n<docmeta name=\"pageType\" value=\"method\">\n<docmeta name=\"isExperimental\" value=\"true\">\n"
  },
  {
    "path": "docs/reference/application/application.md",
    "content": "# Application (`sails`)\n\nThe Sails application object contains all relevant runtime state for a Sails application.\nBy default, it is exposed globally as `sails` and accessible almost anywhere in your code.\n\n> Most users of the framework will only need to know about the `sails` application object in order to access a few basic methods and their custom configuration. Less commonly used methods can be found in the [advanced usage](https://sailsjs.com/documentation/reference/application/advanced-usage) section.\n\n\n### Properties\n\nThe application object has a number of useful methods and properties.\nThe officially supported methods on the `sails` object are covered by the other\npages in this section.  Here are a few of its most useful properties:\n\n##### sails.models\n\nA dictionary of all loaded [Sails models](https://sailsjs.com/documentation/concepts/models-and-orm/models), indexed by their _identity_.\n\nBy default, a model's identity is the lowercased version of its filename, without the **.js** extension.  For example, the default identity for a model loaded from `api/models/PowerPuff.js` would be `powerpuff`, and the model would be accessible via `sails.models.powerpuff`.  A model's identity can be customized by setting an `identity` property in its module file.\n\n\n##### sails.helpers\n\nA dictionary of all accessible [helpers](https://sailsjs.com/documentation/concepts/helpers), including organics.\n\n\n##### sails.config\n\nThe full set of configuration options for the Sails instance, loaded from a combination of environment variables, `.sailsrc` files, user-configuration files, and defaults.  See the [configuration concepts section](https://sailsjs.com/documentation/concepts/configuration) for a full overview of configuring Sails, and the [configuration reference](https://sailsjs.com/documentation/reference/configuration) for details on individual options.\n\n##### sails.sockets\n\nA set of convenience methods for low-level interaction with connected websockets.  See the [`sails.sockets.*` reference section](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets) for details.\n\n\n### Advanced usage\n\nFor more options and implementation details (including instructions for programmatic usage) see [Advanced usage](https://sailsjs.com/documentation/reference/application/advanced-usage).\n\n<docmeta name=\"displayName\" value=\"Application\">\n"
  },
  {
    "path": "docs/reference/application/sails.config.custom.md",
    "content": "# sails.config.custom\n\nThe runtime values of your app's [custom configuration settings](https://sailsjs.com/documentation/reference/configuration/sails-config-custom).\n\n### Usage\n\n```usage\nsails.config.custom;\n```\n\n### Example\n\nIn an action or helper:\n\n```javascript\nsails.config.custom.mailgunApiKey;\n// -> \"key-testkeyb183848139913858e8abd9a3\"\n```\n\n### Notes\n\n> + For information on how to set custom configuration in the first place, see [Reference > Config > sails.config.custom](https://sailsjs.com/documentation/reference/configuration/sails-config-custom).\n\n\n<docmeta name=\"displayName\" value=\"sails.config.custom\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/application/sails.getDatastore.md",
    "content": "# sails.getDatastore()\nAccess a particular [datastore](https://sailsjs.com/documentation/concepts/models-and-orm#?datastores), or the default datastore.\n\n```usage\nsails.getDatastore(datastoreName);\n```\n\n### Usage\n\n\n|   |          Argument           | Type                | Details\n|---|---------------------------- | ------------------- |:-----------\n| 1 |        datastoreName        | ((string?))         |  If specified, this is the name of the datastore to look up. Otherwise, if you leave this blank, this `getDatastore()` will return the default datastore for your app.\n\n#### Returns\n\n**Type:** ((Dictionary))\n\nA [datastore instance](https://sailsjs.com/documentation/reference/waterline-orm/datastores).\n\n<docmeta name=\"displayName\" value=\"sails.getDatastore()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/application/sails.getUrlFor.md",
    "content": "# sails.getUrlFor()\n\nLook up the first route pointing at the specified target (e.g. `entrance/view-login`) and return its URL.\n\n\n\n```usage\nsails.getUrlFor(target);\n```\n\n\n### Usage\n\n\n|   |          Argument           | Type                | Details\n|---|---------------------------- | ------------------- |:-----------\n| 1 |        target               | ((string))          | The route target string; e.g. `entrance/view-login` or `PageController.login`\n\n\n##### Returns\n\n**Type:** ((string))\n\n```javascript\n'/login'\n```\n\n\n\n### Example\n\nIn a view...\n\n```ejs\n<a href=\"<%= sails.getUrlFor('entrance/view-login') %>\">Login</a>\n<a href=\"<%= sails.getUrlFor('entrance/view-signup') %>\">Signup</a>\n```\n\nOr, if you're using traditional controllers:\n\n```ejs\n<a href=\"<%= sails.getUrlFor('PageController.login') %>\">Login</a>\n<a href=\"<%= sails.getUrlFor('PageController.signup') %>\">Signup</a>\n```\n\n### Notes\n> - This function searches the Sails app's explicitly configured routes, [`sails.config.routes`](https://sailsjs.com/documentation/reference/configuration/sails-config-routes).  Shadow routes bound by hooks (including [blueprint routes](https://sailsjs.com/documentation/reference/blueprint-api#?blueprint-routes)) will not be matched.\n> - If a matching target cannot be found, this function throws an `E_NOT_FOUND` error (i.e. if you catch the error and check its `code` property, it will be the string `E_NOT_FOUND`).\n> - If more than one route matches the specified target, the first match is returned.\n> - The HTTP method (or \"verb\") from the route address is ignored, if relevant.\n\n<docmeta name=\"displayName\" value=\"sails.getUrlFor()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/application/sails.log.md",
    "content": "# sails.log()\n\nLog a message or some data at the \"debug\" [log level](https://sailsjs.com/documentation/reference/configuration/sails-config-log) using Sails' [built-in logger](https://sailsjs.com/documentation/concepts/logging).\n\n\n```usage\nsails.log(...);\n```\n\n\n### Usage\n\nThis function's usage is purposely very similar to Node's [`console.log()`](https://nodejs.org/api/console.html#console_console_log_data), but with a handful of extra features&mdash;namely support for multiple log levels with colorized, prefixed console output.\n\nNote that standard `console.log()` conventions from Node.js apply:\n - takes an [unlimited number](https://en.wikipedia.org/wiki/Variadic_function) of arguments, separated by commas\n - printf-style parameterization (&agrave; la [`util.format()`](https://nodejs.org/api/util.html#util_util_format_format))\n - objects, dates, arrays, and most other data types are pretty-printed using the built-in logic in [`util.inspect()`](https://nodejs.org/api/util.html#util_util_inspect_object_options) (e.g. you see `{ pet: { name: 'Hamlet' } }` instead of `[object Object]`.)\n - if you log an object with a custom `inspect()` method, that method will run automatically, and the string that it returns will be written to the console.\n\n\n\n### Example\n\n```javascript\nvar sum = +req.param('x') + +req.param('y');\nsails.log();\nsails.log('Hey %s, did you know that the sum of %d and %d is %d?', req.param('name'), +req.param('x'), +req.param('y'), sum);\nsails.log('Bet you didn\\'t know robots could do math, huh?');\nsails.log();\nsails.log('Anyways, here is a dictionary containing all the parameters I received in this request:', req.allParams());\nsails.log('Until next time!');\nreturn res.ok();\n```\n\n\n\n\n### Notes\n> - For a deeper conceptual exploration of logging in Sails, see [concepts/logging](https://sailsjs.com/documentation/concepts/logging).\n> - Remember that, in addition to being exposed as an alternative to calling `console.log` directly, the built-in logger in Sails is called internally by the framework.  The Sails logger can be configured, or completely overridden, using built-in log configuration settings ([`sails.config.log`](https://sailsjs.com/documentation/reference/configuration/sails-config-log)).\n> - Keep in mind that, like any part of Sails, `sails.log` is completely optional.  Most&mdash;but not all&mdash;Sails apps take advantage of the built-in logger: some users prefer to stick with `console.log()`, while others `require()` more feature-rich libraries like [Winston](https://www.npmjs.com/package/winston). If you aren't sure what your app needs yet, start with the built-in logger and go from there.\n\n<docmeta name=\"displayName\" value=\"sails.log()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/blueprint-api/Add.md",
    "content": "# Add (blueprint)\n\nAdd a foreign record (e.g. a comment) to one of this record's collections (e.g. \"comments\").\n\n```usage\nPUT /:model/:id/:association/:fk\n```\n\nThis action adds a reference to some other record (the \"foreign\", or \"child\" record) onto a particular collection of this record (the \"primary\", or \"parent\" record).\n\n+ If the specified `:id` does not correspond with a primary record that exists in the database, this responds using `res.notFound()`.\n+ If the specified `:fk` does not correspond with a foreign record that exists in the database, this responds using `res.notFound()`.\n+ If the primary record is already associated with this foreign record, this action will not modify any records.  (Note that currently, in the case of a many-to-many association, it _will_ add duplicate junction records!  To resolve this, add a multi-column index at the database layer, if possible.  We are currently working on a friendlier solution/default for users of MongoDB, sails-disk, and other NoSQL databases.)\n+ Note that if the association is \"2-way\" (meaning it has `via`), then the foreign key or collection it points to with that `via` will also be updated on the foreign record.\n\n\n### Parameters\n\n Parameter                          | Type                                    | Details\n:-----------------------------------| --------------------------------------- |:---------------------------------\n model          | ((string))   | The [identity](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?identity) of the containing model for the parent record.<br/><br/>e.g. `'employee'` (in `/employee/7/involvedinPurchases/47`)\n id                | ((string))    | The desired parent record's primary key value.<br/><br/>e.g. `'7'` (in `/employee/7/involvedInPurchases/47`)\n association       | ((string))                             | The name of the collection attribute.<br/><br/>e.g. `'involvedInPurchases'`\n fk | ((string))    | The primary key value (usually id) of the child record to add to this collection.<br/><br/>e.g. `'47'`\n\n\n### Example\n\nAdd purchase #47 to the list of purchases that Dolly (employee #7) has been involved in:\n\n```\nPUT /employee/7/involvedInPurchases/47\n```\n\n[![Run in Postman](https://s3.amazonaws.com/postman-static/run-button.png)](https://www.getpostman.com/run-collection/96217d0d747e536e49a4)\n\n##### Expected response\n\nThis returns \"Dolly\", the parent record.  Notice she is now involved in purchase #47:\n\n```json\n{\n  \"id\": 7,\n  \"name\": \"Dolly\",\n  \"createdAt\": 1485462079725,\n  \"updatedAt\": 1485476060873,\n  \"involvedInPurchases\": [\n    {\n      \"amount\": 10000,\n      \"createdAt\": 1485476060873,\n      \"updatedAt\": 1485476060873,\n      \"id\": 47,\n      \"cashier\": 7\n    }\n  ]\n}\n```\n\n\n##### Using jQuery\n\n```javascript\n$.put('/employee/7/involvedInPurchases/47', function (purchases) {\n  console.log(purchases);\n});\n```\n\n##### Using Angular\n\n```javascript\n$http.put('/employee/7/involvedInPurchases/47')\n.then(function (purchases) {\n  console.log(purchases);\n});\n```\n\n##### Using sails.io.js\n\n```javascript\nio.socket.put('/employee/7/involvedInPurchases/47', function (purchases) {\n  console.log(purchases);\n});\n```\n\n##### Using [cURL](http://en.wikipedia.org/wiki/CURL)\n\n```bash\ncurl http://localhost:1337/employee/7/involvedInPurchases/47 -X \"PUT\"\n```\n\n\n### Socket notifications\n\nIf you have WebSockets enabled for your app, then every client [subscribed](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub) to the primary record will receive a notification in which the notification event name is the primary model identity (e.g. `'employee'`), and the message has the following format:\n\n```usage\nid: <the parent record primary key value>,\nverb: 'addedTo',\nattribute: <the parent record collection attribute name>,\naddedIds: <the now-added child records' primary key values>\n```\n\nFor instance, continuing the example above, all clients subscribed to Dolly, aka employee #7, (_except_ for the client making the request) would receive the following message:\n\n```javascript\n{\n  id: 7,\n  verb: 'addedTo',\n  attribute: 'involvedInPurchases',\n  addedIds: [ 47 ]\n}\n```\n\n**Clients subscribed to the child record receive an additional notification:**\n\nAssuming `involvedInPurchases` had a `via`, then either `updated` or `addedTo` notifications would also be sent to any clients who were [subscribed](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub) to purchase #47, the child record we just added.\n\n> If the `via`-linked attribute on the other side is [also plural](https://sailsjs.com/documentation/concepts/models-and-orm/associations/many-to-many) (e.g. `cashiers`), then another `addedTo` notification will be sent. Otherwise, if the `via` [points at a singular attribute](https://sailsjs.com/documentation/concepts/models-and-orm/associations/one-to-many) (e.g. `cashier`) then the [`updated` notification](https://sailsjs.com/documentation/reference/blueprint-api/update#?socket-notifications) will be sent.\n\n**Finally, a third notification might be sent:**\n\nIf adding this purchase to Dolly's collection would \"steal\" it from another employee's `involvedInPurchases`, then any clients subscribed to that other, stolen-from employee record (e.g. Motoki, employee #12) would receive a `removedFrom` notification (see [**Blueprints > remove from**](https://sailsjs.com/documentation/reference/blueprint-api/remove-from#?socket-notifications).\n\n\n### Notes\n\n> + If you'd like to spend some more time with Dolly, a more detailed walkthrough related to the example above is available [here](https://gist.github.com/mikermcneil/e5a20b03be5aa4e0459b).\n> + This action is for dealing with _plural_ (\"collection\") attributes.  If you want to set or unset a _singular_ (\"model\") attribute, just use [update](https://sailsjs.com/documentation/reference/blueprint-api/update) and set the foreign key to the id of the new foreign record (or `null` to clear the association).\n> If you want to completely _replace_ the set of records in the collection with another set, use the [replace](https://sailsjs.com/documentation/reference/blueprint-api/replace) blueprint.\n> + The example above assumes \"rest\" blueprints are enabled, and that your project contains at least an 'Employee' model with attribute: `involvedInPurchases: {collection: 'Purchase', via: 'cashier'}` as well as a `Purchase` model with attribute: `cashier: {model: 'Employee'}`.  You can quickly achieve this by running:\n>\n>   ```shell\n>   $ sails new foo\n>   $ cd foo\n>   $ sails generate model purchase\n>   $ sails generate model employee\n>   ```\n>\n> ...then editing `api/models/Purchase.js` and `api/models/Employee.js`.\n\n\n<docmeta name=\"displayName\" value=\"add to\">\n<docmeta name=\"pageType\" value=\"endpoint\">\n"
  },
  {
    "path": "docs/reference/blueprint-api/Create.md",
    "content": "# Create (blueprint)\n\nCreate a new record in your database.\n\n```usage\nPOST /:model\n```\n\nResponds with a JSON dictionary representing the newly created instance.  If a validation error occurred, a JSON response with the invalid attributes and a `400` status code will be returned instead.\n\nAdditionally, if the [`autoWatch` setting](https://sailsjs.com/documentation/reference/configuration/sails-config-blueprints?properties) is on (which it is by default), then a \"created\" notification will be published to all client sockets which are _watching_ this model; that is, client sockets who have previously sent a request to the \"Find\" blueprint action.  Those same sockets will also be subscribed to hear about subsequent changes to the new record.\n\nFinally, if this blueprint action is triggered via a socket request, then the requesting socket will ALSO be subscribed to the newly created record.  In other words, if the record is subsequently updated or deleted using blueprints, a message will be sent to that client socket informing them of the change.  See [`.subscribe()`](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/subscribe) for more info.\n\n### Parameters\n\nParameters should be sent in the [request body](https://www.getpostman.com/docs/requests#body).  By default, Sails understands the most common types of encodings for body parameters, including url-encoding, form-encoding, and JSON.\n\n Parameter      | Type                                                      | Details\n -------------- | --------------------------------------------------------- |:---------------------------------\n model          | ((string))   | The [identity](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?identity) of the model in which the new record should be created.<br/><br/>e.g. `'purchase'` (in `POST /purchase`)\n _*_            | ((json?))                                                  | Send [body parameters](https://www.getpostman.com/docs/requests#body) with the same names as the attribute defined on your model to set those values on your new record.  <br/> <br/>These values are handled the same way as if they were passed into the model's <a href=\"https://sailsjs.com/documentation/reference/waterline-orm/models/create\">.create()</a> method.\n\n### Example\n\nCreate a new user named \"Applejack\" with a hobby of \"pickin\", who is involved in purchases #13 and #25:\n\n`POST /pony`\n\n```json\n{\n  \"name\": \"Applejack\",\n  \"hobby\": \"pickin\",\n  \"involvedInPurchases\": [13,25]\n}\n```\n\n[![Run in Postman](https://s3.amazonaws.com/postman-static/run-button.png)](https://www.getpostman.com/run-collection/96217d0d747e536e49a4)\n\n##### Example response\n```json\n{\n  \"id\": 47,\n  \"name\": \"Applejack\",\n  \"hobby\": \"pickin\",\n  \"createdAt\": 1485550575626,\n  \"updatedAt\": 1485550603847,\n  \"involvedInPurchases\": [\n    {\n      \"id\": 13,\n      \"amount\": 10000,\n      \"createdAt\": 1485550525451,\n      \"updatedAt\": 1485550544901\n    },\n    {\n      \"id\": 25,\n      \"amount\": 4.50,\n      \"createdAt\": 1485550561340,\n      \"updatedAt\": 1485550561340\n    }\n  ]\n}\n```\n\n### Socket notifications\n\nIf you have WebSockets enabled for your app, then every socket client who is \"watching\" this model (has sent a request to the model's [\"find where\" blueprint action](https://sailsjs.com/documentation/reference/blueprint-api/find-where)) will receive a \"created\" notification where the event name is the model identity (e.g. `user`), and the message has the following format:\n\n```\nverb: 'created',\ndata: <a dictionary of the attribute values of the new record (without associations)>\nid: <the new record primary key>,\n```\n\nFor instance, continuing the example above, all clients who are watching the `User` model (_except_ for the client making the request) would receive the following message:\n```js\nid: 47,\nverb: 'created',\ndata: {\n  id: 47,\n  name: 'Applejack',\n  hobby: 'pickin',\n  createdAt: 1485550575626,\n  updatedAt: 1485550603847\n}\n```\n\n**Clients subscribed to newly-associated child records will receive a notification, too:**\n\nSince the new record in our example included an initial value for `involvedInPurchases`, an association pointed at by `via` on the other side, then `addedTo` notifications would also be sent to any clients who are [subscribed](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub) to those now-associated child records on the other side of the relationship&mdash;in this case, purchases 13 and 25.  See [**Blueprints > add to**](https://sailsjs.com/documentation/reference/blueprint-api/add-to) for more info about the structure of those notifications.\n\n<docmeta name=\"displayName\" value=\"create\">\n<docmeta name=\"pageType\" value=\"endpoint\">\n\n"
  },
  {
    "path": "docs/reference/blueprint-api/Destroy.md",
    "content": "# Destroy (blueprint)\n\nDelete the record specified by `id` from the database forever and notify subscribed sockets.\n\n```usage\nDELETE /:model/:id\n```\n\nThis destroys the record that matches the **id** parameter and responds with a JSON dictionary representing the destroyed instance. If no model instance exists matching the specified **id**, a `404` is returned.\n\nAdditionally, a `destroy` event will be published to all sockets subscribed to the record room, and all sockets currently subscribed to the record will be unsubscribed from it.\n\n\n### Parameters\n\n Parameter                          | Type                                    | Details\n ---------------------------------- | --------------------------------------- |:---------------------------------\n model          | ((string))   | The [identity](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?identity) of the containing model.<br/><br/>e.g. `'purchase'` (in `/purchase/7`)\n id<br/>*(required)*                | ((string))                              | The primary key value of the record to destroy, specified in the path.  <br/>e.g. `'7'` (in `/purchase/7`) .\n\n\n\n### Example\n\nDelete Pinkie Pie:\n\n`DELETE /user/4`\n\n[![Run in Postman](https://s3.amazonaws.com/postman-static/run-button.png)](https://www.getpostman.com/run-collection/96217d0d747e536e49a4)\n\n##### Expected response\n\n```json\n{\n  \"name\": \"Pinkie Pie\",\n  \"hobby\": \"kickin\",\n  \"id\": 4,\n  \"createdAt\": 1485550644076,\n  \"updatedAt\": 1485550644076\n}\n```\n\n### Socket notifications\n\nIf you have WebSockets enabled for your app, then every client [subscribed](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub) to the destroyed record will receive a notification where the event name is that of the model identity (e.g. `user`), and the &ldquo;message&rdquo; has the following format:\n\n```\nverb: 'destroyed',\nid: <the record primary key>,\nprevious: <a dictionary of the attribute values of the destroyed record (including populated associations)>\n```\n\nFor instance, continuing the example above, all clients subscribed to `User` #4 (_except_ for the client making the request) might receive the following message:\n\n```js\nid: 4,\nverb: 'destroyed',\nprevious: {\n  name: 'Pinkie Pie',\n  hobby: 'kickin',\n  createdAt: 1485550644076,\n  updatedAt: 1485550644076\n}\n```\n\n**If the destroyed record had any links to other records, there might be some additional notifications:**\n\nAssuming the record being destroyed in our example had an association with a `via`, then either `updated` or `removedFrom` notifications would also be sent to any clients who are [subscribed](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub) to those child records on the other side of the relationship.  See [**Blueprints > remove from**](https://sailsjs.com/documentation/reference/blueprint-api/remove-from) and [**Blueprints > update**](https://sailsjs.com/documentation/reference/blueprint-api/update) for more info about the structure of those notifications.\n\n> If the association pointed at by the `via` is plural (e.g. `cashiers`), then the `removedFrom` notification will be sent. Otherwise, if the `via` points at a singular association (e.g. `cashier`) then the `updated` notification will be sent.\n\n\n<docmeta name=\"displayName\" value=\"destroy\">\n<docmeta name=\"pageType\" value=\"endpoint\">\n\n"
  },
  {
    "path": "docs/reference/blueprint-api/Find.md",
    "content": "# Find (blueprint)\n\nFind a list of records that match the specified criteria and (if possible) subscribe to each of them.\n\n```usage\nGET /:model\n```\n\nResults may be filtered, paginated, and sorted based on the blueprint configuration and/or parameters sent in the request.\n\nIf the action was triggered via a socket request, the requesting socket will be \"subscribed\" to all records returned. If any of the returned records are subsequently updated or deleted, a message will be sent to that socket's client informing them of the change. See the [docs for Model.subscribe()](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/subscribe) for details.\n\n\n### Parameters\n\n Parameter      | Type         | Details\n -------------- | ------------ |:---------------------------------\n model          | ((string))   | The [identity](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?identity) of the containing model.<br/><br/>e.g. `'purchase'` (in `GET /purchase`)\n _*_              | ((string?))   | To filter results based on a particular attribute, specify a query parameter with the same name as the attribute defined on your model. <br/> <br/> For instance, if our `Purchase` model has an **amount** attribute, we could send `GET /purchase?amount=99.99` to return a list of $99.99 purchases.\n _where_          | ((string?))   | Instead of filtering based on a specific attribute, you may instead choose to provide a `where` parameter with the WHERE piece of a [Waterline criteria](https://sailsjs.com/documentation/concepts/models-and-orm/query-language), _encoded as a JSON string_.  This allows you to take advantage of `contains`, `startsWith`, and other sub-attribute criteria modifiers for more powerful `find()` queries. <br/> <br/> e.g. `?where={\"name\":{\"contains\":\"theodore\"}}`\n _limit_          | ((number?))   | The maximum number of records to send back (useful for pagination). Defaults to 30. <br/> <br/> e.g. `?limit=100`\n _skip_           | ((number?))   | The number of records to skip (useful for pagination). <br/> <br/> e.g. `?skip=30`\n _sort_           | ((string?))   | The sort order. By default, returned records are sorted by primary key value in ascending order. <br/> <br/> e.g. `?sort=lastName%20ASC`\n _select_         | ((string?))   | The attributes to include each record in the result, specified as a comma-delimited list.  By default, all attributes are selected.  Not valid for plural (&ldquo;collection&rdquo;) association attributes.<br/> <br/> e.g. `?select=name,age`.\n _omit_           | ((string?))   | The attributes to exclude from each record in the result, specified as a comma-delimited list.  Cannot be used in conjuction with `select`.    Not valid for plural (&ldquo;collection&rdquo;) association attributes.<br/> <br/> e.g. `?omit=favoriteColor,address`.\n _populate_       | ((string))    | If specified, overide the default automatic population process. Accepts a comma-separated list of attribute names for which to populate record values, or specify `false` to have no attributes populated. See [here](https://sailsjs.com/documentation/concepts/models-and-orm/records#?populated-values) for more information on how the population process fills out attributes in the returned list of records according to the model's defined associations.\n\n\n\n### Example\n\nFind up to 30 of the newest purchases in our database:\n\n```text\nGET /purchase?sort=createdAt DESC&limit=30\n```\n\n[![Run in Postman](https://s3.amazonaws.com/postman-static/run-button.png)](https://www.getpostman.com/run-collection/96217d0d747e536e49a4)\n\n##### Expected response\n\ne.g.\n```json\n[\n {\n   \"amount\": 49.99,\n   \"id\": 1,\n   \"createdAt\": 1485551132315,\n   \"updatedAt\": 1485551132315\n },\n {\n   \"amount\": 99.99,\n   \"id\": 47,\n   \"createdAt\": 1485551158349,\n   \"updatedAt\": 1485551158349\n }\n]\n```\n\n\n##### Using jQuery\n\n> See [jquery.com](http://jquery.com/) for more documentation.\n\n```javascript\n$.get('/purchase?sort=createdAt DESC', function (purchases) {\n  console.log(purchases);\n});\n```\n\n\n##### Using sails.io.js\n\n> See [sails.io.js](https://sailsjs.com/documentation/reference/web-sockets/socket-client) for more documentation.\n\n```javascript\nio.socket.get('/purchase?sort=createdAt DESC', function (purchases) {\n  console.log(purchases);\n});\n```\n\n##### Using Angular\n\n> See [Angular](https://angularjs.org/) for more documentation.\n\n```javascript\n$http.get('/purchase?sort=createdAt DESC')\n.then(function (res) {\n  var purchases = res.data;\n  console.log(purchases);\n});\n```\n\n\n##### Using cURL\n\n> You can read more about [cURL on Wikipedia](http://en.wikipedia.org/wiki/CURL).\n\n```bash\ncurl http://localhost:1337/purchase?sort=createdAt%20DESC\n```\n\n\n### Notes\n\n> + The example above assumes \"rest\" blueprints are enabled, and that your project contains a `Purchase` model.  You can quickly achieve this by running:\n>\n>   ```bash\n>   $ sails new foo\n>   $ cd foo\n>   $ sails generate model purchase\n>   $ sails lift\n>     # You will see a prompt about database auto-migration settings.\n>     # Just choose 1 (alter) and press <ENTER>.\n>   ```\n\n\n<docmeta name=\"displayName\" value=\"find where\">\n<docmeta name=\"pageType\" value=\"endpoint\">\n\n"
  },
  {
    "path": "docs/reference/blueprint-api/FindOne.md",
    "content": "# Find one (blueprint)\n\nLook up the record with the specified `id` from the database, and (if possible) subscribe to the record in order to hear about any future changes.\n\n```usage\nGET /:model/:id\n```\n\n\nThe **findOne()** blueprint action returns a single record from the model (given by `:model`) as a JSON object. The specified `id` is the [primary key](http://en.wikipedia.org/wiki/Unique_key) of the desired record.\n\nIf the action was triggered via a socket request, the requesting socket will be \"subscribed\" to the returned record. If the record is subsequently updated or deleted, a message will be sent to that socket's client informing them of the change. See the [.subscribe()](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/subscribe) docs for more info.\n\n\n### Parameters\n\n Parameter                          | Type                                    | Details\n ---------------------------------- | --------------------------------------- |:---------------------------------\n model          | ((string))   | The [identity](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?identity) of the containing model.<br/><br/>e.g. `'purchase'` (in `/purchase/7`)\n id                | ((string))    | The desired target record's primary key value<br/><br/>e.g. `'7'` (in `/purchase/7`).\n _populate_       | ((string?))    | If specified, overide the default automatic population process. Accepts a comma-separated list of attribute names for which to populate record values, or specify `false` to have no attributes populated. See [here](https://sailsjs.com/documentation/concepts/models-and-orm/records#?populated-values) for more information on how the population process fills out attributes in the returned record according to the model's defined associations.\n _select_         | ((string?))   | The attributes to include in the result, specified as a comma-delimited list.  By default, all attributes are selected.  Not valid for plural (&ldquo;collection&rdquo;) association attributes.<br/> <br/> e.g. `?select=name,age`.\n _omit_           | ((string?))   | The attributes to exclude from the result, specified as a comma-delimited list.  Cannot be used in conjuction with `select`.    Not valid for plural (&ldquo;collection&rdquo;) association attributes.<br/> <br/> e.g. `?omit=favoriteColor,address`.\n\n\n### Example\nFind the purchase with id #1:\n\n```text\nGET /purchase/1\n```\n\n[![Run in Postman](https://s3.amazonaws.com/postman-static/run-button.png)](https://www.getpostman.com/run-collection/96217d0d747e536e49a4)\n\n##### Expected Response\n\n ```json\n {\n   \"amount\": 49.99,\n   \"id\": 1,\n   \"createdAt\": 1485551132315,\n   \"updatedAt\": 1485551132315\n }\n ```\n\n\n<docmeta name=\"displayName\" value=\"find one\">\n<docmeta name=\"pageType\" value=\"endpoint\">\n\n"
  },
  {
    "path": "docs/reference/blueprint-api/Populate.md",
    "content": "# Populate (blueprint)\n\nPopulate and return foreign record(s) for the given association of this record.\n\n\n```usage\nGET /:model/:id/:association\n```\n\nIf the specified association is plural (\"collection\"), this action returns the list of associated records as a JSON-encoded array of dictionaries (plain JavaScript objects).  If the specified association is singular (\"model\"), this action returns the associated record as a JSON-encoded dictionary.\n\n\n  Parameter      | Type         | Details\n :-------------- | ------------ |:---------------------------------\n model           | ((string))   | The [identity](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?identity) of the containing model.<br/><br/>e.g. `'purchase'` (in `GET /purchase/47/cashier`)\n id              | ((string))   | The primary key of the parent record.<br/><br/>e.g. `'47'` (in `GET /purchase/47/cashier`)\n association     | ((string))   | The name of the association.<br/><br/>e.g. `'cashier'` (in `GET /purchase/47/cashier`) or `'products'` (in `GET /purchase/47/products`)\n _where_          | ((string?))   | Instead of filtering based on a specific attribute, you may instead choose to provide a `where` parameter with the WHERE piece of a [Waterline criteria](https://sailsjs.com/documentation/concepts/models-and-orm/query-language), _encoded as a JSON string_.  This allows you to take advantage of `contains`, `startsWith`, and other sub-attribute criteria modifiers for more powerful `find()` queries. <br/> <br/> e.g. `?where={\"name\":{\"contains\":\"theodore\"}}`\n _limit_          | ((number?))   | The maximum number of records to send back (useful for pagination). Defaults to 30. <br/> <br/> e.g. `?limit=100`\n _skip_           | ((number?))   | The number of records to skip (useful for pagination). <br/> <br/> e.g. `?skip=30`\n _sort_           | ((string?))   | The sort order. By default, returned records are sorted by primary key value in ascending order. <br/> <br/> e.g. `?sort=lastName%20ASC`\n _select_         | ((string?))   | The attributes to include in each record in the result, specified as a comma-delimited list.  By default, all attributes are selected.  Not valid for plural (&ldquo;collection&rdquo;) association attributes.<br/> <br/> e.g. `?select=name,age`.\n _omit_           | ((string?))   | The attributes to exclude from each record in the result, specified as a comma-delimited list.  Cannot be used in conjuction with `select`.    Not valid for plural (&ldquo;collection&rdquo;) association attributes.<br/> <br/> e.g. `?omit=favoriteColor,address`.\n\n\n### Example\n\nPopulate the `cashier` who conducted purchase #47:\n\n```text\n`GET /purchase/47/cashier`\n```\n\n[![Run in Postman](https://s3.amazonaws.com/postman-static/run-button.png)](https://www.getpostman.com/run-collection/96217d0d747e536e49a4)\n\n##### Expected response\n\n```json\n{\n  \"name\": \"Dolly\",\n  \"id\": 7,\n  \"createdAt\": 1485462079725,\n  \"updatedAt\": 1485476060873,\n}\n```\n\n**Using [jQuery](http://jquery.com/):**\n\n```javascript\n$.get('/purchase/47/cashier', function (cashier) {\n  console.log(cashier);\n});\n```\n\n**Using [Angular](https://angularjs.org/):**\n\n```javascript\n$http.get('/purchase/47/cashier')\n.then(function (cashier) {\n  console.log(cashier);\n});\n```\n\n**Using [sails.io.js](https://sailsjs.com/documentation/reference/web-sockets/socket-client):**\n\n```javascript\nio.socket.get('/purchase/47/cashier', function (cashier) {\n  console.log(cashier);\n});\n```\n\n**Using [cURL](http://en.wikipedia.org/wiki/CURL):**\n\n```bash\ncurl http://localhost:1337/purchase/47/cashier\n```\n\n### Populating a collection\n\nYou can also populate a collection. For example, to populate the `involvedInPurchases` of employee #7:\n\n`GET /employee/7/involvedInPurchases`\n\n##### Expected response\n\n```json\n[\n  {\n    \"amount\": 10000,\n    \"createdAt\": 1485476060873,\n    \"updatedAt\": 1485476060873,\n    \"id\": 47,\n    \"cashier\": 7\n  },\n  {\n    \"amount\": 50,\n    \"createdAt\": 1487015460792,\n    \"updatedAt\": 1487015476357,\n    \"id\": 52,\n    \"cashier\": 7\n  }\n]\n```\n\n\n\n### Notes\n\n> + In the first example above, if purchase #47 did not have a `cashier` (i.e. `null`), then this action would respond with a 404 status code.\n> + The examples above assume \"rest\" blueprint routing is enabled (or that you've bound this blueprint action as a comparable [custom route](https://sailsjs.com/documentation/concepts/routes/custom-routes)), and that your project contains at least an empty `Employee` model as well as a `Purchase` model, and that `Employee` has the association attribute: `involvedInPurchases: {model: 'Purchase'}` and that `Purchase` has `cashier: {model: 'Employee'}`.  You can quickly achieve this by running:\n>\n>   ```shell\n>   $ sails new foo\n>   $ cd foo\n>   $ sails generate model purchase\n>   $ sails generate model employee\n>   ```\n> ...then editing `api/models/Employee.js` and `api/models/Purchase.js`.\n\n\n<docmeta name=\"displayName\" value=\"populate where\">\n<docmeta name=\"pageType\" value=\"endpoint\">\n\n"
  },
  {
    "path": "docs/reference/blueprint-api/Remove.md",
    "content": "# Remove (blueprint)\n\nRemove a foreign record (e.g. a comment) from one of this record's collections (e.g. \"comments\").\n\n```usage\nDELETE /:model/:id/:association/:fk\n```\n\nThis action removes a reference to some other record (the \"foreign\" or \"child\" record) from a collection of this record (the \"primary\" or \"parent\" record).  Note that this does not actually destroy the foreign record, it just unlinks it.\n\n+ If the primary record does not exist, this responds using `res.notFound()`.\n+ If the foreign record does not exist, this responds using `res.notFound()`.\n+ If the collection doesn't contain a reference to the foreign record, this action will not modify any records.\n+ If the association is \"2-way\" (meaning it has `via`), then the foreign key or collection it points to with that `via` will also be updated on the foreign record.\n\n### Parameters\n\n Parameter                          | Type                                    | Details\n:---------------------------------- | --------------------------------------- |:---------------------------------\n model | ((string)) | The [identity](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?identity) of the containing model for the parent record.<br/><br/>e.g. `'store'` (in `/store/16/employeesOfTheMonth/7`)\n id | ((string)) | The desired parent record's primary key value.<br/><br/>e.g. `'16'` (in `/store/16/employeesOfTheMonth/7`)\n association       | ((string))                              | The name of the collection attribute.<br/><br/>e.g. `'employeesOfTheMonth'`\n fk  | ((string))    | The primary key value (usually id) of the child record to remove from the collection.<br/><br/>e.g. `'7'`\n\n\n### Example\n\nSay you're building an app for a small chain of grocery stores.  Each store has a giant television screen that displays the current \"Employees of the Month\" at that store, so that customers and team members see it when they walk in the door.  In order to be sure it is up to date, you build a scheduled job (e.g. using [cron](https://en.wikipedia.org/wiki/Cron)) that runs on the first day of every month to change the \"Employees of the Month\" for each store in their system.\n\nLet's say that, as a part of this scheduled job, we send a request to remove Dolly (employee #7) from store #16's `employeesOfTheMonth`:\n\n```text\nDELETE /store/16/employeesOfTheMonth/7\n```\n[![Run in Postman](https://s3.amazonaws.com/postman-static/run-button.png)](https://www.getpostman.com/run-collection/96217d0d747e536e49a4)\n\n##### Expected response\n\n```json\n{\n  \"id\": 16,\n  \"name\": \"Parmer and N. Lamar\",\n  \"createdAt\": 1485552033435,\n  \"updatedAt\": 1485552048794,\n  \"employeesOfTheMonth\": [\n    {\n      \"id\": 12,\n      \"name\": \"Motoki\",\n      \"createdAt\": 1485462079725,\n      \"updatedAt\": 1485476060873\n    },\n    {\n      \"id\": 4,\n      \"name\": \"Timothy\",\n      \"createdAt\": 1485462079727,\n      \"updatedAt\": 1485476090874\n    }\n  ]\n}\n```\n\n### Socket notifications\n\nIf you have WebSockets enabled for your app, then every client [subscribed](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub) to the parent record will receive a notification about the removed child, where the notification event name is that of the parent model identity (e.g. `store`), and the &ldquo;message&rdquo; has the following format:\n\n```\nid: <the parent record's primary key value>,\nverb: 'removedFrom',\nattribute: <the parent record collection attribute name>,\nremovedIds: <the now-removed child records' primary key values>\n```\n\nFor instance, continuing the example above, all clients subscribed to employee #7 (_except_ for the client making the request) would receive the following message:\n\n```javascript\n{\n  id: 16,\n  verb: 'removedFrom',\n  attribute: 'employeesOfTheMonth',\n  removedIds: [ 7 ]\n}\n```\n\n**Clients subscribed to the child record receive an additional notification:**\n\nAssuming `employeesOfTheMonth` was defined with a `via`, then either `updated` or `removedFrom` notifications would also be sent to any clients who were [subscribed](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub) to Dolly, the child record we removed.\n\n> If the `via`-linked attribute on the other side is [also plural](https://sailsjs.com/documentation/concepts/models-and-orm/associations/many-to-many) (e.g. `employeeOfTheMonthAtStores`), then another `removedFrom` notification will be sent. Otherwise, if the `via` [points at a singular attribute](https://sailsjs.com/documentation/concepts/models-and-orm/associations/one-to-many) (e.g. `employeeOfTheMonthAtStore`) then the [`updated` notification](https://sailsjs.com/documentation/reference/blueprint-api/update#?socket-notifications) will be sent.\n\n\n### Notes\n\n> + If you'd like to spend some more time with Dolly, a more detailed walkthrough for the example above is available [here](https://gist.github.com/mikermcneil/e5a20b03be5aa4e0459b).\n> + This action is for dealing with _plural_ (\"collection\") attributes.  If you want to set or unset a _singular_ (\"model\") attribute, just use [update](https://sailsjs.com/documentation/reference/blueprint-api/update) and set the foreign key to the id of the new foreign record (or `null` to clear the association).\n> + If you want to completely _replace_ the set of records in the collection with another set, use the [replace](https://sailsjs.com/documentation/reference/blueprint-api/replace) blueprint.\n\n<docmeta name=\"displayName\" value=\"remove from\">\n<docmeta name=\"pageType\" value=\"endpoint\">\n\n"
  },
  {
    "path": "docs/reference/blueprint-api/Replace.md",
    "content": "# Replace (blueprint)\n\nReplace all of the foreign records in one of this record's collections (e.g. \"comments\").\n\n```usage\nPUT /:model/:id/:association\n```\n\nThis action resets references to \"foreign\", or \"child\" records that are members of a particular collection of _this_ record (the \"primary\", or \"parent\" record), replacing any existing references in the collection.\n\n+ If the specified `:id` does not correspond with a primary record that exists in the database, this responds using `res.notFound()`.\n+ Note that, if the association is \"2-way\" (meaning it has `via`), then the foreign key or collection on the foreign record(s) will also be updated.\n\n\n### Parameters\n\n Parameter                          | Type                                    | Details\n:-----------------------------------| --------------------------------------- |:---------------------------------\n model          | ((string))   | The [identity](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?identity) of the containing model for the parent record.<br/><br/>e.g. `'employee'` (in `/employee/7/involvedinPurchases`)\n id                | ((string))    | The desired parent record's primary key value.<br/><br/>e.g. `'7'` (in `/employee/7/involvedInPurchases`)\n association       | ((string))                             | The name of the collection attribute.<br/><br/>e.g. `'involvedInPurchases'`\n fks | ((array))    | The primary key values (usually ids) of the child records to use as the new members of this collection.<br/><br/>e.g. `[47, 65]`\n\n> _The `fks` parameter should be sent in the PUT request body, unless you are making this request using a development-only [shortcut blueprint route](https://sailsjs.com/documentation/concepts/blueprints/blueprint-routes#?shortcut-routes), in which case you can simply include it in the query string as `?fks=[47,65]`._\n\n### Example\nSuppose you are in charge of keeping records for a large chain of grocery stores, and Dolly the cashier (employee #7) had been taking credit for being involved in a large number of purchases, when really she had only checked out two customers. Since the owner of the grocery store chain is very forgiving, Dolly gets to keep her job, but now you have to update Dolly's `involvedInPurchases` collection so that it _only_ contains purchases #47 and #65:\n\n`PUT /employee/7/involvedInPurchases`\n\n```json\n[47, 65]\n```\n\n[![Run in Postman](https://s3.amazonaws.com/postman-static/run-button.png)](https://www.getpostman.com/run-collection/96217d0d747e536e49a4)\n\n##### Expected response\n\nThis returns Dolly, the parent record.  Notice that her record only shows her being involved in purchases #47 and #65:\n\n```json\n{\n  \"id\": 7,\n  \"name\": \"Dolly\",\n  \"createdAt\": 1485462079725,\n  \"updatedAt\": 1485476060873,\n  \"involvedInPurchases\": [\n    {\n      \"amount\": 10000,\n      \"createdAt\": 1485551132315,\n      \"updatedAt\": 1486355134239,\n      \"id\": 47,\n      \"cashier\": 7\n    },\n    {\n      \"amount\": 5667,\n      \"createdAt\": 1483551158349,\n      \"updatedAt\": 1485355134284,\n      \"id\": 65,\n      \"cashier\": 7\n    }\n  ]\n}\n```\n\n### Socket notifications\n\nIf you have WebSockets enabled for your app, then every client [subscribed](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub) to the parent record will receive one [`addedTo` notification](https://sailsjs.com/documentation/reference/blueprint-api/add-to#?socket-notifications) for each child record in the new collection (if any).\n\nFor instance, continuing the example above, let's assume that Dolly's previous `involvedInPurchases` included purchases #65, #42, and #33. All clients subscribed to Dolly's employee record (_except_ for the client making the request) would receive two kinds of notifications: `addedTo` for the purchase she was not previously involved in (#47), and `removedFrom` for the purchases she is no longer involved in (#42 and #33).\n\n```javascript\n{\n  id: 7,\n  verb: 'addedTo',\n  attribute: 'involvedInPurchases',\n  addedIds: [ 47 ]\n}\n```\n\nand\n\n```javascript\n{\n  id: 7,\n  verb: 'removedFrom',\n  attribute: 'involvedInPurchases',\n  removedIds: [ 42, 33 ]\n}\n```\n\n> Note that purchase #65 is not included in the `addedTo` notification, since it was in Dolly's previous list of `involvedInPurchases`.\n\n**Clients subscribed to the child records receive additional notifications:**\n\nAssuming `involvedInPurchases` had a `via`, then either `updated` or `addedTo`/`removedFrom` notifications would also be sent to clients who were [subscribed](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub) to any of the purchases we just linked or unlinked.\n\n> If the `via`-linked attribute on the other side (Purchase) is [also plural](https://sailsjs.com/documentation/concepts/models-and-orm/associations/many-to-many) (e.g. `cashiers`), then an `addedTo` or `removedFrom` notification will be sent. Otherwise, if the `via` [points at a singular attribute](https://sailsjs.com/documentation/concepts/models-and-orm/associations/one-to-many) (e.g. `cashier`) then the [`updated` notification](https://sailsjs.com/documentation/reference/blueprint-api/update#?socket-notifications) will be sent.\n\n**Finally, a third kind of notification might be sent:**\n\nIf giving Dolly this new collection of Purchases would \"steal\" any of them from other employees' `involvedInPurchases`, then any clients subscribed to those other, stolen-from employee records (e.g. Motoki, employee #12 and Timothy, employee #4) would receive `removedFrom` notifications. (See [**Blueprints > remove from**](https://sailsjs.com/documentation/reference/blueprint-api/remove-from#?socket-notifications)).\n\n\n### Notes\n\n> + Remember, this blueprint replaces the _entire_ set of associated records for the given attribute.  To add or remove a single associated record from the collection, leaving the rest of the collection unchanged, use the \"add\" or \"remove\" blueprint actions. (See [**Blueprints > add to**](https://sailsjs.com/documentation/reference/blueprint-api/add-to) and [**Blueprints > remove from**](https://sailsjs.com/documentation/reference/blueprint-api/remove-from)).\n\n\n<docmeta name=\"displayName\" value=\"replace\">\n<docmeta name=\"pageType\" value=\"endpoint\">\n"
  },
  {
    "path": "docs/reference/blueprint-api/Update.md",
    "content": "# Update (blueprint)\n\nUpdate an existing record in the database and notify subscribed sockets that it has changed.\n\n```usage\nPATCH /:model/:id\n```\n\nThis updates the record in the model which matches the **id** parameter and responds with the newly updated record as a JSON dictionary.  If a validation error occurred, a JSON response with the invalid attributes and a `400` status code will be returned instead.  If no model instance exists matching the specified **id**, a `404` is returned.\n\n\n### Parameters\n\n_Attributes to change should be sent in the HTTP body as form-encoded values or JSON._\n\n Parameter                          | Type                                                    | Details\n ---------------------------------- | ------------------------------------------------------- |:---------------------------------\n model                              | ((string))                                              | The [identity](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?identity) of the containing model.<br/><br/>e.g. `'product'` (in `PATCH /product/5`)\n id                                 | ((string))                                              | The primary key value of the record to update.<br/><br/>e.g. `'5'` (in `PATCH /product/5`)\n *                                 | ((json))                                                 | For `PATCH` (RESTful) requests, pass in body parameters with the same name as the attributes defined on your model to set those values on the desired record. For `GET` (shortcut) requests, add the parameters to the query string.\n\n### Example\n\nChange Applejack's hobby to \"kickin\":\n\n`PATCH /user/47`\n\n```json\n{\n  \"hobby\": \"kickin\"\n}\n```\n\n[![Run in Postman](https://s3.amazonaws.com/postman-static/run-button.png)](https://www.getpostman.com/run-collection/96217d0d747e536e49a4)\n\n##### Expected response\n```json\n{\n  \"hobby\": \"kickin\",\n  \"id\": 47,\n  \"name\": \"Applejack\",\n  \"createdAt\": 1485462079725,\n  \"updatedAt\": 1485476060873\n}\n```\n\n### Socket notifications\n\nIf you have WebSockets enabled for your app, then every client [subscribed](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub) to the updated record will receive a notification where the event name is that of the model identity (e.g. `user`), and the data &ldquo;payload&rdquo; has the following format:\n\n```\nverb: 'updated',\nid: <the record primary key>,\ndata: <a dictionary of changes made to the record>,\nprevious: <the record prior to the update>\n```\n\nFor instance, continuing the example above, all clients subscribed to `User` #47 (_except_ for the client making the request) would receive the following message:\n\n```js\n{\n  id: 47,\n  verb: 'updated',\n  data: {\n    id: 47,\n    hobby: 'kickin'\n    updatedAt: 1485476060873\n  },\n  previous: {\n    hobby: 'pickin',\n    id: 47,\n    name: 'Applejack',\n    createdAt: 1485462079725,\n    updatedAt: 1485462079725\n  }\n}\n```\n\n**If the update changed any links to other records, there might be some additional notifications:**\n\n\n\n\nIf we were reassigning user #47 to store #25, we'd update `store`, which represents the &ldquo;one&rdquo; side of a [one-to-many association](https://sailsjs.com/documentation/concepts/models-and-orm/associations/one-to-many). For instance:\n\n`PATCH /user/47`\n\n```json\n{\n  \"store\": 25\n}\n```\n\nClients subscribed to the new store (25) would receive an `addedTo` notification, and a `removedFrom` notification would be sent to any clients subscribed to the old store. See the [add blueprint reference](https://sailsjs.com/documentation/reference/blueprint-api/add-to) and the [remove blueprint reference](https://sailsjs.com/documentation/reference/blueprint-api/remove-from) for more info about those notifications.\n\n\n\n### Notes\n\n> + This action can be used to replace an entire collection association (for example, to replace a user&rsquo;s list of friends), achieving the same result as the [`replace` blueprint action](https://sailsjs.com/documentation/reference/blueprint-api/replace).  To modify items in a collection individually, use the [add](https://sailsjs.com/documentation/reference/blueprint-api/add-to) or [remove](https://sailsjs.com/documentation/reference/blueprint-api/remove-from) actions.\n> + In previous Sails versions, this action was bound to the `PUT /:model/:id` route.\n\n\n<docmeta name=\"displayName\" value=\"update\">\n<docmeta name=\"pageType\" value=\"endpoint\">\n\n"
  },
  {
    "path": "docs/reference/blueprint-api/blueprint-api.md",
    "content": "# Blueprint API\n\n### Overview\n\nFor a conceptual overview of blueprints, see [Concepts > Blueprints](https://sailsjs.com/documentation/concepts/blueprints).\n\n### Activating/deactivating blueprint routes in your app\n\nThe process for activating/deactivating blueprints varies slightly with the kind of blueprint route you are concerned with (RESTful routes, shortcut routes, or action routes).  See the [Blueprint Routes documentation section](https://sailsjs.com/documentation/concepts/blueprints?blueprint-routes) for a discussion of the different blueprint types.\n\n\n### Overriding blueprints\n\nTo change a blueprint route, we recommend [explicitly configuring a custom route](https://sailsjs.com/documentation/concepts/routes/custom-routes).  Similarly, if you want to override a blueprint action, we recommend writing your own [custom action](https://sailsjs.com/documentation/concepts/actions-and-controllers). \n\nBut if you really know what you're doing, then read on:\n\n##### RESTful / shortcut routes and actions\n\nTo override a RESTful blueprint route for a single model, simply create an action in the relevant controller file (or a [standalone action](https://sailsjs.com/documentation/concepts/actions-and-controllers#?standalone-actions) in the relevant folder) with the appropriate name: [_find_](https://sailsjs.com/documentation/reference/blueprint-api/find-where), [_findOne_](https://sailsjs.com/documentation/reference/blueprint-api/find-one), [_create_](https://sailsjs.com/documentation/reference/blueprint-api/create), [_update_](https://sailsjs.com/documentation/reference/blueprint-api/update), [_destroy_](https://sailsjs.com/documentation/reference/blueprint-api/destroy), [_populate_](https://sailsjs.com/documentation/reference/blueprint-api/populate), [_add_](https://sailsjs.com/documentation/reference/blueprint-api/add) or [_remove_](https://sailsjs.com/documentation/reference/blueprint-api/remove).\n\n> If you&rsquo;d like to override a particular blueprint for _all_ models, check out the <a href=\"https://www.npmjs.com/package/sails-hook-custom-blueprints\" target=\"_blank\">sails-hook-custom-blueprints plugin</a>.\n> It's important to realize that, even if you haven't defined these yourself, Sails will respond with built-in CRUD logic for each model in the form of a JSON API (including support for sort, pagination, and filtering) as long as action or shortcut blueprints are enabled in your [blueprints configuration](https://sailsjs.com/documentation/reference/configuration/sails-config-blueprints).\n\n\n### Blueprints and resourceful PubSub\n\nThe blueprint API is compatible with WebSockets (as are any of your custom actions and policies), thanks to the virtual request interpreter.  Check out the reference section on the browser SDK ([Reference > WebSockets > sails.io.js](https://sailsjs.com/documentation/reference/web-sockets/socket-client)) for example usage.\n\n##### Blueprints and `.subscribe()`\n\nBy default, the **Find** and **Find One** blueprint actions will call [`.subscribe()`](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/subscribe) automatically when a socket request is used. This subscribes the requesting socket to each of the returned records.  However, if the _same_ socket sends a request to the **Update** or **Destroy** actions with `io.socket.put()` (for example) this will *not* by default cause a message to be sent to the requesting socket, but to the *other* connected, subscribed sockets.  This is intended to allow UI code to use the client-side SDK's callback to handle the server response separately, e.g. to replace a loading spinner.\n\n\n##### Blueprints and \"auto-watch\"\n\nBy default, the **find** blueprint action (when triggered via a WebSocket request) will subscribe the requesting socket to notifications about _new_ instances of that model being created.  This behavior can be changed for all models by setting [`sails.config.blueprints.autoWatch`](https://sailsjs.com/documentation/reference/configuration/sails-config-blueprints) to `false`.\n\n\n##### Disabling blueprint routes on a per-controller basis\n\n> The following technique is only supported for compatibility reasons.  Please just use custom routes, whether or not you are using blueprint actions!\n\nIf you are using controllers, rather than standalone action files, it is possible to **disable** certain settings from [`config/blueprints.js`](https://sailsjs.com/documentation/anatomy/my-app/config/blueprints-js) on a per-controller basis by defining a `_config` key in your controller definition:\n\n```javascript\n// In /api/controllers/PetController.js\nmodule.exports = {\n  _config: {\n    actions: false,\n    shortcuts: false,\n    rest: false\n  }\n}\n```\n\n> Disabling `shortcuts`-style automatic routes on a per-controller basis is not supported.  This is never necessary, because you should never use `shortcuts: true` in production.\n\n\n\n<docmeta name=\"displayName\" value=\"Blueprint API\">\n"
  },
  {
    "path": "docs/reference/cli/cli.md",
    "content": "# Command-line interface (CLI)\n\nSails comes with a convenient command-line tool to quickly get your app scaffolded and running. The CLI has commands for creating, starting, and debugging your Sails applications, as well as for getting your version info. For information about each command's usage, see the reference pages in this section.\n\n<docmeta name=\"displayName\" value=\"Command-line interface\">\n\n"
  },
  {
    "path": "docs/reference/cli/sailsconsole.md",
    "content": "# `sails console`\n\nLift your Node.js/Sails.js app in interactive mode, and enter the [REPL](http://nodejs.org/api/repl.html).  This means you can access and use all of your models, helpers, configuration, services, and the `sails` app instance.  Useful for trying out Waterline queries, quickly managing your data, and checking out your project's runtime configuration.\n\n```usage\nsails console\n```\nBy default, this still lifts the server, so your routes will be accessible via HTTP and sockets (e.g. in a browser).\n\n\n### Usage\n`sails console` takes the following options:\n  * `--dontLift`: start `sails console` without lifting the server\n\n### Example\n\n```text\n$ sails console\n\ninfo: Starting app in interactive mode...\n\ninfo: Welcome to the Sails console.\ninfo: ( to exit, type <CTRL>+<C> )\n\nsails>\n```\n\n\n\n\n\n### Global variables in `sails console`\n\nSails exposes [the same global variables](https://sailsjs.com/documentation/reference/Globals) in the REPL as it does in your app code. By default, you have access to the `sails` app instance and your models, as well as any of your other configured globals (for example, lodash (`_`) and async (`async`)).\n\n\n> **Warning**\n>\n> In Node versions earlier than v6, using `_` as a variable in the REPL will cause unexpected behavior.  As an alternative, simply import the Lodash module as a variable:\n>\n> ```bash\n> sails> var lodash = require('lodash');\n> sails> console.log(lodash.range(1, 5));\n> ```\n\n\n### More examples\n\n##### Waterline\n\nThe format `Model.action(query).exec(console.log)` console.log is good for seeing the results.\n\n```text\nsails> User.create({name: 'Brian', password: 'sailsRules'}).fetch().exec(console.log)\nundefined\nsails> undefined { name: 'Brian',\n  password: 'sailsRules',\n  createdAt: \"2014-08-07T04:29:21.447Z\",\n  updatedAt: \"2014-08-07T04:29:21.447Z\",\n  id: 1 }\n```\n\nIt inserts it into the database, which is pretty cool. However, you might be noticing the `undefined` and `null`&mdash;don't worry about those. Remember that the .exec() returns errors and data for values, so `.exec(console.log)` has the same effect as `.exec(console.log(err, data))`. The second method will remove the undefined message, but add null on a new line. Whether you want to type more is up to you.\n\n> Note that starting with Node 6, an object&rsquo;s constructor name is displayed next to it in the console.  For example, when using the [`sails-mysql` adapter](https://sailsjs.com/documentation/concepts/extending-sails/adapters/available-adapters#?sailsmysql), the `create` query mentioned above would output:\n>\n> ```text\n> sails> undefined RowDataPacket { name: 'Brian',\n>   password: 'sailsRules',\n>   createdAt: \"2014-08-07T04:29:21.447Z\",\n>   updatedAt: \"2014-08-07T04:29:21.447Z\",\n>   id: 1 }\n> ```\n\n##### Exposing Sails\n\nIn `sails console`, type `sails` to view a list of Sails properties. You can use this to learn more about Sails, override properties, or check to see if you disabled globals.\n\n```text\nsails> sails\n  |>   [a lifted Sails app on port 1337]\n\\___/  For help, see: https://sailsjs.com/documentation/concepts/\n\nTip: Use `sails.config` to access your app's runtime configuration.\n\n1 Models:\nUser\n\n1 Controllers:\nUserController\n\n20 Hooks:\nmoduleloader,logger,request,orm,views,blueprints,responses,controllers,sockets,p\nubsub,policies,services,csrf,cors,i18n,userconfig,session,grunt,http,projecthooks\n\nsails>\n```\n\n\n<docmeta name=\"displayName\" value=\"sails console\">\n<docmeta name=\"pageType\" value=\"command\">\n"
  },
  {
    "path": "docs/reference/cli/sailsdebug.md",
    "content": "# `sails debug`\n\n> ##### _**This command should only be used with older versions of Node.  For Node v6 and above, use [`sails inspect`](https://sailsjs.com/documentation/reference/command-line-interface/sails-inspect).**_\n\nAttach the node debugger and lift the Sails app (similar to running `node --debug app.js`). You can then use [node-inspector](https://github.com/node-inspector/node-inspector) to debug your app as it runs.\n\n```usage\nsails debug\n```\n\n\n### Usage\nTakes the same options as [`sails lift`](https://sailsjs.com/documentation/reference/command-line-interface/sails-lift), listed [here](https://sailsjs.com/documentation/reference/command-line-interface/sails-lift#?usage).\n\n\n### Example\n\n```text\n$ sails debug\n\ninfo: Running node-inspector on this app...\ninfo: If you don't know what to do next, type `help`\ninfo: Or check out the docs:\ninfo: http://nodejs.org/api/debugger.html\n\ninfo: ( to exit, type <CTRL>+<C> )\n\ndebugger listening on port 5858\n```\n\n\n> To use the standard (command-line) Node debugger with Sails, you can always just run `node debug app.js`.\n\n### Using Node Inspector\n\nTo debug your Sails app using Node Inspector, first install it over npm:\n\n```bash\n$ npm install -g node-inspector\n```\n\nThen, launch it with the `node-inspector` command:\n\n```bash\n$ node-inspector\n```\n\nNow, you can lift your Sails app in debug mode:\n\n```bash\n$ sails debug\n```\n\nOnce the application is launched, visit http://127.0.0.1:8080?port=5858 in Opera or Chrome (Sorry, other browsers!). Now you can request your app as usual on port 1337 and debug your code from the browser.\n\n> **How it works**\n> Node.js includes a TCP-based debugger. When you start your application using `sails debug`, Node.js lifts your app and opens a socket on port `5858`. This socket allows external tools to interact with and control the debugger. Node Inspector, accessible via the port `8080`, is this kind of tool.\n\n> If you don't see your files in the browser at http://127.0.0.1:8080?port=5858 or if it's very slow to load, try running Node Inspector with the `--no-preload` argument. [See the Node Inspector repo](https://github.com/node-inspector/node-inspector) for more details.\n\n\n<docmeta name=\"displayName\" value=\"sails debug\">\n<docmeta name=\"pageType\" value=\"command\">\n"
  },
  {
    "path": "docs/reference/cli/sailsgenerate.md",
    "content": "# Sails generate\n\nGenerate a code file (or multiple files) in a Sails app.\n\n```usage\nsails generate <generator>\n```\n\nSails ships with several _generators_ to help you scaffold new projects, spit out boilerplate code for common files, and automate your development process.\n\n### Core generators\n\nThe following _core generators_ are bundled with Sails:\n\n|  Command                        | Details               |\n|:--------------------------------|:----------------------|\n| sails generate page             | Generate four pages: .ejs, .less, page script, and view action. You must add your .less file to the importer and you must set your route for your new page to work. **Note**: `sails generate page` is intended for use with projects generated with the \"Web app\" template. You can still use this command if you're not using the web app template, but you'll need to delete the `assets/js/pages/page-name.page.js` file that's been generated, as it relies on dependencies that don't come bundled with an \"Empty\" Sails app.\n| sails generate model            | Generate **api/models/Foo.js**, including attributes with the specified types if provided.<br /> For example, `sails generate model User username isAdmin:boolean` will generate a User model with a `username` string attribute and an `isAdmin` boolean attribute.\n| sails generate action           | Generate a standalone [action](https://sailsjs.com/documentation/concepts/actions-and-controllers/generating-actions-and-controllers#?generating-standalone-actions).\n| sails generate helper           | Generate a [helper](https://sailsjs.com/documentation/concepts/helpers) at **api/helpers/foo.js**.\n| sails generate controller       | Generate **api/controllers/FooController.js**, including actions with the specified names if provided.\n| sails generate hook             | Generate a [project hook](https://sailsjs.com/documentation/concepts/extending-sails/hooks/project-hooks) in **api/hooks/foo/**.\n| sails generate generator        | Generate a **foo** folder containing the files necessary for building a new generator.\n| sails generate response         | Generate a [custom response](https://sailsjs.com/documentation/concepts/extending-sails/custom-responses) at **api/responses/foo.js**\n| sails generate adapter          | Generate a **api/adapters/foo/** folder containing the files necessary for building a new adapter.\n| sails generate sails.io.js      | Generate a sails.io.js file at the specified location, overwriting the default sails.io.js if applicable.\n| _sails generate api_            | _Generate **api/models/Foo.js** and **api/controllers/FooController.js**._\n| _sails generate new_            | _Alias for [`sails new`](https://sailsjs.com/documentation/reference/command-line-interface/sails-new)._\n| _sails generate etc_            | **Experimental.** Adds the following files to your app:<br/>&bull; .gitignore <br/>&bull; .jshintrc <br/>&bull; .editorconfig <br/>&bull; .npmignore <br/>&bull; .travis.yml <br/>&bull; .appveyor.yml\n\n\n### Custom generators\n\n[Custom / third party generators](https://sailsjs.com/documentation/concepts/extending-sails/generators) allow you to extend or override the default functionality of `sails generate` (for example, by creating a generator that outputs view files for your favorite [view engine](https://sailsjs.com/documentation/concepts/views/view-engines)).\n\nYou can also use custom generators to automate frequent tasks or generate app-specific files.  For example, if you are using React, you might wire up a quick custom generator to allow you to generate [React components](https://facebook.github.io/react/docs/react-component.html) in the appropriate folder in your project (`sails generate react component`).\n\n<docmeta name=\"displayName\" value=\"sails generate\">\n<docmeta name=\"pageType\" value=\"command\">\n\n"
  },
  {
    "path": "docs/reference/cli/sailsinspect.md",
    "content": "# Sails inspect\n\n> ##### _**This command should only be used with modern versions of Node.  For Node v5 and below, use [`sails debug`](https://sailsjs.com/documentation/reference/command-line-interface/sails-debug).**_\n\nAttach the Node debugger and lift the Sails app (similar to running `node --inspect app.js`). You can then use a tool like Chrome DevTools to interactively debug your apps (see the [Node Inspector docs](https://nodejs.org/en/docs/inspector/) for more information).\n\n```usage\nsails inspect\n```\n\n\n### Usage\nTakes the same options as [`sails lift`](https://sailsjs.com/documentation/reference/command-line-interface/sails-lift), listed [here](https://sailsjs.com/documentation/reference/command-line-interface/sails-lift#?usage).\n\n\n### Example\n\n```text\n$ sails inspect\n\ninfo: Running app in inspect mode...\ninfo: In Google Chrome, go to chrome://inspect for interactive debugging.\ninfo: For other options, see the link below.\ninfo: ( to exit, type <CTRL>+<C> )\n\nDebugger listening on ws://127.0.0.1:9229/7f984b04-b070-4497-bd15-056261a37f7c\nFor help see https://nodejs.org/en/docs/inspector\n```\n\n\n> To use the standard (command-line) Node debugger with Sails, you can always just run `node inspect app.js`.\n\n> If you don't see your files in the Chrome DevTools, try clicking the \"Filesystem\" tab and adding your project folder to the workspace.\n\n\n<docmeta name=\"displayName\" value=\"sails inspect\">\n<docmeta name=\"pageType\" value=\"command\">\n"
  },
  {
    "path": "docs/reference/cli/sailslift.md",
    "content": "# `sails lift`\n\n\nRun the Sails app in the current dir (if `node_modules/sails` exists, it will be used instead of the globally installed Sails).\n\n```usage\nsails lift\n```\n\nBy default, Sails lifts your app in development mode.  In the development environment, Sails uses [Grunt](https://gruntjs.com/) to keep an eye on your files in `/assets`. If you change something (for example in one of your `.css` or `.less` files) and reload your browser, you'll notice that your changes are reflected automatically.\n\nAlso note that, in development mode, your view templates won't be cached in memory.  So, like assets, you can also change your view files without restarting Sails.\n\n> Any changes to back-end logic or configuration (e.g. the files in `config/`, `api/`, or `node_modules/`) _will not take effect_ unless you kill and restart the server (CTRL+C  + `sails lift`).\n\n### Usage:\n\n`sails lift` takes the following options:\n\n  * `--prod` - in production environment\n  * `--port <portNum>` - on the port specified by `portNum` instead of the default (1337)\n  * `--verbose` - with verbose logging enabled\n  * `--silly` - with insane logging enabled\n\n\n### Example\n\n```text\n$ sails lift\n\ninfo: Starting app...\n\ninfo:\ninfo:\ninfo:    Sails              <|\ninfo:    v1.0.0              |\\\ninfo:                       /|.\\\ninfo:                      / || \\\ninfo:                    ,'  |'  \\\ninfo:                 .-'.-==|/_--'\ninfo:                 `--'-------'\ninfo:    __---___--___---___--___---___--___\ninfo:  ____---___--___---___--___---___--___-__\ninfo:\ninfo: Server lifted in `/Users/mikermcneil/code/sandbox/second`\ninfo: To see your app, visit http://localhost:1337\ninfo: To shut down Sails, press <CTRL> + C at any time.\n\ndebug: --------------------------------------------------------\ndebug: :: Sat Apr 05 2014 17:03:39 GMT-0500 (CDT)\n\ndebug: Environment : development\ndebug: Port        : 1337\ndebug: --------------------------------------------------------\n```\n\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"sails lift\">\n<docmeta name=\"pageType\" value=\"command\">\n"
  },
  {
    "path": "docs/reference/cli/sailsnew.md",
    "content": "# `sails new`\n\nCreate a new Sails project.\n\n```usage\nsails new your-app-name\n```\n\n### Usage:\n\nMost Sails apps should be generated simply by running `sails new your-app-name`, without any additional customization.  But `sails new` also accepts the following options:\n\n  * `--no-frontend`: useful when generating a new Sails app that will not be used to serve any front-end assets.  Disables the generation of the `assets/` folder, `tasks/` folder, and related files.\n  * `--minimal`: generates an extremely minimal Sails app.  This disables the same things as `--no-frontend`, along with i18n, Waterline, Grunt, Lodash, Async, sessions, and views.\n  * `--without`: used to generate a Sails app without the specified feature(s). The supported \"without\" options are: `'lodash'`, `'async'`, `'orm'`, `'sockets'`, `'grunt'`, `'i18n'`, `'session'`, and `'views'`. To disable multiple features at once, you can include the options as a comma-separated list, e.g. `sails new your-app-name --without=grunt,views`.\n\n\n### Example\n\nTo create a project called \"test-project\" in `code/testProject/`:\n\n```text\n$ sails new code/testProject\ninfo: Installing dependencies...\nPress CTRL+C to skip.\n(but if you do that, you'll need to cd in and run `npm install`)\ninfo: Created a new Sails app `test-project`!\n```\n\nTo create a Sails project in an existing `myProject/` folder:\n\n```text\n$ cd myProject\n$ sails new .\ninfo: Installing dependencies...\nPress CTRL+C to skip.\n(but if you do that, you'll need to cd in and run `npm install`)\ninfo: Created a new Sails app `my-project`!\n```\n> Creating a new Sails app in an existing folder will only work if the folder is empty.\n\n### Notes:\n> + `sails new` is really just a special [generator](https://sailsjs.com/documentation/concepts/extending-sails/Generators) which runs [`sails-generate-new`](http://github.com/balderdashy/sails-generate-new).  In other words, running `sails new foo` is an alias for running `sails generate new foo`, and like any Sails generator, the actual generator module which gets run can be overridden in your global `~/.sailsrc` file.\n\n\n<docmeta name=\"displayName\" value=\"sails new\">\n<docmeta name=\"pageType\" value=\"command\">\n"
  },
  {
    "path": "docs/reference/cli/sailsversion.md",
    "content": "# `sails --version`\n\nGet the version of your computer's _globally_ installed Sails command-line tool (i.e. the version you installed with `npm install -g sails`).\n\n```usage\nsails --version\n```\n\n### Example\n\n```text\n$ sails --version\n1.0.0\n```\n\n### Notes\n> + Different Sails apps can have different local Sails installs at different versions, since each project encapsulates its dependencies in its `node_modules/` folder.  To get the _locally_ installed version of Sails from within a particular project, run `npm ls sails`.\n\n\n\n<docmeta name=\"displayName\" value=\"sails --version\">\n<docmeta name=\"pageType\" value=\"command\">\n\n"
  },
  {
    "path": "docs/reference/reference.md",
    "content": "# Sails.js Documentation > API Reference\n\n> The contents of this file are overridden automatically during compilation (please do not edit manually!)\n\n<docmeta name=\"displayName\" value=\"API Reference: Table of Contents\">\n<docmeta name=\"isTableOfContents\" value=\"true\">\n"
  },
  {
    "path": "docs/reference/req/req._startTime.md",
    "content": "# `req._startTime`\n\nThe moment that Sails started processing the request, as a [Javascript Date object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date).\n\n> This property is not added when your app is in [production mode](https://sailsjs.com/documentation/concepts/deployment#?set-the-nodeenv-environment-variable-to-production).\n\n### Usage\n```usage\nreq._startTime;\n```\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req._startTime\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/req/req.accepts.md",
    "content": "# `req.accepts()`\n\nReturn whether this request (`req`) advertises that it understands the specified media type.\n\n> If none of the media types are considered acceptable, this returns `false`.  Otherwise, it returns truthy (the media type).\n\n### Usage\n```usage\nreq.accepts(mediaType);\n```\n\n### Example\n\nIf a request is sent with an `\"Accept: application/json\"` header:\n\n```javascript\nreq.accepts('application/json');\n// -> 'application/json'\n\nreq.accepts('json');\n// -> 'json'\n\nreq.accepts('image/png');\n// -> false\n```\n\nIf a request is sent with an `\"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\"` header:\n\n```javascript\nreq.accepts('html');\n// -> 'html'\n\nreq.accepts('text/html');\n// -> 'text/html'\n\nreq.accepts('json');\n// -> false\n```\n\n### Notes\n\n> + The specified media type may be provided as either a [MIME type string](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) such as \"application/json\", or an extension name such as \"json\".\n> + This is implemented by examining the request's [\"Accept\" header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept).\n> + See the [`accepts` package](https://www.npmjs.com/package/accepts) for the finer details of the header-parsing algorithm used in Sails/Express.\n\n<docmeta name=\"displayName\" value=\"req.accepts()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/req/req.acceptsCharsets.md",
    "content": "# `req.acceptsCharsets()`\n\nReturn whether this request (`req`) advertises that it is able to handle any of the specified character set(s), and if so, which one.\n\n> If _more than one_ of the character sets passed in to this method are considered acceptable, then the first one will be returned.  If none of the character sets are considered acceptable, this returns `false`.\n\n### Usage\n\n```usage\nreq.acceptsCharsets(charset);\n```\n\nor:\n+ `req.acceptsCharsets(charset1, charset2, …);`\n\n### Details\n\nUseful for advanced content negotiation where a client may or may not support certain character sets, such as Unicode (UTF-8).\n\n\n### Example\n\nIf a request is sent with a `\"Accept-Charset: utf-8\"` header:\n\n```js\nreq.acceptsCharsets('utf-8');\n// -> 'utf-8'\n\nreq.acceptsCharsets('iso-8859-1', 'utf-16', 'utf-8');\n// -> 'utf-8'\n\nreq.acceptsCharsets('utf-16');\n// -> false\n```\n\n### Notes\n> + This is implemented by examining the request's [`Accept-Charset`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset) header (see [RFC-2616](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2)).\n> + See the [`accepts` module](https://www.npmjs.com/package/accepts) for the finer details of the header-parsing algorithm used in Sails/Express.\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.acceptsCharsets()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/req/req.acceptsLanguages.md",
    "content": "# `req.acceptsLanguages()`\n\nReturn whether this request (`req`) advertises that it understands any of the specified language(s), and if so, which one.\n\n> If _more than one_ of the languages passed in to this method are considered acceptable, then the first one will be returned.  If none of the languages are considered acceptable, this returns `false`.\n> (By languages, we mean natural languages, like English or Japanese, not programming languages.)\n\n\n### Usage\n\n```usage\nreq.acceptsLanguages(language);\n```\n\n_Or:_\n+ `req.acceptsLanguages(language1, language2, …);`\n\n\n### Details\n\nThis method can be useful as a complement to built-in [internationalization and localization](https://sailsjs.com/documentation/concepts/Internationalization), which allows for automatically serving different content to different locales based on the request.\n\n\n### Example\n\nIf a request is sent with `\"Accept-Language: da, en, en-gb, en-us;\"`:\n\n```js\nreq.acceptsLanguages('en');\n// -> 'en'\n\nreq.acceptsLanguages('es');\n// -> false\n\nreq.acceptsLanguages('en-us', 'en', 'en-gb');\n// -> 'en-us'\n\nreq.acceptsLanguages('en-gb', 'en', 'en-us');\n// -> 'en-gb'\n\nreq.acceptsLanguages('es', 'fr');\n// -> false\n```\n\n\n### Notes\n\n> + You can expect the [\"Accept-Language\" header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language) to exist in most requests that originate in web browsers (see [RFC-2616](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4)).\n> + Browsers send the \"Accept-Language\" header automatically, based on the user's language settings.\n> + See the [`accepts` package](https://www.npmjs.com/package/accepts) for the finer details of the header-parsing algorithm used in Sails/Express.\n\n\n\n<docmeta name=\"displayName\" value=\"req.acceptsLanguages()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/req/req.allParams.md",
    "content": "# `req.allParams()`\n\nReturns the value of _all_ parameters sent in the request, merged into a single dictionary (plain JavaScript object). Includes parameters parsed from the URL path, the request body, and the query string, _in that order_. See [`req.param()`](https://sailsjs.com/documentation/reference/request-req/req-param) for details.\n\n### Usage\n\n```usage\nreq.allParams();\n```\n\n\n### Example\n\nUpdate the product with the specified `sku`, setting new values using the parameters that were passed in:\n\n```javascript\nvar values = req.allParams();\n\n// Don't allow `price` or `isAvailable` to be edited.\ndelete values.price;\ndelete values.isAvailable;\n\n// At this point, `values` might look something like this:\n// values ==> { displayName: 'Bubble Trouble Bubble Bath' }\n\nProduct.update({sku: sku})\n.set(values)\n.exec(function (err, newProduct) {\n  // ...\n});\n```\n\n### Notes\n\n>+ The order of precedence means that URL path params override request body params, which will override query string params.\n>+ In past versions of Sails, this method was known as `req.params.all()`, but this could be confusing&mdash;what if you had a route path parameter named \"all\"?  In apps built on Sails v1 or later, you should use `req.allParams()` in favor of `req.params.all()` to avoid such a situation.\n\n\n\n<docmeta name=\"displayName\" value=\"req.allParams()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/req/req.body.md",
    "content": "# `req.body`\n\nAn object containing text parameters from the parsed request body, defaulting to `{}`.\n\nBy default, the request body can be URL-encoded or stringified as JSON.  Support for other formats, such as serialized XML, is possible using the [middleware](https://sailsjs.com/documentation/concepts/Middleware) configuration.\n\n### Usage\n```usage\nreq.body;\n```\n\n### Notes\n>+ If a request contains one or more file uploads, only the text parameters sent _**before**_ the first file parameter will be available in `req.body`.\n>+ When using [Skipper](https://github.com/balderdashy/skipper), the default body parser, this property will be `undefined` for GET requests.\n\n\n\n<docmeta name=\"displayName\" value=\"req.body\">\n<docmeta name=\"pageType\" value=\"property\">\n\n"
  },
  {
    "path": "docs/reference/req/req.cookies.md",
    "content": "# `req.cookies`\n\nAn object containing all of the [**unsigned cookies**](https://github.com/balderdashy/sails/blob/master/docs/PAGE_NEEDED.md) from this request (`req`).\n\n\n### Usage\n```usage\nreq.cookies;\n```\n\n\n### Example\nAssuming the request contained a cookie named \"chocolatechip\" with value \"Yummy:\n\n```javascript\nreq.cookies.chocolatechip;\n// \"Yummy\"\n```\n\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.cookies\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/req/req.file.md",
    "content": "# `req.file()`\n\nBuild and return a [Skipper Upstream](https://github.com/balderdashy/skipper/tree/b0f99c526b6664a2e867e3ef0bafcfff35e6fba2#what-are-upstreams) representing an incoming multipart file upload from the specified `field`.\n\n```usage\nreq.file(field);\n```\n\n\n### Usage\n\n\n|   |          Argument           | Type                | Details                                                                      |\n|---|-----------------------------|:-------------------:|------------------------------------------------------------------------------|\n| 1 |        `field`              |   ((string))        | The name of the file parameter to listen on for uploads; e.g. `avatar`.      |\n\n\n\n### Details\n\n`req.file()` comes from [Skipper](https://github.com/balderdashy/skipper), an opinionated variant of the original Connect body parser. Skipper allows you to take advantage of high-performance, streaming file uploads without any dramatic changes in your application logic.\n\nThis simplifcation comes with a minor caveat:  **text parameters must be included before files in the request body.**  Typically these text parameters contain string metadata that provide additional information about the file upload.\n\nMultipart requests to Sails should send all of their **text parameters**. before sending _any_ **file parameters**.  For instance, if you're building a web front end that communicates with Sails, you should include text parameters _first_ in any form upload or AJAX file upload requests.  The term \"text parameters\" refers to the metadata parameters you might send with the file(s) providing additional information about the upload.\n\n\n### How it works\n\nSkipper treats all file uploads as streams.  This allows users to upload monolithic files with minimal performance impact and no disk footprint, all the while protecting your app against nasty denial-of-service attacks involving TMP files.\n\nWhen a multipart request hits your server, instead of writing temporary files to disk, Skipper buffers the request just long enough to run your app code, allowing you to \"plug in\" to a compatible blob receiver.  If you don't \"plug in\" the data from a particular field, the Upstream hits its \"high water mark\", the buffer is flushed, and subsequent incoming bytes on that field are ignored.\n\n### Example\n\nIn a controller action or policy:\n\n```javascript\n// See the Skipper README on GitHub for usage documentation for `.upload()`, including\n// a complete list of options.\nreq.file('avatar').upload(function (err, uploadedFiles){\n  if (err) return res.serverError(err);\n  return res.json({\n    message: uploadedFiles.length + ' file(s) uploaded successfully!',\n    files: uploadedFiles\n  });\n});\n```\n\n\n### Notes\n> + Remember that the client request's text parameters must be sent before the file parameters!\n> + `req.file()` supports multiple files sent over the same field, but it's important to realize that, as a consequence, the Upstream it returns is actually a stream (buffered event emitter) of potential binary streams (files). Specifically, an [`Upstream`](https://github.com/balderdashy/skipper/tree/b0f99c526b6664a2e867e3ef0bafcfff35e6fba2#what-are-upstreams) is a [Node.js Readable stream](http://nodejs.org/api/stream.html#stream_class_stream_readable) in \"object mode\", where each object is itself an incoming multipart file upload stream.\n> + If you prefer to work directly with the Upstream as a stream of streams, you can omit the `.upload()` method and bind \"finish\" and \"error\" events (or use `.pipe()`) instead.  [Under the covers](https://github.com/balderdashy/skipper/blob/b0f99c526b6664a2e867e3ef0bafcfff35e6fba2/standalone/Upstream/prototype.upload.js), all `.upload()` is doing is piping the **Upstream** into the specified receiver instance, then running the specified callback when the Upstream emits either a `finish` or `error` event.\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.file()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/req/req.fresh.md",
    "content": "# `req.fresh`\n\nA flag indicating that the user-agent sending this request (`req`) wants \"fresh\" data (as indicated by the \"[if-none-match](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26)\", \"[cache-control](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9)\", and/or \"[if-modified-since](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25)\" request headers.)\n\nIf the request wants \"fresh\" data, usually you'll want to `.find()` fresh data from your models and send it back to the client.\n\n### Usage\n```usage\nreq.fresh;\n```\n\n### Example\n```js\nif (req.fresh) {\n  // The user-agent is asking for a more up-to-date version of the requested resource.\n  // Let's hit the database to get some stuff and send it back.\n}\n```\n\n### Notes\n> + See the [`node-fresh`](https://github.com/visionmedia/node-fresh) module for details specific to the implementation in Sails/Express/Koa/Connect.\n\n\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.fresh\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/req/req.get.md",
    "content": "# `req.get()`\n\nReturns the value of the specified `header` field in this request (`req`).  Note that header names are case-_insensitive_.\n\n### Usage\n\n```usage\nreq.get(header);\n```\n\n### Example\nAssuming `req` contains a header named 'myField' with value 'cat':\n\n```javascript\nreq.get('myField');\n// -> cat\n```\n\n### Notes\n>+ The `header` argument is case-insensitive.\n>+ The `header` argument treats both \"referrer\" and \"referer\" as synonyms, because sp3ll1n9.\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.get()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/req/req.headers.md",
    "content": "# `req.headers`\n\nAn object containing the predefined/custom header given in the current request.\n\n### Usage\n\n```usage\nreq.headers;\n```\n\n### Details\n\nOften we want to check the headers of the current request. This can be done easily in Sails.\n\n### Example\n\nSample output of the `req.headers` object:\n\n```javascript\nconsole.log(req.headers);\n\n{ host: 'localhost:1337',\n  connection: 'keep-alive',\n  'cache-control': 'no-cache',\n  'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36',\n  accept: '*/*',\n  'accept-encoding': 'gzip, deflate, sdch',\n  'accept-language': 'en-US,en;q=0.8,hi;q=0.6',\n  cookie: 'sdfkslddklfk; sails.sid=s%3skdlfjkj1231lsdfnsc,m' }\n```\n\n\n### Note\n\nIf you want to access any specific, custom, or predefined header, it can be done with bracket notation:\n\n```javascript\nreq.headers['custom-header'];\n```\n\nor dot notation:\n\n```javascript\nreq.headers.host;\n```\n<docmeta name=\"displayName\" value=\"req.headers\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/req/req.host.md",
    "content": "# `req.host`\n\n> ##### **This method is deprecated and will likely be removed or changed in an upcoming release.**\n> Instead, use [req.hostname](https://sailsjs.com/documentation/reference/request-req/req-hostname).\n\n\nThe hostname of this request, without the port number, as specified by its \"Host\" header.\n\n### Usage\n```usage\nreq.host;\n```\n\n### Example\n\nIf this request's \"Host\" header was \"ww3.staging.ibm.com:1492\":\n\n```javascript\nreq.host;\n// -> \"ww3.staging.ibm.com\"\n```\n\n\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.host\">\n<docmeta name=\"pageType\" value=\"property\">\n<docmeta name=\"isDeprecated\" value=\"true\">\n"
  },
  {
    "path": "docs/reference/req/req.hostname.md",
    "content": "# `req.hostname`\n\nReturns the hostname supplied in the host HTTP header. This header may be set either by the client or by the proxy.\n\n### Usage\n\n```usage\nreq.hostname;\n```\n\n### Example\n\nIf this request's \"Host\" header was \"ww3.staging.ibm.com:1492\":\n\n```javascript\nreq.hostname;\n// -> \"ww3.staging.ibm.com\"\n```\n\n\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.hostname\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/req/req.ip.md",
    "content": "# `req.ip`\n\nThe IP address of the client who sent this request (`req`).\n\n> **Note:**\n>\n> If your Sails app is deployed behind a proxy (on Heroku, for example), then you'll need to do a bit of additional configuration.  Normally, `req.ip` is simply the \"remote address\"&mdash;the IP address of the requesting user agent.  But if the [sails.config.http.trustProxy](https://sailsjs.com/documentation/reference/configuration/sails-config-http) option is enabled, this is the \"[upstream address](https://en.wikipedia.org/wiki/X-Forwarded-For)\".\n\n### Usage\n```usage\nreq.ip;\n```\n\n### Example\n```javascript\nreq.ip;\n// -> \"127.0.0.1\"\n```\n\n\n\n<docmeta name=\"displayName\" value=\"req.ip\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/req/req.ips.md",
    "content": "# `req.ips`\n\nIf [sails.config.http.trustProxy](https://sailsjs.com/documentation/reference/configuration/sails-config-http) is enabled, this variable contains the IP addresses in this request's \"X-Forwarded-For\" header as an array of the IP address strings. Otherwise an empty array is returned.\n\n### Usage\n```usage\nreq.ips;\n```\n\n### Example\nIf a request contains a header, \"X-Forwarded-For: client, proxy1, proxy2\":\n\n```js\nreq.ips;\n// -> [\"client\", \"proxy1\", \"proxy2\"]`\n\n// (\"proxy2\" is the furthest \"down-stream\" IP address)\n```\n\n\n\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.ips\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/req/req.is.md",
    "content": "# `req.is()`\n\nReturns true if this request's declared \"Content-Type\" matches the specified media/mime `type`.\n\nSpecifically, this method matches the given `type` against this request's \"Content-Type\" header.\n\n### Usage\n```usage\nreq.is(type);\n```\n\n\n### Example\nAssuming the request contains a \"Content-Type\" header, \"text/html; charset=utf-8\":\n```javascript\nreq.is('html');\n// -> true\nreq.is('text/html');\n// -> true\nreq.is('text/*');\n// -> true\n```\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.is()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/req/req.isSocket.md",
    "content": "# `req.isSocket`\n\nA flag indicating whether or not this request (`req`) originated from a Socket.io connection.\n\n\n### Usage\n```usage\nreq.isSocket;\n```\n\n### Example\n```javascript\nif (req.isSocket){\n  // You're a socket.  Do cool socket stuff like subscribing.\n  User.subscribe(req, [req.session.userId]);\n}\nelse {\n  // Just another HTTP request.\n  // (`req.isSocket` is undefined)\n}\n```\n\n### Notes\n\n> + Useful for allowing HTTP requests to skip calls to PubSub or WebSocket-centric methods like `subscribe()` or `watch()`  that depend on an actual Socket.io request.  This allows you to reuse backend code for both WebSocket and HTTP clients.\n> + As you might expect, `req.isSocket` doesn't need to be checked before running methods that **publish to other** connected sockets.  Those methods don't depend on the request, so they work either way.\n\n\n\n\n\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.isSocket\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/req/req.md",
    "content": "# Request (`req`)\n\nSails is built on [Express](https://github.com/balderdashy/sails/blob/master/docs/PAGE_NEEDED.md), and uses [Node's HTTP server](http://nodejs.org/api/http.html) conventions.  Because of this, you can access all of the Node and Express methods and properties on the `req` object wherever it is accessible (in your controllers, policies, and custom responses).\n\nA nice side effect of this compatibility is that, in many cases, you can paste existing Node.js code into a Sails app and it will work.  And since Sails implements a transport-agnostic request interpreter, the code in your Sails app is WebSocket-compatible as well.\n\nSails adds a few methods and properties of its own to the `req` object, like [`req.wantsJSON`](https://sailsjs.com/documentation/reference/request-req/req-wants-json) and [`req.allParams()`](https://sailsjs.com/documentation/reference/request-req/req-all-params).  These features are syntactic sugar on top of the underlying implementation, and also support both HTTP and WebSockets.\n\n\n<!--\n### Protocol Support\n\nThe chart below describes support for the methods and properties on [`req`](https://sailsjs.com/documentation/reference/request-req), the Sails request object (`req`), across HTTP and WebSockets:\n\n\n|                          | HTTP    | WebSockets |\n|--------------------------|---------|------------|\n| req.file()               | :white_check_mark: | :white_large_square: |\n| req.param()              | :white_check_mark: | :white_check_mark: |\n| req.route                | :white_check_mark: | :white_check_mark: |\n| req.cookies              | :white_check_mark: | :white_large_square: |\n| req.signedCookies        | :white_check_mark: | :white_large_square: |\n| req.get()                | :white_check_mark: | :white_large_square: |\n| req.accepts()            | :white_check_mark: | :white_large_square: |\n| req.accepted             | :white_check_mark: | :white_large_square: |\n| req.is()                 | :white_check_mark: | :white_large_square: |\n| req.ip                   | :white_check_mark: | :white_check_mark: |\n| req.ips                  | :white_check_mark: | :white_large_square: |\n| req.path                 | :white_check_mark: | :white_large_square: |\n| req.host                 | :white_check_mark: | :white_large_square: |\n| req.fresh                | :white_check_mark: | :white_large_square: |\n| req.stale                | :white_check_mark: | :white_large_square: |\n| req.xhr                  | :white_check_mark: | :white_large_square: |\n| req.protocol             | :white_check_mark: | :white_check_mark: |\n| req.secure               | :white_check_mark: | :white_large_square: |\n| req.session              | :white_check_mark: | :white_check_mark: |\n| req.subdomains           | :white_check_mark: | :white_large_square: |\n| req.method               | :white_check_mark: | :white_check_mark: |\n| req.originalUrl          | :white_check_mark: | :white_large_square: |\n| req.acceptedLanguages    | :white_check_mark: | :white_large_square: |\n| req.acceptedCharsets     | :white_check_mark: | :white_large_square: |\n| req.acceptsCharset()     | :white_check_mark: | :white_large_square: |\n| req.acceptsLanguage()    | :white_check_mark: | :white_large_square: |\n| req.isSocket             | :white_check_mark: | :white_check_mark: |\n| req.allParams()          | :white_check_mark: | :white_check_mark: |\n| req.transport            | :white_large_square: | :white_check_mark: |\n| req.url                  | :white_check_mark: | :white_check_mark: |\n| req.wantsJSON            | :white_check_mark: | :white_check_mark: |\n\n\n### Legend\n\n  - :white_check_mark: - fully supported\n  - :white_large_square: - feature not yet implemented\n  - :heavy_multiplication_x: - unsupported due to protocol restrictions\n\n\n-->\n\n\n<docmeta name=\"displayName\" value=\"Request (`req`)\">\n<docmeta name=\"stabilityIndex\" value=\"3\">\n"
  },
  {
    "path": "docs/reference/req/req.method.md",
    "content": "# `req.method`\n\nThe request method (aka \"verb\").\n\n### Usage\n```usage\nreq.method;\n```\n\n### Example\n\nIf a client sends a POST request to `/product`:\n\n```js\nreq.method;\n// -> \"POST\"\n```\n\n### Notes\n\n> + All requests to a Sails server have a \"method\", even via WebSockets (this is thanks to the request interpreter).\n\n\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.method\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/req/req.options/req.options.md",
    "content": "# req.options\n\n`req.options` is a dictionary (plain JavaScript object) of request-agnostic settings available in your app's actions.\n\nThe purpose of `req.options` is to allow an action's code to access its configured route options, if there are any.  (Simply put, \"route options\" are just any additional properties provided in a [route target](https://sailsjs.com/documentation/concepts/routes/custom-routes#?route-target).)\n\n<!--\nFUTURE: pull out the rest of the content below to a new, separate page under **Concepts > Routes > Route options** and just link to it from in here rather than having all this exist inline.\n\n(Also be sure to consolidate any additional useful content from https://sailsjs.com/documentation/concepts/routes/custom-routes#?route-target-options into the new page, and replace the content under that heading with a sentence that links to the new \"Route options\" page.)\n\n-m  Feb 23, 2017\n-->\n\n### With the blueprint API\n\nRoute options in Sails were originally devised as a more flexible way to configure built-in blueprint actions.\n\nSome special settings must always be provided to [certain blueprint actions](https://sailsjs.com/documentation/reference/blueprint-api).  This provides a way for your app to communicate which model/association a blueprint action should target.  For example, `req.options.model` is the identity of the model that a particular blueprint action should target.  And for blueprint actions that directly involve an association, `req.options.alias` indicates the name of the associating attribute.\n\nYou can take advantage of this in your app order to bind a blueprint action to an arbitrary custom route.  For example, consider the following custom route in [`config/routes.js`](https://sailsjs.com/documentation/anatomy/config/routes-js):\n\n```js\n'GET /foo/bar': {\n  action: 'user/find',\n  model: 'user'\n}\n```\n\nWhenever a GET request to /foo/bar arrives, the `find` blueprint action will run, and `req.options.model` will be available as `user`.  (This is how the built-in, generic \"find\" blueprint action knows that it should communicate with the User model.)\n\n> Need to customize blueprint actions further?  In most cases, the easiest (and most maintainable) way to do this is to write a custom action.  If you're making the transition between the blueprint API and writing your own custom actions for the first time, you might start by checking out [Concepts > Actions & Controllers](https://sailsjs.com/documentation/concepts/actions-and-controllers).\n>\n> Note that there is a middle ground that allows you to programmatically modify some additional aspects of a blueprint action's behavior without overriding it completely (for example, examining the request to determine the criteria that a blueprint action uses when accessing models.)  See [**Reference > sails.config.blueprints > Using parseBlueprintOptions**](https://sailsjs.com/documentation/reference/configuration/sails-config-blueprints#?using-parseblueprintoptions) for more on that.\n\n\n### Custom route options\n\nit is also possible to configure and consume your own _custom_ route options.  For example, imagine you're building a GitHub plugin for Sails.  In order to provide support for handling webhook requests from GitHub, your plugin could register a generic, configurable action like `github/receive-event` that allows any user of your plugin to easily bind it to any route in their app:\n\n\n```js\n'POST /my-cool-webhooks/github/doings-and-things/incoming': {\n  action: 'github/receive-event',\n}\n```\n\nBut now, imagine that one of the purposes for your plugin's generic `receive-event` action is to save a record representing the incoming GitHub event to the app's database (e.g. to track it for future use).  In order to do that, your generic action needs to know which model to use.  So, using a simple approach that is consistent with Sails' built-in blueprint actions, your plugin could support usage like the following:\n\n```js\n'POST /my-cool-webhooks/github/doings-and-things/incoming': {\n  action: 'github/receive-event',\n  model: 'repoactivity'\n}\n```\n\nMeanwhile, in your plugin, the action you register might look something like this:\n\n```js\nmodule.exports = function receiveEvent(req, res) {\n\n  if (_.isUndefined(req.options.model) || !sails.models[req.options.model]) {\n    return res.serverError(new Error('Invalid configuration: To use `github/receive-event`, please set this route's `model` to the identity of one of your app\\'s models.  (Currently, it is `'+req.options.model+'`, which cannot be used.)'));\n  }\n\n  var GitHubEventModel = sails.models[req.options.model];\n  GitHubEventModel.create({\n    raw: req.allParams(),\n    githubId: req.param('id'),\n    // ...\n    // ... etc. (see https://developer.github.com/webhooks/#events)\n  }).exec(function(err) {\n    if (err) { return res.serverError(err); }\n\n    return res.ok();\n  });\n};\n```\n\n> For more about creating this type of plugin, see [Concepts > Extending Sails > Hooks](TODO).\n\n<docmeta name=\"displayName\" value=\"req.options\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/req/req.originalUrl.md",
    "content": "# `req.originalUrl`\n\nFrom the [Express docs](https://expressjs.com/en/4x/api.html#req.originalUrl):\n\n> This property is much like req.url; however, it retains the original request URL, allowing you to rewrite req.url freely for internal routing purposes.\n\nIn almost all cases, you&rsquo;ll want to use [`req.url`](https://sailsjs.com/documentation/reference/request-req/req-url) instead.  In the rare cases where `req.url` is modified (for example, inside of a policy or middleware in order to redirect to an internal route), `req.originalUrl` will give you the URL that was originally requested.\n\n```usage\nreq.originalUrl;\n\n// => \"/search\"\n```\n\n<docmeta name=\"displayName\" value=\"req.originalUrl\">\n<docmeta name=\"pageType\" value=\"property\">\n\n"
  },
  {
    "path": "docs/reference/req/req.param.md",
    "content": "# `req.param()`\n\nReturns the value of the parameter with the specified name.\n\n### Usage\n\n```usage\nreq.param(name[, defaultValue]);\n```\n\n### Details\n\n`req.param()` searches the URL path, body, and query string of the request (_in that order_) for the specified parameter.  If no parameter value exists anywhere in the request with the given `name`, it returns `undefined` or the optional `defaultValue` if specified.\n\n+ URL path parameters ([`req.params`](https://sailsjs.com/documentation/reference/request-req/req-params))\n  + e.g. a request \"/foo/4\" to route `/foo/:id` has URL path params `{ id: 4 }`\n+ body parameters ([`req.body`](https://sailsjs.com/documentation/reference/request-req/req-body))\n  + e.g. a request with a parseable body (e.g. JSON, URL-encoded, or XML) has body parameters equal to its parsed value\n+ query string parameters ([`req.query`](https://sailsjs.com/documentation/reference/request-req/req-query))\n  + e.g. a request \"/foo?email=5\" has query params `{ email: 5 }`\n\n\n### Example\n\nConsider a route (`POST /product/:sku`) that points to a custom action or policy that has the following code:\n\n```javascript\nreq.param('sku');\n// -> 123\n```\n\nWe can get the expected result by sending the `sku` parameter any of the following ways:\n\n+ `POST /product/123`\n+ `POST /product?sku=123`\n+ `POST /product`\n    + with a JSON request body: `{ \"sku\": 123 }`\n\n\n\n### Notes\n>+ The order of precedence means that URL path params will override request body params, which will override query string params.\n> + If you'd like to get ALL parameters from ALL sources (including the URL path, query string, and parsed request body) you can use [`req.allParams()`](https://sailsjs.com/documentation/reference/request-req/req-all-params).\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.param()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/req/req.params.md",
    "content": "# `req.params`\n\nAn object containing parameter values parsed from the URL path.\n\nFor example if you have the route `/user/:name`, then the \"name\" from the URL path wil be available as `req.params.name`.  This object defaults to `{}`.\n\n\n### Usage\n\n```usage\nreq.params;\n```\n\n### Notes\n> + When a route address is defined using a regular expression, each capture group match from the regex is available as `req.params[0]`, `req.params[1]`, etc. This strategy is also applied to unnamed wild-card matches in string routes such as `/file/*`.\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.params\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/req/req.path.md",
    "content": "# `req.path`\n\nThe URL pathname from the [request URL string](http://nodejs.org/api/http.html#http_message_url) of the current request (`req`). Note that this is the part of the URL after and including the leading slash (e.g. `/foo/bar`), but without the query string (e.g. `?name=foo`) or fragment (e.g. `#foobar`.)\n\n\n### Usage\n\n```usage\nreq.path;\n```\n\n\n### Example\n\nAssuming a client sends the following request:\n\n> http://localhost:1337/donor/37?name=foo#foobar\n\n`req.path` will be defined as follows:\n\n```js\nreq.path;\n// -> \"/donor/37\"\n```\n\n\n\n\n### Notes\n> + If you would like the URL query string _as well as_ the path, see [`req.url`](https://sailsjs.com/documentation/reference/request-req/req-url).\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.path\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/req/req.protocol.md",
    "content": "# `req.protocol`\n\nThe protocol used to send this request (`req`).\n\n### Usage\n```usage\nreq.protocol;\n```\n\n### Example\n\n```js\nswitch (req.protocol) {\n  case 'http':\n    // this is an HTTP request\n    break;\n  case 'https':\n    // this is a secure HTTPS request\n    break;\n}\n```\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.protocol\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/req/req.query.md",
    "content": "# `req.query`\n\nA dictionary containing the parsed query-string, defaulting to `{}`.\n\n### Usage\n```usage\nreq.query;\n```\n\n### Example\n\nIf the request is `GET /search?q=mudslide`:\n\n```js\nreq.query.q\n// -> \"mudslide\"\n```\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.query\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/req/req.secure.md",
    "content": "# `req.secure`\n\nIndicates whether or not the request was sent over a secure [TLS](http://en.wikipedia.org/wiki/Transport_Layer_Security) connection (i.e. `https://` or `wss://`).\n\n### Usage\n```usage\nreq.secure;\n```\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.secure\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/req/req.setLocale.md",
    "content": "# `req.setLocale()`\n\nOverride the inferred locale for this request.\n\nNormally, the locale is determined on a per-request basis based on incoming request headers (i.e. a user's browser or device language settings).  This command overrides that setting for a particular request.\n\n### Usage\n```usage\nreq.setLocale(override);\n```\n\n\n### Example\n\nTo allow users to specify their own language settings:\n```js\nif (this.req.me.preferredLocale) {\n  this.req.setLocale(this.req.me.preferredLocale);\n}\nreturn exits.success();\n```\n\nOr, if you are not using the \"Web app\" template and/or actions2:\n```js\nvar me = await User.findOne({ id: req.session.userId });\nif (me.preferredLocale) {\n  req.setLocale(me.preferredLocale);\n}\nreturn res.view('pages/homepage');\n```\n\n\n<docmeta name=\"displayName\" value=\"req.setLocale()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/req/req.setTimeout.md",
    "content": "# `req.setTimeout()`\n\nTime out this request if a response is not sent within the specified number of milliseconds.\n\n### Usage\n```usage\nreq.setTimeout(numMilliseconds);\n```\n\n\n### Example\n\nTo cause requests to a particular action to time out after 4 minutes:\n```js\nreq.setTimeout(240000);\n```\n\n### Notes\n\n+ By default, normal HTTP requests to Node.js/Express/Sails.js apps time out [after 2 minutes](https://nodejs.org/dist/latest/docs/api/http.html#http_server_settimeout_msecs_callback) (120000 milliseconds) if a response is not sent.\n\n<docmeta name=\"displayName\" value=\"req.setTimeout()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/req/req.signedCookies.md",
    "content": "# `req.signedCookies`\n\nA dictionary containing all the signed cookies from the request object, where a signed cookie is one that is protected against modification by the client. This protection is provided by a Base64-encoded HMAC of the cookie value. When retrieving the cookie, if the HMAC signature does not match based on the cookie's value, then the cookie is not available as a member of the `req.signedCookies` object.\n\n### Purpose\nA dictionary containing all of the signed cookies from this request (`req`).\n\n\n### Usage\n```usage\nreq.signedCookies;\n```\n\n\n\n### Example\nAdding a signed cookie named \"chocolatechip\" with value \"Yummy:\n\n```javascript\nres.cookie('chocolatechip', 'Yummy', {signed:true});\n```\n\nRetrieving the cookie:\n```javascript\nreq.signedCookies.chocolatechip;\n// \"Yummy\"\n```\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.signedCookies\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/req/req.socket.md",
    "content": "# `req.socket`\n\nIf the current request (`req`) originated from a connected Socket.IO client, `req.socket` refers to the raw Socket.IO socket instance.\n\n### Usage\n\n```usage\nreq.socket;\n```\n\n\n### Details\n\n> **Warning:**\n>\n> `req.socket` may be deprecated in a future release of Sails.  You should use the [`sails.sockets.*`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets) methods instead.\n\nIf the current request (`req`) did NOT originate from a Socket.IO client, `req.socket` does not have the same meaning.  In the most common scenario&mdash;HTTP requests&mdash;`req.socket` _exists_, but it refers instead to the underlying TCP socket. Before using `req.socket`, you should check the [`req.isSocket`](https://sailsjs.com/documentation/reference/request-req/req-is-socket) flag to ensure the request arrived via a connected Socket.IO client.\n\n`req.socket.id` is a unique identifier representing the current socket.  This is generated by the Socket.IO server when a client first connects and is a valid unique identifier until the socket is disconnected (if the client is a web browser, for example, `req.socket.id` would be valid until the user closes their browser tab).\n\nSails also provides direct, low-level access to all other methods and properties of a Socket.IO `Socket`, including `req.socket` and its methods `req.socket.join`, `req.socket.leave`, `req.socket.broadcast`, etc.  Check out the relevant [Socket.IO docs](https://socket.io/docs/rooms-and-namespaces/#Rooms) for more information.\n\n\n### Example\n\n```js\nif (req.isSocket) {\n  // Low-level Socket.io methods and properties accessible on req.socket.\n  // ...\n}\nelse {\n  // This is not a request from a Socket.io client, so req.socket\n  // may or may not exist.  If this is an HTTP request, req.socket is actually\n  // the underlying TCP socket.\n  // ...\n}\n```\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.socket\">\n<docmeta name=\"pageType\" value=\"property\">\n\n"
  },
  {
    "path": "docs/reference/req/req.subdomains.md",
    "content": "# `req.subdomains`\n\nAn array of all the subdomains in this request's URL.\n\n### Usage\n```usage\nreq.subdomains;\n```\n\n### Example\n\nIf the requested URL was \"https://ww3.staging.ibm.com\":\n\n```javascript\nreq.subdomains;\n// -> ['ww3', 'staging']\n```\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.subdomains\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/req/req.url.md",
    "content": "# `req.url`\n\nLike [`req.path`](https://sailsjs.com/documentation/reference/request-req/req-path), but it also includes the query string suffix.\n\n```usage\nreq.url;\n\n// => \"/search?q=worlds%20largest%20dogs\"\n```\n\n\n### Notes\n> + It is worth mentioning that the URL fragment/hash (e.g. \"#some/clientside/route\") part of the URL is [not available on the server](https://github.com/strongloop/express/issues/1083#issuecomment-5179035). This is an [open issue with the current HTTP specification](http://stackoverflow.com/a/2305927/486547). As a result, if you write an action to redirect from one subdomain to another, for instance, you won't be able to peek at the URL fragment in that action.\n> + However, if you respond with a 302 redirect (i.e. `res.redirect()`), the user agent on the other end will preserve the URL fragment/hash and tack it on to the end of the new redirected URL.  In many cases, this is exactly what you want!\n\n\n\n<docmeta name=\"displayName\" value=\"req.url\">\n<docmeta name=\"pageType\" value=\"property\">\n\n"
  },
  {
    "path": "docs/reference/req/req.wantsJSON.md",
    "content": "# `req.wantsJSON`\n\nA flag indicating whether the requesting client would prefer a JSON response (as opposed to some other format, like XML or HTML.)\n\n`req.wantsJSON` is used by all of the [built-in custom responses](https://sailsjs.com/documentation/anatomy/api/responses) in Sails.\n\n\n### Usage\n```usage\nreq.wantsJSON;\n```\n\n### Details\n\nThe intended purpose of `req.wantsJSON` is to provide a clean, reusable indication of whether the server should respond with JSON or send back something else. It's not the right answer for _every_ content negotiation problem, but it is a simple, go-to solution for most use cases.\n\nFor instance, all major browsers set an \"Accept: text/plain;\" request header for requests typed in the URL field.  In this case, `req.wantsJSON` is false.  For many other cases, though, the distinction is less clear.  In those scenarios, Sails uses heuristics to determine the best value for `req.wantsJSON`.\n\nTechnically, `req.wantsJSON` inspects the request's `\"Content-type\"`, `\"Accepts\"`, and `\"X-Requested-With\"` headers to determine whether the request expects a JSON response.  If the information in these headers is too scanty, Sails errs on the side of JSON, and `req.wantsJSON` will be set to `true`.\n\nThe benefit of `req.wantsJSON` is that it future-proofs your app and makes it less brittle. As best practices for content negotiation change over time (e.g. a new type of consumer device or enterprise user agent introduces a new header), Sails can patch `req.wantsJSON` at the framework level and modify the heuristics accordingly. It also reduces code duplication and saves you the annoyance of manually inspecting the headers in each of your routes.\n\n### Example\n```javascript\nif (req.wantsJSON) {\n  sails.log('This request wants JSON!');\n}\nelse {\n  // `req.wantsJSON` is falsy (undefined), to this request must not want JSON.\n}\n```\n\n### Details\n\nHere is the specific order in which `req.wantsJSON` inspects the request.  **If any of the following match, subsequent checks are ignored.**\n\nA request \"wantsJSON\" if:\n\n+ it looks like an AJAX request\n+ it's a virtual request from a socket\n+ the request DOESN'T explicitly want HTML\n+ the request has a \"json\" content type AND has its \"Accept\" header set\n+ `req.options.wantsJSON` is truthy\n\n### Notes\n> + Lower-level content negotiation is, of course, still possible using `req.is()`, `req.accepts()`, `req.xhr`, and `req.get()`.\n> + As of Sails v0.10, requests originating from a WebSocket client always want JSON.\n\n\n<docmeta name=\"displayName\" value=\"req.wantsJSON\">\n<docmeta name=\"pageType\" value=\"property\">\n\n"
  },
  {
    "path": "docs/reference/req/req.xhr.md",
    "content": "# `req.xhr`\n\nA flag indicating whether the current request (`req`) appears to be an AJAX request (i.e. it was issued with its \"X-Requested-With\" header set to \"XMLHttpRequest\").\n\n\n### Usage\n```usage\nreq.xhr;\n```\n\n### Example\n```javascript\nif (req.xhr) {\n  // Yup, it's AJAX alright.\n}\n```\n\n\n### Notes\n> + Whenever possible, you should prefer the `req.wantsJSON` flag.  Avoid writing custom content negotiation logic into your app, as it makes your code more brittle and verbose.\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"req.xhr\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/res/res.attachment.md",
    "content": "# `res.attachment()`\n\nIndicate to a web browser or other user agent that an outgoing file download sent in this response should be \"Saved as...\" rather than \"Opened\", and optionally specify the name for the newly downloaded file on disk.\n\nSpecifically, this sets the \"Content-Disposition\" header of the current response to \"attachment\". If a `filename` is given, then the \"Content-Type\" will be automatically set based on the extension of the file (e.g. `.jpg` or `.html`), and the \"Content-Disposition\" header will be set to \"filename=`filename`\".\n\n### Usage\n```usage\nres.attachment([filename]);\n```\n\n### Example\n\nThis method should be called prior to streaming down the bytes of your file.\n\nFor example, if you're using the [uploads hook](https://www.npmjs.com/package/sails-hook-uploads) with [actions2](https://sailsjs.com/documentation/concepts/actions-and-controllers#?actions-2):\n\n```js\nfn: async function({id}, exits) {\n  var file = await LegalDoc.findOne({ id });\n  if(!file) { throw 'notFound'; }\n  \n  this.res.attachment(file.downloadName);\n  var downloading = await sails.startDownload(file.uploadFd);\n  return exits.success(downloading);\n}\n```\n\nThat's it!  When accessed in a browser, the file downloaded by this action will be saved as a new file (e.g. \"Tax Return (Lerangis, 2019)\") instead of being directly opened in the browser itself.\n\nUnder the covers, `res.attachment()` isn't doing anything fancy, it just sets response headers:\n\n```javascript\nres.attachment();\n// -> response header will contain:\n//   Content-Disposition: attachment\n```\n\n```javascript\nres.attachment('Tax Return (Lerangis, 2019).pdf');\n// -> response header will contain:\n//   Content-Disposition: attachment; filename=\"Tax Return (Lerangis, 2019).pdf\"\n//   Content-Type: application/pdf\n```\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"res.attachment()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/res/res.badRequest.md",
    "content": "# `res.badRequest()`\n\nThis method is used to send a <a href=\"http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_Error\" target=\"_blank\">400</a> (\"Bad Request\") response back down to the client, indicating that the request is invalid.  This usually means that the request contained invalid parameters or headers, or that it tried to do something not supported by your app logic.\n\n\n\n### Usage\n\n```usage\nreturn res.badRequest();\n```\n\n_Or:_\n+ `return res.badRequest(data);`\n\n\n\n### Details\n\nLike the other built-in custom response modules, the behavior of this method is customizable.\n\nBy default, it works as follows:\n\n+ The status code of the response is set to 400.\n+ Sails sends any provided error `data` as JSON.  If no `data` is provided, a default response body will be sent (the string `\"Bad Request\"`).\n\n\n### Example\n\n```javascript\nif ( req.param('amount') > 123 )\n  return res.badRequest(\n    'Transaction limit exceeded. Please try again with an amount less than $123.'\n  );\n}\n```\n### Notes\n> + This method is **terminal**, meaning it is generally the last line of code your app should run for a given request (hence the advisory usage of `return` throughout these docs).\n>+ `res.badRequest()` (like other userland response methods) can be overridden or modified.  It runs the response method defined in `api/responses/badRequest.js`.  If a `badRequest.js` response method does not exist in your app, Sails will implicitly use the default behavior.\n>+ This method is called automatically by the [Blueprint Actions](https://sailsjs.com/documentation/concepts/blueprints/blueprint-actions) when bad parameters are sent with a request.\n\n\n\n\n\n\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"res.badRequest()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/res/res.clearCookie.md",
    "content": "# `res.clearCookie()`\n\nClears cookie (`name`) in the response.\n\n### Usage\n\n```usage\nres.clearCookie(name [,options]);\n```\n\n### Details\n\nThe path option defaults to \"/\".\n\n\n### Example\n```javascript\nres.cookie('name', 'tobi', { path: '/admin' });\nres.clearCookie('name', { path: '/admin' });\n```\n\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"res.clearCookie()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/res/res.cookie.md",
    "content": "# `res.cookie()`\n\nSets a cookie with name (`name`) and value (`value`) to be sent along with the response.\n\n\n### Usage\n```usage\nres.cookie(name, value [,options]);\n```\n\n\n### Details\n\nThe `path` option defaults to \"/\".\n\n`maxAge` is a convenience option that sets `expires` relative to the current time in milliseconds. \n\n```javascript\nres.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true });\n```\n\nAn object that is passed is then serialized as JSON, which is automatically parsed by the Express body-parser middleware.\n\n```javascript\nres.cookie('cart', { items: [1,2,3] });\nres.cookie('cart', { items: [1,2,3] }, { maxAge: 900000 });\n```\n\nSigned cookies are also supported through this method&mdash;just pass the `signed` option, set to `true`. `res.cookie()` will then use the secret passed into `express.cookieParser(secret)` to sign the value.\n\n```javascript\nres.cookie('name', 'tobi', { signed: true });\n```\n\n\n### Example\n```javascript\nres.cookie('name', 'tobi', {\n  domain: '.example.com',\n  path: '/admin',\n  secure: true\n});\n\nres.cookie('rememberme', '1', {\n  expires: new Date(Date.now() + 900000),\n  httpOnly: true\n});\n```\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"res.cookie()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/res/res.forbidden.md",
    "content": "# `res.forbidden()`\n\nThis method is used to send a <a href=\"http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_Error\" target=\"_blank\">403</a> (\"Forbidden\") response back down to the client, indicating that a request is not allowed.  This usually means the user agent tried to do something it was not allowed to do, like change the password of another user.\n\n\n### Usage\n\n```usage\nreturn res.forbidden();\n```\n\n### Details\n\nLike the other built-in custom response modules, the behavior of this method is customizable.\n\nBy default, it works as follows:\n\n+ The status code of the response is set to 403.\n+ A response body is sent with the string `\"Forbidden\"`.\n\n### Example\n\n```javascript\nif ( !req.session.userId ) {\n  return res.forbidden();\n}\n```\n\n\n### Notes\n> + This method is **terminal**, meaning that it is generally the last line of code your app should run for a given request (hence the advisory usage of `return` throughout these docs).\n>+ `res.forbidden()` (like other userland response methods) can be overridden or modified.  It runs the response method defined in `api/responses/forbidden.js`.  If a `forbidden.js` response method does not exist in your app, Sails will use the default behavior.\n\n\n\n\n<docmeta name=\"displayName\" value=\"res.forbidden()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/res/res.get.md",
    "content": "# `res.get()`\n\nReturns the current value of the specified response header (`header`).\n\n### Usage\n```usage\nres.get(header);\n```\n\n### Example\n```javascript\nres.get('Content-Type');\n// -> \"text/plain\"\n```\n\n### Notes\n>+ The `header` argument is case-insensitive.\n>+ Response headers can be changed up until the response is sent. See [`res.set()`](https://sailsjs.com/documentation/reference/response-res/res-set) for details.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"res.get()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/res/res.json.md",
    "content": "# `res.json()`\n\nSends a JSON response composed of the specified `data`.\n\n### Usage\n```usage\nreturn res.json(data);\n```\n\n### Details\n\nWhen an object or array is passed to it, this method is identical to `res.send()`. Unlike `res.send()`, however, `res.json()` may also be used for explicit JSON conversion of non-objects (null, undefined, etc.), even though these are technically not valid JSON.\n\n### Examples\n\n```javascript\nreturn res.json({ firstName: 'Tobi' });\n```\n\n```javascript\nreturn res.status(201).json({ id: 201721 });\n```\n\n```javascript\nvar leena = await User.findOne({ firstName: 'Leena' });\nif (!leena) { return res.notFound(); }\nreturn res.json(leena.id);//« you can send down primitives, like numbers\n```\n\n### Notes\n> + Don't forget that this method's name is all lowercase.\n> + This method is **terminal**, meaning that it is generally the last line of code your app should run for a given request (hence the advisory usage of `return` throughout these docs).\n\n\n\n\n<docmeta name=\"displayName\" value=\"res.json()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/res/res.jsonp.md",
    "content": "# `res.jsonp()`\n\nSend a JSON or JSONP response.\n\nIdentical to [`res.json()`](https://sailsjs.com/documentation/reference/response-res/res-json) except that, if a request parameter named \"callback\" was provided in the query string, then Sails will send the response data as [JSONP](http://en.wikipedia.org/wiki/JSONP) instead of JSON.  The value of the \"callback\" request parameter will be used as the name of the JSONP function call wrapper in the response.\n\n### Usage\n```usage\nreturn res.jsonp(data);\n```\n\n### Example\n\nIn an action:\n\n```js\nreturn res.jsonp([\n  {\n    name: 'Thelma',\n    id: 1\n  }, {\n    name: 'Leonardo'\n    id: 2\n  }\n]);\n```\n\n\nGiven `?callback=gotStuff`, the code above would send back a response body like:\n\n```javascript\ngotStuff([{name: 'Thelma', id: 1}, {name: 'Louise', id: 2}])\n```\n\n\n\n### Notes\n> + Don't forget that this method's name is all lowercase.\n> + If no \"callback\" request parameter was provided, this method works exactly like `res.json()`.\n> + This method is **terminal**, meaning that it is generally the last line of code your app should run for a given request (hence the advisory usage of `return` throughout these docs).\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"res.jsonp()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/res/res.location.md",
    "content": "# `res.location()`\n\nSets the \"Location\" response header to the specified URL expression (`url`).\n\n### Usage\n```usage\nres.location(url);\n```\n\n### Example\n```javascript\nres.location('/foo/bar');\nres.location('foo/bar');\nres.location('http://example.com');\nres.location('../login');\nres.location('back');\n```\n\n### Notes\n>+ You can use the same kind of URL expressions as in `res.redirect()`.\n\n\n\n\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"res.location()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/res/res.md",
    "content": "# Response (`res`)\n\n\n### Overview\n\nSails is built on [Express](https://github.com/expressjs/) and uses [Node's HTTP server](http://nodejs.org/api/http.html#http_http_createserver_requestlistener) conventions.  As a result, you can access all of the Node and Express methods and properties on the `res` object wherever it is accessible (i.e. in your actions, helpers, and policies).\n\nOne of the benefits of this compatibility is that, in many cases, you can paste existing Node.js code into a Sails app and it will work.  And since Sails implements a transport-agnostic request interpreter, the code in your Sails app is WebSocket-compatible as well.\n\nSails adds a few methods of its own to the `res` object, like [`res.badRequest()`](https://sailsjs.com/documentation/reference/response-res/res-bad-request), [`res.serverError()`](https://sailsjs.com/documentation/reference/response-res/res-server-error), [`res.view()`](https://sailsjs.com/documentation/reference/response-res/res-view).  These features are syntactic sugar on top of the underlying implementation, and support both HTTP _and_ (in many cases) WebSockets.\n\n\n<!--\n### Protocol Support\n\nThe chart below describes support for the methods and properties on the Sails Response object (`res`) across multiple transports:\n\n\n|                |  HTTP   | WebSockets |\n|----------------|---------|------------|\n| res.status() | :white_check_mark: | :white_check_mark: |\n| res.set()    | :white_check_mark: | :white_large_square: |\n| res.get()    | :white_check_mark: | :white_large_square: |\n| res.cookie() | :white_check_mark: | :white_large_square: |\n| res.clearCookie() | :white_check_mark: | :white_large_square: |\n| res.redirect() | :white_check_mark: | :white_check_mark: |\n| res.location() | :white_check_mark: | :white_large_square: |\n| res.charset  | :white_check_mark: | :white_check_mark: |\n| res.send()   | :white_check_mark: | :white_check_mark: |\n| res.json()   | :white_check_mark: | :white_check_mark: |\n| res.jsonp()  | :white_check_mark: | :white_check_mark: |\n| res.type()   | :white_check_mark: | :white_large_square: |\n| res.format() | :white_check_mark: | :white_large_square: |\n| res.attachment() | :white_check_mark: | :white_large_square: |\n| res.sendfile() | :white_check_mark: | :white_large_square: |\n| res.download() | :white_check_mark: | :white_large_square: |\n| res.links()  | :white_check_mark: | :white_large_square: |\n| res.locals    | :white_check_mark: | :white_check_mark: |\n| res.render() | :white_check_mark: | :white_large_square: |\n| res.view()   | :white_check_mark: | :white_large_square: |\n\n\n### Legend\n\n  - :white_check_mark: - fully supported\n  - :white_large_square: - feature not yet implemented\n  - :heavy_multiplication_x: - unsupported due to protocol restrictions\n\n-->\n\n<docmeta name=\"displayName\" value=\"Response (`res`)\">\n<docmeta name=\"stabilityIndex\" value=\"3\">\n"
  },
  {
    "path": "docs/reference/res/res.negotiate.md",
    "content": "# `res.negotiate()`\n\n> _**This method is deprecated**._\n>\n> You should use a [custom response](https://sailsjs.com/documentation/concepts/extending-sails/custom-responses) instead.\n>\n> To handle errors from [Waterline model methods](https://sailsjs.com/documentation/reference/waterline-orm/models), check the `name` property of the error (see the [Waterline error reference](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for more details).\n\nGiven an error (`err`), attempt to guess which error response should be called (`badRequest`, `forbidden`, `notFound`, or `serverError`) by inspecting the `status` property.  If `err` is not a dictionary, or the `status` property does not match a known HTTP status code, then default to `serverError`.\n\nEspecially handy for handling potential validation errors from [Model.create()](https://sailsjs.com/documentation/reference/waterline-orm/models/create) or [Model.update()](https://sailsjs.com/documentation/reference/waterline-orm/models/update).\n\n### Usage\n\n```usage\nreturn res.negotiate(err);\n```\n\n### Details\n\nLike the other built-in custom response modules, the behavior of this method is customizable.\n\n`res.negotiate()` examines the provided error (`err`) and determines the appropriate error-handling behavior from one of the following methods:\n\n+ [`res.badRequest()`](https://sailsjs.com/documentation/reference/response-res/res-bad-request)   (400)\n+ [`res.forbidden()`](https://sailsjs.com/documentation/reference/response-res/res-forbidden)    (403)\n+ [`res.notFound()`](https://sailsjs.com/documentation/reference/response-res/res-not-found)     (404)\n+ [`res.serverError()`](https://sailsjs.com/documentation/reference/response-res/res-server-error)  (500)\n\nThe determination is made based on `err`'s \"status\" property.  If a more specific diagnosis cannot be determined (e.g. `err` doesn't have a \"status\" property, or it's a string), Sails will default to `res.serverError()`.\n\n\n\n### Example\n\n\n```javascript\n// Add Fido's birthday to the database:\nPet.update({name: 'fido'})\n  .set({birthday: new Date('01/01/2010')})\n  .exec(function (err, fido) {\n    if (err) return res.negotiate(err);\n    return res.ok(fido);\n  });\n```\n\n\n### Notes\n> + This method is **terminal**, meaning it is generally the last line of code your app should run for a given request (hence the advisory usage of `return` throughout these docs).\n>+ `res.negotiate()` (like other userland response methods) can be overridden - just define a response module (`/responses/negotiate.js`) and export a function definition.\n>+ This method is used as the default handler for uncaught errors in Sails.  That means it is called automatically if an error is thrown in _any_ request handling code, _but only within the initial step of the event loop_.  You should always specifically handle errors that might arise in callbacks/promises from asynchronous code.\n\n<docmeta name=\"isDeprecated\" value=\"true\">\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"res.negotiate()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/res/res.notFound.md",
    "content": "# res.notFound()\n\nThis method is used to send a <a href=\"http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_Error\" target=\"_blank\">404</a> (\"Not Found\") response using either [res.json()](https://sailsjs.com/documentation/reference/response-res/res-json) or [res.view()](https://sailsjs.com/documentation/reference/response-res/res-view). It is called automatically when Sails receives a request that doesn't match any of its explicit routes or route blueprints (i.e. serves the 404 page).\n\nWhen called manually from your app code, this method is normally used to indicate that the user agent tried to find, update, or delete something that doesn't exist.\n\n\n### Usage\n\n```usage\nreturn res.notFound();\n```\n\n### Details\n\nLike the other built-in custom response modules, the behavior of this method is customizable.\n\nBy default, it works as follows:\n\n+ The status code of the response will be set to 404.\n+ If the request \"[wants JSON](https://sailsjs.com/documentation/reference/request-req/req-wants-json)\" (e.g. the request originated from AJAX, WebSockets, or a REST client like cURL), Sails will send a response body with the string `\"Not Found\"`.\n+ If the request _does not_ \"want JSON\" (e.g. a URL typed into a web browser), Sails will attempt to serve the view located at `views/404.ejs` (assuming the default EJS [view engine](https://sailsjs.com/documentation/concepts/views/view-engines)).  If no such view is found, or an error occurs attempting to serve it, a default response body will be sent with the string `\"Not Found\"`.\n\n### Example\n\n```javascript\nPet.findOne()\n.where({ name: 'fido' })\n.exec(function(err, fido) {\n  if (err) return res.serverError(err);\n  if (!fido) return res.notFound();\n  // ...\n})\n```\n\n\n### Notes\n> + This method is **terminal**, meaning that it is generally the last line of code your app should run for a given request (hence the advisory usage of `return` throughout these docs).\n>+ `res.notFound()` (like other userland response methods) can be overridden or modified.  It runs the response method defined in `api/responses/notFound.js`.  If a `notFound.js` response method does not exist in your app, Sails will use the default behavior.\n\n\n\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"res.notFound()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/res/res.ok.md",
    "content": "# `res.ok()`\n\nThis method is used to send a <a href=\"https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_Success\" target=\"_blank\">200</a> (\"OK\") response back down to the client.\n\n\n### Usage\n\n```usage\nreturn res.ok();\n```\n\n_Or:_\n+ `return res.ok(data);`\n\n\n### Details\n\nLike the other built-in custom response modules, the behavior of this method is customizable.\n\nBy default, it works as follows:\n\n+ The status code of the response will be set to 200.\n+ Sails will send any provided error `data` as JSON.  If no `data` is provided, a default response body will be sent (the string `\"OK\"`).\n\n\n### Example\n\n```javascript\nreturn res.ok();\n```\n\n\n### Notes\n> + This method is **terminal**, meaning that it is generally the last line of code your app should run for a given request (hence the advisory usage of `return` throughout these docs).\n>+ `res.ok()` (like other userland response methods) can be overridden or modified.  It runs the response method defined in `api/responses/ok.js`.  If an `ok.js` response method does not exist in your app, Sails will use the default behavior.\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"res.ok()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/res/res.redirect.md",
    "content": "# `res.redirect()`\n\nRedirect the requesting user agent to the given absolute or relative URL.\n\n\n### Usage\n```usage\nreturn res.redirect(url);\n```\n\n\n_Or:_\n+ `return res.redirect(statusCode, url);`\n\n### Arguments\n\n|   | Argument       | Type        | Details |\n|---|----------------|:-----------:|---------|\n| 1 | _statusCode_   | ((number?)) | An optional status code (e.g. 301).  (If omitted, a status code of 302 will be assumed.)\n| 2 | url            | ((string))  | A URL expression (see below for complete specification).<br/> e.g. `\"http://google.com\"` or `\"/login\"`\n\n\n\n### Details\n\nSails/Express support a few forms of redirection: \n\n+ A fully qualified URI for redirecting to a different domain:\n\n```javascript\nreturn res.redirect('http://google.com');\n```\n\n+ The domain-relative redirect.  For example, if you were on http://example.com/admin/post/new, the following redirect to `/checkout` would land you at http://example.com/checkout:\n\n```javascript\nreturn res.redirect('/checkout');\n```\n\n+ Pathname-relative redirects. If you were on http://example.com/admin/post/new, the following redirect would land you at http//example.com/admin/post:\n\n```javascript\nreturn res.redirect('..');\n```\n+ A back redirect, which allows you to redirect a request back from whence it came from using the \"Referer\" (or \"Referrer\") header (if omitted, redirects to `/` by default):\n\n```javascript\nreturn res.redirect('back');\n```\n\n### Notes\n> + This method is **terminal**, meaning that it is generally the last line of code your app should run for a given request (hence the advisory usage of `return` throughout these docs).\n> + As of Sails v1.x, for HTTP requests, `res.redirect()` [does not respect the status code established by `res.status()`](https://github.com/balderdashy/sails-docs/pull/796#issuecomment-284224746).  Thanks [@Guillaume-Duval](https://github.com/Guillaume-Duval) and [@oshatrk](https://github.com/oshatrk)!\n> + When your app calls `res.redirect()`, Sails sends a response with status code [302](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection), indicating a temporary redirect.  This instructs the user agent to send a new request to the indicated URL.  There is no way to _force_ a user agent to follow redirects, but most clients play nicely.\n> + In general, you should not need to use `res.redirect()` if a request \"wants JSON\" (i.e. [`req.wantsJSON`](https://sailsjs.com/documentation/reference/request-req/req-wants-json)).\n> + If a request originated from the Sails socket client, it always \"wants JSON\", so the [Sails socket client](https://sailsjs.com/documentation/reference/web-sockets/socket-client) does _not_ follow redirects. For this reason, if an action is called via a WebSocket request using (for example) [`io.socket.get()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-get), it will simply receive the appropriate status code and a \"Location\" header indicating the location of the desired resource.  It&rsquo;s up to the client-side code to decide how to handle redirects for WebSocket requests.\n\n\n\n<docmeta name=\"displayName\" value=\"res.redirect()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/res/res.send.md",
    "content": "# `res.send()`\n\nSend a string response in a format other than JSON (XML, CSV, plain text, etc.).\n\nThis method is used in the underlying implementation of most of the other terminal response methods.\n\n### Usage\n```usage\nreturn res.send([string]);\n```\n\n### Details\n\nThis method can be used to send a string of XML.\n\nIf no argument is provided, no response body is sent back&mdash;just the status code.\n\n### Examples\n\nTo allow users to export their own data, while complying with Europe's GDPR regulations, you might send back some dynamic CSV-formatted data, like this:\n\n```javascript\n// Send back some dynamic CSV-formatted data.\nreturn res.set('text/csv').send(`\nsome,csv,like,this\nor,,like,this\n`);\n```\n\nOr, to respond with XML (e.g. for a sitemap):\n\n```javascript\n// Send down some dynamic XML-formatted data.\nreturn res.set('application/xml').send(`<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n  <url>\n    <loc>http://sailsjs.com</loc>\n    <lastmod>2018-03-28T17:02:23.688Z</lastmod>\n    <changefreq>monthly</changefreq>\n  </url>\n</urlset>\n`);\n```\n\nYou can also send arbitrary plain text and use any status code you like:\n\n```javascript\n// You can use any status code you like.\n// (Defaults to 200 unless you specify something else.)\nreturn res.status(420).send('Hello world!');\n```\n\n\n### Notes\n> + This method is **terminal**, meaning that it's generally the last line of code your app should run for a given request (hence the advisory usage of `return` throughout these docs).\n> + If you want to send a dictionary or JSON, use [`res.json()`](https://sailsjs.com/documentation/reference/response-res/res-json).\n> + If you want to send a stream, use [actions2](https://sailsjs.com/documentation/concepts/actions-and-controllers)(preferably) or `.pipe(res)` (if you absolutely must).\n> + If you want to send a custom status code, call [`req.status()`](https://sailsjs.com/documentation/reference/response-res/res-status) first.\n\n\n\n<docmeta name=\"displayName\" value=\"res.send()\">\n\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/res/res.serverError.md",
    "content": "# `res.serverError()`\n\nThis method is used to send a <a href=\"http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_Server_Error\" target=\"_blank\">500</a> (\"Server Error\") response back down to the client, indicating that some kind of server error occurred (i.e. the error is not the requesting user agent's fault).\n\n### Usage\n\n\n```usage\nreturn res.serverError(err);\n```\n\n_Or:_\n+ `return res.serverError();`\n\n### Details\n\nLike the other built-in custom response modules, the behavior of this method is customizable.\n\nBy default, it works as follows:\n\n+ The status code of the response will be set to 500.\n\n+ If the request \"[wants JSON](https://sailsjs.com/documentation/reference/request-req/req-wants-json)\" (e.g. the request originated from AJAX, WebSockets, or a REST client like cURL), Sails will send the provided error `data` as JSON.  If no `data` is provided, a default response body will be sent (the string `\"Internal Server Error\"`).\n\n+ If the request _does not_ \"want JSON\" (e.g. a URL typed into a web browser), Sails will attempt to serve the view located at `views/500.ejs` (assuming the default EJS [view engine](https://sailsjs.com/documentation/concepts/views/view-engines)).  If no such view is found, or an error occurs attempting to serve it, a default response body will be sent with the string `\"Internal Server Error\"`.\n\n\n\n### Example\n\n```javascript\nreturn res.serverError('Salesforce could not be reached');\n```\n\n### Notes\n> + This method is **terminal**, meaning that it is generally the last line of code your app should run for a given request (hence the advisory usage of `return` throughout these docs).\n\n>+ `res.serverError()` (like other userland response methods) can be overridden or modified.  It runs the response method defined in `api/responses/serverError.js`.  If a `serverError.js` response method does not exist in your app, Sails will use the default behavior.\n\n>+ The specified `data` **will be excluded from the JSON response and view locals** if the app is running in the \"production\" environment (i.e. `process.env.NODE_ENV === 'production'`).\n\n\n\n\n<docmeta name=\"displayName\" value=\"res.serverError()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/res/res.set.md",
    "content": "# `res.set()`\n\nSets specified response header (`header`) to the specified value (`value`).\n\nAlternatively, you can pass in a single object argument (`headers`) to set multiple header fields at once, where the keys are the header field names and the corresponding values are the desired values.\n\n### Usage\n```usage\nres.set(header, value);\n```\n\n-or-\n\n```usage\nres.set(headers);\n```\n\n### Example\n```javascript\n\nres.set('Content-Type', 'text/plain');\n\nres.set({\n  'Content-Type': 'text/plain',\n  'Content-Length': '123',\n  'ETag': '12345'\n})\n\n```\n\n\n\n<docmeta name=\"displayName\" value=\"res.set()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/res/res.status.md",
    "content": "# `res.status()`\n\nSet the status code of this response.\n\n### Usage\n```usage\nres.status(statusCode);\n```\n\n### Example\n```javascript\nres.status(418);\nres.send('I am a teapot');\n```\n\n### Notes\n>+ The status code may be set up until the response is sent.\n>+ `res.status()` is effectively just a chainable alias of Node's `res.statusCode = …;`.\n\n\n\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"res.status()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/res/res.type.md",
    "content": "# `res.type()`\n\nSets the \"Content-Type\" response header to the specified `type`.\n\nThis method is pretty forgiving (see examples below), but note that if `type` contains a `\"/\"`, `res.type()` assumes it is a MIME type and interprets it literally.\n\n### Usage\n```usage\nres.type(type);\n```\n\n### Example\n```javascript\nres.type('.html');\nres.type('html');\nres.type('json');\nres.type('application/json');\nres.type('png');\n```\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"res.type()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/res/res.view.md",
    "content": "# `res.view()`\n\nRespond with an HTML page.\n\n\n### Usage\n\n```usage\nreturn res.view(pathToView, locals);\n```\n\n_Or:_\n+ `return res.view(pathToView);`\n+ `return res.view(locals);`\n+ `return res.view();`\n\n\nUses the [configured view engine](https://sailsjs.com/documentation/concepts/views/view-engines) to compile the [view template](https://sailsjs.com/documentation/concepts/views/partials) at `pathToView` into HTML.  If `pathToView` is not provided, serves the conventional view based on the current controller and action.\n\nThe specified [`locals`](https://sailsjs.com/documentation/concepts/views/locals) are merged with your configured app-wide locals, as well as certain built-in locals from Sails and/or your view engine, then passed to the view engine as data.\n\n\n### Arguments\n\n|   | Argument       | Type        | Details |\n|---|----------------|:-----------:|---------|\n| 1 |  pathToView    | ((string))  | The path to the desired view file relative to your app's [`views` folder](https://sailsjs.com/documentation/anatomy/views) (usually `views/`), without the file extension (e.g. `.ejs`), and with no trailing slash.<br/>Defaults to \"identityOfController/nameOfAction\".\n| 2 |  locals        | ((dictionary))  | Data to pass to the view template.  These explicitly specified locals will be merged in to Sails' [built-in locals](https://sailsjs.com/documentation/concepts/views/locals) and your [configured app-wide locals](https://github.com/balderdashy/sails/blob/master/docs/PAGE_NEEDED.md).<br/>Defaults to `{}`.\n\n\n\n### Example\n\nConsider a conventionally configured Sails app with a call to `res.view()` in the `cook()` action of its `OvenController.js`.\n\nWith no `pathToView` argument, `res.view()` will decide the path by combining the identity of the controller (`oven`) and the name of the action (`cook`):\n\n```js\nreturn res.view();\n// -> responds with `views/oven/cook.ejs`\n```\n\nHere's how you would load the same view using an explicit `pathToView`:\n\n```js\nreturn res.view('oven/cook');\n// -> responds with `views/oven/cook.ejs`\n```\n\nFinally, here's a more involved example demonstrating how `res.view` can be combined with Waterline queries:\n\n```js\n// Find the 5 hottest oven brands on the market\nOven.find().sort('heat ASC').exec(function (err, ovens){\n  if (err) return res.serverError(err);\n\n  return res.view('oven/top5', {\n    hottestOvens: ovens\n  });\n  // -> responds using the view at `views/oven/top5.ejs`,\n  // and with the oven data we looked up as view locals.\n  //\n  // e.g. in the view, we might have something like:\n  // ...\n  // <% _.each(hottestOvens, function (aHotOven) { %>\n  //  <li><%= aHotOven.name %></li>\n  // <% }) %>\n  // ...\n});\n\n```\n\n\n### Notes\n> + This method is **terminal**, meaning that it is generally the last line of code your app should run for a given request (hence the advisory usage of `return` throughout these docs).\n> + `res.view()` reads a view file from disk, compiles it into HTML, then streams it back to the client.  If you already have the view in memory, or don't want to stream the compiled HTML directly back to the client, use `sails.hooks.views.render()` instead.\n> + `res.view()` always looks for the _lowercased_ version of a view filename.  For example, if your controller is `FooBarController` and your action is `Baz`, `res.view()` will attempt to find `views/foobar/baz.ejs`.  On _case-sensitive_ filesystems (e.g. Ubuntu Linux), this can lead to unexpected errors when locating views if they are saved with capital letters.  For this reason, it is recommended that you always save your views and view folders in lowercase.\n\n\n\n\n\n\n\n\n\n\n<docmeta name=\"displayName\" value=\"res.view()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/sails.config/miscellaneous.md",
    "content": "# Miscellaneous (`sails.config.*`)\n\nFor a conceptual overview of configuration in Sails, see https://sailsjs.com/documentation/concepts/Configuration.\n\nThis page is a quick reference of assorted configuration topics that don't fit elsewhere, namely top-level properties on the sails.config object.  Many of these properties are best set on a [per-environment basis](https://sailsjs.com/documentation/anatomy/my-app/config/env), or in your [config/local.js](https://sailsjs.com/documentation/concepts/configuration/the-local-js-file).  To set them globally for your app, create a new file in the `config` folder (e.g. `config/misc.js`) and add them there.\n\n### `sails.config.port`\n\nThe `port` setting determines which <a href=\"http://en.wikipedia.org/wiki/Port_(computer_networking)\">TCP port</a> your Sails app will use to listen for incoming requests.  Ports are a [transport-layer](https://en.wikipedia.org/wiki/Transport_layer) concept designed to allow many different networking applications to run at the same time on a single computer.\n\nBy default, if it&rsquo;s set, Sails uses the port configured in your app (`sails.config.port`).  If not, it checks to see if the `PORT` environment variable is set, and uses that if possible.  Otherwise it falls back to port 1337.\n\n> In production, you will probably want Sails to listen on port 80 (or 443, if you have an SSL certificate and are serving your site via `https://`), but depending on where your app is deployed, you may or may not need to actually modify this setting.  For example, if you are deploying behind a proxy, or to a PaaS like [Heroku](http://heroku.com), [Azure App Service](https://azure.microsoft.com/en-us/services/app-service/), or [Deis](http://deis.io/), you probably won't need to configure `sails.config.port`, since in most cases that's handled automatically.  For more guidance and tips related to deploying, scaling, and maintaining Sails in production, see [Concepts > Deployment](https://sailsjs.com/documentation/concepts/deployment).\n\n\n### `sails.config.explicitHost`\n\nBy default, Sails will assume `localhost` as the host that will be listening for incoming requests.  This will work in the majority of hosting environments you encounter, but in some cases ([OpenShift](http://www.openshift.com) being one example) you'll need to explicitly declare the host name of your Sails app.  Setting `explicitHost` tells Sails to listen for requests on that host instead of `localhost`.\n\n\n### `sails.config.environment`\n\nThe runtime &ldquo;environment&rdquo; of your Sails app is usually either `development` or `production`.\n\nIn development, your Sails app will go out of its way to help you (for instance you will receive more descriptive error and debugging output).\n\nIn production, Sails configures itself (and its dependencies) to optimize performance.  You should always put your app in production mode before you deploy it to a server; this helps ensure that your Sails app remains stable, performant, and scalable.\n\n#### Using the \"production\" environment\n\nBy default, Sails determines its environment using the `NODE_ENV` environment variable. If `NODE_ENV` is not set, Sails will look to see if you provided a `sails.config.environment` setting, and use it if possible.  Otherwise, it runs in the development environment.\n\nWhen you lift your app with the `NODE_ENV` environment variable set to `production`, Sails automatically sets `sails.config.environment` to `production` too.  This is the recommended way of switching to production mode. We don't usually recommend configuring `sails.config.environment` manually, since some of Sails&rsquo; dependencies rely on the `NODE_ENV` environment variable, and it is automatically set by most Sails/Node.js hosting services.\n\nIf you attempt to lift a Sails app in the production environment _without_ setting `NODE_ENV` to `production` (for example, by running `sails lift --prod`), Sails automatically sets `NODE_ENV` to `production` for you.  If you attempt to lift a Sails app in production while `NODE_ENV` is set to a _different_ value (for example `NODE_ENV=development sails lift --prod`), the app fails to start.\n\n> For more background on configuring your Sails app for production, see [Concepts > Deployment](https://sailsjs.com/documentation/concepts/deployment).\n\nNote that it is perfectly valid to set `sails.config.environment` to something else entirely, like \"staging\", while still setting `NODE_ENV=production`.  This causes Sails to load a different environment-specific configuration file (e.g. `config/env/staging.js`) and Grunt task (e.g. `tasks/register/staging.js`), while still otherwise acting like it's in production.\n\n\n### `sails.config.hookTimeout`\n\nA time limit, in milliseconds, imposed on all hooks in your app.  Sails will give up if any hook takes longer than this to load.  Defaults to `20000` (20 seconds).\n\n> The most common reason to change this setting is to tolerate slow production Grunt tasks.  For example, if your app is using uglify, and you have lots and lots of client-side JavaScript files in your assets folder, then you might need Sails to wait longer than 20 seconds to compile all of those client-side assets.  For more tips about the production asset pipeline, see [Concepts > Deployment](https://sailsjs.com/documentation/concepts/deployment).\n\n### `sails.config.ssl`\n\nSSL/TLS (transport-layer security) is critical for preventing potential man-in-the-middle attacks.  Without a protocol like SSL/TLS, web basics like securely transmitting login credentials and credit card numbers would be much more complicated and troublesome.  SSL/TLS is not only important for HTTP requests (`https://`), it's also necessary for WebSockets (over `wss://`).  Fortunately, you only need to worry about configuring SSL settings in one place: `sails.config.ssl`.\n\n> ##### SSL and load balancers\n>\n> The `sails.config.ssl` setting is only relevant if you want your _Sails process_ to manage SSL.  This isn't always the case.  For example, if you expect your Sails app to get more traffic over time, it will need to scale to multiple servers, necessitating a load balancer.  Most of the time, for performance and simplicity, it is a good idea to terminate SSL at your load balancer.  If you do that, then since SSL/TLS will have already been dealt with _before packets reach your Sails app_, you won't need to use the `sails.config.ssl` setting at all.  (This is also true if you're using a PaaS like Heroku, or almost any other host with a built-in load balancer.)\n>\n> If you're satisfied that this configuration setting applies to your app, then please continue below for more details.\n\nUse `sails.config.ssl` to set up basic SSL server options, or to indicate that you will be specifying more advanced options in [sails.config.http.serverOptions](https://sailsjs.com/documentation/reference/configuration/sails-config-http#?properties).\n\nIf you specify a dictionary, it should contain both `key` _and_ `cert` keys, _or_ a `pfx` key. The presence of those options indicates to Sails that your app should be lifted with an HTTPS server.  If your app requires a more complex SSL setup (for example by using [SNICallback](https://nodejs.org/api/tls.html#tls_tls_createserver_options_secureconnectionlistener)), set `sails.config.ssl` to `true` and specify your advanced options in [sails.config.http.serverOptions](https://sailsjs.com/documentation/reference/configuration/sails-config-http#?properties).\n\n#### SSL configuration example\n\nFor this example, we'll assume you created a folder in your project, `config/ssl/` and dumped your certificate/key files inside.  Then, in one of your config files, include the following:\n\n```javascript\n// Assuming this is in `config/env/production.js`, and your folder of SSL cert/key files is in `config/ssl/`:\n\nssl: {\n  ca: require('fs').readFileSync(require('path').resolve(__dirname,'../ssl/my-gd-bundle.crt')),\n  key: require('fs').readFileSync(require('path').resolve(__dirname,'../ssl/my-ssl.key')),\n  cert: require('fs').readFileSync(require('path').resolve(__dirname,'../ssl/my-ssl.crt'))\n}\n```\n\n<docmeta name=\"displayName\" value=\"sails.config.*\">\n"
  },
  {
    "path": "docs/reference/sails.config/sails.config.blueprints.md",
    "content": "# `sails.config.blueprints`\n\nThese configurable settings allow you to configure the blueprint API in Sails.  Some settings (like `sails.config.blueprints.autoWatch`) control the behavior of built-in [blueprint actions](https://sailsjs.com/documentation/concepts/blueprints/blueprint-actions), whereas others (like `sails.config.blueprints.shortcuts`) tweak the behavior of implicit [blueprint routing](https://sailsjs.com/documentation/concepts/blueprints/blueprint-actions) and/or determine whether Sails automatically binds certain kinds of blueprint routes at all.\n\n> Remember, blueprint actions can be attached to your custom routes _regardless of whether or not_ you have any kind of implicit blueprint routing enabled.\n\n### Properties\n\n##### Route-related settings\n\n| Property    | Type       | Default   | Details |\n|:------------|:----------:|:----------|:--------|\n| `actions`| ((boolean))|`false`| Whether implicit blueprint (\"shadow\") routes are automatically generated for every action in your app. e.g. having an `api/controllers/foo/bar.js` file or a `bar` function in `api/controllers/FooController.js` would automatically route incoming requests to `/foo/bar` to that action, as long as it is not overridden by a [custom route](https://sailsjs.com/documentation/concepts/routes/custom-routes).  When enabled, this setting _also_ binds additional, special implicit (\"shadow\") routes to any actions named `index`, and for the relative \"root\" URL for your app and each of its controllers.  For example, a `/foo` shadow route for `api/controllers/foo/index.js`, or a `/` shadow route for `api/controllers/index.js`.\n|`rest`|((boolean))|`true`|Automatic REST blueprints enabled? e.g. `'get /:model/:id?'` `'post /:model'` `'put /:model/:id'` `'delete /:model/:id'`.\n|`shortcuts`|((boolean))|`true`|These CRUD shortcuts exist for your convenience during development, but you'll want to disable them in production.: `'/:model/find/:id?'`, `'/:model/create'`, `'/:model/update/:id'`, and `'/:model/destroy/:id'`.\n| `prefix`      | ((string))| `''`     | Optional mount path prefix (e.g. '/api/v2') for all [blueprint routes](https://sailsjs.com/documentation/concepts/blueprints/blueprint-routes), including `rest`, `actions`, and `shortcuts`.  This only applies to implicit blueprint (\"shadow\") routes, not your [custom routes](https://sailsjs.com/documentation/concepts/routes/custom-routes).\n| `restPrefix`  | ((string))| `''`     | Optional mount path prefix for all REST blueprint routes on a controller, e.g. '/api/v2'. (Does not include `actions` and `shortcuts` routes.) This allows you to take advantage of REST blueprint routing, even if you need to namespace your RESTful API methods.  Will be joined to your `prefix` config, e.g. `prefix: '/api'` and `restPrefix: '/rest'`. RESTful actions will be available under `/api/rest`.\n|`pluralize`|((boolean))|false| Whether to use plural model names in blueprint routes, e.g. `/users` for the `User` model. (This only applies to blueprint autoroutes, not manual routes from `sails.config.routes`.)\n\n\n##### Action-related settings\n\n| Property    | Type       | Default   | Details |\n|:------------|:----------:|:----------|:--------|\n|`autoWatch`|((boolean))|`true`| Whether to subscribe the requesting socket in the `find` and `findOne` blueprint action to notifications about newly _created_ records via the blueprint API.\n|`parseBlueprintOptions`|((function))|(See below)|Provide this function in order to override the default behavior for blueprint actions (including search criteria, skip, limit, sort and population).\n\n##### Using `parseBlueprintOptions`\n\nEach blueprint action includes, at its core, a Waterline model method call.  For instance, the `find` blueprint, when run for the `User` model, runs `User.find()` in order to retrieve some user records.  The options that are passed to these Waterline methods are determined by a call to `parseBlueprintOptions()`.  The default version of this method (available via `sails.hooks.blueprints.parseBlueprintOptions()`) determines the default behaviors for blueprints.  You can override `parseBlueprintOptions` in your [blueprints config](https://sailsjs.com/documentation/reference/configuration/sails-config-blueprints) (in [`config/blueprints.js`](https://sailsjs.com/documentation/anatomy/config/blueprints.js)) to customize the behavior for _all_ blueprint actions, or on a [per-route basis](https://sailsjs.com/documentation/concepts/routes/custom-routes#?route-target-options) to customize the behavior for a single route.\n\nThe `parseBlueprintOptions()` method takes a single argument (the [request object](https://sailsjs.com/documentation/reference/request-req)) and is expected to return a dictionary of Waterline query options.  (You can review an unrealistically-expanded example of a such a dictionary [here](https://gist.github.com/mikermcneil/1b87af6b6a8458254deb83a6d1cf264f), but keep in mind that not all keys apply to all blueprint actions. See [source code in Sails code](https://github.com/balderdashy/sails/tree/v1.2.2/lib/hooks/blueprints/actions) for complete details).\n\nAdding your own `parseBlueprintOptions()` is an advanced concept, and it is recommended that you first familiarize yourself with the [default method code](https://github.com/balderdashy/sails/blob/v1.2.2/lib/hooks/blueprints/parse-blueprint-options.js) and use it as a starting point.  For small modifications to blueprint behavior, it is best to first call the default method inside your override and then make changes to the returned query options:\n\n```js\nparseBlueprintOptions: function(req) {\n\n  // Get the default query options.\n  var queryOptions = req._sails.hooks.blueprints.parseBlueprintOptions(req);\n\n  // If this is the \"find\" or \"populate\" blueprint action, and the normal query options\n  // indicate that the request is attempting to set an exceedingly high `limit` clause,\n  // then prevent it (we'll say `limit` must not exceed 100).\n  if (req.options.blueprintAction === 'find' || req.options.blueprintAction === 'populate') {\n    if (queryOptions.criteria.limit > 100) {\n      queryOptions.criteria.limit = 100;\n    }\n  }\n\n  return queryOptions;\n\n}\n```\n\n\n<docmeta name=\"displayName\" value=\"sails.config.blueprints\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/sails.config/sails.config.bootstrap.md",
    "content": "# `sails.config.bootstrap`\n\n### What is this?\n`sails.config.bootstrap` is a customizable seed function that runs before your Sails app is lifted (i.e. starts up).\n\nBy convention, this function is used for:\n  + setting up baseline data\n    + _e.g. find or create an admin user_\n  + running sanity checks on the status of your database\n    + _e.g. count hand records that don't have any fingers. If any are found, then refuse to lift until the database is fixed_\n  + seeding your database with stub data\n    + _e.g. create & associate a few fake \"Clinic\", \"Pet\", and \"Veterinarian\" records to make it easier to test your animal adoption app_\n\nFor an example bootstrap function, generate a new Sails app and have a look at [`config/bootstrap.js`](https://sailsjs.com/documentation/anatomy/config/bootstrap.js).\n\n### Notes\n\n> - Sails will log a warning if the bootstrap function is \"taking too long\".  If your bootstrap function is taking longer to run than the default timeout of 30 seconds and you would like to prevent the warning from being displayed, you can stall it by configuring `sails.config.bootstrapTimeout` to a larger number of milliseconds. (For example, you can increase the timeout to one minute by using `60000`.)\n\n<docmeta name=\"displayName\" value=\"sails.config.bootstrap()\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/sails.config/sails.config.connections.md",
    "content": "# `sails.config.datastores`\n\n### What is this?\n\nDatastore configurations (or simply datastores) are like \"saved settings\" for your adapters.\n\nIn Sails, [database adapters](https://sailsjs.com/documentation/concepts/extending-sails/adapters) are the middleman between your app and some kind of structured data storage (typically a database).  But in order for an adapter to communicate between your Sails app and a particular database, it needs some additional information.  That's where datastores come in.  Datastores are dictionaries (plain JavaScript objects) that specify an `adapter`, as well as other necessary configuration information, like `url`, or `host`, `port`, `user`, and `password`.\n\nWhile this [can be overridden](https://sailsjs.com/documentation/concepts/orm/model-settings) on a per-model basis, out of the box, every model in your app uses a datastore named \"default\".\n\n\n### The default datastore\n\n##### The default development database\nAs a convenience during development, Sails provides a built-in database adapter called `sails-disk`.  This adapter simulates a real database by reading and writing database records to a JSON file on your computer's hard drive.  And while `sails-disk` makes it easy to run your Sails/Node.js app in almost any environment with minimal setup, it is not designed for production use.  Before deploying your app and exposing it to real users, you'll want to choose a proper database such as PostgreSQL, MySQL, MongoDB, etc.  To do that, you'll need to customize your app's default datastore.\n\n##### Using a local MySQL database in development\nUnsurprisingly, the default datastore shared by all of your app's models is named \"default\".  So to hook up a different database, that's the key you'll want to change.  For example, imagine you want to develop against a MySQL server installed locally on your laptop:\n\nFirst, install the [MySQL adapter](http://npmjs.com/package/sails-mysql) for Sails and Waterline:\n\n```bash\nnpm install sails-mysql --save --save-exact\n```\n\nThen edit your default datastore configuration in `config/datastores.js` so that it looks something like this:\n\n```javascript\n// config/datastores.js\nmodule.exports.datastores = {\n  default: {\n    adapter: require('sails-mysql'),\n    url: 'mysql://root:squ1ddy@localhost:3306/my_dev_db_name',\n  }\n};\n```\n\nThat's it!  The next time you lift your app, all of your models will communicate with the specified MySQL database whenever your code executes built-in model methods like `.create()` or `.find()`.\n\n> Want to use a different database?  Don't worry, MySQL is just an example. You can use any [supported database adapter](https://sailsjs.com/documentation/concepts/extending-sails/adapters/available-adapters) in your Sails app.\n\n\n### The connection URL\n\nYou might have noticed that we used `url` here, instead of specifying individual settings like `host`, `port`, `user`, `password`, and `database`.  This is called a _connection URL_ (or \"connection string\"), and it's just another, more concise way, to tell Sails and Waterline about your datastore configuration.\n\nOne major benefit to this style of configuration is that the format of a connection URL is the same across various types of databases. In other words, whether you're using MySQL, PostgreSQL, MongoDB, or almost any other common database technology, you can specify basic configuration using a URL that looks roughly the same:\n\n```\nprotocol://user:password@host:port/database\n```\n\nThe `protocol://` chunk of the URL is always based on the adapter you're using (`mysql://`, `mongodb://`, etc.), and the rest of the URL is composed of the credentials and network information that your app needs to locate and connect to the database.  Here's a deconstructed version of the `url` from the MySQL example above that shows what each section is called:\n\n```\nmysql://  root  :  squ1ddy   @  localhost  :  3306  /  my_dev_db_name\n|         |        |            |             |        |\n|         |        |            |             |        |\nprotocol  user     password     host          port     database\n```\n\nIn production, if you are using a cloud-hosted database, you'll probably be given a connection URL (e.g. `mysql://lkjdsf4:kw8sd@us-west-2.64-8.amazonaws.com:3306/4e843g`).  If not, it's usually a good idea to build one yourself from the individual pieces of information.  For more information about how to configure your particular database, check out the [database adapter reference](https://sailsjs.com/documentation/concepts/extending-sails/adapters/available-adapters).\n\n##### Building your own connection URL\n\nIf you have all of the pieces of information mentioned above, building a connection URL is easy: you just stick them together.  But sometimes, you may not want to specify _all_ of those details (if you want to use the default port, or if you're using a local database that does not require a username and password, for example).\n\nFortunately, since database connection URLs are more or less just normal URLs, you can omit various pieces of information in the same way you might already be familiar with.  For example, here are a few common mashups, all of which are potentially valid connection URLs:\n\n+ `protocol://user:password@host:port/databaseName`\n+ `protocol://user:password@host/databaseName` _(no port)_\n+ `protocol://user@host:port/databaseName` _(no password)_\n+ `protocol://host:port/databaseName` _(neither a username nor a password)_\n\n> Connection URLs are the recommended approach for configuring your Sails app's database(s), so it's best to stick with them if possible.  But technically, _some adapters_ also support configuration of individual settings (`user`, `password`, `host`, `port`, and `database`) as an alternative.  In that scenario, if both the `url` notation and individual settings are used, the non-url configuration options should always take precedence.  You should, however, always use one approach or the other: either the `url` or the individual properties.  Mixing the two configuration strategies may confuse the adapter, or cause the underlying database driver to reject your configuration.\n\n### Production datastore configuration\n\nWhen configuring your app for a production deployment, you won't actually use the `config/datastores.js` file.  Instead, you can take advantage of `config/env/production.js`, a special file of configuration overrides that only get applied in a production environment.  This allows you to override the `url` and `adapter` (or just the `url`) that you set in `config/datastores.js`:\n\n```javascript\n// config/env/production.js\nmodule.exports = {\n  // ...\n  // Override the default datastore settings in production.\n  datastores: {\n    default: {\n      // No need to set `adapter` again, because we already configured it in `config/datastores.js`.\n      url: 'mysql://lkjdsf4a23d9xf4:kkwer4l8adsfasd@u23jrsdfsdf0sad.aasdfsdfsafd.us-west-2.ere.amazonaws.com:3306/ke9944a4x23423g',\n    }\n  },\n  // ...\n};\n```\n\nConnection URLs really shine in production, because you can change them by swapping out a single config key.  Not only does this make your production settings easier to understand, it also allows you to swap out your production database credentials simply by setting an [environment variable](https://sailsjs.com/documentation/concepts/configuration#?setting-sailsconfig-values-directly-using-environment-variables) (`sails_datastores__default__url`).  This is a handy way to avoid immortalizing sensitive database credentials as commits in your version control system.\n\n\n### Supported databases\n\nSails's ORM, [Waterline](https://sailsjs.com/documentation/concepts/models-and-orm), has a well-defined adapter system for supporting all kinds of datastores.  The Sails core team maintains official adapters for [MySQL](http://npmjs.com/package/sails-mysql), [PostgreSQL](http://npmjs.com/package/sails-postgresql), [MongoDB](http://npmjs.com/package/sails-mongo), and [local disk](http://npmjs.com/package/sails-disk); and community adapters exist for databases like Oracle, DB2, MSSQL, OrientDB, and many more.\n\nYou can find an up-to-date list of supported database adapters [here](https://sailsjs.com/documentation/concepts/extending-sails/adapters/available-adapters).\n\n> Still can't find the adapter for your database?  You can also create a [custom adapter](https://sailsjs.com/documentation/concepts/extending-sails/adapters/custom-adapters).  Or if you'd like to modify/update an existing adapter, get in touch with its maintainer.  (Need help?  Click [here](https://sailsjs.com/support) for additional resources.)\n\n\n### Multiple datastores\n\nYou can set up more than one datastore pointed at the same adapter, or at different adapters.\n\nFor example, you might be using MySQL as your primary database but also need to integrate with a _second_ MySQL database that contains data from an existing Java or PHP app.  Meanwhile, you might need to integrate with a _third_ MongoDB database that was left over from a promotional campaign a few months ago.\n\nYou could set up `config/datastores.js` as follows:\n\n```javascript\n// config/datastores.js\nmodule.exports.datastores = {\n  default: {\n    adapter: require('sails-mysql'),\n    url: 'mysql://root@localhost:3306/dev',\n  },\n  existingEcommerceDb: {\n    adapter: require('sails-mysql'),\n    url: 'mysql://djbluegrass:0ldy3ll3r@legacy.example.com:3306/store',\n  },\n  q3PromoDb: {\n    adapter: require('sails-mongo'),\n    url: 'mongodb://djbluegrass:0ldy3ll3r@seasonal-pet-sweaters-promo.example.com:27017/promotional',\n  }\n};\n\n```\n\n> **Note:** If a datastore is using a particular adapter, then _all_ datastores that share that adapter will be loaded on `sails lift`, whether or not models are actually using them.  In the example above, if a model was defined with `datastore: 'existingEcommerceDb'`, then at runtime Waterline would create two MySQL connection pools: one for `existingEcommerceDb` and one for `default`.  Because of this behavior, we recommend commenting out or removing any \"aspirational\" datastore configurations that you're not actually using from `config/datastores.js`.\n\n\n### Best practices\nSome general rules of thumb:\n\n+ To change the datastore you're using _during development_, edit the `default` key in `config/datastores.js` (or use `config/local.js` if you'd rather not check in your credentials).\n+ To configure your default _production_ datastore, use `config/env/production.js` (or set environment variables if you'd rather not check in your credentials).\n+ To override the datastore for a particular model, [set its `datastore`](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?datastore).\n+ Besides the `config/datastores.js` and `config/env/production.js` files, you can configure datastores in [the same way you configure anything else in Sails](https://sailsjs.com/documentation/concepts/configuration), including environment variables, command-line options, and more.\n\n\n\n<docmeta name=\"displayName\" value=\"sails.config.datastores\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/sails.config/sails.config.custom.md",
    "content": "# Custom configuration\n\n### What is this?\n\nThe custom configuration for your app. This is useful for one-off settings specific to your application, like the domain to use when sending emails, or third-party API keys for Stripe, Mailgun, Twitter, Facebook, etc.\n\nThese values are usually set in the [`config/custom.js`](https://sailsjs.com/documentation/anatomy/config/custom-js) file and may be overridden in production using `config/env/production.js`, environment variables, or any  of the other [configuration mechanisms](https://sailsjs.com/documentation/concepts/configuration) provided by Sails.\n\n### Example\n\nFirst, to set custom configuration:\n\n```javascript\n// config/custom.js\nmodule.exports.custom = {\n  mailgunDomain: 'transactional-mail.example.com',\n  mailgunApiKey: 'key-testkeyb183848139913858e8abd9a3'\n};\n```\n\nThen, to access these values from your actions and helpers, use `sails.config.custom`:\n\n```javascript\nsails.config.custom.mailgunApiKey;\n// -> \"key-testkeyb183848139913858e8abd9a3\"\n```\n\n\n<docmeta name=\"displayName\" value=\"sails.config.custom\">\n"
  },
  {
    "path": "docs/reference/sails.config/sails.config.globals.md",
    "content": "# `sails.config.globals`\n\n\nConfiguration for the [global variables](https://developer.mozilla.org/en-US/docs/Glossary/Global_variable) that Sails exposes by default. The globals configuration in Sails is only for controlling global variables introduced by Sails. The options are conventionally specified in the [`config/globals.js`](https://sailsjs.com/anatomy/config/globals-js) configuration file. \n\n\n\n### Properties\n\n| Property    | Type       | Convention  | Details |\n|:-----------|:----------:|:----------|:--------|\n| `_` _(underscore)_  | ((ref))<br/>_or_<br/>((boolean))     | `require('lodash')`  | Expose the specified `lodash` as a global variable (`_`).  Or set this to `false` to disable the `_` global altogether.  _(More on that below.)_\n| `async`  | ((ref))<br/>_or_<br/>((boolean)) | `require('async')` | Expose the specified `async` as a global variable (`async`).  Or set this to `false` to disable the `async` global altogether. _(More on that below.)_\n| `models` | ((boolean)) | `true` | Expose each of your app's models as a global variable (using its \"globalId\").  For example, a model defined in `api/models/User.js` would have a \"globalId\" of `User`.   If this is disabled, then you can still access all of your models by identity in the [`sails.models`](https://sailsjs.com/documentation/reference/application#?sailsmodels) dictionary.\n| `sails` | ((boolean)) | `true` | Expose the `sails` instance representing your app.  Even if this is disabled, you can still get access to it in your actions via `env.sails`, or in your policies via `req._sails`.\n| `services` | ((boolean)) | `true` | Expose each of your app's services as global variables (using their \"globalId\").  E.g. a service defined in `api/services/NaturalLanguage.js` would have a globalId of `NaturalLanguage` by default.  If this is disabled, you can still access your services via `sails.services.*`.\n\n\n### Using global Lodash (`_`) and Async libraries\n\nNewly-generated Sails 1.0 apps have Lodash v3.10.1 and Async v2.0.1 installed by default and enabled globally so that you can reference `_` and `async` in your app code without needing to `require()`.  This is effected with the following default configuration in `config/globals.js`:\n\n```\n{\n  _: require('lodash'),\n\n  async: require('async')\n}\n```\n\nYou can disable access by setting the properties to `false`. Prior to `Sails v1.0` you could set the properties to `true`; this has been deprecated and replaced by the syntax above.\n\nTo use your own version of Lodash or Async, you just need to `npm install` the version you want.  For example, to install the latest version of Lodash 4.x.x:\n\n```sh\nnpm install lodash@^4.x.x --save --save-exact\n```\n\n### Using Lodash (`_`) and Async without globals\n\nIf you have to disable globals, but would still like to use Lodash and/or Async, you're in luck!  With Node.js and NPM, importing packages is very straightforward.\n\nTo use your own version of Lodash or Async without relying on globals, first modify the relevant settings in `config/globals.js`:\n\n```js\n// Disable `_` and `async` globals.\n_: false,\nasync: false,\n```\n\nThen install your own Lodash:\n\n```sh\nnpm install lodash --save --save-exact\n```\n\nOr Async:\n\n```sh\nnpm install async --save --save-exact\n```\n\n\nFinally, just like you'd import [any other Node.js module](https://soundcloud.com/marak/marak-the-node-js-rap), include `var _ = require('lodash');` or `var async = require('async')` at the top of any file where you need them.\n\n\n\n\n### Notes\n\n> + As a shortcut to disable _all_ of the above global variables, you can set `sails.config.globals` itself to `false`.  This does the same thing as if you had manually disabled each of the settings above.\n\n\n\n\n<docmeta name=\"displayName\" value=\"sails.config.globals\">\n<docmeta name=\"pageType\" value=\"property\">\n\n"
  },
  {
    "path": "docs/reference/sails.config/sails.config.http.md",
    "content": "# `sails.config.http`\n\nConfiguration for your app's underlying HTTP server.  These properties are conventionally specified in the [`config/http.js`](https://sailsjs.com/documentation/anatomy/config/http.js) configuration file.\n\n\n### Properties\n\n  Property          | Type       | Default   | Details\n:------------------ |:----------:| --------- |:-------\n `middleware`       | ((dictionary)) | See [conventional defaults for HTTP middleware](https://sailsjs.com/documentation/concepts/Middleware?q=conventional-defaults) | A dictionary of all HTTP middleware functions your app will run on every incoming HTTP request.<br/>[Example](https://gist.github.com/mikermcneil/9cbd68c95839da480e97)\n `middleware.order` | ((array))  | See [conventional defaults for HTTP middleware order](https://github.com/balderdashy/sails/blob/master/lib/hooks/http/index.js#l51-66) | An array of middleware names (strings) indicating the order in which middleware should be run for all incoming HTTP requests.\n `cache`            | ((number)) | `31557600000` _(1 year)_ | The number of milliseconds to cache [static assets](https://sailsjs.com/documentation/concepts/assets) when your app is running in a ['production' environment](https://sailsjs.com/documentation/reference/configuration/sails-config#?sailsconfigenvironment).<br/>More specifically, this is the \"max-age\" that will be included in the \"Cache-Control\" header when responding to requests for static assets&mdash;i.e. any flat files like images, scripts, stylesheets, etc. that are served by Express' static middleware.\n `serverOptions`    | ((dictionary)) | `{}`      | _SSL only_: advanced options to send directly to the [Node `https` module](https://nodejs.org/dist/latest/docs/api/https.html) when creating the server.  These will be merged with your [SSL settings](https://sailsjs.com/documentation/reference/configuration/sails-config#?sailsconfigssl), if any.  See the [createServer docs](https://nodejs.org/dist/latest/docs/api/https.html#https_https_createserver_options_requestlistener) for more info.\n  `trustProxy`      | ((boolean)) _or_ ((function)) | `undefined`      | This tells Sails/Express how it should interpret \"X-Forwarded\" headers.  Only use this setting if you are using HTTPS _and_ if you are deploying behind a proxy (for example, a PaaS like Heroku).  If your app does not fit that description, then leave this as undefined.  Otherwise, you might start by setting this to `true`, which works for many deployments.  If that doesn't work, see [here](https://expressjs.com/en/guide/behind-proxies.html) for all available options.\n\n\n### Customizing the body parser\n\nThe _body parser_ is what Sails/Express apps use to read and understand the body of incoming HTTP requests.  Many different body parsers are available, each with their own strengths and weaknesses.  By default, Sails apps use [Skipper](http://github.com/balderdashy/skipper), a general-purpose solution that knows how to parse most kinds of HTTP request bodies and provides support for streaming, multipart file uploads.\n\n> You can specify a different body parser or a custom function with `req`, `res`, and `next` parameters (just like any other [HTTP middleware function](https://sailsjs.com/documentation/concepts/middleware).)\n\n##### Configuring Skipper\n\nTo customize Skipper, first make sure to `npm install skipper --save` in your app.  Next, uncomment the following code in your `config/http.js` file:\n\n```javascript\nbodyParser: (function _configureBodyParser(){\n  var skipper = require('skipper');\n  var middlewareFn = skipper({\n    strict: true,\n    // ... more Skipper options here ...\n  });\n  return middlewareFn;\n})(),\n```\n\nThen pass in any of the following options from the table below.\n\n  Property                               | Type        | Default   | Details\n:--------------------------------------- |:-----------:|:--------- |:-------\n `maxWaitTimeBeforePassingControlToApp`  | ((number))  | `500`     | The maximum number of milliseconds to wait when processing an incoming multipart request before passing control to your app's policies and controllers.  If this number of milliseconds elapses without any incoming file uploads, and the request hasn't finished sending other data like text parameters (i.e. the form emits \"close\"), then control will be passed without further delay.  For apps running behind particular combinations of load balancers, proxies, and/or SSL, it may be necessary to increase this delay (see https://github.com/balderdashy/skipper/issues/71#issuecomment-217556631).\n `maxTimeToWaitForFirstFile`             | ((number))  | `10000`   | The maximum number of milliseconds to wait for the first file upload to arrive in any given upstream before triggering `.upload()`'s callback.  If the first file upload on a given upstream does not arrive before this number of milliseconds have elapsed, then an `ETIMEOUT` error will fire.\n `maxTimeToBuffer`                         | ((number))  | `4500`    | The maximum number of milliseconds to wait for any given live [upstream](https://github.com/balderdashy/skipper#what-are-upstreams) to be plugged in to a receiver after it begins receiving an incoming file upload.  Skipper pauses upstreams to allow custom code in your app's policies and controller actions to run (e.g. doing database lookups) before you \"plug in\" the incoming file uploads (e.g. `req.file('avatar').upload(...)`) to your desired upload target (local disk, S3, gridfs, etc).  Incoming bytes are managed using [a combination of buffering and TCP backpressure](https://howtonode.org/streams-explained) built into Node.js streams.  The max buffer time is a configurable layer of defense to protect against denial of service attacks that attempt to flood servers with pending file uploads.  If the timeout is exceeded, an EMAXBUFFER error will fire.  The best defense against these types of attacks is to plug incoming file uploads into receivers as early as possible at the top of your controller actions.\n `strict`           | ((boolean)) | `true`    | When enabled, the body of incoming HTTP requests will only be parsed as JSON if it appears to be an array or dictionary (i.e. plain JavaScript object).  Otherwise, if _disabled_, the body parser will accept anything `JSON.parse()` accepts (including `null`, `true`, `false`, numbers, and double-quote-wrapped strings).  While these other types of data are uncommon in practice, they are technically JSON compatible; therefore, this setting is enabled by default.\n `extended`         | ((boolean)) | `true`    | Whether or not to understand multiple text parameters in square bracket notation in the URL-encoded request body (e.g. `courseId[]=ARY%20301&courseId[]=PSY%20420`) encoded  the HTTP body as an array (e.g. `courseId: ['ARY 301', 'PSY 420'], ...`).  Enabled by default.  See https://github.com/expressjs/body-parser#extended for more details.\n `onBodyParserError` | ((function)) | (see details) | An optional function to be called if Skipper encounters an error while parsing the request body (for example, if it encounters malformed JSON).  The function accepts four arguments: `err`, `req`, `res` and `next`.  Sails provides a default implementation that responds to the request with a 400 status and a message detailing the error encountered.  If no `onBodyParserError` function is provided, parser errors will be passed to `next()` and handled by the next available [error-handling middleware](https://expressjs.com/en/guide/error-handling.html).\n\n> Note that, to allow for performance tuning and other advanced configuration, the options you pass in to Skipper this way are also passed through to the underlying Express body parser.  See the [body-parser repo](https://github.com/expressjs/body-parser) for a full list of lower-level options.\n\n\n### Compatibility\n\nMost middleware compatible with [Express](https://github.com/expressjs/), [Connect](https://github.com/senchalabs/connect), [Kraken](http://krakenjs.com/), [Loopback](https://github.com/strongloop/loopback), or [Pillar](https://pillarjs.github.io/) can also be used in a Sails app.\n\n### Notes\n\n> + Note that this HTTP middleware stack configured in `sails.config.http.middleware` is only applied to true HTTP requests&mdash;it is ignored when handling virtual requests (e.g. sockets).\n> + The middleware named `router` is what handles all of your app's explicit routes (i.e. `sails.config.routes`), as well as shadow routes that are injected for blueprints, policies, etc.\n> + You cannot define a custom middleware function with the key `order` (since `sails.config.http.middleware.order` has special meaning).\n\n\n\n<docmeta name=\"displayName\" value=\"sails.config.http\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/sails.config/sails.config.i18n.md",
    "content": "# `sails.config.i18n`\n\nConfiguration for Sails' built-in internationalization and localization features.  By convention, this is set in `config/i18n.js`, from which you can set your supported locales. For more information see the [concepts section on internationalization](https://sailsjs.com/documentation/concepts/Internationalization).\n\n\n### Properties\n\n| Property           | Type        | Default               | Details |\n|:-------------------|:-----------:|:----------------------|:--------|\n| `locales`          | ((array))   | `['en','es','fr','de']` | List of supported [locale codes](http://en.wikipedia.org/wiki/BCP_47). Note that these values and the name of their corresponding translation files must be lowercase.\n| `localesDirectory` | ((string))  | `'config/locales'`     | The app-relative path to the folder containing your locale translations (i.e. stringfiles).  Alternatively, an absolute path maybe provided.\n| `defaultLocale`    | ((string))  | `'en'`                  | The default locale for the site. Note that this setting will be overridden for any request that sends an \"Accept-Language\" header (i.e. most browsers), but it's still useful if you need to localize the response for requests made by non-browser clients (e.g. mobile devices, IoT, cURL, Postman, etc.).\n\n\n\n\n<docmeta name=\"displayName\" value=\"sails.config.i18n\">\n<docmeta name=\"pageType\" value=\"property\">\n\n"
  },
  {
    "path": "docs/reference/sails.config/sails.config.log.md",
    "content": "# `sails.config.log`\n\nConfiguration for the [logger](https://sailsjs.com/documentation/concepts/logging) in your Sails app.  These settings apply whenever you call functions like `sails.log.debug()` or `sails.log.error()` in your app code, as well as when Sails logs a message to the console automatically.  The options here are conventionally specified in the [config/log.js](https://sailsjs.com/documentation/anatomy/config/log.js) configuration file.\n\n\n### Properties\n\n| Property  | Type        | Default     | Details                                                                             |\n|:----------|-------------|:------------|:------------------------------------------------------------------------------------|\n| level   | ((string))  | `'info'`    | Set the level of detail to be shown in your app's log.\n| inspect | ((boolean)) | `true`      | Set to false to disable captain's log's handling of logging, logs will instead be passed to the configured custom logger.  |\n| custom  | ((ref))     | `undefined` | Specify a reference to an instance of a custom logger (such as [Winston](https://github.com/winstonjs/winston)).  If provided, instead of logging directly to the console, the functions exposed by the custom logger will be called, and log messages from Sails will be passed through.  For more information, see [captains-log](https://github.com/balderdashy/captains-log/blob/master/README.md#why-use-a-custom-logger).\n\n### Using a custom logger\n\nIt is sometimes useful to configure a custom logger, particularly for regulatory compliance and organizational requirements (e.g. if your company is using a particular logger in other apps).  In the context of Sails, configuring a custom logger also allows you to intercept all log messages automatically created by the framework, which is handy for setting up email notifications about errors and warnings.\n\n> Don't feel like you _have_ to use a custom logger if you want these sorts of notifications!  In fact, there are usually more straightforward ways to implement features like automated Slack, SMS, or email notifications when errors occur.  One approach is to customize your app's default server error response ([`responses/serverError.js`](https://sailsjs.com/documentation/anatomy/my-app/api/responses/server-error-js)).  Another popular option is to use a product like [Papertrail](https://papertrailapp.com/), or a monitoring service like [AppDynamics](https://www.appdynamics.com/nodejs/sails/) or [NewRelic](https://discuss.newrelic.com/t/using-newrelic-with-sails-js/3338/8).\n\n\nHere's an example configuring [Winston](https://github.com/winstonjs/winston) as a custom logger, defining both a console transport and file transport.\nFirst of all, add `winston` as a dependency of your project:\n\n```bash\nnpm install winston\n```\n\nThen, replace the content of `config/log.js` with the following:\n\n```javascript\n// config/log.js\n\nconst { version } = require('../package');\n\nconst { createLogger, format, transports } = require('winston');\nconst { combine, timestamp, colorize, label, printf, align } = format;\nconst { SPLAT } = require('triple-beam');\nconst { isObject } = require('lodash');\n\nfunction formatObject(param) {\n  if (isObject(param)) {\n    return JSON.stringify(param);\n  }\n  return param;\n}\n\n// Ignore log messages if they have { private: true }\nconst all = format((info) => {\n  const splat = info[SPLAT] || [];\n  const message = formatObject(info.message);\n  const rest = splat.map(formatObject).join(' ');\n  info.message = `${message} ${rest}`;\n  return info;\n});\n\nconst customLogger = createLogger({\n  format: combine(\n    all(),\n    label({ label: version }),\n    timestamp(),\n    colorize(),\n    align(),\n    printf(info => `${info.timestamp} [${info.label}] ${info.level}: ${formatObject(info.message)}`)\n  ),\n  transports: [new transports.Console()]\n});\n\nmodule.exports.log = {\n  custom: customLogger,\n  inspect: false\n  // level: 'info'\n};\n\n```\n\n\n\n<docmeta name=\"displayName\" value=\"sails.config.log\">\n<docmeta name=\"pageType\" value=\"property\">\n\n"
  },
  {
    "path": "docs/reference/sails.config/sails.config.md",
    "content": "# Configuration (`sails.config`)\n\nThe `sails.config` object contains the runtime values of [your app's configuration](https://sailsjs.com/documentation/concepts/configuration). It is assembled automatically when Sails loads your app; merging together command-line arguments, environment variables, your `.sailsrc` file, and the configuration objects exported from any and all modules in your app's [`config/`](https://sailsjs.com/documentation/anatomy/config) directory.\n\nFor more general info about how to configure your Sails app, see the [configuration concepts guide](https://sailsjs.com/documentation/concepts/configuration).  See the other pages in this reference section for details on the configuration files that come with every new Sails app, or read about [custom configuration](https://sailsjs.com/documentation/reference/configuration/sails-config-custom).\n\n<docmeta name=\"displayName\" value=\"Configuration\">\n"
  },
  {
    "path": "docs/reference/sails.config/sails.config.models.md",
    "content": "# `sails.config.models`\n\nYour default, project-wide **model settings**, conventionally specified in the [config/models.js](https://sailsjs.com/documentation/anatomy/config/models-js) configuration file.\n\nMost of the settings below can also be overridden on a per-model basis&mdash;just edit the appropriate model definition file.  There are some additional model settings, too, which are not listed below; these can _only_ be specified on a per-model basis.  For more details, see [Concepts > Model Settings](https://sailsjs.com/documentation/concepts/orm/model-settings).\n\n### Properties\n\n\n  Property             | Type            | Default                         | Details\n :---------------------|:---------------:|:------------------------------- |:--------\n  `attributes`         | ((dictionary))  | _see [Attributes](https://sailsjs.com/documentation/concepts/models-and-orm/attributes)_ | Default [attributes](https://sailsjs.com/documentation/concepts/models-and-orm/attributes) to implicitly include in all of your app's model definitions.  (Can be overridden on an attribute-by-attribute basis.)\n `migrate`             | ((string))   | _see [Model Settings](https://sailsjs.com/documentation/concepts/orm/model-settings)_        | The [auto-migration strategy](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?migrate) for your Sails app.  How & whether Sails will attempt to automatically rebuild the tables/collections/etc. in your schema every time it lifts.\n `schema`              | ((boolean))     | `false`                      | Only relevant for models hooked up to a schemaless database like MongoDB.  If set to `true`, then the ORM will switch into \"schemaful\" mode.  For example, if properties passed in to `.create()`, `.createEach()`, or `.update()` do not correspond to recognized attributes, then they will be stripped out before saving.\n `datastore`           | ((string))   | `'default'`                     | The default [datastore configuration](https://sailsjs.com/documentation/reference/configuration/sails-config-datastores) any given model will use without a configured override.  Avoid changing this.\n `primaryKey`          | ((string))   | `'id'`             | The name of the attribute that every model in your app should use as its primary key by default.  Can be overridden here or on a per-model basis, but there's [usually a better way](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?primarykey).\n  `archiveModelIdentity` | ((string)) _or_ ((boolean))   | `'archive'`             | The identity of the model to use when calling [`.archive()`](https://sailsjs.com/documentation/reference/waterline-orm/models/archive).  By default this is the Archive model, an implicit model automatically defined by Sails/Waterline.  Set to `false` to disable built-in support for soft-deletes.\n\n<docmeta name=\"displayName\" value=\"sails.config.models\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/sails.config/sails.config.policies.md",
    "content": "# `sails.config.policies`\n<!--\n> FUTURE:\n>\n> Merge most of the contents of this file into the main reference section on policies.\n> Include a simple config reference table (with only one row with property: `*`) explaining how\n> this particular config module is read.  But don't worry about trying to explain what policies are here-- instead, link to the full docs on the subject (again, to reduce duplicate content and make this all more maintainable)\n-->\n\nThis configuration is a dictionary that maps [policies](https://sailsjs.com/documentation/concepts/policies) to an app&rsquo;s [actions](https://sailsjs.com/documentation/concepts/actions-and-controllers).  See [Concepts > Policies](https://sailsjs.com/documentation/concepts/policies#?using-policies-with-blueprint-actions) for more info.\n\n### Properties\n\n| Property    | Type       | Default  | Details |\n|:-----------|:----------:|:----------|:--------|\n| (any string)  | ((string))<br/>_or_<br/>((dictionary)) | n/a | Any properties added to `sails.config.policies` will be interpreted as a mapping of policies to a controller or a set of standalone actions.\n\n### Example\n\n```js\nmodule.exports.policies = {\n\n  '*': 'isLoggedIn', // Require user to be logged in to access any action not otherwise mapped in this config\n  'UserController': {\n    'login': true    // Always allow access to the user login action\n  }\n\n\n}\n```\n\n<docmeta name=\"displayName\" value=\"sails.config.policies\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/sails.config/sails.config.routes.md",
    "content": "# `sails.config.routes`\n\nConfiguration for custom (aka \"explicit\") routes.  `sails.config.routes` is a dictionary whose keys are URL paths (the \"route address\") and whose values are one of several types of route handler configurations (called the \"route target\").\n\nFor example:\n\n```\nmodule.exports.routes = {\n\n    'GET /': { view: 'pages/homepage' },\n    'POST /foo/bar': { action: 'foo/bar' }\n}\n```\n\nPlease see the [routes concept overview](https://sailsjs.com/documentation/concepts/routes) for a full discussion of Sails routes, and the [custom routes documentation](https://sailsjs.com/documentation/concepts/routes/custom-routes) for a detailed description of the available configurations for both the route address and route target.\n\n\n<docmeta name=\"displayName\" value=\"sails.config.routes\">\n<docmeta name=\"pageType\" value=\"property\">\n\n"
  },
  {
    "path": "docs/reference/sails.config/sails.config.security.md",
    "content": "# `sails.config.security`\n\nConfiguration for your app's security settings, including how it deals with cross-origin requests (CORS), and which routes require a CSRF token to be included with the request. For an overview of how Sails handles security, see [Concepts > Security](https://sailsjs.com/documentation/concepts/security).\n\n## `sails.config.security.cors`\nConfiguration for Sails' [built-in support for Cross-Origin Resource Sharing](https://sailsjs.com/documentation/concepts/security/cors).  CORS specifies how HTTP requests to your app originating from foreign domains should be treated.  It is primarily used to allow third-party sites to make AJAX requests to your app, which are normally blocked by browsers following the <a href=\"http://en.wikipedia.org/wiki/Same-origin_policy\" target=\"_blank\">same-origin policy</a>.\n\nThese options are conventionally set in the **config/security.js** configuration file.  Note that these settings (with the exception of `allRoutes`) can be changed on a per-route basis in the [**config/routes.js** file](https://sailsjs.com/documentation/concepts/routes/custom-routes#?route-target-options).\n\n### Properties\n\n| Property    | Type       | Default   | Details |\n|:------------|:----------:|:----------|:--------|\n| allRoutes | ((boolean))| false     | Indicates whether the other CORS configuration settings should apply to every route in the app by default.\n| allowOrigins        | ((array)) or ((string))       | `'*'`      | Array of default hosts (beginning with http:// or https://) to grant cross-domain browser access (e.g. AJAX over CORS).  Alternatively, if this is the string `*`, then AJAX requests from _any_ domain will be allowed.<br/><br/>**Warning**: If your CORS settings specify `allRoutes: true` AND `allowOrigins: '*'`, then your app will be fully accessible to sites hosted on foreign domains (except for routes which have their own CORS settings).  If `allowCredentials` is also `true`, you will _probably want to set this to an array of explicit hosts!_  If you don't, then the app will fail to lift for security reasons, unless you circumvent that precaution by enabling the `allowAnyOriginWithCredentialsUnsafe: true` flag.\n| allowRequestMethods |((string))| `'GET, POST, PUT, DELETE, OPTIONS, HEAD'` |Comma-delimited list of HTTP methods that are allowed to be used in CORS requests.  This is only used in response to [preflight requests](https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS#Preflighted_requests), so the inclusion of GET, POST, OPTIONS and HEAD, although customary, is not necessary.\n| allowRequestHeaders |((string))| `'content-type'` |Comma-delimited list of headers that are allowed to be sent with CORS requests.  This is only used in response to [preflight requests](https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS#Preflighted_requests).  _(For example, if you want cross-origin AJAX requests to be able to include their CSRF token as a request header, you might change this to  `'content-type,x-csrf-token'`.)_\n| allowResponseHeaders |((string))|`''`| List of response headers that browsers will be allowed to access.  See [access-control-expose-headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Expose-Headers).\n| allowCredentials |((boolean)) | false | Whether or not cookies can be shared in CORS requests.  _(For example, if `allowCredentials` is not enabled, then when Sails receives an AJAX request from a webpage on some other domain, it won't be able to provide `req.session` when the backend code runs.)_ |\n| allowAnyOriginWithCredentialsUnsafe |((boolean))|false| A safety precaution.  This flag must be enabled in order to use `allowOrigins: '*'` and `allowCredentials: true` _at the same time_.  This essentially negates the security benefits of browsers' cross-origin policy and should be used very carefully.\n\n### Custom route config example\n\nThe following will allow cross-origin AJAX GET, PUT and POST requests to `/foo/bar` from sites hosted `http://foobar.com` and `https://owlhoot.com`.  DELETE requests, or requests from sites on any other domains, will be blocked by the browser.\n\n```javascript\n'/foo/bar': {\n  action: 'foo/bar',\n  cors: {\n    allowOrigins: ['http://foobar.com','https://owlhoot.com'],\n    allowRequestMethods: 'GET,PUT,POST,OPTIONS,HEAD'\n  }\n}\n```\n\n## `sails.config.security.csrf`\n\nConfiguration for Sails' built-in [CSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) protection middleware.  CSRF options are conventionally set in the [`config/security.js`](https://sailsjs.com/documentation/anatomy/config/security.js) configuration file.  For detailed usage instructions, see [Concepts > Security > Cross-Site Request Forgery](https://sailsjs.com/documentation/concepts/security/csrf).\n\nThis setting protects your Sails app against cross-site request forgery (or CSRF) attacks. In addition to the user's session cookie, a would-be attacker also needs this timestamped, secret CSRF token, which is refreshed/granted when the user visits a URL on your app's domain.  This allows you to have certainty that your users' requests haven't been hijacked, and that the requests they're making are intentional and legitimate.\n\n### Properties\n\n| Property    | Type       | Default   | Details |\n|:------------|:----------:|:----------|:--------|\n| `csrf`      | ((boolean)) or ((dictionary))| false     | CSRF protection is disabled by default to facilitate development.  To turn it on, just set `sails.config.security.csrf` to `true`, or for more flexibility, specify `csrf: true` or `csrf: false` in any route in your [`config/routes.js`](https://sailsjs.com/anatomy/config/routes-js) file.\n\n\n\n### Notes\n\n> + In Sails v1.0, `sails.config.csrf.grantTokenViaAjax` and `sails.config.csrf.origin` were removed in favor of the [built-in `security/grant-csrf-token`](https://sailsjs.com/docs/concepts/security/csrf) action.\n\n\n\n<docmeta name=\"displayName\" value=\"sails.config.security\">\n<docmeta name=\"pageType\" value=\"property\">\n\n"
  },
  {
    "path": "docs/reference/sails.config/sails.config.session.md",
    "content": "# `sails.config.session`\n\nConfiguration for Sails' built-in session support.\n\nSails' default session integration leans heavily on the great work already done by Express and Connect, but also adds\na bit of its own special sauce by hooking into the request interpreter.  This allows Sails to access and auto-save any changes your code makes to `req.session` when handling a virtual request from Socket.IO.  Most importantly, it means you can just write code that uses `req.session` in the way you might be used to from Express or Connect, whether your controller actions are designed to handle HTTP requests, WebSocket messages, or both.\n\n### Properties\n\n| Property    | Type       | Default   | Details |\n|:------------|:----------:|:----------|:--------|\n| `adapter`   | ((string))    | `undefined` | If left unspecified, Sails will use the default memory store bundled in the underlying session middleware.  This is fine for development, but in production, you _must_ pass in the name of an installed scalable session store module instead (e.g. `@sailshq/connect-redis`).  See [Production config](https://sailsjs.com/documentation/reference/configuration/sails-config-session#?production-config) below for details.\n| `name`        | ((string))       | `sails.sid`      | The name of the session ID cookie to set in the response (and read from in the request) when sessions are enabled (which is the case by default for Sails apps). If you are running multiple different Sails apps from the same shared cookie namespace (i.e. the top-level DNS domain, like `frog-enthusiasts.net`), you must be especially careful to configure separate unique keys for each separate app, otherwise the wrong cookie could be used.\n| `secret` | ((string))| _n/a_     | This session secret is automatically generated when your new app is created. Care should be taken any time this secret is changed in production, as doing so will invalidate the session cookies of your users, forcing them to log in again.  Note that this is also used as the \"cookie secret\" for signed cookies.\n| `cookie` | ((dictionary)) | _see [below](https://sailsjs.com/documentation/reference/configuration/sails-config-session#?the-session-id-cookie)_ | Configuration for the session ID cookie, including `maxAge`, `secure`, and more.  See [below](https://sailsjs.com/documentation/reference/configuration/sails-config-session#?the-session-id-cookie) for more info.\n| `isSessionDisabled` | ((function)) | (see details) | A function to be run for every request which, if it returns a <a href=\"https://developer.mozilla.org/en-US/docs/Glossary/Truthy\" target=\"_blank\">&ldquo;truthy&rdquo; value</a>, will cause session support to be disabled for the request (i.e. `req.session` will not exist).  By default, this function will check the request path against the [sails.LOOKS_LIKE_ASSET_RX](https://sailsjs.com/documentation/reference/application/advanced-usage/sails-looks-like-asset-rx) regular expression, effectively disabling session support when requesting [assets](https://sailsjs.com/documentation/concepts/assets).\n\n\n\n### Advanced session config\n\nIf you are using Redis as a session store in development, additional configuration options are available. Most apps can use Sails' default Redis support as described [here](https://sailsjs.com/documentation/concepts/sessions#?using-redis-as-the-session-store), but some advanced use cases may include the following optional config:\n\n\n| Property      | Type       | Default  | Details |\n|:--------------|------------|:---------|:--------|\n| `url`          | ((string)) | `undefined` | The URL of the Redis instance to connect to.  This may include one or more of the other settings below, e.g. `redis://:mypass@myredishost.com:1234/5` would indicate a `host` of `myredishost.com`, a `port` of `1234`, a `pass` of `mypass` and a `db` of `5`.  In general, you should use either `url` or a combination of the settings below, to avoid confusion.\n| `host`         | ((string))  |`'127.0.0.1'` | Hostname of your Redis instance.  If a `url` setting is configured, this setting will be ignored.\n| `port`         | ((number)) |`6379`   | Port of your Redis instance.  If a `url` setting is configured, this setting will be ignored.\n| `pass`         | ((string)) | `undefined` | The password for your Redis instance. Leave blank if you are not using a password.  If a `url` setting is configured that includes a password, this setting will override the password in `url`.\n| `db`           | ((number))  |`undefined`   | The index of the database to use within your Redis instance.  If specified, must be an integer.  _(On typical Redis setups, this will be a number between 0 and 15.)_  If a `url` setting is configured that includes a db, this setting will override the db in `url`.\n| `client`       | ((ref))  | `undefined` | An already-connected Redis client to use.  If provided, any `url`, `host` and `port` settings will be ignored.  This setting is useful if you have a Redis Sentinel setup and need to connect using a module like <a href=\"https://www.npmjs.com/package/ioredis\" target=\"_blank\">`ioredis`</a>\n| `onRedisDisconnect` | ((function)) | `undefined` | An optional function for Sails to call if the Redis connection is dropped.  Useful for placing your site in a temporary maintenance mode or \"panic mode\" (see [sails-hook-panic-mode](https://www.npmjs.com/package/sails-hook-panic-mode) for an example).\n| `onRedisReconnect` | ((function)) | `undefined` | An optional function for Sails to call if a previously-dropped Redis connection is restored (see `onDisconnect` above).\n| `handleConstructingSessionStore` | ((function)) | `undefined` | An optional override function for Sails to call instead of the standard session store construction behavior. To use this setting, please first read and understand the [relevant source code](https://github.com/balderdashy/sails/blob/master/lib/hooks/session/index.js#L415).\n\n> Note: `onRedisDisconnect` and `onRedisReconnect` will only be called for Redis clients that are created by Sails for you; if you provide your own Redis client (see the `client` option above), these functions will _not_ be called automatically in the case of a disconnect or reconnect.\n\n\n\n##### Using other session stores\n\nAny session adapter written for Connect/Express works in Sails, as long as you use a compatible version.\n\nThe recommended production session store for Sails.js is Redis... but we realize that, for some apps, that isn't an option.  Fortunately, Sails.js supports almost any Connect/Express-compatible session store-- meaning you can store your sessions almost anywhere, whether that's Mongo, on the local filesystem, or even in a relational database.  Check out the community session stores for Sails.js, Express, and Connect [available on NPM](https://www.npmjs.com/search?q=connect%20session-).\n\n\n\n\n### The session ID cookie\n\nThe built-in session integration in Sails works by using a session ID cookie.  This cookie is [HTTP-only](https://www.owasp.org/index.php/HttpOnly) (as safeguard against [XSS exploits](https://sailsjs.com/documentation/concepts/security/xss)), and by default, is set with the name \"sails.sid\".\n\n\n##### The \"__Host-\" prefix.\n\nBy default, cookies have no integrity against same-site attackers.\n\nIn production enviroments, we recommend that you prefix the \"name\" of your cookie (`sails.config.session.name`) with \"__Host-\" to limit the scope of your cookie to a single origin.\n\nYou can read more about the \"__Host-\" prefix [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#attributes).\n\n```js\nsession: {\n  name: '__Host-sails.sid'\n}\n```\n\n> Note: Adding this prefix requires the [\"secure\" flag](#the-secure-flag) to be set to `true`.\n\n##### Expiration\n\nThe maximum age / expiration of your app's session ID cookie can be set as a number of milliseconds.\n\nFor example, to log users out after 24 hours:\n\n```js\nsession: {\n  cookie: {\n    maxAge: 24 * 60 * 60 * 1000\n  }\n}\n```\n\nOtherwise, by default, this option is set as `null`, meaning that session ID cookies will not send any kind of [\"Expires\" or \"Max Age\" header](https://en.wikipedia.org/wiki/HTTP_cookie) and will last only for as long as a user's web browser is open.\n\n\n##### The \"secure\" flag\n\nWhether to set the [\"Secure\" flag](https://www.owasp.org/index.php/SecureFlag) on the session ID cookie.\n\n```js\nsession: {\n  cookie: {\n    secure: true\n  }\n}\n```\n\nDuring development, when you are not using HTTPS, you should leave `sails.config.session.cookie.secure` as undefined (the default).\n\nBut in production, you'll want to set it to `true`.  This instructs web browsers that they should refuse to send back the session ID cookie _except_ over a secure protocol (`https://`).\n\n> **Note:** If you are using HTTPS behind a proxy/load balancer&mdash;for example, on a PaaS like Heroku&mdash;then you should still set `secure: true`.  But note that, in order for sessions to work with `secure` enabled, you will _also_ need to set another option called [`sails.config.http.trustProxy`](https://sailsjs.com/documentation/reference/configuration/sails-config-http).\n\n\n##### Do I need an SSL certificate?\n\nIn production?  Yes.\n\nIf you are relying on Sails's built-in session integration, please **always use an SSL certificate in production.**  Otherwise, the session ID cookie (or any other secure data) could be transmitted in plain-text, which would make it possible for an attacker in a coffee shop to eavesdrop on one of your authenticated user's HTTP requests, intercept their session ID cookie, then masquerade as them to wreak havoc.\n\nAlso realize that, even if you have an SSL certificate, and you always redirect `http://` to `https://`, for _all_ of your subdomains, it is still important to set `secure: true`.  (Because without it, even if you redirect all HTTP traffic immediately, that _very first request_ will  still have been made over `http://`, and thus would have transmitted the session ID cookie in plain text.)\n\n\n##### Advanced options\n\nTo see other available options (like \"[domain](https://stackoverflow.com/a/7887384/486547)\") for configuring the session ID cookie in Sails, see [express-session#cookie](https://github.com/expressjs/session/blob/v1.15.6/README.md#cookie).\n\n\n\n### Disabling sessions\n\nSessions are enabled by default in Sails.  To disable sessions in your app, disable the `session` hook by changing your `.sailsrc` file.  The process for disabling `session` is identical to the process for [disabling the Grunt hook](https://sailsjs.com/documentation/concepts/assets/disabling-grunt) (just type `session: false` instead of `grunt: false`).\n\n> **Note:**\n> If the session hook is disabled, the session secret configured as `sails.config.session.secret` will still be used to support signed cookies, if relevant.  If the session hook is disabled _AND_ no session secret configuration exists for your app (e.g. because you deleted `config/session.js`), then signed cookies will not be usable in your application.  To make more advanced changes to this behavior, you can customize any of your app's HTTP middleware manually using [`sails.config.http`](https://sailsjs.com/documentation/reference/configuration/sails-config-http).\n\n\n\n<docmeta name=\"displayName\" value=\"sails.config.session\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/sails.config/sails.config.sockets.md",
    "content": "# `sails.config.sockets`\n\n### What is this?\nThese configuration options provide transparent access to Socket.IO, the WebSocket/PubSub server encapsulated by Sails.\n\n### Commonly used options\n\n| Property      | Type       | Default  | Details |\n|:--------------|------------|:---------|:--------|\n| `adapter`      |((string))  |`'memory'`| The queue Socket.IO will use to deliver messages.  Can be set to either `'memory'` or `'@sailshq/socket.io-redis'`.  If `'@sailshq/socket.io-redis'` is specified, you should be sure `@sailshq/socket.io-redis` is amongst your app's dependencies. |\n| `transports`  |((array))  | `['websocket']`     | An array of allowed transport strategies that Sails/Socket.IO will use when connecting clients.  This should _always_ match the [configuration in your socket client (i.e. `sails.io.js`)](https://sailsjs.com/documentation/reference/web-sockets/socket-client#?configuring-the-sailsiojs-library)&mdash;if you change transports here, you need to configure them there, and vice versa.<br/><br/> <em>Note that if you opt to modify the default transports, then you may need to do additional configuration in production.  (For example, if you add the `polling` transport, and your app is running on multiple servers behind a load balancer like Nginx, then you will need to configure that load balancer to support TCP sticky sessions.  However, that _should not_ be necessary out of the box with only the `websocket` transport enabled.)  See [Deployment > Scaling](https://sailsjs.com/documentation/concepts/deployment/scaling) for more tips and best practices.</em> |\n| `onlyAllowOrigins` | ((array)) | `undefined` | Array of hosts (beginning with http:// or https://) from which sockets will be allowed to connect.  By default (i.e. while this is `undefined`) Sails/Socket.IO will allow sockets from _any_ origin to connect, which is useful for testing.  But in production mode, as of Sails v1.0, the framework forces you to configure this option to prevent [cross-site WebSocket hijacking (CSWSH) attacks](https://sailsjs.com/documentation/concepts/security/socket-hijacking).  Consequently, there's a conventional place to configure this setting in [config/env/production.js](https://sailsjs.com/documentation/anatomy/config/env/production-js), or using environment variables.  For example, if you plan on serving web pages from a local Node.js/Sails.js server running in production mode while testing, you&rsquo;ll probably want to add `http://localhost:1337` to this array.<br/><br/>Note that as the name implies (and in contrast to the similar [CORS setting](https://sailsjs.com/documentation/reference/configuration/sails-config-security-cors)), _only_ the origins listed will be allowed to connect.  Also note that this **setting is ignored** if a connecting socket doesn't declare an \"origin\" header in its upgrade request (e.g. a non-browser environment like a native iOS app, command-line script, or custom hardware).  And if you are using a pseudo-browser development platform like Electron, Ionic, React Native, or Cordova/PhoneGap, you'll need to determine what (if any) \"origin\" header your tool is attaching to initial socket connection requests.  For example, Ionic, Cordova, and PhoneGap all send `file://` as their origin.<br/><br/>Finally, note that if you want to override this behavior altogether with your own custom implementation, you can opt to use the `beforeConnect` setting instead.\n\n### Redis configuration\n\n If you are configuring your Sails app for production and plan to [scale to more than one server](https://sailsjs.com/documentation/concepts/deployment/scaling), then you should set `sails.config.sockets.adapter` to `'@sailshq/socket.io-redis'`, set up your Redis instance, and then use the following config to point at it from your app:\n\n| Property      | Type       | Default  | Details |\n|:--------------|------------|:---------|:--------|\n| `url`          | ((string)) | `undefined` | The connection URL for the Redis instance to connect to.  This may include one or more of the other settings below, e.g. `redis://:mypass@myredishost.com:1234/5` would indicate a `host` of `myredishost.com`, a `port` of `1234`, a `pass` of `mypass` and a `db` of `5`.  In general, you should use either `url` _or_ a combination of the settings below, to avoid confusion (the `url` setting will override all of the settings below).\n| `db`           | ((number))  |`undefined`   | The index of the database to use within your redis instance.  If specified, must be an integer.  _(On most Redis setups, this will be a number between 0 and 15.)_\n| `host`         |((string))  |`'127.0.0.1'` | Hostname of your Redis instance.\n| `pass`         | ((string)) | `undefined` | Password for your Redis instance.\n| `port`         |((number)) |`6379`   | Port of your Redis instance.\n\n\n### Advanced configuration\n\nThese configuration options provide lower-level access to the underlying Socket.IO server settings for complete customizability.\n\n| Property   | Type      | Default  | Details |\n|:-----------|:---------:|:---------|:--------|\n| `beforeConnect`|((boolean)), ((function)) | `undefined` | A function that runs every time a new client-side socket attempts to connect to the server, and which can be used to reject or allow the incoming connection.  Useful for tweaking your production environment to prevent [DoS](https://sailsjs.com/documentation/concepts/security/ddos) attacks or reject Socket.IO connections based on business-specific heuristics. See [beforeConnect](https://sailsjs.com/documentation/reference/configuration/sails-config-sockets#?beforeconnect) below for more info. |\n| `afterDisconnect`| ((function)) | `undefined` | A function to run when a client-side socket disconnects from the server.  To define your own custom logic, specify a function like `afterDisconnect: function (session, socket, cb) {}`.\n| `allowUpgrades` | ((boolean)) | `true` | This is a raw configuration option exposed from Engine.io.  It indicates whether to allow Socket.io clients to upgrade the transport that they are using (e.g. start with polling, then upgrade to a true WebSocket connection).  |\n| `cookie` | ((string)), ((boolean)) | `false` | This is a raw configuration option exposed from Engine.io.  It indicates the name of the HTTP cookie that contains the connecting Socket.IO client's socket id.  The cookie will be set when responding to the initial Socket.IO \"handshake\".  Alternatively, may be set to `false` to disable the cookie altogether.  Note that the `sails.io.js` client does not rely on this cookie, so it is disabled (set to `false`) by default for enhanced security.  If you are using Socket.IO directly and need to re-enable this cookie, keep in mind that the conventional setting is `\"io\"`.  |\n| `grant3rdPartyCookie` | ((boolean)) | `true` | Whether to expose a `GET /__getcookie` route that sets an HTTP-only session cookie.  By default, if it detects that it is about to connect to a cross-origin server, the Sails socket client (`sails.io.js`) sends a JSONP request to this endpoint before it begins connecting.  For user agents where 3rd party cookies are possible, this allows `sails.io.js` to connect the socket to the cross-origin Sails server using a user's existing session cookie, if they have one (for example, if they were already logged in). Without this, virtual requests you make from the socket will not be able to access the same session and will need to reauthenticate using some other mechanism.   |\n| `maxHttpBufferSize` | ((number)) | `10E7` | This is a raw configuration option exposed from Engine.io.  It reflects the maximum number of bytes or characters in a message when polling before automatically closing the socket (to avoid [DoS]((https://sailsjs.com/documentation/concepts/security/ddos)). |\n| `path`        | ((string)) | `/socket.io` | Path that client-side sockets should connect to on the server.  See http://socket.io/docs/server-api/#server(opts:object).\n| `pingInterval` | ((number)) | `25000` | This is a raw configuration option exposed from Engine.io.  It reflects the number of milliseconds to wait between \"ping packets\" (this is what \"heartbeats\" has become, more or less).  |\n| `pingTimeout` | ((number)) | `60000` | This is a raw configuration option exposed from Engine.io.  It reflects how many milliseconds without a pong packet to wait before considering a Socket.IO connection closed. |\n| `sendResponseHeaders`|((boolean))  | `true`     | Whether to include response headers in the JWR (JSON WebSocket Response) originated for each socket request (e.g. `io.socket.get()` in the browser). This doesn't affect direct Socket.IO usage, unless you're communicating with Sails via the request interpreter (e.g. making normal calls with the sails.io.js browser SDK).  This can be useful for squeezing out more performance when tuning high-traffic apps, since it reduces total bandwidth usage.  However, as of Sails v0.10, response headers are trimmed whenever possible, so this option should almost never need to be used, even in extremely high-scale applications. |\n| `serveClient`|((boolean))  | `false`     | Whether to serve the default Socket.IO client at `/socket.io/socket.io.js`.  Occasionally useful for advanced debugging. |\n| `onRedisDisconnect` | ((function)) | `undefined` | An optional function for Sails to call if the Redis connection is dropped.  Useful for placing your site in a temporary maintenance mode or \"panic mode\" (see [sails-hook-panic-mode](https://www.npmjs.com/package/sails-hook-panic-mode) for an example).\n| `onRedisReconnect` | ((function)) | `undefined` | An optional function for Sails to call if a previously-dropped Redis connection is restored (see `onDisconnect` above).\n\n> Note: `onRedisDisconnect` and `onRedisReconnect` will only be called for Redis clients that are created by Sails for you; if you provide your own Redis clients (see below), these functions will _not_ be called automatically in the case of a disconnect or reconnect.\n\n### `beforeConnect`\n\nDuring development, when a socket tries to connect, Sails allows it every time (much in the same way any HTTP request is allowed to reach your routes). Then, in production, the `onlyAllowOrigins` array ensures that only incoming socket connections that originate from the base URLs on the whitelist will be permitted to connect to your app.\n\nIf your app needs more flexibility, as an additional precaution you can define your own custom logic to allow or deny socket connections.  To do so, specify a `beforeConnect` function:\n```javascript\nbeforeConnect: function(handshake, proceed) {\n\n  // Send back `true` to allow the socket to connect.\n  // (Or send back `false` to reject the attempt.)\n  return proceed(undefined, true);\n\n},\n```\n\n> Note that if `beforeConnect` is used, then the `onlyAllowOrigins` setting will be ignored.  This allows you to accept socket connections from non-traditional clients (for example, in an [Electron app](electron.atom.io)) that may not set an `origin` header.\n\n### Sockets & sessions\n\nWhen client sockets connect to a Sails app, they authenticate using a session cookie by default (with the session hook enabled).  This allows Sails to associate the virtual requests made from the socket with an existing user session, similar to how normal HTTP requests work.\n\n> A note for browser clients: The user's session cookie is NOT (and will never be) accessible from client-side JavaScript. Using HTTP-only cookies is crucial for your app's security.\n\n##### Cross-origin sockets\nThe sails.io.js client is usually initiated from an HTML page that was already fetched via HTTP, which means that sockets connecting from this sort of browser environment will usually provide a valid session cookie automatically. As a result, everything will behave normally and `req.session` will be available.\n\nHowever, in the case of cross-origin sockets, it is possible to receive a connection upgrade request _without a cookie_ (for certain transports, anyway).  In this case, there is no way to keep track of the requesting user between virtual requests, since there is no identifying information to link them with a session. The sails.io.js client solves this by sending an HTTP request to a CORS+JSONP endpoint first, in order to get a 3rd party cookie. This cookie is then used when opening the socket connection.\n\n##### Non-browser clients\nSimilarly, if a socket connects _without_ providing a session cookie or provides a corrupted cookie, then a temporary, throwaway session entry will be created for it.  The same thing happens if the provided session cookie doesn't match any known session entry.\n\nYou can also configure sails.io.js to pass along an override for the session cookie in the form of a `?cookie` query parameter in the [url when connecting the socket](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-sails).  Sails will use this instead of the actual session cookie that may or may not have been sent in the initial connection upgrade request.  For example, if you were building a standalone Electron app, and you disabled `autoConnect` in favor of connecting a socket manually, you might do:\n\n```javascript\nvar hotSocket = io.sails.connect('http://localhost:1337?cookie=smokeybear');\n```\n\n### Providing your own Redis clients\n\nBy default, Sails will create new Redis clients in the background when using the `@sailshq/socket.io-redis` adapter.  In some cases, you may instead need to create your own Redis clients for PubSub (typically using the <a href=\"https://www.npmjs.com/package/node-redis\" target=\"_blank\">node-redis</a> or <a href=\"https://www.npmjs.com/package/ioredis\">ioredis</a> modules) and provide them to Sails for use in PubSub.  This often comes up when using a <a href=\"https://redis.io/topics/sentinel\" target=\"_blank\">Redis Sentinel</a> setup, which requires that clients connect using a module like <a href=\"https://www.npmjs.com/package/ioredis\" target=\"_blank\">ioredis</a>.  The following advanced configuration options allow you to pass already-connected Redis clients and related config info to Sails.\n\n| Property   | Type      | Default  | Details |\n|:-----------|:---------:|:---------|:--------|\n| `pubClient` | ((ref))  | `undefined` | A custom Redis client used for _publishing_ on channels used by Socket.IO.  If unspecified, Sails will create a client for you. |\n| `subClient` | ((ref)) | `undefined` | A custom Redis client used for _subscribing_ to channels used by Socket.IO.  If unspecified, Sails will create a client for you. |\n| `adminPubClient`| ((ref)) | `undefined` | A custom Redis client for _publishing_ on the internal Sails admin bus, which allows for inter-server communication.  If you provide a client for `pubClient`, you'll likely need to provide a client for this setting as well.\n| `adminSubClient`| ((ref)) | `undefined` | A custom Redis client for _subscribing_ to the internal Sails admin bus, which allows for inter-server communication.  If you provide a client for `subClient`, you'll likely need to provide a client for this setting as well.\n| `subEvent` | ((string)) | `message` | The Redis client event name to subscribe to.  When using clients created with `ioredis`, you&rsquo;ll likely need to set this to `messageBuffer`. |\n\n\n### Notes\n> + In older versions of Sails (&lt;v0.11) and Socket.IO (&lt;v1.0), the `beforeConnect` setting was called `authorization`.\n\n\n<docmeta name=\"displayName\" value=\"sails.config.sockets\">\n<docmeta name=\"pageType\" value=\"property\">\n\n"
  },
  {
    "path": "docs/reference/sails.config/sails.config.views.md",
    "content": "# `sails.config.views`\n\nConfiguration for your app's server-side [views](https://sailsjs.com/documentation/concepts/Views).  The options are conventionally specified in the [`config/views.js`](https://sailsjs.com/documentation/anatomy/config/views.js) configuration file.\n\n\n### Properties\n\n| Property    | Type       | Default   | Details |\n|:------------|:----------:|:----------|:--------|\n| `layout`    | ((string)) -or- ((boolean))     | `\"layout\"`  | Set the default [layout](https://sailsjs.com/documentation/concepts/views/layouts) for your app by specifying the relative path to the desired layout file from your views folder (i.e. `views/`), or disable layout support altogether with `false`.  Built-in support for layouts is only relevant when using `ejs` (see below).\n| `extension` | ((string)) | \"ejs\" | The file extension for view files. |\n| `getRenderFn` | ((function)) | none | A function that Sails will call to get the rendering function for your desired view engine.  See the [view engine documentation](http://sailsjs.com/documentation/concepts/views/view-engines) for more info about specifying a `getRenderFn` value.  If this setting is undefined, Sails will use the built-in EJS renderer.\n| `locals`    | ((dictionary)) | `{}` | Default data to be included as [view locals](http://sailsjs.com/documentation/concepts/views/locals) every time a server-side view is compiled anywhere in this app.  If an optional `locals` argument was passed in directly via `res.view()`, its properties take precedence when both dictionaries are merged and provided to the view (more on that below). |\n\n### Notes\n\n> + If your app is NOT using `ejs` (the default view engine) Sails will function as if the `layout` option was set to `false`.  To take advantage of layouts when using a custom view engine like Jade or Handlebars, check out [that view engine's documentation](https://sailsjs.com/documentation/concepts/views/view-engines) to find the appropriate syntax.\n> + As of Sails 0.12.0, app-wide locals from `sails.config.views.locals` are combined with any one-off locals you use with `res.view()` using a **shallow merge strategy**.  That is, if your app-wide locals configuration is `{foo: 3, bar: { baz: 'beep' } }`, and then you use `res.view({bar: 'boop'})`, your view will have access to `foo` (`3`) and `bar` (`'boop'`).\n\n\n\n\n<docmeta name=\"displayName\" value=\"sails.config.views\">\n<docmeta name=\"pageType\" value=\"property\">\n\n\n"
  },
  {
    "path": "docs/reference/waterline/datastores/datastores.md",
    "content": "# Working with datastores\n\n**Datastores** represent the data sources configured for your app.  A datastore usually represents a particular database, whether that's a database running within a locally installed MySQL server, a remote PostgreSQL database running in your company's data center, or a remote MongoDB database hosted by a cloud provider.\n\n### Configuring datastores\n\nDatastores are configured in [`sails.config.datastores`](https://sailsjs.com/documentation/reference/configuration/sails-config-datastores).\n\nSails apps start out with an implicit datastore which is used by all of your models by default.  For many apps, this is sufficient, but if you are building an app that needs to work with multiple databases, you may also find it helpful to configure additional, named datastores like `legacyProductDb`.\n\n### Using datastores without a model\n\nEvery [model](https://sailsjs.com/documentation/concepts/models-and-orm/models) in a Sails app is wired up to a particular datastore, so every time you call a built-in model method, the model communicates with its configured datastore implicitly.\n\nEven so, it's sometimes useful to be able to communicate with a datastore _outside_ of the context of any particular model.  So, when your app lifts, Sails automatically instantiates objects called _registered datastore instances_ for each of your configured datastores.  To access one of these at runtime, call either [`sails.getDatastore()`](https://sailsjs.com/documentation/reference/application/sails-get-datastore) or the [`.getDatastore()` model method](https://sailsjs.com/documentation/reference/waterline-orm/models/get-datastore).  \n\nRegistered datastores expose some methods and properties of their own, like `.leaseConnection()` and `.manager`, which provide an easy way to talk directly to the underlying database.  (The rest of the pages in this section of the documentation are devoted to covering these datastore methods and properties in detail.)\n\n\n<docmeta name=\"displayName\" value=\"Datastores\">\n"
  },
  {
    "path": "docs/reference/waterline/datastores/driver.md",
    "content": "# `.driver`\n\nThe generic, stateless, low-level driver for this datastore (if supported by the adapter).\n\n```usage\ndatastore.driver;\n```\n\n> This property is not guaranteed to exist for all database adapters.  If the datastore's underlying adapter does not support the [standardized driver interface](https://github.com/node-machine/driver-interface), then `driver` will not exist.\n\n\n### Example\n\nImagine you're building your own structured data visualizer (e.g. phpMyAdmin).  You might want to connect to any number of different databases dynamically.\n\n```javascript\n// Get the generic, stateless driver for our database (e.g. MySQL).\nvar Driver = sails.getDatastore().driver;\n\n// Create our own dynamic connection manager (e.g. connection pool)\nvar manager = (\n  await Driver.createManager({ connectionString: req.param('connectionUrl') })\n).manager;\n\nvar db;\ntry {\n  db = (\n    await Driver.getConnection({ manager: managerReport.manager })\n  ).connection;\n} catch (err) {\n  await Driver.destroyManager({ manager: managerReport.manager });\n  throw err;\n}\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n// Do some stuff here...\n// e.g.\n//     await Driver.sendNativeQuery({\n//       connection: db,\n//       nativeQuery: '...'\n//     });\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n// Finally, before we continue, tear down the dynamic connection manager.\n// (this also takes care of releasing the active connection we acquired above)\nawait Driver.destroyManager({ manager: managerReport.manager });\n\nreturn res.ok();\n```\n\n<docmeta name=\"displayName\" value=\".driver\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/waterline/datastores/leaseConnection.md",
    "content": "# `.leaseConnection()`\n\nLease a new connection from the datastore for use in running multiple queries on the same connection (i.e. so that the logic provided in `during` can reuse the db connection).\n\n\n```usage\nawait datastore.leaseConnection(during);\n```\n\n_Or_\n\n+ `var result = await datastore.leaseConnection(during);`\n\n\n### Usage\n|   |     Argument        | Type                | Details\n|---|---------------------|---------------------|:------------|\n| 1 | during              | ((function))        | A [procedural parameter](https://en.wikipedia.org/wiki/Procedural_parameter) that Sails will call automatically when a connection has been obtained and made ready for you.  It will receive the arguments specified in the \"During\" usage table below. |\n\n##### During\n|   |     Argument        | Type                | Details\n|---|---------------------|---------------------|:------------|\n| 1 | db                  | ((ref))             | Your newly-leased database connection.  (See [`.usingConnection()`](https://sailsjs.com/documentation/reference/waterline-orm/models/using-connection) for more information on what to do with this.) |\n\n> Note that prior to Sails 1.1.0, the recommended usage of `.leaseConnection()` expected your \"during\" code to call a callback (`proceed`) when it finished.  This is no longer necessary as long as you do not actually include a second argument in the function signature of your \"during\" code.\n\n##### Result\n\n| Type                | Details |\n|---------------------|:---------------------------------------------------------------------------------|\n| ((Ref?))            | The optional result data sent back from `during`.  In other words, if, in your `during` function, you did `return 'foo';`, then this will be `'foo'`. |\n\n##### Errors\n\n|     Name        | Type                | When? |\n|:----------------|---------------------|:---------------------------------------------------------------------------------|\n| UsageError      | ((Error))           | Thrown if something invalid was passed in.\n| AdapterError    | ((Error))           | Thrown if something went wrong in the database adapter.\n| Error           | ((Error))           | Thrown if anything else unexpected happens.\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n### Example\n\nLease a database connection from the default datastore, then use it to send two queries before releasing it back to the pool.\n\n```javascript\nvar inventory = await sails.getDatastore()\n.leaseConnection(async (db)=> {\n  var location = await Location.findOne({ id: inputs.locationId })\n  .usingConnection(db);\n  if (!location) {\n    let err = new Error('Cannot find location with that id (`'+inputs.locationId+'`)');\n    err.code = 'E_NO_SUCH_LOCATION';\n    throw err;\n  }\n\n  // Get all products at the location\n  var productOfferings = await ProductOffering.find({ location: inputs.locationId })\n  .populate('productType')\n  .usingConnection(db);\n\n  return productOfferings;\n})\n.intercept('E_NO_SUCH_LOCATION', 'notFound');\n\n// All done!  Whatever we were doing with that database connection worked.\n// Now we can proceed with our business.\n```\n\n\n<docmeta name=\"displayName\" value=\".leaseConnection()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/datastores/manager.md",
    "content": "# `.manager`\n\nThe live connection manager for this datastore.\n\n```usage\ndatastore.manager\n```\n\n>  Depending on the adapter, this might represent a connection pool, a single connection, or just a reference to a preconfigured client library instance.\n\n### Example\nAccess a raw Mongo collection instance representing a model `Pet`.\n```javascript\n// Since the db connection manager exposed by `sails-mongo` is actually\n// the same as the Mongo client's `db` instance, we can treat it as such.\nvar db = Pet.getDatastore().manager;\n\n// Now we can do anything we could do with a Mongo `db` instance:\nvar rawMongoCollection = db.collection(Pet.tableName);\n```\n\n<docmeta name=\"displayName\" value=\".manager\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/waterline/datastores/sendNativeQuery.md",
    "content": "# `.sendNativeQuery()`\n\nExecute a raw SQL query using this datastore.\n\n```usage\nvar rawResult = await datastore.sendNativeQuery(sql, valuesToEscape);\n```\n\n> `.sendNativeQuery()` is only available on Sails/Waterline [datastores](https://sailsjs.com/documentation/reference/waterline-orm/datastores) that are configured to use a SQL database (e.g. MySQL, SQL Server, or PostgreSQL). Note that exact SQL and result format varies between databases, so you'll need to refer to the documentation for your underlying database adapter. (See below for a simple example to help get you started.)\n\n### Usage\n|   |     Argument        | Type                | Details\n|---|---------------------|---------------------|:------------|\n| 1 | sql                 | ((string))          | A SQL string written in the appropriate dialect for this database.  Allows template syntax like `$1`, `$2`, etc. (See example below.)  If you are using custom table names or column names, be sure to reference those directly (rather than model identities and attribute names).  |\n| 2 | valuesToEscape     | ((array?))           | An array of dynamic, untrusted strings to SQL-escape and inject within `sql`.  _(If you have no dynamic values to inject, then just omit this argument or pass in an empty array here.)_\n\n##### Result\n\n| Type                | Details |\n|:--------------------|:---------------------------------------------------------------------------------|\n| ((Ref?))            | The raw result from the database adapter, if any. _(The exact format of this raw result data varies depending on the SQL query you passed in, as well as the adapter/dialect you're using. See example below for links to relevant documentation.)_ |\n\n##### Errors\n\n|     Name        | Type                | When? |\n|:----------------|---------------------|:---------------------------------------------------------------------------------|\n| UsageError      | ((Error))           | Thrown if something invalid was passed in.\n| AdapterError    | ((Error))           | Thrown if something went wrong in the database adapter.\n| Error           | ((Error))           | Thrown if anything else unexpected happens.\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n### Example\n\n> Below, you'll find a generic example that works with just about any relational database.  **But remember**: usage and result data vary depending on the SQL query you send, as well as on the adapter/dialect you're using.  The standard [MySQL adapter](https://sailsjs.com/documentation/concepts/extending-sails/adapters/available-adapters#?sailsmysql) for Sails and Waterline uses the [`mysql`](http://npmjs.com/package/mysql) NPM package.  The [PostgreSQL adapter](https://sailsjs.com/documentation/concepts/extending-sails/adapters/available-adapters#?sailspostgresql) uses [`pg`](http://npmjs.com/package/pg).\n\n\n```js\n// Build our SQL query template.\nvar NAMES_OF_PETS_SQL = `\nSELECT pet.name\nFROM pet\nWHERE pet.species_label = $1 OR pet.species_label = $2`;\n\n// Send it to the database.\nvar rawResult = await sails.sendNativeQuery(NAMES_OF_PETS_SQL, [ 'dog', 'cat' ]);\n\nsails.log(rawResult);\n// (result format depends on the SQL query that was passed in, and the adapter/dialect you're using)\n\n// Then parse the raw result and do whatever you like with it.\n\nreturn exits.success();\n```\n\n\n### Custom table/column names\n\nThe SQL query you write should refer to table names and column names, not model identities and attribute names.  If your models are defined with custom table names, or if their attributes are defined with custom column names, you'll want to be sure you're using those custom names in your native SQL queries.\n\nAre you using custom table/column names and concerned about scattering them throughout your code, because they might change?  Fortunately, there's a way to work around this.  By using the underlying references to `tableName` and `columnName` available on your Waterline model, you can build your SQL query templates without directly referencing column name and table names.\n\nFor example:\n\n```js\nvar NAMES_OF_PETS_SQL = `\nSELECT ${Pet.tableName}.${Pet.schema.name.columnName}\nFROM ${Pet.tableName}\nWHERE\n  ${Pet.tableName}.${Pet.schema.speciesLabel.columnName} = $1\n  OR\n  ${Pet.tableName}.${Pet.schema.speciesLabel.columnName} = $2\n`;\n```\n\nBe aware that you still have to deal with custom column names on the way out!  The `rawResult` you get back from `.sendNativeQuery()` is inherently database-specific and tied to the physical layer, thus it will inherit any complexity you've set up there (including custom table/column names from your model definitions).\n\n\n### Notes\n> + This method only works with SQL databases.  If you are using another database like MongoDB, use [`.manager`](https://sailsjs.com/documentation/reference/waterline-orm/datastores/manager) to get access to the raw MongoDB client, or [`.driver`](https://sailsjs.com/documentation/reference/waterline-orm/datastores/driver) to get access to the static, underlying db library (e.g. `mysql`, `pg`, etc.).\n> + Depending on the adapter you are using, the `valuesToEscape` may be mutated. This was a deliberate decision that was made for performance reasons, but may change in a future major version of Sails. For now if you are passing in a variable for `valuesToEscape` and you're using that variable later on in your code, clone it first.\n\n<docmeta name=\"displayName\" value=\".sendNativeQuery()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/datastores/transaction.md",
    "content": "# `.transaction()`\n\nFetch a preconfigured, deferred object hooked up to the sails-mysql or sails-postgresql adapter (and consequently the appropriate driver).\n\n```usage\nawait datastore.transaction(during);\n```\n\nor\n\n+ `var result = await datastore.transaction(during);`\n\n### Usage\n|   |     Argument        | Type                | Details\n|---|---------------------|---------------------|:------------|\n| 1 | during              | ((function))        | See parameters in the \"`during` usage\" table below. |\n\n##### During\n|   |     Argument        | Type                | Details\n|---|---------------------|---------------------|:------------|\n| 1 | db                  | ((ref))             | The leased (transactional) database connection. (See [`.usingConnection()`](https://sailsjs.com/documentation/reference/waterline-orm/queries/using-connection) for more information on what to do with this.) |\n\n> Note that prior to Sails 1.1.0, the recommended usage of `.transaction()` expected your \"during\" code to call a callback (`proceed`) when it finished.  This is no longer necessary as long as you do not actually include a second argument in the function signature of your \"during\" code.\n\n##### Result\n| Type                | Details |\n|---------------------|:---------------------------------------------------------------------------------|\n|  ((Ref?))            | The optional result data sent back from `during`.  In other words, if in your `during` function you did `return 'foo';`, then this will be `'foo'`. |\n\n##### Errors\n\n|     Name        | Type                | When? |\n|:----------------|---------------------|:---------------------------------------------------------------------------------|\n| UsageError      | ((Error))           | Thrown if something invalid was passed in.\n| AdapterError    | ((Error))           | Thrown if something went wrong in the database adapter.\n| Error           | ((Error))           | Thrown if anything else unexpected happens.\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n\n### Example\n\nSubtract the specified amount from one user's balance and add it to another's.\n\n```javascript\n// e.g. in an action:\n\nvar flaverr = require('flaverr');\n\nawait sails.getDatastore()\n.transaction(async (db)=> {\n\n  var myAccount = await BankAccount.findOne({ owner: this.req.session.userId })\n  .usingConnection(db);\n  if (!myAccount) {\n    throw new Error('Consistency violation: Database is corrupted-- logged in user record has gone missing');\n  }\n\n  var recipientAccount = await BankAccount.findOne({ owner: inputs.recipientId }).usingConnection(db)\n  if (!recipientAccount) {\n    throw flaverr('E_NO_SUCH_RECIPIENT', new Error('There is no recipient with that id'));\n  }\n\n  // Do the math to subtract from the logged-in user's account balance,\n  // and add to the recipient's bank account balance.\n  var myNewBalance = myAccount.balance - inputs.amount;\n\n  // If this would put the logged-in user's account balance below zero,\n  // then abort.  (The transaction will be rolled back automatically.)\n  if (myNewBalance < 0) {\n    throw flaverr('E_INSUFFICIENT_FUNDS', new Error('Insufficient funds'));\n  }\n\n  // Update the current user's bank account\n  await BankAccount.update({ owner: this.req.session.userId })\n  .set({\n    balance: myNewBalance\n  })\n  .usingConnection(db);\n\n  // Update the recipient's bank account\n  await BankAccount.update({ owner: inputs.recipientId })\n  .set({\n    balance: recipientAccount.balance + inputs.amount\n  })\n  .usingConnection(db);\n})\n.intercept('E_INSUFFICIENT_FUNDS', ()=>'badRequest')\n.intercept('E_NO_SUCH_RECIPIENT', ()=>'notFound');\n```\n\n> Note that the example above is just a demonstration; in practice, this kind of increment/decrement logic should also include row-level locking.  [Unsure?](https://sailsjs.com/support).\n\n<docmeta name=\"displayName\" value=\".transaction()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/models/addToCollection.md",
    "content": "# `.addToCollection()`\n\nAdd one or more existing child records to the specified collection (e.g. the `comments` of BlogPost #4).\n\n```usage\nawait Something.addToCollection(parentId, association)\n.members(childIds);\n```\n\n### Usage\n\n|   |     Argument        | Type                                         | Details                            |\n|---|:--------------------|----------------------------------------------|:-----------------------------------|\n| 1 |  parentId           | ((number)) or ((string))                     | The primary key value(s) (i.e. ids) for the parent record(s). <br/>Must be a number or string (e.g. `'507f191e810c19729de860ea'` or `49`).  <br/>Alternatively, an array of numbers or strings may be specified (e.g. `['507f191e810c19729de860ea', '14832ace0c179de897']` or `[49, 32, 37]`).  In this case, _all_ of the child records will be added to the appropriate collection of each parent record.\n| 2 |  association        | ((string))                                   | The name of the plural (\"collection\") association (e.g. \"pets\").\n| 3 |  childIds           | ((array))                                    | The primary key values (i.e. ids) of the child records to add. _Note that this does not [create](https://sailsjs.com/documentation/reference/waterline-orm/models/create) these child records, it just links them to the specified parent(s)._\n\n\n##### Errors\n\n|     Name        | Type                | When? |\n|:----------------|---------------------|:---------------------------------------------------------------------------------|\n| UsageError      | ((Error))           | Thrown if something invalid was passed in.\n| AdapterError    | ((Error))           | Thrown if something went wrong in the database adapter.\n| Error           | ((Error))           | Thrown if anything else unexpected happens.\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n\n### Example\n\nFor user 3, add pets 99 and 98 to the \"pets\" collection:\n\n```javascript\nawait User.addToCollection(3, 'pets')\n.members([99,98]);\n```\n\n> If either user record already has one of those pets in its \"pets\", then we just silently skip over it.\n\n\n### Edge cases\n\n+ If an empty array of child ids is provided, then this is a <a href=\"https://en.wikipedia.org/wiki/NOP_(code)\" target=\"_blank\">no-op</a>.\n+ If an empty array of parent ids is provided, then this is a <a href=\"https://en.wikipedia.org/wiki/NOP_(code)\" target=\"_blank\">no-op</a>.\n\n+ If the parent id (or any _one_ of the parent ids, if specified as an array) does not actually correspond with an existing, persisted record, the exact behavior depends on what kind of association this is:\n  + If this collection is a 1-way association, or a 2-way association where the other side is plural ([many-to-many](https://sailsjs.com/documentation/concepts/models-and-orm/associations/many-to-many)), then Waterline **pretends like the parent record(s) exist anyways**, tracking their relationships as prearranged, \"aspirational\" junction records in the database.\n  + If this is a 2-way association where the other side is singular ([one-to-many](https://sailsjs.com/documentation/concepts/models-and-orm/associations/one-to-many)), then the missing parent records are simply ignored.\n\n+ Along the same lines, if one of the child ids does not actually correspond with an existing, persisted record, then:\n  + If this is a 1-way association, or a 2-way association where the other side is plural ([many-to-many](https://sailsjs.com/documentation/concepts/models-and-orm/associations/many-to-many)), then Waterline **pretends like these hypothetical child record(s) exist anyways**, tracking their relationships as prearranged, \"aspirational\" junction records in the database.\n  + If this is a 2-way association where the other side is singular ([one-to-many](https://sailsjs.com/documentation/concepts/models-and-orm/associations/one-to-many)), then the missing child records are simply ignored.\n\n+ If a parent record's collection _already has_ one or more of these children as members, then, for performance reasons, those memberships might be tracked again (e.g. stored in your database's join table multiple times).  In most cases, that's OK, since it usually doesn't affect future queries (for example, when populating the relevant parent record's collection, the double-tracked relationship will not result in the child being listed more than once).  If you do need to prevent duplicate join table records, there's an easy way to work around this&mdash;assuming you are using a relational database like MySQL or PostgreSQL, then you can create a multi-column index on your join table.  Doing so will cause queries like this to result in an AdapterError with `code: 'E_UNIQUE'`.\n\n### Notes\n> + This method can be used with [`await`](https://github.com/mikermcneil/parley/tree/49c06ee9ed32d9c55c24e8a0e767666a6b60b7e8#usage), promise chaining, or [traditional Node callbacks](https://sailsjs.com/documentation/reference/waterline-orm/queries/exec).\n\n> + If the association is \"2-way\" (meaning it has `via`) then the child records will be modified accordingly.  If the attribute on the other side is singular, then each child record's foreign key will be changed.  If it's plural, then each child record's collection will be modified accordingly.\n\n> + In addition, if the `via` points at a singular (\"model\") attribute on the other side, then `.addToCollection()` will \"steal\" these child records if necessary.  For example, imagine you have an Employee model with this plural (\"collection\") attribute: `involvedInPurchases: { collection: 'Purchase', via: 'cashier' }`.  If you executed `Employee.addToCollection(7, 'involvedInPurchases', [47])` to assign this purchase to employee #7 (Dolly), but purchase #47 was already associated with a different employee (e.g. #12, Motoki), then this would \"steal\" the purchase from Motoki and give it to Dolly.  In other words, if you executed `Employee.find([7, 12]).populate('involvedInPurchases')`, Dolly's `involvedInPurchases` array would contain purchase #47 and Motoki's would not.\n\n<docmeta name=\"displayName\" value=\".addToCollection()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/models/archive.md",
    "content": "# `.archive()`\n\nArchive (\"soft-delete\") records that match the specified criteria, saving them as new records in the built-in Archive model, then destroying the originals.\n\n```usage\nawait Something.archive(criteria);\n```\n\n#### Usage\n\n|   |     Argument        | Type                                         | Details                            |\n|---|:--------------------|----------------------------------------------|:-----------------------------------|\n| 1 |    criteria         | ((dictionary))                               | Records that match this [Waterline criteria](https://github.com/balderdashy/waterline-docs/blob/master/queries/query-language.md) will be archived.  Be warned, if you specify an empty dictionary (`{}`) as your criteria, _all records will be destroyed!_ |\n\n##### Callback\n\n|   |     Argument        | Type                | Details |\n|---|:--------------------|---------------------|:-----------------------------------------------------------------------------|\n| 1 |    err              | ((Error?))          | The error that occurred, or `null` if there were no errors.\n| 2 |  _archivedRecords_  | ((array?)) of ((dictionary))  |  For improved performance, the archived records are not provided to this callback by default.  But if you chain `.fetch()`, then the recently archived records will be sent back. (Be aware that this requires an extra database query in some adapters.)\n\n\n### Example\n\nTo archive a particular user in the the database, use [`.archiveOne()`](https://sailsjs.com/documentation/reference/waterline/archive-one).\n\nOr to archive multiple records in the the database:\n\n```javascript\nawait Pet.archive({ lastActiveAt: { '<': Date.now()-1000*60*60*24*365 } });\n```\n\n### Accessing archived records\nIf you need to access archived records in the future, you can do so by searching the Archive model.  For example, you might pass in the original record's primary key and [model identity](https://sailsjs.com/documentation/reference/waterline-orm/models#?sailsmodels) as constraints in a query.\n\nFor example, to retrieve the archive describing the user we got rid of above:\n\n```javascript\nvar archive = await Archive.findOne({\n  fromModel: 'user',\n  originalRecordId: 1\n});\n\n// The data from the original record is stored as `archive.originalRecord`.\n```\n\n### Notes\n> This method is best used in situations where you would otherwise use [`.destroy()`](https://sailsjs.com/documentation/reference/waterline-orm/models/destroy), but you still need to keep the deleted data somewhere (e.g. for compliance reasons).  If you anticipate needing to access the data again in your app (e.g. if you allow un-deleting), you may want to consider using an `isDeleted` flag instead, since archived records are more difficult to work with programmatically.  (There is no built-in \"unarchive\".)\n\n\n<docmeta name=\"displayName\" value=\".archive()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/models/archiveOne.md",
    "content": "# `.archiveOne()`\n\nArchive (\"soft-delete\") the record that matches the specified criteria, saving it (if it exists) as a new record in the built-in Archive model, then destroying the original.\n\n```usage\nvar originalRecord = await Something.archiveOne(criteria);\n```\n\n> Before attempting to modify the database, Waterline will check to see if the given criteria would match more than one record and, if so, it will throw an error instead of proceeding.\n\n\n### Usage\n\n|   |     Argument        | Type              | Details                            |\n|---|:--------------------|-------------------|:-----------------------------------|\n| 1 | criteria            | ((dictionary))    | The [Waterline criteria](https://sailsjs.com/documentation/concepts/models-and-orm/query-language) to use for matching the record in the database.\n\n##### Result\n\n| Type                | Description      |\n|:--------------------|:-----------------|\n| ((dictionary?))     | Since this method never archives more than one record, if a record is archived then it is always provided as a result.  Otherwise, this returns `undefined`.\n\n\n##### Errors\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n\n### Example\n\n```javascript\nvar finn = await User.archiveOne({ firstName: 'Finn' });\nif (finn) {\n  sails.log('Archived the user named \"Finn\".');\n} else {\n  sails.log('The database does not have a user named \"Finn\".');\n}\n```\n\n\n### Notes\n> This method is best used in situations where you would otherwise use [`.destroyOne()`](https://sailsjs.com/documentation/reference/waterline-orm/models/destroy-one), but you still need to keep the deleted data somewhere (for compliance reasons, maybe).  If you anticipate needing to access the data again in your app (if you allow un-deleting, for example), you may want to consider using an `isDeleted` flag instead, since archived records are more difficult to work with programmatically.  (There is no built-in \"unarchive\".)\n> + This method **does not support .fetch()**, because it _always_ returns the archived record, if one was matched.\n\n\n<docmeta name=\"displayName\" value=\".archiveOne()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/models/avg.md",
    "content": "# `.avg()`\n\nGet the aggregate mean of the specified attribute across all matching records.\n\n```usage\nvar average = await Something.avg(numericAttrName, criteria);\n```\n\n### Usage\n\n|   |     Argument        | Type                                         | Details                            |\n|---|:--------------------|----------------------------------------------|:-----------------------------------|\n| 1 |  numericAttrName    | ((string))                                   | The name of the numeric attribute whose mean will be calculated.\n| 2 |  _criteria_         | ((dictionary?))                                | The [Waterline criteria](https://sailsjs.com/documentation/concepts/models-and-orm/query-language) to use for matching records in the database. If no criteria is specified, the average will be computed across _all_ of this model's records. `avg` queries do not support pagination using `skip` and `limit` or projections using `select`.\n\n\n##### Result\n\n| Type                | Description      |\n|---------------------|:-----------------|\n| ((number))          | The aggregate mean of the specified attribute across all matching records.\n\n\n##### Errors\n\n|     Name        | Type                | When? |\n|:----------------|---------------------|:---------------------------------------------------------------------------------|\n| UsageError       | ((Error))          | Thrown if something invalid was passed in.\n| AdapterError     | ((Error))          | Thrown if something went wrong in the database adapter.\n| Error            | ((Error))          | Thrown if anything else unexpected happens.\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n\n### Example\n\nGet the average balance of bank accounts owned by people between the ages of 35 and 45.\n\n```javascript\nvar averageBalance = await BankAccount.avg('balance')\n.where({\n  ownerAge: { '>=': 35, '<=': 45 }\n});\n```\n\n### Notes\n> + This method can be used with [`await`](https://github.com/mikermcneil/parley/tree/49c06ee9ed32d9c55c24e8a0e767666a6b60b7e8#usage), promise chaining, or [traditional Node callbacks](https://sailsjs.com/documentation/reference/waterline-orm/queries/exec).\n> + Some databases like MySQL may return `null` for this kind of query, however it's best practice for Sails/Waterline adapter authors to return `0` for consistency and type safety in app-level code.\n\n<docmeta name=\"displayName\" value=\".avg()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/models/count.md",
    "content": "# `.count()`\n\nGet the total number of records matching the specified criteria.\n\n```usage\nvar numRecords = await Model.count(criteria);\n```\n\n### Usage\n\n| # | Argument      | Type                  | Details    |\n|---|---------------|:----------------------|:-----------|\n| 1 | _criteria_    | ((dictionary?))       | The [Waterline criteria](https://sailsjs.com/documentation/concepts/models-and-orm/query-language) to use for matching records in the database.  Note that `count` queries do not support pagination using `skip` and `limit` or projections using `select`.\n\n\n##### Result\n\n| Type                | Description      |\n|---------------------|:-----------------|\n| ((number))          | The number of records from your database that match the given criteria.\n\n\n##### Errors\n\n| Name                | Type                | When?                                                        |\n|:--------------------|---------------------|:-------------------------------------------------------------|\n| UsageError          | ((Error))           | Thrown if something invalid was passed in.\n| AdapterError        | ((Error))           | Thrown if something went wrong in the database adapter.\n| Error               | ((Error))           | Thrown if anything else unexpected happens.\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n### Example\n\n```javascript\nvar total = await User.count({name:'Flynn'});\nsails.log(`There ${total===1?'is':'are'} ${total} user${total===1?'':'s'} named \"Flynn\".`);\n```\n\n### Notes\n> + This method can be used with [`await`](https://github.com/mikermcneil/parley/tree/49c06ee9ed32d9c55c24e8a0e767666a6b60b7e8#usage), promise chaining, or [traditional Node callbacks](https://sailsjs.com/documentation/reference/waterline-orm/queries/exec).\n\n\n<docmeta name=\"displayName\" value=\".count()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/models/create.md",
    "content": "# `.create()`\n\nCreate a record in the database.\n\n```usage\nawait Something.create(initialValues);\n```\n\nor\n\n+ `var createdRecord = await Something.create(initialValues).fetch();`\n\n### Usage\n\n|   | Argument            | Type                         | Details                               |\n|---|:--------------------|------------------------------|:--------------------------------------|\n| 1 | initialValues       | ((dictionary))               | The initial values for the new record.  _(Note that, if this model is in [\"schemaful\" mode](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?schema), then any extraneous keys will be silently omitted.)_\n\n> **Note**: For performance reasons, as of Sails v1.0 / Waterline 0.13, the `initialValues` dictionary passed into this model method will be mutated in-place in most situations (whereas in Sails/Waterline v0.12, this was not necessarily the case).\n\n##### Result\n\n| Type                | Description      |\n|---------------------|:-----------------|\n| ((dictionary?))     | For improved performance, the created record is not provided as a result by default.  But if you chain `.fetch()`, then the newly-created record will be sent back. (Be aware that this requires an extra database query in some adapters.)\n\n##### Errors\n\n|     Name        | Type                | When? |\n|--------------------|---------------------|:---------------------------------------------------------------------------------|\n| UsageError            | ((Error))           | Thrown if something invalid was passed in.\n| AdapterError     | ((Error))           | Thrown if something went wrong in the database adapter. See [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for an example of how to negotiate a uniqueness error (i.e. from attempting to create a record with a duplicate that would violate a uniqueness constraint).\n| Error             | ((Error))           | Thrown if anything else unexpected happens.\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n\n##### Meta keys\n\n| Key                 | Type              | Details                                                        |\n|:--------------------|-------------------|:---------------------------------------------------------------|\n| fetch               | ((boolean))       | If set to `true`, then the created record will be sent back.<br/><br/>Defaults to `false`.\n\n> For more information on meta keys, see [.meta()](https://sailsjs.com/documentation/reference/waterline-orm/queries/meta).\n\n\n\n### Example\n\nTo create a user named Finn in the database:\n\n```javascript\nawait User.create({name:'Finn'});\n\nreturn res.ok();\n```\n\n##### Fetching the newly-created record\n```javascript\nvar createdUser = await User.create({name:'Finn'}).fetch();\n\nsails.log('Finn\\'s id is:', createdUser.id);\n```\n\n### Negotiating errors\n\nIt's important to always handle errors from model methods.  But sometimes, you need to look at errors in a more granular way. To learn more about the kinds of errors Waterline returns, and for examples of how to handle them, see [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors).\n\n### Notes\n> + This method can be used with [`await`](https://github.com/mikermcneil/parley/tree/49c06ee9ed32d9c55c24e8a0e767666a6b60b7e8#usage), promise chaining, or [traditional Node callbacks](https://sailsjs.com/documentation/reference/waterline-orm/queries/exec).\n\n\n<docmeta name=\"displayName\" value=\".create()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/models/createEach.md",
    "content": "# `.createEach()`\n\nCreate a set of records in the database.\n\n```usage\nawait Something.createEach(initialValues);\n```\n\nor\n\n+ `var createdRecords = await Something.createEach(initialValues).fetch();`\n\n\n### Usage\n\n|   |     Argument        | Type                                         | Details                            |\n|---|:--------------------|----------------------------------------------|:-----------------------------------|\n| 1 |  initialValues      | ((array?))                                   | An array of dictionaries with attributes for the new records.\n\n> **Note**: For performance reasons, as of Sails v1.0 / Waterline 0.13, the dictionaries in the `initialValues` array passed into this model method will be mutated in-place in most situations (whereas in Sails/Waterline v0.12, this was not necessarily the case).\n\n\n##### Result\n\n| Type                | Description      |\n|---------------------|:-----------------|\n| ((array?)) of ((dictionary))  | The created records are not provided as a result by default, in order to optimize for performance.  To override the default setting, chain `.fetch()` and the newly created records will be sent back. (Be aware that this requires an extra database query in some adapters.)\n\n\n##### Errors\n\n|     Name        | Type                | When? |\n|--------------------|---------------------|:---------------------------------------------------------------------------------|\n| UsageError            | ((Error))           | Thrown if something invalid was passed in.\n| AdapterError     | ((Error))           | Thrown if something went wrong in the database adapter. See [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for an example of how to negotiate a uniqueness error (arising from an attempt to create a record with a duplicate value that would violate a uniqueness constraint).\n| Error             | ((Error))           | Thrown if anything else unexpected happens.\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n\n\n##### Meta keys\n\n| Key                 | Type              | Details                                                        |\n|:--------------------|-------------------|:---------------------------------------------------------------|\n| fetch               | ((boolean))       | If set to `true`, then the created records will be sent back.<br/><br/>Defaults to `false`.\n\n> For more information on meta keys, see [.meta()](https://sailsjs.com/documentation/reference/waterline-orm/queries/meta).\n\n\n### Example\n\nTo create users named Finn and Jake in the database:\n\n```javascript\nawait User.createEach([{name:'Finn'}, {name: 'Jake'}]);\n```\n\n##### Fetching newly created records\n```javascript\nvar createdUsers = User.createEach([{name:'Finn'}, {name: 'Jake'}]).fetch();\nsails.log(`Created ${createdUsers.length} user${createdUsers.length===1?'':'s'}.`);\n```\n\n### Notes\n> + This method can be used with [`await`](https://github.com/mikermcneil/parley/tree/49c06ee9ed32d9c55c24e8a0e767666a6b60b7e8#usage), promise chaining, or [traditional Node callbacks](https://sailsjs.com/documentation/reference/waterline-orm/queries/exec).\n> + The number of records you can add with `.createEach` is limited by the maximum query size of the particular database you&rsquo;re using.  MySQL has a 4MB limit by default, but this can be changed via the [`max_allowed_packet` setting](https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_max_allowed_packet).  MongoDB imposes a 16MB limit on single documents, but essentially has no limit on the number of documents that can be created at once.  PostgreSQL has a very large (around 1GB) maximum size.  Consult your database&rsquo;s documentation for more information about query limitations.\n> + Another thing to watch out for when doing very large bulk inserts is the maximum number of bound variables. This varies per databases but refers to the number of values being substituted in a query. See [maxmimum allowable parameters](http://stackoverflow.com/questions/6581573/what-are-the-max-number-of-allowable-parameters-per-database-provider-type) for more details.\n> + When using `.fetch()` and manually specifying primary key values for new records, the sort order of returned records is not guaranteed (it varies depending on the database adapter in use).\n\n\n<docmeta name=\"displayName\" value=\".createEach()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/models/destroy.md",
    "content": "# `.destroy()`\n\nDestroy records in your database that match the given criteria.\n\n```usage\nawait Something.destroy(criteria);\n```\n\nor\n\n+ `var destroyedRecords = await Something.destroy(criteria).fetch();`\n\n\n### Usage\n\n|   |     Argument        | Type                                         | Details                            |\n|---|:--------------------|----------------------------------------------|:-----------------------------------|\n| 1 |    criteria         | ((dictionary))                               | Records matching this [Waterline criteria](https://sailsjs.com/documentation/concepts/models-and-orm/query-language) will be destroyed.  Be warned, if you specify an empty dictionary (`{}`) as your criteria, _all records will be destroyed!_ `destroy` queries do not support pagination using `skip` and `limit` or projections using `select`. |\n\n\n##### Result\n\n| Type                | Description      |\n|---------------------|:-----------------|\n| ((array?)) of ((dictionary))  | The destroyed records are not provided as a result by default in order to optimize for performance.  To override the default setting, chain `.fetch()` and the newly destroyed records will be sent back. (Be aware that this requires an extra database query in some adapters.)\n\n\n##### Errors\n\n|     Name        | Type                | When? |\n|-----------------|---------------------|:---------------------------------------------------------------------------------|\n| UsageError      | ((Error))           | Thrown if something invalid was passed in.\n| AdapterError    | ((Error))           | Thrown if something went wrong in the database adapter.\n| Error           | ((Error))           | Thrown if anything else unexpected happens.\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n##### Meta keys\n\n| Key                 | Type              | Details                                                        |\n|:--------------------|-------------------|:---------------------------------------------------------------|\n| fetch               | ((boolean))       | If set to `true`, then the array of destroyed records will be sent back.<br/><br/>Defaults to `false`.\n\n> For more information on meta keys, see [.meta()](https://sailsjs.com/documentation/reference/waterline-orm/queries/meta).\n\n\n\n### Example\n\nTo delete any users named Finn from the database:\n\n```javascript\nawait User.destroy({name:'Finn'});\n\nsails.log('Any users named Finn have now been deleted, if there were any.');\n```\n\n\nTo delete two particular users who have been causing trouble:\n\n```javascript\nawait User.destroy({\n  id: { in: [ 3, 97 ] }\n});\n\nsails.log('The records for troublesome users (3 and 97) have been deleted, if they still existed.');\n```\n\n\n##### Fetching destroyed records\n\nTo delete a particular book and fetch the destroyed record, use [.destroyOne()](https://sailsjs.com/documentation/reference/waterline/destroy-one).\n\nTo delete multiple books and fetch all destroyed records:\n\n```javascript\nvar burnedBooks = await Book.destroy({\n  controversiality: { '>': 0.9 }\n}).fetch();\nsails.log('Deleted books:', burnedBooks);\n```\n\n\n\n\n### Notes\n> + This method can be used with [`await`](https://github.com/mikermcneil/parley/tree/49c06ee9ed32d9c55c24e8a0e767666a6b60b7e8#usage), promise chaining, or [traditional Node callbacks](https://sailsjs.com/documentation/reference/waterline-orm/queries/exec).\n> + If you want to confirm that one or more records exist before destroying them, you should first perform a `find()`.  However, it is generally a good idea to _try to do things_ rather than _checking first_, lest you end up with a [race condition](http://people.cs.umass.edu/~emery/classes/cmpsci377/f07/scribe/scribe8-1.pdf).\n\n\n<docmeta name=\"displayName\" value=\".destroy()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/models/destroyOne.md",
    "content": "# `.destroyOne()`\n\nDestroy the record in your database that matches the given criteria, if it exists.\n\n```usage\nvar destroyedRecord = await Something.destroyOne(criteria);\n```\n\n> Before attempting to modify the database, Waterline will check to see if more than one record matches the given criteria. If so, it will throw an error instead of proceeding.\n\n\n### Usage\n\n|   |     Argument        | Type              | Details                            |\n|---|:--------------------|-------------------|:-----------------------------------|\n| 1 | criteria            | ((dictionary))    | The [Waterline criteria](https://sailsjs.com/documentation/concepts/models-and-orm/query-language) to use for matching the record in the database.\n\n##### Result\n\n| Type                | Description      |\n|:--------------------|:-----------------|\n| ((dictionary?))     | Since `.destroyOne()` never destroys more than one record, if a record is destroyed then it is always provided as a result.  Otherwise, `undefined` is returned.\n\n\n##### Errors\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n\n### Example\n\n```javascript\nvar burnedBook = await User.destroyOne({id: 4})\nif (burnedBook) {\n  sails.log('Deleted book with `id: 4`.');\n} else {\n  sails.log('The database does not have a book with `id: 4`.');\n}\n```\n\n\n### Notes\n> + Because it _always_ returns the destroyed record, if one was matched, this method **does not support .fetch()**.\n> + This method can be used with [`await`](https://github.com/mikermcneil/parley/tree/49c06ee9ed32d9c55c24e8a0e767666a6b60b7e8#usage), promise chaining, or [traditional Node callbacks](https://sailsjs.com/documentation/reference/waterline-orm/queries/exec).\n\n\n<docmeta name=\"displayName\" value=\".destroyOne()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/models/find.md",
    "content": "# `.find()`\n\nFind records in your database that match the given criteria.\n\n```usage\nvar records = await Something.find(criteria);\n```\n\n### Usage\n\n|   |     Argument        | Type              | Details                            |\n|---|:--------------------|-------------------|:-----------------------------------|\n| 1 |    criteria         | ((dictionary))    | The [Waterline criteria](https://sailsjs.com/documentation/concepts/models-and-orm/query-language) to use for matching records in the database.\n\n##### Result\n\n| Type                | Description      |\n|---------------------|:-----------------|\n| ((array)) of ((dictionary))   | The array of records from your database that match the given criteria.\n\n\n##### Errors\n\n|     Name        | Type                | When? |\n|:----------------|---------------------|:---------------------------------------------------------------------------------|\n| UsageError      | ((Error))           | Thrown if something invalid was passed in.\n| AdapterError    | ((Error))           | Thrown if something went wrong in the database adapter.\n| Error           | ((Error))           | Thrown if anything else unexpected happens.\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n\n### Example\n\n##### A basic find query\n\nTo find any users named Finn in the database:\n\n```javascript\nvar usersNamedFinn = await User.find({name:'Finn'});\nsails.log('Wow, there are %d users named Finn.  Check it out:', usersNamedFinn.length, usersNamedFinn);\n```\n\n\n##### Using projection\n\nProjection selectively omits the fields returned on found records. This is useful for achieving faster performance and greater security when passing found records to the client. The select clause in a [Waterline criteria](https://sailsjs.com/documentation/concepts/models-and-orm/query-language) takes an array of strings that correspond with attribute names. The record ID is always returned.\n\n```javascript\nvar usersNamedFinn = await User.find({\n  where: {name:'Finn'},\n  select: ['name', 'email']\n});\n```\n\n\nmight yield:\n\n```javascript\n[\n  {\n    id: 7392,\n    name: 'Finn',\n    email: 'finn_2017@gmail.com'\n  },\n  {\n    id: 4427,\n    name: 'Finn',\n    email: 'walkingfinn@outlook.com'\n  }\n  // ...more users named Finn and their email addresses\n]\n```\n\n### Notes\n> + This method can be used with [`await`](https://github.com/mikermcneil/parley/tree/49c06ee9ed32d9c55c24e8a0e767666a6b60b7e8#usage), promise chaining, or [traditional Node callbacks](https://sailsjs.com/documentation/reference/waterline-orm/queries/exec).\n\n<docmeta name=\"importance\" value=\"10\">\n<docmeta name=\"displayName\" value=\".find()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/models/findOne.md",
    "content": "# `.findOne()`\n\nAttempt to find a particular record in your database that matches the given criteria.\n\n```usage\nvar record = await Something.findOne(criteria);\n```\n\n### Usage\n\n|   |     Argument        | Type                                         | Details                            |\n|---|:--------------------|----------------------------------------------|:-----------------------------------|\n| 1 |    criteria         | ((dictionary))                               | The [Waterline criteria](https://sailsjs.com/documentation/concepts/models-and-orm/query-language) to use for matching this record in the database.  (This criteria must never match more than one record.) `findOne` queries do not support pagination using `skip` or `limit`.\n\n##### Result\n\n| Type                | Description      |\n|---------------------|:-----------------|\n| ((dictionary?))     | The record that was found, or `undefined` if no such record could be located.\n\n##### Errors\n\n|     Name        | Type                | When? |\n|:----------------|---------------------|:---------------------------------------------------------------------------------|\n| UsageError      | ((Error))           | Thrown if something invalid was passed in.\n| AdapterError    | ((Error))           | Thrown if something went wrong in the database adapter.\n| Error           | ((Error))           | Thrown if anything else unexpected happens.\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n\n### Example\n\nTo locate the user whose username is \"finn\" in your database:\n\n```javascript\nvar finn = await Users.findOne({\n  username: 'finn'\n});\n\nif (!finn) {\n  sails.log('Could not find Finn, sorry.');\n}\nelse {\n  sails.log('Found \"%s\"', finn.fullName);\n}\n```\n\n\n\n### Notes\n> + This method can be used with [`await`](https://github.com/mikermcneil/parley/tree/49c06ee9ed32d9c55c24e8a0e767666a6b60b7e8#usage), promise chaining, or [traditional Node callbacks](https://sailsjs.com/documentation/reference/waterline-orm/queries/exec).\n> + Being unable to find a record with the given criteria does **not** constitute an error for `findOne()`.  If no matching record is found, the result will be `undefined`.\n\n\n\n<docmeta name=\"importance\" value=\"10\">\n<docmeta name=\"displayName\" value=\".findOne()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/waterline/models/findOrCreate.md",
    "content": "# `.findOrCreate()`\n\nFind the record matching the specified criteria.  If no such record exists, create one using the provided initial values.\n\n```usage\nvar newOrExistingRecord = await Something.findOrCreate(criteria, initialValues);\n```\n\nor, if you need to know whether a new record was created,\n\n```usage\nSomething.findOrCreate(criteria, initialValues)\n.exec(function(err, newOrExistingRecord, wasCreated) {\n\n});\n```\n\n#### Usage\n\n| # | Argument      | Type                  | Details    |\n|---|---------------|:----------------------|:-----------|\n| 1 | _criteria_    | ((dictionary?))       | The [Waterline criteria](https://sailsjs.com/documentation/concepts/models-and-orm/query-language) to use for matching records in the database.  **This particular criteria should always match exactly zero or one records in the database.**\n| 2 |  initialValues | ((dictionary))       | The initial values for the new record, if one is created.\n\n\n\n#### Callback\n|   |     Argument            | Type                | Details |\n|---|:------------------------|---------------------|:---------------------------------------------------------------------------------|\n| 1 |    _err_                | ((Error?))          | The error that occurred, or `undefined` if there were no errors.\n| 2 | _newOrExistingRecord_   | ((dictionary?))     | The record that was found, or `undefined` if no such record could be located.\n| 3 | wasCreated              | ((boolean))         | Whether a new record was created.\n\n\n##### Errors\n\n|     Name        | Type                | When? |\n|:----------------|---------------------|:---------------------------------------------------------------------------------|\n| UsageError      | ((Error))           | Thrown if something invalid was passed in.\n| AdapterError    | ((Error))           | Thrown if something went wrong in the database adapter.\n| Error           | ((Error))           | Thrown if anything else unexpected happens.\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n\n### Example\n\nLet's make sure our test user, Finn, exists:\n\n```javascript\nUser.findOrCreate({ name: 'Finn' }, { name: 'Finn' })\n.exec(async(err, user, wasCreated)=> {\n  if (err) { return res.serverError(err); }\n  \n  if(wasCreated) {\n    sails.log('Created a new user: ' + user.name);\n  }\n  else {\n    sails.log('Found existing user: ' + user.name);\n  }\n});\n```\n\n### Notes\n> + This method can be used with [`await`](https://github.com/mikermcneil/parley/tree/49c06ee9ed32d9c55c24e8a0e767666a6b60b7e8#usage), promise chaining, or [traditional Node callbacks](https://sailsjs.com/documentation/reference/waterline-orm/queries/exec). If you use `await`, be aware that the result will be the record only&mdash;you will not have access to `wasCreated`.\n> + Behind the scenes, this uses `.findOne()`, so if more than one record in the database matches the provided criteria, there will be an error explaining so.\n\n<docmeta name=\"displayName\" value=\".findOrCreate()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/models/getDatastore.md",
    "content": "# `.getDatastore()`\n\nAccess the [datastore](https://sailsjs.com/documentation/concepts/models-and-orm#?datastores) for a particular model.\n\n```usage\nSomething.getDatastore();\n```\n\n### Usage\n\n##### Returns\n\n**Type:** ((Dictionary))\n\nA [datastore instance](https://sailsjs.com/documentation/reference/waterline-orm/datastores).\n\n### Notes\n> + This is a synchronous method, so you don't need to use `await`, promise chaining, or traditional Node callbacks.\n\n\n\n<docmeta name=\"displayName\" value=\".getDatastore()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/models/models.md",
    "content": "# Working with models\n\nThis section of the documentation focuses on the model methods provided by Waterline out of the box.  In addition to these, additional methods can come from hooks (like the [resourceful PubSub methods](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub)) or be manually written in your app to wrap reusable custom code.\n\n> + For an in-depth introduction to models in Sails/Waterline, see [Concepts > Models and ORM > Models](https://sailsjs.com/documentation/concepts/models-and-orm/models).\n> + You can find an example of how to define a model [here](https://gist.github.com/rachaelshaw/f5bf442b2171154aa6021846d1a250f8).\n\n\n\n\n### Built-in model methods\n\nIn general, model methods are _asynchronous_, meaning you cannot just call them and use the return value.  Instead, you must use callbacks, promises or async/await. \nMost built-in model methods accept a callback as an optional final argument. If the callback is not supplied, a chainable Query object is returned, which has methods like `.fetch()`, `.decrypt()`, and `.where()`. See [Working with Queries](https://sailsjs.com/documentation/reference/waterline-orm/queries) for more on that.\n\nHere are some of the most common model methods you will encounter building Node.js apps in Sails:\n\n Method                | Summary\n --------------------- | ------------------------------------------------------------------------\n `.find()`             | Get an array of records which match the specified criteria.\n `.findOne()`          | Get the record which matches the specified criteria, or `undefined` if there isn't one.\n `.updateOne()`        | Update the record that matches the specified criteria, if there is one, using the specified `attrName:value` pairs.\n `.archiveOne()`       | Archive (\"soft-delete\") the record that matches the specified criteria, if there is one.\n `.destroyOne()`       | Permanently and irreversibly destroy the record that matches the specified criteria, if there is one.\n `.create()`           | Create a new record consisting of the specified values.\n `.createEach()`       | Create multiple new records at the same time.\n `.count()`            | Count the total number of records that match certain criteria.\n `.sum()`              | Compute the sum for a given attribute, totalled across all records that match certain criteria.\n `.avg()`              | Compute the arithmetic mean for an attribute, averaged over all records that match certain criteria.\n `.addToCollection()`      | Add existing records from an associated model to one of your collections.\n `.removeFromCollection()` | Remove record(s) from one of your collections.\n\n\nThese methods are just the beginning.  To read more about available model methods in Sails, check out the complete reference in the sidebar.\n\n\n\n<!--\nNot actually all that common:\n `.replaceCollection()`    | Replace all the members in one of your collections with a new set of records from its associated model.\n `.update()`           | Update records matching the specified criteria, setting the specified `attrName:value` pairs.\n `.archive()`          | Archive (\"soft-delete\") all records that match the specified criteria.\n `.stream()`           | Get records that meet the specified criteria one at a time (or batch at a time).\n `.native()`/`query()` | Make a direct call to the underlying database using a native query.\n `.findOrCreate()`     | Lookup a single record which matches the specified criteria, or create it if it doesn't.\n `.destroy()`          | Destroy records matching the specified criteria.\n\n-->\n\n<!-- ![screenshot of the api/models/ folder in a text editor](http://i.imgur.com/xdTZpKT.png) -->\n\n\n\n\n\n### `sails.models`\n\nIf you need to disable global variables in Sails, you can still use `sails.models.<model_identity>` to access your models.\n> Not sure of your model's `identity`? Check out [Concepts > Models and ORM > Model settings](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?identity).\n\n<docmeta name=\"displayName\" value=\"Models\">\n"
  },
  {
    "path": "docs/reference/waterline/models/native.md",
    "content": "# `.native()`\n\n> **As of Sails v1.x, this method is deprecated.**\n> Instead, please change your code to use [`Model.getDatastore().manager`](https://sailsjs.com/documentation/reference/waterline-orm/datastores/manager), which offers a cleaner, simpler API.\n\n`.native()` is only available when using Sails/Waterline with MongoDB.\n\nReturns a raw Mongo collection instance representing the specified model, allowing you to perform raw Mongo queries.\n\nFor full documentation and usage examples, check out the [native Node Mongo driver](https://github.com/mongodb/node-mongodb-native#introduction).\n\n\nNote that `sails-mongo` maintains a single Mongo connection for each of your configured datastores.  Consequently, when using `.native()`, you don't need to close or open `db` manually.  For lower-level usage, you can `require('mongodb')` directly.\n\n### Example\n\n```js\nPet.native(function(err, collection) {\n  if (err) return res.serverError(err);\n\n  collection.find({}, {\n    name: true\n  }).toArray(function (err, results) {\n    if (err) return res.serverError(err);\n    return res.ok(results);\n  });\n});\n```\n\nSource: https://gist.github.com/mikermcneil/483987369d54512b6104\n\n### Notes\n\n> + This method only works with Mongo! For raw functionality in SQL databases, use [`.query()`](https://sailsjs.com/documentation/reference/waterline-orm/models/query).\n\n\n<docmeta name=\"displayName\" value=\".native()\">\n<docmeta name=\"pageType\" value=\"method\">\n<docmeta name=\"isDeprecated\" value=\"true\">\n"
  },
  {
    "path": "docs/reference/waterline/models/query.md",
    "content": "# `.query()`\n\n> **As of Sails v1.0, this method is deprecated.**\n> Instead, please use [`Model.getDatastore().sendNativeQuery()`](https://sailsjs.com/documentation/reference/waterline-orm/datastores/send-native-query), the new version of this method that standardizes the format of SQL escape bindings, as well as fully supporting `.exec()` and promise-based usage.\n\nExecute a raw SQL query using the specified model's datastore.\n\n```usage\nSomeModel.query(sql, valuesToEscape, function(err, rawResult) {\n\n});\n```\n\n> **WARNING:** Unlike other Waterine model methods, `.query()` supports neither promise-based usage nor the use of `.exec()`.  In other words, it does not utilize Waterline's normal deferred object mechanism.  Instead, it provides raw access directly to the underlying database driver.\n\n### Usage\n\n`.query()` is only available on Sails/Waterline models that are configured to use a SQL database (e.g. PostgreSQL or MySQL).  Its purpose is to perform raw SQL queries.  Note that exact usage and result format varies between adapters, so you'll need to refer to the documentation for the underlying database driver.  (See below for a couple of simple examples to help get you started.)\n\n\n|   |     Argument        | Type              | Details                            |\n|---|:--------------------|-------------------|:-----------------------------------|\n| 1 |    sql              | ((string))        | A SQL string written in the appropriate dialect for this model's database.  Allows template syntax, (e.g. `?`, `$1`) the exact style of which depends on the underlying database adapter. _(See examples below.)_\n| 2 |    valuesToEscape   | ((array))         | An array of dynamic, untrusted strings to SQL-escape and inject within the SQL string using the appropriate template syntax for this model's database.  _(If you have no dynamic values to inject, then just use an empty array here.)_\n| 3 |    done             | ((function))      | A callback function that will be triggered when the query completes successfully, or if the adapter encounters an error.\n\n##### Callback\n\n|   |     Argument        | Type                | Details |\n|---|:--------------------|---------------------|:---------------------------------------------------------------------------------|\n| 1 |    _err_            | ((Error?))          | The error that occurred, or a falsy value if there were no errors.  _(The exact format of this error varies depending on the SQL query you passed in and the database adapter you're using.  See examples below for links to relevant documentation.)_\n| 2 |    _rawResult_      | ((Ref?))            | The raw result from the adapter.  _(The exact format of this raw result data varies depending on the SQL query you passed in and the database adapter you're using.  See examples below for links to relevant documentation.)_\n\n\n\n### Example\n\nRemember that usage and result data vary depending on the SQL query you send and the adapter you're using.  Below, you'll find two examples: one for PostgreSQL and one for MySQL.\n\n##### PostgreSQL example\n\nCommunicate directly with [`pg`](http://npmjs.com/package/pg), an NPM package used for communicating with PostgreSQL databases:\n\n```js\nPet.query('SELECT pet.name FROM pet WHERE pet.name = $1', [ 'dog' ] ,function(err, rawResult) {\n  if (err) { return res.serverError(err); }\n\n  sails.log(rawResult);\n  // (result format depends on the SQL query that was passed in, and the adapter you're using)\n\n  // Then parse the raw result and do whatever you like with it.\n\n  return res.ok();\n\n});\n```\n\n##### MySQL example\n\nAssuming the `Pet` model is configured to use the `sails-mysql` adapter, the following code will communicate directly with [`mysql`](http://npmjs.com/package/mysql), an NPM package used for communicating with MySQL databases:\n\n```js\nPet.query('SELECT pet.name FROM pet WHERE pet.name = ?', [ 'dog' ] ,function(err, rawResult) {\n  if (err) { return res.serverError(err); }\n\n  sails.log(rawResult);\n  // ...grab appropriate data...\n  // (result format depends on the SQL query that was passed in, and the adapter you're using)\n\n  // Then parse the raw result and do whatever you like with it.\n\n  return res.ok();\n\n});\n```\n\n### Notes\n> + This method only works with SQL databases.  To get access to the raw MongoDB collection, use [`.native()`](https://sailsjs.com/documentation/reference/waterline-orm/models/native).\n> + This method **does not** support `.exec()` or `.then()`, and it **does not** return a promise.  If you want to \"promisify\" `.query()`, have a look at [this](http://stackoverflow.com/questions/21886630/how-to-use-model-query-with-promises-in-sailsjs-waterline).\n\n\n\n<docmeta name=\"displayName\" value=\".query()\">\n<docmeta name=\"pageType\" value=\"method\">\n<docmeta name=\"isDeprecated\" value=\"true\">\n"
  },
  {
    "path": "docs/reference/waterline/models/removeFromCollection.md",
    "content": "# `.removeFromCollection()`\n\nRemove one or more members (e.g. a comment) from the specified collection (e.g. the `comments` of BlogPost #4).\n\n```usage\nawait Something.removeFromCollection(parentId, association)\n.members(childIds);\n```\n\n### Usage\n\n|   |     Argument        | Type                                         | Details                            |\n|---|:--------------------|----------------------------------------------|:-----------------------------------|\n| 1 |  parentId    | ((number)) or ((string))                   | The primary key value(s) (i.e. ids) for the parent record(s). <br/>Must be a number or string (e.g. `'507f191e810c19729de860ea'` or `49`).  <br/>Alternatively, an array of numbers or strings may be specified (e.g. `['507f191e810c19729de860ea', '14832ace0c179de897']` or `[49, 32, 37]`).  In this case, _all_ of the child records will be removed from the appropriate collection of each parent record.\n| 2 |  association | ((string))                                   | The name of the plural (\"collection\") association (e.g. \"pets\")\n| 3 |  childIds      | ((array))                                    | The primary key values (i.e. ids) of the child records to remove.  _Note that this does not [destroy](https://sailsjs.com/documentation/reference/waterline-orm/models/destroy) these records, it just detaches them from the specified parent(s)._\n\n\n##### Errors\n\n|     Name        | Type                | When? |\n|:----------------|---------------------|:---------------------------------------------------------------------------------|\n| UsageError      | ((Error))           | Thrown if something invalid was passed in.\n| AdapterError    | ((Error))           | Thrown if something went wrong in the database adapter.\n| Error           | ((Error))           | Thrown if anything else unexpected happens.\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n\n### Example\n\nFor user 3, remove pets 99 and 98 from the \"pets\" collection:\n\n```javascript\nawait User.removeFromCollection(3, 'pets')\n.members([99,98]);\n```\n\n\n### Edge cases\n\n+ If the parent id (or any _one_ of the parent ids, if specified as an array) does not actually correspond with an existing, persisted record, then this will modify the existing records and ignore the non-existent ones.\n+ If one of the child ids does not actually correspond with an existing, persisted record, then that child id will be ignored, and only those members that correspond with the other provided child ids will be removed from the collection.\n+ If a parent record's collection _does not have_ one or more of these child ids as members, then the ids of those non-members will be ignored. ((TODO: test with one-to-many))\n+ If an empty array of child ids is provided, then this is a [no-op](https://en.wikipedia.org/wiki/NOP#Code).\n+ If an empty array of parent ids is provided, then this is a [no-op](https://en.wikipedia.org/wiki/NOP#Code).\n\n### Notes\n> + This method can be used with [`await`](https://github.com/mikermcneil/parley/tree/49c06ee9ed32d9c55c24e8a0e767666a6b60b7e8#usage), promise chaining, or [traditional Node callbacks](https://sailsjs.com/documentation/reference/waterline-orm/queries/exec).\n> + If the association is \"two-way\" (meaning it has `via`) then the child records will be modified accordingly.  If the attribute on the other (e.g. \"Pet\") side is singular, the each child record's foreign key (\"owner\") will be set to `null`.  If it's plural, then each child record's collection will be modified accordingly.\n\n\n\n\n<docmeta name=\"displayName\" value=\".removeFromCollection()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/models/replaceCollection.md",
    "content": "# `.replaceCollection()`\n\nReplace all members of the specified collection (e.g. the `comments` of BlogPost #4).\n\n```usage\nawait Something.replaceCollection(parentId, association)\n.members(childIds);\n```\n\n### Usage\n\n|   |     Argument        | Type                                         | Details                            |\n|---|:--------------------|----------------------------------------------|:-----------------------------------|\n| 1 |  parentId           | ((number)) or ((string))                   | The primary key value(s) (i.e. ids) for the parent record(s). <br/>Must be a number or string (e.g. `'507f191e810c19729de860ea'` or `49`).  <br/>Alternatively, an array of numbers or strings may be specified (e.g. `['507f191e810c19729de860ea', '14832ace0c179de897']` or `[49, 32, 37]`). In this case, the child records will be replaced in each parent record.\n| 2 |  association | ((string))                                   | The name of the plural (\"collection\") association (e.g. \"pets\")\n| 3 |  childIds      | ((array))                                    | The primary key values (i.e. ids) for the child records that will be the new members of the association.  _Note that this does not [create](https://sailsjs.com/documentation/reference/waterline-orm/models/create) these records or [destroy](https://sailsjs.com/documentation/reference/waterline-orm/models/destroy) the old ones, it just attaches/detaches records to/from the specified parent(s)._\n\n\n##### Errors\n\n|     Name        | Type                | When? |\n|:----------------|---------------------|:---------------------------------------------------------------------------------|\n| UsageError      | ((Error))           | Thrown if something invalid was passed in.\n| AdapterError    | ((Error))           | Thrown if something went wrong in the database adapter.\n| Error           | ((Error))           | Thrown if anything else unexpected happens.\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n\n\n### Example\n\nFor user 3, replace all pets in the \"pets\" collection with pets 99 and 98:\n\n```javascript\nawait User.replaceCollection(3, 'pets')\n.members([99,98]);\n```\n\n### Edge cases\n\n+ If the parent id does not actually correspond with an existing, persisted record, then this will do nothing.\n+ If one of the child ids does not actually correspond with an existing, persisted record, then that child id will be ignored, and only those members that correspond with the other provided child ids will be included in the replacement collection.\n+ If an empty array of child ids is provided, or if none of the provided child ids correspond to existing records, then this will detach _all_ child records from the parent.\n\n### Notes\n> + This method can be used with [`await`](https://github.com/mikermcneil/parley/tree/49c06ee9ed32d9c55c24e8a0e767666a6b60b7e8#usage), promise chaining, or [traditional Node callbacks](https://sailsjs.com/documentation/reference/waterline-orm/queries/exec).\n> + If the association is \"2-way\" (meaning it has `via`) then the child records will be modified accordingly.  If the attribute on the other side is singular, the each newly-linked-or-unlinked child record's foreign key will be changed.  If it's plural, then each child record's collection will be modified accordingly.\n> + In addition, if the `via` points at a singular (\"model\") attribute on the other side, then `.addToCollection()` will \"steal\" these child records if necessary.  For example, imagine you have an Employee model with this plural (\"collection\") attribute: `involvedInPurchases: { collection: 'Purchase', via: 'cashier' }`.  If you executed `Employee.addToCollection(7, 'involvedInPurchases', [47])` to assign this purchase to employee #7 (Dolly), but purchase #47 was already associated with a different employee (e.g. #12, Motoki), then this would \"steal\" the purchase from Motoki and give it to Dolly.  In other words, if you executed `Employee.find([7, 12]).populate('involvedInPurchases')`, Dolly's `involvedInPurchases` array would contain purchase #47 and Motoki's would not.\n\n\n\n\n<docmeta name=\"displayName\" value=\".replaceCollection()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/models/stream.md",
    "content": "# `.stream()`\n\nStream records from your database to be consumed one at a time or in batches, without first having to buffer the entire result set in memory.\n\n```usage\nawait Something.stream(criteria)\n.eachRecord(async (record)=>{\n\n});\n```\n\n\n\n\n### Usage\n\n|   |     Argument        | Type              | Details                            |\n|---|:--------------------|-------------------|:-----------------------------------|\n| 1 | _criteria_          | ((dictionary))    | The [Waterline criteria](https://sailsjs.com/documentation/concepts/models-and-orm/query-language) to use for matching records in the database.\n\n##### Iteratee\n\n_Use one of the following:_\n\n+ `.eachRecord(async (record)=>{ ... })`\n+ `.eachBatch(async (records)=>{ ... })`\n\n_The custom function you provide to `eachRecord()` or `eachBatch()` will receive the following arguments:_\n\n<br/>\n\n|   |     Argument        | Type                | Details |\n|---|:--------------------|---------------------|:---------------------------------------------------------------------------------|\n| 1 | record or records   | ((dictionary)) or ((array))      | The current record, or the current batch of records.  _A batch array will always contain at least one record, and it will never contain more records than the batch size (thirty by default)._\n\n\n\n\n##### Errors\n\n|     Name        | Type                | When? |\n|:----------------|---------------------|:---------------------------------------------------------------------------------|\n| UsageError      | ((Error))           | Thrown if something invalid was passed in.\n| AdapterError    | ((Error))           | Thrown if something went wrong in the database adapter.\n| Error           | ((Error))           | Thrown if anything else unexpected happens.\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n\n### When should I use this?\n\nThe `.stream()` method is almost exactly like [`.find()`](https://sailsjs.com/documentation/reference/waterline-orm/models/find), except that it fetches records one batch at a time.  Every time a batch of records is loaded, the iteratee function you provided is called one or more times.  If you used `.eachRecord()`, your per-record function will be called once for each record in the batch.  Otherwise, using `.eachBatch()`, your per-batch function will be called once with the entire batch.\n\nThis is useful for working with very large result sets, the kind that might overflow your server's available RAM if you tried to hold the entire set in memory at the same time.  You can use Waterline's `.stream()` method to do the kinds of things you might already be familiar with from Mongo cursors: preparing reports, looping over and modifying database records in a shell script, moving large amounts of data from one place to another, performing complex transformations, or even orchestrating map/reduce jobs.\n\n\n### Examples\n\nWe explore four example situations below:\n\n##### Basic usage\n\nAn action that iterates over users named Finn in the database, one at a time:\n\n```javascript\nawait User.stream({name:'Finn'})\n.eachRecord(async (user)=>{\n\n  if (Math.random() > 0.5) {\n    throw new Error('Oops!  This is a simulated error.');\n  }\n\n  sails.log(`Found a user ${user.id} named Finn.`);\n});\n```\n\n##### Generating a dynamic sitemap\n\nAn action that responds with a dynamically generated sitemap:\n\n```javascript\n// e.g. in an action that handles `GET /sitemap.xml`:\n\nvar sitemapXml = '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">';\n\nawait BlogPost.stream()\n.limit(50000)\n.sort('title ASC')\n.eachRecord((blogPost)=>{\n  sitemapXml += (\n    '<url>\\n'+\n    '  <loc>https://blog.example.com/' + _.escape(encodeURIComponent(blogPost.slug))+'</loc>\\n'+\n    '  <lastmod>'+_.escape(blogPost.updatedAt)+'</lastmod>\\n'+\n    '<changefreq>monthly</changefreq>\\n'+\n    '</url>'\n  );\n});\n\nsitemapXml += '</urlset>';\n```\n\n\n\n##### With `.populate()`\n\nA snippet of a command-line script that searches for creepy comments from someone named \"Bailey Bitterbumps\" and reports them to the authorities:\n\n```js\n// e.g. in a shell script\n\nvar numReported = 0;\n\nawait Comment.stream({ author: 'Bailey Bitterbumps' })\n.limit(1000)\n.skip(40)\n.sort('title ASC')\n.populate('attachedFiles', {\n  limit: 3,\n  sort: 'updatedAt'\n})\n.populate('fromBlogPost')\n.eachRecord(async (comment)=>{\n\n  var isCreepyEnoughToWorryAbout = comment.rawMessage.match(/creepy/) && comment.attachedFiles.length > 1;\n  if (!isCreepyEnoughToWorryAbout) {\n    return;\n  }\n\n  await sails.helpers.sendTemplateEmail.with({\n    template: 'email-creepy-comment-notification',\n    templateData: {\n      url: `https://blog.example.com/${comment.fromBlogPost.slug}/comments/${comment.slug}.`\n    },\n    to: 'authorities@cannedmeat.gov',\n    subject: 'Creepy comment alert'\n  });\n\n  numReported++;\n});\n\nsails.log(`Successfully reported ${numReported} creepy comments.`);\n```\n\n\n##### Batch-at-a-time\n\nIf we ran the code in the previous example, we'd be sending one email per creepy comment... which could be a lot, knowing Bailey Bitterbumps.  Not only would this be slow, it could mean sending _thousands_ of individual API requests to our [transactional email provider](https://documentation.mailgun.com/faqs.html#why-not-just-use-sendmail-postfix-courier-imap), quickly overwhelming our API rate limit.\n\nFor this case, we could use `.eachBatch()` to grab the entire batch of records being fetched, rather than processing individual records one at a time, dramatically reducing the number of necessary API requests.\n\n\n##### Configuring batch size\n\nBy default, `.stream()` uses a batch size of 30.  That means it will load up to 30 records per batch; thus, if you are using `.eachBatch()`, your custom function will receive between 1 and 30 records each time it is called.\n\nTo increase or decrease the batch size, pass an additional argument to `.eachBatch()`:\n\n```javascript\n.eachBatch(100, async (records)=>{\n  console.log(`Got ${records.length} records.`);\n})\n```\n\n> Using `.eachBatch()` in your code is not necessarily more or less efficient than using `.eachRecord()`.  That's because, regardless which iterator you use, Waterline asks the database for more than one record at a time (30, by default).  With `.eachBatch()`,  you can easily configure this batch size using the extra argument described above.  It's also possible to customize the batch size while using `.eachRecord` (for example, to avoid getting rate-limited by a 3rd party API you are using). Just use [`.meta()`](https://sailsjs.com/documentation/reference/waterline-orm/queries/meta).  For example, `.meta({batchSize: 100})`.\n\n\n\n### Notes\n> + This method can be used with [`await`](https://github.com/mikermcneil/parley/tree/49c06ee9ed32d9c55c24e8a0e767666a6b60b7e8#usage), promise chaining, or [traditional Node callbacks](https://sailsjs.com/documentation/reference/waterline-orm/queries/exec).\n> + `.stream()` bails and throws an error _immediately_ upon receiving the first error from any iteratee.\n> + `.stream()` runs the provided iteratee function on each record or batch, one at a time, in series.\n> Prior to Sails 1.1.0, the recommended usage of `.stream()` expected the iteratee to invoke a callback (`next`), which is provided as the second argument.  This is no longer necessary as long as you do not actually include a second argument in the function signature.\n> + Prior to Sails v1.0 / Waterline 0.13, this method had a lower-level interface, exposing a [Readable \"object stream\"](http://nodejs.org/api/stream.html).  This was powerful, but tended to be error-prone.  The new, adapter-agnostic `.stream()` does not rely on emitters or any particular flavor of Node streams.  (Need to get it working the old way?  Don't worry, with a little code, you can still easily build a streams2/streams3-compatible Readable \"object stream\" using the new interface.)\n> + Read more background about the impetus for creating `.stream()` [here](https://gist.githubusercontent.com/mikermcneil/d1e612cd1a8564a79f61e1f556fc49a6/raw/094d49a670e70cc38ae11a9419314542e8e4e5c9/streaming-records-in-sails-v1.md), including additional examples, background information, and implementation details.\n\n\n<docmeta name=\"displayName\" value=\".stream()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/waterline/models/sum.md",
    "content": "# `.sum()`\n\nGet the aggregate sum of the specified attribute across all matching records.\n\n```usage\nvar total = await Something.sum(numericAttrName, criteria);\n```\n\n### Usage\n\n|   |     Argument        | Type                                         | Details                            |\n|---|:--------------------|----------------------------------------------|:-----------------------------------|\n| 1 |  numericAttrName    | ((string))                                   | The name of the numeric attribute to be summed.\n| 2 |  _criteria_         | ((dictionary?))                              | The [Waterline criteria](https://sailsjs.com/documentation/concepts/models-and-orm/query-language) to use for matching records in the database. If no criteria is specified, the sum will be computed across _all_ of this model's records. `sum` queries do not support pagination using `skip` and `limit` or projections using `select`.\n\n\n##### Result\n\n| Type                | Description      |\n|---------------------|:-----------------|\n| ((number))          | The aggregate sum of the specified attribute across all matching records.\n\n\n##### Errors\n\n|     Name        | Type                | When? |\n|:----------------|---------------------|:---------------------------------------------------------------------------------|\n| UsageError      | ((Error))           | Thrown if something invalid was passed in.\n| AdapterError    | ((Error))           | Thrown if something went wrong in the database adapter.\n| Error           | ((Error))           | Thrown if anything else unexpected happens.\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n\n### Example\n\nGet the cumulative account balance of all bank accounts that have less than $32,000 or are flagged as \"suspended\".\n\n\n```javascript\nvar total = await BankAccount.sum('balance')\n.where({\n  or: [\n    { balance: { '<': 32000 } },\n    { suspended: true }\n  ]\n});\n```\n\n### Notes\n> + This method can be used with [`await`](https://github.com/mikermcneil/parley/tree/49c06ee9ed32d9c55c24e8a0e767666a6b60b7e8#usage), promise chaining, or [traditional Node callbacks](https://sailsjs.com/documentation/reference/waterline-orm/queries/exec).\n> + Some databases, like MySQL, may return `null` for this kind of query; however, it's best practice for Sails/Waterline adapter authors to return `0` for consistency and type safety in app-level code.\n\n<docmeta name=\"displayName\" value=\".sum()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/models/update.md",
    "content": "# `.update()`\n\nUpdate all records matching criteria.\n\n```usage\nawait Something.update(criteria)\n.set(valuesToSet);\n```\n\nor\n\n+ `var updatedRecords = await Something.update(criteria).set(valuesToSet).fetch();`\n\n\n### Usage\n\n|   |     Argument        | Type              | Details                            |\n|---|:--------------------|-------------------|:-----------------------------------|\n| 1 | criteria            | ((dictionary))    | The [Waterline criteria](https://sailsjs.com/documentation/concepts/models-and-orm/query-language) to use for matching records in the database. `update` queries do not support pagination using `skip` and `limit`, or projections using `select`.\n| 2 | valuesToSet         | ((dictionary))    | A dictionary (plain JavaScript object) of values that all matching records should be updated to have.  _(Note that, if this model is in [\"schemaful\" mode](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?schema), then any extraneous keys will be silently omitted.)_\n\n> **Note**: For performance reasons, as of Sails v1.0 / Waterline 0.13, the `valuesToSet` object passed into this model method will be mutated in-place in most situations (whereas in Sails/Waterline v0.12, this was not necessarily the case).\n\n##### Result\n\n| Type                | Description      |\n|:--------------------|:-----------------|\n| ((array?))          | The updated records are not provided as a result by default, in order to optimize for performance.  To override the default setting, chain `.fetch()` and an array of the updated records will be sent back. (Be aware that this requires an extra database query in some adapters.)\n\n\n##### Errors\n\n|     Name        | Type                | When? |\n|:-------------------|---------------------|:---------------------------------------------------------------------------------|\n| UsageError\t\t\t| ((Error))           | Thrown if something invalid was passed in.\n| AdapterError\t\t| ((Error))           | Thrown if something went wrong in the database adapter. See [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for an example of how to negotiate a uniqueness error (i.e. from attempting to update one or more records so that they violate a uniqueness constraint).\n| Error    \t\t\t\t| ((Error))           | Thrown if anything else unexpected happens.\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n\n##### Meta keys\n\n| Key                 | Type              | Details                                                        |\n|:--------------------|-------------------|:---------------------------------------------------------------|\n| fetch               | ((boolean))       | If set to `true`, then the array of updated records will be sent back.<br/><br/>Defaults to `false`.\n\n> For more information on meta keys, see [.meta()](https://sailsjs.com/documentation/reference/waterline-orm/queries/meta).\n\n\n\n### Example\n\nTo update a particular record, use [`.updateOne()`](https://sailsjs.com/documentation/reference/waterline-orm/models/update-one).\n\nOr to update one or more records at the same time:\n\n```javascript\nawait User.update({ name:'Pen' })\n.set({\n  name:'Finn'\n});\n\nsails.log('Updated all users named Pen so that their new name is \"Finn\".  I hope they like it.');\n```\n\n##### Fetching updated records\n\nTo fetch updated records, use enable the `fetch` meta key:\n\n```javascript\nvar updatedUsers = await User.update({name:'Finn'})\n.set({\n  name:'Jake'\n})\n.fetch();\n\nsails.log(`Updated all ${updatedUsers.length} user${updatedUsers.length===1?'':'s'} named \"Finn\" to have the name \"Jake\".  Here they are now:`);\nsails.log(updatedUsers);\n```\n\n### Notes\n> + This method can be used with [`await`](https://github.com/mikermcneil/parley/tree/49c06ee9ed32d9c55c24e8a0e767666a6b60b7e8#usage), promise chaining, or [traditional Node callbacks](https://sailsjs.com/documentation/reference/waterline-orm/queries/exec).\n> + This method can be used to replace an entire collection association (for example, a user&rsquo;s list of friends), achieving the same result as the [`replaceCollection` method](https://sailsjs.com/documentation/reference/waterline-orm/models/replace-collection).  To modify items in a collection individually, use the [`addToCollection`](https://sailsjs.com/documentation/reference/waterline-orm/models/add-to-collection) or [removeFromCollection](https://sailsjs.com/documentation/reference/waterline-orm/models/remove-from-collection) methods.\n\n\n<docmeta name=\"displayName\" value=\".update()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/models/updateOne.md",
    "content": "# `.updateOne()`\n\nUpdate the record that matches the given criteria, if such a record exists.\n\n```usage\nvar updatedRecord = await Something.updateOne(criteria)\n.set(valuesToSet);\n```\n\n> Before attempting to modify the database, Waterline will check to see if more than one record matches the given criteria; if so, it will throw an error instead of proceeding.\n\n\n### Usage\n\n|   |     Argument        | Type              | Details                            |\n|---|:--------------------|-------------------|:-----------------------------------|\n| 1 | criteria            | ((dictionary))    | The [Waterline criteria](https://sailsjs.com/documentation/concepts/models-and-orm/query-language) to use for matching the record in the database.\n| 2 | valuesToSet         | ((dictionary))    | A dictionary (plain JavaScript object) of values that all matching records should be updated to have.  _(Note that if this model is in [\"schemaful\" mode](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?schema), then any extraneous keys will be silently omitted.)_\n\n> **Note**: For performance reasons, as of Sails v1.0 / Waterline 0.13, the `valuesToSet` object passed into this model method will be mutated in-place in most situations (whereas in Sails/Waterline v0.12, this was not necessarily the case).\n\n##### Result\n\n| Type                | Description      |\n|:--------------------|:-----------------|\n| ((dictionary?))     | `updateOne()` never updates more than one record, so if a record is updated, then that record is provided as a result.  Otherwise, `undefined` is returned.\n\n\n##### Errors\n\nSee [Concepts > Models and ORM > Errors](https://sailsjs.com/documentation/concepts/models-and-orm/errors) for examples of negotiating errors in Sails and Waterline.\n\n\n### Example\n\n```javascript\nvar updatedUser = await User.updateOne({ firstName:'Pen' })\n.set({\n  firstName:'Finn'\n});\n\nif (updatedUser) {\n  sails.log('Updated the user named \"Pen\" so that their new name is \"Finn\".');\n}\nelse {\n  sails.log('The database does not contain a user named \"Pen\".');\n}\n```\n\n\n### Notes\n> + This method **does not support .fetch()**, because it _always_ returns the modified record if one was matched.\n> + This method can be used with [`await`](https://github.com/mikermcneil/parley/tree/49c06ee9ed32d9c55c24e8a0e767666a6b60b7e8#usage), promise chaining, or [traditional Node callbacks](https://sailsjs.com/documentation/reference/waterline-orm/queries/exec).\n> + This method can be used to replace an entire collection association (for example, a user&rsquo;s list of friends), achieving the same result as the [`replaceCollection` method](https://sailsjs.com/documentation/reference/waterline-orm/models/replace-collection).  To modify items in a collection individually, use the [`addToCollection`](https://sailsjs.com/documentation/reference/waterline-orm/models/add-to-collection) or [removeFromCollection](https://sailsjs.com/documentation/reference/waterline-orm/models/remove-from-collection) methods.\n\n\n<docmeta name=\"displayName\" value=\".updateOne()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/models/validate.md",
    "content": "# `.validate()`\n\nVerify that a value would be valid for a given attribute, then return it, loosely coerced.\n\n```usage\nSomething.validate(attrName, value);\n```\n\n> This validates (and potentially coerces) the provided data as if it was one of the values passed into [`.update()`](https://sailsjs.com/documentation/reference/waterline-orm/models/update).  You might think about it like a \"dry run\".\n\n### Usage\n\n| # | Description   | Accepted Data Types          | Required ? |\n|---|---------------|------------------------------|:-----------|\n| 1 | attrName      | ((string))                   | The name of the attribute to validate against. |\n| 2 | value         | ((ref))                      | The value to validate/normalize. |\n\n### Example\n\nCheck the given string and return a normalized version.\n> Note that if normalization is not possible, this throws an error.  **Be careful: You must manually handle any error thrown from within an asynchronous callback.**\n\n```javascript\nUser.validate('emailAddress', req.param('email'));\nUser.validate('password', req.param('password'));\n```\n\n##### Negotiating errors\n\nThe `.validate()` method can throw any of the usage errors you might see when calling `.update()`.  For example:\n\n```javascript\ntry {\n  var normalizedBalance = BankAccount.validate('balance', '$349.86');\n} catch (err) {\n  switch (err.code) {\n    case 'E_VALIDATION':\n      // => '[Error: Invalid `bankAccount`]'\n      _.each(e.all, function(woe){\n        sails.log(woe.attrName+': '+woe.message);\n      });\n      break;\n    default:\n      throw err;\n  }\n}\n```\n\n### Notes\n> + This is a synchronous method, so you don't need to use `await`, promise chaining, or traditional Node callbacks.\n> + `.validate()` is exposed as a separate method for convenience.  You can always simply call `.create()` or `.update()`, _instead_ of calling `.validate()` first, since those model methods apply the same checks automatically.\n> + `.validate()` is useful when implementing use cases where it is beneficial or more aesthetically pleasing (/[DRY](https://en.wikipedia.org/wiki/Don't_repeat_yourself)) to reuse your model validations for other purposes.  For example, you might want to validate some untrusted data before communicating with a 3rd party API like Mailgun or Stripe, or you might just want to run certain model validations initially to make some code easier to reason about.\n> + `.validate()` does not communicate with the database, and thus it only detects _logical failures_ such as type safety errors and high-level validation rule violations.  It cannot detect problems with _physical-layer_ constraints like uniqueness, since those constraints are checked by the underlying database, not by Sails or Waterline.\n\n\n<docmeta name=\"displayName\" value=\".validate()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/queries/catch.md",
    "content": "# `.catch()`\n\nExecute a Waterline [query instance](https://sailsjs.com/documentation/reference/waterline-orm/queries) using promises.\n\n```usage\n.catch(callback)\n```\n\n> As of Sails v1 and Node.js v8, you can take advantage of [`await`](https://sailsjs.com/documentation/reference/waterline-orm/queries) instead of using this method.\n\n### Usage\n\n|   |     Argument        | Type                                         | Details                            |\n|---|:--------------------|----------------------------------------------|:-----------------------------------|\n| 1 |   filter            | ((dictionary?))                              | An optional dictionary whose properties will be checked against the error. If they all match, then the callback will run. Otherwise, it won't.\n| 2 |   callback          | ((function))                                 | A function that runs if the query fails.<br/><br/> Takes the error as its argument.\n\n\n##### Callback\n\n|   |     Argument        | Type                | Details |\n|---|:--------------------|---------------------|:---------------------------------------------------------------------------------|\n| 1 |   _err_             | ((Error?))          | The Error that occurred, or `undefined` if there were no errors.\n\n\n### Example\n\nTo look up the user with the specified email address:\n\n```javascript\nUser.findOne({\n  email: req.param('email')\n})\n.then(function (user){\n  if(!user) { return res.notFound(); }\n  return res.json(user);\n})\n// If there was some kind of usage / validation error\n.catch({ name: 'UsageError' }, function (err) {\n  return res.badRequest(err);\n})\n// If something completely unexpected happened.\n.catch(function (err) {\n  return res.serverError(err);\n});\n```\n\n\n### Notes\n> + Whenever possible, it is recommended that you use `await` instead of calling this method.\n> + This is an alternative to `.exec()`.  When combined with `.then()`, it provides the same functionality.\n> + The `.catch()` function also returns a promise to allow for chaining.  This is not recommended for any but the most advanced users of promises due to the complex (and arguably non-intuitive) behavior of chained `.catch()` calls.\n> + For more information, see the [bluebird `.catch()` api docs](http://bluebirdjs.com/docs/api/catch).\n\n\n\n<docmeta name=\"displayName\" value=\".catch()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/queries/decrypt.md",
    "content": "# `.decrypt()`\n\nDecrypt any auto-encrypted attributes in the records returned for this particular query.\n\n\n```usage\nquery.decrypt()\n```\n\n### Usage\n\nThis method doesn't accept any arguments.\n\n\n### Example\nTo retrieve user records with `ssn` decrypted:\n```javascript\nawait User.find({fullName: 'Finn Mertens'}).decrypt();\n// =>\n// [ { id: 4, fullName: 'Finn Mertens', ssn: '555-55-5555' } ]\n```\nIf the records were retrieved without `.decrypt()`, you would get:\n```javascript\nawait User.find({fullName: 'Finn Mertens'});\n// =>\n// [ { id: 4, fullName: 'Finn Mertens', ssn: 'YWVzLTI1Ni1nY20kJGRlZmF1bHQ=$F4Du3CAHtmUNk1pn$hMBezK3lwJ2BhOjZ$6as+eXnJDfBS54XVJgmPsg' } ]\n```\n\n### Notes\n> * This is just a shortcut for [`.meta({decrypt: true})`](https://sailsjs.com/documentation/reference/waterline-orm/queries/meta)\n\n<docmeta name=\"displayName\" value=\".decrypt()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/queries/exec.md",
    "content": "# `.exec()`\n\nExecute a Waterline [query instance](https://sailsjs.com/documentation/reference/waterline-orm/queries).\n\n```usage\n.exec(function (err, result) {\n\n})\n```\n\n> As of Sails v1 and Node.js v8, you can take advantage of [`await`](https://sailsjs.com/documentation/reference/waterline-orm/queries) instead of using this method.\n\n### Usage\n\n|   |     Argument        | Type                                         | Details                            |\n|---|:--------------------|----------------------------------------------|:-----------------------------------|\n| 1 |    callback         | ((function))                                 | The Node-style callback that will be called when the query completes, successfully or otherwise.\n\n##### Callback\n\n|   |     Argument        | Type                | Details |\n|---|:--------------------|---------------------|:---------------------------------------------------------------------------------|\n| 1 |    _err_            | ((Error?))          | The Error that occurred, or `undefined` if there were no errors.\n| 2 |    _result_         | ((Ref?))            | The result from the database, if any.  Exact data type depends on the query.  If an error occurred (i.e. `err` is truthy), then this result argument should be ignored.\n\n\n\n\n\n### Example\n\n```javascript\nZookeeper.find().exec((err, zookeepers)=>{\n  if (err) {\n    return res.serverError(err);\n  }\n\n  // would you look at all those zookeepers?\n  return res.json(zookeepers);\n});\n//\n// (don't put code out here)\n```\n\n\n### Notes\n> + If you don't run `.exec()` or use promises, your query will not execute. For help using `.exec()` with model methods like `.find()`, read more about the [chainable query object](https://sailsjs.com/documentation/reference/waterline-orm/queries).\n\n\n\n\n<docmeta name=\"displayName\" value=\".exec()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/waterline/queries/fetch.md",
    "content": "# `.fetch()`\n\nTell Waterline (and the underlying database adapter) to send back records that were updated/destroyed/created when performing an [`.update()`](https://sailsjs.com/documentation/reference/waterline-orm/models/update), [`.create()`](https://sailsjs.com/documentation/reference/waterline-orm/models/create), [`.createEach()`](https://sailsjs.com/documentation/reference/waterline-orm/models/create-each) or [`.destroy()`](https://sailsjs.com/documentation/reference/waterline-orm/models/destroy) query.  Otherwise, no data will be returned (or if you are using callbacks, the second argument to the `.exec()` callback will be `undefined`).\n\n> Warning: This is not recommended for update/destroy queries that affect large numbers of records.\n\n\n```usage\n.fetch()\n```\n\n### Usage\n\nThis method doesn't accept any arguments.\n\n\n### Example\n\n```javascript\nvar newUser = await User.create({ fullName: 'Alice McBailey' }).fetch();\nsails.log(`Hi, ${newUser.fullName}!  Your id is ${newUser.id}.`);\n```\n\n\n### Notes\n> * This is just a shortcut for [`.meta({fetch: true})`](https://sailsjs.com/documentation/reference/waterline-orm/queries/meta)\n\n<docmeta name=\"displayName\" value=\".fetch()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/queries/intercept.md",
    "content": "# `.intercept()`\n\nCapture and intercept the specified error, automatically modifying and re-throwing it, or specifying a new error to be thrown instead.    (Still throws.)\n\n```usage\n.intercept(filter, handler)\n```\nor\n+ `.intercept(handler)` _(to intercept all errors)_\n\n\n\n### Usage\n|   |     Argument    | Type                | Details    |\n|---|-----------------|---------------------|:-----------|\n| 1 | _filter_        | ((string)) or ((dictionary)) | The code of the error that you want to intercept, or a dictionary of criteria for identifying the error to intercept.  (If not provided, ALL errors will be intercepted.) |\n| 2 | handler         | ((function)) or ((string))     | A [procedural parameter](https://en.wikipedia.org/wiki/Procedural_parameter) which Sails calls automatically if the anticipated error is thrown.  It will receive the argument specified in the \"Handler\" usage table below. The handler should return the modified Error, a new Error, or (if applicable) a [special exit signal](https://sailsjs.com/documentation/concepts/actions-and-controllers#?exit-signals). <br/><br/> Alternatively, instead of a function, a string may be provided.  This amounts to the same thing as passing in a handler function that simply returns the string.  (Convenient when using actions2.) |\n\n##### Handler\n|   |     Argument        | Type                | Details\n|---|---------------------|---------------------|:------------------------|\n| 1 | err                 | ((Error))           | The anticipated Error being intercepted. |\n\nReturn an Error instance or (if applicable) a [special exit signal](https://sailsjs.com/documentation/concepts/actions-and-controllers#?exit-signals) that will be thrown from the original logic instead of throwing the intercepted error.\n\n> .intercept() is for intercepting a certain kind of error (or all errors). If you chain on .intercept(), and it matches the error that occurs, then the underlying logic will throw. But what it throws is determined by what your handler function returns.\n\n\n\n### Example\n\nIf every user record in an app needs to have a unique email address, you may want to ensure that error is formatted in a such a way that the appropriate message will be displayed to the end user. To intercept that error:\n```javascript\nvar newUserRecord = await User.create({\n  emailAddress: inputs.emailAddress,\n  fullName: inputs.fullName,\n})\n.intercept('E_UNIQUE', ()=>{ return new Error('There is already an account using that email address!') })\n.fetch();\n```\n\nOr, to handle the same error inside of an [actions2 action](https://sailsjs.com/documentation/concepts/actions-and-controllers#?actions-2), using a [special exit signal](https://sailsjs.com/documentation/concepts/actions-and-controllers#?exit-signals); instead of an Error instance:\n```javascript\nvar newUserRecord = await User.create({\n  emailAddress: inputs.emailAddress,\n  fullName: inputs.fullName,\n})\n.intercept('E_UNIQUE', ()=>'emailAlreadyInUse')\n.fetch();\n```\n\n### Notes\n\n> Note that the usage in our example above could have also been written more concisely as:\n>\n> ```js\n> .intercept('E_UNIQUE', 'emailAlreadyInUse')\n> ```\n>\n> Or less concisely as:\n>\n> ```js\n> .intercept({ code: 'E_UNIQUE' }, ()=>{ return 'emailAlreadyInUse'; })\n> ```\n>\n> For more examples and further explanation of how `.intercept()` works, check out [this related conversation](https://gitter.im/balderdashy/sails?at=5ab44f512b9dfdbc3a113e2f).\n\n<docmeta name=\"displayName\" value=\".intercept()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/queries/limit.md",
    "content": "# `.limit()`\n\nSet the maximum number of records to retrieve when executing a [query instance](https://sailsjs.com/documentation/reference/waterline-orm/queries).\n\n```usage\n.limit(maximum)\n```\n\n### Usage\n|   |     Argument        | Type         | Details    |\n|---|:--------------------|--------------|------------|\n| 1 |  maximum            |  ((number))  | The maximum number of records to retrieve. |\n\n### Example\n\nTo retrieve records for up to 10 users named Jake:\n\n```javascript\nvar jakes = await User.find({ name: 'Jake' }).limit(10);\n\nreturn res.json(jakes);\n```\n\n### Notes\n> * If you set the limit to 0, the query will always return an empty array.\n> * If the limit is greater than the number of records matching the query criteria, all of the matching records will be returned.\n> * The .find() method returns a chainable object if you don't supply a callback.  This method can be chained to .find() to further filter your results.\n\n\n<docmeta name=\"displayName\" value=\".limit()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/queries/meta.md",
    "content": "# `.meta()`\n\nProvide additional options to Waterline when executing a [query instance](https://sailsjs.com/documentation/reference/waterline-orm/queries).\n\n```usage\n.meta(options)\n```\n\n### Usage\n|   |     Argument    | Type                | Details    |\n|---|-----------------|---------------------|:-----------|\n| 1 |  options        |      ((dictionary))       | A dictionary (plain JS object) of options.  See all supported options (aka &ldquo;meta keys&rdquo;) in the table below.         |\n\n##### Supported options\n\nOption                                | Type        | Default  | Details\n:------------------------------------ |-------------|:---------| :------------------------------\nfetch                                 | ((boolean)) | false    | When performing [`.update()`](https://sailsjs.com/documentation/reference/waterline-orm/models/update), [`.create()`](https://sailsjs.com/documentation/reference/waterline-orm/models/create), [`.createEach()`](https://sailsjs.com/documentation/reference/waterline-orm/models/create-each), or [`.destroy()`](https://sailsjs.com/documentation/reference/waterline-orm/models/destroy) queries, set this to `true` to tell the database adapter to send back all records that were updated/destroyed.  Otherwise, the second argument to the `.exec()` callback is `undefined`.  Warning: Enabling this key may cause performance issues for update/destroy queries that affect large numbers of records.\ncascade                               | ((boolean)) | false    | If set to `true` on a [`.destroy()`](https://sailsjs.com/documentation/reference/waterline-orm/models/destroy), this tells Waterline to perform a _\"virtual cascade\"_ for every deleted record.  Thus, deleting a record with a 2-way, _plural association_ ([one-to-many](https://sailsjs.com/documentation/concepts/models-and-orm/associations/one-to-many) or [many-to-many](https://sailsjs.com/documentation/concepts/models-and-orm/associations/many-to-many)) will also cleanly remove all links to other records (by removing join table rows or setting foreign key values to `null`).<br/><br/>This may be desirable if database size is a concern, or if primary keys may be reused for records, but it can negatively impact performance on `.destroy()` calls since it involves executing more queries.<br/><br/>**The `cascade` meta key should only be used with databases like MongoDB** that [don't support](http://stackoverflow.com/questions/20370791/what-is-the-recommended-equivalent-of-cascaded-delete-in-mongodb-for-nm-relatio) cascading delete as a native feature.  If you need cascading delete and your database supports it natively (e.g. MySQL or PostgreSQL), you'll enjoy improved performance by simply adding a [CASCADE constraint](https://dev.mysql.com/doc/refman/5.7/en/create-table-foreign-keys.html) at the physical layer (e.g. phpMyAdmin, Sequel Pro, mySQL prompt, etc.), rather than relying on Waterline's virtual cascade to take effect at runtime.\nskipAllLifecycleCallbacks             | ((boolean)) | false    | Set to `true` to prevent [lifecycle callbacks](https://sailsjs.com/documentation/concepts/models-and-orm/lifecycle-callbacks) from running during the execution of the query.\nskipRecordVerification                | ((boolean)) | false    | Set to `true` to skip Waterline's post-query verification pass of any records returned from the adapter(s).  Useful for tools like sails-hook-orm's automigrations, or to disable warnings for use cases where you know that pre-existing records in the database do not match your model definitions.\nskipExpandingDefaultSelectClause      | ((boolean)) | false    | Set to `true` to force Waterline to skip expanding the `select` clause in criteria when it forges stage 3 queries (i.e. the queries that get passed in to adapter methods).  Normally, if a model declares `schema: true`, then the S3Q `select` clause is expanded to an array of column names, even if the S2Q had factory default `select`/`omit` clauses (which is also what it would have if no explicit `select` or `omit` clauses were included in the original query). Useful for tools like sails-hook-orm's automigrations, where you want temporary access to properties that aren't necessarily in the current set of attribute definitions.  **Warning: Do not use this flag in your web application backend, or at least [ask for help](https://sailsjs.com/support) first.**\ndecrypt                               | ((boolean)) | false    | Set to `true` to decrypt any [auto-encrypted](https://sailsjs.com/documentation/concepts/models-and-orm/attributes#?encrypt) data in the records.\nencryptWith                           | ((string))  | 'default' | The id of a custom key to use for encryption for this particular query. (For decryption, the appropriate key is always used based on the data being decrypted.)\nmakeLikeModifierCaseInsensitive       | ((boolean)) | false     | Set to `true` to make your query case-insensitive (only for use with the MongoDB adapter). |\n\n### Example\n\n```javascript\nvar newUser = await User.create({name: 'alice'})\n.meta({fetch: true});\n\nreturn res.json(newUser);\n```\n\n\n### Notes\n> * The [`.fetch()` method](https://sailsjs.com/documentation/reference/waterline-orm/queries/fetch) is a shorthand for `.meta({fetch: true})`.\n> * In order for `cascade` to work when the `fetch` meta key is _not_ also `true`, Waterline must do an extra `.find().select('id')` before actually performing the `.destroy()` in order to get the IDs of the records that would be destroyed.\n> * Rather than using the `.meta()` query method, you can also set meta keys for a query by passing in a dictionary after the explicit callback.  For example: `User.create({name: 'alice'}, function(err, newUser){/*...*/}, { fetch: true })`.\n\n<docmeta name=\"displayName\" value=\".meta()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/queries/populate.md",
    "content": "# `.populate()`\n\nModify a [query instance](https://sailsjs.com/documentation/reference/waterline-orm/queries) so that, when executed, it will populate child records for the specified collection, optionally filtering by `subcriteria`.  Populate may be called more than once on the same query, as long as each call is for a different association.\n\n\n```usage\n.populate(association, subcriteria)\n```\n\n\n### Usage\n\n|   |     Argument           | Type                                         | Details                            |\n|---|:-----------------------|----------------------------------------------|:-----------------------------------|\n| 1 |    association         | ((string))                                   | The name of the association to populate.  e.g. `snacks`.\n| 2 |    _subcriteria_       | ((dictionary?))                              | Optional.  When populating `collection` associations between two models which reside in the same database, a [Waterline criteria](https://sailsjs.com/documentation/concepts/models-and-orm/query-language) may be specified as a second argument to populate.  This will be used for filtering, sorting, and limiting the array of associated records (e.g. snacks) associated with each primary record.\n\n> **Important:** Both the basic join polyfill (cross-datastore populate, or populate between models whose configured adapter does not provide a `.join()` implementation) and the subcriteria argument to `.populate()` are fully supported in Sails **individually**. However, using the subcriteria argument to `.populate()` at the same time as the join polyfill is experimental. This means that, if an association spans multiple datastores or its datastore's configured adapter does not support a physical layer join, then you should not rely on the subcriteria argument to `.populate()`. If you try that in production, you will see a warning logged to the console. SQL adapters such as [sails-postgresql](https://github.com/balderdashy/sails-postgresql) and [sails-mysql](https://github.com/balderdashy/sails-mysql) support native joins and should be okay to use the subcriteria argument.\n\n> **Note:** If you are using `schema: false`, only defined attributes will be populated.\n\n### Example\n\n##### Populating a model association\n\nThe following finds any users named Finn in the database and, for each one, also populates their dad:\n```javascript\nvar usersNamedFinn = await User.find({name:'Finn'}).populate('dad');\n\nsails.log('Wow, there are %d users named Finn.', usersNamedFinn.length);\nsails.log('Check it out, some of them probably have a dad named Joshua or Martin:', usersNamedFinn);\n\nreturn res.json(usersNamedFinn);\n```\n\nThis might yield:\n\n```javascript\n[\n  {\n    id: 7392,\n    age: 13,\n    name: 'Finn',\n    createdAt: 1451088000000,\n    updatedAt: 1545782400000,\n    dad: {\n      id: 108,\n      age: 47,\n      name: 'Joshua',\n      createdAt: 1072396800000,\n      updatedAt: 1356480000000,\n      dad: null\n    }\n  },\n  // ...more users\n]\n```\n\n\n##### Populating a collection association\n\n> This example uses the optional subcriteria argument.\n\nThe following finds any users named Finn in the database and, for each one, also populates their three hippest purple swords, in descending order of hipness:\n\n```javascript\n// Warning: This is only safe to use on large datasets if both models are in the same database,\n// and the adapter supports optimized populates.\n// (e.g. cannot do this with the `User` model in PostgreSQL and the `Sword` model in MongoDB)\nvar usersNamedFinn = await User.find({ name:'Finn' })\n.populate('currentSwords', {\n  where: {\n    color: 'purple'\n  },\n  limit: 3,\n  sort: 'hipness DESC'\n});\n\n// Note that Finns without any swords are still included -- their `currentSwords` arrays will just be empty.\nsails.log('Wow, there are %d users named Finn.', usersNamedFinn.length);\nsails.log('Check it out, some of them probably have non-empty arrays of purple swords:', usersNamedFinn);\n\nreturn res.json(usersNamedFinn);\n```\n\nThis might yield:\n\n```javascript\n[\n  {\n    id: 7392,\n    age: 13,\n    name: 'Finn',\n    createdAt: 1451088000000,\n    updatedAt: 1545782400000,\n    dad: 108,//<< not populated\n    swords: [//<< populated\n      {\n        id: 9,\n        title: 'Grape Soda Sword',\n        color: 'purple',\n        createdAt: 1540944000000,\n        updatedAt: 1540944000000\n      },\n      // ...more swords\n    ]\n  },\n  // ...more users\n]\n```\n\n\n<docmeta name=\"displayName\" value=\".populate()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/waterline/queries/queries.md",
    "content": "# Working with queries\n\n**Queries** (aka _query instances_) are the chainable deferred objects returned from model methods like `.find()` and `.create()`.  They represent a not-quite-yet-fulfilled intent to fetch or modify records from the database.\n\n\n```usage\nvar query = Zookeeper.find();\n```\n\nThe purpose of query instances is to provide a convenient, chainable syntax for working with your models.  Methods like `.populate()`, `.where()`, and `.sort()` allow you to refine database calls _before_ they're sent down the wire. Then, when you're ready to fire the query off to the database, you can just `await` it.\n\n> If you are using an older version of Node.js that does not support JavaScript's `await` keyword, you can use `.exec()` or `.then()`+`.catch()`.  See the section on \"Promises and Callbacks\" below for more information.\n\nMost of the time, you won't think about query instances as objects _per se_, but as just another part of the syntax for communicating with the database.  In fact, you may already be using these objects in your Sails app! If so, the following syntax should look familiar:\n\n```js\nvar zookeepers = await Zookeeper.find();\n```\n\nIn this example, the call to `Zookeeper.find()` returns a query instance, but _doesn't actually do anything_ until it is executed using the `await` keyword, and then the result is assigned to the `zookeepers` variable.\n\n\n### How it works\n\nWhen you **execute** a query using `await`, a lot happens.\n\n```js\nawait query;\n```\n\nFirst, the query is \"shaken out\" by Waterline core into a [normalized query](https://sailsjs.com/documentation/concepts/models-and-orm/query-language).  Then it passes through the relevant Waterline adapter(s) for translation to the raw query syntax of your database(s) (e.g. Redis or Mongo commands, various SQL dialects, etc.)  Next, each involved adapter uses its native Node.js database driver to send the query out over the network to the corresponding physical database.\n\nWhen the adapter receives a response, it is marshalled to the Waterline interface spec and passed back up to Waterline core, where it is integrated with any other raw adapter responses into a coherent result set.  At that point, it undergoes one last normalization before being passed back to \"userland\" (i.e. your code) for consumption by your app.\n\n\n### Error handling\n\nYou can use a try/catch to handle specific errors, if desired:\n\n```js\nvar zookeepersAtThisZoo;\ntry {\n  zookeepersAtThisZoo = await Zookeeper.find({\n    zoo: req.param('zoo')\n  }).limit(30);\n} catch (err) {\n  switch (err.name) {\n    case 'UsageError': return res.badRequest(err);\n    default: throw err;\n  }\n}\n\nreturn res.json(zookeepersAtThisZoo);\n```\n\nThe specific kinds of errors you could receive vary based on what kind of query you are executing.  See the reference docs for the various query methods for more specific information.\n\n\n### Promises and callbacks\n\nAs an alternative to `await`, Sails and Waterline provide support for callbacks and promise-chaining.  In general, you should **use `await` whenever possible**; it leads to simpler, easier-to-understand code, and helps prevent DDoS vulnerabilities and stability issues that can arise from throwing uncaught exceptions in asynchronous callbacks.  That said, sometimes it is necessary to maintain backwards compatibility with an older version of Node.js.  For this reason, all queries in Sails and Waterline expose an [`.exec()`](https://sailsjs.com/documentation/reference/waterline-orm/queries/exec) method.\n\n\n```js\nZookeeper.find().exec(function afterFind(err, zookeepers) {\n\n  // Careful!  Do not throw an error in here without a `try` block!\n  // (Even a simple typo or null pointer exception could crash the process!)\n\n  if (err) {\n    // uh oh\n    // (handle error; e.g. `return res.serverError(err)`)\n    return;\n  }\n\n  // would you look at all those zookeepers?\n  // (now let's do the next thing;\n  //  e.g. `_.reduce(zookeepers, ...)` and/or `return res.json(zookeepers)`)\n  // …\n});\n//\n// (don't put code out here)\n```\n\n\nAs shown in the example above, the query is not executed right away, but notice that instead of using `await` to execute the query and wait for its result, we use the traditional `.exec()` method with a callback function.  With this usage, we cannot rely on try/catch and normal error handling in JavaScript to take care of our errors!  Instead, we have to manually handle them in our callback to `.exec()`.  This style of error handling is the traditional approach used in Node.js apps prior to ~Summer 2017.\n\n\nUnder the covers, Sails and Waterline also provide a minimalist integration with the [Bluebird](https://github.com/petkaantonov/bluebird) promise library, exposing `.then()` and `.catch()` methods.\n\n\n```js\nZookeeper.find()\n.then(function (zookeepers) {...})\n.catch(function (err) {...});\n//\n// (don't put code out here)\n```\n\nIn this example, the callback passed into `.catch()` is equivalent to the contents of the `if(err) {}` block from the `.exec()` example above (e.g. `res.serverError()`).  Similarly, the `.then()` callback is equivalent to the code below the `if(err) {}` and early `return`.\n\nIf you are a fan of promises and have a reasonable amount of experience with them, you should have no problem working with this interface.  However if you are not very familiar with promises, or don't care one way or another, you will probably have an easier time working with `.exec()`, since it uses standard Node.js callback conventions.\n\n> If you decide to use traditional promise chaining for a particular query in your app, please make sure that you provide callbacks for both `.then()` _and_ `.catch()`.  Otherwise errors could go unhandled, and unpleasant race conditions and memory leaks could ensue. This is not just a Sails or Waterline concept. Rather, it's something to be aware of whenever you implement this type of usage in JavaScript&mdash;particularly in Node.js&mdash;since unhandled errors in server-side code tend to be more problematic than their client-side counterparts.   Omitting `.catch()` is equivalent to ignoring the `err` argument in a conventional Node callback, and it is similarly insidious.  In fact, this is hands-down one of the most common sources of bugs for Node.js developers of all skill levels.\n>\n> Proper error handling is particularly easy to neglect if you're new to asynchronous code. Once you've been at it for a while, you'll get in the habit of handling your asynchronous errors right after (or even better, right before) you write code that handles the successful case. Habits like this immunize your apps to those common bugs discussed above.\n>\n> (Better yet: just use `await`!)\n\n\n\n\n### Notes\n\n> + A query instance is not _exactly_ the same thing as a Promise, but it's close enough for our purposes.  The difference is that a query instance in Sails and Waterline is actually a Deferred, as implemented by the [parley](https://npmjs.com/package/parley) library.  That means it doesn't start executing immediately.  Instead, it only begins executing when you kick it off with either `await`, `.exec()`, `.then()`, or `.toPromise()`.\n> + A Node-style callback can be passed directly as a final argument to model methods (e.g. `.find()`).  In this case, the query will be executed immediately, and model methods _will not_ return a query instance (instead, the Node-style callback you provided will be triggered when the query is complete).  Unless you are doing something very advanced, you are generally better off sticking to standard usage; i.e. calling `.exec()` or calling `.then()` and `.catch()`.\n\n\n\n<docmeta name=\"displayName\" value=\"Queries\">\n"
  },
  {
    "path": "docs/reference/waterline/queries/skip.md",
    "content": "# `.skip()`\n\nIndicate the number of records to skip before returning the results from executing a [query instance](https://sailsjs.com/documentation/reference/waterline-orm/queries).\n\n```usage\n.skip(numRecordsToSkip)\n```\n\n\n### Usage\n\n|   |     Argument        | Type            | Details    |\n|---|:--------------------|-----------------|------------|\n| 1 |  numRecordsToSkip   | ((number))      | The number of records to skip. |\n\n\n### Example\n\nTo retrieve records for all but the original user named Jake:\n\n```javascript\nvar fakeJakes = await User.find({ name: 'Jake' });\n.skip(1);\n\nreturn res.json(fakeJakes);\n```\n\n### Notes\n> If the &ldquo;skip&rdquo; value is greater than the number of records matching the query criteria, the query will return an empty array.\n> The .find() method returns a chainable object if you don't supply a callback.  This method can be chained to .find() to further filter your results.\n\n\n<docmeta name=\"displayName\" value=\".skip()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/queries/sort.md",
    "content": "# `.sort()`\n\nSet the order in which retrieved records should be returned when executing a [query instance](https://sailsjs.com/documentation/reference/waterline-orm/queries).\n\n```usage\n.sort(sortClause)\n```\n\n### Usage\n|   |     Argument     | Type                | Details |\n|---|:-----------------|---------------------|------------|\n| 1 |  sortClause      | ((string)) _or_ ((array)) of ((dictionary)) | If specified as a string, this should be formatted as: an attribute name, followed by a space, followed by either `ASC` or `DESC` to indicate an _ascending_ or _descending_ sort (e.g. `name ASC`). <br/>If specified as an array, then each array item should be a dictionary with a single key representing the attribute to sort by, whose value is either `ASC` or `DESC`. The array syntax allows for sorting by multiple attributes, using the array order to establish precedence <br/>(e.g. `[ { name: 'ASC' }, { age:  'DESC'} ]`). |\n\n### Example\n\nTo sort users named Jake by age, in ascending order:\n```javascript\nvar users = await User.find({ name: 'Jake'})\n.sort('age ASC');\n\nreturn res.json(users);\n```\n\nTo sort users named Finn, first by age, then by when they joined:\n```javascript\nvar users = await User.find({ name: 'Finn'})\n.sort([\n  { age: 'ASC' },\n  { createdAt: 'ASC' },\n]);\n\nreturn res.json(users);\n```\n\n### Notes\n> The .find() method returns a chainable object if you don't supply a callback.  This method can be chained to .find() to further filter your results.\n\n<docmeta name=\"displayName\" value=\".sort()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/queries/then.md",
    "content": "# `.then()`\n\nExecute a Waterline [query instance](https://sailsjs.com/documentation/reference/waterline-orm/queries) using promises.\n\n```usage\n.then(callback)\n```\n\n> As of Sails v1 and Node.js v8, you can take advantage of [`await`](https://sailsjs.com/documentation/reference/waterline-orm/queries) instead of using this method.\n\n\n### Usage\n\n|   |     Argument        | Type                                         | Details                            |\n|---|:--------------------|----------------------------------------------|:-----------------------------------|\n| 1 |   callback          | ((function))                                 | A function that runs if the query successfully completes<br/><br/> Takes the result of the query as its argument.\n\n\n##### Callback\n\n|   |     Argument        | Type                | Details |\n|---|:--------------------|---------------------|:---------------------------------------------------------------------------------|\n| 1 |    _result_         | ((Ref?))            | The result from the database, if any.  Exact data type depends on the query.\n\n\n### Example\n\nTo look up the user with the specified email address:\n\n```javascript\nUser.findOne({\n  email: req.param('email')\n})\n.then(function (user){\n  if (!user) { return res.notFound(); }\n  return res.json(user);\n})\n.catch(function (err) { return res.serverError(err); });\n```\n\n\n### Notes\n> + Whenever possible, it is recommended that you use `await` instead of calling this method.\n> + This is an alternative to `.exec()`.  When combined with `.catch()`, it provides the same functionality.\n> + The `.then()` function returns a promise to allow for chaining.\n> + For more information, see the [bluebird `.then()` api docs](http://bluebirdjs.com/docs/api/then).\n\n\n\n\n<docmeta name=\"displayName\" value=\".then()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/queries/toPromise.md",
    "content": "# `.toPromise()`\n\nBegin executing a Waterline [query instance](https://sailsjs.com/documentation/reference/waterline-orm/queries) and return a promise.\n\n```usage\n.toPromise();\n```\n\n> This is an alternative to `.exec()`.\n\n\n### Notes\n\n> + For more information, see the [bluebird `Promise.promisify()` API docs](http://bluebirdjs.com/docs/api/promise.promisify.html).\n\n<docmeta name=\"displayName\" value=\".toPromise()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/queries/tolerate.md",
    "content": "# `.tolerate()`\n\nTolerate (swallow) the specified error, and return a new result value (or `undefined`) instead.  (Don't throw.)\n\n```usage\n.tolerate(filter, handler)\n```\n\n_Or:_\n+ `.tolerate(filter)`\n+ `.tolerate(handler)` _(to tolerate all errors)_\n\n\n### Usage\n|   |     Argument    | Type                | Details    |\n|---|-----------------|---------------------|:-----------|\n| 1 | filter          | ((string)) or ((dictionary)) | The code of the error that you want to intercept, or a dictionary of criteria for identifying the error to intercept. |\n| 2 | _handler_       | ((function?))        | An optional [procedural parameter](https://en.wikipedia.org/wiki/Procedural_parameter), called automatically by Sails if the anticipated error is thrown.  It receives the argument specified in the \"Handler\" usage table below. If specified, the handler should return a value that will be used as the result. If omitted, the anticipated error will be swallowed and the result of the query will be `undefined`. |\n\n##### Handler\n|   |     Argument        | Type                | Details\n|---|---------------------|---------------------|:------------------------|\n| 1 | err                 | ((Error))           | Your anticipated Error. |\n\n> `.tolerate()` is useful for tolerating a kind of error (or all errors). If you chain on `.tolerate()` and it matches the error that occurs, then the underlying logic won't throw. Instead, it returns the return value of the handler function you passed into .tolerate().\n\n\n\n\n### Example\n\nSay you're building an address book that doesn't allow records with duplicate email addresses. To instead swallow the error caused by entering a non-unique email address and update the existing contact:\n\n```javascript\nlet newOrExistingContact = await Contact.create({\n  emailAddress,\n  fullName\n})\n.fetch()\n.tolerate('E_UNIQUE');\n\nif(!newOrExistingContact) {\n  newOrExistingContact = await Contact.updateOne({ emailAddress })\n  .set({ fullName })\n  .fetch();\n}\n```\n\n\n\n<docmeta name=\"displayName\" value=\".tolerate()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/queries/usingConnection.md",
    "content": "# `.usingConnection()`\n\nSpecify an existing database connection to use for this [query](https://sailsjs.com/documentation/reference/waterline-orm/queries).\n\n```usage\n.usingConnection(connection);\n```\n\n### Usage\n\n|   |     Argument        | Type                                         | Details                            |\n|---|:--------------------|----------------------------------------------|:-----------------------------------|\n| 1 |   connection        | ((ref))                                      | An existing database connection obtained using [`.transaction()`](https://sailsjs.com/documentation/reference/waterline-orm/datastores/transaction) or [`.leaseConnection()`](https://sailsjs.com/documentation/reference/waterline-orm/datastores/lease-connection).\n\n### Example\n\nAn example of `.usingConnection()` usage can be found in the example for [`.transaction()`](https://sailsjs.com/documentation/reference/waterline-orm/datastores/transaction#?example).\n\n\n<docmeta name=\"displayName\" value=\".usingConnection()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/queries/where.md",
    "content": "# `.where()`\n\nSpecify a where clause for filtering a query.\n\n```usage\n.where(whereClause)\n```\n\n\n### Usage\n|   |     Arguments      | Type                | Details    |\n|---|:-------------------|---------------------|------------|\n| 1 |  whereClause          |  ((dictionary))     | The [where clause](https://sailsjs.com/documentation/concepts/models-and-orm/query-language) to use for matching records in the database. |\n\n\n### Example\n\nTo find all the users named Finn whose email addresses start with 'f':\n```javascript\nvar users = await User.find()\n.where({ name: 'Finn', 'emailAddress' : { startsWith : 'f' } });\n\nreturn res.json(users);\n```\n\n### Notes\n> The criteria provided in the `.where()` method takes precendence over the the criteria provided in `.find()`.\n\n> The `.find()` method returns a chainable object if you don't supply a callback.  This method can be chained to `.find()` to further filter your results.\n\n\n\n<docmeta name=\"displayName\" value=\".where()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/records/records.md",
    "content": "# Records\n\nIn Sails, [records](https://sailsjs.com/documentation/concepts/models-and-orm/records) come from model methods like `.find()` and represent data from your database. You can work with records just like you would any other data.\n\n```js\nvar orders = await Order.find();\n// `orders` is an array of records\n```\n\n### Working with populated records\nIf a record came from a query that used `.populate()`, it may contain populated values (or \"child records\") which represent the associated data. To add, remove, or replace these child records, use [model methods](https://sailsjs.com/documentation/reference/waterline-orm/models).\n\n\n<docmeta name=\"displayName\" value=\"Records\">\n\n"
  },
  {
    "path": "docs/reference/waterline/records/toJSON.md",
    "content": "# `.toJSON()`\n\n### Purpose\nWhenever Waterline retrieves a record, it checks whether or not the record&rsquo;s model has a [`customToJSON`](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?customtojson) method defined; if so, Waterline adds the method to the record as its `toJSON` property.  `toJSON` is _**not intended to be called directly in your code**_. Instead, it is used automatically when a record is serialized via a call to <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON()_behavior\" target=\"_blank\">`JSON.stringify()`</a>.  The [`res.json()` method](https://sailsjs.com/documentation/reference/response-res/res-json), in particular, stringifies objects in this way.\n\nWhen a [`customToJSON`](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?customtojson) is defined for a model, the `.toJSON()` method will be added to records retrieved via [`.find()`](https://sailsjs.com/documentation/reference/waterline-orm/models/find), [`.findOne()`](https://sailsjs.com/documentation/reference/waterline-orm/models/find-one), [`.findOrCreate()`](https://sailsjs.com/documentation/reference/waterline-orm/models/find-or-create) and [`.stream()`](https://sailsjs.com/documentation/reference/waterline-orm/models/stream), as well as those retrieved by setting the [`fetch` meta key](https://sailsjs.com/documentation/reference/waterline-orm/queries/meta) to `true` in calls to [`.create()`](https://sailsjs.com/documentation/reference/waterline-orm/models/create), [`.createEach()`](https://sailsjs.com/documentation/reference/waterline-orm/models/create-each), [`.update()`](https://sailsjs.com/documentation/reference/waterline-orm/models/update) and [`.destroy()`](https://sailsjs.com/documentation/reference/waterline-orm/models/destroy).  If any child records are attached via [`.populate()`](https://sailsjs.com/documentation/reference/waterline-orm/queries/populate), and the corresponding models have [`customToJSON`](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?customtojson) methods, then the child records will also have `.toJSON()` functions attached.\n\nSee the [customToJSON documentation](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?customtojson) for more info on how to customize the way your records are presented.\n\n<docmeta name=\"displayName\" value=\".toJSON()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/waterline/waterline.md",
    "content": "# Waterline (ORM)\n\nBy default, new Sails apps are bundled with an ORM called [Waterline](http://waterlinejs.org) (implemented in the [sails-hook-orm](http://npmjs.com/package/sails-hook-orm) dependency).\n\n> To learn more about using Waterline, start in [Concepts > Models & ORM](https://sailsjs.com/documentation/concepts/models-and-orm).\n\n### Reference\n\nThis section of the documentation contains a reference of all of the methods available at runtime on [models](https://sailsjs.com/documentation/reference/waterline-orm/models), [queries](https://sailsjs.com/documentation/reference/waterline-orm/queries), and [datastores](https://sailsjs.com/documentation/reference/waterline-orm/datastores).\n\n\n\n<docmeta name=\"displayName\" value=\"Waterline (ORM)\">\n\n"
  },
  {
    "path": "docs/reference/websockets/resourceful-pubsub/get-room-name.md",
    "content": "# `.getRoomName()`\n\nRetrieve the name of the PubSub &ldquo;room&rdquo; for a given record.\n\n```js\nSomething.getRoomName(id);\n```\n\n### Usage\n\n|   | Argument   | Type         | Details |\n|---|:-----------|:------------:|:--------|\n| 1 | id         | ((number)) <br> or <br> ((string))    | The ID (primary key value) of the record to get the PubSub room name for.\n\n### Example\n\n```javascript\n  // On the server:\n\n  subscribeAllBobWatchersToKaren: function (req, res) {\n\n    // Look up all users named \"bob\" or \"karen\".\n    User.find({name: ['bob', 'karen']}, function(err, users) {\n      if (err) {return res.serverError(err);}\n\n      // Get Bob's ID.  We'll assume there is only one Bob.\n      var bobId = _.find(users, { name: 'bob' }).id;\n\n      // Get Karen's ID.  We'll assume there is only one Karen.\n      var karenId = _.find(users, { name: 'karen' }).id;\n\n      // Subscribe all of Bob's sockets to Karen.\n      sails.sockets.addRoomMembersToRooms(User.getRoomName(bobId), User.getRoomName(karenId));\n\n      return res.send();\n    });\n\n  }\n```\n\n<docmeta name=\"displayName\" value=\".getRoomName()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/websockets/resourceful-pubsub/publish.md",
    "content": "# `.publish()`\n\nBroadcast an arbitrary message to socket clients [subscribed](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/subscribe) to one or more of this model's [records](https://sailsjs.com/documentation/concepts/models-and-orm).\n\n```js\nSomething.publish(ids, data, req);\n```\n\n> The event name for this broadcast is the same as the model's identity.\n\n### Usage\n\n|   | Argument   | Type         | Details |\n|---|:-----------|:------------:|:--------|\n| 1 | ids        | ((array))    | An array of record ids (primary key values).\n| 2 | data       | ((json))     | The data to broadcast.\n| 3 | _req_      | ((req?))     | Optional.  If provided, then the requesting socket will *not* receive the broadcast.\n\n\n\n### Example\n\n```javascript\n  // On the server:\n\n  tellSecretToBobs: function (req, res) {\n\n    // Get the secret from the request.\n    var secret = req.param('secret');\n\n    // Look up all users named \"Bob\".\n    User.find({name: 'bob'}, function(err, bobs) {\n      if (err) {return res.serverError(err);}\n\n      // Tell the secret to every client who is subscribed to these users,\n      // except for the client that made this request in the first place.\n      // Note that the secret is wrapped in a dictionary with a `verb` property -- this is not\n      // required, but helpful if you'll also be listening for events from Sails blueprints.\n      User.publish(_.pluck(bobs, 'id'), {\n        verb: 'published',\n        theSecret: secret\n      }, req);\n\n      return res.send();\n    });\n\n  }\n```\n\n```javascript\n  // On the client:\n\n  // Subscribe this client socket to Bob-only secrets\n  // > See the `.subscribe()` documentation for more info about subscribing to records:\n  // > https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/subscribe\n  io.socket.get('/subscribeToBobSecrets');\n\n  // Whenever a `user` event is received, do something.\n  io.socket.on('user', function(msg) {\n     if (msg.verb === 'published') {\n       console.log('Got a secret only Bobs can hear:', msg.theSecret);\n     }\n     // else if (msg.verb === 'created') { ... }\n     // else if (msg.verb === 'updated') { ... }\n  });\n```\n\n### Notes\n> + Be sure to check that `req.isSocket === true` before passing in `req` to refer to the requesting socket.  If used, the provided `req` must be from a socket request, not just any old HTTP request.\n\n\n<docmeta name=\"displayName\" value=\".publish()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/websockets/resourceful-pubsub/resourceful-pubsub.md",
    "content": "# Resourceful PubSub (RPS)\n\n### Overview\n\nFor apps that rely heavily on [realtime](https://sailsjs.com/documentation/concepts/realtime) client-server communication&mdash;for example, peer-to-peer chat and social networking apps&mdash;sending and listening for socket events can quickly become overwhelming.  Sails helps smooth away some of the complexity associated with socket events by introducing the concept of **resourceful PubSub** ([Publish / Subscribe](http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern)).  Every model in your app is automatically equipped with resourceful PubSub methods, which provide a conventional, data-centric interface for both _broadcasting notifications_ and _subscribing sockets to notifications_ about individual database records.\n\nIf your app is currently using the [blueprint API](https://sailsjs.com/documentation/reference/blueprint-api), you are already using resourceful PubSub methods!  They are embedded in the default blueprint actions bundled with Sails and are called automatically when those actions run, causing requesting sockets to be subscribed when data is fetched and messages to be broadcasted to already-subscribed sockets when data is changed. (Sockets can be subscribed via a call to [`.subscribe()`](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/subscribe) or due to a previous socket request to the [`find`](https://sailsjs.com/documentation/reference/blueprint-api/find) or [`findOne`](https://sailsjs.com/documentation/reference/blueprint-api/find-one) blueprints.)\n\nEven when writing custom code, you can manually call the methods described in this section in lieu of using `sails.sockets.*` methods directly.  Think of resourceful PubSub methods as a way of standardizing the interface for socket communication across your application&mdash;these interface elements might be the names of rooms, the schema for data transmitted as socket messages, or the names of socket events.  These methods are designed _exclusively_ for scenarios where one or more user interfaces are listening to socket events in order to stay in sync with the backend.  If that does not fit your use case or if you are having trouble deciding, don't worry; just call [`sails.sockets.broadcast()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/broadcast), [`sails.sockets.join()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/join), or [`sails.sockets.leave()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/leave) directly, instead.  It is perfectly acceptable to use either approach, or even _both_ approaches in the same app.\n\n\n### Methods\n\nSails exposes three different resourceful PubSub (RPS) methods: `.publish()`, `.subscribe()`, and `.unsubscribe()`.\n\nTo get a deeper understanding of resourceful PubSub methods, you may find it useful to familiarize yourself with the underlying [`sails.sockets.*`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets) methods first.  That's because each RPS method is more or less just a contextualized wrapper around one of the simpler `sails.sockets.*` methods:\n\n+ [`.publish()`](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/publish) is like _[`sails.sockets.broadcast()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/broadcast)_\n+ [`.subscribe()`](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/subscribe) is like _[`sails.sockets.join()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/join)_\n+ [`.unsubscribe()`](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/unsubscribe)  is like _[`sails.sockets.leave()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/leave)_\n\nThe biggest difference between these methods and their counterparts in `sails.sockets.*` is that RPS methods expose a higher-level interface.  For example, RPS methods choose room names for you behind the scenes, and they infer a conventional event name based on your model's identity.\n\n\n### Listening for events on the client\n\nWhile you are free to use any JavaScript library to listen for socket events on the client, Sails provides its own socket client called [`sails.io.js`](https://sailsjs.com/documentation/reference/web-sockets/socket-client) as a convenient way to communicate with the Sails server from any web browser or Node.js process that supports Socket.IO.  Using the Sails socket client makes listening for resourceful PubSub events as easy as:\n\n```javascript\nio.socket.on('<model identity>', function (msg) {\n\n});\n```\n\n> The _[model identity](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?identity)_ is typically the lowercased version of the model name, unless it has been manually configured in the model file.\n\n\n### Example\n\nLet&rsquo;s say you have a model named `User` in your app, with a single &ldquo;name&rdquo; attribute.  First, we&rsquo;ll add a listener for &ldquo;user&rdquo; events:\n\n```javascript\nio.socket.on('user', function(msg){\n  console.log(msg);\n})\n```\n\nThis will log any notifications that our client socket receives to the console, so long as those socket notifications have \"user\" as their event name.  However, we won&rsquo;t actually receive those messages until we *subscribe* this client socket to one or more existing `User` records (in our server-side code).\n\nIf your app has the blueprint API enabled, then subscribing the client socket to the `User` records is really easy.  In addition to fetching data, if the [\"Find\" blueprint action](https://sailsjs.com/documentation/reference/blueprint-api/find-where) is accessed via a [socket request](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-get), then it calls [`User.subscribe()`](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/subscribe) (a resourceful PubSub method) automatically.\n\nFor example, imagine you write some client-side code that sends a socket `GET` request to `http://localhost:1337/user`:\n\n```js\nio.socket.get('/user', function(resData) {\n  console.log(resData);\n});\n```\n\nWhen that runs, it will hit the \"Find\" blueprint action, which returns the current list of users from the Sails server.  And if we'd sent a normal HTTP request (like `jQuery.get('/user')`), then that's all that would happen.  But because we sent a _socket request_, the server _also_ subscribed our client socket to future notifications (calls to [`.publish()`](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/publish)) about the user records that were returned.\n\n> See [`io.socket.get()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-get) for more info about using the `sails.io.js` client to send virtual requests.\n\nUnlike `.subscribe()`, the RPS `.publish()` method can run from anywhere&mdash;a controller action triggered as the result of a socket request, an AJAX request, or even a cURL request from the command line.  Alternatively, `.publish()` could be called from a custom helper or in a command-line script.\n\n\nContinuing with the above example, if you were to open an additional browser window and go to the following URL:\n\n```\n/user/create?name=joe\n```\n\nYou would see something like the following in the console of the original window:\n\n```js\n{\n\tverb: 'created',\n  id: 1,\n  data: {\n    id: 1,\n    name: 'joe',\n    createdAt: '2014-08-01T05:50:19.855Z'\n    updatedAt: '2014-08-01T05:50:19.855Z'\n  }\n}\n```\n\nWhat you're seeing here is a dictionary (aka plain JavaScript object) that was broadcasted by the [\"Create\" blueprint action](https://sailsjs.com/documentation/reference/blueprint-api/create).  In the case of the blueprint API, the format of this data is standardized, but in your app, you can use `.publish()` to broadcast any data you like.\n\n\n<docmeta name=\"displayName\" value=\"Resourceful PubSub\">\n"
  },
  {
    "path": "docs/reference/websockets/resourceful-pubsub/subscribe.md",
    "content": "# `.subscribe()`\n\nSubscribe the requesting client socket to changes/deletions of one or more database records.\n\n```js\nSomething.subscribe(req, ids);\n```\n\n\n### Usage\n\n|   | Argument   | Type         | Details |\n|---|:-----------|:------------:|:--------|\n| 1 | req        | ((req))      | The incoming socket request (`req`) containing the socket to subscribe.\n| 2 | ids        | ((array))    | An array of record ids (primary key values).\n\n\nWhen a client socket is subscribed to a record, it is a member of its dynamic \"record room\".  That means it will receive all messages broadcasted to that room by [`.publish()`](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/publish).\n\n### Example\n\n\nOn the server, in a controller action:\n\n```javascript\n// On the server:\n\nif (!this.req.isSocket) {\n  throw {badRequest: 'Only a client socket can subscribe to Louies.  But you look like an HTTP request to me.'};\n}\n\n// Let's say our client socket has a problem with people named \"louie\".\n\n// First we'll find all users named \"louie\" (or \"louis\" even-- we should be thorough)\nlet usersNamedLouie = await User.find({ or: [{name: 'louie'},{name: 'louis'}] });\n\n// Now we'll subscribe our client socket to each of these records.\nUser.subscribe(this.req, _.pluck(usersNamedLouie, 'id'));\n\n// All done!  We might send down some data, or just an empty 200 (OK) response.\n```\n\n\n\n\nThen, back in our client-side code:\n\n```javascript\n// On the client:\n\n// Send a request to the \"subscribeToLouies\" action, subscribing this client socket\n// to all future events that the server publishes about Louies.\nio.socket.get('/foo/bar/subscribeToLouies', function (data, jwr){\n  if (jwr.error) {\n    console.error('Could not subscribe to Louie-related notifications: '+jwr.error);\n    return;\n  }\n\n  console.log('Successfully subscribed.');\n\n});\n```\n\n\nFrom now on, as long as our requesting client socket stays connected, it will receive a notification any time our server-side code (e.g. other actions or helpers) calls `User.publish()` for one of the Louies we subscribed to above.\n\nIn order for our client-side code to handle these future notifications, it must _listen_ for the relevant event with `.on()`.  For example:\n\n```js\n// On the client:\n\n// Whenever a `user` event is received, say something.\nio.socket.on('user', function(msg) {\n  console.log('Got a message about a Louie: ', msg);\n});\n```\n\nSee [Concepts > Realtime](https://sailsjs.com/documentation/concepts/realtime) for more background on the difference between rooms and events in Sails/Socket.IO.\n\n\n\n### Multiple rooms per record\n\nFor some applications, you may find yourself needing to manage two different channels related to the same record.  To accomplish this, you can combine [`.getRoomName()`](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/get-room-name) and [`sails.sockets.join()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/join):\n\n```js\n// On the server, in your subscribe action…\n\nif (!orgId) { throw 'badRequest'; }\n\nif (!this.req.isSocket) { throw {badRequest: 'This action is designed for use with WebSockets.'}; }\n\nlet me = await User.findOne({\n  id: this.req.session.userId\n})\n.populate('globalAdminOfOrganizations');\n\n// Subscribe to general notifications.\nOrganization.subscribe(this.req, orgId);\n\n// If this user is a global admin of this organization, then also subscribe them to\n// an additional private room (this is used for additional notifications intended only\n// for global admins):\nif (globalAdminOfOrganizations.includes(orgId)) {\n  let privateRoom = Organization.getRoomName(`${orgId}-admins-only`);\n  sails.sockets.join(this.req, privateRoom);\n}\n\n```\n\nLater, to publish to one of these rooms, just compute the appropriate room name (e.g. \"13-admins-only\") and use [`sails.sockets.broadcast()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/broadcast) to blast out your notification.\n\n\n\n### Notes\n\n> + Be sure and check `req.isSocket === true` before passing in `req` to refer to the requesting socket.  The provided `req` must be from a socket request, not just any old HTTP request.\n> + `.subscribe()` will only work with requests made over a Socket.IO connection (e.g. using `io.socket.get()`), *not* over an HTTP connection (e.g. using `jQuery.get()`).  See the [`sails.io.js` socket client documentation](https://sailsjs.com/documentation/reference/web-sockets/socket-client) for information on using client sockets to send WebSockets/Socket.IO messages with Sails.\n> + This function does _not actually talk to the database_!  In fact, none of the resourceful PubSub methods do.  Rather, these make up a simplified abstraction layer built on top of the lower-level `sails.sockets` methods, designed to make your app cleaner and easier to debug by using conventional names for events/rooms/namespaces etc.\n\n\n\n\n<docmeta name=\"displayName\" value=\".subscribe()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/websockets/resourceful-pubsub/unsubscribe.md",
    "content": "# `.unsubscribe()`\n\nUnsubscribe the requesting client socket from one or more database records.\n\n```js\nSomething.unsubscribe(req, ids);\n```\n\n### Usage\n\n|   | Argument   | Type         | Details |\n|---|:-----------|:------------:|:--------|\n| 1 | req        | ((req))      | The incoming socket request (`req`) containing the socket to unsubscribe.\n| 2 | ids        | ((array))    | An array of record ids (primary key values).\n\n\n\n### Example\n\nOn the server:\n\n```javascript\nunsubscribeFromUsersNamedLenny: function (req, res) {\n\n  if (!req.isSocket) {\n    return res.badRequest();\n  }\n\n  User.find({name: 'Lenny'}).exec(function(err, lennies) {\n    if (err) { return res.serverError(err); }\n\n    var lennyIds = _.pluck(lennies, 'id');\n\n    User.unsubscribe(req, lennyIds);\n\n    return res.ok();\n\n  });\n},\n```\n\n\n### Notes\n> + Be sure to check that `req.isSocket === true` before passing in `req` to refer to the requesting socket.  The provided `req` must be from a socket request, not just any old HTTP request.\n> + `unsubscribe` will only work when the request is made over a socket connection (e.g. using `io.socket.get()`), *not* over HTTP (e.g. using `jQuery.get()`).\n\n\n<docmeta name=\"displayName\" value=\".unsubscribe()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/websockets/sails.io.js/SailsSocket/SailsSocket.md",
    "content": "# SailsSocket\n\nBy default, [`sails.io.js`](https://sailsjs.com/documentation/reference/web-sockets/socket-client) automatically connects a single socket (`io.socket`) almost immediately after it loads.  This allows your client-side code to send socket requests to a particular Sails server and to receive events and data sent from that server.  For 99% of apps, this is all you need.\n\nHowever, for certain advanced use cases (including automated tests), it can be helpful to connect additional sockets from the same instance of the socket client (e.g. browser tab).  For this reason, Sails exposes the `SailsSocket` class.\n\n\n### Overview\n\nThe `sails.io.js` library works by wrapping low-level [Socket.io](http://socket.io) clients in instances of the `SailsSocket` class.  This class provides higher-level methods like [`.get()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-get) and [`.post()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-post) to your sockets, allowing you to communicate with your Sails app in a familiar way.\n\n\n### Creating a SailsSocket instance\n\nAny web page that loads the `sails.io.js` will create a new SailsSocket instance on page load unless [`io.sails.autoConnect`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-sails#?autoconnect) is set to `false`.  This instance is then available as the global variable [`io.socket`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket).\n\nAdditional SailsSocket instances can be created via calls to [`io.sails.connect`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-sails#?the-connect-method):\n\n```javascript\nvar newSailsSocket = io.sails.connect();\n```\n\n\n<docmeta name=\"displayName\" value=\"SailsSocket\">\n<docmeta name=\"pageType\" value=\"class\">\n"
  },
  {
    "path": "docs/reference/websockets/sails.io.js/SailsSocket/methods.md",
    "content": "# SailsSocket methods\n\nThis section describes the methods available on each SailsSocket instance.  Most of these methods can be called before the socket even connects to the server.  In the case of request methods like `.get()` and `.request()`, calls will be queued up until the socket connects, at which time they will be executed in order.\n\n### Basic methods\n\nThe most common methods you will use with a SailsSocket instance are documented in the main Socket Client reference section.  These include [`.get()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-get), [`.put()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-put), [`.post()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-post), [`.delete()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-delete), [`.request()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-request), [`.on()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-on) and [`.off()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-off).\n\n### Advanced methods\n\nIn addition to the basic communication and event-listening methods, each SailsSocket instance (including `io.socket`) exposes several methods for dealing with server connections.\n\n##### `.isConnected()`\n\nDetermines whether the SailsSocket instance is currently connected to a server; returns `true` if a connection has been established.\n\n```js\nio.socket.isConnected();\n```\n\n##### `.isConnecting()`\n\nDetermines whether the SailsSocket instance is currently in the process of connecting to a server; returns `true` if a connection is being attempted.\n\n```js\nio.socket.isConnecting();\n```\n\n\n##### `.mightBeAboutToAutoConnect()`\n\nDetects when the SailsSocket instance has already loaded but is not yet fully configured or has not attempted to autoconnect. \n\nThe `sails.io.js` library waits one tick of the event loop before checking whether [`autoConnect`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-sails#?iosailsautoconnect) is enabled and, if so, trying to connect.  This allows you to configure the `SailsSocket` instance (for example, by setting `io.sails.url`) before an attempt is made to estabilish a connection.  The `mightBeAboutToAutoConnect()` method returns `true` in the situation where `sails.io.js` has loaded, but the requisite tick of the event loop has not yet elapsed.\n\n```js\nio.socket.mightBeAboutToAutoConnect();\n```\n\n##### `.disconnect()`\n\nDisconnects a SailsSocket instance from the server; throws an error if the socket is already disconnected.\n\n```js\nio.socket.disconnect();\n```\n\n##### `.reconnect()`\n\nReconnects a SailsSocket instance to a server after it's been disconnected (either involuntarily or via a call to [`.disconnect()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/methods#?disconnect)).  The instance connects using its currently configured [properties](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/properties).  `.reconnect()` throws an error if the socket is already connected to a server.\n\n```js\nio.socket.reconnect();\n```\n\n> When an instance is in a disconnected state, its properties may be changed. This means that an instance that has been disconnected from one server can be reconnected to another without losing its event bindings or queued requests.\n\n\n##### `.removeAllListeners()`\n\nStops listening for any server-related events on a SailsSocket instance, including `connect` and `disconnect`.\n\n```js\nio.socket.removeAllListeners();\n```\n\n\n\n<docmeta name=\"displayName\" value=\"Methods\">\n\n"
  },
  {
    "path": "docs/reference/websockets/sails.io.js/SailsSocket/properties.md",
    "content": "# SailsSocket properties\n\n### Overview\n\nThis page describes the properties available on each [SailsSocket instance](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket).  These properties are set in the initial call to `io.sails.connect`, which creates the SailsSocket and cannot be changed while the socket is connected (with the exception of `headers`).\n\nIf the socket becomes disconnected (either involuntarily or as a result of a call to [`.disconnect`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/methods#?disconnect)), its properties can be changed until the socket connects again.  This means that an instance that has been disconnected from one server can be reconnected to another without losing its event bindings or queued requests.\n\n### Common properties\n\n  Property           | Type       | Default   | Details\n :-------------------|------------|:----------|:------------------------\n `url`               | ((string)) | Value of [`io.sails.url`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/properties#?iosails-defaults) | The URL to which the socket is connected or will attempt to connect.\n `transports`        | ((array))  | Value of [`io.sails.transports`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/properties#?iosails-defaults) | The transports by which the socket will attempt to connect.  Transports will be tried in order with upgrades allowed; that is, if you list both \"polling\" and \"websocket\", then after establishing a long-polling connection, the server will attempt to upgrade it to a websocket connection.  This setting should match the value of `sails.config.sockets.transports` in your Sails app.\n`headers` | ((dictionary)) | Value of [`io.sails.headers`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/properties#?iosails-defaults) | Dictionary of headers to be sent by default with every request from this socket after it connects.  Can be overridden via the `headers` option in [`.request()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-request).  See `initialConnectionHeaders` below for information on setting headers for the initial socket handshake request.\n\n### Advanced properties\n\n  Property          | Type       | Default   | Details\n :------------------ |----------|:--------- |:-------\n `query`              | ((string)) | Value of [`io.sails.query`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/properties#?iosails-defaults)    | Query string to use with the initial connection to the server.  In server code, this can be accessed via `req.socket.handshake.query` in controller actions or `handshake._query` in [socket lifecycle callbacks](https://sailsjs.com/documentation/reference/configuration/sails-config-sockets).  Note that information about the `sails.io.js` SDK version will be tacked onto whatever query string you specify.  A common usage of `query` is to set `nosession=true`, indicating that the Sails app should _not_ associate the connecting socket with a browser session.\n `initialConnectionHeaders` | ((dictionary)) | Value of [`io.sails.initialConnectionHeaders`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/properties#?iosails-defaults) | _Node.js only&mdash;not available in browser._ Dictionary of headers to be sent with the _initial connection to the server_ (as opposed to the `headers` property above, which contains headers to be sent with every socket request made _after_ the initial connection).  In server code, the initial connection headers can be accessed via `req.socket.handshake.headers` in controller actions or `socket.handshake.headers` in [socket lifecycle callbacks](https://sailsjs.com/documentation/reference/configuration/sails-config-sockets).  This is useful for (for example) sending a `cookie` header with the initial handshake, allowing a socket to connect to a previously-established Sails session.\n `useCORSRouteToGetCookie` | ((boolean)) or ((string)) | Value of [`io.sails.useCORSRouteToGetCookie`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/properties#?iosails-defaults) | Only relevant in browser environments and if you are relying on the default Sails session + session cookies for authentication.  For cross-origin socket connections, use this property to choose a route to send an initial JSONP request in order to retrieve a cookie, so that the right session can be established.  The route should respond with the string `'_sailsIoJSConnect();'`, which will allow the connection to continue.  If `useCORSRouteToGetCookie` is `true`, the default `/__getcookie` route on the Sails server will be used.  If it is `false`, no attempt will be made to contact the remote server before connecting the socket.  *Note: this strategy may fail on certain browsers (including certain versions of Safari) which block third-party cookies by default.*\n\n### `io.sails.*` defaults\n\nThe `io.sails` object can be used to provide default values for new client sockets.  For example, setting `io.sails.url = \"http://myapp.com:1234\"` will cause every new client socket to connect to `http://myapp.com:1234`, unless a `url` value is provided in the call to `io.sails.connect()`.\n\nThe following are the default values for properties in `io.sails`:\n\n  Property          | Default\n :------------------|:-------\n `url`              | In browser, the URL of the page that loaded the `sails.io.js` script.  In Node.js, no default.\n `transports`       | `['websocket']`\n`headers` | `{}`\n`query` | `''`\n`initialConnectionHeaders` | `{}`\n`useCORSRouteToGetCookie` | `true`\n\n<docmeta name=\"displayName\" value=\"Properties\">\n\n"
  },
  {
    "path": "docs/reference/websockets/sails.io.js/io.sails.md",
    "content": "# The `io.sails` object\n\n### Overview\n\nThe `io.sails` object is the home of global configuration options for the `sails.io.js` library and any sockets it creates.  Most of the properties on `io.sails` are used as settings when connecting a client socket to the server or as top-level configuration for the client library itself.  `io.sails` also provides a `.connect()` method used for creating new socket connections manually.\n\nSee [Socket Client](https://sailsjs.com/documentation/reference/web-sockets/socket-client) for information about your different options for configuring `io.sails`.\n\n### The `.connect()` method\n\nIf [`io.sails.autoConnect`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-sails#?autoconnect) is `false`, or if you need to create more than one socket connection with the `sails.io.js` library, you do so via `io.sails.connect([url], [options])`.  Both arguments are optional, and the value of the `io.sails` properties (like `url`, `transports`, etc.) are used as defaults.  See the [SailsSocket properties reference](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/properties) for options.\n\n### `io.sails.autoConnect`\n\nWhen `io.sails.autoConnect` is set to `true` (the default setting), the library will wait one cycle of the event loop after loading and then attempt to create a new [`SailsSocket`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket) and connect it to the URL specified by `io.sails.url`.  When used in the browser, the new socket will be exposed as `io.socket`.  When used in a Node.js script, the new socket will be attached as the `socket` property of the variable used to initialize the `sails.io.js` library.\n\n### `io.sails.reconnection`\n\nWhen `io.sails.reconnection` is set to `true`, sockets will automatically (and continuously) attempt to reconnect to the server if they become disconnected unexpectedly (that is, _not_ as the result of a call to [`.disconnect()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/methods#?disconnect)).  If set to `false` (the default), no automatic reconnection attempt will be made.  Defaults to `false`.\n\n### `io.sails.environment`\n\nUse `io.sails.environment` to set an environment for `sails.io.js`, which affects how much information is logged to the console.  Valid values are `development` (full logs) and `production` (minimal logs).\n\n### Other properties and defaults\n\nThe other properties of `io.sails` are used as defaults when creating new sockets (either the eager socket or via [`io.sails.connect()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-sails#?the-connect-method)).  See the [SailsSocket properties reference](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/properties) for a full list of available options, as well as a table of the default `io.sails` values.  Here are the most commonly used properties:\n\n  Property          | Type       | Default   | Details\n :------------------ |----------|:--------- |:-------\n url                | ((string)) | Value of [`io.sails.url`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/properties#?iosails-defaults) | The URL that the socket is connected to, or will attempt to connect to.\n transports         | ((array))  | Value of [`io.sails.transports`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/properties#?iosails-defaults) | The transports that the socket will attempt to connect using.  Transports will be tried in order, with upgrades allowed: that is, if you list both \"polling\" and \"websocket\", then after establishing a long-polling connection the server will attempt to upgrade it to a websocket connection.  This setting should match the value of `sails.config.sockets.transports` in your Sails app.\n headers   | ((dictionary)) | Value of [`io.sails.headers`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/properties#?iosails-defaults) | Dictionary of headers to be sent by default with every request from this socket.  Can be overridden via the `headers` option in [`.request()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-request).\n\n\n\n\n<docmeta name=\"displayName\" value=\"io.sails\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/websockets/sails.io.js/io.socket.md",
    "content": "# `io.socket`\n\n### Overview\n\nWhen used in the browser, `sails.io.js` creates a global instance of the [SailsSocket](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket) class as soon as it loads and attempts to connect it to the server after waiting one event loop cycle (to allow for configuration options to be changed).  As with any [SailsSocket](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket), you can start using its properties and methods even before it connects to the server. Any requests or event bindings will be queued up and replayed once the connection is established.\n\n### Configuration Options\n\nLike any [SailsSocket](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket) instance, `io.socket` is affected by the global [`io.sails`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-sails) settings.  The `sails.io.js` library waits for one event loop cycle before attempting to connect `io.socket` to the server, giving you a chance to change any settings first.\n\n##### Example\n\nChanging the server that `io.socket` connects to\n\n```html\n<script type=\"text/javascript\" src=\"/js/dependencies/sails.io.js\"></script>\n<script type=\"text/javascript\">\nio.sails.url = \"http://somesailsapp.com\";\n</script>\n```\n\n### Properties\n\nSee the [SailsSocket properties reference](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/properties) for a full list of properties available on `io.socket`.\n\n### Methods\n\nFor basic server communication and event listening methods, see the other `io.socket.*` pages in this section.  For advanced methods involving server connection, see the [SailsSocket advanced methods reference](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/methods).\n\n<docmeta name=\"displayName\" value=\"io.socket\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/websockets/sails.io.js/io.socket.off.md",
    "content": "# `io.socket.off()`\n\nUnbind the specified event handler (opposite of [`.on()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-on)).\n\n```js\nio.socket.off(eventIdentity, handlerFn);\n```\n\n> **This method is here for completeness, but most apps should not need to use it.**  See below for more information.\n\n\n### Usage\n\n\n|   | Argument   | Type         | Details |\n|---|------------|:------------:|:--------|\n| 1 | eventIdentity | ((string))   | The unique event identity associated with a server-sent message, e.g. \"recipe\".\n| 2 | handlerFn     | ((function)) | The event handler function to unbind from the specified event.\n\n\n\n### Notes\n\n> + If you decide to use this method, be careful!  `io.socket.off()` does **not** stop the this client-side socket from receiving any server-sent messages, it just prevents the specified event handler from firing.  Usually, the desired effect is to prevent messages _from being sent altogether_, which is critical if your server-sent messages contain private data. This happens automatically when a socket disconnects, but there are also less-common use cases where it is necessary to unsubscribe sockets from rooms while they are still connected.  For example, consider a scenario where an admin user is banned from your system while viewing a realtime dashboard, and your app needs to prevent them from receiving all subsequent realtime updates. To force a client socket to stop receiving broadcasted messages, **do not use this method**.  Instead, unsubscribe the socket in your server-side code:\n>   + If the room was joined using `sails.sockets.join()`, call `sails.sockets.leave()`.\n>   + If the room was joined using resourceful PubSub methods, call `.unsubscribe()` or `.unwatch()` as appropriate.\n> + In order to use `.off()`, you will need to store the `handlerFn` argument you passed in to [`.on()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-on) in a variable.\n\n\n<docmeta name=\"displayName\" value=\"io.socket.off()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/websockets/sails.io.js/io.socket.on.md",
    "content": "# `io.socket.on()`\n\nStart listening for socket events from Sails with the specified `eventName`.  Triggers the provided callback function when a matching event is received.\n\n```js\nio.socket.on(eventName, function (msg) {\n  // ...\n});\n```\n\n\n### Usage\n\n|   | Argument    | Type         | Details |\n|---|-------------|:------------:|:--------|\n| 1 | eventName   | ((string))   | The name of the socket event, e.g. `'recipe'` or `'welcome'`.\n| 2 | handlerFn   | ((function)) | An event handler that will be called when the server broadcasts a notification to this socket.  Will only be called if the incoming socket notification matches `eventName`.\n\n\n##### Event handler\n\n|   | Argument  | Type            | Details |\n|---|:----------|:---------------:|:--------|\n| 1 | msg       | ((json))        | The data from the socket notification.\n\n\n\n### When is the event handler called?\n\nThis event handler is called when the client receives an incoming socket notification that matches the specified event name (e.g. `'welcome'`).  This happens when the server broadcasts a message to this socket directly, or to a room of which it is a member.  To broadcast a socket notification, you need to either use the [blueprint API](https://sailsjs.com/documentation/concepts/blueprints) or write some server-side code (e.g. in an action, helper, or even in a command-line script).  This is typically achieved in one of the following ways:\n\n\n###### Low-level socket methods (`sails.sockets`)\n+ server blasts out a message to all connected sockets (see [sails.sockets.blast()](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/blast))\n+ server broadcasts a message directly to a particular socket using its unique ID or to an entire room full of sockets (see [sails.sockets.broadcast()](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/broadcast))\n\n\n###### Resourceful Pubsub Methods\n+ server broadcasts a message about a record, which multiple sockets might be subscribed to (see [Model.publish()](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/publish)\n+ server broadcasts a message as part of the \"Create\" blueprint action _(only relevant if using [blueprints](https://sailsjs.com/documentation/concepts/blueprints))_\n\n\n\n### Example\n\nListen for the \"order\" event:\n\n```javascript\nio.socket.on('order', function onServerSentEvent (msg) {\n  // msg => {...whatever the server broadcasted...}\n});\n```\n\n\n##### Realtime cafeteria\n\nImagine you're building an ordering system for a chain of restaurants:\n\n```javascript\n// In your frontend code...\n// (This example uses jQuery and Lodash for simplicity. But you can use any library or framework you like.)\n\nvar ORDER_IN_LIST = _.template('<li data-id=\"<%- order.id %>\"><p><%- order.summary %></p></li>');\n\n$(function whenDomIsReady(){\n\n  // Every time we receive a relevant socket event...\n  io.socket.on('order', function (msg) {\n\n    // Let's see what the server has to say...\n    switch(msg.verb) {\n\n      case 'created': (function(){\n\n        // Render the new order in the DOM.\n        var newOrderHtml = ORDER_IN_LIST(msg.data);\n        $('#orders').append(newOrderHtml);\n\n      })(); return;\n\n      case 'destroyed': (function(){\n\n        // Find any existing orders w/ this id in the DOM.\n        //\n        // > Remember: To prevent XSS attacks and bugs, never build DOM selectors\n        // > using untrusted provided by users.  (In this case, we know that \"id\"\n        // > did not come from a user, so we can trust it.)\n        var $deletedOrders = $('#orders').find('[data-id=\"'+msg.id+'\"]');\n\n        // Then, if there are any, remove them from the DOM.\n        $deletedOrders.remove();\n\n      })(); return;\n\n      // Ignore any unrecognized messages\n      default: return;\n\n    }//< / switch >\n\n  });//< / io.socket.on() >\n\n});//< / when DOM is ready >\n```\n\n> Note that this example assumes the backend calls [`.publish()`](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/publish) or [`.broadcast()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/broadcast) at some point.  That might be through custom code, or via the [blueprint API](https://sailsjs.com/documentation/concepts/blueprints).\n\n\n### The `'connect'` event\nBy default, when the Sails socket client is loaded on a page, it begins connecting a socket for you automatically.  When using the default, auto-connecting socket (`io.socket`), you don't have to wait for the socket to connect before using it.  In other words, you can listen for other socket events or call methods like [`io.socket.get()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-get) immediately.  The Sails socket client will queue up anything you do in the meantime and then replay it automatically once the connection is live.\n\nConsequently, direct usage of the `'connect'` event **is not necessary for most apps**.  But in the spirit of completeness, it is worth mentioning that you can also bind your own `'connect'` handler:\n\n```javascript\nio.socket.on('connect', function onConnect(){\n  console.log('This socket is now connected to the Sails server.');\n});\n```\n\n### The `'disconnect'` event\n\nIf a socket's connection to the server was interrupted&mdash;perhaps because the server was restarted, or the client had some kind of network issue&mdash;it is possible to handle `disconnect` events in order to display an error message or even to manually reconnect the socket again.\n\n```javascript\nio.socket.on('disconnect', function onDisconnect(){\n  console.log('This socket lost connection to the Sails server');\n});\n```\n\n> Sockets can be configured to reconnect automatically.  However, as of Sails v1, the Sails socket client disables this behavior by default.  In practice, since your user interface might have missed socket notifications while disconnected, you'll almost always want to handle any related custom logic by hand.  (For example, a \"Check your internet connection\" error message).\n\n\n\n### Notes\n>+ Remember that a socket only stays subscribed to a room for as long as it is connected&mdash;e.g. as long as the browser tab is open&mdash;or until it is manually unsubscribed on the server using [`.unsubscribe()`](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/unsubscribe) or [`.leave()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/leave).\n>+ When listening for socket messages from resourceful PubSub calls and blueprints, the event name is always the same as the identity of the calling model.  For example, if you have a model named \"UserComment\", the model's identity (and therefore the socket event name used by [`UserComment.publish()`](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub)) is \"usercomment\".\n>+ For context, socket notifications are also sometimes referred to as \"server-sent events\" or \"[comet](http://en.wikipedia.org/wiki/Comet_(programming)) messages\".\n\n\n<docmeta name=\"displayName\" value=\"io.socket.on()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/websockets/sails.io.js/sails.io.js.md",
    "content": "# Socket client (`sails.io.js`)\n\n> This section of the docs is about the Sails socket client SDK for the browser.  It is written in JavaScript, and is also usable on the server.\n>\n> There are also a handful of community projects implementing Sails/Socket.IO clients for native iOS, Android, and Windows Phone.\n\n\n### Overview\n\nThe Sails socket client ([`sails.io.js`](https://github.com/balderdashy/sails.io.js)) is a tiny browser library that is bundled by default in new Sails apps.  It is a lightweight wrapper that sits on top of the Socket.IO client and whose purpose is to make sending and receiving messages from your Sails backend as simple as possible.\n\nThe main responsibility of `sails.io.js` is to provide a familiar, Ajax-like interface for communicating with your Sails app using WebSockets/Socket.IO.  That basically means providing `.get()`, `.post()`, `.put()`, and `.delete()` methods that allow you take advantage of realtime features while still reusing the same backend routes you're using for the rest of your app.  In other words, running `io.socket.post('/user')` in your browser will be routed within your Sails app in exactly the same way as an HTTP POST request to the same route.\n\n\n### Basic usage (browser)\n\nIn the browser, all that is required to use `sails.io.js` is to include the library in a `<SCRIPT>` tag.  Sails adds the library to the `assets/js/dependencies` folder of all new apps, so you can write:\n\n```html\n<!--\n  This will import the sails.io.js library bundled in your Sails app by default.\n  The bundled version embeds minified code for the Socket.io client as well.\n  One tick of the event loop after importing this script, a new \"eager\" socket\n  will automatically be created begin connecting unless you configure it not to.\n-->\n<script type=\"text/javascript\" src=\"/js/dependencies/sails.io.js\"></script>\n```\n\nand then use `io.socket` as a global variable in subsequent inline or external scripts.  For detailed instructions and examples of everyday usage, see [`io.socket`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket).\n\n\n\n\n### Basic usage (Node.js)\n\nTo use the Sails socket client SDK in a Node.js script, you will need to install and require both the `sails.io.js` and `socket.io-client` libraries:\n\n```javascript\n// Initialize the sails.io.js library with the socket.io-client module,\n// which will automatically create and connect a new socket as io.socket\n// unless you configure it not to.\nvar io = require('sails.io.js')( require('socket.io-client') );\n```\n\nSee the [sails.io.js GitHub repo](https://github.com/balderdashy/sails.io.js) for more information on using the Sails socket client from Node.js.\n\n\n### Configuring the `sails.io.js` library\n\n> This section focuses on the most common runtime environment for the JavaScript socket client: the browser.  See the [`sails.io.js` GitHub repository](https://github.com/balderdashy/sails.io.js) for help configuring the socket client for use in a Node.js script.\n\nThere are two ways to configure Sails' socket client in the browser: using HTML attributes on the `<script>` tag or by programmatically modifying the `io.sails` object.\n\n##### Basic configuration using HTML attributes\n\nThe easiest way to configure the four most common settings for the socket client (`autoConnect`, `environment`, `headers`, and `url`) is by sticking one or more HTML attributes on the script tag:\n\n```html\n<script src=\"/js/dependencies/sails.io.js\"\n  autoConnect=\"false\"\n  environment=\"production\"\n  headers='{ \"x-csrf-token\": \"<%= typeof _csrf !== 'undefined' ? _csrf : '' %>\" }'\n></script>\n```\n\nThis example will disable the eager socket connection, force the client environment to \"production\" (which disables logs), and set an `x-csrf-token` header that will be sent in every socket request (unless overridden).  Note that composite values like the `headers` dictionary are wrapped in a pair of _single-quotes_. That's because composite values specified this way must be _JSON-encoded_, meaning that their key names and value strings must be enclosed in double quotes (for a simlilar reason, the strings within the value string are enclosed in single quotes). \n\nAny configuration that can be provided as an HTML attribute can alternately be provided prefixed with `data-` (e.g. `data-autoConnect`, `data-environment`, `data-headers`, `data-url`).  This is for folks who need to support browsers that have issues with nonstandard HTML attributes (or if the idea of using nonstandard HTML attributes just creeps you out). If both the standard HTML attribute and the `data-` prefixed HTML attribute are provided, the latter takes precendence.\n\n\n> **Note:**\n> In order to use this approach for configuring the socket client, if you are using the default Grunt asset pipeline (which automatically injects script tags), you will need to remove `sails.io.js` from your `pipeline.js` file, and instead include an explicit `<script>` tag, which imports it.\n\n\n\n\n##### Programmatic configuration using `io.sails`\n\nAs of Sails v0.12.x, only the most basic configuration options may be set using HTML attributes.  If you want to configure any of the other options not mentioned above, you will need to interact with `io.sails` programmatically.  Fortunately, the approach described above is really just a convenient shortcut for doing just that!  Heres how it works:\n\nWhen you load it on the page in a `<script>` tag, the `sails.io.js` library waits for one cycle of the event loop before _automatically connecting_ a socket (if `io.sails.autoConnect` is enabled, [see below](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-sails#?autoconnect)).  This allows any properties that you specify on `io.sails` to be set before the socket begins connecting.  However, in order to ensure that the `io.sails` properties are read before connection, you should put the code setting those properties immediately after the `<script>` tag that includes `sails.io.js`:\n\n```html\n<script src=\"/js/dependencies/sails.io.js\"></script>\n<script type=\"text/javascript\">\n  io.sails.url = 'https://myapp.com';\n</script>\n<!-- ...other scripts... -->\n```\n\nNormally, the socket client always connects to the server where the script is being served.  The example above will cause the eager (auto-connecting) socket to attempt a (cross-domain) socket connection to the Sails server running at `https://myapp.com`, instead.\n\n> **Note:**\n> If you are using the default Grunt asset pipeline (which automatically injects script tags), it is a good idea to exclude `sails.io.js` from your `pipeline.js` file, instead explicitly adding a `<script>` tag for it.  This ensures that your configuration will be applied _before_ the \"eager\" auto-connecting socket begins connecting, since it means that the inline `<script>` tag you are using for programmatic configuration (setting `io.sails.url = 'https://myapp.com';`, for example) is executed _immediately after_ the socket client.\n\n\n\n\n### Advanced usage\n\nYou can also create and connect client sockets manually using [`io.sails.connect`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-sails#?the-connect-method).  This returns an instance of the `SailsSocket`. For more information about use cases that are less common and more advanced, such as connecting multiple sockets, see [SailsSocket](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket).\n\n##### Advanced configuration\n\nThe `sails.io.js` library and its individual client sockets have a handful of configuration options.  Global configuration lives in [`io.sails`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-sails), which&mdash;among other things&mdash;allows you to disable the \"eager\" socket and default settings for new sockets.  Individual sockets can also be configured when they are manually connected&mdash;see [`io.sails.connect()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-sails#?the-connect-method) for more information on that.\n\n\n\n\n\n\n### Frequently asked questions\n\n##### Can I use this with XYZ front-end framework?\n\nYes.  The Sails socket client can be used to great effect with any front-end framework, whether it's Angular, React, Ember, Backbone, Knockout, jQuery, [FishBerry](https://mrsharpoblunto.github.io/foswig.js/), etc.\n\n\n##### Do I have to use this?\n\nNo. The Sails socket client is extremely helpful when building realtime/chat features in a browser-based UI, but like the rest of the `assets/` directory, it is probably not particularly useful if you are building a [native Android app](https://stackoverflow.com/questions/25081188/sending-socket-request-from-client-ios-android-to-sails-js-server/25081189#25081189) or an API with no user interface at all.\n\nFortunately, like every other boilerplate file and folder in Sails, the socket client is completely optional. To remove it, just delete `assets/js/dependencies/sails.io.js`.\n\n\n##### How does this work?\n\nUnder the hood, the socket client (`sails.io.js`) emits Socket.IO messages with reserved names that, when interpreted by Sails, are routed to the appropriate policies/controllers/etc. according to your app's routes and blueprint configuration.\n\n##### How do I tell my Sails app _not_ to connect a socket with the current browser session?\n\nBy default, a socket connection will be linked to the current browser session (if any) using the `cookie` header that is sent with the initial socket handshake.  In order to turn off this behavior, add `nosession=true` to the [`query` property](https://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/properties#?advanced-properties) of the socket before it connects. For example:\n\n```\n<script src=\"/js/dependencies/sails.io.js\"></script>\n<script type=\"text/javascript\">io.sails.query='nosession=true';</script>\n```\n\n##### Can I bypass this client and use Socket.IO directly?\n\nIt is possible to bypass the request interpreter in your Sails app and communicate with Socket.IO directly.  However, it is not reccommended, since it breaks the convention-over-configuration philosophy used elsewhere in the framework. The Sails socket client (`sails.io.js`) is unobtrusive:  it works by wrapping the native Socket.IO client and exposing a higher-level API that takes advantage of the virtual request interpreter in Sails to send simulated HTTP requests.  This makes your backend code more reusable, reduces the barrier to entry for developers new to using WebSockets/Socket.IO, and keeps your app easier to reason about.\n\n> **Note:**\n> In very rare circumstances (e.g. compatibility with an existing/legacy frontend using Socket.IO directly), bypassing the request interpreter is a _requirement_.  If you find yourself in this position, you can use the Socket.IO client, SDK, and then use `sails.io` on the backend to access the raw Socket.IO instance.  Please embark on this road only if you have extensive experience working directly with Socket.IO, and only if you have first reviewed the internals of the [`sockets` hook](https://github.com/balderdashy/sails-hook-sockets) (particularly the \"admin bus\" implementation, a Redis integration that sits on top of @sailshq/socket.io-redis and powers Sails's multi-server support for joining/leaving rooms).\n\n\n<docmeta name=\"displayName\" value=\"Socket client\">\n\n"
  },
  {
    "path": "docs/reference/websockets/sails.io.js/socket.delete.md",
    "content": "# `io.socket.delete()`\n\nSend a virtual DELETE request to a Sails server using Socket.IO.\n\n```js\nio.socket.delete(url, data, function (data, jwres){\n  // ...\n});\n```\n\n\n### Usage\n\n|   | Argument   | Type         | Details |\n|---|------------|:------------:|---------|\n| 1 | url        | ((string))   | The destination URL path, e.g. \"/checkout\".\n| 2 | _data_     | ((json?))    | Optional request data. If provided, it will be URL-encoded and appended to `url` (existing query string params in url will be preserved).\n| 3 | _callback_ | ((function?)) | Optional callback. If provided, it will be called when the server responds.\n\n##### Callback\n\n|   | Argument  | Type         | Details |\n|---|-----------|:------------:|---------|\n| 1 | resData   | ((json))        | Data received in the response from the Sails server (=== `jwres.body`, equivalent to the HTTP response body).\n| 2 | jwres     | ((dictionary))      | A JSON WebSocket Response object.  Has `headers`, a `body`, and a `statusCode`.\n\n\n### Example\n\n```html\n<script>\nio.socket.delete('/users/9', function (resData) {\n  resData; // => {id:9, name: 'Timmy Mendez', occupation: 'psychic'}\n});\n</script>\n```\n\n\n### Notes\n> + Remember that you can communicate with _any of your routes_ using socket requests.\n> + Need to customize request headers?  Check out the slightly lower-level [`io.socket.request()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-request) method. To set custom headers for _all_ outgoing requests, check out [`io.sails.headers`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-sails).\n\n\n<docmeta name=\"displayName\" value=\"io.socket.delete()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/websockets/sails.io.js/socket.get.md",
    "content": "# `io.socket.get()`\n\nSend a socket request (virtual GET) to a Sails server using Socket.IO.\n\n```js\nio.socket.get(url, data, function (resData, jwres){\n  // ...\n});\n```\n\n### Usage\n\n\n|   | Argument   | Type         | Details |\n|---|:-----------|:------------:|:--------|\n| 1 | url        | ((string))   | The destination URL path, e.g. \"/checkout\".\n| 2 | _data_     | ((json?))        | Optional request data. If provided, it will be URL-encoded and appended to `url` (existing query string params in url will be preserved).\n| 3 | _callback_ | ((function?)) | Optional callback. If provided, it will be called when the server responds.\n\n##### Callback\n\n|   | Argument  | Type            | Details |\n|---|:----------|:---------------:|:--------|\n| 1 | resData   | ((json))        | Data, if any, sent in the response from the Sails server.  This is the same thing as `jwres.body`.\n| 2 | jwres     | ((dictionary))  | A JSON WebSocket response, which consists of `headers` (a ((dictionary))), `body` (((json))), and `statusCode` (a ((number))).\n\n\n\n### Example\n\n```html\n<script>\nio.socket.get('/users/9', function (resData) {\n  // resData => {id:9, name: 'Timmy Mendez'}\n});\n</script>\n```\n\n### Notes\n> + Remember that you can communicate with _any of your routes_ using socket requests.\n> + Need to customize request headers?  Check out the slightly lower-level [`io.socket.request()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-request) method. To set custom headers for _all_ outgoing requests, check out [`io.sails.headers`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-sails).\n\n<docmeta name=\"displayName\" value=\"io.socket.get()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/websockets/sails.io.js/socket.patch.md",
    "content": "# `io.socket.patch()`\n\nSend a socket request (virtual PATCH) to a Sails server using Socket.IO.\n\n```js\nio.socket.patch(url, data, function (resData, jwres){\n  // ...\n});\n```\n\n\n### Usage\n\n\n|   | Argument   | Type         | Details |\n|---|------------|:------------:|---------|\n| 1 | url        | ((string))   | The destination URL path, e.g. \"/checkout\".\n| 2 | _data_     | ((json?))    | Optional request data. If provided, it will be JSON-encoded and included as the virtual HTTP body.\n| 3 | _callback_ | ((function?)) | Optional callback. If provided, it will be called when the server responds.\n\n##### Callback\n\n|   | Argument  | Type         | Details |\n|---|-----------|:------------:|---------|\n| 1 | resData   | ((json))     | Data received in the response from the Sails server (=== `jwres.body`, equivalent to the HTTP response body).\n| 2 | jwres     | ((dictionary))| A JSON WebSocket Response object.  Has `headers`, a `body`, and a `statusCode`.\n\n\n### Example\n\n```html\n<script>\nio.socket.patch('/users/9', { occupation: 'psychic' }, function (resData, jwr) {\n  resData.statusCode; // => 200\n});\n</script>\n```\n\n\n### Notes\n> + Remember that you can communicate with _any of your routes_ using socket requests.\n> + Need to customize request headers?  Check out the slightly lower-level [`io.socket.request()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-request) method. To set custom headers for _all_ outgoing requests, check out [`io.sails.headers`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-sails).\n\n\n\n<docmeta name=\"displayName\" value=\"io.socket.patch()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/websockets/sails.io.js/socket.post.md",
    "content": "# `io.socket.post()`\n\nSend a socket request (virtual POST) to a Sails server using Socket.IO.\n\n```js\nio.socket.post(url, data, function (resData, jwres){\n  // ...\n});\n```\n\n### Usage\n\n\n|   | Argument   | Type         | Details |\n|---|------------|:------------:|---------|\n| 1 | url        | ((string))   | The destination URL path, e.g. \"/checkout\".\n| 2 | _data_     | ((json?))        | Optional request data. If provided, it will be JSON-encoded and included as the virtual HTTP body.\n| 3 | _callback_ | ((function?)) | Optional callback. If provided, it will be called when the server responds.\n\n##### Callback\n\n|   | Argument  | Type         | Details |\n|---|-----------|:------------:|---------|\n| 1 | resData   | ((json))     | Data received in the response from the Sails server (=== `jwres.body`, and also equivalent to the HTTP response body).\n| 2 | jwres     | ((dictionary))      | A JSON WebSocket Response object.  Has `headers`, a `body`, and a `statusCode`.\n\n\n### Example\n\n```html\n<script>\nio.socket.post('/users', { name: 'Timmy Mendez' }, function (resData, jwRes) {\n  jwRes.statusCode; // => 200\n});\n</script>\n```\n\n\n### Notes\n> + Remember that you can communicate with _any of your routes_ using socket requests.\n> + Need to customize request headers?  Check out the slightly lower-level [`io.socket.request()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-request) method. To set custom headers for _all_ outgoing requests, check out [`io.sails.headers`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-sails).\n\n\n<docmeta name=\"displayName\" value=\"io.socket.post()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/websockets/sails.io.js/socket.put.md",
    "content": "# `io.socket.put()`\n\nSend a socket request (virtual PUT) to a Sails server using Socket.IO.\n\n```js\nio.socket.put(url, data, function (resData, jwres){\n  // ...\n});\n```\n\n\n### Usage\n\n\n|   | Argument   | Type         | Details |\n|---|------------|:------------:|---------|\n| 1 | url        | ((string))   | The destination URL path, e.g. \"/checkout\".\n| 2 | _data_     | ((json?))    | Optional request data. If provided, it will be JSON-encoded and included as the virtual HTTP body.\n| 3 | _callback_ | ((function?)) | Optional callback. If provided, it will be called when the server responds.\n\n##### Callback\n\n|   | Argument  | Type         | Details |\n|---|-----------|:------------:|---------|\n| 1 | resData   | ((json))     | Data received in the response from the Sails server (=== `jwres.body`, equivalent to the HTTP response body).\n| 2 | jwres     | ((dictionary))      | A JSON WebSocket Response object.  Has `headers`, a `body`, and a `statusCode`.\n\n\n### Example\n\n```html\n<script>\nio.socket.put('/users/9', { occupation: 'psychic' }, function (resData, jwr) {\n  resData.statusCode; // => 200\n});\n</script>\n```\n\n\n### Notes\n> + Remember that you can communicate with _any of your routes_ using socket requests.\n> + Need to customize request headers?  Check out the slightly lower-level [`io.socket.request()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-request) method. To set custom headers for _all_ outgoing requests, check out [`io.sails.headers`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-sails).\n\n\n\n<docmeta name=\"displayName\" value=\"io.socket.put()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/websockets/sails.io.js/socket.request.md",
    "content": "# `io.socket.request()`\n\nSend a virtual request to a Sails server using Socket.IO.\n\nThis function is very similar to [`io.socket.get()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-get), [`io.socket.post()`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket-post), etc. except that it provides lower-level access to the request headers, parameters, method, and URL of the request.\n\nUsing the automatically-created [`io.socket`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-socket) instance:\n\n```js\nio.socket.request(options, function (resData, jwres)){\n  // ...\n  // jwres.headers\n  // jwres.statusCode\n  // jwres.body === resData\n  // ...\n});\n```\n\n\n### Usage\n\n\n| Option   | Type         | Details |\n|:-----------|:------------:|:--------|\n| method    | ((string))   | The HTTP request method; e.g. `'GET'`.\n| url       | ((string))   | The destination URL path; e.g. \"/checkout\".\n| _data_    | ((json?))    | Optional. If provided, this request data will be JSON-encoded and included as the virtual HTTP body.\n| _headers_ | ((dictionary?))   | Optional. If provided, this dictionary of string headers will be sent as virtual request headers.\n\n\n##### Callback\n\n|   | Argument  | Type         | Details |\n|---|:----------|:------------:|:--------|\n| 1 | `resData` | ((json))     | Data received in the response from the Sails server (=== `jwres.body`, and also equivalent to the HTTP response body).\n| 2 | `jwres`   | ((dictionary))      | A JSON WebSocket Response object.  Has `headers`, a `body`, and a `statusCode`.\n\n\n\n\n\n### Example\n\n```javascript\nio.socket.request({\n  method: 'get',\n  url: '/user/3/friends',\n  data: {\n    limit: 15\n  },\n  headers: {\n    'x-csrf-token': 'ji4brixbiub3'\n  }\n}, function (resData, jwres) {\n  if (jwres.error) {\n    console.log(jwres.statusCode); // => e.g. 403\n    return;\n  }\n\n  console.log(jwres.statusCode); // => e.g. 200\n\n});\n```\n\n\n\n### Notes\n> + A helpful analogy might be to think of the difference between `io.socket.get` and this method as the difference between JQuery's `$.get` and `$.ajax`.\n> + Remember that you can communicate with _any of your routes_ using socket requests.\n> + Need to set custom headers for _all_ outgoing requests?  Check out [`io.sails.headers`](https://sailsjs.com/documentation/reference/web-sockets/socket-client/io-sails).\n\n<docmeta name=\"displayName\" value=\"io.socket.request()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/websockets/sails.sockets/sails.sockets.addRoomMembersToRooms.md",
    "content": "# `.addRoomMembersToRooms()`\n\nSubscribe all members of a room to one or more additional rooms.\n\n```js\nsails.sockets.addRoomMembersToRooms(sourceRoom, destRooms, cb);\n```\n\n\n\n### Usage\n\n|   | Argument   | Type        | Details |\n|---|------------|:-----------:|:--------|\n| 1 | sourceRoom | ((string)) | The room from which to retrieve members.\n| 2 | destRooms  | ((string)), ((array))  | The room or rooms to which to subscribe the members of `sourceRoom`.\n| 3 | cb         | ((function?))| An optional callback, which will be called when the operation is complete on the current server (see notes below for more information) or if fatal errors were encountered.  In the case of errors, it will be called with a single argument (`err`).\n\n\n### Example\n\nIn a controller action:\n\n```javascript\nsubscribeFunRoomMembersToFunnerRooms: function(req, res) {\n  sails.sockets.addRoomMembersToRooms('funRoom', ['greatRoom', 'awesomeRoom'], function(err) {\n    if (err) {return res.serverError(err);}\n    res.json({\n      message: 'Subscribed all members of `funRoom` to `greatRoom` and `awesomeRoom`!'\n    });\n  });\n}\n```\n\n### Notes\n> + In a multi-server environment, the callback function (`cb`) will be executed when the `.addRoomMembersToRooms()` call completes _on the current server_.  This does not guarantee that other servers in the cluster have already finished running the operation.\n\n<docmeta name=\"displayName\" value=\".addRoomMembersToRooms()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/websockets/sails.sockets/sails.sockets.blast.md",
    "content": "# `.blast()`\n\nBroadcast a message to all sockets connected to the server (or any server in the cluster, if you have a multi-server deployment using Redis).\n\n```javascript\nsails.sockets.blast(data);\n```\n\nor:\n+ `sails.sockets.blast(eventName, data);`\n+ `sails.sockets.blast(data, socketToOmit);`\n+ `sails.sockets.blast(eventName, data, socketToOmit);`\n\n\n\n### Usage\n\n|   |         Argument           | Type                | Details                                                           |\n|---|:-------------------------- | ------------------- |:----------------------------------------------------------------- |\n| 1 |        _eventName_         | ((string?))         | Optional. Defaults to `'message'`.\n| 2 |        data                | ((json))            | The data to send in the message.\n| 3 |       _socketToOmit_       | ((req?))            | Optional. If provided, the socket associated with this socket request will **not** receive the message blasted out to everyone else.  Useful when the broadcast-worthy event is triggered by a requesting user who doesn't need to hear about it again.\n\n\n\n\n### Example\n\nIn a controller action...\n\n```javascript\nsails.sockets.blast('user_logged_in', {\n  msg: 'User #' + user.id + ' just logged in.',\n  user: {\n    id: user.id,\n    username: user.username\n  }\n}, req);\n```\n\n### Notes\n> + Be sure to check that `req.isSocket === true` before passing in `req` to this method. For the socket to be omitted, the current `req`  must be from a socket request, not just any HTTP request.\n\n\n<docmeta name=\"displayName\" value=\".blast()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/websockets/sails.sockets/sails.sockets.broadcast.md",
    "content": "# `.broadcast()`\n\nBroadcast a message to all sockets in a room (or to a particular socket).\n\n```javascript\nsails.sockets.broadcast(roomNames, data);\n```\n\n_Or:_\n+ `sails.sockets.broadcast(roomNames, eventName, data);`\n+ `sails.sockets.broadcast(roomNames, data, socketToOmit);`\n+ `sails.sockets.broadcast(roomNames, eventName, data, socketToOmit);`\n\n\n### Usage\n\n|   |          Argument           | Type                | Details\n|---|:--------------------------- | ------------------- |:-----------\n| 1 |        roomNames              | ((string)), ((Array))          | The name of one or more rooms in which to broadcast a message (see [sails.sockets.join](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/join)).  To broadcast to individual sockets, use their IDs as room names.\n| 2 |        _eventName_            | ((string?))          | Optional. The unique name of the event used by the client to identify this message.  Defaults to `'message'`.\n| 3 |        data                   | ((json))          | The data to send in the message.\n| 4 |        _socketToOmit_         | ((req?))          | Optional. If provided, the socket belonging to the specified socket request will *not* receive the message.  This is useful if you trigger the broadcast from a client, but don't want that client to receive the message itself (for example, sending a message to everybody else in a chat room).\n\n\n### Example\n\nIn an action, service, or arbitrary script on the server:\n\n```javascript\nsails.sockets.broadcast('artsAndEntertainment', { greeting: 'Hola!' });\n```\n\nOn the client:\n\n```javascript\nio.socket.on('message', function (data){\n  console.log(data.greeting);\n});\n```\n\n\n##### Additional Examples\n\nMore examples of `sails.sockets.brodcast()` usage are [available here](https://gist.github.com/mikermcneil/0a4d05750768a99b4fcb), including broadcasting to multiple rooms, using a custom event name, and omitting the requesting socket.\n\n\n### Notes\n> + `sails.sockets.broadcast()` is more or less equivalent to the functionality of `.emit()` and `.broadcast()` in Socket.IO.\n> + Every socket is automatically subscribed to a room with its ID as the name, allowing direct messaging to a socket via [`sails.sockets.broadcast()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/sails-sockets-broadcast)\n> + Be sure to check that `req.isSocket === true` before passing in `req` as `socketToOmit`. For the requesting socket to be omitted, the request (`req`) must be from a socket request, not just any old HTTP request.\n> + `data` must be JSON-serializable; i.e. it's best to use plain dictionaries/arrays, and make sure your data does not contain any circular references. If you aren't sure, build your broadcast `data` manually, or call something like [`rttc.dehydrate(data,true,true)`](https://github.com/node-machine/rttc/blob/master/README.md#dehydratevalue-allownullfalse-dontstringifyfunctionsfalse) on it first.\n\n<docmeta name=\"displayName\" value=\".broadcast()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/websockets/sails.sockets/sails.sockets.getid.md",
    "content": "# `.getId()`\n\nParse the socket ID from an incoming socket request (`req`).\n\n```javascript\nsails.sockets.getId(req);\n```\n\n### Usage\n\n|   |          Argument           | Type                | Details\n|---| --------------------------- | ------------------- | -----------\n| 1 |           req               | ((req))             | A socket request (`req`).\n\n\nOnce acquired, the socket object's ID can be used to send direct messages to that socket (see [sails.sockets.broadcast](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/broadcast)).\n\n\n### Example\n\n```javascript\n// Controller action\ngetSocketID: function(req, res) {\n  if (!req.isSocket) {\n    return res.badRequest();\n  }\n\n  var socketId = sails.sockets.getId(req);\n  // => \"BetX2G-2889Bg22xi-jy\"\n\n  sails.log('My socket ID is: ' + socketId);\n\n  return res.json(socketId);\n}\n```\n\n\n### Notes\n> + Be sure to check that `req.isSocket === true` before passing in `req`. This method does not work for HTTP requests!\n\n\n<docmeta name=\"displayName\" value=\".getId()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/websockets/sails.sockets/sails.sockets.id.md",
    "content": "# sails.sockets.id()\n\nThis method is an alias for [sails.sockets.getId()](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/get-id) deprecated in Sails v0.12.  Please see the [v0.12 migration guide](https://sailsjs.com/documentation/concepts/upgrading/to-v-0-12) for more information.\n\n<docmeta name=\"displayName\" value=\"sails.sockets.id()\">\n<docmeta name=\"isDeprecated\" value=\"true\">\n"
  },
  {
    "path": "docs/reference/websockets/sails.sockets/sails.sockets.join.md",
    "content": "# `.join()`\n\nSubscribe a socket to a room.\n\n```js\nsails.sockets.join(socket, roomName);\n```\n\nor:\n\n+ `sails.sockets.join(socket, roomName, cb);`\n\n\n### Usage\n\n|   | Argument   | Type        | Details |\n|---|------------|:-----------:|:--------|\n| 1 | socket     | ((string)), ((req)) | The socket to be subscribed.  May be specified by the socket's ID or an incoming socket request (`req`).\n| 2 | roomName   | ((string))  | The name of the room to which the socket will be subscribed.  If the room does not exist yet, it will be created.\n| 3 | _cb_       | ((function?))| An optional callback, which will be called when the operation is complete on the current server (see notes below for more information), or if fatal errors were encountered.  In the case of errors, it will be called with a single argument (`err`).\n\n\n### Example\n\nIn a controller action:\n\n```javascript\nsubscribeToFunRoom: function(req, res) {\n  if (!req.isSocket) {\n    return res.badRequest();\n  }\n\n  var roomName = req.param('roomName');\n  sails.sockets.join(req, roomName, function(err) {\n    if (err) {\n      return res.serverError(err);\n    }\n\n    return res.json({\n      message: 'Subscribed to a fun room called '+roomName+'!'\n    });\n  });\n}\n```\n\n### Notes\n> + `sails.sockets.join()` is more or less equivalent to the functionality of `.join()` in Socket.IO, but with additional built-in support for multi-server deployments.  With [recommended production settings](https://sailsjs.com/documentation/concepts/deployment/scaling), `sails.sockets.join()` works as documented, no matter what server the code happens to be running on or the server to which the target socket is connected.\n> + In a multi-server environment, when calling `.join()` with a socket ID argument, the callback function (`cb`) will be executed when the `.join()` call completes _on the current server_.  This does not guarantee that other servers in the cluster have already finished running the operation.\n> + Every socket is automatically subscribed to a room with its ID as the name, allowing direct messaging to a socket via [`sails.sockets.broadcast()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/sails-sockets-broadcast)\n> + Be sure to check that `req.isSocket === true` before passing in `req` as the target socket.  For that to work, the provided `req` must be from a socket request, not just any old HTTP request.\n\n\n<docmeta name=\"displayName\" value=\".join()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/websockets/sails.sockets/sails.sockets.leave.md",
    "content": "# `.leave()`\n\nUnsubscribe a socket from a room.\n\n\n```js\nsails.sockets.leave(socket, roomName);\n```\n\nor:\n\n+ `sails.sockets.leave(socket, roomName, cb);`\n\n\n### Usage\n\n|   | Argument   | Type        | Details |\n|---|------------|:-----------:|:--------|\n| 1 | socket     | ((string)), ((req)) | The socket to be unsubscribed.  May be either the incoming socket request (req) or the ID of another socket.\n| 2 | roomName   | ((string))  | The name of the room to which the socket will be unsubscribed.\n| 3 | _cb_       | ((function?))| An optional callback, which will be called when the operation is complete on the current server (see notes below for more information), or if fatal errors were encountered.  In the case of errors, it will be called with a single argument (`err`).\n\n\n### Example\n\nIn a controller action, unsubscribe the requesting socket from the specified room:\n\n```javascript\nleaveFunRoom: function(req, res) {\n  if ( _.isUndefined(req.param('roomName')) ) {\n    return res.badRequest('`roomName` is required.');\n  }\n\n  if (!req.isSocket) {\n    return res.badRequest('This endpoints only supports socket requests.');\n  }\n\n  var roomName = req.param('roomName');\n  sails.sockets.leave(req, roomName, function(err) {\n    if (err) {return res.serverError(err);}\n    return res.json({\n      message: 'Left a fun room called '+roomName+'!'\n    });\n  });\n}\n```\n\n\n##### Additional Examples\n\nMore examples of `sails.sockets.leave()` usage are [available here](https://gist.github.com/mikermcneil/971b4e92d833211a0243), including unsubscribing other sockets by ID, deeper integration with the database, usage within a service, and usage with the `async` library.\n\n\n### Notes\n> + `sails.sockets.leave()` is more or less equivalent to the functionality of `.leave()` in Socket.IO, but with additional built-in support for multi-server deployments.  With [recommended production settings](https://sailsjs.com/documentation/concepts/deployment/scaling), `sails.sockets.leave()` works as documented no matter what server the code happens to be running on or the server to which the target socket is connected.\n> + In a multi-server environment, when calling `.leave()` with a socket ID argument, the callback function (`cb`) will be executed when the `.leave()` call completes _on the current server_.  This does not guarantee that other servers in the cluster have already finished running the operation.\n> + Be sure to check that `req.isSocket === true` before passing in `req` as the socket to be unsubscribed.  For that to work, the provided `req` must be from a socket request, not just any old HTTP request.\n\n\n\n<docmeta name=\"displayName\" value=\".leave()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/websockets/sails.sockets/sails.sockets.leaveAll.md",
    "content": "# `.leaveAll()`\n\nUnsubscribe all members of a room (e.g. `chatroom7`) from that room _and_ every other room they are currently subscribed to, except the automatic room associated with their socket ID.\n\n```javascript\nsails.sockets.leaveAll(roomName, cb);\n```\n\n\n### Usage\n\n|   | Argument   | Type        | Details |\n|---|:-----------|:-----------:|:--------|\n| 1 | roomName   | ((string)) | The room to evactuate.  Note that this room's members will be forced to leave _all of their rooms_, not just this one.\n| 2 | cb         | ((function?))| An optional callback, which will be called when the operation is complete _on the current server_ (see notes below for more information), or if fatal errors were encountered.  In the case of errors, it will be called with a single argument (`err`).\n\n### Example\n\nIn a controller action:\n\n```javascript\nunsubscribeFunRoomMembersFromEverything: function(req, res) {\n\n  sails.sockets.leaveAll('funRoom', function(err) {\n    if (err) { return res.serverError(err); }\n\n    // Unsubscribed all sockets in \"funRoom\" from \"funRoom\".\n    // And... from every other room too.\n\n    return res.ok();\n\n  });\n}\n```\n\n\n### Notes\n> + In a multi-server environment, the callback function (`cb`) will be executed when the `.leaveAll()` call completes _on the current server_.  This does not guarantee that other servers in the cluster have already finished running the operation.\n\n<docmeta name=\"displayName\" value=\".leaveAll()\">\n<docmeta name=\"pageType\" value=\"method\">\n\n"
  },
  {
    "path": "docs/reference/websockets/sails.sockets/sails.sockets.md",
    "content": "# Sockets (`sails.sockets`)\n\n### Overview\n\nSails exposes several methods (`sails.sockets.*`) that provide a simple interface for [realtime communication](https://sailsjs.com/documentation/concepts/realtime) with connected socket clients.  These are useful for pushing events and data to connected clients in realtime, rather than waiting for their HTTP requests.  These methods are available regardless of whether a client socket was connected from a browser tab, an iOS app, or your favorite household IoT appliance.\n\nThese methods are implemented using a built-in instance of [Socket.IO](http://socket.io), which is available directly as [`sails.io`](https://sailsjs.com/documentation/reference/application/advanced-usage#?sailsio).  However, you should _almost never_ use `sails.io` directly.  Instead, you should call the methods available on `sails.sockets.*`.  In addition, for certain use cases, you might also want to take advantage of [resourceful PubSub methods](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub), which access a higher level of abstraction and are used by Sails' built-in [blueprint API](https://sailsjs.com/documentation/reference/blueprint-api).\n\n\n### Methods\n\n| Method                             | Description                                              |\n|:-----------------------------------|:---------------------------------------------------------|\n| [`.addRoomMembersToRooms()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/add-room-members-to-rooms)        | Subscribe all members of a room to one or more additional rooms.\n| [`.blast()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/blast)        | Broadcast a message to all sockets connected to the server.\n| [`.broadcast()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/broadcast)        | Broadcast a message to all sockets in a room.\n| [`.getId()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/get-id)        | Parse the socket ID from an incoming socket request (`req`).\n| [`.join()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/join)        | Subscribe a socket to a room.\n| [`.leave()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/leave)        | Unsubscribe a socket from a room.\n| [`.leaveAll()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/leave-all)        | Unsubscribe all members of one room from that room _and_ from every other room they are currently subscribed to, except the automatic room with the same name as each socket ID.\n| [`.removeRoomMembersFromRooms()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/remove-room-members-from-rooms)        | Unsubscribe all members of a room from one or more other rooms.\n\n\n> Don't see a method you're looking for above?  A number of `sails.sockets` methods were deprecated in Sails v0.12, either because a more performant alias was already available, or for performance and scalability reasons.  Please see the [v0.12 migration guide](https://sailsjs.com/documentation/concepts/upgrading/to-v-0-12) for more information.\n\n\n\n<docmeta name=\"displayName\" value=\"sails.sockets\">\n<docmeta name=\"pageType\" value=\"property\">\n"
  },
  {
    "path": "docs/reference/websockets/sails.sockets/sails.sockets.removeRoomMembersFromRoom.md",
    "content": "# `.removeRoomMembersFromRooms()`\n\nUnsubscribe all members of a room from one or more other rooms.\n\n```js\nsails.sockets.removeRoomMembersFromRooms(sourceRoom, destRooms, cb);\n```\n\n\n### Usage\n\n|   | Argument       | Type                         | Details |\n|---|----------------|:----------------------------:|:--------|\n| 1 | sourceRoom     | ((string))                   | The room from which to retrieve members.\n| 2 | destRooms      | ((string)), ((array))        | The room or rooms from which to unsubscribe the members of `sourceRoom`.\n| 3 | cb             | ((function?))                | An optional callback, which will be called when the operation is complete _on the current server_ (see notes below for more information), or if fatal errors were encountered.  In the case of errors, it will be called with a single argument (`err`).\n\n\n### Example\n\nIn a controller action:\n\n```javascript\nunsubscribeFunRoomMembersFromFunnerRooms: function(req, res) {\n  sails.sockets.removeRoomMembersFromRooms('funRoom', ['greatRoom', 'awesomeRoom'], function(err) {\n    if (err) {return res.serverError(err);}\n    res.json({\n      message: 'Unsubscribed all members of `funRoom` from `greatRoom` and `awesomeRoom`!'\n    });\n  });\n}\n```\n\n### Notes\n> + In a multi-server environment, the callback function (`cb`) will be executed when the `.removeRoomMembersFromRooms()` call completes _on the current server_.  This does not guarantee that other servers in the cluster have already finished running the operation.\n\n<docmeta name=\"displayName\" value=\".removeRoomMembersFromRooms()\">\n<docmeta name=\"pageType\" value=\"method\">\n"
  },
  {
    "path": "docs/reference/websockets/websockets.md",
    "content": "# WebSockets\n\nFor a full discussion of realtime concepts in Sails, see the [Realtime concept documentation](https://sailsjs.com/documentation/concepts/realtime).\n\nFor information on client-to-server socket communication, see the [Socket Client (sails.io.js)](https://sailsjs.com/documentation/reference/web-sockets/socket-client).\n\nFor information on server-to-client socket communication, see the [sails.sockets](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets).\n\nFor information on using realtime messages to communicate changes in Sails models, see the [Resourceful PubSub reference](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub).\n\nSails uses [socket.io](http://socket.io) as the underlying engine for realtime communication.  Every Sails app has a Socket.IO instance available as `sails.io`.  However, most `socket.io` functionality is wrapped for convenience (and safety) by a `sails.sockets` method.\n\n<docmeta name=\"displayName\" value=\"WebSockets\">\n\n"
  },
  {
    "path": "docs/security/README.md",
    "content": "# docs/security\n\nThis section contains the official policy for disclosing security vulnerabilities in Sails or our dependencies.  It is made available at https://sailsjs.com/security-policy.\n\n\n### Notes\n> - This README file **is not compiled to HTML** for the website.  It is just here to explain what you're looking at.\n> - Depending on what branch of `sails` you are currently viewing, the domain may vary. See the top-level documentation README file for information about working with the markdown files in this repo, and to understand the branching/versioning strategy.\n\n<docmeta name=\"notShownOnWebsite\" value=\"true\">\n"
  },
  {
    "path": "docs/security/SAILS-SECURITY-POLICY.md",
    "content": "# Security policy\n\nSails is committed to providing a secure framework, and quickly responding to any suspected security vulnerabilities.  Contributors work carefully to ensure best practices, but we also rely heavily on the community when it comes to discovering, reporting, and remediating security issues.\n\n### Reporting a security issue in Sails\n\nIf you believe you've found a security vulnerability in Sails, Waterline, or one of the other modules maintained by the Sails core team, please send an email to **critical at sailsjs dot com**.  In the spirit of [responsible disclosure](https://en.wikipedia.org/wiki/Responsible_disclosure), we ask that you privately report any security vulnerability at that email address, and give us time to patch the issue before publishing the details</em>.\n\n### What is a security vulnerability?\n\nA security vulnerability is any major bug or unintended consequence that could compromise a Sails.js app in production.\n\nFor example, an issue where Sails crashes in a development environment when using non-standard Grunt tasks is _not a security vulnerability_.  On the other hand, if it was possible to perform a trivial DoS attack on a Sails cluster running in a production environment and using documented best-practices (a la the [Express/Connect body parser issue](http://expressjs-book.com/index.html%3Fp=140.html)), that _is a security vulnerability_ and we want to know about it.\n\n> Note that this definition includes any such vulnerability that exists due to one of our dependencies.  In this case, an upgrade to a different version of the dependency is not always necessary: for example, when Express 3 deprecated multipart upload support in core, Sails.js dealt with the feature mismatch by implementing a wrapper around the `multiparty` module called [Skipper](https://github.com/balderdashy/skipper#history).\n\n### What should be included in the email?\n\n- The name and NPM version string of the module where you found the security vulnerability (e.g. Sails, Waterline, other core module).\n- A summary of the vulnerability\n- The code you used when you discovered the vulnerability or a code example of the vulnerability (whichever is shorter).\n- Whether you want us to make your involvement public.  If you want such a reference the name and link you wish to be referred (e.g. Jane Doe's link to her GitHub account)\n\n> Please respect the core team's privacy and do not send bugs resulting from undocumented usage, questions, or feature requests to this email address.\n\n### The process\nWhen you report a vulnerability, one of the project members will respond to you within a maximum of 72 hours.  This response will most likely be an acknowledgement that we've received the report and will be investigating it immediately.  Our target patching timeframe for most security vulnerabilities is 14 days.\n\nBased upon the nature of the vulnerability, and the amount of time it would take to fix, we'll either send out a patch that disables the broken feature, provide an estimate of the time it will take to fix, and/or document best practices to follow to avoid production issues.\n\nYou can expect follow-up emails outlining the progression of a solution to the vulnerability along with any other questions we may have regarding your experience.\n\n##### When a solution is achieved we do the following:\n\n- notify you\n- release a patch on NPM\n- coordinate with [Node Security](http://nodesecurity.io) to issue an [advisory](https://nodesecurity.io/advisories?search=sails), crediting you (unless you expressly asked not to be identified)\n- publicize the release via our [newsgroup](https://groups.google.com/forum/#!forum/sailsjs)\n\n### Is this an SLA?\n\nNo. The Sails framework is available under the [MIT license](https://sailsjs.com/license), which does not include a service level agreement.  However, the core team and contributors care deeply about Sails, and all of us have websites and APIs running on Sails in production.  We will _always_ publish a fix for any serious security vulnerability as soon as possible-- not just out of the kindness of our hearts, but because it could affect our apps (and our customer's apps) too.\n\n> For more support options, see https://sailsjs.com/support.\n\n\n"
  },
  {
    "path": "docs/tutorials/coffeeScript.md",
    "content": "# Using CoffeeScript in a Sails app\n\n**The recommended language for building Node.js+Sails apps is JavaScript.**\n\nBut Sails also supports using CoffeeScript to write your custom app code (like [actions](http://www.sailsjs.com/documentation/concepts/actions-and-controllers) and [models](http://www.sailsjs.com/documentation/concepts/core-concepts-table-of-contents/models-and-orm)).  You can enable this support in three steps:\n\n1. Run `npm install coffee-script --save` in your app folder.\n2. Add the following line at the top of your app's `app.js` file:\n```javascript\nrequire('coffee-script/register');\n```\n3. Start your app with `node app.js` instead of `sails lift`.\n\n### Using CoffeeScript generators\n\nIf you want to use CoffeeScript to write your controllers, models or config files, just follow these steps:\n 1. Install the generators for CoffeeScript (optional): <br/>`npm install --save-dev sails-generate-controller-coffee sails-generate-model-coffee`\n 2. To generate scaffold code, add `--coffee` when using one of the supported generators from the command-line:\n```bash\nsails generate api <foo> --coffee\n# Generate api/models/Foo.coffee and api/controllers/FooController.coffee\nsails generate model <foo> --coffee\n# Generate api/models/Foo.coffee\nsails generate controller <foo> --coffee\n# Generate api/controllers/FooController.coffee\n```\n\n<docmeta name=\"displayName\" value=\"Using CoffeeScript\">\n"
  },
  {
    "path": "docs/tutorials/full-stack-javascript.md",
    "content": "# Full-stack JavaScript with Sails\n\nThis video tutorial is an in-depth guide to building your first Node.js/Sails.js app, taught by [the creator of the framework](https://twitter.com/mikermcneil), and following the best practices and conventions our team uses for all our projects. We walk you through setting up your development environment and building our demo app, [Ration](https://ration.io).\n\n\n#### Links\n+ [Take the course](https://platzi.com/cursos/sails-js/)\n+ [Try out the demo app (Ration)](https://ration.io)\n+ [Download the source code](https://github.com/mikermcneil/ration) for the demo app\n\n<docmeta name=\"displayName\" value=\"Full-stack JavaScript with Sails\">\n"
  },
  {
    "path": "docs/tutorials/low-level-mysql-access.md",
    "content": "# Low-level MySQL usage (advanced)\n\nThis tutorial steps through how to access the raw MySQL connection instance from the [`mysql` package](https://www.npmjs.com/package/mysql).  This is useful for getting access to low level APIs available only in the raw client itself. \n\n> Note: Many Node.js / Sails apps using MySQL will never need the kind of low-level usage described here.  If you find yourself running up against the limitations of the ORM, there is usually a workaround that does not involve writing code for the underlying database.  Even then, if you're just looking to use custom native SQL queries, read no further-- instead, check out [`sendNativeQuery()`](/documentation/reference/waterline-orm/datastores/send-native-query) instead.\n>\n> Also, before we proceed, make sure you have a datastore configured to use a functional MySQL database.\n\n### Get access to an active MySQL connection\n\nTo obtain an active connection from the MySQL package you can call the [`.leaseConnection()`](/documentation/reference/waterline-orm/datastores/lease-connection) method of a registered datastore object (RDI).\n\n1. Get the registered datastore instance for the connection:\n\n```javascript\n// Get the named datastore\nvar rdi = sails.getDatastore('default');\n\n// Get the datastore configured for a specific model\nvar rdi = Product.getDatastore();\n```\n\n2. Call the `leaseConnection()` method to obtain an active connection:\n\n```javascript\nrdi.leaseConnection(function(connection, proceed) {\n  db.query('SELECT * from `user`;', function(err, results, fields) {\n    if (err) {\n      return proceed(err);\n    }\n\n    proceed(undefined, results);\n  });\n}, function(err, results) {\n  // Handle results here after the connection has been closed\n})\n```\n\n### Get access to the low-level driver\n\nTo get access to the low-level driver and MySQL package in a Sails app, you can grab them from the registered datastore object (RDI).\n\n1. Get the registered datastore instance for the connection:\n\n```javascript\n// Get the named datastore\nvar rdi = sails.getDatastore('default');\n\n// Get the datastore configured for a specific model\nvar rdi = Product.getDatastore();\n```\n\n2. Get the driver from the datastore instance which contains the MySQL module:\n\n```javascript\nvar mysql = rdi.driver.mysql;\n```\n\n3. You can now use the module to make native requests and call other function native to the MySQL module:\n\n```javascript\n// Get the named datastore\nvar rdi = sails.getDatastore('default');\n\n// Grab the MySQL module from the datastore instance\nvar mysql = rdi.driver.mysql;\n\n// Create a new connection\nvar connection = mysql.createConnection({\n  host     : 'localhost',\n  user     : 'root',\n  password : 'password',\n  database: 'example_database'\n});\n\n// Make a query and pipe the results\nconnection.query('SELECT * FROM posts')\n  .stream({highWaterMark: 5})\n  .pipe(...);\n```\n\n<docmeta name=\"displayName\" value=\"Low-level MySQL usage (advanced)\">\n"
  },
  {
    "path": "docs/tutorials/mongo.md",
    "content": "# Using MongoDB with Node.js/Sails.js\n\nSails supports the popular [MongoDB database](https://www.mongodb.com/) via the [sails-mongo adapter](https://www.npmjs.com/package/sails-mongo).\n\n> First, make sure you have access to a running MongoDB server, either on your development machine or in the cloud.  Below, 'mongodb://root@localhost/foo' refers to a locally-installed MongoDB using \"foo\" as the database name.  Be sure to replace that [connection URL](https://sailsjs.com/documentation/reference/configuration/sails-config-datastores#?the-connection-url) with the appropriate string for your database.\n\n### Developing locally with MongoDB\n\nTo use MongoDB in your Node.js/Sails app during development:\n\n1. Run `npm install sails-mongo` in your app folder.\n2. In your `config/datastores.js` file, edit the `default` datastore configuration:\n\n    ```js\n    default: {\n      adapter: 'sails-mongo',\n      url: 'mongodb://root@localhost/foo'\n    }\n    ```\n3. In your `config/models.js` file, edit the default `id` attribute to have the appropriate `type` and `columnName` for MongoDB's primary keys:\n\n    ```js\n    attributes: {\n      id: { type: 'string', columnName: '_id' },\n      //…\n    }\n    ```\n\nThat's it!  Lift your app again and you should be good to go.\n\n#### Case-sensitivity\n\nAfter configuring your project to use MongoDB, you may notice that your Waterline [queries](https://sailsjs.com/documentation/reference/waterline-orm/queries) are now case-sensitive by default. To do case-insensitive queries, you can use [`.meta({makeLikeModifierCaseInsensitive: true})`](https://sailsjs.com/documentation/reference/waterline-orm/queries/meta).\n\n### Deploying your app with MongoDB\n\nTo use MongoDB in production, edit your adapter setting in `config/env/production.js`:\n\n```js\nadapter: 'sails-mongo',\n```\n\nYou may also configure your [connection URL](https://sailsjs.com/documentation/reference/configuration/sails-config-datastores#?the-connection-url) -- but many developers prefer not to check sensitive credentials into version control.  Another option is to use an environment variable:\n\n```\nsails_datastores__default__url=mongodb://heroku_12345678:random_password@ds029017.mLab.com:29017/heroku_12345678\n```\n\n> To use MongoDB in your staging environment, edit `config/env/staging.js`.  Depending on your application, it may be acceptable to check in your staging database credentials to version control, since they are less of a security risk.\n\n\n### Low-level MongoDB usage (advanced)\n\nAs with all of the [Sails database adapters](https://sailsjs.com/documentation/concepts/extending-sails/adapters/available-adapters), you can use any of the [Waterline model methods](https://sailsjs.com/documentation/reference/waterline-orm/models) to interact with your models when using `sails-mongo`.\n\nFor many apps, that's all you'll need-- from \"hello world\" to production.  Even if you run into limitations, they can usually be worked around without writing Mongo-specific code.  However, for situations when there is no alternative, it is possible to use the Mongo driver directly in your Sails app.\n\nTo access the lower-level &ldquo;native&rdquo; MongoDB client directly, use the [`.manager`](https://sailsjs.com/documentation/reference/waterline-orm/datastores/manager) property of the [datastore instance](https://sailsjs.com/documentation/reference/application/sails-get-datastore).\n\nAs of `sails-mongo` v2.0.0 and above, you can access the [`MongoClient`](https://mongodb.github.io/node-mongodb-native/3.5/api/MongoClient.html) object via `manager.client`. This gives you access to the latest MongoDB improvements, like [`ClientSession`](https://mongodb.github.io/node-mongodb-native/3.5/api/ClientSession.html),\nand with it, transactions, [change streams](https://mongodb.github.io/node-mongodb-native/3.5/api/ChangeStream.html), and other new features.\n\n```js\nvar mongoClient = Pet.getDatastore().manager.client;\n\nvar results = await mongoClient.db('test')\n.collection('pet')\n.find({}, { name: 1 })\n.toArray();\n\nconsole.log(results);\n```\n\nFor a full list of methods available in the native MongoDB client, see the [Node.js MongoDB Driver API reference](https://mongodb.github.io/node-mongodb-native/3.5/api/Collection.html).\n\n\n<docmeta name=\"displayName\" value=\"Using MongoDB\">\n"
  },
  {
    "path": "docs/tutorials/tutorials.md",
    "content": "# Tutorials\n\nHere you can find step-by-step guides for dealing with a few specific Sails use cases. There's a wealth of great information and tutorials about Sails across the web; these are just a few that have been personally vetted by members of the core team.  Currently, the objective of this section of the docs is to cover some less common scenarios that users have asked about, but that would add unnecessary complexity to the reference & conceptual documentation if they were included there. In the future, we hope to also address some common _\"How do I ...?\"_ questions that we hear from newcomers to the framework.\n\nIf you have a use case you would like to see addressed here, and/or are interested in writing a tutorial yourself, we appreciate your input! Just take a look at our [contribution guide](https://sailsjs.com/documentation/contributing) if you haven't already, then open an issue in the [`sails`](https://github.com/balderdashy/sails/issues/new) repo with a proposal for the tutorial you'd like to see/write.\n\n<docmeta name=\"displayName\" value=\"Tutorials\">\n<docmeta name=\"isOverviewPage\" value=\"true\">\n"
  },
  {
    "path": "docs/tutorials/typeScript.md",
    "content": "# Using TypeScript in a Sails app\n\n**The recommended language for building Node.js+Sails apps is JavaScript.**\n\nBut Sails also supports using TypeScript to write your custom app code (like [actions](http://www.sailsjs.com/documentation/concepts/actions-and-controllers) and [models](https://sailsjs.com/documentation/concepts/models-and-orm)).  You can enable this support in just a few steps:\n\n1. Run `npm install typescript ts-node --save` in your app folder.\n2. Install the necessary typings for your app.  At the very least you'll probably want to:\n   ```\n   npm install @types/node --save\n   npm install @types/express --save\n   ```\n3. Add the following line at the top of your app's `app.js` file:\n```javascript\nrequire('ts-node/register');\n```\n4. Start your app with `node app.js` instead of `sails lift`.\n\nTo get you started, here's an example of a traditional Sails [controller](https://sailsjs.com/documentation/concepts/actions-and-controllers) written in Typescript, courtesy of [@oshatrk](https://github.com/oshatrk):\n\n```typescript\n// api/controllers/SomeController.ts\ndeclare var sails: any;\n\nexport function hello(req:any, res:any, next: Function):any {\n  res.status(200).send('Hello from Typescript!');\n}\n```\n\nTo try that example out, configure a route so that its target points at `SomeController.hello`, relift, and then visit the route in your browser or with a tool like Postman.\n\n<docmeta name=\"displayName\" value=\"Using TypeScript\">\n"
  },
  {
    "path": "docs/upgrading/To0.10.md",
    "content": "# Upgrading to Sails v0.10\n\nFor the most part, running sails lift in an existing v0.9 project should just work. The core contributors have taken a number of steps to make the upgrade as easy as possible, and if you follow the deprecation messages in the console, you should do just fine.\n\nSails v0.10 comes with some big changes. The sections below provide a high level overview of what's changed, major bug fixes, enhancements and new features, as well as a basic tutorial on how to upgrade your v0.9.x Sails app to v0.10.\n\n## File uploads\n\nThe Connect multipart middleware [will soon be officially deprecated](http://www.senchalabs.org/connect/multipart.html). But since this module was used as the built-in HTTP body parser in Sails v0.9 and Express v3, this is a breaking change for v0.9 Sails projects relying on `req.files`.\n\nBy default in v0.10, Sails includes [skipper](https://github.com/balderdashy/skipper), a body parser which allows for streaming file uploads without buffering tmp files to disk. For run-of-the-mill file upload use cases, Skipper comes with bundled support for uploads to local disk (via skipper-disk), but streaming uploads can be plugged in to any of its supported adapters.\n\nFor examples/documentation, please see the Skipper repository as well as the Sails documentation on `req.file()`.\n\n### Why?\n\nA body parser's job is to parse the \"body\" of incoming multipart HTTP requests. Sometimes, that \"body\" includes text parameters, but sometimes, it includes file uploads.\n\nConnect multipart is great code, and it supports both file uploads AND text parameters in multipart requests. But like most modules of its kind, it accomplishes this by buffering file uploads to disk. This can quickly overwhelm a server's available disk space, and in many cases exposes a serious DoS attack vulnerability.\n\nSkipper is unique in that it supports **streaming** file uploads, but also maintains support for metadata in the request body (i.e. JSON/XML/urlencoded request body parameters). It uses a handful of heuristics to make sure only the files you're expecting get plugged in and received by the blob adapter, and other (potentially malicous) file fields are ignored.\n\n> #### ** Important!**\n> For Skipper to work, you _must include all text parameters BEFORE file parameters_ in file upload requests to the server. Once Skipper sees the first file field, it stops waiting for text parameters (this is to avoid unnecessary/unsafe buffering of file data).\n\n### Configuring a different body parser\n\nAs with most things in Sails, you can use any Connect/Express/Sails-compatible bodyparser you like. To switch back to **connect-multipart**, or any other body parser (like **formidable** or **busboy**), change your app's http configuration.\n\n## Blueprints\n\nA new blueprint action (`findOne`) has been added. For instance, if you have a `FooController` and `Foo` model, then send a request to `/foo/5`, the `findOne` action in your `FooController` will run. If you don't have a `findOne` action, the `findOne` blueprint action will be used in its stead. Requests sent to `/foo` will still run the find controller/blueprint action.\n\n## Policies\n\nPolicies work exactly as they did in v0.9- however there is a new consideration you should take into account: Due to the introduction of the more specific `findOne()` blueprint action mentioned above, you will want to make sure you're handling it explicitly in your policy mapping configuration.\n\nFor example, let's say you have a v0.9 app whose `policies.js` configuration prevents access to the `find` action in your `DoveController`:\n\n```\nmodule.exports.policies = {\n  '*': true,\n  DoveController: {\n    find: false\n  }\n};\n```\n\nAssuming rest blueprint routes are enabled, this would prevent access to requests like both `/dove` and `/dove/14`. But now in v0.10, since `/dove/14` will actually run the `findOne` action, we must handle it explicitly:\n\n```\nmodule.exports.policies = {\n  '*': true,\n  DoveController: {\n    find: false,\n    findOne: false\n  }\n};\n```\n\n## Pubsub\n\n### Summary\n+ `message` socket (i.e. \"comment\") event on client is now `modelIdentity` (where \"modelIdentity\" is different depending on the model that the `publish*()` method was called from.\n+ Clients are no longer subscribed to model-creation events by the blueprint routes. To listen for creation events, use `Model.watch()`.\n+ The events that were formerly `create`, `update`, and `destroy` are now `created`, `updated`, and `destroyed`.\n\n### Details\nThe biggest change to pubsub is that Socket.io events are emitted under the name of the model emitting them. Previously, your client listened for the `message` event and then had to determine which model it came from based on the included data:\n\n```\nsocket.on('message', function(cometEvent) {\n   if (cometEvent.model == 'user') {\n     // Handle inbound messages related to a user record\n   }\n   else if (cometEvent.model === 'product') {\n     // Handle inbound messages related to a product record\n   }\n   // ...\n}\n```\nNow, you subscribe to the identity of the model:\n```\nsocket.on('user', function(cometEvent) {\n  // Handle inbound messages related to a user record\n});\n\nsocket.on('product', function (cometEvent) {\n  // Handle inbound messages related to a product record\n});\n```\nThis helps to structure your front end code.\n\nThe way you subscribe clients to models has also changed. Previously, you specified whether you were subscribing to the model class (class room) or one or more model instances based on the parameters that you passed to `Model.subscribe`. It was effectively one method to do two very different things.\n\nNow, you use `Model.subscribe()` to subscribe only to model instances (records). You can also specify event \"contexts\", or types, that you'd like to hear about. For example, if you only wanted to get messages about updates to an instance, you would call `User.subscribe(req, myUser, 'update')`. If no context is given in a call to `.subscribe()`, then all contexts specified by the model class's autosubscribe property will be used.\n\nTo subscribe to model creation events, you can now use `Model.watch()`. Upon subscription, your clients will receive messages every time a new record is created on that model using the blueprint routes, and will automatically be subscribed to the new instance as well.\n\nRemember, when working with blueprints, clients are no longer auto subscribed to the class room. This must be done manually.\n\nFinally, if you want to see all pubsub messages from all models, you can access the `firehose`, a development-only tool that broadcasts messages about _everything_ that happens to your models. You can subscribe to the firehose using `sails.sockets.subscribeToFirehose(socket)`, or on the front end by making a socket request to `/firehose`. The firehose will broadcast a `firehose` event whenever a model is created, updated, destroyed, added to, removed from or messaged. This effectively replaces the `message` event used in previous Sails versions.\n\nTo see examples of the new pubsub methods in action, see [SailsChat](https://github.com/balderdashy/sailschat).\n\n## Arguments to lifecycle callbacks are now typecasted\n\nPreviously, with `schema: true`, if you sent an attribute value to a `.create()` or `.update()` that did not match the expected type declared in the model's attributes, the value you passed in would still be accessible in your model's lifecycle callbacks.\n\nIn Sails/Waterline v0.10, this is no longer the case. Values passed to `.create()` and `.update()` are type-casted before your lifecycle callbacks run. Affected lifecycle callbacks include `beforeUpdate()`, `beforeCreate()`, and `beforeValidate()`.\n\n## beforeValidation() is now beforeValidate()\n\nIf you were using the `beforeValidation` or `afterValidation` model lifecycle callbacks in any of your models, you should change them to `beforeValidate` or `afterValidate`. This change was made in Waterline to match the style of the other lifecycle callbacks (e.g. `beforeCreate`, `afterUpdate`, etc.).\n\n## .done() vs. .exec()\n\n** The old (/confusing?) meaning of `.done()` has been deprecated.**\n\nIn Sails <= v0.8, the syntax for executing an ORM query was `Model. [ … ] .done( cb )`. In v0.9, when promise support was added, the `Model. [ … ] .exec( cb )` became the recommended replacement, since `.done()` has a special meaning in the promise spec. However, the original usage of `.done()` was left untouched to make upgrading from v0.8 to v0.9 easier.\n\nBut as of Sails/Waterline v0.10, the original meaning of `.done()` has been officially deprecated to allow for a more robust promise implementation going forward, and pluggable promise library support (e.g. choose `Q` or `Bluebird` etc.).\n\n## Associations\n\nSails v0.10 introduces associations between data models. Since the work we've done on associations is largely additive, your existing models should still just work. That said, this is a powerful new feature that allows you to write less code and makes your app more maintainable, so we suggest taking advantage of it! To learn about how to use associations in Sails, check out the docs.\n\nAssociations (or \"relations\") are really just special attributes. Instead of string or integer values, you can specify an instance of a model or a collection of model instances. You can think about this kind of like an object (`{...}`) or an array (`[{...}, {...}]`) you might store as JSON in a NoSQL database. The difference is, in Sails, this works with any of the supported databases, and even allows you to populate (i.e. join) across different databases and types of databases.\n\n## Generators\n\nSails has had support for generating code for a while now (e.g. `sails generate controller foo`) but in v0.10, we wanted to make this feature more extensible, open, and accessible to everybody in the Sails community. With that in mind, v0.10 comes with a complete rewrite of the command-line tool, and pluggable generators. Want to be able to run `sails generate blog foo` to make a new blog built on Sails? Create a `blog` generator (run sails `generate generator blog`), add your templates, and configure the generator to copy the new templates over. Then you can release it to the community by publishing an npm module called `sails-generate-blog`. Compatibility with Yeoman generators is also in our roadmap.\n\n## Command-line tool\n\nThe big change here is how you create a new api. In the past you called `sails generate new_api`. This would generate a new controller and model called `new_api` in the appropriate places. This is now done using `sails generate api new_api`.\n\nYou can still generate models and controllers seperately using the same CLI Commands.\n\nAlso, `--linker` switch is no longer available. In previous version, if `--linker` switch was provided, it created a `myApp/assets/linker folder`, with `js`, `styles` and `templates` folders inside. In this new version, the `myApp/assets/linker` folder is not created. Compiling CoffeeScript and Less is the default behavior now, right from the `myApp/assets/js` and `myApp/assets/scripts` folders.\n\n## Custom server responses\n\nIn v0.10, you can now generate your own custom server responses.\n\nLike before, there are a few that we automatically create for you. Instead of generating `myApp/config/500.js` and other `.js` responses in the config directory, they are now generated in `myApp/api/responses/`.\n\nTo migrate, you will need to create a new v0.10 project and copy the `myApp/api/responses` directory into your existing app. You will then modify the appropriate .js file to reflect any customization you made in your response logic files (500.js,etc).\n\n## Legacy data stored in the temporary sails-disk database\n\n`sails-disk`, used by default in new Sails projects, now stores data a bit differently. If you have some temporary data stored in a 0.9.x project, you'll want to wipe it out and start fresh. To do this:\n\nFrom your project's root directory:\n\n```\n$ rm .tmp/disk.db\n```\n\n## Adapter/Database Configuration\n\n`config.adapters` (in `myApp/config/adapters.js`) is now config.connections (in new projects, this is generated in `myApp/config/connections.js`). Also, `config.model` is now `config.models`.\n\nYour app's default `connection` (i.e. database) should now be configured as a string `config.models.connection` used by default for model. New projects are generated with a `/config/models.js` file that includes the default connection.\n\nTo configure a model to use specific adapters, you must now specify them in the `connection` key instead of `adapters`.\n\nFor example:\n```\nmodule.exports = {\n\n    connection: ['someMongoDatabase'],\n\n    attributes: {\n        name:{\n            type     : 'string',\n            required : true\n        }\n    }\n};\n```\n\n## Blueprints/Controller configuration\n\nThe object literal describing controller configuration overrides for controller blueprints should change from:\n```\n...\n_config: {\n  blueprints: {\n    rest: true,\n    ...\n  }\n}\n```\nto:\n```\n...\n_config: {\n    rest: true,\n    ...\n}\n```\n\n## Layout paths:\nIn Sails v0.9, you could use the following syntax to specify `auth/someLayout.ejs` as a custom layout when rendering a view:\n```\nreturn res.view('auth/login',{\n  layout: 'someLayout'\n});\n```\nHowever in Sails v0.10, all layout paths are relative to your app's views path. In other words, the relative path of the layout is no longer resolved from the view's own path-- it is now always resolved from the views path. This makes it easier to understand which file is being used, particularly when layout files have similar names:\n```\nreturn res.view('auth/login', {\n  layout: 'auth/someLayout'\n});\n```\n\n\n<docmeta name=\"displayName\" value=\"To v.0.10\">\n<docmeta name=\"version\" value=\"0.10.0\">\n"
  },
  {
    "path": "docs/upgrading/To0.11.md",
    "content": "# Upgrading to Sails v0.11\n\n\n**tldr;**\n\nv0.11 comes with many minor improvements, as well as some internal cleanup in core.  The biggest change is that Sails core is now using Socket.io v1.\n\nAlmost none of this should affect the existing code in project, but there are a few important differences and new features to be aware of.  We've listed them below.\n\n\n## Differences\n\n#### Upgrade the Socket.io / Sails.io browser client\n\nOld v0.9 socket.io client will no longer work, so consequently you'll need to upgrade your sails.io.js client from v0.9 or v0.10 to v0.11.\n\nTo do this, just remove your sails.io.js client and install the new one.  We've bundled a new generator that will do this for you, assuming your sails.io.js client is in the conventional location at `assets/js/dependencies/sails.io.js` (i.e. if you haven't moved or renamed it):\n\n```sh\nsails generate sails.io.js --force\n```\n\n\n####  `onConnect` lifecycle callback\n\n> **tldr;**\n>\n> Remove your `onConnect` function from `config/sockets.js`.\n\nThe `onConnect` lifecycle callback has been deprecated.  Instead, if you need to do something when a new socket is connected, send a request from the newly-connected client to do so.  The purpose of `onConnect` was always for optimizing performance (eliminating the need to do this initial extra round-trip with the server), yet its use can lead to confusion and race conditions. If you desperately need to eliminate the server roundtrip, you can bind a handler directly on `sails.io.on('connect', function (newlyConnectedSocket){})` in your bootstrap function (`config/bootstrap.js`). However, note that this is discouraged.  Unless you're facing _true_ production performance issues, you should use the strategy mentioned above for your \"on connection\" logic (i.e. send an initial request from the client after the socket connects).  Socket requests are lightweight, so this doesn't add any tangible overhead to your application, and it will help make your code more predictable.\n\n\n\n####  `onDisconnect` lifecycle callback\n\nThe `onDisconnect` lifecycle callback has been deprecated in favor of `afterDisconnect`.\n\nIf you were using `onDisconnect` previously, you might have had to change the `session`, then call `session.save()` manually.  In v0.11, this works in almost exactly the same way, except that `afterDisconnect` receives an additional 3rd argument: a callback function.  This way, you can just call the provided callback when your `afterDisconnect` logic has finished, so that Sails can persist any changes you've made to the session automatically.  Finally, as you might expect, you won't need to call `session.save()` manually anymore- it is now taken care of for you (just like `req.session` in a normal route, action, or policy.)\n\n\n> **tldr;**\n> Rename your `onDisconnect` function in `config/sockets.js` with the following:\n>\n```\nafterDisconnect: function (session, socket, cb) {\n // Be sure to call the callback\n return cb();\n}\n```\n\n\n\n\n####  Other configuration in `config/sockets.js`\n\nMany of the configuration options in Socket.io v1 have changed, so you'll want to update your `config/sockets.js` file accordingly.\n\n+ if you haven&rsquo;t customized any of the options in `config/sockets.js` for your app, you can safely remove or comment out the entire file and let the Sails defaults do their magic.  Otherwise, refer to the new [Sails sockets  documentation](https://sailsjs.com/documentation/reference/configuration/sails-config-sockets) to ensure that your configuration is still valid and avoid unwanted hair loss.\n+ if you are scaling to multiple servers in an environment that does *not support sticky sessions* (this includes Heroku), you'll need to set your `transports` to `['websocket']` in both `config/socket.js` and your client--see [our Scaling doc](https://sailsjs.com/documentation/concepts/deployment/scaling#?preparing-your-app-for-a-clustered-deployment) for more info.\n+ if you were using a custom `authorization` function to restrict socket connections, you'll now want to use `beforeConnect`.  `authorization` was deprecated by Socket.io v1, but `beforeConnect` (which maps to the `allowRequest` option from Engine.io) works just the same way.\n+ if you were using other low-level socket configuration that was passed directly to socket.io v1, be sure and check out the [reference page on sailsjs.com](https://sailsjs.com/documentation/reference/configuration/sails-config-sockets) where all of the new configuration options are covered in detail.\n\n\n#### The \"firehose\"\n\nThe \"firehose\" feature for testing with sockets has been deprecated.  If you don't know what that means, you have nothing to worry about. The basic usage will continue to work for a while, but it will soon be removed from core and should not be relied upon in your app.  This also applies to the following methods:\n  + sails.sockets.subscribeToFirehose()\n  + sails.sockets.unsubscribeFromFirehose()\n  + sails.sockets.drink()\n  + sails.sockets.spit()\n  + sails.sockets.squirt()\n\n> If you want the \"firehose\" back, let [Mike know on twitter](http://twitter.com/mikermcneil) (it can be brought back as a separate hook).\n\n#### Config files in subfolders\n\nIt has always been the intention that files in the Sails `config` folder have no precedence over each other, and that the filenames and subfolders (with the exception of `local.js` and the `env` and `locale` subfolders) be used merely for organization.  However, in previous Sails versions, saving config files in subfolders would have the effect that the filename would be added as a key in `sails.config`, so that if you saved some config in `config/foo/bar.js`, then that config would be namespaced under `sails.config.bar`.  This was unintentional and potentially confusing as 1) the directory name is ignored, and 2) moving the file would change the config key.  This has been fixed in v0.11.x: config files in subfolders will be treated the same as those in the root `config` folder.  If you are for some reason relying on the old behavior, you may set `dontFlattenConfig` to `true` in your `.sailsrc` file, but we would strongly recommend that you instead just namespace the config yourself by setting the desired key on `module.exports`; for example `module.exports.foo = {...}`.  See [issue #2544](https://github.com/balderdashy/sails/issues/2544) for more details.\n\n#### Waterline now uses Bluebird\n\nAs of v0.11, Waterline now supports Bluebird (instead of q) for promises.  If you are using `.exec()` you won't be affected-- only if you are using `.then()`.  See https://github.com/balderdashy/sails/issues/1186 for more information.\n\n\n## New features\n\nSails v0.11 also comes with some new stuff that we thought you'd like to know about:\n\n\n#### User-level hooks\n\nHooks can now be installed directly from NPM.\n\nThis means you can now install hooks with a single command in your terminal.  For instance, consider the [`autoreload` hook](https://github.com/sgress454/sails-hook-autoreload) by [@sgress454](https://twitter.com/sgress454), which watches for changes to your backend code so you don't need to kill and re-lift the server every time you change your controllers, routes, models, etc.\n\nTo install the `autoreload` hook, run:\n\n```sh\nnpm install sails-hook-autoreload\n```\n\nThis is just one example of what's possible.  As you might already know, hooks are the lowest-level pluggable abstraction in Sails.  They allow authors to tap into the lift process, listen for events, inject custom \"shadow\" routes, and, in general, take advantage of raw access to the `sails` runtime.\nMost of the features you're familiar with in Sails have actually already been implemented as \"core\" hooks for over a year, including:\n\n+ `blueprints` _(which provides the blueprint API)_\n+ `sockets`    _(which provides socket.io integration)_\n+ `grunt`      _(which provides Grunt integration)_\n+ `orm`        _(which provides integration with the Waterline ORM, and imports your projects adapters, models, etc.)_\n+ `http`       _(which provides an HTTP server)_\n+ and 16 others.\n\nYou can read more about how to write your own hooks in the [new and improved \"Extending Sails\" documentation](https://sailsjs.com/documentation/concepts/extending-sails) on https://sailsjs.com.\n\n\n#### Socket.io v1.x\n\nThe upgrade to Socket.io v1.0 shouldn't actually affect your app-level code, provided you are using the layer of abstraction provided by Sails itself; everything from the `sails.sockets.*` wrapper methods and \"up\" (resourceful pubsub, blueprints)\nIf you are using underlying socket.io methods in your apps, or are just curious about what changed in Socket.io v1.0, be sure and check out the [complete Socket.io 1.0 migration guide](http://socket.io/docs/migrating-from-0-9/) from Guillermo and the socket.io team.\n\n#### Ever-increasing modularity\n\nAs part of the upgrade to Socket.io v1.0, we pulled out the core `sockets` hook into a separate repository.  This allowed us to write some modular, hook-specific tests for the socket.io interpreter, which will make things easier to maintain, customize, and override.\nThis also allows the hook to grow at its own pace, and puts related issues in one place.\n\nConsider this a test of the pros and cons of pulling other hooks out of the sails core repo over the next few months.  This will make Sails core lighter, faster, and more extensible, with fewer core dependencies, shorter \"lift\" time for most apps, and faster `npm install`s.\n\n\n#### Testing, the \"virtual\" request interpreter, and the `sails.request()` method\n\nIn the process of pulling the `sockets` hook _out_ of core, the logic which interprets requests has been normalized and is now located _in_ Sails core.  As a result, the `sails.request()` method is much more powerful.\n\nThis method allows you to communicate directly with the request interpreter in Sails without lifting your server onto a port.  It's the same mechanism that Sails uses to map incoming messages from Socket.io to \"virtual requests\" that have the familiar `req` and `res` streams.\n\nThe primary use case for `sails.request()` is in writing faster-running unit and integration tests, but it's also handy for proxying to mounted apps (or \"sub-apps\").\n\nFor instance, here is an example (using mocha) of how you might test one of your app's routes:\n\n```js\nvar assert = require('assert');\nvar Sails = require('sails').Sails;\n\nbefore(function beforeRunningAnyTests (done){\n\n  // Load the app (no need to \"lift\" to a port)\n  sails.load({\n    log: {\n      level: 'warn'\n    },\n    hooks: {\n      grunt: false\n    }\n  }, function whenAppIsReady(err){\n    if (err) return done(err);\n\n    // At this point, the `sails` global is exposed, although we\n    // could have disabled it above with our config overrides to\n    // `sails.load()`. In fact, you can actually use this technique\n    // to set any configuration setting you like.\n    return done();\n  });\n});\n\nafter(function afterTestsFinish (done) {\n  sails.lower(done);\n});\n\ndescribe('GET /hotpockets', function (){\n\n  it('should respond with a 200 status code', function (done){\n\n    sails.request({\n      method: 'get',\n      url: '/hotpockets',\n      params: {\n        limit: 10,\n        sort: 'price ASC'\n      }\n    }, function (err, clientRes, body) {\n      if (err) return done(err);\n\n      assert.equal(clientRes.statusCode, 200);\n      return done();\n    });\n\n  });\n});\n```\n\n\n#### `config/env/` subfolders\n\nIn v0.10.x, we added the `config/env` folder (thanks to [@clarkorz](https://github.com/clarkorz)), where you can add config files that will be loaded only in the appropriate environment (e.g. `config/env/production.js` for production environment, `config/env/development` for development, etc.).  In v0.11.x we've added the ability to specify whole subfolders per-environment.  For example, *all* config files saved to the `config/env/production` will be loaded and merged on top of other configuration when the environment is set to `production`.  Note that if both a `config/env/production` folder and a `config/env/production.js` file are present, the `config/env/production.js` settings will take precedence.  And, as always, `local.js` is merged on top of all other files, and `.sailsrc` rules them all.\n\n\n## Questions?\n\nAs always, if you run into issues upgrading, or if any of the notes above don't make sense, let us know and we'll do what we can to clarify.\n\nFinally, to those of you that have contributed to the project since the v0.10 release in August: we can't stress enough how much we value your continued support and encouragement.  There is a pretty massive stream of issues, pull requests, documentation tweaks, and questions, but it always helps to know that we're in this together :)\n\nThanks.\n\n-[@mikermcneil](https://github.com/mikermcneil/), [@sgress454](https://github.com/sgress454/) and [@particlebanana](https://github.com/particlebanana/)\n\n\n<docmeta name=\"displayName\" value=\"To v0.11\">\n<docmeta name=\"version\" value=\"0.11.0\">\n"
  },
  {
    "path": "docs/upgrading/To0.12.md",
    "content": "# Upgrading to Sails v0.12\n\nSails v0.12 comes with an upgrade to Socket.io and Express, as well as many bug fixes and performance enhancements. While you should find that this version is mostly backwards compatible with Sails v0.11, there are some major changes to `sails.sockets.*` methods which may affect your app. Those changes are addressed in the migration guide below, so if you are upgrading an existing app from v0.11 and are using `sails.sockets` methods, please be sure and carefully read the information below. Aside from those changes, running `sails lift` in an existing project should just work.\n\nThe sections below provide a high-level overview of what's changed, major bug fixes, enhancements and new features, as well as a basic tutorial on how to upgrade your v0.11.x Sails app to v0.12.\n\n## Installing the update\n\nRun the following command from the root of your Sails app:\n\n```bash\nnpm install sails@~0.12.0 --force --save\n```\n\nThe `--force` flag will override the existing Sails dependency installed in your `node_modules/` folder with the latest patch release of Sails v0.12, and the `--save` flag will update your package.json file so that future npm installs will also use the new version.\n\n\n## Things to do immediately after upgrading\n\n + If your app uses the `socket.io-redis` adapter, upgrade to at least version 1.0.0 (`npm install --save socket.io-redis@^1.0.0`).\n + If your app is using the Sails socket client (e.g. `assets/js/dependencies/sails.io.js`) on the front end, also install the newest version (`sails generate sails.io.js --force`).\n\n\n## Overview of changes in v0.12\n\n> For a full list of changes, see the changelog file for [Sails](https://github.com/balderdashy/sails/blob/master/CHANGELOG.md), as well as those for [Waterline](https://github.com/balderdashy/waterline/blob/master/CHANGELOG.md), [sails-hook-sockets](https://github.com/balderdashy/sails-hook-sockets/blob/master/CHANGELOG.md) and [sails.io.js](https://github.com/balderdashy/sails.io.js/blob/master/CHANGELOG.md).\n\n+ Security enhancements: updated several dependencies with potential vulnerabilities.\n+ Reverse routing functionality is now built into Sails core via the new [`sails.getRouteFor()`](https://sailsjs.com/documentation/reference/application/sails-get-route-for) and [`sails.getUrlFor()`](https://sailsjs.com/documentation/reference/application/sails-get-url-for) methods.\n+ Generally improved multi-node support (and therefore scalability) of low-level `sails.socket.*` methods, and made additional adjustments and improvements related to the latest socket.io upgrade.  Added a much tighter Redis integration that sits on top of `socket.io-redis`, using a Redis client to implement cross-server communication rather than an additional socket client.\n+ Cleaned up the API for `sails.socket.*` methods, normalizing overloaded functions and deprecating methods which cause problems in multiserver deployments (more on that below).\n+ Added a few brand new sails.sockets methods: `.leaveAll()`, `.addRoomMembersToRooms()`, and `.removeRoomMembersFromRooms()`.\n+ `sails.sockets.id()` is now `sails.sockets.getId()` (backwards compatible with deprecation message).\n+ New Sails apps are now generated with the updated version of `sails.io.js` (the JavaScript Sails socket client).  This upgrade bundles the latest version of `socket.io-client`, as well as some more advanced functionality (including the ability to specify common headers for all virtual socket requests).\n+ Upgraded to latest trusted versions of `grunt-contrib-*` dependencies (eliminates many NPM deprecation warnings and provides better error messages from NPM).\n+ If you are using NPM v3, running `sails new` will now run `npm install` instead of symlinking your new app's initial dependencies.  This is slower than you may be used to, but it is a necessary change due to changes in the way NPM handles nested dependencies.  The core maintainers are [working on](https://github.com/npm/npm/issues/10013#issuecomment-178238596) a better long-term solution, but in the meantime if you frequently run `sails new` and the slowdown is bugging you, consider temporarily downgrading to an earlier version of NPM (v2.x).  If the installed version of NPM is prior to version 3, Sails will continue to take advantage of the classic symlinking strategy.\n\n\n## Socket Methods\n\nWithout question, the biggest change in Sails v0.12 is to the API of the low-level `sails.sockets` methods exposed by the `sockets` hook.  In order to ensure that Sails apps perform flawlessly in a [multi-server (aka \"multi-node\" or \"clustered\") environment](https://sailsjs.com/documentation/concepts/realtime/multi-server-environments), several [low-level methods](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets) have been deprecated and some new ones have been added.\n\nThe following `sails.sockets` methods have been deprecated:\n\n + [`.emit()`](https://0.12.sailsjs.com/documentation/reference/web-sockets/sails-sockets/sails-sockets-emit)\n + [`.id()`](https://0.12.sailsjs.com/documentation/reference/web-sockets/sails-sockets/sails-sockets-id) (renamed to [`.getId()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/get-id))\n + [`.socketRooms()`](https://0.12.sailsjs.com/documentation/reference/web-sockets/sails-sockets/sails-sockets-socket-rooms)\n + [`.rooms()`](https://0.12.sailsjs.com/documentation/reference/web-sockets/sails-sockets/sails-sockets-rooms)\n + [`.subscribers()`](https://0.12.sailsjs.com/documentation/reference/web-sockets/sails-sockets/sails-sockets-subscribers)\n\nIf you are using any of those methods in your app, they will still work in v0.12 but _you should replace them as soon as possible_ as they may be removed from Sails in the next version.  See the individual doc pages for each method for more information.\n\n## Resourceful PubSub Methods\n\nThe [`.subscribers()`](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/subscribers) resourceful PubSub method has been deprecated for the same reasons as [`sails.sockets.subscribers()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/sails-sockets-subscribers).  Follow the guidelines in the docs for replacing this method if you are using it in your code.\n\n\n## Waterline (ORM) Updates\n\nSails v0.12 comes with the latest version of the Waterline ORM (v0.11.0).  There are two API changes to be aware of:\n\n##### `.save()` no longer provides a second argument to its callback\n\nThe callback to the `.save()` instance method no longer receives a second argument.  While requiring the second argument was convenient, it made `.save()` less performant, especially for apps working with millions of records.  This change resolves those issues by eliminating the need to build redundant queries, and preventing your database from having to process them.\n\nIf there are places in your app where you have code like this:\n```javascript\nsierra.save(function (err, modifiedSierra){\n  if (err) { /* ... */  return; }\n\n  // ...\n});\n```\n\nYou should replace it with:\n```javascript\nsierra.save(function (err){\n  if (err) { /* ... */  return; }\n\n  // ...\n});\n```\n\n\n\n##### Custom column/field names for built-in timestamps\n\nYou can now configure a custom column name (i.e. field name, for Mongo/Redis folks) for the built-in `createdAt` and `updatedAt` attributes.  In the past, the top-level `autoCreatedAt` and `autoUpdatedAt` model settings could be specified as `false` to disable the automatic injection of `createdAt` and `updatedAt` altogether.  That _still works as it always has_, but now you can also specify string values for one or both of these settings instead.  If a string is specified, it will be understood as the custom column (/field) name to use for the automatic timestamp.\n\n```javascript\n{\n  attributes: {},\n  autoCreatedAt: 'my_cool_created_when_timestamp',\n  autoUpdatedAt: 'my_cool_updated_at_timestamp'\n}\n```\n\nIf you were using the [workaround suggested by @sgress454 here](http://stackoverflow.com/a/24562385/486547), you may want to take advantage of this simpler approach instead.\n\n\n\n## SQL Adapter Performance\n\n[Sails-PostgreSQL](https://github.com/balderdashy/sails-postgresql) and [Sails-MySQL](https://github.com/balderdashy/sails-mysql) recieved patch updates that significantly improved performance when populating associations. Thanks to [@jianpingw](https://github.com/jianpingw) for digging into the source and finding a bug that was processing database records too many times. If you are using either of these adapters, upgrading to `sails-postgresql@0.11.1` or `sails-mysql@0.11.3` will give you a significant performance boost.\n\n\n## Contributing\n\nWhile not technically part of the release, Sails v0.12 is accompanied by some major improvements to the tools and resources available to contributors.  More core hooks are now fully documented ([controllers](https://github.com/balderdashy/sails/tree/master/lib/hooks/controllers)|[grunt](https://github.com/balderdashy/sails/tree/master/lib/hooks/grunt)|[logger](https://github.com/balderdashy/sails/tree/master/lib/hooks/logger)|[cors](https://github.com/balderdashy/sails/tree/master/lib/hooks/cors)|[responses](https://github.com/balderdashy/sails/tree/master/lib/hooks/responses)|[orm](https://github.com/balderdashy/sails/tree/master/lib/hooks/orm)), and the team has put together a [Code of Conduct](https://github.com/balderdashy/sails/blob/master/CODE-OF-CONDUCT.md) for contributing to the Sails project.\n\nThe biggest change for contributors is the [updated contribution guide](https://github.com/balderdashy/sails/blob/master/CONTRIBUTING.md), which contains the new, streamlined process for feature/enhancement proposals and for merging features, enhancements, and patches into core.  As the Sails framework has grown (both the code base and the user base), it's become necessary to establish clearer processes for how issue contributions, code contributions, and contributions to the documentation are reviewed and merged.\n\n\n## Documentation\n\nThis release also comes with a deep clean of the official reference documentation, and some minor usability improvements to the online docs at [https://sailsjs.com/documentation](https://sailsjs.com/documentation). The entire Sails website is now available in [Japanese](http://sailsjs.jp/), and four other [translation projects](https://github.com/balderdashy/sails/tree/master/docs#in-other-languages) are underway for Korean, Brazillian Portugese, Taiwanese Mandarin, and Spanish.\n\nIn addition, the Sails.js project (finally) has an [official blog](http://blog.sailsjs.com).  The Sails.js blog is the new source for all longform updates and announcements about Sails, as well as for our related projects like Waterline, Skipper, and the machine specification.\n\n\n\n## Need Help?\n\nIf you run into an unexpected issue upgrading your Sails app to v0.12.0, please review our contribution guide and [submit an issue in the Sails GitHub repo](https://github.com/balderdashy/sails/blob/master/CONTRIBUTING.md).\n\n\n\n<docmeta name=\"displayName\" value=\"To v0.12\">\n<docmeta name=\"version\" value=\"0.12.0\">\n"
  },
  {
    "path": "docs/upgrading/To1.0.md",
    "content": "# Upgrading to Sails v1.0\n\nSails v1.0 is here!  Keep reading for a high-level overview of what's changed in this release, and to learn about some new features you might want to take advantage of in your app.\n\n\n### A note about breaking changes\nWhile working on this version of Sails, a lot of the decisions we made favored a better developer experience over backwards compatibility. Because of this, the upgrade to Sails 1.0 will involve dealing with more breaking changes than previous versions. But when you're finished, there'll be a much better chance that the features you're using in Sails are things that its author and maintainers understand thoroughly and use almost every day.\n\nFor more about the philosophy behind many of the breaking changes in 1.0, you can read Mike McNeil's in-depth explanation [here](https://gitter.im/balderdashy/sails?at=5a1d8fcd3a80a84b5b907099).\n\n\n### Upgrading an existing app using the automated tool\n\nReady to upgrade your existing v0.12.x Sails app to version 1.0?  To get started, we recommend using the Sails 1.0 upgrade tool, which will help with some of the most common migration tasks.  To use the tool, first install Sails 1.0 globally with `npm install -g sails@^1.0.0` and then run `sails upgrade`.  After the tool runs, it will create a report for you with a list of remaining items that need to be manually upgraded.\n\n### Upgrading an existing app manually\n\nThe checklist below covers the changes most likely to affect the majority of apps.\n\nIf your app still has errors or warnings on startup after following this checklist, or if you're seeing something unexpected, head back to this document and take a look further down the page.  (One of the guides for covering various app components will probably be applicable.)\n\n> We've done a lot of work to make the upgrade process as seamless as possible, particularly when it comes to the errors and warnings you'll see on the console.  But if you're stumped or have lingering questions about any of the changes below, feel free to [drop by the Sails community Gitter channel](https://sailsjs.com/support).  (If your company is using Sails Flagship, you can also chat directly with the Sails core team [here](https://flagship.sailsjs.com/ask).)\n\n### tl;dr checklist: things you simply _must_ do when upgrading to version 1.0\n\nThe upgrade tool does its best to help with some of these items, but it won&rsquo;t change your app-specific code for you!\n\n+ **Step 0**: Check your Node version\n+ **Step 1**: Install hooks & update dependencies\n+ **Step 2**: Update configuration\n+ **Step 3**: Modify client-side code for the new blueprint API\n+ **Step 4**: Adopt the new release of Waterline ORM\n\n##### Step 0: Check your Node version!\n\nIf your app needs to support Node versions earlier than v4, you will not be able to upgrade to Sails 1.0, as Sails 1.0 no longer supports Node v0.x. The earliest version of Node supported by Sails 1.0 is Node 4.x.\n\n##### Step 1: Install hooks & update dependencies\nSails v1 introduces [custom builds](https://github.com/balderdashy/sails/pull/3504).  This means that certain core hooks are now installed as direct dependencies of your app, giving you more control over your dependencies and making `npm install sails` run _considerably_ faster.  So, the first thing you'll need to do is install the core hooks you're using.  (And while you're at it, be sure to update the other dependencies mentioned in the list below.)\n\n* **Install the `sails-hook-orm` package** into your app with `npm install --save sails-hook-orm`, unless your app has the ORM hook disabled.\n* **Install the `sails-hook-sockets` package** into your app with `npm install --save sails-hook-sockets`, unless your app has the sockets hook disabled.\n* **Install the `sails-hook-grunt` package** into your app with `npm install --save sails-hook-grunt`, unless your app has the Grunt hook disabled.\n* **Install the latest version of your database adapter**.  For example, if you're using `sails-mysql`, do `npm install --save sails-mysql@latest`.\n* **Upgrade your `sails.io.js` websocket client** with `sails generate sails.io.js`.  See the [\"Websockets\" section below](https://sailsjs.com/documentation/upgrading/to-v-1-0/#?websockets) for more details.\n\n\n##### Step 2: Update configuration\nSails v1 comes with several improvements in app configuration. For example, automatic install of lodash and async can now be customized to any version, and view engine configuration syntax is now consistent with that of Express v4+. The most significant change to configuration, however, is related to one of the most exciting new features in Sails v1: [datastores](https://sailsjs.com/documentation/reference/waterline-orm/datastores).  To make sure you correctly upgrade the configuration for your database(s) and other settings, be sure to carefully read through the steps below and apply the necessary changes.\n\n* **Update your `config/globals.js` file** (unless your app has `sails.config.globals` set to `false`)\n  + Set `models` and `sails` to have boolean values (`true` or `false`).\n  + Set `async` and `lodash` to either have `require('async')` and `require('lodash')` respectively, or else `false`. You may need to `npm install --save lodash` and `npm install --save async`, as well.\n* **Comment out any database configuration your aren&rsquo;t using** in `config/connections.js`.  Unlike previous versions, Sails 1.0 will load _all_ database adapters that are referenced in config files, regardless of whether they are actually used by a model.  See the [migration guide section on database configuration](https://sailsjs.com/documentation/upgrading/to-v-1-0/#?changes-to-database-configuration) for more info.\n* **The `/csrfToken` route** is no longer provided to all apps by default when using CSRF.  If you're utilizing this route in your app, you'll need to manually add it to `config/routes.js` as `'GET /csrfToken': { action: 'security/grant-csrf-token' }`.\n* **If your app relies on [action shadow routes](https://sailsjs.com/documentation/concepts/blueprints/blueprint-routes#?action-routes)** (where every custom controller action is automatically mapped to a route), you&rsquo;ll need to update your `config/blueprints.js` file and set `actions` to `true`.  This setting is now `false` by default.\n* **If your app uses CoffeeScript or TypeScript** see the [CoffeeScript](https://sailsjs.com/documentation/tutorials/using-coffee-script) and [TypeScript](https://sailsjs.com/documentation/tutorials/using-type-script) tutorials for update information.\n* **If your app uses a view engine other than EJS**, you&rsquo;ll need to configure it yourself in the `config/views.js` file, and you'll likely need to run `npm install --save consolidate` for your project.  See the \"Views\" section below for more details.\n* **If your `api` or `config` folders and subfolders contain any non-source files**, they&rsquo;ll need to be moved.  The exception is for Markdown (.md) and text (.txt) files, which will continue to be ignored.  Sails will attempt to read all other files in those folders as code, allowing for more flexibility in choosing between Javascript dialects (see the notes about CoffeeScript and TypeScript above).\n\n##### Step 3: Modify client-side code for the new blueprint API\nAs well as having been expanded to include a new endpoint, there also are a couple of minor&mdash;but breaking&mdash;changes to the blueprint API that may require you to make changes to your client-side code.\n\n* **If your app uses blueprint routes**, be aware that a couple of implicit \"shadow\" routes have had their HTTP method (aka verb) changed:\n  + the RESTful blueprint route address for [**add**](https://sailsjs.com/documentation/reference/blueprint-api/add-to) has changed from `POST` to `PUT`.\n  + the RESTful blueprint route address for [**update**](https://sailsjs.com/documentation/reference/blueprint-api/update) has changed from `PUT` to `PATCH`.\n* **If your app relies on the default socket notifications from blueprint actions**, be aware that there have been some performance-related upgrades that change the structure of these messages somewhat:\n  + Sails no longer publishes separate `addedTo` notifications, one for each new member of a collection. Those individual notifications are now rolled up into a single notification, and the new message contains an array of ids (`addedIds`) instead of just one.\n  + Sails no longer publishes separate `removedFrom` notifications, one for each former member of a collection. Sails now rolls those up into a single notification, and the new message now contains an array of ids (`removedIds`) instead of just one.\n\n\n##### Step 4: Adopt the new release of Waterline ORM\nThe new release of Waterline ORM (v0.13) introduces full support for SQL transactions, the ability to include or omit attributes in result sets (aka \"projections\"), dynamic database connections, and more extensive granular control over query behavior.  It also includes a major stability and performance overhaul, which comes with a few breaking changes to usage.  The bullet points below cover the most common issues you're likely to run into with the Waterline upgrade.\n\n* **If your app relies on getting records back from `.create()`, `.createEach()`, `.update()`, or `.destroy()` calls**, you&rsquo;ll need to update your model settings to indicate that you want those methods to fetch records (or chain a `.fetch()` to individual calls).  See the [migration guide section on `create()`, `.createEach()`, `.update()`, and `.destroy()` results](https://sailsjs.com/documentation/upgrading/to-v-1-0/#?changes-to-create-createeach-update-and-destroy-results) for more info.\n* **If your app relies on using the `.add()`, `.remove()`, and `.save()` methods to modify collections**, you will need to update them to use the new [.addToCollection](https://sailsjs.com/documentation/reference/waterline-orm/models/add-to-collection), [.removeFromCollection](https://sailsjs.com/documentation/reference/waterline-orm/models/remove-from-collection), and [.replaceCollection](https://sailsjs.com/documentation/reference/waterline-orm/models/replace-collection) model methods.\n* **Waterline queries will now rely on the database for case sensitivity.** This means that in most adapters your queries will now be case-sensitive, whereas before they were not. This may have unexpected consequences if you are used to having case-insensitive queries. For more information on how to manage this for databases such as MySQL, see the [case sensitivity docs](https://sailsjs.com/documentation/concepts/models-and-orm/models#?case-sensitivity).\n* **Waterline no longer supports nested creates or updates**, and this change extends to the related blueprints.  If your app relies on these features, see the [migration guide section on nested creates and updates](https://sailsjs.com/documentation/upgrading/to-v-1-0/#?nested-creates-and-updates) for more info.\n* **If your app sets a model attribute to `null`** using `.create()`, `.findOrCreate()` or `.update()`, you&rsquo;ll need to change the type of that attribute to `json`, or use the base value for the existing attribute type, instead of `null` (e.g. `0` for numbers).  See [the validations docs](https://sailsjs.com/documentation/concepts/models-and-orm/validations#?null-and-empty-string) for more info.\n* **The `create` blueprint response is now fully populated**, just like responses from `find`, `findOne`, `update` and `destroy`.  To suppress population of records, add a `parseBlueprintOptions` to your blueprints config or to a specific route.  See the [blueprints configuration reference](https://sailsjs.com/documentation/reference/configuration/sails-config-blueprints#?using-parseblueprintoptions) for more information.\n* **If you're using `createEach`** to insert large numbers of rows into a database, keep in mind that the Sails 1.0-compatible versions of most adapters now optimize the `createEach` method to use a single query, instead of using one query per row.  Depending on your database, per-request data size limits may apply.  See the [notes at the bottom of the `.createEach()` reference page](https://sailsjs.com/documentation/reference/waterline-orm/models/create-each#?notes) for more information.\n* **The `size` property for attributes** is no longer supported.  Instead, you may indicate column size using [the `columnType` property](https://sailsjs.com/documentation/concepts/models-and-orm/attributes#?columntype).\n* **The `defaultsTo` property for attributes may no longer be defined as a function.** Instead, you will either need to hard-code a default value, or remove the `defaultsTo` entirely and update your code to determine the appropriate value for the attribute before creating new records. (This can either be handled before calls to `.create()`/`.createEach()` in your actions, or in the model's [`beforeCreate`](https://sailsjs.com/documentation/concepts/models-and-orm/lifecycle-callbacks#?lifecycle-callbacks-on-create)).\n\n\n\n### Other breaking changes\n\nThe upgrade guide above provides for the most common upgrade issues that Sails contributors have encountered when upgrading various apps between version 0.12 and version 1.0. Every app is different, though, so we recommend reading through the points below, as well.  Not all of the changes discussed will necessarily apply to your app, but some might.\n\n* **Several properties and methods on `req` now work a little differently:**\n  * `req.accepted` has been replaced with [`req.accepts()`](https://sailsjs.com/documentation/reference/request-req/req-accepts)\n  * `req.acceptedLanguages` and `req.acceptsLanguage()` have been replaced with [`req.acceptsLanguages()`](https://sailsjs.com/documentation/reference/request-req/req-accepts-languages)\n  * `req.acceptedCharsets` and `req.acceptsCharset()` have been replaced with [`req.acceptsCharsets()`](https://sailsjs.com/documentation/reference/request-req/req-accepts-charsets)\n* **Several `req.options` properties related to blueprints are no longer supported.**  Instead, the new `parseBlueprintOptions` method can be used to give you complete control over blueprint behavior.  See the [blueprints configuration reference](https://sailsjs.com/documentation/reference/configuration/sails-config-blueprints#?using-parseblueprintoptions) for more information.\n* **The `defaultLimit` and `populate` blueprint configuration options are no longer supported.** Instead, the new `parseBlueprintOptions` method can be used to give you complete control over blueprint behavior.  See the [blueprints configuration reference](https://sailsjs.com/documentation/reference/configuration/sails-config-blueprints#?using-parseblueprintoptions) for more information.\n* **The `.findOne()` query method no longer supports `sort` and `limit` modifiers, and will throw an error if the given criteria match more than one record**.  If you want to find a single record using anything besides a `unique` attribute (like the primary key) as criteria, use `.find(<criteria>).limit(1)` instead (keeping in mind that this will return an array of one item).\n* **`autoPk`, `autoCreatedAt` and `autoUpdatedAt`** are no longer supported as top-level model properties.  See the [migration guide section on model config changes](https://sailsjs.com/documentation/upgrading/to-v-1-0/#?changes-to-model-configuration) for more information.\n* **Dynamic finders** (such as `User.findById()`) are no longer added to your models automatically.  You can implement these yourself as [custom model methods](https://sailsjs.com/documentation/concepts/models-and-orm/models#?custom-model-methods).\n* **Model Instance Methods** are no longer supported. This allows records returned from find queries to be plain JavaScript objects instead of model record instances.\n* **Custom `.toJSON()`** instance methods are no longer supported.  Instead, add a [`customToJSON` method](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?customtojson) to the model class (outside of the `attributes` dictionary).  See the [model settings documentation](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings) for more information.\n* **The `.toObject()` instance method** is no longer added to every record.  When implementing [`customToJSON`](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings#?customtojson) for a model, be sure to clone the record using `_.omit()`, `_.pick()` or `_.clone()`.\n* **`autoUpdatedAt` timestamps can now be manually updated** in calls to `.update()` (previously, the passed-in attribute value would be ignored).  The previous behavior faciliated the use of `.save()`, which is no longer supported.  Now, you can update the `updatedAt` if you need to (but generally you should let Sails do this for you!)\n* **`beforeValidate` and `afterValidate` lifecycle callbacks no longer exist**. Use one of the [many other lifecycle callbacks](https://sailsjs.com/documentation/concepts/models-and-orm/lifecycle-callbacks) to tap into the query.\n* **`afterDestroy` lifecycle callback now receives a single record**. It has been normalized to work the same way as the `afterUpdate` callback and call the function once for each record that has been destroyed rather than once with all the destroyed records.\n* **Many resourceful PubSub methods have changed** (see the PubSub section below for the full list).  If your app only uses the automatic RPS functionality provided by blueprints (and doesn&rsquo;t call RPS methods directly), no updates are required.\n* **The `.find()` model method no longer automatically coerces constraints that are provided for unrecognized attributes**.  For example, if you execute `Purchase.find({ amount: '12' })`, e.g. via blueprints (http://localhost:1337/purchase?amount=12), and there is no such \"amount\" attribute, then even if the database contains a record with the numeric equivalent (`12`), it will not be matched.  (This is only relevant when using MongoDB and sails-disk.)  If you are running into problems because of this, either define the attribute as a number or (if you're using blueprints) use an explicit `where` clause instead (e.g. `http://localhost.com:1337/purchase?where={\"amount\":12}`).\n* **Custom blueprints and the associated blueprint route syntax have been removed**.  This functionality can be replicated using custom actions, helpers, and routes.  See the \"Replacing custom blueprints\" section below for more information.\n* **Blueprint action shadow routes no longer include `/:id?`** at the end -- that is, if you have a `UserController.js` with a `tickle` action, you will no longer get a `/user/tickle/:id?` route (instead, it will be just `/user/tickle`).  Apps relying on those routes should add them manually to their `config/routes.js` file.\n* **`sails.getBaseUrl`**, deprecated in v0.12.x, has been removed.  See the [v0.12 docs for `getBaseUrl`](http://0.12.sailsjs.com/documentation/reference/application/sails-get-base-url) for more information on why it was removed and how you should replace it.\n* **`req.params.all()`**, deprecated in v0.12.x, has been removed.  Use `req.allParams()` instead.\n* **`sails.config.dontFlattenConfig`**, deprecated in v0.12.x, has been removed.  See the [original notes about `dontFlattenConfig`](https://sailsjs.com/documentation/upgrading/to-v-0-11#?config-files-in-subfolders) for details.\n* **The order of precedence for `req.param()` and `req.allParams()` has changed.**  It is now consistently path > body > query (that is, url path params override request body params, which override query string params).\n* **`req.validate()`** has been removed.  Use [`actions2`](https://sailsjs.com/documentation/concepts/actions-and-controllers#?actions-2) instead.\n* **The default `res.created()` response has been removed.**  If you&rsquo;re calling `res.created()` directly in your app, and you don't have an `api/responses/created.js` file, you&rsquo;ll need to create one.\n + On a related note, the [Blueprint create action](https://sailsjs.com/documentation/reference/blueprint-api/create) will now return a 200 status code upon success, instead of 201.\n* **The default `notFound` and `serverError` responses no longer accept a `pathToView` argument.** They will only attempt to serve a `404` or `500` view.  If you need to be able to call these responses with different views, you can customize the responses by adding `api/responses/notFound.js` or `api/responses/serverError.js` files to your app.\n* **The default `badRequest` or `forbidden` responses no longer display views**.  If you don&rsquo;t already have the `api/responses/badRequest.js` and `api/responses/forbidden.js` files, you&rsquo;ll need add them yourself and write custom code if you want them to display view files.\n* **The <a href=\"https://www.npmjs.com/package/connect-flash\" target=\"_blank\">`connect-flash`</a> middleware has been removed** (so `req.flash()` will no longer be available by default).  If you wish to continue using `req.flash()`, run `npm install --save connect-flash` in your app folder and [add the middleware manually](https://sailsjs.com/documentation/concepts/middleware).\n* **The `POST /:model/:id` blueprint RESTful route has been removed.**  If your app is relying on this route, you&rsquo;ll need to add it manually to `config/routes.js` and bind it to a custom action.\n* **The `handleBodyParserError` middleware has been removed**; in its place, the <a href=\"https://www.npmjs.com/package/skipper\" target=\"_blank\">Skipper body parser</a> now has its own `onBodyParserError` method.\n  + If you have customized the [middleware order](https://sailsjs.com/documentation/concepts/middleware#?adding-or-overriding-http-middleware), you&rsquo;ll need to remove `handleBodyParserError` from the array.\n  + If you've overridden `handleBodyParserError`, you&rsquo;ll need to instead override `bodyParser` with your own customized version of Skipper, including your error-handling logic in the `onBodyParserError` option.\n* **The `methodOverride` middleware has been removed.** If your app utilizes this middleware:\n  + `npm install --save method-override`\n  + Make sure your `sails.config.http.middleware.order` array (in `config/http.js`) includes `methodOverride` somewhere before `router`\n  + Add `methodOverride: require('method-override')()` to `sails.config.http.middleware`.\n* **The `router` middleware is no longer overrideable.**  Instead, the Express 4 router is used for routing both external and internal (aka &ldquo;virtual&rdquo;) requests.  It&rsquo;s still important to have a `router` entry in `sails.config.http.middleware.order` to delimit which middleware should be added before and after the router.\n* **The query modifiers `lessThan`, `lessThanOrEqual`, `greaterThan`, and `greaterThanOrEqual` have been removed**. Use the shorthand versions instead (`<`, `<=`, `>`, `>=`).\n* **The [`find one`](https://sailsjs.com/documentation/reference/blueprint-api/find-one) and [`find`](https://sailsjs.com/documentation/reference/blueprint-api/find-where) blueprint actions** now accept a `populate=false` rather than `populate=` to specify that no attributes should be populated.\n* **The [`add`](https://sailsjs.com/documentation/reference/blueprint-api/add-to) and [`remove`](https://sailsjs.com/documentation/reference/blueprint-api/remove-from) blueprint actions** now require that the primary key of the child record to add or remove be supplied as part of the URL, rather than allowing it to be passed on the query string or in the body.\n* **The [`destroy`](https://sailsjs.com/documentation/reference/blueprint-api/destroy) blueprint action** now requires that the primary key of the record to destroy be supplied as part of the URL, rather than allowing it to be passed on the query string or in the body.\n* **The `sails.config.session.routesDisabled` setting has changed** to `sails.config.session.isSessionDisabled()`, a function.  See the [`config/session.js` docs](https://sailsjs.com/documentation/reference/configuration/sails-config-session) for more information on configuring `isSessionDisabled()`.\n* **The experimental &ldquo;switchback-style&rdquo; usage for Waterline methods is no longer supported**.  Only function callbacks may be used with Waterline model methods.\n* **The experimental `create` auto-migration scheme is no longer supported**.  It is highly recommended that you use a migration tool such as [Knex](http://knexjs.org/#Migrations) to handle migrations of your production database.\n* **The experimental `forceLoadAdapter` datastore setting is no longer supported**.  Instead, all adapters referenced in `config/datastores.js` (formerly `config/connections.js`) are automatically loaded whenever Sails lifts.\n* **The experimental `usage` route option has been removed.**  It is recommended that you perform any route parameter validation in your controller code.\n* **The experimental &ldquo;associated-item&rdquo; blueprint shadow routes have been removed.** These were routes like `GET /user/1/pets/2`, whose functionality can be replicated by simply using the much-clearer route `GET /pets/2`.\n* **The experimental `.validate()` method in model classes** (e.g. `User.validate()`) is now fully supported, but its usage has changed.  See the [`.validate()` docs](https://sailsjs.com/documentation/reference/waterline-orm/models/validate) for more information.\n* **The ordering of attributes** in the internal representation of model classes has changed (association attributes are now sorted at the bottom).  This has the effect of causing tables created using `migrate: 'alter'` to have their columns in a different order than in previous versions of Waterline, so be aware of this if column ordering is important in your application.  As a reminder, auto-migrations are intended to help you design your schema as you build your app.  They are not guaranteed to be consistent regarding any details of your physical database columns besides setting the column name, type (including character set / encoding if specified) and uniqueness.\n* **Using `_config` to link a controller to a model** will no longer work.  This was never a supported feature, but it was used in some projects to change the URLs that were mapped to the blueprint actions for a model.  Please use [`restPrefix`](https://sailsjs.com/documentation/reference/configuration/sails-config-blueprints#?properties) instead.\n* **The `find()`, `destroy()`, and `update()` methods**  ignore `undefined` attributes. These methods will strip undefined attributes from their search criteria, e.g. `User.update({id: undefined}).with({ firstName: 'Finn'})` would update **every** user record. Read more about this in [this Github issue](https://github.com/balderdashy/sails/issues/4639#issuecomment-320369193)\n\n### Changes to database configuration\n\n* The `sails.config.connections` setting has been deprecated in favor of `sails.config.datastores`.  If you lift an app that still has `sails.config.connections` configured, you&rsquo;ll get a warning which you can avoid by simply changing `module.exports.connections` in `config/connections.js` to `module.exports.datastores`.  For your own sanity, it&rsquo;s recommended that you also change the filename to `config/datastores.js`.\n* The `sails.config.models.connection` setting has been deprecated in favor of `sails.config.models.datastore`.  As above, changing the name of the property in `config/models.js` should be sufficient to turn off any warnings.\n* Every app now has a default datastore (appropriately named `default`) that is configured to use a built-in version of the [`sails-disk` adapter](https://github.com/balderdashy/sails-disk).  In Sails 1.0, the default value of `sails.config.models.datastore` is `default` (rather than `localDiskDb`). The recommended approach to setting the default datastore for your models is to simply to add the desired configuration under the `default` key in `config/datastores.js`, and leave the `datastore` key in `config/models.js` undefined, rather than the previous approach of setting `datastore` to (for example) `myPostgresqlDb` and then adding a `myPostgresqlDb` key to `config/datastores.js`.  This makes it a lot easier to change the datastore used by different environments (for instance, by changing the configuration of the `default` datastore in `config/env/production.js`).\n* _All_ datastores that are configured in an app will be loaded at runtime (rather than only loading datastores that were being used by at least one model).  This has the benefit of allowing the use of a datastore outside the context of an individual model, but it does mean that if you don&rsquo;t want to connect to a certain database when Sails lifts, you should comment out that datastore connection config!\n\n### Nested creates and updates\n\n* The [`.create()`](https://sailsjs.com/documentation/reference/waterline-orm/models/create), [`.update()`](https://sailsjs.com/documentation/reference/waterline-orm/models/update) and [`.add()`](https://sailsjs.com/documentation/reference/waterline-orm/models/find) model methods no longer support creating a new &ldquo;child&rdquo; record to link immediately to a new or existing parent.  For example, given a `User` model with a singular association to an `Animal` model through an attribute called `pet`, it is not possible to set `pet` to a dictionary representing values for a brand new `Animal` (aka a &ldquo;nested create&rdquo;).  Instead, create the new `Animal` first and use its primary key to set `pet` when creating the new `User`.\n* Similarly, the [create](https://sailsjs.com/documentation/reference/blueprint-api/create), [update](https://sailsjs.com/documentation/reference/blueprint-api/update) and [add](https://sailsjs.com/documentation/reference/blueprint-api/add-to) blueprint actions no longer support nested creates.\n* The [`.update()`](https://sailsjs.com/documentation/reference/waterline-orm/models/update) model method and its associated [blueprint action](https://sailsjs.com/documentation/reference/blueprint-api/update) no longer support replacing an entire plural association.  If a record is linked to one or more other records via a [&ldquo;one-to-many&rdquo;](https://sailsjs.com/documentation/concepts/models-and-orm/associations/one-to-many) or [&ldquo;many-to-many&rdquo;](https://sailsjs.com/documentation/concepts/models-and-orm/associations/many-to-many) association and you wish to link it to an entirely different set of records, use the [`.replaceCollection()` model method](https://sailsjs.com/documentation/reference/waterline-orm/models/replace-collection) or the [replace blueprint action](https://sailsjs.com/documentation/reference/blueprint-api/replace).\n\n### Changes to model configuration\n\n##### tl;dr\n\nRemove any `autoPK`, `autoCreatedAt` and `autoUpdatedAt` properties from your models, and add the following to your `config/models.js` file:\n\n```javascript\n  attributes: {\n    createdAt: { type: 'number', autoCreatedAt: true, },\n    updatedAt: { type: 'number', autoUpdatedAt: true, },\n    id: { type: 'number', autoIncrement: true}, // <-- for SQL databases\n    id: { type: 'string', columnName: '_id'}, // <-- for MongoDB\n  }\n```\n\n##### The `autoPK` top-level property is no longer supported\n\nThis property was formerly used to indicate whether or not Waterline should create an `id` attribute as the primary key for a model.  Starting with Sails v1.0 / Waterline 0.13, Waterline will no longer create any attributes in the background.  Instead, the `id` attribute must be defined explicitly.  There is also a new top-level model property called `primaryKey`, which can be set to the name of the attribute that should be used as the model's primary key.  This value defaults to `id` for every model, so in general you won't have to set it yourself.\n\n##### The `autoUpdatedAt` and `autoCreatedAt` model settings are now attribute-level properties\n\nThese properties were formerly used to indicate whether or not Waterline should create `createdAt` and `updatedAt` timestamps for a model.  Starting with Sails v1.0 / Waterline 0.13, Waterline will no longer create these attributes in the background.  Instead, the `createdAt` and `updatedAt` attributes must be defined explicitly if you want to use them.  By adding `autoCreatedAt: true` or `autoUpdatedAt: true` to an attribute definition, you can instruct Waterline to set that attribute to the current timestamp whenever a record is created or updated. Depending on the type of these attributes, the timestamps will be generated in one of two formats:\n  + For `type: 'string'`, these timestamps are stored in the same way as they were in Sails 0.12: as timezone-agnostic ISO 8601 JSON timestamp strings (e.g. `'2017-12-30T12:51:10Z'`).  So if any of your front-end code is relying on the timestamps as strings it's important to set this to `string`.\n  + For `type: 'number'`, these timestamps are stored as JS timestamps (the number of milliseconds since Jan 1, 1970 at midnight UTC).\n\nFurthermore, for any attribute, if you pass `new Date()` as a constraint within a Waterline criteria's `where` clause, or as a new record, or within the values to set in a `.update()` query, then these same rules are applied based on the type of the attribute. If the attribute is `type: 'json'`, it uses the latter approach.\n\n<!-- TODO: finish filling in the gaps for this section:\n##### Changes to built-in data types\n\nAs of Sails v1.0 / Waterline 0.13, we've made changes to the way that data types and type safety work in the ORM. This allows us to do more as far as type validation/coercion, which makes your app more future-proof and less error-prone[1]()[2]()[3](). As a result, we've narrowed down the `type` options to the following:\n\n+ `'string'`\n+ `'number'`\n+ `'boolean'`\n+ `'json'`\n+ _`'ref'`_ _(advanced: do not use unless you have personally inspected the source code of your adapter to understand how it handles data of this type - this is a direct channel between the adapter and your app.)_\n\nThis means that the following types are **no longer supported** (but can be simulated in most cases by including `columnType` and/or validation rules in your attribute definition):\n\n+ `'text'` _(use `type: 'string'` and `columnType: 'TEXT'`)_\n+ `'integer'` _(use `type: 'number'`, `columnType: 'INT'` and `isInteger: true`)_\n+ `'float'` _(use `type: 'number'` and `columnType: 'FLOAT'`)_\n+ `'date'`\n+ `'datetime'`\n+ `'binary'`\n+ `'array'` _(use `type: 'json'`)_\n+ `'mediumtext'` _(use `type: 'string'` and `columnType: 'MEDIUMTEXT'`)_\n+ `'longtext'` _(use `type: 'string'` and `columnType: 'LONGTEXT'`)_\n+ `'objectid'`\n+ `'email'` _(use `type: 'string'` and `isEmail: true`)_\n-->\n\n### Changes to `.create()`, `.createEach()`, `.update()`, and `.destroy()` results\n\nAs of Sails v1.0 / Waterline 0.13, the default result from `.create()`, `.createEach()`, `.update()`, and `.destroy()` has changed.\n\nTo encourage better performance and easier scalability, `.create()` no longer sends back the created record. Similarly, `.createEach() ` no longer sends back an array of created records, `.update()` no longer sends back an array of _updated_ records, and `.destroy()` no longer sends back _destroyed_ records.  Instead, the second argument to the .exec() callback is now `undefined` (or the first argument to `.then()`, if you're using promises).\n\nThis makes your app more efficient by removing unnecessary `find` queries, and it makes it possible to use `.update()` and `.destroy()` to modify many different records in large datasets, rather than falling back to lower-level native queries.\n\nYou can still instruct the adapter to send back created or modified records for a single query by using the `fetch` method.  For example:\n\n```js\nArticle.update({\n  category: 'health-and-wellness',\n  status: 'draft'\n})\n.set({\n  status: 'live'\n})\n.fetch()\n.exec(function(err, updatedRecords){\n  //...\n});\n```\n\n\n> If the prospect of changing all of your app's queries seems daunting, there is a temporary convenience you might want to take advantage of.\n> To ease the process of upgrading an existing app, you can tell Sails/Waterline to fetch created/updated/destroyed records for ALL of your app's `.create()`/`.createEach()`/`.update()`/`.destroy()` queries.  Just edit your app-wide model settings in `config/models.js`:\n>\n> ```js\n> fetchRecordsOnUpdate: true,\n> fetchRecordsOnDestroy: true,\n> fetchRecordsOnCreate: true,\n> fetchRecordsOnCreateEach: true,\n> ```\n>\n> That's it!  Still, to improve performance and future-proof your app, you should go through all of your `.create()`, `.createEach()`, `.update()`, and `.destroy()` calls and add `.fetch()` when you can.  Support for these model settings will eventually be removed in Sails v2.\n\n### Changes to Waterline criteria usage\n* For performance reasons, as of Sails v1.0 / Waterline 0.13, criteria passed into Waterline's model methods will now be mutated in-place in most situations (whereas in Sails/Waterline v0.12, this was not necessarily the case).\n* Aggregation clauses (`sum`, `average`, `min`, `max`, and `groupBy`) are no longer supported in criteria.  Instead, see new model methods [.sum()](https://sailsjs.com/documentation/reference/waterline-orm/models/sum) and [.avg()](https://sailsjs.com/documentation/reference/waterline-orm/models/avg).\n* Changes to limit and skip:\n  + `limit: 0` **no longer does the same thing as `limit: undefined`**.  Instead of matching ∞ results, it now matches 0 results.\n  + Avoid specifying a limit of < 0.  It is still ignored, and acts like `limit: undefined`, but it now logs a deprecation warning to the console.\n  + `skip: -20` **no longer does the same thing as `skip: undefined`**.  Instead of skipping zero results, it now refuses to run with an error.\n  + Limit must be < Number.MAX_SAFE_INTEGER (...with one exception: for compatibility/convenience, `Infinity` is tolerated and normalized to `Number.MAX_SAFE_INTEGER` automatically.)\n  + Skip must be < Number.MAX_SAFE_INTEGER\n\n\n##### Change in support for mixed `where` clauses\nCriteria dictionaries with a mixed `where` clause are no longer supported. For example, instead of:\n```javascript\n{\n  username: 'santaclaus',\n  limit: 4,\n  select: ['beardLength', 'lat', 'long']\n}\n```\nyou should use:\n```javascript\n{\n  where: { username: 'santaclaus' },\n  limit: 4,\n  select: ['beardLength', 'lat', 'long']\n}\n```\n> Note that you can still do `{ username: 'santaclaus' }` as shorthand for `{ where: { username: 'santaclaus' } }`, you just can't mix other top-level criteria clauses (like `limit`) alongside constraints (e.g. `username`).\n>\n> For places where you're using Waterline's chainable deferred object to build criteria, don't worry about this&mdash;it's already taken care of for you.\n\n### Security\n\nNew apps created with Sails 1.0 will contain a **config/security.js** file instead of individual **config/cors.js** and **config/csrf.js** files. Apps migrating from earlier versions can keep their existing files, as long as they perform the following upgrades:\n\n* Change `module.exports.cors` to `module.exports.security.cors` in `config/cors.js`\n* Change CORS config settings names to match the newly documented names in [Reference > Configuration > sails.config.security](https://sailsjs.com/documentation/reference/configuration/sails-config-security#?sailsconfigsecuritycors)\n* Change `module.exports.csrf` to `module.exports.security.csrf` in `config/csrf.js`.  This value is now simply `true` or `false`; no other CSRF options are supported (see below).\n* `sails.config.csrf.routesDisabled` is no longer supported. Instead, add `csrf: false` to any route in `config/routes.js` that you wish to be unprotected by CSRF, for example:\n\n```js\n'POST /some-thing': { action: 'do-a-thing', csrf: false },\n```\n\n* `sails.config.csrf.origin` is no longer supported. Instead, you can add any custom CORS settings directly to your CSRF token route configuration, for example:\n\n```js\n'GET /csrfToken': {\n  action: 'security/grant-csrf-token',\n  cors: {\n    allowOrigins: ['http://foobar.com', 'https://owlhoot.com']\n  }\n}\n```\n\n* `sails.config.csrf.grantTokenViaAjax` is no longer supported.  This setting was used to turn the CSRF token-granting route on or off.  In Sails 1.0, you add that route manually in your `config/routes.js` file (see above). If you don&rsquo;t want to grant CSRF tokens via AJAX, just leave that route out of `config/routes.js`.\n\n### Views\n\nFor maximum flexibility, Consolidate is no longer bundled with Sails.  If you are using a view engine besides EJS, you'll probably want to install Consolidate as a direct dependency of your app.  You can then configure the view engine in `config/views.js`, like so:\n\n```javascript\nextension: 'swig',\ngetRenderFn: function() {\n  // Import `consolidate`.\n  var cons = require('consolidate');\n  // Return the rendering function for Swig.\n  return cons.swig;\n}\n```\n\nAdding custom configuration to your view engine is a lot easier in Sails 1.0:\n\n```javascript\nextension: 'swig',\ngetRenderFn: function() {\n  // Import `consolidate`.\n  var cons = require('consolidate');\n  // Import `swig`.\n  var swig = require('swig');\n  // Configure `swig`.\n  swig.setDefaults({tagControls: ['{?', '?}']});\n  // Set the module that Consolidate uses for Swig.\n  cons.requires.swig = swig;\n  // Return the rendering function for Swig.\n  return cons.swig;\n}\n```\n\nNote that the [built-in support for layouts](https://sailsjs.com/documentation/concepts/views/layouts) still works for the default EJS views, but layout support for other view engines (e.g. Handlebars or Ractive) is not bundled with Sails 1.0.\n\n### Resourceful PubSub\n\n* Removed deprecated `backwardsCompatibilityFor0.9SocketClients` setting.\n* Removed deprecated `.subscribers()` method.\n* Removed deprecated \"firehose\" functionality.\n* Removed support for 0.9.x socket client API.\n* The following resourceful pubsub methods have also been removed:\n  * `.publishAdd()`\n  * `.publishCreate()`\n  * `.publishDestroy()`\n  * `.publishRemove()`\n  * `.publishUpdate()`\n  * `.watch()`\n  * `.unwatch()`\n  * `.message()`\n\nIn place of the removed methods, you should use the new `.publish()` method, or the low-level [sails.sockets](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets) methods.  Keep in mind that unlike `.message()`, `.publish()` does _not_ wrap your data in an envelope containing the record ID, so&mdash;if it's important&mdash;you'll need to include the ID yourself as part of the data.  For example, in Sails v0.12.x, `User.message(123, {owl: 'hoot'})` would have resulted in the following notification being broadcasted to clients:\n\n```\n{\n  verb: 'messaged',\n  id: 123,\n  data: {\n    owl: 'hoot'\n  }\n}\n```\n\nBy contrast, in Sails v1.0, `User.publish(123, {owl: 'hoot'})` will simply broadcast:\n```\n{\n  owl: 'hoot'\n}\n```\n\n\n\n### Replacing custom blueprints\n\nOut of the box, it is no longer possible to add a file to `api/blueprints/` that will automatically be used as a blueprint action for all models.  However, this behavior can easily be replicated by installing [`sails-hook-custom-blueprints`](https://www.npmjs.com/package/sails-hook-custom-blueprints).\n\n<!--\nAnother way is to add a route like `'POST /:model': 'SharedController.create'` to the bottom of your `config/routes.js` file, and then add the custom `create` blueprint to a `api/controllers/SharedController.js` file (or a `api/controllers/shared/create.js` standalone action).\n\nYet another option would be to add a `api/helpers/create.js` helper which takes a model name and dictionary of values as inputs (see [Concepts > Helpers](https://sailsjs.com/documentation/concepts/helpers)), and call that helper from the related action for each model (e.g. `UserController.create`).\n-->\n\n### Express 4\n\nSails 1.0 comes with an update to the internal Express server from version 3 to version 4 (thanks to some great work by [@josebaseba](http://github.com/josebaseba)).  This change is mainly about maintainability for the Sails framework and should be transparent to your app.  However, there are a couple of differences worth noting:\n\n* The `404`, `500` and `startRequestTimer` middleware are now built-in to every Sails app, and have been removed from the `sails.config.http.middleware.order` array.  If your app has an overridden `404` or `500` handler, you should instead override `api/responses/notFound.js` and `api/responses/serverError.js` respectively.\n* Session middleware that was designed specifically for Express 3 (e.g. very old versions of `connect-redis` or `connect-mongo`) will no longer work, so you&rsquo;ll need to upgrade to more recent versions.\n* The `sails.config.http.customMiddleware` feature is deprecated in Sails 1.0.  It will still work for now, but may be removed in a later release.  Instead of using `customMiddleware` to modify the Express app directly, use regular (`req`, `res`, `next`) middleware instead.  For instance, you can replace something like:\n\n```\ncustomMiddleware: function(app) {\n  var passport = require('passport');\n  app.use(passport.initialize());\n  app.use(passport.session());\n}\n```\n\nwith something like:\n```\nvar passport = require('passport');\nmiddleware: {\n  passportInit: passport.initialize(),\n  passportSession: passport.session()\n},\n```\nbeing sure to insert `passportInit` and `passportSession` into your `middleware.order` array in `config/http.js`.\n\n### Response methods\n * `.jsonx()` is deprecated. If you have files in `api/responses` that you haven't customized at all, you can just delete them and let the Sails default responses work their magic.  If you have files in `api/responses` that you&rsquo;d like to keep, replace any occurences of `res.jsonx()` in those files with `res.json()`.\n * `res.negotiate()` is deprecated. Use `res.serverError()`, `res.badRequest()`, or a [custom response](https://sailsjs.com/documentation/concepts/extending-sails/custom-responses) instead.\n\n\n### i18n\n\nSails 1.0 switches from using the [i18n](http://npmjs.org/package/i18n) to the lighter-weight [i18n-2](http://npmjs.org/package/i18n-2) module.  The overwhelming majority of users should see no difference in their apps.  However, if you&rsquo;re using the `sails.config.i18n.updateFiles` option, be aware that this is no longer supported; instead, locale files will _always_ be updated in development mode, and _never_ in production mode.  If this is a problem or you&rsquo;re missing some other feature from the i18n module, you can install [sails-hook-i18n](http://npmjs.org/package/sails-hook-i18n) to revert to pre-Sails-1.0 functionality.\n\n> If your 0.12 application is running into issues during upgrade due to its use of i18n features, see [#4343](https://github.com/balderdashy/sails/issues/4343) for more troubleshooting tips.\n\n### WebSockets\n\nAll Sails 1.0 projects that use websockets must install the latest `sails-hook-sockets` dependency (`npm install --save sails-hook-sockets`).  This version of `sails-hook-sockets` differs from previous ones in a couple of ways:\n\n* The default `transports` setting is simply `['websocket']`.  In the majority of production deployments, restricting your app to the `websocket` transport (rather than using `['polling', 'websocket']`) avoids problems with sessions (see the pre-1.0 [scaling guide notes](https://github.com/balderdashy/sails-docs/blob/1038b38cb34fd945086480ee45325a1ac95a0950/concepts/Deployment/Scaling.md#notes) for details).  If you&rsquo;re using the `sails.io.js` websocket client, the easiest way to make your app compatible with the new websocket settings is to install the new `sails.io.js` version with `sails generate sails.io.js`.  The latest version of that package also defaults to the &ldquo;websocket-only&rdquo; transport strategy.  If you&rsquo;ve customized the `transports` setting in your front-end code and `config/sockets.js` file, then you'll just need to continue to ensure that the values in both places match.\n* The latest `sails-hook-sockets` hook uses a newer version of Socket.io.  See the [Socket.io changelog](https://github.com/socketio/socket.io/blob/master/History.md#150--2016-10-06) for a full update, but keep in mind that socket IDs no longer have `/#` prepended to them by default.\n\n### Grunt\n\nThe Grunt task-management functionality that was formerly part of the Sails core has now been moved into the separate `sails-hook-grunt` module.  Existing apps simply need to `npm install --save sails-hook-grunt` to continue using Grunt.  However, with a modification to your app&rsquo;s `Gruntfile.js`, you can take advantage of the fact that `sails-hook-grunt` includes all of the `grunt-contrib` modules that previously had to be installed at the project level.  The new `Gruntfile.js` contains:\n\n```\nmodule.exports = function(grunt) {\n\n  var loadGruntTasks = require('sails-hook-grunt/accessible/load-grunt-tasks');\n\n  // Load Grunt task configurations (from `tasks/config/`) and Grunt\n  // task registrations (from `tasks/register/`).\n  loadGruntTasks(__dirname, grunt);\n\n};\n```\n\nAssuming that you haven&rsquo;t customized the Gruntfile in your app, you can replace `Gruntfile.js` with that code and then safely run:\n\n```\nnpm uninstall --save grunt-contrib-clean\nnpm uninstall --save grunt-contrib-coffee\nnpm uninstall --save grunt-contrib-concat\nnpm uninstall --save grunt-contrib-copy\nnpm uninstall --save grunt-contrib-cssmin\nnpm uninstall --save grunt-contrib-jst\nnpm uninstall --save grunt-contrib-less\nnpm uninstall --save grunt-contrib-uglify\nnpm uninstall --save grunt-contrib-watch\nnpm uninstall --save grunt-sails-linker\nnpm uninstall --save grunt-sync\nnpm uninstall --save grunt-cli\n```\n\nto remove those dependencies from your project.\n\n\n### Troubleshooting\n\n\n##### Still displaying v0.12 at launch?\n\nMake sure you have `sails` installed locally in your project, and that you're using the v1 version of the command-line tool.\n\nTo install the v1.0 globally, run `npm install sails@^1.0.0 -g`. To install it for a particular Sails app, cd into that app's directory, then run `npm install sails@^1.0.0 --save`.  (After installing locally, be sure to also install the necessary hooks -- see above.)\n\n\n\n<docmeta name=\"displayName\" value=\"To v.1.0\">\n<docmeta name=\"version\" value=\"1.0.0\">\n"
  },
  {
    "path": "docs/upgrading/upgrading.md",
    "content": "# Upgrading\n\nLike most Node packages, Sails respects [semantic versioning](http://semver.org/).  For example, if you are using Sails v0.11.3, and then upgrade to Sails v0.11.4, you shouldn't need to change your application code.  This is called a **patch release**.  On the other hand, if you upgrade from Sails v0.11.3 to v1.0.0, you can expect some _breaking changes_, meaning that you will need to change your Sails app's code in order to use the new version.  With any framework or tool, _some_ breaking changes are inevitable over time, but you can expect to see these kinds of changes less often as the APIs in Node and Sails continue to stabilize.  In the meantime, the core maintainers strive to minimize breaking changes and maintain backwards compatibility where possible.\n\n### Version notes\n\nFor details about changes between versions, as well as a migration guide to assist you in making an necessary changes to your app, please refer to the appropriate page:\n\n- [v1.x](https://sailsjs.com/documentation/upgrading/to-v-1-0)\n- [v0.12.x](https://sailsjs.com/documentation/concepts/upgrading/to-v-0-12)\n- [v0.11.x](https://sailsjs.com/documentation/concepts/upgrading/to-v-0-11)\n- [v0.10.x](https://sailsjs.com/documentation/concepts/upgrading/to-v-0-10)\n\n\n### Notes\n\n> - Like Node.js, minor version bumps in Sails versions prior to v1.0 included breaking changes&mdash;e.g. upgrading from v0.11.3 to v0.12.0 might force you to make some changes to your code.  But from v1.0.0 and on, minor version (the second number) releases should be fully backwards compatible.  For example, v1.1.0 to v1.2.0 should not force you to make changes to your code, whereas upgrading to v2.0.0 might.\n> - If you are more than one version behind the latest release and run into difficulties, consider updating your app one step at a time. The migration guides are written with a particular version diff in mind, and it's best to isolate as many variables as possible.  For instance, if you are running Sails v0.11 and trying to upgrade to Sails v1.5.18 but having trouble, try first upgrading to Sails v0.11, then v0.12, _then_ v1.5.18.\n\n\n<docmeta name=\"displayName\" value=\"Upgrading\">\n<docmeta name=\"isOverviewPage\" value=\"true\">\n"
  },
  {
    "path": "docs/version-notes/0.10.x/0.10.x.md",
    "content": "<docmeta name=\"displayName\" value=\"0.10.x\">\n<docmeta name=\"version\" value=\"0.10.0\">\n"
  },
  {
    "path": "docs/version-notes/0.10.x/Changelog0.10.0-rc9.md",
    "content": "# 0.10.0-rc9 Changelog\n\n+ Associations\n  + Adapter-level support for optimized joins (SQL databases and Mongo)\n  + Built-in support for in-memory joins.  Allows for cross-database and even cross-adapter joins! (e.g. a User in Mongo has many Messages in a MySQL database called `legacy_messages`, and also a Role in a MySQL database called `myapp`.  These can be automatically joined together using the same ORM syntax as normal.)\n\n+ Better Error Handling in Waterline\n\n+ Revamped Sails CLI\n  + Generators w/ support for coffeescript\n  + Support for dry runs (`--dry`) for `sails generate` and `sails new`\n  + Experimental support for custom generators\n\n+ API Blueprints\n  + Blueprints are injected into project, allowing the built-in API to be customized.\n  + Dramatic simplification of how blueprints are injected-- by implicitly including them in the routes file.\n  + Backwards compatibility for blueprints on <=v0.9 apps can be achieved by plugging in a simple config to re-enable the traditional support and configurations.\n  + Blueprint routes automatically take associations into account, e.g.:\n    + `GET /user/2/dogs` -- get dogs belonging to user #2\n    + `GET /user/2/dad` -- get dad belonging to user #2\n    + `PUT /user/2/dogs` -- add a dog to user #2\n    + `DELETE /user/2/dogs/2` -- remove dog #5 from user #2\n\n+ PubSub\n  + Simplified dramatically- removed concept of class rooms (most of the time, this isn't exactly what you want anyways)\n  + Blueprints still work the same way by introspecting your app's schema and taking advantage of information about assocations to create logical publish/subscribe dependencies, relying on the global channel in cases where a shared instance doesn't exist.\n  + Reduced to a handful of simple methods:\n    + `SomeModel.publish()` -- publish to model instance\n    + `SomeModel.subscribe()` -- subscribe socket to model instance\n    + `SomeModel.unsubscribe()` -- unsubscribe socket from model instance\n    + `sails.publish()` -- publish to global channel\n    + `sails.subscribe()` -- subscribe socket to global channel\n    + `sails.unsubscribe()` -- unsubscribe socket to global channel\n\n+ Error Negotiation Shortcuts\n  + Automatically content-negotiate a response-- configurable in `500.js`, `404.js`, `400.js`, `403.js`\n  + `res.serverError( msgOrObj )`\n  + `res.notFound()`\n  + `res.forbidden( msgOrObj )`\n  + `res.badRequest( msgOrObj )`\n\n\n\n# Deprecated\n### Overview\nThe following features are considered deprecated and should at some point be removed from the codebase\n\n# Dynamic Finder Methods\n\n- .findOneBy`<attribute>`In()\n- .findOneBy`<attribute>`Like()\n- .findBy`<attribute>`In()\n- .findBy`<attribute>`Like() \n- .countBy`<attribute>`In()\n- .countBy`<attribute>`Like()\n- .`<attribute>`Contains()\n \n# CRUD Class Methods\n- .findAll()\n- .findOneLike()\n- .findLike()\n- .contains()\n- .join()\n- .select()\n- .findOrCreateEach()\n- .join()\n- .startsWith()\n- .endsWith()\n\n\n<docmeta name=\"displayName\" value=\"0.10.0-rc9 Changelog\">\n<docmeta name=\"version\" value=\"0.10.0\">\n"
  },
  {
    "path": "docs/version-notes/0.10.x/Changelog0.10x.md",
    "content": "# Upgrading to v0.10\n\nFor the most part, running sails lift in an existing v0.9 project should just work. The core contributors have taken a number of steps to make the upgrade as easy as possible, and if you follow the deprecation messages in the console, you should do just fine.\n\nSails v0.10 comes with some big changes. The sections below provide a high level overview of what's changed, major bug fixes, enhancements and new features, as well as a basic tutorial on how to upgrade your v0.9.x Sails app to v0.10.\n\n## File uploads\n\nThe Connect multipart middleware [will soon be officially deprecated](http://www.senchalabs.org/connect/multipart.html). But since this module was used as the built-in HTTP body parser in Sails v0.9 and Express v3, this is a breaking change for v0.9 Sails projects relying on `req.files`.\n\nBy default in v0.10, Sails includes [skipper](https://github.com/balderdashy/skipper), a body parser which allows for streaming file uploads without buffering tmp files to disk. For run-of-the-mill file upload use cases, Skipper comes with bundled support for uploads to local disk (via skipper-disk), but streaming uploads can be plugged in to any of its supported adapters.\n\nFor examples/documentation, please see the Skipper repository as well as the Sails documentation on `req.file()`.\n\n### Why?\n\nA body parser's job is to parse the \"body\" of incoming multipart HTTP requests. Sometimes, that \"body\" includes text parameters, but sometimes, it includes file uploads.\n\nConnect multipart is great code, and it supports both file uploads AND text parameters in multipart requests. But like most modules of its kind, it accomplishes this by buffering file uploads to disk. This can quickly overwhelm a server's available disk space, and in many cases exposes a serious DoS attack vulnerability.\n\nSkipper is unique in that it supports **streaming** file uploads, but also maintains support for metadata in the request body (i.e. JSON/XML/urlencoded request body parameters). It uses a handful of heuristics to make sure only the files you're expecting get plugged in and received by the blob adapter, and other (potentially malicous) file fields are ignored.\n\n> #### ** Important!**\n> For Skipper to work, you _must include all text parameters BEFORE file parameters_ in file upload requests to the server. Once Skipper sees the first file field, it stops waiting for text parameters (this is to avoid unnecessary/unsafe buffering of file data).\n\n### Configuring a different body parser\n\nAs with most things in Sails, you can use any Connect/Express/Sails-compatible bodyparser you like. To switch back to **connect-multipart**, or any other body parser (like **formidable** or **busboy**), change your app's http configuration.\n\n## Blueprints\n\nA new blueprint action (`findOne`) has been added. For instance, if you have a `FooController` and `Foo` model, then send a request to `/foo/5`, the `findOne` action in your `FooController` will run. If you don't have a `findOne` action, the `findOne` blueprint action will be used in its stead. Requests sent to `/foo` will still run the find controller/blueprint action.\n\n## Policies\n\nPolicies work exactly as they did in v0.9- however there is a new consideration you should take into account: Due to the introduction of the more specific `findOne()` blueprint action mentioned above, you will want to make sure you're handling it explicitly in your policy mapping configuration.\n\nFor example, let's say you have a v0.9 app whose `policies.js` configuration prevents access to the `find` action in your `DoveController`:\n\n```javascript\nmodule.exports.policies = {\n  '*': true,\n  DoveController: {\n    find: false\n  }\n};\n```\n\nAssuming rest blueprint routes are enabled, this would prevent access to requests like both `/dove` and `/dove/14`. But now in v0.10, since `/dove/14` will actually run the `findOne` action, we must handle it explicitly:\n\n```javascript\nmodule.exports.policies = {\n  '*': true,\n  DoveController: {\n    find: false,\n    findOne: false\n  }\n};\n```\n\n## Pubsub\n\n### Summary\n+ `message` socket (i.e. \"comment\") event on client is now `modelIdentity` (where \"modelIdentity\" is different depending on the model that the `publish*()` method was called from.\n+ Clients are no longer subscribed to model-creation events by the blueprint routes. To listen for creation events, use `Model.watch()`.\n+ The events that were formerly `create`, `update`, and `destroy` are now `created`, `updated`, and `destroyed`.\n\n### Details\nThe biggest change to pubsub is that Socket.io events are emitted under the name of the model emitting them. Previously, your client listened for the `message` event and then had to determine which model it came from based on the included data:\n\n```javascript\nsocket.on('message', function(cometEvent) {\n   if (cometEvent.model == 'user') {\n     // Handle inbound messages related to a user record\n   }\n   else if (cometEvent.model === 'product') {\n     // Handle inbound messages related to a product record\n   }\n   // ...\n}\n```\nNow, you subscribe to the identity of the model:\n```javascript\nsocket.on('user', function(cometEvent) {\n  // Handle inbound messages related to a user record\n});\n\nsocket.on('product', function (cometEvent) {\n  // Handle inbound messages related to a product record\n});\n```\nThis helps to structure your front end code.\n\nThe way you subscribe clients to models has also changed. Previously, you specified whether you were subscribing to the model class (class room) or one or more model instances based on the parameters that you passed to `Model.subscribe`. It was effectively one method to do two very different things.\n\nNow, you use `Model.subscribe()` to subscribe only to model instances (records). You can also specify event \"contexts\", or types, that you'd like to hear about. For example, if you only wanted to get messages about updates to an instance, you would call `User.subscribe(req, myUser, 'update')`. If no context is given in a call to `.subscribe()`, then all contexts specified by the model class's autosubscribe property will be used.\n\nTo subscribe to model creation events, you can now use `Model.watch()`. Upon subscription, your clients will receive messages every time a new record is created on that model using the blueprint routes, and will automatically be subscribed to the new instance as well.\n\nRemember, when working with blueprints, clients are no longer auto subscribed to the class room. This must be done manually.\n\nFinally, if you want to see all pubsub messages from all models, you can access the `firehose`, a development-only tool that broadcasts messages about _everything_ that happens to your models. You can subscribe to the firehose using `sails.sockets.subscribeToFirehose(socket)`, or on the front end by making a socket request to `/firehose`. The firehose will broadcast a `firehose` event whenever a model is created, updated, destroyed, added to, removed from or messaged. This effectively replaces the `message` event used in previous Sails versions.\n\nTo see examples of the new pubsub methods in action, see [SailsChat](https://github.com/balderdashy/sailschat).\n\n## Arguments to lifecycle callbacks are now typecasted\n\nPreviously, with `schema: true`, if you sent an attribute value to a `.create()` or `.update()` that did not match the expected type declared in the model's attributes, the value you passed in would still be accessible in your model's lifecycle callbacks.\n\nIn Sails/Waterline v0.10, this is no longer the case. Values passed to `.create()` and `.update()` are type-casted before your lifecycle callbacks run. Affected lifecycle callbacks include `beforeUpdate()`, `beforeCreate()`, and `beforeValidate()`.\n\n## beforeValidation() is now beforeValidate()\n\nIf you were using the `beforeValidation` or `afterValidation` model lifecycle callbacks in any of your models, you should change them to `beforeValidate` or `afterValidate`. This change was made in Waterline to match the style of the other lifecycle callbacks (e.g. `beforeCreate`, `afterUpdate`, etc.).\n\n## .done() vs. .exec()\n\n** The old (/confusing?) meaning of `.done()` has been deprecated.**\n\nIn Sails <= v0.8, the syntax for executing an ORM query was `Model. [ … ] .done( cb )`. In v0.9, when promise support was added, the `Model. [ … ] .exec( cb )` became the recommended replacement, since `.done()` has a special meaning in the promise spec. However, the original usage of `.done()` was left untouched to make upgrading from v0.8 to v0.9 easier.\n\nBut as of Sails/Waterline v0.10, the original meaning of `.done()` has been officially deprecated to allow for a more robust promise implementation going forward, and pluggable promise library support (e.g. choose `Q` or `Bluebird` etc.).\n\n## Associations\n\nSails v0.10 introduces associations between data models. Since the work we've done on associations is largely additive, your existing models should still just work. That said, this is a powerful new feature that allows you to write less code and makes your app more maintainable, so we suggest taking advantage of it! To learn about how to use associations in Sails, check out the docs.\n\nAssociations (or \"relations\") are really just special attributes. Instead of string or integer values, you can specify an instance of a model or a collection of model instances. You can think about this kind of like an object (`{...}`) or an array (`[{...}, {...}]`) you might store as JSON in a NoSQL database. The difference is, in Sails, this works with any of the supported databases, and even allows you to populate (i.e. join) across different databases and types of databases.\n\n## Generators\n\nSails has had support for generating code for a while now (e.g. `sails generate controller foo`) but in v0.10, we wanted to make this feature more extensible, open, and accessible to everybody in the Sails community. With that in mind, v0.10 comes with a complete rewrite of the command-line tool, and pluggable generators. Want to be able to run `sails generate blog foo` to make a new blog built on Sails? Create a `blog` generator (run sails `generate generator blog`), add your templates, and configure the generator to copy the new templates over. Then you can release it to the community by publishing an npm module called `sails-generate-blog`. Compatibility with Yeoman generators is also in our roadmap.\n\n## Command-line tool\n\nThe big change here is how you create a new api. In the past you called `sails generate new_api`. This would generate a new controller and model called `new_api` in the appropriate places. This is now done using `sails generate api new_api`.\n\nYou can still generate models and controllers seperately using the same CLI Commands.\n\nAlso, `--linker` switch is no longer available. In previous version, if `--linker` switch was provided, it created a `myApp/assets/linker folder`, with `js`, `styles` and `templates` folders inside. In this new version, the `myApp/assets/linker` folder is not created. Compiling CoffeeScript and Less is the default behavior now, right from the `myApp/assets/js` and `myApp/assets/scripts` folders.\n\n## Custom server responses\n\nIn v0.10, you can now generate your own custom server responses.\n\nLike before, there are a few that we automatically create for you. Instead of generating `myApp/config/500.js` and other `.js` responses in the config directory, they are now generated in `myApp/api/responses/`.\n\nTo migrate, you will need to create a new v0.10 project and copy the `myApp/api/responses` directory into your existing app. You will then modify the appropriate .js file to reflect any customization you made in your response logic files (500.js,etc).\n\n## Legacy data stored in the temporary sails-disk database\n\n`sails-disk`, used by default in new Sails projects, now stores data a bit differently. If you have some temporary data stored in a 0.9.x project, you'll want to wipe it out and start fresh. To do this:\n\nFrom your project's root directory:\n\n```\n$ rm .tmp/disk.db\n```\n\n## Adapter/Database Configuration\n\n`config.adapters` (in `myApp/config/adapters.js`) is now config.connections (in new projects, this is generated in `myApp/config/connections.js`). Also, `config.model` is now `config.models`.\n\nYour app's default `connection` (i.e. database) should now be configured as a string `config.models.connection` used by default for model. New projects are generated with a `/config/models.js` file that includes the default connection.\n\nTo configure a model to use specific adapters, you must now specify them in the `connection` key instead of `adapters`.\n\nFor example:\n```javascript\nmodule.exports = {\n\n    connection: ['someMongoDatabase'],\n\n    attributes: {\n        name:{\n            type     : 'string',\n            required : true\n        }\n    }\n};\n```\n\n## Blueprints/Controller configuration\n\nThe object literal describing controller configuration overrides for controller blueprints should change from:\n```\n...\n_config: {\n  blueprints: {\n    rest: true,\n    ...\n  }\n}\n```\nto:\n```\n...\n_config: {\n    rest: true,\n    ...\n}\n```\n\n## Layout paths:\nIn Sails v0.9, you could use the following syntax to specify `auth/someLayout.ejs` as a custom layout when rendering a view:\n```javascript\nreturn res.view('auth/login',{\n  layout: 'someLayout'\n});\n```\nHowever in Sails v0.10, all layout paths are relative to your app's views path. In other words, the relative path of the layout is no longer resolved from the view's own path-- it is now always resolved from the views path. This makes it easier to understand which file is being used, particularly when layout files have similar names:\n```javascript\nreturn res.view('auth/login', {\n  layout: 'auth/someLayout'\n});\n```\n\n\n<docmeta name=\"displayName\" value=\"0.10.0 Migration Guide\">\n<docmeta name=\"version\" value=\"0.10.0\">\n"
  },
  {
    "path": "docs/version-notes/0.11.x/0.11.x.md",
    "content": "<docmeta name=\"displayName\" value=\"0.11.x\">\n<docmeta name=\"version\" value=\"0.11.0\">\n"
  },
  {
    "path": "docs/version-notes/0.11.x/MigrationGuide0.11.md",
    "content": "# v0.11 Migration Guide\n\n\n**tldr;**\n\nv0.11 comes with many minor improvements, as well as some internal cleanup in core.  The biggest change is that Sails core is now using Socket.io v1.\n\nAlmost none of this should affect the existing code in project, but there are a few important differences and new features to be aware of.  We've listed them below.\n\n\n## Differences\n\n#### Upgrade the Socket.io / Sails.io browser client\n\nOld v0.9 socket.io client will no longer work, so consequently you'll need to upgrade your sails.io.js client from v0.9 or v0.10 to v0.11.\n\nTo do this, just remove your sails.io.js client and install the new one.  We've bundled a new generator that will do this for you, assuming your sails.io.js client is in the conventional location at `assets/js/dependencies/sails.io.js` (i.e. if you haven't moved or renamed it):\n\n```sh\nsails generate sails.io.js --force\n```\n\n\n####  `onConnect` lifecycle callback\n\n> **tldr;**\n>\n> Remove your `onConnect` function from `config/sockets.js`.\n\nThe `onConnect` lifecycle callback has been deprecated.  Instead, if you need to do something when a new socket is connected, send a request from the newly-connected client to do so.  The purpose of `onConnect` was always for optimizing performance (eliminating the need to do this initial extra round-trip with the server), yet its use can lead to confusion and race conditions. If you desperately need to eliminate the server roundtrip, you can bind a handler directly on `sails.io.on('connect', function (newlyConnectedSocket){})` in your bootstrap function (`config/bootstrap.js`). However, note that this is discouraged.  Unless you're facing _true_ production performance issues, you should use the strategy mentioned above for your \"on connection\" logic (i.e. send an initial request from the client after the socket connects).  Socket requests are lightweight, so this doesn't add any tangible overhead to your application, and it will help make your code more predictable.\n\n\n\n####  `onDisconnect` lifecycle callback\n\nThe `onDisconnect` lifecycle callback has been deprecated in favor of `afterDisconnect`.\n\nIf you were using `onDisconnect` previously, you might have had to change the `session`, then call `session.save()` manually.  In v0.11, this works in almost exactly the same way, except that `afterDisconnect` receives an additional 3rd argument: a callback function.  This way, you can just call the provided callback when your `afterDisconnect` logic has finished, so that Sails can persist any changes you've made to the session automatically.  Finally, as you might expect, you won't need to call `session.save()` manually anymore- it is now taken care of for you (just like `req.session` in a normal route, action, or policy.)\n\n\n> **tldr;**\n> Rename your `onDisconnect` function in `config/sockets.js` with the following:\n>\n> ```\n> afterDisconnect: function (session, socket, cb) {\n>   // Be sure to call the callback\n>   return cb();\n> }\n> ```\n\n\n\n\n####  Other configuration in `config/sockets.js`\n\nMany of the configuration options in Socket.io v1 have changed, so you'll want to update your `config/sockets.js` file accordingly.\n\n+ if you haven&rsquo;t customized any of the options in `config/sockets.js` for your app, you can safely remove or comment out the entire file and let the Sails defaults do their magic.  Otherwise, refer to the new [Sails sockets  documentation](https://sailsjs.com/documentation/reference/configuration/sails-config-sockets) to ensure that your configuration is still valid and avoid unwanted hair loss.\n+ if you are scaling to multiple servers in an environment that does *not support sticky sessions*, you'll need to set your `transports` to `['websocket']` in both `config/socket.js` and your client--see [our Scaling doc](https://sailsjs.com/documentation/concepts/deployment/scaling#?preparing-your-app-for-a-clustered-deployment) for more info.\n+ if you were using a custom `authorization` function to restrict socket connections, you'll now want to use `beforeConnect`.  `authorization` was deprecated by Socket.io v1, but `beforeConnect` (which maps to the `allowRequest` option from Engine.io) works just the same way.\n+ if you were using other low-level socket configuration that was passed directly to socket.io v1, be sure and check out the [reference page on sailsjs.com](https://sailsjs.com/documentation/reference/configuration/sails-config-sockets) where all of the new configuration options are covered in detail.\n\n\n#### The \"firehose\"\n\nThe \"firehose\" feature for testing with sockets has been deprecated.  If you don't know what that means, you have nothing to worry about. The basic usage will continue to work for a while, but it will soon be removed from core and should not be relied upon in your app.  This also applies to the following methods:\n  + sails.sockets.subscribeToFirehose()\n  + sails.sockets.unsubscribeFromFirehose()\n  + sails.sockets.drink()\n  + sails.sockets.spit()\n  + sails.sockets.squirt()\n\n> If you want the \"firehose\" back, let [Mike know on twitter](http://twitter.com/mikermcneil) (it can be brought back as a separate hook).\n\n#### Config files in subfolders\n\nIt has always been the intention that files in the Sails `config` folder have no precedence over each other, and that the filenames and subfolders (with the exception of `local.js` and the `env` and `locale` subfolders) be used merely for organization.  However, in previous Sails versions, saving config files in subfolders would have the effect that the filename would be added as a key in `sails.config`, so that if you saved some config in `config/foo/bar.js`, then that config would be namespaced under `sails.config.bar`.  This was unintentional and potentially confusing as 1) the directory name is ignored, and 2) moving the file would change the config key.  This has been fixed in v0.11.x: config files in subfolders will be treated the same as those in the root `config` folder.  If you are for some reason relying on the old behavior, you may set `dontFlattenConfig` to `true` in your `.sailsrc` file, but we would strongly recommend that you instead just namespace the config yourself by setting the desired key on `module.exports`; for example `module.exports.foo = {...}`.  See [issue #2544](https://github.com/balderdashy/sails/issues/2544) for more details.\n\n#### Waterline now uses Bluebird\n\nAs of v0.11, Waterline now supports Bluebird (instead of q) for promises.  If you are using `.exec()` you won't be affected-- only if you are using `.then()`.  See https://github.com/balderdashy/sails/issues/1186 for more information.\n\n\n## New features\n\nSails v0.11 also comes with some new stuff that we thought you'd like to know about:\n\n\n#### User-level hooks\n\nHooks can now be installed directly from NPM.\n\nThis means you can now install hooks with a single command in your terminal.  For instance, consider the [`autoreload` hook](https://github.com/sgress454/sails-hook-autoreload) by [@sgress454](https://twitter.com/sgress454), which watches for changes to your backend code so you don't need to kill and re-lift the server every time you change your controllers, routes, models, etc.\n\nTo install the `autoreload` hook, run:\n\n```sh\nnpm install sails-hook-autoreload\n```\n\nThis is just one example of what's possible.  As you might already know, hooks are the lowest-level pluggable abstraction in Sails.  They allow authors to tap into the lift process, listen for events, inject custom \"shadow\" routes, and, in general, take advantage of raw access to the `sails` runtime.\nMost of the features you're familiar with in Sails have actually already been implemented as \"core\" hooks for over a year, including:\n\n+ `blueprints` _(which provides the blueprint API)_\n+ `sockets`    _(which provides socket.io integration)_\n+ `grunt`      _(which provides Grunt integration)_\n+ `orm`        _(which provides integration with the Waterline ORM, and imports your projects adapters, models, etc.)_\n+ `http`       _(which provides an HTTP server)_\n+ and 16 others.\n\nYou can read more about how to write your own hooks in the [new and improved \"Extending Sails\" documentation](https://sailsjs.com/documentation/concepts/extending-sails) on https://sailsjs.com.\n\n\n#### Socket.io v1.x\n\nThe upgrade to Socket.io v1.0 shouldn't actually affect your app-level code, provided you are using the layer of abstraction provided by Sails itself; everything from the `sails.sockets.*` wrapper methods and \"up\" (resourceful pubsub, blueprints)\nIf you are using underlying socket.io methods in your apps, or are just curious about what changed in Socket.io v1.0, be sure and check out the [complete Socket.io 1.0 migration guide](http://socket.io/docs/migrating-from-0-9/) from Guillermo and the socket.io team.\n\n#### Ever-increasing modularity\n\nAs part of the upgrade to Socket.io v1.0, we pulled out the core `sockets` hook into a separate repository.  This allowed us to write some modular, hook-specific tests for the socket.io interpreter, which will make things easier to maintain, customize, and override.\nThis also allows the hook to grow at its own pace, and puts related issues in one place.\n\nConsider this a test of the pros and cons of pulling other hooks out of the sails core repo over the next few months.  This will make Sails core lighter, faster, and more extensible, with fewer core dependencies, shorter \"lift\" time for most apps, and faster `npm install`s.\n\n\n#### Testing, the \"virtual\" request interpreter, and the `sails.request()` method\n\nIn the process of pulling the `sockets` hook _out_ of core, the logic which interprets requests has been normalized and is now located _in_ Sails core.  As a result, the `sails.request()` method is much more powerful.\n\nThis method allows you to communicate directly with the request interpreter in Sails without lifting your server onto a port.  It's the same mechanism that Sails uses to map incoming messages from Socket.io to \"virtual requests\" that have the familiar `req` and `res` streams.\n\nThe primary use case for `sails.request()` is in writing faster-running unit and integration tests, but it's also handy for proxying to mounted apps (or \"sub-apps\").\n\nFor instance, here is an example (using mocha) of how you might test one of your app's routes:\n\n```js\nvar assert = require('assert');\nvar Sails = require('sails').Sails;\n\nbefore(function beforeRunningAnyTests (done){\n\n  // Load the app (no need to \"lift\" to a port)\n  sails.load({\n    log: {\n      level: 'warn'\n    },\n    hooks: {\n      grunt: false\n    }\n  }, function whenAppIsReady(err){\n    if (err) return done(err);\n\n    // At this point, the `sails` global is exposed, although we\n    // could have disabled it above with our config overrides to\n    // `sails.load()`. In fact, you can actually use this technique\n    // to set any configuration setting you like.\n    return done();\n  });\n});\n\nafter(function afterTestsFinish (done) {\n  sails.lower(done);\n});\n\ndescribe('GET /hotpockets', function (){\n\n  it('should respond with a 200 status code', function (done){\n\n    sails.request({\n      method: 'get',\n      url: '/hotpockets',\n      params: {\n        limit: 10,\n        sort: 'price ASC'\n      }\n    }, function (err, clientRes, body) {\n      if (err) return done(err);\n\n      assert.equal(clientRes.statusCode, 200);\n      return done();\n    });\n\n  });\n});\n```\n\n\n#### `config/env/` subfolders\n\nIn v0.10.x, we added the `config/env` folder (thanks to [@clarkorz](https://github.com/clarkorz)), where you can add config files that will be loaded only in the appropriate environment (e.g. `config/env/production.js` for production environment, `config/env/development` for development, etc.).  In v0.11.x we've added the ability to specify whole subfolders per-environment.  For example, *all* config files saved to the `config/env/production` will be loaded and merged on top of other configuration when the environment is set to `production`.  Note that if both a `config/env/production` folder and a `config/env/production.js` file are present, the `config/env/production.js` settings will take precedence.  And, as always, `local.js` is merged on top of all other files, and `.sailsrc` rules them all.\n\n\n## Questions?\n\nAs always, if you run into issues upgrading, or if any of the notes above don't make sense, let us know and we'll do what we can to clarify.\n\nFinally, to those of you that have contributed to the project since the v0.10 release in August: we can't stress enough how much we value your continued support and encouragement.  There is a pretty massive stream of issues, pull requests, documentation tweaks, and questions, but it always helps to know that we're in this together :)\n\nThanks.\n\n-[@mikermcneil](https://github.com/mikermcneil/), [@sgress454](https://github.com/sgress454/) and [@particlebanana](https://github.com/particlebanana/)\n\n\n\n<docmeta name=\"displayName\" value=\"0.10 to 0.11 Migration Guide\">\n<docmeta name=\"version\" value=\"0.11.0\">\n"
  },
  {
    "path": "docs/version-notes/0.12.x/0.12.x.md",
    "content": "<docmeta name=\"displayName\" value=\"0.12.x\">\n<docmeta name=\"version\" value=\"0.12.0\">\n"
  },
  {
    "path": "docs/version-notes/0.12.x/migration-guide-0.12.md",
    "content": "# Upgrading to Sails v0.12\n\nSails v0.12 comes with an upgrade to Socket.io and Express, as well as many bug fixes and performance enhancements. You will find that this version is mostly backwards compatible with Sails v0.11, however there are some major changes to `sails.sockets.*` methods which may or may not affect your app. Most of the migration guide below deals with those changes, so if you are upgrading an existing app from v0.11 and are using `sails.sockets` methods, please be sure and carefully read the information below in case it affects your app.  Other than that, running `sails lift` in an existing project should just work.\n\nThe sections below provide a high level overview of what's changed, major bug fixes, enhancements and new features, as well as a basic tutorial on how to upgrade your v0.11.x Sails app to v0.12.\n\n## Installing the update\n\nRun the following command from the root of your Sails app:\n\n```bash\nnpm install sails@0.12.0 --force --save\n```\n\nThe `--force` flag will override the existing Sails dependency installed in your `node_modules/` folder with Sails v0.12, and the `--save` flag will update your package.json file so that future npm installs will also use the new version.\n\n\n## Things to do immediately after upgrading\n\n + If your app uses the `socket.io-redis` adapter, upgrade to at least version 1.0.0 (`npm install --save socket.io-redis@^1.0.0`).\n + If your app is using the Sails socket client (e.g. `assets/js/dependencies/sails.io.js`) on the front end, also install the newest version (`sails generate sails.io.js --force`)\n\n\n## Overview of changes in v0.12\n\n> For a full list of changes, see the changelog file for [Sails](https://github.com/balderdashy/sails/blob/master/CHANGELOG.md), as well as those for [Waterline](https://github.com/balderdashy/waterline/blob/master/CHANGELOG.md), [sails-hook-sockets](https://github.com/balderdashy/sails-hook-sockets/blob/master/CHANGELOG.md) and [sails.io.js](https://github.com/balderdashy/sails.io.js/blob/master/CHANGELOG.md).\n\n + Security enhancements: updated several dependencies with potential vulnerabilities\n + Reverse routing functionality is now built in to Sails core via the new [`sails.getRouteFor()`](https://sailsjs.com/documentation/reference/application/sails-get-route-for) and [`sails.getUrlFor()`](https://sailsjs.com/documentation/reference/application/sails-get-url-for) methods\n+ Generally improved multi-node support (and therefore scalability) of low-level `sails.socket.*` methods, and made additional adjustments and improvements related to the latest socket.io upgrade.  Added a much tighter Redis integration that sits on top of `socket.io-redis`, using a Redis client to implement cross-server communication rather than an additional socket client.\n+ Cleaned up the API for `sails.socket.*` methods, normalizing overloaded functions and deprecating methods which cause problems in multiserver deployments (more on that below).\n+ Added a few brand new sails.sockets methods: `.leaveAll()`, `.addRoomMembersToRooms()`, and `.removeRoomMembersFromRooms()`\n+ `sails.sockets.id()` is now `sails.sockets.getId()` (backwards compatible w/ deprecation message)\n+ New Sails apps are now generated with the updated version of `sails.io.js` (the JavaScript Sails socket client).  This upgrade bundles the latest version of `socket.io-client`, as well as some more advanced functionality (including the ability to specify common headers for all virtual socket requests)\n+ Upgraded to latest trusted versions of `grunt-contrib-*` dependencies (eliminates many NPM deprecation warnings and provides better error messages from NPM).\n+ If you are using NPM v3, running `sails new` will now run `npm install` instead of symlinking your new app's initial dependencies.  This is slower than you may be used to, but is a necessary change due to changes in the way NPM handles nested dependencies.  The core maintainers are [working on](https://github.com/npm/npm/issues/10013#issuecomment-178238596) a better long-term solution, but in the mean time if you run `sails new` a lot and the slowdown is bugging you, consider temporarily downgrading to an earlier version of NPM (v2.x).  If the installed version of NPM is < version 3, Sails will continue to take advantage of the classic symlinking strategy.\n\n\n## Socket Methods\n\nWithout question, the biggest change in Sails v0.12 is to the API of the low-level `sails.sockets` methods exposed by the `sockets` hook.  In order to ensure that Sails apps perform flawlessly in a [multi-server (aka \"multi-node\" or \"clustered\") environment](https://sailsjs.com/documentation/concepts/realtime/multi-server-environments), several [low-level methods](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets) have been deprecated, and some new ones have been added.\n\nThe following `sails.sockets` methods have been deprecated:\n\n + [`.emit()`](https://0.12.sailsjs.com/documentation/reference/web-sockets/sails-sockets/sails-sockets-emit)\n + [`.id()`](https://0.12.sailsjs.com/documentation/reference/web-sockets/sails-sockets/sails-sockets-id) (renamed to [`.getId()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/get-id))\n + [`.socketRooms()`](https://0.12.sailsjs.com/documentation/reference/web-sockets/sails-sockets/sails-sockets-socket-rooms)\n + [`.rooms()`](https://0.12.sailsjs.com/documentation/reference/web-sockets/sails-sockets/sails-sockets-rooms)\n + [`.subscribers()`](https://0.12.sailsjs.com/documentation/reference/web-sockets/sails-sockets/sails-sockets-subscribers)\n\nIf you are using any of those methods in your app, they will still work in v0.12 but _you should replace them as soon as possible_ as they may be removed from Sails in the next version.  See the individual doc pages for each method for more information.\n\n## Resourceful PubSub Methods\n\nThe [`.subscribers()`](https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/subscribers) resourceful pubsub method has been deprecated for the same reasons as [`sails.sockets.subscribers()`](https://sailsjs.com/documentation/reference/web-sockets/sails-sockets/sails-sockets-subscribers).  Follow the guidelines in the docs for replacing this method if you are using it in your code.\n\n\n## Waterline (ORM) Updates\n\nSails v0.12 comes with the latest version of the Waterline ORM (v0.11.0).  There are two API changes to be aware of:\n\n##### `.save()` no longer provides a second argument to its callback\n\nThe callback to the `.save()` instance method no longer receives a second argument.  While convenient, the requirement of providing this second argument made `.save()` less performant, especially for apps working with millions of records.  This change resolves those issues by eliminating the need to build redundant queries, and preventing your database from having to process them.\n\nIf there are places in your app where you have code like this:\n```javascript\nsierra.save(function (err, modifiedSierra){\n  if (err) { /* ... */  return; }\n\n  // ...\n});\n```\n\nYou should replace it with:\n```javascript\nsierra.save(function (err){\n  if (err) { /* ... */  return; }\n\n  // ...\n});\n```\n\n\n\n##### Custom column/field names for built-in timestamps\n\nYou can now configure a custom column name (i.e. field name for Mongo/Redis folks) for the built-in `createdAt` and `updatedAt` attributes.  In the past, the top-level `autoCreatedAt` and `autoUpdatedAt` model settings could be specified as `false` to disable the automatic injection of `createdAt` and `updatedAt` altogether.  That _still works as it always has_, but now you can also specify string values for one or both of these settings instead.  If a string is specified, it will be understood as the custom column (/field) name to use for the automatic timestamp.\n\n```javascript\n{\n  attributes: {},\n  autoCreatedAt: 'my_cool_created_when_timestamp',\n  autoUpdatedAt: 'my_cool_updated_at_timestamp'\n}\n```\n\nIf you were using the [workaround suggested by @sgress454 here](http://stackoverflow.com/a/24562385/486547), you may want to take advantage of this simpler approach instead.\n\n\n\n## SQL Adapter Performance\n\n[Sails-PostgreSQL](https://github.com/balderdashy/sails-postgresql) and [Sails-MySQL](https://github.com/balderdashy/sails-mysql) recieved patch updates that significantly improved performance when populating associations. Thanks to [@jianpingw](https://github.com/jianpingw) for digging into the source and finding a bug that was processing database records too many times. If you are using either of these adapters, upgrading to `sails-postgresql@0.11.1` or `sails-mysql@0.11.3` will give you a significant performance boost.\n\n\n## Contributing\n\nWhile not technically part of the release, Sails v0.12 is accompanied by some major improvements to the tools and resources available to contributors.  More core hooks are now fully documented ([controllers](https://github.com/balderdashy/sails/tree/master/lib/hooks/controllers)|[grunt](https://github.com/balderdashy/sails/tree/master/lib/hooks/grunt)|[logger](https://github.com/balderdashy/sails/tree/master/lib/hooks/logger)|[cors](https://github.com/balderdashy/sails/tree/master/lib/hooks/cors)|[responses](https://github.com/balderdashy/sails/tree/master/lib/hooks/responses)|[orm](https://github.com/balderdashy/sails/tree/master/lib/hooks/orm)), and the team has put together a [Code of Conduct](https://github.com/balderdashy/sails/blob/master/CODE-OF-CONDUCT.md) for contributing to the Sails project.\n\nThe biggest change for contributors is the [updated contribution guide](https://github.com/balderdashy/sails/blob/master/CONTRIBUTING.md), which contains the new, streamlined process for feature/enhancement proposals and for merging features, enhancements, and patches into core.  As the Sails framework has grown (both the code base and the user base), it's become necessary to establish clearer processes for how issue contributions, code contributions, and contributions to the documentation are reviewed and merged.\n\n\n## Documentation\n\nThis release also comes with a deep clean of the official reference documentation, and some minor usability improvements to the online docs at [https://sailsjs.com/documentation](https://sailsjs.com/documentation). The entire Sails website is now available in [Japanese](http://sailsjs.jp/), and four other [translation projects](https://github.com/balderdashy/sails/tree/master/docs#in-other-languages) are underway for Korean, Brazilian Portugese, Taiwanese Mandarin, and Spanish.\n\nIn addition, the Sails.js project (finally) has an [official blog](http://blog.sailsjs.com).  The Sails.js blog is the new source for all longform updates and announcements about Sails, as well as for our related projects like Waterline, Skipper and the machine specification.\n\n\n\n## Need Help?\n\nIf you run into an unexpected issue upgrading your Sails app to v0.12.0, please review our contribution guide and [submit an issue in the Sails GitHub repo](https://github.com/balderdashy/sails/blob/master/CONTRIBUTING.md).\n\n\n<docmeta name=\"displayName\" value=\"0.12 Migration Guide\">\n<docmeta name=\"version\" value=\"0.12.0\">\n"
  },
  {
    "path": "docs/version-notes/0.8.x/0.8.x.md",
    "content": "<docmeta name=\"displayName\" value=\"0.8.x\">\n<docmeta name=\"version\" value=\"0.8.0\">\n"
  },
  {
    "path": "docs/version-notes/0.8.x/Changelog0.8.7x.md",
    "content": "# Changelog 0.8.7x\n### 0.8.79\n\n+ Adapter definitions are no longer functions-- instead the direct definition object is accepted. This makes it easier, cleaner, and more declarative to create adapters.\n+ Merged waterline into main Sails repo.\n+ Brought in sails-util and sails-moduleloader, moved watelrine tests into top level.\n+ Attribute values in models in result sets from Waterline are now cast to numbers, if they are number-looking strings.\n+ Substantial refactoring of waterline model-augmentation logic.\n+ Added TODO for asynchronous module loading for future.\n+ Upgraded waterline-dirty dep.\n\n### 0.8.77\n\n+ Patch updates the waterline-dirty dependency to deal with an issue with that adapter returning objects which map directly to the in-memory database (was causing changes made to found models to be persisted without calling `.save()`)\n\n<docmeta name=\"displayName\" value=\"0.8.7x Changelog\">\n<docmeta name=\"version\" value=\"0.8.7\">\n\n"
  },
  {
    "path": "docs/version-notes/0.8.x/Changelog0.8.8x.md",
    "content": "\n# Changelog 0.8.8x\n### 0.8.80\n+ Refactored app layout to make it a bit more straightforward. To check out the the new folder structure, make a new project with `sails new foo`\n+ Added robot.txt in new app generation\n+ Bound all methods in adapter to have the right context.\n\n### 0.8.82\n_Sunday, February 24, 2013_\n+ Bootstrap function fires warning if callback not triggered after a few seconds (thanks [@virpool](https://github.com/virpool))\n+ Bug fixes w/ pubsub/model convenience methods.\n\n### 0.8.83\n_Saturday, March 2, 2013_\n+ Support for streaming large datasets from models `(e.g. User.stream().pipe(res);)`\n+ Bug fix for chains of multiple policies (thanks [@themouette](https://github.com/themouette))\n+ Jade template support (thanks [@valinorsgatekeeper](https://github.com/valinorsgatekeeper)\n+ AssetRack integration for more robust css/js/template/LESS management, replaces Rigging (thanks [@techpines](https://github.com/techpines))\n+ Fixed some docs /refactored (thanks @slantzjr)\n+ Bundled excruciatingly simple \"authenticated\" policy in new projects\n+ Made \"redirect\" work in API scaffolds\n+ Renamed waterline-* adapter modules as sails-*. Added backwards compat.\n+ Added .gitkeep in all directories when generating new projects to make sure they get committed\n+ Bootstrap and log config now available in project template\n+ View config now available in new projects as 'config/views.js'\n+ Better error checking in the `sails` CLI\n+ Docs\n+ Added app.js file back in, but this time hidden as '.app.js'. It can be run however you like, or you can use npm debug to debug it. To run daemonized, you can use `forever start .app.js`\n+ Added notion of `sails.explicitHost` to track whether a host was explicitly specified. If it was not, Express takes the approach of accepting `all connections via INADDR_ANY` (see [http://expressjs.com/2x/guide.html#app.listen()](http://expressjs.com/2x/guide.html#app.listen())) Now, if you specify `sails.config.host`, `sails.explicitHost` gets set, and Express will start the server deliberately using the host you specify. In certain PaaS deployments, this is required. For instance, this was causing problems in an Openshift deployment environment (big thanks to @hypereive for figuring that out).\n\n### 0.8.84\n_Saturday, March 2, 2013_\n+ Bug fixes: (explicit hosts, and included an additional file in new app generation)\n\n### 0.8.85\n_Sunday, March 3, 2013_\n+ Check for and warn if port is currently being used on lift, with support for explicit hosts [https://github.com/balderdashy/sails/issues/197](https://github.com/balderdashy/sails/issues/197))\n+ Model.stream() support over socket.io [https://github.com/balderdashy/sails/issues/196](https://github.com/balderdashy/sails/issues/196))\n\n### 0.8.86\n_Monday, March 4, 2013_\n+ Patch to allow for easier SSL configuration.\n\n### 0.8.87\n_Monday, March 4, 2013_\n+ Patch fixes updates sails-dirty version which fixes sorting by date\n\n### 0.8.88\n+ Adds coffeescript support on the front-end in dev and production environments via [asset-rack](https://github.com/techpines/asset-rack) [@techpines](thanks https://github.com/techpines)!)\n\n### 0.8.892\n+ Front-end CoffeeScript support in AssetRack (thanks [@techpines](https://github.com/techpines)!)\n+ Chained policy support\n+ New styles for default home page (thanks [@egdelwonk](https://github.com/egdelwonk)!)\n+ Windows compat. fix (thanks [@feroc1ty](https://github.com/feroc1ty)!)\n+ Support for string IDs (thanks [@tedkulp](https://github.com/tedkulp)!)\n+ Attribute scaffolding for model generation (thanks [@Tidwell](https://github.com/Tidwell))\n+ Support for big int string conversion in ID normalization (thanks [@d4mn](https://github.com/d4mn)!)\n\n### 0.8.895\n+ Policies: Fixed the \"*\" route for controllers.\n+ Policies: The \"*\" policy can now be set to false\n+ Collections: Type restrictions are cleaner\n+ Adapters: Default was changed to memory due to an issue with node-dirty\n+ Log: sails.config.log.level is passed to socket.io\n+ Assets: Bug fixed: not calling next when compiling LESS with syntax (thanks vicapow)\n+ Assets: Typescript supported on front-end (thanks Diullei)\n+ Assets: Meaningful LESS errors were added (thanks vicapow)\n\n\n<docmeta name=\"displayName\" value=\"0.8.8x Changelog\">\n<docmeta name=\"version\" value=\"0.8.8\">\n"
  },
  {
    "path": "docs/version-notes/0.8.x/Changelog0.8.9.md",
    "content": "# Changelog 0.8.9\n_April 9, 2013_\n+ Controllers must now also be generated to use the default API (they can be empty)\n+ Haml template support on back-end for new projects (thanks [@dcbartlett](https://github.com/dcbartlett))\n+ default values in models (defaultsTo)\n+ Chained policies fixed\n+ Removed all reference to blueprints as \"scaffolds\". Blueprints are more than temporary placeholders-- they are the preferred method of serving an API from your app.\n+ Refactored most of the code base\n+ Removed CRUD synonyms\n+ Main: Compatibility with Node v0.10.0 (patches node-dirty)\n+ Main: Fixed crash that happened when absolute path was given as appPath\n+ Assets: Added more logging features for LESS.\n+ Assets: Reset.css now in mixins\n+ Assets: LESS assets are deligated to Rack.LessAsset\n+ Assets: LESS assets served from asset-rack will have their extensions changed to css\n+ Policies: Implemented the controller syntax for defining a policy.\n+ Naming: scaffolds is now known as blueprints\n+ Naming: blueprints is now known as boilerplates\n+ Routing: Added controller.action syntax\n+ Routing: Removed CRUD Synonyms-- now you must explicitly use find, findAll, create, destroy, update (can't use `get`, `detail`, `delete`, `edit`, etc. to indicate the same thing. Turns out this was actually annoying, not helpful)\n+ Routing: Fix in API blueprint for regression around PUT/DELETE automatic RESTful routes\n+ Routing: Fix for resourceful routing. /model/[id] didn't work with verbs. It now does.\n+ Config: _ and async no longer have to be global (but they are by default) They are configurable with `sails.config.globals._` and `sails.config.globals.async` (thanks [@particlebanana](https://github.com/particlebanana)!)\n+ New sails project can now be created in the current dir with `sails new .` (thanks [@collinwren](https://github.com/collinwren)!)\n+ More tests (thanks [@collinwren](https://github.com/collinwren) and [@benrudolph](https://github.com/benrudolph))\n+ Travis CI integration (thanks [@collinwren](https://github.com/collinwren)!)\n\n\n<docmeta name=\"displayName\" value=\"0.8.9 Changelog\">\n<docmeta name=\"version\" value=\"0.8.9\">\n"
  },
  {
    "path": "docs/version-notes/0.8.x/ChangelogPre-0.8.77.md",
    "content": "# Changelog &#60;0.8.77\n\n+ I wasn't keeping good notes, sorry. :(\n+ Check out <a target=\"_blank\" href=\"https://github.com/balderdashy/sails/commits/master\">https://github.com/balderdashy/sails/commits/master</a> if you want to dive in.\n\n<docmeta name=\"displayName\" value=\"Pre-0.8.77 Changelog\">\n<docmeta name=\"version\" value=\"0.8.0\">\n\n"
  },
  {
    "path": "docs/version-notes/0.9.x/0.9.x.md",
    "content": "<docmeta name=\"displayName\" value=\"0.9.x\">\n<docmeta name=\"version\" value=\"0.9.0\">\n"
  },
  {
    "path": "docs/version-notes/0.9.x/Changelog0.9.0.md",
    "content": "# Changelog 0.9.0\n_July 10, 2013_\n### Sails.js\n+ Main: Express 3.x has been integrated.\n+ Main: CSRF Attack Protection was added as part of the core. Uses express-csrf, plus a token-based approach for SPAs and embedded apps (Chrome extensions, JavaScript plugins).\n+ Main: Most of the core has been refactored for performance, code clarity, and simplicity to make contributions easier.\n+ Main: Most of the core has been pulled into hooks. In a subsequent patch release for 0.9.x, this process will make Socket.io optional.\n+ Controllers: Automatic routing is now disable-able.\n+ Assets: Grunt integration replaces Asset Rack.\n+ Assets: Public folder removed from new projects.\n+ Assets: Temporary 'public' folder is automatically built on lift, using the contents of the assets folder.\n+ Assets: Static assets can be compiled with \"sails build\" for external hosting of front-end assets\n+ Assets: Grunt ecosystem allows for a [wide variety](https://github.com/gruntjs/grunt-contrib) of front-end template/css/js preprocessor support (sass, hbs, stylus, dust, typescript, etc.)\n+ Routing: Automatic 404 and 500 routing is replaced.\n+ Assets: Asset bundling is now disabled by default, use `sails new foo --linker` to enable it\n+ Config: Most configuration is now also explicit in new projects. Defaults are still provided underneath.\n+ Sockets: Socket.IO can now be configured with the options detailed in config/sockets.js.\n+ Sockets: Built-in support for Redis MQ-- allows you to scale realtime apps to a multi-instance deployment without necessitating sticky sessions at your load balancer.\n+ Views: Express 3 killed support for layouts/view partials. Sails has been extended to maintain support for them with ejs and jade, but otherwise you are limited to what is supported by the engine itself.\n+ Views: Automatic routing to views is now disable-able.\n+ Sessions: Built-in support for Redis and Mongo sessions for scaling your app to multi-instance deployments.\n\n### Waterline\n+ ORM: Waterline has been pulled out of Sails.js... Again. (See [Waterline](https://github.com/balderdashy/waterline))\n+ ORM: Model attributes now support validations. (See [Anchor](https://github.com/balderdashy/anchor))\n+ ORM: Custom instance methods can now be defined on models as virtual attributes.\n+ ORM: Lifecycle Callbacks have been added. (See [Lifecycle Callbacks](https://github.com/balderdashy/sails-docs/tree/0.9))\n+ ORM: findAll() has been replaced with find().\n+ ORM: find() has been replaced with findOne().\n+ ORM: .done() promise now works on all ORM methods\n+ ORM: Complete support for the Promise specificiation has been added.\n\n### Anchor\n+ Validations: Too many added to list, see [Validations](https://github.com/balderdashy/sails-docs/tree/0.9)\n\n<docmeta name=\"displayName\" value=\"0.9.0 Changelog\">\n<docmeta name=\"version\" value=\"0.9.0\">\n"
  },
  {
    "path": "docs/version-notes/0.9.x/Changelog0.9.16.md",
    "content": "# Changelog 0.9.16\n_December 16, 2013_\n\n+ [@sgress454](https://github.com/sgress454) Hotfix for CORS issue when no Origin header is present. … f42da3c\n+ [@mikermcneil](https://github.com/mikermcneil) Update README.md 1bf1d15\n+ [@bicherele](https://github.com/bicherele) Update es.json … ddb9a07\n+ [@andyzhau](https://github.com/andyzhau) Fix the join room variable reference error. 57783a3\n+ [@mikermcneil](https://github.com/mikermcneil) Use npm version of linker 5459119\n+ [@mikermcneil](https://github.com/mikermcneil) Update CHANGELOG.md aae737f\n+ [@devel-pa](https://github.com/devel-pa) lodash library updated to last version available (2.4.1) c71ce81\n+ [@mikermcneil](https://github.com/mikermcneil) Hot fix to protect connect cookie parsing. e181656\n+ [@mikermcneil](https://github.com/mikermcneil) Rebased from #1012 as hotfix for windows view issue. 031ebe1\n\n<docmeta name=\"displayName\" value=\"0.9.16 Changelog\">\n<docmeta name=\"version\" value=\"0.9.16\">\n"
  },
  {
    "path": "docs/version-notes/0.9.x/Changelog0.9.4.md",
    "content": "# Changelog 0.9.4\n_September 5, 2013_\n+ Improved CSRF prevention support (thanks to [@sgress454](https://github.com/sgress454))\n+ Support for CORS (thanks to [@sgress454](https://github.com/sgress454))\n+ CoffeeScript supported client-side by default in gruntfile thanks to @reecelewellen\n+ Improves/fixes internationalization (thanks to [@xdissent](https://github.com/xdissent) and [@silvinci](https://github.com/silvinci))\n+ Removed vanilla HAML support and tests since it was incomplete (jade is still supported)\n+ Config: Sails core is no longer automatically copied as a dependency during `sails new`. This speeds up the process significantly and avoids occassional recursive copy death spirals.\n+ Config: Added explicit `--port` option to `sails lift`.\n+ Sockets: Added query string parsing to requests.\n+ Sockets: Headers can now be specified in requests (**_This has implications on full compatibility w/ most Express middleware!_**)\n+ Routing: Fixed issues with default 404 and 500 responses.\n+ Other minor bug fixes/inconsistencies and documentation enhancements\n\n> And thanks a ton to anybody I left out! Send me a message on twitter and I'll add you.\n\n<docmeta name=\"displayName\" value=\"0.9.4 Changelog\">\n<docmeta name=\"version\" value=\"0.9.4\">\n"
  },
  {
    "path": "docs/version-notes/0.9.x/Changelog0.9.7.md",
    "content": "# Changelog 0.9.7\n_October 10, 2013_\n+ Complete improvement/refactoring of configuration loader (fixes bugs)\n+ Complete improvement/refactoring of ORM loader (fixes bugs)\n+ Continued improvements of tests\n+ Include a modified version of consolidate to better support view engines\n+ Blueprints are now configurable per-controller (thanks [@xdissent](https://github.com/xdissent), and everyone else who helped!)\n+ (waiting to expose this and deprecate the old behavior in the docs until the next minor release to avoid causing any breaking changes)\n+ New `prefix` option in global blueprint config, as well as per-controller.\n+ New `jsonp` option in global controller config, as well as per-controller.\n+ New `pluralize` option in global controller config, as well as per-controller.\n\n+ Models can now easily use one or more custom named connections which use different adapters\n+ (waiting to expose this and deprecate the old behavior in the docs until the next minor release to avoid causing any breaking changes)\n\n+ Adds configurable default behavior for 403/404/500/400 HTTP status code error cases.\n+ (waiting to expose this and deprecate the old behavior in the docs until the next minor release to avoid causing any breaking changes)\n\n+ Properly namespace the io in bundled sails.io.js client in new projects (thanks [@drosen0](https://github.com/drosen0))\n+ Better handle crash scenario, particularly in nodemon (thanks [@edy](https://github.com/edy))\n\n> Thanks to everyone else I missed, and to everyone else who helped out with this release!\n\n<docmeta name=\"displayName\" value=\"0.9.7 Changelog\">\n<docmeta name=\"version\" value=\"0.9.7\">\n"
  },
  {
    "path": "docs/version-notes/1.0.x/migration-guide-1.0.md",
    "content": "> The 1.0 migration guide now lives in the 'Upgrading' section, [here](https://github.com/balderdashy/sails/blob/master/docs/upgrading/To1.0.md).\n"
  },
  {
    "path": "errors/README.md",
    "content": "# errors/\n\n\n> FUTURE:\n> 1. Fold in messages inline instead of using the stringfile (`sails-stringfile` still isn't worth it, at least not until post-v1)\n> 2. ~~Ideally we don't call `process.exit()` at all- instead, consistently call sails.lower().  See comment at bottom of `fatal.js`.~~ That's ok sometimes actually (We have process handlers anyway.)  The issue is that we really need to inline these, because it all depends on the context.\n> 3. Inline these errors where they're being used.  Then this directory can be deleted.\n"
  },
  {
    "path": "errors/fatal.js",
    "content": "/**\n * Module dependencies\n */\nvar nodeutil = require('util');\nvar CaptainsLog = require('captains-log');\n\n// Once per process:\n// Build logger using best-available information\n// when this module is initially required.\nvar rconf = require('../lib/app/configuration/rc')();\nvar log = CaptainsLog(rconf.log);\n\n\n/**\n * Fatal Errors\n */\nmodule.exports = {\n\n  // Lift-time and load-time errors\n  failedToLoadSails: function(err) {\n    log.error();\n\n    // If the error is something the user can fix (as opposed to an internal Sails error AKA bug),\n    // just show the error message, not the whole stack trace into Sails core.\n    if (err.name && err.name === 'userError') {\n      log.error(err.message);\n    }\n\n    else {\n      // Dont log stack trace if this is a recognized load/lift-time error\n      // (& also comes from a core hook)\n      switch (err.code) {\n        case 'include-all:COULD_NOT_REQUIRE':\n        case 'E_COULD_NOT_LOAD_ADAPTER':\n        case 'E_ADAPTER_NOT_INSTALLED':\n        case 'E_BIND_ERR':\n          log.error(err.message); break;\n        default:\n          log.error(err);\n      }\n    }\n\n    console.error();\n    log.error('Could not load Sails app.');\n    log.error();\n    log.error('Tips:');\n    log.error(' • First, take a look at the error message above.');\n    log.error(' • Make sure you\\'ve installed dependencies with `npm install`.');\n    log.error(' • Check that this app was built for a compatible version of Sails.');\n    log.error(' • Have a question or need help?  (http://sailsjs.com/support)');\n    _terminateProcess(1);\n  },\n\n  noPackageJSON: function() {\n    log.error('Cannot read package.json in the current directory (' + process.cwd() + ')');\n    log.error('Are you sure this is a Sails app?');\n    _terminateProcess(1);\n  },\n\n  notSailsApp: function() {\n    log.error('The package.json in the current directory does not list Sails as a dependency...');\n    log.error('Are you sure `' + process.cwd() + '` is a Sails app?');\n    _terminateProcess(1);\n  },\n\n  badLocalDependency: function(pathToLocalSails, requiredVersion) {\n    log.error(\n      'The local Sails dependency installed at `' + pathToLocalSails + '` ' +\n      'has a corrupted, missing, or un-parsable package.json file.'\n    );\n    log.error('You may consider running:');\n    log.error('rm -rf ' + pathToLocalSails + ' && npm install sails@' + requiredVersion);\n    _terminateProcess(1);\n  },\n\n  // FUTURE: inline this error\n  // app/loadHooks.js:42\n  malformedHook: function() {\n    log.error('Malformed hook!');\n    log.error('Hooks should be a function with one argument (`sails`)');\n    _terminateProcess(1);\n  },\n\n  // FUTURE: inline this error\n  // app/load.js:146\n  hooksTookTooLong: function() {\n    var hooksTookTooLongErr = 'Hooks are taking way too long to get ready...  ' +\n      'Something might be amiss.\\nAre you using any custom hooks?\\nIf so, make sure the hook\\'s ' +\n      '`initialize()` method is triggering its callback.';\n    log.error(hooksTookTooLongErr);\n    process.exit(1);\n  },\n\n\n\n  // Invalid user module errors\n  invalidCustomResponse: function(responseIdentity) {\n    log.error('Cannot define custom response `' + responseIdentity + '`.');\n    log.error('`res.' + responseIdentity + '` has special meaning in Connect/Express/Sails.');\n    log.error('Please remove the `' + responseIdentity + '` file from the `responses` directory.');\n    _terminateProcess(1);\n  },\n\n\n  __UnknownPolicy__: function(policy, source, pathToPolicies) {\n    source = source || 'config.policies';\n\n    log.error('Unknown policy, \"' + policy + '\", referenced in `' + source + '`.');\n    log.error('Are you sure that policy exists?');\n    log.error('It would be located at: `' + pathToPolicies + '/' + policy + '.js`');\n    return _terminateProcess(1);\n  },\n\n  __InvalidConnection__: function(connection, sourceModelId) {\n    log.error('In model (' + sourceModelId + '), invalid connection ::', connection);\n    log.error('Must contain an `adapter` key referencing the adapter to use.');\n    return _terminateProcess(1);\n  },\n\n  __UnknownConnection__: function(connectionId, sourceModelId) {\n    log.error('Unknown connection, \"' + connectionId + '\", referenced in model `' + sourceModelId + '`.');\n    log.error('Are you sure that connection exists?  It should be defined in `sails.config.connections`.');\n\n    // var probableAdapterModuleName = connectionId.toLowerCase();\n    // if ( ! probableAdapterModuleName.match(/^(sails-|waterline-)/) ) {\n    //   probableAdapterModuleName = 'sails-' + probableAdapterModuleName;\n    // }\n    // log.error('Otherwise, if you\\'re trying to use an adapter named `' + connectionId + '`, please run ' +\n    //   '`npm install ' + probableAdapterModuleName + '@' + sails.majorVersion + '.' + sails.minorVersion + '.x`');\n    return _terminateProcess(1);\n  },\n\n\n  __ModelIsMissingConnection__: function(sourceModelId) {\n    log.error(nodeutil.format('One of your models (%s) doesn\\'t have a connection.', sourceModelId));\n    log.error('Do you have a default `connection` in your `config/models.js` file?');\n    return _terminateProcess(1);\n  },\n\n  __UnknownAdapter__: function(adapterId, sourceModelId /*, sailsMajorV, sailsMinorV */) {\n    log.error('Trying to use unknown adapter, \"' + adapterId + '\", in model `' + sourceModelId + '`.');\n    log.error('Are you sure that adapter is installed in this Sails app?');\n    log.error('If you wrote a custom adapter with identity=\"' + adapterId + '\", it should be in this app\\'s adapters directory.');\n\n    var probableAdapterModuleName = adapterId.toLowerCase();\n    if (!probableAdapterModuleName.match(/^(sails-|waterline-)/)) {\n      probableAdapterModuleName = 'sails-' + probableAdapterModuleName;\n    }\n    log.error('Otherwise, if you\\'re trying to use an adapter named `' + adapterId + '`, please run ' +\n      '`npm install ' + probableAdapterModuleName + ' --save'/*'@' + sailsMajorV + '.' + sailsMinorV + '.x`'*/);\n    return _terminateProcess(1);\n  },\n\n  __InvalidAdapter__: function(attemptedModuleName, supplementalErrMsg) {\n    log.error('There was an error attempting to require(\"' + attemptedModuleName + '\")');\n    log.error('Is this a valid Sails/Waterline adapter?  The following error was encountered ::');\n    log.error(supplementalErrMsg);\n\n    return _terminateProcess(1);\n  }\n};\n\n\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n// FUTURE: Make all of this more elegant (see the info in errors/README)\n// (involves getting rid of this file)\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n\n\n/**\n * _terminateProcess\n *\n * Terminate the process as elegantly as possible.\n * If process.env is 'test', throw instead.\n *\n * @param  {[type]} code [console error code]\n * @param  {[type]} opts [currently unused]\n */\nfunction _terminateProcess(code /*, opts */) {\n\n  // FUTURE: get rid of this (actual handling will be inline where the fatal errors are\n  // actually coming from, so we'll be able to handle it there by actually throwing.\n  // That way, it's up to the caller whether it wants to catch the original error and\n  // do a deliberate process.exit and omit the error stack (which can be disorienting\n  // for folks new to SSJ/Node.js))\n  //\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  // TODO: Double-check on this, and then remove it if possible:\n  // (I'm pretty sure we can get rid of this now b/c all tests have been updated.\n  // We should definitely never be checking that NODE_ENV is or isn't anything other\n  // than \"production\")\n  if (process.env.NODE_ENV === 'test') {\n    throw new Error({\n      type: 'terminate',\n      code: code,\n      // options: {\n      //   todo: 'put the stuff from the original errors in here'\n      // }\n      // ^^ Removed this in Sails v1 since it was useless anyways. ~Mike Dec 11, 2016\n    });\n  }//-•\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n  return process.exit(code);\n}\n"
  },
  {
    "path": "errors/index.js",
    "content": "// Merge together error sub-modules\nmodule.exports = {\n  fatal: require('./fatal'),\n  warn: require('./warn')\n};\n"
  },
  {
    "path": "errors/warn.js",
    "content": "/**\n * Module dependencies\n */\n\nvar nodepath = require('path');\nvar CaptainsLog = require('captains-log');\n\n// Once per process:\n// Build logger using best-available information\n// when this module is initially required.\nvar rconf = require('../lib/app/configuration/rc')();\nvar log = CaptainsLog(rconf.log);\n\n\n/**\n * Warnings\n */\nmodule.exports = {\n\n  incompatibleLocalSails: function(requiredVersion, localVersion) {\n    log.warn('Trying to lift app using a local copy of `sails`');\n    log.warn('(located in ' + nodepath.resolve(process.cwd(), 'node_modules/sails') + ')');\n    log.warn();\n    log.warn('But the package.json in the current directory indicates a dependency');\n    log.warn('on Sails `' + requiredVersion + '`, and the locally installed Sails is `' + localVersion + '`!');\n    log.warn();\n    log.warn('If you run into compatibility issues, try installing ' + requiredVersion + ' locally:');\n    log.warn('    $ npm install sails@' + requiredVersion);\n    log.warn();\n    log.blank();\n  },\n\n\n\n  // Verbose-only warnings:\n\n  noPackageJSON: function() {\n    log.warn('Cannot read package.json in the current directory (' + process.cwd() + ')');\n    log.warn('Are you sure this is a Sails app?');\n    log.warn();\n  },\n\n  notSailsApp: function() {\n    log.warn('The package.json in the current directory does not list Sails as a dependency...');\n    log.warn('Are you sure `' + process.cwd() + '` is a Sails app?');\n    log.warn();\n  },\n\n  badLocalDependency: function(pathToLocalSails, requiredVersion) {\n    log.warn(\n      'The local Sails dependency installed at `' + pathToLocalSails + '` ' +\n      'has a corrupted, missing, or un-parsable package.json file.'\n    );\n    log.warn('You may consider running:');\n    log.warn('rm -rf ' + pathToLocalSails + ' && npm install sails@' + requiredVersion);\n    log.warn();\n  }\n};\n"
  },
  {
    "path": "lib/EVENTS.md",
    "content": "# Core Events\n\n## Status\n\n> ##### Stability: [2](http://nodejs.org/api/documentation.html#documentation_stability_index) - Unstable\n>\n> The API is in the process of settling, but has not yet had sufficient real-world testing to be considered stable.\n>\n> Backwards-compatibility will be maintained if reasonable.\n\n\n## Purpose\n\nThe instantiated `sails` object is a Node EventEmitter.\n\n> WARNING\n> `sails.on(*)` events are for contributors to the core or developers building custom hooks.\n>\n> **Please do not use these events directly in your app. You have been warned!**\n\n\n### Background\n\nEvents have been a feature of the Sails core since v0.9.\n\n+ Developers needed an easier way to modify the Sails core for their needs.  Hooks and events make this possible!\n+ Events themselves originated as a feature to allow hooks to talk to each other during and after the app bootstrapping process.\n+ For posterity, the original `sails.on(*)` event proposal: https://gist.github.com/mikermcneil/5898598\n\n### Best Practices\n\nAlthough it can be tempting, it's really best not to add new events to `sails` in your app code.  In general, consistent conventions, clarity, and simplicity are the best practice for developing apps, because it makes them easier to extend, and makes it easier for you to remember how everything works when you come back to it later (not to mention everyone else on your team!)\n\nIf you want to add/trigger events to monkeypatch your Sails core, it's best to do this by authoring a hook.  More information will show up as we learn more about best practices around that process, but one thing we've definitely learned is that you're better off namespacing your events and firing them on a single object (`sails`), than emitting and listening on different objects.  Why?  Sometimes objects get deleted or copied, and this can make a big mess.\n\nIf you need a special event in your hook, you *will* want to namespace it.  For instance, if I'm adding a hook called `enforceRestfulSesssions` that limits the actions that can be added to controllers to encourage code consistency, I might have a `hook:enforceRestfulSesssions:checked` event that fires when all of the controllers have been checked.  This is so that other hooks that know about `enforceRestfulSesssions` can wait until it has finished its check before proceeding  (whether it's just me, or other people on my team, or if I release my hook and it gets popular, other people in the Sails community).\n\nIn my hook's initialize method, I might have the following:\n\n```javascript\n\n// Wait until all the middleware from this app's controllers have loaded\nsails.after('hook:controllers:loaded', function () {\n\n  // Do stuff\n  // e.g. prevent any methods called `login`, `logout` or `signup`\n  // since we've opted organizationally for using CRUD on a SessionController instead\n  // .....code here........\n\n  // When you're done, fire an event in\n  sails.emit('hook:enforceRestfulSesssions:checked');\n\n});\n```\n\n\n\n## Reference\n\n### Lifecycle\n\n##### `lifted`\nCalled after drawing the sailboat.\n\n##### `ready`\nCalled when all hooks are loaded and the internal router is ready to handle requests.\ni.e. the HTTP hook listens for `ready` before binding its HTTP server.\n\n##### `lower`\nCalled when `sails.lower()` is called.  `sails.lower()` is called automatically when the process is halted.\n\n##### `router:before`\nCalled before any of the app's configured static routes have been bound.\ni.e. a hook might listen to this event to bind some middleware.\n\n##### `router:after`\nCalled after all of the app's configured static routes have been bound.\ni.e. a hook might listen to this event to bind a \"shadow route\" to a blueprint.\n\n##### `router:done`\nCalled when all routes have been bound, including those originating from hooks (i.e. things listening for `router:after`.)\n\n##### `router:reset`\nCalled when the router is flushed (i.e. all routes are unbound).\nThe `http` hook (i.e. Express), and any other attached servers which maintain their own routes should listen for this event so they know to unbind their private routes.\n\n\n### Lift-time\n\n##### `router:bind`\nCalled when a route is bound. This allows hooks to handle routes directly if they want to-\n\n\nShould receive a single argument, \"routeObj\", which looks like:\n```\n{\n  path: 'String',\n  target: function theFnBoundtoTheRoute (req, res, next) {},\n  verb: 'String',\n  options: 'Object'\n}\n```\n\n##### `router:unbind`\nCalled when a route is unbound.\n\n\n### Runtime\n\n> NOTE: these events should only be relied on by attached servers without their own routers, or when a hook\n> implementation prefers to use the built-in Sails router.\n>\n> The optimal behavior for the http hook implemented on Express, for instance, is to listen to `router:bind`\n> from the built-in router and listen for the routes itself using `app.use`.  On the other hand, in the `sockets` hook,\n> Socket.io needs to use the `router:request` event to simulate a connect-style router since it\n> can't bind dynamic routes ahead of time.\n\n\n##### `router:request`\nCalled when a request is received by the Sails router.  Should receive three arguments, `req`, `res`, and `next`.\n\n##### `router:request:500`\nAbsolute last-resort handler for server errors.\nCalled when a request encounters an error and isn't handled by other means.\nShould receive three arguments, `err`, `req`, and `res`.\n\n##### `router:request:404`\nAbsolute last-resort handler for requests which don't match any routes.\nCalled when a request doesn't match any routes (or shadow routes, including 404/slug handlers), and this case isn't handled by other means.\nShould receive two arguments, `req`, and `res`.\n\n##### `router:route`\nCalled every time a request is routed.  Compare with `router:request`- e.g.:\n\n```\nsails.router.bind('/foo/*', noop);\nsails.router.bind('/foo/:bar', noop);\nsails.router.bind('/foo/explicit', noop);\n\n// Request to /foo/x will emit `router:request` only once, but `router:route` three times.\n```\n\n\n\n\n## Usage\n\n#### `sails.on()`\n\nFires your handler **NEXT TIME** the event is triggered and **EVERY TIME AFTERWARD**.\n\n```javascript\nsails.on('hook:yourHookID:someEvent', function yourEventHandler ( /* a, b, c, ..., z */ ) {\n  // your implementation\n});\n```\n\n#### `sails.once()`\n\nFires your handler **NEXT TIME** the specified event is triggered, and then stop listening.\n\n```javascript\nsails.once('hook:yourHookID:someEvent', function yourEventHandler ( /* a, b, c, ..., z */ ) {\n  // your implementation\n});\n```\n\n#### `sails.after()`\n\nFires your handler **IF THE SPECIFIED EVENT HAS ALREADY BEEN TRIGGERED** or **WHEN IT IS TRIGGERED**.\n\nKind of like jQuery's `$(document).ready()`, except `document` is whatever you want.\nUseful for checking whether some state has been achieved yet.\n\n```javascript\nsails.after('hook:yourHookID:someEvent', function yourEventHandler ( /* a, b, c, ..., z */ ) {\n  // your implementation\n});\n```\n\nYou can actually wait for several events using `.after` as well:\n\n```javascript\nsails.after(['hook:yourHookID:someEvent', 'hook:someOtherHookID:someOtherEvent'], function yourEventHandler ( /* a, b, c, ..., z */ ) {\n  // your implementation\n});\n```\n\n<!--\n\nThis can be omitted for now- it really shouldn't be used in userspace.\nMay be deprecated, API may change.  Please do not use.\n\n\n#### sails.emit\n\nEmit the specified event with the specified arguments to all listeners.\n\n```javascript\nsails.emit('hook:yourHookID:someEvent', 'arbitrary', 'number', {of: 'arguments'}, ['allowed']);\n```\n\n-->\n\n\n## FAQ\n\n> If you have a question that isn't covered here, please feel free to send a PR adding it to this section (even if you don't have the answer!)\n"
  },
  {
    "path": "lib/README.md",
    "content": "# Understanding Core\n\nWelcome to the Sails.js Core.\n\nThe goal of this file is to provide a light overview of the structure and philosophy of Sails.js for core contributors, as well as establish code and documentation conventions.\n\nMany of the subdirectories herein contain a `README.md` file with more information about that particular component.\n\n\n## Overview\n\nThe Sails.js core runs when an app is fired up with `sails.load` or `sails.lift`.\n\n\n## Stability Index\n\nSee the [Sails Project Stability Index](https://github.com/balderdashy/sails/blob/master/docs/contributing/stability-index.md) for more information.  We use a slight variation of the [stability index] used by [Node.js core](http://nodejs.org/api/documentation.html#documentation_stability_index); partially out of allegiance, but mostly for consistency.\n\n\n## FAQ\n\n> If you have an unanswered question that isn't covered here, and that you feel would add value for the community, please feel free to send a PR adding it to this section.\n"
  },
  {
    "path": "lib/app/README.md",
    "content": "# App Lifecycle\n\n\n## API Status\n\n> ##### Stability: [3](http://nodejs.org/api/documentation.html#documentation_stability_index) - Stable\n\n\n## Purpose\n\nThe `app` directory contains logic concerned with the lifecycle of the Sails core itself.  This includes:\n\n+ Loading and initializing hooks\n+ Loading the router\n+ Populating middleware library\n+ Teardown and cleanup of the currently-running instance of sails\n\n\n## Loading Steps\n\nThe Sails core has been iterated upon several times to make it easier to maintain and extend.\nAs a result, it has a very particular loading order, which its hooks depend on heavily.\nThis process is summarized below.\n\n#### Prepare Configuration Object\n\nPopulate `sails.config` with core (hook-agnostic) implicit defaults. Then apply the initial known set of configuration overrides, including command-line options, environment variables, and programmatic configuration (i.e. options passed to `sails.load` or `sails.lift`.)\nThe most important core implicit default configuration is the set of built-in hooks.\n\n#### Load Hooks\n\nLoad hooks in the proper order.\n\n#### Populate Middleware Registry\n\nGrab `this.middleware` from each hook and make it available on the `sails` object as `sails.middleware.[HOOK_ID]`.\n\n#### Assemble Router\n\nPrepare the core Router, then emit multiple events on the `sails` object informing hooks that they can safely bind routes.\n\n#### Expose global variables\n\nAfter all hooks have initialized, Sails exposes global variables\n(by default: `sails` object, models, services, `_`, and `async`)\n\n#### Initialize App Runtime\n\n> This step does not run when `sails.load()` is used programmatically.\n> To also run the initialization step, use `sails.lift()` instead.\n\n+ Start attached servers (by default: Express and Socket.io)\n+ Run the bootstrap function (`sails.config.bootstrap`)\n\n\n\n## FAQ\n\n\n+ What is the difference between `sails.lift()` and `sails.load()`?\n  + `lift()` === `load()` + `initialize()`.  It does everything `load()` does, plus it starts any attached servers (e.g. HTTP) and logs a picture of a boat.\n\n> If you have a question that isn't covered here, please feel free to send a PR adding it to this section (even if you don't have the answer!)\n\n\n"
  },
  {
    "path": "lib/app/Sails.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar util = require('util');\nvar events = require('events');\nvar _ = require('@sailshq/lodash');\nvar CaptainsLog = require('captains-log');\nvar loadSails = require('./load');\nvar mixinAfter = require('./private/after');\nvar __Router = require('../router');\n\n\n\n/**\n * Construct a Sails (app) instance.\n *\n * @constructor\n */\n\nfunction Sails() {\n\n  // Inherit methods from EventEmitter\n  events.EventEmitter.call(this);\n\n  // Remove memory-leak warning about max listeners\n  // See: http://nodejs.org/docs/latest/api/events.html#events_emitter_setmaxlisteners_n\n  this.setMaxListeners(0);\n\n  // Keep track of spawned child processes\n  this.childProcesses = [];\n\n  // Ensure CaptainsLog exists\n  this.log = CaptainsLog();\n\n  // Keep a hash of loaded actions\n  this._actions = {};\n\n  // Keep a hash of loaded action middleware\n  this._actionMiddleware = {};\n\n  // Build a Router instance (which will attach itself to the sails object)\n  __Router(this);\n\n  // Mixin `load()` method to load the pieces\n  // of a Sails app\n  this.load = loadSails(this);\n\n  // Mixin support for `Sails.prototype.after()`\n  mixinAfter(this);\n\n  // Bind `this` context for all `Sails.prototype.*` methods\n  this.load = _.bind(this.load, this);\n  this.request = _.bind(this.request, this);\n  this.lift = _.bind(this.lift, this);\n  this.lower = _.bind(this.lower, this);\n  this.initialize = _.bind(this.initialize, this);\n  this.exposeGlobals = _.bind(this.exposeGlobals, this);\n  this.runBootstrap = _.bind(this.runBootstrap, this);\n  this.isLocalSailsValid = _.bind(this.isLocalSailsValid, this);\n  this.isSailsAppSync = _.bind(this.isSailsAppSync, this);\n  this.inspect = _.bind(this.inspect, this);\n  this.toString = _.bind(this.toString, this);\n  this.toJSON = _.bind(this.toJSON, this);\n  this.all = _.bind(this.all, this);\n  this.get = _.bind(this.get, this);\n  this.post = _.bind(this.post, this);\n  this.put = _.bind(this.put, this);\n  this['delete'] = _.bind(this['delete'], this);\n  this.getActions = _.bind(this.getActions, this);\n  this.registerAction = _.bind(this.registerAction, this);\n  this.registerActionMiddleware = _.bind(this.registerActionMiddleware, this);\n  this.reloadActions = _.bind(this.reloadActions, this);\n\n}\n\n\n// Extend from EventEmitter to allow hooks to listen to stuff\nutil.inherits(Sails, events.EventEmitter);\n\n\n// Public methods\n////////////////////////////////////////////////////////\n\nSails.prototype.lift = require('./lift');\n\nSails.prototype.lower = require('./lower');\n\nSails.prototype.getRouteFor = require('./get-route-for');\nSails.prototype.getUrlFor = require('./get-url-for');\n\nSails.prototype.reloadActions = require('./reload-actions');\n\nSails.prototype.getActions = require('./get-actions');\nSails.prototype.registerAction = require('./register-action');\nSails.prototype.registerActionMiddleware = require('./register-action-middleware');\n\n\n// Public properties\n////////////////////////////////////////////////////////\n\n// Regular expression to match request paths that look like assets.\nSails.prototype.LOOKS_LIKE_ASSET_RX = /^[^?]*\\/[^?\\/]+\\.[^?\\/]+(\\?.*)?$/;\n\n\n// Experimental methods\n////////////////////////////////////////////////////////\n\nSails.prototype.request = require('./request');\n\n\n// Expose Express-esque synonyms for low-level usage of router\nSails.prototype.all = function(path, action) {\n  this.router.bind(path, action);\n  return this;\n};\nSails.prototype.get = function(path, action) {\n  this.router.bind(path, action, 'get');\n  return this;\n};\nSails.prototype.post = function(path, action) {\n  this.router.bind(path, action, 'post');\n  return this;\n};\nSails.prototype.put = function(path, action) {\n  this.router.bind(path, action, 'put');\n  return this;\n};\nSails.prototype.del = Sails.prototype['delete'] = function(path, action) {\n  this.router.bind(path, action, 'delete');\n  return this;\n};\n\n\n/**\n * .getRc()\n *\n * Get a dictionary of config from env vars, CLI opts, and `.sailsrc` file(s).\n *\n * @returns {Dictionary}\n */\n\nSails.prototype.getRc = require('./configuration/rc');\n\n\n// FUTURE: expose a flavored version of sails-generate as `.generate()`\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n// ```\n// Sails.prototype.generate = function (){ /* ... */ };\n// ```\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n\n\n\n// Private methods:\n////////////////////////////////////////////////////////\n\nSails.prototype.initialize = require('./private/initialize');\nSails.prototype.exposeGlobals = require('./private/exposeGlobals');\nSails.prototype.runBootstrap = require('./private/bootstrap');\nSails.prototype.isLocalSailsValid = require('./private/isLocalSailsValid');\nSails.prototype.isSailsAppSync = require('./private/isSailsAppSync');\n\n\n\n// Presentation methods:\n////////////////////////////////////////////////////////\nSails.prototype.inspect = require('./private/inspect');\nSails.prototype.toString = require('./private/toString');\nSails.prototype.toJSON = require('./private/toJSON');\n\n\n\n// Expose Sails constructor:\n////////////////////////////////////////////////////////\nmodule.exports = Sails;\n"
  },
  {
    "path": "lib/app/configuration/default-hooks.js",
    "content": "/**\n * Default hooks\n *\n * (order still matters for now for some of these-\n *  but mostly not, due to our use of events...\n *  ...but for a few core hooks, e.g. `moduleloader`,\n *  it still does.)\n *\n *\n * > FUTURE: make sure order does not matter, then once proven/tested,\n * > document it as such. (This will require adding a test that scrambles\n * > the order of this array then loads Sails over and over.)\n */\n\nmodule.exports = {\n  'moduleloader': true,//<< FUTURE: absorb into core (i.e. federate its methods out to the places where they are used and remove support for `sails.modules`)\n  'logger': true,//<< FUTURE: absorb into core (i.e. like what we did w/ the controllers hook -- can live in `lib/app/private/log-ship.js`, and the rest can be inlined)\n  'request': true,\n\n  // -•-  For posterity, this is where the `orm` hook was formerly inserted (please don't get rid of this until we're a few patch releases into Sails v1, just so it's easier to reference.)\n\n  'views': true,\n  'blueprints': true,//<< FUTURE: pull this out into a standalone hook and have it work like the other core hooks that get installed as peers (unless you do --without=blueprints)\n  'responses': true,\n  'helpers': true,\n\n  // -•-  For posterity, this is where the `sockets` hook was formerly inserted (please don't get rid of this until we're a few patch releases into Sails v1, just so it's easier to reference.)\n\n  'pubsub': true,//<< FUTURE: **pull the private methods into the blueprints hook, and pull the PUBLIC methods into sails-hook-sockets -- i.e. if orm hook available, then sails-hook-sockets decorates models with RPS methods**\n  'policies': true,\n  'services': true,\n  'security': true,\n  'i18n': true,//<< FUTURE: pull this out into a standalone hook and have it work like the other core hooks that get installed as peers (unless you do --without=i18n)\n  'userconfig': true,//<< FUTURE: absorb into core (i.e. like what we did w/ the controllers hook -- can live in `lib/app/configuration`)\n  'session': true,\n\n  // -•-  For posterity, this is where the `grunt` hook was formerly inserted (please don't get rid of this until we're a few patch releases into Sails v1, just so it's easier to reference.)\n\n  'http': true,\n  'userhooks': true//<< FUTURE: absorb into core (i.e. like what we did w/ the controllers hook -- its logic can live in `lib/app/private`, and be driven by `lib/hooks/index.js`)\n};\n"
  },
  {
    "path": "lib/app/configuration/index.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar path = require('path');\nvar _ = require('@sailshq/lodash');\nvar DEFAULT_HOOKS = require('./default-hooks');\n\nmodule.exports = function(sails) {\n\n  /**\n   * Expose new instance of `Configuration`\n   */\n\n  return new Configuration();\n\n\n  function Configuration() {\n\n\n    /**\n     * Sails default configuration\n     *\n     * @api private\n     */\n    this.defaults = function defaultConfig(appPath) {\n\n      var defaultEnv;\n      // If we're not loading the userconfig hook, which normally takes care\n      // of ensuring that we have an environment, then make sure we set one here.\n      if (_.isObject(sails.config.hooks) && sails.config.hooks.userconfig === false ||\n         (_.isArray(sails.config.loadHooks) && sails.config.loadHooks.indexOf('userconfig') === -1)\n      ) {\n        defaultEnv = sails.config.environment || 'development';\n      }\n\n      // If `appPath` not specified, unfortunately, this is a fatal error,\n      // since reasonable defaults cannot be assumed\n      if (!appPath) {\n        throw new Error('No `appPath` specified!');\n      }\n\n      // Set up config defaults\n      return {\n\n        environment: defaultEnv,\n\n        // Note: to avoid confusion re: timing, `hooks` configuration may eventually be removed\n        // from `sails.config` in favor of something more flexible / obvious, e.g. the `app` object\n        // itself (i.e. because you can't configure hooks in `userconfig`-- only in `overrides`).\n\n        // Core (default) hooks\n        hooks: _.reduce(DEFAULT_HOOKS, function (memo, hookBundled, hookIdentity) {\n\n          // if `true`, then the core hook is bundled in the `lib/hooks/` directory\n          // as `lib/hooks/HOOK_IDENTITY`.\n          if (hookBundled === true) {\n            memo[hookIdentity] = require('../../hooks/'+hookIdentity);\n          }\n          // if it's a string, then the core hook is an NPM dependency of sails,\n          // so require it (which grabs it from `node_modules/`)\n          else if (_.isString(hookBundled)) {\n            var hook;\n            try {\n              hook = require(hookBundled);\n            } catch (unusedErr) {\n              // FUTURE: provide access to error details instead of swallowing\n              throw new Error('Sails internal error: Could not require(\\''+hookBundled+'\\').');\n            }\n            memo[hookIdentity] = hook;\n          }\n          // otherwise freak out\n          else {\n            throw new Error('Sails internal error: `'+hookIdentity+'`, a core hook, is invalid!');\n          }\n          return memo;\n        }, {}) || {},\n\n        // Save appPath in implicit defaults\n        // appPath is passed from above in case `sails lift` was used\n        // This is the directory where this Sails process is being initiated from.\n        // (  usually this means `process.cwd()`  )\n        appPath: appPath,\n\n        // Built-in path defaults\n        paths: {\n          tmp: path.resolve(appPath, '.tmp')\n        },\n\n        // Start off `routes` and `middleware` as empty objects\n        routes: {},\n        middleware: {},\n\n        // Set implicit default for sniffing tactic.\n        // Respected by helpers and actions, when running the configured\n        // bootstrap function, and when invoking the `initialize` method\n        // in hooks.\n        implementationSniffingTactic: 'analogOrClassical'\n\n      };\n    };\n\n\n\n    /**\n     * Load the configuration modules\n     *\n     * @api private\n     */\n\n    this.load = require('./load')(sails);\n\n\n\n    // Bind the context of all instance methods\n    _.bindAll(this);\n\n  }\n\n};\n"
  },
  {
    "path": "lib/app/configuration/load.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar path = require('path');\nvar fs = require('fs');\nvar _ = require('@sailshq/lodash');\nvar async = require('async');\nvar CaptainsLog = require('captains-log');\nvar mergeDictionaries = require('merge-dictionaries');\n\n\nmodule.exports = function(sails) {\n\n  /**\n   * Expose Configuration loader\n   *\n   * Load command-line overrides\n   *\n   * FUTURE: consider merging this into the `app` directory\n   *\n   * For reference, config priority is:\n   * --> implicit defaults\n   * --> environment variables\n   * --> user config files\n   * --> local config file\n   * --> configOverride ( in call to sails.lift() )\n   * --> --cmdline args\n   */\n\n  return function loadConfig(cb) {\n\n    // Save reference to context for use in closures\n    var self = this;\n\n    // Commence with loading/validating/defaulting all the rest of the config\n    async.auto({\n\n      /**\n       * Until this point, `sails.config` is composed only of\n       * configuration overrides passed into `sails.lift(overrides)`\n       * (or `sails.load(overrides)`-- same thing)\n       *\n       * This step clones this into an \"overrides\" object, negotiating cmdline\n       * shortcuts into the properly namespaced sails configuration options.\n       */\n      mapOverrides: function(cb) {\n\n        // Clone the `overrides` that were passed in.\n        // TODO -- since this code is only called as a result of `sails.load()`, which already\n        //         clones the overrides, is this clone necessary?\n        var overrides = _.clone(sails.config || {});\n\n        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n        // FUTURE: Try bringing the rconf stuff from bin/sails-lift in here\n        // (that way, we don't have to rely on duplicate code in app.js and in bin/sails-lift.js)\n        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n\n        // Map Sails options from overrides, handling a few special \"shortcuts\"\n        // (i.e. allowing for CLI arguments like `--verbose`, instead of `--log.level=verbose`)\n        try {\n          overrides = _.merge(overrides, {\n\n            // `--verbose` command-line shortcut\n            // `--silly` command-line shortcut\n            // `--silent` command-line shortcut\n            log: overrides.verbose ? {\n              level: 'verbose'\n            } : overrides.silly ? {\n              level: 'silly'\n            } : overrides.silent ? {\n              level: 'silent'\n            } : undefined,\n\n            // `--port=?` command-line shortcut\n            port: overrides.port || undefined,\n\n            // `--safe` command-line shortcut\n            // `--alter` command-line shortcut\n            // `--drop` command-line shortcut\n            models: (function(){\n              if (overrides.safe) {\n                return { migrate: 'safe' };\n              }\n              else if (overrides.drop) {\n                return { migrate: 'drop' };\n              }\n              else if (overrides.alter) {\n                return { migrate: 'alter' };\n              }\n              else {\n                return undefined;\n              }\n            })(),\n\n            // `--redis` command-line shortcut\n            session: (function(){\n              if (overrides.redis) {\n                return { adapter: '@sailshq/connect-redis' };\n              }\n              return undefined;\n            })(),\n            sockets: (function(){\n              if (overrides.redis) {\n                return { adapter: '@sailshq/socket.io-redis' };\n              }\n              return undefined;\n            })(),\n\n            // `--prod` command-line shortcut\n            // `--staging` command-line shortcut\n            // `--dev` command-line shortcut\n            environment: (function(){\n              if (overrides.staging) {// --staging\n                return 'staging';\n              } else if (overrides.prod){// --prod  (but it's cleaner to use NODE_ENV=production with no other environment instead)\n                return 'production';\n              } else if (overrides.dev) {// --dev  (deprecated)\n                console.warn('`--dev` option is deprecated: Please do not use it.');\n                // Note: we use `console.warn` here because we're not guaranteed\n                // to have a working logger yet.\n                return 'development';\n              } else {\n                return undefined;\n              }\n            })()//†\n\n          });\n\n        } catch (e) { return cb(e); }\n\n        // Pass on overrides object\n        return cb(undefined, overrides);\n      },\n\n\n\n      /**\n       * Immediately instantiate the default logger in case a log-worthy event occurs\n       * Even though the app might actually use its own custom logger, we don't know\n       * all of the user configurations yet.\n       *\n       * Makes sails.log accessible for the first time\n       */\n      logger: ['mapOverrides',\n        function(asyncData, cb) {\n          var logConfigSoFar = asyncData.mapOverrides.log;\n          sails.log = new CaptainsLog(logConfigSoFar);\n          cb();\n        }\n      ],\n\n\n      /**\n       * Expose version/dependency info for the currently-running\n       * Sails on the `sails` object (from its `package.json`)\n       */\n      versionAndDependencyInfo: function(cb) {\n\n        var pathToThisVersionOfSails = path.join(__dirname, '../../..');\n        var json;\n        try {\n          json = JSON.parse(fs.readFileSync(path.resolve(pathToThisVersionOfSails, 'package.json'), 'utf8'));\n        } catch (e) {\n          return cb(e);\n        }\n        sails.version = json.version;\n        sails.majorVersion = sails.version.split('.')[0].replace(/[^0-9]/g, '');\n        sails.minorVersion = sails.version.split('.')[1].replace(/[^0-9]/g, '');\n        sails.patchVersion = sails.version.split('.')[2].replace(/[^0-9]/g, '');\n        sails.dependencies = json.dependencies;\n\n        cb();\n      },\n\n\n      /**\n       * Ensure that environment variables are applied to important configs\n       */\n      mixinDefaults: ['mapOverrides',\n        function(results, cb) {\n\n          // Get overrides\n          var overrides = results.mapOverrides;\n\n          // Apply environment variables\n          // (if the config values are not set in overrides)\n          overrides.environment = overrides.environment || process.env.NODE_ENV;\n          overrides.port = overrides.port || process.env.PORT;\n\n          // Generate implicit, built-in framework defaults for the app\n          var implicitDefaults = self.defaults(overrides.appPath || process.cwd());\n\n          // Extend copy of implicit defaults with user config\n          // TODO -- is the _.clone() necessary?\n          var mergedConfig = mergeDictionaries(_.clone(implicitDefaults), overrides);\n          return cb(undefined, mergedConfig);\n        }\n      ]\n\n    },\n\n\n    function configLoaded(err, results) {\n      if (err) {\n        sails.log.error('Error encountered loading config ::\\n', err);\n        return cb(err);\n      }\n\n      // Override the previous contents of sails.config with the new, validated\n      // config w/ defaults and overrides mixed in the appropriate order.\n      sails.config = results.mixinDefaults;\n\n      return cb();\n    });\n  };\n\n};\n"
  },
  {
    "path": "lib/app/configuration/rc.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar minimist = require('minimist');\nvar rc = require('../../util/rc.js');\nvar rttc = require('rttc');\n\n\n/**\n * Load configuration from .rc files and env vars\n * @param  {String} namespace [namespace to look for env vars under (defaults to `sails`)]\n * @return {Dictionary} A dictionary of config values gathered from .rc files, with env vars and command line options merged on top\n */\n\nmodule.exports = function(namespace) {\n\n  // Default namespace to `sails`.\n  namespace = namespace || 'sails';\n\n  // Locate and load .rc files if they exist.\n  var conf = rc(namespace);\n\n  // Load in overrides from the environment, using `rttc.parseHuman` to\n  // guesstimate the types.\n  // NOTE -- the code below is lifted from the `rc` module, and modified to:\n  //  1. Pass JSHint\n  //  2. Run `parseHuman` on values\n  // If at some point `rc` exposes metadata about which configs came from\n  // the environment, we can simplify our code by just running `parseHuman`\n  // on those values instead of doing the work to pluck them from the env.\n\n  // Construct the expected env var prefix from the namespace.\n  var prefix = namespace + '_';\n\n  // Cache the prefix length so we don't have to keep looking it up.\n  var l = prefix.length;\n\n  // Loop through the env vars, looking for ones with the right prefix.\n  _.each(process.env, function(val, key) {\n\n    // If this var's name has the right prefix...\n    if((key.indexOf(prefix)) === 0) {\n\n      // Replace double-underscores with dots, to work with Lodash _.set().\n      var keypath = key.substring(l).replace(/__/g,'.');\n\n      // Attempt to parse the value as JSON.\n      try {\n        val = rttc.parseHuman(val, 'json');\n      }\n      // If that doesn't work, humanize the value without providing a schema.\n      catch(unusedErr) {\n        val = rttc.parseHuman(val);\n      }\n\n      // Override the current value at this keypath in `conf` (which currently contains\n      // the string value of the env var) with the now (possibly) humanized value.\n      _.set(conf, keypath, val);\n\n    }\n\n  });\n\n  // Load command line arguments, since they need to take precedence over env.\n  var argv = minimist(process.argv.slice(2));\n\n  // Merge the command line arguments back on top.  Minimist allows nested config (i.e. --log.level=silly),\n  // hence the _.merge().\n  conf = _.merge(conf, argv);\n\n  return conf;\n\n};\n"
  },
  {
    "path": "lib/app/get-actions.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar _ = require('@sailshq/lodash');\n\n/**\n * Sails.prototype.getActions()\n *\n * Return a shallow clone of the loaded actions dictionary.\n *\n * @returns {Dictionary} A shallow clone of all actions, indexed by their unique identifier.\n *\n * @this {SailsApp}\n * ----------------------------------------------------------------------------------------\n *\n * Usage:\n *\n * ```\n * sails.getActions();\n * // =>\n * // {\n * //   'duck/quack': {...},\n * //   // ...\n * // }\n * ```\n */\nmodule.exports = function getActions() {\n\n  // Return a shallow clone of the actions dictionary, so that the caller\n  // can't modify the actions.\n  return _.clone(this._actions);\n\n};\n"
  },
  {
    "path": "lib/app/get-route-for.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\nvar detectVerb = require('../util/detect-verb');\n\n\n/**\n * getRouteFor()\n *\n * Look up more information about the first explicit route defined in this app\n * which has the given route target.\n *\n * Note that this function _only searches explicit routes_ which have been configured\n * manually (e.g. in `config/routes.js`).  For more info, see:\n * https://github.com/balderdashy/sails/issues/3402#issuecomment-171633341\n *\n * @this {SailsApp}\n * ----------------------------------------------------------------------------------------\n *\n * Usage:\n *\n * ```\n * getRouteFor('DuckController.quack');\n * getRouteFor({ target: 'DuckController.quack' });\n * // =>\n * // {\n * //   url: '/ducks/:id/quack',\n * //   method: 'post'\n * // }\n * ```\n */\nmodule.exports = function getRouteFor(routeQuery){\n\n  // Get reference to sails app instance.\n  var sails = this;\n\n  // Get the identity of the action we're trying to look up, based on the route we're querying.\n  var actionToLookup;\n  try {\n    actionToLookup = sails.router.getActionIdentityForTarget(routeQuery);\n  } catch (unusedErr) { // FUTURE: provide access to error details instead of swallowing\n    var invalidUsageErr = new Error(`Usage error: sails.getRouteFor() expects a string route target (e.g. \"DuckController.quack\") or a dictionary with either a \"target\" (e.g. {target: \"DuckController.quack\"}) or an \"action\" (e.g. {controller: \"duck\", action: \"quack\"} or {action: \"duck/quack\"}).  But instead, it received a ${typeof routeQuery}: ${util.inspect(routeQuery, {depth: null})}`);\n    invalidUsageErr.code = 'E_USAGE';\n    throw invalidUsageErr;\n  }\n\n  // Now look up the first route with this target (`routeTargetToLookup`).\n  var firstMatchingRouteAddress = _.find(_.keys(sails.router.explicitRoutes), function (address) {\n    var target = sails.router.explicitRoutes[address];\n    // Attempt to look up the action that corresponds to this route target.\n    var actionIdentity;\n    try {\n      actionIdentity = sails.router.getActionIdentityForTarget(target);\n    } catch (e) {\n      // If the target is not an action (i.e if it is a view, a raw req/res function or something else)\n      // then just return false (it's not a match for the action we're looking up).\n      if (e.code === 'E_NOT_ACTION_TARGET') {\n        return false;\n      }\n      // If some other error occurred looking up the target, then throw it.\n      throw e;\n    }\n    // Ok, we got an action identity.  Does it match the identity of the action we're trying to look up?\n    return (actionIdentity === actionToLookup);\n  });\n\n  // If no route was found, throw an error.\n  if (!firstMatchingRouteAddress) {\n    var unrecognizedTargetErr = new Error('Route not found: No explicit route could be found in this app with the specified target (`'+util.inspect(routeQuery, {depth: null})+'`).');\n    unrecognizedTargetErr.code = 'E_NOT_FOUND';\n    throw unrecognizedTargetErr;\n  }\n\n  // Now that the raw route address been located, we'll normalize it:\n  //\n  // If route address is '*', it will be automatically corrected to `/*` when bound, so also reflect that here.\n  firstMatchingRouteAddress = firstMatchingRouteAddress === '*' ? '/*' : firstMatchingRouteAddress;\n\n  // Then we parse it into its HTTP method and URL pattern parts.\n  var parsedAddress = detectVerb(firstMatchingRouteAddress);\n\n  // At this point we being building the final return value- the route info dictionary.\n  var routeInfo = {};\n  routeInfo.method = parsedAddress.verb || '';\n  routeInfo.url = parsedAddress.path;\n\n\n  // And finally return the route info.\n  return routeInfo;\n\n};\n"
  },
  {
    "path": "lib/app/get-url-for.js",
    "content": "/**\n * Module dependencies\n */\n\n// N/A\n\n\n/**\n * getUrlFor()\n *\n * Look up the URL of this app's first explicit route with the given route target.\n *\n * Note that this function _only searches explicit routes_ which have been configured\n * manually (e.g. in `config/routes.js`).  For more info, see:\n * https://github.com/balderdashy/sails/issues/3402#issuecomment-171633341\n *\n *\n * @this {SailsApp}\n * ----------------------------------------------------------------------------------------\n *\n * Usage:\n *\n * ```\n * getUrlFor('DuckController.quack');\n * // => '/ducks/:id/quack'\n *\n * getUrlFor({ target: 'DuckController.quack' });\n * // => '/ducks/:id/quack'\n * ```\n */\nmodule.exports = function getUrlFor(routeQuery){\n\n  // Get reference to sails app instance.\n  var sails = this;\n\n  // Now attempt to look up the first route that matches the specified argument\n  // and if it works, then return its URL.\n  return sails.getRouteFor(routeQuery).url;\n\n};\n"
  },
  {
    "path": "lib/app/index.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar _ = require('@sailshq/lodash');\nvar Sails = require('./Sails');\n\n\n/**\n * Expose `Sails` factory...thing.\n * (maintains backwards compatibility w/ constructor usage)\n */\n\nmodule.exports = SailsFactory;\n\nfunction SailsFactory() {\n  return new Sails();\n}\n\n\n// Backwards compatibility for Sails singleton usage:\nvar singleton = SailsFactory();\nSailsFactory.isLocalSailsValid = _.bind(singleton.isLocalSailsValid, singleton);\nSailsFactory.isSailsAppSync = _.bind(singleton.isSailsAppSync, singleton);\n\n\n\n"
  },
  {
    "path": "lib/app/lift.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar _ = require('@sailshq/lodash');\nvar async = require('async');\nvar chalk = require('chalk');\n\n/**\n * Sails.prototype.lift()\n *\n * Load the app, then bind process listeners and emit the internal \"ready\" event.\n * The \"ready\" event is listened for by core hooks; for example, the HTTP hook uses\n * it to start listening for requests.\n *\n * > This method also logs the ASCII art for the characteristic ship.\n *\n * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n * @param {Dictionary?} configOverride\n *        Overrides that will be deep-merged (w/ precedence) on top of existing configuration.\n * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n * @callback {Function?} done\n *        @param {Error?} err\n *\n * A Node-style callback that wil be triggered when the lift has completed (one way or another)\n * > If the `done` callback is omitted, then:\n * >  • If the lift fails, Sails will log the underlying fatal error using `sails.log.error()`.\n * >  • Otherwise, Sails will log \"App lifted successfully.\" using `sails.log.verbose()`.\n * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n * @api public\n * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n */\n\nmodule.exports = function lift(configOverride, done) {\n\n  var sails = this;\n\n  // configOverride is optional.\n  if (_.isFunction(configOverride)) {\n    done = configOverride;\n    configOverride = {};\n  }\n\n  // Callback is optional (but recommended.)\n  done = done || function defaultCallback(err) {\n    if (err) {\n      sails.log.error('Failed to lift app:',err);\n      if(err.raw) {\n        sails.log.error('More details (raw):', err.raw);\n      }\n      sails.log.silly('(You are seeing the above error message because no custom callback was programmatically provided to `.lift()`.)');\n      return;\n    }\n\n    sails.log.verbose('App lifted successfully.');\n    sails.log.silly('(You are seeing the \"App lifted successfully\" verbose log message because no custom callback was programmatically provided to `.lift()`.)');\n  };\n\n  async.series([\n\n    function (next) {\n      sails.load(configOverride, next);\n    },\n\n    function (next){\n      sails.initialize(next);\n    },\n\n  ], function whenSailsIsReady(err) {\n    if (err) {\n      sails.lower(function (additionalErrLoweringSails){\n\n        if (additionalErrLoweringSails) {\n          sails.log.error('When trying to lower the app as a result of a failed lift, encountered an error:', additionalErrLoweringSails);\n        }//>-\n\n        return done(err);\n\n      });//</sails.lower>\n      return;\n\n    }//-•\n\n\n    // If `config.noShip` is set, skip the startup message.\n    // Otherwise, gather app meta-info and log startup message (the boat).\n    if (!_.isObject(sails.config.log) || !sails.config.log.noShip) {\n\n      sails.log.ship && sails.log.ship();\n      sails.log.info(('Server lifted in `' + sails.config.appPath + '`'));\n\n\n      sails.log.info(('To shut down Sails, press <CTRL> + C at any time.'));\n      sails.log.info(('Read more at '+chalk.underline('https://sailsjs.com/support')+'.'));\n      sails.log.blank();\n      sails.log(chalk.grey(Array(56).join('-')));\n      sails.log(chalk.grey(':: ' + new Date()));\n      sails.log.blank();\n      sails.log('Environment : ' + sails.config.environment);\n\n      // Only log the host if an explicit host is set\n      if (!_.isUndefined(sails.config.explicitHost)) {\n        sails.log('Host        : ' + sails.config.explicitHost); // 12 - 4 = 8 spaces\n      }\n      sails.log('Port        : ' + sails.config.port); // 12 - 4 = 8 spaces\n\n      // > Note that we don't try to include the \"Local: \" stuff\n      // > unless we're pretty sure which URL it would be a good idea to try and visit.\n      // > (even then, it's not 100% or anything.  But at least with these checks, it's\n      // > not wrong MOST of the time.)\n      if (process.env.NODE_ENV !== 'production' &&\n        (!sails.config.ssl || _.isEqual(sails.config.ssl, {})) &&\n        !sails.config.http.serverOptions &&\n        !sails.config.explicitHost\n      ) {\n        sails.log('Local       : ' +  chalk.underline('http://localhost:'+sails.config.port));\n      }\n\n      sails.log.verbose('NODE_ENV  : ' + (process.env.NODE_ENV||chalk.gray('(not set)'))); // 12 - 8 - 2 = 2 spaces\n      sails.log.silly();\n      sails.log.silly('Version Info:');\n      sails.log.silly('node        : ' + (process.version));\n      sails.log.silly('engine (v8) : ' + (process.versions.v8));\n      sails.log.silly('openssl     : ' + (process.versions.openssl));\n      sails.log(chalk.grey(Array(56).join('-')));\n    }//>-\n\n\n    // Emit 'lifted' event.\n    sails.emit('lifted');\n\n    // Set `isLifted` (private dignostic flag)\n    sails.isLifted = true;\n\n    // try {console.timeEnd('core_lift');}catch(e){}\n\n    return done(undefined, sails);\n\n  });//</async.series()>\n};\n"
  },
  {
    "path": "lib/app/load.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\nvar async = require('async');\nvar flaverr = require('flaverr');\nvar __Configuration = require('./configuration');\nvar __initializeHooks = require('./private/loadHooks');\nvar __loadActionModules = require('./private/controller/load-action-modules');\nvar __checkGruntConfig = require('./private/checkGruntConfig');\n\n/**\n * @param  {SailsApp} sails\n * @returns {Function}\n */\nmodule.exports = function(sails) {\n\n  var Configuration = __Configuration(sails);\n  var initializeHooks = __initializeHooks(sails);\n  var checkGruntConfig = __checkGruntConfig(sails);\n\n  /**\n   * Expose loader start point.\n   * (idempotent)\n   *\n   * @api public\n   */\n  return function load(configOverride, cb) {\n\n    if (sails._exiting) {\n      return cb(new Error('\\n*********\\nCannot load or lift an app after it has already been lowered. \\nYou can make a new app instance with:\\nvar SailsApp = require(\\'sails\\').Sails;\\nvar sails = new SailsApp();\\n\\nAnd then you can do:\\nsails.load([opts,] cb)\\n\\n'));\n    }\n\n    // Log a verbose log message about the fact that EVEN MORE verbosity\n    // is available in `silly` mode.\n    sails.log.verbose('• • • • • • • • • • • • • • • • • • • • • • • • • • • • • •');\n    sails.log.verbose('•  Loading Sails with \"verbose\" logging enabled...        •');\n    sails.log.verbose('•  (For even more details, try \"silly\".)                  •');\n    sails.log.silly  ('•  Actually, looks like you\\'re already using \"silly\"!     •');\n    sails.log.verbose('•                                                         •');\n    sails.log.verbose('•  http://sailsjs.com/config/log                          •');\n    sails.log.verbose('• • • • • • • • • • • • • • • • • • • • • • • • • • • • • •');\n\n    // configOverride is optional\n    if (_.isFunction(configOverride)) {\n      cb = configOverride;\n      configOverride = {};\n    }\n\n    // Ensure override is an object and clone it (or make an empty object if it's not).\n    // The shallow clone protects against the caller accidentally adding/removing props\n    // to the config after Sails has loaded (but they could still mess with nested config).\n    configOverride = configOverride || {};\n    sails.config = _.clone(configOverride);\n\n\n    // If host is explicitly specified, set `explicitHost`\n    // (otherwise when host is omitted, Express will accept all connections via INADDR_ANY)\n    if (configOverride.host) {\n      configOverride.explicitHost = configOverride.host;\n    }\n\n    // Optionally expose services, models, sails, _, async, etc. as globals as soon as the\n    // user config loads.\n    sails.on('hook:userconfig:loaded', sails.exposeGlobals);\n\n    async.auto({\n\n      // Apply core defaults and hook-agnostic configuration,\n      // esp. overrides including command-line options, environment variables,\n      // and options that were passed in programmatically.\n      config: [Configuration.load],\n\n      // Verify that the combination of Sails environment and NODE_ENV is valid\n      // as early as possible -- that is, as soon as we know for sure what the\n      // Sails environment is.\n      verifyEnv: ['config', function(results, cb) {\n        // If the userconfig hook is active, wait until it's finished to\n        // verify the environment, since it might be set in a config file.\n        if (_.isUndefined(sails.config.hooks) || (sails.config.hooks !== false && sails.config.hooks.userconfig !== false)) {\n          sails.on('hook:userconfig:loaded', verifyEnvironment);\n        }\n        // Otherwise verify it right now.  The Sails environment will be\n        // whatever was set on the command line, or via the sails_environment\n        // env var, or defaulted to \"development\".\n        else {\n          verifyEnvironment();\n        }\n        return cb();\n      }],\n\n      // Check if the current app needs the Grunt hook installed but doesn't have it.\n      grunt: ['config', checkGruntConfig],\n\n      // Load hooks into memory, with their middleware and routes\n      hooks: ['verifyEnv', 'config', helpLoadHooks],\n\n      // Load actions from disk and config overrides\n      controller: ['hooks', function(results, cb) {\n        __loadActionModules(sails, cb);\n      }],\n\n      // Populate the \"registry\"\n      // Houses \"middleware-esque\" functions bound by various hooks and/or Sails core itself.\n      // (i.e. `function (req, res [,next]) {}`)\n      //\n      // (Basically, that means we grab an exposed `middleware` object,\n      // full of functions, from each hook, then make it available as\n      // `sails.middleware.[HOOK_ID]`.)\n      //\n      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n      // FUTURE: finish refactoring to change \"middleware\" nomenclature\n      // to avoid confusion with the more specific (and more common)\n      // usage of the term.\n      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n      registry: ['hooks',\n        function populateRegistry(results, cb) {\n\n          // Iterate through hooks and absorb the middleware therein\n          // Save a reference to registry and expose it on\n          // the Sails instance.\n          sails.middleware = sails.registry =\n          // Namespace functions by their source hook's identity\n          _.reduce(sails.hooks, function(registry, hook, identity) {\n            registry[identity] = hook.middleware;\n            return registry;\n          }, {});\n\n          sails.emit('middleware:registered');\n\n          cb();\n        }\n      ],\n\n      // Load the router and bind routes in `sails.config.routes`\n      router: ['registry', sails.router.load]\n\n    }, ready__(cb));\n\n    // Makes `app.load()` chainable\n    return sails;\n  };\n\n\n  // ==============================================================================\n  // < inline function declarations >\n  //    ██╗    ██╗███╗   ██╗██╗     ██╗███╗   ██╗███████╗    ███████╗███╗   ██╗    ██████╗ ███████╗███████╗███████╗    ██╗\n  //   ██╔╝    ██║████╗  ██║██║     ██║████╗  ██║██╔════╝    ██╔════╝████╗  ██║    ██╔══██╗██╔════╝██╔════╝██╔════╝    ╚██╗\n  //  ██╔╝     ██║██╔██╗ ██║██║     ██║██╔██╗ ██║█████╗      █████╗  ██╔██╗ ██║    ██║  ██║█████╗  █████╗  ███████╗     ╚██╗\n  //  ╚██╗     ██║██║╚██╗██║██║     ██║██║╚██╗██║██╔══╝      ██╔══╝  ██║╚██╗██║    ██║  ██║██╔══╝  ██╔══╝  ╚════██║     ██╔╝\n  //   ╚██╗    ██║██║ ╚████║███████╗██║██║ ╚████║███████╗    ██║     ██║ ╚████║    ██████╔╝███████╗██║     ███████║    ██╔╝\n  //    ╚═╝    ╚═╝╚═╝  ╚═══╝╚══════╝╚═╝╚═╝  ╚═══╝╚══════╝    ╚═╝     ╚═╝  ╚═══╝    ╚═════╝ ╚══════╝╚═╝     ╚══════╝    ╚═╝\n  //\n\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  // FUTURE: extrapolate the following inline function definitions into\n  // separate files -- or if appropriate (and if there's no tangible impact\n  // on lift performance) then pull them inline above.\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n  /**\n   * Load hooks in parallel\n   * let them work out dependencies themselves,\n   * taking advantage of events fired from the sails object\n   *\n   * @api private\n   */\n  function helpLoadHooks(results, cb) {\n    sails.hooks = { };\n\n    // If config.hooks is disabled, skip hook loading altogether\n    if (sails.config.hooks === false) {\n      return cb();\n    }\n\n    async.series([\n      function(cb) {\n        loadHookDefinitions(sails.hooks, cb);\n      },\n      function(cb) {\n        initializeHooks(sails.hooks, cb);\n      }\n    ], function(err) {\n      if (err) { return cb(err); }\n\n      // Inform any listeners that the initial, built-in hooks\n      // are finished loading\n      sails.emit('hooks:builtIn:ready');\n      sails.log.silly('Built-in hooks are ready.');\n      return cb();\n    });\n  }\n\n  /**\n   * Load built-in hook definitions from `sails.config.hooks`\n   * and put them back into `hooks` (probably `sails.hooks`)\n   *\n   * @api private\n   */\n  function loadHookDefinitions(hooks, cb) {\n\n    // Mix in user-configured hook definitions\n    _.extend(hooks, sails.config.hooks);\n\n    // Make sure these changes to the hooks object get applied\n    // to sails.config.hooks to keep logic consistent\n    // (I think we can get away w/o this, but leaving as a stub)\n    // sails.config.hooks = hooks;\n\n    // If user configured `loadHooks`, only include those.\n    if (sails.config.loadHooks) {\n      if (!_.isArray(sails.config.loadHooks)) {\n        return cb('Invalid `loadHooks` config.  ' +\n          'Please specify an array of string hook names.\\n' +\n          'You specified ::' + util.inspect(sails.config.loadHooks));\n      }\n\n      _.each(hooks, function(def, hookName) {\n        if (!_.contains(sails.config.loadHooks, hookName)) {\n          hooks[hookName] = false;\n        }\n      });\n      sails.log.verbose('Deliberate partial load-- will only initialize hooks ::', sails.config.loadHooks);\n    }\n\n    return cb();\n  }\n\n  function verifyEnvironment() {\n    // At this point, the Sails environment is set to its final value,\n    // whether it came from the command line or a config file. So we\n    // can now compare it to the NODE_ENV environment variable and\n    // act accordingly.  This may involve changing NODE_ENV to \"production\",\n    // which we want to do as early as possible since dependencies might\n    // be relying on that value.\n\n    // If the Sails environment is production, but NODE_ENV is undefined,\n    // log a warning and change NODE_ENV to \"production\".\n    if (sails.config.environment === 'production' && process.env.NODE_ENV !== 'production' ) {\n      if (_.isUndefined(process.env.NODE_ENV)) {\n        sails.log.debug('Detected Sails environment is \"production\", but NODE_ENV is `undefined`.');\n        sails.log.debug('Automatically setting the NODE_ENV environment variable to \"production\".');\n        sails.log.debug();\n        process.env.NODE_ENV = 'production';\n      } else {\n        throw flaverr({ name: 'userError', code: 'E_INVALID_NODE_ENV' }, new Error('When the Sails environment is set to \"production\", NODE_ENV must also be set to \"production\" (but it was set to \"' + process.env.NODE_ENV + '\" instead).'));\n      }\n    }\n  }\n\n  /**\n   * Returns function which is fired when Sails is ready to go\n   *\n   * @api private\n   */\n  function ready__(cb) {\n    return function(err) {\n      if (err) {\n        return cb && cb(err);\n      }\n\n      sails.log.silly('The router & all hooks were loaded successfully.');\n\n      // If userconfig hook is turned off, still load globals.\n      if (sails.config.hooks && sails.config.hooks.userconfig === false ||\n           (sails.config.loadHooks && sails.config.loadHooks.indexOf('userconfig') === -1)) {\n        sails.exposeGlobals();\n      }\n\n      // If the Sails environment is set to \"production\" but the Node environment isn't,\n      // log a warning.\n      if (sails.config.environment === 'production' && process.env.NODE_ENV !== 'production') {\n        sails.log.warn('Detected Sails environment of `production`, but Node environment is `' + process.env.NODE_ENV + '`.\\n' +\n                       'It is recommended that in production mode, both the Sails and Node environments be set to `production`.');\n      }\n\n      cb && cb(null, sails);\n    };\n  }\n\n  //    ██╗    ██╗    ██╗███╗   ██╗██╗     ██╗███╗   ██╗███████╗    ███████╗███╗   ██╗    ██████╗ ███████╗███████╗███████╗    ██╗\n  //   ██╔╝   ██╔╝    ██║████╗  ██║██║     ██║████╗  ██║██╔════╝    ██╔════╝████╗  ██║    ██╔══██╗██╔════╝██╔════╝██╔════╝    ╚██╗\n  //  ██╔╝   ██╔╝     ██║██╔██╗ ██║██║     ██║██╔██╗ ██║█████╗      █████╗  ██╔██╗ ██║    ██║  ██║█████╗  █████╗  ███████╗     ╚██╗\n  //  ╚██╗  ██╔╝      ██║██║╚██╗██║██║     ██║██║╚██╗██║██╔══╝      ██╔══╝  ██║╚██╗██║    ██║  ██║██╔══╝  ██╔══╝  ╚════██║     ██╔╝\n  //   ╚██╗██╔╝       ██║██║ ╚████║███████╗██║██║ ╚████║███████╗    ██║     ██║ ╚████║    ██████╔╝███████╗██║     ███████║    ██╔╝\n  //    ╚═╝╚═╝        ╚═╝╚═╝  ╚═══╝╚══════╝╚═╝╚═╝  ╚═══╝╚══════╝    ╚═╝     ╚═╝  ╚═══╝    ╚═════╝ ╚══════╝╚═╝     ╚══════╝    ╚═╝\n  //\n  // </ inline function declarations (see note above) >\n  // ==============================================================================\n\n\n};\n"
  },
  {
    "path": "lib/app/lower.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar _ = require('@sailshq/lodash');\nvar async = require('async');\n\n\n/**\n * Sails.prototype.lower()\n *\n * The inverse of `lift()`, this method\n * shuts down all attached servers.\n *\n * It also unbinds listeners and terminates child processes.\n *\n * @api public\n */\n\nmodule.exports = function lower(options, cb) {\n  var sails = this;\n\n  sails.log.verbose('Lowering sails...');\n\n  // `options` is optional.\n  if (_.isFunction(options)) {\n    cb = options;\n    options = undefined;\n  }\n\n  // Callback is optional\n  cb = cb || function(err) {\n    if (err)  { return sails.log.error(err); }\n  };\n\n  options = options || {};\n  options.delay = options.delay || 100;\n\n  // Flag `sails._exiting` as soon as the app has begun to shut down.\n  // This may be used by core hooks and other parts of core\n  // (e.g. to stop handling HTTP requests and prevent ugly error msgs).\n  sails._exiting = true;\n\n  var beforeShutdown = (sails.config && sails.config.beforeShutdown) || function(cb) {\n    return cb();\n  };\n\n  // Wait until beforeShutdown logic runs\n  beforeShutdown(function(err) {\n\n    // If an error occurred, don't stop-- still go ahead and take care of other teardown tasks.\n    if (err) {\n      sails.log.error(err);\n    }\n\n    // Try to kill all child processes\n    _.each(sails.childProcesses, function kill(childProcess) {\n      sails.log.silly('Sent kill signal to child process (' + childProcess.pid + ')...');\n      try {\n        childProcess.kill('SIGINT');\n      } catch (e) {\n        sails.log.error('While lowering Sails app: received error killing child process:', e.stack);\n      }\n    });\n\n    // Shut down HTTP server\n    sails.emit('lower');\n    // (Note for future: would be cleaner to provide a way to defer this to the http\n    // and sockets hooks-- i.e. having hooks expose a `teardown(cb)` interceptor. Keep\n    // in mind we'd need a way to distinguish between a graceful shutdown and a force\n    // kill.  In a force kill situation, it's never ok for the process to hang.)\n\n    async.series([\n\n      function shutdownSockets(cb) {\n\n        // If the sockets hook is disabled, skip this.\n        // Also skip if the socket server is piggybacking on the main HTTP server, to avoid\n        // the onClose event possibly being called multiple times (because you can't tell\n        // socket.io to close without it trying to close the http server).  If we're piggybacking\n        // we'll call sails.io.close in the main \"shutdownHTTP\" code below.\n        if (!_.isObject(sails.hooks) || !sails.hooks.sockets || !sails.io || (sails.io && sails.io.httpServer && sails.hooks.http.server === sails.io.httpServer)) {\n          return cb();\n        }\n\n        var timeOut;\n\n        try {\n          sails.log.silly('Shutting down socket server...');\n          timeOut = setTimeout(function() {\n            sails.io.httpServer.removeListener('close', onClose);\n            return cb();\n          }, 100);\n          sails.io.httpServer.unref();\n          sails.io.httpServer.once('close', onClose);\n          sails.io.close();\n        } catch (e) {\n          sails.log.verbose('Error occurred closing socket server: ', e);\n          clearTimeout(timeOut);\n          return cb();\n        }\n\n        function onClose() {\n          sails.log.silly('Socket server shut down successfully.');\n          clearTimeout(timeOut);\n          cb();\n        }\n\n      },\n\n      function shutdownHTTP(cb) {\n        if (!_.isObject(sails.hooks) || !sails.hooks.http || !sails.hooks.http.server) {\n          return cb();\n        }\n\n        var timeOut;\n\n        try {\n          sails.log.silly('Shutting down HTTP server...');\n\n          // Allow process to exit once this server is closed\n          sails.hooks.http.server.unref();\n\n          // If we have a socket server and it's piggybacking on the main HTTP server, tell\n          // socket.io to close now.  This may call `.close()` on the HTTP server, which will\n          // happen again below, but the second synchronous call to .close() will have no\n          // additional effect.  Leaving this as-is in case future versions of socket.io\n          // DON'T automatically close the http server for you.\n          if (sails.io && sails.io.httpServer && sails.hooks.http.server === sails.io.httpServer) {\n            sails.io.close();\n          }\n\n          // If the \"hard shutdown\" option is on, destroy the server immediately,\n          // severing all connections\n          if (options.hardShutdown) {\n            sails.hooks.http.destroy();\n          }\n          // Otherwise just stop the server from accepting new connections,\n          // and wait options.delay for the existing connections to close\n          // gracefully before destroying.\n          else {\n            timeOut = setTimeout(sails.hooks.http.destroy, options.delay);\n            sails.hooks.http.server.close();\n          }\n\n          // Wait for the existing connections to close\n          sails.hooks.http.server.once('close', function () {\n            sails.log.silly('HTTP server shut down successfully.');\n            clearTimeout(timeOut);\n            cb();\n          });\n\n        } catch (e) {\n          sails.log.verbose('Error occurred closing HTTP server: ', e);\n          clearTimeout(timeOut);\n          return cb();\n        }\n      },\n\n      function removeListeners(cb) {\n        // Manually remove all event listeners\n        _.each(_.keys(sails._events)||[], function (eventName){\n          sails.removeAllListeners(eventName);\n        });\n\n        var listeners = sails._processListeners;\n        if (listeners) {\n          process.removeListener('SIGUSR2', listeners.sigusr2);\n          process.removeListener('SIGINT', listeners.sigint);\n          process.removeListener('SIGTERM', listeners.sigterm);\n          process.removeListener('exit', listeners.exit);\n        }\n        sails._processListeners = null;\n\n        // If `sails.config.process.removeAllListeners` is set, do that.\n        // This is no longer necessary due to https://github.com/balderdashy/sails/pull/2693\n        // Deprecating for v0.12.\n        if (sails.config && sails.config.process && sails.config.process.removeAllListeners) {\n          sails.log.debug('sails.config.process.removeAllListeners is deprecated; please remove listeners individually!');\n          process.removeAllListeners();\n        }\n\n        cb();\n      },\n    ], function (err) {\n      if (err) {\n        // This should never happen because `err` is never passed in any of the async\n        // functions above.  Still, just to be safe, we set up an error log.\n        sails.log.error('While lowering Sails app: received unexpected error:', err.stack);\n        return cb(err);\n      }\n\n      return cb();\n\n    });//</async.series>\n\n  });//</beforeShutdown()>\n\n};\n"
  },
  {
    "path": "lib/app/private/after.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar async = require('async');\n\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n// FUTURE\n// Pull _most of this_ into a separate module, since it's not specific\n// to Sails, and has come up in a few different places.\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n\n/**\n * Mix-in an `after` function to an EventEmitter.\n *\n * If `events` have already fired, trigger fn immediately (with no args)\n * Otherwise bind a normal one-time event using `EventEmitter.prototype.once()`.\n * Useful for checking whether or not something has finished loading, etc.\n *\n * This is a lot like jQuery's `$(document).ready()`.\n *\n * @param  {EventEmitter} emitter The Sails application instance\n */\n\nmodule.exports = function mixinAfter(emitter) {\n\n\n  /**\n   * { emitter.warmEvents }\n   *\n   * Events which have occurred at least once\n   * (Required to support `emitter.after()`)\n   */\n  emitter.warmEvents = {};\n\n\n  var _originalEmit = emitter.emit;\n  /**\n   * emitter.emit()\n   *\n   * Synchronously calls each of the listeners registered for the event named eventName, in the order they were registered, passing the supplied arguments to each.\n   *\n   * Override `EventEmitter.prototype.emit` to keep track of all the events that have occurred once.\n   * (Required to support `emitter.after()`)\n   *\n   * @param {String} eventName [name of the event]\n   * @return {Boolean} Returns true if the event had listeners, false otherwise.\n   * @see https://nodejs.org/api/events.html#events_emitter_emit_eventname_args\n   */\n  emitter.emit = function(eventName) {\n    var args = Array.prototype.slice.call(arguments, 0);\n    emitter.warmEvents[eventName] = true;\n    return _originalEmit.apply(emitter, args);\n  };\n\n\n  /**\n   * `emitter.after()`\n   *\n   * Fires your handler **IF THE SPECIFIED EVENT HAS ALREADY BEEN TRIGGERED** or **WHEN IT IS TRIGGERED**.\n   *\n   * @param  {String|Array} events   [name of the event(s)]\n   * @param  {Function}     fn       [event handler function]\n   */\n\n  emitter.after = function(events, fn) {\n\n    // Support a single event or an array of events\n    if (!_.isArray(events)) {\n      events = [events];\n    }\n\n    // Convert named event dependencies into an array\n    // of async-compatible functions.\n    var dependencies = _.reduce(events, function (dependencies, event) {\n\n      // Push on the handler function.\n      dependencies.push(function handlerFn(cb) {\n\n        // If the event has already fired, then just execute our callback.\n        if (emitter.warmEvents[event]) {\n          return cb();\n        }\n        // But otherwise, bind a one-time-use handler that listens for the\n        // first time this event is fired, and then executes our callback\n        // once it does.\n        else {\n          emitter.once(event, function (){\n            return cb();\n          });\n        }\n\n      });//</declared and pushed on handler function>\n\n      return dependencies;\n\n    }, []);//</_.reduce() :: iterate over each event in order to build `dependencies` (an array of handler functions)>\n\n\n    // When all events have fired, call `fn`\n    // (all arguments passed to `emit()` calls are discarded)\n    async.parallel(dependencies, function(err) {\n      if (err) {\n        console.error('Consistency violation: Received `err`, but this should be impossible!  Here is the error: '+err.stack);\n        console.error('^^^If you are seeing this message, then please report this error at http://sailsjs.com/bugs.  (Continuing anyway...)');\n      }//>-\n\n      return fn();\n    });\n\n  };\n\n};\n"
  },
  {
    "path": "lib/app/private/bootstrap.js",
    "content": "/**\n * Module dependencies\n */\n\nvar STRIP_COMMENTS_RX = /(\\/\\/.*$)|(\\/\\*[\\s\\S]*?\\*\\/)|(\\s*=[^,\\)]*(('(?:\\\\'|[^'\\r\\n])*')|(\"(?:\\\\\"|[^\"\\r\\n])*\"))|(\\s*=[^,\\)]*))/mg;\n\n\n\n/**\n * runBootstrap()\n *\n * Run the configured bootstrap function.\n *\n * @this {SailsApp}\n *\n * @param  {Function} done [description]\n *\n * @api private\n */\n\nmodule.exports = function runBootstrap(done) {\n\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  // > FUTURE: Add tests that verify that the bootstrap function may\n  // > be disabled or set explicitly w/o running, depending on user\n  // > config. (This is almost certainly good to go already, just worth\n  // > an extra test since it was mentioned specifically way back in\n  // > https://github.com/balderdashy/sails/commit/926baaad92dba345db64c2ec9e17d35711dff5a3\n  // > and thus was a problem that came up when shuffling things around\n  // > w/ hook loading.)\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n  var sails = this;\n\n  // Run bootstrap script if specified\n  // Otherwise, do nothing and continue\n  if (!sails.config.bootstrap) {\n    return done();\n  }\n\n  sails.log.verbose('Running the setup logic in `sails.config.bootstrap(done)`...');\n\n  // If bootstrap takes too long, display warning message\n  // (just in case user forgot to call THEIR bootstrap's `done` callback, if\n  // they're using that approach)\n  var timeoutMs = sails.config.bootstrapTimeout || 30000;\n  var timer = setTimeout(function bootstrapTookTooLong() {\n    sails.log.warn(\n    'Bootstrap is taking a while to finish ('+timeoutMs+' milliseconds).\\n'+\n    'If this is unexpected, and *if* the bootstrap function uses a callback,\\n'+\n    'maybe double-check to be sure that callback is getting called.\\n'+\n    ' [?] Read more: https://sailsjs.com/config/bootstrap');\n  }, timeoutMs);\n\n  var ranBootstrapFn = false;\n  (function(proceed){\n    try {\n      var seemsToExpectCallback = true;\n      if (sails.config.implementationSniffingTactic === 'analogOrClassical') {\n        var hasParameters = (function(fn){\n          var fnStr = fn.toString().replace(STRIP_COMMENTS_RX, '');\n          var parametersAsString = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')'));\n          // console.log('::',parametersAsString, parametersAsString.replace(/\\s*/g,'').length);\n          return parametersAsString.replace(/\\s*/g,'').length !== 0;\n        })(sails.config.bootstrap);//†\n        seemsToExpectCallback = hasParameters;\n      }\n      if (sails.config.bootstrap.constructor.name === 'AsyncFunction') {\n        var promise;\n        if (seemsToExpectCallback) {\n          promise = sails.config.bootstrap(proceed);\n        } else {\n          promise = sails.config.bootstrap(function(unusedErr){\n            proceed(new Error('Unexpected attempt to invoke callback.  Since this \"bootstrap\" function does not appear to expect a callback parameter, this stub callback was provided instead.  Please either explicitly list the callback parameter among the arguments or change this code to no longer use a callback.'));\n          })\n          .then(function(){\n            proceed();\n          });\n        }//ﬁ\n        promise.catch(function(e) {\n          proceed(e);\n          // (Note that we don't do `return proceed(e)` here.  That's on purpose--\n          // to avoid sending the wrong idea to you, dear reader)\n        });\n      }\n      else {\n        if (seemsToExpectCallback) {\n          sails.config.bootstrap(proceed);\n        } else {\n          sails.config.bootstrap(function(unusedErr){\n            proceed(new Error('Unexpected attempt to invoke callback.  Since this \"bootstrap\" function does not appear to expect a callback parameter, this stub callback was provided instead.  Please either explicitly list the callback parameter among the arguments or change this code to no longer use a callback.'));\n          });\n          return proceed();\n        }\n      }\n    } catch (e) { return proceed(e); }\n  })(function (err){\n    if (ranBootstrapFn) {\n      if (err) {\n        sails.log.error('The bootstrap function encountered an error *AFTER* it already ran once!  Details:',err);\n      }\n      else {\n        sails.log.error('The bootstrap function (`sails.config.bootstrap`) signaled that it is finished, but it already ran once!  (*If* it is using a callback, check that the callback is not being called more than once.)');\n      }\n      return;\n    }//-•\n    ranBootstrapFn = true;\n    clearTimeout(timer);\n\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    // Note that async+await+bluebird+Node 8 errors are not necessarily \"true\" Error instances,\n    // as per _.isError() anyway (see https://github.com/node-machine/machine/commits/6b9d9590794e33307df1f7ba91e328dd236446a9).\n    // So if we want improve the stack trace here, we'd have to be a bit more relaxed and tolerate\n    // these sorts of \"errors\" directly as well (by tweezing out the `cause`, which is where the\n    // original Error lives.)\n    // FUTURE: try that out\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n    return done(err);\n\n  });//</ self-calling function >\n\n};\n"
  },
  {
    "path": "lib/app/private/checkGruntConfig.js",
    "content": "/**\n * Module dependencies\n */\nvar crypto = require('crypto');\nvar path = require('path');\nvar fs = require('fs');\nvar _ = require('@sailshq/lodash');\n\nvar GRUNT_FILE_HASHES = [\n  // v0.10.x - v0.12.x\n  'LC+ibPOKSr+pnfsUCCqCN6QobNQ=',\n  'Koim03Z2n89h3BxknLlFfO1rTMQ=',\n  '1gm6DioH6w1qsObN8Riopf98TLE=',\n  'GwpKmvwrsJNW83hN3DTWdo3cmgM=',\n  '/Fuc2veAiInWAYLHSFFG7CZb1fY=',\n  'sbGaQEjDsJUaoxBt75vU51k/XAQ=',\n  'nbNGVMWlWgGp+6w5/hpOctqb3Zg=',\n  '0mC0iC9vKOn1ZsZD3PIXRbiVpeE=',\n  'uvunqwrU4favfaTz+r+jj9HrCBo=',\n  'oyQisk4tEr2xmoL0agollwB47BQ=',\n  'CENhG5uQtyYchMfsSR1hrXip2n8=',\n  'r8sSfwQd2H5rvWHlOqGVJvVV2fM=',\n  'gU+eObj1p6i/fBxFMmnT71rD9nY=',\n  'px4a1ssydoV0oPaS9jUIQcJENQY=',\n  'VSBY7VlgZQFWjwZnqIuoGqSnUGE=',\n  'Wlt+xKwDUMonGwG7Nft9CTzELR0=',\n  'Z0E2pGVhvZ9W4xxnmwZ+fl5v4eM=',\n  'rwg3LZZpX4NCaKXyZrwLCXwA7do=',\n  'n0Rgeh72CLkgowuiEnD8kXN1BjU=',\n  '54FhWGvi0cGKWqPcC8lGZObeyDE=',\n  'lMUqRINbKUqt7dlYpUfFbbaKUec=',\n  '3c4GtS51hOeJgCrzwEnieoRQl+Y=',\n  'yQF6e3lJEQL1rfcMTzaQfYuHmiY=',\n  '73gW9Db+T+auwjCC1pKy2i5EuLM=',\n  'bi7qfWlEwCvkWNq2tBByU+UMlrM=',\n  'aNXJ8DfeOsAcwdZlDos85/STc1g=',\n  'eLMtNcLVGJj8Ybw3LS2bgHO/I2o=',\n  // v1.0\n  'H7L2crM/z2r0M0UiHqsagAoDsT0=',\n];\n\nmodule.exports = function(sails) {\n\n  return function(results, cb) {\n\n    // If the Grunt hook is explicitly turned off, don't worry about this check.\n    if (sails.config.hooks.grunt === false) {\n      return cb();\n    }\n\n    // Load this app's package.json and dependencies\n    var appPackageJSON;\n    try {\n      appPackageJSON = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), 'package.json')));\n    } catch (unusedErr) {\n      // If there's an error loading the package.json, just ignore it.\n      // This is indicative of a bigger issue that will likely be dealt with elsewhere,\n      // and is not the responsibility of the \"check for Grunt hook\" code.\n      return cb();\n    }\n\n    // If this app has sails-hook-grunt installed, then we're all good.\n    if (\n      (appPackageJSON.dependencies && appPackageJSON.dependencies['sails-hook-grunt']) ||\n      (appPackageJSON.devDependencies && appPackageJSON.devDependencies['sails-hook-grunt'])\n    ) {\n      return cb();\n    }\n\n    // Attempt to hash the contents of this app's Gruntfile.js (if it has one).\n    var hash = (function() {\n      try {\n        var Gruntfile = fs.readFileSync(path.resolve(process.cwd(), 'Gruntfile.js'));\n        return crypto.createHash('sha1').update(Gruntfile).digest('base64');\n      } catch (unusedErr) {\n        return null;\n      }\n    })();//ˆ\n\n    // If we didn't get a hash, it's probably because the Gruntfile doesn't exist.\n    // If that's the case, then there's nothing to worry about (Grunt won't run),\n    // and if there was some other error, that's weird but not something we need to\n    // deal with in this advisory warning code.\n    if (!hash) {\n      return cb();\n    }\n\n    // Check the hash against known Gruntfiles, and if it matches either, log the warning.\n    // Otherwise, it means the file's been customized, and we'll just trust that the user\n    // knows what they're doing.\n    if (_.contains(GRUNT_FILE_HASHES, hash)) {\n      sails.log.debug('Warning: Grunt functionality may not work properly with your current configuration.');\n      sails.log.debug('Run `npm install sails-hook-grunt --save` to continue using Grunt with your app.');\n      sails.log.debug();\n    }\n\n    return cb();\n\n  };\n\n};\n"
  },
  {
    "path": "lib/app/private/controller/README.md",
    "content": "# Actions in Sails\n\nIn Sails, an _action_ is a named request handler that is intended to be bound directly to a route in an app's `config/routes.js` file.  Actions may be loaded from disk (typically from the `api/controllers` project folder and subfolders), from runtime configuration (in `sails.config.controllers.moduleDefinitions`) or added by hooks (using `sails.registerAction`).\n\n##### Benefits of actions\n\nBy using actions to represent the majority of the code that is executed at runtime in user apps, we get the following benefits:\n\n* An easy way to reference all of the available request handlers -- calling `sails.getActions()` returns a list of all actions registered by the user _and_ by hooks like the blueprints hook.\n* Simplified user routing -- instead of hooks adding routes whose address must be configured separately (e.g. the `/csrfToken` route) or referenced using a separate syntax (e.g. the `{response: 'notFound'}` route target syntax), hooks simply provide an action for the app developer to route to in `config/routes.js` using a single, streamlined syntax.\n* Ability to easily add middleware that applies only to app-level code, and not to all routes.  Instead of binding to `/*` in a hook and then excluding assets (or forgetting to), `registerActionMiddleware` can be used to add handlers _only_ to actions.\n\n##### Should my hook register an action, bind a route, or both?\n\nIf you are creating a hook that adds request handlers to a Sails app, and you want the app developer to be able to bind their own routes to those request handlers, then you should register the handler as an action (the core \"blueprints\", \"responses\" and \"views\" hooks are good examples of this).  This doesn't mean you can't also bind routes directly within the hook (this is how blueprint RESTful routes are created, for example), especially if the user may override your hook's action with their own (as may happen with blueprint or response actions).\n\nOn the other hand, if your hook creates a request handler that is not intended to be bound directly by the app developer, and/or it is not intended to be overridden, you may not want to register that handler as an action.  Handlers that _modify_ requests and then call `next()` (for example the `addLocalizationMethod` handler in the core `i18n` hook) generally fall into this category.\n\nSomewhere in the middle lie hooks that create a request handler that you may want the app developer to be able to bind directly, but which should _not_ be overridable.  A good example of this is the core `CSRF` hook; besides adding CSRF protection to an app's routes, it also provides an action which returns the current CSRF token.  It is desirable to allow this action to be bound to a route in the `config/routes.js` file (so that the app developer can choose the address to bind it to), but it is _not_ desirable for the user to override the action with their own code.  In this case, a hook can register the action as \"not overridable\" by beginning namespacing it under `_.` (e.g. `sails.registerAction(myAction, '_.csrf.return-token')`).\n\n> Alternate idea: have hooks register actions under UPPERCASED version of their name, e.g. `CSRF.return-token`\n\n##### Action middleware\n\nAction middleware are functions that are intended to _modify_ actions (or more accurately, the requests that the actions handle).  You may register middleware that affects a single action, a subset of actions, or all actions.  Note that action middleware _only_ affects actions; if you need to modify _all_ requests (particularly, if you need to modify requests that return assets), you should still bind a route directly to `/*` in your hook.\n\n\n\n## Action-related methods in Sails\n\n### Public methods\n\n##### `sails.registerAction(action, identity)`\n\nRegisters an action in Sails.  It is very similar in function to the private `registerAction` method, with the notable exception that there is no `force` argument: if a call to `registerAction` results in an attempt to overwrite an existing key in the actions dictionary, the call will trigger an error with code `E_CONFLICT`.\n\n##### `sails.getActions()`\n\nReturns a shallow clone of the internal Sails actions dictionary.  This is a flat (i.e. one-level) dictionary where the keys are the kebab-cased, dash-delimited action identities, and the values are the action functions (all actions in the dictionary will have been converted to `req, res` functions at this point).\n\n##### `sails.registerActionMiddleware(middleware, includeActions, excludeActions)`\n\nRegisters middleware that will run before the specified actions.  Middleware should be `req, res, next` functions.  The `includeActions` and `excludeActions` arguments are strings or arrays of strings describing the actions that the middleware should (or should not) be attached to, using `*` as a wildcard to cover multiple actions at once.\n\nExamples:\n\n**Register middleware that affects all actions**:\n```\nsails.registerActionMiddleware(mustBeLoggedIn, '*')\n````\n\n**Register middleware that affects all `user` actions**:\n```\nsails.registerActionMiddleware(mustBeLoggedIn, 'user.*')\n````\n\n**Register middleware that affects all `user` and `pet` actions**:\n```\nsails.registerActionMiddleware(mustBeLoggedIn, ['user.*', 'pet.*'])\n````\n\n**Register middleware that affects all `user` and `pet` actions _except_ user.hello**:\n```\nsails.registerActionMiddleware(mustBeLoggedIn, ['user.*', 'pet.*'], 'user.hello')\n````\n\n**Register middleware that affects all `user` and `pet` actions _except_ ones namespaced under \"public\"**:\n```\nsails.registerActionMiddleware(mustBeLoggedIn, ['user.*', 'pet.*'], ['user.public.*', 'pet.public.*'])\n````\n\n\n\n### Private utilities\n\n> _Please do not use these in a hook (even a core hook) or in any userland code!  They may change at any time, without warning!_\n\n##### `loadActionModules()`\n\nWhen Sails loads, this method loads all of the files underneath the controllers directory (`api/controllers` by default) and attempts to parse them into actions that can be bound to routes.  File in the controllers directory can either be pascal-cased and ending in \"Controller\" (e.g. MyController.js), in which case they are expected to be _dictionaries_ of actions, or else kebab-cased and lowercased (e.g. my-action.js) in which case they are expected to contain a single action.  An action may be a function which accepts `req` and `res` as arguments, or a [node-machine](http://node-machine.org) definition which will be parsed by [machine-as-action](https://github.com/treelinehq/machine-as-action).\n\nAfter actions are loaded from disk, any actions specified under the `sails.config.controllers.moduleDefinitions` config key are merged on top of those actions.  This allows Sails apps to be constructed dynamically at runtime.\n\nNote that this method is called internally by Sails _after_ hooks have loaded (or in the case of a `Sails.reloadModules` call, after they have _reloaded_).  This ensures that user actions always take precedence over those added by hooks.\n\n##### `helpRegisterAction(action, identity, [force])`\n\nThis method takes an _action_ (in the form of a function or a machine definition, which is transformed into a function via `machine-as-action`) and adds it to the internal `Sails._actions` dictionary under the key specified by `identity`.  Keys in the internal dictionary can be overridden by setting the `force` argument to `true`; otherwise any conflict will result in an error being thrown.\n\n\n"
  },
  {
    "path": "lib/app/private/controller/help-register-action.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar assert = require('assert');\nvar _ = require('@sailshq/lodash');\nvar flaverr = require('flaverr');\nvar machineAsAction = require('machine-as-action');\n\n\n/**\n * helpRegisterAction()\n *\n * @param  {SailsApp} sails\n * @param  {Function|Dictionary} action   [either a req/res function, or an actions2 (i.e. machine-as-action) definition]\n * @param  {String} identity   [the identity to register this action as]\n * @param  {Boolean} force\n *\n * @throws {Error} If there is a conflicting, previously-registered action\n *         @property {String} code (==='E_CONFLICT')\n *         @property {String} identity  [the conflicting identity (always the same as what was passed in)]\n *\n * @throws {Error} If the action is invalid\n *         @property {String} code (==='E_INVALID')\n *         @property {String} identity  [the action identity (always the same as what was passed in)]\n *         @property {Error} origError  [the original (raw/underlying) error from `machine-as-action`]\n */\n\nmodule.exports = function helpRegisterAction(sails, action, identity, force) {\n\n  assert(_.isObject(sails) && _.isObject(sails._actions), new Error('Consistency violation: `sails` (a Sails app instance) should be passed in as the first argument.'));\n  assert(_.isFunction(action) || _.isObject(action), new Error('Consistency violation: `action` (2nd arg) should be provided as either a req/res/next function or a machine def (actions2), but instead, got: '+util.inspect(action,{depth:null})));\n  assert(_.isString(identity), new Error('Consistency violation: Identity should be provided as a string, but instead, got: '+util.inspect(identity,{depth:null})));\n\n  // Get a reference to the Sails private actions hash.\n  var actions = sails._actions;\n\n  // Make sure identity is lowercased.\n  identity = identity.toLowerCase();\n\n  // Identities should only have letters, numbers, dots, dashes and slashes.\n  var IS_VALID_ACTION_IDENTITY_RX = /^[a-z_\\$][a-z0-9-_.\\$]*(\\/[a-z_\\$][a-z0-9-_\\$.]*)*$/;\n  if (!identity.match(IS_VALID_ACTION_IDENTITY_RX)) {\n    throw flaverr({ name: 'userError', code: 'E_INVALID_ACTION_IDENTITY' }, new Error('Could not register action with invalid identity `' + identity + '`'));\n  }\n\n  // If we already registered an action with this identity, bail unless `force` is true.\n  if (actions[identity] && !force) {\n    throw flaverr({ name: 'userError', code: 'E_CONFLICT', identity: identity}, new Error('The action `' + identity + '` could not be registered because it conflicts with a previously-registered action.'));\n  }\n\n  // If the action is already a function, hope it's a req/res function\n  // and save it in our set of actions.\n  if (_.isFunction(action)) {\n    actions[identity] = action;\n\n  }\n  // Otherwise try to interpret it as an actions2 definition and build a Callable:\n  else {\n\n    try {\n      actions[identity] = machineAsAction(_.extend({\n        implementationSniffingTactic: sails.config.implementationSniffingTactic||undefined,\n      }, action));\n    }\n    catch (e) {\n      throw flaverr({ name: 'userError', code: 'E_INVALID', identity: identity, origError: e}, new Error('The action `' + identity + '` could not be registered.  It looks like a machine definition (actions2), but it could not be used to build an action.\\nDetails: '+e.stack));\n    }\n  }\n\n  // Set the _middlewareType, which is used when the log level is \"silly\" to\n  // identify what kind of a thing a route address is bound to.\n  actions[identity]._middlewareType = actions[identity]._middlewareType || 'ACTION: ' + identity;\n\n};\n"
  },
  {
    "path": "lib/app/private/controller/load-action-modules.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar path = require('path');\nvar _ = require('@sailshq/lodash');\nvar includeAll = require('include-all');\nvar flaverr = require('flaverr');\nvar helpRegisterAction = require('./help-register-action');\n\n\n/**\n * loadActionModules()\n *\n * @param  {SailsApp} sails\n * @param  {Function} cb\n */\nmodule.exports = function loadActionModules (sails, cb) {\n\n  sails.config.paths = sails.config.paths || {};\n  sails.config.paths.controllers = sails.config.paths.controllers || 'api/controllers';\n\n  // Keep track of actions loaded from disk, so we can detect conflicts.\n  var actionsLoadedFromDisk = {};\n\n  // Load all files under the controllers folder.\n  includeAll.optional({\n    dirname: sails.config.paths.controllers,\n    filter: /(^[^.]+\\.(?:(?!md|txt).)+$)/,\n    flatten: true,\n    keepDirectoryPath: true\n  }, function(err, files) {\n    if (err) { return cb(err); }\n\n    try {\n\n      // Set up a var to hold a list of invalid files.\n      var garbage = [];\n      // Traditional controllers are PascalCased and end with the word \"Controller\".\n      var traditionalRegex = new RegExp('^((?:(?:.*)/)*([0-9A-Z][0-9a-zA-Z_]*))Controller\\\\..+$');\n      // Actions are kebab-cased.\n      var actionRegex = new RegExp('^((?:(?:.*)/)*([a-z][a-z0-9-]*))\\\\..+$');\n\n\n      // Loop through all of the files returned from include-all.\n      _.each(files, function(moduleDef) {\n\n        // Get the original filepath of the action or controller.\n        var filePath = moduleDef.globalId;\n\n        // If the filepath starts with a dot, ignore it.\n        if (filePath[0] === '.') {return;}\n\n        // If the file is in a subdirectory, transform any dots in the subdirectory\n        // path into slashes.\n        if (path.dirname(filePath) !== '.') {\n          filePath = path.dirname(filePath).replace(/\\./g, '/') + '/' + path.basename(filePath);\n        }\n\n        // Declare a var to hold the eventual action identity.\n        var identity = '';\n\n        // Attempt to match the file path to the pattern of a traditional controller file.\n        var match = traditionalRegex.exec(filePath);\n\n        // Is it a traditional controller?\n        if (match) {\n\n          // If it looks like a traditional controller, but it's not a dictionary,\n          // throw it in the can.\n          if (!_.isObject(moduleDef) || _.isArray(moduleDef) || _.isFunction(moduleDef)) {\n            return garbage.push(filePath);\n          }\n\n          // Get the controller identity (e.g. /somefolder/somecontroller)\n          identity = match[1];\n\n          // Loop through each action in the controller file's dictionary.\n          _.each(moduleDef, function(action, actionName) {\n\n            // Ignore strings (this could be the \"identity\" property of a module).\n            if (_.isString(action)) {return;}\n\n            // Give the action name `_config` special treatement: just merge it into the blueprint\n            // config instead of trying to load it as an action.\n            if (actionName === '_config') {\n              if (sails.config.blueprints) {\n                sails.config.blueprints._controllers[identity.toLowerCase()] = action;\n              }\n              return;\n            }\n\n            // The action identity is the controller identity + the action name,\n            // with path separators transformed to dots.\n            // e.g. somefolder.somecontroller.dostuff\n            var actionIdentity = (identity + '/' + actionName).toLowerCase();\n\n            // If the action identity matches one we've already loaded from disk, bail.\n            if (actionsLoadedFromDisk[actionIdentity]) {\n              throw flaverr({ name: 'userError', code: 'E_CONFLICT', identity: actionIdentity}, new Error('The action `' + actionName + '` in `' + filePath + '` conflicts with a previously-loaded action.'));\n            }\n\n            // Attempt to load the action into our set of actions.\n            // Since the following code might throw E_CONFLICT errors, we'll inject a `try` block here\n            // to intercept them and wrap the Error.\n            try {\n              helpRegisterAction(sails, action, actionIdentity, true);\n            } catch (e) {\n              switch (e.code) {\n\n                case 'E_CONFLICT':\n                  // Improve error message with addtl contextual information about where this action came from.\n                  // (plus a slightly better stack trace)\n                  throw flaverr({\n                    name: 'userError', code: 'E_CONFLICT', identity: actionIdentity },\n                    new Error('Failed to register `' + actionName + '`, an action in the controller loaded from `'+filePath+'` because it conflicts with a previously-registered action.')\n                  );\n\n                default:\n                  throw e;\n              }\n            }//</catch>\n\n            // Flag that an action with the given identity was successfully loaded from disk.\n            actionsLoadedFromDisk[actionIdentity] = true;\n\n          });\n        } // </ is it a traditional controller? >\n\n        // Okay, it's not a traditional controller.  Is it an action?\n        // Attempt to match the file path to the pattern of an action file,\n        // and make sure it is either a function OR a dictionary containing\n        // a function as its `fn` property.\n        else if ((match = actionRegex.exec(filePath)) && (_.isFunction(moduleDef) || !_.isUndefined(moduleDef.machine) || !_.isUndefined(moduleDef.friendlyName) || _.isFunction(moduleDef.fn))) {\n\n          // The action identity is the same as the module identity\n          // e.g. somefolder/dostuff\n          var actionIdentity = match[1].toLowerCase();\n          if (actionsLoadedFromDisk[actionIdentity]) {\n            throw flaverr({ name: 'userError', code: 'E_CONFLICT', identity: actionIdentity }, new Error('The action `' + _.last(actionIdentity.split('/')) + '` in `' + filePath + '` conflicts with a previously-loaded action.'));\n          }\n\n          // Attempt to load the action into our set of actions.\n          // This may throw an error, which will be caught below.\n          try {\n            helpRegisterAction(sails, moduleDef, actionIdentity, true);\n          }\n          catch (e) {\n            switch (e.code) {\n\n              // Improve Error with addtl contextual information about where this action came from.\n              case 'E_CONFLICT':\n                throw flaverr({ name: 'userError', code: 'E_CONFLICT', identity: actionIdentity }, new Error(\n                  'Failed to register `' + _.last(actionIdentity.split('/')) + '`, an action loaded from `'+filePath+'` because it conflicts with a previously-registered action.'\n                ));\n\n              default:\n                throw e;\n            }\n          }//</catch>\n\n          // Flag that an action with the given identity was successfully loaded from disk.\n          actionsLoadedFromDisk[actionIdentity] = true;\n\n        } // </ is it an action?>\n\n        // Otherwise give up on this file, it's GARBAGE.\n        // No, no, it's probably a very nice file but it's\n        // no controller as far as we're concerned.\n        else {\n          garbage.push(filePath);\n        } // </ it is garbage>\n\n      }); // </each(file from includeAll)>\n\n\n      // Complain about garbage.\n      if (garbage.length) {\n        sails.log.warn('---------------------------------------------------------------------------');\n        sails.log.warn('Files in the `controllers` directory may be traditional controllers or \\n' +\n                     'action files.  Traditional controllers are dictionaries of actions, with \\n' +\n                     'pascal-cased filenames ending in \"Controller\" (e.g. MyGreatController.js).\\n' +\n                     'Action files are kebab-cased (e.g. do-stuff.js) and contain a single action.\\n'+\n                     'The following file'+(garbage.length > 1 ? 's were' : ' was')+' ignored for not meeting those criteria:');\n        _.each(garbage, function(filePath){sails.log.warn('- '+filePath);});\n        sails.log.warn('----------------------------------------------------------------------------\\n');\n      }\n\n      // (Shallow) merge stuff from sails.config.controllers.moduleDefinitions on top of any loaded files.\n      // Note that the third argument (force) to `helpRegisterAction` is `true`, so there's no danger\n      // of identity conflicts.  Actions defined in `moduleDefinitions` will override anything else.\n      _.each(_.get(sails, 'config.controllers.moduleDefinitions') || {}, function(action, actionIdentity) {\n        helpRegisterAction(sails, action, actionIdentity, true);\n      });\n\n    } catch (e) { return cb(e); }\n\n    // Get a list of the action identities.\n    var actionIdentities = _.keys(sails._actions);\n\n    // Flag indicating that warnings were raised (for formatting purposes).\n    var raisedWarnings = false;\n\n    // Now that we have all the actions loaded, loop through the registered action middleware\n    // and raise a warning about any that don't correspond to a registered action.\n    _.each(sails._actionMiddleware, function(fns, target) {\n      // Iterate over the list of action globs (e.g. 'foo', 'foo/bar', 'foo/bar/*', '!baz/boop') that a middleware is targeting.\n      _.each(_.map(target.split(','), _.trim), function(actionGlob) {\n        // Ignore * (it matches everything) and anything starting with '!'\n        // (doesn't matter if a middleware is NOT applied to a non-existent action).\n        if (actionGlob === '*' || actionGlob[0] === '!') { return; }\n        // If the glob doesn't contain a wildcard, and it exactly matches a known action identity, it's ok.\n        if (actionGlob.indexOf('*') === -1) {\n          if (actionIdentities.indexOf(actionGlob) > -1) { return; }\n        }\n        // Otherwise, if one of the known action identities would match against the glob, it's okay.\n        else {\n          var actionGlobWithoutWildcard = actionGlob.replace(/\\/\\*$/, '');\n          if (_.find(actionIdentities, function(actionIdentity) {\n            return actionIdentity.indexOf(actionGlobWithoutWildcard) === 0;\n          })) {\n            return;\n          }\n        }\n        // Otherwise, construct a warning using the _middlewareType properties (if available) of the middleware functions\n        // that were mapped to this action glob.\n        var warning = 'Action middleware ';\n        warning += (function(){\n          var fnDescs = _.reduce(fns, function(memo, fn) {\n            if (fn._middlewareType) { memo.push(fn._middlewareType); }\n            return memo;\n          }, []);\n          if (fnDescs.length) {\n            return '(' + fnDescs.join(', ') + ') ';\n          }\n          return '';\n        })();//†\n        warning += 'was bound to a target `' + actionGlob + '` that doesn\\'t match any registered actions.';\n        sails.log.warn(warning);\n        raisedWarnings = true;\n      });//∞\n    });//∞\n\n    // If we raised any warnings, add an extra line break afterwards.\n    if (raisedWarnings) {\n      console.log();\n    }\n\n    // All done.\n    return cb();\n\n  }); // </includeAll>\n\n};\n"
  },
  {
    "path": "lib/app/private/exposeGlobals.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\nvar flaverr = require('flaverr');\n\n\n/**\n * exposeGlobals()\n *\n * Expose certain global variables\n * (if config says so)\n *\n * @throws E_BAD_GLOBAL_CONFIG\n *\n * @this {SailsApp}\n * @api private\n */\n\nmodule.exports = function exposeGlobals() {\n  var sails = this;\n\n  // Implicit default for globals is `false`, to allow for intuitive programmatic\n  // usage of `sails.lift()`/`sails.load()` in automated tests, command-line scripts,\n  // scheduled jobs, etc.\n  //\n  // > Note that this is not the same as the boilerplate `config/globals.js` settings,\n  // > since the use of certain global variables is still the recommended approach for\n  // > the code you write in your Sails app's controller actions, etc.\n  if (_.isUndefined(sails.config.globals)) {\n    sails.config.globals = false;\n    return;\n  }\n\n  // If globals config is provided, it must be either `false` or a dictionary.\n  else if (sails.config.globals !== false && (!_.isObject(sails.config.globals) || _.isArray(sails.config.globals) || _.isFunction(sails.config.globals))) {\n    throw flaverr({ name: 'userError', code: 'E_BAD_GLOBAL_CONFIG' }, new Error('As of Sails v1, if `sails.config.globals` is defined, it must either be `false` or a dictionary (plain JavaScript object) or `false`.  But instead, got: '+util.inspect(sails.config.globals, {depth:null})+'\\n> Note: if no globals config is specified, Sails will now assume `false` (no globals).  This is to allow for more intuitive programmatic usage.\\nFor more info, see http://sailsjs.com/config/globals'));\n  }\n\n  // Globals explicitly disabled.\n  if (sails.config.globals === false) {\n    sails.log.verbose('No global variables will be exposed.');\n    return;\n  }\n\n  sails.log.verbose('Exposing global variables... (you can customize/disable this by modifying the properties in `sails.config.globals`.  Set it to `false` to disable all globals.)');\n\n  // `sails.config.globals._` must be false or an object.\n  // (it's probably a function with lots of extra properties, but to future-proof, we'll allow any type of object)\n  if (sails.config.globals._ !== false) {\n    if (!_.isObject(sails.config.globals._)) {\n      throw flaverr({ name: 'userError', code: 'E_BAD_GLOBAL_CONFIG' }, new Error('As of Sails v1, `sails.config.globals._` must be either `false` or a locally-installed version of Lodash (typically `require(\\'lodash\\')`).  For more info, see http://sailsjs.com/config/globals'));\n    }\n    global['_'] = sails.config.globals._;\n  }\n  // `sails.config.globals.async` must be false or an object.\n  // (it's probably a plain object aka dictionary, but to future-proof, we'll allow any type of object)\n  if (sails.config.globals.async !== false) {\n    if (!_.isObject(sails.config.globals.async)) {\n      throw flaverr({ name: 'userError', code: 'E_BAD_GLOBAL_CONFIG' }, new Error('As of Sails v1, `sails.config.globals.async` must be either `false` or a locally-installed version of `async` (typically `require(\\'async\\')`)  For more info, see http://sailsjs.com/config/globals'));\n    }\n    global['async'] = sails.config.globals.async;\n  }\n\n  // `sails.config.globals.sails` must be a boolean\n  if (sails.config.globals.sails !== false) {\n    if (sails.config.globals.sails !== true) {\n      throw flaverr({ name: 'userError', code: 'E_BAD_GLOBAL_CONFIG' }, new Error('As of Sails v1, `sails.config.globals.sails` must be either `true` or `false` (Tip: you may need to uncomment the `sails` setting in your `config/globals.js` file).  For more info, see http://sailsjs.com/config/globals'));\n    }\n    global['sails'] = sails;\n  }\n\n  // `sails.config.globals.models` must be a boolean.\n  // `orm` hook takes care of actually globalizing models and adapters (if enabled)\n  if (sails.config.globals.models !== false && sails.config.globals.models !== true) {\n    throw flaverr({ name: 'userError', code: 'E_BAD_GLOBAL_CONFIG' }, new Error('As of Sails v1, `sails.config.globals.models` must be either `true` or `false` (you may need to uncomment the `models` setting in your `config/globals.js` file).  For more info, see http://sailsjs.com/config/globals'));\n  }\n\n  // `services` hook takes care of globalizing services (if enabled)\n  // It does this by default for now, so that we don't have to document configuring\n  // services, which we're trying to phase out in favor of helpers.\n\n};\n"
  },
  {
    "path": "lib/app/private/initialize.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar async = require('async');\n\n\n\n\n/**\n * Sails.prototype.initialize()\n *\n * Start the Sails server\n * NOTE: sails.load() should be run first.\n *\n * @param {Function?} callback  [optional]\n *\n * @api private\n */\n\nmodule.exports = function initialize(cb) {\n\n  var sails = this;\n\n  // Callback is optional\n  cb = cb || function(err) {\n    if (err) { sails.log.error(err); }\n  };\n\n  // Indicate that server is starting\n  sails.log.verbose('Starting app at ' + sails.config.appPath + '...');\n\n  var listeners = {\n    sigusr2: function() {\n      sails.lower(function() {\n        process.kill(process.pid, 'SIGUSR2');\n      });\n    },\n    sigint: function() {\n      sails.lower(function (){\n        process.exit();\n      });\n    },\n    sigterm: function() {\n      sails.lower(function (){\n        process.exit();\n      });\n    },\n    exit: function() {\n      if (!sails._exiting) {\n        sails.lower();\n      }\n    }\n  };\n\n  // Add \"beforeShutdown\" events\n  process.once('SIGUSR2', listeners.sigusr2);\n\n  process.on('SIGINT', listeners.sigint);\n  process.on('SIGTERM', listeners.sigterm);\n  process.on('exit', listeners.exit);\n\n  sails._processListeners = listeners;\n\n  // Run the app bootstrap\n  sails.runBootstrap(function afterBootstrap(err) {\n    if (err) {\n      sails.log.error('Bootstrap encountered an error: (see below)');\n      return cb(err);\n    }\n\n    // Fire the `ready` event\n    // Since Express 4, the router is built in, so middlewares are divided between\n    // pre-route and post-route. The way to tell when to do the split is via the\n    // ready event\n    // More info in lib/hooks/http/initialize.js:378\n    sails.emit('ready');\n\n\n    // Now loop over each hook, and if it exposes a `handleLift` function, then run it.\n    // (this is used by attached servers, etc.)\n    if (!_.isObject(sails.hooks)) { return cb(new Error('Consistency violation: `sails.hooks` should be a dictionary.')); }\n    async.each(Object.keys(sails.hooks), function (hookName, next){\n      if (!_.isFunction(sails.hooks[hookName].handleLift)) {\n        return next();\n      }\n      return sails.hooks[hookName].handleLift(next);\n    }, function (err){\n      if (err) { return cb(err); }\n      return cb(null, sails);\n    });\n\n  });\n};\n"
  },
  {
    "path": "lib/app/private/inspect.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\n\n\n\n/**\n * Sails.prototype.inspect()\n *\n * The string that should be returned when this `Sails` instance\n * is passed to `util.inspect()` (i.e. when logged w/ `console.log()`)\n *\n * @return {String}\n */\n\nmodule.exports = function inspect () {\n  var sails = this;\n\n  return util.format('\\n'+\n  '  |>   %s', this.toString()) + '\\n' +\n  '\\\\___/  For help, see: http://sailsjs.com/documentation/concepts/'+\n  '\\n\\n' +\n  'Tip: Use `sails.config` to access your app\\'s runtime configuration.'+\n  '\\n\\n' +\n  util.format('%d Models:\\n', _(sails.models).toArray().value().length) +\n  _(sails.models).toArray().filter(function (it) {return !it.junctionTable;}).pluck('globalId').value() +\n  '\\n\\n' +\n  // util.format('%d Actions:\\n', Object.keys(sails.getActions()).length)+\n  // _(sails.getActions()).keys().map(function (it) {return _.camelCase(it.replace(/^.*(\\/[^\\/]+)$/, '$1'));}).value() +\n  // '\\n\\n' +\n  // util.format('%d Controllers:\\n', _(sails.controllers).toArray().value().length)+\n  // _(sails.controllers).toArray().pluck('globalId').map(function (it) {return it+'Controller';}).value() +\n  // '\\n\\n' +\n  // 'Routes:\\n'+\n  // _(sails.routes).toArray().filter(function (it) {return !it.junctionTable;}).pluck('globalId').map(function (it) {return it+'Controller';}).value() +\n  // '\\n\\n' +\n  util.format('%d Hooks:\\n', _(sails.hooks).toArray().value().length)+\n  _(sails.hooks).toArray().pluck('identity').value() +\n  '\\n' +\n  '';\n};\n"
  },
  {
    "path": "lib/app/private/isLocalSailsValid.js",
    "content": "/**\n * Module dependencies\n */\n\nvar fs = require('fs');\nvar path = require('path');\nvar semver = require('semver');\nvar CaptainsLog = require('captains-log');\nvar Err = require('../../../errors');\n\n\n// FUTURE: change the name of this to `isLocalSailsValidSync()`\n\n/**\n * Check if the specified installation of Sails is valid for the specified project.\n *\n * @param sailsPath\n * @param appPath\n */\n\nmodule.exports = function isLocalSailsValid(sailsPath, appPath) {\n\n  var sails = this;\n\n  var appPackageJSON;\n  var appDependencies;\n\n  // Has no package.json file\n  if (!fs.existsSync(appPath + '/package.json')) {\n    Err.warn.noPackageJSON();\n  }\n  else {\n    // Load this app's package.json and dependencies\n    try {\n      appPackageJSON = JSON.parse(fs.readFileSync(path.resolve(appPath, 'package.json'), 'utf8'));\n    } catch (unusedErr) {\n      Err.warn.notSailsApp();\n      return;\n    }\n\n    appDependencies = appPackageJSON.dependencies;\n\n\n    // Package.json exists, but doesn't list Sails as a dependency\n    if (!(appDependencies && appDependencies.sails)) {\n      Err.warn.notSailsApp();\n      return;\n    }\n\n  }\n\n  // Ensure the target Sails exists\n  if (!fs.existsSync(sailsPath)) {\n    return false;\n  }\n\n  // Read the package.json in the local installation of Sails\n  var sailsPackageJSON;\n  try {\n    sailsPackageJSON = JSON.parse(fs.readFileSync(path.resolve(sailsPath, 'package.json'), 'utf8'));\n  } catch (unusedErr) {\n    // Local Sails has a missing or corrupted package.json\n    Err.warn.badLocalDependency(sailsPath, appDependencies.sails);\n    return;\n  }\n\n  // Lookup sails dependency requirement in app's package.json\n  var requiredSailsVersion = appDependencies.sails;\n\n  // If you're using a `git://` sails dependency, you probably know\n  // what you're doing, but we'll let you know just in case.\n  var expectsGitVersion = requiredSailsVersion.match(/^git:\\/\\/.+/);\n  // FUTURE: expand this to check the various other permutations\n  // of extremely loose SVRs (e.g. Github dependencies, `*`, `>=0.0.0`, etc.)\n  if (expectsGitVersion) {\n    var log = sails.log ? sails.log : CaptainsLog();\n\n    log.blank();\n    log.debug('NOTE:');\n    log.debug('This app depends on an unreleased version of Sails:');\n    log.debug(requiredSailsVersion);\n    log.blank();\n  }\n\n  // Ignore `latest`, `beta` and `edge`\n  // (kind of like how we handle specified git:// deps)\n  var expectsLatest = requiredSailsVersion === 'latest';\n  // if (expectsLatest) {\n  //   // FUTURE: potentially log something here (need to test if it's annoying or not...)\n  // }\n  var expectsBeta = requiredSailsVersion === 'beta';\n  // if (expectsBeta) {\n  //   // FUTURE: potentially log something here (need to test if it's annoying or not...)\n  // }\n  var expectsEdge = requiredSailsVersion === 'edge';\n  // if (expectsEdge) {\n  //   // FUTURE: potentially log something here (need to test if it's annoying or not...)\n  // }\n\n  // Error out if it has the wrong version in its package.json\n  if (!expectsLatest && !expectsBeta && !expectsEdge && !expectsGitVersion) {\n\n    // Use semver for version comparison\n    if (!semver.satisfies(sailsPackageJSON.version, requiredSailsVersion)) {\n      Err.warn.incompatibleLocalSails(requiredSailsVersion, sailsPackageJSON.version);\n    }\n  }\n\n  // If we made it this far, the target Sails installation must be OK\n  return true;\n};\n"
  },
  {
    "path": "lib/app/private/isSailsAppSync.js",
    "content": "/**\n * Module dependencies\n */\n\nvar fs = require('fs');\nvar path = require('path');\n\n\n/**\n * Check if the specified appPath contains something that looks like a Sails app.\n *\n * @param {String} appPath\n */\n\nmodule.exports = function isSailsAppSync(appPath) {\n\n  // Has no package.json file\n  if (!fs.existsSync(path.join(appPath, 'package.json'))) {\n    return false;\n  }\n\n  // Package.json exists, but doesn't list Sails as a dependency\n  var appPackageJSON;\n  try {\n    appPackageJSON = JSON.parse(fs.readFileSync(path.resolve(appPath, 'package.json'), 'utf8'));\n  } catch (unusedErr) {\n    return false;\n  }\n  var appDependencies = appPackageJSON.dependencies;\n  if (!(appDependencies && appDependencies.sails)) {\n    return false;\n  }\n\n  return true;\n};\n"
  },
  {
    "path": "lib/app/private/loadHooks.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\nvar async = require('async');\nvar defaultsDeep = require('merge-defaults');// « TODO: Get rid of this\nvar __hooks = require('../../hooks');\n\n\n\n\n/**\n * @param  {SailsApp} sails\n * @returns {Function}\n */\nmodule.exports = function(sails) {\n\n  var Hook = __hooks(sails);\n\n  // Keep an array of all the hook timeouts.\n  // This way if a hook fails to load, we can clear all the timeouts at once.\n  var hookTimeouts = [];\n  // NOTE: There's no particular reason this (^^) is outside of the function being returned below.\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  // FUTURE: pull it in below to avoid leading to any incorrect assumptions about race conditions, etc.)\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n  /**\n   * Resolve the hook definitions and then finish loading them\n   *\n   * @api private\n   */\n  return function initializeHooks(hooks, cb) {\n\n\n    // ==============================================================================\n    // < inline function declarations >\n    //    ██╗    ██╗███╗   ██╗██╗     ██╗███╗   ██╗███████╗    ███████╗███╗   ██╗    ██████╗ ███████╗███████╗███████╗    ██╗\n    //   ██╔╝    ██║████╗  ██║██║     ██║████╗  ██║██╔════╝    ██╔════╝████╗  ██║    ██╔══██╗██╔════╝██╔════╝██╔════╝    ╚██╗\n    //  ██╔╝     ██║██╔██╗ ██║██║     ██║██╔██╗ ██║█████╗      █████╗  ██╔██╗ ██║    ██║  ██║█████╗  █████╗  ███████╗     ╚██╗\n    //  ╚██╗     ██║██║╚██╗██║██║     ██║██║╚██╗██║██╔══╝      ██╔══╝  ██║╚██╗██║    ██║  ██║██╔══╝  ██╔══╝  ╚════██║     ██╔╝\n    //   ╚██╗    ██║██║ ╚████║███████╗██║██║ ╚████║███████╗    ██║     ██║ ╚████║    ██████╔╝███████╗██║     ███████║    ██╔╝\n    //    ╚═╝    ╚═╝╚═╝  ╚═══╝╚══════╝╚═╝╚═╝  ╚═══╝╚══════╝    ╚═╝     ╚═╝  ╚═══╝    ╚═════╝ ╚══════╝╚═╝     ╚══════╝    ╚═╝\n    //\n\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    // FUTURE: extrapolate the following three inline function definitions\n    // into separate files.\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n    /**\n     * FUTURE: extrapolate\n     * @param  {[type]} id [description]\n     * @return {[type]}    [description]\n     */\n    function prepareHook(id) {\n\n      var rawHookFn = hooks[id];\n\n      // Backwards compatibility:\n      if (rawHookFn === 'false') {\n\n        // FUTURE: Do not allow the string \"false\" here (now that all environment variables\n        // are handled via rttc.parseHuman, this is no longer necessary)\n        sails.log.debug('The string \"false\" was configured for `sails.config.hooks[\\''+id+'\\']`.');\n        sails.log.debug('For compatibility\\'s sake, automatically changing this to `false` (boolean).');\n        sails.log.debug('(Note that this backwards-compatibility check will be removed in a future');\n        sails.log.debug('release of Sails, so be sure to update this app ASAP.)');\n        rawHookFn = false;\n\n      }//>-\n\n\n      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n      // COMPATIBILITY NOTE:\n      // There used to be a check here, to the effect of this:\n      // ```\n      // // Check if this hook has a dot in the name.\n      // // If so, something is wrong.\n      // var doesHookHaveDotInName = !!id.match(/\\./);\n      // if (doesHookHaveDotInName) {\n      // var partBeforeDot = id.split('.')[0];\n      // hooks[partBeforeDot] = false;\n      // ```\n      //\n      // But it was removed in Sails v1, since it was no longer relevant.\n      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n\n      // Allow disabling of hooks by setting them to `false`.\n      if (rawHookFn === false) {\n        delete hooks[id];\n        return;\n      }\n\n      // Check for invalid hook config\n      if (hooks.userconfig && !hooks.moduleloader) {\n        return cb('Invalid configuration:: Cannot use the `userconfig` hook w/o the `moduleloader` hook enabled!');\n      }\n\n      // Handle folder-defined modules (default to index.js)\n      // Since a hook definition must be a function\n      if (_.isObject(rawHookFn) && !_.isArray(rawHookFn) && !_.isFunction(rawHookFn)) {\n        rawHookFn = rawHookFn.index;\n      }\n\n      if (!_.isFunction(rawHookFn)) {\n        sails.log.error('Malformed hook! (' + id + ')');\n        sails.log.error('Hooks should be a function with one argument (`sails`)');\n        sails.log.error('But instead, got:', rawHookFn);\n        process.exit(1);\n      }\n\n      // Instantiate the hook\n      var def = rawHookFn(sails);\n\n      // Mix in an `identity` property to hook definition\n      def.identity = id.toLowerCase();\n\n      // If a config key was defined for this hook when it was loaded,\n      // (probably because a user is overridding the default config key)\n      // set it on the hook definition\n      def.configKey = rawHookFn.configKey || def.identity;\n\n      // New up an actual Hook instance\n      hooks[id] = new Hook(def);\n    }//ƒ\n\n\n    /**\n     * Apply a hook's \"defaults\" property\n     *\n     * FUTURE: extrapolate\n     *\n     * @param  {[type]} hook [description]\n     * @return {[type]}      [description]\n     */\n    function applyDefaults(hook) {\n\n      // Get the hook defaults\n      var defaults = (_.isFunction(hook.defaults) ?\n                            hook.defaults(sails.config) :\n                            hook.defaults) || {};\n\n      // Replace the special __configKey__ key with the actual config key\n      if (hook.defaults.__configKey__ && hook.configKey) {\n        hook.defaults[hook.configKey] = hook.defaults.__configKey__;\n        delete hook.defaults.__configKey__;\n      }\n\n      defaultsDeep(sails.config, defaults);\n    }//ƒ\n\n    /**\n     * Load a hook (bind its routes, load any modules and initialize it)\n     *\n     * FUTURE: extrapolate\n     *\n     * @param  {[type]}   id [description]\n     * @param  {Function} cb [description]\n     * @return {[type]}      [description]\n     */\n    function loadHook(id, cb) {\n      // TODO: refactor this^^^\n      //  (no need for an inline function declaration)\n\n      // Validate `hookTimeout` setting, if present.\n      if (!_.isUndefined(sails.config.hookTimeout)) {\n        if (!_.isNumber(sails.config.hookTimeout) || sails.config.hookTimeout < 1 || Math.floor(sails.config.hookTimeout) !== sails.config.hookTimeout) {\n          return cb(new Error('Invalid `hookTimeout` config!  If set, this should be a positive whole number, but instead got `'+sails.config.hookTimeout+'`.  Please change this setting, then try lifting again.'));\n        }\n      }\n\n      var timestampBeforeLoad = Date.now();\n      var DEFAULT_HOOK_TIMEOUT = 40000;\n      var timeoutInterval = (sails.config[hooks[id].configKey || id] && sails.config[hooks[id].configKey || id]._hookTimeout) || sails.config.hookTimeout || DEFAULT_HOOK_TIMEOUT;\n\n      var hookTimeout;\n      if (id !== 'userhooks') {\n        hookTimeout = setTimeout(function tooLong() {\n          // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n          // FUTURE: sniff hook id here to improve error msg, e.g.:\n          // ```\n          // ((id === 'grunt') ? 'It looks like Grunt is still compiling your assets.' : '...')\n          // ```\n          // ^^But note that this would require a bit more work: currently, the id here isn't\n          // necessarily the hook that timed out.  (It could be a dependent hook.)\n          // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n          var err = new Error(\n            'Sails is taking too long to load.\\n'+\n            '\\n'+\n            '--  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --\\n'+\n            ' Troubleshooting tips:\\n'+\n            '  -• Were you still reading/responding to an interactive prompt?\\n'+\n            '     (Whoops, sorry!  Please lift again and try to respond a bit more quickly.)\\n'+\n            '\\n'+\n            '  -• Do you have a lot of stuff in `assets/`?  Grunt might still be running.\\n'+\n            '    (Try increasing the hook timeout.  Currently it is '+(sails.config.hookTimeout||DEFAULT_HOOK_TIMEOUT)+'.\\n'+\n            '     e.g. `sails lift --hookTimeout='+(Math.max(DEFAULT_HOOK_TIMEOUT, 2*(sails.config.hookTimeout||DEFAULT_HOOK_TIMEOUT)))+'`)\\n'+\n            '\\n'+\n            '  -• Is `'+id+'` a custom or 3rd party hook?\\n'+\n            '    (*If* `initialize()` is using a callback, make sure it\\'s being called.)\\n'+\n            '--  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --\\n'\n          );\n          err.code = 'E_HOOK_TIMEOUT';\n          cb(err);\n        }, timeoutInterval);\n        hookTimeouts.push(hookTimeout);\n      }\n      hooks[id].load(function(err) {\n\n        // Sanity check: (see https://trello.com/c/1jCljHHP for an example of a\n        // potential bug this catches)\n        if (!process.nextTick) {\n          return cb(new Error('Consistency violation: Hmm... it looks like something is wrong with Node\\'s `process` global.  Check it out:\\n'+util.inspect(process)));\n        }\n\n        if (id !== 'userhooks') {\n          clearTimeout(hookTimeout);\n        }\n        if (err) {\n          // Clear all hook timeouts so that the process doesn't hang because\n          // something is waiting for this failed hook to load.\n          _.each(hookTimeouts, function(hookTimeout) {clearTimeout(hookTimeout);});\n          if (id !== 'userhooks') {\n            sails.log.error('A hook (`' + id + '`) failed to load!');\n          }\n          sails.emit('hook:' + id + ':error');\n\n          // Defer a tick to allow other stuff to happen\n          process.nextTick(function(){ cb(err); });\n          return;\n        }\n\n        sails.log.verbose(id, 'hook loaded successfully. ('+(Date.now() - timestampBeforeLoad)+'ms)');\n        sails.emit('hook:' + id + ':loaded');\n\n        // Defer a tick to allow other stuff to happen\n        process.nextTick(function(){ cb(); });\n      });\n    }//ƒ\n\n    //    ██╗    ██╗    ██╗███╗   ██╗██╗     ██╗███╗   ██╗███████╗    ███████╗███╗   ██╗    ██████╗ ███████╗███████╗███████╗    ██╗\n    //   ██╔╝   ██╔╝    ██║████╗  ██║██║     ██║████╗  ██║██╔════╝    ██╔════╝████╗  ██║    ██╔══██╗██╔════╝██╔════╝██╔════╝    ╚██╗\n    //  ██╔╝   ██╔╝     ██║██╔██╗ ██║██║     ██║██╔██╗ ██║█████╗      █████╗  ██╔██╗ ██║    ██║  ██║█████╗  █████╗  ███████╗     ╚██╗\n    //  ╚██╗  ██╔╝      ██║██║╚██╗██║██║     ██║██║╚██╗██║██╔══╝      ██╔══╝  ██║╚██╗██║    ██║  ██║██╔══╝  ██╔══╝  ╚════██║     ██╔╝\n    //   ╚██╗██╔╝       ██║██║ ╚████║███████╗██║██║ ╚████║███████╗    ██║     ██║ ╚████║    ██████╔╝███████╗██║     ███████║    ██╔╝\n    //    ╚═╝╚═╝        ╚═╝╚═╝  ╚═══╝╚══════╝╚═╝╚═╝  ╚═══╝╚══════╝    ╚═╝     ╚═╝  ╚═══╝    ╚═════╝ ╚══════╝╚═╝     ╚══════╝    ╚═╝\n    //\n    // </ inline function declarations (see note above) >\n    // ==============================================================================\n\n\n    // Now do a few things, one after another.\n    async.series(\n      {\n\n        // First load the moduleloader (if any)\n        moduleloader: function(cb) {\n          if (!hooks.moduleloader) {\n            return cb();\n          }\n          prepareHook('moduleloader');\n          applyDefaults(hooks['moduleloader']);\n          hooks['moduleloader'].configure();\n          loadHook('moduleloader', cb);\n        },\n\n        // Next load the user config (if any)\n        userconfig: function(cb) {\n          if (!hooks.userconfig) {\n            return cb();\n          }\n          prepareHook('userconfig');\n          applyDefaults(hooks['userconfig']);\n          hooks['userconfig'].configure();\n          loadHook('userconfig', cb);\n        },\n\n        // Next get the user hooks (if any), which will be\n        // added to the list of hooks to load\n        userhooks: function(cb) {\n          if (!hooks.userhooks) {\n            return cb();\n          }\n          prepareHook('userhooks');\n          applyDefaults(hooks['userhooks']);\n          hooks['userhooks'].configure();\n          loadHook('userhooks', cb);\n        },\n\n        validate: function(cb) {\n          if (hooks.controllers) {\n            sails.log.debug('=================================================================================');\n            sails.log.debug('Ignoring `controllers` hook:');\n            sails.log.debug('As of Sails v1, `controllers` can no longer be disabled/enabled as hooks.');\n            sails.log.debug('Instead, Sails core now understands controller actions as first-class citizens.');\n            sails.log.debug('See the Sails v1.0 upgrade guide: http://sailsjs.com/upgrading');\n            sails.log.debug('=================================================================================');\n            delete hooks.controllers;\n          }\n          return cb();\n        },\n\n        // Prepare all other hooks\n        prepare: function(cb) {\n          async.each(_.without(_.keys(hooks), 'userconfig', 'moduleloader', 'userhooks'), function (id, cb) {\n            prepareHook(id);\n            // Defer to next tick to allow other stuff to happen\n            process.nextTick(cb);\n          }, cb);\n        },\n\n        // Apply the default config for all other hooks\n        defaults: function(cb) {\n          async.each(_.without(_.keys(hooks), 'userconfig', 'moduleloader', 'userhooks'), function (id, cb) {\n            var hook = hooks[id];\n            applyDefaults(hook);\n            // Defer to next tick to allow other stuff to happen\n            process.nextTick(cb);\n          }, cb);\n        },\n\n        // Run configuration method for all other hooks\n        configure: function(cb) {\n          async.each(_.without(_.keys(hooks), 'userconfig', 'moduleloader', 'userhooks'), function (id, cb) {\n            var hook = hooks[id];\n            try {\n              hook.configure();\n            } catch (err) {\n              return process.nextTick(function(){ cb(err); });\n            }\n            // Defer to next tick to allow other stuff to happen\n            process.nextTick(cb);\n          }, cb);\n        },\n\n        // Load all other hooks\n        load: function(cb) {\n          async.each(_.without(_.keys(hooks), 'userconfig', 'moduleloader', 'userhooks'), function (id, cb) {\n            sails.log.silly('Loading hook: ' + id);\n            loadHook(id, cb);\n          }, cb);\n        }\n      },\n\n      function afterwards(err) {\n        if (err) { return cb(err); }\n        return cb();\n      }\n    );//</async.series>\n  };\n};\n"
  },
  {
    "path": "lib/app/private/toJSON.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\n\n\n/**\n * SailsApp.prototype.toJSON()\n *\n * Get a JSON-serializable representation of the current Sails app.\n *\n * @this {SailsApp}\n * @returns {JSON} [a JSON-compatible summary of this Sails app]\n */\n\nmodule.exports = function toJSON() {\n\n  // `this` refers to our Sails app instance.\n  //\n  // > Here we set up a local variable, `sails`.  This is for familiarity,\n  // > so that we don't accidentally write code in this file that relies on\n  // > access to the Sails global.\n  var sails = this;\n\n\n  // Build JSON serializable dictionary that summarizes the Sails app instance.\n  var sailsAppSummary;\n  try {\n\n    sailsAppSummary = _.reduce(sails, function (_jsonSerializable, val, key) {\n\n      // Allow `config` to go straight through as-is.\n      // > (non JSON-serializable things will have to be handled later --\n      // > we don't want to introduce the slowness of an rttc.dehydrate() here)\n      if (key === 'config') {\n        _jsonSerializable[key] = val;\n      }\n      //‡\n      // Turn `hooks` into an array of hook identities.\n      else if (key === 'hooks') {\n        _jsonSerializable.hooks = _.reduce(val, function (memo, hook, hookName) {\n          memo.push(hookName);\n          return memo;\n        }, []);\n      }\n      //‡\n      // Turn `models` into an array of \"model summary\" dictionaries.\n      else if (key === 'models') {\n        _jsonSerializable[key] = _.reduce(val, function (memo, Model) {\n\n          // Skip virtual models (i.e. junctions)\n          if (Model.junctionTable) { return memo; }\n\n          // But otherwise, push on a stripped down version of the model.\n          // > (again, any nested, non-JSON-serializable things will have to be handled\n          // > later -- we don't want to introduce the slowness of an rttc.dehydrate() here)\n          memo.push({\n            identity: Model.identity,\n            globalId: Model.globalId,\n            datastore: Model.datastore,\n            tableName: Model.tableName,\n            hasSchema: Model.hasSchema,\n            primaryKey: Model.primaryKey,\n            attributes: Model.attributes,\n          });\n\n          return memo;\n\n        }, []);\n      }\n      //‡\n      // Otherwise, this is some other key on the Sails app instance.\n      else {\n        // (So, we'll just ignore it, omitting it from this JSON-serializable value we're building.)\n      }//>-\n\n      return _jsonSerializable;\n\n    }, {});//</_.reduce :: sailsAppSummary>\n\n\n  } catch (e) {\n    throw new Error('Consistency violation: Unexpected error when attempting to build a JSON-serializable version of the Sails app instance.  Details: '+e.stack);\n  }\n\n  // Return our JSON serializable summary of this Sails app instance.\n  return sailsAppSummary;\n\n};\n"
  },
  {
    "path": "lib/app/private/toString.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\n\n\n/**\n * Sails.prototype.toString()\n *\n * e.g.\n * ('This is how `sails` looks when toString()ed: ' + sails)\n *\n * @returns {String}\n */\nmodule.exports = function toString () {\n  return util.format('[a %sSails app%s]', this.isLifted ? 'lifted ' : '', this.isLifted && this.config.port ? ' on port '+this.config.port : '');\n};\n"
  },
  {
    "path": "lib/app/register-action-middleware.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar flaverr = require('flaverr');\n\n\n/**\n * Sails.prototype.registerActionMiddleware()\n *\n * Register an action middleware with Sails.\n *\n * > Action middleware runs before the action or actions specified by the `actionsGlobKey`.\n *\n * -------------------------------------------------------------------------------------------\n * @param {Function|Array} middleware\n *        The `(req,res,next)` function to register, or an array of such functions.\n *\n * @param {String} actionsGlobKey\n *        A special, limited glob expression that indicates the action or actions that\n *        this action middleware should apply to.  Use * at the end for a wildcard;\n *        e.g. `user/*` will apply to any actions whose identities begin with `user/`.\n *        Use a ! at the beginning to indicate that the action middleware should NOT\n *        apply to the actions specified by the glob, e.g. `!user/foo` or `!user/*`.\n *\n * @context {SailsApp}\n *\n * @api public\n */\n\nmodule.exports = function registerActionMiddleware(middleware, actionsGlobKey) {\n\n  var sails = this;\n\n  // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --\n  // FUTURE: explore how we might extend machine-as-action or implement\n  // something entirely new (e.g. `machine-as-middleware`) that is kinda\n  // like machine-as-action, but where the success response calls `next`)\n  // This would be so that  machine defs can be registered as middleware?\n  // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --\n\n  if (!_.isArray(middleware)) {\n    middleware = [middleware];\n  }\n\n  if (!_.all(middleware, _.isFunction)) {\n    throw flaverr({ name: 'userError', code: 'E_NON_FN_POLICY' }, new Error('Attempted to register action middleware(s) (aka policies) for `' + actionsGlobKey + '` but one or more provided action middlewares (policies) was not a function.'));\n  }\n\n  // Get or create the array for this glob key.\n  var existingActionMiddlewareRegisteredForGlobKey = sails._actionMiddleware[actionsGlobKey] || [];\n\n  // Add these middlewares to the array.\n  existingActionMiddlewareRegisteredForGlobKey = existingActionMiddlewareRegisteredForGlobKey.concat(middleware);\n\n  // Assign the array back to our `_actionMiddleware` dictionary.\n  sails._actionMiddleware[actionsGlobKey] = existingActionMiddlewareRegisteredForGlobKey;\n\n};\n"
  },
  {
    "path": "lib/app/register-action.js",
    "content": "/**\n * Module dependencies\n */\n\nvar helpRegisterAction = require('./private/controller/help-register-action');\n\n\n/**\n * Sails.prototype.registerAction()\n *\n * Register an action with Sails.\n *\n * Registered actions may be subsequently bound to routes.\n * This method will throw an error if an action with the specified\n * identity has already been registered.\n *\n * @param {Function|Dictionary} action  [The action to register]\n * @param {String} identity [The identity of the action]\n *\n * @context {SailsApp}\n *\n * @throws {Error} If there is a conflicting, previously-registered action, and `force` is not true\n *         @property {String} code (==='E_CONFLICT')\n *         @property {String} identity  [the conflicting identity (always the same as what was passed in)]\n *\n * @throws {Error} If the action is invalid\n *         @property {String} code (==='E_INVALID')\n *         @property {String} identity  [the action identity (always the same as what was passed in)]\n *         @property {Error} origError  [the original (raw/underlying) error from `machine-as-action`]\n *\n * @api public\n */\nmodule.exports = function registerAction(action, identity, force) {\n\n  var sails = this;\n\n  // Call the private `helpRegisterAction` method.\n  helpRegisterAction(sails, action, identity, force);\n\n};\n"
  },
  {
    "path": "lib/app/reload-actions.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar _ = require('@sailshq/lodash');\nvar async = require('async');\nvar loadActionModules = require('./private/controller/load-action-modules');\n\n\n/**\n * Sails.prototype.reloadActions()\n *\n * Reload actions for any hook that has a `registerActions` method.\n *\n * @param {Dictionary} options\n *        @property {Array} skipHooks [an Array of identities of hooks to _not_ reload actions for.]\n *\n */\nmodule.exports = function reloadActions(options, cb) {\n\n  var sails = this;\n\n  // Allow for options to be left out.\n  if (_.isFunction(options)) {\n    cb = options;\n    options = {};\n  }\n  // Default options to an empty dictionary.\n  else if (!_.isObject(options)) {\n    options = {};\n  }\n\n  // Default `hooksToSkip` to an empty array.\n  var hooksToSkip = options.hooksToSkip || [];\n\n  // The list of hooks we want to reload is the list of all hooks minus the hooks to skip.\n  var hooksToReload = _.difference(_.keys(sails.hooks), hooksToSkip);\n\n  // Clear the actions dictionary.\n  sails._actions = {};\n\n  // Reload the actions.\n  async.each(hooksToReload, function(hookIdentity, next) {\n    if (_.isFunction(sails.hooks[hookIdentity].registerActions)) {\n      sails.hooks[hookIdentity].registerActions(next);\n    } else {\n      return next();\n    }\n  }, function doneReloadingActions(err) {\n    if (err) {return cb(err);}\n    // Reload the controller actions.\n    loadActionModules(sails, cb);\n  });\n\n\n};\n"
  },
  {
    "path": "lib/app/request.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\nvar Transform = require('stream').Transform;\nvar QS = require('querystring');\nvar detectVerb = require('../util/detect-verb');\n\n\n/**\n * Originate a new client request instance and lob it at this Sails\n * app at the specified route `address`.\n *\n * Particularly useful for running unit/integration tests without\n * actually having to bind the HTTP and/or WebSocket servers to\n * a TCP port.\n *\n * @param  {String} address\n * @param  {Object} body\n * @param  {Function} cb\n * @return {Stream.Readable}\n *\n * @api public\n */\n\nmodule.exports = function request( /* address, body, cb */ ) {\n\n  var sails = this;\n\n  //\n  // Body params may be passed in to DELETE, HEAD, and GET requests,\n  // even though these types of requests don't normally contain a body.\n  // (this method just serializes them as if they were sent in the querystring)\n  //\n\n\n  // Normalize usage\n  var address = arguments[0];\n  var body;\n  var cb;\n\n  var method;\n  var headers;\n  var url;\n\n  // Usage:\n  // sails.request(opts, cb)\n  // • opts.url\n  // • opts.method\n  // • opts.params\n  // • opts.headers\n  //\n  // (`opts.url` is required)\n  if (_.isObject(arguments[0]) && arguments[0].url) {\n    url = detectVerb(arguments[0].url).original;\n    method = arguments[0].method || detectVerb(arguments[0].url).verb;\n    headers = arguments[0].headers || {};\n    body = arguments[0].params || arguments[0].data || {};\n  }\n  // console.log('called sails.request() ');\n  // console.log('headers: ',headers);\n  // console.log('method: ',method);\n\n\n  // Usage:\n  // sails.request(address, [params], cb)\n  if (arguments[2]) {\n    cb = arguments[2];\n    body = arguments[1];\n  }\n  if (_.isFunction(arguments[1])) {\n    cb = arguments[1];\n  } else if (arguments[1]) {\n    body = arguments[1];\n  }\n\n  // If route has an HTTP verb (e.g. `get /foo/bar`, `put /bar/foo`, etc.) parse it out,\n  // (unless method or url was explicitly defined)\n  method = method || detectVerb(address).verb;\n  method = method ? method.toUpperCase() : 'GET';\n  url = url || detectVerb(address).original;\n\n  // Parse query string (`req.query`)\n  var queryStringPos = url.indexOf('?');\n\n  // If this is a GET, HEAD, or DELETE request, treat the \"body\"\n  // as parameters which should be serialized into the querystring.\n  if (_.isObject(body) && _.contains(['GET', 'HEAD', 'DELETE'], method)) {\n\n    var stringifiedParams = QS.stringify(body);\n\n    if (queryStringPos === -1) {\n      url += '?' + stringifiedParams;\n    } else {\n      url = url.substring(0, queryStringPos) + '?' + stringifiedParams;\n    }\n  }\n\n\n\n  // Build HTTP Client Response stream\n  var clientRes = new MockClientResponse();\n  clientRes.on('finish', function() {\n\n    // console.log('clientRes finished. Headers:',clientRes.headers);\n\n    // Only dump the buffer if a callback was supplied\n    if (cb) {\n\n      // Attempt to read the response buffer into a string\n      try {\n        clientRes.body = clientRes.read();\n        clientRes.body = clientRes.body.toString();\n      } catch (unusedErr) {}\n\n      // Don't include body if it is empty.\n      if (!clientRes.body) {delete clientRes.body;}\n\n      // Now, if appropriate, parse the body as JSON.\n      // (Attempt to parse as JSON if the content-type response header indicates it\n      // would be a good idea -- and of course if there's a body.)\n      if (!_.isUndefined(clientRes.body) && clientRes.headers['content-type'] === 'application/json') {\n        clientRes.body = JSON.parse(clientRes.body);\n      }\n\n      // If status code is indicative of an error, send the\n      // response body or status code as the first error argument.\n      if (clientRes.statusCode < 200 || clientRes.statusCode >= 400) {\n        var error = new Error(util.inspect(clientRes.body || clientRes.statusCode));\n        if (clientRes.body) {error.body = clientRes.body;}\n        error.status = clientRes.statusCode;\n        return cb(error);\n      }\n      else {\n        return cb(undefined, clientRes, clientRes.body);\n      }\n    }\n  });\n  clientRes.on('error', function(err) {\n    err = err || new Error('Error on response stream');\n    if (cb) { return cb(err); }\n    else { return clientRes.emit('error', err); }\n  });\n\n  // To kick things off, pass `opts` (as req) and `res` to the Sails router\n  sails.router.route({\n    method: method,\n    url: url,\n    body: body,\n    headers: headers || {}\n  }, {\n    _clientRes: clientRes\n  });\n\n  // Return clientRes stream\n  return clientRes;\n\n};\n\n\n\nfunction MockClientResponse() {\n  Transform.call(this);\n}\nutil.inherits(MockClientResponse, Transform);\nMockClientResponse.prototype._transform = function(chunk, encoding, next) {\n  this.push(chunk);\n  next();\n};\n\n"
  },
  {
    "path": "lib/hooks/README.md",
    "content": "#Hooks\n\n## Status\n\n> ##### Stability: [2](https://github.com/balderdashy/sails-docs/blob/master/contributing/stability-index.md) - Stable\n\n\n## Purpose\n\nMost of the non-essential Sails core has been pulled into hooks already.\nThese hooks may eventually be pulled out into separate modules, or they may continue to live in the main Sails repo (like Connect middleware).\n\nHooks were introduced to Sails as part of major refactor designed to make the framework more modular and testable. Their primary purpose was originally to pull all but the most minimal functionality of Sails into independent modules.\nToday, most of the non-essential Sails core are hooks. These hooks may eventually be pulled out into separate modules, or they may continue to live in the main Sails repo (like Connect middleware).\n\nThis architecture has allowed for built-in hooks to be overridden or disabled, and even for new hooks to be mixed-in to projects.\n\n\nThis gave way to hooks becoming a proper plugin system.  Nowadays, the goal of hooks is to provide an API that is flexible and powerful enough for plugin developers or folks who need to hack Sails core, but also predictable, documented, and easy to install for end users.\n\nSee http://sailsjs.com/documentation/concepts/extending-sails/hooks for more information.\n\n\n> **For historical purposes, here is the original proposal from the v0.9 days:**\n> https://gist.github.com/mikermcneil/5746660\n\n\n\n## FAQ\n\n> If you have a question that isn't covered here, please feel free to send a PR adding it to this section (even if you don't have the answer!)\n"
  },
  {
    "path": "lib/hooks/blueprints/README.md",
    "content": "# sails-hook-blueprints\n\nImplements support for the blueprint API in Sails.\n\n> This is a core hook in the Sails.js framework.  You can override or disable it using your `.sailsrc` file or environment variables.  See [Concepts > Configuration](http://sailsjs.com/docs/concepts/configuration) for more information.\n\n\n## Purpose\n\nThis hook's responsibilities are:\n\n1. Use `sails.modules` to read blueprints from the user's app into `self.middleware`.\n2. Bind shadow routes to blueprint actions and controller actions.\n3. Listen for `route:typeUnknown` on `sails`, interpret route syntax which should match a blueprint action, and bind the appropriate middleware (this happens when the Router is loaded, after all the hooks.)\n\n\n## Help\n\nHave questions or having trouble?  Click [here](http://sailsjs.com/support).\n\n> For more information on overriding core hooks, check out [Extending Sails > Hooks](http://sailsjs.com/documentation/concepts/extending-sails/hooks).\n\n\n## Bugs &nbsp; [![NPM version](https://badge.fury.io/js/sails-hook-blueprints.svg)](http://npmjs.com/package/sails-hook-blueprints)\n\nTo report a bug, [click here](http://sailsjs.com/bugs).\n\n\n## Contributing\n\nPlease observe the guidelines and conventions laid out in the [Sails project contribution guide](http://sailsjs.com/documentation/contributing) when opening issues or submitting pull requests.\n\n[![NPM](https://nodei.co/npm/sails-hook-blueprints.png?downloads=true)](http://npmjs.com/package/sails-hook-blueprints)\n\n## License\n\nThe [Sails framework](http://sailsjs.com) is free and open-source under the [MIT License](http://sailsjs.com/license).\n"
  },
  {
    "path": "lib/hooks/blueprints/actionUtil.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\nvar flaverr = require('flaverr');\nvar mergeDefaults = require('merge-defaults');// « TODO: Get rid of this\n\n/**\n * Utility methods used in built-in blueprint actions.\n *\n * @type {Object}\n */\nvar actionUtil = {\n\n  /**\n   * Given a Waterline query and an express request, populate\n   * the appropriate/specified association attributes and\n   * return it so it can be chained further ( i.e. so you can\n   * .exec() it )\n   *\n   * @param  {Query} query         [waterline query object]\n   * @param  {Request} req\n   * @return {Query}\n   */\n  populateRequest: function(query, req) {\n    var DEFAULT_POPULATE_LIMIT = req._sails.config.blueprints.defaultLimit || 30;\n    var _options = req.options;\n    var aliasFilter = req.param('populate');\n    var shouldPopulate = _.isUndefined(_options.populate) ? (req._sails.config.blueprints.populate) : _options.populate;\n\n    // Convert the string representation of the filter list to an Array. We\n    // need this to provide flexibility in the request param. This way both\n    // list string representations are supported:\n    //   /model?populate=alias1,alias2,alias3\n    //   /model?populate=[alias1,alias2,alias3]\n    if (typeof aliasFilter === 'string') {\n      aliasFilter = aliasFilter.replace(/\\[|\\]/g, '');\n      aliasFilter = (aliasFilter) ? aliasFilter.split(',') : [];\n    }\n\n    var associations = [];\n\n    _.each(_options.associations, function(association) {\n      // If an alias filter was provided, override the blueprint config.\n      if (aliasFilter) {\n        shouldPopulate = _.contains(aliasFilter, association.alias);\n      }\n\n      // Only populate associations if a population filter has been supplied\n      // with the request or if `populate` is set within the blueprint config.\n      // Population filters will override any value stored in the config.\n      //\n      // Additionally, allow an object to be specified, where the key is the\n      // name of the association attribute, and value is true/false\n      // (true to populate, false to not)\n      if (shouldPopulate) {\n        var populationLimit =\n          _options['populate_' + association.alias + '_limit'] ||\n          _options.populate_limit ||\n          _options.limit ||\n          DEFAULT_POPULATE_LIMIT;\n\n        associations.push({\n          alias: association.alias,\n          limit: populationLimit\n        });\n      }\n    });\n\n    return actionUtil.populateQuery(query, associations, req._sails);\n  },\n\n  /**\n   * Given a Waterline query and Waterline model, populate the\n   * appropriate/specified association attributes and return it\n   * so it can be chained further ( i.e. so you can .exec() it )\n   *\n   * @param  {Query} query         [waterline query object]\n   * @param  {Model} model         [waterline model object]\n   * @return {Query}\n   */\n  populateModel: function(query, model) {\n    return actionUtil.populateQuery(query, model.associations);\n  },\n\n\n  /**\n   * Given a Waterline query, populate the appropriate/specified\n   * association attributes and return it so it can be chained\n   * further ( i.e. so you can .exec() it )\n   *\n   * @param  {Query} query         [waterline query object]\n   * @param  {Array} associations  [array of objects with an alias\n   *                                and (optional) limit key]\n   * @return {Query}\n   */\n  populateQuery: function(query, associations, sails) {\n    var DEFAULT_POPULATE_LIMIT = (sails && sails.config.blueprints.defaultLimit) || 30;\n\n    return _.reduce(associations, function(query, association) {\n      var options = {};\n      if (association.type === 'collection') {\n        options.limit = association.limit || DEFAULT_POPULATE_LIMIT;\n      }\n      return query.populate(association.alias, options);\n    }, query);\n  },\n\n  /**\n   * Subscribe deep (associations)\n   *\n   * @param  {[type]} associations [description]\n   * @param  {[type]} record       [description]\n   * @return {[type]}              [description]\n   */\n  subscribeDeep: function ( req, record ) {\n    _.each(req.options.associations, function (assoc) {\n\n      // Look up identity of associated model\n      var ident = assoc[assoc.type];\n      var AssociatedModel = req._sails.models[ident];\n\n      if (req.options.autoWatch) {\n        AssociatedModel._watch(req);\n      }\n\n      // Subscribe to each associated model instance in a collection\n      if (assoc.type === 'collection') {\n        _.each(record[assoc.alias], function (associatedInstance) {\n          AssociatedModel.subscribe(req, [associatedInstance[AssociatedModel.primaryKey]]);\n        });\n      }\n      // If there is an associated to-one model instance, subscribe to it\n      else if (assoc.type === 'model' && _.isObject(record[assoc.alias])) {\n        AssociatedModel.subscribe(req, [record[assoc.alias][AssociatedModel.primaryKey]]);\n      }\n    });\n  },\n\n\n  /**\n   * Parse primary key value for use in a Waterline criteria\n   * (e.g. for `find`, `update`, or `destroy`)\n   *\n   * @param  {Request} req\n   * @return {Integer|String}\n   */\n  parsePk: function ( req ) {\n\n    var pk = req.options.id || (req.options.where && req.options.where.id) || req.param('id');\n\n    // FUTURE: make this smarter...\n    // (e.g. look for actual primary key of model and look for it\n    //  in the absence of `id`.)\n\n    // exclude criteria on id field\n    pk = _.isPlainObject(pk) ? undefined : pk;\n    return pk;\n  },\n\n\n\n  /**\n   * Parse primary key value from parameters.\n   * Throw an error if it cannot be retrieved.\n   *\n   * @param  {Request} req\n   * @return {Integer|String}\n   */\n  requirePk: function (req) {\n    var pk = module.exports.parsePk(req);\n\n    // Validate the required `id` parameter\n    if ( !pk ) {\n\n      var err = new Error(\n      'No `id` parameter provided.'+\n      '(Note: even if the model\\'s primary key is not named `id`- '+\n      '`id` should be used as the name of the parameter- it will be '+\n      'mapped to the proper primary key name)'\n      );\n      err.status = 400;\n      throw err;\n    }\n\n    return pk;\n  },\n\n\n\n  /**\n   * Parse `criteria` for a Waterline `find` or `update` from all\n   * request parameters.\n   *\n   * @param  {Request} req\n   *\n   * @returns {Dictionary}\n   *          The normalized WHERE clause\n   *\n   * @throws {Error} If WHERE clause cannot be parsed...\n   *                 ...whether that's for syntactic reasons (JSON.parse),\n   *                 or for semantic reasons (Waterline's `forgeStageTwoQuery()`).\n   *         @property {String} `name: 'UsageError'`\n   */\n  parseCriteria: function ( req ) {\n\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    // FUTURE: this should be renamed to `.parseWhere()`\n    // (\"criteria\" means the entire dictionary, including\n    // `where` -- but also `skip`, `limit`, etc.)\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n    // Allow customizable blacklist for params NOT to include as criteria.\n    req.options.criteria = req.options.criteria || {};\n    req.options.criteria.blacklist = req.options.criteria.blacklist || ['limit', 'skip', 'sort', 'populate'];\n\n    // Validate blacklist to provide a more helpful error msg.\n    var blacklist = req.options.criteria && req.options.criteria.blacklist;\n    if (blacklist && !_.isArray(blacklist)) {\n      throw new Error('Invalid `req.options.criteria.blacklist`. Should be an array of strings (parameter names.)');\n    }\n\n    // Look for explicitly specified `where` parameter.\n    var where = req.allParams().where;\n\n    // If `where` parameter is a string, try to interpret it as JSON.\n    // (If it cannot be parsed, throw a UsageError.)\n    if (_.isString(where)) {\n      try {\n        where = JSON.parse(where);\n      } catch (e) {\n        throw flaverr({ name: 'UsageError' }, new Error('Could not JSON.parse() the provided `where` clause. Here is the raw error: '+e.stack));\n      }\n    }//>-•\n\n    // If `where` has not been specified, but other unbound parameter variables\n    // **ARE** specified, build the `where` option using them.\n    if (!where) {\n\n      // Prune params which aren't fit to be used as `where` criteria\n      // to build a proper where query\n      where = req.allParams();\n\n      // Omit built-in runtime config (like query modifiers)\n      where = _.omit(where, blacklist || ['limit', 'skip', 'sort']);\n\n      // Omit any params that have `undefined` on the RHS.\n      where = _.omit(where, function(p) {\n        if (_.isUndefined(p)) { return true; }\n      });\n\n    }//>-\n\n    // Deep merge w/ req.options.where.\n    where = _.merge({}, req.options.where || {}, where) || undefined;\n\n    // Return final `where`.\n    return where;\n  },\n\n\n  /**\n   * Parse `values` for a Waterline `create` or `update` from all\n   * request parameters.\n   *\n   * @param  {Request} req\n   * @return {Dictionary}\n   */\n  parseValues: function (req) {\n\n    // Allow customizable blacklist for params NOT to include as values.\n    req.options.values = req.options.values || {};\n    req.options.values.blacklist = req.options.values.blacklist;\n\n    // Validate blacklist to provide a more helpful error msg.\n    var blacklist = req.options.values.blacklist;\n    if (blacklist && !_.isArray(blacklist)) {\n      throw new Error('Invalid `req.options.values.blacklist`. Should be an array of strings (parameter names.)');\n    }\n\n    // Start an array to hold values\n    var values;\n\n    // Make an array out of the request body data if it wasn't one already;\n    // this allows us to process multiple entities (e.g. for use with a \"create\" blueprint) the same way\n    // that we process singular entities.\n    var bodyData = _.isArray(req.body) ? req.body : [req.allParams()];\n\n    // Process each item in the bodyData array, merging with req.options, omitting blacklisted properties, etc.\n    var valuesArray = _.map(bodyData, function(element){\n      var values;\n      // Merge properties of the element into req.options.value, omitting the blacklist\n      values = mergeDefaults(element, _.omit(req.options.values, 'blacklist'));\n      // Omit properties that are in the blacklist (like query modifiers)\n      values = _.omit(values, blacklist || []);\n      // Omit any properties w/ undefined values\n      values = _.omit(values, function(p) {\n        if (_.isUndefined(p)) {\n          return true;\n        }\n      });\n\n      return values;\n    });\n\n    // If req.body is an array, simply return our array of processed values\n    if (_.isArray(req.body)) {return valuesArray;}\n\n    // Otherwaise grab the first (and only) value from valuesArray\n    values = valuesArray[0];\n\n    return values;\n  },\n\n\n\n  /**\n   * Determine the model class to use w/ this blueprint action.\n   * @param  {Request} req\n   * @return {WLCollection}\n   */\n  parseModel: function (req) {\n\n    // Ensure a model can be deduced from the request options.\n    var model = req.options.model || req.options.controller;\n    if (!model) { throw new Error(util.format('No \"model\" specified in route options.')); }\n\n    var Model = req._sails.models[model];\n    if ( !Model ) { throw new Error(util.format('Invalid route option, \"model\".\\nI don\\'t know about any models named: `%s`',model)); }\n\n    return Model;\n  },\n\n\n\n  /**\n   * @param  {Request} req\n   */\n  parseSort: function (req) {\n    var sort = req.param('sort') || req.options.sort;\n    if (_.isUndefined(sort)) {return undefined;}\n\n    // If `sort` is a string, attempt to JSON.parse() it.\n    // (e.g. `{\"name\": 1}`)\n    if (_.isString(sort)) {\n      try {\n        sort = JSON.parse(sort);\n        // If it is not valid JSON (e.g. because it's just some other string),\n        // then just fall back to interpreting it as-is (e.g. \"name ASC\")\n      } catch(unusedErr) {}\n    }\n    return sort;\n  },\n\n  /**\n   * @param  {Request} req\n   */\n  parseLimit: function (req) {\n    var DEFAULT_LIMIT = req._sails.config.blueprints.defaultLimit || 30;\n    var limit = req.param('limit') || (typeof req.options.limit !== 'undefined' ? req.options.limit : DEFAULT_LIMIT);\n    if (limit) { limit = +limit; }\n    return limit;\n  },\n\n\n  /**\n   * @param  {Request} req\n   */\n  parseSkip: function (req) {\n    var DEFAULT_SKIP = 0;\n    var skip = req.param('skip') || (typeof req.options.skip !== 'undefined' ? req.options.skip : DEFAULT_SKIP);\n    if (skip) { skip = +skip; }\n    return skip;\n  }\n};\n\n\nmodule.exports = actionUtil;\n"
  },
  {
    "path": "lib/hooks/blueprints/actions/add.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar formatUsageError = require('../formatUsageError');\n\n\n/**\n * Add Record To Collection\n *\n * http://sailsjs.com/docs/reference/blueprint-api/add-to\n *\n * Associate one record with the collection attribute of another.\n * e.g. add a Horse named \"Jimmy\" to a Farm's \"animals\".\n *\n */\n\nmodule.exports = function addToCollection (req, res) {\n\n  var parseBlueprintOptions = req.options.parseBlueprintOptions || req._sails.config.blueprints.parseBlueprintOptions;\n\n  // Set the blueprint action for parseBlueprintOptions.\n  req.options.blueprintAction = 'add';\n\n  var queryOptions = parseBlueprintOptions(req);\n  var Model = req._sails.models[queryOptions.using];\n\n  var relation = queryOptions.alias;\n\n  // The primary key of the parent record\n  var parentPk = queryOptions.targetRecordId;\n\n  // Get the model class of the child in order to figure out the name of\n  // the primary key attribute.\n  var associationAttr = _.findWhere(Model.associations, { alias: relation });\n  var ChildModel = req._sails.models[associationAttr.collection];\n\n  // The primary key of the child record;\n  var childPk = queryOptions.associatedIds[0];\n\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  // FUTURE: Use a database transaction here, if all of the involved models\n  // are using the same datastore, and if that datastore supports transactions.\n  // e.g.\n  // ```\n  // Model.getDatastore().transaction(function during(db, proceed){ ... })\n  // .exec(function afterwards(err, result){}));\n  // ```\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n  Model.findOne(parentPk).meta(queryOptions.meta).exec(function foundParent(err, parentRecord) {\n    if (err) {  return res.serverError(err); }\n\n    // No such parent record?  Bail out with a 404.\n    if (!parentRecord) { return res.notFound(); }\n\n    // Look up the child record to make sure it exists.\n    ChildModel.findOne(childPk).exec(function foundChild(err, childRecord) {\n      if (err) { return res.serverError(err); }\n\n      // No such child record?  Bail out with a 404.\n      if (!childRecord) {return res.notFound();}\n\n      // Add the child record to the parent.\n      Model.addToCollection(parentPk, relation, childPk).exec( function(err) {\n\n        if (err) {\n          switch (err.name) {\n            // Any kind of usage error coming back from Waterline,\n            // (e.g. a bad criteria), is met with a 400 status code.\n            case 'UsageError': return res.badRequest(formatUsageError(err, req));\n            case 'AdapterError':\n              switch (err.code) {\n                // If this child record is already a member of this collection,\n                // then just continue along to the publishing below-- we'll still\n                // respond w/ a 200 status code.\n                // (see http://sailsjs.com/documentation/reference/blueprint-api/add-to)\n                case 'E_UNIQUE': break;\n                // Any other kind of adapter error is unexpected, so use 500.\n                default: return res.serverError(err);\n              } break;\n            // Otherwise, it's some other unexpected error, so use 500.\n            default: return res.serverError(err);\n          }\n        }\n\n        // Broadcast updates if pubsub hook is enabled.\n        if (req._sails.hooks.pubsub) {\n\n          // Subscribe to the model you're adding to, if this was a socket request\n          if (req.isSocket) { Model.subscribe(req, [parentPk]); }\n          // Publish to subscribed sockets\n          Model._publishAdd(parentPk, relation, childPk, !req.options.mirror && req);\n          // If the inverse relationship on the child model is a singular association, and\n          // the association attribute on the child was not `null` before, then notify the\n          // former parent that this child has been \"stolen\".\n          if (associationAttr.via && ChildModel.attributes[associationAttr.via].model && !_.isNull(childRecord[associationAttr.via])) {\n            Model._publishRemove(childRecord[associationAttr.via], relation, childPk, !req.options.mirror && req, {noReverse: true});\n          }\n\n        }\n\n        // Finally, look up the parent record again and populate the relevant collection.\n        var query = Model.findOne(parentPk, queryOptions.populates).meta(queryOptions.meta);\n        query.exec(function(err, matchingRecord) {\n          if (err) { return res.serverError(err); }\n          if (!matchingRecord) { return res.serverError(); }\n          if (!matchingRecord[relation]) { return res.serverError(); }\n          return res.ok(matchingRecord);\n        });\n\n      });\n\n    }); // </ ChildModel.findOne(childPk) >\n\n  }); // </ Model.findOne(parentPk)>\n\n};\n"
  },
  {
    "path": "lib/hooks/blueprints/actions/create.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar async = require('async');\nvar formatUsageError = require('../formatUsageError');\n\n/**\n * Create Record\n *\n * http://sailsjs.com/docs/reference/blueprint-api/create\n *\n * An API call to crete a single model instance using the specified attribute values.\n *\n */\n\nmodule.exports = function createRecord (req, res) {\n\n  var parseBlueprintOptions = req.options.parseBlueprintOptions || req._sails.config.blueprints.parseBlueprintOptions;\n\n  // Set the blueprint action for parseBlueprintOptions.\n  req.options.blueprintAction = 'create';\n\n  var queryOptions = parseBlueprintOptions(req);\n  var Model = req._sails.models[queryOptions.using];\n\n  // Get the new record data.\n  var data = queryOptions.newRecord;\n\n  // Look for any many-to-one collections that are being set.\n  // For example, User.create({pets: [1, 2, 3]}) where `pets` is a collection of `Pet`\n  // via an `owner` attribute that is `model: 'user'`.\n  // We need to know about these so that, if any of the new children already had parents,\n  // those parents get `removedFrom` notifications.\n  async.reduce(_.keys(Model.attributes), [], function(memo, attrName, nextAttrName) {\n\n    var attrDef = Model.attributes[attrName];\n    if (\n      // Does this attribute represent a plural association.\n      attrDef.collection &&\n      // Is this attribute set with a non-empty array?\n      _.isArray(data[attrName]) && data[attrName].length > 0 &&\n      // Does this plural association have an inverse attribute on the related model?\n      attrDef.via &&\n      // Is that inverse attribute a singular association, making this a many-to-one relationship?\n      req._sails.models[attrDef.collection].attributes[attrDef.via].model\n    ) {\n      // Create an `in` query looking for all child records whose primary keys match\n      // those in the array that the new parent's association attribute (e.g. `pets`) is set to.\n      var criteria = {};\n      criteria[req._sails.models[attrDef.collection].primaryKey] = data[attrName];\n      req._sails.models[attrDef.collection].find(criteria).exec(function(err, newChildren) {\n        if (err) {return nextAttrName(err);}\n        // For each child, see if the inverse attribute already has a value, and if so,\n        // push a new `removedFrom` notification onto the list of those to send.\n        _.each(newChildren, function(child) {\n          if (child[attrDef.via]) {\n            memo.push({\n              id: child[attrDef.via],\n              removedId: child[req._sails.models[attrDef.collection].primaryKey],\n              attribute: attrName\n            });\n          }\n        });\n        return nextAttrName(undefined, memo);\n      });\n    }\n\n    else {\n      return nextAttrName(undefined, memo);\n    }\n\n  }, function (err, removedFromNotificationsToSend) {\n\n    if (err) {return res.serverError(err);}\n\n\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    // FUTURE: Use a database transaction here, if supported by the datastore.\n    // e.g.\n    // ```\n    // Model.getDatastore().transaction(function during(db, proceed){ ... })\n    // .exec(function afterwards(err, result){}));\n    // ```\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n    // Create new instance of model using data from params\n    Model.create(data).meta(queryOptions.meta).exec(function created (err, newInstance) {\n\n      // Differentiate between waterline-originated validation errors\n      // and serious underlying issues. Respond with badRequest if a\n      // validation error is encountered, w/ validation info, or if a\n      // uniqueness constraint is violated.\n      if (err) {\n        switch (err.name) {\n          case 'AdapterError':\n            switch (err.code) {\n              case 'E_UNIQUE': return res.badRequest(err);\n              default: return res.serverError(err);\n            }//•\n          case 'UsageError': return res.badRequest(formatUsageError(err, req));\n          default: return res.serverError(err);\n        }\n      }//-•\n\n      // If we didn't fetch the new instance, just return 'OK'.\n      if (!newInstance) {\n        return res.ok();\n      }\n\n      // Look up and populate the new record (according to `populate` options in request / config)\n      Model\n      .findOne(newInstance[Model.primaryKey], queryOptions.populates)\n      .exec(function foundAgain(err, populatedRecord) {\n        if (err) { return res.serverError(err); }\n        if (!populatedRecord) { return res.serverError('Could not find record after creating!'); }\n\n        // If we have the pubsub hook, use the model class's publish method\n        // to notify all subscribers about the created item\n        if (req._sails.hooks.pubsub) {\n          if (req.isSocket) {\n            Model.subscribe(req, [populatedRecord[Model.primaryKey]]);\n            Model._introduce(populatedRecord);\n          }\n          Model._publishCreate(populatedRecord, !req.options.mirror && req);\n          if (removedFromNotificationsToSend.length) {\n            _.each(removedFromNotificationsToSend, function(notification) {\n              Model._publishRemove(notification.id, notification.attribute, notification.removedId, !req.options.mirror && req, {noReverse: true});\n            });\n          }\n        }//>-\n\n        // Send response\n        res.ok(populatedRecord);\n      }); // </foundAgain>\n\n    });\n\n  });\n\n\n\n};\n"
  },
  {
    "path": "lib/hooks/blueprints/actions/destroy.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar formatUsageError = require('../formatUsageError');\n\n/**\n * Destroy One Record\n *\n * http://sailsjs.com/docs/reference/blueprint-api/destroy\n *\n * Destroys the single model instance with the specified `id` from\n * the data adapter for the given model if it exists.\n *\n */\n\nmodule.exports = function destroyOneRecord (req, res) {\n\n  var parseBlueprintOptions = req.options.parseBlueprintOptions || req._sails.config.blueprints.parseBlueprintOptions;\n\n  // Set the blueprint action for parseBlueprintOptions.\n  req.options.blueprintAction = 'destroy';\n\n  var queryOptions = parseBlueprintOptions(req);\n  var Model = req._sails.models[queryOptions.using];\n\n  var criteria = {};\n  criteria[Model.primaryKey] = queryOptions.criteria.where[Model.primaryKey];\n\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  // FUTURE: Use a database transaction here, if supported by the datastore.\n  // e.g.\n  // ```\n  // Model.getDatastore().transaction(function during(db, proceed){ ... })\n  // .exec(function afterwards(err, result){}));\n  // ```\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n  var query = Model.findOne(_.cloneDeep(criteria), queryOptions.populates).meta(queryOptions.meta);\n  query.exec(function foundRecord (err, record) {\n    if (err) {\n      // If this is a usage error coming back from Waterline,\n      // (e.g. a bad criteria), then respond w/ a 400 status code.\n      // Otherwise, it's something unexpected, so use 500.\n      switch (err.name) {\n        case 'UsageError': return res.badRequest(formatUsageError(err, req));\n        default: return res.serverError(err);\n      }\n    }//-•\n\n    if(!record) { return res.notFound('No record found with the specified `id`.'); }\n\n    // (Note: this could be achieved in a single query, but a separate `findOne`\n    // is used first to provide a better experience for front-end developers\n    // integrating with the blueprint API out of the box. However, we'll also include\n    // the meta query optons for the purpose of enabling the `afterDestroy`\n    // lifecycle callback (which only runs if `.meta({fetch: true})` is included).\n    Model.destroy(_.cloneDeep(criteria)).meta(queryOptions.meta)\n    .exec(function destroyedRecord (err) {\n      if (err) {\n        switch (err.name) {\n          case 'UsageError': return res.badRequest(formatUsageError(err, req));\n          default: return res.serverError(err);\n        }\n      }//-•\n\n      if (req._sails.hooks.pubsub) {\n        Model._publishDestroy(criteria[Model.primaryKey], !req._sails.config.blueprints.mirror && req, {previous: record});\n        if (req.isSocket) {\n          Model.unsubscribe(req, [record[Model.primaryKey]]);\n          Model._retire(record);\n        }\n      }\n\n      return res.ok(record);\n    });\n  });\n};\n"
  },
  {
    "path": "lib/hooks/blueprints/actions/find.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar actionUtil = require('../actionUtil');\nvar formatUsageError = require('../formatUsageError');\n\n/**\n * Find Records\n *\n * http://sailsjs.com/docs/reference/blueprint-api/find\n *\n * An API call to find and return model instances from the data adapter\n * using the specified criteria.  If an id was specified, just the instance\n * with that unique id will be returned.\n *\n */\n\nmodule.exports = function findRecords (req, res) {\n\n  var parseBlueprintOptions = req.options.parseBlueprintOptions || req._sails.config.blueprints.parseBlueprintOptions;\n\n  // Set the blueprint action for parseBlueprintOptions.\n  req.options.blueprintAction = 'find';\n\n  var queryOptions = parseBlueprintOptions(req);\n  var Model = req._sails.models[queryOptions.using];\n\n  Model\n  .find(queryOptions.criteria, queryOptions.populates).meta(queryOptions.meta)\n  .exec(function found(err, matchingRecords) {\n    if (err) {\n      // If this is a usage error coming back from Waterline,\n      // (e.g. a bad criteria), then respond w/ a 400 status code.\n      // Otherwise, it's something unexpected, so use 500.\n      switch (err.name) {\n        case 'UsageError': return res.badRequest(formatUsageError(err, req));\n        default: return res.serverError(err);\n      }\n    }//-•\n\n    if (req._sails.hooks.pubsub && req.isSocket) {\n      Model.subscribe(req, _.pluck(matchingRecords, Model.primaryKey));\n      // Only `._watch()` for new instances of the model if\n      // `autoWatch` is enabled.\n      if (req.options.autoWatch) { Model._watch(req); }\n      // Also subscribe to instances of all associated models\n      _.each(matchingRecords, function (record) {\n        actionUtil.subscribeDeep(req, record);\n      });\n    }//>-\n\n    return res.ok(matchingRecords);\n\n  });//</ .find().exec() >\n\n};\n"
  },
  {
    "path": "lib/hooks/blueprints/actions/findOne.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar actionUtil = require('../actionUtil');\nvar formatUsageError = require('../formatUsageError');\n\n/**\n * Find One Record\n *\n * http://sailsjs.com/docs/reference/blueprint-api/find-one.\n *\n * > Blueprint action to find and return the record with the specified id.\n *\n */\n\nmodule.exports = function findOneRecord (req, res) {\n\n  var parseBlueprintOptions = req.options.parseBlueprintOptions || req._sails.config.blueprints.parseBlueprintOptions;\n\n  // Set the blueprint action for parseBlueprintOptions.\n  req.options.blueprintAction = 'findOne';\n\n  var queryOptions = parseBlueprintOptions(req);\n  var Model = req._sails.models[queryOptions.using];\n\n  // Only use the `where`, `select` or `omit` from the criteria (nothing else is valid for findOne).\n  queryOptions.criteria = _.pick(queryOptions.criteria, ['where', 'select', 'omit']);\n\n  // Only use the primary key in the `where` clause.\n  queryOptions.criteria.where = _.pick(queryOptions.criteria.where, Model.primaryKey);\n\n\n  Model\n  .findOne(queryOptions.criteria, queryOptions.populates).meta(queryOptions.meta)\n  .exec(function found(err, matchingRecord) {\n    if (err) {\n      // If this is a usage error coming back from Waterline,\n      // (e.g. a bad criteria), then respond w/ a 400 status code.\n      // Otherwise, it's something unexpected, so use 500.\n      switch (err.name) {\n        case 'UsageError': return res.badRequest(formatUsageError(err, req));\n        default: return res.serverError(err);\n      }\n    }//-•\n\n    if(!matchingRecord) {\n      req._sails.log.verbose('In `findOne` blueprint action: No record found with the specified id (`'+queryOptions.criteria.where[Model.primaryKey]+'`).');\n      return res.notFound();\n    }\n\n    if (req._sails.hooks.pubsub && req.isSocket) {\n      Model.subscribe(req, [matchingRecord[Model.primaryKey]]);\n      actionUtil.subscribeDeep(req, matchingRecord);\n    }\n\n    return res.ok(matchingRecord);\n\n  });\n\n};\n"
  },
  {
    "path": "lib/hooks/blueprints/actions/populate.js",
    "content": "/**\n * Module dependencies\n */\n\nvar actionUtil = require('../actionUtil');\nvar formatUsageError = require('../formatUsageError');\n\n/**\n * Populate (or \"expand\") an association\n *\n * http://sailsjs.com/docs/reference/blueprint-api/populate\n *\n */\n\nmodule.exports = function populate(req, res) {\n\n  var sails = req._sails;\n\n  var parseBlueprintOptions = req.options.parseBlueprintOptions || req._sails.config.blueprints.parseBlueprintOptions;\n\n  // Set the blueprint action for parseBlueprintOptions.\n  req.options.blueprintAction = 'populate';\n\n  var queryOptions = parseBlueprintOptions(req);\n  var Model = req._sails.models[queryOptions.using];\n\n  var attrName = queryOptions.alias;\n  if (!attrName || !Model) { return res.serverError(); }\n\n  // The primary key of the parent record\n  var parentPk = queryOptions.criteria.where[Model.primaryKey];\n\n  Model\n    .findOne(parentPk, queryOptions.populates).meta(queryOptions.meta)\n    .exec(function found(err, matchingRecord) {\n      if (err) {\n        // If this is a usage error coming back from Waterline,\n        // (e.g. a bad criteria), then respond w/ a 400 status code.\n        // Otherwise, it's something unexpected, so use 500.\n        switch (err.name) {\n          case 'UsageError': return res.badRequest(formatUsageError(err, req));\n          default: return res.serverError(err);\n        }\n      }//-•\n\n      if (!matchingRecord) {\n        sails.log.verbose('In `populate` blueprint action: No parent record found with the specified id (`'+parentPk+'`).');\n        return res.notFound();\n      }//-•\n\n      if (!matchingRecord[attrName]) {\n        sails.log.verbose('In `populate` blueprint action: Specified parent record ('+parentPk+') does not have a `'+attrName+'`.');\n        return res.notFound();\n      }//-•\n\n      // Subcribe to relevant record(s), if appropriate.\n      if (sails.hooks.pubsub && req.isSocket) {\n        Model.subscribe(req, matchingRecord);\n        actionUtil.subscribeDeep(req, matchingRecord);\n        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n        // FUTURE:\n        // Only subscribe to the associated record(s) without watching the entire\n        // associated model.  (Currently, `.subscribeDeep()` also calls `.watch()`,\n        // if `autoWatch` is enabled.)\n        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n      }\n\n      return res.ok(matchingRecord[attrName]);\n\n    });\n};\n"
  },
  {
    "path": "lib/hooks/blueprints/actions/remove.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar formatUsageError = require('../formatUsageError');\n\n\n/**\n * Remove a member from an association\n *\n * http://sailsjs.com/docs/reference/blueprint-api/remove-from\n *\n */\n\nmodule.exports = function remove(req, res) {\n\n  var parseBlueprintOptions = req.options.parseBlueprintOptions || req._sails.config.blueprints.parseBlueprintOptions;\n\n  // Set the blueprint action for parseBlueprintOptions.\n  req.options.blueprintAction = 'remove';\n\n  var queryOptions = parseBlueprintOptions(req);\n  var Model = req._sails.models[queryOptions.using];\n\n  var relation = queryOptions.alias;\n\n  // The primary key of the parent record\n  var parentPk = queryOptions.targetRecordId;\n\n  // Get the model class of the child in order to figure out the name of\n  // the primary key attribute.\n  var associationAttr = _.findWhere(Model.associations, { alias: relation });\n  var ChildModel = req._sails.models[associationAttr.collection];\n\n  // The primary key of the child record;\n  var childPk = queryOptions.associatedIds[0];\n\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  // FUTURE: Use a database transaction here, if all of the involved models\n  // are using the same datastore, and if that datastore supports transactions.\n  // e.g.\n  // ```\n  // Model.getDatastore().transaction(function during(db, proceed){ ... })\n  // .exec(function afterwards(err, result){}));\n  // ```\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n  Model.findOne(parentPk).meta(queryOptions.meta).exec(function foundParent(err, parentRecord) {\n    if (err) { return res.serverError(err); }\n    if (!parentRecord) { return res.notFound(); }\n\n    // Look up the child record to make sure it exists.\n    ChildModel.findOne(childPk).exec(function foundChild(err, childRecord) {\n      if (err) { return res.serverError(err); }\n\n      // No such child record?  Bail out with a 404.\n      if (!childRecord) {return res.notFound();}\n\n      Model.removeFromCollection(parentPk, relation, childPk).exec(function(err) {\n        if (err) {\n          // If this is a usage error coming back from Waterline,\n          // (e.g. a bad criteria), then respond w/ a 400 status code.\n          // Otherwise, it's something unexpected, so use 500.\n          switch (err.name) {\n            case 'UsageError': return res.badRequest(formatUsageError(err, req));\n            default: return res.serverError(err);\n          }\n        }//-•\n\n        // Finally, look up the parent record again and populate the relevant collection.\n        var query = Model.findOne(parentPk, queryOptions.populates).meta(queryOptions.meta);\n        query.exec(function found(err, parentRecord) {\n          if (err) { return res.serverError(err); }\n          if (!parentRecord) { return res.serverError(); }\n          if (!parentRecord[relation]) { return res.serverError(); }\n          if (!parentRecord[Model.primaryKey]) { return res.serverError(); }\n\n          // If we have the pubsub hook, use the model class's publish method\n          // to notify all subscribers about the removed item\n          if (req._sails.hooks.pubsub) {\n            Model._publishRemove(parentRecord[Model.primaryKey], relation, childPk, !req._sails.config.blueprints.mirror && req);\n          }\n\n          return res.ok(parentRecord);\n        });\n      });\n\n    });\n\n  });\n\n};\n"
  },
  {
    "path": "lib/hooks/blueprints/actions/replace.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar async = require('async');\nvar formatUsageError = require('../formatUsageError');\n\n/**\n * Replace Records in Collection\n *\n * http://sailsjs.com/docs/reference/blueprint-api/replace\n *\n * Replace the associated records in the given collection with\n * different records.  For example, replace all of a user's pets.\n *\n */\n\nmodule.exports = function replaceCollection (req, res) {\n\n  var parseBlueprintOptions = req.options.parseBlueprintOptions || req._sails.config.blueprints.parseBlueprintOptions;\n\n  // Set the blueprint action for parseBlueprintOptions.\n  req.options.blueprintAction = 'replace';\n\n  var queryOptions = parseBlueprintOptions(req);\n  var Model = req._sails.models[queryOptions.using];\n\n  var relation = queryOptions.alias;\n\n  // The primary key of the parent record\n  var parentPk = queryOptions.targetRecordId;\n\n  var childPks = queryOptions.associatedIds;\n\n  var removedFromNotificationsToSend = [];\n  var existingChildPks = [];\n\n  // Get the relevant association attribute on the parent model.\n  var attr = Model.attributes[relation];\n\n  // Get the related (\"child\") model.\n  var relatedModel = req._sails.models[attr.model || attr.collection];\n\n  // Get the inverse attribute (if any) on the related model.\n  var inverseAttr = attr.via && relatedModel.attributes[attr.via];\n\n  async.auto({\n\n    // If this is a many-to-one relationship, get all of the existing child PKs so that we can\n    // inform them of their removal (if they're not in the new set), and get all of the parent PKs\n    notificationsForExistingParentsOfReplacementChildren: function(cb) {\n\n      // If there is no inverse attribute on the related model, then this is a via-less collection\n      // which uses an implicit join table, so there's no \"stealing\" of children.\n      if (!inverseAttr) { return cb(); }\n\n      // If the inverse relationship on the related model is a collection, then this is\n      // a many-to-many relationship, so again, no stolen children.\n      if (inverseAttr.collection) { return cb(); }\n\n      // Ok, this is a many-to-one relationship, so let's find all of the \"replacement\" children\n      // and add `removedFrom` notifications for each (if the current parent is different from the new parent).\n      var criteria = {};\n      criteria[relatedModel.primaryKey] = childPks;\n      criteria[attr.via] = {'!=': parentPk};\n      relatedModel.stream(criteria).select([attr.via]).eachRecord(function(childRecord, nextChild) {\n\n        if (childRecord[attr.via] !== null) {\n          removedFromNotificationsToSend.push({\n            id: childRecord[attr.via],\n            removedId: childRecord[relatedModel.primaryKey],\n            attribute: relation,\n            reverse: false\n          });\n        }\n\n        return nextChild();\n\n      }).exec(cb);\n\n    },\n\n    notificationsForExistingChildrenOfParent: function(cb) {\n\n      // If this is a many-to-many or a via-less relationship, then we can't query the related model\n      // to find the existing children of our parent.  We'll have to just do a find + populate.\n      if (!inverseAttr || inverseAttr.collection) {\n\n        var parentCriteria = {};\n        parentCriteria[Model.primaryKey] = parentPk;\n        var populateCriteria = {\n          select: [relatedModel.primaryKey]\n        };\n        Model.findOne(parentCriteria).populate(relation, populateCriteria).exec(function(err, parentRecord) {\n          if (err) {return cb(err);}\n          _.each(parentRecord[relation], function(child) {\n            existingChildPks.push(child[relatedModel.primaryKey]);\n            if (!_.contains(childPks, child[relatedModel.primaryKey])) {\n              removedFromNotificationsToSend.push({\n                id: parentPk,\n                removedId: child[relatedModel.primaryKey],\n                attribute: relation,\n                reverse: true\n              });\n            }\n          });\n          return cb();\n        });//_∏_\n\n        return;\n      }//-•\n\n      // Otherwise, this is a many-to-one relationship, and we can query the related model.\n      var criteria = {\n        where: {},\n        select: [relatedModel.primaryKey]\n      };\n      criteria.where[attr.via] = parentPk;\n      relatedModel.stream(criteria).eachRecord(function(childRecord, nextChild) {\n        existingChildPks.push(childRecord[relatedModel.primaryKey]);\n        if (!_.contains(childPks, childRecord[relatedModel.primaryKey])) {\n          removedFromNotificationsToSend.push({\n            id: parentPk,\n            removedId: childRecord[relatedModel.primaryKey],\n            attribute: relation,\n            reverse: true\n          });\n        }\n\n        return nextChild();\n\n      }).exec(cb);\n\n    }\n\n\n  }, function(err) {\n\n    if (err) {\n      return res.serverError(err);\n    }\n\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    // FUTURE: Use a database transaction here, if all of the involved models\n    // are using the same datastore, and if that datastore supports transactions.\n    // e.g.\n    // ```\n    // Model.getDatastore().transaction(function during(db, proceed){ ... })\n    // .exec(function afterwards(err, result){}));\n    // ```\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n    Model.replaceCollection(parentPk, relation, childPks).exec( function(err) {\n\n      if (err) {\n        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n        // FUTURE:  When we support transactions in blueprints, if this request was\n        // able to use a transaction, handle E_UNIQUE by sending back a 409 (\"Conflict\")\n        // instead of a 5xx error-- since in that case, all of our changes from this\n        // blueprint action would have been rolled back along with the transaction\n        // upon failure.\n        //\n        // For example, we'd call `proceed(err)` like normal, then in the \"afterwards\"\n        // callback from `.transaction()`, we'd check for E_UNIQUE, and if we see it, call:\n        // ```\n        // return res.badRequest(err);\n        // ```\n        // - - - - - - - - - - - - - - - - - - - - - - - -\n\n        switch (err.name) {\n          // If this is a usage error coming back from Waterline,\n          // (e.g. a bad criteria), then respond w/ a 400 status code.\n          case 'UsageError': return res.badRequest(formatUsageError(err, req));\n          case 'AdapterError':\n            switch(err.code) {\n              // If there is a uniqueness error, then this collection have been simultaneously\n              // mucked around with by some other query that is editing records at the same time.\n              // (Because we know that uniqueness errors cannot happen because of duplicates\n              // in the array of child ids-- duplicate child ids are ignored.)  So for now,\n              // we'll respond with a 500 error in this case, but see the note above about how\n              // this will be handled in the future.\n              case 'E_UNIQUE': return res.serverError(err);\n              // Any other kind of adapter error is unexpected, so use 500.\n              default: return res.serverError(err);\n            }//•\n          // Otherwise, it's some other unexpected error, so use 500.\n          default: return res.serverError(err);\n        }\n      }//-•\n\n      // Broadcast updates to subscribers of the child records.\n      if (req._sails.hooks.pubsub) {\n\n        // Subscribe to the model you're adding to, if this was a socket request\n        if (req.isSocket) { Model.subscribe(req, [parentPk]); }\n\n        // Publish to subscribed sockets\n        _.each(_.difference(childPks, existingChildPks), function(childPk) {\n          Model._publishAdd(parentPk, relation, childPk, !req.options.mirror && req);\n        });\n\n        if (removedFromNotificationsToSend.length) {\n          _.each(removedFromNotificationsToSend, function(notification) {\n            Model._publishRemove(notification.id, notification.attribute, notification.removedId, !req.options.mirror && req, {noReverse: !notification.reverse});\n          });\n        }\n\n      }\n\n      var query = Model.findOne(parentPk, queryOptions.populates).meta(queryOptions.meta);\n      query.exec(function(err, matchingRecord) {\n        if (err) { return res.serverError(err); }\n        if (!matchingRecord) { return res.serverError(); }\n        if (!matchingRecord[relation]) { return res.serverError(); }\n        return res.ok(matchingRecord);\n      });\n\n    }); // </ Model.replaceCollection(parentPk)>\n\n  });\n\n\n};\n"
  },
  {
    "path": "lib/hooks/blueprints/actions/update.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar formatUsageError = require('../formatUsageError');\n\n\n/**\n * Update One Record\n *\n * http://sailsjs.com/docs/reference/blueprint-api/update\n *\n * An API call to update a model instance with the specified `id`,\n * treating the other unbound parameters as attributes.\n *\n */\n\nmodule.exports = function updateOneRecord (req, res) {\n\n  var parseBlueprintOptions = req.options.parseBlueprintOptions || req._sails.config.blueprints.parseBlueprintOptions;\n\n  // Set the blueprint action for parseBlueprintOptions.\n  req.options.blueprintAction = 'update';\n\n  var queryOptions = parseBlueprintOptions(req);\n  var Model = req._sails.models[queryOptions.using];\n\n  var criteria = {};\n  criteria[Model.primaryKey] = queryOptions.criteria.where[Model.primaryKey];\n\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  // FUTURE: Use a database transaction here, if supported by the datastore.\n  // e.g.\n  // ```\n  // Model.getDatastore().transaction(function during(db, proceed){ ... })\n  // .exec(function afterwards(err, result){}));\n  // ```\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n  // Find and update the targeted record.\n  //\n  // (Note: this could be achieved in a single query, but a separate `findOne`\n  //  is used first to provide a better experience for front-end developers\n  //  integrating with the blueprint API.)\n  Model.findOne(\n    _.cloneDeep(criteria),\n    _.cloneDeep(queryOptions.populates)\n  )\n  .exec(function (err, matchingRecord) {\n    if (err) {\n      switch (err.name) {\n        case 'UsageError': return res.badRequest(formatUsageError(err, req));\n        default: return res.serverError(err);\n      }\n    }//-•\n\n    if (!matchingRecord) {\n      return res.notFound();\n    }//•\n\n    // This should only update a single record\n    Model.updateOne(_.cloneDeep(criteria))\n    .set(queryOptions.valuesToSet)\n    .meta(queryOptions.meta)\n    .exec(function (err, updatedRecord) {\n\n      // Differentiate between waterline-originated validation errors\n      // and serious underlying issues. Respond with badRequest if a\n      // validation error is encountered, w/ validation info, or if a\n      // uniqueness constraint is violated.\n      if (err) {\n        switch (err.name) {\n          case 'AdapterError':\n            switch (err.code) {\n              case 'E_UNIQUE': return res.badRequest(err);\n              default: return res.serverError(err);\n            }//•\n          case 'UsageError': return res.badRequest(formatUsageError(err, req));\n          default: return res.serverError(err);\n        }\n      }//•\n\n      if (!updatedRecord) {\n        return res.notFound();\n      }//•\n\n      // If we have the pubsub hook, use the Model's publish method\n      // to notify all subscribers about the update.\n      if (req._sails.hooks.pubsub) {\n        if (req.isSocket) {\n          Model.subscribe(req, [updatedRecord[Model.primaryKey]]);\n        }//ﬁ\n\n        // The _.cloneDeep()s ensure that only plain dictionaries are broadcast.\n        // > TODO: why is that important?\n        var pk = updatedRecord[Model.primaryKey];\n        Model._publishUpdate(pk, _.cloneDeep(queryOptions.valuesToSet), !req.options.mirror && req, {\n          previous: _.cloneDeep(matchingRecord)\n        });\n      }//ﬁ\n\n      // Do a final query to populate the associations of the record.\n      //\n      // (Note: again, this extra query could be eliminated, but it is\n      //  included by default to provide a better interface for integrating\n      //  front-end developers.)\n      Model.findOne(\n        _.cloneDeep(criteria),\n        _.cloneDeep(queryOptions.populates)\n      )\n      .exec(function foundAgain(err, populatedRecord) {\n        if (err) { return res.serverError(err); }\n        if (!populatedRecord) { return res.serverError('Could not find record after updating!'); }\n        res.ok(populatedRecord);\n      }); // </.findOne() (for populating the updated record)>\n    });// </.updateOne()>\n  }); // </.findOne() to get the ORIGINAL populated record>\n};\n"
  },
  {
    "path": "lib/hooks/blueprints/formatUsageError.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\n\n/**\n * Give Waterline UsageErrors from blueprints a toJSON function for nicer output.\n */\n\nmodule.exports = function(err, req) {\n\n  err.toJSON = function (){\n    // Include the error code and the array of RTTC validation errors\n    // for easy programmatic parsing.\n    var jsonReadyErrDictionary = _.pick(err, ['code', 'details']);\n    // And also include a more front-end-friendly version of the error message.\n    var preamble =\n    'The server could not fulfill this request (`'+req.method+' '+req.path+'`) '+\n    'due to a problem with the parameters that were sent.  See the `details` for more info.';\n\n    // If NOT running in production, then provide additional details and tips.\n    if (process.env.NODE_ENV !== 'production') {\n      jsonReadyErrDictionary.message = preamble+'  '+\n      '**The following additional tip will not be shown in production**:  '+\n      'Tip: Check your client-side code to make sure that the request data it '+\n      'sends matches the expectations of the corresponding attributes in your '+\n      'model.  Also check that your client-side code sends data for every required attribute.';\n    }\n    // If running in production, use a message that is more terse.\n    else {\n      jsonReadyErrDictionary.message = preamble;\n    }\n    //>-\n\n    return jsonReadyErrDictionary;\n\n  };//</define :: err.toJSON()>\n\n  return err;\n\n};\n"
  },
  {
    "path": "lib/hooks/blueprints/index.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar util = require('util');\nvar pluralize = require('pluralize');\nvar STRINGFILE = require('sails-stringfile');\nvar flaverr = require('flaverr');\nvar BlueprintController = {\n  create: require('./actions/create'),\n  find: require('./actions/find'),\n  findone: require('./actions/findOne'),\n  update: require('./actions/update'),\n  destroy: require('./actions/destroy'),\n  populate: require('./actions/populate'),\n  add: require('./actions/add'),\n  remove: require('./actions/remove'),\n  replace: require('./actions/replace'),\n};\n\n\n\n/**\n * Blueprints (Core Hook)\n *\n * Stability: 1 - Experimental\n * (see http://nodejs.org/api/documentation.html#documentation_stability_index)\n */\n\nmodule.exports = function(sails) {\n\n  /**\n   * Private dependencies.\n   * (need access to `sails`)\n   */\n\n  var onRoute = require('./onRoute')(sails);\n\n\n\n  var hook;\n\n  /**\n   * Expose blueprint hook definition\n   */\n  return {\n\n    /**\n     * Default configuration to merge w/ top-level `sails.config`\n     * @type {Object}\n     */\n    defaults: {\n\n      // These config options are mixed into the route options (req.options)\n      // and made accessible from the blueprint actions.  Most of them currently\n      // relate to the shadow (i.e. implicit) routes which are created, and are\n      // interpreted by this hook.\n      blueprints: {\n\n        // Blueprint/Shadow-Routes Enabled\n        //\n        // e.g. '/frog/jump': 'FrogController.jump'\n        actions: false,\n        // e.g. '/frog/find/:id?': 'FrogController.find'\n        shortcuts: true,\n        // e.g. 'get /frog/:id?': 'FrogController.find'\n        rest: true,\n\n\n\n        // Blueprint/Shadow-Route Modifiers\n        //\n        // e.g. 'get /api/v2/frog/:id?': 'FrogController.find'\n        prefix: '',\n\n        // Blueprint/REST-Route Modifiers\n        // Will work only for REST and will extend `prefix` option\n        //\n        // e.g. 'get /api/v2/frog/:id?': 'FrogController.find'\n        restPrefix: '',\n\n        // e.g. 'get /frogs': 'FrogController.find'\n        pluralize: false,\n\n\n\n        // Configuration of the blueprint actions themselves:\n\n        // Whether to run `Model.watch()` in the `find` blueprint action.\n        autoWatch: true,\n\n        // Private per-controller config.\n        _controllers: {},\n\n        parseBlueprintOptions: function(req) {\n          return req._sails.hooks.blueprints.parseBlueprintOptions(req);\n        }\n\n      }\n\n    },\n\n    configure: function() {\n\n      if (sails.config.blueprints.jsonp) {\n        throw flaverr({ name: 'userError', code: 'E_JSONP_UNSUPPORTED' }, new Error('JSONP support was removed from the blueprints API in Sails 1.0 (detected sails.config.blueprints.jsonp === '  + sails.config.blueprints.jsonp + ')'));\n      }\n\n      if (!_.isUndefined(sails.config.blueprints.defaultLimit)) {\n        sails.log.debug('The `sails.config.blueprints.defaultLimit` option is no longer supported in Sails 1.0.');\n        sails.log.debug('Instead, you can use a `parseBlueprintOptions` function to fully customize blueprint behavior.');\n        sails.log.debug('See http://sailsjs.com/docs/reference/configuration/sails-config-blueprints#?using-parseblueprintoptions.');\n        sails.log.debug('(Setting the default limit to 30 in the meantime.)');\n        sails.log.debug();\n      }\n\n      if (!_.isUndefined(sails.config.blueprints.populate)) {\n        sails.log.debug('The `sails.config.blueprints.populate` option is no longer supported in Sails 1.0.');\n        sails.log.debug('Instead, you can use a `parseBlueprintOptions` function to fully customize blueprint behavior.');\n        sails.log.debug('See http://sailsjs.com/docs/reference/configuration/sails-config-blueprints#?using-parseblueprintoptions.');\n        sails.log.debug('(Will populate all associations in blueprints in the meantime.)');\n        sails.log.debug();\n      }\n\n    },\n\n    parseBlueprintOptions: require('./parse-blueprint-options'),\n\n    /**\n     * Internal list of action functions that may be bound via shadow routes.\n     * @type {Object}\n     */\n    _actions: {},\n\n    /**\n     * Initialize is fired first thing when the hook is loaded.\n     *\n     * @param  {Function} cb\n     */\n    initialize: function (cb) {\n\n      // Provide hook context to closures\n      hook = this;\n\n      // Set the _middlewareType of each blueprint action to 'BLUEPRINT: <action>'.\n      _.each(BlueprintController, function(fn, key) {\n        fn._middlewareType = 'BLUEPRINT: ' + key;\n      });\n\n      // Register route syntax for binding blueprints directly.\n      // This is deprecated, so onRoute currently just logs a warning.\n      sails.on('route:typeUnknown', onRoute);\n\n      // Wait until after user routes have been bound to bind our\n      // own \"shadow routes\" (action routes, RESTful routes,\n      // shortcut routes and index routes).\n      sails.on('router:after', hook.bindShadowRoutes);\n\n      // If the ORM hook is active, wait for it to load, then create actions\n      // for each model.\n      if (sails.hooks.orm) {\n        sails.after('hook:orm:loaded', function() {\n          hook.registerActions(cb);\n        });\n      }\n      // Otherwise we're done!\n      else {\n        return cb();\n      }\n    },\n\n\n    bindShadowRoutes: function() {\n\n      var logWarns = function(warns) {\n        sails.log.blank();\n        _.each(warns, function (warn) {\n          sails.log.warn(warn);\n        });\n        STRINGFILE.logMoreInfoLink(STRINGFILE.get('links.docs.config.blueprints'), sails.log.warn);\n      };\n\n      // Local reference to the sails blueprints config.\n      var config = sails.config.blueprints;\n\n      // Get a copy of the Sails actions dictionary.\n      var actions = sails.getActions();\n\n      // Determine whether any model is using the default archive model.\n      var defaultArchiveInUse = _.any(sails.models, function(model) { return model.archiveModelIdentity === 'archive'; });\n\n      //  ┬  ┬┌─┐┬  ┬┌┬┐┌─┐┌┬┐┌─┐  ┌─┐┬─┐┌─┐┌─┐┬─┐ ┬┌─┐┌─┐\n      //  └┐┌┘├─┤│  │ ││├─┤ │ ├┤   ├─┘├┬┘├┤ ├┤ │┌┴┬┘├┤ └─┐\n      //   └┘ ┴ ┴┴─┘┴─┴┘┴ ┴ ┴ └─┘  ┴  ┴└─└─┘└  ┴┴ └─└─┘└─┘\n\n      // Validate prefix for generated routes.\n      if ( config.prefix ) {\n        if ( !_(config.prefix).isString() ) {\n          sails.after('lifted', function () {\n            logWarns([\n              'Ignoring invalid blueprint prefix configured for controllers.',\n              '`prefix` should be a string, e.g. \"/api/v1\".'\n            ]);\n          });\n          return;\n        }\n        if ( !config.prefix.match(/^\\//) ) {\n          var originalPrefix = config.prefix;\n          sails.after('lifted', function () {\n            logWarns([\n              util.format('Invalid blueprint prefix (\"%s\") configured for controllers.', originalPrefix),\n              util.format('For now, assuming you meant:  \"%s\".', config.prefix)\n            ]);\n          });\n\n          config.prefix = '/' + config.prefix;\n        }\n      }\n\n      // Validate prefix for RESTful routes.\n      if ( config.restPrefix ) {\n        if ( !_(config.restPrefix).isString() ) {\n          sails.after('lifted', function () {\n            logWarns([\n              'Ignoring invalid blueprint rest prefix configured for controllers',\n              '`restPrefix` should be a string, e.g. \"/api/v1\".'\n            ]);\n          });\n          return;\n        }\n        if ( !config.restPrefix.match(/^\\//) ) {\n          var originalRestPrefix = config.restPrefix;\n          sails.after('lifted', function () {\n            logWarns([\n              util.format('Invalid blueprint restPrefix (\"%s\") configured for controllers (should start with a `/`).', originalRestPrefix),\n              util.format('For now, assuming you meant:  \"%s\".', config.restPrefix)\n            ]);\n          });\n\n          config.restPrefix = '/' + config.restPrefix;\n        }\n      }\n\n      //  ╔═╗╔═╗╔╦╗╦╔═╗╔╗╔  ┬─┐┌─┐┬ ┬┌┬┐┌─┐┌─┐\n      //  ╠═╣║   ║ ║║ ║║║║  ├┬┘│ ││ │ │ ├┤ └─┐\n      //  ╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝  ┴└─└─┘└─┘ ┴ └─┘└─┘\n\n      // If action routing is turned on, bind a route pointing\n      // at each action in the Sails actions dictionary\n\n      if ( config.actions ) {\n\n        // Loop through each action in the dictionary\n        _.each(actions, function(action, key) {\n          // If this is a blueprint action, only skip it.\n          // It'll be handled in the \"shortcut routes\" section,\n          // if those routes are enabled.\n          if (action._middlewareType && action._middlewareType.indexOf('BLUEPRINT') === 0) {\n            return;\n          }\n          // If this action belongs to a controller with blueprint action routes turned off, skip it.\n          if (_.any(config._controllers, function(config, controllerIdentity) {\n            return config.actions === false && key.indexOf(controllerIdentity) === 0;\n          })) {\n            return;\n          }\n\n          // Add the route prefix (if any) and bind the route to that URL.\n          var url = config.prefix + '/' + key;\n          sails.router.bind(url, key);\n        });\n\n      }\n\n\n      //  ╔═╗╦ ╦╔═╗╦═╗╔╦╗╔═╗╦ ╦╔╦╗  ┬─┐┌─┐┬ ┬┌┬┐┌─┐┌─┐\n      //  ╚═╗╠═╣║ ║╠╦╝ ║ ║  ║ ║ ║   ├┬┘│ ││ │ │ ├┤ └─┐\n      //  ╚═╝╩ ╩╚═╝╩╚═ ╩ ╚═╝╚═╝ ╩   ┴└─└─┘└─┘ ┴ └─┘└─┘\n\n      // If shortcut blueprint routing is turned on, bind CRUD routes\n      // for each model using GET-only urls.\n      if ( config.shortcuts ) {\n\n        // Loop through each model.\n        _.each(sails.models, function(Model, identity) {\n\n          if (identity === 'archive' && defaultArchiveInUse) {\n            return;\n          }\n\n          // If this there is a matching controller with blueprint shortcut routes turned off, skip it.\n          if (_.any(config._controllers, function(config, controllerIdentity) {\n            return config.shortcuts === false && identity === controllerIdentity;\n          })) {\n            return;\n          }\n\n          // Determine the base route for the model.\n          var baseShortcutRoute = (function() {\n            // Start with the model identity.\n            var baseRouteName = identity;\n            // Pluralize it if plurization option is on.\n            if (config.pluralize) {\n              baseRouteName = pluralize(baseRouteName);\n            }\n            // Add the route prefix and base route name together.\n            return config.prefix + '/' + baseRouteName;\n          })();\n\n          _bindShortcutRoute('get %s/find', 'find');\n          _bindShortcutRoute('get %s/find/:id', 'findOne');\n          _bindShortcutRoute('get %s/create', 'create');\n          _bindShortcutRoute('get %s/update/:id', 'update');\n          _bindShortcutRoute('get %s/destroy/:id', 'destroy');\n\n          // Bind \"rest\" blueprint/shadow routes based on known associations in our model's schema\n          // Bind add/remove for each `collection` associations\n          _.each(_.where(Model.associations, {type: 'collection'}), function (association) {\n            var alias = association.alias;\n            _bindAssocRoute('get %s/:parentid/%s/add/:childid', 'add', alias);\n            _bindAssocRoute('get %s/:parentid/%s/replace', 'replace', alias);\n            _bindAssocRoute('get %s/:parentid/%s/remove/:childid', 'remove', alias);\n          });\n\n          // and populate for both `collection` and `model` associations,\n          // if we didn't already do it above for RESTful routes\n          if ( !config.rest ) {\n            _.each(Model.associations, function (association) {\n              var alias = association.alias;\n              _bindAssocRoute('get %s/:parentid/%s', 'populate', alias );\n            });\n          }\n\n          function _bindShortcutRoute(template, blueprintActionName) {\n            // Get the route URL for this shortcut\n            var shortcutRoute = util.format(template, baseShortcutRoute);\n            // Bind it to the appropriate action, adding in some route options including a deep clone of the model associations.\n            // The clone prevents the blueprint action from accidentally altering the model definition in any way.\n            sails.router.bind(shortcutRoute, identity + '/' + blueprintActionName, null, { model: identity, associations: _.cloneDeep(Model.associations), autoWatch: sails.config.blueprints.autoWatch });\n          }\n\n          function _bindAssocRoute(template, blueprintActionName, alias) {\n            // Get the route URL for this shortcut\n            var assocRoute = util.format(template, baseShortcutRoute, alias);\n            // Bind it to the appropriate action, adding in some route options including a deep clone of the model associations.\n            // The clone prevents the blueprint action from accidentally altering the model definition in any way.\n            sails.router.bind(assocRoute, identity + '/' + blueprintActionName, null, { model: identity, alias: alias, associations: _.cloneDeep(Model.associations), autoWatch: sails.config.blueprints.autoWatch  });\n          }\n\n        });\n      }\n\n      //  ╦═╗╔═╗╔═╗╔╦╗  ┬─┐┌─┐┬ ┬┌┬┐┌─┐┌─┐\n      //  ╠╦╝║╣ ╚═╗ ║   ├┬┘│ ││ │ │ ├┤ └─┐\n      //  ╩╚═╚═╝╚═╝ ╩   ┴└─└─┘└─┘ ┴ └─┘└─┘\n\n      // If RESTful blueprint routing is turned on, bind CRUD routes\n      // for each model.\n      if ( config.rest ) {\n\n        // Loop throug each model.\n        _.each(sails.models, function(Model, identity) {\n\n          if (identity === 'archive' && defaultArchiveInUse) {\n            return;\n          }\n\n          // If this there is a matching controller with blueprint shortcut routes turned off, skip it.\n          if (_.any(config._controllers, function(config, controllerIdentity) {\n            return config.rest === false && identity === controllerIdentity;\n          })) {\n            return;\n          }\n\n          // Determine the base REST route for the model.\n          var baseRestRoute = (function() {\n            // Start with the model identity.\n            var baseRouteName = identity;\n            // Pluralize it if plurization option is on.\n            if (config.pluralize) {\n              baseRouteName = pluralize(baseRouteName);\n            }\n            // Add the route prefix, RESTful route prefix and base route name together.\n            return config.prefix + config.restPrefix + '/' + baseRouteName;\n          })();\n\n          _bindRestRoute('get %s', 'find');\n          _bindRestRoute('get %s/:id', 'findOne');\n          _bindRestRoute('post %s', 'create');\n          _bindRestRoute('patch %s/:id', 'update');\n          _bindRestRoute('delete %s/:id?', 'destroy');\n\n          // Bind the `put :model/:id` route to the update action, first bind a route that\n          // logs a warning about using `PUT` instead of `PATCH`.\n          // Some route options are set as well, including a deep clone of the model associations.\n          // The clone prevents the blueprint action from accidentally altering the model definition in any way.\n          sails.router.bind(\n            util.format('put %s/:id', baseRestRoute),\n            function (req, res, next) {\n              sails.log.debug('Using `PUT` to update a record is deprecated in Sails 1.0.  Use `PATCH` instead!');\n              return next();\n            }\n          );\n          _bindRestRoute('put %s/:id', 'update');\n\n          // Bind \"rest\" blueprint/shadow routes based on known associations in our model's schema\n          // Bind add/remove for each `collection` associations\n          _.each(_.where(Model.associations, {type: 'collection'}), function (association) {\n            var alias = association.alias;\n            _bindAssocRoute('put %s/:parentid/%s/:childid', 'add', alias);\n            _bindAssocRoute('put %s/:parentid/%s', 'replace', alias);\n            _bindAssocRoute('delete %s/:parentid/%s/:childid', 'remove', alias);\n\n          });\n\n          // and populate for both `collection` and `model` associations\n          _.each(Model.associations, function (association) {\n            var alias = association.alias;\n            _bindAssocRoute('get %s/:parentid/%s', 'populate', alias );\n          });\n\n          function _bindRestRoute(template, blueprintActionName) {\n            // Get the URL for the RESTful route\n            var restRoute = util.format(template, baseRestRoute);\n            // Bind it to the appropriate action, adding in some route options including a deep clone of the model associations.\n            // The clone prevents the blueprint action from accidentally altering the model definition in any way.\n            sails.router.bind(restRoute, identity + '/' + blueprintActionName, null, { model: identity, associations: _.cloneDeep(Model.associations), autoWatch: sails.config.blueprints.autoWatch  });\n          }\n\n          function _bindAssocRoute(template, blueprintActionName, alias) {\n            // Get the URL for the RESTful route\n            var assocRoute = util.format(template, baseRestRoute, alias);\n            // Bind it to the appropriate action, adding in some route options including a deep clone of the model associations.\n            // The clone prevents the blueprint action from accidentally altering the model definition in any way.\n            sails.router.bind(assocRoute, identity + '/' + blueprintActionName, null, { model: identity, alias: alias, associations: _.cloneDeep(Model.associations), autoWatch: sails.config.blueprints.autoWatch  });\n          }\n\n        });\n\n      }\n\n      //  ╦╔╗╔╔╦╗╔═╗═╗ ╦  ┬─┐┌─┐┬ ┬┌┬┐┌─┐┌─┐\n      //  ║║║║ ║║║╣ ╔╩╦╝  ├┬┘│ ││ │ │ ├┤ └─┐\n      //  ╩╝╚╝═╩╝╚═╝╩ ╚═  ┴└─└─┘└─┘ ┴ └─┘└─┘\n      //\n      //  If action routing is turned on, bind a route pointing\n      //  any action ending in `/index` to the base of that\n      //  action's path, e.g. 'user.index' => '/user'\n\n      if ( config.actions ) {\n\n        // Loop through each action in the dictionary\n        _.each(actions, function(action, key) {\n          // Does the key end in `/index` (or is it === `index`)?\n          if (key === 'index' || key.match(/\\/index$/)) {\n\n            // If this action belongs to a controller with blueprint action routes turned off, skip it.\n            if (_.any(config._controllers, function(config, controllerIdentity) {\n              return config.actions === false && key.indexOf(controllerIdentity) === 0;\n            })) {\n              return;\n            }\n\n            // Strip the `.index` off the end.\n            var index = key.replace(/\\/?index$/,'');\n            // Replace any remaining dots with slashes.\n            var url = '/' + index;\n            // Bind the url to the action.\n            sails.router.bind(url, key);\n          }\n        });\n\n      }\n\n    },\n\n    registerActions: function(cb) {\n\n      // Determine whether or not any model is using the default archive.\n      var defaultArchiveInUse = _.any(sails.models, function(model) { return model.archiveModelIdentity === 'archive'; });\n\n      // Loop through all of the loaded models and add actions for each.\n      // Even though we're adding the same exact actions for each model,\n      // (e.g. user/find and pet/find are the same), it's important that\n      // each model gets its own set so that they can have different\n      // action middleware (e.g. policies) applied to them.\n      _.each(_.keys(sails.models), function(modelIdentity) {\n\n        if (modelIdentity === 'archive' && defaultArchiveInUse) {\n          return;\n        }\n\n        sails.registerAction(BlueprintController.create, modelIdentity + '/create');\n        sails.registerAction(BlueprintController.find, modelIdentity + '/find');\n        sails.registerAction(BlueprintController.findone, modelIdentity + '/findOne');\n        sails.registerAction(BlueprintController.update, modelIdentity + '/update');\n        sails.registerAction(BlueprintController.destroy, modelIdentity + '/destroy');\n        sails.registerAction(BlueprintController.populate, modelIdentity + '/populate');\n        sails.registerAction(BlueprintController.add, modelIdentity + '/add');\n        sails.registerAction(BlueprintController.remove, modelIdentity + '/remove');\n        sails.registerAction(BlueprintController.replace, modelIdentity + '/replace');\n      });\n      return cb();\n    }\n\n  };\n\n};\n"
  },
  {
    "path": "lib/hooks/blueprints/onRoute.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar _ = require('@sailshq/lodash');\n\n\n\n// NOTE:\n// Since controllers load blueprint actions by default anyways, this route syntax handler\n// can be replaced with `{action: 'find'}, {action: 'create'}, ...` etc.\n\n\n/**\n * Expose route parser.\n * @type {Function}\n */\nmodule.exports = function(sails) {\n\n  /**\n   * interpretRouteSyntax\n   *\n   * \"Teach\" router to understand direct references to blueprints\n   * as a target to sails.router.bind()\n   * (i.e. in the `routes.js` file)\n   *\n   * @param  {[type]} route [description]\n   * @return {[type]}       [description]\n   * @api private\n   */\n  return function interpretRouteSyntax(route) {\n    var target = route.target;\n\n    if (_.isFunction(target)) {\n      throw new Error('Consistency violation: route target is a function, but is being handled by blueprint hook instead of Sails router!');\n    }\n\n    if (_.isArray(target)) {\n      throw new Error('Consistency violation: route target is an array, but is being handled by blueprint hook instead of Sails router!');\n    }\n\n    if (!_.isObject(target)) {\n      throw new Error('Consistency violation: route target is a ' + typeof(target) + ', but is being handled by blueprint hook instead of Sails router!');\n    }\n\n    // Support referencing blueprints in explicit routes\n    // (`{ blueprint: 'create' }` et. al.)\n    if (!_.isUndefined(target.blueprint)) {\n\n      var errMsg = 'The `blueprint` route target syntax is no longer supported.';\n      if (_.isString(target.blueprint) && _.isString(target.model)) {\n        errMsg = ' Use {action: \\'' + target.model.toLowerCase() + '.' + target.blueprint + '\\'} instead!';\n      }\n      sails.log.error(errMsg);\n      return;\n\n    }\n\n  };\n\n};\n"
  },
  {
    "path": "lib/hooks/blueprints/parse-blueprint-options.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\nvar flaverr = require('flaverr');\n\n\n\n/**\n * parseBlueprintOptions()\n *\n * Parse information from the request for use in a blueprint action.\n *\n * > This is just the default implementation -- it can be overridden.\n * > See http://sailsjs.com/config/blueprints for more information.\n *\n * | Term                  | Meaning\n * |:----------------------|:----------------------------------------------------------------------------------------|\n * | route option          | e.g. `model`, `alias`, `parseBlueprintOptions`, `action`, etc. (+ non-standard options)\n * | query key             | e.g. `criteria`, `newRecord`, `valuesToSet`, `meta`, `using`, etc. (fully standardized)\n * | blueprint option      | e.g. `criteria`, `newRecord`, `valuesToSet`, `meta`, `using`, etc. (fully standardized)\n *\n * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n * @param {Request} req\n *\n * @returns {Dictionary}\n *          The final dict of \"blueprint options\"; special settings that\n *          tell a blueprint action what to do when it runs.  (They are\n *          roughly equivalent to Waterline query keys.)\n * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n */\n\nmodule.exports = function parseBlueprintOptions(req) {\n\n  //  ███████╗███████╗████████╗██╗   ██╗██████╗\n  //  ██╔════╝██╔════╝╚══██╔══╝██║   ██║██╔══██╗\n  //  ███████╗█████╗     ██║   ██║   ██║██████╔╝\n  //  ╚════██║██╔══╝     ██║   ██║   ██║██╔═══╝\n  //  ███████║███████╗   ██║   ╚██████╔╝██║\n  //  ╚══════╝╚══════╝   ╚═╝    ╚═════╝ ╚═╝\n  // If you're copying code from one of the sections in the switch statement below,\n  // you'll probably also want to copy this setup code.\n\n  // Set some defaults.\n  var DEFAULT_LIMIT = 30;\n  var DEFAULT_POPULATE_LIMIT = 30;\n\n  // Get the name of the blueprint action being run.\n  var blueprint = req.options.blueprintAction;\n\n  //  ┌─┐┌─┐┬─┐┌─┐┌─┐  ┌┬┐┌─┐┌┬┐┌─┐┬\n  //  ├─┘├─┤├┬┘└─┐├┤   ││││ │ ││├┤ │\n  //  ┴  ┴ ┴┴└─└─┘└─┘  ┴ ┴└─┘─┴┘└─┘┴─┘\n\n  // Get the model identity from the action name (e.g. 'user/find').\n  var model = req.options.action.split('/')[0];\n  if (!model) { throw new Error(util.format('No \"model\" specified in route options.')); }\n\n  // Get the model class.\n  var Model = req._sails.models[model];\n  if ( !Model ) { throw new Error(util.format('Invalid route option, \"model\".\\nI don\\'t know about any models named: `%s`',model)); }\n\n  //  ┌┬┐┌─┐┌─┐┌─┐┬ ┬┬ ┌┬┐  ┌─┐┌─┐┌─┐┬ ┬┬  ┌─┐┌┬┐┌─┐┌─┐\n  //   ││├┤ ├┤ ├─┤│ ││  │   ├─┘│ │├─┘│ ││  ├─┤ │ ├┤ └─┐\n  //  ─┴┘└─┘└  ┴ ┴└─┘┴─┘┴   ┴  └─┘┴  └─┘┴─┘┴ ┴ ┴ └─┘└─┘\n\n  // Get the default populates array\n  var defaultPopulates = _.reduce(Model.associations, function(memo, association) {\n    if (association.type === 'collection') {\n      memo[association.alias] = {\n        where: {},\n        limit: DEFAULT_POPULATE_LIMIT,\n        skip: 0,\n        select: [ '*' ],\n        omit: []\n      };\n    } else {\n      memo[association.alias] = {};\n    }\n    return memo;\n  }, {});\n\n  // Initialize the queryOptions dictionary we'll be returning.\n  var queryOptions = {\n    using: model,\n    populates: defaultPopulates\n  };\n\n  switch (blueprint) {\n\n    //  ███████╗██╗███╗   ██╗██████╗         ██╗\n    //  ██╔════╝██║████╗  ██║██╔══██╗       ██╔╝\n    //  █████╗  ██║██╔██╗ ██║██║  ██║      ██╔╝\n    //  ██╔══╝  ██║██║╚██╗██║██║  ██║     ██╔╝\n    //  ██║     ██║██║ ╚████║██████╔╝    ██╔╝\n    //  ╚═╝     ╚═╝╚═╝  ╚═══╝╚═════╝     ╚═╝\n    //\n    //  ███████╗██╗███╗   ██╗██████╗  ██████╗ ███╗   ██╗███████╗\n    //  ██╔════╝██║████╗  ██║██╔══██╗██╔═══██╗████╗  ██║██╔════╝\n    //  █████╗  ██║██╔██╗ ██║██║  ██║██║   ██║██╔██╗ ██║█████╗\n    //  ██╔══╝  ██║██║╚██╗██║██║  ██║██║   ██║██║╚██╗██║██╔══╝\n    //  ██║     ██║██║ ╚████║██████╔╝╚██████╔╝██║ ╚████║███████╗\n    //  ╚═╝     ╚═╝╚═╝  ╚═══╝╚═════╝  ╚═════╝ ╚═╝  ╚═══╝╚══════╝\n\n    case 'find':\n    case 'findOne':\n\n      queryOptions.criteria = {};\n\n      //  ┌─┐┌─┐┬─┐┌─┐┌─┐  ╔═╗╦═╗╦╔╦╗╔═╗╦═╗╦╔═╗\n      //  ├─┘├─┤├┬┘└─┐├┤   ║  ╠╦╝║ ║ ║╣ ╠╦╝║╠═╣\n      //  ┴  ┴ ┴┴└─└─┘└─┘  ╚═╝╩╚═╩ ╩ ╚═╝╩╚═╩╩ ╩\n\n      queryOptions.criteria.where = (function getWhereCriteria(){\n\n        var where = {};\n\n        // For `findOne`, set \"where\" to just look at the primary key.\n        if (blueprint === 'findOne') {\n          where[Model.primaryKey] = req.param('id');\n          return where;\n        }\n\n        // Look for explicitly specified `where` parameter.\n        where = req.allParams().where;\n\n        // If `where` parameter is a string, try to interpret it as JSON.\n        // (If it cannot be parsed, throw a UsageError.)\n        if (_.isString(where)) {\n          try {\n            where = JSON.parse(where);\n          } catch (e) {\n            throw flaverr({ name: 'UsageError' }, new Error('Could not JSON.parse() the provided `where` clause. Here is the raw error: '+e.stack));\n          }\n        }//>-•\n\n        // If `where` has not been specified, but other unbound parameter variables\n        // **ARE** specified, build the `where` option using them.\n        if (!where) {\n\n          // Prune params which aren't fit to be used as `where` criteria\n          // to build a proper where query\n          where = req.allParams();\n\n          // Omit built-in runtime config (like query modifiers)\n          where = _.omit(where, ['limit', 'skip', 'sort', 'populate', 'select', 'omit']);\n\n          // Omit any params that have `undefined` on the RHS.\n          where = _.omit(where, function(p) {\n            if (_.isUndefined(p)) { return true; }\n          });\n\n        }//>-\n\n        // Return final `where`.\n        return where;\n\n      })();\n\n      //  ┌─┐┌─┐┬─┐┌─┐┌─┐  ┌─┐┌─┐┬  ┌─┐┌─┐┌┬┐\n      //  ├─┘├─┤├┬┘└─┐├┤   └─┐├┤ │  ├┤ │   │\n      //  ┴  ┴ ┴┴└─└─┘└─┘  └─┘└─┘┴─┘└─┘└─┘ ┴\n      if (!_.isUndefined(req.param('select'))) {\n        queryOptions.criteria.select = req.param('select').split(',').map(function(attribute) {return attribute.trim();});\n      }\n\n      //  ┌─┐┌─┐┬─┐┌─┐┌─┐  ┌─┐┌┬┐┬┌┬┐\n      //  ├─┘├─┤├┬┘└─┐├┤   │ │││││ │\n      //  ┴  ┴ ┴┴└─└─┘└─┘  └─┘┴ ┴┴ ┴\n      else if (!_.isUndefined(req.param('omit'))) {\n        queryOptions.criteria.omit = req.param('omit').split(',').map(function(attribute) {return attribute.trim();});\n      }\n\n      //  ┌─┐┌─┐┬─┐┌─┐┌─┐  ┬  ┬┌┬┐┬┌┬┐\n      //  ├─┘├─┤├┬┘└─┐├┤   │  │││││ │\n      //  ┴  ┴ ┴┴└─└─┘└─┘  ┴─┘┴┴ ┴┴ ┴\n\n      if (!_.isUndefined(req.param('limit'))) {\n        queryOptions.criteria.limit = req.param('limit');\n      } else {\n        queryOptions.criteria.limit = DEFAULT_LIMIT;\n      }\n\n      //  ┌─┐┌─┐┬─┐┌─┐┌─┐  ┌─┐┬┌─┬┌─┐\n      //  ├─┘├─┤├┬┘└─┐├┤   └─┐├┴┐│├─┘\n      //  ┴  ┴ ┴┴└─└─┘└─┘  └─┘┴ ┴┴┴\n\n      if (!_.isUndefined(req.param('skip'))) { queryOptions.criteria.skip = req.param('skip'); }\n\n      //  ┌─┐┌─┐┬─┐┌─┐┌─┐  ┌─┐┌─┐┬─┐┌┬┐\n      //  ├─┘├─┤├┬┘└─┐├┤   └─┐│ │├┬┘ │\n      //  ┴  ┴ ┴┴└─└─┘└─┘  └─┘└─┘┴└─ ┴\n\n      if (!_.isUndefined(req.param('sort'))) {\n        queryOptions.criteria.sort = (function getSortCriteria() {\n          var sort = req.param('sort');\n          if (_.isUndefined(sort)) {return undefined;}\n\n          // If `sort` is a string, attempt to JSON.parse() it.\n          // (e.g. `{\"name\": 1}`)\n          if (_.isString(sort)) {\n            try {\n              sort = JSON.parse(sort);\n              // If it is not valid JSON (e.g. because it's just some other string),\n              // then just fall back to interpreting it as-is (e.g. \"name ASC\")\n            } catch(unusedErr) {}\n          }\n          return sort;\n        })();\n      }\n\n      //  ┌─┐┌─┐┬─┐┌─┐┌─┐  ┌─┐┌─┐┌─┐┬ ┬┬  ┌─┐┌┬┐┌─┐\n      //  ├─┘├─┤├┬┘└─┐├┤   ├─┘│ │├─┘│ ││  ├─┤ │ ├┤\n      //  ┴  ┴ ┴┴└─└─┘└─┘  ┴  └─┘┴  └─┘┴─┘┴ ┴ ┴ └─┘\n\n      // If a `populate` param was sent, filter the attributes to populate\n      // against that value.\n      // e.g.:\n      //   /model?populate=alias1,alias2,alias3\n      //   /model?populate=[alias1,alias2,alias3]\n      if (req.param('populate')) {\n\n        queryOptions.populates = (function getPopulates() {\n          // Get the request param.\n          var attributes = req.param('populate');\n          // If it's `false`, populate nothing.\n          if (attributes === 'false') {\n            return {};\n          }\n          // Split the list on commas.\n          attributes = attributes.split(',');\n          // Trim whitespace off of the attributes.\n          attributes = _.reduce(attributes, function(memo, attribute) {\n            memo[attribute.trim()] = {};\n            return memo;\n          }, {});\n          return attributes;\n        })();\n      }\n\n      break;\n\n    //   ██████╗██████╗ ███████╗ █████╗ ████████╗███████╗\n    //  ██╔════╝██╔══██╗██╔════╝██╔══██╗╚══██╔══╝██╔════╝\n    //  ██║     ██████╔╝█████╗  ███████║   ██║   █████╗\n    //  ██║     ██╔══██╗██╔══╝  ██╔══██║   ██║   ██╔══╝\n    //  ╚██████╗██║  ██║███████╗██║  ██║   ██║   ███████╗\n    //   ╚═════╝╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝   ╚═╝   ╚══════╝\n    case 'create':\n\n      // Set `fetch: true`\n      queryOptions.meta = { fetch: true };\n\n      //  ┌─┐┌─┐┬─┐┌─┐┌─┐  ┬  ┬┌─┐┬  ┬ ┬┌─┐┌─┐\n      //  ├─┘├─┤├┬┘└─┐├┤   └┐┌┘├─┤│  │ │├┤ └─┐\n      //  ┴  ┴ ┴┴└─└─┘└─┘   └┘ ┴ ┴┴─┘└─┘└─┘└─┘\n\n      queryOptions.newRecord = (function getNewRecord(){\n\n        // Use all of the request params as values for the new record.\n        var values = req.allParams();\n\n        // Attempt to JSON parse any collection attributes into arrays.  This is to allow\n        // setting collections using the shortcut routes.\n        _.each(Model.attributes, function(attrDef, attrName) {\n          if (attrDef.collection && (!req.body || !req.body[attrName]) && (req.query && _.isString(req.query[attrName]))) {\n            try {\n              values[attrName] = JSON.parse(req.query[attrName]);\n              // If it is not valid JSON (e.g. because it's just a normal string),\n              // then fall back to interpreting it as-is\n            } catch(unusedErr) {}\n\n          }\n        });\n\n        return values;\n\n      })();\n\n\n      break;\n\n    //  ██╗   ██╗██████╗ ██████╗  █████╗ ████████╗███████╗\n    //  ██║   ██║██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔════╝\n    //  ██║   ██║██████╔╝██║  ██║███████║   ██║   █████╗\n    //  ██║   ██║██╔═══╝ ██║  ██║██╔══██║   ██║   ██╔══╝\n    //  ╚██████╔╝██║     ██████╔╝██║  ██║   ██║   ███████╗\n    //   ╚═════╝ ╚═╝     ╚═════╝ ╚═╝  ╚═╝   ╚═╝   ╚══════╝\n    case 'update':\n\n      queryOptions.criteria = {\n        where: {}\n      };\n\n      queryOptions.criteria.where[Model.primaryKey] = req.param('id');\n\n      // Note that we do NOT set `fetch: true`, because if we do so, some versions\n      // of Waterline complain that `fetch` need not be included with .updateOne().\n      // (Now that we take advantage of .updateOne() in blueprints, this is a thing.)\n      queryOptions.meta = {};\n\n      //  ┌─┐┌─┐┬─┐┌─┐┌─┐  ┬  ┬┌─┐┬  ┬ ┬┌─┐┌─┐\n      //  ├─┘├─┤├┬┘└─┐├┤   └┐┌┘├─┤│  │ │├┤ └─┐\n      //  ┴  ┴ ┴┴└─└─┘└─┘   └┘ ┴ ┴┴─┘└─┘└─┘└─┘\n\n      queryOptions.valuesToSet = (function getValuesToSet(){\n\n        // Use all of the request params as values for the new record, _except_ `id`.\n        var values = _.omit(req.allParams(), 'id');\n\n        // No matter what, don't allow changing the PK via the update blueprint\n        // (you should just drop and re-add the record if that's what you really want)\n        if (typeof values[Model.primaryKey] !== 'undefined' && values[Model.primaryKey] !== queryOptions.criteria.where[Model.primaryKey]) {\n          req._sails.log.warn('Cannot change primary key via update blueprint; ignoring value sent for `' + Model.primaryKey + '`');\n        }\n        // Make sure the primary key is unchanged\n        values[Model.primaryKey] = queryOptions.criteria.where[Model.primaryKey];\n\n        return values;\n\n      })();\n\n\n      break;\n\n    //  ██████╗ ███████╗███████╗████████╗██████╗  ██████╗ ██╗   ██╗\n    //  ██╔══██╗██╔════╝██╔════╝╚══██╔══╝██╔══██╗██╔═══██╗╚██╗ ██╔╝\n    //  ██║  ██║█████╗  ███████╗   ██║   ██████╔╝██║   ██║ ╚████╔╝\n    //  ██║  ██║██╔══╝  ╚════██║   ██║   ██╔══██╗██║   ██║  ╚██╔╝\n    //  ██████╔╝███████╗███████║   ██║   ██║  ██║╚██████╔╝   ██║\n    //  ╚═════╝ ╚══════╝╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝    ╚═╝\n    case 'destroy':\n\n      queryOptions.criteria = {};\n\n      queryOptions.criteria = {\n        where: {}\n      };\n\n      queryOptions.criteria.where[Model.primaryKey] = req.param('id');\n\n      // Set `fetch: true`\n      queryOptions.meta = { fetch: true };\n\n\n\n      break;\n\n    //   █████╗ ██████╗ ██████╗\n    //  ██╔══██╗██╔══██╗██╔══██╗\n    //  ███████║██║  ██║██║  ██║\n    //  ██╔══██║██║  ██║██║  ██║\n    //  ██║  ██║██████╔╝██████╔╝\n    //  ╚═╝  ╚═╝╚═════╝ ╚═════╝\n    case 'add':\n\n      if (!req.options.alias) {\n        throw new Error('Missing required route option, `req.options.alias`.');\n      }\n      queryOptions.alias = req.options.alias;\n\n      queryOptions.targetRecordId = req.param('parentid');\n\n      queryOptions.associatedIds = [req.param('childid')];\n\n      break;\n\n    //  ██████╗ ███████╗███╗   ███╗ ██████╗ ██╗   ██╗███████╗\n    //  ██╔══██╗██╔════╝████╗ ████║██╔═══██╗██║   ██║██╔════╝\n    //  ██████╔╝█████╗  ██╔████╔██║██║   ██║██║   ██║█████╗\n    //  ██╔══██╗██╔══╝  ██║╚██╔╝██║██║   ██║╚██╗ ██╔╝██╔══╝\n    //  ██║  ██║███████╗██║ ╚═╝ ██║╚██████╔╝ ╚████╔╝ ███████╗\n    //  ╚═╝  ╚═╝╚══════╝╚═╝     ╚═╝ ╚═════╝   ╚═══╝  ╚══════╝\n    case 'remove':\n\n      if (!req.options.alias) {\n        throw new Error('Missing required route option, `req.options.alias`.');\n      }\n      queryOptions.alias = req.options.alias;\n\n      queryOptions.targetRecordId = req.param('parentid');\n\n      queryOptions.associatedIds = [req.param('childid')];\n\n      break;\n\n    //  ██████╗ ███████╗██████╗ ██╗      █████╗  ██████╗███████╗\n    //  ██╔══██╗██╔════╝██╔══██╗██║     ██╔══██╗██╔════╝██╔════╝\n    //  ██████╔╝█████╗  ██████╔╝██║     ███████║██║     █████╗\n    //  ██╔══██╗██╔══╝  ██╔═══╝ ██║     ██╔══██║██║     ██╔══╝\n    //  ██║  ██║███████╗██║     ███████╗██║  ██║╚██████╗███████╗\n    //  ╚═╝  ╚═╝╚══════╝╚═╝     ╚══════╝╚═╝  ╚═╝ ╚═════╝╚══════╝\n    case 'replace':\n\n      if (!req.options.alias) {\n        throw new Error('Missing required route option, `req.options.alias`.');\n      }\n      queryOptions.alias = req.options.alias;\n\n      queryOptions.criteria = {};\n\n      queryOptions.criteria = {\n        where: {}\n      };\n\n      queryOptions.targetRecordId = req.param('parentid');\n\n      queryOptions.associatedIds = _.isArray(req.body) ? req.body : req.query[req.options.alias];\n\n      if (_.isString(queryOptions.associatedIds)) {\n        try {\n          queryOptions.associatedIds = JSON.parse(queryOptions.associatedIds);\n        } catch (e) {\n          throw flaverr({ name: 'UsageError', raw: e }, new Error(\n            'The associated ids provided in this request (for the `' + req.options.alias + '` collection) are not valid.  '+\n            'If specified as a string, the associated ids provided to the \"replace\" blueprint action must be parseable as '+\n            'a JSON array, e.g. `[1, 2]`.'\n            // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n            // FUTURE: Use smart example depending on the expected pk type (e.g. if string, show mongo ids instead)\n            // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n          ));\n        }//</catch>\n      }\n\n      break;\n\n    //  ██████╗  ██████╗ ██████╗ ██╗   ██╗██╗      █████╗ ████████╗███████╗\n    //  ██╔══██╗██╔═══██╗██╔══██╗██║   ██║██║     ██╔══██╗╚══██╔══╝██╔════╝\n    //  ██████╔╝██║   ██║██████╔╝██║   ██║██║     ███████║   ██║   █████╗\n    //  ██╔═══╝ ██║   ██║██╔═══╝ ██║   ██║██║     ██╔══██║   ██║   ██╔══╝\n    //  ██║     ╚██████╔╝██║     ╚██████╔╝███████╗██║  ██║   ██║   ███████╗\n    //  ╚═╝      ╚═════╝ ╚═╝      ╚═════╝ ╚══════╝╚═╝  ╚═╝   ╚═╝   ╚══════╝\n    case 'populate':\n\n      if (!req.options.alias) {\n        throw new Error('Missing required route option, `req.options.alias`.');\n      }\n\n      var association = _.find(Model.associations, {alias: req.options.alias});\n      if (!association) {\n        throw new Error('Consistency violation: `populate` blueprint could not find association `' + req.options.alias + '` in model `' + Model.globalId + '`.');\n      }\n\n      queryOptions.alias = req.options.alias;\n\n      queryOptions.criteria = {};\n\n      //  ┌─┐┌─┐┬─┐┌─┐┌─┐  ╔═╗╦═╗╦╔╦╗╔═╗╦═╗╦╔═╗\n      //  ├─┘├─┤├┬┘└─┐├┤   ║  ╠╦╝║ ║ ║╣ ╠╦╝║╠═╣\n      //  ┴  ┴ ┴┴└─└─┘└─┘  ╚═╝╩╚═╩ ╩ ╚═╝╩╚═╩╩ ╩\n\n      queryOptions.criteria = {};\n\n      queryOptions.criteria = {\n        where: {}\n      };\n\n      queryOptions.criteria.where[Model.primaryKey] = req.param('parentid');\n\n      queryOptions.populates = {};\n      queryOptions.populates[req.options.alias] = {};\n\n      // If this is a to-many association, add a `where` clause.\n      if (association.collection) {\n        queryOptions.populates[req.options.alias].where = (function getPopulateCriteria(){\n\n          var where = req.allParams().where;\n\n          // If `where` parameter is a string, try to interpret it as JSON.\n          // (If it cannot be parsed, throw a UsageError.)\n          if (_.isString(where)) {\n            try {\n              where = JSON.parse(where);\n            } catch (e) {\n              throw flaverr({ name: 'UsageError' }, new Error('Could not JSON.parse() the provided `where` clause. Here is the raw error: '+e.stack));\n            }\n          }//>-•\n\n          // If `where` has not been specified, but other unbound parameter variables\n          // **ARE** specified, build the `where` option using them.\n          if (!where) {\n\n            // Prune params which aren't fit to be used as `where` criteria\n            // to build a proper where query\n            where = req.allParams();\n\n            // Omit built-in runtime config (like top-level criteria clauses)\n            where = _.omit(where, ['limit', 'skip', 'sort', 'populate', 'select', 'omit', 'parentid']);\n            // - - - - - - - - - - - - - - - - - - - - -\n            // ^^TODO: what about `where` itself?\n            // - - - - - - - - - - - - - - - - - - - - -\n\n            // Omit any params that have `undefined` on the RHS.\n            where = _.omit(where, function(p) {\n              if (_.isUndefined(p)) { return true; }\n            });\n\n          }//>-\n\n          // Return final `where`.\n          return where;\n\n        })();\n      }\n\n      //  ┌─┐┌─┐┬─┐┌─┐┌─┐  ┌─┐┌─┐┬  ┌─┐┌─┐┌┬┐\n      //  ├─┘├─┤├┬┘└─┐├┤   └─┐├┤ │  ├┤ │   │\n      //  ┴  ┴ ┴┴└─└─┘└─┘  └─┘└─┘┴─┘└─┘└─┘ ┴\n      if (!_.isUndefined(req.param('select'))) {\n        queryOptions.populates[req.options.alias].select = req.param('select').split(',').map(function(attribute) {return attribute.trim();});\n      }\n\n      //  ┌─┐┌─┐┬─┐┌─┐┌─┐  ┌─┐┌┬┐┬┌┬┐\n      //  ├─┘├─┤├┬┘└─┐├┤   │ │││││ │\n      //  ┴  ┴ ┴┴└─└─┘└─┘  └─┘┴ ┴┴ ┴\n      else if (!_.isUndefined(req.param('omit'))) {\n        queryOptions.populates[req.options.alias].omit = req.param('omit').split(',').map(function(attribute) {return attribute.trim();});\n      }\n\n      //\n      //  ┌─┐┌─┐┬─┐┌─┐┌─┐  ┬  ┬┌┬┐┬┌┬┐\n      //  ├─┘├─┤├┬┘└─┐├┤   │  │││││ │\n      //  ┴  ┴ ┴┴└─└─┘└─┘  ┴─┘┴┴ ┴┴ ┴\n\n      if (!_.isUndefined(req.param('limit'))) {\n        queryOptions.populates[req.options.alias].limit = req.param('limit');\n      }\n      // If this is a to-many association, use the default limit if not was provided.\n      else if (association.collection) {\n        queryOptions.populates[req.options.alias].limit = DEFAULT_LIMIT;\n      }\n\n      //  ┌─┐┌─┐┬─┐┌─┐┌─┐  ┌─┐┬┌─┬┌─┐\n      //  ├─┘├─┤├┬┘└─┐├┤   └─┐├┴┐│├─┘\n      //  ┴  ┴ ┴┴└─└─┘└─┘  └─┘┴ ┴┴┴\n\n      if (!_.isUndefined(req.param('skip'))) { queryOptions.populates[req.options.alias].skip = req.param('skip'); }\n\n      //  ┌─┐┌─┐┬─┐┌─┐┌─┐  ┌─┐┌─┐┬─┐┌┬┐\n      //  ├─┘├─┤├┬┘└─┐├┤   └─┐│ │├┬┘ │\n      //  ┴  ┴ ┴┴└─└─┘└─┘  └─┘└─┘┴└─ ┴\n\n      if (!_.isUndefined(req.param('sort'))) {\n        queryOptions.populates[req.options.alias].sort = (function getSortCriteria() {\n          var sort = req.param('sort');\n          if (_.isUndefined(sort)) {return undefined;}\n\n          // If `sort` is a string, attempt to JSON.parse() it.\n          // (e.g. `{\"name\": 1}`)\n          if (_.isString(sort)) {\n            try {\n              sort = JSON.parse(sort);\n              // If it is not valid JSON (e.g. because it's just a normal string),\n              // then fall back to interpreting it as-is (e.g. \"fullName ASC\")\n            } catch(unusedErr) {}\n          }\n          return sort;\n        })();//ˆ\n      }\n\n      break;\n\n  }\n\n  return queryOptions;\n\n};\n"
  },
  {
    "path": "lib/hooks/helpers/index.js",
    "content": "/**\n * Module dependencies\n */\n\nvar fs = require('fs');\nvar path = require('path');\nvar _ = require('@sailshq/lodash');\nvar chalk = require('chalk');\nvar machine = require('machine');\nvar loadHelpers = require('./private/load-helpers');\nvar iterateHelpers = require('./private/iterate-helpers');\n\n\n/**\n * Helpers hook\n */\n\nmodule.exports = function(sails) {\n\n  // Private variable used below to keep track of whether a compatibility\n  // warning message might need to be displayed.\n  var _prereleaseCompatWarning;\n\n  return {\n\n\n    defaults: {\n      helpers: {\n        // Custom usage/miscellaneous options:\n        usageOpts: {\n          arginStyle: 'serial',\n          execStyle: 'natural'\n        },\n\n\n        // Experimental: Programmatically provide a dictionary of helpers.\n        moduleDefinitions: undefined,\n      }\n    },\n\n\n    configure: function() {\n\n      // Check SVR of the `sails` dep in this app's package.json file.\n      //\n      // If it points at a 1.0 prerelease less than `1.0.0-44`, then log a warning\n      // explaining what's going on with helpers (we needed to make a breaking\n      // change in a prerelease), and that the old way can be achieved through\n      // configuration.  Also mention that, to avoid breaking this app, we've\n      // applied the old configuration automatically, but that if the SVR in the\n      // package.json is changed, then this protection will disappear, and this\n      // app will potentially break.\n      //\n      // To resolve this, either:\n      //  • (A) configure `sails.config.helpers.usageOpts` as `{arginStyle: 'named', execStyle: 'deferred'}`\n      //  • or (B) change any code in this app that invokes helpers to take advantage of the new default style\n      //           of usage, then upgrade to the latest version of Sails v1.0 and update\n      //           your package.json file accordingly to make this warning go away.\n      if (sails.config.helpers.usageOpts.arginStyle !== 'named' || sails.config.helpers.usageOpts.execStyle !== 'deferred') {\n\n        var localSailsSVR = (function _gettingSVROfLocalSailsDep(){\n          var pjPath = path.resolve(sails.config.appPath, 'package.json');\n\n          var rawPjText;\n          try {\n            rawPjText = fs.readFileSync(pjPath, 'utf8');\n          } catch (unusedErr) {}\n\n          var appPj;\n          try {\n            appPj = JSON.parse(rawPjText);\n          } catch (unusedErr) {}\n\n          return appPj && appPj.dependencies && appPj.dependencies.sails;\n        })();//†\n\n        var prerelease = localSailsSVR && localSailsSVR.match(/1\\.0\\.0\\-(.+)$/);\n        prerelease = prerelease && prerelease[1];\n        var isMinSVRPointedAtSensitiveV1Prerelease = prerelease && Number(prerelease) < 44;\n\n        if (isMinSVRPointedAtSensitiveV1Prerelease) {\n          sails.config.helpers.usageOpts.arginStyle = 'named';\n          sails.config.helpers.usageOpts.execStyle = 'deferred';\n\n          // Note that we don't actually log the warning right now-- we won't do that\n          // until a bit later, in .initialize().  Even then, we'll only actually log\n          // the warning if there are helpers defined in the app.  (Because if there\n          // aren't any helpers, logging a warning would just be annoying!)\n          _prereleaseCompatWarning =\n          '---------------------------------------------------------------------\\n'+\n          'Based on its package.json file, it looks like this app was built\\n'+\n          'using the Sails beta, but with a version prior to v1.0.0-44.\\n'+\n          '(This app depends on sails@'+localSailsSVR+'.)\\n'+\n          '\\n'+\n          'In the 1.0.0-44 prerelease of Sails, changes were introduced.  By\\n'+\n          'default, helpers now expect serial arguments instead of a dictionary\\n'+\n          'of named parameters.  In other words, you\\'d now call:\\n'+\n          '  await sails.helpers.passwords.changePassword(\\'abc123\\')\\n'+\n          'Instead of:\\n'+\n          '  await sails.helpers.passwords.changePassword({password:\\'abc123\\'})\\n'+\n          '\\n'+\n          'Additionally, it is no longer necessary to call .now() or .execSync()\\n'+\n          'for synchronous helpers-- by default they are invoked automatically.\\n'+\n          '(Not a fan?  Sorry about the inconvenience!  And don\\'t worry, it\\'s\\n'+\n          'easy to change.)\\n'+\n          '\\n'+\n          'To avoid breaking this app, some special settings that make Sails\\n'+\n          'backwards-compatible have been set automatically for you.  But please\\n'+\n          'be sure to take the steps below to resolve this as soon as possible.\\n'+\n          '(What if you forgot about this and changed your package.json file?\\n'+\n          'You might inadvertently remove this compatibility check...  And if\\n'+\n          'that were to happen, the next time you tried to lift your app, your\\n'+\n          'helpers would no longer work!)\\n'+\n          '\\n'+\n          'To resolve this, use one of the following solutions:\\n'+\n          '\\n'+\n          ' (A) <<<<Quick & dirty>>>>\\n'+\n          ' If you need a quick fix, or you just prefer to call helpers the old\\n'+\n          ' way, no problem: just nestle this in your .sailsrc file:\\n'+\n          '    \"helpers\": {\\n'+\n          '      \"usageOpts\": {\\n'+\n          '        \"arginStyle\": \"named\",\\n'+\n          '        \"execStyle\": \"deferred\"\\n'+\n          '      }\\n'+\n          '    }\\n'+\n          ' ^^That will make helpers behave exactly like they did before.\\n'+\n          '\\n'+\n          ' (B) <<<<Recommended>>>>\\n'+\n          ' Change any relevant code in this app (e.g. `sails.helpers.x({…})`)\\n'+\n          ' to take advantage of serial usage, or chain on .with({…}).  Then, \\n'+\n          ' update the `sails` dependency in your package.json file so that it\\n'+\n          ' satisfies ^1.0.0-44 or higher.\\n'+\n          '\\n'+\n          ' Note: If you go with this approach, it\\'s not all or nothing.  You\\n'+\n          ' you can always use .with() to call a helper with named parameters\\n'+\n          ' on a one-off basis.  For example:\\n'+\n          '   await sails.helpers.changePassword.with({password:\\'abc123\\'});\\n'+\n          '\\n'+\n          '\\n'+\n          '(To hide this message, apply one of the solutions suggested above.)\\n'+\n          '\\n'+\n          ' [?] If you\\'re unsure, visit https://sailsjs.com/support\\n'+\n          '---------------------------------------------------------------------\\n';\n        }//ﬁ\n      }//ﬁ\n\n      // Define `sails.helpers` here so that it can potentially be used by other hooks.\n      // > NOTE: This is NOT `sails.config.helpers`-- this is `sails.helpers`!\n      // > (As for sails.config.helpers, it's set automatically based on our `defaults above)\n      sails.helpers = {};\n      Object.defineProperty(sails.helpers, Symbol.for('nodejs.util.inspect.custom'), {\n        enumerable: false,\n        configurable: false,\n        writable: true,\n        value: function inspect(){\n\n          // Tree diagram:\n          // ```\n          //    .\n          //    ├── …\n          //    │   ├── …\n          //    │   │   └── …\n          //    │   ├── …\n          //    │   └── …\n          //    │\n          //    ├── …\n          //    │   ├── …\n          //    │   │   └── …\n          //    │   ├── …\n          //    │   │   ├── …\n          //    │   │   ├── …\n          //    │   │   └── …\n          //    │   ├── …\n          //    │   └── …\n          //    │\n          //    ├── …\n          //    │   └── …\n          //    │\n          //    ├── …\n          //    ├── …\n          //    └── …\n          // ```\n          var treeDiagram = (function(){\n            var OFFSET             = '   ';\n            var TAB                = '    ';\n            var SYMBOL_INITIAL     = '.   ';\n            var SYMBOL_NO_BRANCH   = '│   ';\n            var SYMBOL_MID_BRANCH  = '├── ';\n            var SYMBOL_LAST_BRANCH = '└── ';\n\n            var treeDiagram = '';\n            treeDiagram += OFFSET + SYMBOL_INITIAL + '\\n';\n            iterateHelpers(\n              sails.helpers,\n              function _onBeforeStartingPack(pack, key, depth, isFirst, isLast, lastnessPerAncestor){\n                var indentation = _.reduce(lastnessPerAncestor, function(indentation, wasLast){\n                  if (wasLast) {\n                    indentation += TAB;\n                  } else {\n                    indentation += SYMBOL_NO_BRANCH;\n                  }\n                  return indentation;\n                }, '');\n\n                if (isLast) {\n                  treeDiagram += OFFSET + indentation + SYMBOL_LAST_BRANCH + key + '\\n';\n                } else {\n                  treeDiagram += OFFSET + indentation + SYMBOL_MID_BRANCH + key + '\\n';\n                }\n              },\n              undefined,// « no need for an _onAfterFinishingPack notifier here, so we omit it\n              function _onHelper(callable, methodName, depth, isFirst, isLast, lastnessPerAncestor){\n                var indentation = _.reduce(lastnessPerAncestor, function(indentation, wasLast){\n                  if (wasLast) {\n                    indentation += TAB;\n                  } else {\n                    indentation += SYMBOL_NO_BRANCH;\n                  }\n                  return indentation;\n                }, '');\n\n                if (isLast) {\n                  treeDiagram += OFFSET + indentation + SYMBOL_LAST_BRANCH + (callable.toJSON()._fromLocalSailsApp ? chalk.bold.cyan(methodName) : chalk.italic(methodName)) + chalk.gray('()')+'\\n';\n                  if (depth === 2) {\n                    treeDiagram += OFFSET + indentation + '\\n';\n                  }\n                } else {\n                  treeDiagram += OFFSET + indentation + SYMBOL_MID_BRANCH + (callable.toJSON()._fromLocalSailsApp ? chalk.bold.cyan(methodName) : chalk.italic(methodName)) + chalk.gray('()')+'\\n';\n                }\n              }\n            );\n            return treeDiagram;\n          })();//†\n\n          // Examples (asynchronous and synchronous)\n          var example1 = (function(){\n            var exampleArginPhrase = '';\n            if (sails.config.helpers.usageOpts.arginStyle === 'named') {\n              exampleArginPhrase = '{dir: \\'./colorado/\\'}';\n            } else if (sails.config.helpers.usageOpts.arginStyle === 'serial') {\n              exampleArginPhrase = '\\'./colorado/\\'';\n            }\n\n            return 'var contents = await sails.helpers.fs.ls('+exampleArginPhrase+');';\n          })();//†\n          var example2 = (function(){\n            var exampleArginPhrase = '';\n            if (sails.config.helpers.usageOpts.arginStyle === 'named') {\n              exampleArginPhrase = '{style: \\'url-friendly\\'}';\n            } else if (sails.config.helpers.usageOpts.arginStyle === 'serial') {\n              exampleArginPhrase = '\\'url-friendly\\'';\n            }\n\n            if (sails.config.helpers.usageOpts.execStyle === 'deferred') {\n              return 'var name = sails.helpers.strings.random('+exampleArginPhrase+').now();';\n            } else if (sails.config.helpers.usageOpts.execStyle === 'immediate' || sails.config.helpers.usageOpts.execStyle === 'natural') {\n              return 'var name = sails.helpers.strings.random('+exampleArginPhrase+');';\n            }\n            throw new Error('Consistency violation: Unrecognized arginStyle/execStyle in sails.config.helpers.usageOpts  (This should never happen, since it should have already been validated and prevented from being built- please report at https://sailsjs.com/bugs)');\n          })();//†\n\n          return ''+\n          '-------------------------------------------------------\\n'+\n          ' sails.helpers\\n'+\n          '\\n'+\n          ' Available methods:\\n'+\n          treeDiagram+'\\n'+\n          '\\n'+\n          ' Example usage:\\n'+\n          '   '+example1+'\\n'+\n          '   '+example2+'\\n'+\n          '\\n'+\n          ' More info:\\n'+\n          '   https://sailsjs.com/support\\n'+\n          '-------------------------------------------------------\\n';\n        }//ƒ\n      });//…)\n\n    },\n\n\n    initialize: function(done) {\n\n      // Load helpers from the appropriate folder.\n      loadHelpers(sails, function(err) {\n        if (err) { return done(err); }\n\n        // If deemed relevant, log the prerelease compatibility warning that\n        // we built above.  (Then clear it out, since we don't want to ever\n        // display it again during this \"lift\" cycle-- even if the experimental\n        // .reload() method is in use.)\n        if (_prereleaseCompatWarning && _.keys(sails.helpers).length > 0) {\n          sails.log.warn(_prereleaseCompatWarning);\n          _prereleaseCompatWarning = '';\n        }\n\n        return done();\n      });//_∏_\n\n    },\n\n    /**\n     * @experimental\n     * (This might change at any time, without a major version release!)\n     */\n    furnishPack: function(slug, packInfo){\n      packInfo = packInfo || {};\n      slug = _.map(slug.split('.'), _.kebabCase).join('.');\n      var slugKeyPath = _.map(slug.split('.'), _.camelCase).join('.');\n      var chunks = slugKeyPath.split('.');\n\n      if (chunks.length > 1) {\n        sails.log.verbose(\n          'Watch out!  Nesting helpers more than one sub-folder deep can be a liability.  '+\n          'It also means that you\\'ll need to type more every time you want to use '+\n          'your helper.  Instead, try keeping your directory structure as flat as possible; '+\n          'i.e. in general, having more explicit filenames is better than having deep, '+\n          'complicated folder hierarchies.'\n        );\n      }\n\n      // If pack already exists, avast.\n      if (_.get(sails.helpers, slugKeyPath)) {\n        return;\n      }\n\n      // Ancestor packs:\n      var thisKeyPath;\n      var theseChunks;\n      var parentKeyPath;\n      var parentPackOrRoot;\n      for (var i = 0; i < chunks.length - 1; i++) {\n        theseChunks = chunks.slice(0,i+1);\n        thisKeyPath = theseChunks.join('.');\n        parentKeyPath = theseChunks.slice(0, -1).join('.');\n        if (!_.get(sails.helpers, thisKeyPath)) {\n          parentPackOrRoot = parentKeyPath ? _.get(sails.helpers, parentKeyPath) : sails.helpers;\n          parentPackOrRoot[chunks[i]] = machine.pack({\n            name: 'sails.helpers.'+chunks.slice(0,i+1).join('.'),\n            defs: {},\n            customize: _.extend({}, sails.config.helpers.usageOpts, {\n              implementationSniffingTactic: sails.config.implementationSniffingTactic||undefined\n            })\n          });\n        }\n      }//∞\n\n      // Main pack:\n      parentKeyPath = chunks.slice(0, -1).join('.');\n      parentPackOrRoot = parentKeyPath ? _.get(sails.helpers, parentKeyPath) : sails.helpers;\n      parentPackOrRoot[chunks[chunks.length - 1]] = machine.pack(_.extend({}, packInfo, {\n        name: 'sails.helpers.'+slugKeyPath,\n        customize: _.extend({}, sails.config.helpers.usageOpts, {\n          implementationSniffingTactic: sails.config.implementationSniffingTactic||undefined\n        })\n      }));\n    },\n\n    /**\n     * @experimental\n     * (This might change at any time, without a major version release!)\n     */\n    furnishHelper: function(identityPlusMaybeSlug, nmDef){\n\n      // Ensure we're starting off with dot-delimited, kebab-cased hops.\n      identityPlusMaybeSlug = _.map(identityPlusMaybeSlug.split('.'), _.kebabCase).join('.');\n\n      var chunks = identityPlusMaybeSlug.split('.');\n\n      // slug ('foo-bar.baz-bing.beep.boop')\n      // identity ('do-something')\n      var slug = chunks.length >= 2 ? chunks.slice(0, -1).join('.') : undefined;\n      var identity = _.last(chunks);\n\n      // Camel-case every part of the file path, and join with dots\n      // e.g. admin-stuff.foo.do-something => adminStuff.foo.doSomething\n      var slugKeyPath = slug ? _.map(slug.split('.'), _.camelCase).join('.') : undefined;\n      var fullKeyPath = slug ? slugKeyPath + '.' + machine.getMethodName(identity) : machine.getMethodName(identity);\n\n      if (!_.get(sails.helpers, fullKeyPath)) {\n\n        // Work our way down\n        if (slug && !_.get(sails.helpers, slugKeyPath)) {\n          this.furnishPack(slug, {\n            name: 'sails.helpers.'+slugKeyPath,\n            defs: {}\n          });\n        }//ﬁ\n\n        // And then build the helper last\n        // > (can't do it first!  We'd confuse `_.get()`!)\n\n        // Use provided `identity` if no explicit identity was set.\n        // (Otherwise, as of machine@v15, this could fail with an ImplementationError.)\n        if (!nmDef.identity) {\n          nmDef.identity = identity;\n        }\n\n        // Attach new method to the appropriate pack.\n        // e.g. sails.helpers.userHelpers.foo.myHelper\n        if (slug) {\n          var parentPack = _.get(sails.helpers, slugKeyPath);\n          parentPack.registerDefs(\n            (function(){\n              var defs = {};\n              defs[identity] = nmDef;\n              return defs;\n            })()//†\n          );\n        } else {\n          sails.helpers[machine.getMethodName(identity)] = machine.buildWithCustomUsage(_.extend(\n            {},\n            sails.config.helpers.usageOpts,\n            {\n              def: nmDef,\n              implementationSniffingTactic: sails.config.implementationSniffingTactic\n            }\n          ));\n        }\n\n      }//ﬁ\n\n    },\n\n\n    /**\n     * sails.hooks.helpers.reload()\n     *\n     * @param  {Dictionary?}   helpers [if specified, these helpers will replace all existing helpers.  Otherwise, if omitted, helpers will be freshly reloaded from disk, and old helpers will be thrown away.]\n     * @param  {Function} done    [optional callback]\n     *\n     * @experimental\n     * (This might change at any time, without a major version release!)\n     */\n    reload: function(helpers, done) {\n\n      // Handle variadic usage\n      if (typeof helpers === 'function') {\n        done = helpers;\n        helpers = undefined;\n      }\n\n      // Handle optional callback\n      done = done || function _noopCb(err){\n        if (err) {\n          sails.log.error('Could not reload helpers due to an error:', err, '\\n(continuing anyway...)');\n        }\n      };//ƒ\n\n      // If we received an explicit set of helpers to load, use them.\n      // Otherwise reload helpers from the appropriate folder.\n      if (helpers) {\n        sails.helpers = helpers;\n        return done();\n      } else {\n        return loadHelpers(sails, done);\n      }\n    }//ƒ\n\n\n  };\n\n};\n"
  },
  {
    "path": "lib/hooks/helpers/private/iterate-helpers.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\n\n\n/**\n * iterateHelpers()\n *\n * @private\n * ---------------------------------------------------------------\n * @param  {Dictionary} root\n *         the initial pack, or `sails.helpers`\n *\n * @param  {Function?} onBeforeRecurse(branch,key,depth,isFirst,isLast,lastnessPerAncestor)\n *\n * @param  {Function?} onAfterRecurse(branch,key,depth,isFirst,isLast,lastnessPerAncestor)\n *\n * @param  {Function?} onLeaf(leaf,key,depth,isFirst,isLast,lastnessPerAncestor)\n * ---------------------------------------------------------------\n */\nmodule.exports = function iterateHelpers(initialPackOrRoot, onBeforeRecurse, onAfterRecurse, onLeaf){\n\n  (function $recurse(parentPackOrRoot, lastnessPerAncestor, depth){\n\n    // Build an array of keys, sorted by:\n    //  • packs first, then helpers\n    //  • alphabetical by key after that\n    var keys = _.sortByAll(_.keys(parentPackOrRoot), function(key){\n      var branch = parentPackOrRoot[key];\n      if (!_.isFunction(branch) && branch.toJSON && branch.toJSON().defs) {\n        //(pack)\n        return '________'+key;\n      } else if (_.isFunction(branch) && branch.toJSON && branch.toJSON().identity) {\n        //(helper)\n        return key;\n      } else {\n        //(mystery meat?)\n        return Infinity;\n      }\n    });\n\n    _.each(keys, function(key, keyIdx){\n      var branch = parentPackOrRoot[key];\n      var isFirst = keyIdx === 0;\n      var isLast = keyIdx === keys.length - 1;\n\n      // Duck-type this branch and handle it accordingly.\n      if (!_.isFunction(branch) && branch.toJSON && branch.toJSON().defs) {\n        // (pack)\n        if (onBeforeRecurse) {\n          onBeforeRecurse(branch, key, depth + 1, isFirst, isLast, lastnessPerAncestor);\n        }\n        $recurse(branch, lastnessPerAncestor.concat([isLast]), depth + 1);\n        if (onAfterRecurse) {\n          onAfterRecurse(branch, key, depth + 1, isFirst, isLast, lastnessPerAncestor);\n        }\n      } else if (_.isFunction(branch) && branch.toJSON && branch.toJSON().identity) {\n        // (helper)\n        if (onLeaf) {\n          onLeaf(branch, key, depth + 1, isFirst, isLast, lastnessPerAncestor);\n        }\n      } else {\n        // (mystery meat?)\n        // ignore it.\n      }\n    });//∞\n\n  })(initialPackOrRoot, [], 0);//‰\n\n};//ƒ\n"
  },
  {
    "path": "lib/hooks/helpers/private/load-helpers.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar flaverr = require('flaverr');\nvar includeAll = require('include-all');\n\n\n\n/**\n * loadHelpers()\n *\n * Load helper definitions from disk, build them into Callables, then attach\n * them to the `sails.helpers` dictionary.\n *\n * @param {SailsApp} sails\n * @param {Function} done\n *        @param {Error?} err\n */\nmodule.exports = function loadHelpers(sails, done) {\n\n  // Load helper defs out of the specified folder\n  includeAll.optional({\n    dirname: sails.config.paths.helpers,\n    filter: /^([^.]+)\\.(?:(?!md|txt).)+$/,\n    flatten: true,\n    keepDirectoryPath: true\n  }, function(err, helperDefs) {\n    if (err) { return done(err); }\n\n    // If any helpers were specified when loading Sails, add those on\n    // top of the ones loaded from disk.  (Experimental)\n    if (sails.config.helpers.moduleDefinitions) {\n      // Note that this is a shallow merge!\n      _.extend(helperDefs, sails.config.helpers.moduleDefinitions);\n    }\n\n    try {\n      // Loop through each helper def, attempting to build each one as\n      // a Callable (a.k.a. \"wet machine\")\n      _.each(helperDefs, function(helperDef, identity) {\n        try {\n          // Camel-case every part of the file path, and join with dots\n          // e.g. /user-helpers/foo/my-helper => userHelpers.foo.myHelper\n          var keyPath = _.map(identity.split('/'), _.camelCase).join('.');\n\n          // Save _loadedFrom property for debugging purposes.\n          // (e.g. `financial/calculate-mortgage-series`)\n          helperDef._loadedFrom = identity;\n\n          // Save _fromLocalSailsApp for internal use.\n          helperDef._fromLocalSailsApp = true;\n\n          // Use filename-derived `identity` REGARDLESS if an explicit identity\n          // was set.  (And exclude any extra hierarchy.)  Otherwise, as of\n          // machine@v15, this could fail with an ImplementationError.\n          helperDef.identity = identity.match(/\\//) ? _.last(identity.split('/')) : identity;\n\n          // Check helper def to make sure it doesn't include any obvious signs\n          // of confusion with actions -- e.g. no \"responseType\".  If anything\n          // like that is detected, log a warning.\n          if (helperDef.files) {\n            sails.log.warn(\n              'Ignoring unexpected `files` property in helper definition loaded '+\n              'from '+helperDef._loadedFrom+'.  This feature can only be used '+\n              'by actions, not by helpers!'\n            );\n          }\n          var hasAnyConfusingExitProps = (\n            _.isObject(helperDef.exits) &&\n            _.any(helperDef.exits, function(exitDef){\n              return (\n                _.isObject(exitDef) &&\n                (\n                  exitDef.responseType !== undefined ||\n                  exitDef.viewTemplatePath !== undefined ||\n                  exitDef.statusCode !== undefined\n                )\n              );\n            })\n          );\n          if (hasAnyConfusingExitProps) {\n            sails.log.warn(\n              'Ignoring unexpected property in one of the exits of the helper '+\n              'definition loaded from '+helperDef._loadedFrom+'.  Features like '+\n              '`responseType`, `viewTemplatePath`, and `statusCode` can only be '+\n              'used by actions, not by helpers!'\n            );\n          }\n\n          // Build & expose helper on `sails.helpers`\n          // > e.g. sails.helpers.userHelpers.foo.myHelper\n          sails.hooks.helpers.furnishHelper(keyPath, helperDef);\n        } catch (err) {\n          // If an error occurs building the callable, throw here to bust\n          // out of the _.each loop early\n          throw flaverr({\n            code: 'E_FAILED_TO_BUILD_CALLABLE',\n            identity: helperDef.identity,\n            loadedFrom: identity,\n            raw: err\n          }, err);\n        }\n      });//∞\n\n    } catch (err) {\n\n      // Handle any errors building Callables for our helpers by sending the\n      // errors through the hook callback, which will cause Sails to halt lifting.\n      if (flaverr.taste('E_FAILED_TO_BUILD_CALLABLE', err)) {\n        return done(flaverr({\n          message: 'Failed to load helper `' + err.loadedFrom +'` into a Callable!  '+err.message\n        }, err));\n      } else {\n        return done(err);\n      }\n\n    }//</ caught >\n\n    // --• Everthing worked!\n    return done();\n\n  });\n\n};\n"
  },
  {
    "path": "lib/hooks/http/README.md",
    "content": "# http (Core Hook)\n\n## Status\n\n> ##### Stability: [2](https://github.com/balderdashy/sails-docs/blob/master/contributing/stability-index.md) - Stable\n\n\n## Dependencies\n\nIn order for this hook to load, the following other hooks must have already finished loading:\n\n- moduleloader\n\n\n## Peers\n\nIf the following other core hooks are enabled, the behavior of this hook will change:\n\n- session\n- views\n\n\n## Dependents\n\nIf this hook is disabled, in order for Sails to load, the following other core hooks must also be disabled:\n\n- views\n\n\n\n## Purpose\n\nThis hook's responsibilities are:\n\n##### Start an HTTP server and handle requests\n\nThis hook starts an http or https server and listens for incoming requests when Sails core\nemits an event letting us know it's time to \"lift\" (rather than just \"load\").\n\n##### Bind the configured middleware, along with built-in defaults\n\nThis hook binds built-in HTTP middleware, in addition to custom middleware functions (`sails.config.http.middleware`).\nThe order in which middleware can be bound is configurable in `sails.config.http.middleware.order`.\n\n> Note that it is possible for the configured HTTP middleware stack to be shared with the\n> core router built into Sails-- this would make the same stack take effect for all virtual requests\n> including sockets.  Currently, an abbreviated version of this stack is built-in to `lib/router/`\n> in an imperative way (rather than the declarative approach used here: a sorted array of named middleware).\n>\n> In Sails core, this has been explored in a number of different ways in the past.\n> In the future, it would be possible to add a separate middleware stack configuration for virtual\n> requests (including socket requests).  However, while this would certainly be more consistent, in practice,\n> this would have an unwanted impact on performance.\n\n\n\n\n## Implicit Defaults\n\nThis hook sets the following implicit default configuration on `sails.config`:\n\n> **TODO: document**\n>\n> _(if you'd like to help, please send a pull request expanding this section.  See `hooks/logger/README.md` for an example)_\n\n\n\n## Events\n\n##### `hook:http:loaded`\n\nEmitted when this hook has been automatically loaded by Sails core, and triggered the callback in its `initialize` function.\n\n\n\n## Methods\n\n> **TODO: document**\n>\n> _(if you'd like to help, please send a pull request expanding this section.  See `hooks/responses/README.md` for an example)_\n\n\n\n## FAQ\n\n> If you have a question about this hook that isn't covered here, please feel free to send a PR adding it to this section (even if you don't have the answer, a core maintainer will merge your PR and add an answer as soon as possible)\n\n"
  },
  {
    "path": "lib/hooks/http/get-configured-http-middleware-fns.js",
    "content": "/**\n * Module dependencies\n */\n\nvar Path = require('path');\nvar util = require('util');\nvar flaverr = require('flaverr');\nvar _ = require('@sailshq/lodash');\n\n\n\n/**\n * getBuiltInHttpMiddleware()\n *\n * Return a dictionary containing all built-in middleware in Sails,\n * applying configuration along the way.\n *\n * @param  {Router} expressRouterMiddleware [i.e. `app.router`]\n * @param  {SailsApp} sails\n * @return {Dictionary}\n *         @property {Function} *\n *             @param {Request} req\n *             @param {Response} res\n *             @param {Function} next\n */\nmodule.exports = function getBuiltInHttpMiddleware (expressRouterMiddleware, sails) {\n\n  // Note that the environment of a Sails app is officially determined by\n  // `sails.config.environment`. Normally, that is identical to what you'll\n  // find inside `process.env.NODE_ENV`.\n  //\n  // However it is possible for NODE_ENV and `sails.config.environment to vary\n  // (e.g. `sails.config.environment==='staging'` and `process.env.NODE_ENV==='production'`).\n  //\n  // Some middleware _depends on the NODE_ENV environment variable_ to determine\n  // its behavior.  Since NODE_ENV may have been set automatically, this is why the\n  // relevant requires are included _within_ this function, rather than up top.\n  //\n  // This is also why the NODE_ENV environment variable is used here to determine\n  // whether or not to consider the app \"in production\".  This way, if you set\n  // `NODE_ENV=production` explicitly, you can still use something like \"staging\"\n  // or \"sandbox\" for your `sails.config.environment` in order to take advantage\n  // of env-specific config files; while still having dependencies work like they\n  // will in production (since NODE_ENV is set).\n  //\n  var IS_NODE_ENV_PRODUCTION = (process.env.NODE_ENV === 'production');\n\n\n\n  return _.defaults(sails.config.http.middleware || {}, {\n\n    // Configure flat file server to serve static files\n    // (By default, all explicit+shadow routes take precedence over flat files)\n    www: (function() {\n      var flatFileMiddleware = require('serve-static')(sails.config.paths['public'], {\n        maxAge: sails.config.http.cache\n      });\n\n      return flatFileMiddleware;\n    })(),\n\n    // If a Connect session store is configured, hook it up to Express\n    session: (function() {\n      // Silently do nothing if there's no session hook.\n      // You can still have session middleware without the session hook enabled,\n      // you just have to provide it yourself by configuring sails.config.http.middleware.session.\n      if (!sails.hooks.session) {\n        sails.log.verbose('Cannot load default HTTP session middleware when Sails session hook is disabled.  Skipping...');\n        return;\n      }\n      // Complain a bit louder if the session hook is enabled, but not configured.\n      if (!sails.config.session) {\n        sails.log.error('Cannot load default HTTP session middleware without `sails.config.session` configured.  Skipping...');\n        return;\n      }\n\n      var configuredSessionMiddleware = sails._privateSessionMiddleware;\n\n      return function session(req, res, next){\n\n        // --•\n        // Run the session middleware.\n        configuredSessionMiddleware(req,res,function (err) {\n          if (!err) {\n            return next();\n          }\n\n          var errMsg = 'Error occurred in session middleware :: ' + util.inspect((err&&err.stack)?err.stack:err, false, null);\n          sails.log.error(errMsg);\n\n          // If headers have already been sent (e.g. because of timing issues in application-level code),\n          // then don't attempt to send another response.\n          // (but still log a warning)\n          if (res.headersSent) {\n            sails.log.warn('The session middleware encountered an error and triggered its callback, but response headers have already been sent.  Rather than attempting to send another response, failing silently...');\n            return;\n          }\n\n          // --•\n          // Otherwise, we can go ahead and send a response.\n          return res.status(400).send(errMsg);\n        });\n      };\n\n    })(),\n\n\n    // Build configured favicon mwr function.\n    favicon: (function (){\n      var toServeFavicon = require('serve-favicon');\n      var pathToDefaultFavicon = Path.resolve(__dirname,'./default-favicon.ico');\n      var serveFaviconMwr = toServeFavicon(pathToDefaultFavicon);\n      return serveFaviconMwr;\n    })(),\n\n\n    cookieParser: (function() {\n\n      var cookieParser = sails.config.http.middleware.cookieParser;\n      if (!cookieParser) {\n        cookieParser = require('cookie-parser');\n      }\n\n      var sessionSecret = sails.config.session && sails.config.session.secret;\n\n      // If available, Sails uses the configured session secret for signing cookies.\n      if (sessionSecret) {\n        // Ensure secret is a string.  This check happens in the session hook as well,\n        // but sails.config.session.secret may still be provided even if the session hook\n        // is turned off, so to be extra anal we'll check here as well.\n        if (!_.isString(sessionSecret)) {\n          throw flaverr({ name: 'userError', code: 'E_INVALID_SESSION_SECRET' }, new Error('If provided, sails.config.session.secret should be a string.'));\n        }\n        return cookieParser(sessionSecret);\n      }\n      // If no session secret was provided in config\n      // (e.g. if session hook is disabled and config/session.js is removed)\n      // then we do not enable signed cookies by providing a cookie secret.\n      // (note that of course signed cookies can still be enabled in a Sails app:\n      // see conceptual docs on disabling the session hook for info)\n      else {\n        return cookieParser();\n      }\n    })(),\n\n    compress: IS_NODE_ENV_PRODUCTION && require('compression')(),\n\n\n    // Configures the middleware function used for parsing the HTTP request body, if enabled.\n    bodyParser: (function() {\n\n      var opts = {};\n      var fn;\n\n      opts.onBodyParserError = function (err, req, res, next) {// eslint-disable-line no-unused-vars\n        // Note that we _need_ all four arguments in order for this function\n        // to have special meaning as an error handler (i.e. to Express)\n\n        var bodyParserFailureErrorMsg = 'Unable to parse HTTP body- error occurred :: ' + util.inspect((err&&err.stack)?err.stack:err, false, null);\n        sails.log.error(bodyParserFailureErrorMsg);\n        if (IS_NODE_ENV_PRODUCTION) {\n          return res.status(400).send();\n        }\n        return res.status(400).send(bodyParserFailureErrorMsg);\n      };\n\n      // Handle original bodyParser config:\n      ////////////////////////////////////////////////////////\n      // If a body parser was configured, use it\n      if (sails.config.http.bodyParser) {\n        fn = sails.config.http.bodyParser;\n        return fn(opts);\n      } else if (sails.config.http.bodyParser === false) {\n        // Allow for explicit disabling of bodyParser using traditional\n        // `express.bodyParser` conf\n        return undefined;\n      }\n\n      // Default to built-in bodyParser:\n      fn = require('skipper');\n      return fn(opts);\n\n    })(),\n\n\n    // Add powered-by Sails header\n    poweredBy: function xPoweredBy(req, res, next) {\n      res.header('X-Powered-By', 'Sails <sailsjs.com>');\n      next();\n    }\n\n  });\n};\n"
  },
  {
    "path": "lib/hooks/http/index.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar path = require('path');\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\nvar flaverr = require('flaverr');\nvar toStartServer = require('./start');\nvar toInitializeHttpHook = require('./initialize');\n\n\nmodule.exports = function(sails) {\n\n\n  /**\n   * Expose `http` hook definition\n   */\n\n  return {\n\n\n    defaults: {\n\n      // Self-awareness: the host the server *thinks it is*\n      // (this is necessary for some production environments-- only set it if you _absolutely_ need it)\n      explicitHost: undefined,\n\n      // Port to run this app on\n      port: 1337,\n\n      // SSL cert settings end up here\n      ssl: {},\n\n      // Path static files will be served from\n      // Uses `path.resolve()` to accept either:\n      //  • an absolute path\n      //  • a relative path from the app root (sails.config.appPath)\n      paths: {\n        public: '.tmp/public'\n      },\n\n\n      // New http-only middleware config\n      // (provides default middleware)\n      http: {\n        middleware: {\n          order: [\n            'cookieParser',\n            'session',\n            'bodyParser',\n            'compress',\n            'poweredBy',\n            'router',\n            'www',\n            'favicon',\n          ],\n\n          // Built-in HTTP middleware functions are injected after the express\n          // app instance has been created (i.e. `app`). See `./initialize.js`\n          // and `./get-configured-http-middleware-fns.js` in this hook for details.\n\n        },\n\n        // HTTP cache configuration\n        //\n        // > Implicit default in production is 365.25 days (in dev: 1 milisecond).\n        // FUTURE: remove implicit production default, and if this is production\n        // and no cache was set, log a warning (in `configure`)\n        cache: process.env.NODE_ENV !== 'production' ? 1 : 31557600000,\n\n        // Extra options to pass directly into the Express server\n        // when it is instantiated\n        //      (or false to disable)\n        //\n        // This is the options object for the `createServer` method, as discussed here:\n        // • http://nodejs.org/docs/v4.0.0/api/https.html#https_class_https_server\n        // • http://nodejs.org/docs/v6.0.0/api/https.html#https_class_https_server\n        // • http://nodejs.org/docs/v7.0.0/api/https.html#https_class_https_server\n        serverOptions: undefined,\n\n        // Custom express middleware function to use.\n        // (FUTURE: add deprecation message if this is attempted-- instead recommend using an arbitrary middleware)\n        customMiddleware: undefined,\n\n        // Should be left false unless behind a proxy.\n        // (this is passed in to Express as the \"trust proxy\" setting)\n        trustProxy: false,\n\n      }//< .http>\n\n    },//< / defaults >\n\n\n\n    configure: function() {\n\n      // If one piece of the ssl config is specified, ensure the other required piece is there\n      if (sails.config.ssl && (\n        sails.config.ssl.cert && !sails.config.ssl.key\n      ) || (!sails.config.ssl.cert && sails.config.ssl.key)) {\n        throw flaverr({ name: 'userError', code: 'E_INVALID_SSL_CONFIG' }, new Error('Invalid SSL configuration in `sails.config.ssl`!  Must include `cert` and `key` properties!'));\n      }\n\n      // Deprecate `customMiddlware` option.\n      if (sails.config.http.customMiddleware) {\n        sails.log.debug('Warning: use of `customMiddleware` is deprecated in Sails 1.0.');\n        sails.log.debug('Instead, use an Express 4-compatible middleware (res, res, next) function.');\n        sails.log.debug('See http://sailsjs.com/docs/upgrading/to-v-1-0#?express-4 for more info.');\n        sails.log.debug();\n      }\n\n      // Path static files will be served from\n      //\n      // Uses `path.resolve()` to accept either:\n      //  • an absolute path\n      //  • a relative path from the app root (sails.config.appPath)\n      sails.config.paths.public = path.resolve(sails.config.appPath, sails.config.paths.public);\n\n\n      // If no _explicit_ middleware order is specified, make sure the implicit default order\n      // will be used. This allows overriding built-in middleware functions (like `www`)\n      // without having to explicitly configure the `sails.config.http.middleware.order` array.\n      sails.config.http.middleware.order = sails.config.http.middleware.order || sails.hooks.http.defaults(sails.config).http.middleware.order;\n      // Note that this (^^) is probably not necessary anymore.\n\n\n      //  ┌┐ ┌─┐┌─┐┬┌─┬ ┬┌─┐┬─┐┌┬┐┌─┐  ┌─┐┌─┐┌┬┐┌─┐┌─┐┌┬┐┬┌┐ ┬┬  ┬┌┬┐┬ ┬\n      //  ├┴┐├─┤│  ├┴┐│││├─┤├┬┘ ││└─┐  │  │ ││││├─┘├─┤ │ │├┴┐││  │ │ └┬┘\n      //  └─┘┴ ┴└─┘┴ ┴└┴┘┴ ┴┴└──┴┘└─┘  └─┘└─┘┴ ┴┴  ┴ ┴ ┴ ┴└─┘┴┴─┘┴ ┴  ┴\n      //   ┬   ┌┬┐┌─┐┌─┐┬─┐┌─┐┌─┐┌─┐┌┬┐┬┌─┐┌┐┌  ┬ ┬┌─┐┬─┐┌┐┌┬┌┐┌┌─┐┌─┐\n      //  ┌┼─   ││├┤ ├─┘├┬┘├┤ │  ├─┤ │ ││ ││││  │││├─┤├┬┘││││││││ ┬└─┐\n      //  └┘   ─┴┘└─┘┴  ┴└─└─┘└─┘┴ ┴ ┴ ┴└─┘┘└┘  └┴┘┴ ┴┴└─┘└┘┴┘└┘└─┘└─┘\n      // Backwards compatibility and/or deprecation messages for:\n      //  • `sails.config.host`    => `sails.config.explicitHost`.\n      //  • `sails.config.express` => `sails.config.http`.\n      //  • `sails.config.express.loadMiddleware` => `sails.config.http`.\n      //  • `sails.config.cache.maxAge` => `sails.config.http.cache`.\n      if (sails.config.host) {\n        sails.log.debug('The `sails.config.host` setting is deprecated in Sails 1.0.');\n        sails.log.debug('Please use `sails.config.explicitHost` instead.\\n');\n        sails.config.explicitHost = sails.config.host;\n      }\n\n      if (sails.config.express) {\n        throw flaverr({ name: 'userError', code: 'E_INVALID_HTTP_CONFIG' }, new Error(\n          '\\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'+\n          'The `sails.config.express` setting is no longer available in Sails 1.0.\\n'+\n          'Please use `sails.config.http.js` instead (available in `config/http.js` in new apps).\\n'+\n          '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'\n        ));\n      }\n\n      if (sails.config.http.loadMiddleware) {\n        throw flaverr({ name: 'userError', code: 'E_INVALID_HTTP_CONFIG' }, new Error(\n          '\\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'+\n          'The `sails.config.http.loadMiddleware` setting is no longer available in Sails 1.0.\\n'+\n          'Please use `sails.config.http.middleware.order` instead.\\n'+\n          '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'\n        ));\n      }\n\n      if (sails.config.cache) {\n        throw flaverr({ name: 'userError', code: 'E_INVALID_HTTP_CONFIG' }, new Error(\n          '\\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'+\n          'The `sails.config.cache` setting is no longer available in Sails 1.0.\\n'+\n          'Please use `sails.config.http.cache` instead.\\n'+\n          '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'\n        ));\n      }\n\n      if (sails.config.http.trustProxy === 0 || sails.config.http.trustProxy === '' || sails.config.http.trustProxy === null || _.isNaN(sails.config.http.trustProxy)) {\n        throw flaverr({ name: 'userError', code: 'E_HTTP_BAD_TRUSTPROXY' }, new Error(\n          '\\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'+\n          'The `sails.config.http.trustProxy` property cannot be zero, empty string, null or NaN.\\n'+\n          'The property is currently set to: `' + util.inspect(sails.config.http.trustProxy) + '`.\\n'+\n          'To indicate that your app is directly facing the internet, set `trustProxy` to `false`.\\n'+\n          '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'));\n      }\n\n      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n      // BACKWARDS COMPATIBILITY:\n      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n      if (sails.config.http.bodyParser) {\n        sails.log.debug('The `sails.config.http.bodyParser` setting is deprecated in Sails 1.0.');\n        sails.log.debug('Please use `sails.config.http.middleware.bodyParser` instead.');\n        sails.log.debug('See http://sailsjs.com/docs/concepts/middleware for more details.\\n');\n\n        if (!sails.config.http.middleware.bodyParser) {\n          sails.config.http.middleware.bodyParser = sails.config.http.bodyParser;\n        }\n\n      }\n\n      if (sails.config.http.methodOverride) {\n        sails.log.debug('The `sails.config.http.methodOverride` setting is deprecated in Sails 1.0.');\n        sails.log.debug('Please use `sails.config.http.middleware.methodOverride` instead.');\n        sails.log.debug('Also note that in Sails 1.0, the `methodOverride` module is no longer');\n        sails.log.debug('included by default -- you\\'ll need to `npm install method-override --save`');\n        sails.log.debug('and add `methodOverride` to the `sails.config.http.middleware.order` array.');\n        sails.log.debug('See http://sailsjs.com/docs/concepts/middleware for more details.\\n');\n\n        if (!sails.config.http.middleware.methodOverride) {\n          sails.config.http.middleware.methodOverride = sails.config.http.methodOverride;\n        }\n\n      }\n\n      if (sails.config.http.cookieParser) {\n        sails.log.debug('The `sails.config.http.cookieParser` setting is deprecated in Sails 1.0.');\n        sails.log.debug('Please use `sails.config.http.middleware.cookieParser` instead.');\n        sails.log.debug('See http://sailsjs.com/docs/concepts/middleware for more details.\\n');\n\n        if (!sails.config.http.middleware.cookieParser) {\n          sails.config.http.middleware.cookieParser = sails.config.http.cookieParser;\n        }\n\n      }\n\n      //  ┬  ┬┌─┐┬─┐┬┌─┐┬ ┬  ┌┬┐┬┌┬┐┌┬┐┬  ┌─┐┬ ┬┌─┐┬─┐┌─┐\n      //  └┐┌┘├┤ ├┬┘│├┤ └┬┘  ││││ ││ │││  ├┤ │││├─┤├┬┘├┤\n      //   └┘ └─┘┴└─┴└   ┴   ┴ ┴┴─┴┘─┴┘┴─┘└─┘└┴┘┴ ┴┴└─└─┘\n      //   Make sure that middleware in the order exists, and that every\n      //   custom middleware is present in the order.\n\n      // Loop through all of the middleware in `sails.config.http.middleware`, and verify that it's\n      // in the order (skipping the `order` key itself).\n      _.each(_.without(_.keys(sails.config.http.middleware), 'order'), function (middlewareName) {\n\n        // If the custom middleware isn't in the middleware order, bail.\n        // Make an exception for 404, 500 and startRequest timer, which we'll handle more\n        // gently when initializing the hook.\n        if (!_.contains(sails.config.http.middleware.order.concat(['404', '500', 'startRequestTimer']), middlewareName)) {\n          throw flaverr({ name: 'userError', code: 'E_INVALID_HTTP_CONFIG' }, new Error(\n            '\\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'+\n            'Detected a custom middleware `' + middlewareName + '` that does not appear in the\\n'+\n            'middleware order.  Please add `' + middlewareName + '` to `sails.config.http.middleware.order`.\\n'+\n            'See http://sailsjs.com/docs/concepts/middleware for more info.\\n'+\n            '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'\n          ));\n        }\n\n      });\n\n      // Now loop through all of the middleware names in `sails.config.http.middleware.order` (ignoring\n      // the built-in ones) and verify that there's a matching custom middleware.\n      // Make an exception for the middleware that was removed from the order in Sails 1.0, which we'll\n      // handle more gently when initializing the hook.\n      _.each(_.difference(sails.config.http.middleware.order, sails.hooks.http.defaults.http.middleware.order.concat(['404', '500', 'startRequestTimer', 'handleBodyParserError', 'methodOverride', '$custom'])), function(middlewareName) {\n\n        if (!_.isFunction(sails.config.http.middleware[middlewareName])) {\n          throw flaverr({ name: 'userError', code: 'E_INVALID_HTTP_CONFIG' }, new Error(\n            '\\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'+\n            'Detected an entry for `' + middlewareName + '` in `sails.config.http.middleware.order`,\\n'+\n            'but `sails.config.http.middleware[\\'' + middlewareName + '\\']` is undefined or not a function.\\n'+\n            'Please provide a custom `req, res, next` middleware function for `' + middlewareName + '`,\\n'+\n            'or remove it from the order. See http://sailsjs.com/docs/concepts/middleware for more info.\\n'+\n            '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'\n          ));\n\n        }\n\n      });\n\n\n\n    },\n\n\n    /**\n     * Initialize is fired first thing when the hook is loaded\n     * but after waiting for user config (if applicable).\n     */\n    initialize: toInitializeHttpHook(sails),\n\n\n    /**\n     * `handleLift` is fired when sails is ready for HTTP requests to\n     * start coming in.\n     *\n     * @param  {Function} done\n     */\n    handleLift: function(done){\n      // In order for `sails.config` to be correct, this needs to happen in here.\n      var startServer = toStartServer(sails);\n\n      // Now that Sails is ready, start listening for requests on\n      // the express server.\n      startServer(done);\n    }\n\n  };\n};\n"
  },
  {
    "path": "lib/hooks/http/initialize.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar _ = require('@sailshq/lodash');\nvar getConfiguredHttpMiddlewareFns = require('./get-configured-http-middleware-fns');\n\n\n\nmodule.exports = function(sails) {\n\n  /**\n   * initialize()\n   *\n   * Configure the encapsulated Express server that will be used to serve actual HTTP requests\n   */\n\n  return function initialize(cb) {\n\n    // Before proceeding, wait for the session hook--\n    // or if it is disabled, then go ahead and proceed\n    // (but change the middleware order config so we don't\n    //  attempt to handle sessions).\n    (function _waitForSessionHookIfApplicable(next){\n      // If the session hook is available...\n      if (sails.hooks.session) {\n        // Then wait until after session hook has initialized\n        // so that the proper session config is available for use\n        // in the built-in \"session\" middleware.\n        sails.after('hook:session:loaded', function () {\n          return next();\n        });\n      }\n      // Otherwise, the session hook is NOT available.\n      else {\n        // Then, if it present, rip out \"session\" from the configured\n        // middleware order so we don't try to use the built-in session\n        // middleware.\n        _.pull(sails.config.http.middleware.order, 'session');\n        return next();\n      }\n    })(function _afterLoadingSessionHookIfApplicable(err) {\n      if (err) { return cb(err); }\n\n      try {\n\n        // Required to be here due to dynamic NODE_ENV settings via command line args\n        // (i.e. if we `require` this above w/ everything else, the NODE_ENV might not be set properly yet)\n        var express = require('express');\n\n        // Create express app instance.\n        var expressApp = express();\n\n        // Create a new router object to handle routes bound in Sails apps.\n        // We do this because Express doesn't provide a public API to return\n        // its built-in router (expressApp._router), and we need direct access\n        // to the router object in order to do `unbind` and `reset`.\n        //\n        // Note that we don't add this router to the express app until right\n        // before its time to add any \"post-router\" middleware (i.e. after\n        // the \"ready\" event is received).\n        var internalExpressRouter = express.Router();\n\n        // Expose express app as `sails.hooks.http.app` for use in other files\n        // in this hook, and in other core hooks.\n        sails.hooks.http.app = expressApp;\n\n        // Disable the default powered-by header (required by Express 3.x).\n        expressApp.disable('x-powered-by');\n\n        // Determine whether or not to create an HTTPS server\n        var isUsingSSL =\n          (sails.config.ssl === true) ||\n          (sails.config.ssl.key && sails.config.ssl.cert) ||\n          sails.config.ssl.pfx;\n\n        // Merge SSL into server options\n        var serverOptions = sails.config.http.serverOptions || {};\n        _.extend(serverOptions, sails.config.ssl);\n\n        // Lodash 3's _.merge attempts to transform buffers into arrays;\n        // so if we detect an array, then transform it back into a buffer.\n        _.each(['key', 'cert', 'pfx'], function _eachSSLOption(sslOption) {\n          if (_.isArray(serverOptions[sslOption])) {\n            serverOptions[sslOption] = Buffer.from(serverOptions[sslOption]);\n          }\n        });\n        // ^^^ The following is probably not relevant anymore, because `_.merge()`\n        // is not being used above.  Leaving for compatibility reasons (just to be safe).\n\n        // Get the appropriate server creation method for the protocol\n        var createServer = isUsingSSL ?\n          require('https').createServer :\n          require('http').createServer;\n\n        // Use serverOptions if they were specified\n        // Manually create http server using Express app instance\n        if(process.version.match(/^v(\\d+\\.\\d+)/)[1] < 10.12){\n          if (sails.config.http.serverOptions || isUsingSSL) {\n            sails.hooks.http.server = createServer(serverOptions, expressApp);\n          }\n          else {\n            sails.hooks.http.server = createServer(expressApp);\n          }\n        }\n        else {\n          sails.hooks.http.server = createServer(serverOptions, expressApp);\n        }\n        // Keep track of all openTcpConnections that come in,\n        // so we can destroy them later if we want to.\n        var openTcpConnections = {};\n\n        // Listen for `connection` events on the raw HTTP server.\n        sails.hooks.http.server.on('connection', function _onNewTCPConnection(tcpConnection) {\n          var key = tcpConnection.remoteAddress + ':' + tcpConnection.remotePort;\n          openTcpConnections[key] = tcpConnection;\n          tcpConnection.on('close', function() {\n            delete openTcpConnections[key];\n          });\n        });\n\n        // Create a `destroy` method we can use to do a hard shutdown of the server.\n        sails.hooks.http.destroy = function(done) {\n          sails.log.verbose('Destroying http server...');\n          sails.hooks.http.server.close(done);\n\n          // FUTURE: consider moving this loop ABOVE `sails.hooks.http.server.close(done)`\n          // for clarity (since at this point we've passed control via `done`)\n          for (var key in openTcpConnections) {\n            openTcpConnections[key].destroy();\n          }\n\n        };//</define `sails.hooks.http.destroy()`>\n\n        // Configure views if hook enabled\n        if (sails.hooks.views) {\n\n          // FUTURE: explore handling this differently to avoid potential\n          // timing issues with view engine configuration\n          sails.after('hook:views:loaded', function() {\n            var View = require('./view');\n\n            // Use View subclass to allow case-insensitive view lookups\n            expressApp.set('view', View);\n\n            // Set up location of server-side views and their engine\n            expressApp.set('views', sails.config.paths.views);\n\n            // Teach Express how to render templates w/ our configured view extension\n            expressApp.engine(sails.config.views.extension, sails.hooks.views._renderFn);\n\n            // Set default view engine\n            sails.log.silly('Setting default Express view engine to ' + sails.config.views.extension + '...');\n            expressApp.set('view engine', sails.config.views.extension);\n\n          });//</ after hook:views:loaded >\n\n        }//>-\n\n        // In non-production environments, format `res.json()` output nicely.\n        // > https://expressjs.com/en/4x/api.html#app.set\n        if (process.env.NODE_ENV !== 'production') {\n          expressApp.set('json spaces', 2);\n        }\n\n        // Set Express \"trust proxy\" if appropriate.\n        // > https://expressjs.com/en/guide/behind-proxies.html\n        if (sails.config.http.trustProxy) {\n          expressApp.set('trust proxy', sails.config.http.trustProxy);\n        }\n\n        // Whenever Sails binds a route, bind it to the internal Express router.\n        sails.on('router:bind', function(route) {\n          // Clone the route so that if a route handler messes with the options, the changes\n          // don't get persisted and used in subsequent requests.\n          route = _.cloneDeep(route);\n          internalExpressRouter[route.verb || 'all'](route.path || '/*', route.target);\n        });\n\n        // Whenever Sails unbinds a route, remove it from the internal Express router.\n        sails.on('router:unbind', function(routeToRemove) {\n          // Remove any route which matches the path and verb of the argument\n          _.remove(internalExpressRouter.stack, function(layer) {\n            return (layer.route.path === routeToRemove.path && layer.route.methods[routeToRemove.verb] === true);\n          });\n        });\n\n        // Whenever Sails resets its router, clear out the internal Express router.\n        sails.on('router:reset', function() {\n          internalExpressRouter.stack = [];\n        });\n\n        // Now expressApp.use() an initial piece of middleware to bind\n        // _core, mandatory properties_ to the incoming `req`.\n        // This middleware cannot be disabled in userland configuration--\n        // and that's done on purpose.\n        expressApp.use(function _exposeSailsOnReq (req, res, next){\n          // Expose req._sails on incoming HTTP request instances.\n          //\n          // This is also handled separately for virtual requests in `lib/router/`:\n          // (see https://github.com/balderdashy/sails/pull/3599#issuecomment-195665040)\n          req._sails = sails;\n\n          // Wrap `req.param()` in a shim that normalizes the behavior of `req.param('length')`.\n          // (see https://github.com/balderdashy/sails/issues/3738#issue-156095626)\n          var origReqParam = req.param;\n          req.param = function getValForParam (name){\n            if (name === 'length') {\n              // If `req.params.length` is a string, instead of a number, then we know this request\n              // must have matched a route address like `/foo/bar/:length/baz`, so in that case, we'll\n              // allow `req.param('length')` to return the runtime value of `length` as a string.\n              if (_.isString(req.params.length)) {\n                return req.params.length;\n              }\n              else if (!_.isArray(req.body) && _.isObject(req.body) && !_.isUndefined(req.body.length) && !_.isNull(req.body.length)) {\n                // > In future versions of Sails, this shim will likely be modified to allow the `null` literal to be received\n                // > as a value for a body parameter and accessed in `req.param()`.\n                // > (This is because `null` and `undefined` are distinct and lossless when serializing and deserializing to\n                // > and from JSON-- so it's really specifically for standard JSON response bodies.)\n                // >\n                // > However, this is a departure from the behavior of Express, and a breaking change- so it will\n                // > not happen until the release of Sails v1 (or possibly in a pre v1.0 minor version.)\n                return req.body.length;\n              }\n              else if (_.isObject(req.query) && !_.isUndefined(req.query.length) && !_.isNull(req.query.length)) {\n                return req.query.length;\n              }\n              else { return undefined; }\n            }\n            return origReqParam.apply(req, Array.prototype.slice.call(arguments));\n          };\n\n          return next();\n        });\n\n        // If there's a `handleBodyParserError` middleware in the order, and there isn't\n        // a custom definition for it (i.e. it's trying to use the default) log a\n        // deprecation warning.\n        if (_.contains(sails.config.http.middleware.order, 'handleBodyParserError') && !_.isFunction(sails.config.http.middleware.handleBodyParserError)) {\n          sails.log.debug('The `handleBodyParserError` middleware has been removed in Sails 1.0.');\n          sails.log.debug('To avoid this message, remove `handleBodyParserError` from ');\n          sails.log.debug('the `order` array in `config/http.js`.');\n          sails.log.debug('See http://sailsjs.com/upgrading for more info.\\n');\n        }\n\n        // If there's a `methodOverride` middleware in the order, and there isn't\n        // a custom definition for it (i.e. it's trying to use the default) log a\n        // deprecation warning.\n        if (_.contains(sails.config.http.middleware.order, 'methodOverride') && !_.isFunction(sails.config.http.middleware.methodOverride)) {\n          sails.log.debug('The `methodOverride` middleware has been removed in Sails 1.0.');\n          sails.log.debug('To avoid this message, remove `methodOverride` from ');\n          sails.log.debug('the `order` array in `config/http.js`.');\n          sails.log.debug('See http://sailsjs.com/upgrading for more info.\\n');\n        }\n\n\n        // Ignore explicit declarations of `startRequestTimer`, `404` and `500` middleware.\n        var removedHttpMiddleware = _.remove(sails.config.http.middleware.order, function(middleware) {\n          return _.contains(['handleBodyParserError', 'startRequestTimer', '404', '500'], middleware);\n        });\n\n        // Warn about an explicit `startRequestTimer` in the order, or a custom middleware implementation of it.\n        if (_.contains(removedHttpMiddleware, 'startRequestTimer') || sails.config.http.middleware.startRequestTimer) {\n          sails.log.debug('The `startRequestTimer` middleware is added to your app automatically in Sails 1.0.');\n          if (sails.config.http.middleware.startRequestTimer) {\n            sails.log.debug('(ignoring custom implementation in `sails.config.http.middleware.startRequestTimer)');\n          } else {\n            sails.log.debug('(ignoring entry in the `sails.config.http.middleware.order` list)');\n          }\n          sails.log.debug('See http://sailsjs.com/documentation/reference/request-req/req-start-time for more info.\\n');\n        }\n\n        // Warn about an explicit `404` in the order, or a custom middleware implementation of it.\n        if (_.contains(removedHttpMiddleware, '404') || sails.config.http.middleware['404']) {\n          sails.log.debug('The `404` middleware is added to your app automatically in Sails 1.0.');\n          if (sails.config.http.middleware['404']) {\n            sails.log.debug('(ignoring custom implementation in `sails.config.http.middleware[\\'404\\']`)');\n          } else {\n            sails.log.debug('(ignoring entry in the `sails.config.http.middleware.order` list)');\n          }\n          sails.log.debug('If you wish to customize the 404 functionality for your app, you can');\n          sails.log.debug('do so by creating a custom `notFound` response as `api/responses/notFound.js`.');\n          sails.log.debug('See http://sailsjs.com/documentation/concepts/custom-responses for more info.\\n');\n        }\n\n        // Warn about an explicit `500` in the order.\n        if (_.contains(removedHttpMiddleware, '500') || sails.config.http.middleware['500']) {\n          sails.log.debug('The `500` middleware is added to your app automatically in Sails 1.0.');\n          if (sails.config.http.middleware['500']) {\n            sails.log.debug('(ignoring custom implementation in `sails.config.http.middleware[\\'500\\']`)');\n          } else {\n            sails.log.debug('(ignoring entry in the `sails.config.http.middleware.order` list)');\n          }\n          sails.log.debug('If you wish to customize the 500 functionality for your app, you can');\n          sails.log.debug('do so by creating a custom `serverError` response as `api/responses/serverError.js`.');\n          sails.log.debug('See http://sailsjs.com/documentation/concepts/custom-responses for more info.\\n');\n        }\n\n        // Then build a dictionary of configured middleware functions, including\n        // built-in middleware as well as any middleware provided in\n        // `sails.config.http.middleware`.\n        var configuredHttpMiddlewareFns = getConfiguredHttpMiddlewareFns(expressApp, sails);\n\n        // Add in the middleware to record the request start time.\n        expressApp.use(function startRequestTimer(req, res, next) {\n          req._startTime = new Date();\n          next();\n        });\n\n        // Split the middleware order into \"pre-router\" and \"post-router\" middleware.\n        // The internal \"startRequestTimer\" always comes first.\n        var preRouterMiddleware = [];\n        var postRouterMiddleware = null;\n        _.each(sails.config.http.middleware.order, function(middlewareKey) {\n          if (middlewareKey === 'router') { postRouterMiddleware = []; }\n          else if ( _.isArray(postRouterMiddleware) ) {\n            postRouterMiddleware.push(middlewareKey);\n          }\n          else {\n            preRouterMiddleware.push(middlewareKey);\n          }\n        });\n\n        // If a custom `loadMiddleware` function was configured, then call it to \"use\"\n        // the configured middleware (instead of doing it automatically with the more\n        // modern `sails.config.http.middleware.order` configuration).\n        //\n        // This is primarily for backwards compatibility for the undocumented\n        // `express.loadMiddleware` config that is still in use in legacy apps\n        // from the 2013-early 2014 time frame.\n        //\n        // It is no longer relevant in most cases thanks to `sails.config.http.middleware`,\n        // and may be removed in an upcoming release.\n        if (sails.config.http.loadMiddleware) {\n          sails.config.http.loadMiddleware(expressApp, configuredHttpMiddlewareFns, sails);\n        }\n        // Otherwise (i.e. the normal case) we `.use()` each of the configured\n        // middleware functions in the configured order (`sails.config.http.middleware.order`).\n        else {\n          _.each(preRouterMiddleware, function (middlewareKey) {\n\n            // `$custom` is a special entry in the middleware order config that exists\n            // purely for compatibility.  When procesing `$custom`, we check to see if\n            // `sails.config.http.customMiddleware`, was provided and if so, call it\n            // with the express app instance as an argument (rather than calling\n            // `sails.config.http.middleware.$custom`).\n            // If `customMiddleware` is not being used, we just ignore `$custom` altogether.\n            if (middlewareKey === '$custom') {\n              if (sails.config.http.customMiddleware) {\n                // Allows for injecting a custom function to attach middleware.\n                // (This is here for compatibility, and for situations where the raw Express\n                //  app instance is necessary for configuring middleware).\n                sails.config.http.customMiddleware(expressApp);\n              }\n              // Either way, bail at this point (we don't want to do anything further with $custom)\n              return;\n            }\n\n            // Look up the referenced middleware function.\n            var referencedMwr = configuredHttpMiddlewareFns[middlewareKey];\n\n            // If a middleware fn by this name is not configured (i.e. `undefined`),\n            // then skip this entry & write a verbose log message.\n            if (_.isUndefined(referencedMwr)) {\n              sails.log.verbose('An entry (`%s`) in `sails.config.http.middleware.order` references an unrecognized middleware function-- that is, it was not provided as a key in the `sails.config.http.middleware` dictionary. Skipping...', middlewareKey);\n              return;\n            }\n            // On the other hand, if the referenced middleware appears to be disabled\n            // _on purpose_, or because _it is not compatible_, then just skip it and\n            // don't log anything. (i.e. it is `null` or `false`)\n            if (!referencedMwr) {\n              return;\n            }\n\n            // Otherwise, we're good to go, so go ahead and use the referenced\n            // middleware function.\n            expressApp.use(referencedMwr);\n\n          });//</each item in `sails.config.http.middleware.order`>\n        }\n\n        // www, favicon, 404 and 500 middleware should be attached at the very end.\n        // In previous Sails versions (that used Express <4), the router was added\n        // as part of the middleware stack in sails.config.http.middleware.order,\n        // so we could just put these 4 middleware after `router` in that list.\n        // In Express 4, the router is built in, so we have to wait until the\n        // server is fully initialized and then add the post-router middleware after.\n        sails.once('ready', function addPostRouterMiddleware() {\n\n          expressApp.use(internalExpressRouter);\n\n          _.each(postRouterMiddleware, function (middlewareKey) {\n\n            // Look up the referenced middleware function.\n            var referencedMwr = configuredHttpMiddlewareFns[middlewareKey];\n\n            // If a middleware fn by this name is not configured (i.e. `undefined`),\n            // then skip this entry & write a verbose log message.\n            if (_.isUndefined(referencedMwr)) {\n              sails.log.verbose('An entry (`%s`) in `sails.config.http.middleware.order` references an unrecognized middleware function-- that is, it was not provided as a key in the `sails.config.http.middleware` dictionary. Skipping...', middlewareKey);\n              return;\n            }\n            // On the other hand, if the referenced middleware appears to be disabled\n            // _on purpose_, or because _it is not compatible_, then just skip it and\n            // don't log anything. (i.e. it is `null` or `false`)\n            if (!referencedMwr) {\n              return;\n            }\n\n            // Otherwise, we're good to go, so go ahead and use the referenced\n            // middleware function.\n            expressApp.use(referencedMwr);\n\n          });\n\n          // Add the default 404 middleware.\n          expressApp.use(function handleUnmatchedRequest(req, res) {\n            // Explicitly ignore error arg to avoid inadvertently\n            // turning this into an error handler\n            sails.emit('router:request:404', req, res);\n          });\n\n          // Add the default 500 middleware.\n          expressApp.use(function handleError(err, req, res, next) {// eslint-disable-line no-unused-vars\n            // Note that we _need_ all four arguments in order for this function\n            // to have special meaning as an error handler (i.e. to Express)\n            sails.emit('router:request:500', err, req, res);\n          });\n\n        });\n\n        // All done!\n        return cb();\n\n      } catch (e) { return cb(e); }\n      // ^ for improving the readability of any bugs in the above code,\n      // or for unhandled errors from our dependencies, or for unexpected\n      // configuration.\n\n    });//</_afterLoadingSessionHookIfApplicable>\n  };//</initialize()>\n};\n"
  },
  {
    "path": "lib/hooks/http/start.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar async = require('async');\nvar flaverr = require('flaverr');\n\n\nmodule.exports = function (sails) {\n\n  return function startServer (cb) {\n\n    // Used to warn about possible issues if starting the server is taking a long time\n    var liftAbortTimer;\n    var liftTimeout = sails.config.liftTimeout || 4000;\n    // TODO: pull this defaulting into `defaults`\n    // and also ensure this config is properly documented.\n\n    async.auto({\n\n      // Start Express server\n      start: function (next) {\n\n        var explicitHost = sails.config.explicitHost;\n\n        // If host is explicitly declared, include it in express's listen() call\n        if (explicitHost) {\n          sails.log.verbose('Restricting access to explicit host: '+explicitHost);\n          sails.hooks.http.server.listen(sails.config.port, explicitHost, next);\n        }\n        else {\n          // Listen for error events that may be emitted as the server attempts to start\n          sails.hooks.http.server.on('error', failedToStart);\n          sails.hooks.http.server.listen(sails.config.port, function(err) {\n            // Remove the error listener so future error events emitted by the server\n            // don't get handled by the \"failedToStart\" function below.\n            sails.hooks.http.server.removeListener('error', failedToStart);\n            return next(err);\n          });\n        }\n\n        // Start timer in case this takes suspiciously long...\n        liftAbortTimer = setTimeout(failedToStart, liftTimeout);\n\n        // If the server fails to start because of an error, or if it's just taking\n        // too long, show some troubleshooting notes and bail out.\n        function failedToStart(err) {\n\n          // If this was called because of an actual error, clear the timeout\n          // so failedToStart doesn't get called again.\n          if (err) {\n            clearTimeout(liftAbortTimer);\n          }\n\n          // If sails is exiting already, don't worry about the timer going off.\n          if (sails._exiting) {return;}\n\n          // Figure out if this user is on Windows\n          var isWin = !!process.platform.match(/^win/);\n\n          // If server isn't starting, provide general troubleshooting information,\n          // sharpened with a few simple heuristics:\n          console.log('');\n          if (err) {\n            sails.log.error('Server failed to start.');\n            if (err.code) {\n              sails.log.error('(received error: ' + err.code + ')');\n            }\n          } else {\n            sails.log.error('Server is taking a while to start up (it\\'s been ' + (liftTimeout / 1000) + ' seconds).');\n          }\n\n          sails.log.error();\n          sails.log.error('Troubleshooting tips:');\n          sails.log.error();\n\n          // 0. Just a slow Grunt task\n          if (sails.hooks.grunt && ! (err && err.code === 'EADDRINUSE')) {\n            if (process.env.NODE_ENV === 'production') {\n              sails.log.error(\n                ' -> Do you have a slow Grunt task?  You are running in production mode where, by default, tasks are configured to minify the JavaScript and CSS/LESS files in your assets/ directory.  Sometimes, these processes can be slow, particularly if you have lots of these types of files.'\n              );\n            }\n            else {\n              sails.log.error(\n                ' -> Do you have a slow Grunt task, or lots of assets?'\n              );\n            }\n            sails.log.error();\n          }\n\n          // 1. Unauthorized\n          if (sails.config.port < 1024) {\n            sails.log.error(\n            ' -> Do you have permission to use port ' + sails.config.port + ' on this system?',\n            // Don't mention `sudo` to Windows users-- I hear you guys get touchy about that sort of thing :)\n            (isWin) ? '' : '(you might try `sudo`)'\n            );\n\n            sails.log.error();\n          }\n\n          // 2. Invalid or unauthorized explicitHost configuration.\n          if (explicitHost) {\n            sails.log.error(\n            ' -> You might remove your explicit host configuration and try lifting again (you specified',\n            '`'+explicitHost+'`',\n            '.)');\n\n            sails.log.error();\n          }\n\n          // 3. Something else is running on this port\n          sails.log.error(\n          ' -> Is something else already running on port', sails.config.port,\n          (explicitHost ? (' with hostname ' + explicitHost) : '') + '?'\n          );\n          sails.log.error();\n\n          // 4. invalid explicitHost\n          if (!explicitHost) {\n            sails.log.error(\n            ' -> Are you deploying on a platform that requires an explicit hostname,',\n            'like OpenShift?');\n            sails.log.error(\n            '    (Try setting the `explicitHost` config to the hostname where the server will be accessible.)'\n            );\n            sails.log.error(\n            '    (e.g. `mydomain.com` or `183.24.244.42`)'\n            );\n          }\n          console.log('');\n\n          // Lower Sails to do any necessary cleanup\n          sails.lower(function(){\n            // Exit with a non-zero value to indicate an error\n            process.exit(1);\n            // TODO: For a more graceful shutdown, we should instead consider:\n            // return next(new Error('blah blah'));\n          });\n        }//</failedToStart>\n      },\n\n      verify: ['start', function (results, next) {\n        var explicitHost = sails.config.explicitHost;\n\n        // Check for port conflicts\n        // Ignore this check if explicit host is set, since other more complicated things might be going on.\n        if( !explicitHost && !sails.hooks.http.server.address() ) {\n          var portBusyErrorMsg = '';\n          portBusyErrorMsg += 'Trying to start server on port ' + sails.config.port + ' but can\\'t...';\n          portBusyErrorMsg += 'Something else is probably running on that port!' + '\\n';\n          portBusyErrorMsg += 'Please disable the other server, or choose a different port and try again.';\n          sails.log.error(portBusyErrorMsg);\n          throw flaverr({ name: 'userError', code: 'E_PORT_BUSY' }, new Error(portBusyErrorMsg));\n          // TODO: For a more graceful shutdown, we should instead consider:\n          // return next(new Error(portBusyErrorMsg));\n        }\n\n        next();\n      }]\n\n    }, function expressListening (err) {\n      clearTimeout(liftAbortTimer);\n      if (err) { return cb(err); }\n\n      // Announce that express is now listening on a port\n      sails.emit('hook:http:listening');\n      return cb();\n    });\n  };\n\n};\n"
  },
  {
    "path": "lib/hooks/http/view.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar path = require('path');\nvar glob = require('glob');\nvar ExpressView = require('express/lib/view');\nvar expressUtils = require('express/lib/utils');\n\n\n\nvar globPath = function(viewPath) {\n  // return glob.sync(path, {\n  //  nocase: true\n  // });\n  return glob.sync(path.basename(viewPath), {\n    cwd: path.dirname(viewPath),\n    nocase: true\n  });\n};\n\n/**\n * `exists()`\n *\n * Helper function to check existence of the specified path amongst the app's views.\n * @param  {String} viewPath\n * @return {Boolean}\n */\nvar exists = function(viewPath) {\n  return globPath(viewPath).length > 0;\n};\n\n\n/**\n * @constructs {SailsView}\n */\nfunction SailsView (name, options) {\n  ExpressView.call(this, name, options);\n}\nutil.inherits(SailsView, ExpressView);\n\nSailsView.prototype.lookup = function(viewPath) {\n  var viewExt = this.ext;\n  var rootPath = this.root;\n\n  // <path>.<engine>\n  if (!expressUtils.isAbsolute(viewPath)) {\n    viewPath = path.join(rootPath, viewPath);\n  }\n  if (exists(viewPath)) {\n    return viewPath; //return globPath(viewPath)[0];\n  }\n\n  // <path>/index.<engine>\n  viewPath = path.join(path.dirname(viewPath), path.basename(viewPath, viewExt), 'index' + viewExt);\n  if (exists(viewPath)) {\n    return viewPath; //return globPath(path)[0];\n  }\n};\n\n\nmodule.exports = SailsView;\n\n"
  },
  {
    "path": "lib/hooks/i18n/index.js",
    "content": "module.exports = function(sails) {\n\n  /**\n   * Module dependencies.\n   */\n\n  var util = require('util');\n  var path = require('path');\n  var _ = require('@sailshq/lodash');\n  var i18nFactory = require('i18n-2');\n\n\n  // The file extension for locales files (e.g. in config/locales/)\n  var I18N_LOCALES_FILE_EXTENSION = '.json';\n\n  // Declare a var to hold the hook's singleton i18n instance.\n  // (if we're unable to initialize this hook properly and we skip it,\n  // then this will still be set to `undefined`)\n  var i18n;\n\n  // Hold the resolved absolute path to the configured locales dir.\n  // (if we're unable to initialize this hook properly and we skip it,\n  // then this will still be set to `undefined`)\n  var resolvedLocalesDirectory;\n\n\n  /**\n   * Expose hook definition\n   */\n\n  return {\n\n    defaults: {\n      // i18n\n      i18n: {\n        locales: [],\n        // ^^this is just the implicit default (is overridden by boilerplate settings in `config/i18n.js`)\n        defaultLocale: 'en',\n        localesDirectory: 'config/locales/'\n      }\n    },\n\n\n    configure: function() {\n\n      if (!_.isArray(sails.config.i18n.locales)) {\n        throw new Error('`sails.config.i18n.locales` must be an array of strings like [\\'en\\', \\'es\\'], but instead got: '+util.inspect(sails.config.i18n.locales, {depth:null})+'');\n      }\n\n      if (!sails.config.i18n.localesDirectory || !_.isString(sails.config.i18n.localesDirectory)) {\n        throw new Error('`sails.config.i18n.localesDirectory` must be a string like \"config/locales/\".  But, instead got: '+util.inspect(sails.config.i18n.localesDirectory, {depth:null})+'');\n      }\n\n      // If we have a default locale config, and it exists in the list of configured locales,\n      // move it to the top of the list.  This is a workaround for https://github.com/jeresig/i18n-node-2/issues/90\n      if (sails.config.i18n.defaultLocale && sails.config.i18n.locales && _.contains(sails.config.i18n.locales, sails.config.i18n.defaultLocale)) {\n        sails.config.i18n.locales = [sails.config.i18n.defaultLocale].concat(_.without(sails.config.i18n.locales, sails.config.i18n.defaultLocale));\n      }\n\n    },\n\n    initialize: function(cb) {\n\n      var self = this;\n\n      // If the array of locales is empty, then bail out now.\n      // (There's nothing for us to do in this hook.)\n      if (sails.config.i18n.locales.length === 0) {\n        sails.log.verbose('Skipping i18n hook because configured locales (`sails.config.i18n.locales`) is an empty array.');\n\n        // Delete sails.hooks.i18n so that checks like `if (sails.hooks.i18n)` come back falsey.\n        delete sails.hooks.i18n;\n\n        // Add __ and i18n functions that just pass through the string (and return a warning).\n        sails.__ = sails.i18n = function(str) { sails.log.warn('i18n hook has disabled itself due to misconfiguration -- returning input string as-is.  Set `sails.config.i18n.locales` to activate i18n.');  return str; };\n        // Add passthru __ and i18n to res.locals.\n        sails.on('router:before', function () {\n          sails.router.bind('all /*', function addLocalizationMethod (req, res, next) {\n            res.locals.__ = res.locals.i18n = sails.__;\n            return next();\n          });\n\n        });//</sails.on>\n\n        return cb();\n      }//-•\n\n      // Determine the abs path to the locales dir, resolving relative from the appPath.\n      // (supports absolute or relative path)\n      resolvedLocalesDirectory = path.resolve(sails.config.appPath, sails.config.i18n.localesDirectory);\n\n      // Override logger while initializing i18n-2, since it uses console function directly.\n      // We'll just buffer any messages and replay them when i18n-2 is done (or fails) initializing.\n      var logs = [];\n      var warns = [];\n      var errors = [];\n      var origLog = console.log;\n      var origWarn = console.warn;\n      var origError = console.error;\n      console.log = function() {logs.push(Array.prototype.slice.call(arguments));};\n      console.warn = function() {warns.push(Array.prototype.slice.call(arguments));};\n      console.error = function() {errors.push(Array.prototype.slice.call(arguments));};\n\n      // Attempt to initialize i18n.  This will fail if there's no `config/locales` directory.\n      try {\n\n        i18n = new i18nFactory({\n          locales: sails.config.i18n.locales,\n          defaultLocale: sails.config.i18n.defaultLocale,\n          directory: resolvedLocalesDirectory,\n          extension: I18N_LOCALES_FILE_EXTENSION\n        });\n\n        // Add all of the i18n prototype methods into this hook.\n        _.each(i18nFactory.prototype, function(val, key) {\n          if (_.isFunction(val)) {\n            self[key] = i18n[key].bind(i18n);\n          }\n        });\n\n        // Expose global access to locale strings\n        sails.__ = self.__;\n        sails.i18n = self.__;\n\n      }\n      catch (e) {\n        sails.log.error('Failed to initialize i18n hook.  (Tip: Does the ' + resolvedLocalesDirectory + ' directory exist?)');\n        sails.log.error(e.stack);\n        return cb(e);\n      }\n\n      // Restore the original console logger functions, and then\n      // replay any logs that were generated while trying to init i18n-2.\n      console.log = origLog;\n      console.warn = origWarn;\n      console.error = origError;\n      _.each(logs, function(log) {sails.log.verbose.apply(this, log);});\n      _.each(warns, function(warn) {sails.log.warn.apply(this, warn);});\n      _.each(errors, function(error) {sails.log.error.apply(this, error);});\n\n\n\n      // When Sails is ready to bind \"before\" shadow routes, bind a shadow route that\n      // adds the localization methods and locals.\n      sails.on('router:before', function (){\n        sails.router.bind('all /*', function addLocalizationMethod (req, res, next) {\n          return sails.hooks.i18n.expressMiddleware(req, res, next);\n        });\n\n      });//</sails.on>\n\n\n      // Finally, call the callback to indicate that the hook is done initializing.\n      return cb();\n\n    },\n\n    // Express middleware that adds translation capabilities (e.g. the __ function)\n    // to the `res` object.  Useful mainly for doing internationalization in views.\n    expressMiddleware: function (req, res, next) {\n\n      // If we don't have res.locals, we don't have anything to mix i18n options onto.\n      if (!res.locals) { return next(new Error('Provided `res` must contain `res.locals`.')); }\n\n      // If we weren't able to initialize the singleton i18n module, then we\n      // should never have made it here.\n      if (!i18n) { return next(new Error('Consistency violation: This should never happen -- sails.hooks.i18n.expressMiddleware should never be available if i18n could not be initialized.')); }\n\n      // Try to create a new i18n instance.  This is necessary because\n      // locale is set on a per-instance basis, and the request header\n      // may change the locale for a given instance (but we wouldn't\n      // want it to change for the instance connected to `sails.__()` )\n      try {\n        req.i18n = new i18nFactory({\n          locales: sails.config.i18n.locales,\n          defaultLocale: sails.config.i18n.defaultLocale,\n          directory: resolvedLocalesDirectory,\n          extension: I18N_LOCALES_FILE_EXTENSION,\n          request: req\n        });\n\n        // Mix translation capabilities into res.locals.\n        i18nFactory.registerMethods(res.locals, req);\n\n        // Add `setLocale()` method for convenience.\n        // (This is documented and fully supported as of Sails v1.0 and beyond.)\n        req.setLocale = req.i18n.setLocale.bind(req.i18n);\n\n        // For backwards compatibility:\n        //  • Add `i18n` method as alias to `__`\n        //  • Add `getLocale()` method to `req`\n        res.locals.i18n = res.locals.__;\n        req.getLocale = req.i18n.getLocale.bind(req.i18n);\n\n      } catch (e) {\n        // Seeing as we have a valid i18n singleton already, the\n        // initialization failing now seems more serious, but we\n        // still don't want to crash because of it.\n        // We should at least log the error though.\n        sails.log.error('Error attaching i18n to response:');\n        sails.log.error(e);\n      }\n\n      // Continue processing the request.\n      return next();\n\n    },\n\n  };\n};\n"
  },
  {
    "path": "lib/hooks/index.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar _ = require('@sailshq/lodash');\nvar flaverr = require('flaverr');\nvar async = require('async');\nvar STRIP_COMMENTS_RX = /(\\/\\/.*$)|(\\/\\*[\\s\\S]*?\\*\\/)|(\\s*=[^,\\)]*(('(?:\\\\'|[^'\\r\\n])*')|(\"(?:\\\\\"|[^\"\\r\\n])*\"))|(\\s*=[^,\\)]*))/mg;\n\n\n\nmodule.exports = function(sails) {\n\n\n  /**\n   * Expose hook constructor\n   *\n   * @api private\n   */\n\n  return function Hook(definition) {\n\n    // Flags to indicate whether or not this hook's `initialize` function is asynchronous (i.e. declared with `async`)\n    // and whether or not it has any parameters.\n    var hasAsyncInit;\n    var initSeemsToExpectParameters;\n\n    // A few sanity checks to make sure te provided definition does not contain any reserved properties.\n    if (!_.isObject(definition)) {\n      // This particular behavior can be made a bit less genteel in future versions (it is currently\n      // forgiving for backwards compatibility)\n      definition = definition || {};\n    }\n\n    if (_.isFunction(definition.config)) {\n      throw flaverr({ name: 'userError', code: 'E_INVALID_HOOK_CONFIG' }, new Error('Error defining hook: `config` is a reserved property and cannot be used as a custom hook method.'));\n    }\n    if (_.isFunction(definition.middleware)) {\n      throw flaverr({ name: 'userError', code: 'E_INVALID_HOOK_CONFIG' }, new Error('Error defining hook: `middleware` is a reserved property and cannot be used as a custom hook method.'));\n    }\n\n\n\n    /**\n     * Load the hook asynchronously\n     *\n     * @api private\n     */\n\n    this.load = function(cb) {\n\n      var self = this;\n\n      // TODO: refactor this: (no need for an inline function declaration)\n      var routeCallbacks = function(routes) {\n        _.each(routes, function(middleware, route) {\n          middleware._middlewareType = self.identity.toUpperCase() + ' HOOK' + (middleware.name ? (': ' + middleware.name) : '');\n          sails.router.bind(route, middleware);\n        });\n      };//ƒ\n\n      // Determine if this hook should load based on Sails environment & hook config\n      if (this.config.envs &&\n        this.config.envs.length > 0 &&\n        this.config.envs.indexOf(sails.config.environment) === -1) {\n        return cb();\n      }\n\n      // Convenience config to bind routes before any of the static app routes\n      sails.on('router:before', function() {\n        routeCallbacks(self.routes.before);\n      });\n\n      // Convenience config to bind routes after the static app routes\n      sails.on('router:after', function() {\n        routeCallbacks(self.routes.after);\n      });\n\n      // Run loadModules method if moduleloader is loaded\n      async.auto({\n\n        modules: function(cb) {\n\n          if (sails.config.hooks.moduleloader) {\n\n            return self.loadModules(cb);\n\n          }\n          return cb();\n        }\n\n      }, function(err) {\n        if (err) { return cb(err); }\n\n        // console.log(self.identity, self.initialize.toString());\n        try {\n          var seemsToExpectCallback = true;\n          if (sails.config.implementationSniffingTactic === 'analogOrClassical') {\n            seemsToExpectCallback = initSeemsToExpectParameters;\n            // (TODO: also locate and update relevant error messages)\n          }\n\n          if (hasAsyncInit) {\n            var promise;\n            if (seemsToExpectCallback) {\n              promise = self.initialize(cb);\n            } else {\n              promise = self.initialize(function(unusedErr){\n                cb(new Error('Unexpected attempt to invoke callback.  Since this \"initialize\" function does not appear to expect a callback parameter, this stub callback was provided instead.  Please either explicitly list the callback parameter among the arguments or change this code to no longer use a callback.'));\n              })\n              .then(function(){\n                cb();\n              });\n            }//ﬁ\n            promise.catch(function(e) {\n              cb(e);\n              // (Note that we don't do `return proceed(e)` here.  That's on purpose--\n              // to avoid sending the wrong idea to you, dear reader)\n            });\n          } else {\n            if (seemsToExpectCallback) {\n              self.initialize(cb);\n            } else {\n              self.initialize(function(unusedErr){\n                cb(new Error('Unexpected attempt to invoke callback.  Since this \"initialize\" function does not appear to expect a callback parameter, this stub callback was provided instead.  Please either explicitly list the callback parameter among the arguments or change this code to no longer use a callback.'));\n              });\n              return cb();\n            }\n          }\n        } catch (e) { return cb(e); }\n      });\n\n    };\n\n\n\n    /**\n     * `defaults`\n     *\n     * Default configuration for this hook.\n     *\n     * Hooks may override this function, or use a dictionary instead.\n     *\n     * @type {Function|Dictionary}\n     *       @returns {Dictionary} [default configuration for this hook to be merged into sails.config]\n     */\n    this.defaults = function() {\n      return {};\n    };\n\n    /**\n     * `configure`\n     *\n     * If this hook provides this function, the provided implementation should\n     * normalize and validate configuration related to this hook.  That config is\n     * already in `sails.config` at the time this function is called.  Any modifications\n     * should be made in place on `sails.config`\n     *\n     * Hooks may override this function.\n     *\n     * @type {Function}\n     */\n    this.configure = function() {\n\n    };\n\n    /**\n     * `loadModules`\n     *\n     * Load any modules as a dictionary and pass the loaded modules to the callback when finished.\n     *\n     * Hooks may override this function (This runs before `initialize()`!)\n     *\n     * @type {Function}\n     * @async\n     */\n    this.loadModules = function(cb) {\n      return cb();\n    };\n\n\n    /**\n     * `initialize`\n     *\n     * If provided, this implementation should prepare the hook, then trigger the callback.\n     *\n     * Hooks may override this function.\n     *\n     * @type {Function}\n     * @async\n     */\n    this.initialize = function(cb) {\n      return cb();\n    };\n\n\n\n    // Ensure that the hook definition has valid properties\n    _normalize(this);\n    definition = _normalize(definition);\n\n    // Merge default definition with overrides in the definition passed in\n    _.extend(definition.config, this.config, definition.config);\n    _.extend(definition.middleware, this.middleware, definition.middleware);\n    _.extend(definition.routes.before, this.routes.before, definition.routes.before);\n    _.extend(definition.routes.after, this.routes.after, definition.routes.after);\n    _.extend(this, definition);\n\n    // Set a flag if this hook has an async `initialize` function, and\n    // whether or not that function seems to be expecting any parameters.\n    hasAsyncInit = this.initialize.constructor.name === 'AsyncFunction';\n    initSeemsToExpectParameters = (function(fn){\n      var fnStr = fn.toString().replace(STRIP_COMMENTS_RX, '');\n      var parametersAsString = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')'));\n      // console.log('::',parametersAsString, parametersAsString.replace(/\\s*/g,'').length);\n      return parametersAsString.replace(/\\s*/g,'').length !== 0;\n    })(this.initialize);//†\n\n\n    // Bind context of new methods from definition\n    _.bindAll(this);\n\n\n\n    /**\n     * Ensure that a hook definition has the required properties.\n     *\n     * @returns {Dictionary} [coerced hook definition]\n     * @api private\n     */\n\n    function _normalize(def) {\n\n      def = def || {};\n\n      // Default hook config\n      def.config = def.config || {};\n\n      // list of environments to run in, if empty defaults to all\n      def.config.envs = def.config.envs || [];\n\n      def.middleware = def.middleware || {};\n\n      // Default hook routes\n      def.routes = def.routes || {};\n      def.routes.before = def.routes.before || {};\n      def.routes.after = def.routes.after || {};\n\n      return def;\n    }\n  };\n\n};\n"
  },
  {
    "path": "lib/hooks/logger/README.md",
    "content": "# logger (Core Hook)\n\n\n## Status\n\n> ##### Stability: [0](https://github.com/balderdashy/sails-docs/blob/master/contributing/stability-index.md) - Deprecated\n>\n> This hook will almost certainly be merged into core (see FAQ below).\n\n\n\n## Dependencies\n\nIn order for this hook to load, the following other hooks must have already finished loading:\n\n- moduleloader\n- userconfig\n\n\n## Dependents\n\nIf this hook is disabled, in order for Sails to load, the following other core hooks must also be disabled:\n\n_N/A_\n\n\n## Purpose\nThis hook's responsibilities are:\n\n\n##### Set up CaptainsLog\nInstantiate a [CaptainsLog](https://github.com/balderdashy/captains-log) logger instance.\n\n\n##### Expose `sails.log` function\nPublicly expose `sails.log()` function (see http://sailsjs.com/documentation/concepts/logging)\n\n\n##### Add `sails.log.ship()` method\nAdd an extra method to the logger which teaches it how to draw a ship in ASCII.\n\n\n\n\n\n## Implicit Defaults\nThis hook sets the following implicit default configuration on `sails.config`:\n\n\n| Property                                       | Type          | Default         |\n|------------------------------------------------|:-------------:|-----------------|\n| `sails.config.log.level`                       | ((string))    | `'info'`        |\n\n\n\n\n\n## Events\n\n##### `hook:logger:loaded`\n\nEmitted when this hook has been automatically loaded by Sails core, and triggered the callback in its `initialize` function.\n\n\n\n\n## FAQ\n\n+ Why is this a hook and not part of core?\n  + Originally, it was as a way of separating concerns.  But realistically, this particular hook _could_ be merged into core (under `app`) in a future release.  But realistically since the core configuration process does everything this hook does anyways, this hook _might as well_ be merged into core (under `app`).  Look for this to happen in a future release.\n\n> If you have a question that isn't covered here, please feel free to send a PR adding it to this section (even if you don't have the answer!)\n"
  },
  {
    "path": "lib/hooks/logger/index.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar CaptainsLog = require('captains-log');\nvar buildShipFn = require('./ship');\n\n\nmodule.exports = function(sails) {\n\n\n  /**\n   * Expose `logger` hook definition\n   */\n\n  return {\n\n\n    defaults: {\n      log: {\n        level: 'info'\n      }\n    },\n\n\n    configure: function() {\n\n    },\n\n\n    /**\n     * Initialize is fired when the hook is loaded,\n     * but after waiting for user config.\n     */\n\n    initialize: function(cb) {\n\n      // Get basic log functions\n      var log = CaptainsLog(sails.config.log);\n\n      // Mix in log.ship() method\n      log.ship = buildShipFn(\n        sails.version ? ('v' + sails.version) : '',\n        log.info\n      );\n\n      // Expose log on sails object\n      sails.log = log;\n\n      return cb();\n    }\n\n  };\n};\n"
  },
  {
    "path": "lib/hooks/logger/ship.js",
    "content": "/**\n * Draw an ASCII image of a ship\n */\nmodule.exports = function _drawShip(message, log) {\n  log = log || console.log;\n\n  // There are 20 characters before the ship's mast on the 2nd line,\n  // starting from the 'v' (inclusive)\n  var mesageLen = message.length;\n  var numSpaces = 19 - mesageLen;\n  for (var i = 0; i < numSpaces; i++) {\n    message += ' ';\n  }\n\n  return function() {\n    log('');\n    log('               .-..-.');\n    log('');\n    log('   ' + 'Sails   ' + '           ' + '<' + '|' + '    .-..-.');\n    log('   ' + message + ' |\\\\');\n    log('                      /|.\\\\');\n    log('                     / || \\\\');\n    log('                   ,\\'  |\\'  \\\\');\n    log('                .-\\'.-==|/_--\\'');\n    log('                `--\\'-------\\' ');\n    log('   __---___--___---___--___---___--___');\n    log(' ____---___--___---___--___---___--___-__');\n    log('');\n  };\n};\n"
  },
  {
    "path": "lib/hooks/moduleloader/README.md",
    "content": "# `moduleloader` (Core Hook)\n\nThis hook exposes `sails.modules`, a set of functions which other core hooks call to load modules from an app's configured directories in `sails.config.paths`.\n\nThe moduleloader hook is always the first core hook to load; even before `userconfig`.  Consequently, in order to customize `sails.config.paths`, you need to inject configuration into the load process using env variables, the .sailsrc file, or by passing in an option to the programmatic call to sails.lift (i.e. in app.js). Otherwise, by the time your user configuration files in config/* have loaded, it is too late (this hook has already run using the default paths).\n"
  },
  {
    "path": "lib/hooks/moduleloader/index.js",
    "content": "module.exports = function(sails) {\n\n  /**\n   * Module dependencies\n   */\n  var path = require('path');\n  var fs = require('fs');\n  var async = require('async');\n  var _ = require('@sailshq/lodash');\n  var includeAll = require('include-all');\n  var mergeDictionaries = require('merge-dictionaries');\n  var COMMON_JS_FILE_EXTENSIONS = require('common-js-file-extensions');\n\n\n  /**\n   * Module constants\n   */\n\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  // Supported file extensions for imperative code files such as hooks:\n  //  • 'js' (.js)\n  //  • 'ts' (.ts)\n  //  • 'es6' (.es6)\n  //  • ...etc.\n  //\n  // > For full list, see:\n  // > https://github.com/luislobo/common-js-file-extensions/blob/210fd15d89690c7aaa35dba35478cb91c693dfa8/README.md#code-file-extensions\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  var BASIC_SUPPORTED_FILE_EXTENSIONS = COMMON_JS_FILE_EXTENSIONS.code;\n\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  // Supported file extensions, ONLY for configuration files:\n  //  • All of the normal supported extensions like 'js', plus\n  //  • 'json' (.json)\n  //  • 'json5' (.json5)\n  //  • 'json.ls' (.json.ls)\n  //  • ...etc.\n  //\n  // > For full list, see:\n  // > https://github.com/luislobo/common-js-file-extensions/blob/210fd15d89690c7aaa35dba35478cb91c693dfa8/README.md#configobject-file-extensions\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  var SUPPORTED_FILE_EXTENSIONS_FOR_CONFIG = COMMON_JS_FILE_EXTENSIONS.config.concat(BASIC_SUPPORTED_FILE_EXTENSIONS);\n\n\n  /**\n   * Module loader\n   *\n   * Load code files from a Sails app into memory; modules like controllers,\n   * models, services, config, etc.\n   */\n  return {\n\n\n    defaults: function (config) {\n\n      var localConfig = {\n\n        // The path to the application\n        appPath: config.appPath ? path.resolve(config.appPath) : process.cwd(),\n\n        // Paths for application modules and key files\n        // If `paths.app` not specified, use process.cwd()\n        // (the directory where this Sails process is being initiated from)\n        paths: {\n\n          // Configuration\n          //\n          // For `userconfig` hook\n          config: path.resolve(config.appPath, 'config'),\n\n          // Server-Side Code\n          //\n          // For `controllers` hook\n          controllers: path.resolve(config.appPath, 'api/controllers'),\n          // For `policies` hook\n          policies: path.resolve(config.appPath, 'api/policies'),\n          // For `services` hook\n          services: path.resolve(config.appPath, 'api/services'),\n          // For `orm` hook\n          adapters: path.resolve(config.appPath, 'api/adapters'),\n          models: path.resolve(config.appPath, 'api/models'),\n          // For `userhooks` hook\n          hooks: path.resolve(config.appPath, 'api/hooks'),\n          // For `blueprints` hook\n          blueprints: path.resolve(config.appPath, 'api/blueprints'),\n          // For `responses` hook\n          responses: path.resolve(config.appPath, 'api/responses'),\n          // For `helpers` hook\n          helpers: path.resolve(config.appPath, 'api/helpers'),\n\n          // Server-Side View templates\n          //\n          // For `views` hook\n          views: path.resolve(config.appPath, 'views'),\n          layout: path.resolve(config.appPath, 'views/layout.ejs')\n        }\n\n      };\n\n      return localConfig;\n    },\n\n    initialize: function(cb) {\n      // Expose self as `sails.modules`.\n      sails.modules = sails.hooks.moduleloader;\n      // |\n      // |_Note that, in the future, the moduleloader's methods will be federated\n      // | out to the places where they're being used, instead of relying on\n      // | having those other modules call the appropriate method on `sails.modules.*()`.\n\n      return cb();\n    },\n\n    configure: function() {\n\n      // Default to process.cwd()\n      sails.config.appPath = sails.config.appPath ? path.resolve(sails.config.appPath) : process.cwd();\n\n      _.extend(sails.config.paths, {\n\n        // Configuration\n        //\n        // For `userconfig` hook\n        config: path.resolve(sails.config.appPath, sails.config.paths.config),\n\n        // Server-Side Code\n        //\n        // For `controllers` hook\n        controllers: path.resolve(sails.config.appPath, sails.config.paths.controllers),\n        // For `policies` hook\n        policies: path.resolve(sails.config.appPath, sails.config.paths.policies),\n        // For `services` hook\n        services: path.resolve(sails.config.appPath, sails.config.paths.services),\n        // For `orm` hook\n        adapters: path.resolve(sails.config.appPath, sails.config.paths.adapters),\n        models: path.resolve(sails.config.appPath, sails.config.paths.models),\n        // For `userhooks` hook\n        hooks: path.resolve(sails.config.appPath, sails.config.paths.hooks),\n        // For `blueprints` hook\n        blueprints: path.resolve(sails.config.appPath, sails.config.paths.blueprints),\n        // For `responses` hook\n        responses: path.resolve(sails.config.appPath, sails.config.paths.responses),\n\n        // Server-Side HTML\n        //\n        // For `views` hook\n        views: path.resolve(sails.config.appPath, sails.config.paths.views),\n        layout: path.resolve(sails.config.appPath, sails.config.paths.layout)\n      });\n    },\n\n    /**\n     * Load user config from app\n     *\n     * @param {Object} options\n     * @param {Function} cb\n     */\n    loadUserConfig: function (cb) {\n\n      async.auto({\n        'config/*': function loadOtherConfigFiles (cb) {\n          includeAll.aggregate({\n            dirname   : sails.config.paths.config,\n            exclude   : ['locales', /local\\..+/],\n            excludeDirs: /(locales|env)$/,\n            filter    : new RegExp('^(.+)\\\\.(' + SUPPORTED_FILE_EXTENSIONS_FOR_CONFIG.join('|') + ')$'),\n            flatten   : true,\n            keepDirectoryPath: true,\n            identity  : false\n          }, cb);\n        },\n\n        'config/local' : function loadLocalOverrideFile (cb) {\n          includeAll.aggregate({\n            dirname   : sails.config.paths.config,\n            filter    : new RegExp('^local\\\\.(' + SUPPORTED_FILE_EXTENSIONS_FOR_CONFIG.join('|') + ')$'),\n            identity  : false\n          }, cb);\n        },\n\n        // Load environment-specific config folder, e.g. config/env/development/*\n        'config/env/**': ['config/local', function loadEnvConfigFolder (asyncData, cb) {\n          // If there's an environment already set in sails.config, then it came from the environment\n          // or the command line, so that takes precedence.  Otherwise, check the config/local.js file\n          // for an environment setting.  Lastly, default to development.\n          var env = sails.config.environment || asyncData['config/local'].environment || 'development';\n          includeAll.aggregate({\n            dirname   : path.resolve( sails.config.paths.config, 'env', env ),\n            filter    : new RegExp('^(.+)\\\\.(' + SUPPORTED_FILE_EXTENSIONS_FOR_CONFIG.join('|') + ')$'),\n            optional  : true,\n            flatten   : true,\n            keepDirectoryPath: true,\n            identity  : false\n          }, cb);\n        }],\n\n        // Load environment-specific config file, e.g. config/env/development.js\n        'config/env/*' : ['config/local', function loadEnvConfigFile (asyncData, cb) {\n          // If there's an environment already set in sails.config, then it came from the environment\n          // or the command line, so that takes precedence.  Otherwise, check the config/local.js file\n          // for an environment setting.  Lastly, default to development.\n          var env = sails.config.environment || asyncData['config/local'].environment || 'development';\n          includeAll.aggregate({\n            dirname   : path.resolve( sails.config.paths.config, 'env' ),\n            filter    : new RegExp('^' + _.escapeRegExp(env) + '\\\\.(' + SUPPORTED_FILE_EXTENSIONS_FOR_CONFIG.join('|') + ')$'),\n            optional  : true,\n            flatten   : true,\n            keepDirectoryPath: true,\n            identity  : false\n          }, cb);\n        }]\n\n      }, function (err, asyncData) {\n        if (err) { return cb(err); }\n        // Save the environment override, if any.\n        var env = sails.config.environment;\n        // Merge the configs, with env/*.js files taking precedence over others, and local.js\n        // taking precedence over everything.\n        var config = mergeDictionaries(\n          asyncData['config/*'],\n          asyncData['config/env/**'],\n          asyncData['config/env/*'],\n          asyncData['config/local']\n        );\n        // Set the environment, but don't allow env/* files to change it; that'd be weird.\n        config.environment = env || asyncData['config/local'].environment || 'development';\n        // Return the user config\n        cb(undefined, config);\n      });\n    },\n\n    /**\n     * Load adapters\n     *\n     * @param {Object} options\n     * @param {Function} cb\n     */\n    loadAdapters: function (cb) {\n\n      // Load things like `api/adapters/FooAdapter.js`\n      includeAll.optional({\n        dirname: sails.config.paths.adapters,\n        filter: /^(.+Adapter)\\..+$/,\n        replaceExpr: /Adapter/,\n        flatten: true,\n        depth: 1\n      }, function(err, classicStyleAdapters) {\n        if (err) {\n          return cb(err);\n        }\n\n        // Load things like `api/adapters/foo/index.js`\n        fs.readdir(sails.config.paths.adapters, function(err, contents) {\n          if (err) {\n            if (err.code === 'ENOENT') {\n              return cb(undefined, classicStyleAdapters);\n            }\n            return cb(err);\n          }\n\n          var folderStyleAdapters = {};\n          try {\n            _.each(contents, function(filename) {\n\n              var absPath = path.join(sails.config.paths.adapters, filename);\n\n              // Exclude things that aren't directories, and directories that start with dots.\n              if (_.startsWith(filename, '.')) {\n                return;\n              }\n              var stats = fs.statSync(absPath);\n              if (!stats.isDirectory()) {\n                return;\n              }\n\n              // But otherwise, if we see a directory in here, try to require it.\n              // (this follows the rules of the package.json file if there is one--\n              //  or otherwise uses index.js by convention)\n              var adapterDef = require(absPath);\n\n              // Use the name of the folder as the identity.\n              folderStyleAdapters[filename] = adapterDef;\n\n            }); //</_.each()>\n          } catch (e) {\n            return cb(e);\n          }\n\n          // Finally, send back the merged-together set of adapters.\n          return cb(undefined, _.extend(classicStyleAdapters, folderStyleAdapters));\n\n        }); //</fs.readdir>\n      }); //</includeall.optional>\n    },\n\n    /**\n     * Load app's model definitions\n     *\n     * @param {Object} options\n     * @param {Function} cb\n     */\n    loadModels: function (cb) {\n      // Get the main model files\n      includeAll.optional({\n        dirname   : sails.config.paths.models,\n        filter    : /^(.+)\\.(?:(?!md|txt).)+$/,\n        replaceExpr : /^.*\\//,\n        flatten: true\n      }, function(err, models) {\n        if (err) { return cb(err); }\n\n        // ---------------------------------------------------------\n        // Get any supplemental files (BACKWARDS-COMPAT.)\n        includeAll.optional({\n          dirname   : sails.config.paths.models,\n          filter    : /(.+)\\.attributes.json$/,\n          replaceExpr : /^.*\\//,\n          flatten: true\n        }, bindToSails(function(err, supplements) {\n          if (err) { return cb(err); }\n\n          if (_.keys(supplements).length > 0) {\n            sails.log.debug('The use of `.attributes.json` files is deprecated, and support will be removed in a future release of Sails.');\n          }\n\n          return cb(undefined, _.merge(models, supplements));\n        }));\n        // ---------------------------------------------------------\n      });\n    },\n\n    /**\n     * Load app services\n     *\n     * @param {Object} options\n     * @param {Function} cb\n     */\n    loadServices: function (cb) {\n      includeAll.optional({\n        dirname     : sails.config.paths.services,\n        filter      : /^(.+)\\.(?:(?!md|txt).)+$/,\n        depth     : 1,\n        caseSensitive : true\n      }, bindToSails(cb));\n    },\n\n    /**\n     * Check for the existence of views in the app\n     *\n     * @param {Object} options\n     * @param {Function} cb\n     */\n    statViews: function (cb) {\n      includeAll.optional({\n        dirname: sails.config.paths.views,\n        filter: /^(.+)\\.(?:(?!md|txt).)+$/,\n        replaceExpr: null,\n        dontLoad: true\n      }, cb);\n    },\n\n    /**\n     * Load app policies\n     *\n     * @param {Object} options\n     * @param {Function} cb\n     */\n    loadPolicies: function (cb) {\n      includeAll.optional({\n        dirname: sails.config.paths.policies,\n        filter: /^(.+)\\.(?:(?!md|txt).)+$/,\n        replaceExpr: null,\n        flatten: true,\n        keepDirectoryPath: true\n      }, bindToSails(cb));\n    },\n\n    /**\n     * Load app hooks\n     *\n     * > Note that, while `sails.config.hooks` is respected here in this\n     * > function, the `sails.config.loadHooks` setting in regards to\n     * > user hooks is taken care of in the initialize() method of the\n     * > userhooks hook itself.\n     *\n     * @param {Object} options\n     * @param {Function} cb\n     */\n    loadUserHooks: function (cb) {\n\n      var defaultInstalledHooks = _.filter(_.values(require('../../app/configuration/default-hooks')), function(val) {return val !== true;});\n\n      // Get the current app's package.json file (defaulting to an empty dictionary)\n      var appPackageJson;\n      try {\n        appPackageJson = require(path.resolve(sails.config.appPath, 'package.json'));\n      } catch (unusedErr) {\n        appPackageJson = {};\n      }\n\n      async.auto({\n        // Load user hooks from the \"api/hooks\" folder\n        hooksFolder: function(cb) {\n          includeAll.optional({\n            dirname: sails.config.paths.hooks,\n            filter: new RegExp('^(.+)\\\\.(' + BASIC_SUPPORTED_FILE_EXTENSIONS.join('|') + ')$'),\n\n            // Hooks should be defined as either single files as a function\n            // OR (better yet) a subfolder with an index.js file\n            // (like a standard node module)\n            depth: 2\n          }, cb);\n        },\n\n        // Load package.json files from node_modules to check for hooks\n        nodeModulesFolder: function(cb) {\n          includeAll.optional({\n            dirname: path.resolve(sails.config.appPath, 'node_modules'),\n            filter: /^(package\\.json)$/,\n            excludeDirs: /^\\./,\n            // Look inside namespaced folders e.g. node_modules/@sailsjs/sails-hook-foo\n            depth: 3,\n            // Don't actually load the files, since malformed ones would cause a crash.\n            // Just keep track of where they are and we'll load them carefully below.\n            dontLoad: true\n          }, function(err, modules) {\n            if (err) { return cb(err); }\n\n            // Now that we have a map of where the package.json files are, flatten that\n            // map and load the files carefully.  Map might look something like:\n            // { angular2:\n            //    { animate: {},\n            //      bundles: { web_worker: undefined },\n            //      es6: { dev: undefined, prod: undefined },\n            //      examples: { router: undefined },\n            //      http: {},\n            //      'package.json': true,\n            //      etc...\n            modules = (function _flatten(modules, installedHooks, currentPath, level) {\n              installedHooks = installedHooks || {};\n              currentPath = currentPath || '';\n              level = level || 0;\n              // Loop through the keys in the current map object\n              Object.keys(modules).forEach(function(identity) {\n                // If it represents a package.json file, attempt to load it and, if\n                // successful, save it in our set of found files.  If unsuccessful,\n                // just ignore it.\n                if (identity === 'package.json' && modules[identity] === true) {\n                  var filePath = path.resolve(sails.config.appPath, 'node_modules', currentPath, identity);\n                  try {\n                    // Attempt to load the package.json file\n                    var packageJson = require(filePath);\n                    // If the module isn't declared as a Sails hook, ignore it.\n                    if (!packageJson.sails || !packageJson.sails.isHook) {\n                      return;\n                    }\n                    // If the module isn't saved in this app's package.json, ignore it.\n                    if (!_.get(appPackageJson, 'dependencies.' + packageJson.name) && !_.get(appPackageJson, 'devDependencies.' + packageJson.name) && !_.get(appPackageJson, 'optionalDependencies.' + packageJson.name)) {\n                      sails.log.debug('Ignoring hook `' + packageJson.name + '` because it isn\\'t saved as any kind of dependency in your package.json file.');\n                      sails.log.debug('(You could try installing it with `npm install ' + packageJson.name +' --save`.  Or if you aren\\'t using this hook,');\n                      sails.log.debug('just remove it from the node_modules/ folder and this message will stop appearing.)');\n                      sails.log.debug();\n                      return;\n                    }\n\n                    // If it's one of our default hooks, ignore it so that it can be safely overridden.\n                    if (_.contains(defaultInstalledHooks, packageJson.name)) {\n                      return;\n                    }\n                    // Save a reference to this installed hook, which we'll use to require\n                    // the full module below.\n                    installedHooks[currentPath] = packageJson;\n                  } catch(e) {\n                    sails.log.verbose('While searching for installable hooks, found invalid package.json file at `'+filePath+'`.  Details:',e.stack);\n                    return;\n                  }\n                }\n                // If the key represents an object, recursively search within it, but only if it's directly\n                // under node_modules or under a node_modules/@something (namespaced) folder\n                if (_.isObject(modules[identity]) && level === 0 || (level === 1 && currentPath[0] === '@')) {\n                  var nextPath;\n                  if (currentPath) { nextPath = path.join(currentPath,identity); }\n                  else { nextPath = identity; }\n\n                  _flatten(modules[identity], installedHooks, nextPath, level + 1 );\n                }\n              });//</forEach() :: key in `modules`>\n\n              // Return the dictionary of installed hooks we found.\n              return installedHooks;\n            })(modules);//</ invoked self-calling recursive function :: _flatten()>\n\n            return cb(undefined, modules);\n          });//</includeAll.optional() :: loading package.json files from the node_modules folder to check for hooks>\n        }\n      }, function(err, results) {\n        if (err) {return cb(err);}\n\n        // Marshall the hooks by checking that they are valid.  The ones from the\n        // api/hooks folder are assumed to be okay, as long as they aren't explicitly turned off.\n        var hooks = _.reduce(results.hooksFolder, function(memo, module, identity) {\n          if (sails.config.hooks[identity] !== false && sails.config.hooks[identity] !== 'false') {\n            memo[identity] = module;\n          }\n          return memo;\n        }, {});\n\n        try {\n\n          // Loop through the package.json files of the hooks we found in the node_modules folder.\n          _.extend(hooks, _.reduce(results.nodeModulesFolder, function(memo, modulePackageJson, identity) {\n\n            // Any special config for this hook will be under the `sails` key in the package.json file.\n            var hookConfig = modulePackageJson.sails;\n\n            // Determine the name the hook should be added as\n            var hookName;\n\n            if (!_.isEmpty(hookConfig.hookName)) {\n              hookName = hookConfig.hookName;\n            }\n            // If an identity was specified in sails.config.installedHooks, use that\n            else if (sails.config.installedHooks && sails.config.installedHooks[identity] && sails.config.installedHooks[identity].name) {\n              hookName = sails.config.installedHooks[identity].name;\n            }\n            // Otherwise use the module name, with namespacing and initial \"sails-hook-\" stripped off if it exists\n            else {\n              // Strip off any NPM namespacing and/or sails-hook- prefix\n              hookName = identity.replace(/^(@.+?[\\/\\\\])?(sails-hook-)?/, '');\n            }\n\n            if (sails.config.hooks[hookName] === false || sails.config.hooks[hookName] === 'false') {\n              return memo;\n            }\n\n            // Allow overriding core hooks\n            if (sails.hooks[hookName]) {\n              sails.log.verbose('Found hook: `'+hookName+'` in `node_modules/`.  Overriding core hook w/ the same identity...');\n            }\n\n            // If we have a hook in api/hooks with this name, throw an error\n            if (hooks[hookName]) {\n              var err = (function (){\n                var msg =\n                'Found hook: `' + hookName + '`, in `node_modules/`, but a hook with that identity already exists in `api/hooks/`. '+\n                'The hook defined in your `api/hooks/` folder will take precedence.';\n                var err = new Error(msg);\n                err.code = 'E_INVALID_HOOK_NAME';\n                return err;\n              })();\n              sails.log.warn(err);\n              return memo;\n            }\n\n            // Load the hook code\n            var hook = require(path.resolve(sails.config.appPath, 'node_modules', identity));\n\n            // Set its config key (defaults to the hook name)\n            hook.configKey = (sails.config.installedHooks && sails.config.installedHooks[identity] && sails.config.installedHooks[identity].configKey) || hookName;\n\n            // Add this to the list of hooks to load\n            memo[hookName] = hook;\n\n            return memo;\n          }, {}));//</_.reduce() + _.extend()>\n\n          return bindToSails(cb)(null, hooks);\n\n        } catch (e) { return cb(e); }\n      });//</after async.auto>\n    },//<loadUserHooks>\n\n    /**\n     * Load custom blueprint actions.\n     *\n     * @param {Object} options\n     * @param {Function} cb\n     */\n    loadBlueprints: function (cb) {\n      includeAll.optional({\n        dirname: sails.config.paths.blueprints,\n        filter: /^(.+)\\.(?:(?!md|txt).)+$/,\n        useGlobalIdForKeyName: true\n      }, cb);\n    },\n\n    /**\n     * Load custom API responses.\n     *\n     * @param {Object} options\n     * @param {Function} cb\n     */\n    loadResponses: function (cb) {\n      includeAll.optional({\n        dirname: sails.config.paths.responses,\n        filter: /^(.+)\\.(?:(?!md|txt).)+$/,\n        useGlobalIdForKeyName: true\n      }, bindToSails(cb));\n    },\n\n    optional: includeAll.optional,\n    required: includeAll.required,\n    aggregate: includeAll.aggregate,\n    exists: includeAll.exists\n\n  };\n\n\n\n  /**\n   * Private helper function used above.\n   *\n   * @param  {Function} cb [description]\n   * @return {Function}\n   *         @param {Error?} err\n   *         @param {Dictionary} modules\n   */\n  function bindToSails(cb) {\n    return function(err, modules) {\n      if (err) {return cb(err);}\n      _.each(modules, function(moduleDef) {\n        // Add a reference to the Sails app that loaded the module\n        moduleDef.sails = sails;\n        // Bind all methods to the module context\n        _.bindAll(moduleDef);\n      });\n      return cb(undefined, modules);\n    };\n  }//</bindToSails definition (private helper function)>\n\n};\n"
  },
  {
    "path": "lib/hooks/policies/README.md",
    "content": "# Policies (Core Hook)\n\n## Status\n\n> ##### Stability: [2](https://github.com/balderdashy/sails-docs/blob/master/contributing/stability-index.md) - Stable\n\n\n## Purpose\n\nThis hook's responsibilities are:\n\n1. Use `sails.modules` to read policies from the user's app into `self.middleware`.\n2. Normalize the policy mapping config (`sails.config.policies`)\n3. Listen for `route:typeUnknown` and bind a policy if the route requests it.\n4. Listen for `router:before` and when it fires, transform loaded middleware that match the policy mapping config (i.e. controller actions) to arrays of functions, where the original middleware is \"protected\" by one or more relevant policy middleware.\n\n\n\n## FAQ\n\n> No frequently asked questions yet...\n>\n> If you have a question, please feel free to send a PR adding it to this section (even if you don't have the answer!)\n\n"
  },
  {
    "path": "lib/hooks/policies/index.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar flaverr = require('flaverr');\nvar Err = require('../../../errors');\n\n\n/**\n * Policies hook\n * @param  {SailsApp} sails\n */\nmodule.exports = function(sails) {\n\n  /**\n   * Expose `policies` hook definition\n   */\n  var policyHookDef = {\n\n    defaults: {\n\n      // Default policy mappings (allow all)\n      policies: { }\n    },\n\n    configure: function () {\n      this.middleware || (this.middleware = { });\n    },\n\n    /**\n     * Initialize is fired first thing when the hook is loaded\n     *\n     * @api public\n     */\n    initialize: function(cb) {\n\n      var self = this;\n\n      // Grab policies config & policy modules and trigger callback\n      self.loadMiddleware(function (err) {\n        if (err) { return cb(err); }\n\n        sails.log.silly('Finished loading policy middleware functions.  Preparing to bind policies based on config...');\n        try {\n          self.bindPolicies();\n        } catch (e) { return cb(e); }\n\n        return cb();\n      });\n\n    },\n\n    /**\n     * Wipe everything and (re)load middleware from policies\n     * (policies.js config is already loaded at this point)\n     *\n     * @api private\n     */\n    loadMiddleware: function(cb) {\n\n      var self = this;\n\n      // Load policy modules from disk.\n      sails.log.silly('Loading policy modules from app...');\n      sails.modules.loadPolicies(function modulesLoaded (err, modules) {\n        if (err) { return cb(err); }\n\n        // Add the loaded policies to our internal dictionary.\n        _.extend(self.middleware, modules);\n\n        // If any policies were specified when loading Sails, add those on\n        // top of the ones loaded from disk.\n        if (sails.config.policies && sails.config.policies.moduleDefinitions) {\n          _.extend(self.middleware, sails.config.policies.moduleDefinitions);\n        }\n\n        // Validate that all policies are functions.\n        try {\n          _.each(_.keys(self.middleware), function(policyName) {\n            // If we find a bad'n, bail out.\n            if (!_.isFunction(sails.hooks.policies.middleware[policyName])) {\n              throw flaverr({ name: 'userError', code: 'E_INVALID_POLICY' }, new Error('Failed loading invalid policy `' + policyName + '` (expected a function, but got a `' + typeof(sails.hooks.policies.middleware[policyName]) + '`)' ));\n            }\n          });\n        } catch (e) {\n          return cb(e);\n        }\n\n        // Set the _middlewareType property on each policy.\n        _.each(self.middleware, function(policyFn, policyName) {\n          policyFn._middlewareType = 'POLICY: '+policyName;\n        });\n\n        return cb();\n      });\n    },\n\n    /**\n     * Curry the policy chains into the appropriate controller functions\n     *\n     * @api private\n     */\n    bindPolicies: function() {\n\n      // Build / normalize policy config\n      this.mapping = this.buildPolicyMap();\n\n      // Register action middleware for each item in the map\n      _.each(this.mapping, function(policies, targets) {\n        sails.registerActionMiddleware(policies, targets);\n      });\n\n      // Emit event to let other hooks know we're ready to go\n      sails.log.silly('Policy-controller bindings complete!');\n      sails.emit('hook:policies:bound');\n    },\n\n\n    /**\n     * Build normalized, hook-internal representation of policy mapping\n     * by performing a non-destructive parse of `sails.config.policies`\n     *\n     * @returns {Object} mapping\n     * @api private\n     */\n    buildPolicyMap: function () {\n\n      // Loop through the keys looking for the old-style \"controller-based\" policy config,\n      // and if we find it then expand it out to the new style.\n      _.each(_.without(_.keys(sails.config.policies), 'moduleDefinitions'), function(key) {\n\n        // Is this a plain dictionary, e.g. UserController: { '*': true } ?\n        if (_.isPlainObject(sails.config.policies[key])) {\n\n          // Get the controller name by stripping off the (optional) trailing \"Controller\"\n          var controller = key.replace(/Controller$/,'').toLowerCase();\n\n          // For each item (i.e. action) in the dictionary, add an entry to the config.\n          _.each(_.keys(sails.config.policies[key]), function(action) {\n\n            // Get the policies to attach to this action.\n            var policies = sails.config.policies[key][action];\n\n            // Add the target/policies mapping to sails.config.policies\n            sails.config.policies[controller + '/' + action.toLowerCase()] = policies;\n          });\n\n          // Remove the deprecated config key.\n          delete sails.config.policies[key];\n\n        }\n\n        // Make sure all standalone action glob keys are lowercased.\n        else if (key !== key.toLowerCase()) {\n\n          sails.config.policies[key.toLowerCase()] = sails.config.policies[key];\n          delete sails.config.policies[key];\n\n        }\n\n      });\n\n      // Sort the policy keys alphabetically, ensuring that more restrictive\n      // keys (e.g. user/foo) come after less restrictive (e.g. user/*).\n      // Ignore `moduleDefinitions` since it is a special key used to allow\n      // programmatic setting of policy functions.\n      var actionsToProtect = _.without(_.keys(sails.config.policies), 'moduleDefinitions').sort();\n\n      // Declare a \"never allow\" function to use when a policy of `false` is encountered.\n      var neverAllow = function neverAllow (req, res) {\n        return res.forbidden();\n      };\n      neverAllow._middlewareType = 'POLICY: false (neverAllow)';\n\n      // Declare a \"never allow\" function to use when a policy of `false` is encountered.\n      var alwaysAllow = function alwaysAllow (req, res, next) {\n        return next();\n      };\n      alwaysAllow._middlewareType = 'POLICY: true (alwaysAllow)';\n\n      // Loop through the keys and create the map.\n      var mapping = _.reduce(actionsToProtect, function (memo, target, index) {\n\n        // Allow bald `true` and `false` policies by wrapping them in an array.\n        if (sails.config.policies[target] === true || sails.config.policies[target] === false) {\n          sails.config.policies[target] = [sails.config.policies[target]];\n        }\n\n        // Make sure policies are contained in an array.\n        if (!_.isArray(sails.config.policies[target])) {\n          sails.config.policies[target] = [sails.config.policies[target]];\n        }\n\n        // Get the policies the user wants to add to this set of actions.\n        // Note the use of _.compact to transform [undefined] into [].\n        var policies = _.compact(_.map(sails.config.policies[target], function(policy) {\n          // If the policy is `true`, make sure it's the only one for this target.\n          if (policy === true) {\n            if (sails.config.policies[target].length > 1) {\n              throw flaverr({ name: 'userError', code: 'E_INVALID_POLICY_CONFIG' }, new Error('Invalid policy setting for `' + target + '`: if `true` is specified, it must be the only policy in the array.'));\n            }\n            // Map `true` to  the \"always allow\" policy.\n            return alwaysAllow;\n          }\n          // If the policy is `false`, make sure it's the only one for this target.\n          if (policy === false) {\n            if (sails.config.policies[target].length > 1) {\n              throw flaverr({ name: 'userError', code: 'E_INVALID_POLICY_CONFIG' }, new Error('Invalid policy setting for `' + target + '`: if `false` is specified, it must be the only policy in the array.'));\n            }\n            // Map `false` to  the \"never allow\" policy.\n            return neverAllow;\n          }\n          // If the policy is a string, make sure it corresponds to one of the policies we loaded.\n          if (_.isString(policy)) {\n            if (!sails.hooks.policies.middleware[policy.toLowerCase()]) {\n              throw flaverr({ name: 'userError', code: 'E_INVALID_POLICY_CONFIG' }, new Error('Invalid policy setting for `' + target + '`: `' + policy + '` does not correspond to any of the loaded policies.'));\n            }\n            return sails.hooks.policies.middleware[policy.toLowerCase()];\n          }\n          // If the policy is a function, return it.\n          if (_.isFunction(policy)) {\n            policy._middlewareType = 'POLICY: ' + (policy.name || 'anonymous');\n            return policy;\n          }\n          // Otherwise just bail.\n          throw flaverr({ name: 'userError', code: 'E_INVALID_POLICY_CONFIG' }, new Error('Invalid policy setting for `' + target + '`: a policy must be a string, a function or `false`.'));\n\n        }));\n\n        // Start an array of targets that this set of policies will be applied to or ignored for.\n        var allowDenyList = [target];\n\n        // If this is the global target, loop through the rest of the targets and exclude them\n        // from this one.  We may change this behavior / make it optional in the future,\n        // but for now policies are NOT cumulative.\n        if (target === '*') {\n          (function() {\n            for (var i = index + 1; i < actionsToProtect.length; i++) {\n              var nextTarget = actionsToProtect[i];\n              allowDenyList.push('!' + nextTarget);\n            }\n          })();\n        }\n\n        // If this target is a wildcard, then any other target that matches it will\n        // override it.  We may change this behavior / make it optional in the future,\n        // but for now policies are NOT cumulative.\n        else if (target.slice(-2) === '/*') {\n          (function() {\n            // Get a version of the target without the /*\n            var nakedTarget = target.slice(0,-2);\n            // Get a version of the target without the .\n            var slashTarget = target.slice(0,-1);\n            // If we already bound a policy to the naked target, then flag that the\n            // current policy should _not_ be applied to it.\n            if (memo[nakedTarget]) {\n              allowDenyList.push('!' + nakedTarget);\n            }\n            // Now run through the rest of the targets in the list, and if any of them\n            // start with the \"slashTarget\", make sure this policy does _not_ apply to them.\n            // So if our target is `user/foo/*`, and we see `user/foo/bar` in the list,\n            // we will add that to the blacklist for this policy.\n            for (var i = index + 1; i < actionsToProtect.length; i++) {\n              var nextTarget = actionsToProtect[i];\n              if (nextTarget.indexOf(slashTarget) === 0) {\n                allowDenyList.push('!' + nextTarget);\n              }\n              // As soon as we find a non-matching target, we're done (because they're\n              // arranged in alphabetical order).\n              else {\n                break;\n              }\n            }\n          })();\n        }\n\n        // Transform the allow/deny list into a comma-delimited string that can be\n        // understood by `registerActionMiddleware`.\n        memo[allowDenyList.join(',')] = policies;\n\n        return memo;\n\n      }, {});\n\n      return mapping;\n    },\n\n  };\n\n  /**\n   * Bind `route:typeUnknown` event handler in order to support\n   * the `policy: '...` route option.\n   *\n   * > This allows for manually mapping policies directly on top\n   * > of explicit routes; e.g. in `config/routes.js`\n   */\n  sails.on('route:typeUnknown', function bindDirectlyToRoute (event) {\n\n    // Only pay attention to delegated route events\n    // if `policy` is declared in event.target\n    if ( !event.target || !event.target.policy ) {\n      return;\n    }\n\n    var policyId = event.target.policy.toLowerCase();\n\n    // Policy doesn't exist\n    if (!sails.hooks.policies.middleware[policyId] ) {\n      var routeAddrToDisplay = event.verb ? event.verb+' '+event.path : event.path;\n      Err.fatal.__UnknownPolicy__ (policyId, routeAddrToDisplay, sails.config.paths.policies);\n      // ^^That begins terminating the process.\n      return;\n    }//-•\n\n    // Bind policy function to route.\n    // Make sure to merge the target and options together, so that route options like `skipRegex`\n    // are still applied to requests before running the policy.\n    var fn = sails.hooks.policies.middleware[policyId];\n    sails.router.bind(event.path, fn, event.verb, _.merge(event.options, event.target));\n  });\n\n  return policyHookDef;\n};\n\n\n"
  },
  {
    "path": "lib/hooks/pubsub/README.md",
    "content": "# Pubsub (core hook)\n\nSee http://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub for more info.\n"
  },
  {
    "path": "lib/hooks/pubsub/index.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\n\n\n\n\n/**\n * Module errors\n */\n\nvar Err = {\n  dependency: function (dependent, dependency) {\n    return new Error( '\\n' +\n      'Cannot use `' + dependent + '` hook ' +\n      'without the `' + dependency + '` hook enabled!'\n    );\n  }\n};\n\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n// TODO: Remove this hook altogether, instead splitting its contents between\n// the `blueprints` and `sockets` hooks.\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n\n/**\n * pubsub hook\n *\n * > Implements public resourceful pubsub (RPS) methods, as well as some\n * > private methods used by the blueprints hook.\n */\n\nmodule.exports = function(sails) {\n\n  // Private function for parsing a potential instance ID.\n  var parseId = function (id) {\n    if(!_.isObject(this.attributes, this.primaryKey)) {\n      return id;\n    }\n    var pkAttrDef = this.attributes[this.primaryKey];\n    if(_.isPlainObject(pkAttrDef)) {\n      if (pkAttrDef.type === 'number') {\n        return parseInt(id);\n      } else if (pkAttrDef.type === 'string') {\n        return new String(id).toString(); //jshint ignore:line\n      }\n    }\n\n    return id;\n  };\n\n  /**\n   * Check that records are a list, if not, make them a list\n   * Also if they are ids, make them dummy objects with an `id` property\n   *\n   * @param {Object|Array|String|Finite} records\n   * @returns {Array} array of things that have an `id` property\n   *\n   * @api private\n   * @synchronous\n   */\n  var pluralize = function (records) {\n\n    // If `records` is a non-array object,\n    // turn it into a single-item array (\"pluralize\" it)\n    // e.g. { id: 7 } -----> [ { id: 7 } ]\n    if ( !_.isArray(records) ) {\n      var record = records;\n      records = [record];\n    }\n\n    // If a list of ids things look ids (finite numbers or strings),\n    // wrap them up as dummy objects; e.g. [1,2] ---> [ {id: 1}, {id: 2} ]\n    var self = this;\n    return _.map(records, function (record) {\n      if ( _.isString(record) || _.isFinite(record) ) {\n        var id = record;\n        var data = {};\n        data[self.primaryKey] = id;\n        return data;\n      }\n      if (_.isNull(record) || _.isUndefined(record)) {\n        throw new Error('Could not coerce value into an array of records!');\n      }\n      return record;\n    });\n  };\n\n  /**\n   * Expose Hook definition\n   */\n\n  return {\n\n\n    initialize: function(cb) {\n\n      var self = this;\n\n      // If `views` or `orm` hook is not enabled, complain and disable the hook.\n      if (!sails.hooks.sockets || !sails.hooks.orm) {\n        sails.log.verbose('Cannot use `pubsub` hook without the `sockets` and `orm` hooks enabled!  (Skipping...)');\n        delete sails.hooks.pubsub;\n        return cb();\n      }\n\n\n      // If `views` or `orm` hook is not enabled, complain and respond w/ error\n      if (!sails.hooks.sockets) {\n        return cb( Err.dependency('pubsub', 'sockets') );\n      }\n\n\n      if (!sails.hooks.orm) {\n        return cb( Err.dependency('pubsub', 'orm') );\n      }\n\n      // Wait for `hook:orm:loaded`\n      sails.on('hook:orm:loaded', function() {\n\n        // Do the heavy lifting\n        self.augmentModels();\n\n        // Indicate that the hook is fully loaded\n        cb();\n\n      });\n\n      // When the orm is reloaded, re-apply all of the pubsub methods to the\n      // models\n      sails.on('hook:orm:reloaded', function() {\n        self.augmentModels();\n\n        // Trigger an event in case something needs to respond to the pubsub reload\n        sails.emit('hook:pubsub:reloaded');\n      });\n\n    },\n\n    augmentModels: function() {\n      // Augment models with room/socket logic (& bind context)\n      for (var identity in sails.models) {\n        var AugmentedModel = _.defaults(sails.models[identity], getPubsubMethods(), {autosubscribe: true} );\n        _.bindAll(AugmentedModel,\n          'subscribe',\n          'unsubscribe',\n          'publish',\n          '_watch',\n          '_room',\n          '_introduce',\n          '_retire',\n          '_publishCreate',\n          '_publishUpdate',\n          '_publishDestroy',\n          '_publishAdd',\n          '_publishRemove'\n        );\n        sails.models[identity] = AugmentedModel;\n      }\n    }\n\n  };\n\n\n  /**\n   * These methods get appended to the Model class objects\n   * Some take req.socket as an argument to get access\n   * to user('s|s') socket object(s)\n   */\n\n  function getPubsubMethods () {\n\n    return {\n\n      //  ██████╗ ██╗   ██╗██████╗ ██╗     ██╗ ██████╗\n      //  ██╔══██╗██║   ██║██╔══██╗██║     ██║██╔════╝\n      //  ██████╔╝██║   ██║██████╔╝██║     ██║██║\n      //  ██╔═══╝ ██║   ██║██╔══██╗██║     ██║██║\n      //  ██║     ╚██████╔╝██████╔╝███████╗██║╚██████╗\n      //  ╚═╝      ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═════╝\n      //\n\n      /**\n       * Broadcast a custom message to sockets connected to the specified records\n       * @param {Object|String|Finite} records -- record or ID of record whose subscribers should receive the message\n       * @param {Object|Array|String|Finite} data -- the message payload\n       * @param {Request|Socket} req - if specified, broadcast using this\n       *                         socket (effectively omitting it)\n       *\n       */\n\n      publish: function(ids, data, req) {\n\n        var self = this;\n\n        // ids is required.\n        if (!ids) {\n          sails.log.error('`' + self.identity + '.publish` : missing or empty second argument `ids`. API is `.publish(ids, data [, req])`.');\n          return;\n        }\n\n        // ids must be an array of primary keys -- we'll coerce it (with a warning) if it's not.\n        if (!_.isArray(ids) || _.any(ids, function(id) {return !_.isString(id) && !_.isNumber(id);})) {\n          sails.log.debug('The first argument passed to `' + self.identity + '.publish()` must be an array of ids.  To subscribe to a single record, wrap the id in an array.');\n          try {\n            ids = _.pluck(pluralize.apply(this, [ids]), this.primaryKey);\n          } catch (err) {\n            throw new Error('We tried to transform `' + util.inspect(ids, {depth: 2}) + '` into an array of IDs, but there was a problem (could some values have been `null` or `undefined`?)  Details: '+err.stack);\n          }\n          sails.log.debug('For example: `[' + ids[0] + ']`');\n          sails.log.debug('Wrapping it in an array for you this time...');\n        }\n\n        // If a request object was sent, get its socket, otherwise assume a socket was sent.\n        var socketToOmit = (req && req.socket ? req.socket : req);\n\n        // Ensure that we're working with a clean, unencumbered object\n        data = _.cloneDeep(data);\n\n        // Loop through the record IDs to broadcast to.\n        _.each(ids, function(id) {\n          var room = self._room(id);\n          sails.sockets.broadcast( room, self.identity, data, socketToOmit );\n        });\n\n      },\n\n      /**\n      * Subscribe a socket to a handful of records in this model\n      *\n      * Usage:\n      * Model.subscribe(req, ids)\n      *\n      * @param {Request|Socket} req - request containing the socket to subscribe, or the socket itself\n      * @param {Array} ids - array of ids of instances to subscribe to\n      *\n      *   // Subscribe to User.update() and User.destroy()\n      *   // for the specified instances (or user.save() / user.destroy()):\n      *   User.subscribe(req.socket, users)\n      *\n      * @api public\n      */\n      subscribe: function (req, ids) {\n\n        var self = this;\n\n        // If a request object was sent, get its socket, otherwise assume a socket was sent.\n        var socket = sails.sockets.parseSocket(req);\n\n        // Request must originate from a socket.\n        if (!socket) {\n          sails.log.debug('`Model.subscribe()` called by a non-socket request. Only requests originating from a connected socket may be subscribed. Ignoring...');\n          return;\n        }\n\n        if (!ids) {\n          sails.log.error('`' + self.identity + '.subscribe` : missing or empty second argument `ids`. API is `.subscribe(request, ids)`.');\n          return;\n        }\n\n        if (!_.isArray(ids) || _.any(ids, function(id) {return !_.isString(id) && !_.isNumber(id);})) {\n          sails.log.debug('The second argument passed to `' + self.identity + '.subscribe()` must be an array of ids.  To subscribe to a single record, wrap the id in an array.');\n          try {\n            ids = _.pluck(pluralize.apply(this, [ids]), this.primaryKey);\n          } catch (err) {\n            throw new Error('We tried to transform `' + util.inspect(ids, {depth: 2}) + '` into an array of IDs, but there was a problem (could some values have been `null` or `undefined`?)  Details: '+err.stack);\n          }\n          sails.log.debug('For example: `[' + ids[0] + ']`');\n          sails.log.debug('Wrapping it in an array for you this time...');\n        }\n\n        for (let id of ids) {\n          // Attempt to join the room for the specified instance.\n          sails.sockets.join( socket, self._room(id) );\n          sails.log.silly(\n            'Subscribed to the ' +\n            self.globalId + ' with id=' + id + '\\t(room :: ' + self._room(id) + ')'\n          );\n        }//∞\n      },\n\n      /**\n       * Unsubscribe a socket from some records\n       *\n       * Usage:\n       * Model.unsubscribe(req, ids)\n       *\n       * @param {Request|Socket} req - request containing the socket to unsubscribe, or the socket itself\n       * @param {Array} ids - array of ids of instances to unsubscribe from\n       */\n\n      unsubscribe: function (req, ids) {\n\n        var self = this;\n\n        // If a request object was sent, get its socket, otherwise assume a socket was sent.\n        var socket = sails.sockets.parseSocket(req);\n\n        if (!socket) {\n          sails.log.debug('`Model.unsubscribe()` called by a non-socket request. Only requests originating from a connected socket may be subscribed. Ignoring...');\n          return;\n        }\n\n        // If no ids provided, unsubscribe from the class room\n        if (!ids) {\n          sails.log.error('`' + self.identity + '.unsubscribe` : missing or empty second argument `ids`. API is `.subscribe(request, ids)`.');\n          return;\n        }\n\n        // ids must be an array of primary keys -- we'll coerce it (with a warning) if it's not.\n        if (!_.isArray(ids) || _.any(ids, function(id) {return !_.isString(id) && !_.isNumber(id);})) {\n          sails.log.debug('The second argument passed to `' + self.identity + '.unsubscribe()` must be an array of ids.  To subscribe to a single record, wrap the id in an array.');\n          try {\n            ids = _.pluck(pluralize.apply(this, [ids]), this.primaryKey);\n          } catch (err) {\n            throw new Error('We tried to transform `' + util.inspect(ids, {depth: 2}) + '` into an array of IDs, but there was a problem (could some values have been `null` or `undefined`?)  Details: '+err.stack);\n          }\n          sails.log.debug('For example: `[' + ids[0] + ']`');\n          sails.log.debug('Wrapping it in an array for you this time...');\n        }\n\n        for (let id of ids) {\n          // Attempt to leave the room for the specified instance.\n          sails.sockets.leave( socket, self._room(id));\n          sails.log.silly(\n            'Unsubscribed from the ' +\n            self.globalId + ' with id=' + id + '\\t(room :: ' + self._room(id) + ')'\n          );\n        }//∞\n      },\n\n      /**\n       * Get the socket room name for a model instance.\n       *\n       * Usage:\n       * Model.getRoomName(id)\n       *\n       * @param {Number|String} id - the ID of the instance to get the room name for.\n       */\n\n      getRoomName: function(id) {\n        return this._room(id);\n      },\n\n      //  ██████╗ ██████╗ ██╗██╗   ██╗ █████╗ ████████╗███████╗\n      //  ██╔══██╗██╔══██╗██║██║   ██║██╔══██╗╚══██╔══╝██╔════╝\n      //  ██████╔╝██████╔╝██║██║   ██║███████║   ██║   █████╗\n      //  ██╔═══╝ ██╔══██╗██║╚██╗ ██╔╝██╔══██║   ██║   ██╔══╝\n      //  ██║     ██║  ██║██║ ╚████╔╝ ██║  ██║   ██║   ███████╗\n      //  ╚═╝     ╚═╝  ╚═╝╚═╝  ╚═══╝  ╚═╝  ╚═╝   ╚═╝   ╚══════╝\n      //\n\n      /**\n       * Broadcast a resourceful pubsub message to sockets connected to the specified records\n       * (or null to broadcast to the entire class room)\n       *\n       * @param {Object|Array|String|Finite} records -- records whose subscribers should receive the message\n       * @param {Object|Array|String|Finite} data -- the message payload\n       * socket (effectively omitting it)\n       *\n       * @api private\n       */\n\n      _publishRPS: function (records, data, req) {\n\n        var self = this;\n\n        records = pluralize.apply(this, [records]);\n        var ids = _.pluck(records, this.primaryKey);\n        if ( ids.length === 0 ) {\n          sails.log.debug('Can\\'t publish a message to an empty list of instances-- ignoring...');\n        }\n\n        // If a request object was sent, get its socket, otherwise assume a socket was sent.\n        var socketToOmit = (req && req.socket ? req.socket : req);\n\n        // Ensure that we're working with a clean, unencumbered object\n        data = _.cloneDeep(data);\n\n        // Loop through the record IDs to broadcast to.\n        _.each(ids, function(id) {\n          sails.sockets.broadcast( self._room(id), self.identity, data, socketToOmit );\n        });\n\n      },\n\n      /**\n       * @param  {String|Number} id Unique ID (i.e. primary key) of the record to get the room for\n       * @param  {String} name Name of the room to get the identifier for\n       * @return {String}    name of the instance room for an instance of this model w/ given id\n       * @synchronous\n       */\n      _room: function (id) {\n\n        if (!id) {\n          sails.log.error('Must specify an `id` when calling `Model._room(id)`');\n          return;\n        }\n\n        return 'sails_model_'+this.identity+'_'+id+':'+this.identity;\n      },\n\n      /**\n       * @return {String} name of this model's global class room\n       * @synchronous\n       * @api private\n       */\n      _classRoom: function() {\n        return 'sails_model_create_'+this.identity;\n      },\n\n\n      /**\n       * Publish an update on a particular model\n       *\n       * @param {String|Finite} id\n       *    - primary key of the instance we're referring to\n       *\n       * @param {Object} changes\n       *    - an object of changes to this instance that will be broadcasted\n       *\n       * @param {Request|Socket} req - if specified, broadcast using this socket (effectively omitting it)\n       *\n       * @api public\n       */\n\n      _publishUpdate: function (id, changes, req, options) {\n        var reverseAssociation;\n\n        // Make sure there's an options object\n        options = options || {};\n\n        // Ensure that we're working with a clean, unencumbered object\n        changes = _.cloneDeep(changes);\n\n        // Enforce valid usage\n        var validId = _.isString(id) || _.isFinite(id);\n        if ( !validId  ) {\n          return sails.log.error(\n            'Invalid usage of `' + this.identity +\n            '._publishUpdate(id, changes, [socketToOmit])`'\n          );\n        }\n\n        if (_.isFunction(this._beforePublishUpdate)) {\n          this._beforePublishUpdate(id, changes, req, options);\n        }\n\n        // Coerce id to match the attribute type of the primary key of the model\n        id = parseId.apply(this,[id]);\n        var data = {\n          model: this.identity,\n          verb: 'update',\n          data: changes,\n          id: id\n        };\n\n        if (options.previous && !options.noReverse) {\n\n          var previous = options.previous;\n\n          // If any of the changes were to association attributes, publish add or remove messages.\n          _.each(changes, function(val, key) {\n\n            // If value wasn't changed, do nothing\n            if (val === previous[key]) {\n              return;\n            }\n\n            // Find an association matching this attribute\n            var association = _.find(this.associations, {alias: key});\n\n            // If the attribute isn't an assoctiation, return\n            if (!association) {\n              return;\n            }\n\n            // Get the associated model class\n            var ReferencedModel = sails.models[association.type === 'model' ? association.model : association.collection];\n\n            // Bail if this attribute isn't in the model's schema\n            if (association.type === 'model') {\n\n              var previousPK = _.isObject(previous[key]) ? previous[key][ReferencedModel.primaryKey] : previous[key];\n              var newPK = _.isObject(val) ? val[this.primaryKey] : val;\n              if (previousPK === newPK) {\n                return;\n              }\n\n              // Get the inverse association definition, if any\n              reverseAssociation = _.find(ReferencedModel.associations, {collection: this.identity, via: key}) || _.find(ReferencedModel.associations, {model: this.identity, via: key});\n\n              if (!reverseAssociation) {return;}\n\n              // If this is a to-many association, do _publishAdd or _publishRemove as necessary\n              // on the other side\n              if (reverseAssociation.type === 'collection') {\n                // If there was a previous value, alert the previously associated model\n                if (previous[key]) {\n                  ReferencedModel._publishRemove(previousPK, reverseAssociation.alias, id, req, {noReverse:true});\n                }\n                // If there's a new value (i.e. it's not null), alert the newly associated model\n                if (val) {\n                  ReferencedModel._publishAdd(newPK, reverseAssociation.alias, id, req, {noReverse:true});\n                }\n              }\n              // Otherwise do a _publishUpdate\n              else {\n\n                var pubData = {};\n\n                // If there was a previous association, notify it that it has been nullified\n                if (previous[key]) {\n                  pubData[reverseAssociation.alias] = null;\n                  ReferencedModel._publishUpdate(previousPK, pubData, req, {noReverse:true});\n                }\n                // If there's a new association, notify it that it has been linked\n                if (val) {\n                  pubData[reverseAssociation.alias] = id;\n                  ReferencedModel._publishUpdate(newPK, pubData, req, {noReverse:true});\n                }\n\n              }\n\n            }\n\n            else {\n\n              // Get the reverse association definition, if any\n              reverseAssociation = _.find(ReferencedModel.associations, {collection: this.identity, via: key}) || _.find(ReferencedModel.associations, {model: this.identity, alias: association.via});\n\n              if (!reverseAssociation) {return;}\n\n              // If we can't get the previous PKs (b/c previous isn't populated), bail\n              if (_.isUndefined(previous[key])) {\n                return;\n              }\n\n              // Get the previous set of IDs\n              var previousPKs = _.pluck(previous[key], ReferencedModel.primaryKey);\n              // Get the current set of IDs\n              var updatedPKs = _.map(val, function(_val) {\n                if (_.isObject(_val)) {\n                  return _val[ReferencedModel.primaryKey];\n                } else {\n                  return _val;\n                }\n              });\n              // Find any values that were added to the collection\n              var addedPKs = _.difference(updatedPKs, previousPKs);\n              // Find any values that were removed from the collection\n              var removedPKs = _.difference(previousPKs, updatedPKs);\n\n              // If this is a to-many association, do _publishAdd or _publishRemove as necessary\n              // on the other side\n              if (reverseAssociation.type === 'collection') {\n\n                // Alert any removed models\n                _.each(removedPKs, function(pk) {\n                  ReferencedModel._publishRemove(pk, reverseAssociation.alias, id, req, {noReverse:true});\n                });\n                // Alert any added models\n                _.each(addedPKs, function(pk) {\n                  ReferencedModel._publishAdd(pk, reverseAssociation.alias, id, req, {noReverse:true});\n                });\n\n              }\n              // Otherwise do a _publishUpdate\n              else {\n\n                // Alert any removed models\n                _.each(removedPKs, function(pk) {\n                  var pubData = {};\n                  pubData[reverseAssociation.alias] = null;\n                  ReferencedModel._publishUpdate(pk, pubData, req, {noReverse:true});\n                });\n                // Alert any added models\n                _.each(addedPKs, function(pk) {\n                  var pubData = {};\n                  pubData[reverseAssociation.alias] = id;\n                  ReferencedModel._publishUpdate(pk, pubData, req, {noReverse:true});\n                });\n\n              }//</else>\n\n            }//</else>\n          }, this);//</_.each()>\n        }//</ if `previous` and `!noReverse` >\n\n        // If a request object was sent, get its socket, otherwise assume a socket was sent.\n        var socketToOmit = (req && req.socket ? req.socket : req);\n\n        data.verb = 'updated';\n        data.previous = options.previous;\n        delete data.model;\n\n        // Broadcast to the model instance room\n        this._publishRPS(id, data, socketToOmit);\n\n        if (_.isFunction(this._afterPublishUpdate)) {\n          this._afterPublishUpdate(id, changes, req, options);\n        }\n\n\n      },\n\n      /**\n       * Publish the destruction of a particular model\n       *\n       * @param {String|Finite} id\n       *    - primary key of the instance we're referring to\n       *\n       * @param {Request|Socket} req - if specified, broadcast using this socket (effectively omitting it)\n       *\n       */\n\n      _publishDestroy: function (id, req, options) {\n        var reverseAssociation;\n\n        options = options || {};\n\n        // Enforce valid usage\n        var invalidId = !id || _.isObject(id);\n        if ( invalidId ) {\n          return sails.log.error(\n            'Invalid usage of `' + this.identity +\n            '._publishDestroy(id, [socketToOmit])`'\n          );\n        }\n\n        if (_.isFunction(this._beforePublishDestroy)) {\n          this._beforePublishDestroy(id, req, options);\n        }\n\n        // Coerce id to match the attribute type of the primary key of the model\n        id = parseId.apply(this,[id]);\n\n        var data = {\n          model: this.identity,\n          verb: 'destroy',\n          id: id,\n          previous: options.previous\n        };\n\n        // If a request object was sent, get its socket, otherwise assume a socket was sent.\n        var socketToOmit = (req && req.socket ? req.socket : req);\n\n        data.verb = 'destroyed';\n        delete data.model;\n\n        // Broadcast to the model instance room\n        this._publishRPS(id, data, socketToOmit);\n\n        // Unsubscribe everyone from the model instance\n        this._retire(id);\n\n        if (options.previous) {\n\n          var previous = options.previous;\n\n          // Loop through associations and alert as necessary\n          _.each(this.associations, function(association) {\n\n            var ReferencedModel;\n\n            // If it's a to-one association, and it wasn't falsy, alert\n            // the reverse side\n            if (association.type === 'model' && association.alias && previous[association.alias]) {\n              ReferencedModel = sails.models[association.model];\n              // Get the inverse association definition, if any\n              reverseAssociation = _.find(ReferencedModel.associations, {collection: this.identity}) || _.find(ReferencedModel.associations, {model: this.identity});\n\n              if (reverseAssociation) {\n                // If it's a to-one, publish a simple update alert\n                var referencedModelId = _.isObject(previous[association.alias]) ? previous[association.alias][ReferencedModel.primaryKey] : previous[association.alias];\n                if (reverseAssociation.type === 'model') {\n                  var pubData = {};\n                  pubData[reverseAssociation.alias] = null;\n                  ReferencedModel._publishUpdate(referencedModelId, pubData, req, {noReverse:true});\n                }\n                // If it's a to-many, publish a \"removed\" alert\n                else {\n                  ReferencedModel._publishRemove(referencedModelId, reverseAssociation.alias, id, req, {noReverse:true});\n                }\n              }\n            }\n\n            else if (association.type === 'collection' && association.via && previous[association.alias] && previous[association.alias].length) {\n              ReferencedModel = sails.models[association.collection];\n              // Get the inverse association definition, if any\n              var reverseAttribute = ReferencedModel.attributes[association.via];\n              _.each(previous[association.alias], function(associatedModel) {\n                // If it's a to-one, publish a simple update alert\n                if (reverseAttribute.model) {\n                  var pubData = {};\n                  pubData[association.via] = null;\n                  ReferencedModel._publishUpdate(associatedModel[ReferencedModel.primaryKey], pubData, req, {noReverse:true});\n                }\n                // If it's a to-many, publish a \"removed\" alert\n                else {\n                  ReferencedModel._publishRemove(associatedModel[ReferencedModel.primaryKey], association.via, id, req, {noReverse:true});\n                }\n              });\n            }\n\n          }, this);\n\n        }\n\n        if (_.isFunction(this._afterPublishDestroy)) {\n          this._afterPublishDestroy(id, req, options);\n        }\n\n      },\n\n\n      /**\n       * _publishAdd\n       *\n       * @param  {[type]} id           [description]\n       * @param  {[type]} alias        [description]\n       * @param  {[type]} idAdded      [description]\n       * @param  {[type]} socketToOmit [description]\n       */\n\n      _publishAdd: function(id, alias, added, req, options) {\n        var reverseAssociation;\n\n        // Make sure there's an options object\n        options = options || {};\n\n        // Enforce valid usage\n        var invalidId = !id || _.isObject(id);\n        var invalidAlias = !alias || !_.isString(alias);\n        var invalidAddedId = !added || _.isArray(added);\n        if ( invalidId || invalidAlias || invalidAddedId ) {\n          return sails.log.error(\n            'Invalid usage of `' + this.identity +\n            '._publishAdd(id, alias, idAdded|recordAdded, [socketToOmit])`'\n          );\n        }\n\n        // Get the model on the opposite side of the association\n        var reverseModel = sails.models[_.find(this.associations, {alias: alias}).collection];\n\n        // Determine whether `added` was provided as a pk value or an object\n        var idAdded;\n\n        // If it is a pk value, we'll turn it into `idAdded`:\n        if (!_.isObject(added)) {\n          idAdded = added;\n          added = undefined;\n        }\n        // Otherwise we'll leave it as `added` for use below, and determine `idAdded` by examining the object\n        // using our knowledge of what the name of the primary key attribute is.\n        else {\n          idAdded = added[reverseModel.primaryKey];\n\n          // If we don't find a primary key value, we'll log an error and return early.\n          if (!_.isString(idAdded) && !_.isNumber(idAdded)) {\n            sails.log.error(\n            'Invalid usage of _publishAdd(): expected object provided '+\n            'for `recordAdded` to have a `%s` attribute', reverseModel.primaryKey\n            );\n            return;\n          }\n        }\n\n        // Coerce id to match the attribute type of the primary key of the model\n        id = parseId.apply(this,[id]);\n\n        // Coerce idAdded to match the attribute type of the primary key of the reverse model\n        idAdded = parseId.apply(reverseModel,[idAdded]);\n\n\n        // Lifecycle event\n        if (_.isFunction(this._beforePublishAdd)) {\n          this._beforePublishAdd(id, alias, idAdded, req);\n        }\n\n        // If a request object was sent, get its socket, otherwise assume a socket was sent.\n        var socketToOmit = (req && req.socket ? req.socket : req);\n\n        this._publishRPS(id, (function (){\n          var event = {\n            id: id,\n            verb: 'addedTo',\n            attribute: alias,\n            addedId: idAdded\n          };\n          if (added) {\n            event.added = added;\n          }\n          return event;\n        })(), socketToOmit);\n\n        if (!options.noReverse) {\n\n          var data;\n\n          // Subscribe to the model you're adding\n          if (req && req.isSocket) {\n            data = {};\n            data[reverseModel.primaryKey] = idAdded;\n            reverseModel.subscribe(req, [idAdded]);\n          }\n\n          // Find the reverse association, if any\n          reverseAssociation = _.find(reverseModel.associations, {alias: _.find(this.associations, {alias: alias}).via});\n          if (reverseAssociation) {\n            // If this is a many-to-many association, do a _publishAdd for the\n            // other side.\n            if (reverseAssociation.type === 'collection') {\n              reverseModel._publishAdd(idAdded, reverseAssociation.alias, id, req, {noReverse:true});\n            }\n\n            // Otherwise, do a _publishUpdate\n            else {\n              data = {};\n              data[reverseAssociation.alias] = id;\n              reverseModel._publishUpdate(idAdded, data, req, {noReverse:true});\n            }\n          }\n\n        }\n\n\n        if (_.isFunction(this._afterPublishAdd)) {\n          this._afterPublishAdd(id, alias, idAdded, req);\n        }\n\n      },\n\n\n      /**\n       * _publishRemove\n       *\n       * @param  {[type]} id           [description]\n       * @param  {[type]} alias        [description]\n       * @param  {[type]} idRemoved    [description]\n       * @param  {[type]} socketToOmit [description]\n       */\n\n      _publishRemove: function(id, alias, idRemoved, req, options) {\n        var reverseAssociation;\n\n        // Make sure there's an options object\n        options = options || {};\n\n        // Enforce valid usage\n        var invalidId = !id || _.isObject(id);\n        var invalidAlias = !alias || !_.isString(alias);\n        var invalidRemovedId = !idRemoved || _.isObject(idRemoved);\n        if ( invalidId || invalidAlias || invalidRemovedId ) {\n          return sails.log.error(\n            'Invalid usage of `' + this.identity +\n            '._publishRemove(id, alias, idRemoved, [socketToOmit])`'\n          );\n        }\n        if (_.isFunction(this._beforePublishRemove)) {\n          this._beforePublishRemove(id, alias, idRemoved, req);\n        }\n\n        // Get the reverse model.\n        var reverseModel = sails.models[_.find(this.associations, {alias: alias}).collection];\n\n        // Coerce id to match the attribute type of the primary key of the model\n        id = parseId.apply(this,[id]);\n\n        // Coerce idRemoved to match the attribute type of the primary key of the reverse model\n        idRemoved = parseId.apply(reverseModel,[idRemoved]);\n\n        // If a request object was sent, get its socket, otherwise assume a socket was sent.\n        var socketToOmit = (req && req.socket ? req.socket : req);\n\n        this._publishRPS(id, {\n          id: id,\n          verb: 'removedFrom',\n          attribute: alias,\n          removedId: idRemoved\n        }, socketToOmit);\n\n\n        if (!options.noReverse) {\n\n          // Get the reverse association, if any.\n          reverseAssociation = _.find(reverseModel.associations, {alias: _.find(this.associations, {alias: alias}).via});\n\n          if (reverseAssociation) {\n            // If this is a many-to-many association, do a _publishAdd for the\n            // other side.\n            if (reverseAssociation.type === 'collection') {\n              reverseModel._publishRemove(idRemoved, reverseAssociation.alias, id, req, {noReverse:true});\n            }\n\n            // Otherwise, do a _publishUpdate\n            else {\n              var data = {};\n              data[reverseAssociation.alias] = null;\n              reverseModel._publishUpdate(idRemoved, data, req, {noReverse:true});\n            }\n          }\n\n        }\n\n        if (_.isFunction(this._afterPublishRemove)) {\n          this._afterPublishRemove(id, alias, idRemoved, req);\n        }\n\n      },\n\n      /**\n       * Publish the creation of model or an array of models\n       *\n       * @param {[Object]|Object} models\n       *                - the data to publish\n       *\n       * @param {Request|Socket} req - Optional request for broadcast.\n       * @api private\n       */\n      _publishCreate: function(models, req, options){\n        var self = this;\n\n        // Pluralize so we can use this method regardless of it is an array or not\n        models = pluralize.apply(this, [models]);\n\n        //Publish all models\n        _.each(models, function(values){\n          self._publishCreateSingle(values, req, options);\n        });\n      },\n      /**\n       * Publish the creation of a model\n       *\n       * @param {Object} values\n       *                - the data to publish\n       *\n       * @param {Request|Socket} req - if specified, broadcast using this socket (effectively omitting it)\n       * @api private\n       */\n\n      _publishCreateSingle: function(values, req, options) {\n        var reverseAssociation;\n\n        options = options || {};\n\n        if (_.isUndefined(values[this.primaryKey])) {\n          return sails.log.error(\n            'Invalid usage of _publishCreate() :: ' +\n            'Values must have an `'+this.primaryKey+'`, instead got ::\\n' +\n            util.inspect(values)\n          );\n        }\n\n        if (_.isFunction(this._beforePublishCreate)) {\n          this._beforePublishCreate(values, req);\n        }\n\n        var id = values[this.primaryKey];\n\n        // Coerce id to match the attribute type of the primary key of the model\n        id = parseId.apply(this,[id]);\n\n        // If any of the added values were association attributes, publish add or remove messages.\n        _.each(values, function(val, key) {\n\n          // If the user hasn't yet given this association a value, bail out\n          if (val === null) {\n            return;\n          }\n\n          var association = _.find(this.associations, {alias: key});\n\n          // If the attribute isn't an assoctiation, return\n          if (!association) {\n            return;\n          }\n\n          // Get the associated model class\n          var ReferencedModel = sails.models[association.type === 'model' ? association.model : association.collection];\n\n          // Bail if the model doesn't exist\n          if (!ReferencedModel) {\n            return;\n          }\n\n\n          // Bail if this attribute isn't in the model's schema\n          if (association.type === 'model') {\n\n            // Get the inverse association definition, if any\n            reverseAssociation = _.find(ReferencedModel.associations, {collection: this.identity, via: key}) || _.find(ReferencedModel.associations, {model: this.identity, via: key});\n\n            if (!reverseAssociation) {return;}\n\n            // If this is a to-many association, do _publishAdd on the other side\n            if (reverseAssociation.type === 'collection') {\n              ReferencedModel._publishAdd(\n                // Depending on the `populate` setting, the val could be an object or a primary key,\n                // so we'll allow for both.\n                val[ReferencedModel.primaryKey] || val,\n                reverseAssociation.alias,\n                id,\n                req,\n                {noReverse:true}\n              );\n            }\n\n          }\n\n          else {\n\n            // Get the inverse association definition, if any\n            reverseAssociation = _.find(ReferencedModel.associations, {collection: this.identity, via: key}) || _.find(ReferencedModel.associations, {model: this.identity, alias: association.via});\n\n            if (!reverseAssociation) {return;}\n\n            // If this is a to-many association, do publishAdds on the other side\n            if (reverseAssociation.type === 'collection') {\n\n              // Alert any added models\n              _.each(val, function(pk) {\n                // Depending on the `populate` setting, the val could be an object or a primary key,\n                // so we'll allow for both.\n                if (_.isObject(pk)) {\n                  pk = pk[ReferencedModel.primaryKey];\n                }\n                ReferencedModel._publishAdd(pk, reverseAssociation.alias, id, req, {noReverse:true});\n              });\n\n            }\n\n            // Otherwise do a _publishUpdate\n            else {\n              // Alert any added models\n              _.each(val, function(pk) {\n                // Depending on the `populate` setting, the val could be an object or a primary key,\n                // so we'll allow for both.\n                if (_.isObject(pk)) {\n                  pk = pk[ReferencedModel.primaryKey];\n                }\n                var pubData = {};\n                pubData[reverseAssociation.alias] = id;\n                ReferencedModel._publishUpdate(pk, pubData, req, {noReverse:true});\n              });\n\n            }\n\n          }\n\n        }, this);\n\n        // Ensure that we're working with a plain object\n        values = _.clone(values);\n\n        // If a request object was sent, get its socket, otherwise assume a socket was sent.\n        var socketToOmit = (req && req.socket ? req.socket : req);\n\n        // Publish to classroom\n        var payload = {\n          verb: 'created',\n          data: values,\n          id: values[this.primaryKey]\n        };\n        sails.log.silly('Published message to ', this._classRoom(), ': ', payload);\n        var eventName = this.identity;\n        sails.sockets.broadcast(this._classRoom(), eventName, payload, socketToOmit);\n\n        // Subscribe watchers to the new instance\n        if (!options.noIntroduce) {\n          this._introduce(values[this.primaryKey]);\n        }\n\n        if (_.isFunction(this._afterPublishCreate)) {\n          this._afterPublishCreate(values, req);\n        }\n\n      },\n\n\n      /**\n       *\n       * @return {[type]} [description]\n       */\n      _watch: function ( req ) {\n\n        var socket = sails.sockets.parseSocket(req);\n\n        if (!socket) {\n          sails.log.debug('`Model._watch()` called by a non-socket request. Only requests originating from a connected socket may be subscribed. Ignoring...');\n          return;\n        }//-•\n\n        sails.sockets.join(socket, this._classRoom());\n        sails.log.silly('Subscribed socket ', sails.sockets.getId(socket), 'to', this._classRoom());\n\n      },\n\n      /**\n       * Introduce a new instance\n       *\n       * Take all of the subscribers to the class room and 'introduce' them\n       * to a new instance room\n       *\n       * @param {String|Finite} id\n       *    - primary key of the instance we're referring to\n       *\n       * @api private\n       */\n\n      _introduce: function(model) {\n\n        var self = this;\n\n        // Get the instance ID\n        var id = model[this.primaryKey] || model;\n\n        // Use addRoomMembersToRooms to subscribe everyone in the class room to the model identity instance room\n        sails.sockets.addRoomMembersToRooms(self._classRoom(), self._room(id) );\n\n      },\n\n      /**\n       * Bid farewell to a destroyed instance\n       * Take all of the socket subscribers in this instance room\n       * and unsubscribe them from it\n       */\n      _retire: function(model) {\n\n        var self = this;\n\n        // Get the instance ID\n        var id = model[this.primaryKey] || model;\n\n        // Use removeRoomMembersFromRooms to unsubscribe everyone in the class room from the model identity instance room\n        sails.sockets.removeRoomMembersFromRooms(self._classRoom(), self._room(id) );\n      }\n\n    };\n  }\n\n};\n"
  },
  {
    "path": "lib/hooks/request/README.md",
    "content": "# request (Core Hook)\n\n> In future releases, the various responsibilities of this hook will likely be farmed out to other hooks and/or pulled into core.\n\n## Purpose\n\nThis hook's responsibilities are:\n\n##### Add properties to `req`\n+ req.params.all()\n+ req.wantsJSON()\n+ req.explicitlyAcceptsHTML()\n+ req.baseUrl\n+ req.port\n+ req._sails (access to the app's `sails` object in case it's not global)\n\n##### Set default view locals (i.e. `app.locals`)\n+ `_` (lodash)\n+ `session`\n+ `req`\n+ `res`\n+ `sails`\n\n> Note that this will likely move into the `views` hook in the future.\n\n\n\n## FAQ\n\n> If you have a question that isn't covered here, please feel free to send a PR adding it to this section (even if you don't have the answer!)\n"
  },
  {
    "path": "lib/hooks/request/index.js",
    "content": "/**\n * Module dependencies.\n */\nvar _mixinLocals = require('./locals');\nvar _mixinReqParam = require('./param');\nvar _mixinReqParamsAll = require('./params.all');\nvar _mixinServerMetadata = require('./metadata');\nvar _mixinReqQualifiers = require('./qualifiers');\nvar _mixinReqValidate = require('./validate');\n\n\n\n/*\nNOTE:\n\nMost of the contents of this file could be eventually migrated into the prototypes of the `req` and `res` objects\nwe're extending from our Express router (`_privateRouter`).  This would also need to happen separately in the HTTP hook (since\nits req and res are distinct), which is why adding the methods via middleware has been a perfectly convenient abstraction\nfor the time being.\n\nHowever, this can be rather hard to understand, and as we make an effort to make hooks easier to work with, it may be\nwise to abstract these built-in Sails functions in a more declarative way, maybe even outside of hooks altogether.\nThis is particularly pertinent in the case of errors ( e.g. res.serverError() ).\n\nIf you have any ideas, please let me know! (@mikermcneil)\n */\n\nmodule.exports = function(sails) {\n\n\n  /**\n   * Extend middleware req/res for this route w/ new methods / qualifiers.\n   */\n\n  return {\n\n\n    defaults: {\n\n    },\n\n\n    /**\n     * Bind req/res syntactic sugar before applying any app-level routes\n     */\n\n    initialize: function(cb) {\n\n\n      // Bind an event handler to inject logic before running each individual\n      // middleware function within a route/request.\n      sails.on('router:route', function(requestState) {\n        // *****************************************************************\n        // Warning: this is a hot code path!\n        // Remember to be sensitive to performance.\n        // *****************************************************************\n        var req = requestState.req;\n        var res = requestState.res;\n\n        // req.params.all() must be recalculated before matching each route\n        // since path params (`req.params`) might have changed.\n        _mixinReqParamsAll(req, res);\n\n        // Since req.param() is deprecated with Express4 we use this facade\n        _mixinReqParam(req, res);\n\n      });\n\n      return cb();\n    },\n\n\n    routes: {\n\n      before: {\n        'all /*': function addMixins (req, res, next) {\n\n          // Provide access to `sails` object\n          req._sails = req._sails || sails;\n\n          // Add a few `res.locals` by default\n          _mixinLocals(req, res);\n\n          // Add information about the server to the request context\n          _mixinServerMetadata(req, res);\n\n          // Add `req.validate()` method\n          // (Warning: this is actually just an error as of Sails v1.0!  See impl for more info.)\n          _mixinReqValidate(req, res);\n\n          // Only apply HTTP-focused middleware if it makes sense\n          // (i.e. if this is an HTTP request)\n          if (req.protocol === 'http' || req.protocol === 'https') {\n            _mixinReqQualifiers(req, res);\n          }\n\n          return next();\n        }//< / 'all /*' >\n      }\n\n    }\n  };\n};\n"
  },
  {
    "path": "lib/hooks/request/locals.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\n\n\n\n/**\n * default locals\n *\n * Always share some basic metadata with views.\n * Roughly analogous to `app.locals` in Express.\n *\n * > Application local variables are provided to all templates rendered\n * > within the application. This is useful for providing helper functions\n * > to templates, as well as app-level data.\n * >\n * > http://expressjs.com/api.html#app.locals\n *\n * @param {Request} req\n * @param {Response} res\n * @api private\n */\n\nmodule.exports = function _mixinLocals(req, res) {\n\n  // TODO:\n  // Actually take advantage of `app.locals`\n  // for this logic.\n\n  // TODO:\n  // we might look at pruning the stuff being\n  // passed in here, to improve the optimizations\n  // of Express's production view cache.\n\n  _.extend(res.locals, {\n    _: _,\n    session: req.session,\n    req: req,\n    res: res,\n    sails: req._sails\n  });\n\n};\n"
  },
  {
    "path": "lib/hooks/request/metadata.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\n\n\n/**\n * _mixinServerMetadata()\n *\n * Set server metadata on the specified `req`, mutating it in-place.\n * (Host, port, etc.)\n *\n * This is for ALL requests, virtual or not.\n *\n * @param {Request} req\n *\n * @api private\n */\n\nmodule.exports = function _mixinServerMetadata(req) {\n\n  // Get reference to `sails` (Sails app instance) for convenience.\n  var sails = req._sails;\n\n  // FUTURE: bring this back, probably.\n  // (but note that it wasn't actually being used anyway as of Sails v0.12-- was being overridden below)\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  // // Access to server port, if available\n  // if (sails.hooks.http) {\n  //   var nodeHTTPServer = sails.hooks.http.server;\n  //   var nodeHTTPServerAddress = (nodeHTTPServer && nodeHTTPServer.address && nodeHTTPServer.address()) || {};\n  //   req.port = req.port || (nodeHTTPServerAddress && nodeHTTPServerAddress.port) || 80;\n  // }\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n  // Set req.port and req.baseUrl using the Host header and req.protocol\n  //\n  // We trust req.protocol to be set by Express when \"trust proxy\" is enabled.\n  // But Express only delivers the host devoid of a port, so we have to delve into\n  // HTTP headers to pick out the host port ourselves.\n  //\n  // FUTURE: revisit this ^^\n  var trustProxy;\n  if (req.app && req.app.get('trust proxy')) {\n    trustProxy = req.app.get('trust proxy');\n  }\n  else if (sails.hooks.http && sails.config.http.trustProxy) {\n    // If this is a virtual request, then \"trust proxy\" will not have been set\n    // on the current `req.app`, but we still want to consider this the same case\n    // if the sails.config.http.trustProxy was enabled.\n    trustProxy = sails.config.http.trustProxy;\n  }\n  else {\n    trustProxy = false;\n  }\n\n\n  if (!_.isFunction(req.get)) {\n    throw new Error('Consistency violation: At this point (in the request hook), req.get() should always exist as a function.');\n  }\n\n  // Determine host.\n  var host = '';\n  var xForwardedHostHeader = req.get('X-Forwarded-Host');\n  var hostHeader = req.get('Host');\n  if (trustProxy && xForwardedHostHeader) {\n    // FUTURE: use hostname-- because if trustProxy was configured, it means that we should be able to (as of E4)\n    host = xForwardedHostHeader.split(/,\\s*/)[0];\n  }\n  else if (hostHeader) {\n    host = hostHeader;\n  }\n  else {\n    host = 'could.not.determine.host';\n  }\n\n\n  // Determine host port\n  // (FUTURE: come back to this, esp insofar as it affects virtual requests -- we need to respect trustProxy)\n  var defaultPort;\n  if (req.protocol === 'https' || req.protocol === 'wss') {\n    defaultPort = 443;\n  } else {\n    defaultPort = 80;\n  }\n  var hostPort = parseInt(host.split(/:/)[1], 10) || defaultPort;\n  req.port = hostPort;\n\n  // Determine appropriate baseUrl\n  // (FUTURE: come back to this, esp insofar as it affects virtual requests -- we need to respect trustProxy)\n  req.baseUrl = req.protocol + '://' + host;\n\n};\n"
  },
  {
    "path": "lib/hooks/request/param.js",
    "content": "/**\n * _mixinReqParam\n *\n * Facade for req.param('sth') with Express4\n * Looking for the param in req.params && req.query && req.body\n *\n * Note: this has to be applied per-route, not per request,\n * in order to refer to the proper route/path parameters\n *\n * @param {Request} req\n * @param {Response} res\n * @api private\n */\n\nmodule.exports = function _mixinReqParam(req /*, res */) {\n\n  req.param = function(param, defaultValue) {\n    // If the param exists as a route param, use it.\n    if (typeof req.params[param] !== 'undefined') {\n      return req.params[param];\n    }\n    // If the param exists as a body param, use it.\n    if (req.body && typeof req.body[param] !== 'undefined') {\n      return req.body[param];\n    }\n    // Return the query param, if it exists.\n    return typeof req.query[param] !== 'undefined' ? req.query[param] : defaultValue;\n  };\n\n};\n"
  },
  {
    "path": "lib/hooks/request/params.all.js",
    "content": "/**\n * Module dependencies\n */\nvar _ = require('@sailshq/lodash');\n\n\n\n/**\n * _mixinReqParamsAll\n *\n * Mixes in `req.params.all()`, a convenience method to grab all parameters,\n * whether they're in the path (req.params), query string (req.query),\n * or request body (req.body).\n *\n * Note: this has to be determined per-route, not per request,\n * in order to refer to the proper route/path parameters.\n * e.g. if a request comes in and matches `GET /about` AND `GET /:username`\n * then the set of all params varies depending on which handler is being run.\n * Now that said, since changing the implementation to avoid precalculating\n * unless allParams is actually called, this may not necessarily need to be\n * the case anymore. More exhaustive testing would be required to make that change.\n *\n *\n * @param {Request} req\n * @param {Response} res\n * @api private\n */\n\nmodule.exports = function _mixinReqParamsAll(req /*, res */) {\n\n  // Add `req.allParams()` method.\n  req.allParams = function () {\n    // Combines parameters from the query string, and encoded request body\n    // to compose a monolithic object of named parameters, irrespective of source\n    var allParams = _.extend({}, req.query, req.body);\n\n    // Mixin route params, as long as they have defined values\n    _.each(Object.keys(req.params), function(paramName) {\n      if (allParams[paramName] || !_.isUndefined(req.params[paramName])) {\n        allParams[paramName] = !_.isUndefined(req.params[paramName]) ? req.params[paramName] : allParams[paramName];\n      }\n    });\n    return allParams;\n  };\n\n\n\n  /////////////////////////////////////////////////////////////////////////////////////////////////////////\n  // Note:\n  // req.params.all() was removed in Sails v1.0 in favor of `req.allParams()`.\n  /////////////////////////////////////////////////////////////////////////////////////////////////////////\n  //\n  // The following code was removed for performance reasons.\n  // (Object.defineProperty() is slow-- see `parley` benchmarks + commit history c.a. Oct-Dec 2016)\n  /////////////////////////////////////////////////////////////////////////////////////////////////////////\n  // // Define a new non-enumerable property: req.params.all()\n  // // and make it a synonym to `req.allParams()`\n  // // (but only if `req.params.all` doesn't already exist!)\n  // if (!req.params.all) {\n  //   Object.defineProperty(req.params, 'all', {\n  //     value: function (){\n  //       throw new Error('req.params.all() is no longer supported as of Sails v1.0.  Please use req.allParams() instead.');\n  //     }\n  //   });\n  // }\n  /////////////////////////////////////////////////////////////////////////////////////////////////////////\n\n};\n"
  },
  {
    "path": "lib/hooks/request/qualifiers.js",
    "content": "/**\n * Module dependencies\n */\n\nvar STRINGFILE = require('sails-stringfile');\n\n\n\n/**\n * Mix in convenience flags about this request\n *\n * @param {Request} req\n * @param {Response} res\n * @api private\n */\n\nmodule.exports = function _mixinReqQualifiers(req, res) {\n  var accept = req.get('Accept') || '';\n\n  // Flag indicating whether HTML was explicitly mentioned in the Accepts header\n  req.explicitlyAcceptsHTML = (accept.indexOf('html') !== -1);\n\n  // Flag indicating whether a request would like to receive a JSON response\n  //\n  // This qualification is determined based on a handful of heuristics, including:\n  // • if this looks like an AJAX request\n  // • if this is a virtual request from a socket\n  // • if this request DOESN'T explicitly want HTML\n  // • if this request has a \"json\" content-type AND ALSO has its \"Accept\" header set\n  // • if this request has the option \"wantsJSON\" set\n  req.wantsJSON = req.xhr;\n  req.wantsJSON = req.wantsJSON || req.isSocket;\n  req.wantsJSON = req.wantsJSON || !req.explicitlyAcceptsHTML;\n  req.wantsJSON = req.wantsJSON || (req.is('json') && req.get('Accept'));\n  req.wantsJSON = req.wantsJSON || req.options.wantsJSON;\n\n\n  // Deprecated properties\n  bindReqDeprecationNotice(req, 'isJson');\n  bindReqDeprecationNotice(req, 'isAjax');\n  bindResDeprecationNotice(res, 'viewExists');\n};\n\n\n\n/**\n * Bind deprecation notices for `req.*` properties from 0.8.x,\n * but only in development env, and only if the property\n * doesn't already exist (i.e. in case a user-defined\n * hook bound it on the `req` object.)\n *\n * @param  {Request} req\n * @param  {String} key\n */\nfunction bindReqDeprecationNotice(req, key) {\n  if (process.env.NODE_ENV === 'production' || req[key]) { return; }\n\n  // Attach a getter\n  Object.defineProperty(req, key, {\n    value: function showDeprecationNotice() {\n      var e = STRINGFILE.get('upgrade.req.' + key);\n      throw new Error(e);\n    }\n  });\n}\n\n\n/**\n * Bind deprecation notices for `res.*` properties from 0.8.x,\n * but only in development env, and only if the property\n * doesn't already exist (i.e. in case a user-defined\n * hook bound it on the `res` object.)\n *\n * @param  {Response} res\n * @param  {String} key\n */\nfunction bindResDeprecationNotice(res, key) {\n  if (process.env.NODE_ENV === 'production' || res[key]) { return; }\n\n  // Attach a getter\n  Object.defineProperty(res, key, {\n    value: function showDeprecationNotice() {\n      var e = STRINGFILE.get('upgrade.res.' + key);\n      throw new Error(e);\n    }\n  });\n}\n"
  },
  {
    "path": "lib/hooks/request/validate.js",
    "content": "/**\n * Module dependencies\n */\n\nvar flaverr = require('flaverr');\n\n\n\n/**\n * Mixes in `req.validate`.\n *\n * @param  {Request} req\n * @param  {Response} res\n * @return {Request}\n *\n * Note that built-in support for req.validate() has changed in Sails v1.0\n * (alongwith other major changes to `anchor`.)\n */\nmodule.exports = function (req /*, res */) {\n\n  /**\n   * req.validate()\n   *\n   * @param  {Object} usage\n   *         (supports either `{type: {}}` or `{}`)\n   *\n   * @param  {String} redirectTo\n   *         (optional)\n   *\n   * @throws {Error}\n   * @api deprecated\n   */\n\n  req.validate = function (/* usage, redirectTo */) {\n    throw flaverr({ code: 'E_REQ_VALIDATE_UNSUPPORTED' }, new Error('As of Sails v1, `req.validate` is no longer supported.  Instead use actions2: http://sailsjs.com/docs/concepts/controllers'));\n  };//</function definition :: req.validate>\n\n  return req;\n};\n"
  },
  {
    "path": "lib/hooks/responses/README.md",
    "content": "# responses (Core Hook)\n\n## Status\n\n> ##### Stability: [2](https://github.com/balderdashy/sails-docs/blob/master/contributing/stability-index.md) - Stable\n\n\n## Dependencies\n\nIn order for this hook to load, the following other hooks must have already finished loading:\n\n- moduleloader\n\n\n\n## Dependents\n\nIf this hook is disabled, in order for Sails to load, the following other core hooks must also be disabled:\n\n- blueprints\n\n\n\n## Purpose\n\nThis hook's responsibilities are:\n\n\n##### Support `response` route target syntax\n\nThis hook listens for the `route:typeUnknown` event, and if the unknown route target syntax contains a `response` key, it binds the route address to a middleware function that does nothing except send that response.\n\nFor example:\n\n```\n'post /foo': {\n  response: 'ok'\n}\n```\n\n...would run `res.ok()` whenever a POST request to `/foo` is received.\n\n\n##### Load custom responses\n\nWhen Sails loads, this hook loads custom response files from the app's responses folder and merges them with built-in defaults, storing them in-memory as \"outlet functions\".  Conventionally this is `api/responses/*.js`, but it can be configured in `sails.config.paths`.\n\n\n##### Bind shadow route that exposes response functions as `res.*`\n\nThis hook binds a shadow route that intercepts all incoming requests and attaches a method to `res` for each of the outlet functions (representing custom responses) that were prepared when the hook was initialized.\n\n\n\n## Implicit Defaults\n\nThis hook sets the following implicit default configuration on `sails.config`:\n\n_N/A_\n\n\n\n## Events\n\n##### `hook:responses:loaded`\n\nEmitted when this hook has been automatically loaded by Sails core, and triggered the callback in its `initialize` function.\n\n\n\n## Methods\n\n\n#### sails.hooks.responses.loadModules()\n\nLoad custom responses modules from the responses directory in the current app (conventionally this is `api/responses/`).\n\n```javascript\nsails.hooks.responses.loadModules(cb);\n```\n\n\n###### Usage\n\n\n|     |          Argument           | Type                | Details\n| --- | --------------------------- | ------------------- | ----------------------------------------------------------------------------------\n| 1   |        **cb**               | ((function))        | Fires when the custom response modules have been loaded or if an error occurs.\n\n\n> ##### API: Private\n> - Please do not use this method in userland (i.e. in your app or even in a custom hook or other type of Sails plugin).\n> - Because it is a private API of a core hook, if you use this method in your code it may stop working or change without warning, at any time.\n> - If you would like to see a version of this method made public and its API stabilized, please open a [proposal](https://github.com/balderdashy/sails/blob/master/CONTRIBUTING.md#v-proposing-features-and-enhancements).\n>\n> _(internally in core, note that this is called by the `moduleloader` hook)_\n\n\n\n## FAQ\n\n> If you have a question about this hook that isn't covered here, please feel free to send a PR adding it to this section (even if you don't have the answer, a core maintainer will merge your PR and add an answer as soon as possible)\n"
  },
  {
    "path": "lib/hooks/responses/defaults/badRequest.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\n\n\n\n/**\n * 400 (Bad Request) Handler\n *\n * Usage:\n * return res.badRequest();\n * return res.badRequest(data);\n *\n * e.g.:\n * ```\n * return res.badRequest(\n *   'Please choose a valid `password` (6-12 characters)',\n *   'trial/signup'\n * );\n * ```\n */\n\nmodule.exports = function badRequest(data) {\n\n  // Get access to `req` and `res`\n  var req = this.req;\n  var res = this.res;\n\n  // Get access to `sails`\n  var sails = req._sails;\n\n  // Log error to console\n  if (!_.isUndefined(data)) {\n    sails.log.verbose('Sending 400 (\"Bad Request\") response: \\n', data);\n  }\n\n  // Set status code\n  res.status(400);\n\n  // If no data was provided, use res.sendStatus().\n  if (_.isUndefined(data)) {\n    return res.sendStatus(400);\n  }\n\n  if (_.isError(data)) {\n    // If the data is an Error instance and it doesn't have a custom .toJSON(),\n    // then util.inspect() it instead (otherwise res.json() will turn it into an empty dictionary).\n    // > Note that we don't do this in production, since (depending on your Node.js version) inspecting\n    // > the Error might reveal the `stack`.  And since `res.badRequest()` could certainly be used in\n    // > production, we wouldn't want to inadvertently dump a stack trace.\n    if (!_.isFunction(data.toJSON)) {\n      if (process.env.NODE_ENV === 'production') {\n        return res.sendStatus(400);\n      }\n      // No need to JSON stringify (this is already a string).\n      return res.send(util.inspect(data));\n    }\n  }\n  return res.json(data);\n\n};\n"
  },
  {
    "path": "lib/hooks/responses/defaults/forbidden.js",
    "content": "/**\n * Module dependencies\n */\n\n// n/a\n\n\n\n/**\n * 403 (Forbidden) Handler\n *\n * Usage:\n * return res.forbidden();\n *\n * e.g.:\n * ```\n * return res.forbidden();\n * ```\n */\n\nmodule.exports = function forbidden () {\n\n  // Get access to `res`\n  var res = this.res;\n\n  // Send status code and \"Forbidden\" message\n  return res.sendStatus(403);\n\n};\n"
  },
  {
    "path": "lib/hooks/responses/defaults/negotiate.js",
    "content": "/**\n * Generic Error Handler / Classifier\n *\n * Calls the appropriate custom response for a given error,\n * out of the bundled response modules:\n * badRequest, forbidden, notFound, & serverError\n *\n * Defaults to `res.serverError`\n *\n * Usage:\n * ```javascript\n * if (err) return res.negotiate(err);\n * ```\n *\n * @param {*} error(s)\n *\n * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n * WARNING: THIS FUNCTION IS DEPRECATED!\n * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n */\n\nmodule.exports = function negotiate (err) {\n\n  // Get access to request (`req`), response (`res`), and Sails app (`sails`).\n  var req = this.req;\n  var res = this.res;\n  var sails = req._sails;\n\n  sails.log.debug('`res.negotiate` is deprecated.  Use a custom response instead (see http://sailsjs.com/docs/concepts/custom-responses).\\n');\n\n  var statusCode = 500;\n  var body = err;\n\n  try {\n\n    statusCode = err.status || 500;\n\n    // Set the status\n    // (should be taken care of by res.* methods, but this sets a default just in case)\n    res.status(statusCode);\n\n  } catch (unusedErr) {}\n\n  // Respond using the appropriate custom response\n  if (statusCode === 403) { return res.forbidden(body); }\n  if (statusCode === 404) { return res.notFound(body); }\n  if (statusCode >= 400 && statusCode < 500) { return res.badRequest(body); }\n  return res.serverError(body);\n};\n"
  },
  {
    "path": "lib/hooks/responses/defaults/notFound.js",
    "content": "/**\n * Module dependencies\n */\n\n// n/a\n\n\n\n/**\n * 404 (Not Found) Handler\n *\n * Usage:\n * return res.notFound();\n * return res.notFound(err);\n * return res.notFound(err, 'some/specific/notfound/view');\n *\n * e.g.:\n * ```\n * return res.notFound();\n * ```\n *\n * NOTE:\n * If a request doesn't match any explicit routes (i.e. `config/routes.js`)\n * or route blueprints (i.e. \"shadow routes\", Sails will call `res.notFound()`\n * automatically.\n */\n\nmodule.exports = function notFound () {\n\n  // Get access to `req` and `res`\n  var req = this.req;\n  var res = this.res;\n\n  // Get access to `sails`\n  var sails = req._sails;\n\n  // Set status code\n  res.status(404);\n\n  // If the request wants JSON, send back the appropriate status code.\n  if (req.wantsJSON || !res.view) {\n    return res.sendStatus(404);\n  }\n\n  return res.view('404', {}, function (err, html) {\n    // If a view error occured, fall back to JSON.\n    if (err) {\n      //\n      // Additionally:\n      // • If the view was missing, ignore the error but provide a verbose log.\n      if (err.code === 'E_VIEW_FAILED') {\n        sails.log.verbose('res.notFound() :: Could not locate view for error page (sending text instead).  Details: ', err);\n      }\n      // Otherwise, if this was a more serious error, log to the console with the details.\n      else {\n        sails.log.warn('res.notFound() :: When attempting to render error page view, an error occured (sending text instead).  Details: ', err);\n      }\n      return res.sendStatus(404);\n    }\n\n    return res.send(html);\n  });\n\n};\n"
  },
  {
    "path": "lib/hooks/responses/defaults/ok.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\n\n\n\n/**\n * 200 (OK) Response\n *\n * Usage:\n * return res.ok();\n * return res.ok(data);\n *\n * @param  {JSON?} data\n * @param  {Ref?} noLongerSupported\n */\n\nmodule.exports = function sendOK (data, noLongerSupported) {\n\n  // Get access to `req` and `res`\n  var req = this.req;\n  var res = this.res;\n\n  // Get access to `sails`\n  var sails = req._sails;\n\n  // If a second argument was given, log a message.\n  if (noLongerSupported) {\n    sails.log.debug('The second argument to `res.ok()` is deprecated.');\n    sails.log.debug('To serve a view via `res.ok()`, override the response');\n    sails.log.debug('in \\'api/responses/ok.js\\'.\\n');\n  }\n\n  // Set status code\n  res.status(200);\n\n  // If no data was provided, use res.sendStatus().\n  if (_.isUndefined(data)) {\n    return res.sendStatus(200);\n  }\n\n  // Extreme edge case (very rare to pass an Error into res.ok() -- but still, just in case)\n  // If the data is an Error instance and it doesn't have a custom .toJSON(),\n  // then util.inspect() it instead (otherwise res.json() will turn it into an empty dictionary).\n  // > Note that we don't do this in production, since (depending on your Node.js version) inspecting\n  // > the Error might reveal the `stack`.  And since `res.ok()` could certainly be used in\n  // > production, and it could inadvertently be passed an Error instance, we censor the stack trace\n  // > as a simple failsafe.\n  if (_.isError(data)) {\n    if (!_.isFunction(data.toJSON)) {\n      if (process.env.NODE_ENV === 'production') {\n        return res.sendStatus(200);\n      }\n      // No need to JSON stringify (it's already a string).\n      return res.send(util.inspect(data));\n    }\n  }\n  return res.json(data);\n\n};\n"
  },
  {
    "path": "lib/hooks/responses/defaults/serverError.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar flaverr = require('flaverr');\n\n\n\n/**\n * 500 (Server Error) Response\n *\n * Usage:\n * return res.serverError();\n * return res.serverError(err);\n * return res.serverError(err, 'some/specific/error/view');\n *\n * NOTE:\n * If something throws in a policy or controller, or an internal\n * error is encountered, Sails will call `res.serverError()`\n * automatically.\n */\n\nmodule.exports = function serverError (data) {\n\n  // Get access to `req` and `res`\n  var req = this.req;\n  var res = this.res;\n\n  // Get access to `sails`\n  var sails = req._sails;\n\n  // Log error to console\n  if (data !== undefined) {\n    sails.log.error('Sending 500 (\"Server Error\") response: \\n', flaverr.parseError(data) || data);\n  }\n\n  // Don't output error data with response in production.\n  var dontRevealErrorInResponse = process.env.NODE_ENV === 'production';\n  if (dontRevealErrorInResponse) {\n    data = undefined;\n  }\n\n  // Set status code\n  res.status(500);\n\n  // If appropriate, serve data as JSON.\n  if (req.wantsJSON || !res.view) {\n    // If no data was provided, use res.sendStatus().\n    if (data === undefined) {\n      return res.sendStatus(500);\n    }\n    // If the data is an error instance and it doesn't have a custom .toJSON(),\n    // use its stack instead (otherwise res.json() will turn it into an empty dictionary).\n    if (_.isError(data)) {\n      if (!_.isFunction(data.toJSON)) {\n        data = data.stack;\n        // No need to stringify the stack (it's already a string).\n        return res.send(data);\n      }\n    }\n    return res.json(data);\n  }\n\n  return res.view('500', { error: data }, function (err, html) {\n\n    // If a view error occured, fall back to JSON.\n    if (err) {\n      //\n      // Additionally:\n      // • If the view was missing, ignore the error but provide a verbose log.\n      if (err.code === 'E_VIEW_FAILED') {\n        sails.log.verbose(\n          'res.serverError() :: Could not locate view for error page'+\n          (dontRevealErrorInResponse? '':' (sending JSON instead)')+'.  '+\n          'Details: ', err\n        );\n      }\n      // Otherwise, if this was a more serious error, log to the console with the details.\n      else {\n        sails.log.warn(\n          'res.serverError() :: When attempting to render error page view, '+\n          'an error occured'+(dontRevealErrorInResponse? '':' (sending JSON instead)')+'.  '+\n          'Details: ', err\n        );\n      }\n      return res.json(data);\n    }\n\n    return res.send(html);\n  });\n\n};\n"
  },
  {
    "path": "lib/hooks/responses/index.js",
    "content": "/**\n * Dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar chalk = require('chalk');\nvar STRINGFILE = require('sails-stringfile');\nvar Err = require('../../../errors/fatal');\nvar onRoute = require('./onRoute');\n\n\n\n\n/**\n * Expose hook definition\n */\n\nmodule.exports = function(sails) {\n\n  return {\n\n    defaults: {},\n    configure: function() {\n\n      // Legacy (< v0.10) support for configured handlers\n      if (typeof sails.config[500] === 'function') {\n        sails.after('lifted', function() {\n          STRINGFILE.logDeprecationNotice('sails.config[500]',\n            STRINGFILE.get('links.docs.migrationGuide.responses'),\n            sails.log.debug);\n          sails.log.debug('sails.config[500] (i.e. `config/500.js`) has been superceded in Sails v0.10.');\n          sails.log.debug('Please define a \"response\" instead. (i.e. api/responses/serverError.js)');\n          sails.log.debug('Your old handler is being ignored. (the format has been upgraded in v0.10)');\n          sails.log.debug('If you\\'d like to use the default handler, just remove this configuration option.');\n        });\n      }\n      if (typeof sails.config[404] === 'function') {\n        sails.after('lifted', function() {\n          STRINGFILE.logDeprecationNotice('sails.config[404]',\n            STRINGFILE.get('links.docs.migrationGuide.responses'),\n            sails.log.debug);\n          sails.log.debug('Please define a \"response\" instead. (i.e. api/responses/notFound.js)');\n          sails.log.debug('Your old handler is being ignored. (the format has been upgraded in v0.10)');\n          sails.log.debug('If you\\'d like to use the default handler, just remove this configuration option.');\n        });\n      }\n    },\n\n\n\n    /**\n     * When this hook is loaded...\n     */\n\n    initialize: function(cb) {\n\n      // Register route syntax that allows explicit routes\n      // to be bound directly to custom responses by name.\n      // (e.g. {response: 'foo'})\n      sails.on('route:typeUnknown', onRoute(sails));\n\n      cb();\n    },\n\n\n\n    /**\n     * Fetch relevant modules, exposing them on `sails` subglobal if necessary,\n     */\n    loadModules: function(cb) {\n      var hook = this;\n\n      sails.log.silly('Loading runtime custom response definitions...');\n      sails.modules.loadResponses(function loadedRuntimeErrorModules(err, responseDefs) {\n        if (err) { return cb(err); }\n\n        // Check that none of the custom responses provided from userland collie with\n        // reserved response methods/properties.\n        //\n        // Note: this could be made more flexible in the future-- I've found it to be\n        // helpful to sometimes override res.view() in apps.  That said, in those circumstances,\n        // I've been able to accomplish this by manually overriding res.view in a custom hook.\n        // That said, if that won't work for your use case, please let me know (tweet @mikermcneil).\n        var reservedResKeys = [\n          'view',\n          'status', 'set', 'get', 'cookie', 'clearCookie', 'redirect',\n          'location', 'charset', 'send', 'json', 'jsonp', 'type', 'format',\n          'attachment', 'sendfile', 'download', 'links', 'locals', 'render'\n        ];\n        _.each(responseDefs, function (responseDef, customResponseKey) {\n          if ( _.contains(reservedResKeys, customResponseKey) ) {\n            Err.invalidCustomResponse(customResponseKey);\n          }\n        });\n\n        // Mix in the built-in default definitions for custom responses.\n        _.defaults(responseDefs, {\n          ok: require('./defaults/ok'),\n          negotiate: require('./defaults/negotiate'),\n          notFound: require('./defaults/notFound'),\n          serverError: require('./defaults/serverError'),\n          forbidden: require('./defaults/forbidden'),\n          badRequest: require('./defaults/badRequest')\n        });\n\n        // Expose combined custom/default response method definitions on the hook.\n        // (e.g. `serverError`, `notFound`, `ok`, etc.)\n        // TODO: use this instead of exposing as \"middleware\", since that's confusing naming.\n\n        // Register blueprint actions as middleware of this hook.\n        hook.middleware = responseDefs;\n\n        return cb();\n      });\n    },\n\n\n\n    /**\n     * Shadow route bindings\n     * @type {Object}\n     */\n    routes: {\n      before: {\n\n        /**\n         * Add custom response methods to `res`.\n         *\n         * @param {Request} req\n         * @param {Response} res\n         * @param  {Function} next\n         * @api private\n         */\n        'all /*': function addResponseMethods(req, res, next) {\n\n          // Attach res.jsonx to `res` object\n          _mixinJsonxMethod(req, res);\n\n          // Attach custom responses to `res` object\n          // Provide access to `req` and `res` in each of their `this` contexts.\n          _.each(sails.middleware.responses, function eachMethod(responseFn, name) {\n            res[name] = responseFn.bind({\n              req: req,\n              res: res\n            });\n          });\n\n          // Proceed!\n          return next();\n        }\n      }\n    }\n\n  };\n};\n\n\n\n\n/**\n * [_mixinJsonxMethod description]\n * @param  {[type]} req [description]\n * @param  {[type]} res [description]\n * @return {[type]}     [description]\n */\nfunction _mixinJsonxMethod(req, res) {\n\n  function _stringifyJsonxError(err) {\n    var plainObject = {};\n    Object.getOwnPropertyNames(err).forEach(function (key) {\n      plainObject[key] = err[key];\n    });\n    return JSON.stringify(plainObject);\n  }\n\n  function _handleJsonxError(err){\n    var serializedErr;\n    var jsonSerializedErr;\n\n    try {\n      serializedErr = _stringifyJsonxError(err);\n      jsonSerializedErr = JSON.parse(serializedErr);\n      if (!jsonSerializedErr.stack || !jsonSerializedErr.message) {\n        jsonSerializedErr.message = err.message;\n        jsonSerializedErr.stack = err.stack;\n      }\n      return jsonSerializedErr;\n    }\n    catch (unusedErr){\n      return {name: err.name, message: err.message, stack: err.stack};\n    }\n  }\n\n  /**\n   * res.jsonx(data)\n   *\n   * Serve JSON (and allow JSONP if enabled in `req.options`)\n   *\n   * @param  {Object} data\n   *\n   * - - - - - - - - - - - - - - - - - -\n   * WARNING: THIS IS DEPRECATED!\n   * - - - - - - - - - - - - - - - - - -\n   */\n  res.jsonx = res.jsonx || function jsonx (data){\n\n    var caller = jsonx.caller && jsonx.caller.name;\n\n    // Get easy access to Sails app instance.\n    var sails = req._sails;\n\n    // Log a deprecation notice.\n    sails.log.debug('***********************************************************************************');\n    sails.log.debug('`res.jsonx()` is deprecated in Sails v1.0 and will be removed in a future release.');\n    sails.log.debug(chalk.bold('Any files in `api/responses/` that haven\\'t been customized can simply be removed.'));\n    sails.log.debug(chalk.gray('Otherwise see http://sailsjs.com/upgrading for options to replace `res.jsonx()`.'));\n    if (caller) {\n      sails.log.debug(chalk.gray('(jsonx was called from `' + caller + '`)'));\n    }\n    sails.log.debug('***********************************************************************************');\n\n    // Send conventional status message if no data was provided\n    // (see http://expressjs.com/en/api.html#res.send)\n    if (_.isUndefined(data)) {\n      return res.status(res.statusCode).send();\n    }\n    else if (typeof data !== 'object') {\n      // (note that this guard includes arrays, functions, and even `null`)\n      return res.send(data);\n    }\n\n    // When responding with an Error instance, if it's going to get sringified into\n    // a dictionary with no `.stack` or `.message` properties, add them in.\n    if (data instanceof Error) {\n      data = _handleJsonxError(data);\n    }\n\n    if ( req.options.jsonp && !req.isSocket ) {\n      return res.jsonp(data);\n    }\n    else {\n      return res.json(data);\n    }\n  };//ƒ\n}//ƒ\n\n\n\n\n// Note for later\n// We could differentiate between 500 (generic error message)\n// and 504 (gateway did not receive response from upstream server) which could describe an IO problem\n// This is worth having a think about, since there are 2 fundamentally different kinds of \"server errors\":\n// (a) An infrastructural issue, or 504  (e.g. MySQL database randomly crashed or Twitter is down)\n// (b) Unexpected bug in app code, or 500 (e.g. `req.session.user.id`, but `req.session.user` doesn't exist)\n//\n// See the Sails project roadmap (sailsjs.com/roadmap) for future plans to better account for this.\n"
  },
  {
    "path": "lib/hooks/responses/onRoute.js",
    "content": "\n\nmodule.exports = function (sails) {\n\n\n  /**\n   * Handle `route:typeUnknown` events.\n   * This \"teaches\" the router to understand `response` in route target syntax.\n   * This allows route addresses to be bound directly to one of this Sails app's response modules.\n   * (i.e. usually defined in your app's `api/responses/` folder, or in the case of res.ok(),\n   *  res.serverError(), etc., provided by default from Sails core)\n   *\n   * e.g.\n   * ```\n   * 'get /admin/sweet-dashboard-or-report-or-something': { response: 'notImplemented' }\n   * ```\n   *\n   * @param {Dictionary} route\n   *        route target definition\n   */\n  return function onRoute (route) {\n\n    // If we have a matching response, use it.\n    if (route.target && route.target.response) {\n      if (sails.middleware.responses[route.target.response]) {\n        sails.log.silly('Binding response ('+route.target.response+') to '+route.verb+' '+route.path);\n        sails.router.bind(route.path, function(req, res) {\n          res[route.target.response]();\n        }, route.verb, route.options);\n      }\n      // Invalid respose?  Ignore and continue.\n      else {\n        sails.log.error(route.target.response +' :: ' +\n        'Ignoring invalid attempt to bind route to an undefined response:',\n        'for path: ', route.path, route.verb ? ('and verb: ' + route.verb) : '');\n        return;\n      }\n    }\n  };\n\n};\n\n"
  },
  {
    "path": "lib/hooks/security/README.md",
    "content": "# security (Core Hook)\n\n\n## Status\n\n> ##### Stability: [2](https://github.com/balderdashy/sails-docs/blob/master/contributing/stability-index.md) - Stable\n\n\n\n## Dependencies\n\nIn order for this hook to load, the following other hooks must have already finished loading:\n\n- moduleloader\n- userconfig\n\n\n## Dependents\n\nIf this hook is disabled, in order for Sails to load, the following other core hooks must also be disabled:\n\n_N/A_\n\n\n## Purpose\n\nThis hook's responsibilities are:\n\n\n##### Bind shadow routes to set appropriate CORS headers\n\nWhen Sails loads, this hook binds a `router:before` listener so that it can bind routes before the router binds explicit routes.  Then it binds shadow routes for the appropriate endpoints based on `sails.config.cors` (also mixing in its implicit defaults).\n\n##### Sets up CRSF action\n\nIt generates `security/grant-csrf-token` action \n\n## Implicit Defaults\n\nThis hook sets the following implicit default configuration on `sails.config.security`:\n\n\n| Property                                      | Type          | Default         |\n|-----------------------------------------------|:-------------:|-----------------|\n| `sails.config.security.cors.allowOrigins`                    | ((string))    | `'*'`\n| `sails.config.security.cors.allRoutes`                 | ((boolean))   | `false`\n| `sails.config.security.cors.allowCredentials`               | ((boolean))   | `false`\n| `sails.config.security.cors.allowRequestMethods`                   | ((string))    | `'GET, HEAD, PUT, PATCH, POST, DELETE'`\n| `sails.config.security.cors.allowRequestHeaders`                   | ((string))    | `'content-type'`\n| `sails.config.security.cors.allowResponseHeaders`             | ((string))    | `''` _(empty string)_\n| `sails.config.security.cors.allowAnyOriginWithCredentialsUnsafe`             | ((boolean))    | `false`\n| `sails.config.security.csrf`             | ((boolean))    | `false`\n\n\n\n\n## Events\n\n##### `hook:security:loaded`\n\nEmitted when this hook has been automatically loaded by Sails core, and triggered the callback in its `initialize` function.\n\n\n\n\n## FAQ\n\n> If you have a question that isn't covered here, please feel free to send a PR adding it to this section (even if you don't have the answer!)\n"
  },
  {
    "path": "lib/hooks/security/cors/index.js",
    "content": "module.exports = function(sails) {\n\n  /**\n   * Module dependencies.\n   */\n\n  var _ = require('@sailshq/lodash');\n  var setHeaders = require('./set-headers');\n  var setPreflightConfig = require('./set-preflight-config');\n  var detectVerb = require('../../../util/detect-verb');\n\n  /**\n   * Expose hook definition\n   */\n\n  return function initializeCors() {\n\n    // Once it's time to bind shadow routes, get to bindin'.\n    sails.on('router:before', function () {\n      // (FUTURE: consider changing this ^^ to `sails.after()` for consistency)\n\n      // If we're setting CORS on all routes by default, set up a universal route for it here.\n      // CORS can still be turned off for specific routes by setting `cors:false`\n      if (sails.config.security.cors.allRoutes === true) {\n        sails.router.bind('/*', setHeaders(sails.config.security.cors), 'all', {_middlewareType: 'CORS HOOK: sendHeaders'});\n      }\n      // Otherwise, default to blocking all cross-origin requests.\n      else {\n        sails.router.bind('/*', setHeaders({allowOrigins: false}), null, {_middlewareType: 'CORS HOOK: clearHeaders'});\n      }\n\n      // Declare a var to hold the various CORS settings for preflight OPTIONS routes, which we'll build up\n      // as we look at the route configs below.\n      var optionsRouteConfigs = {};\n\n      // Loop through all configured routes, looking for CORS options\n      _.each(sails.router.explicitRoutes, function(routeConfig, route) {\n\n        // Get some info about the route, like its path and verb.\n        var routeInfo = detectVerb(route);\n        var path = routeInfo.original.toLowerCase();\n        var verb = routeInfo.verb.toLowerCase();\n\n        // Get a handle to the route CORS config.\n        var routeCorsConfig = routeConfig.cors;\n\n        // If this route doesn't have its own CORS config, move on.\n        if (_.isUndefined(routeCorsConfig)) { return; }\n\n        // If this route is pointing to the CSRF token route, log a warning.\n        if (routeCorsConfig !== false && routeConfig.action === 'security/grant-csrf-token') {\n          sails.log.verbose('The `grant-csrf-token` action is not supported for cross-origin requests in situations/browsers where 3rd party cookies are blocked.');\n          sails.log.verbose('(You are seeing this message because the route `' + verb + ' ' + path + '` has CORS settings configured.)');\n        }\n\n        optionsRouteConfigs[path] = optionsRouteConfigs[path] || {};\n\n        // If cors is set to `true`, and we're not doing all routes by default, set\n        // the CORS headers for this route using the default origin\n        if (routeCorsConfig === true) {\n          if (!sails.config.security.cors.allRoutes) {\n            // Use the default CORS config for this path on an OPTIONS request\n            optionsRouteConfigs[path][verb || 'default'] = sails.config.security.cors;\n            sails.router.bind(route, setHeaders(sails.config.security.cors), null, {_middlewareType: 'CORS HOOK: setHeaders'});\n          }\n        }\n\n        // If cors is set to `false`, clear the CORS headers for this route\n        else if (routeCorsConfig === false) {\n          // Clear headers on an OPTIONS request for this path\n          optionsRouteConfigs[path][verb || 'default'] = 'clear';\n          sails.router.bind(route, setHeaders({allowOrigins: false}), 'all', {_middlewareType: 'CORS HOOK: clearHeaders'});\n          return;\n        }\n\n\n        // Else if cors is set to a string, use that has the origin\n        else if (typeof routeCorsConfig === 'string') {\n          optionsRouteConfigs[path][verb || 'default'] = _.extend({allowOrigins: [routeCorsConfig]});\n          sails.router.bind(route, setHeaders(_.extend({}, sails.config.security.cors, {allowOrigins: [routeCorsConfig], methods: verb})), null, {_middlewareType: 'CORS HOOK: setHeaders'});\n        }\n\n        // Else if cors is an object, use that as the config\n        else if (_.isPlainObject(routeCorsConfig)) {\n\n          // Set configuration for the preflight OPTIONS request for this route.\n          optionsRouteConfigs[path][verb || 'default'] = routeCorsConfig;\n\n          // Bind a route that will set CORS headers for this url/path combo.\n          sails.router.bind(route, setHeaders(_.extend({}, routeCorsConfig)), null, {_middlewareType: 'CORS HOOK: setHeaders'});\n        }\n\n        // Otherwise we don't recognize the CORS config, so throw a warning\n        else {\n          sails.log.warn('Invalid CORS settings for route '+route);\n        }\n\n      });\n\n      // Now that we have `optionsRouteConfigs`, a list of all of the routes that (possibly) need\n      // to be preflighted, construct a route that will handle OPTIONS requests for all of those routes.\n      // Sending the result of `setPreflightConfig` (a function) into `setHeaders` will cause `setHeaders`\n      // to run the function in order to determine the CORS options to use.\n      sails.router.bind('options /*', setHeaders(setPreflightConfig(optionsRouteConfigs, sails.config.security.cors)), 'options', {_middlewareType: 'CORS HOOK: preflight'});\n\n    });\n\n\n    // Continue loading this Sails app.\n    return;\n\n  };\n\n\n};\n"
  },
  {
    "path": "lib/hooks/security/cors/set-headers.js",
    "content": "/**\n * This script is a modified version of the Express 4 CORS module:\n * https://github.com/expressjs/cors\n * We're making a modified version because that one leaks headers,\n * but is otherwise still more full-featured and well-thought-out\n * than what we were previously using to set headers.\n *\n * By 'leaks headers', we mean that in certain cases the module\n * would set headers like `Access-Control-Allow-Origin` or\n * `Access-Control-Allow-Methods` even if the requesting origin\n * was not whitelisted.  User agents would still reject the response,\n * but it would allow attackers to sniff some information about\n * what the server _would_ allow.\n *\n * This version of the module _only_ sends headers if the origin\n * in the request is whitelisted.\n */\n(function () {\n\n  'use strict';\n\n  var vary = require('vary');\n\n  var defaults = {\n    origin: '*',\n    methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',\n    preflightContinue: false,\n    // Note -- the original module default for this setting is 204\n    optionsSuccessStatus: 200\n  };\n\n  function isString(s) {\n    return typeof s === 'string' || s instanceof String;\n  }\n\n  function isOriginAllowed(origin, allowedOrigin) {\n    if (Array.isArray(allowedOrigin)) {\n      for (var i = 0; i < allowedOrigin.length; ++i) {\n        if (isOriginAllowed(origin, allowedOrigin[i])) {\n          return true;\n        }\n      }\n      return false;\n    } else if (isString(allowedOrigin)) {\n      return origin === allowedOrigin;\n    } else if (allowedOrigin instanceof RegExp) {\n      return allowedOrigin.test(origin);\n    } else {\n      return !!allowedOrigin;\n    }\n  }\n\n  function configureOrigin(options, req) {\n    var requestOrigin = req.headers.origin;\n    var headers = [];\n\n    // If the allowed origin is '*' (or not set, in which case defaulting to '*'),\n    // then we'll send an `Access-Control-Allow-Origin` header.\n    if (!options.origin || options.origin === '*') {\n      // allow any origin\n      headers.push([{\n        key: 'Access-Control-Allow-Origin',\n        value: '*'\n      }]);\n    }\n    // Otherwise we'll send the header if and ONLY if the requesting origin matches\n    // one of the whitelisted origins.  Note that the Express CORS module makes an\n    // exception here if `origin` is set to a string, and always returns the header\n    // even if the requesting origin doesn't match, which seems like a security leak.\n    else {\n      if (isOriginAllowed(requestOrigin, options.origin)) {\n        // reflect origin\n        headers.push([{\n          key: 'Access-Control-Allow-Origin',\n          value: requestOrigin\n        }]);\n        // Also send a \"vary\" header to allow proxies to cache this request correctly.\n        headers.push([{\n          key: 'Vary',\n          value: 'Origin'\n        }]);\n      }\n    }\n\n    return headers;\n  }\n\n  function configureMethods(options) {\n    var methods = options.methods || defaults.methods;\n    if (methods.join) {\n      methods = options.methods.join(','); // .methods is an array, so turn it into a string\n    }\n    return {\n      key: 'Access-Control-Allow-Methods',\n      value: methods\n    };\n  }\n\n  function configureCredentials(options) {\n    if (options.credentials === true) {\n      return {\n        key: 'Access-Control-Allow-Credentials',\n        value: 'true'\n      };\n    }\n    return null;\n  }\n\n  function configureAllowedHeaders(options, req) {\n    var headers = options.allowedHeaders || options.headers;\n    if (!headers) {\n      headers = req.headers['access-control-request-headers']; // .headers wasn't specified, so reflect the request headers\n    } else if (headers.join) {\n      headers = headers.join(','); // .headers is an array, so turn it into a string\n    }\n    if (headers && headers.length) {\n      return {\n        key: 'Access-Control-Allow-Headers',\n        value: headers\n      };\n    }\n    return null;\n  }\n\n  function configureExposedHeaders(options) {\n    var headers = options.exposedHeaders;\n    if (!headers) {\n      return null;\n    } else if (headers.join) {\n      headers = headers.join(','); // .headers is an array, so turn it into a string\n    }\n    if (headers && headers.length) {\n      return {\n        key: 'Access-Control-Expose-Headers',\n        value: headers\n      };\n    }\n    return null;\n  }\n\n  function configureMaxAge(options) {\n    var maxAge = options.maxAge && options.maxAge.toString();\n    if (maxAge && maxAge.length) {\n      return {\n        key: 'Access-Control-Max-Age',\n        value: maxAge\n      };\n    }\n    return null;\n  }\n\n  function applyHeaders(headers, res) {\n    for (var i = 0, n = headers.length; i < n; i++) {\n      var header = headers[i];\n      if (header) {\n        if (Array.isArray(header)) {\n          applyHeaders(header, res);\n        } else if (header.key === 'Vary' && header.value) {\n          vary(res, header.value);\n        } else if (header.value) {\n          res.setHeader(header.key, header.value);\n        }\n      }\n    }\n  }\n\n  function cors(options, req, res, next) {\n    var headers = [];\n    var method = req.method && req.method.toUpperCase && req.method.toUpperCase();\n\n    if (method === 'OPTIONS') {\n      // preflight\n      headers = configureOrigin(options, req);\n      // ONLY send additional headers if configureOrigin added the `Access-Control-Allow-Origin`\n      // header, meaning that the requesting origin was whitelisted.\n      if (headers.length) {\n        headers.push(configureCredentials(options, req));\n        headers.push(configureMethods(options, req));\n        headers.push(configureAllowedHeaders(options, req));\n        headers.push(configureMaxAge(options, req));\n        headers.push(configureExposedHeaders(options, req));\n        applyHeaders(headers, res);\n      }\n\n      if (options.preflightContinue ) {\n        return next();\n      } else {\n        res.statusCode = options.optionsSuccessStatus || defaults.optionsSuccessStatus;\n        res.end();\n      }\n    } else {\n      // actual response\n      headers = configureOrigin(options, req);\n      // ONLY send additional headers if configureOrigin added the `Access-Control-Allow-Origin`\n      // header, meaning that the requesting origin was whitelisted.\n      if (headers.length) {\n        headers.push(configureCredentials(options, req));\n        headers.push(configureExposedHeaders(options, req));\n        applyHeaders(headers, res);\n      }\n      return next();\n    }\n  }\n\n  function middlewareWrapper(o) {\n    // if no options were passed in, use the defaults\n    if (!o || o === true) {\n      o = {};\n    }\n    if (o.origin === undefined) {\n      o.origin = defaults.origin;\n    }\n    if (o.methods === undefined) {\n      o.methods = defaults.methods;\n    }\n    if (o.preflightContinue === undefined) {\n      o.preflightContinue = defaults.preflightContinue;\n    }\n\n    // if options are static (either via defaults or custom options passed in), wrap in a function\n    var optionsCallback = null;\n    if (typeof o === 'function') {\n      optionsCallback = o;\n    } else {\n      optionsCallback = function (req, cb) {\n        cb(null, o);\n      };\n    }\n\n    return function corsMiddleware(req, res, next) {\n      optionsCallback(req, function (err, options) {\n\n        // Transform the Sails CORS options configs into those expected by this module.\n        options = {\n          origin: options.allowOrigins,\n          credentials: options.allowCredentials,\n          methods: options.allowRequestMethods,\n          headers: options.allowRequestHeaders,\n          exposedHeaders: options.allowResponseHeaders\n        };\n\n        // If origin is `*` and `credentials` is true, that means that `allowAnyOriginWithCredentialsUnsafe`\n        // has been set in Sails, so we'll change the origin to `true` (which causes the request origin to\n        // be reflected in the response).\n        if (options.origin === '*' && options.credentials === true) {\n          options.origin = true;\n        }\n\n        if (err) {\n          return next(err);\n        } else {\n          var originCallback = null;\n          if (options.origin && typeof options.origin === 'function') {\n            originCallback = options.origin;\n          } else if (options.origin) {\n            originCallback = function (origin, cb) {\n              cb(null, options.origin);\n            };\n          }\n\n          if (originCallback) {\n            originCallback(req.headers.origin, function (err2, origin) {\n              if (err2 || !origin) {\n                return next(err2);\n              } else {\n                var corsOptions = Object.create(options);\n                corsOptions.origin = origin;\n                cors(corsOptions, req, res, next);\n              }\n            });\n          } else {\n            return next();\n          }\n        }\n      });\n    };\n  }\n\n  // can pass either an options hash, an options delegate, or nothing\n  module.exports = middlewareWrapper;\n\n}());\n"
  },
  {
    "path": "lib/hooks/security/cors/set-preflight-config.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar _ = require('@sailshq/lodash');\nvar pathToRegexp = require('path-to-regexp');\n\n/**\n *\n * Since the CORS headers module we're using (adapted from https://github.com/expressjs/cors) allows you to provide\n * options in two ways -- either by passing in a dictionary or by passing in a function that takes the request object and\n * returns the options at runtime.  `setPreflightConfig` prepares a function like that, using a dictionary of information\n * about Sails routes and their CORS preflight configs, that will be bound to `OPTIONS /*`.  That way, when a browser\n * makes a preflight request to (for example) 'PUT /foo', the constructed function will be run, will look up\n * `preflightConfigs['/foo']['put']` or `preflightConfigs['/foo']['default']` and use that dictionary of options\n * (combined with the Sails defaults) to tell the CORS module which headers to send back.\n *\n * setPreflightConfig\n * @param  {Dictionary} preflightConfigs A dictionary mapping route path -> dictionary of CORS configs indexed by method (where 'default' is a valid method)\n * @param  {Dictionary} defaultConfig    The default CORS config for Sails.\n * @return {Function} A function that returns the correct set of CORS options for an OPTIONS request to a given path and verb.\n */\nmodule.exports = function setPreflightConfig(preflightConfigs, defaultConfig) {\n\n  return function (req, cb) {\n\n    var path = req.path;\n    var method = (req.headers['access-control-request-method'] || '').toLowerCase() || 'default';\n\n    var corsConfig = _.reduce(preflightConfigs, function(memo, configs, preflightConfigPath) {\n      if (memo) {return memo;}\n      var regex = pathToRegexp(preflightConfigPath, []);\n      if (path.match(regex) && (configs[method] || configs.default)) {\n        return (configs[method] || configs.default);\n      }\n    }, null);\n\n    // If no CORS config is present for this route, set `allowOrigins` to false which\n    // will result in the `acess-control-allow-origin` header being unset.\n    if (!corsConfig) {\n      return cb(null, { allowOrigins: false });\n    }\n\n    // Otherwise merge the route CORS config into the default CORS config and use that\n    // for the OPTIONS response headers.\n    return cb(null, _.extend({}, defaultConfig, corsConfig));\n\n  };\n\n};\n\n"
  },
  {
    "path": "lib/hooks/security/csrf/grant-csrf-token.js",
    "content": "/**\n * Action which grants a CSRF token to the requestor.\n */\nmodule.exports = function grantCsrfToken (req, res /*, next */) {\n  // Don't grant CSRF tokens over sockets.\n  if (req.isSocket) {\n    if (process.env.NODE_ENV === 'production') {\n      return res.notFound();\n    }\n    return res.badRequest(new Error('Cannot access CSRF token via socket request.  Instead, send an HTTP request (i.e. XHR) to this endpoint.'));\n  }\n  // Send the CSRF token wrapped in an object.\n  return res.json({\n    _csrf: res.locals._csrf\n  });\n};\n"
  },
  {
    "path": "lib/hooks/security/csrf/index.js",
    "content": "module.exports = function(sails) {\n\n  /**\n   * Module dependencies.\n   */\n\n  var csurf = require('@sailshq/csurf');\n  var _ = require('@sailshq/lodash');\n  var pathToRegexp = require('path-to-regexp');\n  var detectVerb = require('../../../util/detect-verb');\n\n\n  return function initializeCsrf() {\n\n    // Instantiate CSRF middleware\n    var csrfMiddleware = csurf();\n\n    // Loop through the configured routes in order, and create a blacklist and a whitelist\n    // containing regexes to check routes against.\n    var blacklist = [];\n    var whitelist = [];\n    // Regex to check if the route is...a regex.\n    var regExRoute = /^r\\|(.*)\\|(.*)$/;\n\n    sails.on('router:after', function() {\n\n      var sortedRouteAddresses = sails.router.getSortedRouteAddresses();\n\n      _.each(sortedRouteAddresses, function(address) {\n\n        var routeInfo = detectVerb(address);\n        var path = routeInfo.original;\n        var verb = routeInfo.verb.toLowerCase();\n        var target = sails.router.explicitRoutes[address];\n\n        // Ignore targets that don't have CSRF explicitly set.\n        if (target.csrf !== true && target.csrf !== false) {\n          return;\n        }\n\n        // Ignore targets with GET, HEAD or OPTIONS methods (with warning)\n        if (verb === 'get' || verb === 'head' || verb === 'options') {\n          sails.log.debug('Ignoring `csrf: ' + target.csrf + '` setting for route `' + verb + ' ' + path + '`.');\n          sails.log.debug('CSRF protection does not apply to GET, HEAD or OPTIONS routes.');\n          sails.log.debug();\n          return;\n        }\n\n        // Perform the check\n        var matches = path.match(regExRoute);\n        var regex;\n\n        // If it *is* a regex, create a RegExp object that Express can bind,\n        // pull out the params, and wrap the handler in regexRouteWrapper\n        if (matches) {\n          regex = new RegExp(matches[1]);\n        } else {\n          path = path.toLowerCase();\n          regex = pathToRegexp(path);\n        }\n\n\n        if (target.csrf === false && sails.config.security.csrf === true) {\n          blacklist.push({method: verb || '', regex: regex});\n        }\n\n        else if (target.csrf === true && sails.config.security.csrf === false) {\n          whitelist.push({method: verb || '', regex: regex});\n        }\n\n      });\n\n    });\n\n    sails.on('router:before', function () {\n\n      // Start with a clear res.locals._csrf in every request.\n      sails.router.bind('ALL /*', function clearCSRFTokenLocal (req, res, next) {\n        res.locals._csrf = '';\n        return next();\n      });\n\n      // Check CSRF token on relevant requests, and add the CSRF token as res.locals._csrf\n      // where applicable.\n      sails.router.bind('/*', function(req, res, next) {\n\n        // If global CSRF is disabled check the whitelist.\n        if (sails.config.security.csrf === false) {\n          // If nothing in the whitelist matches, continue on without checking for a CSRF token.\n          if (!_.any(whitelist, function(whitelistedRoute) {\n            return req.path.match(whitelistedRoute.regex) && (!whitelistedRoute.method || whitelistedRoute.method === req.method.toLowerCase());\n          })) {\n            return next();\n          }\n        }\n        // Otherwise check the blacklist\n        else {\n          // If anything in the blacklist matches, continue on without checking for a CSRF token.\n          if (_.any(blacklist, function(blacklistedRoute) {\n            return req.path.match(blacklistedRoute.regex) && (!blacklistedRoute.method || blacklistedRoute.method === req.method.toLowerCase());\n          })) {\n            return next();\n          }\n        }\n\n        // Handle session being disabled.\n        if (!req.session) {\n          // For GET, HEAD and OPTIONS requests, continue on.  These aren't covered by CSRF anyway.\n          if (_.contains(['get', 'head', 'options'], req.method.toLowerCase())) {\n            return next();\n          }\n          // In development mode, give a more explicit account of what's happening.\n          if (process.env.NODE_ENV === 'development') {\n            return next(new Error('Route `' + req.method + ' ' + req.path + '` has CSRF enabled, but the session is disabled!'));\n          }\n          // In production, just return the same CSRF mismatch error you'd get with a bad/missing token in the request.\n          else {\n            return res.forbidden('CSRF mismatch');\n          }\n        }\n\n        return csrfMiddleware(req, res, function(err) {\n          if (err) {\n            // Only attempt to handle invalid csrf tokens\n            if (err.code !== 'EBADCSRFTOKEN') {\n              return next(err);\n            }\n            return res.forbidden('CSRF mismatch');\n          }\n          // If this is not a socket request, provide the CSRF token in res.locals,\n          // so it can be bootstrapped into a view.  For purposes of CSRF, we're\n          // treating sockets as inherently insecure (note that we disable\n          // the grant-csrf-token action for sockets as well).  You can certainly\n          // _spend_ CSRF tokens over sockets, you just can't retrieve them.\n          if (!req.isSocket) {\n            res.locals._csrf = req.csrfToken();\n          }\n          next();\n        });\n\n      }, null, {_middlewareType: 'CSRF HOOK: CSRF'});\n\n    });\n\n    return;\n\n  };\n\n};\n"
  },
  {
    "path": "lib/hooks/security/index.js",
    "content": "module.exports = function(sails) {\n\n  /**\n   * Module dependencies.\n   */\n\n  var _ = require('@sailshq/lodash');\n  var flaverr = require('flaverr');\n\n  var checkOriginUrl = require('../../util/check-origin-url');\n  var detectVerb = require('../../util/detect-verb');\n\n  var initializeCors = require('./cors')(sails);\n  var initializeCsrf = require('./csrf')(sails);\n  var grantCsrfToken = require('./csrf/grant-csrf-token');\n\n  /**\n   * Expose hook definition\n   */\n\n  return {\n\n    defaults: {\n\n      security: {\n\n        cors: {\n          allowOrigins: '*',\n          allRoutes: false,\n          allowCredentials: false,\n          allowRequestMethods: 'GET,HEAD,PUT,PATCH,POST,DELETE',\n          allowRequestHeaders: 'content-type',\n          allowResponseHeaders: '',\n          allowAnyOriginWithCredentialsUnsafe: false\n        },\n\n        csrf: false\n\n      }\n\n    },\n\n    configure: function() {\n\n\n      //   ██████╗ ██████╗ ███╗   ██╗███████╗██╗ ██████╗ ██╗   ██╗██████╗ ███████╗\n      //  ██╔════╝██╔═══██╗████╗  ██║██╔════╝██║██╔════╝ ██║   ██║██╔══██╗██╔════╝\n      //  ██║     ██║   ██║██╔██╗ ██║█████╗  ██║██║  ███╗██║   ██║██████╔╝█████╗\n      //  ██║     ██║   ██║██║╚██╗██║██╔══╝  ██║██║   ██║██║   ██║██╔══██╗██╔══╝\n      //  ╚██████╗╚██████╔╝██║ ╚████║██║     ██║╚██████╔╝╚██████╔╝██║  ██║███████╗\n      //   ╚═════╝ ╚═════╝ ╚═╝  ╚═══╝╚═╝     ╚═╝ ╚═════╝  ╚═════╝ ╚═╝  ╚═╝╚══════╝\n      //\n      //   ██████╗███████╗██████╗ ███████╗\n      //  ██╔════╝██╔════╝██╔══██╗██╔════╝\n      //  ██║     ███████╗██████╔╝█████╗\n      //  ██║     ╚════██║██╔══██╗██╔══╝\n      //  ╚██████╗███████║██║  ██║██║\n      //   ╚═════╝╚══════╝╚═╝  ╚═╝╚═╝\n\n      if (sails.config.csrf) {\n        sails.log.debug('The `sails.config.csrf` config has been deprecated.');\n        sails.log.debug('Please use `sails.config.security.csrf` instead.');\n        sails.log.debug('(we\\'ll use your `sails.config.csrf` settings for now).\\n');\n        sails.config.security.csrf = sails.config.csrf;\n      }\n\n      if (sails.config.security.csrf === true && !sails.hooks.session) {\n        throw flaverr({ name: 'userError', code: 'E_INVALID_SECURITY_CONFIG' }, new Error(\n          '\\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'+\n          'Detected `sails.config.security.csrf set to `true` while session hook is disabled.\\n'+\n          'Sails CSRF support requires the session hook to be enabled.\\n'+\n          'See http://sailsjs.com/docs/reference/config/sails-config-session#?disabling-sessions.\\n'+\n          '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'\n        ));\n      }\n\n      if (!_.isUndefined(sails.config.security.csrf.routesDisabled)) {\n        throw flaverr({ name: 'userError', code: 'E_INVALID_SECURITY_CONFIG' }, new Error(\n          '\\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'+\n          'Invalid global CSRF settings: `routesDisabled` is no longer supported as of Sails v1.0.\\n'+\n          'Instead, set `csrf: false` in `config/routes.js` for any route that you want exempted\\n'+\n          'from CSRF protection.\\n'+\n          'For more info see: http://sailsjs.com/docs/concepts/security/csrf#?enabling-csrf-protection.\\n'+\n          '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'\n        ));\n      }\n\n      if (!_.isUndefined(sails.config.security.csrf.origin)) {\n        throw flaverr({ name: 'userError', code: 'E_INVALID_SECURITY_CONFIG' }, new Error(\n          '\\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'+\n          'Invalid global CSRF settings: `origin` is no longer supported as of Sails v1.0.\\n'+\n          'Instead, apply CORS settings directly to the CSRF-token-dispensing route in `config/routes.js`.\\n'+\n          'For more info see: \\n'+\n          'http://next.sailsjs.com/docs/concepts/security/csrf#?using-ajax-websockets\\n'+\n          'http://next.sailsjs.com/documentation/concepts/security/cors\\n'+\n          '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'\n        ));\n      }\n\n      if (!_.isUndefined(sails.config.security.csrf.grantTokenViaAjax)) {\n        throw flaverr({ name: 'userError', code: 'E_INVALID_SECURITY_CONFIG' }, new Error(\n          '\\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'+\n          'Invalid global CSRF settings: `grantTokenViaAjax` is no longer supported as of Sails v1.0.\\n'+\n          'Instead, add a route to your `config/routes.js` file using the `security/grant-csrf-token` action.\\n'+\n          'For more info see: http://next.sailsjs.com/docs/concepts/security/csrf#?using-ajax-websockets\\n'+\n          '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'\n        ));\n      }\n\n\n      //   ██████╗ ██████╗ ███╗   ██╗███████╗██╗ ██████╗ ██╗   ██╗██████╗ ███████╗\n      //  ██╔════╝██╔═══██╗████╗  ██║██╔════╝██║██╔════╝ ██║   ██║██╔══██╗██╔════╝\n      //  ██║     ██║   ██║██╔██╗ ██║█████╗  ██║██║  ███╗██║   ██║██████╔╝█████╗\n      //  ██║     ██║   ██║██║╚██╗██║██╔══╝  ██║██║   ██║██║   ██║██╔══██╗██╔══╝\n      //  ╚██████╗╚██████╔╝██║ ╚████║██║     ██║╚██████╔╝╚██████╔╝██║  ██║███████╗\n      //   ╚═════╝ ╚═════╝ ╚═╝  ╚═══╝╚═╝     ╚═╝ ╚═════╝  ╚═════╝ ╚═╝  ╚═╝╚══════╝\n      //\n      //   ██████╗ ██████╗ ██████╗ ███████╗\n      //  ██╔════╝██╔═══██╗██╔══██╗██╔════╝\n      //  ██║     ██║   ██║██████╔╝███████╗\n      //  ██║     ██║   ██║██╔══██╗╚════██║\n      //  ╚██████╗╚██████╔╝██║  ██║███████║\n      //   ╚═════╝ ╚═════╝ ╚═╝  ╚═╝╚══════╝\n\n      //  ┌─┐┬  ┌─┐┌┐ ┌─┐┬    ┌─┐┌─┐┌┐┌┌─┐┬┌─┐\n      //  │ ┬│  │ │├┴┐├─┤│    │  │ ││││├┤ ││ ┬\n      //  └─┘┴─┘└─┘└─┘┴ ┴┴─┘  └─┘└─┘┘└┘└  ┴└─┘\n      if (!_.isUndefined(sails.config.cors)) {\n        sails.log.debug('The `sails.config.cors` config has been deprecated.');\n        sails.log.debug('Please use `sails.config.security.cors` instead.');\n        sails.log.debug('(we\\'ll use your `sails.config.cors` settings for now).\\n');\n        sails.config.security.cors = _.extend(sails.config.security.cors, sails.config.cors);\n      }\n\n      // Fail to lift if `securityLevel` is used\n      if (!_.isUndefined(sails.config.security.cors.securityLevel)) {\n        throw flaverr({ name: 'userError', code: 'E_INVALID_SECURITY_CONFIG' }, new Error(\n          '\\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'+\n          'Invalid global CORS settings: `securityLevel` is no longer supported as of Sails v1.0.\\n'+\n          'Instead, to secure your socket requests use `sails.config.sockets.onlyAllowOrigins`.\\n'+\n          'For more info see: http://sailsjs.com/config/sockets.\\n'+\n          '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'\n        ));\n      }\n\n      // Deprecate `origin` in favor of `allowOrigins`\n      if (!_.isUndefined(sails.config.security.cors.origin)) {\n        sails.log.debug('The `sails.config.security.cors.origin` config has been deprecated.');\n        sails.log.debug('Please use `sails.config.security.cors.allowOrigins` instead.');\n        sails.log.debug('(See http://sailsjs.com/config/security for more info.)'+'\\n');\n        sails.config.security.cors.allowOrigins = sails.config.security.cors.origin;\n        delete sails.config.security.cors.origin;\n      }\n\n      // Deprecate declaring `allowOrigins` as a string (except for '*').\n      if (_.isString(sails.config.security.cors.allowOrigins) && sails.config.security.cors.allowOrigins !== '*') {\n        sails.log.debug('When specifying multiple origins, the `sails.config.security.cors.allowOrigins`');\n        sails.log.debug('setting should be an array of strings. We\\'ll split it up for you this time...\\n');\n        sails.config.security.cors.allowOrigins = _.map(sails.config.security.cors.allowOrigins.split(','), function(origin){ return origin.trim(); });\n      }\n\n      // Bail out if `allowOrigins` is not an array or `*`.\n      else if (!_.isUndefined(sails.config.security.cors.allowOrigins) && sails.config.security.cors.allowOrigins !== '*' && !_.isArray(sails.config.security.cors.allowOrigins)) {\n        throw flaverr({ name: 'userError', code: 'E_INVALID_SECURITY_CONFIG' }, new Error('Invalid global CORS settings: if `allowOrigins` is specified, it must be either \\'*\\' or an array of strings.  See http://sailsjs.com/config/security for more info.'));\n      }\n\n      // Deprecate `credentials` in favor of `allowCredentials`\n      if (!_.isUndefined(sails.config.security.cors.credentials)) {\n        sails.log.debug('The `sails.config.security.cors.credentials` config has been deprecated.');\n        sails.log.debug('Please use `sails.config.security.cors.allowCredentials` instead.\\n');\n        sails.config.security.cors.allowCredentials = sails.config.security.cors.credentials;\n        delete sails.config.security.cors.credentials;\n      }\n\n      // Deprecate `headers` in favor of `allowRequestHeaders`\n      if (!_.isUndefined(sails.config.security.cors.headers)) {\n        sails.log.debug('The `sails.config.security.cors.headers` config has been deprecated.');\n        sails.log.debug('Please use `sails.config.security.cors.allowRequestHeaders` instead.\\n');\n        sails.config.security.cors.allowRequestHeaders = sails.config.security.cors.headers;\n        delete sails.config.security.cors.headers;\n      }\n\n      // Deprecate `methods` in favor of `allowRequestMethods`\n      if (!_.isUndefined(sails.config.security.cors.methods)) {\n        sails.log.debug('The `sails.config.security.cors.methods` config has been deprecated.');\n        sails.log.debug('Please use `sails.config.security.cors.allowRequestMethods` instead.\\n');\n        sails.config.security.cors.allowRequestMethods = sails.config.security.cors.methods;\n        delete sails.config.security.cors.methods;\n      }\n\n      // Deprecate `sails.config.cors.exposeHeaders` in favor of `sails.config.cors.allowResponseHeaders`\n      if (!_.isUndefined(sails.config.security.cors.exposeHeaders)) {\n        sails.log.debug('The `sails.config.security.cors.exposeHeaders` config has been deprecated.');\n        sails.log.debug('Please use `sails.config.security.cors.allowResponseHeaders` instead.\\n');\n        if (!sails.config.security.cors.allowResponseHeaders) {\n          sails.config.security.cors.allowResponseHeaders = sails.config.security.cors.exposeHeaders;\n        }\n        delete sails.config.security.cors.exposeHeaders;\n      }\n\n      // Split up non-* strings into an array.\n      // We'll complain about this later when we actually act on the route's CORS config\n      // rather than just validating it.\n      if (_.isString(sails.config.security.cors.allowOrigins) && sails.config.security.cors.allowOrigins !== '*') {\n        sails.log.debug('When specifying multiple allowable CORS origins, the sails.config.security.cors.allowOrigins setting');\n        sails.log.debug('should be an array of strings. We\\'ll split it up for you this time...\\n');\n        sails.config.security.cors.allowOrigins = _.map(sails.config.security.cors.allowOrigins.split(','), function(origin){ return origin.trim(); });\n      }\n      // If `allowOrigins` is not `*` and not an array at this point, bail.\n      else if (sails.config.security.cors.allowOrigins && sails.config.security.cors.allowOrigins !== '*' && !_.isArray(sails.config.security.cors.allowOrigins)) {\n        throw flaverr({ name: 'userError', code: 'E_BAD_ORIGIN_CONFIG' }, new Error('Invalid global CORS settings: if `sails.config.security.cors.allowOrigins` is specified, it must be \\'*\\' or an array of strings.'));\n      }\n\n      // Validate the passed-in origins.\n      // `checkOriginUrl` will throw if any origins are poorly-formed.\n      if (_.isArray(sails.config.security.cors.allowOrigins)) {\n        try {\n          _.each(sails.config.security.cors.allowOrigins, function(origin) {\n            checkOriginUrl(origin);\n          });\n        } catch (e) {\n          // If we got a poorly-formed origin, throw a more descriptive error.\n          if (e.code === 'E_INVALID') {\n            throw flaverr({ name: 'userError', code: 'E_INVALID_SECURITY_CONFIG' }, new Error('Invalid global CORS `allowOrigins` setting: ' + e.message+'  (See http://sailsjs.com/config/security for help.)'));\n          }\n          // Otherwise just throw whatever error we got.\n          throw e;\n        }\n      }\n\n      // If the app attempts to set `allowOrigins: '*'` and `allowCredentials: true`, bail out\n      if (sails.config.security.cors.allowOrigins === '*' && sails.config.security.cors.allowCredentials === true) {\n        if (sails.config.security.cors.allowAnyOriginWithCredentialsUnsafe !== true) {\n          throw flaverr({ name: 'userError', code: 'E_INVALID_SECURITY_CONFIG' }, new Error('Invalid global CORS settings: if `allowOrigins` is \\'*\\', `allowCredentials` cannot also be `true` (unless you enable the `allowAnyOriginWithCredentialsUnsafe` flag).  For more info, see http://sailsjs.com/config/security.'));\n        }\n        sails.config.security.cors.allowOrigins = true;\n      }\n\n      // If we're operating in unsafe mode, and origin is '*' and credentials is `true`,\n      // set the default origin to `true` as well which means \"reflect origin header\".\n      if (sails.config.security.cors.allowAnyOriginWithCredentialsUnsafe && sails.config.security.cors.credentials === true && sails.config.security.cors.allowOrigins === '*') {\n        sails.config.security.cors.allowOrigins = true;\n      }\n\n      //  ┬─┐┌─┐┬ ┬┌┬┐┌─┐  ┌─┐┌─┐┌┐┌┌─┐┬┌─┐\n      //  ├┬┘│ ││ │ │ ├┤   │  │ ││││├┤ ││ ┬\n      //  ┴└─└─┘└─┘ ┴ └─┘  └─┘└─┘┘└┘└  ┴└─┘\n\n      // Loop through all of the explicitly-configured routes and look for\n      // deprecated config and/or fatal config issues.\n      _.each(sails.config.routes, function(routeConfig, address) {\n\n        // Get some info about the route, like its path and verb.\n        // This is used in console messages.\n        var routeInfo = detectVerb(address);\n        var path = routeInfo.original.toLowerCase();\n        var verb = routeInfo.verb.toLowerCase();\n\n        // If this route doesn't have a CORS config, continue.\n        if (!_.isPlainObject(routeConfig.cors)) { return; }\n\n        // Get a reference to the route CORS config, so that we don't\n        // accidentally mess with routeConfig instead.\n        var routeCorsConfig = routeConfig.cors;\n\n        // Handle deprecated config.\n        // Deprecate `origin` in favor of `allowOrigins`\n        if (!_.isUndefined(routeCorsConfig.origin)) {\n          sails.log.debug('In route `' + ((verb ? (verb + ' ') : '') + path) + '`: ');\n          sails.log.debug('The `cors.origin` config has been deprecated.');\n          sails.log.debug('Please use `cors.allowOrigins` instead.');\n          sails.log.debug('(See http://sailsjs.com/config/security for more info.)'+'\\n');\n          routeCorsConfig.allowOrigins = routeCorsConfig.origin;\n          delete routeCorsConfig.origin;\n        }\n\n        // Deprecate `credentials` in favor of `allowCredentials`\n        if (!_.isUndefined(routeCorsConfig.credentials)) {\n          sails.log.debug('In route `' + ((verb ? (verb + ' ') : '') + path) + '`: ');\n          sails.log.debug('The `cors.credentials` config has been deprecated.');\n          sails.log.debug('Please use `cors.allowCredentials` instead.\\n');\n          routeCorsConfig.allowCredentials = routeCorsConfig.credentials;\n          delete routeCorsConfig.credentials;\n        }\n\n        // Deprecate `headers` in favor of `allowRequestHeaders`\n        if (!_.isUndefined(routeCorsConfig.headers)) {\n          sails.log.debug('In route `' + ((verb ? (verb + ' ') : '') + path) + '`: ');\n          sails.log.debug('The `cors.headers` config has been deprecated.');\n          sails.log.debug('Please use `cors.allowRequestHeaders` instead.\\n');\n          routeCorsConfig.allowRequestHeaders = routeCorsConfig.headers;\n          delete routeCorsConfig.headers;\n        }\n\n        // Deprecate `methods` in favor of `allowRequestMethods`\n        if (!_.isUndefined(routeCorsConfig.methods)) {\n          sails.log.debug('In route `' + ((verb ? (verb + ' ') : '') + path) + '`: ');\n          sails.log.debug('The `cors.methods` config has been deprecated.');\n          sails.log.debug('Please use `cors.allowRequestMethods` instead.\\n');\n          routeCorsConfig.allowRequestMethods = routeCorsConfig.methods;\n          delete routeCorsConfig.methods;\n        }\n\n        // Deprecate `sails.config.cors.exposeHeaders` in favor of `sails.config.cors.allowResponseHeaders`\n        if (!_.isUndefined(routeCorsConfig.exposeHeaders)) {\n          sails.log.debug('In route `' + ((verb ? (verb + ' ') : '') + path) + '`: ');\n          sails.log.debug('The `cors.exposeHeaders` config has been deprecated.');\n          sails.log.debug('Please use `cors.allowResponseHeaders` instead.\\n');\n          if (!routeCorsConfig.allowResponseHeaders) {\n            routeCorsConfig.allowResponseHeaders = routeCorsConfig.exposeHeaders;\n          }\n          delete routeCorsConfig.exposeHeaders;\n        }\n\n        // Apply the global CORS settings as defaults for the route CORS config.\n        routeCorsConfig = _.defaults(routeCorsConfig, sails.config.security.cors);\n\n        // Bail if `allowOrigins` is `*`, `allowCredentials` is `true` and `allowAnyOriginWithCredentialsUnsafe` is not true.\n        if (routeCorsConfig.allowOrigins === '*' && routeCorsConfig.allowCredentials === true && routeCorsConfig.allowAnyOriginWithCredentialsUnsafe !== true) {\n          throw flaverr({ name: 'userError', code: 'E_UNSAFE'}, new Error('Route `' + address + '` has invalid CORS settings: if `allowOrigins` is \\'*\\', `credentials` cannot be `true` unless `allowAnyOriginWithCredentialsUnsafe` is also true.'));\n        }\n\n        // Split up non-* strings into an array.\n        if (_.isString(routeCorsConfig.allowOrigins) && routeCorsConfig.allowOrigins !== '*') {\n          sails.log.debug('In route `' + ((verb ? (verb + ' ') : '') + path) + '`: ');\n          sails.log.debug('When specifying multiple allowable CORS origins, the allowOrigins setting');\n          sails.log.debug('should be an array of strings. We\\'ll split it up for you this time...\\n');\n          routeCorsConfig.allowOrigins = _.map(routeCorsConfig.allowOrigins.split(','), function(origin){ return origin.trim(); });\n        }\n        // If `allowOrigins` is not `*` and not an array at this point, bail.\n        else if (routeCorsConfig.allowOrigins && routeCorsConfig.allowOrigins !== '*' && !_.isArray(routeCorsConfig.allowOrigins)) {\n          throw flaverr({ name: 'userError', code: 'E_BAD_ORIGIN_CONFIG'}, new Error('Route `' + address + '` has invalid CORS settings: if `allowOrigins` is specified, it must be \\'*\\' or an array of strings.'));\n        }\n\n        // If `allowOrigins` is an array, loop through and validate each origin.\n        if (_.isArray(routeCorsConfig.allowOrigins)) {\n          try {\n            _.each(routeCorsConfig.allowOrigins, function(origin) {\n              checkOriginUrl(origin);\n            });\n          }\n          // If an error occurred validating an origin, forward it up the chain.\n          catch (e) {\n            // If it's an actual origin validation error, gussy it up first.\n            if (e.code === 'E_INVALID') {\n              throw flaverr({ name: 'userError', code: 'E_INVALID_ORIGIN'}, new Error('Route `' + address + '` has invalid CORS `allowOrigins` setting: ' + e.message));\n            }\n            // Otherwise just throw whatever error we got.\n            throw e;\n          }\n        }\n\n      });\n\n    },\n\n    initialize: function(cb) {\n\n      try {\n        initializeCors();\n        initializeCsrf();\n        return sails.hooks.security.registerActions(cb);\n      }\n      catch (err) {\n        return cb(err);\n      }\n\n    },\n\n    registerActions: function(cb) {\n\n      // Add the csrf-token-granting action (see below for the function definition).\n      sails.registerAction(grantCsrfToken, 'security/grant-csrf-token');\n\n      return cb();\n\n    }\n\n  };\n\n};\n"
  },
  {
    "path": "lib/hooks/services/index.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\n\n\n\n/**\n * @param  {SailsApp} sails\n * @return {Function}\n */\nmodule.exports = function (sails) {\n\n  /**\n   * `services`\n   *\n   * The definition of `services`, a core hook.\n   *\n   * @param  {SailsApp} sails\n   * @return {Dictionary}\n   */\n  return {\n\n    /**\n     * Implicit defaults which will be merged into sails.config before this hook is loaded...\n     * @type {Dictionary}\n     */\n    defaults: {\n      globals: {\n        services: true\n      }\n    },\n\n\n\n    /**\n     * Before any hooks have begun loading...\n     * (called automatically by Sails core)\n     */\n    configure: function() {\n      // This initial setup of `sails.services` was included here as an experimental\n      // feature so that these modules would be accessible for other hooks.  This will be\n      // deprecated in Sails v1.0 in favor of (likely) the ability for hook authors to register\n      // or unregister services programatically.  In addition, services will no longer be exposed\n      // on the `sails` app instance.\n      //\n      // Expose an empty dictionary for `sails.services` so that it is\n      // guaranteed to exist.\n      sails.services = {};\n    },\n\n\n\n    /**\n     * Before THIS hook has begun loading...\n     * (called automatically by Sails core)\n     */\n    loadModules: function(cb) {\n\n      // In future versions of Sails, the empty registry of services can be initialized here:\n      // sails.services = {};\n\n      sails.log.silly('Loading app services...');\n\n      // Load service modules using the module loader\n      // (by default, this loads services from files in `api/services/*`)\n      sails.modules.loadServices(function(err, modules) {\n        if (err) {\n          sails.log.error('Error occurred loading modules ::');\n          sails.log.error(err);\n          return cb(err);\n        }\n\n        // Expose services on `sails.services` to provide access even when globals are disabled.\n        _.extend(sails.services, modules);\n\n        // Expose globals (if enabled)\n        if (sails.config.globals.services) {\n          _.each(sails.services, function(service, identity) {\n            var globalId = service.globalId || service.identity || identity;\n            global[globalId] = service;\n          });\n        }\n\n        // Relevant modules have finished loading.\n        return cb();\n      });\n    }// </loadModules>\n  };//</hook definition>\n};\n"
  },
  {
    "path": "lib/hooks/session/README.md",
    "content": "## Session Hook\n\nAt configuration-time, this hook loads verifies valid configuration of the connect session store (configurable in `sails.config.session`),\nAt lift-time, it instantiates the session store and makes it accesible via `sails.session`.\n\nIt includes methods for:\n  + attaching a connect session to a socket.io connection\n  + generating new sessions\n  + getting and setting the session\n\n\n\n##### Contributing to this hook\nIt would be great to see a generic connect session adapter with support for the existing 'connections' in sails (ie. waterline adapters).  See [connect-waterline](https://www.npmjs.com/package/connect-waterline).\n"
  },
  {
    "path": "lib/hooks/session/index.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar path = require('path');\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\nvar flaverr = require('flaverr');\nvar Redis = require('machinepack-redis');\n\n// (this dependency is just for creating new cookies)\nvar uid = require('uid-safe');\n\n// (these two dependencies are only here for sails.session.parseSessionIdFromCookie(),\n//  which is only here to enable socket lifecycle callbacks)\nvar parseCookie = require('cookie').parse;\nvar stringifyCookie = require('cookie').serialize;\nvar unsignCookie = require('cookie-signature').unsign;\nvar signCookie = require('cookie-signature').sign;\nvar pathToRegexp = require('path-to-regexp');\n\nmodule.exports = function(app) {\n\n  // `session` hook definition\n  var SessionHook = {\n\n\n    defaults: {\n      session: {\n        adapter: 'memory',\n        name: 'sails.sid',\n        // By default, disable session for requests to paths that look like static assets.\n        isSessionDisabled: function (req){\n          return !!req.path.match(req._sails.LOOKS_LIKE_ASSET_RX);\n        },\n        // Added overrideable function for constructing the session store based on https://github.com/balderdashy/sails/pull/7172\n        // • `sessionConfig` is what Sails has loaded for sails.config.session\n        // • `configuredSessionAdapter` is the package like what you get from doing require('connect-mongo')\n        // • `expressSessionFromSailsCore` is the version of express-session used by Sails core session hook, provided for your convenience\n        handleConstructingSessionStore: (sessionConfig, configuredSessionAdapter, expressSessionFromSailsCore) => {\n          let CustomStore = configuredSessionAdapter(expressSessionFromSailsCore);\n          return new CustomStore(sessionConfig);\n        }\n      }\n    },\n\n\n    /**\n     * Normalize and validate configuration for this hook.\n     * Then fold any modifications back into `sails.config`\n     */\n    configure: function() {\n\n      // Validate config\n      // Ensure that session config is at least an object of some kind.\n      if (app.config.session) {\n        if (!_.isObject(app.config.session)) {\n          throw flaverr({ name: 'userError', code: 'E_INVALID_SESSION_CONFIG' }, new Error('Invalid custom session store configuration!\\n' +\n            '\\n' +\n            'Basic usage ::\\n' +\n            '{ session: { adapter: \"memory\", secret: \"someVerySecureString\", /* ...if applicable: host, port, etc... */ } }' +\n            '\\n\\nCustom usage ::\\n' +\n            '{ session: { store: { /* some custom connect session store instance */ }, secret: \"someVerySecureString\", /* ...custom settings.... */ } }'\n          ));\n        }\n\n      }\n\n      if (!app.config.session.secret && process.env.NODE_ENV !== 'production') {\n        app.config.session.secret = 'extremely-secure-keyboard-cat';\n        app.log.debug('Warning: no session secret was set!  In development mode, we\\'ll set this for you,');\n        app.log.debug('but session secret must be manually specified in production.');\n        app.log.debug('To set up a session secret, add or update it in `config/session.js`:');\n        app.log.debug('module.exports.session = { secret: \\'extremely-secure-keyboard-cat\\' }');\n        app.log.debug();\n        app.log.debug('(Or if you don\\'t need sessions enabled, disable the hook.)');\n        app.log.debug();\n        app.log.debug('For more help:');\n        app.log.debug(' • https://sailsjs.com/config/session');\n        app.log.debug(' • https://sailsjs.com/support');\n        app.log.debug();\n      }\n\n      // Throw if the old `routesDisabled` is used instad of `isSessionDisabled`.\n      if (app.config.session.routesDisabled) {\n        throw flaverr({ name: 'userError', code: 'E_INVALID_SESSION_CONFIG' }, new Error(\n                        '\\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'+\n                        'The `sails.config.session.routesDisabled` property is no longer supported in Sails 1.0.\\n'+\n                        'Instead, specify a `sails.config.session.isSessionDisabled` function which takes the\\n'+\n                        'request object as an argument and returns `true` if the session should be disabled,\\n'+\n                        'and `false` otherwise.\\n'+\n                        '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'));\n      }\n\n      // Throw if `isSessionDisabled` defined, but is not a function.\n      if (!_.isUndefined(app.config.session.isSessionDisabled) && !_.isFunction(app.config.session.isSessionDisabled)) {\n        throw flaverr({ name: 'userError', code: 'E_INVALID_SESSION_CONFIG' }, new Error(\n                        '\\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'+\n                        'The `sails.config.session.isSessionDisabled` property, if specified, must be a function.\\n'+\n                        'Instead, got: `' + util.inspect(app.config.session.isSessionDisabled) + '`.\\n'+\n                        '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'));\n      }\n\n      // Throw if `cookie.secure` is defined, but is not a boolean.\n      if (!_.isUndefined(_.get(app.config.session, 'cookie.secure')) && !_.isBoolean(app.config.session.cookie.secure)) {\n        throw flaverr({ name: 'userError', code: 'E_SESSION_BAD_COOKIE_SECURE' }, new Error(\n                        '\\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'+\n                        'The `sails.config.session.cookie.secure` property, if specified, must be a boolean.\\n'+\n                        'Instead, got: `' + util.inspect(app.config.session.cookie.secure) + '` (which is type `' + (typeof app.config.session.cookie.secure) + '`).\\n'+\n                        '-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\\n'));\n      }\n\n      // Throw if `cookie.secure` is defined, but is not a boolean.\n      if (process.env.NODE_ENV === 'production') {\n        if (_.get(app.config.session, 'cookie.secure') !== true) {\n          app.log.debug('Warning: since `sails.config.session.cookie.secure` is not set to `true`, the session');\n          app.log.debug('cookie will be sent over non-TLS connections (i.e. with insecure http:// requests).');\n          app.log.debug('To enable secure cookies, set `sails.config.session.cookie.secure` to `true`.');\n          app.log.debug();\n          app.log.debug('If your app is behind a proxy or load balancer (e.g. on a PaaS like Heroku), you');\n          app.log.debug('may also need to set `sails.config.http.trustProxy` to `true`.');\n          app.log.debug();\n          app.log.debug('For more help:');\n          app.log.debug(' • https://sailsjs.com/config/session#?the-secure-flag');\n          app.log.debug(' • https://sailsjs.com/config/session#?do-i-need-an-ssl-certificate');\n          app.log.debug(' • https://sailsjs.com/config/sails-config-http#?properties');\n          app.log.debug(' • https://sailsjs.com/support');\n          app.log.debug();\n        }\n\n        else {\n          app.log.debug('Please note: since `sails.config.session.cookie.secure` is set to `true`, the session cookie ');\n          app.log.debug('will _only_ be sent over TLS connections (i.e. secure https:// requests).');\n          app.log.debug('Requests made via http:// will not include a session cookie!');\n          app.log.debug();\n          if (app.config.http.trustProxy === false) {\n            app.log.debug('Also, note that since `sails.config.http.trustProxy` is set to `false`, secure cookies');\n            app.log.debug('(and potentially all sessions+login over \"https://\") may not work if your app is hosted');\n            app.log.debug('behind a proxy or load balancer -- for example, on a PaaS like Heroku or EBS.');\n            app.log.debug();\n          }\n          app.log.debug('For more help:');\n          app.log.debug(' • https://sailsjs.com/config/session#?the-secure-flag');\n          app.log.debug(' • https://sailsjs.com/config/session#?do-i-need-an-ssl-certificate');\n          app.log.debug(' • https://sailsjs.com/config/sails-config-http#?properties');\n          app.log.debug(' • https://sailsjs.com/support');\n          app.log.debug();\n        }\n      }\n\n\n      // If session secret is undefined, set a secure, one-time use secret\n      if (!app.config.session || !app.config.session.secret) {\n\n        app.log.verbose('Session secret not defined...');\n\n        if (process.env.NODE_ENV === 'production') {\n          throw new Error(\n            'Session secret should be manually specified in production!\\n'+\n            'To set up a session secret, add or update it in `config/session.js`:\\n'+\n            'module.exports.session = { secret: \\'extremely-secure-keyboard-cat\\' }\\n'+\n            '\\n'+\n            '(Or if you don\\'t need sessions enabled, disable the hook.)\\n'+\n            '\\n'+\n            'For more help:\\n'+\n            ' • https://sailsjs.com/config/session\\n'+\n            ' • https://sailsjs.com/support'\n          );\n        }\n\n      }\n      //‡\n      // If secret _is_ defined, make sure it's a string\n      else if (app.config.session.secret && !_.isString(app.config.session.secret)) {\n        throw flaverr({ name: 'userError', code: 'E_INVALID_SESSION_CONFIG' }, new Error('If provided, sails.config.session.secret should be a string.'));\n      }\n\n\n      // Validate `routesDisabled`, if specified.\n      if (app.config.session.routesDisabled && !_.isArray(app.config.session.routesDisabled)) {\n        throw flaverr({ name: 'userError', code: 'E_INVALID_SESSION_CONFIG' }, new Error('Invalid `sails.config.session.routesDisabled` configuration!\\n' +\n          '(must be an array of route address strings)'\n        ));\n      }\n\n\n      // Backwards-compatibility / shorthand notation\n      // (allow mongo or redis session stores to be specified directly)\n      if (app.config.session.adapter === 'redis') {\n        app.config.session.adapter = 'connect-redis';\n      }\n      else if (app.config.session.adapter === 'mongo') {\n        app.config.session.adapter = 'connect-mongo';\n      }\n\n      // If `key` is provided, transform it to `name` and log a warning.\n      if (_.isString(app.config.session.key)) {\n        app.config.session.name = app.config.session.key;\n        app.log.debug('The `sails.config.session.key` setting is deprecated; please use `sails.config.session.name` instead.\\n');\n      }\n\n      // If a URL was provided, make sure it has no trailing slash.\n      if (_.isString(app.config.session.url)) {\n        app.config.session.url = app.config.session.url.replace(/\\/$/,'');\n      }\n\n    },\n\n    /**\n     * initialize() is run when the session hook is loaded.\n     *\n     * (Its primary responsibility is to create a session store instance\n     *  and keep it around.)\n     *\n     * @api private\n     */\n    initialize: function(cb) {\n\n      // Build `sails.hooks.session.routesDisabled`.\n      // (only salient if `sails.config.session.routesDisabled` was specified)\n      try {\n        // Regex to check if the route is...a regex.\n        var regExRoute = /^r\\|(.*)\\|(.*)$/;\n\n        app.hooks.session.routesDisabled = _.reduce(app.config.session.routesDisabled || [], function (memo, routeAddressStr){\n\n          // Validate and parse route address.\n          if (!_.isString(routeAddressStr)){\n            throw new Error('Cannot parse route address (`'+routeAddressStr+'`). Must be a string.');\n          }\n          var addrPieces = routeAddressStr.split(/\\s/);\n\n          var method;\n          var urlPattern;\n          if (addrPieces.length === 1) {\n            method = '';\n            urlPattern = addrPieces[0];\n          }\n          else if (addrPieces.length === 2) {\n            method = addrPieces[0];\n            urlPattern = addrPieces[1];\n          }\n          else {\n            throw new Error('Cannot parse route address (`'+routeAddressStr+'`). When split on whitespace, there are either too many or too few pieces (`'+addrPieces.length+'`).');\n          }\n\n          // Generate a regular expression from the URL pattern string.\n          var urlPatternRegExp;\n\n\n          // Perform the check\n          var matches = urlPattern.match(regExRoute);\n\n          // If it *is* a regex, create a RegExp object that Express can bind,\n          // pull out the params, and wrap the handler in regexRouteWrapper\n          if (matches) {\n            urlPatternRegExp = new RegExp(matches[1]);\n          } else {\n            urlPatternRegExp = pathToRegexp(urlPattern, []);\n          }\n\n          memo.push({\n            method: method,\n            urlPatternRegExp: urlPatternRegExp\n          });\n          return memo;\n        }, []);//</_.reduce()>\n\n      } catch (e) {\n        return cb(\n          new Error('Failed to parse one of the route addresses provided in `sails.config.session.routesDisabled`.\\n'+\n          'If specified, this config must be an array of normal route address strings.\\n'+\n          'Error details:'+e.stack)\n        );\n      }\n\n\n      //  ┌─┐┌─┐┌┬┐  ┬ ┬┌─┐  ┌─┐┬─┐┌─┐┬  ┬┬┌┬┐┌─┐┌┬┐  ┌─┐┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐\n      //  └─┐├┤  │   │ │├─┘  ├─┘├┬┘│ │└┐┌┘│ ││├┤  ││  ├─┤ ││├─┤├─┘ │ ├┤ ├┬┘\n      //  └─┘└─┘ ┴   └─┘┴    ┴  ┴└─└─┘ └┘ ┴─┴┘└─┘─┴┘  ┴ ┴─┴┘┴ ┴┴   ┴ └─┘┴└─\n      (function setupAdapter(proceed) {\n\n        // If no adapter config was provided, skip down to creating the session middleware.\n        if (!_.isObject(app.config.session) || !app.config.session.adapter) { return proceed(); }\n\n        // 'memory' is a special case\n        if (app.config.session.adapter === 'memory') {\n          var MemoryStore = require('express-session').MemoryStore;\n          app.config.session.store = new MemoryStore();\n          return proceed();\n        }//‡\n        // For all other adapters, we'll try to require the module and do some setup.\n        else {\n\n          //  ┌─┐┌─┐┌┬┐  ┬ ┬┌─┐  ┬─┐┌─┐┌┬┐┬┌─┐  ┌─┐┌┬┐┌─┐┌─┐┌┬┐┌─┐┬─┐\n          //  └─┐├┤  │   │ │├─┘  ├┬┘├┤  │││└─┐  ├─┤ ││├─┤├─┘ │ ├┤ ├┬┘\n          //  └─┘└─┘ ┴   └─┘┴    ┴└─└─┘─┴┘┴└─┘  ┴ ┴─┴┘┴ ┴┴   ┴ └─┘┴└─\n          (function maybeConnectToRedis(proceed) {\n\n            // If the adapter isn't set to `connect-redis`/`@sailshq/connect-redis`,\n            // or an existing Redis client is being provided, skip this part.\n            if ((app.config.session.adapter !== 'connect-redis' && app.config.session.adapter !== '@sailshq/connect-redis') || app.config.session.client) {\n              return proceed();\n            }//•\n\n            // If a connection URL is provided, use that, otherwise construct one from the pieces\n            // provided in the session config.\n            var url = app.config.session.url || Redis.createConnectionUrl(_.pick(app.config.session, ['host', 'port', 'pass', 'db'])).now();\n\n            // Create a Redis connection manager.\n            Redis.createManager({\n              connectionString: url,\n              meta: _.omit(app.config.session, ['host', 'port', 'pass', 'db', 'url', 'adapter', 'prefix']),\n              // Handle failures on the connection.\n              onUnexpectedFailure: function(err) {\n                // If Sails is already on the way out, ignore the Redis issue.\n                if (app._exiting) {\n                  return;\n                }\n\n                // Log the error from Redis in verbose mode.\n                app.log.verbose('Redis connection manager failed unexpectedly.  Details:', err);\n\n                // If the Redis client disconnected, say something and run any custom logic\n                // that was provided for this occasion.\n                if (err.failureType === 'end') {\n                  if (_.isFunction(app.config.session.onRedisDisconnect)) {\n                    app.config.session.onRedisDisconnect();\n                  } else {\n                    app.log.error('Redis session server went offline...');\n                  }\n                  // If a disconnected client comes back, say something and run any custom logic\n                  // that was provided for this occasion.\n                  err.connection.once('ready', function() {\n                    if (_.isFunction(app.config.session.onRedisReconnect)) {\n                      app.config.session.onRedisReconnect();\n                    } else {\n                      app.log.error('Redis session server came back online...');\n                    }\n                  });\n                }\n              }\n            }).exec(function(err, createManagerResult) {\n              if (err) { return proceed(err); }\n\n              // Use the manager to create a new Redis connection.\n              Redis.getConnection({\n                manager: createManagerResult.manager\n              }).switch({\n                error: function(err) { return proceed(err); },\n                failed: function(report) { return proceed(report.error); },\n                success: function(result) {\n                  // Save the connected client into the session config so that it can be used\n                  // by the connect-redis module.\n                  app.config.session.client = result.connection;\n                  return proceed();\n                }\n              });\n\n            });\n          })(function afterMaybeConnectToRedis(err) {\n            if (err) { return proceed(err); }\n\n            // This local variable is used to hold the connect session adapter.\n            // (we determine what it is below)\n            var SessionAdapter;\n\n            // If `sails.config.session.adapter` is a string, attempt to require the\n            // module identified by the string.\n            if (_.isString(app.config.session.adapter)) {\n\n              try {\n                SessionAdapter = require(path.resolve(app.config.appPath, 'node_modules', app.config.session.adapter));\n              }\n              catch(rawRequireErr) {\n\n                // If an error occurred while attempting to require() the adapter, include\n                // some (hopefully) helpful instructions on installing the adapter.\n                return proceed(new Error(\n                  // 'Could not require `' + app.config.session.adapter + '` (a session adapter).\\n'+\n                  'Do you have `' + app.config.session.adapter + '` installed locally?\\n'+\n                  'If not, try running the following command in your app\\'s root directory:\\n'+\n                  'npm install ' + app.config.session.adapter + '\\n'+\n                  '(Note: Make sure to install a Connect session adapter that is compatible with this version of Sails.)\\n'+\n                  '\\n'+\n                  'For debugging purposes, here is the error from attempting to run `require(\\''+app.config.session.adapter+'\\')`:\\n'+\n                  '---\\n'+\n                  (function _getAppropriateMessageFromRawRequireErr(){\n                    if (_.isError(rawRequireErr)) { return rawRequireErr.stack; }\n                    else if (_.isString(rawRequireErr)) { return rawRequireErr; }\n                    else { return util.inspect(rawRequireErr, { depth: null }); }\n                  })()+'\\n'+\n                  '---\\n'\n                ));\n\n              }//</catch :: require>\n\n            }//</if .session.adapter is a string>\n            //‡\n            // Otherwise if it's an object (including a function!), set SessionAdapter to that value.\n            else if (_.isObject(app.config.session.adapter)) {\n              SessionAdapter = app.config.session.adapter;\n            }\n            // Otherwise bail, because sails.config.session.adapter is invalid.\n            else {\n              return proceed(new Error('If configured, `sails.config.session.adapter` should be a reference to an Express session adapter!  Instead got `' + util.inspect(app.config.session.adapter)));\n            }\n\n            // Okay, so now we have an adapter that we can call to create an\n            // Express session store.  So we'll attempt to create the store\n            // by passing the `express-session` module and adapter into the\n            // handleConstructingSessionStore function\n            try {\n              app.config.session.store = app.config.session.handleConstructingSessionStore(app.config.session, SessionAdapter, require('express-session'));\n            }\n            catch (rawSessionStoreCreationErr) {\n\n              // Failed attempting to initialize adapter; output a message w/ error info\n              return proceed(new Error(\n                'Encountered error attempting to instantiate a session store using the installed version of `' + app.config.session.adapter + '` (a session adapter), or with your custom handleConstructingSessionStore function.\\n'+\n                'Raw error from the session adapter:\\n'+\n                '---\\n'+\n                (function _getAppropriateMessageFromRawSessionAdapterErr(){\n                  if (_.isError(rawSessionStoreCreationErr)) {\n                    // FUTURE: negotiate faw error and give better error msg depending on code\n                    // (not sure if things are quite ready in the express-session adapter spec yet to make this possible)\n                    return rawSessionStoreCreationErr.stack;\n                  }\n                  else if (_.isString(rawSessionStoreCreationErr)) { return rawSessionStoreCreationErr; }\n                  else { return util.inspect(rawSessionStoreCreationErr, { depth: null }); }\n                })()+'\\n'+\n                '---\\n'+\n                '\\n'\n              ));\n\n            } //</catch :: failed to instantiate session adapter by passing in express-session>\n\n            return proceed();\n          });//</ self-calling function>\n\n        }//</else (if we're using a custom store and NOT the memory store)>\n\n      })(function afterSettingUpAdapter (err) {//~∞%°\n        if (err) { return cb(err); }\n\n        // Expose hook as `sails.session`\n        app.session = SessionHook;\n\n        // Build configuration the raw session middleware, using the\n        // session config built above (including the adapter and store)\n        // and adding a couple of defaults for extra options like `resave`.\n        var opts = _.extend({\n          resave: true,// FUTURE: set `resave: false` (see https://github.com/expressjs/session/tree/8e56128d8ba014ab586521247977b0d4e67340f9#resave)\n          saveUninitialized: true// FUTURE: set `saveUninitialized: false` (see https://github.com/expressjs/session/tree/8e56128d8ba014ab586521247977b0d4e67340f9#saveuninitialized)\n        }, app.config.session);\n\n        // Get a raw express-session middleware function using the options\n        // we just built.\n        var rawSessionMiddleware = require('express-session')(opts);\n\n        // Now wrap up the raw middleware in our own req/res/next function, and expose\n        // it privately so it can be used by the private Sails router and the HTTP session middleware.\n        app._privateSessionMiddleware = function(req, res, next) {\n          // If an `isSessionDisabled` function is configured, run it against the current request\n          // and if it returns `true`, skip the session middleware entirely.\n          if(app.config.session.isSessionDisabled && app.config.session.isSessionDisabled(req)) {\n            return next();\n          }\n\n          // Run the express session middleware that actually sets up the session.\n          return rawSessionMiddleware(req, res, next);\n\n        };\n\n        return cb();\n\n      }); //</self-calling function that sets up adapter)>\n\n    }, // </initialize>\n\n\n    /**\n     * Generate a cookie to represent a new session.\n     *\n     * @return {String}\n     * @api private\n     */\n\n    generateNewSidCookie: function (){\n\n      var sid = uid.sync(24);\n      var signedSid = 's:' + signCookie(sid, app.config.session.secret);\n      var cookie = stringifyCookie(app.config.session.name, signedSid, {});\n      return cookie;\n    },\n\n\n\n    /**\n     * Parse and unsign (i.e. decrypt) the provided cookie to get the session id.\n     *\n     * (adapted from code in the `express-session`)\n     *\n     * @param  {String} cookie\n     * @return {String}                [sessionId]\n     *\n     * @throws {Error} If cookie cannot be parsed or unsigned\n     */\n    parseSessionIdFromCookie: function (cookie){\n\n      // e.g. \"lolcatparty\"\n      var sessionSecret = app.config.session.secret;\n\n      // Parse cookie\n      var parsedSidCookie = parseCookie(cookie)[app.config.session.name];\n\n      if (typeof parsedSidCookie !== 'string') {\n        throw flaverr({ status: 401, code: 'E_SESSION_PARSE_COOKIE' }, new Error('No sid cookie exists'));\n      }//-•\n\n      if (parsedSidCookie.substr(0, 2) !== 's:') {\n        throw flaverr({ status: 401, code: 'E_SESSION_PARSE_COOKIE' }, new Error('Cookie unsigned'));\n      }//-•\n\n      // Unsign cookie\n      var sessionId = unsignCookie(parsedSidCookie.slice(2), sessionSecret);\n\n      if (sessionId === false) {\n        throw flaverr({ status: 401, code: 'E_SESSION_PARSE_COOKIE' }, new Error('Cookie signature invalid'));\n      }//-•\n\n      return sessionId;\n    },\n\n\n    /**\n     * @param {String} sessionId\n     * @param {Function} cb\n     *\n     * @api private\n     */\n    get: function(sessionId, cb) {\n      if (!_.isFunction(cb)) {\n        throw new Error('Invalid usage :: `sails.hooks.session.get(sessionId, cb)`');\n      }\n\n      app.config.session.store.get(sessionId, function (err, session) {\n        if (err) { return cb(err); }\n\n        if (!session) {\n          return cb(flaverr('E_SESSION', new Error('Session could not be loaded.')));\n        }\n\n        return cb(null, session);\n\n      });//</store.get>\n\n    },\n\n    /**\n     * @param {String} sessionId\n     * @param {} data\n     * @param {Function} cb\n     *\n     * @api private\n     */\n    set: function(sessionId, data, cb) {\n      if (!_.isFunction(cb)) {\n        throw new Error('Invalid usage :: `sails.hooks.session.set(sessionId, data, cb)`');\n      }\n\n      // Attempt to persist data (upsert) to the session entry with the given `sessionId`.\n      app.config.session.store.set(sessionId, data, function (err) {\n        if (err) { return cb(err); }\n\n        // Now look up the session so it can be sent back in its entirety.\n        app.config.session.store.get(sessionId, function (err, session) {\n          if (err) { return cb(err); }\n\n          if (!session) {\n            return cb(flaverr('E_SESSION', new Error('Session (`'+sessionId+'`) could not be located after saving.')));\n          }\n\n          return cb(null, session);\n\n        });//</store.get>\n      });//</store.set>\n\n    }\n\n  };\n\n  return SessionHook;\n\n};\n"
  },
  {
    "path": "lib/hooks/userconfig/README.md",
    "content": "## Userconfig Hook\n\nThis hook loads app-level user configuration using the moduleloader hook (from the config directory located at `sails.config.paths.config`) It is always loaded first.\n\n##### Contributing to this hook\nNot a good place to jump in right now.  Please tweet @mikermcneil before working on this part!\n"
  },
  {
    "path": "lib/hooks/userconfig/index.js",
    "content": "module.exports = function(sails) {\n\n\n  /**\n   * Module dependencies\n   */\n\n  var _ = require('@sailshq/lodash');\n  var mergeDictionaries = require('merge-dictionaries');\n\n\n  /**\n   * Userconfig\n   *\n   * Load configuration files.\n   */\n  return {\n\n\n    // Default configuration\n    defaults: {},\n\n\n    /**\n     * Fetch relevant modules, exposing them on `sails` subglobal if necessary,\n     */\n    loadModules: function (cb) {\n\n      sails.log.silly('Loading app config...');\n\n      // Grab reference to mapped overrides\n      // - - - - - - - - - - - - - - - - - - - - - - - - - - -\n      // FUTURE: Optimization: do we need this _.clone()?\n      // - - - - - - - - - - - - - - - - - - - - - - - - - - -\n      var overrides = _.clone(sails.config);\n\n\n      // If appPath not specified yet, use process.cwd()\n      // (the directory where this Sails process is being initiated from)\n      if ( ! overrides.appPath ) {\n        sails.config.appPath = process.cwd();\n      }\n\n      // Load config dictionary from app modules\n      sails.modules.loadUserConfig(function loadedAppConfigModules (err, userConfig) {\n        if (err) { return cb(err); }\n\n        // Finally, extend user config with overrides\n        var config = {};\n\n        // Merge the overrides into the loaded user config.\n        config = mergeDictionaries(userConfig, overrides);\n\n        // Ensure final configuration object is valid\n        // (in case moduleloader fails miserably)\n        config = _.isObject(config) ? config : (sails.config || {});\n\n        // Save final config into sails.config\n        sails.config = config;\n\n        cb();\n      });\n    }\n  };\n};\n"
  },
  {
    "path": "lib/hooks/userhooks/README.md",
    "content": "## Userhooks Hook\n\nThis hook loads hooks from the user hooks directory, configurable in `sails.config.paths.hooks` (defaults to `api/hooks`) of a sails app.  If the directory doesn't exist, this hook is a no-op.\n\n##### Contributing to this hook\nNot a good place to jump in right now.  Please tweet @mikermcneil before working on this part!\n"
  },
  {
    "path": "lib/hooks/userhooks/index.js",
    "content": "var _ = require('@sailshq/lodash');\n\nmodule.exports = function(sails) {\n\n  /**\n   * `userhooks`\n   *\n   * Sails hook for loading user plugins (hooks)\n   */\n  return {\n\n    defaults: { },\n\n    initialize: function(cb) {\n\n      if ( !sails.config.hooks.moduleloader ) {\n        return cb('Cannot load user hooks without `moduleloader` hook enabled!');\n      }\n\n      sails.log.silly('Loading user hooks...');\n\n      // Load user hook definitions\n      sails.modules.loadUserHooks(function hookDefinitionsLoaded(err, hooks) {\n        if (err) { return cb(err); }\n\n        // Ensure hooks is valid\n        hooks = _.isObject(hooks) ? hooks : {};\n\n        // If `sails.config.loadHooks` is set, then only include user hooks if\n        // they are explicitly listed therein.\n        // > Note that `sails.config.hooks` is taken care of as part of the\n        // > implementation of `sails.modules.loadUserHooks()`.\n        if (sails.config.loadHooks) {\n          sails.log.silly('Since `sails.config.userHooks` was specified, checking user hooks against it to make sure they should actually be loaded...');\n          _.each(hooks, function(def, hookName) {\n            if (!_.contains(sails.config.loadHooks, hookName)) {\n              delete hooks[hookName];\n              sails.log.verbose('Skipped loading \"'+hookName+'\" hook, because `sails.config.loadHooks` was specified but did not explicitly include this hook\\'s name.');\n            }\n          });\n        }//ﬁ\n\n        // Add the user hooks to the list of hooks to load\n        // (excluding any that were omitted)\n        _.extend(sails.hooks, hooks);\n\n        return cb();\n\n      });\n    }\n  };\n};\n"
  },
  {
    "path": "lib/hooks/views/configure.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar flaverr = require('flaverr');\nvar _ = require('@sailshq/lodash');\n\n\n/**\n * Marshal relevant parts of sails global configuration,\n * issue deprecation notices, etc.\n *\n * @param  {Sails} sails\n */\nmodule.exports = function configure ( sails ) {\n\n  if (sails.config.views.engine) {\n    sails.log.debug('The `config.views.engine` config has been deprecated.');\n    sails.log.debug('In Sails 1.x, use `config.views.extension` to choose your view');\n    sails.log.debug('extension (defaults to \".ejs\"), and use `config.views.getRenderFn`');\n    sails.log.debug('to configure your template engine or leave it undefined to use');\n    sails.log.debug('the built-in EJS template support.\\n');\n    sails.config.views.extension = sails.config.views.engine.ext || 'ejs';\n    delete sails.config.views.engine;\n  }\n\n\n  // Make sure the extension is valid.\n  if (sails.config.views.extension === '' || (!_.isString(sails.config.views.extension) && sails.config.views.extension !== false)) {\n    throw flaverr({ name: 'userError', code: 'E_INVALID_VIEW_CONFIG' }, new Error('`sails.config.views.extension` must either be a string or `false`.'));\n  }\n\n  // Let user know that a leading . is not required in the viewEngine option and then fix it\n  if (sails.config.views.extension[0] === '.') {\n    sails.log.warn('A leading `.` is not required in the config.views.extension option.  Removing it for you...');\n    sails.config.views.extension = sails.config.views.extension.substr(1);\n  }\n\n  // Make sure the `getRenderFn` is valid, if provided.\n  if (!_.isUndefined(sails.config.views.getRenderFn) && !_.isFunction(sails.config.views.getRenderFn)) {\n    throw flaverr({ name: 'userError', code: 'E_INVALID_VIEW_CONFIG' }, new Error('`sails.config.views.getRenderFn`, if provided, must be a function (got ' + util.inspect(sails.config.views.getRenderFn) + ')'));\n  }\n\n  else if (sails.config.views.getRenderFn) {\n    var renderFn = sails.config.views.getRenderFn();\n    if (!_.isFunction(renderFn)) {\n      throw flaverr({ name: 'userError', code: 'E_INVALID_VIEW_CONFIG' }, new Error('`sails.config.views.getRenderFn` returned an invalid value. (expected a function, but got: ' + util.inspect(renderFn) + ')'));\n    }\n    sails.hooks.views._renderFn = renderFn;\n\n    if (sails.config.views.layout) {\n      sails.log.error('Ignoring `sails.config.views.layout`...');\n      sails.log.error('Sails\\' built-in layout support only works with the default EJS view engine.');\n      sails.log.error('You\\'re using a custom view engine, so you\\'ll need to implement layouts on your own!');\n    }\n\n  }\n\n  else {\n    // Custom layout location\n    // (if string specified, it's used as the relative path from the views folder)\n    // (if not string, but truthy, relative path from views folder defaults to ./layout.*)\n    // (if falsy, don't use layout)\n    if ( !_.isString(sails.config.views.layout) && sails.config.views.layout ) {\n      sails.config.views.layout = 'layout.' + sails.config.views.extension;\n    }\n  }\n\n};\n"
  },
  {
    "path": "lib/hooks/views/default-view-rendering-fn.js",
    "content": "/**\n * Module dependencies\n */\n\nvar path = require('path');\nvar fs = require('fs');\nvar _ = require('@sailshq/lodash');\nvar ejs = require('ejs');\n\nvar exists = fs.existsSync || path.existsSync;\nvar resolve = path.resolve;\nvar extname = path.extname;\nvar dirname = path.dirname;\nvar join = path.join;\nvar basename = path.basename;\n\n\n/**\n * Implement EJS layouts and partials (a la Express 2).\n *\n * This is a slightly modified (for EJS >= 2.3.4) version of the defunct ejs-locals package:\n * ------------------------------------------------------------------------------------------\n * https://github.com/randometc/ejs-locals\n * (The MIT License)\n *\n * Copyright (c) 2012 Robert Sköld <robert@publicclass.se> Copyright (c) 2012 Tom Carden <tom@tom-carden.co.uk>\n *\n * 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:\n * ------------------------------------------------------------------------------------------\n * For further explanation, see:\n *  => http://sailsjs.com/documentation/concepts/views/layouts#?why-do-layouts-only-work-for-ejs\n */\n\nmodule.exports = function renderFile(file, options, fn){\n\n  // Express used to set options.locals for us, but now we do it ourselves\n  // (EJS does some __proto__ magic to expose these funcs/values in the template)\n  if (!options.locals) {\n    options.locals = {};\n  }\n\n  // Make sure the absolute path to view is always available.\n  // (needed for partials)\n  options.filename = file;\n\n  if (!options.locals.blocks) {\n    // one set of blocks no matter how often we recurse\n    var blocks = { scripts: new Block(), stylesheets: new Block() };\n    options.locals.blocks = blocks;\n    options.locals.scripts = blocks.scripts;\n    options.locals.stylesheets = blocks.stylesheets;\n    options.locals.block = block.bind(blocks);\n    options.locals.stylesheet = stylesheet.bind(blocks.stylesheets);\n    options.locals.script = script.bind(blocks.scripts);\n  }\n  // override locals for layout/partial bound to current options\n  options.locals.layout  = layout.bind(options);\n  options.locals.partial = partial.bind(options);\n\n  ejs.renderFile(file, _.extend(options, options.locals), options, function(err, html) {\n\n    if (err) {\n      return fn(err,html);\n    }\n\n    var layout = options.locals._layoutFile;\n\n    // for backward-compatibility, allow options to\n    // set a default layout file for the view or the app\n    // (NB:- not called `layout` any more so it doesn't\n    // conflict with the layout() function)\n    if (layout === undefined) {\n      layout = options._layoutFile;\n    }\n\n    if (layout) {\n\n      // use default extension\n      var engine = options.settings['view engine'] || 'ejs';\n      var desiredExt = '.'+engine;\n\n      // apply default layout if only \"true\" was set\n      if (layout === true) {\n        layout = path.sep + 'layout' + desiredExt;\n      }\n      if (extname(layout) !== desiredExt) {\n        layout += desiredExt;\n      }\n\n      // clear to make sure we don't recurse forever (layouts can be nested)\n      delete options.locals._layoutFile;\n      delete options._layoutFile;\n      // make sure caching works inside ejs.renderFile/render\n      delete options.filename;\n\n      if (layout.length > 0 && layout[0] === path.sep) {\n        // if layout is an absolute path, find it relative to view options:\n        layout = join(options.settings.views, layout.slice(1));\n      } else {\n        // otherwise, find layout path relative to current template:\n        layout = resolve(dirname(file), layout);\n      }\n\n      // now recurse and use the current result as `body` in the layout:\n      options.locals.body = html;\n      renderFile(layout, options, fn);\n    } else {\n      // no layout, just do the default:\n      fn(null, html);\n    }\n  });\n\n};\n\n/**\n * Memory cache for resolved object names.\n */\n\nvar cache = {};\n\n/**\n * Resolve partial object name from the view path.\n * (Or, for performance, use the cached version if available.)\n *\n * Examples:\n *\n *   \"user.ejs\" becomes \"user\"\n *   \"forum thread.ejs\" becomes \"forumThread\"\n *   \"forum/thread/post.ejs\" becomes \"post\"\n *   \"blog-post.ejs\" becomes \"blogPost\"\n *\n * @return {String}\n * @api private\n */\n\nfunction resolveObjectName(view){\n  if (cache[view]) { return cache[view]; }\n\n  cache[view] =\n  view.split('/')\n  .slice(-1)[0]\n  .split('.')[0]\n  .replace(/^_/, '')\n  .replace(/[^a-zA-Z0-9 ]+/g, ' ')\n  .split(/ +/).map(function(word, i){\n    return i ? word[0].toUpperCase() + word.substr(1) : word;\n  }).join('');\n\n  return cache[view];\n}\n\n/**\n * Lookup partial path from base path of current template:\n *\n *   - partial `_<name>`\n *   - any `<name>/index`\n *   - non-layout `../<name>/index`\n *   - any `<root>/<name>`\n *   - partial `<root>/_<name>`\n *\n * Options:\n *\n *   - `cache` store the resolved path for the view, to avoid disk I/O\n *\n * @param {String} root, full base path of calling template\n * @param {String} partial, name of the partial to lookup (can be a relative path)\n * @param {Object} options, for `options.cache` behavior\n * @return {String}\n * @api private\n */\n\nfunction lookup(root, partial, options){\n\n  var engine = options.settings['view engine'] || 'ejs';\n  var desiredExt = '.' + engine;\n  var ext = extname(partial) || desiredExt;\n  var key = [ root, partial, ext ].join('-');\n\n  if (options.cache && cache[key]) { return cache[key]; }\n\n  // Make sure we use dirname in case of relative partials\n  // ex: for partial('../user') look for /path/to/root/../user.ejs\n  var dir = dirname(partial);\n  var base = basename(partial, ext);\n\n  // _ prefix takes precedence over the direct path\n  // ex: for partial('user') look for /root/_user.ejs\n  partial = resolve(root, dir,'_'+base+ext);\n  if( exists(partial) ) {\n    if (options.cache) { cache[key] = partial; }\n    return partial;\n  }\n\n  // Try the direct path\n  // ex: for partial('user') look for /root/user.ejs\n  partial = resolve(root, dir, base+ext);\n  if( exists(partial) ) {\n    if (options.cache) { cache[key] = partial; }\n    return partial;\n  }\n\n  // Try index\n  // ex: for partial('user') look for /root/user/index.ejs\n  partial = resolve(root, dir, base, 'index'+ext);\n  if( exists(partial) ) {\n    if (options.cache) { cache[key] = partial; }\n    return partial;\n  }\n\n  // FIXME:\n  // * there are other path types that Express 2.0 used to support but\n  //   the structure of the lookup involved View class methods that we\n  //   don't have access to any more\n  // * we probaly need to pass the Express app's views folder path into\n  //   this function if we want to support finding partials relative to\n  //   it as well as relative to the current view\n  // * we have no tests for finding partials that aren't relative to\n  //   the calling view\n\n  return null;\n}\n\n\n/**\n * Render `view` partial with the given `options`. Optionally a\n * callback `fn(err, str)` may be passed instead of writing to\n * the socket.\n *\n * Options:\n *\n *   - `object` Single object with name derived from the view (unless `as` is present)\n *\n *   - `as` Variable name for each `collection` value, defaults to the view name.\n *     * as: 'something' will add the `something` local variable\n *     * as: this will use the collection value as the template context\n *     * as: global will merge the collection value's properties with `locals`\n *\n *   - `collection` Array of objects, the name is derived from the view name itself.\n *     For example _video.html_ will have a object _video_ available to it.\n *\n * @param  {String} view\n * @param  {Object|Array} options, collection or object\n * @return {String}\n * @api private\n */\n\nfunction partial(view, options){\n\n  var collection;\n  var object;\n  var locals;\n  var name;\n\n  // parse options\n  if( options ){\n    // collection\n    if( options.collection ){\n      collection = options.collection;\n      delete options.collection;\n    } else if( 'length' in options ){\n      collection = options;\n      options = {};\n    }\n\n    // locals\n    if( options.locals ){\n      locals = options.locals;\n      delete options.locals;\n    }\n\n    // object\n    if( 'Object' !== options.constructor.name ){\n      object = options;\n      options = {};\n    } else if( options.object !== undefined ){\n      object = options.object;\n      delete options.object;\n    }\n  } else {\n    options = {};\n  }\n\n  // merge locals into options\n  if( locals ) {\n    options.__proto__ = locals;\n  }\n\n  // merge app locals into options\n  for(var k in this) {\n    options[k] = options[k] || this[k];\n  }\n\n  // extract object name from view\n  name = options.as || resolveObjectName(view);\n\n  // find view, relative to this filename\n  // NOTE -- the original `dirname(options.filename)` stopped working\n  // after ejs-locals was inlined, perhaps due to changed in EJS.\n  // The `options.absPathToView` value is set above in the\n  // main `renderFile` function. -SMG 10/27/2016\n  var root = dirname(options.filename);\n  var file = lookup(root, view, options);\n  var key = file + ':string';\n  if( !file ) {\n    throw new Error('Could not find partial ' + view);\n  }\n\n  // read view\n  var source = options.cache\n    ? cache[key] || (cache[key] = fs.readFileSync(file, 'utf8'))\n    : fs.readFileSync(file, 'utf8');\n\n  // Update the options.filename to point to the current partial,\n  // so that relative paths can work in calling nested partials.\n  // -SMG 10/27/2016\n  options.filename = file;\n\n  // re-bind partial for relative partial paths\n  options.partial = partial.bind(options);\n\n  // render partial\n  function render(){\n    if (object) {\n      if ('string' === typeof name) {\n        options[name] = object;\n      } else if (name === global) {\n        // wtf?\n        // merge(options, object);\n      }\n    }\n    // TODO Support other templates (but it's sync now...)\n    var html = ejs.render(source, options, options);\n    return html;\n  }\n\n  // Collection support\n  if (collection) {\n    var len = collection.length;\n    var buf = '';\n    var keys;\n    var prop;\n    var val;\n    var i;\n\n    if ('number' === typeof len || Array.isArray(collection)) {\n      options.collectionLength = len;\n      for (i = 0; i < len; ++i) {\n        val = collection[i];\n        options.firstInCollection = i === 0;\n        options.indexInCollection = i;\n        options.lastInCollection = i === len - 1;\n        object = val;\n        buf += render();\n      }\n    } else {\n      keys = Object.keys(collection);\n      len = keys.length;\n      options.collectionLength = len;\n      options.collectionKeys = keys;\n      for (i = 0; i < len; ++i) {\n        prop = keys[i];\n        val = collection[prop];\n        options.keyInCollection = prop;\n        options.firstInCollection = i === 0;\n        options.indexInCollection = i;\n        options.lastInCollection = i === len - 1;\n        object = val;\n        buf += render();\n      }\n    }\n\n    return buf;\n  } else {\n    return render();\n  }\n}\n\n/**\n * Apply the given `view` as the layout for the current template,\n * using the current options/locals. The current template will be\n * supplied to the given `view` as `body`, along with any `blocks`\n * added by child templates.\n *\n * `options` are bound  to `this` in renderFile, you just call\n * `layout('myview')`\n *\n * @param  {String} view\n * @api private\n */\nfunction layout(view){\n  this.locals._layoutFile = view;\n}\n\nfunction Block() {\n  this.html = [];\n}\n\nBlock.prototype = {\n  toString: function() {\n    return this.html.join('\\n');\n  },\n  append: function(more) {\n    this.html.push(more);\n  },\n  prepend: function(more) {\n    this.html.unshift(more);\n  },\n  replace: function(instead) {\n    this.html = [ instead ];\n  }\n};\n\n/**\n * Return the block with the given name, create it if necessary.\n * Optionally append the given html to the block.\n *\n * The returned Block can append, prepend or replace the block,\n * as well as render it when included in a parent template.\n *\n * @param  {String} name\n * @param  {String} html\n * @return {Block}\n * @api private\n */\nfunction block(name, html) {\n  // bound to the blocks object in renderFile\n  var blk = this[name];\n  if (!blk) {\n    // always create, so if we request a\n    // non-existent block we'll get a new one\n    blk = this[name] = new Block();\n  }\n  if (html) {\n    blk.append(html);\n  }\n  return blk;\n}\n\n// bound to scripts Block in renderFile\nfunction script(path, type) {\n  if (path) {\n    this.append('<script src=\"'+path+'\"'+(type ? 'type=\"'+type+'\"' : '')+'></script>');\n  }\n  return this;\n}\n\n// bound to stylesheets Block in renderFile\nfunction stylesheet(path, media) {\n  if (path) {\n    this.append('<link rel=\"stylesheet\" href=\"'+path+'\"'+(media ? 'media=\"'+media+'\"' : '')+' />');\n  }\n  return this;\n}\n\n"
  },
  {
    "path": "lib/hooks/views/escape-html-entities-deep.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar rttc = require('rttc');\n\n\n/**\n * escapeHtmlEntitiesDeep()\n *\n * Escape all HTML entities which exist as strings in the provided\n * data. If the provided data contains any dictionaries or arrays,\n * traverse them recursively. Note that the returned value will be\n * JSON-compatible, and the dehydration process will be carried out\n * using the rules established in rttc.dehydrate().\n *\n * @param {Dictionary} data\n *           The dictionary of data to escape.\n *\n * @returns {JSON} a recursively-HTML-escaped copy of the provided data.\n */\nmodule.exports = function escapeHtmlEntitiesDeep(data){\n\n  return rttc.rebuild(data, function escape(val, type){\n    // _.escape() is for escaping strings for use in HTML.\n    // (this is just the same thing that Lodash uses when you use `<%- %>` in templates)\n    if (type === 'string') {\n      return _.escape(val);\n    }\n    else {\n      return val;\n    }\n  });\n\n};\n"
  },
  {
    "path": "lib/hooks/views/get-implicit-defaults.js",
    "content": "/**\n * getImplicitDefaults()\n *\n * Get a dictionary of implicit defaults this hook would like to merge\n * into `sails.config` when Sails is loaded.\n *\n * @param  {Dictionary} existingConfig\n *         Existing configuration which has already been loaded\n *         e.g. the Sails app path, and any config overrides (programmtic, from .sailsrc, etc)\n *\n * @returns {Dictionary}\n */\n\nmodule.exports = function getImplicitDefaults (existingConfig) {\n  return {\n    views: {\n\n      // Extension for view files\n      extension: 'ejs',\n\n      // Layout is on by default, in the top level of the view directory\n      // false === don't use a layout\n      // string === path to layout (absolute or relative to views directory), without extension\n      layout: 'layout'\n    },\n\n    paths: {\n      views: existingConfig.appPath + '/views',\n      layout: existingConfig.appPath + '/views/layout.ejs'\n    }\n  };\n};\n"
  },
  {
    "path": "lib/hooks/views/html-scriptify.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\nvar rttc = require('rttc');\nvar escapeHtmlEntitiesDeep = require('./escape-html-entities-deep');\nvar minifiedUnescapeHtmlEntitiesDeepLiteStr = require('./unescape-html-entities-deep-lite.min.string.js');\n\n\n/**\n * htmlScriptify()\n *\n * Generate a string of HTML code that can be injected onto a page\n * in order to expose a JSON-serializable version of the provided\n * data to client-side JavaScript.\n *\n * @required {Dictionary} options.data\n *           The dictionary (i.e. of locals) that will be converted into an HTML snippet containing\n *           our script tag. If any of the keys cannot be coerced to be JSON-serializable (i.e.\n *           contain nothing that isn't a string, number, boolean, plain dictionary, array, or null),\n *           then they are simply excluded.  Additionally, each key is recursively parsed to snip off\n *           any circular references and otherwise ensure full JSON-serializability of ever nested key\n *           therein.  See rttc.dehydrate() for more information.\n *\n * @optional {Array} options.keys\n *           An array of strings; the names of keys in data which should be exposed to on the namespace. If left unspecified, all keys in data will be exposed.\n *\n * @optional {String} options.namespace\n *           The name of the key on the window object where data should be exposed. Defaults to 'SAILS_LOCALS'.\n *\n * @optional {Boolean} options.dontUnescapeOnClient\n *           Defaults to false. When false (by default) client-side JavaScript code will be\n *           injected around the exposed data. When the page loads, the injected client-side\n *           JavaScript runs, unescaping the values so that they are accessible to client-side\n *           JavaScript with no further transformation necessary (i.e. they are immediately\n *           usable just like they would be if they had been fetched using AJAX). If this flag\n *           is enabled, no additional client-side JavaScript code will be injected and so the\n *           exposed values will still be escaped; e.g. `window.SAILS_LOCALS.funnyFace === '&lt;o_o&gt;'`\n *           (this is useful for customizing client-side unescaping logic)\n *\n *\n * @returns {String} a string of HTML code-- specifically a script tag containing the exposed data.\n *\n * --\n * Example usage:  (`sails console`)\n * sails> sails.hooks.views.htmlScriptify({data: {n: 'stuff<script>'}, dontUnescapeOnClient: true})\n */\nmodule.exports = function htmlScriptify(options){\n\n  //  ██╗   ██╗ █████╗ ██╗     ██╗██████╗  █████╗ ████████╗███████╗    ██╗   ██╗███████╗ █████╗  ██████╗ ███████╗\n  //  ██║   ██║██╔══██╗██║     ██║██╔══██╗██╔══██╗╚══██╔══╝██╔════╝    ██║   ██║██╔════╝██╔══██╗██╔════╝ ██╔════╝\n  //  ██║   ██║███████║██║     ██║██║  ██║███████║   ██║   █████╗      ██║   ██║███████╗███████║██║  ███╗█████╗\n  //  ╚██╗ ██╔╝██╔══██║██║     ██║██║  ██║██╔══██║   ██║   ██╔══╝      ██║   ██║╚════██║██╔══██║██║   ██║██╔══╝\n  //   ╚████╔╝ ██║  ██║███████╗██║██████╔╝██║  ██║   ██║   ███████╗    ╚██████╔╝███████║██║  ██║╚██████╔╝███████╗\n  //    ╚═══╝  ╚═╝  ╚═╝╚══════╝╚═╝╚═════╝ ╚═╝  ╚═╝   ╚═╝   ╚══════╝     ╚═════╝ ╚══════╝╚═╝  ╚═╝ ╚═════╝ ╚══════╝\n  //\n  if (!_.isObject(options)) {\n    throw new Error('Usage: A dictionary of options should be provided as the sole argument `htmlScriptify()`');\n  }\n\n  // Verify `data`.\n  if (_.isUndefined(options.data)) {\n    throw new Error('Usage: `data` is a required option');\n  }\n  if (!_.isObject(options.data) || _.isArray(options.data) || _.isFunction(options.data)) {\n    throw new Error(\n      'Usage: `data` should be provided as a dictionary.  But instead, got: '+\n      util.inspect(options.data,{depth:null})\n    );\n  }//--•\n\n  // Verify `keys`, if provided.\n  //\n  // > If unspecified, we leave `options.keys` as `undefined`.\n  // > (this indicates that all keys in provided data are permitted; i.e. no whitelist.)\n  if (!_.isUndefined(options.keys)) {\n    try {\n      options.keys = rttc.validate(['string'], options.keys);\n    } catch (e) {\n      if (e.code === 'E_INVALID') {\n        throw new Error('Usage: If provided, `keys` should be an array of strings');\n      } else { throw e; }\n    }\n  }//--•\n  else {\n    // (if unspecified, we leave `options.keys` as `undefined`)\n  }//>-\n\n  // Verify `namespace`, if provided (or use default)\n  //\n  // > Note that while we might also consider validating `namespace` as an\n  // > ecmascript-compatible variable name, in the interest of avoiding any\n  // > more dependencies here, we do not.\n  if (!_.isUndefined(options.namespace)) {\n    try {\n      options.namespace = rttc.validate('string', options.namespace);\n    } catch (e) {\n      if (e.code === 'E_INVALID') {\n        throw new Error('Usage: If provided, `namespace` should be a string');\n      } else { throw e; }\n    }\n  }\n  else {\n    options.namespace = 'SAILS_LOCALS';\n  }//>-\n\n  // Verify `dontUnescapeOnClient` flag, if provided (or use default)\n  //\n  // > If special backwards-compatible support for older browsers is needed, or any\n  // > other customizations to the client-side escaping code are necessary, then\n  // > the built-in client-side escaping can be disabled using `dontUnescapeOnClient: true`.\n  if (!_.isUndefined(options.dontUnescapeOnClient)) {\n    try {\n      options.dontUnescapeOnClient = rttc.validate('boolean', options.dontUnescapeOnClient);\n    } catch (e) {\n      if (e.code === 'E_INVALID') { throw new Error('Usage: If provided, `dontUnescapeOnClient` should be either `true` or `false`'); }\n      else { throw e; }\n    }\n  }\n  else {\n    options.dontUnescapeOnClient = false;\n  }//>-\n\n  // console.log('options.data',options.data);\n  // console.log('_.keys(options.data)',_.keys(options.data));\n\n\n  //  ██████╗ ██╗   ██╗██╗██╗     ██████╗     ██╗  ██╗████████╗███╗   ███╗██╗\n  //  ██╔══██╗██║   ██║██║██║     ██╔══██╗    ██║  ██║╚══██╔══╝████╗ ████║██║\n  //  ██████╔╝██║   ██║██║██║     ██║  ██║    ███████║   ██║   ██╔████╔██║██║\n  //  ██╔══██╗██║   ██║██║██║     ██║  ██║    ██╔══██║   ██║   ██║╚██╔╝██║██║\n  //  ██████╔╝╚██████╔╝██║███████╗██████╔╝    ██║  ██║   ██║   ██║ ╚═╝ ██║███████╗\n  //  ╚═════╝  ╚═════╝ ╚═╝╚══════╝╚═════╝     ╚═╝  ╚═╝   ╚═╝   ╚═╝     ╚═╝╚══════╝\n  //\n  //  ████████╗ ██████╗     ██████╗ ███████╗████████╗██╗   ██╗██████╗ ███╗   ██╗\n  //  ╚══██╔══╝██╔═══██╗    ██╔══██╗██╔════╝╚══██╔══╝██║   ██║██╔══██╗████╗  ██║\n  //     ██║   ██║   ██║    ██████╔╝█████╗     ██║   ██║   ██║██████╔╝██╔██╗ ██║\n  //     ██║   ██║   ██║    ██╔══██╗██╔══╝     ██║   ██║   ██║██╔══██╗██║╚██╗██║\n  //     ██║   ╚██████╔╝    ██║  ██║███████╗   ██║   ╚██████╔╝██║  ██║██║ ╚████║\n  //     ╚═╝    ╚═════╝     ╚═╝  ╚═╝╚══════╝   ╚═╝    ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═══╝\n  //\n  // Build and return HTML to inject.\n  var html = '<script type=\"text/javascript\">';\n  html += ' (function (){ ';\n\n  // By default, we inject client-side code to perform unescaping.\n  // But if the `dontUnescapeOnClient` flag was enabled, then we don't build the\n  // code to do the unescaping. (useful for customizing client-side unescaping logic;\n  // e.g. for legacy browser compatibility <= IE 8)\n  if (!options.dontUnescapeOnClient) {\n\n    // Inject client-side JavaScript code that will be used to unescape the\n    // bootstrapped data.\n    //\n    // This is kind of like _.unescape()...except it also has to be recursive.\n    // Luckily, we don't have to worry about circular references or any of the\n    // other not-quite-JSON stuff, since we know this was just serialized.\n    html += ' var unescape = ' + minifiedUnescapeHtmlEntitiesDeepLiteStr + ';';\n\n  }//</if :: needed to inject client-side unescape function (`dontUnescapeOnClient` flag was NOT enabled)>\n  //>-\n\n  html += ' window.'+options.namespace+' = { ';\n\n  // Determine the relevant keys to inject.\n  // (filtering using `options.keys` whitelist, if provided)\n  var keysToInject = _.keys(options.data);\n  if (!_.isUndefined(options.keys)) {\n    keysToInject = _.intersection(keysToInject, options.keys);\n  }\n\n  // Then inject them in our <script> tag string.\n  _.each(keysToInject, function eachRelevantKey(key){\n    var unsafeVal = options.data[key];\n\n    // If this top-level key in the provided data is undefined, exclude it altogether.\n    if (_.isUndefined(unsafeVal)) { return; }\n\n    // Now, dive into `unsafeVal` and recursively HTML-escape any nested strings.\n    // Then, compile the whole thing into a JavaScript string which will accurately\n    // represent it as an r-value (watching out for circular refs along the way).\n    var escapedData = rttc.compile(escapeHtmlEntitiesDeep(unsafeVal));\n\n    // If the `dontUnescapeOnClient` flag was set, then just stick the compiled,\n    // still-HTML-escaped data in place.  (It will have to be recursively unescaped\n    // by hand in the app's custom client-side code!)\n    if (options.dontUnescapeOnClient) {\n      html += ''+key+': '+escapedData+',';\n    }\n    // Otherwise, we're including the client-side code to unescape the data,\n    // so run our unescape function from above.\n    else {\n      html += ''+key+': unescape('+escapedData+'),';\n    }\n  });\n  html += ' }; ';\n  html += ' })(); ';\n  html += '</script>';\n\n  return html;\n\n};\n\n"
  },
  {
    "path": "lib/hooks/views/index.js",
    "content": "/**\n * Module dependencies\n */\n\nvar configure = require('./configure');\nvar getImplicitDefaults = require('./get-implicit-defaults');\nvar onRoute = require('./onRoute');\nvar defaultViewRenderingFn = require('./default-view-rendering-fn');\nvar addResViewMethod = require('./res.view');\nvar render = require('./render');\nvar statViews = require('./stat-views');\nvar htmlScriptify = require('./html-scriptify');\n\n\n\n\n\nmodule.exports = function (sails) {\n\n  /**\n   * `views` hook\n   */\n  return {\n\n    defaults: getImplicitDefaults,\n\n    // The view rendering function -- may be overriden if `sails.config.views.getRenderFn` is provided.\n    _renderFn: defaultViewRenderingFn,\n\n    configure: function (){\n      configure(sails);\n    },\n\n    render: render(sails),\n\n    htmlScriptify: htmlScriptify,\n\n    /**\n     * Standard responsibilities of `initialize` are to load middleware methods\n     * and listen for events to know when to bind any special routes.\n     *\n     * @api private\n     */\n    initialize: function (cb) {\n\n      if (!sails.hooks.http) {\n        var err = new Error('`views` hook requires the `http` hook, but the `http` hook is disabled.  Please enable both or neither.');\n        err.code = 'E_HOOKINIT_DEP';\n        err.type = err.code;//<<TODO: remove this\n        err.name = 'failed requires `http` hook';\n        return cb(err);\n      }\n\n      // Before handing off incoming requests, bind handler that adds the `res.view()` method to `res`.\n      // (flagging middleware along the way)\n      addResViewMethod._middlewareType = 'VIEWS HOOK: addResViewMethod';\n      sails.on('router:before', function () {\n\n        // But wait until after internationalization has happened\n        // (if applicable)\n        if (sails.hooks.i18n) {\n          sails.after('hook:i18n:loaded', function () {\n            sails.router.bind('/*', addResViewMethod, 'all', { });\n          });\n        }\n        else {\n          sails.router.bind('/*', addResViewMethod, 'all');\n        }\n      });\n\n      // Register `{view:'/foo'}` route target syntax.\n      sails.on('route:typeUnknown', function (route){\n        return onRoute(sails, route);\n      });\n\n      // Expose `sails.renderView()` function to userland.\n      // (experimental!)\n      sails.renderView = this.render;\n\n      // Check for the existence of view files.\n      //\n      // This existence tree is used later to detect\n      // and prepare implicit actions for each view file\n      // to support routes with targets like `{view:'...'}`.\n      statViews(sails, this, function (err){\n        if (err) {\n          return cb(err);\n        }\n        return cb();\n      });\n    }\n  };\n\n};\n"
  },
  {
    "path": "lib/hooks/views/onRoute.js",
    "content": "/**\n * Module dependencies\n */\n\nvar path = require('path');\nvar _ = require('@sailshq/lodash');\n\n\n\n/**\n * onRoute()\n *\n * This is used for handling `route:typeUnknown` events; i.e. to\n * support { view: 'foo/bar' } notation. This \"teaches\" the router\n * to understand `view` in route target syntax. This allows route\n * addresses to be bound directly to serve specific views without\n * going through a custom action.\n *\n * e.g.\n * ```\n * 'get /': { view: 'pages/homepage' }\n * ```\n *\n * @param {Sails} sails\n * @param {Dictionary} route\n *        combined route target + route address dictionary\n */\n\nmodule.exports = function onRoute (sails, route) {\n\n\n  // Ignore unknown route syntax\n  if ( !_.isPlainObject(route.target) || !_.isString(route.target.view) ) {\n    // If it needs to be understood by another hook, the hook would have also received\n    // the typeUnknown event, so we don't need to do anything here.\n    return;\n  }\n  // Ensure there isn't a `.` in the view name.\n  // (This limitation will be improved in a future version of Sails.)\n  else if (route.target.view.match(/\\./)) {\n    sails.log.error('Ignoring attempt to bind route (`%s`) to a view with a `.` in the name (`%s`).',route.path, route.target.view);\n    return;\n  }\n  // Otherwise construct an action function which serves a view and then bind it to the route.\n  else {\n\n    // Merge target into `options` to get hold of relevant route options:\n    route.options = _.merge(route.options, route.target);\n    // Note: this (^) could be moved up into lib/router/bind.js, since its\n    // only pertinent for core options such as `skipAssets`.  There would need\n    // to be changes in other hooks as well.\n\n    // Transform the view path into something Lodash _.get will understand (i.e. dots not slashes)\n    var transformedViewAddress = route.target.view.replace(/\\//g, '.');\n    var referenceInViewsHash = _.get(sails.views, transformedViewAddress);\n\n    // Look up the view in our views hash and see if it is `true`\n    // (i.e. indicating it is a view template file)\n    if (referenceInViewsHash === true) {\n      return sails.router.bind(route.path, function serveView(req, res) {\n        return res.view(route.target.view);\n      }, route.verb, route.options);\n    }\n    // Look for a relative `/index` view if the specified view\n    // is in the views hash as a dictionary (i.e. indicating it is a directory)\n    else if (_.isObject(referenceInViewsHash) && referenceInViewsHash.index === true) {\n      var indexViewIdentity = path.join(route.target.view,'/index');\n      return sails.router.bind(route.path, function serveView(req, res) {\n        return res.view(indexViewIdentity);\n      }, route.verb, route.options);\n    }\n    // Otherwise, the specified view in this route target doesn't match a\n    // known view in the project, so ignore the attempt and inform the user.\n    else {\n      sails.log.error('Ignoring attempt to bind route (`%s`) to unknown view: `%s`',route.path, route.target.view);\n    }\n  }\n};\n"
  },
  {
    "path": "lib/hooks/views/render.js",
    "content": "/**\n * Module dependencies\n */\n\nvar path = require('path');\nvar _ = require('@sailshq/lodash');\nvar flaverr = require('flaverr');\nvar parley = require('parley');\n\n\n/**\n * @param  {SailsApp} sails\n * @return {Function}\n */\nmodule.exports = function (sails) {\n\n  /**\n   * renderView()\n   *\n   * Return the HTML string obtained by loading and compiling the specified view\n   * template with a dictionary of dynamic runtime data (and/or other view engine\n   * other view engine options like `layout`.)\n   *\n   * Usage:\n   * ```\n   * var html = await sails.renderView('emails/reminders/email-verify-your-account', {\n   *   fullName: 'Maggie Thatcher',\n   *   accountCreatedAt: 1510521386197\n   * });\n   * ```\n   *\n   * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n   * @param {String} relPathToView\n   *        Path to the view file to load (minus the file extension, and\n   *        relative to the app's `views/` directory.)\n   *\n   * @param {Dictionary} _options\n   *        View locals and/or options to pass to template renderer.\n   *\n   * @param {Function} optionalCb(err, compiledHtml)\n   *        An optional callback.  If provided, it will be called when the\n   *        view rendering is complete and this function will return `undefined`\n   *        instead of returning a Deferred.\n   *        @param {Error} err\n   *        @param {String} compiledHtml\n   * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n   *\n   * @returns {Deferred?}\n   *          (See https://npmjs.com/package/parley for more information.)\n   *\n   * @api public\n   */\n\n  return function renderView(relPathToView, _options, optionalCb) {\n\n    // Build an omen, if appropriate.\n    var omen = flaverr.omen(renderView);\n\n    // Build & return Deferred (or if callback was provided, immediately run\n    // the following logic and then trigger that callback.)\n    return parley(\n      function (done) {\n\n        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n        // TODO: pull out shared logic between this file and res.view.js\n        // into a separate file.\n        //\n        // TODO: reuse code in res.view.js for most of this to make it more maintainable\n        // currently it is not in sync w/ improvements/fixes in that other module.\n        //\n        // (^^^ In particular, note that `exposeLocalsToBrowser` et al is not available\n        // here in sails.renderView() as of yet)\n        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n        if (!relPathToView && !_.isString(relPathToView)) {\n          return done(flaverr({\n            message: 'First argument must be a string -- the relative path to the desired template (from the `views/` folder).'\n          }), omen);\n        }//•\n\n        // Shallow clone the provided locals, since we'll be making some modifications.\n        // > (Note that this IS NO LONGER A DEEP CLONE!  So references to sails config etc\n        // > provided to views should be respected, and not changed.)\n        var options = _.extend({}, _options || {});\n\n        // Trim trailing slash\n        if (relPathToView[(relPathToView.length - 1)] === '/') {\n          relPathToView = relPathToView.slice(0, -1);\n        }\n\n        // if local `layout` is set to true or unspecified\n        // fall back to global config\n        var layout = options.layout;\n        if (layout === undefined || layout === true) {\n          layout = sails.config.views.layout;\n        }\n\n        // Disable sails built-in layouts for all view engine's except for ejs\n        if (sails.config.views.getRenderFn) {\n          layout = false;\n        }\n\n        var pathToViews = sails.config.paths.views;\n        var absPathToView = path.join(pathToViews, relPathToView) + '.' + sails.config.views.extension;\n\n        // Set layout file if enabled (using ejs-locals)\n        if (layout) {\n          // If a layout was specified, set view local so it will be used\n          options._layoutFile = layout;\n        }\n\n        options.view = options.view || {\n          path: relPathToView,\n          pathFromViews: relPathToView,\n          pathFromApp: path.join(path.relative(sails.config.appPath, sails.config.paths.views), relPathToView),\n          ext: sails.config.views.extension\n        };\n\n        // In development, provide access to complete path to view\n        // via `__dirname`\n        if (process.env.NODE_ENV !== 'production') {\n          options.__dirname = options.__dirname ||\n            absPathToView + '.' + sails.config.views.extension;\n        }\n\n        // Handle compatibility issues with certain view rendering engines.\n        // > Copy all the current options into 'locals', and explicitly set two settings,\n        // > just in case the template engine expects them there.\n        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n        // TODO: Explain which view engines require this in comments here, or if that\n        // doesn't happen, then remove this code.\n        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n        _.extend(options, {\n          locals: _.extend({}, options),\n          settings: {\n            'view engine': sails.config.views.extension,\n            'views': sails.config.paths.views\n          }\n        });\n\n        // If the i18n hook is enabled, internationalize before proceeding.\n        (function _internationalizingIfRelevant(proceed){\n          if (!sails.hooks.i18n) {\n            return proceed();\n          }\n\n          // Set up a minimal mock request for the i18n hook to use.\n          var req = {\n            headers: {}\n          };\n\n          // If a locale was specified as an option, render the view with that locale\n          req.headers['accept-language'] = options.locale || sails.hooks.i18n.defaultLocale;\n          req.locale = options.locale || sails.hooks.i18n.defaultLocale;\n          sails.hooks.i18n.expressMiddleware(req, options, function(err) {\n            if (err) { return proceed(err); }\n            return proceed();\n          });//_∏_\n\n        })(function(err) {\n          if (err) { return done(err); }\n\n          // Finally, compile the view template.\n          sails.hooks.views._renderFn(absPathToView, options, function(err, compiledHtml){\n            if (err) { return done(err); }\n            return done(undefined, compiledHtml);\n          });//_∏_\n\n        });//_∏_ (†)\n\n      },\n      optionalCb,\n      undefined,\n      undefined,\n      omen\n    );//…)\n\n  };//ƒ\n\n};\n"
  },
  {
    "path": "lib/hooks/views/res.view.js",
    "content": "/**\n * Module dependencies\n */\n\nvar path = require('path');\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\nvar htmlScriptify = require('./html-scriptify');\n\n\n/**\n * Adds `res.view()` (an enhanced version of res.render) and `res.guessView()` methods to response object.\n * `res.view()` automatically renders the appropriate view based on the calling middleware's source route\n * Note: the original function is still accessible via res.render()\n *\n * @param {Request}  req\n * @param {Response} res\n * @param {Function} next\n */\n\nmodule.exports = function _addResViewMethod(req, res, next) {\n\n  var sails = req._sails;\n\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  // TODO: don't pass `next` into this method impl to avoid confusing situations.\n  // i.e. wrap it up:\n  // ```\n  // function (req,res,next) { _addResViewMethod(req,res); next(); }\n  // ```\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n\n\n  /**\n   * res.guessView([locals], [couldNotGuessCb])\n   *\n   * @param  {Object} locals\n   * @param  {Function} couldNotGuessCb\n   */\n  res.guessView = function (locals, couldNotGuessCb) {\n\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    // FUTURE: Completely remove res.guessView()\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    sails.log.warn(\n      '`res.guessView()` is deprecated in Sails >= v1.0.  If you want to continue to use it\\n'+\n      'in your Sails app, please just drop its implementation into a hook.\\n'+\n      ' [?] Unsure or need advice?  Stop by https://sailsjs.com/support'\n    );\n\n    return res.view(locals, function viewReady(err, html) {\n\n      // If this is an \"implied view doesn't exist\" error,\n      // just serve JSON instead.\n      if (err && (err.code === 'E_VIEW_INFER' || err.code === 'E_VIEW_FAILED')) {\n        return (couldNotGuessCb||res.serverError)(err);\n      }\n\n      // But if some other sort of error occurred, call `res.serverError`\n      else if (err) {return res.serverError(err);}\n\n      // Otherwise we're good, serve the view\n      return res.send(html);\n    });\n  };//</defun:: res.guessView()>\n\n\n\n  /**\n   * res.view([specifiedPath|locals], [locals])\n   *\n   * @param {String} specifiedPath\n   *        -> path to the view file to load (minus the file extension)\n   *          relative to the app's `views` directory\n   * @param {Object} locals\n   *        -> view locals (data that is accessible in the view template)\n   * @param {Function} optionalCb(err)\n   *        -> called when the view rendering is complete (response is already sent, or in the process)\n   *          (probably should be @api private)\n   * @api public\n   */\n  res.view = function(/* specifiedPath, locals, optionalCb */) {\n\n    var specifiedPath = arguments[0];\n    var locals = arguments[1];\n    var optionalCb = arguments[2];\n\n    // sails.log.silly('Running res.view() with arguments:',arguments);\n\n    // By default, generate a path to the view using what we know about the controller+action\n    var relPathToView;\n\n    // Ensure req.target is an object, then merge it into req.options\n    // (this helps ensure backwards compatibility for users who were relying\n    //  on `req.target` in previous versions of Sails)\n    req.options = _.defaults(req.options, req.target || {});\n\n    // Try to guess the view by looking at the controller/action\n    if (!req.options.view && req.options.action) {\n      relPathToView = req.options.action.replace(/\\./g, '/');\n    }\n    // Use the new view config\n    else {relPathToView = req.options.view;}\n\n    // Now we have a reasonable guess in `relPathToView`\n\n    // If the path to a view was explicitly specified, use that\n    // Serve the view specified\n    //\n    // If first arg is not an obj or function, treat it like the path\n    if (specifiedPath && !_.isObject(specifiedPath) && !_.isFunction(specifiedPath)) {\n      relPathToView = specifiedPath;\n    }\n\n    // If the \"locals\" argument is actually the \"specifiedPath\"\n    // give em the old switcheroo\n    if (!relPathToView && _.isString(arguments[1])) {\n      relPathToView = arguments[1] || relPathToView;\n    }\n    // If first arg ISSSSS AN object, treat it like locals\n    if (_.isObject(arguments[0])) {\n      locals = arguments[0];\n    }\n    // If the second argument is a function, treat it like the callback.\n    if (_.isFunction(arguments[1])) {\n      optionalCb = arguments[1];\n      // In which case if the first argument is a string, it means no locals were specified,\n      // so set `locals` to an empty dictionary and log a warning.\n      if (_.isString(arguments[0])) {\n        sails.log.warn('`res.view` called with (path, cb) signature (using path `' + specifiedPath + '`).  You should use `res.view(path, {}, cb)` to render a view without local variables.');\n        locals = {};\n      }\n    }\n\n    // if (_.isFunction(locals)) {\n    //   optionalCb = locals;\n    //   locals = {};\n    // }\n    // if (_.isFunction(specifiedPath)) {\n    //   optionalCb = specifiedPath;\n    // }\n\n    // If a view path cannot be inferred, send back an error instead\n    if (!relPathToView) {\n      var err = new Error();\n      err.name = 'Error in res.view()';\n      err.code = 'E_VIEW_INFER';\n      err.type = err.code;// <<TODO remove this\n      err.message = 'No path specified, and no path could be inferred from the request context.';\n\n      // Prevent endless recursion:\n      if (req._errorInResView) { return res.sendStatus(500); }\n      req._errorInResView = err;\n\n      if (optionalCb) { return optionalCb(err); }\n      else {return res.serverError(err);}\n    }\n\n\n    // Ensure specifiedPath is a string (important)\n    relPathToView = '' + relPathToView + '';\n\n    // Ensure `locals` is an object\n    locals = _.isObject(locals) ? locals : {};\n\n    // Mixin locals from req.options.\n    // TODO -- replace this _.merge() with a call to the merge-dictionaries module?\n    if (req.options.locals) {\n      locals = _.merge(locals, req.options.locals);\n    }\n\n    // Merge with config views locals\n    if (sails.config.views.locals) {\n      // Formerly a deep merge: `_.merge(locals, sails.config.views.locals, _.defaults);`\n      // Now shallow- see https://github.com/balderdashy/sails/issues/3500\n      _.defaults(locals, sails.config.views.locals);\n    }\n\n    // If the path was specified, but invalid\n    // else if (specifiedPath) {\n    //   return res.serverError(new Error('Specified path for view (' + specifiedPath + ') is invalid!'));\n    // }\n\n    // Trim trailing slash\n    if (relPathToView[(relPathToView.length - 1)] === '/') {\n      relPathToView = relPathToView.slice(0, -1);\n    }\n\n    var pathToViews = sails.config.paths.views;\n    var absPathToView = path.join(pathToViews, relPathToView);\n    var absPathToLayout;\n    var relPathToLayout;\n    var layout = false;\n\n    // Deal with layout options only if there is no custom rendering function in place --\n    // that is, only if we're using the default EJS layouts.\n    if (!sails.config.views.getRenderFn) {\n\n      layout = locals.layout;\n\n      // If local `layout` is set to true or unspecified\n      // fall back to global config\n      if (locals.layout === undefined || locals.layout === true) {\n        layout = sails.config.views.layout;\n      }\n\n      // Allow `res.locals.layout` to override if it was set:\n      if (typeof res.locals.layout !== 'undefined') {\n        layout = res.locals.layout;\n      }\n\n\n      // At this point, layout should be either `false` or a string\n      if (typeof layout !== 'string') {\n        layout = false;\n      }\n\n      // Set layout file if enabled (using ejs-locals)\n      if (layout) {\n\n        // Solve relative path to layout from the view itself\n        // (required by ejs-locals module)\n        absPathToLayout = path.join(pathToViews, layout);\n        relPathToLayout = path.relative(path.dirname(absPathToView), absPathToLayout);\n\n        // If a layout was specified, set view local so it will be used\n        res.locals._layoutFile = relPathToLayout;\n\n        // sails.log.silly('Using layout at: ', absPathToLayout);\n      }\n    }\n\n    // Locals passed in to `res.view()` override app and route locals.\n    _.each(locals, function(local, key) {\n      res.locals[key] = local;\n    });\n\n\n    // Provide access to view metadata in locals\n    // (for convenience)\n    if (_.isUndefined(res.locals.view)) {\n      res.locals.view = {\n        path: relPathToView,\n        absPath: absPathToView,\n        pathFromViews: relPathToView,\n        pathFromApp: path.join(path.relative(sails.config.appPath, sails.config.paths.views), relPathToView),\n        ext: sails.config.views.extension\n      };\n    }\n\n    // Set up the `exposeLocalsToBrowser` view helper method\n    // (unless there is already a local by the same name)\n    //\n    // Background:\n    //  -> https://github.com/balderdashy/sails/pull/3522#issuecomment-174242822\n    if (_.isUndefined(res.locals.exposeLocalsToBrowser)) {\n      res.locals.exposeLocalsToBrowser = function (options){\n        if (!_.isObject(options)) { options = {}; }\n\n        // Note:\n        // We get access to locals using a reference obtained via closure--\n        // and since this view helper won't be used until AFTER the rest of\n        // the code in THIS file has run, we know any relevant changes to\n        // `locals` below will be available, since we're referring to the\n        // same object.\n\n        // Note that we include both explicit locals passed to res.view(),\n        // and implicitly-set locals from `res.locals`.  But we exclude\n        // non-relevant built-in properties like `sails` and `_`, as well\n        // as experimental properties like `view`.\n        //\n        // Also note that we create a new dictionary to avoid tampering.\n        var relevantLocals = {};\n\n        _.each(_.union(_.keys(res.locals), _.keys(locals)), function(localName){\n\n          // Explicitly exclude `_locals`, which appears even in explicit locals.\n          // (FUTURE: longer term, could look into doing this _everywhere_ as an optimization-\n          //  but need to investigate other view engines for potential differences)\n          if (localName === '_locals') {}\n          // Explicitly exclude `layout`, since it has special meaning,\n          // even when it appears even in explicit locals.\n          else if (localName === 'layout') {}\n          // Otherwise, use explicit local, if available\n          else if (locals[localName] !== undefined) {\n            relevantLocals[localName] = locals[localName];\n          }\n          // Otherwise, use the one from res.locals... maybe.\n          else {\n            if (localName === '_csrf') {\n              // Special case for CSRF token\n              // > If the security hook is disabled, there won't be a CSRF token in the locals.\n              // > If the hook is enabled but CSRF is disabled for this route, the token will\n              // > be an empty string.  In either of those cases we can just skip it.\n              if (res.locals._csrf !== undefined && res.locals._csrf !== '') {\n                relevantLocals._csrf = res.locals._csrf;\n              }\n            }\n            else if (_.contains(['_', 'sails', 'view', 'session', 'req', 'res', '__dirname', '_layoutFile'], localName)) {\n              // Exclude any other auto-injected implicit locals\n            }\n            else if (_.isFunction(res.locals[localName])) {\n              // Exclude any functions\n            }\n            else {\n              // Otherwise include it!\n              relevantLocals[localName] = res.locals[localName];\n            }\n          }\n        });//∞\n\n        // Return an HTML string which includes a special script tag.\n        return htmlScriptify({\n          data: relevantLocals,\n          keys: options.keys,\n          namespace: options.namespace,\n          dontUnescapeOnClient: options.dontUnescapeOnClient\n        });\n      };//</defun :: res.locals.exposeLocalsToBrowser()>\n    }//>-\n\n    // Unless this is production, provide access to complete view path to view via `__dirname` local.\n    if (process.env.NODE_ENV !== 'production') {\n      res.locals.__dirname =\n        res.locals.__dirname ||\n        (absPathToView + '.' + sails.config.views.extension);\n    }\n\n    // If silly logging is enabled, display some diagnostic information about the res.view() call:\n    if (specifiedPath) { sails.log.silly('View override argument passed to res.view(): ', specifiedPath); }\n    sails.log.silly('Serving view at rel path: ', relPathToView);\n    sails.log.silly('View root: ', sails.config.paths.views);\n\n    // Render the view\n    return res.render(relPathToView, locals, function viewFailedToRender(err, renderedViewStr) {\n\n\n      // Prevent endless recursion:\n      if (err && req._errorInResView) {\n        return res.status(500).send(err);\n      }\n\n\n      if (err) {\n        req._errorInResView = err;\n\n        // Ensure that if res.serverError() likes to serve views,\n        // it won't this time because we ran into a view error.\n        req.wantsJSON = true;\n\n        // Enhance the raw Express view error object\n        // (this error appears when a view is missing)\n        if (_.isObject(err) && err.view) {\n          err = _.extend({\n            message: util.format(\n              'Could not render view \"%s\".  Tried locating view file @ \"%s\".%s',\n              relPathToView,\n              absPathToView,\n              (layout ? util.format(' Layout configured as \"%s\", so tried using layout @ \"%s\")', layout, absPathToLayout) : '')\n            ),\n            code: 'E_VIEW_FAILED',\n            status: 500\n          }, err);\n          err.inspect = function () {\n            return err.message;\n          };\n        }\n      }\n\n      // If specified, trigger `res.view()` callback instead of proceeding\n      if (typeof optionalCb === 'function') {\n        // The provided optionalCb callback will receive the error (if there is one)\n        // as the first argument, and the rendered HTML as the second argument.\n        return optionalCb(err, renderedViewStr);\n      }\n      else {\n\n        // if a template error occurred, don't rely on any of the Sails request/response methods\n        // (since they may not exist yet at this point in the request lifecycle.)\n        if (err) {\n\n          //////////////////////////////////////////////////////////////////\n          // TODO:\n          // Consider removing this log and deferring to the logging that is\n          // happening in res.serverError() instead.\n          // sails.log.error('Error rendering view at ::', absPathToView);\n          // sails.log.error('with layout located at ::', absPathToLayout);\n          // sails.log.error(err && err.message);\n          //\n          //////////////////////////////////////////////////////////////////\n\n          //////////////////////////////////////////////////////////////////\n          // TODO:\n          // Consider just calling some kind of default error handler fn here\n          // in order to consolidate the \"i dont know wtf i should do w/ this err\" logic.\n          // (keep in mind the `next` we have here is NOT THE SAME as the `next` from\n          //  the point when this error occurred!  It is the next from when this\n          //  method was initially attached to the request object in the views hook.)\n          //\n          if (res.serverError) {\n            req.wantsJSON = true;\n            return res.serverError(err);\n          }\n          else if (process.env.NODE_ENV !== 'production') {\n            return res.status(500).send(err);\n          }\n          else {return res.sendStatus(500);}\n          //\n          //////////////////////////////////////////////////////////////////\n        }\n\n        // If verbose logging is enabled, write a log w/ the layout and view that was rendered.\n        sails.log.verbose('Rendering view: \"%s\" (located @ \"%s\")', relPathToView,absPathToView);\n        if (layout) {\n          sails.log.verbose('• using configured layout:: %s (located @ \"%s\")', layout, absPathToLayout);\n        }\n\n        // Finally, send the compiled HTML from the view down to the client\n        res.send(renderedViewStr);\n      }\n\n    });\n  };//</defun :: res.view() >\n\n  next();\n};\n\n\n\n// Express version updates should be closely monitored.\n// Express is a \"hard\" dependency.\n//\n// While unlikely this will change, it's worth noting that this implementation\n// relies on express's private implementation of res.render() here:\n// https://github.com/visionmedia/express/blob/master/lib/response.js#L799\n//\n// To be safe, the version of the Express dependency in package.json will remain locked\n// until it can be verified that each subsequent version is compatible.  Even patch releases!!\n"
  },
  {
    "path": "lib/hooks/views/stat-views.js",
    "content": "/**\n * Stat view files and expose the existence tree on `sails.views`.\n *\n * @param  {Sails}    sails\n * @param  {Hook}     hook\n * @param  {Function} cb\n *         @param {Error} err\n *         @param {Dictionary} detectedViews\n *\n *\n * @api private\n */\n\nmodule.exports = function statViews (sails, hook, cb) {\n  sails.modules.statViews(function (err, detectedViews) {\n    if (err) {\n      return cb(err);\n    }\n\n    // Save existence tree in `sails.views` for consumption later\n    sails.views = detectedViews || {};\n\n    return cb(null, detectedViews);\n  });\n\n};\n"
  },
  {
    "path": "lib/hooks/views/unescape-html-entities-deep-lite.min.string.js",
    "content": "// This module exports the `toString()`-ed, minified version of the function defined below (`unescapeHtmlEntitiesDeepLite()`).\n//\n// We could do something fancier, but realistically, this shouldn't need to change much.\n// If it does, the simplest thing to do is drop it into https://skalman.github.io/UglifyJS-online/\n// then take the output from that, and run `toString()` on it in the Node REPL (that way you get\n// single quotes, vs. the double-quotes you'll get in the Chrome JavaScript console.)\n//\n// Last generated at: 13:49:09 CDT, Sep 28, 2016\nmodule.exports = 'function unescapeHtmlEntitiesDeepLite(r){if(\"function\"!=typeof Array.isArray||\"function\"!=typeof Array.prototype.forEach||\"function\"!=typeof Array.prototype.map||\"function\"!=typeof Object.keys)throw Error(\"Unsupported browser: Missing support for `Array.isArray`, `Array.prototype.forEach`, `Array.prototype.map`, or `Object.keys`!  (Sails\\' built-in HTML-unescaping for exposed locals supports IE9 and up.)\");return function t(r){if(null===r)return r;if(r===!0||r===!1)return r;if(\"number\"==typeof r)return r;if(\"string\"==typeof r){var e=/&(?:amp|lt|gt|quot|#39|#96);/g,o=RegExp(e.source);if(\"\"===r)return r;if(o.test(r)){var n={\"&amp;\":\"&\",\"&lt;\":\"<\",\"&gt;\":\">\",\"&quot;\":\\'\"\\',\"&#39;\":\"\\'\",\"&#96;\":\"`\"};return r=r.replace(e,function(r){return n[r]})}return r}return Array.isArray(r)?r=r.map(function(r){return t(r)}):(Object.keys(r).forEach(function(e){r[e]=t(r[e],e)}),r)}(r)}';\n\n\n// The rest of the code in this file is NEVER ACTUALLY USED DIRECTLY.\n// It is here as a clear, simple point of reference for how the string above was generated.\n// ==================================================================================================================\n\n/**\n * Module dependencies\n */\n\n// N/A\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n// This logic DOES NOT rely on any dependencies.\n// This is so that the function can be used in the browser, or on the server.\n//\n// This function is also sensitive to browser compatibility.\n// (see http://kangax.github.io/compat-table/es5/ and check \"Show obsolete platforms\")\n//\n// The following code is based off of `_.unescape()` and `rttc.rebuild()`, the\n// latter of which is itself influenced by isaac's `JSON.stringifySafe()`.\n// (see https://github.com/isaacs/json-stringify-safe/commit/02cfafd45f06d076ac4bf0dd28be6738a07a72f9#diff-c3fcfbed30e93682746088e2ce1a4a24\n//  but note that the cycle replacer, etc. have been removed for conciseness,\n//  since this function can safely make the strict assumption that incoming\n//  data is already guaranteed to be 100% bidirectionally JSON-compatible.)\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n// Note that we leave the function below intact so that it can be statically analyzed.\n// It is not actually used by backend code!!\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n/* eslint-disable no-unused-vars */\n\n/**\n * unescapeHtmlEntitiesDeepLite()\n *\n * Recursively HTML-unescape all strings in the provided data (destructive).\n *\n * > • If the provided data contains any dictionaries or arrays, this traverses them recursively\n * >   and unescapes any deeply-nested strings.  Remember: any dictionaries/arrays in the original\n * >   value **will be mutated in-place**!\n * >\n * > • Also note that the provided value is assumed to already be 100% bidirectionally JSON-compatible.\n * >   That means no undefined values in arrays, or `Infinity`, etc!!  In other words, you should be\n * >   able to do JSON.stringify() on this data, then JSON.parse() the resulting string, and get _EXACTLY_\n * >   what you started with!\n *\n *\n * @param {JSONCompatible} data\n *           The data to escape. Must be 100% bidirectionally JSON-compatible-- which is stricter than normal! (see above)\n *\n * @returns {JSONCompatible}\n *          The provided data, which has now been destructively HTML-unescaped.\n */\nfunction unescapeHtmlEntitiesDeepLite(data){\n\n  // Check availability of features.\n  // (Default unescaping supports IE 9 and up.\n  //  If you need <=IE 8, etc check out http://kangax.github.io/compat-table/es5/#es5shim.\n  //  For complete flexibility, you can also implement your own unescaping code.\n  //  To do that, pass in `dontUnescapeOnClient: false` when calling the\n  //  `exposeLocalsToBrowser()` view partial.)\n  if (\n    typeof Array.isArray !== 'function' ||\n    typeof Array.prototype.forEach !== 'function' ||\n    typeof Array.prototype.map !== 'function' ||\n    typeof Object.keys !== 'function'\n  ) {\n    throw new Error('Unsupported browser: Missing support for `Array.isArray`, `Array.prototype.forEach`, `Array.prototype.map`, or `Object.keys`!  (Sails\\' built-in HTML-unescaping for exposed locals supports IE9 and up.)');\n  }\n\n\n  // Now rebuild the data recursively.\n  //\n  // > This is a self-invoking recursive function.\n  // > The initial call (made below) sets `thisVal` to `data`.\n  return (function _unescapeRecursive (thisVal) {\n\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    // No need to worry about errors, regexps, dates, functions,\n    // readable streams, buffers, constructors, Infinity, -Infinity,\n    // or `-0` (negative zero).\n    // ***(see note above about bidirectional-JSON-compatible-ness)***\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n    // If this is `null`, then leave it as-is.\n    if (thisVal === null) {\n      return thisVal;\n    }\n    // ‡\n    // If this is a boolean, then leave it as-is.\n    else if (thisVal === true || thisVal === false) {\n      return thisVal;\n    }\n    // ‡\n    // If this is a number, then leave it as-is.\n    else if (typeof thisVal === 'number') {\n      return thisVal;\n    }\n    // ‡\n    // If this is a string, then convert any unsafe HTML entities it contains\n    // into their decoded, real-world, life-on-the-streets character equivalents.\n    else if (typeof thisVal === 'string') {\n\n      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n      // The code below is a port of the unescaping implementation from\n      // within Lodash since v3.x, thru v4.x, and undoubtedly beyond.\n      // It has been modified only to match the coding conventions and\n      // context of Sails.\n      //\n      // For reference, see:\n      //  • Entry point          - https://github.com/lodash/lodash/blob/3.10.1/lodash.js#L11008-L11031\n      //                           (for future reference, see also https://github.com/lodash/lodash/blob/4.16.1/lodash.js#L14932)\n      //\n      //  • RegExps              - https://github.com/lodash/lodash/blob/3.10.1/lodash.js#L81\n      //                           (for future reference, see also https://github.com/lodash/lodash/blob/4.16.1/lodash.js#L121)\n      //\n      //  • `unescapeHtmlChar()` - https://github.com/lodash/lodash/blob/3.10.1/lodash.js#L650-L659\n      //                           (for future reference, see also https://github.com/lodash/lodash/blob/4.16.1/lodash.js#L1333)\n      //\n      //  • `htmlUnescapes`      - https://github.com/lodash/lodash/blob/3.10.1/lodash.js#L215-L223\n      //                           (for future reference, see also https://github.com/lodash/lodash/blob/4.16.1/lodash.js#L376)\n      //\n      // > For future reference, see also `basePropertyOf`:\n      // > https://github.com/lodash/lodash/blob/4.16.1/lodash.js#L888\n      // > (in Lodash 4.x, it's a waypoint between `unescapeHtmlChar()` and the `htmlUnescapes` constant)\n      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n      // Define RegExps for matching HTML entities.\n      var X_ESCAPED_HTML = /&(?:amp|lt|gt|quot|#39|#96);/g;\n      var X_HAS_ESCAPED_HTML = RegExp(X_ESCAPED_HTML.source);\n\n      // If this is empty string, we can go ahead and bail (as an optimization.)\n      // Also, another optimization: if it does not contain any HTML entities representing\n      // unsafe characters, then we bail rather than wasting cycles on a `.replace()`.\n      // Otherwise, that means we have to do a bit more work.  In this case, we'll replace\n      // each one of those HTML entities with the corresponding unsafe character.\n      if (thisVal === '') {\n        return thisVal;\n      }\n      else if (!X_HAS_ESCAPED_HTML.test(thisVal)) {\n        return thisVal;\n      }\n      else {\n        var ENTITY_TO_CHAR_MAPPING = {\n          '&amp;': '&',\n          '&lt;': '<',\n          '&gt;': '>',\n          '&quot;': '\"',\n          '&#39;': '\\'',\n          '&#96;': '`' // << note that this is only necessary because we're using the `_.escape()` from Lodash 3.10.1.\n        };\n        thisVal = thisVal.replace(X_ESCAPED_HTML, function (htmlEntityStr) {\n          return ENTITY_TO_CHAR_MAPPING[htmlEntityStr];\n        });\n        return thisVal;\n      }\n\n    }//</if this is a string>\n    // ‡\n    // If this is an array, recursively unescape it.\n    else if (Array.isArray(thisVal)) {\n      thisVal = thisVal.map(function (item) {\n        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n        // No need to worry about stripping undefined items.\n        // ***(see note above about bidirectional-JSON-compatible-ness)***\n        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n        return _unescapeRecursive(item);\n      });\n      return thisVal;\n    }\n    // ‡\n    // Otherwise, this must be an \"object\".  And since we know it's\n    // neither `null` nor an array, we can safely assume it is a dictionary.\n    // ***(see note above about bidirectional-JSON-compatible-ness)***\n    //\n    // So we'll recursively unescape it.\n    else {\n      Object.keys(thisVal).forEach(function (key) {\n        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n        // No need to worry about stripping keys with `undefined` values.\n        // ***(see note above about bidirectional-JSON-compatible-ness)***\n        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n        thisVal[key] = _unescapeRecursive(thisVal[key], key);\n      });\n      return thisVal;\n    }\n\n  })(data);//</invoked self-calling function :: _unescapeRecursive>\n\n}\n"
  },
  {
    "path": "lib/index.js",
    "content": "/**\n * Module dependencies\n */\n\nvar Sails = require('./app');\n\n\n// Instantiate and expose a Sails singleton\n// (maintains legacy support)\nmodule.exports = new Sails();\n\n// Expose constructor as `.Sails` for convenience/tests:\n// =========================================================\n// To access the Sails app constructor, do:\n// var Sails = require('sails').constructor;\n// or:\n// var Sails = require('sails').Sails;\n//\n// Then:\n// var newApp = new Sails();\n// =========================================================\nmodule.exports.Sails = Sails;\n"
  },
  {
    "path": "lib/router/README.md",
    "content": "# Router\n\n## What does it do?\n\nThe core Router in Sails is the main (_but not ONLY_) player responsible for routing requests.\nIt is not involved with HTTP, WebSockets, or other internet protocols directly-- instead, it emits\nevents on the `sails` object (a Node EventEmitter) when a route should be bound, allowing flexibility\nin hooks' implementations.\n\nThe core Router includes a latent Express instance which is used only for internal routing of requests,\nand is not actually used by any application code in userland-- that's the job of hooks.  It _may_, however,\nbe used by app-level unit tests, in order to run test suites without having to lift a server and occupy a network port.\n\n\n## Which hooks attach servers / use the Router?\n\nAt the time of this writing, the `http` hook listens for `bind` events emitted from the core Router\nand binds them directly to an external instance of Express.\n\nOn the other hand, the `sockets` hook defers to the core router, emitting a `request` event whenever\nit receives and interprets a new, appropriately-formatted, socket message.  The core Router intercepts this\nand routes the request using its known middleware bindings. (core middleware, blueprint aka \"shadow\" routes,\nand statically configured routes from the `routes.js` config file in userland)\n\n\n## FAQ\n\n+ When an HTTP request hits the server, does it hit the Sails router before it hits the Express router?\n  + No- it only hits the Express router.\n\n+ OK.. what requests DO hit the Sails router?\n  + Requests to other attached servers that don't have their own routers, e.g. the Socket.io interpreter, will hit the Sails router's wildcard handler, which will then talk to the attached server and simulate the appropriate route.\n\n+ What happens after an HTTP request hits the Express router?\n  + Sails does not touch the Express router once it's been set up.\n\n+ When and *how* are the routes in your `routes.js` file processed?\n  + `routes.js` is read by the `userconfig` hook, which loads it into `sails.config.routes`.\n  + `sails.config.routes` is used by the Sails router at lifttime (to bind routes to the external Express router) AND at runtime (to detect matches in wildcard routes coming from other attached servers like the Socket.io interpreter)\n\n"
  },
  {
    "path": "lib/router/bind.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\nvar flaverr = require('flaverr');\nvar detectVerb = require('../util/detect-verb');\n\n\n/**\n * Expose `bind` method.\n */\nmodule.exports = bind;\n\n\n\n/**\n * Bind new route(s)\n *\n * @param {String|RegExp} path\n * @param {String|Object|Array|Function} target\n * @param {String} verb (optional)\n * @param {Object} options (optional)\n *\n * @this {SJSRouter}\n * @return {SJSApp}\n *\n * @api private\n */\n\nfunction bind( /* path, target, verb, options */ ) {\n  var sails = this.sails;\n\n  var args = sanitize.apply(this, Array.prototype.slice.call(arguments));\n  var path = args.path;\n  var target = args.target;\n  var verb = args.verb;\n  var options = args.options;\n\n  // Don't allow paths with \"length\" as a route param, because Express chokes on it\n  if (path.match(/\\/:length($|\\/)/)) {\n    throw flaverr({name: 'userError', code: 'E_ROUTE_WITH_LENGTH'}, new Error('Failed to bind route: `'+ path +'`\\n'+\n    'Routes which contain `/:length` in their address URL are not supported by Sails/Express (consider using `/:len`)'));\n  }\n\n  // Bind a list of multiple functions in order\n  if (_.isArray(target)) {\n    bindArray.apply(this, [path, target, verb, options]);\n  }\n\n  // Handle string redirects\n  // (to either public-facing URLs or internal routes)\n  else if (_.isString(target) && target.match(/^(https?:|\\/)/)) {\n    bindRedirect.apply(this, [path, target, verb, options]);\n  }\n\n  // Otherwise if the target is a string, it must be an action.\n  else if (_.isString(target) || (_.isPlainObject(target) && (target.controller || target.action))) {\n    bindAction.apply(this, [path, target, verb, options]);\n  }\n\n  // Bind a middleware function directly\n  else if (_.isFunction(target)) {\n    bindFunction.apply(this, [path, target, verb, options]);\n  }\n\n  // If target is an object with a `target`, pull out the rest\n  // of the keys as route options and then bind the target.\n  else if (_.isPlainObject(target) && (target.target || target.fn)) {\n    var _target = target.target || target.fn;\n    // TODO -- replace _.merge() with a call to merge-dictionaries module?\n    options = _.merge(options, _.omit(target, 'target'));\n    bind.apply(this, [path, _target, verb, options]);\n  }\n\n  else {\n\n    // If we make it here, the router doesn't know how to parse the target.\n    //\n    // This doesn't mean that it's necessarily invalid though--\n    // so we'll emit an event informing any listeners that an unrecognized route\n    // target was encountered.  Then hooks can listen to this event and act\n    // accordingly.  This makes it easier to add functionality to Sails.\n    sails.emit('route:typeUnknown', {\n      path: path,\n      target: target,\n      verb: verb,\n      options: options\n    });\n\n    // Note that, in the future, it would be good to track emissions of \"typeUnknown\" to\n    // avoid logic errors that result in circular routes.\n    // (part of the effort to make a more friendly environment for custom hook developers)\n  }\n\n  // Makes `.bind()` chainable (sort of)\n  return sails;\n\n}\n\n\n\n/**\n * Requests will be redirected to the specified string\n * (which should be a URL or redirectable path.)\n *\n * @api private\n */\nfunction bindRedirect(path, redirectTo, verb, options) {\n  var sails = this.sails;\n\n  bind.apply(this,[path, function(req, res) {\n    sails.log.verbose('Redirecting request (`' + path + '`) to `' + redirectTo + '`...');\n    res.redirect(redirectTo);\n  }, verb, options]);\n}\n\n/**\n * Bind a previously-loaded action to a URL.\n * (which should be a URL or redirectable path.)\n *\n * @api private\n */\nfunction bindAction(path, target, verb, options) {\n\n  var self = this;\n\n  var sails = this.sails;\n\n  var actionIdentity;\n  try {\n    actionIdentity = self.getActionIdentityForTarget(target);\n  } catch (e) {\n    throw flaverr({name: e.name || 'sailsError', code: e.code || 'E_UNKNOWN_BIND_ERROR'}, new Error('Error attempting to bind `' + (verb || 'ALL') + ' ' + path + '` to ' + JSON.stringify(target) + ': ' + e.message));\n  }\n\n  if (_.isObject(target)) {\n    // Fold any other properties in the target into a shallow clone of the \"options\" dictionary\n    options = _.extend({}, options, _.omit(target, 'action'));\n  }\n\n  // If there's no loaded action with that identity, log a warning and continue.\n  if (!sails._actions[actionIdentity]) {\n    sails.log.warn('Ignored attempt to bind route (' + path + ') to unknown action ::', target);\n    return;\n  }\n\n  // Add \"action\" property to the route options, and set the _middlewareType property if the function doesn't already have one.\n  _.extend(options || {}, {action: actionIdentity, _middlewareType: (sails._actions[actionIdentity] && sails._actions[actionIdentity]._middlewareType || 'ACTION: ' + actionIdentity)});\n\n  // Loop through all of the registered action middleware, and find\n  // any that should apply to the action with the given identity.\n  var actionMiddlewareToRun = _.reduce(sails._actionMiddleware, function(memo, middlewareList, key) {\n    // Split the key into an array and sort it so that strings starting with '!' come first.\n    var targets = key.split(',').sort();\n    _.any(targets, function(target) {\n      // Remove any whitespace surrounding the target.\n      target = target.trim();\n      // If the target starts with a '!' (meaning that any actions matching it should _not_\n      // run the middleware), and the target matches, bust out of this loop early.\n      if (target[0] === '!') {\n        target = target.substr(1);\n        if (\n          // Does the target end in a `/*`, and the action identity matches the wildcard?\n          (target.slice(-2) === '/*' && ((actionIdentity.indexOf(target.slice(0,-1)) === 0) || actionIdentity === target.slice(0, -2)) ) ||\n          // Does the target match the action identity exactly?\n          (actionIdentity === target)\n        ) {\n          // We found a matching target, so we can exit this loop.\n          return true;\n        }\n      }\n      // If the target doesn't start with a '!', it means we already got past all of the\n      // negative targets (since the targets are sorted alphabetically), so we can safely\n      // add any middleware that this action matches.\n      else {\n        if (\n          // If the registered action middleware key is '*'...\n          target === '*' ||\n          // Or ends in '/*' so that the current action identity matches the wildcard...\n          (target.slice(-2) === '/*' && ((actionIdentity.indexOf(target.slice(0,-1)) === 0) || actionIdentity === target.slice(0, -2)) ) ||\n          // Or matches the current action identity exactly...\n          (actionIdentity === target)\n        ) {\n          // Then add the action middleware from this key to the list of middleware\n          // to run before the action.\n          memo = memo.concat(middlewareList);\n          // We found a matching target, so we can exit this loop.\n          return true;\n        }\n      }\n      // Check the next target.\n      return false;\n    });\n    // Keep on reducin'.\n    return memo;\n  }, []);\n\n  // Get a unique list of middleware, in case any were added more than once.\n  actionMiddlewareToRun = _.uniq(actionMiddlewareToRun);\n\n  // Bind each middleware to the identity.\n  _.each(actionMiddlewareToRun, function(middleware) {\n    // console.log('binding middleware', middleware.toString(), 'to path', verb + ' ' + path);\n    bind.apply(self, [path, middleware, verb, options]);\n  });\n\n  // Now, bind a function to the route which calls the specified action.\n  bind.apply(this,[path, function(req, res, next) {\n\n    // If the specified action doesn't exist in the internal actions dictionary.\n    // bail out early.\n    if (!_.isFunction(sails._actions[actionIdentity])) {\n      return next(new Error('Consistency violation: Request (' + req.method + ' ' + req.path + ') matched a route that is bound to action `' + actionIdentity + '`, but no such action has been registered.  This never should have happened, because the route never should have been bound in the first place.'));\n    }\n\n    // Create a mock \"next\" function to catch unauthorized use of the third argument to route handlers.\n    var mockNext = function(err) {\n      if (err) {\n        return next(new Error('`next` (as in req,res,next) should never be called in an action function (but in action `' + actionIdentity + '`, it was!)  It was called with an error: ' + (_.isError(err) ? err.stack : util.inspect(err, {\n          depth: null\n        }) + '') + '  Please use a method like `res.serverError()` or `res.badRequest()` instead.'));\n      }\n\n      return next(new Error('`next` (as in req,res,next) should never be called in an action function (but in action `' + actionIdentity + '`, it was!)  It was called with no arguments.  Please use a method like `res.ok()` or `res.json()` instead.'));\n\n    };//ƒ\n\n    try {\n      // Catch errors in async actions.  See more notes about async route handlers in the `bindFunction` code below.\n      // > FUTURE: optimize by precomputing this constructor.name check\n      if (sails._actions[actionIdentity].constructor.name === 'AsyncFunction') {\n        // Call the action with the specified identity, passing in req and res, as well as `mockRes` to catch unauthorized\n        // use of `next` inside of end-user action code.\n        var promise = sails._actions[actionIdentity](req, res, mockNext);\n        promise.catch(function(e) {\n          // If we do catch an error, use `next` to let Express handle it correctly.\n          next(e);\n        });\n\n      }\n\n      // For synchronous actions, just call the function.\n      else {\n        // Call the action with the specified identity, passing in req and res, as well as `mockRes` to catch unauthorized\n        // use of `next` inside of end-user action code.\n        return sails._actions[actionIdentity](req, res, mockNext);\n      }\n    } catch(e) {\n      // If we do catch an error, use `next` to let Express handle it correctly.\n      return next(e);\n    }\n\n\n  }, verb, options]);\n}\n\n\n/**\n * Recursively bind an array of targets in order\n *\n * TODO: Use a counter to prevent indefinite loops--\n * only possible if a bad route is bound,\n * but would still potentially be helpful.\n *\n * @api private\n */\nfunction bindArray(path, target, verb, options) {\n  var self = this;\n  var sails = this.sails;\n\n  if (target.length === 0) {\n    sails.log.verbose('Ignoring empty array in `router.bind(' + path + ')`...');\n  } else {\n    // Bind each middleware fn\n    _.each(target, function(fn) {\n      bind.apply(self,[path, fn, verb, options]);\n    });\n  }\n}\n\n\n\n/**\n * Attach middleware function to route.\n *\n * @api private\n */\nfunction bindFunction(path, fn, verb, options) {\n  var sails = this.sails;\n\n  // Make sure (optional) options is a valid plain object ({})\n  // TODO -- replace _.isPlainObject with _.isObject && !_.isArray && !_.isFunction ?\n  // TODO -- if we're doing _.cloneDeep here, do we need it in all the places we do it in blueprints?\n  options = _.isPlainObject(options) ? _.cloneDeep(options) : {};\n\n  // Warn about no-longer-used blueprint request options.\n  if (_.intersection(_.keys(options), ['populate', 'skip', 'limit', 'sort', 'where']).length > 0) {\n    sails.log.debug('In route `' + verb + ' ' + path + ':');\n    sails.log.debug('The `populate`, `skip`, `limit`, `sort` and `where` route options are no longer supported in Sails 1.0.');\n    sails.log.debug('Instead, you can use a `parseBlueprintOptions` function to fully customize blueprint behavior for a route.');\n    sails.log.debug('See http://sailsjs.com/docs/reference/configuration/sails-config-blueprints#?using-parseblueprintoptions.');\n    sails.log.debug();\n  }\n\n  // Get the _middlewareType of the function.\n  var _middlewareType =\n    // If it was set on the function itself, use that.\n    fn._middlewareType ||\n    // Otherwise if options._middlewareType is set (probably because the function was defined inline\n    // in a call to `.bind()`), use that.\n    options._middlewareType ||\n    // Otherwise if the function has a name, use that.\n    ('FUNCTION: ' + (fn.name || '<anonymous>'));\n\n  // Set the middleware type on the function.  This can be useful for debugging if the same function\n  // was bound in different contexts (like different actions).\n  fn._middlewareType = _middlewareType;\n\n  // Remove any _middlewareType property from the options.  It's done its job, and we don't need\n  // it to get merged into req.options.\n  // delete options._middlewareType;\n\n  // Log info about the bound route in SILLY mode.\n  sails.log.silly('Binding route :: ', verb || '', path, _middlewareType);\n\n\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n  // FUTURE: simplify away the unnecessary function declarations below and inline the logic instead.\n  // (will make it much clearer what's going on; and any minimal performance impact will be in the form of gains)\n  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n  /**\n   * `router:route`\n   *\n   * Create a closure that emits the `router:route` event each time the route is hit\n   * before actually triggering the target function.\n   *\n   * NOTE: Modifications to route path parameters (i.e. `req.params`) or to `req.options`\n   * must be made here, since their values can change not only on a per-request, but\n   * also a per-route basis.\n   */\n  var enhancedFn = function routeTargetFnWrapper(req, res, next) {\n\n    // Set req.options, using `options` to supply default values.\n    req.options = _.merge({}, options || {}, req.options || {});\n\n\n    // This event can be tapped into to take control of\n    // (synchronous) logic that should be run before each bound\n    // route handler function runs.\n    sails.emit('router:route', {\n      req: req,\n      res: res,\n      next: next,\n      options: options,\n      fn: fn\n    });\n\n\n    // Trigger original route handler function.\n    //\n    // > Note that, if it is an async function, then we also attach a handler to `.catch()`\n    // > its return value (which will be a promise) in order to handle rejections in the same\n    // > way we handle exceptions that are thrown synchronously (mainly for the purpose of being able to use async/await)\n    // > (https://trello.com/c/UdK9ooJ3/108-es7-async-await-in-core-sniff-request-handler-function-to-see-if-it-s-an-async-function-if-so-then-grab-the-return-value-from-th)\n    try {\n      if (fn.constructor.name === 'AsyncFunction') {\n        // FUTURE: benchmark this and, if tangible enough, allow configuration to be used to hard-code functions\n        // one way or the other.  (Frankly, seems like we could just forcefully swap all request handling functions\n        // over to be async functions -- but that'd be kind of a big change and I'd rather wait for a later release\n        // unless we can prove that that'd definitely be a 100% backwards compatible change)\n        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n        var promise = fn(req, res, next);\n\n        promise.catch(function(e) {\n          // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n          // Note that async+await+bluebird+Node 8 errors are not necessarily \"true\" Error instances,\n          // as per _.isError() anyway (see https://github.com/node-machine/machine/commits/6b9d9590794e33307df1f7ba91e328dd236446a9).\n          // So if we want improve the stack trace here, we'd have to be a bit more relaxed and tolerate\n          // these sorts of \"errors\" directly as well (by tweezing out the `cause`, which is where the\n          // original Error lives.)\n          //\n          // Note: This is now taken care of automatically by flaverr.parseError()\n          // (The implementation of this \"tweezing\" is in the default serverError\n          // response handler though.)\n          // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n          next(e);\n          // (Note that we don't do `return next(e)` here.  That's on purpose--\n          // to avoid sending the wrong idea to you, dear reader)\n        });\n      }\n      else {\n        fn(req, res, next);\n      }\n    } catch (e) { return next(e); }\n  };\n\n  /**\n   * Wrap a regex route in a helper function that pulls out regex params\n   *\n   * Example: for route: 'r|/\\\\d+/(.*)/(.*)$|foo,bar', the two parenthesized\n   * groups would be pulled out as req.params[0] and req.params[1] by Express;\n   * the regexRouteWrapper would then map them to req.params['foo'] and req.params['bar']\n   *\n   * @param  {array} params List of params to apply to the req.params object\n   * @return {Function} A middleware function\n   */\n  var regexRouteWrapper = function(params) {\n\n    return function(req, res, next) {\n      // Apply the regex route params\n      params.forEach(function(param, index) {\n        req.params[param] = req.params[index];\n      });\n      // Call enhancedFn (which is just defined above)\n      enhancedFn(req, res, next);\n    };\n  };\n\n  /**\n   * Wrap a route in a helper function that first checks whether the URL matches\n   * any of a set of regexes, and if so, skips the defined handler.\n   *\n   * @param  {array}   regexes Array of regexes to match the URL against\n   * @param  {Function} fn      Middleware function to run if URL does NOT match regexes\n   * @return {Function} A middleware function\n   */\n  var skipRegexesWrapper = function(regexes, fn) {\n\n    // Remove anything that's not a regex\n    regexes = _.compact(regexes.map(function(regex) {\n      if (regex instanceof RegExp) {\n        return regex;\n      }\n      sails.log.warn('Invalid regex \"' + regex + '\" supplied to skipRegexesWrapper; ignoring.');\n      return undefined;\n    }));\n\n\n    return function(req, res, next) {\n\n      // Check for matches\n      for (var i = 0; i < regexes.length; i++) {\n        if (req.url.match(regexes[i])) {\n          // If we find one, bail out\n          return next();\n        }\n      }\n\n      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n      // TODO: Need to double-check on this, but shouldn't this call `enhancedFn`, instead of just `fn`?\n      // If so, then we can just make that change.  Otherwise, we need to do more here.\n      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n      // Otherwise continue with the handler\n      return fn(req, res, next);\n\n    };\n\n  };\n\n  // If verb is not specified, default to CRUD methods.\n  // You can still explicitly route to \"all /path\" if you want ALLLLlllll the things.\n  var targetVerb = verb || ['get', 'put', 'post', 'delete', 'patch'];\n\n  // Ensure targetVerb is an array of lowercased verbs.\n  if (!Array.isArray(targetVerb)) {targetVerb = [targetVerb.toLowerCase()];}\n  else {\n    targetVerb = _.map(targetVerb, function(verb) { return verb.toLowerCase(); });\n  }\n\n  // Function to actually bind\n  var targetFn;\n\n  // Regex to check if the route is...a regex.\n  var regExRoute = /^r\\|(.*)\\|(.*)$/;\n\n  // Perform the check\n  var matches = path.match(regExRoute);\n\n  // If it *is* a regex, create a RegExp object that Express can bind,\n  // pull out the params, and wrap the handler in regexRouteWrapper\n  if (matches) {\n    path = new RegExp(matches[1]);\n    var params = matches[2].split(',');\n    targetFn = regexRouteWrapper(params);\n  }\n\n  // Otherwise just bind enhancedFn\n  else {\n    targetFn = enhancedFn;\n  }\n\n  // If options.skipRegex is specified, make sure it's an array\n  if (options.skipRegex) {\n    if (!Array.isArray(options.skipRegex)) {\n      options.skipRegex = [options.skipRegex];\n    }\n  }\n  // Otherwise just make it an empty array\n  else {\n    options.skipRegex = [];\n  }\n\n  // For GET routes ending in pattern vars, default `skipAssets` to true.\n  if (_.isString(path) && path.match(/\\:[^\\/]+\\/?$/) && _.isUndefined(options.skipAssets) && _.contains(targetVerb, 'get')) {\n    options.skipAssets = true;\n  }\n\n  // If \"skipAssets\" option is true, add the skipAssets regex\n  // to the options.skipRegex array\n  if (options.skipAssets) {\n    options.skipRegex.push(sails.LOOKS_LIKE_ASSET_RX);\n  }\n\n  // If we have anything in the options.skipRegex array, wrap\n  // the target function again.\n  if (options.skipRegex.length) {\n    targetFn = skipRegexesWrapper(options.skipRegex, targetFn);\n  }\n\n  // Loop through the verbs we want to bind\n  targetVerb.forEach(function(verb) {\n\n    // Bind the function to the private router\n    sails.router._privateRouter[verb](path, targetFn);\n\n    // Emit an event to make hooks aware that a route was bound\n    // This allows hooks to handle routes directly if they want to-\n    // e.g. with Express, the handler for this event looks like:\n    // sails.hooks.http.app[verb || 'all'](path, target);\n    sails.emit('router:bind', {\n      path: path,\n      target: targetFn,\n      verb: verb,\n      options: options,\n      originalFn: fn\n    });\n\n  });\n\n\n}\n\n\n\n/**\n * Sanitize the arguments to `sails.router.bind()`\n *\n * @returns {Object} sanitized arguments\n * @api private\n */\nfunction sanitize(path, target, verb, options) {\n  options = options || {};\n\n  // If trying to bind '*', that's probably not what was intended, so fix it up\n  path = path === '*' ? '/*' : path;\n\n  // If route has an HTTP verb (e.g. `get /foo/bar`, `put /bar/foo`, etc.) parse it out,\n  var detectedVerb = detectVerb(path);\n  // then prune it from the path\n  path = detectedVerb.original;\n  // Keep track of parsed verb so we know if it was specified later\n  options.detectedVerb = detectedVerb;\n\n  // If a verb override was not specified,\n  // use the detected verb from the string route\n  if (!verb) {\n    verb = detectedVerb.verb;\n  }\n\n  return {\n    path: path,\n    target: target,\n    verb: verb,\n    options: options\n  };\n}\n"
  },
  {
    "path": "lib/router/bindDefaultHandlers.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\n\n/**\n * Default 500 and 404 handler.\n * (defers to res.serverError() and res.notFound() whenever possible)\n *\n * With default hook configuration, these handlers apply to both HTTP\n * and virtual requests\n */\nmodule.exports = function(sails) {\n\n\n  return {\n\n    /**\n     * Default 500 handler.\n     * (for errors implicitly thrown in middleware/routes)\n     *\n     * @param  {*} err\n     * @param  {Request} req\n     * @param  {Response} res\n     */\n    500: function(err, req, res) {\n\n      // console.log('* * * FIRED DEFAULT HANDLER (500) * * *');\n      // console.log('args:',arguments);\n      // console.log('* * * </FIRED_DEFAULT_HANDLER_500> * * *');\n      // console.log();\n\n      // First, check for special built-in errors from Express.\n      // We don't necessarily want to treat any error that is thrown with\n      // a `status` property of 400 as if it were intentional.  So we also check\n      // the error message.  In Express 5, hopefully this can be improved a bit\n      // further.\n\n      if (_.isError(err)) {\n\n        var msgMatches = err.message.match(/^Failed to decode param \\'([^']+)\\'/);\n        if (err.status === 400 && msgMatches) {\n          sails.log.verbose('Bad request: Could not decode the requested URL ('+req.path+')');\n          // Note for future: The problematic URL section is: `msgMatches[1]`\n          return res.status(400).send('Bad request: Could not decode requested URL.');\n        }\n\n      }\n\n      // Next, try to use `res.serverError()`, if it exists and is valid.\n      try {\n\n        if (typeof res.serverError === 'function') {\n          return res.serverError(err);\n        }//>-\n\n      } catch (unusedErr) { /* ignore any unexpected error encountered when attempting to respond w/ res.serverError(). */ }\n\n      // Catch-all:\n      // Log a message and try to use `res.send` to respond.\n      try {\n\n        sails.log.error('Server Error:');\n        sails.log.error(err);\n        if (process.env.NODE_ENV === 'production') {\n          return res.sendStatus(500);\n        }\n        else {\n          return res.status(500).send(err);\n        }\n\n      } catch (errorSendingResponse) {\n\n        // Serious error occurred-- unable to send response.\n        //\n        // Note that in the future, we could also emit an `abort` message on the request object\n        // in this case-- then if an attached server is managing this request, it could monitor\n        // for `abort` events and manage its private resources (e.g. TCP sockets) accordingly.\n        // However, such contingencies should really handled by the underlying HTTP hook, so\n        // this might not actually make sense.\n        sails.log.error('But no response could be sent because another error occurred:');\n        sails.log.error(errorSendingResponse);\n\n      }//</catch>\n    },\n\n\n\n    /**\n     * Default 404 handler.\n     * (for unmatched routes)\n     *\n     * @param  {Request} req\n     * @param  {Response} res\n     */\n    404: function(req, res) {\n\n      // Use `notFound` handler if it exists\n      try {\n        if (typeof res.notFound === 'function') {\n          return res.notFound();\n        }\n      } catch (unusedErr) { /* If res.notFound() doesn't exists, or fails w/ an error, then silently ignore that and try other ways to send a 404. */ }\n\n      // Catch-all:\n      // Log a message and try to use `res.send` to respond.\n      try {\n        sails.log.verbose('A request (%s) did not match any routes, and no `res.notFound` handler is configured.', req.url);\n        res.sendStatus(404);\n        return;\n      } catch (err) {\n        // Serious error occurred-- unable to send response.\n        //\n        // Note that in the future, we could also emit an `abort` message on the request object\n        // in this case-- then if an attached server is managing this request, it could monitor\n        // for `abort` events and manage its private resources (e.g. TCP sockets) accordingly.\n        // However, such contingencies should really handled by the underlying HTTP hook, so\n        // this might not actually make sense.\n        sails.log.error('An unmatched route was encountered in a request...');\n        sails.log.error('But no response could be sent because an error occurred:');\n        sails.log.error(err);\n        return;\n      }\n    }//ƒ\n  };\n\n};\n"
  },
  {
    "path": "lib/router/index.js",
    "content": "/**\n * Module dependencies.\n */\n\nvar Readable = require('stream').Readable;\nvar QS = require('querystring');\nvar _ = require('@sailshq/lodash');\nvar router = require('@sailshq/router');\nvar flaverr = require('flaverr');\nvar sortRouteAddresses = require('sort-route-addresses');\nvar buildReq = require('./req');\nvar buildRes = require('./res');\nvar defaultHandlers = require('./bindDefaultHandlers');\nvar detectVerb = require('../util/detect-verb');\n\n// Private var to hold sorted route addresses\nvar sortedRouteAddresses = [];\n\n/**\n * Expose new instance of `Router`\n *\n * @api private\n */\nmodule.exports = function(sails) {\n  return new Router({sails: sails});\n};\n\n\n\n/**\n * Initialize a new `Router`\n *\n * @param {Object} options\n * @api private\n */\n\nfunction Router(options) {\n\n  options = options || {};\n  this.sails = options.sails;\n  this.defaultHandlers = defaultHandlers(this.sails);\n\n  // Expose router on `sails` object\n  this.sails.router = this;\n\n  // Instantiate the private router as an instance of `router.\n  this._privateRouter = router();\n\n  // Return the array of sorted route addresses, cloned for our protection.\n  this.getSortedRouteAddresses = function() { return _.clone(sortedRouteAddresses); };\n\n  // Bind the context of all instance methods\n  this.load = _.bind(this.load, this);\n  this.bind = _.bind(this.bind, this);\n  this.unbind = _.bind(this.unbind, this);\n  this.reset = _.bind(this.reset, this);\n  this.flush = _.bind(this.flush, this);\n  this.route = _.bind(this.route, this);\n  this.getActionIdentityForTarget = _.bind(this.getActionIdentityForTarget, this);\n}\n\n\n/**\n * _privateRouter\n *\n * This internal \"private\" instance of an Express app object\n * is used only for routing. (i.e. it will not be used for\n * listening to actual HTTP requests; instead, one or more\n * delegate servers can be attached- see the `http` or\n * `sockets` hooks for examples of attaching a server to\n * Sails)\n *\n * NOTE: Requires calling `load()` before use in order to\n * provide access to the proper NODE_ENV, since Express\n * uses that to determine its environment (development vs.\n * production.)\n */\n\n// Router.prototype._privateRouter;\n\n\n\n/**\n * `sails.router.load()`\n *\n * Expose the router, create the Express private router,\n * then call flush(), which will bind configured routes\n * and emit the appropriate events.\n *\n * Note the `results, cb` signature, which is necessary\n * because this function is called from an async.auto()\n * where it has dependencies.\n *\n * @api public\n */\n\nRouter.prototype.load = function(results, cb) {\n  var sails = this.sails;\n\n  sails.log.silly('Loading router...');\n\n  // Maintain a reference to the static route config\n  this.explicitRoutes = sails.config.routes;\n\n  // Save reference to sails logger\n  this.log = sails.log;\n\n  var sessionSecret = sails.config.session && sails.config.session.secret;\n  // If a session store is configured, hook it up as `req.session` by passing\n  // it down to the session middleware\n  if (!sails.hooks.session) {\n    // If available, Sails uses the configured session secret for signing cookies.\n    if (sessionSecret) {\n      // Ensure secret is a string.  This check happens in the session hook as well,\n      // but sails.config.session.secret may still be provided even if the session hook\n      // is turned off, so to be extra anal we'll check here as well.\n      if (!_.isString(sessionSecret)) {\n        return cb(new Error('If provided, sails.config.session.secret should be a string.'));\n      }\n    }\n  }\n  if (sessionSecret) {\n    sails._privateCpMware = require('cookie-parser')(sessionSecret);\n  } else {\n    sails._privateCpMware = require('cookie-parser')();\n  }\n\n  // Wipe any existing routes and bind them anew\n  try {\n    this.flush();\n  }\n  // Catch any errors thrown by code handling the router:before and router:after events.\n  catch(e) {\n    return cb(e);\n  }\n\n  // Listen for requests\n  sails.on('router:request', this.route);\n\n  // Listen for unhandled errors and unmatched routes\n  sails.on('router:request:500', this.defaultHandlers[500]);\n  sails.on('router:request:404', this.defaultHandlers[404]);\n\n  cb();\n};\n\n\n\n/**\n * `sails.router.route(partialReq, partialRes)`\n *\n * Interpret the specified (usually partial) request and response objects into\n * streams with all of the expected methods, then routes the fully-formed request\n * using the built-in private router. Useful for creating virtual request/response\n * streams from non-HTTP sources, like Socket.io or unit tests.\n *\n * This method is not always helpful-- it is not called for HTTP requests, for instance,\n * since the true HTTP req/res streams already exist.  In that case, at lift-time, Sails\n * calls `router:bind`, which loads Sails' routes as normal middleware/routes in the http hook.\n * stack will run as usual.\n *\n * On the other hand, Socket.io needs to use this method (i.e. the `router:request` event)\n * to simulate a connect-style router since it can't bind dynamic routes ahead of time.\n *\n * Keep in mind that, if `route` is not used, the implementing server is responsible\n * for routing to Sails' default `next(foo)` handler.\n *\n * @param {Request} req\n * @param {Response} res\n * @api private\n */\n\nRouter.prototype.route = function(req, res) {\n  var sails = this.sails;\n  var _privateRouter = this._privateRouter;\n\n  // If sails is `_exiting`, ignore the request.\n  if (sails._exiting) {\n    return;\n  }\n\n  // Provide access to SailsApp instance as `req._sails`.\n  req._sails = req._sails || sails;\n\n  // Note that, at this point, `req` and `res` are just dictionaries containing\n  // the properties of each object that have been built up _so far_.\n  //\n  // Use base req and res definitions to ensure the specified\n  // objects are at least ducktype-able as standard node HTTP\n  // req and req streams.\n  //\n  // Make sure request and response objects have reasonable defaults\n  // (will use the supplied definitions if possible)\n  req = buildReq(req);\n  res = buildRes(req, res);\n\n  // Default to 200 status code for OPTIONS requests.\n  // The built-in Express OPTIONS handler just calls `res.end()` (rather\n  // than `res.send()`), so no status code gets set and our mock res.writeHead\n  // method complains.\n  if (req.method === 'OPTIONS' && !res.statusCode) {\n    res.status(200);\n  }\n\n  // console.log('\\n\\n\\n\\n=======================\\nReceived request to %s %s\\nwith req.body:\\n',req.method,req.url, req.body);\n\n  // Run some basic middleware\n  sails.log.silly('Handling virtual request :: Running virtual querystring parser...');\n  qsParser(req,res, function (err) {\n    if (err) {\n      return res.status(400).send(err && err.stack);\n    }\n\n    // Parse cookies\n    parseCookies(req, res, function(err){\n      if (err) {\n        return res.status(400).send(err && err.stack);\n      }\n\n      // console.log('Ran cookie parser');\n      // console.log('res.writeHead= ',res.writeHead);\n\n      // Load session (if relevant)\n      loadSession(req, res, function (err) {\n        if (err) {\n          return res.status(400).send(err && err.stack);\n        }\n        // console.log('res is now:\\n',res);\n        // console.log('\\n\\n');\n        // console.log('Ran session middleware');\n        // console.log('req.sessionID= ',req.sessionID);\n        // console.log('The loaded req.session= ',req.session);\n\n        sails.log.silly('Handling virtual request :: Running virtual body parser...');\n        bodyParser(req,res, function (err) {\n          if (err) {\n            return res.status(400).send(err && err.stack);\n          }\n\n          // Use our private router to route the request\n          _privateRouter(req, res, function handleUnmatchedNext(err) {\n            //\n            // In the event of an unmatched `next()`, `next('foo')`,\n            // or `next('foo', errorCode)`...\n            //\n\n            // Use the default server error handler\n            if (err) {\n              sails.log.silly('Handling virtual request :: Running final \"error\" handler...');\n              sails.emit('router:request:500', err, req, res);\n              return;\n            }\n\n            // Or the default not found handler\n            sails.log.silly('Handling virtual request :: Running final \"not found\" handler...');\n            sails.emit('router:request:404', req, res);\n            return;\n          });\n        });\n      });\n\n    });\n  });\n\n};\n\n\n\n/**\n * `sails.router.bind()`\n *\n * Bind new route(s)\n *\n * @param {String|RegExp} path\n * @param {String|Object|Array|Function} bindTo\n * @param {String} verb\n * @api private\n */\n\nRouter.prototype.bind = require('./bind');\n\n\n\n/**\n * `sails.router.unbind()`\n *\n * Unbind existing route\n *\n * @param {Object} route\n * @api private\n */\n\nRouter.prototype.unbind = function(routeToRemove) {\n\n  var sails = this.sails;\n\n  // Inform attached servers that route should be unbound\n  sails.emit('router:unbind', routeToRemove);\n\n  // Remove any route which matches the path and verb of the argument\n  _.remove(this._privateRouter.stack, function(layer) {\n    return (layer.route.path === routeToRemove.path && layer.route.methods[routeToRemove.verb] === true);\n  });\n\n};\n\n\n\n/**\n * `sails.router.reset()`\n *\n * Unbind all routes currently attached to the router\n *\n * @api private\n */\n\nRouter.prototype.reset = function() {\n\n  var sails = this.sails;\n\n  // Make sure that all the routes are deleted\n  this._privateRouter.stack = [];\n\n  // Emit reset event to allow attached servers to\n  // unbind all of their routes as well\n  sails.emit('router:reset');\n\n};\n\n\n\n/**\n * `sails.router.flush()`\n *\n * Unbind all current routes, then re-bind everything, re-emitting the routing\n * lifecycle events (e.g. `router:before` and `router:after`)\n *\n * @param {Object} routes - (optional)\n *  If specified, replaces `this.explicitRoutes` before flushing.\n *\n * @api private\n */\n\nRouter.prototype.flush = function(routes) {\n\n  var self = this;\n  var sails = this.sails;\n\n  // Wipe routes\n  this.reset();\n\n  // Fired before static routes are bound\n  sails.emit('router:before');\n\n  // If specified, replace `this.explicitRoutes`\n  if (routes) {\n    this.explicitRoutes = routes;\n  }\n\n  // Updated the sorted route address cache\n  sortedRouteAddresses = sortRouteAddresses(_.keys(this.explicitRoutes));\n\n  // Iterate over each address and bind the route that the address is for.\n  _.each(sortedRouteAddresses, function(address) {\n    var target = self.explicitRoutes[address];\n    var verb = detectVerb(address).verb;\n\n    // If the route address ends in a pattern var (e.g. /:id) or a wildcard (i.e. /*)\n    // and it declares a method that could be used to request an asset, and the route\n    // doesn't explicitly declare `skipAssets` true or false, then it should!\n    var shouldDeclareSkipAssets = (\n      _.isUndefined(target.skipAssets) &&\n      (address.match(/\\/\\*\\/?$/) || address.match(/^r\\|/)) &&\n      (!verb || _.contains(['all', 'get', 'head', 'options'], verb))\n    );\n    if (shouldDeclareSkipAssets) {\n      sails.log.warn('Warning: route `' + address + '` should explicitly declare `skipAssets: true` or `skipAssets: false` to ensure correct handling of assets!');\n      sails.log.warn('See http://sailsjs.com/docs/concepts/routes/url-slugs for more info.');\n      console.log();\n    }\n\n    self.bind(address, target);\n  });\n\n  // Fired after static routes are bound\n  sails.emit('router:after');\n};\n\n\n/**\n * Given a route target configuration, return an action identity for that target.\n * @param  {Dictionary|String} target The route target to get an action identity for\n * @return {String}        An action identity like `user/find`\n */\nRouter.prototype.getActionIdentityForTarget = function getActionIdentityForTarget(target) {\n\n  var actionIdentity;\n\n  // Unwrap { target: '...' } targets.\n  if (target && target.target) {\n    target = target.target;\n  }\n\n  // Handle dictionary targets:\n  // {controller: 'UserController', action: 'create'}\n  // - or -\n  // {action: 'user.create'}\n  if (_.isObject(target) && !_.isArray(target) && !_.isFunction(target)) {\n\n    // Attempt to handle `{controller: 'UserController', action: 'create'}` target.\n    if (target.controller) {\n      if (!target.action) {\n        throw flaverr({name: 'userError', code: 'E_NOT_ACTION_TARGET'}, new Error('If `controller` is specified, `action` must be also!'));\n      }\n\n      actionIdentity = target.controller.replace('Controller', '') + '/' + target.action;\n    }\n    // Attempt to handle `{action: 'user.create'}` target.\n    else if (target.action) {\n      // Get the action identity by lowercasing the value of the `action` property.\n      actionIdentity = target.action;\n    }\n\n    else {\n      throw flaverr({name: 'userError', code: 'E_NOT_ACTION_TARGET'}, new Error('If target is a dictionary, it must contain an `action` property!'));\n    }\n\n    // Bail if the action contains characters other than letters, numbers, dashes and forward slashes.\n    if (!actionIdentity.match(/^[a-zA-Z_\\$]+[a-zA-Z0-9_\\/\\-\\$]*$/)) {\n      // If the action didn't contain weird characters, make a suggestion by removing \"Controller\" and\n      // replacing dots with slashes.\n      var didYouMean = '';\n      var RX_DOESNT_HAVE_ANY_WEIRD_CHARS = /[^a-zA-Z0-9.\\/\\-\\$]/;\n      if (!actionIdentity.match(RX_DOESNT_HAVE_ANY_WEIRD_CHARS)) {\n        didYouMean = ' Did you mean `' + actionIdentity.replace('Controller', '').replace(/\\./g,'/') + '`?';\n      }\n      throw flaverr({name: 'userError', code: 'E_NOT_ACTION_TARGET'}, new Error(\n        '\\nCould not parse invalid action `' + actionIdentity + '`.' + didYouMean + '\\n\\n' +\n        'See http://sailsjs.com/docs/concepts/routes/custom-routes#?controller-action-target-syntax\\n'+\n        'for more info on controller/action and standalone action route syntax.\\n'\n\n      ));\n    }\n\n  }\n\n  // Handle string targets:\n  // 'UserController.create'\n  // - or -\n  // 'user.create'\n  // - or -\n  // 'user/create'\n  else if (_.isString(target)) {\n\n    // Normalize the action identity by removing `Controller` and replacing `.` with `/`\n    actionIdentity = target.replace(/Controller/,'').replace(/\\./g,'/');\n\n    // If the result contains anything other than letters, numbers, dashes, underscores or forward-slashes, bail.\n    if (!actionIdentity.match(/^[a-zA-Z_\\$]+[a-zA-Z0-9_\\/\\-\\$]*$/)) {\n      throw flaverr({name: 'userError', code: 'E_NOT_ACTION_TARGET'}, new Error(\n        '\\nCould not parse invalid action `' + target + '`.\\n'+\n        'See http://sailsjs.com/docs/concepts/routes/custom-routes#?controller-action-target-syntax\\n'+\n        'for more info on controller/action and standalone action route syntax.\\n'\n      ));\n    }\n\n  }\n\n  else if (_.isArray(target)) {\n\n    actionIdentity = (function(){\n      var actionTarget = _.find(target, function(targetComponent) {\n        return targetComponent.action;\n      });\n      if (actionTarget) {\n        return getActionIdentityForTarget(actionTarget);\n      }\n      throw flaverr({name: 'userError', code: 'E_NOT_ACTION_TARGET'}, new Error('Target was an array without any items containing an action!'));\n    })();\n\n  }\n\n  else {\n    throw flaverr({name: 'userError', code: 'E_NOT_ACTION_TARGET'}, new Error('Target must be a dictionary or string!'));\n  }\n\n  // Replace all dots with slashes, and ensure lowercase.\n  actionIdentity = actionIdentity.replace(/\\./g, '/').toLowerCase();\n\n  return actionIdentity;\n};\n\n\n\n\n\n\n\n////////////////////////////////////////////////////////////////////////////////////////////////////\n//\n// ||     Private functions\n// \\/\n//\n////////////////////////////////////////////////////////////////////////////////////////////////////\n\n// Extremely simple query string parser (`req.query`)\nfunction qsParser(req,res,next) {\n  var queryStringPos = req.url.indexOf('?');\n  if (queryStringPos !== -1) {\n    req.query = _.merge(req.query, QS.parse(req.url.substr(queryStringPos + 1)));\n  }\n  else {\n    req.query = req.query || {};\n  }\n  next();\n}\n// Extremely simple body parser (`req.body`)\nfunction bodyParser (req, res, next) {\n\n  // Set up a mock `req.file()` clarifying that req.file() is not available\n  // outside of the context of Skipper (i.e. in this case, most commonly from\n  // socket.io virtual requests).\n  req.file = function fileUploadsNotAvailable(){\n    return res.status(500).send('Streaming file uploads via `req.file()` are only available over HTTP with Skipper.');\n  };\n\n  var bodyBuffer='';\n  if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'DELETE'){\n    req.body = _.extend({}, req.body);\n    return next();\n  }\n\n  // Ensure that `req` is a readable stream at this point\n  if ( !(req instanceof Readable) ) {\n    return next(new Error('Sails Internal Error: `req` should be a Readable stream by the time `route()` is called'));\n  }\n\n  req.on('readable', function() {\n    var chunk;\n    while (null !== (chunk = req.read())) {\n      bodyBuffer += chunk;\n    }\n  });\n  req.on('end', function() {\n\n    var parsedBody;\n    try {\n      parsedBody = JSON.parse(bodyBuffer);\n    } catch (unusedErr) {}\n\n    // TODO -- replace _.merge() with a call to merge-dictionaries module?\n    req.body = _.merge(req.body, parsedBody);\n    next();\n  });\n}\n\n\n\n\n\n\n\n/**\n * [parseCookies description]\n * @param  {[type]}   req  [description]\n * @param  {[type]}   res  [description]\n * @param  {Function} next [description]\n * @return {[type]}        [description]\n */\nfunction parseCookies (req, res, next){\n\n  // req._sails.log.verbose('Parsing cookie:',req.headers.cookie);\n\n  if (req._sails._privateCpMware) {\n    // Run the middleware\n    return req._sails._privateCpMware(req, res, next);\n  }\n\n  // Otherwise don't even worry about it.\n  return next();\n}\n\n\n\n/**\n * [loadSession description]\n * @param  {[type]}   req  [description]\n * @param  {[type]}   res  [description]\n * @param  {Function} next [description]\n * @return {[type]}        [description]\n */\nfunction loadSession (req, res, next){\n\n  // If a session store is configured, and we haven't deliberately disabled\n  // session support for this request by setting the \"nosession\" header,\n  // hook up the store up as `req.session` by passing it down to the\n  // session middleware.\n  if (req._sails._privateSessionMiddleware && !req.headers.nosession) {\n\n    // Access store preconfigured session middleware as a private property on the app instance.\n    return req._sails._privateSessionMiddleware(req, res, next);\n  }\n\n  // Otherwise don't even worry about it.\n  return next();\n}\n\n\n"
  },
  {
    "path": "lib/router/mock-req.js",
    "content": "/**\n * mock-req\n * v0.2.0\n *\n * https://www.npmjs.com/package/mock-req\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 diachedelic\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of\n * this software and associated documentation files (the \"Software\"), to deal in\n * the Software without restriction, including without limitation the rights to\n * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\n * the Software, and to permit persons to whom the Software is furnished to do so,\n * subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\n * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\n * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n/* eslint-disable */\nmodule.exports = MockIncomingMessage;\n\nvar Transform = require('stream').Transform,\n  util = require('util');\n\nfunction MockIncomingMessage(options) {\n  var self = this;\n  options = options || {};\n\n  Transform.call(this);\n  this._writableState.objectMode = true;\n  this._readableState.objectMode = false;\n\n  // Copy unreserved options\n  var reservedOptions = [\n    'method',\n    'url',\n    'headers',\n    'rawHeaders'\n  ];\n\n  Object.keys(options).forEach(function(key) {\n    if (reservedOptions.indexOf(key) === -1)\n      self[key] = options[key];\n  });\n\n  this.method = options.method || 'GET';\n  this.url = options.url || '';\n\n  // Set header names\n  this.headers = {};\n  this.rawHeaders = [];\n  if (options.headers)\n    Object.keys(options.headers).forEach(function(key) {\n      var val = options.headers[key];\n\n      if(val !== undefined) {\n        if (typeof val !== 'string') {\n          val += '';\n        }\n\n        self.headers[key.toLowerCase()] = val;\n        self.rawHeaders.push(key);\n        self.rawHeaders.push(val);\n      }\n    });\n\n  // Auto-end when no body\n  if (this.method === 'GET' || this.method === 'HEAD' || this.method === 'DELETE')\n    this.end();\n}\n\nutil.inherits(MockIncomingMessage, Transform);\n\nMockIncomingMessage.prototype._transform = function(chunk, encoding, next) {\n  if (this._failError)\n    return this.emit('error', this._failError);\n\n  if (typeof chunk !== 'string' && !Buffer.isBuffer(chunk))\n    chunk = JSON.stringify(chunk);\n\n  this.push(chunk);\n\n  next();\n};\n\n// Causes the request to emit an error when the body is read.\nMockIncomingMessage.prototype._fail = function(error) {\n  this._failError = error;\n};\n\n/* eslint-enable */\n"
  },
  {
    "path": "lib/router/mock-res.js",
    "content": "/**\n * mock-res\n * v0.3.0\n *\n * https://www.npmjs.com/package/mock-res\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 diachedelic\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of\n * this software and associated documentation files (the \"Software\"), to deal in\n * the Software without restriction, including without limitation the rights to\n * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\n * the Software, and to permit persons to whom the Software is furnished to do so,\n * subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\n * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\n * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n/* eslint-disable */\nmodule.exports = MockServerResponse;\n\nvar Transform = require('stream').Transform,\n  util = require('util'),\n  STATUS_CODES = require('http').STATUS_CODES;\n\nfunction MockServerResponse(finish) {\n  Transform.call(this);\n\n  this.statusCode = 200;\n  this.statusMessage = STATUS_CODES[this.statusCode];\n\n  this._header = this._headers = {};\n  if (typeof finish === 'function')\n    this.on('finish', finish);\n}\n\nutil.inherits(MockServerResponse, Transform);\n\nMockServerResponse.prototype._transform = function(chunk, encoding, next) {\n  this.push(chunk);\n  next();\n};\n\nMockServerResponse.prototype.setHeader = function(name, value) {\n  this._headers[name.toLowerCase()] = value;\n};\n\nMockServerResponse.prototype.getHeader = function(name) {\n  return this._headers[name.toLowerCase()];\n};\n\nMockServerResponse.prototype.removeHeader = function(name) {\n  delete this._headers[name.toLowerCase()];\n};\n\nMockServerResponse.prototype.writeHead = function(statusCode, reason, headers) {\n  if (arguments.length == 2 && typeof arguments[1] !== 'string') {\n    headers = reason;\n    reason = undefined;\n  }\n  this.statusCode = statusCode;\n  this.statusMessage = reason || STATUS_CODES[statusCode] || 'unknown';\n  if (headers) {\n    for (var name in headers) {\n      this.setHeader(name, headers[name]);\n    }\n  }\n};\n\nMockServerResponse.prototype._getString = function() {\n  return Buffer.concat(this._readableState.buffer).toString();\n};\n\nMockServerResponse.prototype._getJSON = function() {\n  return JSON.parse(this._getString());\n};\n\n/* Not implemented:\nMockServerResponse.prototype.writeContinue()\nMockServerResponse.prototype.setTimeout(msecs, callback)\nMockServerResponse.prototype.headersSent\nMockServerResponse.prototype.sendDate\nMockServerResponse.prototype.addTrailers(headers)\n*/\n\n/* eslint-enable */\n"
  },
  {
    "path": "lib/router/req.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar defaultsDeep = require('merge-defaults');// « TODO: Get rid of this\nvar MockReq = require('./mock-req');// «FUTURE: consolidate that into this file\nvar parseurl = require('parseurl');\n\n/**\n * Factory which builds generic Sails request object (i.e. `req`).\n *\n * This generic implementation of `req` forms the basis for\n * Sails' transport-agnostic support of Connect/Express\n * middleware.  Used by hooks (i.e. sockets) but also for\n * tests-- both at the app-level and in Sails core.\n *\n * @param {Dictionary} _req\n *        the properties of this simulated request object that\n *        have been built up _so far_.\n *\n * @return {Request} simulated HTTP request object\n * @idempotent\n */\n\nmodule.exports = function buildRequest (_req) {\n\n  // Make sure _req is not undefined\n  _req = _req||{};\n\n  // Start our request object, which will be built by inheriting/transforming\n  // properties of _req and adding some spice of our own\n  var req;\n\n  // Attempt to parse the URL in _req, so that we can get the querystring\n  // and path.  (But if it fails for any reason, ignore the error and fall back\n  // to an empty dictionary.)\n  var parsedUrl;\n  try {parsedUrl = parseurl(_req) || {};}\n  catch (unusedErr) {parsedUrl = {};}\n\n  // If `_req` appears to be a stream (duck-typing), then don't try\n  // and turn it into a mock stream again.\n  if (typeof _req === 'object' && _req.read) {\n    req = _req;\n  }\n  else {\n\n    if (_req.headers && typeof _req.headers === 'object') {\n      for (let headerKey of Object.keys(_req.headers)) {\n        // Strip undefined headers\n        if (undefined === _req.headers[headerKey]) {\n          delete _req.headers[headerKey];\n          continue;\n        }//•\n        // Make sure all remaining headers are strings\n        if (typeof _req.headers[headerKey] !== 'string') {\n          try {\n            _req.headers[headerKey] = ''+_req.headers[headerKey];\n            // FUTURE: This behavior is likely being relied upon by apps, so we can't just change it.\n            // But in retrospect, it would probably be better to straight-up reject this here if it's not\n            // a string, since HTTP header values are always supposed to be strings; or at least primitives.\n            // So maybe reject non-primitives, reject `null`, and then accept primitives, but be smart about\n            // this, especially in the context of what the client is doing.\n          } catch (unusedErr) {\n            delete _req.headers[headerKey];\n          }\n        }\n      }//∞\n    }//ﬁ\n\n    // Create a mock IncomingMessage stream.\n    req = new MockReq({\n      method: _req && (_.isString(_req.method) ? _req.method.toUpperCase() : 'GET'),\n      headers: _req && _req.headers || {},\n      url: _req && _req.url\n    });\n\n    // Add .get() and .header() methods to match express 3\n    req.get = req.header = function (name) {\n      switch (name = name.toLowerCase()) {\n        case 'referer':\n        case 'referrer':\n          return this.headers.referrer || this.headers.referer;\n        default:\n          return this.headers[name];\n      }\n    };\n\n    // Now pump client request body to the mock IncomingMessage stream (req)\n    // Req stream ends automatically if this is a GET or HEAD or DELETE request\n    // (since there is no request body in that case) so no need to do it again.\n    if (req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'DELETE') {\n\n      // Only write the body if there IS a body.\n      if (req.body) {\n        req.write(req.body);\n      }\n      req.end();\n    }\n  }\n\n  // Track request start time\n  req._startTime = new Date();\n\n  ////////////////////////////////////////////////////////////////////////////////\n  // Note that other core methods _could_ be added here for use w/ the virtual\n  // router.  But as per convo w/ dougwilson, the same _cannot_ be done for HTTP\n  // requests coming out of Express.  They would either have to (a) rely on modifying\n  // the HTTP request (IncomingMessage) prototype, or (B) rely on context (i.e. `this`),\n  // which would require `_.bind()`-ing them to avoid issues when triggered from\n  // userland code. And re: (B) at that point, the performance impact is effectively\n  // the same as if they were attached on the fly on a per-request basis.\n  //\n  // So we only initially attach `req.*` methods & properties here which are _not_\n  // already built-in to the mock request, and which are _not_ already taken care of\n  // by hooks, AND which don't rely on `res` (because it hasn't been built yet).\n  ////////////////////////////////////////////////////////////////////////////////\n\n  // Provide defaults for other request state and methods\n  req = defaultsDeep(req, {\n    params: [],\n    query: (_req && _req.query) || require('querystring').parse(parsedUrl.query) || {},\n    body: (_req && _req.body) || {},\n    param: function(paramName, defaultValue) {\n\n      var key;\n      var params = {};\n      for (key in (req.params || {}) ) {\n        params[key] = params[key] || req.params[key];\n      }\n      for (key in (req.query || {}) ) {\n        params[key] = params[key] || req.query[key];\n      }\n      for (key in (req.body || {}) ) {\n        params[key] = params[key] || req.body[key];\n      }\n\n      // Grab the value of the parameter from the appropriate place\n      // and return it\n      if (typeof params[paramName] !== 'undefined') {\n        return params[paramName];\n      } else {\n        return defaultValue;\n      }\n\n    },\n    wantsJSON: (_req && _req.wantsJSON === false) ? false : true,\n    method: 'GET',\n    originalUrl: _req.originalUrl || _req.url,\n    path: _req.path || parsedUrl.pathname\n  }, _req||{});\n\n  return req;\n};\n"
  },
  {
    "path": "lib/router/res.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar http = require('http');\nvar Transform = require('stream').Transform;\nvar _ = require('@sailshq/lodash');\nvar flaverr = require('flaverr');\nvar MockRes = require('./mock-res');// «FUTURE: consolidate that into this file\n\n\n/**\n * Ensure that response object has a minimum set of reasonable defaults\n * Used primarily as a test fixture.\n *\n * @api private\n * @idempotent\n */\n\nmodule.exports = function _buildResponse (req, _res) {\n  _res = _res||{};\n  req = req||{};\n\n  var res;\n\n  // If `_res` appears to be a stream (duck-typing), then don't try\n  // and turn it into a mock stream again.\n  if (typeof _res === 'object' && _res.end) {\n    res = _res;\n  }\n  else {\n    res = new MockRes();\n    delete res.statusCode;\n  }\n\n\n  // Ensure res.headers and res.locals exist.\n  res = _.extend(res, {locals: {}, headers: {}, _headers: {}});\n  res = _.extend(res, _res);\n\n  // Now that we're sure `res` is a Transform stream, we'll handle the two different\n  // approaches which a user of the virtual request interpreter might have taken:\n\n  // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n  // (1) Providing a callback function (`_clientCallback`)\n  //\n  // If a `_clientCallback` function was provided, also pipe `res` into a\n  // fake clientRes stream where the response `body` will be buffered.\n  if (res._clientCallback) {\n\n    // If `res._clientRes` WAS NOT provided, then create one\n    if (!res._clientRes) {\n      res._clientRes = new MockClientResponse();\n    }\n\n    // Session is saved automatically since the virtual request interpreter is\n    // using `express-session` directly as of https://github.com/balderdashy/sails/commit/58e93f5a5f2e667e3fbeddf5b4b356f813e3555e.\n\n    // The stream should trigger the callback when it finishes or errors.\n    res._clientRes.on('finish', function() {\n      return res._clientCallback(res._clientRes);\n    });\n    res._clientRes.on('error', function(err) {\n      err = err || new Error('Error on response stream');\n      res._clientRes.statusCode = 500;\n      res._clientRes.body = err;\n      return res._clientCallback(res._clientRes);\n    });\n\n  }\n  // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n\n  // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n  // (2) Providing a Writable stream (`_clientRes`)\n  //\n  // If a `_clientRes` response Transform stream was provided, pipe `res` directly to it.\n  if (res._clientRes) {\n    res.pipe(res._clientRes);\n  }\n  //\n  // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n\n\n  // Track whether headers have been written\n  // (TODO: pull all this into mock-res via a PR)\n\n  // res.writeHead() is wrapped in closure by the `on-header` module,\n  // but it still needs the underlying impl\n  res.writeHead = function ( /* statusCode, [reasonPhrase], headers */) {\n    // console.log('\\n\\n• res.writeHead(%s)', Array.prototype.slice.call(arguments));\n    var statusCode = +arguments[0];\n\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    // FUTURE: Actually use the \"reasonPhrase\", if one was provided.\n    // ```\n    // var reasonPhrase = (function(){\n    //   if (arguments[2] && _.isString(arguments[1])) {\n    //     return arguments[1];\n    //   }\n    //   return undefined;\n    // })();\n    // ```\n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n    var newHeaders = (function (){\n      if (arguments[2] && _.isObject(arguments[2])) {\n        return arguments[2];\n      }\n      return arguments[1];\n    })();\n\n    if (!statusCode) {\n      throw new Error('`statusCode` must be passed to res.writeHead().');\n    }\n    // Set status code\n    res.statusCode = statusCode;\n\n    // Ensure `._headers` have been merged into `.headers`\n    _.extend(res.headers, res._headers);\n\n    if (newHeaders) {\n      if (!_.isObject(newHeaders)) {\n        throw new Error('`headers` must be passed to res.writeHead() as an object. Got: '+util.inspect(newHeaders, false, null));\n      }\n      // Set new headers\n      _.extend(res.headers, newHeaders);\n    }\n\n    // Set status code and headers on the `_clientRes` stream so they are accessible\n    // to the provider of that stream.\n    // (this has to happen in `send()` because the code/headers might have just changed)\n    if (res._clientRes) {\n      // console.log('Setting headers on clientRes- res.headers = ',res.headers);\n      res._clientRes.headers = res.headers;\n      res._clientRes.statusCode = res.statusCode;\n    }\n\n  };\n\n\n  // Wrap res.write() and res.end() to get them to call writeHead()\n  var prevWrite = res.write;\n  res.write = function (){\n    res.writeHead(res.statusCode, _.extend(res._headers,res.headers));\n    // console.log('res.write():: called writeHead with headers=',_.extend(res._headers,res.headers));\n    prevWrite.apply(res, Array.prototype.slice.call(arguments));\n  };\n  var prevEnd = res.end;\n  res.end = function (){\n    res.writeHead(res.statusCode, _.extend(res._headers,res.headers));\n    // console.log('our res.end() was triggered');\n    // console.log('res.end():: called writeHead with headers=',_.extend(res._headers,res.headers));\n    prevEnd.apply(res, Array.prototype.slice.call(arguments));\n  };\n\n\n  // we get `setHeader` from mock-res\n  // see http://nodejs.org/api/http.html#http_response_setheader_name_value\n  //\n  // Usage:\n  // response.setHeader(\"Set-Cookie\", [\"type=ninja\", \"language=javascript\"]);\n\n  // If we ever need to wrap it...\n  //\n  // var prevSetHeader = res.setHeader;\n  // res.setHeader = function (){\n  //   prevSetHeader.apply(res, Array.prototype.slice.call(arguments));\n  // };\n\n  // res.status()\n  res.status = res.status || function _statusShim (statusCode) {\n    res.statusCode = statusCode;\n    return res;\n  };\n\n  // res.sendStatus()\n  // (send a text representation of a status code)\n  res.sendStatus = res.sendStatus || function _sendStatusShim (statusCode) {\n\n    // Get the status codes from the HTTP module\n    var statusCodes = http.STATUS_CODES;\n\n    // If this is a known code, use its name (e.g. \"FORBIDDEN\" or \"OK\").\n    // Otherwise, just turn the number into a string.\n    var body = statusCodes[statusCode] || String(statusCode);\n\n    // Set the response status code.\n    res.statusCode = statusCode;\n\n    // Send the response.\n    return res.send(body);\n  };\n\n  // res.send()\n  res.send = res.send || function _sendShim (data, noLongerSupported) {\n    if (!_.isUndefined(noLongerSupported)) {\n      throw new Error('The 2-ary usage of `res.send()` is no longer supported in Express 4/Sails v1.  Please use `res.status(statusCode).send(body)` instead.');\n    }\n\n    // Don't allow users to respond/redirect more than once per request\n    // FUTURE: prbly move this check to our `res.writeHead()` impl\n    try {\n      onlyAllowOneResponse(res);\n    }\n    catch (e) {\n      if (req._sails && req._sails.log && req._sails.log.error) {\n        req._sails.log.error(e);\n        return;\n      }\n      console.error(e);\n      return;\n    }\n\n    // Ensure charset is set\n    res.charset = res.charset || 'utf-8';\n\n    // Ensure headers are set\n    _.extend(res.headers, res._headers);\n\n    // Ensure statusCode is set\n    res.statusCode = res.statusCode || 200;\n\n    // if a `_clientCallback` was specified, we'll skip the streaming stuff for res.send().\n    if (res._clientCallback) {\n\n      // Hard-code `res.body` rather than writing to the stream.\n      // (but don't include body if it is empty)\n      if (!_.isUndefined(data)) {\n        res.body = data;\n        // Then expose on res._clientRes.body\n        res._clientRes.body = res.body;\n      }\n\n      // End the `res` stream (which will in turn end the `res._clientRes` stream)\n      res.end();\n      return;\n    }\n\n    //\n    // Otherwise, the hook using the interpreter must have provided us with a `res._clientRes` stream,\n    // so we'll need to serialize everything to work w/ that stream.\n    //\n\n    // console.log('\\n---\\nwriting to clientRes stream...');\n    // console.log('res.headers =>',res.headers);\n    // console.log('res._headers =>',res._headers);\n\n    // Write body to `res` stream\n    if (!_.isUndefined(data)) {\n\n      try {\n\n        var toWrite;\n\n        // If the data is already a string, don't stringify it.\n        // (This allows for sending plain text, XML, etc.)\n        if (_.isString(data)) {\n          toWrite = data;\n        }\n        else {\n          try {\n            toWrite = JSON.stringify(data);\n            if (!res.get('content-type')) {\n              res.set('content-type', 'application/json');\n            }\n          }\n          catch(e) {\n            throw new Error(\n              'Failed to stringify specified JSON response body :: ' + util.inspect(data) +\n              '\\nError:\\n' + util.inspect(e)\n            );\n          }\n          // if (process.env.NODE_ENV !== 'production') {\n          //   toWrite = e.message;\n          // }\n        }//>-\n\n        res.write(toWrite);\n\n      } catch (e) {\n        if (req._sails && req._sails.log && req._sails.log.error) {\n          req._sails.log.error(e);\n        }\n        else {\n          console.error(e);\n        }\n        res.statusCode = 500;\n      }\n    }//</if data was defined>\n\n    // End the `res` stream.\n    res.end();\n  };\n\n  // res.json()\n  res.json = res.json || function _jsonShim (data, noLongerSupported) {\n    if (!_.isUndefined(noLongerSupported)) {\n      throw new Error('The 2-ary usage of `res.json()` is no longer supported in Express 4/Sails v1.  Please use `res.status(statusCode).json(body)` instead.');\n    }\n\n    // If data is a string, JSON stringify it.\n    // (Otherwise, we can just rely on `send` to do that for us.)\n    if (_.isString(data)) {\n      data = JSON.stringify(data);\n      res.set('content-type', 'application/json');\n    }\n\n    return res.status(res.statusCode || 200).send(data);\n  };\n\n  // res.render()\n  res.render = res.render || function _renderShim (relativeViewPath, locals, cb) {\n    if (_.isFunction(locals)) {\n      cb = locals;\n      locals = {};\n    }\n\n    try {\n      if (!req._sails) {\n        throw new Error('Cannot call res.render() - `req._sails` was not attached');\n      }\n      if (!req._sails.renderView) {\n        throw new Error('Cannot call res.render() - `req._sails.renderView` was not attached (perhaps `views` hook is not enabled?)');\n      }\n\n      res.set('content-type', 'text/html');\n\n      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n      // TODO:\n      // Instead of this shim, turn `sails.renderView` into something like\n      // `sails.hooks.views.render()`, and then call it.\n      throw flaverr({statusCode: 501}, new Error('Not implemented in core yet'));\n      //\n      // Instead, do something like the following:\n      // ```\n      // var html;\n      // // ...\n      // if (cb) {\n      //   return cb(undefined, html);\n      // }\n      // else {\n      //   return res.status(200).send(html);\n      // }\n      // ```\n      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n    } catch (e) {\n      if (cb) { return cb(e); }\n\n      // NOTE: We don't try to use res.serverError() here because we might\n      // _already_ be in the midst of a res.serverError() call.\n\n      if (req._sails && req._sails.log && req._sails.log.error) {\n        req._sails.log.error('res.render() failed: ', e);\n      }\n      else {\n        console.error('res.render() failed: ', e);\n      }\n\n      if (process.env.NODE_ENV === 'production') { return res.status(e.statusCode||500).send(e.message); }\n      else { return res.status(e.statusCode||500).send(); }\n    }\n\n  };\n\n  // res.redirect()\n  res.redirect = res.redirect || function _redirectShim (address, noLongerSupported) {\n    if (!_.isUndefined(noLongerSupported)) {\n      throw new Error('The 2-ary usage of `res.redirect()` is no longer supported in Express 4/Sails v1.  Please use `res.status(statusCode).redirect(address)` instead.');\n    }\n\n    // For familiarity, set content-type header:\n    res.set('content-type', 'text/html');\n\n    // Set location header\n    res.set('Location', address);\n\n    return res.status(res.statusCode||302).send('Redirecting to '+encodeURI(address));\n  };\n\n\n\n  /**\n   * res.set( headerName, value )\n   *\n   * @param {[type]} headerName [description]\n   * @param {[type]} value   [description]\n   */\n  res.set = function (headerName, value) {\n    res.headers = res.headers || {};\n    res.headers[headerName] = value;\n    return this;\n  };\n\n  /**\n   * res.get( headerName )\n   *\n   * @param  {[type]} headerName [description]\n   * @return {[type]}            [description]\n   */\n  res.get = function (headerName) {\n    return res.headers && res.headers[headerName];\n  };\n\n\n\n  return res;\n\n\n};\n\n\n/**\n * NOTE: ALL RESPONSES (INCLUDING REDIRECTS) ARE PREVENTED ONCE THE RESPONSE HAS BEEN SENT!!\n * Even though this is not strictly required with sockets, since res.redirect()\n * is an HTTP-oriented method from Express, it's important to maintain consistency.\n *\n * @api private\n */\nfunction onlyAllowOneResponse (res) {\n  if (res._virtualResponseStarted) {\n    throw new Error('Cannot write to response more than once');\n  }\n  res._virtualResponseStarted = true;\n}\n\n\n// The constructor for clientRes stream\n// (just a normal transform stream)\nfunction MockClientResponse() {\n  Transform.call(this);\n}\nutil.inherits(MockClientResponse, Transform);\nMockClientResponse.prototype._transform = function(chunk, encoding, next) {\n  this.push(chunk);\n  next();\n};\n"
  },
  {
    "path": "lib/util/check-origin-url.js",
    "content": "/**\n * Module dependencies\n */\n\nvar url = require('url');\nvar _ = require('@sailshq/lodash');\nvar flaverr = require('flaverr');\nvar util = require('util');\n\n\n/**\n * checkOriginUrl()\n *\n * @param {String} originUrl\n *        The origin URL to check.\n *        (This is used when parsing the relevant config from within `sails.config.security`\n *         or `sails.config.sockets`.)\n *\n * @throws {Error} if not valid\n *   @property {String} code  (==='E_INVALID')\n */\n\nmodule.exports = function checkOriginUrl(originUrl) {\n\n  if (!_.isString(originUrl) || originUrl === '') {\n    throw flaverr('E_INVALID', new Error('Must specify a non-empty string, but instead got: '+util.inspect(originUrl, {depth: null})));\n  }\n\n  if (!originUrl.match(/^https?:\\/\\//)) {\n    throw flaverr('E_INVALID', new Error('Must specify a protocol like http:// or https://, but instead got: '+originUrl));\n  }\n\n  // Now do a mostly-correct parse of the URL.\n  var parsedOriginUrl = url.parse(originUrl);\n\n  var isHttps = parsedOriginUrl.protocol === 'https:';\n\n  if (isHttps && parsedOriginUrl.port === '443') {\n    throw flaverr('E_INVALID', new Error('Should not explicitly specify port 443 with https:// (it is implied).  But instead got: '+originUrl));\n  }\n  if (!isHttps && parsedOriginUrl.port === '80') {\n    throw flaverr('E_INVALID', new Error('Should not explicitly specify port 80 with https:// (it is implied).  But instead got: '+originUrl));\n  }\n\n  // Ensure there is no path or query string or fragment or anything like that.\n  if (parsedOriginUrl.pathname !== '/' || parsedOriginUrl.path !== '/') {\n    throw flaverr('E_INVALID', new Error('Should not specify a path, query string, URL fragment, or anything like that (but instead, got `'+originUrl+'`)'));\n  }\n\n  // Ensure there is no trailing slice\n  var lastCharacter = originUrl.slice(-1);\n  if (lastCharacter === '/') {\n    throw flaverr('E_INVALID', new Error('Should not specify a trailing slash, but instead got: '+originUrl));\n  }\n\n};\n"
  },
  {
    "path": "lib/util/deep-extend.js",
    "content": "// NOTE: This inlined version of the deep-extend npm package is only used by rc (which was also inlined)\n\n/*!\n * @description Recursive object extending\n * @author Viacheslav Lotsmanov <lotsmanov89@gmail.com>\n * @license MIT\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2013-2018 Viacheslav Lotsmanov\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of\n * this software and associated documentation files (the \"Software\"), to deal in\n * the Software without restriction, including without limitation the rights to\n * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\n * the Software, and to permit persons to whom the Software is furnished to do so,\n * subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\n * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\n * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\n'use strict';\nvar deepExtend;\n\n/**\n * Extending object that entered in first argument.\n *\n * Returns extended object or false if have no target object or incorrect type.\n *\n * If you wish to clone source object (without modify it), just use empty new\n * object as first argument, like this:\n *   deepExtend({}, yourObj_1, [yourObj_N]);\n */\ndeepExtend = module.exports = function (/*obj_1, [obj_2], [obj_N]*/) {\n\n  function isSpecificValue(val) {\n    return (\n      val instanceof Buffer\n      || val instanceof Date\n      || val instanceof RegExp\n    ) ? true : false;\n  }\n\n  function cloneSpecificValue(val) {\n    if (val instanceof Buffer) {\n      var x = Buffer.alloc\n        ? Buffer.alloc(val.length)\n        : new Buffer(val.length);\n      val.copy(x);\n      return x;\n    } else if (val instanceof Date) {\n      return new Date(val.getTime());\n    } else if (val instanceof RegExp) {\n      return new RegExp(val);\n    } else {\n      throw new Error('Unexpected situation');\n    }\n  }\n\n  /**\n   * Recursive cloning array.\n   */\n  function deepCloneArray(arr) {\n    var clone = [];\n    arr.forEach(function (item, index) {\n      if (typeof item === 'object' && item !== null) {\n        if (Array.isArray(item)) {\n          clone[index] = deepCloneArray(item);\n        } else if (isSpecificValue(item)) {\n          clone[index] = cloneSpecificValue(item);\n        } else {\n          clone[index] = deepExtend({}, item);\n        }\n      } else {\n        clone[index] = item;\n      }\n    });\n    return clone;\n  }\n\n  function safeGetProperty(object, property) {\n    return property === '__proto__' ? undefined : object[property];\n  }\n\n  if (arguments.length < 1 || typeof arguments[0] !== 'object') {\n    return false;\n  }\n\n  if (arguments.length < 2) {\n    return arguments[0];\n  }\n\n  var target = arguments[0];\n\n  // convert arguments to array and cut off target object\n  var args = Array.prototype.slice.call(arguments, 1);\n\n  var val;\n  var src;\n  var clone;// eslint-disable-line no-unused-vars\n\n  args.forEach(function (obj) {\n    // skip argument if isn't an object, is null, or is an array\n    if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {\n      return;\n    }\n\n    Object.keys(obj).forEach(function (key) {\n      src = safeGetProperty(target, key); // source value\n      val = safeGetProperty(obj, key); // new value\n\n      // recursion prevention\n      if (val === target) {\n        return;\n\n      /**\n       * if new value isn't object then just overwrite by new value\n       * instead of extending.\n       */\n      } else if (typeof val !== 'object' || val === null) {\n        target[key] = val;\n        return;\n\n      // just clone arrays (and recursive clone objects inside)\n      } else if (Array.isArray(val)) {\n        target[key] = deepCloneArray(val);\n        return;\n\n      // custom cloning and overwrite for specific objects\n      } else if (isSpecificValue(val)) {\n        target[key] = cloneSpecificValue(val);\n        return;\n\n      // overwrite by new value if source isn't object or array\n      } else if (typeof src !== 'object' || src === null || Array.isArray(src)) {\n        target[key] = deepExtend({}, val);\n        return;\n\n      // source value and new value is objects both, extending...\n      } else {\n        target[key] = deepExtend(src, val);\n        return;\n      }\n    });\n  });\n\n  return target;\n};\n"
  },
  {
    "path": "lib/util/detect-verb.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\n\n\n/**\n * Detect HTTP verb in an expression like:\n * `get baz`    or     `get /foo/baz`\n *\n * @api private\n */\n\nmodule.exports = function (haystack) {\n  var verbExpr = /^\\s*(all|get|post|put|delete|trace|options|connect|patch|head)\\s+/i;\n  var verbSpecified = _.last(haystack.match(verbExpr) || []) || '';\n  verbSpecified = verbSpecified.toLowerCase();\n\n  // If a verb was specified, eliminate the verb from the original string\n  if (verbSpecified) {\n    haystack = haystack.replace(verbExpr,'').trim();\n  } else {\n    haystack = haystack.trim();\n  }\n\n  return {\n    verb: verbSpecified,\n    original: haystack,\n    path: haystack\n  };\n};\n"
  },
  {
    "path": "lib/util/rc.js",
    "content": "// Note: This is an inlined version of the rc npm package.\n/*!\n *\n * @description Recursive object extending\n * @author Viacheslav Lotsmanov <lotsmanov89@gmail.com>\n * @license MIT\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2013-2018 Viacheslav Lotsmanov\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of\n * this software and associated documentation files (the \"Software\"), to deal in\n * the Software without restriction, including without limitation the rights to\n * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\n * the Software, and to permit persons to whom the Software is furnished to do so,\n * subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\n * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\n * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n*/\n\n/**\n * Module dependencies\n */\n\n\n'use strict';\nvar fs   = require('fs');\nvar ini  = require('ini');\nvar path = require('path');\nvar etc = '/etc';\nvar win = process.platform === 'win32';\nvar home = win ? process.env.USERPROFILE : process.env.HOME;\nvar deepExtend = require('./deep-extend.js');\n\nmodule.exports = function rc(name, defaults, argv, parse) {\n\n\n  var parseFn = function (content) {\n    //if it ends in .json or starts with { then it must be json.\n    //must be done this way, because ini accepts everything.\n    //can't just try and parse it and let it throw if it's not ini.\n    //everything is ini. even json with a syntax error.\n\n    if(/^\\s*{/.test(content)) {\n      return JSON.parse(content);\n    }\n    return ini.parse(content);\n\n  };\n\n  var fileFn = function () {\n    var args = [].slice.call(arguments).filter(function (arg) { return arg !== null;});\n\n    //path.join breaks if it's a not a string, so just skip this.\n    for(var i in args) {\n      if('string' !== typeof args[i]) {\n        return;\n      }\n    }\n\n    var file = path.join.apply(null, args);\n    try {\n      return fs.readFileSync(file,'utf-8');\n    } catch (unusedErr) {\n      return;\n    }\n  };\n\n  var jsonFn = function () {\n    var content = fileFn.apply(null, arguments);\n    return content ? parseFn(content) : null;\n  };\n\n  var envFn = function (prefix, env) {\n    env = env || process.env;\n    var obj = {};\n    var l = prefix.length;\n    for(var k in env) {\n      if(k.toLowerCase().indexOf(prefix.toLowerCase()) === 0) {\n\n        var keypath = k.substring(l).split('__');\n\n        // Trim empty strings from keypath array\n        var _emptyStringIndex;\n        while ((_emptyStringIndex=keypath.indexOf('')) > -1) {\n          keypath.splice(_emptyStringIndex, 1);\n        }\n\n        var cursor = obj;\n        keypath.forEach(function _buildSubObj(_subkey,i){\n\n          // (check for _subkey first so we ignore empty strings)\n          // (check for cursor to avoid assignment to primitive objects)\n          if (!_subkey || typeof cursor !== 'object') {\n            return;\n          }\n\n          // If this is the last key, just stuff the value in there\n          // Assigns actual value from env variable to final key\n          // (unless it's just an empty string- in that case use the last valid key)\n          if (i === keypath.length-1) {\n            cursor[_subkey] = env[k];\n          }\n\n\n          // Build sub-object if nothing already exists at the keypath\n          if (cursor[_subkey] === undefined) {\n            cursor[_subkey] = {};\n          }\n\n          // Increment cursor used to track the object at the current depth\n          cursor = cursor[_subkey];\n\n        });\n\n      }\n\n    }\n\n    return obj;\n  };\n\n  var findFn = function () {\n    var rel = path.join.apply(null, [].slice.call(arguments));\n\n    function find(start, rel) {\n      var file = path.join(start, rel);\n      try {\n        fs.statSync(file);\n        return file;\n      } catch (unusedErr) {\n        if(path.dirname(start) !== start) {// root\n          return find(path.dirname(start), rel);\n        }\n      }\n    }\n    return find(process.cwd(), rel);\n  };\n\n\n\n  if('string' !== typeof name) {\n    throw new Error('rc(name): name *must* be string');\n  }\n  if(!argv) {\n    argv = require('minimist')(process.argv.slice(2));\n  }\n  defaults = (\n      'string' === typeof defaults\n    ? jsonFn(defaults) : defaults\n  ) || {};\n\n  parse = parse || parseFn;\n\n  var env = envFn(name + '_');\n\n  var configs = [defaults];\n  var configFiles = [];\n  function addConfigFile (file) {\n    if (configFiles.indexOf(file) >= 0) {return;}\n    var fileConfig = fileFn(file);\n    if (fileConfig) {\n      configs.push(parse(fileConfig));\n      configFiles.push(file);\n    }\n  }\n\n  // which files do we look at?\n  if (!win) {\n    [path.join(etc, name, 'config'), path.join(etc, name + 'rc')].forEach(addConfigFile);\n  }\n  if (home) {\n    [\n      path.join(home, '.config', name, 'config'),\n      path.join(home, '.config', name),\n      path.join(home, '.' + name, 'config'),\n      path.join(home, '.' + name + 'rc')\n    ].forEach(addConfigFile);\n    addConfigFile(findFn('.'+name+'rc'));\n  }\n  if (env.config) {\n    addConfigFile(env.config);\n  }\n  if (argv.config) {\n    addConfigFile(argv.config);\n  }\n\n  return deepExtend.apply(null, configs.concat([\n    env,\n    argv,\n    configFiles.length ? {configs: configFiles, config: configFiles[configFiles.length - 1]} : undefined,\n  ]));\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"sails\",\n  \"author\": \"Mike McNeil <@mikermcneil>\",\n  \"version\": \"1.5.17\",\n  \"description\": \"API-driven framework for building realtime apps, using MVC conventions (based on Express and Socket.io)\",\n  \"license\": \"MIT\",\n  \"homepage\": \"https://sailsjs.com\",\n  \"keywords\": [\n    \"mvc\",\n    \"web-framework\",\n    \"express\",\n    \"sailsjs\",\n    \"sails.js\",\n    \"REST\",\n    \"API\",\n    \"orm\",\n    \"socket.io\"\n  ],\n  \"bin\": {\n    \"sails\": \"./bin/sails.js\"\n  },\n  \"engines\": {\n    \"node\": \">= 0.10.0\",\n    \"npm\": \">= 1.4.0\"\n  },\n  \"dependencies\": {\n    \"@sailshq/lodash\": \"^3.10.6\",\n    \"@sailshq/router\": \"^1.3.9\",\n    \"async\": \"2.6.4\",\n    \"captains-log\": \"^2.0.5\",\n    \"chalk\": \"2.3.0\",\n    \"commander\": \"2.11.0\",\n    \"common-js-file-extensions\": \"1.0.2\",\n    \"compression\": \"1.8.1\",\n    \"connect\": \"3.6.5\",\n    \"cookie\": \"0.7.2\",\n    \"cookie-parser\": \"1.4.7\",\n    \"cookie-signature\": \"1.1.0\",\n    \"@sailshq/csurf\": \"1.11.1\",\n    \"ejs\": \"3.1.10\",\n    \"express\": \"4.22.0\",\n    \"express-session\": \"1.18.2\",\n    \"flaverr\": \"^1.10.0\",\n    \"glob\": \"7.1.2\",\n    \"i18n-2\": \"0.7.3\",\n    \"include-all\": \"^4.0.0\",\n    \"machine\": \"^15.2.2\",\n    \"machine-as-action\": \"^10.3.1\",\n    \"machinepack-process\": \"^4.0.1\",\n    \"machinepack-redis\": \"^2.0.2\",\n    \"merge-defaults\": \"0.2.2\",\n    \"merge-dictionaries\": \"1.0.0\",\n    \"minimist\": \"1.2.6\",\n    \"parley\": \"^3.3.4\",\n    \"parseurl\": \"1.3.2\",\n    \"path-to-regexp\": \"1.9.0\",\n    \"pluralize\": \"1.2.1\",\n    \"prompt\": \"1.2.1\",\n    \"rttc\": \"^10.0.0-0\",\n    \"sails-generate\": \"^2.0.11\",\n    \"sails-stringfile\": \"^0.3.3\",\n    \"semver\": \"7.5.2\",\n    \"serve-favicon\": \"2.4.5\",\n    \"serve-static\": \"1.16.2\",\n    \"skipper\": \"^0.9.5\",\n    \"sort-route-addresses\": \"^0.0.4\",\n    \"uid-safe\": \"2.1.5\",\n    \"vary\": \"1.1.2\",\n    \"whelk\": \"^6.0.1\"\n  },\n  \"devDependencies\": {\n    \"benchmark\": \"^2.1.2\",\n    \"connect-redis\": \"3.3.2\",\n    \"eslint\": \"5.16.0\",\n    \"expect.js\": \"0.3.1\",\n    \"fs-extra\": \"4.0.2\",\n    \"machinepack-fs\": \"^8.0.2\",\n    \"mocha\": \"3.0.2\",\n    \"nunjucks\": \"3.0.1\",\n    \"portfinder\": \"1.0.13\",\n    \"@sailshq/request\": \"2.88.3\",\n    \"root-require\": \"0.3.1\",\n    \"sails-hook-orm\": \"^4.0.2\",\n    \"sails-hook-sockets\": \"^3.0.0\",\n    \"sails.io.js\": \"^1.0.0\",\n    \"session-file-store\": \"1.1.2\",\n    \"should\": \"9.0.0\",\n    \"socket.io-client\": \"2.0.3\",\n    \"supertest\": \"1.1.0\",\n    \"tmp\": \"0.0.29\"\n  },\n  \"bugs\": {\n    \"url\": \"http://sailsjs.com/bugs\"\n  },\n  \"scripts\": {\n    \"test\": \"nodever=`node -e \\\"console.log('\\\\`node -v\\\\`'[1]);\\\"` && if [ $nodever != \\\"0\\\" ]; then npm run lint; fi && npm run custom-tests\",\n    \"custom-tests\": \"node ./node_modules/mocha/bin/mocha -b\",\n    \"lint\": \"node ./node_modules/eslint/bin/eslint . --max-warnings=0 --ignore-pattern 'test/' --ignore-pattern 'testApp/'\"\n  },\n  \"main\": \"./lib/index.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/balderdashy/sails.git\"\n  }\n}\n"
  },
  {
    "path": "test/.eslintrc",
    "content": "{\n  //   ╔═╗╔═╗╦  ╦╔╗╔╔╦╗┬─┐┌─┐  ┌─┐┬  ┬┌─┐┬─┐┬─┐┬┌┬┐┌─┐\n  //   ║╣ ╚═╗║  ║║║║ ║ ├┬┘│    │ │└┐┌┘├┤ ├┬┘├┬┘│ ││├┤\n  //  o╚═╝╚═╝╩═╝╩╝╚╝ ╩ ┴└─└─┘  └─┘ └┘ └─┘┴└─┴└─┴─┴┘└─┘\n  //  ┌─  ┌─┐┌─┐┬─┐  ┌─┐┬ ┬┌┬┐┌─┐┌┬┐┌─┐┌┬┐┌─┐┌┬┐  ┌┬┐┌─┐┌─┐┌┬┐┌─┐  ─┐\n  //  │   ├┤ │ │├┬┘  ├─┤│ │ │ │ ││││├─┤ │ ├┤  ││   │ ├┤ └─┐ │ └─┐   │\n  //  └─  └  └─┘┴└─  ┴ ┴└─┘ ┴ └─┘┴ ┴┴ ┴ ┴ └─┘─┴┘   ┴ └─┘└─┘ ┴ └─┘  ─┘\n  // > An .eslintrc configuration override for use with the tests in this directory.\n  //\n  // (See .eslintrc in the root directory of this package for more info.)\n\n  \"extends\": [\n    \"../.eslintrc\"\n  ],\n\n  \"env\": {\n    \"mocha\": true\n  }\n\n}\n"
  },
  {
    "path": "test/README.md",
    "content": "# Sails tests\n\n\n## Run the tests\n\nFrom the root directory of Sails core, run:\n\n```bash\nnpm test\n```\n\n> Or if you're using Windows:\n>\n> ```cmd\n> npm run custom-tests\n> ```\n\n## Goals\n\n1. Identify latent inconsistencies or issues that we don't know about yet.\n2. Provide low-level coverage of functionality that is difficult or time-consuming to QA / notice.\n3. Protect the core from any future breaking changes.\n4. Prevent regression.\n5. Make merging pull requests easier by removing me (@mikermcneil) as the bottleneck for merging pull requests. (we can just run the tests to see if a change broke anything)\n6. Make it easier for folks to contribute more tests, and help unify the style and structure of our existing tests.\n\n\n## Writing tests\n> For more information about writing tests (structural conventions, what to test, what _not_ to test) see the [Contribution guide](https://github.com/balderdashy/sails-docs/blob/master/contributing/code-submission-guidelines/writing-tests.md).\n"
  },
  {
    "path": "test/benchmarks/README.md",
    "content": "# Benchmarks\n\n### Run the benchmarks\n\nFrom the root directory of sails:\n\n```sh\n$ BENCHMARK=true mocha test/benchmarks\n```\n\nTo get a more detailed report with millisecond timings for each benchmark, run:\n\n```sh\n$ BENCHMARK=true mocha test/benchmarks -v\n```\n\n\n### Goals\n\nThese tests are related to benchmarking the performance of different parts of Sails.  For now, our benchmark tests should be \"integration\" or \"acceptance\" tests.  By that, I mean they should measure a specific \"user action\" (e.g. running `sails new`, running `sails lift`, sending an HTTP request to a dummy endpoint, connecting a Socket.io client, etc.).\n\n\n\n##### Why test features first, and not each individual method?\n\nFeature-wide benchmarks are the \"lowest-hanging fruit\", if you will.  We'll spend much less development time, and still get valuable benchmarks that will give us ongoing data on Sails performance.  This way, we'll know where to start writing lower-level benchmarks to identify choke-points.\n\n\n##### Writing good benchmarks\n+ Pick what you want to test.\n+ Whatever you choose does not have to be atomic (see examples above)-- in an ideal world, we would have benchmarks for every single function in our apps, but that is not how things work today.\n+ Write a benchmark test that isolates that functionality. (the hard part)\n+ Then see how many milliseconds it takes. (easy)\n\n> **Advice from Felix Geisendörfer ([@felixge](https://github.com/felixge))**\n>\n>  + First of all, keep in mind our problems are definitely not the same as Felix's, and we must remember to follow [his own advice](https://github.com/felixge/faster-than-c#taking-performance-advice-from-strangers): `[What]...does not work is taking performance advise (euro-sic) from strangers...`  That said, he's got some great ideas.\n>  + [Benchmark-Driven Optimization](https://github.com/felixge/faster-than-c#benchmark-driven-development)\n>  + I also highly recommend this [talk on optimization and benchmarking](http://2012.jsconf.eu/speaker/2012/09/05/faster-than-c-parsing-node-js-streams-.html) ([slides](https://github.com/felixge/faster-than-c)).\n\n\n### Things to test\n\nHere are the most important things we need to benchmark:\n\n##### Features:\n\n+ Bootstrap\n  + `sails.load` (programmatic)\n  + `sails.lift` (programmatic) and `sails lift` (CLI)\n  + `sails load`\n  + `sails new` and `sails generate *`\n    + (could be pulled into generic generator suite, like adapters)\n\n+ Router\n  + private Sails requests via `sails.emit('request')`\n  + http requests to the HTTP server\n  + http file uploads to the HTTP server\n  + connections to the socket.io server\n  + socket emissions to the socket.io server\n  + socket broadcasts FROM the socket.io server (pubsub hook)\n\n\n> Thankfully, the ORM is already covered by the benchmarks in Waterline core and its generic adapter tests.\n\n\n##### Measuring:\n\n+ Execution time\n+ Memory usage\n\n##### Under varying levels of stress:\n\n+ Low concurrency (c1k)\n+ High-moderate concurrency (c10k)\n\n##### In varying environments:\n\n+ Every permutation of the core hook configuration\n+ With different configuration options set\n\n\n### Considerations\n\nSome important things to consider when benchmarking Node.js / Express-based apps in general:\n\n+ Keep in mind that, unless you use the cluster module, or spin up multiple instances of the server, you're testing performance on one CPU.  Most production servers, cloud or not, have more than one CPU available.  This may or may not be relevant, depending on the benchmark and whether it is CPU-intensive.\n+ Be sure to configure [`maxSockets`](http://nodejs.org/api/http.html#http_agent_maxsockets), since most of the requests in a benchmark test are likely to originate from the same source.\n\n> **Sources:**\n> + https://groups.google.com/forum/#!topic/nodejs/tgATyqF-HIc\n\n\n\n### Benchmarking libraries\n\n> Don't know the best route here yet-- but here are some links for reference.  Would love to hear your ideas!\n\n+ https://github.com/spumko/flod\n+ https://github.com/LearnBoost/mongoose/blob/3.8.x/benchmarks/benchjs/casting.js\n+ https://npmjs.org/package/benchmark\n\n"
  },
  {
    "path": "test/benchmarks/helpers/benchmarx.js",
    "content": "var Benchmark = require('benchmark');\nvar _ = require('@sailshq/lodash');\n\n/**\n * benchmarx()\n * ---------------------------\n * @param  {String}   name\n * @param  {Array}   testFns  [array of functions]\n * @param  {Function}   notifier\n * @param  {Function} done\n */\nmodule.exports = function benchmarx (name, testFns, done) {\n  Benchmark.options.minSamples = 500;\n  var suite = new Benchmark.Suite({ name: name });\n  _.each(testFns, function (testFn) {\n    suite = suite.add(testFn.name, {\n      defer: true,\n      fn: function (deferred) {\n        testFn(function _afterRunningTestFn(err){\n          process.nextTick(function _afterEnsuringAsynchronous(){\n            if (err) {\n              console.error('An error occured when attempting to benchmark this code:\\n',err);\n              // Resolve the deferred either way.\n            }\n\n            deferred.resolve();\n          });//</afterwards cb from waiting for nextTick>\n        });//</afterwards cb from running test fn>\n      }\n    });//<suite.add>\n  });//</each testFn>\n\n  suite.on('cycle', function(event) {\n    console.log(' •',String(event.target), '(avg ' + (event.target.stats.mean * 1000) + ' ms)');\n  })\n  .on('complete', function() {\n    console.log('Fastest is ' + this.filter('fastest').map('name'));\n    console.log('Slowest is ' + this.filter('slowest').map('name'));\n    return done(undefined, this);\n  })\n  .run();\n};\n"
  },
  {
    "path": "test/benchmarks/sails.load.test.js",
    "content": "var _ = require('@sailshq/lodash');\nvar chalk = require('chalk');\nvar portfinder = require('portfinder');\nportfinder.basePort = 2001;\n\nvar SHOW_VERBOSE_BENCHMARK_REPORT = _.any(process.argv, function(arg) {\n  return arg.match(/-v/);\n});\n\nif (process.env.BENCHMARK) {\n\n  describe('benchmarks', function() {\n\n    describe('sails.load()', function() {\n      before(setupBenchmarks);\n      after(reportBenchmarks);\n\n\n      //\n      // Instantiate\n      //\n\n      benchmark('require(\"sails\")', function(cb) {\n        var Sails = require('../../lib/app');\n        var sails = new Sails();\n        return cb();\n      });\n\n\n      //\n      // Load\n      //\n\n      benchmark('sails.load  [first time, no hooks]', function(cb) {\n        var Sails = require('../../lib/app');\n        var sails = new Sails();\n        sails.load({\n          log: {\n            level: 'error'\n          },\n          globals: false,\n          loadHooks: []\n        }, _getTestCleanupCallback(sails, cb));\n      });\n\n      benchmark('sails.load  [again, no hooks]', function(cb) {\n        this.expected = 25;\n        this.comment = 'faster b/c of require cache';\n\n        var Sails = require('../../lib/app');\n        var sails = new Sails();\n        sails.load({\n          log: {\n            level: 'error'\n          },\n          globals: false,\n          loadHooks: []\n        }, _getTestCleanupCallback(sails, cb));\n      });\n\n      benchmark('sails.load  [with moduleloader hook]', function(cb) {\n        this.expected = 25;\n        this.comment = 'faster b/c of require cache';\n\n        var Sails = require('../../lib/app');\n        var sails = new Sails();\n\n        sails.load({\n          log: {\n            level: 'error'\n          },\n          globals: false,\n          loadHooks: ['moduleloader']\n        }, _getTestCleanupCallback(sails, cb));\n\n      });\n\n      benchmark('sails.load  [all core hooks]', function(cb) {\n        this.expected = 3000;\n\n        var Sails = require('../../lib/app');\n        var sails = new Sails();\n        sails.load({\n          log: {\n            level: 'error'\n          },\n          globals: false\n        }, _getTestCleanupCallback(sails, cb));\n      });\n\n      benchmark('sails.load  [again, all core hooks]', function(cb) {\n        this.expected = 3000;\n\n        var Sails = require('../../lib/app');\n        var sails = new Sails();\n        sails.load({\n          log: {\n            level: 'error'\n          },\n          globals: false\n        }, _getTestCleanupCallback(sails, cb));\n      });\n\n\n      //\n      // Lift\n      //\n\n      benchmark('sails.lift  [w/ a hot require cache]', function(cb) {\n        this.expected = 3000;\n\n        var Sails = require('../../lib/app');\n        var sails = new Sails();\n        portfinder.getPort(function(err, port) {\n          if (err) { throw err; }\n\n          sails.lift({\n            log: {\n              level: 'error'\n            },\n            port: port,\n            globals: false\n          }, _getTestCleanupCallback(sails, cb));\n        });\n      });\n\n      benchmark('sails.lift  [again, w/ a hot require cache]', function(cb) {\n        this.expected = 3000;\n\n        var Sails = require('../../lib/app');\n        var sails = new Sails();\n        portfinder.getPort(function(err, port) {\n          if (err) { throw err; }\n\n          sails.lift({\n            log: {\n              level: 'error'\n            },\n            port: port,\n            globals: false\n          }, _getTestCleanupCallback(sails, cb));\n        });\n      });\n\n    });\n\n\n  });\n\n\n  /**\n   * Run the specified function, capturing time elapsed.\n   *\n   * @param  {[type]}   description [description]\n   * @param  {Function} fn          [description]\n   * @param  {Function} afterwards\n   */\n  function benchmark(description, fn) {\n\n    it(description, function (cb) {\n      var self = this;\n\n      var t1 = process.hrtime();\n\n      fn.apply(self, [\n        function _callbackFromFn(err) {\n          var _result = {};\n\n          // If a `comment` or `expected` was provided, harvest it\n          _result.expected = self.expected;\n          self.expected = null;\n\n          _result.comment = self.comment;\n          self.comment = null;\n\n          var diff = process.hrtime(t1);\n\n          _.result.duration = (diff[0] * 1e6) + (diff[1] / 1e3);\n          _result.benchmark = description;\n\n          // console.log('finished ',_result);\n          self.benchmarks.push(_result);\n\n          if (err) {\n            return cb(err);\n          }\n\n          return cb.apply(Array.prototype.slice.call(arguments));\n       }\n      ]);\n    });//</it>\n  }\n\n\n  /**\n   * Use in mocha's `before`\n   *\n   * @this {Array} benchmarks\n   */\n  function setupBenchmarks() {\n    this.benchmarks = [];\n  }\n\n\n  /**\n   * Use in mocha's `after`\n   *\n   * @this {Array} benchmarks\n   */\n  function reportBenchmarks() {\n    var output = '\\n\\nBenchmark Report ::\\n';\n    output += _.reduce(this.benchmarks, function(memo, result) {\n\n      // Convert to ms-\n      var ms = (result.duration / 1000.0);\n\n      // round to 0 decimal places\n      function _roundDecimalTo(num, numPlaces) {\n        return +(Math.round(num + ('e+' + numPlaces)) + ('e-' + numPlaces));\n      }\n      ms = _roundDecimalTo(ms, 2);\n\n\n      var expected = result.expected || 1000;\n\n      // threshold: the \"failure\" threshold\n      var threshold = result.expected;\n\n      var color =\n        (ms < 1 * expected / 10) ? 'green' :\n        (ms < 3 * expected / 10) ? 'green' :\n        (ms < 6 * expected / 10) ? 'cyan' :\n        (ms < threshold) ? 'yellow' :\n        'red';\n\n      ms += 'ms';\n      ms = ms[color];\n\n      // Whether to show expected ms\n      var showExpected = true; // ms >= threshold;\n\n      return memo + '\\n ' +\n        chalk.grey((result.benchmark + '') + ' :: ') + ms +\n\n        // Expected ms provided, and the test took quite a while\n        chalk.grey(result.expected && showExpected ? '\\n   (expected ' + expected + 'ms' +\n          (result.comment ? ' --' + result.comment : '') +\n          ')' :\n\n          // Comment provided - but no expected ms\n          (result.comment ? '\\n   (' + result.comment + ')\\n' : '')\n        );\n    }, '');\n\n    // Log output (optional)\n    if (SHOW_VERBOSE_BENCHMARK_REPORT) {\n      console.log(output);\n    }\n  }\n\n\n\n\n  /**\n   *\n   * @param  {Function} cb [description]\n   * @return {Function}\n   */\n  function _getTestCleanupCallback(app, cb) {\n    return function afterLoadingSails (err) {\n      if(err) {\n        return cb(new Error('Failed with error: '+err.stack));\n      }\n      app.lower(function (errLowering){\n        if (errLowering) {\n          return cb(new Error('Everything was otherwise ok, but failed to `.lower()` app. Details:' + errLowering.stack));\n        }\n        if (err) { return cb(err); }\n        return cb();\n      });\n    };\n  }\n\n}\n"
  },
  {
    "path": "test/benchmarks/sails.request.generic.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar assert = require('assert');\nvar tmp = require('tmp');\nvar _ = require('@sailshq/lodash');\nvar request = require('@sailshq/request');\n\nvar Filesystem = require('machinepack-fs');\n\nvar appHelper = require('../integration/helpers/appHelper');\nvar Sails = require('../../lib').constructor;\nvar benchmarx = require('./helpers/benchmarx');\n\nif (process.env.BENCHMARK) {\n\n  tmp.setGracefulCleanup();\n\n\n  describe('benchmarks', function() {\n    describe('sails requests :: ', function() {\n      describe('generic requests ::', function() {\n\n        this.timeout(0);\n        describe('baseline (load only, no hooks) ::', function() {\n\n          var curDir, tmpDir, sailsApp;\n          var warn;\n          var warnings = [];\n\n\n          before(function(done) {\n            // Cache the current working directory.\n            curDir = process.cwd();\n            // Create a temp directory.\n            tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n            // Switch to the temp directory.\n            process.chdir(tmpDir.name);\n\n            // Load the Sails app.\n            (new Sails()).load({\n              loadHooks: [],\n              log: {level: 'silent'},\n              routes: {\n                '/test1': function(req, res) { return res.status(200).send(); },\n                'GET /test2': function(req, res) { return res.status(200).send(); },\n                'POST /test3': function(req, res) { return res.status(200).send(); },\n                '/test4/:id': function(req, res) { return res.status(200).send(); },\n                '/test5/*': function(req, res) { return res.status(200).send(); },\n                'r|test6/\\\\d+|foo': function(req, res) { return res.status(200).send(); },\n\n                '/test7': function(req, res) { return res.status(200).send('foo'); },\n                '/test8': function(req, res) { return res.status(200).json({foo: 'bar'}); },\n\n                'POST /test9': function(req, res) { return res.status(200).send(req.param('foo')); },\n\n                'POST /test10': function(req, res) { return res.status(200).json(req.allParams); },\n              }\n            }, function(err, _sails) {\n              sailsApp = _sails;\n              return done(err);\n            });\n          });\n\n          after(function(done) {\n            sailsApp.lower(function() {\n              process.chdir(curDir);\n              return done();\n            });\n          });\n\n          it('', function(done) {\n            benchmarx('', [\n              function route_with_no_verb(done) {\n                sailsApp.request('http://localhost:1342/test1', done);\n              },\n              function route_with_GET_verb(done) {\n                sailsApp.request('http://localhost:1342/test2', done);\n              },\n              function route_with_POST_verb(done) {\n                sailsApp.request({\n                  url: 'http://localhost:1342/test3',\n                  method: 'post',\n                  data: {foo: 'bar'}\n                }, done);\n              },\n              function route_with_dynamic_param(done) {\n                sailsApp.request('http://localhost:1342/test4/123', done);\n              },\n              function route_with_wildcard(done) {\n                sailsApp.request('http://localhost:1342/test5/abc/123', done);\n              },\n              function route_with_regex(done) {\n                sailsApp.request('http://localhost:1342/test6/666', done);\n              },\n              function respond_with_string(done) {\n                sailsApp.request('http://localhost:1342/test7', done);\n              },\n              function respond_with_json(done) {\n                sailsApp.request('http://localhost:1342/test8', done);\n              },\n              function reflect_one_param(done) {\n                sailsApp.request({\n                  url: 'http://localhost:1342/test9',\n                  method: 'post',\n                  data: {foo: 'bar'}\n                }, done);\n              },\n              function reflect_all_params(done) {\n                sailsApp.request({\n                  url: 'http://localhost:1342/test10',\n                  method: 'post',\n                  data: {foo: 'bar', abc: 123}\n                }, done);\n              }\n            ], done);\n          });\n\n        });\n\n        describe('lift w/ no hooks besides http and request) ::', function() {\n\n          var curDir, tmpDir, sailsApp;\n          var warn;\n          var warnings = [];\n\n          this.timeout(0);\n\n          before(function(done) {\n            // Cache the current working directory.\n            curDir = process.cwd();\n            // Create a temp directory.\n            tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n            // Switch to the temp directory.\n            process.chdir(tmpDir.name);\n\n            // Load the Sails app.\n            (new Sails()).lift({\n              port: 1342,\n              loadHooks: ['http', 'request'],\n              log: {level: 'silent'},\n              routes: {\n                '/test1': function(req, res) { return res.status(200).send(); },\n                'GET /test2': function(req, res) { return res.status(200).send(); },\n                'POST /test3': function(req, res) { return res.status(200).send(); },\n                '/test4/:id': function(req, res) { return res.status(200).send(); },\n                '/test5/*': function(req, res) { return res.status(200).send(); },\n                'r|test6/\\\\d+|foo': function(req, res) { return res.status(200).send(); },\n\n                '/test7': function(req, res) { return res.status(200).send('foo'); },\n                '/test8': function(req, res) { return res.status(200).json({foo: 'bar'}); },\n\n                'POST /test9': function(req, res) { return res.status(200).send(req.param('foo')); },\n\n                'POST /test10': function(req, res) { return res.status(200).json(req.allParams); },\n              }\n            }, function(err, _sails) {\n              sailsApp = _sails;\n              return done(err);\n            });\n          });\n\n          after(function(done) {\n            sailsApp.lower(function() {\n              process.chdir(curDir);\n              return done();\n            });\n          });\n\n          it('', function(done) {\n            benchmarx('', [\n              function route_with_no_verb(done) {\n                request.get('http://localhost:1342/test1', done);\n              },\n              function route_with_GET_verb(done) {\n                request.get('http://localhost:1342/test2', done);\n              },\n              function route_with_POST_verb(done) {\n                request({\n                  url: 'http://localhost:1342/test3',\n                  method: 'post',\n                  json: {foo: 'bar'}\n                }, done);\n              },\n              function route_with_dynamic_param(done) {\n                request.get('http://localhost:1342/test4/123', done);\n              },\n              function route_with_wildcard(done) {\n                request.get('http://localhost:1342/test5/abc/123', done);\n              },\n              function route_with_regex(done) {\n                request.get('http://localhost:1342/test6/666', done);\n              },\n              function respond_with_string(done) {\n                request.get('http://localhost:1342/test7', done);\n              },\n              function respond_with_json(done) {\n                request.get('http://localhost:1342/test8', done);\n              },\n              function reflect_one_param(done) {\n                request.post('http://localhost:1342/test9', {foo: 'bar'}, done);\n              },\n              function reflect_all_params(done) {\n                request.post('http://localhost:1342/test10', {foo: 'bar', abc: 123}, done);\n              }\n            ], done);\n          });\n\n        });\n\n        describe('lift with all default hooks ::', function() {\n\n          var curDir, tmpDir, sailsApp;\n          var warn;\n          var warnings = [];\n\n          this.timeout(0);\n\n          before(function(done) {\n            // Cache the current working directory.\n            curDir = process.cwd();\n            // Create a temp directory.\n            tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n            // Switch to the temp directory.\n            process.chdir(tmpDir.name);\n            // Link dependencies so that default hooks will work\n            appHelper.linkDeps(tmpDir.name);\n\n            // Load the Sails app.\n            (new Sails()).lift({\n              port: 1342,\n              log: {level: 'silent'},\n              routes: {\n                '/test1': function(req, res) { return res.status(200).send(); },\n                'GET /test2': function(req, res) { return res.status(200).send(); },\n                'POST /test3': function(req, res) { return res.status(200).send(); },\n                '/test4/:id': function(req, res) { return res.status(200).send(); },\n                '/test5/*': function(req, res) { return res.status(200).send(); },\n                'r|test6/\\\\d+|foo': function(req, res) { return res.status(200).send(); },\n\n                '/test7': function(req, res) { return res.status(200).send('foo'); },\n                '/test8': function(req, res) { return res.status(200).json({foo: 'bar'}); },\n\n                'POST /test9': function(req, res) { return res.status(200).send(req.param('foo')); },\n\n                'POST /test10': function(req, res) { return res.status(200).json(req.allParams); },\n              }\n            }, function(err, _sails) {\n              sailsApp = _sails;\n              return done(err);\n            });\n          });\n\n          after(function(done) {\n            sailsApp.lower(function() {\n              process.chdir(curDir);\n              return done();\n            });\n          });\n\n          it('', function(done) {\n            benchmarx('', [\n              function route_with_no_verb(done) {\n                request.get('http://localhost:1342/test1', done);\n              },\n              function route_with_GET_verb(done) {\n                request.get('http://localhost:1342/test2', done);\n              },\n              function route_with_POST_verb(done) {\n                request({\n                  url: 'http://localhost:1342/test3',\n                  method: 'post',\n                  json: {foo: 'bar'}\n                }, done);\n              },\n              function route_with_dynamic_param(done) {\n                request.get('http://localhost:1342/test4/123', done);\n              },\n              function route_with_wildcard(done) {\n                request.get('http://localhost:1342/test5/abc/123', done);\n              },\n              function route_with_regex(done) {\n                request.get('http://localhost:1342/test6/666', done);\n              },\n              function respond_with_string(done) {\n                request.get('http://localhost:1342/test7', done);\n              },\n              function respond_with_json(done) {\n                request.get('http://localhost:1342/test8', done);\n              },\n              function reflect_one_param(done) {\n                request.post('http://localhost:1342/test9', {foo: 'bar'}, done);\n              },\n              function reflect_all_params(done) {\n                request.post('http://localhost:1342/test10', {foo: 'bar', abc: 123}, done);\n              }\n            ], done);\n          });\n\n        });\n\n      });\n    });\n  });\n\n}\n"
  },
  {
    "path": "test/fixtures/constants.js",
    "content": "/**\n * Constants about the Sails framework.\n * For use in tests.\n *\n * @type {Object}\n */\nmodule.exports = {\n  EXPECTED_DEFAULT_HOOKS: require('../../lib/app/configuration/default-hooks')\n};\n"
  },
  {
    "path": "test/fixtures/customHooks.js",
    "content": "/**\n * Stub custom hooks for use in tests.\n *\n * @type {Object}\n */\nmodule.exports = {\n\n  // Extremely simple hook that doesn't do anything.\n  NOOP: function (sails) {\n    return { identity: 'noop' };\n  },\n\n  // Depends on 'noop' hook\n  NOOP2: function (sails) {\n    return {\n      // TODO: indicate dependency on 'noop' hook\n      identity: 'noop2'\n    };\n  },\n\n  // Deliberately rotten hook- it throws.\n  SPOILED_HOOK: function (sails) {\n    throw new Error('smells nasty');\n  },\n\n  // Hook to test `defaults` object\n  DEFAULTS_OBJ: function(sails) {\n    return {\n      identity: 'defaults_obj',\n      defaults: {\n        foo: 'bar',\n        inky: {\n          dinky: 'doo',\n          pinky: 'dont'\n        }\n      }\n    };\n  },\n\n  // Hook to test `defaults` function\n  DEFAULTS_FN: function(sails) {\n    return {\n      identity: 'defaults_fn',\n      defaults: function() {\n        return {\n          foo: 'bar',\n          inky: {\n            dinky: 'doo',\n            pinky: 'dont'\n          }\n        };\n      }\n    };\n  },\n\n  // Hook to test `initialize` function\n  INIT_FN: function(sails) {\n    return {\n      identity: 'init_fn',\n      initialize: function(cb) {\n        sails.config.hookInitLikeABoss = true;\n        return cb();\n      }\n    };\n  },\n\n  // Hook to test `configure` function\n  CONFIG_FN: function(sails) {\n    return {\n      identity: 'config_fn',\n      configure: function() {\n        // Test that loaded config is available by copying a value\n        sails.config.hookConfigLikeABoss = sails.config.testConfig;\n      }\n    };\n  },\n\n  // Hook to test `routes` function\n  ROUTES: function(sails) {\n    return {\n      identity: 'routes',\n      routes: {\n        before: {\n          'GET /foo': function(req, res, next) {\n            sails.config.foo = 'a';\n            next();\n          }\n        },\n        after: {\n          'GET /foo': function(req, res, next) {\n            sails.config.foo = sails.config.foo + 'c';\n            res.send(sails.config.foo);\n          }\n        }\n      }\n    };\n  },\n\n  // Hook to test `routes` function\n  ADVANCED_ROUTES: function(sails) {\n    return {\n      identity: 'advanced_routes',\n      initialize: function(cb) {\n        sails.on('router:before', function() {\n          sails.router.bind('GET /foo', function(req, res, next) {\n            sails.config.foo = sails.config.foo + 'b';\n            next();\n          });\n        });\n        sails.on('router:after', function() {\n          sails.router.bind('GET /foo', function(req, res, next) {\n            sails.config.foo = sails.config.foo + 'e';\n            res.send(sails.config.foo);\n          });\n        });\n        cb();\n      },\n      routes: {\n        before: {\n          'GET /foo': function(req, res, next) {\n            sails.config.foo = 'a';\n            next();\n          }\n        },\n        after: {\n          'GET /foo': function(req, res, next) {\n            sails.config.foo = sails.config.foo + 'd';\n            next();\n          }\n        }\n      }\n    };\n  },\n\n};\n"
  },
  {
    "path": "test/fixtures/middleware.js",
    "content": "/**\n * Stub middleware/handlers for use in tests.\n *\n * @type {Dictionary}\n */\nmodule.exports = {\n\n  HELLO: function(req, res) {\n    return res.send('hello world!');\n  },\n\n  GOODBYE: function(req, res) {\n    return res.send('goodbye world!');\n  },\n\n  HELLO_500: function(req, res) {\n    return res.status(500).send('hello world!');\n  },\n\n  JSON_HELLO: function(req, res) {\n    return res.json({\n      hello: 'world'\n    });\n  },\n\n  SOMETHING_THAT_THROWS: function(req, res) {\n    throw 'oops';\n  },\n\n};\n"
  },
  {
    "path": "test/helpers/RouteFactory.helper.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar util = require('util');\n\n\n/**\n * @constructor\n */\nfunction RouteFactory(prefix) {\n  this._prefix = prefix;\n  this._nextTestRoute = 0;\n}\nRouteFactory.prototype.next = function() {\n  this._nextTestRoute++;\n  this.current = util.format('/tests/%s%d', this._prefix ? this._prefix + '/' : '', this._nextTestRoute);\n  return this.current;\n};\n\n\nmodule.exports = function (){\n  return new RouteFactory();\n};\n\n"
  },
  {
    "path": "test/helpers/router.js",
    "content": "/**\n * Module dependencies\n */\nvar _ = require('@sailshq/lodash');\n\n\n\nvar $Router = {\n\n\n  /**\n   * Run custom tests if provided\n   * @param  {Function} customTests\n   * @return {$Router}\n   */\n  test: function(customTests) {\n    customTests();\n\n    // Chainable\n    return $Router;\n  },\n\n\n  /**\n   * unbind(route)\n   *   .expect(expectations)\n   *\n   */\n  unbind: function(route) {\n    var args = Array.prototype.slice.call(arguments);\n\n    it('binds the given route', function() {\n      this.sails.router.bind.should.be.ok;\n      this.sails.router.bind.apply(this.sails.router, args);\n    });\n\n    it('unbinds the given route', function() {\n      this.sails.router.unbind.should.be.ok;\n      var route = {verb: args[0].split(' ')[0], path: args[0].split(' ')[1]};\n      this.sails.router.unbind.call(this.sails.router, route);\n    });\n\n    var Chainable = {\n      shouldDelete: function(expected) {\n        it('should delete route in _privateRouter', function() {\n          var routeFound = false;\n          _.each(this.sails.router._privateRouter.stack, function(stack){\n            var samePath = stack.route.path === expected.path;\n            var sameMethod = stack.route.methods[expected.method] || stack.route.methods['_all'];\n            if(samePath && sameMethod) routeFound = true;\n          });\n          routeFound.should.be.false;\n        });\n\n        return Chainable;\n      }\n    };\n\n    return Chainable;\n  },\n\n\n  /**\n   * bindRoute(route, handler)\n   *   .expectBoundRoute(expectedRoute)\n   *   .test(fnContainingCustomMochaTests)\n   *\n   */\n  bind: function (route, handler) {\n    var args = Array.prototype.slice.call(arguments);\n\n    it('binds the given route', function() {\n      this.sails.router.bind.should.be.ok;\n      this.sails.router.bind.apply(this.sails.router, args);\n    });\n\n    var Chainable = {\n      expectBoundRoute: function(expected) {\n        var readableRoute = expected.method + ' ' + expected.path;\n\n        it('should create ' + readableRoute + ' in _privateRouter router', function() {\n          var routeFound = false;\n          _.each(this.sails.router._privateRouter.stack, function(stack){\n            var samePath = stack.route.path === expected.path;\n            var sameMethod = stack.route.methods[expected.method] || stack.route.methods['_all'];\n            if(samePath && sameMethod) routeFound = true;\n          });\n          routeFound.should.be.true;\n        });\n        return Chainable;\n      },\n\n      // Run custom tests if provided\n      test: function(customTests) {\n        customTests();\n\n        return Chainable;\n      }\n    };\n\n    return Chainable;\n  }\n\n};\n\nmodule.exports = $Router;\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n// var helper = {\n\n//  /**\n//   * Send a mock request to the instance of Sails in the test context.\n//   *\n//   * @param {String} url    [relative URL]\n//   * @param {Object} options\n//   *\n//   * @return {Function}   [bdd]\n//   */\n//  request: function ( url, options ) {\n//    return function (done) {\n\n//      var self = this;\n//      var _fakeClient = function (err, response) {\n//        if (err) return done(err);\n//        self.response = response;\n//        done();\n//      };\n\n//      // Emit a request event (will be intercepted by the Router)\n//      this.sails.emit('router:request', _req(), _res(), _fakeClient);\n//    };\n//  },\n\n\n\n//  /**\n//   * Bind a route.\n//   *\n//   * @param {String|RegExp} path\n//   * @param {String|Object|Array|Function} target\n//   * @param {String} verb (optional)\n//   * @param {Object} options (optional)\n//   *\n//   * @return {Function}   [bdd]\n//   */\n\n//  bind: function () {\n//    var args = Array.prototype.slice.call(arguments);\n//    return function () {\n//      this.sails.router.bind.apply(this.sails.router, args);\n//    };\n//  }\n// };\n// module.exports = helper;\n\n\n\n// // Private methods\n\n// /**\n//  * Test fixture to send requests to Sails.\n//  *\n//  * @api private\n//  */\n\n// function _req ( req ) {\n//  var _enhancedReq = util.defaults(req || {}, {\n//    params: {},\n//    url: '/',\n//    param: function(paramName) {\n//      return _enhancedReq.params[paramName];\n//    },\n//    wantsJSON: true,\n//    method: 'get'\n//  });\n\n//  return _enhancedReq;\n// }\n\n\n// /**\n//  * Test fixture to receive responses from Sails.\n//  *\n//  * @api private\n//  */\n\n// function _res (res) {\n\n//  var _enhancedRes = util.defaults(res || {}, {\n//    send: function(/* ... */) {\n//      var args = _normalizeResArgs(Array.prototype.slice.call(arguments));\n\n//      _enhancedRes._cb(null, {\n//        body: args.other,\n//        headers: {},\n//        status: args.statusCode || 200\n//      });\n//    },\n//    json: function(body, statusCode) {\n\n//      // Tolerate bad JSON\n//      var json = util.stringify(body);\n//      if ( !json ) {\n//        var failedStringify = new Error(\n//          'Failed to stringify specified JSON response body :: ' + body\n//        );\n//        return _enhancedRes.send(failedStringify.stack, 500);\n//      }\n\n//      return _enhancedRes.send(json,statusCode);\n//    }\n//  });\n\n//  return _enhancedRes;\n// }\n\n\n// /**\n//  * As long as one of them is a number (i.e. a status code),\n//  * allows a 2-nary method to be called with flip-flopped arguments:\n//  *   method( [statusCode|other], [statusCode|other] )\n//  *\n//  * This avoids confusing errors & provides Express 2.x backwards compat.\n//  *\n//  * E.g. usage in res.send():\n//  *   var args    = normalizeResArgs.apply(this, arguments),\n//  *     body    = args.other,\n//  *     statusCode  = args.statusCode;\n//  *\n//  * @api private\n//  */\n// function _normalizeResArgs( args ) {\n\n//  // Traditional usage:\n//  // `method( other [,statusCode] )`\n//  var isTraditionalUsage =\n//    'number' !== typeof args[0] &&\n//    ( !args[1] || 'number' === typeof args[1] );\n\n//  if ( isTraditionalUsage ) {\n//    return {\n//      statusCode: args[1],\n//      other: args[0]\n//    };\n//  }\n\n//  // Explicit usage, i.e. Express 3:\n//  // `method( statusCode [,other] )`\n//  return {\n//    statusCode: args[0],\n//    other: args[1]\n//  };\n// }\n\n\n\n// describe('receives a request', function() {\n//  to('home route (/)', function() {\n//    before(RouterHelper.request('/'));\n//    __it('should trigger the default notFound (404) handler');\n//    __it('should receive a 404 response from default handler', expect.equal('response.status', 404));\n//    __it('should not receive a reponse body', expect.notExists('response.body'));\n//  });\n// });\n\n// to('a simple fn which calls res.send()', function () {\n//  var route = 'get /simple';\n//  var fn = function (req, res) { res.send('ok!'); };\n//  var expectedResponse = { status: 200 };\n\n//  __it('binds the route', RouterHelper.bind(route, fn));\n//  __it('should now exist in the _privateRouter router');\n//  __it('receives a request to the route',RouterHelper.request(route));\n//  __it('should have called the proper fn');\n//  __it('should have sent the expected status code in the response', expect.equal('response.status', expectedResponse.status));\n//  __it('should have sent the expected response body', expect.equal('response.body', expectedResponse.body));\n//  __it('should have sent the expected response headers', expect.equal('response.headers', expectedResponse.headers));\n// });\n\n// to('a simple fn which throws', function () {\n//  var route = 'get /throws';\n//  var fn = function (req, res) { throw new Error('heh heh'); };\n//  var expectedResponse = { status: 500 };\n\n//  __it('binds the route', RouterHelper.bind(route, fn));\n//  __it('should now exist in the _privateRouter router');\n//  __it('receives a request to the route', RouterHelper.request(route));\n//  __it('should have called the proper fn');\n//  __it('should have sent the proper response', expect.equal('response', expectedResponse));\n// });\n// });\n\n\n// // private bdd helpers\n// function __it(name, fn) {\n//  it('\\n\\t    ...it ' + name, fn);\n// }\n// function to(name,fn) {\n//  describe('\\n\\t-- to ' +name+'...', fn);\n// }\n"
  },
  {
    "path": "test/helpers/sails.js",
    "content": "/**\n * Module dependencies\n */\nvar _ = require('@sailshq/lodash');\nvar util = require('util');\nvar should = require('should');\nvar domain = require('domain');\nvar Sails = require('root-require')('lib/app');\n\n/**\n * Manage an instance of Sails\n *\n * @type {Object}\n */\nvar helper = {\n\n\n  /**\n   * Can call:\n   *  -> helper.load()\n   *  -> helper.load.withAllHooksDisabled()\n   *  -> helper.load.expectingTerminatedProcess()\n   */\n  load: (function () {\n\n    /**\n     * _cleanOptions()\n     *\n     * @param {Object} options\n     * @type {Function}\n     * @api private\n     */\n    function _cleanOptions (options) {\n      var testDefaults = { log: {level: 'error'}, globals: false };\n      options = _.isObject(options) ? options : {};\n      return _.defaults(options, testDefaults);\n    }\n\n\n    /**\n     * This function is returned by this test helper\n     * to be called by subsequent tests.\n     *\n     * @param  {Object} options\n     * @return {SJSApp}\n     */\n    var _load = function (options) {\n\n      var testDescription, msSlowThreshold;\n      var sailsOpts = _cleanOptions(options);\n\n      // Defaults\n      // (except use test defaults)\n      if (!_.isObject(options)) {\n        testDescription = 'default settings';\n        msSlowThreshold = 750;\n      }\n      else {\n        // Specified options + defaults\n        // (except default log level to 'error')\n        testDescription = util.inspect(options);\n        msSlowThreshold = 2000;\n      }\n\n\n      return _with(testDescription, sailsOpts, msSlowThreshold);\n    };\n\n\n    /**\n     * [withAllHooksDisabled description]\n     * @return {[type]} [description]\n     */\n    _load.withAllHooksDisabled = function () {\n      return _with('all hooks disabled', {\n        log: {level: 'error'},\n        globals: false,\n        loadHooks: []\n      }, 500);\n    };\n\n\n    /**\n     * [expectFatalError description]\n     * @param  {[type]} options [description]\n     * @return {[type]}         [description]\n     */\n    _load.expectFatalError = function( options ) {\n      options = _.isObject(options) ? options : {};\n      var sailsOpts = _cleanOptions(options);\n\n      it(', sails should deliberately terminate process', function (done) {\n        var sails = new Sails();\n\n        // TODO:\n        // Pull this error domain into the core and\n        // wrap the hook loading process (or a comparable solution.)\n        // Should not have to do this here!\n\n        // Use error domain to catch failure\n        var deliberateErrorDomain = domain.create();\n        deliberateErrorDomain.on('error', function (err) {\n          console.log('domain emitted error', err);\n          deliberateErrorDomain.exit();\n          return done();\n        });\n        deliberateErrorDomain.run(function () {\n          sails.load(sailsOpts || {}, function (err) {\n            var e =\n            'Should not have made it to load() ' +\n            'callback, with or without an error!';\n            if (err) e+='\\nError: ' + util.inspect(err);\n            deliberateErrorDomain.exit();\n            return done(new Error(e));\n          });\n        });\n\n      });\n    };\n\n    return _load;\n\n  })(),\n\n\n  /**\n   * @return {Sails} `sails` instance from mocha context\n   */\n  get: function (passbackFn) {\n    // Use mocha context to get a hold of the Sails instance\n    it('should get a Sails instance', function () {\n      passbackFn(this.sails);\n    });\n  }\n};\n\n\n\nmodule.exports = helper;\n\n\n\n\n\n\n/**\n * Setup and teardown a Sails instance for testing.\n *\n * @param  {String} description\n * @param  {Object} sailsOpts\n * @param  {Integer} msThreshold [before we consider it \"slow\"]\n *\n * @returns {SJSApp}\n * @api private\n */\nfunction _with (description, sailsOpts, msThreshold) {\n\n\n  var sails = new Sails();\n\n  it('sails loaded (with ' + description + ')', function (done) {\n    if (msThreshold) { this.slow(msThreshold); }\n\n\n    // Expose a new app instance as `this.sails`\n    // for other tests to use.\n    this.sails = sails;\n\n    // Load the app\n    sails.load(sailsOpts || {}, done);\n  });\n\n  after(function teardown(done) {\n    // Make sure the app is done\n    sails.lower(function(){setTimeout(done, 100);});\n  });\n\n  return sails;\n}\n\n\n"
  },
  {
    "path": "test/helpers/test-spawning-sails-child-process-in-cwd.js",
    "content": "/**\n * Module dependencies\n */\n\nvar path = require('path');\nvar _ = require('@sailshq/lodash');\nvar request = require('@sailshq/request');\nvar MProcess = require('machinepack-process');\n\n\n/**\n * testSpawningChildProcessInCwd()\n *\n * This concisely-named test helper function injects a describe block, testing how Sails fares\n * when it comes time to `sails lift` or `sails console` or `node app.js` or otherwise start\n * a Sails app in the current working directory.\n *\n *\n * @optional {Array} cliArgs\n *           an array of additional string CLI args/opts to pass to the Sails process on start\n *           (e.g. `['--prod', '--port=1331']`)\n *\n * @optional {Dictionary} envVars\n *           a dictionary of environment variables to supply to the spawned process.\n *           Note: the default is to use the current process's environment, so if you set this\n *           option, you probably want to merge process.env into the value you use\n *\n * @optional {Dictionary} httpRequestInstructions\n *           A dictionary that gets passed in to `request()` when this helper attempts\n *           to contact the Sails server running in the child process.\n *           If provided at all, this can/must contain:\n *             @required {String} method\n *             @required {String} uri\n *             @optional {String} expectedStatusCode  [defaults to 200]\n *\n * @optional {Function} fnWithAdditionalTests\n *           A function with additional custom tests; that is, it has one or more `it()` blocks inside.\n *\n * @optional {Boolean} expectFailedLift\n *           A flag which, if enabled, causes this test helper to _expect_ the app to fail.\n *           Also if it is set AND `httpRequestInstructions` are set, then the HTTP request\n *           will still be sent-- but just to _make sure_ it fails too.\n */\nmodule.exports = function testSpawningSailsLiftChildProcessInCwd (opts){\n\n  describe('and then waiting for a bit', function() {\n\n    // We can't use HTTP or ws:// requests for IPC for these tests, because we're trying\n    // to determine whether the Sails app has _actually loaded_, not just whether HTTP\n    // requests will work (although that's good to know too).\n    //\n    // To work around that, we use a timeout.\n    // N_SECONDS is used below to determine how long to wait for the lift before\n    // attempting to shut her down with a SIGTERM. We _could_ wait for output, but that's\n    // pretty vague (since it could be anything). At least with the timeout, we know that\n    // if Sails is still lifting, the test will fail.\n    //\n    // In the future, we could use some other sort of IPC strategy to pull this off,\n    // but that would involve changes to the actual Sails core code base, which seems\n    // silly and potentially costly as far as time and technical debt in the code base.\n    // Anyway, it's unnecessary since this works so... daintily.\n    var N_SECONDS = 10;\n\n    // The max # of seconds to wait for graceful shutdown is used below.\n    // It's the other piece of how we know the app must have successfully lifted vs not.\n    // In addition to the N_SECONDS, this extra time gets tacked on at the very end below.\n    var MAX_SECONDS_TO_WAIT_FOR_GRACEFUL_SHUTDOWN = 1;\n\n    // Tell mocha not to look red and angry, since we totally planned for it to take this long.\n    this.slow((N_SECONDS*1000)+(MAX_SECONDS_TO_WAIT_FOR_GRACEFUL_SHUTDOWN*1000)+1500);\n\n\n    // This variable will hold the reference to the child process.\n    var sailsLiftProc;\n\n    // Spawn the child process\n    before(function(done) {\n      sailsLiftProc = MProcess.spawnChildProcess({\n        command: 'node',\n        cliArgs: opts.cliArgs,\n        environmentVars: opts.envVars\n      }).now();\n\n      // For debugging, as needed:\n      // sailsLiftProc.stdout.on('data', function (data){\n      //   console.log('stdout:',''+data);\n      // });\n      // sailsLiftProc.stderr.on('data', function (data){\n      //   console.log('stderr:',''+data);\n      // });\n\n      // After N seconds, continue to the test.\n      setTimeout(function (){\n        return done();\n      }, N_SECONDS*1000);\n    });//</before>\n\n\n    it('should still be lifted', function(done) {\n      // Technically, we don't really know if the server is still lifted in here.\n      // But we will find out in a bit (in the `after`).  So we'll do another little\n      // timeout, this time just somewhere between 0 and 500ms.  Why not have a little\n      // fun right?  Throwing a little entropy into the mix. Bam!\n      setTimeout(function (){\n        return done();\n      }, Math.floor(Math.random()*500));\n    });//</it>\n\n\n\n    // Now if httpRequestInstructions were provided, we ping to the server to see whether this puppy\n    // is ready to handle all those hot hot HTTP requests we have planned for it.\n    // (expectations of response vary based on options passed to this helper)\n    if (!_.isUndefined(opts.httpRequestInstructions)){\n      // If the `opts.expectFailedLift` flag was provided, we're actually expecting an error here.\n      if (opts.expectFailedLift) {\n        it('should FAIL when a `'+opts.httpRequestInstructions.method+'` request is sent to `'+opts.httpRequestInstructions.uri+', because we\\'re expecting Sails to have failed when attempting to lift`', function(done) {\n          request(opts.httpRequestInstructions, function(err, response, body) {\n            if (err) {\n              // Since the `opts.expectFailedLift` flag was provided, we're actually expecting an error here.\n              return done();\n            }\n            return done(new Error('Expected request to fail, since Sails should not have lifted successfully'));\n          });\n        });//</it>\n      }\n      // Normal case (expecting sails to have lifted)\n      else {\n        it('should respond with a 200 status code when a `'+opts.httpRequestInstructions.method+'` request is sent to `'+opts.httpRequestInstructions.uri+'`', function(done) {\n          request(opts.httpRequestInstructions, function(err, response, body) {\n            if (err) {\n              // This kind of \"omg server is not online\" error is generally rather bad.\n              return done(err);\n            }\n            if (response.statusCode !== (opts.httpRequestInstructions.expectedStatusCode||200)) {\n              return done(new Error('Expected to get a '+(opts.httpRequestInstructions.expectedStatusCode||200)+' status code from the server, but instead all we got was this lousy status code: `' + response.statusCode + '`'));\n            }\n            return done();\n          });\n        });//</it>\n      }\n    }//</httpRequestInstructions were provided>\n\n\n    // Now run any additional tests.\n    // (i.e. this function contains `it` blocks)\n    if (_.isFunction(opts.fnWithAdditionalTests)) {\n      opts.fnWithAdditionalTests();\n    }\n\n\n    // Now send a SIGTERM signal.\n    after(function (done){\n      MProcess.killChildProcess({\n        childProcess: sailsLiftProc,\n        maxMsToWait: MAX_SECONDS_TO_WAIT_FOR_GRACEFUL_SHUTDOWN*1000\n      }).exec(function (err){\n        // If it worked, that means our child process was\n        // still alive N seconds after it began lifting, and\n        // that it was able to shut down gracefully in a\n        // reasonable amount of time (see `maxMsToWait` above).\n        if (!err) {\n          // The one exception is if the `opts.expectFailedLift` flag was provided, in which case this\n          // is actually bad: In that case, it means the server clearly must have been lifted (since we\n          // just finished SIGTERMing it to death)\n          if (opts.expectFailedLift) {\n            return done(new Error('Hmm... But the Sails app should not have been lifted (the graceful shutdown should have failed).'));\n          }\n          // But otherwise in the general case, this means we're good.\n          return done();\n        }\n\n        // Otherwise it didn't work, which means our child process never lifted\n        // properly, or it was still lifting after N seconds.\n        //\n        // It also means we need to force-kill the child process or risk\n        // loosing little Grunt daemons to run amock in our machines.\n        MProcess.killChildProcess({\n          childProcess: sailsLiftProc,\n          force: true\n        }).exec(function (_forceKillErr){\n          // Now, if the `opts.expectFailedLift` flag was provided, this is actually what we want.\n          if (opts.expectFailedLift) {\n            return done();\n          }\n\n          // But normally it's totally bad news that we're having to SIGKILL this thing,\n          // so we send back an error whether or not the SIGKILL worked.\n          if (_forceKillErr) {\n            return done(new Error('So there was a problem:\\n'+err.stack+'\\nBut hey, also force-killing the child process didn\\'t work.  So something weird is going on, I\\'d say.  Check out the deets on that force kill error:\\n' + _forceKillErr.stack));\n          }\n          else {\n            return done(new Error('Should have been able to gracefully shut down child process, because it should have been lifted. Heres the error that came back when attempting the graceful shutdown (although honestly it prbly doesn\\'t matter-- it\\'s more likely the app just didn\\'t successfully lift).  Graceful shutdown error:\\n'+err.stack));\n          }\n        });\n      });\n    });//</after>\n  });//</describe>\n};\n\n"
  },
  {
    "path": "test/helpers/test-spawning-sails-lift-child-process-in-cwd.js",
    "content": "/**\n * Module dependencies\n */\n\nvar path = require('path');\nvar _ = require('@sailshq/lodash');\nvar request = require('@sailshq/request');\nvar MProcess = require('machinepack-process');\nvar testSpawningSailsChildProcessInCwd = require('./test-spawning-sails-child-process-in-cwd');\n\n/**\n * testSpawningSailsLiftChildProcessInCwd()\n *\n * This concisely-named test helper function injects a describe block, testing how Sails fares\n * when it comes time to `sails lift` in the current working directory.\n *\n * @required  {String} pathToSailsCLI\n *         the absolute path to the Sails CLI\n *\n * @required {Array} liftCliArgs\n *           an array of additional string CLI args/opts to pass to `sails lift`\n *           (e.g. `['--prod', '--port=1331']`)\n *\n * @optional {Dictionary} envVars\n *           a dictionary of environment variables to supply to the spawned process.\n *           Note: the default is to use the current process's environment, so if you set this\n *           option, you probably want to merge process.env into the value you use\n *\n * @optional {Dictionary} httpRequestInstructions\n *           A dictionary that gets passed in to `request()` when this helper attempts\n *           to contact the Sails server running in the child process.\n *           If provided at all, this can/must contain:\n *             @required {String} method\n *             @required {String} uri\n *             @optional {String} expectedStatusCode  [defaults to 200]\n *\n * @optional {Function} fnWithAdditionalTests\n *           A function with additional custom tests; that is, it has one or more `it()` blocks inside.\n *\n * @optional {Boolean} expectFailedLift\n *           A flag which, if enabled, causes this test helper to _expect_ the lift to fail.\n *           Also if it is set AND `httpRequestInstructions` are set, then the HTTP request\n *           will still be sent-- but just to _make sure_ it fails too.\n */\nmodule.exports = function testSpawningSailsLiftChildProcessInCwd (_opts){\n\n  if (!_.isArray(_opts.liftCliArgs)){\n    throw new Error('Consistency violation: Missing or invalid option (`liftCliArgs` should be an array)  in `testSpawningSailsLiftChildProcessInCwd()`. I may just be a test helper, but I\\'m serious about assertions!!!');\n  }\n\n  if (!_.isString(_opts.pathToSailsCLI)){\n    throw new Error('Consistency violation: Missing or invalid option (`pathToSailsCLI` should be a string) in `testSpawningSailsLiftChildProcessInCwd()`. I may just be a test helper, but I\\'m serious about assertions!!!');\n  }\n\n  var opts = _.extend({\n    cliArgs: [_opts.pathToSailsCLI, 'lift'].concat(_opts.liftCliArgs)\n  }, _opts);\n\n  return testSpawningSailsChildProcessInCwd(opts);\n\n};\n\n"
  },
  {
    "path": "test/hooks/blueprints/initialize.test.js",
    "content": "/**\n * Module dependencies\n */\nvar supertest = require('supertest');\n\nvar $Sails = require('../../helpers/sails');\nvar $Router = require('../../helpers/router');\n\n\ndescribe('Blueprints hook', function (){\n\n  describe('without ORM hook', function (){\n    $Sails.load({\n      globals: false,\n      loadHooks: [\n        'moduleloader',\n        'userconfig',\n        'blueprints'\n      ]\n    });\n\n\n    // TODO: test that blueprint actions are loaded\n    // TODO: test shadow routes are bound for:\n    //          + all controller actions\n    //          + controllers' index action\n  });\n\n\n\n  describe('with controllers hook', function (){\n    $Sails.load({\n      globals: false,\n      loadHooks: [\n        'moduleloader',\n        'userconfig',\n        'controllers',\n        'blueprints'\n      ]\n    });\n\n    // TODO: test that blueprint actions are loaded\n    // TODO: test shadow routes are bound:\n    //      + controller.*()\n    //      + controller.index()\n    //      + CRUD methods (find(),create(),etc.)\n    //        + RESTful (GET,POST,PUT,DELETE)\n    //        + URL-bar shortcuts (/find, /create, etc.)\n\n  });\n\n\n\n  describe('with ORM hook', function (){\n    $Sails.load({\n      globals: false,\n      loadHooks: [\n        'moduleloader',\n        'userconfig',\n        'orm',\n        'blueprints'\n      ]\n    });\n\n    // TODO: test that blueprint actions are loaded\n    // TODO: test shadow routes are bound:\n    //      + controller.*()\n    //      + controller.index()\n    //      + CRUD methods (find(),create(),etc.)\n    //        + RESTful (GET,POST,PUT,DELETE)\n    //        + URL-bar shortcuts (/find, /create, etc.)\n\n  });\n\n\n  describe('with ORM and controllers hooks', function (){\n    $Sails.load({\n      globals: false,\n      loadHooks: [\n        'moduleloader',\n        'userconfig',\n        'orm',\n        'controllers',\n        'blueprints'\n      ]\n    });\n\n    // TODO: test that blueprint actions are loaded\n    // TODO: test shadow routes are bound:\n    //      + controller.*()\n    //      + controller.index()\n    //      + CRUD methods (find(),create(),etc.)\n    //        + RESTful (GET,POST,PUT,DELETE)\n    //        + URL-bar shortcuts (/find, /create, etc.)\n\n  });\n\n  describe('with ORM and policies hooks', function (){\n    $Sails.load({\n      globals: false,\n      loadHooks: [\n        'moduleloader',\n        'userconfig',\n        'orm',\n        'policies',\n        'blueprints'\n      ]\n    });\n\n    // TODO: test that blueprint actions are loaded\n    // TODO: test shadow routes are bound:\n    //      + controller.*()\n    //      + controller.index()\n    //      + CRUD methods (find(),create(),etc.)\n    //        + RESTful (GET,POST,PUT,DELETE)\n    //        + URL-bar shortcuts (/find, /create, etc.)\n\n  });\n\n\n  describe('with controllers and policies hooks', function (){\n    $Sails.load({\n      globals: false,\n      loadHooks: [\n        'moduleloader',\n        'userconfig',\n        'controllers',\n        'policies',\n        'blueprints'\n      ]\n    });\n\n    // TODO: test that blueprint actions are loaded\n    // TODO: test shadow routes are bound:\n    //      + controller.*()\n    //      + controller.index()\n    //      + CRUD methods (find(),create(),etc.)\n    //        + RESTful (GET,POST,PUT,DELETE)\n    //        + URL-bar shortcuts (/find, /create, etc.)\n\n  });\n\n\n  describe('with controllers, policies, and orm hooks', function (){\n    $Sails.load({\n      globals: false,\n      loadHooks: [\n        'moduleloader',\n        'userconfig',\n        'controllers',\n        'policies',\n        'orm',\n        'blueprints'\n      ]\n    });\n\n    // TODO: test that blueprint actions are loaded\n    // TODO: test shadow routes are bound:\n    //      + controller.*()\n    //      + controller.index()\n    //      + CRUD methods (find(),create(),etc.)\n    //        + RESTful (GET,POST,PUT,DELETE)\n    //        + URL-bar shortcuts (/find, /create, etc.)\n\n  });\n\n\n});\n\n\n\n\n\n"
  },
  {
    "path": "test/hooks/http/initialize.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar assert = require('assert');\nvar util = require('util');\nvar Sails = require('../../../lib').Sails;\nvar $Router = require('../../helpers/router');\nvar request = require('@sailshq/request');\n\ndescribe('HTTP hook', function (){\n\n  describe('with custom bodyparser middleware config', function() {\n\n    var app;\n    before(function(done) {\n      app = Sails();\n      app.lift({\n        globals: false,\n        loadHooks: [\n          'moduleloader',\n          'userconfig',\n          'http'\n        ],\n        log: {level: 'silent'},\n        http: {\n          middleware: {\n            bodyParser: function(req, res, next) {\n              req.foo = 'bar';\n              return next();\n            }\n          }\n        },\n        routes: {\n          'get /': function(req, res) {return res.send(req.foo);}\n        },\n        port: 1342\n      }, done);\n    });\n\n    it('should be able to respond to requests using the custom bodyparser', function(done) {\n      request.get('http://localhost:1342', function(err, res, body) {\n        if (err) { return done(err); }\n        try {\n          assert.equal(body, 'bar');\n        }\n        catch (e) {return done(e);}\n        return done();\n      });\n    });\n\n    after(function(done) {\n      app.lower(done);\n    });\n  });\n\n  describe('with invalid `trustProxy` config', function() {\n\n    it('should throw an error', function(done) {\n\n      var app = Sails();\n      app.lift({\n        globals: false,\n        port: 1535,\n        environment: 'development',\n        log: {level: 'silent'},\n        http: {\n          trustProxy: ''\n        },\n        hooks: {grunt: false, pubsub: false},\n      }, function(err, _app) {\n\n        if (err && err.code && err.code === 'E_HTTP_BAD_TRUSTPROXY') {\n          return done();\n        }\n\n        if (err) {\n          return done(err);\n        }\n\n        _app.lower(function(err) {\n          if (err) {\n            return done(new Error('App lifted when it should have failed with E_HTTP_BAD_TRUSTPROXY.  Additionally, an error occurred while lowering: ' + util.inspect(err)));\n          }\n          return done(new Error('App lifted when it should have failed with E_HTTP_BAD_TRUSTPROXY'));\n        });\n\n      });\n\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/hooks/pubsub/initialize.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar Sails = require('../../../lib').Sails;\n\n\nxdescribe('Pubsub hook', function (){\n  describe('loading a Sails app', function (){\n\n    describe('without ORM hook', function (){\n      var app = Sails();\n      it('should fail', function (done){\n        app.load({\n          globals: false,\n          log: {level: 'silent'},\n          loadHooks: ['moduleloader','userconfig','pubsub']\n        }, function (err){\n          if (err) { return done(); }\n          return done(new Error('Should have failed to load the pubsub hook w/o the `orm` hook.'));\n        });\n      });\n      after(app.lower);\n    });\n\n    describe('without sockets hook', function (){\n      var app = Sails();\n      it('should fail', function (done){\n        app.load({\n          globals: false,\n          log: {level: 'silent'},\n          loadHooks: ['moduleloader','userconfig','orm', 'pubsub']\n        }, function (err){\n          if (err) { return done(); }\n          return done(new Error('Should have failed to load the pubsub hook w/o the `sockets` hook.'));\n        });\n      });\n      after(app.lower);\n    });\n\n    describe('without http hook', function (){\n      var app = Sails();\n      it('should fail', function (done){\n        app.load({\n          globals: false,\n          log: {level: 'silent'},\n          loadHooks: ['moduleloader','userconfig','orm', 'sockets', 'pubsub']\n        }, function (err){\n          if (err) { return done(); }\n          return done(new Error('Should have failed to load the pubsub hook w/o the `http` hook.'));\n        });\n      });\n      after(app.lower);\n    });\n\n    describe('with `orm` and `sockets` hooks', function (){\n      var app = Sails();\n      it('should load successfully', function (done){\n        app.load({\n          globals: false,\n          log: {level: 'warn'},\n          hooks: {\n            sockets: require('sails-hook-sockets'),\n            orm: require('sails-hook-orm'),\n          },\n          loadHooks: ['moduleloader','userconfig','orm', 'http', 'sockets', 'pubsub']\n        }, function (err){\n          if (err) { return done(err); }\n          return done();\n        });\n      });\n      after(app.lower);\n    });\n  });\n\n});\n"
  },
  {
    "path": "test/hooks/request/initialize.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar assert = require('assert');\nvar util = require('util');\nvar $Sails = require('../../helpers/sails');\nvar $Router = require('../../helpers/router');\n\n\ndescribe('Request hook', function (){\n\n  var sails = $Sails.load({\n    globals: false,\n    loadHooks: [\n      'moduleloader',\n      'userconfig',\n      'request'\n    ]\n  });\n\n  it('should expose `req.allParams()`', function (done) {\n    var ROUTEADDRESS = '/req_allParams';\n    sails.router.bind(ROUTEADDRESS, function (req, res) {\n      try {\n        assert(typeof req.allParams === 'function', 'req.allParams() should be defined when request hook is enabled.');\n      } catch (e) {\n        res.sendStatus(500);\n        return done(e);\n      }\n      res.sendStatus(200);\n      done();\n    })\n    .emit('router:request', {url: ROUTEADDRESS});\n  });\n\n\n  // NO LONGER SUPPORTED\n  it('should expose `req.validate()`-- but calling it should always fail', function (done) {\n    var ROUTEADDRESS = '/req_validate';\n    sails.router.bind(ROUTEADDRESS, function (req, res, next) {\n      assert(typeof req.validate === 'function', 'req.validate() should be defined when request hook is enabled.');\n\n      try {\n        req.validate('foo');\n      }\n      catch (e) {\n        return res.sendStatus(420);\n      }\n\n      return res.sendStatus(200);\n    });\n\n    sails.request(ROUTEADDRESS, function (err){\n      try {\n        assert(err && err.status === 420, new Error('Expecting error: it should no longer be supported'));\n      } catch (e) { return done(e); }\n\n      return done();\n    });\n\n  });//</it>\n\n});\n"
  },
  {
    "path": "test/hooks/request/req.metadata.test.js",
    "content": "var assert = require('assert');\nvar mixinMetadata = require('../../../lib/hooks/request/metadata');\n// var Sails = require('../../../lib/app');\n\ndescribe('Request hook', function () {\n  describe('metadata', function () {\n\n    beforeEach(function() {\n      this.req = {\n        headers: {},\n        get: function(key) { return this.headers[key]; },\n        app: {\n          data: { 'trust proxy': false },\n          get: function(key) { return this.data[key]; }\n        },\n        _sails: {\n          hooks: {},\n          config: {}\n        }\n      };\n    });\n\n    describe('without a reverse proxy', function() {\n      it('should set req.port to 80 for http requests', function() {\n        this.req.protocol = 'http';\n        this.req.host = 'example.org';\n        this.req.headers.Host = 'example.org';\n        mixinMetadata(this.req);\n        assert.equal(this.req.port, 80);\n      });\n\n      it('should set req.port to 443 for https requests', function() {\n        this.req.protocol = 'https';\n        this.req.host = 'example.org';\n        this.req.headers.Host = 'example.org';\n        mixinMetadata(this.req);\n        assert.equal(this.req.port, 443);\n      });\n\n      it('should not add a port to baseUrl on port 80 or 443', function() {\n        this.req.protocol = 'http';\n        this.req.host = 'example.org';\n        this.req.headers.Host = 'example.org';\n        mixinMetadata(this.req);\n        assert.equal(this.req.baseUrl, 'http://example.org');\n\n        this.req.protocol = 'https';\n        this.req.host = 'example.org';\n        this.req.headers.Host = 'example.org';\n        mixinMetadata(this.req);\n        assert.equal(this.req.baseUrl, 'https://example.org');\n      });\n\n      it('should add a port to baseUrl on a custom port', function() {\n        this.req.protocol = 'http';\n        this.req.host = 'example.org';\n        this.req.headers.Host = 'example.org:1337';\n        mixinMetadata(this.req);\n        assert.equal(this.req.baseUrl, 'http://example.org:1337');\n      });\n\n      it('should handle running as HTTP on port 443', function() {\n        this.req.protocol = 'http';\n        this.req.host = 'example.org';\n        this.req.headers.Host = 'example.org:443';\n        mixinMetadata(this.req);\n        assert.equal(this.req.port, 443);\n        assert.equal(this.req.baseUrl, 'http://example.org:443');\n      });\n    });\n\n    describe('with a reverse proxy and app.enable(\"trust proxy\")', function() {\n\n      beforeEach(function() {\n        // Fake a situation where trust proxy is enabled\n        // (without having set sails.config.http accordingly)\n        this.req.app.data['trust proxy'] = true;\n      });\n\n      /*\n       * In this case, req.protocol as set by Express is aware of the\n       * X-Forwarded-Proto header field. The only complication: req.host\n       * doesn't include a port number; req.header('host') might be wrong, too,\n       * so we can't trust it either.\n       */\n\n      it('should handle a simple HTTP case with X-Forwarded-Host', function() {\n        this.req.protocol = 'http'; // we assume Express got this right\n        this.req.host = 'server.local';\n        this.req.headers.Host = 'server.local';\n        this.req.headers['X-Forwarded-Host'] = 'example.org';\n        mixinMetadata(this.req);\n        assert.equal(this.req.port, 80);\n        assert.equal(this.req.baseUrl, 'http://example.org');\n      });\n\n      it('should handle when X-Forwarded-Host is not set', function() {\n        this.req.protocol = 'http'; // we assume Express got this right\n        this.req.host = 'server.local';\n        this.req.headers.Host = 'example.org:81';\n        mixinMetadata(this.req);\n        assert.equal(this.req.port, 81);\n        assert.equal(this.req.baseUrl, 'http://example.org:81');\n      });\n\n      it('should handle a simple HTTPS case with X-Forwarded-Host', function() {\n        this.req.protocol = 'https'; // we assume Express got this right\n        this.req.host = 'server.local';\n        this.req.headers.Host = 'server.local';\n        this.req.headers['X-Forwarded-Host'] = 'example.org';\n        mixinMetadata(this.req);\n        assert.equal(this.req.port, 443);\n        assert.equal(this.req.baseUrl, 'https://example.org');\n      });\n\n      it('should handle running on a nonstandard port', function() {\n        this.req.protocol = 'https'; // we assume Express got this right\n        this.req.host = 'server.local';\n        this.req.headers.Host = 'server.local:10000';\n        this.req.headers['X-Forwarded-Host'] = 'example.org';\n        mixinMetadata(this.req);\n        assert.equal(this.req.port, 443);\n        assert.equal(this.req.baseUrl, 'https://example.org');\n      });\n\n      it('should handle a list of x-forwarded-host values', function() {\n        this.req.protocol = 'https'; // we assume Express got this right\n        this.req.host = 'server2.local';\n        this.req.headers.Host = 'server2.local:10000';\n        this.req.headers['X-Forwarded-Host'] = 'example.org, server1.local';\n        mixinMetadata(this.req);\n        assert.equal(this.req.port, 443);\n        assert.equal(this.req.baseUrl, 'https://example.org');\n      });\n\n      it('should handle running on a weird port through a reverse proxy', function() {\n        this.req.protocol = 'http';\n        this.req.host = 'server.local';\n        this.req.headers.Host = 'server.local:1000';\n        this.req.headers['X-Forwarded-Host'] = 'example.org:81';\n        mixinMetadata(this.req);\n        assert.equal(this.req.port, 81);\n        assert.equal(this.req.baseUrl, 'http://example.org:81');\n      });\n\n      it('should handle running as HTTP on port 443', function() {\n        this.req.protocol = 'http';\n        this.req.host = 'server.local';\n        this.req.headers.Host = 'server.local:443';\n        this.req.headers['X-Forwarded-Host'] = 'example.org:443';\n        mixinMetadata(this.req);\n        assert.equal(this.req.port, 443);\n        assert.equal(this.req.baseUrl, 'http://example.org:443');\n      });\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/hooks/request/req.options.sticky.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar assert = require('assert');\n\nvar $Sails = require('../../helpers/sails');\nvar $Router = require('../../helpers/router');\n\n\n\n\n\ndescribe('Request hook', function (){\n\n  var sails = $Sails.load({\n    globals: false,\n    log: {\n      level: 'silent'\n    },\n    loadHooks: [\n      'moduleloader',\n      'userconfig',\n      'request',\n      'responses'\n    ]\n  });\n\n  describe('using req.options in multiple requests', function () {\n\n    var opts;\n    before(function(done) {\n      var ROUTEADDRESS = '/route';\n      sails.router.bind(ROUTEADDRESS, function (req,res,next) {\n        req.options[req.param('opt')] = true;\n        return res.status(200).send(JSON.stringify(req.options));\n      });\n      sails.emit('router:request', {\n        url: ROUTEADDRESS,\n        query: {\n          opt: 'foo'\n        }\n      }, {send: function(){}});\n      sails.emit('router:request', {\n        url: ROUTEADDRESS,\n        query: {\n          opt: 'bar'\n        }\n      }, {\n        send: function (data) {\n          opts = JSON.parse(data);\n          return done();\n        }\n      });\n    });\n\n    it('req.options should not be sticky', function () {\n      assert(!opts.foo, 'req.options.foo from first request carried over to second request!');\n    });\n\n  });\n\n\n\n});\n"
  },
  {
    "path": "test/hooks/views/ejs/index.i18n.ejs",
    "content": "<h1><%= __('hello') %></h1>\n"
  },
  {
    "path": "test/hooks/views/intialize.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar assert = require('assert');\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\nvar Sails = require('../../../lib').constructor;\n\n\ndescribe('Views hook', function (){\n\n  it('should FAIL to initialize if the http hook is NOT enabled', function (done) {\n\n    var sailsApp = new Sails();\n    sailsApp.load({\n      globals: false,\n      log: { level: 'silent' },\n      loadHooks: [\n        'moduleloader',\n        'userconfig',\n        'views'\n      ]\n    }, function (err) {\n      try {\n        if(!err) { throw new Error('Should have failed to failed to load Sails!'); }\n        if (err.code !== 'E_HOOKINIT_DEP') { throw new Error('Should have failed w/ error code: `E_HOOKINIT_DEP`.  But instead, code is: `'+err.code+'`'); }\n        if (!_.isUndefined(err.status)) { throw new Error('Error should not have a `status` property!  But instead, status is: `'+err.status+'`'); }\n      } catch (e) { return done(e); }\n      return done();\n    });\n\n  });//</it>\n\n\n  it('should initialize as long as the http hook is included (even without the session hook)', function (done) {\n\n    var sailsApp = new Sails();\n    sailsApp.load({\n      globals: false,\n      loadHooks: [\n        'moduleloader',\n        'userconfig',\n        'http',\n        'views'\n      ]\n    }, done);\n\n  });//</it>\n\n\n  it('should initialize w/ the session hook', function (done) {\n\n    var sailsApp = new Sails();\n    sailsApp.load({\n      globals: false,\n      loadHooks: [\n        'moduleloader',\n        'userconfig',\n        'http',\n        'session',\n        'views'\n      ]\n    }, done);\n\n  });//</it>\n\n});\n\n"
  },
  {
    "path": "test/hooks/views/locales/en.json",
    "content": "{\n  \"hello\": \"Hello\"\n}\n"
  },
  {
    "path": "test/hooks/views/locales/es.json",
    "content": "{\n  \"hello\": \"Hola\"\n}\n"
  },
  {
    "path": "test/hooks/views/locales/eu.json",
    "content": "{\n  \"hello\": \"Kaixo\"\n}\n"
  },
  {
    "path": "test/hooks/views/res.render.i18n.js",
    "content": "/**\n * Module dependencies\n */\n\nvar assert = require('assert');\nvar Sails = require('../../../lib').constructor;\nvar path = require('path');\n\ndescribe('sails.hooks.views.render() with i18n', function (){\n\n  var renderWithLocale = function(req, res){\n    var data = {\n      locale: req.param('lang'),\n      layout: false\n    };\n    sails.hooks.views.render('index.i18n', data, function(err, html){\n      if (err) {\n        return res.status(500).send(err);\n      }\n      return res.status(200).send(html);\n    });\n  };\n\n  var renderWithoutLocale = function(req, res){\n    var data = {\n      layout: false\n    };\n    sails.hooks.views.render('index.i18n', data, function(err, html){\n      if (err) {\n        return res.status(500).send(err);\n      }\n      return res.status(200).send(html);\n    });\n  };\n\n  // Load a Sails app\n  var app;\n  before(function (done) {\n    app = new Sails()\n    .load({\n      globals: { sails: true, models: false, _: false, async: false, services: false } ,\n      loadHooks: [\n        'moduleloader',\n        'userconfig',\n        'http',\n        'logger',\n        'i18n',\n        'views'\n      ],\n      routes: {\n        'get /render/:lang': renderWithLocale,\n        'get /render'      : renderWithoutLocale\n      },\n      i18n: {\n        locales: ['en', 'es', 'eu'],\n        defaultLocale: 'eu',\n        localesDirectory: path.join(__dirname, './locales')\n      },\n      paths: {\n        views: path.join(__dirname, './ejs')\n      }\n    }, done);\n  });\n\n\n  it('should show the message in basque', function (done) {\n    app\n    .request({url:'/render/eu', method: 'get'}, function(err, res){\n      assert.equal('<h1>Kaixo</h1>', res.body.trim());\n      done();\n    });\n  });\n\n  it('should show the message in spanish', function (done) {\n    app\n    .request({url:'/render/es', method: 'get'}, function(err, res){\n      assert.equal('<h1>Hola</h1>', res.body.trim());\n      done();\n    });\n  });\n\n  it('should show the message in english', function (done) {\n    app\n    .request({url:'/render/en', method: 'get'}, function(err, res){\n      assert.equal('<h1>Hello</h1>', res.body.trim());\n      done();\n    });\n  });\n\n  it('should show the message in basque by default if unknown lang', function (done) {\n    app\n    .request({url:'/render/de', method: 'get'}, function(err, res){\n      assert.equal('<h1>Kaixo</h1>', res.body.trim());\n      done();\n    });\n  });\n\n  it('should show the message in basque by default', function (done) {\n    app\n    .request({url:'/render', method: 'get'}, function(err, res){\n      assert.equal('<h1>Kaixo</h1>', res.body.trim());\n      done();\n    });\n  });\n\n});\n"
  },
  {
    "path": "test/hooks/views/res.view.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar assert = require('assert');\nvar util = require('util');\nvar Sails = require('../../../lib').constructor;\nvar RouteFactory = require('root-require')('test/helpers/RouteFactory.helper');\n\n\ndescribe('res.view()', function (){\n\n  // Get some mock routes to use in the tests below\n  var mockRoutes = RouteFactory('res_view()');\n\n  // Load a Sails app\n  var app;\n  before(function (done) {\n    app = new Sails()\n    .load({\n      globals: false,\n      loadHooks: [\n        'moduleloader',\n        'userconfig',\n        'http',\n        'views'\n      ]\n    }, done);\n  });\n\n\n  it('should exist when the views hook is enabled', function (done) {\n    app\n    .get(mockRoutes.next(), function (req, res) {\n      assert.equal(typeof res.view, 'function', 'res.view() should be defined when request hook is enabled.');\n      return res.sendStatus(200);\n    })\n    .request(mockRoutes.current, done);\n  });\n\n\n  it.skip('should work w/ basic usage', function (done) {\n\n    // NOTE:\n    // In order to test this using `app.request` (i.e. w/o lifting),\n    // we need to finish the `res.render()` functionality in core.\n\n    // TODO: adapt this test accordingly\n\n    app.get(mockRoutes.next(), function (req, res) {\n      return res.view('foo');\n    })\n    .request(mockRoutes.current, function (err, res, body) {\n      if (err) return done(err);\n      assert.equal(res.statusCode, 200);\n      assert.equal(body, VIEW_CONTENTS);\n    });\n  });\n\n});\n\n"
  },
  {
    "path": "test/hooks/views/skipAssets.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar path = require('path');\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\nvar tmp = require('tmp');\nvar request = require('@sailshq/request');\nvar MProcess = require('machinepack-process');\nvar MFilesystem = require('machinepack-fs');\nvar testSpawningSailsLiftChildProcessInCwd = require('../../helpers/test-spawning-sails-lift-child-process-in-cwd');\nvar appHelper = require('../../integration/helpers/appHelper');\n\ntmp.setGracefulCleanup();\n\n\n\ndescribe('skipAssets', function() {\n\n  describe('Generate and lift a sails app which has a wildcard route WITHOUT using skipAssets', function() {\n\n    // Track the location of the Sails CLI, as well as the current working directory\n    // before we stop hopping about all over the place.\n    var originalCwd = process.cwd();\n    var pathToSailsCLI = path.resolve(__dirname, '../../../bin/sails.js');\n\n    var pathToTestApp;\n\n    before(function(done) {\n      // Create a temp directory.\n      var tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n      pathToTestApp = path.resolve(tmpDir.name, 'testApp');\n      // Create a new Sails app.\n      MProcess.executeCommand({\n        command: util.format('node %s new %s --fast --traditional --without=lodash,async', pathToSailsCLI, 'testApp'),\n      }).exec(function(err) {\n        if (err) {return done(err);}\n        appHelper.linkDeps(pathToTestApp);\n        return done();\n      });\n    });\n\n\n    // Change its routes file to use `skipAssets`.\n    before(function (done){\n      MFilesystem.write({\n        destination: path.join(pathToTestApp, 'config/routes.js'),\n        string: 'module.exports.routes = { \\'get /*\\': function(req, res) {return res.header(\\'content-type\\',\\'text/plain\\').send(\\'some text\\'); } };',\n        force: true,\n      }).exec(done);\n    });\n\n    // And CD in.\n    before(function (){\n      process.chdir(pathToTestApp);\n    });\n\n    // Lift the app using `sails lift` in the CWD,\n    // ensuring everything works as expected.\n    describe('running `sails lift', function (){\n      testSpawningSailsLiftChildProcessInCwd({\n        pathToSailsCLI: pathToSailsCLI,\n        liftCliArgs: ['--port=1331', '--hooks.grunt=false', '--hooks.pubsub=false', '--traditional'],\n        httpRequestInstructions: {\n          method: 'GET',\n          uri: 'http://localhost:1331',\n        },\n        fnWithAdditionalTests: function (){\n          it('should return a view when requesting `http://localhost:1331/js/dependencies/sails.io.js`', function (done){\n            request({\n              method: 'GET',\n              uri: 'http://localhost:1331/js/dependencies/sails.io.js',\n            }, function(err, response, body) {\n              if (err) { return done(err); }\n              try {\n                if (!_.isString(response.headers['content-type'])) {\n                  return done(new Error('Expected a response content-type header when requesting an asset. `skipAssets` seems to be failing silently!'));\n                }\n                if (!response.headers['content-type'].match(/text\\/plain/)) {\n                  return done(new Error('Expected text response content-type header when requesting an asset (but got `'+response.headers['content-type']+'`). `skipAssets` seems to be failing silently!'));\n                }\n                if (body !== 'some text') {\n                  return done(new Error('Expected text response an asset (but got `'+body+'`). `skipAssets` seems to be failing silently!'));\n                }\n              }\n              catch (e) { return done(e); }\n              return done();\n            });\n          });\n        }\n      });\n    });\n\n    // And CD back to where we were before.\n    after(function () {\n      process.chdir(originalCwd);\n    });\n\n\n  });\n\n  describe('Generate and lift a sails app which has a wildcard route using skipAssets', function() {\n\n    // Track the location of the Sails CLI, as well as the current working directory\n    // before we stop hopping about all over the place.\n    var originalCwd = process.cwd();\n    var pathToSailsCLI = path.resolve(__dirname, '../../../bin/sails.js');\n\n    var pathToTestApp;\n\n    before(function(done) {\n      // Create a temp directory.\n      var tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n      pathToTestApp = path.resolve(tmpDir.name, 'testApp');\n      // Create a new Sails app.\n      MProcess.executeCommand({\n        command: util.format('node %s new %s --fast --traditional --without=lodash,async', pathToSailsCLI, 'testApp'),\n      }).exec(function(err) {\n        if (err) {return done(err);}\n        appHelper.linkDeps(pathToTestApp);\n        return done();\n      });\n    });\n\n\n    // Change its routes file to use `skipAssets`.\n    before(function (done){\n      MFilesystem.write({\n        destination: path.join(pathToTestApp, 'config/routes.js'),\n        string: 'module.exports.routes = { \\'get /*\\': { view: \\'pages/homepage\\', skipAssets: true }, \\'/js/dependencies/sails.io.js\\': function(req,res){ return res.header(\\'content-type\\', \\'application/javascript\\').send(\\'ok\\'); } };',\n        force: true,\n      }).exec(done);\n    });\n\n    // And CD in.\n    before(function (){\n      process.chdir(pathToTestApp);\n    });\n\n    // Lift the app using `sails lift` in the CWD,\n    // ensuring everything works as expected.\n    describe('running `sails lift', function (){\n      testSpawningSailsLiftChildProcessInCwd({\n        pathToSailsCLI: pathToSailsCLI,\n        liftCliArgs: ['--port=1331', '--hooks.grunt=false', '--hooks.pubsub=false'],\n        httpRequestInstructions: {\n          method: 'GET',\n          uri: 'http://localhost:1331',\n        },\n        fnWithAdditionalTests: function (){\n          it('should return a JavaScript file when requesting `http://localhost:1331/js/dependencies/sails.io.js`', function (done){\n            request({\n              method: 'GET',\n              uri: 'http://localhost:1331/js/dependencies/sails.io.js',\n            }, function(err, response, body) {\n              if (err) { return done(err); }\n              try {\n                if (!_.isString(response.headers['content-type'])) {\n                  return done(new Error('Expected a response content-type header when requesting an asset. `skipAssets` seems to be failing silently!'));\n                }\n                if (!response.headers['content-type'].match(/application\\/javascript/)) {\n                  return done(new Error('Expected javascript response content-type header when requesting an asset (but got `'+response.headers['content-type']+'`). `skipAssets` seems to be failing silently!'));\n                }\n                if (body !== 'ok') {\n                  return done(new Error('Expected body of `sails.io.js` file to be returned, but instead got something else. `skipAssets` seems to be failing silently!'));\n                }\n              }\n              catch (e) { return done(e); }\n              return done();\n            });\n          });\n        }\n      });\n    });\n\n    // And CD back to where we were before.\n    after(function () {\n      process.chdir(originalCwd);\n    });\n\n\n  });//</Generate and lift a sails app which has a wildcard route using skipAssets>\n});\n\n\n"
  },
  {
    "path": "test/init.js",
    "content": "// Initialization script that runs once before any tests.\n//\n\n// Set the \"SAILS_NEW_LINK\" env var so that the \"sails new\" generator\n// always uses symlinks (rather than doing npm install) regardless of\n// which NPM version is installed.\n//\n// Traditionally, \"sails new\" has sped up the process of generating a\n// new Sails app by symlinking required project dependencies from the\n// Sails module's node_modules folder.  However, starting with NPM 3,\n// this shortcut will cause subsequent dependencies (installed by the\n// end-user) to fail on install, due to the new flattened node_modules\n// file structure.\n//\n// Starting with NPM 3, doing \"sails new\" currently causes\n// \"npm install\" to run, in order for the dependencies in the new Sails\n// app to be properly flattened.  However, this takes a long time--too long\n// for tests.  Since none of the tests install separate dependencies\n// in the fixture app, we can get away with using the old symlink strategy,\n// which can be activated with a --link option, or with an environment var.\nprocess.env.SAILS_NEW_LINK = true;\n"
  },
  {
    "path": "test/integration/README.md",
    "content": "# Integration tests\n\nThe goal of these tests is to run Sails just like you or I would, and verify that no anomolies have been introduced in general.\n\nThis is a great place to jump in if you're interested in contributing code to Sails core!\n\n## How Can I Help?\n\nWe could use your help writing more integration tests that test Sails under specific conditions.\n\n#### Integration tests for each core hook\n\nWhen writing an integration tests that verifies the behavior of a particular hook, the following assertions should be made:\n\n1. Did the hook's default config make it into `sails.config`?\n2. Did `sails.load` work as expected with the hook enabled and all the hooks it depends on enabled?\n3. Did `sails.lift` work as expected with the hook enabled and all the hooks it depends on enabled?\n4. Post-`sails.load`, is the process/application state correct? (i.e. did the hook do what it was supposed to do?)\n5. Did the hook do what it was supposed to do after tearing down the server using `sails.lower()`?\n\n\n#### Integration tests for the hook loader\n\nWe could really use more tests for the following cases:\n\n1. If we `sails.load` using `loadHooks` to allow only specific hooks, or `hooks` to disable particular hooks, only the specified hooks should actually be loaded.\n2. `sails.load()` should fail if a test tries to load a hook that depends on other hooks, but those other hooks are disabled.  Note that `sails.load` should _fail with a relevant error message_ and _should not_ hang in this case.\n\n\n"
  },
  {
    "path": "test/integration/cert/sailstest-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIC8zCCAdugAwIBAgIUVJ+uHbbJ46i7KrMx4BR8RofnQU4wDQYJKoZIhvcNAQEL\nBQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDEwOTAyNTAzM1oXDTM2MDEw\nNzAyNTAzM1owFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF\nAAOCAQ8AMIIBCgKCAQEAwZ3B22dLj9gzxzDHhLIb6iVoQ2uwUCmr6eEYbHQlgkfM\ns0ssmvS2QZZ2246k1W2r/DvjkO2Xobmtzm5uvAPoCyc6gcwJgehpNJCvH5ON+Zid\n+3ow5VzhVZi023vYTfQ7K1fbdUEY9DF776hZSFk5WvUyj5L+Pu+mxcFLdNZ1PYEo\nYyPj5wnFswiua474euscKpr4OXykRuOeGikBipLsZtAeTi727WA922/q5vuCZO1g\nBxCZLoLDyVSgQqC2zepPI69w0Chq6LE4zsRHDUMZ3K+IVPu3Z8E8POeREQqyIUhi\nRZusplCqllIVTAuWNXwFvICji0r/8bIeX2JvR9XxowIDAQABoz0wOzAaBgNVHREE\nEzARgglsb2NhbGhvc3SHBH8AAAEwHQYDVR0OBBYEFF4uEUf8VhSWqMbsKgRnhyW4\nc9RYMA0GCSqGSIb3DQEBCwUAA4IBAQBXDTR6QZiVaKnuChHKuHdsIbl27+YQKxlF\nuQksB0SXiTek6tK2euI/1MNkIxLxtK6Hjh9cOWmCp/4rE77Xrw6WaztFVLTj3zAY\ndUOaAtpi1mC6kQtfbF456rBvwOoQeR96HmrTk9epILSgV5+eqfBJIxyrSsoFExEK\nl3LMMxkD0Y/S7TT7zmt29m+Pj26ArZIIW7i3NdaqJNlOb08761wMghvGrGjGBCQq\nseKH4BD5K03qr1D69h61mg0JIbSgZqWhRL4pvDRKYG0nprIu7fS9+xv25UAhCp0N\n9f8vvUS+yduuUhp/1AKhstoPX4vPUHLPYn0kLjZShEiyPE9ZRXOB\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/integration/cert/sailstest-key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDBncHbZ0uP2DPH\nMMeEshvqJWhDa7BQKavp4RhsdCWCR8yzSyya9LZBlnbbjqTVbav8O+OQ7Zehua3O\nbm68A+gLJzqBzAmB6Gk0kK8fk435mJ37ejDlXOFVmLTbe9hN9DsrV9t1QRj0MXvv\nqFlIWTla9TKPkv4+76bFwUt01nU9gShjI+PnCcWzCK5rjvh66xwqmvg5fKRG454a\nKQGKkuxm0B5OLvbtYD3bb+rm+4Jk7WAHEJkugsPJVKBCoLbN6k8jr3DQKGrosTjO\nxEcNQxncr4hU+7dnwTw855ERCrIhSGJFm6ymUKqWUhVMC5Y1fAW8gKOLSv/xsh5f\nYm9H1fGjAgMBAAECggEAEXxmT6LELj+M0MaIN/6MV6WW0jZdlaY0NbEm++4Zaesc\ngtM206yaJGsRKcJEnL0eggjR0Pr3bZtPv4vPSgFIUAmNvSWb2wGnm4aEA/2j6t2f\nsJO+hFhsRi2LPlWwmVaFwxclZZePFPuLGxlz+f0k5iDftuiQ0a178FkFFlsJlGSq\nkC9pedSyCqoWuFpfVvpoe4rn3HN5e3BnrqjX2gk2+fTcVP0QwLoS+8YsmTs/sMgG\nueQoWhBlwKqeEEaRbhhACeCjSfplDVkrQUEeGiHq917IYvGgWDryDUxNWGsTccSD\n8IuXrnJKvmQWW8GrrqtyUQXtz/Eu18fFcDi/7qGi0QKBgQD108gf7qXAgR598ywv\n6t43+6MHsBYf9ZNbSMsOTufVUSF7yYxm0FW82NmkLizY1KWymONPjOz7WdmrqyjF\n3w5Ujf+wNq6t/TjCWD7EYpwvDk8qhp2r4vpJG6ie/B1oBMazaz23B2/i57Ftbr7w\n5NopKB4bEGcZmo3vw1bgnuwvDQKBgQDJoN4xkasdZFLeV263jKuY/w3v+e+ypO7l\nPl+6MFv/v0WN+KVKVLssy93MiOQBmyg6haIXlnIQkYMkSTT7goA6FydfHhC2TgGh\nzOxFOrKZgYCkmHUe1SKvBftXQqJ8nErEACeo48475Y5n0tk0V+JnI4Vo9qtGXr6S\n2ciYtCj3bwKBgQCEH8e4IfREeyGAYGqndnzpaf496454ruz8ayt4DUDdjjWI6tLj\nj6YFUifn7kl8YQ6N506FOyFEFw6/DcdkUnbJS2jZtQo9yZPwIK3br4RyZiZ2nNOx\nxtTu5kbC7I6Bkc+aL1GERiMEubLLNnK51sbKyB0mPrKrOD6BV2QiZkhbIQKBgAPN\neONOb/+56KYw1/G2QXY9OTIRcKfZ3HeOWZfVWabVIKawzc09E9qgbapx2nr9RiD0\nbD4tpDETzXlduBYWO/zceu2cT4xgpD888ifMF5o1iwuPpIXUVzcd0cOvigj3maFg\nr17MDROsHKdwnpASKD7xuI5mOIy3NLjoSpQ2sZ8ZAoGAYV+248Oxt2WQsy/zeXoA\ntFBNonGL39TtqmiD7553Q/VaR1BpwdClJE51XN+xEJVhBfhmNh1oREZFHWQC1smF\nJnmabcjHjP8VrwvRy/yrtUoefsvMGZnDFd+hnBJRdL9JoyHDtYb+IbpjM+ZtZVbz\nWgWiitQeWoCY86qONNPmWWs=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "test/integration/fixtures/hooks/installable/add-policy/index.js",
    "content": "module.exports = function(sails) {\n\n  /**\n   * List of hooks that required for adminpanel to work\n   */\n  var requiredHooks = [\n    'blueprints',\n    'controllers',\n    'http',\n    'policies',\n  ];\n\n  function forbiddenPolicy(req, res, next) {\n    return res.forbidden();\n  }\n\n  return {\n\n    defaults: {\n      __configKey__: {}\n    },\n\n    initialize: function(cb) {\n      try {\n        var eventsToWaitFor = [];\n        try {\n          /**\n           * Check hooks availability\n           */\n          _.forEach(requiredHooks, function (hook) {\n            if (!sails.hooks[hook]) {\n              throw new Error('Cannot use `add-policy` hook without the `' + hook + '` hook.');\n            }\n            //if (hook === 'policies') {\n            //    eventsToWaitFor.push('hook:' + hook + ':bound');\n            //} else {\n            eventsToWaitFor.push('hook:' + hook + ':loaded');\n            //}\n          });\n        } catch(err) {\n          if (err) {\n            return cb(err);\n          }\n        }\n        if (!sails.hooks.policies.middleware) {\n          sails.hooks.policies.middleware = {};\n        }\n        sails.hooks.policies.middleware.forbidden = forbiddenPolicy;\n\n        cb();\n      } catch(err) {\n        return cb(err);\n      }\n    }\n\n  };\n\n};\n"
  },
  {
    "path": "test/integration/fixtures/hooks/installable/add-policy/package.json",
    "content": "{\n  \"name\": \"sails-hook-add-policy\",\n  \"main\": \"index.js\",\n  \"sails\": {\n    \"isHook\": true\n  }\n}\n"
  },
  {
    "path": "test/integration/fixtures/hooks/installable/async/index.js.txt",
    "content": "module.exports = function(sails) {\n\n  return {\n\n    initialize: async function(cb) {\n\n      var dumb = function() {\n        return new Promise(function(resolve, reject) {\n          setTimeout(function() {\n            if (sails.config.custom && sails.config.custom.reject) {\n              return reject('foo');\n            }\n            return resolve('foo')\n          }, 100);\n        });\n      };\n      this.val = await dumb();\n      return cb();\n    }\n\n  };\n\n};\n"
  },
  {
    "path": "test/integration/fixtures/hooks/installable/shout/index.js",
    "content": "module.exports = function(sails) {\n\n  var phrase;\n  return {\n\n    defaults: {\n      __configKey__: {\n        phrase: 'make it rain'\n      }\n    },\n\n    initialize: function(cb) {\n      phrase = sails.config[this.configKey].phrase;\n      this.isShoutyHook = true;\n      cb();\n    },\n\n    routes: {\n      before: {\n        'GET /shout': function(req, res, next) {\n          res.send(phrase);\n        }\n      }\n    }\n\n  };\n\n};\n"
  },
  {
    "path": "test/integration/fixtures/hooks/installable/shout/package.json",
    "content": "{\n  \"name\": \"sails-hook-shout\",\n  \"main\": \"index.js\",\n  \"sails\": {\n    \"isHook\": true\n  }\n}\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/api/controllers/EmptyController.js",
    "content": "module.exports = {};\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/api/controllers/PetController.js",
    "content": "module.exports = {\n\n  watch: function(req, res) {\n    req._sails.models.pet.watch(req);\n    res.sendStatus(200);\n  }\n\n};\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/api/controllers/QuizController.js",
    "content": "module.exports = {};\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/api/controllers/TestController.js",
    "content": "module.exports = {\n  verb: function(req, res) {\n    res.send(req.method.toLowerCase());\n  },\n\n  dynamic: function(req, res) {\n    res.json(req.allParams());\n  },\n\n  index: function(req, res) {\n    res.send('index');\n  },\n\n  find: function(req, res) {\n    res.send('find');\n  },\n\n  findOne: function(req, res) {\n    res.send('findOne');\n  },\n\n  create: function(req, res) {\n    res.send('create');\n  },\n\n  update: function(req, res) {\n    res.send('update');\n  },\n\n  destroy: function(req, res) {\n    res.send('destroy');\n  },\n\n  CapitalLetters: function(req, res) {\n    res.send('CapitalLetters');\n  }\n};\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/api/controllers/UserController.js",
    "content": "module.exports = {\n\n  watch: function(req, res) {\n    req._sails.models.user.watch(req);\n    res.sendStatus(200);\n  },\n\n  message: function(req, res) {\n    req._sails.models.user.findOne({\n      user_id: 1\n    }, function(err, user) {\n      if (err) {\n        return res.status(500).json({\n          error: err\n        });\n      }\n      else if (!user) {\n        return res.status(404).json({\n          error: 'Expected specified user (with user_id=1) to exist...'\n        });\n      } else {\n        req._sails.models.user.publish([user.user_id], {\n          greeting: 'hello'\n        }, req);\n        return res.sendStatus(200);\n      }\n    });\n  },\n\n  subscribe: function(req, res) {\n\n    req._sails.models.user.subscribe(req, [req.param('id')]);\n    res.sendStatus(200);\n  }\n\n\n};\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/api/controllers/UserProfileController.js",
    "content": "module.exports = {};"
  },
  {
    "path": "test/integration/fixtures/sampleapp/api/controllers/ViewTestController.js",
    "content": "module.exports = {\n\n  index: function(req, res, next) {\n    res.view();\n  },\n\n  create: function(req, res, next) {\n    res.view();\n  },\n\n    viewOptions: function(req, res, next) {\n      res.view();\n    },\n\n    viewOptionsOverride: function(req, res, next) {\n      res.view('viewtest/viewOptions', {foo:'!baz!'});\n    },\n\n    csrf: function(req, res, next) {\n      res.view();\n    }\n\n};\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/api/models/Empty.js",
    "content": "module.exports = {\n  attributes: {\n    foo: 'string'\n  }\n};\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/api/models/Pet.js",
    "content": "module.exports = {\n  primaryKey: 'pet_id',\n  attributes: {\n    id: false,\n    pet_id: {\n      type: 'integer',\n      autoIncrement: true\n    },\n    name: 'string',\n    owner: {\n      model: 'user'\n    },\n    bestFriend: {\n      model: 'user'\n    },\n    parents: {\n      collection: 'pet',\n      via: 'children'\n    },\n    children: {\n      collection: 'pet',\n      via: 'parents'\n    },\n    bestFurryFriend: {\n      model: 'pet'\n    },\n    vets: {\n      collection: 'user',\n      via: 'patients'\n    },\n    isPet: {\n      type: 'boolean',\n      defaultsTo: true\n    }\n  }\n};\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/api/models/Quiz.js",
    "content": "module.exports = {\n  attributes: {\n    bar: 'string'\n  }\n};\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/api/models/Test.js",
    "content": "module.exports = {};"
  },
  {
    "path": "test/integration/fixtures/sampleapp/api/models/User.js",
    "content": "module.exports = {\n  primaryKey: 'user_id',\n  attributes: {\n    id: false,\n    user_id: {\n      type: 'integer',\n      autoIncrement: true\n    },\n    name: 'string',\n    pets: {\n      collection: 'pet',\n      via: 'owner'\n    },\n    patients: {\n      collection: 'pet',\n      via: 'vets'\n    },\n    profile: {\n      model: 'userprofile'\n    }\n  }\n\n};\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/api/models/UserProfile.js",
    "content": "module.exports = {\n\n  attributes: {\n    user: {\n      model: 'user'\n    },\n    zodiac: 'string'\n  }\n\n};\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/api/policies/error_policy.js",
    "content": "/**\n * Error Policy Fixture\n *\n * Sends an Error Object to the callback\n */\n\nmodule.exports = function(req, res, next) {\n  return res.serverError('Test Error');\n};\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/api/policies/fake_auth.js",
    "content": "/**\n * Fake Auth Policy Fixture\n *\n * Fakes an Authentication\n */\n\nmodule.exports = function(req, res, next) {\n  req.session.authenticated = true;\n  next();\n};"
  },
  {
    "path": "test/integration/fixtures/sampleapp/api/services/TestService.js",
    "content": "module.exports = {};\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/config/local.js",
    "content": "module.exports = {\n  log: {\n    level: 'silent'\n  },\n  views: {\n    locals: {\n      foo: '!bar!'\n    }\n  },\n  models: {\n    migrate: 'alter',\n    schema: true\n  },\n  globals: false\n};\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/views/app/index.ejs",
    "content": "App index file\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/views/app/user/homepage.ejs",
    "content": "I'm deeply nested!\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/views/pages/homepage.ejs",
    "content": "<!-- Default home page -->\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/views/viewtest/create.ejs",
    "content": "createView\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/views/viewtest/csrf.ejs",
    "content": "csrf=<%= _.isUndefined(_csrf) ? 'no_token' : _csrf %>\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/views/viewtest/index.ejs",
    "content": "indexView\n"
  },
  {
    "path": "test/integration/fixtures/sampleapp/views/viewtest/viewOptions.ejs",
    "content": "<%=foo%>"
  },
  {
    "path": "test/integration/fixtures/users.js",
    "content": "var async = require('async');\nmodule.exports = function(sails, cb) {\n\n  var users = [];\n  for (var userId = 1; userId <= 11; userId++) {\n      var user = {\n          name: 'user_'+userId,\n          pets: [],\n          profile: {}\n      };\n      for (var petId = 1; petId <= 11; petId++) {\n          user.pets.push({name: 'user_'+userId+'_pet_'+petId});\n      }\n      user.profile.zodiac = 'user_'+userId+'_zodiac';\n      users.push(user);\n  }\n\n  async.forEach(users, function create(user, cb) {sails.models.user.create(user).exec(cb);}, cb);\n\n};\n"
  },
  {
    "path": "test/integration/generate.test.js",
    "content": "describe('API and adapter generators', function() {\n\n  var assert = require('assert');\n  var fs = require('fs-extra');\n  var exec = require('child_process').exec;\n  var path = require('path');\n\n  // Make existsSync not crash on older versions of Node\n  fs.existsSync = fs.existsSync || require('path').existsSync;\n\n  function capitalize(string) {\n    return string.charAt(0).toUpperCase() + string.slice(1);\n  }\n  var sailsBin = path.resolve('./bin/sails.js');\n  var appName = 'testApp';\n\n  this.slow(1000);\n\n  before(function(done) {\n\n    if (fs.existsSync(appName)) {\n      fs.removeSync(appName);\n    }\n\n    exec('node ' + sailsBin + ' new ' + appName + ' --fast --traditional --without=lodash,async', function(err) {\n      if (err) { return done(new Error(err)); }\n\n      // Move into app directory and update sailsBin relative path\n      process.chdir(appName);\n      sailsBin = path.resolve('..', sailsBin);\n\n      done();\n    });\n  }); //</before>\n\n  after(function(done) {\n\n    // return to test directory\n    process.chdir('../');\n\n    if (fs.existsSync(appName)) {\n      fs.removeSync(appName);\n    }\n\n    done();\n  });\n\n  describe('sails generate model <modelname>', function() {\n    var modelName = 'user';\n\n    it('should throw an error if no model name is specified', function(done) {\n\n      exec('node ' + sailsBin + ' generate model', function(err) {\n        assert.equal(err.code, 1);\n        done();\n      });\n    });\n\n    it('should create a model file in models folder', function(done) {\n\n      exec('node ' + sailsBin + ' generate model ' + modelName, function(err) {\n        if (err) done(new Error(err));\n\n        assert.doesNotThrow(function() {\n          fs.readFileSync('./api/models/' + capitalize(modelName) + '.js', 'utf8');\n        });\n\n        done();\n      });\n    });\n\n    it('should throw an error if a model with the same name exists', function(done) {\n\n      exec('node ' + sailsBin + ' generate model ' + modelName, function(err) {\n        assert.equal(err.code, 1);\n        done();\n      });\n    });\n  });\n\n  describe('sails generate controller <controllerName>', function() {\n    var controllerName = 'user';\n\n    it('should throw an error if no controller name is specified', function(done) {\n\n      exec('node ' + sailsBin + ' generate controller', function(err) {\n        assert.equal(err.code, 1);\n        done();\n      });\n    });\n\n    it('should create a controller file in controllers folder', function(done) {\n\n      exec('node ' + sailsBin + ' generate controller ' + controllerName, function(err) {\n        if (err) { return done(new Error(err)); }\n\n        assert.doesNotThrow(function() {\n          fs.readFileSync('./api/controllers/' + capitalize(controllerName) + 'Controller.js', 'utf8');\n        });\n\n        done();\n      });\n    });\n\n    it('should throw an error if a controller with the same name exists', function(done) {\n\n      exec('node ' + sailsBin + ' generate controller ' + controllerName, function(err) {\n        assert.equal(err.code, 1);\n        done();\n      });\n    });\n  });\n\n  describe('sails generate adapter <modelname>', function() {\n    var adapterName = 'mongo';\n\n    it('should throw an error if no adapter name is specified', function(done) {\n\n      exec('node ' + sailsBin + ' generate adapter', function(err) {\n        assert.equal(err.code, 1);\n        done();\n      });\n    });\n\n    it('should create a adapter file in adapters folder', function(done) {\n\n      exec('node ' + sailsBin + ' generate adapter ' + adapterName, function(err) {\n        if (err) { return done(err); }\n\n        assert.doesNotThrow(function() {\n          fs.readFileSync('./api/adapters/' + adapterName + '/index.js', 'utf8');\n        });\n\n        done();\n      });\n    });\n\n    it('should throw an error if an adapter with the same name exists', function(done) {\n\n      exec('node ' + sailsBin + ' generate adapter ' + adapterName, function(err) {\n        assert.equal(err.code, 1);\n        done();\n      });\n    });\n  });\n\n  describe('sails generate', function() {\n    var modelName = 'post';\n\n    it('should display usage if no generator type is specified', function(done) {\n\n      exec('node ' + sailsBin + ' generate', function(err, msg) {\n        if (err) { return done(err); }\n\n        assert.notEqual(msg.indexOf('Usage'), -1);\n\n        done();\n      });\n    });\n\n  });\n\n  describe('sails generate api <apiname>', function() {\n\n    var apiName = 'foo';\n\n    it('should display usage if no api name is specified', function(done) {\n\n      exec('node ' + sailsBin + ' generate api', function(err, dumb, response) {\n        assert.notEqual(response.indexOf('Usage'), -1);\n        done();\n      });\n    });\n\n    it('should create a controller and a model file', function(done) {\n\n      exec('node ' + sailsBin + ' generate api ' + apiName, function(err) {\n        if (err) { return done(err); }\n\n        assert.doesNotThrow(function() {\n          fs.readFileSync('./api/models/' + capitalize(apiName) + '.js', 'utf8');\n        });\n\n        assert.doesNotThrow(function() {\n          fs.readFileSync('./api/controllers/' + capitalize(apiName) + 'Controller.js', 'utf8');\n        });\n\n        done();\n      });\n    });\n\n    it('should throw an error if a controller file and model file with the same name exists', function(done) {\n\n      exec('node ' + sailsBin + ' generate api ' + apiName, function(err) {\n        assert.equal(err.code, 1);\n        done();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/integration/globals.test.js",
    "content": "/**\n * Test dependencies\n */\n\nvar assert = require('assert');\nvar tmp = require('tmp');\nvar path = require('path');\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\nvar appHelper = require('./helpers/appHelper');\nvar Filesystem = require('machinepack-fs');\nvar MProcess = require('machinepack-process');\nvar pathToSailsCLI = path.resolve(__dirname, '../../bin/sails.js');\n\ndescribe('globals :: ', function() {\n\n  var curDir;\n\n  describe('with default settings in an app lifted programmatically with no configuration', function() {\n\n    var result;\n\n    before(function(done) {\n\n      // Cache the current working directory.\n      curDir = process.cwd();\n\n      // Create a temp directory.\n      var tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n\n      Filesystem.ensureDir({path: 'node_modules'}).exec(function(err) {\n        if (err) {return done(err);}\n\n        // Set up some app files (a model, a service and config/models.js with `migrate: 'alter'`)\n        setupAppFiles();\n\n        // Symlink Sails to the new app\n        appHelper.linkSails(tmpDir.name);\n\n        MProcess.executeCommand({\n          command: util.format('node expose_globals.js'),\n        }).exec(function(err, output) {\n          if (err) {return done(err);}\n          if (output.stderr) {return done(output.stderr);}\n          try {\n            result = JSON.parse(output.stdout);\n          } catch (e) {\n            return done(e);\n          }\n          return done();\n        });\n\n      });\n\n    });\n\n    it('should not expose `async` as a global', function() {\n      assert.equal(result.async, false);\n    });\n\n    it('should not expose `_` as a global', function() {\n      assert.equal(result._, false);\n    });\n\n    it('should not expose services as globals', function() {\n      assert.equal(result.services, false);\n    });\n\n    it('should not expose models as globals', function() {\n      assert.equal(result.models, false);\n    });\n\n    it('should not expose sails as a global', function() {\n      assert.equal(result.sails, false);\n    });\n\n    after(function() {\n      process.chdir(curDir);\n    });\n\n  });\n\n\n  describe('with default settings in an app generated with `sails new`', function() {\n\n    var result;\n\n    before(function(done) {\n\n      // Cache the current working directory.\n      curDir = process.cwd();\n\n      // Create a temp directory.\n      var tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n\n      // Get the full path to the directory containing the app\n      var pathToTestApp = path.resolve(tmpDir.name, 'testApp');\n\n      // Create a new Sails app w/out npm install.\n      MProcess.executeCommand({\n        command: util.format('node %s new %s --fast --traditional', pathToSailsCLI, 'testApp'),\n      }).exec(function(err) {\n        if (err) {return done(err);}\n\n        // Switch to the app directory.\n        process.chdir(pathToTestApp);\n\n        // Set up some app files (a model, a service and config/models.js with `migrate: 'alter'`)\n        setupAppFiles();\n\n        // Symlink dependencies to the new app\n        appHelper.linkDeps(pathToTestApp);\n\n        // Symlink Sails to the new app\n        appHelper.linkSails(pathToTestApp);\n\n        // Symlink `lodash` to the new app\n        appHelper.linkLodash(pathToTestApp);\n\n        // Symlink `async` to the new app\n        appHelper.linkAsync(pathToTestApp);\n\n        MProcess.executeCommand({\n          command: util.format('node expose_globals.js'),\n        }).exec(function(err, output) {\n          if (err) {return done(err);}\n          if (output.stderr) {return done(output.stderr);}\n          try {\n            result = JSON.parse(output.stdout);\n          } catch (e) {\n            return done(new Error('Error parsing child process output as JSON. Error details: ' +e.stack+'\\nAnd here\\'s the raw output that could not be parsed as JSON:\\n'+output.stdout));\n          }\n\n          return done();\n        });\n\n      });\n\n    });\n\n    it('should NO LONGER expose `async` as a global', function() {\n      assert.equal(result.async, false);\n    });\n\n    it('should expose `_` as a global', function() {\n      assert(_.isArray(result._));\n      assert(_.contains(result._, 'contains'));\n    });\n\n    it('should expose services as globals', function() {\n      assert(_.isArray(result.services));\n      assert(_.contains(result.services, 'Foo'));\n    });\n\n    it('should expose models as globals', function() {\n      assert(_.isArray(result.models));\n      assert(_.contains(result.models, 'User'));\n    });\n\n    it('should expose sails as a global', function() {\n      assert.equal(result.sails, true);\n    });\n\n    after(function() {\n      process.chdir(curDir);\n    });\n\n  });\n\n\n  describe('with custom async/lodash in an app generated with `sails new`', function() {\n\n    var result;\n\n    before(function(done) {\n\n      // Cache the current working directory.\n      curDir = process.cwd();\n\n      // Create a temp directory.\n      var tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n\n      // Get the full path to the directory containing the app\n      var pathToTestApp = path.resolve(tmpDir.name, 'testApp');\n\n      // Create a new Sails app w/out npm install.\n      MProcess.executeCommand({\n        command: util.format('node %s new %s --fast --traditional', pathToSailsCLI, 'testApp'),\n      }).exec(function(err) {\n        if (err) {return done(err);}\n\n        // Switch to the app directory.\n        process.chdir(pathToTestApp);\n\n        // Set up some app files (a model, a service and config/models.js with `migrate: 'alter'`)\n        setupAppFiles();\n\n        // Symlink dependencies to the new app\n        appHelper.linkDeps(pathToTestApp);\n\n        // Symlink Sails to the new app\n        appHelper.linkSails(pathToTestApp);\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'node_modules/@sailshq/lodash/package.json',\n          string: '{\"name\": \"lodash\", \"version\": \"0.0.0\"}'\n        }).execSync();\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'node_modules/@sailshq/lodash/index.js',\n          string: 'module.exports = {\"foo\": \"bar\"}'\n        }).execSync();\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'node_modules/async/package.json',\n          string: '{\"name\": \"async\", \"version\": \"0.0.0\"}'\n        }).execSync();\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'node_modules/async/index.js',\n          string: 'module.exports = {\"owl\": \"hoot\"}'\n        }).execSync();\n\n        MProcess.executeCommand({\n          command: util.format('node expose_globals.js'),\n        }).exec(function(err, output) {\n          if (err) {return done(err);}\n          if (output.stderr) {return done(output.stderr);}\n          try {\n            result = JSON.parse(output.stdout);\n          } catch (e) {\n            return done(e);\n          }\n          return done();\n        });\n\n      });\n\n    });\n\n    it('should NO LONGER expose `async` as a global', function() {\n      assert.equal(result.async, false);\n    });\n\n    it('should expose `_` as a global, using the custom lodash', function() {\n      assert(_.isArray(result._));\n      assert(_.contains(result._, 'foo'));\n    });\n\n    it('should expose services as globals', function() {\n      assert(_.isArray(result.services));\n      assert(_.contains(result.services, 'Foo'));\n    });\n\n    it('should expose models as globals', function() {\n      assert(_.isArray(result.models));\n      assert(_.contains(result.models, 'User'));\n    });\n\n    it('should expose sails as a global', function() {\n      assert.equal(result.sails, true);\n    });\n\n    after(function() {\n      process.chdir(curDir);\n    });\n\n  });\n\n  describe('with `_`, `async`, `models`, `sails` and `services` set to `false`', function() {\n\n    var result;\n\n    before(function(done) {\n\n      // Cache the current working directory.\n      curDir = process.cwd();\n\n      // Create a temp directory.\n      var tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n\n      // Get the full path to the directory containing the app\n      var pathToTestApp = path.resolve(tmpDir.name, 'testApp');\n\n      // Create a new Sails app w/out npm install.\n      MProcess.executeCommand({\n        command: util.format('node %s new %s --fast --traditional', pathToSailsCLI, 'testApp'),\n      }).exec(function(err) {\n        if (err) {return done(err);}\n\n        // Switch to the app directory.\n        process.chdir(pathToTestApp);\n\n        // Set up some app files (a model, a service and config/models.js with `migrate: 'alter'`)\n        setupAppFiles();\n\n        // Symlink dependencies to the new app\n        appHelper.linkDeps(pathToTestApp);\n\n        // Symlink Sails to the new app\n        appHelper.linkSails(pathToTestApp);\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'config/globals.js',\n          string: 'module.exports.globals = { _: false, async: false, models: false, sails: false, services: false}'\n        }).execSync();\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'node_modules/lodash/package.json',\n          string: '{\"name\": \"lodash\", \"version\": \"0.0.0\"}'\n        }).execSync();\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'node_modules/lodash/index.js',\n          string: 'module.exports = {\"foo\": \"bar\"}'\n        }).execSync();\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'node_modules/async/package.json',\n          string: '{\"name\": \"async\", \"version\": \"0.0.0\"}'\n        }).execSync();\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'node_modules/async/index.js',\n          string: 'module.exports = {\"owl\": \"hoot\"}'\n        }).execSync();\n\n        MProcess.executeCommand({\n          command: util.format('node expose_globals.js'),\n        }).exec(function(err, output) {\n          if (err) {return done(err);}\n          if (output.stderr) {return done(output.stderr);}\n          try {\n            result = JSON.parse(output.stdout);\n          } catch (e) {\n            return done(e);\n          }\n          return done();\n        });\n\n      });\n\n    });\n\n    it('should not expose `async` as a global', function() {\n      assert.equal(result.async, false);\n    });\n\n    it('should not expose `_` as a global', function() {\n      assert.equal(result._, false);\n    });\n\n    it('should not expose services as globals', function() {\n      assert.equal(result.services, false);\n    });\n\n    it('should not expose models as globals', function() {\n      assert.equal(result.models, false);\n    });\n\n    it('should not expose sails as a global', function() {\n      assert.equal(result.sails, false);\n    });\n\n    after(function() {\n      process.chdir(curDir);\n    });\n\n  });\n\n\n  describe('with `_` set to `true`', function() {\n\n    before(function(done) {\n\n      // Cache the current working directory.\n      curDir = process.cwd();\n\n      // Create a temp directory.\n      var tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n\n      Filesystem.ensureDir({path: 'node_modules'}).exec(function(err) {\n        if (err) {return done(err);}\n\n        // Set up some app files (a model, a service and config/models.js with `migrate: 'alter'`)\n        setupAppFiles();\n\n        // Symlink Sails to the new app\n        appHelper.linkSails(tmpDir.name);\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'config/globals.js',\n          string: 'module.exports.globals = { _: true }'\n        }).execSync();\n\n        return done();\n\n      });\n\n    });\n\n    it('should fail to lift', function(done) {\n\n      MProcess.executeCommand({\n        command: util.format('node expose_globals.js'),\n      }).exec(function(err, output) {\n        if (output.stderr) {\n          if (output.stderr.match('E_BAD_GLOBAL_CONFIG')) {\n            return done();\n          }\n          return done(err);\n        }\n        return done(new Error('Sails should have failed to lift!'));\n      });\n    });\n\n    after(function() {\n      process.chdir(curDir);\n    });\n\n  });\n\n  describe('with `async` set to `true`', function() {\n\n    before(function(done) {\n\n      // Cache the current working directory.\n      curDir = process.cwd();\n\n      // Create a temp directory.\n      var tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n\n      Filesystem.ensureDir({path: 'node_modules'}).exec(function(err) {\n        if (err) {return done(err);}\n\n        // Set up some app files (a model, a service and config/models.js with `migrate: 'alter'`)\n        setupAppFiles();\n\n        // Symlink Sails to the new app\n        appHelper.linkSails(tmpDir.name);\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'config/globals.js',\n          string: 'module.exports.globals = { async: true }'\n        }).execSync();\n\n        return done();\n\n      });\n\n    });\n\n    it('should fail to lift', function(done) {\n\n      MProcess.executeCommand({\n        command: util.format('node expose_globals.js'),\n      }).exec(function(err, output) {\n        if (output.stderr) {\n          if (output.stderr.match('E_BAD_GLOBAL_CONFIG')) {\n            return done();\n          }\n          return done(err);\n        }\n        return done(new Error('Sails should have failed to lift!'));\n      });\n    });\n\n    after(function() {\n      process.chdir(curDir);\n    });\n\n  });\n\n  describe('with `models` set to `undefined`', function() {\n\n    before(function(done) {\n\n      // Cache the current working directory.\n      curDir = process.cwd();\n\n      // Create a temp directory.\n      var tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n\n      Filesystem.ensureDir({path: 'node_modules'}).exec(function(err) {\n        if (err) {return done(err);}\n\n        // Set up some app files (a model, a service and config/models.js with `migrate: 'alter'`)\n        setupAppFiles();\n\n        // Symlink Sails to the new app\n        appHelper.linkSails(tmpDir.name);\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'config/globals.js',\n          string: 'module.exports.globals = { async: false, _: false, sails: false }'\n        }).execSync();\n\n        return done();\n\n      });\n\n    });\n\n    it('should fail to lift', function(done) {\n\n      MProcess.executeCommand({\n        command: util.format('node expose_globals.js'),\n      }).exec(function(err, output) {\n        if (output.stderr) {\n          if (output.stderr.match('E_BAD_GLOBAL_CONFIG')) {\n            return done();\n          }\n          return done(err);\n        }\n        return done(new Error('Sails should have failed to lift!'));\n      });\n    });\n\n    after(function() {\n      process.chdir(curDir);\n    });\n\n  });\n\n  describe('with `sails` set to `undefined`', function() {\n\n    before(function(done) {\n\n      // Cache the current working directory.\n      curDir = process.cwd();\n\n      // Create a temp directory.\n      var tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n\n      Filesystem.ensureDir({path: 'node_modules'}).exec(function(err) {\n        if (err) {return done(err);}\n\n        // Set up some app files (a model, a service and config/models.js with `migrate: 'alter'`)\n        setupAppFiles();\n\n        // Symlink Sails to the new app\n        appHelper.linkSails(tmpDir.name);\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'config/globals.js',\n          string: 'module.exports.globals = { async: false, _: false, models: false }'\n        }).execSync();\n\n        return done();\n\n      });\n\n    });\n\n    it('should fail to lift', function(done) {\n\n      MProcess.executeCommand({\n        command: util.format('node expose_globals.js'),\n      }).exec(function(err, output) {\n        if (output.stderr) {\n          if (output.stderr.match('E_BAD_GLOBAL_CONFIG')) {\n            return done();\n          }\n          return done(err);\n        }\n        return done(new Error('Sails should have failed to lift!'));\n      });\n    });\n\n    after(function() {\n      process.chdir(curDir);\n    });\n\n  });\n\n  describe('with `sails.config.globals` set to `true`', function() {\n\n    before(function(done) {\n\n      // Cache the current working directory.\n      curDir = process.cwd();\n\n      // Create a temp directory.\n      var tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n\n      Filesystem.ensureDir({path: 'node_modules'}).exec(function(err) {\n        if (err) {return done(err);}\n\n        // Set up some app files (a model, a service and config/models.js with `migrate: 'alter'`)\n        setupAppFiles();\n\n        // Symlink Sails to the new app\n        appHelper.linkSails(tmpDir.name);\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'config/globals.js',\n          string: 'module.exports.globals = true'\n        }).execSync();\n\n        return done();\n\n      });\n\n    });\n\n    it('should fail to lift', function(done) {\n\n      MProcess.executeCommand({\n        command: util.format('node expose_globals.js'),\n      }).exec(function(err, output) {\n        if (err) { return done(err); }\n        if (output.stderr) {\n          if (output.stderr.match('E_BAD_GLOBAL_CONFIG')) {\n            return done();\n          }\n          return done(new Error(output.stderr));\n        }\n        return done(new Error('Sails should have failed to lift!'));\n      });\n    });\n\n    after(function() {\n      process.chdir(curDir);\n    });\n\n  });\n\n\n});\n\nfunction setupAppFiles() {\n\n  Filesystem.writeSync({\n    force: true,\n    destination: 'api/models/User.js',\n    string: 'module.exports = { attributes: { name: \\'string\\' } };'\n  }).execSync();\n\n  Filesystem.writeSync({\n    force: true,\n    destination: 'api/services/Foo.js',\n    string: 'module.exports = function TheFooService() {};'\n  }).execSync();\n\n  Filesystem.writeSync({\n    force: true,\n    destination: 'config/models.js',\n    string: 'module.exports.models = {migrate: \\'alter\\', attributes: {id: { type: \\'number\\', autoIncrement: true}}};'\n  }).execSync();\n\n\n  Filesystem.writeSync({\n    force: true,\n    destination: 'expose_globals.js',\n    string: '(' + (function logGlobalVarsIIFERiddle() {\n      /* eslint-disable no-undef */\n      var Sails = require('sails');\n      Sails.load({log: {level: 'silent'}}, function(err, sailsApp) {\n        if (err) {console.error(err); return;}\n        console.log(JSON.stringify({\n          async:  typeof async !== 'undefined' && Object.keys(async),\n          _: typeof _ !== 'undefined' && Object.keys(_),\n          sails: typeof sails !== 'undefined' && sails.constructor.name === 'Sails',\n          models: typeof sailsApp.models !== 'undefined' && Object.keys(sailsApp.models).reduce(function(memo, key) {if (global[sailsApp.models[key].globalId]){memo.push(sailsApp.models[key].globalId);}return memo;}, []),\n          services: typeof sailsApp.services !== 'undefined' && Object.keys(sailsApp.services).reduce(function(memo, key) {if (global[sailsApp.services[key].globalId]){memo.push(sailsApp.services[key].globalId);}return memo;}, [])\n        }));\n        sailsApp.lower();\n      });\n      /* eslint-enable no-undef */\n    }).toString()  + ')();'\n  }).execSync();\n\n}\n"
  },
  {
    "path": "test/integration/helpers/appHelper.js",
    "content": "/**\n * Module dependencies\n */\n\nvar path = require('path');\nvar child_process = require('child_process');\nvar exec = child_process.exec;\nvar fs = require('fs-extra');\nvar _ = require('@sailshq/lodash');\nvar SocketIOClient = require('socket.io-client');\ndelete require.cache[require.resolve('socket.io-client')];\nvar SailsIOClient = require('sails.io.js');\nvar Sails = require('../../../lib/app');\n\n\n\n\n\n// Build a Sails socket client instance.\n//\n// (Of course, this runs as soon as this file is first required.\n//  But it's OK because we don't actually connect except in the\n//  functions below.)\nvar io = SailsIOClient(SocketIOClient);\nio.sails.environment = 'production';\nio.sails.autoConnect = false;\n\n\n\n// Make existsSync not crash on older versions of Node\nfs.existsSync = fs.existsSync || path.existsSync;\n// ^ probably not necessary anymore, this is only relevant for pre-Node-0.8\n// (or maybe it was Node 0.8, can't remember). Anyways, it was back when\n// `existsSync()` lived in the `path` lib.\n\n\n\n\n\n\nmodule.exports = {\n\n\n\n  /**\n   * Spin up a child process and use the `sails` CLI to create a namespaced\n   * test app. If no appName is given use: 'testApp'.\n   *\n   * It copies all the files in the fixtures folder into their\n   * respective place in the test app so you don't need to worry\n   * about setting up the fixtures.\n   */\n\n  build: function(appName, done) {\n\n    // `appName` is optional.\n    if (_.isFunction(appName)) {\n      done = appName;\n      appName = 'testApp';\n    }\n\n    // But `done` callback is required.\n    if (!_.isFunction(done)) {\n      throw new Error('When using the appHelper\\'s `build()` method, a callback argument is required');\n    }\n\n\n    var pathToLocalSailsCLI = path.resolve('./bin/sails.js');\n\n\n    // Cleanup old test fixtures\n    if (fs.existsSync(appName)) {\n      fs.removeSync(path.resolve('./', appName));\n    }\n\n    // Create an empty directory for the test app.\n    var appDirPath = path.resolve('./', appName);\n    fs.mkdirSync(appDirPath);\n\n    //\n    process.chdir(appName);\n    child_process.exec('node ' + pathToLocalSailsCLI + ' new --fast --traditional --without=lodash,async', function(err) {\n      if (err) {\n        return done(err);\n      }\n      // Symlink dependencies\n      module.exports.linkDeps('.');\n      // Copy test fixtures to the test app.\n      fs.copy('../test/integration/fixtures/sampleapp', './', done);\n    });\n  },\n\n\n\n  /**\n   * Remove a test app (clean up files on disk.)\n   *\n   * @sync (because it sync filesystem methods)\n   */\n  teardown: function(appName) {\n    appName = appName ? appName : 'testApp';\n\n    var dir = path.resolve('./', appName);\n    if (fs.existsSync(dir)) {\n      fs.removeSync(dir);\n    }\n  },\n\n\n\n\n\n  liftQuiet: function(options, callback) {\n\n    if (_.isFunction(options)) {\n      callback = options;\n      options = null;\n    }\n\n    options = options || {};\n    _.defaults(options, {\n      log: {\n        level: 'silent'\n      }\n    });\n\n    return module.exports.lift(options, callback);\n\n  },\n\n\n\n  lift: function(options, callback) {\n\n    // Clear NODE_ENV to avoid unintended consequences.\n    delete process.env.NODE_ENV;\n\n    if (_.isFunction(options)) {\n      callback = options;\n      options = null;\n    }\n\n    options = options || {};\n    _.defaults(options, {\n      port: 1342,\n      environment: process.env.TEST_ENV,\n      globals: false\n    });\n    options.hooks = options.hooks || {};\n    options.hooks.grunt = options.hooks.grunt || false;\n\n    Sails().lift(options, function(err, sails) {\n      if (err) {\n        return callback(err);\n      }\n      return callback(null, sails);\n    });\n\n  },\n\n  load: function(options, callback) {\n\n    // Clear NODE_ENV to avoid unintended consequences.\n    delete process.env.NODE_ENV;\n\n    if (_.isFunction(options)) {\n      callback = options;\n      options = null;\n    }\n\n    options = options || {};\n    _.defaults(options, {\n      port: 1342,\n      environment: process.env.TEST_ENV\n    });\n    options.hooks = options.hooks || {};\n    options.hooks.grunt = options.hooks.grunt || false;\n\n    Sails().load(options, function(err, sails) {\n      if (err) {\n        return callback(err);\n      }\n      return callback(null, sails);\n    });\n\n  },\n\n\n  buildAndLift: function(appName, options, callback) {\n    if (_.isFunction(options)) {\n      callback = options;\n      options = null;\n    }\n    module.exports.build(appName, function(err) {\n      if (err) {\n        return callback(err);\n      }\n      module.exports.lift(options, callback);\n    });\n  },\n\n  liftWithTwoSockets: function(options, callback) {\n    if (_.isFunction(options)) {\n      callback = options;\n      options = null;\n    }\n    module.exports.lift(options, function(err, sails) {\n      if (err) {\n        return callback(err);\n      }\n\n      var socket1 = io.sails.connect('http://localhost:1342', {\n        multiplex: false,\n      });\n      socket1.on('connect', function() {\n        var socket2 = io.sails.connect('http://localhost:1342', {\n          multiplex: false,\n        });\n        socket2.on('connect', function() {\n          return callback(null, sails, socket1, socket2);\n        });\n      });\n    });\n  },\n\n  buildAndLiftWithTwoSockets: function(appName, options, callback) {\n    if (_.isFunction(options)) {\n      callback = options;\n      options = null;\n    }\n    module.exports.build(appName, function(err) {\n      if (err) {\n        return callback(err);\n      }\n      module.exports.liftWithTwoSockets(options, callback);\n    });\n  },\n\n  linkDeps: function(appPath) {\n\n    // Get the given app's package.json (defaulting to an empty dictionary).\n    var packageJson;\n    try {\n      packageJson = require(path.resolve(appPath, 'package.json'));\n    } catch (e) {\n      packageJson = {};\n    }\n\n    var deps = ['sails-hook-orm', 'sails-hook-sockets'];\n    _.each(deps, function(dep) {\n      // Create a symlink\n      fs.ensureSymlinkSync(path.resolve(__dirname, '..', '..', '..', 'node_modules', dep), path.resolve(appPath, 'node_modules', dep));\n      // Add a entry into the package.json dependencies\n      packageJson.dependencies = packageJson.dependencies || {};\n      packageJson.dependencies[dep] = '0.0.0';\n    });\n\n    // Output the update package.json\n    fs.writeFileSync(path.resolve(appPath, 'package.json'), JSON.stringify(packageJson));\n  },\n\n  linkLodash: function(appPath) {\n    fs.ensureSymlinkSync(path.resolve(__dirname, '..', '..', '..', 'node_modules', '@sailshq', 'lodash'), path.resolve(appPath, 'node_modules', '@sailshq', 'lodash'));\n  },\n\n\n  linkAsync: function(appPath) {\n    fs.ensureSymlinkSync(path.resolve(__dirname, '..', '..', '..', 'node_modules', 'async'), path.resolve(appPath, 'node_modules', 'async'));\n  },\n\n\n  linkSails: function(appPath) {\n    fs.ensureSymlinkSync(path.resolve(__dirname, '..', '..', '..'), path.resolve(appPath, 'node_modules', 'sails'));\n  },\n\n};\n\n\n\n\n\n\n"
  },
  {
    "path": "test/integration/helpers/httpHelper.js",
    "content": "var request = require('@sailshq/request');\nvar fs = require('fs');\n\n\n/**\n * Original test helpers\n *\n * TODO: refactor these into the other more modern set of test helpers.\n * (these are from waaaayyyy back)\n */\n\nmodule.exports = {\n\n  // Write specified routing config to the  `config/routes.js` file.\n  writeRoutes: function(routesConfig) {\n    fs.writeFileSync('config/routes.js', 'module.exports.routes = ' + JSON.stringify(routesConfig));\n  },\n\n  // Write specified blueprints config to the  `config/blueprints.js` file.\n  writeBlueprint: function(blueprintsConfig) {\n    fs.writeFileSync('config/blueprints.js', 'module.exports.blueprints = ' + JSON.stringify(blueprintsConfig));\n  },\n\n\n  // Make a request to an already-lifted Sails server running on port 1342.\n  testRoute: function(method, options, callback) {\n\n    // Prefix url with domain:port\n    if (typeof options === 'string') {\n      options = {url: 'http://localhost:1342/' + options};\n    } else {\n      options.url = 'http://localhost:1342/' + options.url;\n    }\n\n    options.method = (method === 'del') ? 'delete' : method;\n\n    request(options, function(err, response, body) {\n      if (err) {\n        return callback(err, response, body);\n      }\n\n      return callback(null, response, body);\n    });\n\n  }\n};\n"
  },
  {
    "path": "test/integration/helpers/socketHelper.js",
    "content": "var fs = require('fs');\n\nmodule.exports = {\n\n  // Write routes object to blueprint config file\n  writeModelConfig: function(config) {\n    fs.writeFileSync('config/models.js', 'module.exports.models = {autosubscribe: [], connection: \"localDiskDb\"}');\n  },\n\n  // Starts sails server, makes request, returns response, kills sails server\n  testRoute: function(socket, method, options, callback) {\n\n    var url, data = {};\n    // Prefix url with domain:port\n    if (typeof options === 'string') {\n      url = options;\n    } else {\n      url = options.url;\n    }\n\n    if (method === 'get') {\n      socket[method](url, function(response) {\n        callback(null, response);\n      });\n    }\n\n    else {\n      socket[method](url, data, function(response) {\n        callback(null, response);\n      });\n    }\n\n  }\n};\n"
  },
  {
    "path": "test/integration/hook.3rdparty.test.js",
    "content": "/**\n * Test dependencies\n */\nvar assert = require('assert');\nvar httpHelper = require('./helpers/httpHelper.js');\nvar appHelper = require('./helpers/appHelper');\nvar util = require('util');\nvar path = require('path');\nvar fs = require('fs-extra');\nvar _ = require('@sailshq/lodash');\n\ndescribe('hooks :: ', function() {\n\n  var sailsprocess;\n\n  describe('installing a 3rd-party hook', function() {\n    var appName = 'testApp';\n\n    // Before each test, remove the test app's package.json from the require cache,\n    // so that we can change its dependencies on a per-test basis.\n    beforeEach(function() {\n      delete require.cache[path.resolve(__dirname, '../..', appName, 'package.json')];\n    });\n\n    before(function() {\n      appHelper.teardown();\n    });\n\n    describe('into node_modules/sails-hook-shout', function(){\n\n      before(function(done) {\n        // Add `sails-hook-shout` as a dependency of the test app.\n        fs.outputFileSync(path.resolve(__dirname, '../..', appName, 'package.json'), '{\"dependencies\":{\"sails-hook-shout\":\"0.0.0\"}}');\n          // Copy the hook into the test app's node_modules folder.\n        fs.mkdirs(path.resolve(__dirname, '../..', appName, 'node_modules'), function(err) {\n          if (err) {return done(err);}\n          fs.copySync(path.resolve(__dirname, 'fixtures/hooks/installable/shout'), path.resolve(__dirname,'../../testApp/node_modules/sails-hook-shout'));\n          process.chdir(path.resolve(__dirname, '../..', appName));\n          done();\n        });\n      });\n\n      after(function() {\n        process.chdir('../');\n        // Sleep for 500ms--otherwise we get timing errors for this test on Windows\n        setTimeout(function() {\n          appHelper.teardown();\n        }, 500);\n      });\n\n      describe('with default settings', function() {\n\n        var sails;\n\n        before(function(done) {\n          appHelper.liftQuiet(function(err, _sails) {\n            if (err) {return done(err);}\n            sails = _sails;\n            return done();\n          });\n        });\n\n        after(function(done) {\n          sails.lower(function(){setTimeout(done, 100);});\n        });\n\n        it('should install a hook into `sails.hooks.shout`', function() {\n\n          assert(sails.hooks.shout);\n\n        });\n\n        it('should use merge the default hook config', function() {\n\n          assert.equal(sails.config.shout.phrase, 'make it rain');\n\n        });\n\n        it('should bind a /shout route that responds with the default phrase', function(done) {\n          httpHelper.testRoute('GET', 'shout', function(err, resp, body) {\n            assert.equal(body, 'make it rain');\n            return done();\n          });\n        });\n\n      });\n\n      describe('with hooks.shout set to boolean false', function() {\n\n        var sails;\n\n        before(function(done) {\n          appHelper.liftQuiet({hooks: {shout: false}}, function(err, _sails) {\n            if (err) {return done(err);}\n            sails = _sails;\n            return done();\n          });\n        });\n\n        after(function(done) {\n          sails.lower(function(){setTimeout(done, 100);});\n        });\n\n        it('should not install a hook into `sails.hooks.shout`', function() {\n\n          assert(_.isUndefined(sails.hooks.shout));\n\n        });\n\n      });\n\n\n      describe('with hooks.shout set to the string `false`', function() {\n\n        var sails;\n\n        before(function(done) {\n          appHelper.liftQuiet({hooks: {shout: 'false'}}, function(err, _sails) {\n            if (err) {return done(err);}\n            sails = _sails;\n            return done();\n          });\n        });\n\n        after(function(done) {\n          sails.lower(function(){setTimeout(done, 100);});\n        });\n\n        it('should not install a hook into `sails.hooks.shout`', function() {\n\n          assert(_.isUndefined(sails.hooks.shout));\n\n        });\n\n      });\n\n      describe('with hook-level config options', function() {\n\n        var sails;\n\n        before(function(done) {\n          appHelper.liftQuiet({shout: {phrase: 'yolo'}}, function(err, _sails) {\n            if (err) {return done(err);}\n            sails = _sails;\n            return done();\n          });\n        });\n\n        after(function(done) {\n          sails.lower(function(){setTimeout(done, 100);});\n        });\n\n        it('should bind a /shout route that responds with the configured phrase', function(done) {\n          httpHelper.testRoute('GET', 'shout', function(err, resp, body) {\n            assert(body === 'yolo');\n            return done();\n          });\n        });\n\n      });\n\n      describe('setting the config key to `shoutHook`', function() {\n\n        var sails;\n\n        before(function(done) {\n          appHelper.liftQuiet({installedHooks: {'sails-hook-shout': {configKey: 'shoutHook'}}, shoutHook: {phrase: 'holla back!'}}, function(err, _sails) {\n            if (err) {return done(err);}\n            sails = _sails;\n            return done();\n          });\n        });\n\n        after(function(done) {\n          sails.lower(function(){setTimeout(done, 100);});\n        });\n\n\n        it('should bind a /shout route that responds with the configured phrase', function(done) {\n          httpHelper.testRoute('GET', 'shout', function(err, resp, body) {\n            assert(body === 'holla back!');\n            return done();\n          });\n        });\n\n      });\n\n      describe('setting the hook name to `foobar`', function(){\n\n          var sails;\n\n          before(function(done) {\n            appHelper.liftQuiet({installedHooks: {'sails-hook-shout': {name: 'foobar'}}}, function(err, _sails) {\n              if (err) {return done(err);}\n              sails = _sails;\n              return done();\n            });\n          });\n\n          after(function(done) {\n            sails.lower(function(){setTimeout(done, 100);});\n          });\n\n          it('should install a hook into `sails.hooks.foobar`', function() {\n\n            assert(sails.hooks.foobar);\n\n          });\n\n          it('should use merge the default hook config', function() {\n\n            assert(sails.config.foobar.phrase === 'make it rain', sails.config.foobar.phrase);\n\n          });\n\n          it('should bind a /shout route that responds with the default phrase', function(done) {\n            httpHelper.testRoute('GET', 'shout', function(err, resp, body) {\n              assert(body === 'make it rain');\n              return done();\n            });\n          });\n\n      });\n\n      describe('setting the hook name to `security` (an existing hook)', function(){\n\n          var sails;\n          before(function(done) {\n            appHelper.liftQuiet({installedHooks: {'sails-hook-shout': {name: 'security'}}}, function(err, _sails) {\n              sails = _sails;\n              done(err);\n            });\n          });\n\n          after(function(done) {\n            sails.lower(function(){setTimeout(done, 100);});\n          });\n\n          it('should replace the core `security` hook', function() {\n            assert(sails.hooks.security.isShoutyHook);\n          });\n\n      });\n\n\n    });\n\n    describe('into node_modules/shouty', function(){\n\n      before(function(done) {\n        // Add `shouty` as a dependency of the test app.\n        fs.outputFileSync(path.resolve(__dirname, '../..', appName, 'package.json'), '{\"dependencies\":{\"shouty\":\"0.0.0\"}}');\n        fs.mkdirs(path.resolve(__dirname, '../..', appName, 'node_modules'), function(err) {\n          if (err) {return done(err);}\n          fs.copySync(path.resolve(__dirname, 'fixtures/hooks/installable/shout'), path.resolve(__dirname,'../../testApp/node_modules/shouty'));\n          var packageJson = JSON.parse(fs.readFileSync(path.resolve(__dirname,'../../testApp/node_modules/shouty','package.json')));\n          packageJson.name = 'shouty';\n          fs.writeFileSync(path.resolve(__dirname,'../../testApp/node_modules/shouty','package.json'), JSON.stringify(packageJson));\n          process.chdir(path.resolve(__dirname, '../..', appName));\n          done();\n        });\n      });\n\n      after(function(done) {\n        process.chdir('../');\n        // Sleep for 500ms--otherwise we get timing errors for this test on Windows\n        setTimeout(function() {\n          appHelper.teardown();\n          return done();\n        }, 500);\n      });\n\n      describe('with default settings', function() {\n\n        var sails;\n\n        before(function(done) {\n          appHelper.liftQuiet(function(err, _sails) {\n            if (err) {return done(err);}\n            sails = _sails;\n            return done();\n          });\n        });\n\n        after(function(done) {\n          sails.lower(function(){setTimeout(done, 100);});\n        });\n\n\n        it('should install a hook into `sails.hooks.shouty`', function() {\n          assert(sails.hooks.shouty);\n\n        });\n\n        it('should use merge the default hook config', function() {\n\n          assert(sails.config.shouty.phrase === 'make it rain', sails.config.shouty.phrase);\n\n        });\n\n        it('should bind a /shout route that responds with the default phrase', function(done) {\n          httpHelper.testRoute('GET', 'shout', function(err, resp, body) {\n            assert(body === 'make it rain');\n            return done();\n          });\n        });\n\n      });\n\n      describe('with `hookName` set to `security` in the package.json', function() {\n        var sails;\n        before(function(done) {\n          delete require.cache[path.resolve(__dirname,'../../testApp/node_modules/shouty','package.json')];\n          var packageJson = JSON.parse(fs.readFileSync(path.resolve(__dirname,'../../testApp/node_modules/shouty','package.json')));\n          packageJson.sails.hookName = 'security';\n          fs.writeFileSync(path.resolve(__dirname,'../../testApp/node_modules/shouty','package.json'), JSON.stringify(packageJson));\n          appHelper.liftQuiet(function(err, _sails) {\n            if (err) {return done(err);}\n            sails = _sails;\n            return done();\n          });\n        });\n\n        after(function(done) {\n          sails.lower(function(){setTimeout(done, 100);});\n        });\n\n        it('should replace the core `security` hook', function() {\n          assert(sails.hooks.security.isShoutyHook);\n        });\n      });\n\n    });\n\n    describe('into node_modules/sails-hook-security', function(){\n\n      var sails;\n      before(function(done) {\n        // Add `sails-hook-security` as a dependency of the test app.\n        fs.outputFileSync(path.resolve(__dirname, '../..', appName, 'package.json'), '{\"dependencies\":{\"sails-hook-security\":\"0.0.0\"}}');\n        fs.mkdirs(path.resolve(__dirname, '../..', appName, 'node_modules'), function(err) {\n          if (err) {return done(err);}\n          fs.copySync(path.resolve(__dirname, 'fixtures/hooks/installable/shout'), path.resolve(__dirname,'../../testApp/node_modules/sails-hook-security'));\n          var packageJson = JSON.parse(fs.readFileSync(path.resolve(__dirname,'../../testApp/node_modules/sails-hook-security','package.json')));\n          packageJson.name = 'sails-hook-security';\n          fs.writeFileSync(path.resolve(__dirname,'../../testApp/node_modules/sails-hook-security','package.json'), JSON.stringify(packageJson));\n          process.chdir(path.resolve(__dirname, '../..', appName));\n          appHelper.liftQuiet(function(err, _sails) {\n            if (err) {return done(err);}\n            sails = _sails;\n            return done();\n          });\n        });\n      });\n\n      after(function(done) {\n        sails.lower(function(err) {\n          process.chdir('../');\n          appHelper.teardown();\n          return done(err);\n        });\n      });\n\n      it('should replace the core `security` hook', function() {\n        assert(sails.hooks.security.isShoutyHook);\n      });\n\n    });\n\n\n    describe('into node_modules/security', function(){\n\n      var sails;\n      before(function(done) {\n        // Add `security` as a dependency of the test app.\n        fs.outputFileSync(path.resolve(__dirname, '../..', appName, 'package.json'), '{\"dependencies\":{\"security\":\"0.0.0\"}}');\n        fs.mkdirs(path.resolve(__dirname, '../..', appName, 'node_modules'), function(err) {\n          if (err) {return done(err);}\n          fs.copySync(path.resolve(__dirname, 'fixtures/hooks/installable/shout'), path.resolve(__dirname,'../../testApp/node_modules/security'));\n          var packageJson = JSON.parse(fs.readFileSync(path.resolve(__dirname,'../../testApp/node_modules/security','package.json')));\n          packageJson.name = 'security';\n          fs.writeFileSync(path.resolve(__dirname,'../../testApp/node_modules/security','package.json'), JSON.stringify(packageJson));\n          process.chdir(path.resolve(__dirname, '../..', appName));\n          appHelper.liftQuiet(function(err, _sails) {\n            if (err) {return done(err);}\n            sails = _sails;\n            return done();\n          });\n        });\n      });\n\n      after(function(done) {\n        sails.lower(function(err) {\n          process.chdir('../');\n          appHelper.teardown();\n          return done(err);\n        });\n      });\n\n      it('should replace the core `security` hook', function() {\n        assert(sails.hooks.security.isShoutyHook);\n      });\n\n    });\n\n    describe('into node_modules/@my-modules/shouty', function(){\n\n      describe('with default settings', function() {\n\n        var sails;\n        before(function(done) {\n          // Add `@my-modules/shouty` as a dependency of the test app.\n          fs.outputFileSync(path.resolve(__dirname, '../..', appName, 'package.json'), '{\"dependencies\":{\"@my-modules/shouty\":\"0.0.0\"}}');\n          fs.mkdirs(path.resolve(__dirname, '../..', appName, 'node_modules', '@my-modules'), function(err) {\n            if (err) {return done(err);}\n            fs.copySync(path.resolve(__dirname, 'fixtures/hooks/installable/shout'), path.resolve(__dirname,'../../testApp/node_modules/@my-modules/shouty'));\n            var packageJson = JSON.parse(fs.readFileSync(path.resolve(__dirname,'../../testApp/node_modules/@my-modules/shouty','package.json')));\n            packageJson.name = '@my-modules/shouty';\n            fs.writeFileSync(path.resolve(__dirname,'../../testApp/node_modules/@my-modules/shouty','package.json'), JSON.stringify(packageJson));\n            process.chdir(path.resolve(__dirname, '../..', appName));\n            appHelper.liftQuiet(function(err, _sails) {\n              if (err) {return done(err);}\n              sails = _sails;\n              return done();\n            });\n          });\n        });\n\n        after(function(done) {\n          sails.lower(function(err) {\n            process.chdir('../');\n            appHelper.teardown();\n            return done(err);\n          });\n        });\n\n        it('should install a hook into `sails.hooks.shouty`', function() {\n          assert(sails.hooks.shouty);\n        });\n\n      });\n\n      describe('with `hookName` set to `security` in the package.json', function() {\n\n        var sails;\n        before(function(done) {\n          fs.outputFileSync(path.resolve(__dirname, '../..', appName, 'package.json'), '{\"dependencies\":{\"@my-modules/shouty\":\"0.0.0\"}}');\n          delete require.cache[path.resolve(__dirname,'../../testApp/node_modules/@my-modules/shouty')];\n          delete require.cache[path.resolve(__dirname,'../../testApp/node_modules/@my-modules/shouty','package.json')];\n          fs.mkdirs(path.resolve(__dirname, '../..', appName, 'node_modules', '@my-modules'), function(err) {\n            if (err) {return done(err);}\n            fs.copySync(path.resolve(__dirname, 'fixtures/hooks/installable/shout'), path.resolve(__dirname,'../../testApp/node_modules/@my-modules/shouty'));\n            var packageJson = JSON.parse(fs.readFileSync(path.resolve(__dirname,'../../testApp/node_modules/@my-modules/shouty','package.json')));\n            packageJson.sails.hookName = 'security';\n            packageJson.name = '@my-modules/shouty';\n            fs.writeFileSync(path.resolve(__dirname,'../../testApp/node_modules/@my-modules/shouty','package.json'), JSON.stringify(packageJson));\n            process.chdir(path.resolve(__dirname, '../..', appName));\n            appHelper.liftQuiet(function(err, _sails) {\n              if (err) {return done(err);}\n              sails = _sails;\n              return done();\n            });\n          });\n        });\n\n        after(function(done) {\n          sails.lower(function(err) {\n            process.chdir('../');\n            appHelper.teardown();\n            return done(err);\n          });\n        });\n\n        it('should replace the core `security` hook', function() {\n          assert(sails.hooks.security.isShoutyHook);\n        });\n\n      });\n\n    });\n\n    describe('into node_modules/@my-modules/sails-hook-security', function(){\n\n      var sails;\n      before(function(done) {\n        // Add `@my-modules/sails-hook-security` as a dependency of the test app.\n        fs.outputFileSync(path.resolve(__dirname, '../..', appName, 'package.json'), '{\"dependencies\":{\"sails-hook-shout\":\"0.0.0\"}}');\n        fs.mkdirs(path.resolve(__dirname, '../..', appName, 'node_modules', '@my-modules'), function(err) {\n          if (err) {return done(err);}\n          fs.copySync(path.resolve(__dirname, 'fixtures/hooks/installable/shout'), path.resolve(__dirname,'../../testApp/node_modules/@my-modules/sails-hook-security'));\n          var packageJson = JSON.parse(fs.readFileSync(path.resolve(__dirname,'../../testApp/node_modules/@my-modules/sails-hook-security','package.json')));\n          fs.writeFileSync(path.resolve(__dirname,'../../testApp/node_modules/@my-modules/sails-hook-security','package.json'), JSON.stringify(packageJson));\n          process.chdir(path.resolve(__dirname, '../..', appName));\n          appHelper.liftQuiet(function(err, _sails) {\n            if (err) {return done(err);}\n            sails = _sails;\n            return done();\n          });\n        });\n      });\n\n      after(function(done) {\n        sails.lower(function(err) {\n          process.chdir('../');\n          appHelper.teardown();\n          return done(err);\n        });\n      });\n\n      it('should replace the core `security` hook', function() {\n        assert(sails.hooks.security.isShoutyHook);\n      });\n\n    });\n\n    describe('into node_modules/@my-modules/sails-hook-security, with no corresponding package.json entry', function(){\n\n      var sails;\n      before(function(done) {\n        fs.mkdirs(path.resolve(__dirname, '../..', appName, 'node_modules', '@my-modules'), function(err) {\n          if (err) {return done(err);}\n          fs.copySync(path.resolve(__dirname, 'fixtures/hooks/installable/shout'), path.resolve(__dirname,'../../testApp/node_modules/@my-modules/sails-hook-security'));\n          var packageJson = JSON.parse(fs.readFileSync(path.resolve(__dirname,'../../testApp/node_modules/@my-modules/sails-hook-security','package.json')));\n          fs.writeFileSync(path.resolve(__dirname,'../../testApp/node_modules/@my-modules/sails-hook-security','package.json'), JSON.stringify(packageJson));\n          process.chdir(path.resolve(__dirname, '../..', appName));\n          appHelper.liftQuiet(function(err, _sails) {\n            if (err) {return done(err);}\n            sails = _sails;\n            return done();\n          });\n        });\n      });\n\n      after(function(done) {\n        sails.lower(function(err) {\n          process.chdir('../');\n          appHelper.teardown();\n          return done(err);\n        });\n      });\n\n      it('should NOT replace the core `security` hook', function() {\n        assert(!sails.hooks.security.isShoutyHook);\n      });\n\n    });\n\n    describe('with an invalid package.json file', function(){\n\n      var sails;\n      before(function(done) {\n        delete require.cache[path.resolve(__dirname,'../../testApp/node_modules/@my-modules/sails-hook-security/package.json')];\n        fs.mkdirs(path.resolve(__dirname, '../..', appName, 'node_modules', '@my-modules'), function(err) {\n          if (err) {return done(err);}\n          fs.copySync(path.resolve(__dirname, 'fixtures/hooks/installable/shout'), path.resolve(__dirname,'../../testApp/node_modules/@my-modules/sails-hook-security'));\n          fs.outputFileSync(path.resolve(__dirname,'../../testApp/node_modules/@my-modules/sails-hook-security/package.json'), '{\"foo\":<%=bar%>}');\n          process.chdir(path.resolve(__dirname, '../..', appName));\n          return done();\n        });\n      });\n\n      after(function(done) {\n        sails ? sails.lower(function(err) {\n          process.chdir('../');\n          appHelper.teardown();\n          return done(err);\n        }): done();\n      });\n\n      it('should lift without crashing', function(done) {\n        appHelper.liftQuiet(function(err, _sails) {\n          if (err) {return done(err);}\n          sails = _sails;\n          assert(!sails.hooks.security.isShoutyHook);\n          return done();\n        });\n      });\n\n    });\n\n  });\n\n\n\n});\n"
  },
  {
    "path": "test/integration/hook.blueprints.action.routes.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar assert = require('assert');\nvar tmp = require('tmp');\nvar _ = require('@sailshq/lodash');\n\nvar Filesystem = require('machinepack-fs');\n\nvar Sails = require('../../lib').constructor;\n\ntmp.setGracefulCleanup();\n\n/**\n * Errors\n */\n\nvar Err = {\n  badResponse: function(response) {\n    return 'Wrong server response!  Response :::\\n' + util.inspect(response.body);\n  }\n};\n\n\n\n/**\n * Tests\n */\n\ndescribe('blueprints :: ', function() {\n\n  describe('actions routes :: ', function() {\n\n    describe('when turned on :: ', function() {\n\n      var curDir, tmpDir, sailsApp;\n\n      before(function(done) {\n        // Cache the current working directory.\n        curDir = process.cwd();\n        // Create a temp directory.\n        tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n        // Switch to the temp directory.\n        process.chdir(tmpDir.name);\n\n        (new Sails()).load({\n          hooks: {\n            grunt: false, views: false, policies: false, pubsub: false, i18n: false\n          },\n          models: {\n            migrate: 'drop',\n            schema: true\n          },\n          blueprints: {\n            actions: true,\n            shortcuts: false,\n            rest: false\n          },\n          log: {level: 'error'},\n          controllers: {\n            moduleDefinitions: {\n              'toplevellegacy/fnaction': function (req, res) { res.send('legacy fn action!'); },\n              'toplevellegacy/machineaction': { exits: {success: {outputExample: 'abc123'} }, fn: function (inputs, exits) { exits.success('legacy machine action!'); } },\n              'top-level-standalone-fn': function (req, res) { res.send('top level standalone fn!'); },\n              'top-level-standalone-machine': { exits: {success: {outputExample: 'abc123'} },  fn: function (inputs, exits) { exits.success('top level standalone machine!'); } },\n              'somefolder/someotherfolder/nestedlegacy/fnaction': function (req, res) { res.send('nested legacy fn action!'); },\n              'somefolder/someotherfolder/nestedlegacy/machineaction': { exits: {success: {outputExample: 'abc123'} },  fn: function (inputs, exits) { exits.success('nested legacy machine action!'); } },\n              'somefolder/someotherfolder/nested-standalone-machine': { exits: {success: {outputExample: 'abc123'} },  fn: function (inputs, exits) { exits.success('nested standalone machine!'); } }\n            }\n          }\n\n        }, function(err, _sails) {\n          if (err) { return done(err); }\n          sailsApp = _sails;\n          return done();\n        });\n      });\n\n      after(function(done) {\n        sailsApp.lower(function() {\n          process.chdir(curDir);\n          return done();\n        });\n      });\n\n      it('should bind a route to \\'ALL /toplevellegacy/fnaction\\'', function(done) {\n        sailsApp.request('POST /toplevellegacy/fnaction', {}, function (err, resp, data) {\n          assert(!err, err);\n          assert.deepEqual(data, 'legacy fn action!');\n          sailsApp.request('GET /toplevellegacy/fnaction', {}, function (err, resp, data) {\n            assert(!err, err);\n            assert.deepEqual(data, 'legacy fn action!');\n            done();\n          });\n        });\n      });\n\n      it('should bind a route to \\'ALL /toplevellegacy/machineaction\\'', function(done) {\n        sailsApp.request('POST /toplevellegacy/machineaction', {}, function (err, resp, data) {\n          assert(!err, err);\n          assert.deepEqual(data, 'legacy machine action!');\n          sailsApp.request('GET /toplevellegacy/machineaction', {}, function (err, resp, data) {\n            assert(!err, err);\n            assert.deepEqual(data, 'legacy machine action!');\n            done();\n          });\n        });\n      });\n\n      it('should bind a route to \\'ALL /top-level-standalone-fn\\'', function(done) {\n        sailsApp.request('POST /top-level-standalone-fn', {}, function (err, resp, data) {\n          assert(!err, err);\n          assert.deepEqual(data, 'top level standalone fn!');\n          sailsApp.request('GET /top-level-standalone-fn', {}, function (err, resp, data) {\n            assert(!err, err);\n            assert.deepEqual(data, 'top level standalone fn!');\n            done();\n          });\n        });\n      });\n\n      it('should bind a route to \\'ALL /top-level-standalone-machine\\'', function(done) {\n        sailsApp.request('POST /top-level-standalone-machine', {}, function (err, resp, data) {\n          assert(!err, err);\n          assert.deepEqual(data, 'top level standalone machine!');\n          sailsApp.request('GET /top-level-standalone-machine', {}, function (err, resp, data) {\n            assert(!err, err);\n            assert.deepEqual(data, 'top level standalone machine!');\n            done();\n          });\n        });\n      });\n\n      it('should bind a route to \\'ALL /somefolder/someotherfolder/nestedlegacy/fnaction\\'', function(done) {\n        sailsApp.request('POST /somefolder/someotherfolder/nestedlegacy/fnaction', {}, function (err, resp, data) {\n          assert(!err, err);\n          assert.deepEqual(data, 'nested legacy fn action!');\n          sailsApp.request('GET /somefolder/someotherfolder/nestedlegacy/fnaction', {}, function (err, resp, data) {\n            assert(!err, err);\n            assert.deepEqual(data, 'nested legacy fn action!');\n            done();\n          });\n        });\n      });\n\n      it('should bind a route to \\'ALL /somefolder/someotherfolder/nestedlegacy/machineaction\\'', function(done) {\n        sailsApp.request('POST /somefolder/someotherfolder/nestedlegacy/machineaction', {}, function (err, resp, data) {\n          assert(!err, err);\n          assert.deepEqual(data, 'nested legacy machine action!');\n          sailsApp.request('GET /somefolder/someotherfolder/nestedlegacy/machineaction', {}, function (err, resp, data) {\n            assert(!err, err);\n            assert.deepEqual(data, 'nested legacy machine action!');\n            done();\n          });\n        });\n      });\n\n      it('should bind a route to \\'ALL /somefolder/someotherfolder/nested-standalone-machine\\'', function(done) {\n        sailsApp.request('POST /somefolder/someotherfolder/nested-standalone-machine', {}, function (err, resp, data) {\n          assert(!err, err);\n          assert.deepEqual(data, 'nested standalone machine!');\n          sailsApp.request('GET /somefolder/someotherfolder/nested-standalone-machine', {}, function (err, resp, data) {\n            assert(!err, err);\n            assert.deepEqual(data, 'nested standalone machine!');\n            done();\n          });\n        });\n      });\n    }); // </ describe('when turned on :: ', ... >\n\n    describe('when turned off globally', function() {\n\n      var curDir, tmpDir, sailsApp;\n\n      before(function(done) {\n        // Cache the current working directory.\n        curDir = process.cwd();\n        // Create a temp directory.\n        tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n        // Switch to the temp directory.\n        process.chdir(tmpDir.name);\n\n        (new Sails()).load({\n          hooks: {\n            grunt: false, views: false, policies: false, pubsub: false, i18n: false\n          },\n          models: {\n            migrate: 'drop',\n            schema: true\n          },\n          blueprints: {\n            actions: false,\n            shortcuts: false,\n            rest: false\n          },\n          log: {level: 'error'},\n          controllers: {\n            moduleDefinitions: {\n              'toplevellegacy/fnaction': function (req, res) { res.send('legacy fn action!'); },\n              'toplevellegacy/machineaction': { exits: {success: {outputExample: 'abc123'} }, fn: function (inputs, exits) { exits.success('legacy machine action!'); } },\n              'top-level-standalone-fn': function (req, res) { res.send('top level standalone fn!'); },\n              'top-level-standalone-machine': { exits: {success: {outputExample: 'abc123'} },  fn: function (inputs, exits) { exits.success('top level standalone machine!'); } },\n              'somefolder/someotherfolder/nestedlegacy/fnaction': function (req, res) { res.send('nested legacy fn action!'); },\n              'somefolder/someotherfolder/nestedlegacy/machineaction': { exits: {success: {outputExample: 'abc123'} },  fn: function (inputs, exits) { exits.success('nested legacy machine action!'); } },\n              'somefolder/someotherfolder/nested-standalone-machine': { exits: {success: {outputExample: 'abc123'} },  fn: function (inputs, exits) { exits.success('nested standalone machine!'); } }\n            }\n          }\n\n        }, function(err, _sails) {\n          if (err) { return done(err); }\n          sailsApp = _sails;\n          return done();\n        });\n      });\n\n      after(function(done) {\n        sailsApp.lower(function() {\n          process.chdir(curDir);\n          return done();\n        });\n      });\n\n      it('should not bind a route to \\'ALL /toplevellegacy/fnaction\\'', function(done) {\n        sailsApp.request('POST /toplevellegacy/fnaction', {}, function (err, resp, data) {\n          assert(err);\n          assert.equal(err.status, 404);\n          return done();\n        });\n      });\n\n      it('should bind a route to \\'ALL /top-level-standalone-fn\\'', function(done) {\n        sailsApp.request('POST /top-level-standalone-fn', {}, function (err, resp, data) {\n          assert(err);\n          assert.equal(err.status, 404);\n          return done();\n        });\n      });\n    }); // </ describe('when turned off globally :: ', ... >\n\n    describe('when turned off for a single controller', function() {\n\n      var curDir, tmpDir, sailsApp;\n\n      before(function(done) {\n        // Cache the current working directory.\n        curDir = process.cwd();\n        // Create a temp directory.\n        tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n        // Switch to the temp directory.\n        process.chdir(tmpDir.name);\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'api/controllers/NoController.js',\n          string: 'module.exports = { _config: { actions: false }, test: function (req, res) { return res.ok(); } }'\n        }).execSync();\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'api/controllers/YesController.js',\n          string: 'module.exports = { test: function (req, res) { return res.ok(); } }'\n        }).execSync();\n\n        (new Sails()).load({\n          hooks: {\n            grunt: false, views: false, policies: false, pubsub: false, i18n: false\n          },\n          models: {\n            migrate: 'drop',\n            schema: true\n          },\n          blueprints: {\n            actions: true,\n            shortcuts: false,\n            rest: false\n          },\n          log: {level: 'error'},\n\n        }, function(err, _sails) {\n          if (err) { return done(err); }\n          sailsApp = _sails;\n          return done();\n        });\n      });\n\n      after(function(done) {\n        sailsApp.lower(function() {\n          process.chdir(curDir);\n          return done();\n        });\n      });\n\n      it('should not bind a route to \\'ALL /no/test\\'', function(done) {\n        sailsApp.request('POST /no/test', {}, function (err, resp, data) {\n          assert(err);\n          assert.equal(err.status, 404);\n          return done();\n        });\n      });\n\n      it('should bind a route to \\'ALL /yes/test\\'', function(done) {\n        sailsApp.request('POST /yes/test', {}, function (err, resp, data) {\n          assert(!err, err);\n          done();\n        });\n      });\n    }); // </ describe('for a single controller', ... >\n\n  });\n\n});\n"
  },
  {
    "path": "test/integration/hook.blueprints.blacklist.test.js",
    "content": "/**\n * Test dependencies\n */\n\nvar assert = require('assert');\nvar httpHelper = require('./helpers/httpHelper.js');\nvar appHelper = require('./helpers/appHelper');\nvar util = require('util');\nvar async = require('async');\nvar fixture = require('./fixtures/users.js');\nvar _ = require('@sailshq/lodash');\nvar fs = require('fs-extra');\nvar path = require('path');\nvar Sails = require('../../lib/app');\n\n\n\n\nxdescribe('blueprints :: ', function() {\n\n  describe('using the values blacklist ::', function() {\n\n    describe('updating a model with a non-primary-key \"id\" attribute', function() {\n\n      before(function(done) {\n        // Build the app\n        appHelper.build(function(err) {\n          if (err) { return done(err); }\n\n          var Goal = {\n            attributes: {\n              hash: {\n                type: 'string',\n                unique: true,\n                primaryKey: true\n              },\n              id: 'integer',\n              active: 'boolean'\n            }\n          };\n\n          fs.outputFileSync(path.resolve(__dirname,'../../testApp/api/models/Goal.js'), 'module.exports = ' + JSON.stringify(Goal) + ';');\n          fs.outputFileSync(path.resolve(__dirname,'../../testApp/api/controllers/GoalController.js'), 'module.exports = {};');\n          return done();\n        });\n      });\n\n\n      var sails = Sails();\n\n      before(function(done) {\n        sails.load({\n          hooks: {\n            grunt: false,\n            i18n: false\n          },\n          globals: false,\n          log: {\n            level: 'silent'\n          }\n        }, function(err) {\n          if (err) {return done(err);}\n          sails.models.goal.create({id: 1, hash: 'abc', active: false}).exec(done);\n        });\n      });\n\n\n      it('should update the record successfully', function(done) {\n        sails.request('put /goal/abc', {active: true}, function(err, response, body) {\n          if (err) {return done(err);}\n          assert.equal (response.statusCode, 200);\n          assert.equal(body.id, 1);\n          assert.equal(body.hash, 'abc');\n          assert.equal(body.active, true);\n          return done();\n        });\n      });\n\n      after(function(done) {\n        sails.lower(function(err){\n          if (err) {return done(err);}\n          setTimeout(done, 100);\n        });\n      });//</after>\n\n      after(function(done) {\n        process.chdir('../');\n        appHelper.teardown();\n        return done();\n      });//</after>\n    });//</describe(updating a model with a non-primary-key \"id\" attribute)>\n  });//</describe>\n});//</describe>\n\n\n"
  },
  {
    "path": "test/integration/hook.blueprints.index.routes.test.js",
    "content": "/**\n * Test dependencies\n */\n\nvar util = require('util');\nvar assert = require('assert');\nvar tmp = require('tmp');\nvar _ = require('@sailshq/lodash');\n\nvar appHelper = require('./helpers/appHelper');\n\nvar Sails = require('../../lib').constructor;\n\n/**\n * Errors\n */\nvar Err = {\n  badResponse: function(response) {\n    return 'Wrong server response!  Response :::\\n' + util.inspect(response.body);\n  }\n};\n\n\ndescribe('blueprints :: ', function() {\n\n  var curDir, tmpDir, sailsApp;\n  var extraSailsConfig = {};\n\n  before(function(done) {\n    // Cache the current working directory.\n    curDir = process.cwd();\n    // Create a temp directory.\n    tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n    // Switch to the temp directory.\n    process.chdir(tmpDir.name);\n    appHelper.linkDeps(tmpDir.name);\n\n    (new Sails()).load(_.merge({\n      hooks: {\n        grunt: false, views: false, policies: false, pubsub: false, i18n: false\n      },\n      orm: {\n        moduleDefinitions: {\n          models: { 'user': {} }\n        }\n      },\n      models: {\n        migrate: 'drop',\n        attributes: {\n          createdAt: { type: 'number', autoCreatedAt: true, },\n          updatedAt: { type: 'number', autoUpdatedAt: true, },\n          id: { type: 'number', autoIncrement: true}\n        }\n      },\n      blueprints: {\n        shortcuts: false,\n        actions: true,\n        rest: true,\n        index: true\n      },\n      log: {level: 'error'},\n      controllers: {\n        moduleDefinitions: {\n          'index': function (req, res) { res.send('top-level index!'); },\n          'secondlevel/index': function (req, res) { res.send('second-level index!'); },\n          'thirdlevel/index': function (req, res) { res.send('third-level index!'); },\n          'user/index': function (req, res) { res.send('user index!'); }\n        }\n      }\n\n    }, extraSailsConfig), function(err, _sails) {\n      if (err) { return done(err); }\n      sailsApp = _sails;\n      return done();\n    });\n  });\n\n  after(function(done) {\n    sailsApp.lower(function() {\n      process.chdir(curDir);\n      return done();\n    });\n  });\n\n  it('should bind \\'ALL /\\' to the `index` action', function(done) {\n    sailsApp.request('POST /', {}, function (err, resp, data) {\n      assert(!err, err);\n      assert.deepEqual(data, 'top-level index!');\n      sailsApp.request('GET /', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data, 'top-level index!');\n        done();\n      });\n    });\n  });\n\n  it('should bind \\'ALL /secondlevel\\' to the `secondlevel.index` action', function(done) {\n    sailsApp.request('POST /secondlevel', {}, function (err, resp, data) {\n      assert(!err, err);\n      assert.deepEqual(data, 'second-level index!');\n      sailsApp.request('GET /secondlevel', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data, 'second-level index!');\n        done();\n      });\n    });\n  });\n\n  it('should bind \\'ALL /thirdlevel\\' to the `thirdlevel.index` action', function(done) {\n    sailsApp.request('POST /thirdlevel', {}, function (err, resp, data) {\n      assert(!err, err);\n      assert.deepEqual(data, 'third-level index!');\n      sailsApp.request('GET /thirdlevel', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data, 'third-level index!');\n        done();\n      });\n    });\n  });\n\n  it('should not override RESTful routes', function(done) {\n    sailsApp.request('POST /user', {}, function (err, resp, data) {\n      assert(!err, err);\n      assert.deepEqual(data.id, 1);\n      sailsApp.request('GET /user', function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data.length, 1);\n        assert.deepEqual(data[0].id, 1);\n        done();\n      });\n    });\n  });\n\n});\n\n"
  },
  {
    "path": "test/integration/hook.blueprints.restful.routes.test.js",
    "content": "/**\n * Test dependencies\n */\n\nvar util = require('util');\nvar assert = require('assert');\nvar tmp = require('tmp');\nvar _ = require('@sailshq/lodash');\nvar Filesystem = require('machinepack-fs');\n\nvar appHelper = require('./helpers/appHelper');\nvar Sails = require('../../lib').constructor;\n\n/**\n * Errors\n */\nvar Err = {\n  badResponse: function(response) {\n    return 'Wrong server response!  Response :::\\n' + util.inspect(response.body);\n  }\n};\n\n\ndescribe('blueprints :: ', function() {\n\n  var curDir, tmpDir, sailsApp;\n\n  var extraSailsConfig = {};\n\n  describe('restful routes :: ', function() {\n\n    describe('when turned off globaly :: ', function() {\n\n      before(function(done) {\n        // Cache the current working directory.\n        curDir = process.cwd();\n        // Create a temp directory.\n        tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n        // Switch to the temp directory.\n        process.chdir(tmpDir.name);\n        appHelper.linkDeps(tmpDir.name);\n        (new Sails()).load({\n          hooks: {\n            grunt: false, views: false, policies: false, pubsub: false, i18n: false\n          },\n          orm: {\n            moduleDefinitions: {\n              models: {\n                user: {\n                  attributes: {\n                    name: 'string',\n                    pets: {\n                      collection: 'pet',\n                      via: 'owner'\n                    }\n                  }\n                },\n                pet: {\n                  attributes: {\n                    name: 'string',\n                    owner: {\n                      model: 'user'\n                    }\n                  }\n                }\n              },\n            }\n          },\n          models: {\n            migrate: 'drop',\n            schema: true,\n            attributes: {\n              createdAt: { type: 'number', autoCreatedAt: true, },\n              updatedAt: { type: 'number', autoUpdatedAt: true, },\n              // id: { type: 'string', unique: true, columnName: '_id'},\n              id: { type: 'number', autoIncrement: true}\n            }\n          },\n          blueprints: {\n            rest: false,\n            shortcuts: false,\n            actions: false\n          },\n          log: {level: 'error'}\n        }, function(err, _sails) {\n          if (err) { return done(err); }\n          sailsApp = _sails;\n          return done();\n        });\n      });\n\n      after(function(done) {\n        extraSailsConfig = {};\n        sailsApp.lower(function() {\n          process.chdir(curDir);\n          return done();\n        });\n      });\n\n      it('a get request to /:model should return a 404', function(done) {\n        sailsApp.models.user.create({name: 'al'}).exec(function(err) {\n          if (err) {return done (err);}\n          sailsApp.request('get /user', function (err, resp, data) {\n            assert(err);\n            assert.equal(err.status, 404);\n            done();\n          });\n        });\n      });\n\n    });\n\n    describe('when turned off for a specific controller :: ', function() {\n\n      before(function(done) {\n        // Cache the current working directory.\n        curDir = process.cwd();\n        // Create a temp directory.\n        tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n        // Switch to the temp directory.\n        process.chdir(tmpDir.name);\n        appHelper.linkDeps(tmpDir.name);\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'api/controllers/UserController.js',\n          string: 'module.exports = { _config: { rest: false } }'\n        }).execSync();\n\n        (new Sails()).load({\n          hooks: {\n            grunt: false, views: false, policies: false, pubsub: false, i18n: false\n          },\n          orm: {\n            moduleDefinitions: {\n              models: {\n                user: {\n                  attributes: {\n                    name: 'string',\n                    pets: {\n                      collection: 'pet',\n                      via: 'owner'\n                    }\n                  }\n                },\n                pet: {\n                  attributes: {\n                    name: 'string',\n                    owner: {\n                      model: 'user'\n                    }\n                  }\n                }\n              },\n            }\n          },\n          models: {\n            migrate: 'drop',\n            schema: true,\n            attributes: {\n              createdAt: { type: 'number', autoCreatedAt: true, },\n              updatedAt: { type: 'number', autoUpdatedAt: true, },\n              // id: { type: 'string', unique: true, columnName: '_id'},\n              id: { type: 'number', autoIncrement: true}\n            }\n          },\n          blueprints: {\n            shortcuts: false,\n            actions: false\n          },\n          log: {level: 'error'}\n        }, function(err, _sails) {\n          if (err) { return done(err); }\n          sailsApp = _sails;\n          return done();\n        });\n      });\n\n      after(function(done) {\n        extraSailsConfig = {};\n        sailsApp.lower(function() {\n          process.chdir(curDir);\n          return done();\n        });\n      });\n\n      it('a get request to the /:model with REST disabled should return a 404', function(done) {\n        sailsApp.models.user.create({name: 'al'}).exec(function(err) {\n          if (err) {return done (err);}\n          sailsApp.request('get /user', function (err, resp, data) {\n            assert(err);\n            assert.equal(err.status, 404);\n            done();\n          });\n        });\n      });\n\n      it('a get request to the /:model with REST enabled should return JSON for all of the instances of the test model', function(done) {\n        sailsApp.models.pet.create({name: 'rex'}).exec(function(err) {\n          if (err) {return done (err);}\n          sailsApp.request('get /pet', function (err, resp, data) {\n            assert(!err, err);\n            assert.equal(data.length, 1);\n            assert.equal(data[0].name, 'rex');\n            assert.equal(data[0].id, 1);\n            done();\n          });\n        });\n      });\n\n    });\n\n    describe('when turned on :: ', function() {\n\n      beforeEach(function(done) {\n        // Cache the current working directory.\n        curDir = process.cwd();\n        // Create a temp directory.\n        tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n        // Switch to the temp directory.\n        process.chdir(tmpDir.name);\n        appHelper.linkDeps(tmpDir.name);\n        (new Sails()).load(_.merge({\n          hooks: {\n            grunt: false, views: false, policies: false, i18n: false\n          },\n          orm: {\n            moduleDefinitions: {\n              models: { 'user': {} }\n            }\n          },\n          models: {\n            migrate: 'drop',\n            schema: true,\n            attributes: {\n              createdAt: { type: 'number', autoCreatedAt: true, },\n              updatedAt: { type: 'number', autoUpdatedAt: true, },\n              // id: { type: 'string', unique: true, columnName: '_id'},\n              id: { type: 'number', autoIncrement: true}\n            }\n          },\n          blueprints: {\n            shortcuts: false,\n            actions: false\n          },\n          log: {level: 'error'}\n        }, extraSailsConfig), function(err, _sails) {\n          if (err) { return done(err); }\n          sailsApp = _sails;\n          return done();\n        });\n      });\n\n      afterEach(function(done) {\n        sailsApp.lower(function() {\n          process.chdir(curDir);\n          return done();\n        });\n      });\n\n      describe('basic usage :: ', function() {\n\n        before(function() {\n          extraSailsConfig = {\n            orm: {\n              moduleDefinitions: {\n                models: {\n                  user: {\n                    attributes: {\n                      name: 'string',\n                      pets: {\n                        collection: 'pet',\n                        via: 'owner'\n                      },\n                      animalFriends: {\n                        collection: 'pet',\n                        via: 'humanFriends'\n                      }\n                    }\n                  },\n                  pet: {\n                    attributes: {\n                      name: 'string',\n                      owner: {\n                        model: 'user'\n                      },\n                      humanFriends: {\n                        collection: 'user',\n                        via: 'animalFriends'\n                      }\n                    }\n                  }\n                },\n              }\n            }\n          };\n        });\n\n        after(function() {\n          extraSailsConfig = {};\n        });\n\n        describe('a get request to /archive (the default archive model', function() {\n          it('should return a 404', function(done) {\n            sailsApp.request('get /archive', function (err) {\n              assert(err, 'Should have received an error trying to access blueprint for archive model, but didn\\'t!');\n              assert.equal(err.status, 404);\n              done();\n            });\n          });\n        });\n\n        describe('a get request to /:model', function() {\n\n          describe('where a single instance of the model exists', function() {\n\n            it('should return JSON for all of the instances of the test model', function(done) {\n              sailsApp.models.user.create({name: 'al'}).exec(function(err) {\n                if (err) {return done (err);}\n                sailsApp.request('get /user', function (err, resp, data) {\n                  assert(!err, err);\n                  assert.equal(data.length, 1);\n                  assert.equal(data[0].name, 'al');\n                  assert.equal(data[0].id, 1);\n                  done();\n                });\n              });\n            });\n\n\n            it('should populate all associations of the test model', function(done) {\n              sailsApp.models.pet.createEach([{name: 'alice'}, {name: 'tex'}, {name: 'bailey'}]).meta({fetch: true}).exec(function(err, pets) {\n                sailsApp.models.user.create({name: 'al', pets: _.pluck(pets, 'id')}).exec(function(err) {\n                  if (err) {return done (err);}\n                  sailsApp.request('get /user', function (err, resp, data) {\n                    assert(!err, err);\n                    assert.equal(data.length, 1);\n                    assert.equal(data[0].name, 'al');\n                    assert.equal(data[0].id, 1);\n                    assert.equal(data[0].pets.length, 3);\n                    done();\n                  });\n                });\n              });\n            });\n\n            it('should limit populate records to the default limit (30)', function(done) {\n              var instancesToCreate = _.map(_.range(1,41), function(i) {\n                return { name: 'pet' + i };\n              });\n              sailsApp.models.pet.createEach(instancesToCreate).meta({fetch: true}).exec(function(err, pets) {\n                sailsApp.models.user.create({name: 'al', pets: _.pluck(pets, 'id')}).exec(function(err) {\n                  if (err) {return done (err);}\n                  sailsApp.request('get /user', function (err, resp, data) {\n                    assert(!err, err);\n                    assert.equal(data.length, 1);\n                    assert.equal(data[0].name, 'al');\n                    assert.equal(data[0].id, 1);\n                    assert.equal(data[0].pets.length, 30);\n                    done();\n                  });\n                });\n              });\n            });\n\n          });\n\n          describe('where 40 instances of the model exist, with no limit set', function() {\n\n            it('should return JSON for 30 instances of the test model (becase the default limit is 30)', function(done) {\n              var instancesToCreate = _.map(_.range(1,41), function(i) {\n                return { name: 'user_' + i };\n              });\n              sailsApp.models.user.createEach(instancesToCreate).exec(function(err) {\n                if (err) {return done (err);}\n                sailsApp.request('get /user', function (err, resp, data) {\n                  assert(!err, err);\n                  assert.equal(data.length, 30);\n                  done();\n                });\n              });\n            });\n\n          });\n\n          describe('where 40 instances of the model exist, with limit set to 35', function() {\n\n            it('should return JSON for 35 instances of the test model', function(done) {\n              var instancesToCreate = _.map(_.range(1,41), function(i) {\n                return { name: 'user_' + i };\n              });\n              sailsApp.models.user.createEach(instancesToCreate).exec(function(err) {\n                if (err) {return done (err);}\n                sailsApp.request('get /user?limit=35', function (err, resp, data) {\n                  assert(!err, err);\n                  assert.equal(data.length, 35);\n                  done();\n                });\n              });\n            });\n\n          });\n\n        });\n\n        describe('a get request to /:model?id=1', function() {\n\n          it('should return an array of 1 item', function(done) {\n            sailsApp.models.user.create({name: 'jeremy'}).exec(function(err) {\n              if (err) {return done (err);}\n              sailsApp.request('get /user?id=1', function (err, resp, data) {\n                assert(!err, err);\n                assert(_.isArray(data), 'Should have receieved an array, but got: ' + util.inspect(data, {depth: null}));\n                assert.equal(data.length, 1);\n                assert.equal(data[0].name, 'jeremy');\n                assert.equal(data[0].id, 1);\n                done();\n              });\n            });\n          });\n        });\n\n\n        describe('a get request to /:model/:id', function() {\n\n          it('should return JSON for the requested instance of the test model', function(done) {\n            sailsApp.models.user.create({name: 'ron'}).exec(function(err) {\n              if (err) {return done (err);}\n              sailsApp.request('get /user/1', function (err, resp, data) {\n                assert(!err, err);\n                assert.equal(data.name, 'ron');\n                assert.equal(data.id, 1);\n                done();\n              });\n            });\n          });\n        });\n\n        describe('a patch request to /:model/:id', function() {\n\n          it('should return JSON for an updated instance of the test model', function(done) {\n            sailsApp.models.user.create({name: 'dave'}).exec(function(err) {\n              if (err) {return done (err);}\n              sailsApp.request('patch /user/1', {name: 'larry'}, function (err, resp, data) {\n                if (err) {return done (err);}\n                assert.equal(data.name, 'larry');\n                assert.equal(data.id, 1);\n                sailsApp.models.user.findOne({id: 1}).exec(function(err, user) {\n                  if (err) {return done (err);}\n                  assert(user);\n                  assert.equal(user.name, 'larry');\n                  return done();\n                });\n              });\n            });\n          });\n        });\n\n        describe('a put request to /:model/:id', function() {\n\n          it('should return JSON for an updated instance of the test model', function(done) {\n            sailsApp.models.user.create({name: 'dave'}).exec(function(err) {\n              if (err) {return done (err);}\n              sailsApp.request('put /user/1', {name: 'bob'}, function (err, resp, data) {\n                if (err) {return done (err);}\n                assert.equal(data.name, 'bob');\n                assert.equal(data.id, 1);\n                sailsApp.models.user.findOne({id: 1}).exec(function(err, user) {\n                  if (err) {return done (err);}\n                  assert(user);\n                  assert.equal(user.name, 'bob');\n                  return done();\n                });\n              });\n            });\n          });\n        });\n\n        describe('a post request to /:model', function() {\n\n          it('should return JSON for a newly created instance of the test model', function(done) {\n            sailsApp.request('post /user', {name: 'joe'}, function (err, resp, data) {\n              assert(!err, err);\n              assert.equal(data.name, 'joe');\n              assert.equal(data.id, 1);\n              sailsApp.models.user.findOne({id: 1}).exec(function(err, user) {\n                if (err) {return done (err);}\n                assert(user);\n                assert.equal(user.name, 'joe');\n                return done();\n              });\n            });\n          });\n        });\n\n\n        describe('a delete request to /:model', function() {\n\n          it('should return JSON for the deleted instance of the test model', function(done) {\n            sailsApp.models.user.create({name: 'bubba'}).exec(function(err) {\n              if (err) {return done (err);}\n              sailsApp.request('delete /user/1', function (err, resp, data) {\n                assert(!err, err);\n                assert.equal(data.name, 'bubba');\n                assert.equal(data.id, 1);\n                sailsApp.models.user.findOne({id: 1}).exec(function(err, user) {\n                  if (err) {return done (err);}\n                  assert(!user);\n                  return done();\n                });\n              });\n            });\n          });\n        });\n\n        //   █████╗ ███████╗███████╗ ██████╗  ██████╗██╗ █████╗ ████████╗██╗ ██████╗ ███╗   ██╗███████╗\n        //  ██╔══██╗██╔════╝██╔════╝██╔═══██╗██╔════╝██║██╔══██╗╚══██╔══╝██║██╔═══██╗████╗  ██║██╔════╝\n        //  ███████║███████╗███████╗██║   ██║██║     ██║███████║   ██║   ██║██║   ██║██╔██╗ ██║███████╗\n        //  ██╔══██║╚════██║╚════██║██║   ██║██║     ██║██╔══██║   ██║   ██║██║   ██║██║╚██╗██║╚════██║\n        //  ██║  ██║███████║███████║╚██████╔╝╚██████╗██║██║  ██║   ██║   ██║╚██████╔╝██║ ╚████║███████║\n        //  ╚═╝  ╚═╝╚══════╝╚══════╝ ╚═════╝  ╚═════╝╚═╝╚═╝  ╚═╝   ╚═╝   ╚═╝ ╚═════╝ ╚═╝  ╚═══╝╚══════╝\n        //\n\n        describe('associations :: ', function() {\n\n          describe('one to many :: ', function() {\n\n            describe('a post request to /:model with an array specified for a collection attribute', function() {\n\n              it('should return JSON for the new record including the associated collection', function(done) {\n                sailsApp.models.pet.create({name: 'spot'}).meta({fetch: true}).exec(function(err, spot) {\n                  sailsApp.request('post /user', {name: 'will', pets: [spot.id]}, function (err, resp, data) {\n                    if (err) {return done (err);}\n                    assert.equal(data.name, 'will');\n                    assert.equal(data.id, 1);\n                    assert.equal(data.pets.length, 1);\n                    assert.equal(data.pets[0].name, 'spot');\n                    return done();\n                  });\n                });\n              });\n            });\n\n\n            describe('a get request to /:model/:parentid/:association for a plural association', function() {\n\n              describe('where a single child instance exists', function() {\n\n                it('should return JSON for the specified collection of the test model', function(done) {\n                  sailsApp.models.pet.create({name: 'spot'}).meta({fetch: true}).exec(function(err, spot) {\n                    sailsApp.models.user.create({name: 'will', pets: [spot.id]}).meta({fetch: true}).exec(function(err, will) {\n                      if (err) {return done (err);}\n                      sailsApp.request('get /user/1/pets', function (err, resp, data) {\n                        if (err) {return done (err);}\n                        assert.equal(data.length, 1);\n                        assert.equal(data[0].name, 'spot');\n                        assert.equal(data[0].id, 1);\n                        assert.equal(data[0].owner, 1);\n                        return done();\n                      });\n                    });\n                  });\n                });\n\n              });\n\n              describe('where a 40 instances exist, and no limit is given', function() {\n\n                it('should return JSON for 30 records of the specified collection of the test model (since the default limit is 30)', function(done) {\n                  var instancesToCreate = _.map(_.range(1,41), function(i) {\n                    return { name: 'pet_' + i };\n                  });\n                  sailsApp.models.pet.createEach(instancesToCreate).meta({fetch: true}).exec(function(err, pets) {\n                    sailsApp.models.user.create({name: 'will', pets: _.pluck(pets, 'id')}).meta({fetch: true}).exec(function(err, will) {\n                      if (err) {return done (err);}\n                      sailsApp.request('get /user/1/pets', function (err, resp, data) {\n                        if (err) {return done (err);}\n                        assert.equal(data.length, 30);\n                        return done();\n                      });\n                    });\n                  });\n                });\n\n              });\n\n\n              describe('where a 40 instances exist, and a limit of 35 is given', function() {\n\n                it('should return JSON for 35 records of the specified collection of the test model', function(done) {\n                  var instancesToCreate = _.map(_.range(1,41), function(i) {\n                    return { name: 'pet_' + i };\n                  });\n                  sailsApp.models.pet.createEach(instancesToCreate).meta({fetch: true}).exec(function(err, pets) {\n                    sailsApp.models.user.create({name: 'will', pets: _.pluck(pets, 'id')}).meta({fetch: true}).exec(function(err, will) {\n                      if (err) {return done (err);}\n                      sailsApp.request('get /user/1/pets?limit=35', function (err, resp, data) {\n                        if (err) {return done (err);}\n                        assert.equal(data.length, 35);\n                        return done();\n                      });\n                    });\n                  });\n                });\n\n              });\n\n            });\n\n            describe('a get request to /:model/:parentid/:association for a plural association with no associated records', function() {\n\n              it('should return JSON for the specified collection of the test model', function(done) {\n                sailsApp.models.user.create({name: 'will'}).meta({fetch: true}).exec(function(err, will) {\n                  if (err) {return done (err);}\n                  sailsApp.request('get /user/1/pets', function (err, resp, data) {\n                    if (err) {return done (err);}\n                    assert.equal(data.length, 0);\n                    return done();\n                  });\n                });\n              });\n            });\n\n            describe('a get request to /:model/:parentid/:association for a singular association', function() {\n\n              it('should return JSON for the specified collection of the test model', function(done) {\n                sailsApp.models.pet.create({name: 'spot'}).meta({fetch: true}).exec(function(err, spot) {\n                  sailsApp.models.user.create({name: 'will', pets: [spot.id]}).meta({fetch: true}).exec(function(err, will) {\n                    if (err) {return done (err);}\n                    sailsApp.request('get /pet/1/owner', function (err, resp, data) {\n                      if (err) {return done (err);}\n                      assert.equal(data.name, 'will');\n                      assert.equal(data.id, 1);\n                      return done();\n                    });\n                  });\n                });\n              });\n            });\n\n            describe('a get request to /:model/:parentid/:association for a singular association with no associated record', function() {\n\n              it('should return JSON for the specified collection of the test model', function(done) {\n                sailsApp.models.pet.create({name: 'spot'}).meta({fetch: true}).exec(function(err, spot) {\n                  sailsApp.request('get /pet/1/owner', function (err, resp, data) {\n                    if (err) {\n                      if (err.status && err.status === 404) {\n                        return done();\n                      }\n                      return done(new Error('Should have responded with a 404 error, but instead got:' + util.inspect(err, {depth: null})));\n                    }\n                    return done(new Error('Should have responded with a 404 error, but instead got:' + util.inspect(data, {depth: null})));\n                  });\n                });\n              });\n            });\n\n            describe('a get request to /:model/:parentid/:association/:id', function() {\n\n              it('should return a 404', function(done) {\n                sailsApp.models.pet.createEach([{name: 'bubbles'}, {name: 'dempsey'}]).meta({fetch: true}).exec(function(err, pets) {\n                  sailsApp.models.user.create({name: 'roger', pets: _.pluck(pets,'id')}).meta({fetch: true}).exec(function(err) {\n                    if (err) {return done (err);}\n                    sailsApp.request('get /user/1/pets/2', function (err, resp, data) {\n                      if (err) {\n                        if (err.status && err.status === 404) {\n                          return done();\n                        }\n                        return done(new Error('Should have responded with a 404 error, but instead got:' + util.inspect(err, {depth: null})));\n                      }\n                      return done(new Error('Should have responded with a 404 error, but instead got:' + util.inspect(data, {depth: null})));\n                    });\n                  });\n                });\n              });\n            });\n\n            describe('a put request to /:model/:parentid/:association/:id', function() {\n\n              it('should return JSON for an instance of the test model, with its collection updated', function(done) {\n                sailsApp.models.user.create({name: 'ira'}).exec(function(err) {\n                  if (err) {return done (err);}\n                  sailsApp.models.pet.create({name: 'flipper'}).exec(function(err) {\n                    if (err) {return done (err);}\n                    sailsApp.request('put /user/1/pets/1', function (err, resp, data) {\n                      if (err) {return done (err);}\n                      assert.equal(data.name, 'ira');\n                      assert.equal(data.id, 1);\n                      assert.equal(data.pets.length, 1);\n                      assert.equal(data.pets[0].name, 'flipper');\n                      sailsApp.models.user.findOne({id: 1}).populate('pets').exec(function(err, user) {\n                        if (err) {return done (err);}\n                        assert(user);\n                        assert.equal(user.name, 'ira');\n                        assert.equal(user.id, 1);\n                        assert.equal(user.pets.length, 1);\n                        assert.equal(user.pets[0].name, 'flipper');\n                        return done();\n                      });\n                    });\n                  });\n                });\n              });\n            });\n\n            describe('a put request to /:model/:parentid/:association (with empty array)', function() {\n\n              it('should return JSON for an instance of the test model, with its collection replaced', function(done) {\n                sailsApp.models.user.create({name: 'ira', id: 1}).exec(function(err) {\n                  if (err) {return done (err);}\n                  sailsApp.models.pet.create({name: 'flipper', id: 1, owner: 1}).exec(function(err) {\n                    if (err) {return done (err);}\n                    sailsApp.request('put /user/1/pets', [], function (err, resp, data) {\n                      if (err) {return done (err);}\n                      assert.equal(data.name, 'ira');\n                      assert.equal(data.pets.length, 0);\n                      sailsApp.models.pet.findOne({id: 1}).populate('owner').exec(function(err, pet) {\n                        if (err) {return done (err);}\n                        assert(pet);\n                        assert.equal(pet.name, 'flipper');\n                        assert.equal(pet.id, 1);\n                        assert.equal(pet.owner, null);\n                        return done();\n                      });\n                    });\n                  });\n                });\n              });\n            });\n\n            describe('a put request to /:model/:parentid/:association (with new array)', function() {\n\n              it('should return JSON for an instance of the test model, with its collection replaced', function(done) {\n                sailsApp.models.user.create({name: 'zooey'}).exec(function(err) {\n                  if (err) {return done (err);}\n                  sailsApp.models.pet.createEach([{name: 'ralph', id: 1, owner: 1}, {name: 'fiona', id: 2}]).exec(function(err) {\n                    if (err) {return done (err);}\n                    sailsApp.request('put /user/1/pets', [2], function (err, resp, data) {\n                      if (err) {return done (err);}\n                      assert.equal(data.name, 'zooey');\n                      assert.equal(data.pets.length, 1);\n                      assert.equal(data.pets[0].id, 2);\n                      assert.equal(data.pets[0].name, 'fiona');\n                      sailsApp.models.pet.findOne({id: 2}).populate('owner').exec(function(err, pet) {\n                        if (err) {return done (err);}\n                        assert(pet);\n                        assert.equal(pet.name, 'fiona');\n                        assert.equal(pet.id, 2);\n                        assert.equal(pet.owner.name, 'zooey');\n                        assert.equal(pet.owner.id, 1);\n                        return done();\n                      });\n                    });\n                  });\n                });\n              });\n            });\n\n            describe('a delete request to /:model/:parentid/:association/:id', function() {\n\n              it('should return JSON for an instance of the test model, with its collection updated', function(done) {\n                sailsApp.models.pet.create({name: 'alice'}).meta({fetch: true}).exec(function(err, alice) {\n                  sailsApp.models.user.create({name: 'larry', pets: [alice.id]}).meta({fetch: true}).exec(function(err) {\n                    if (err) {return done (err);}\n                    sailsApp.request('delete /user/1/pets/1', function (err, resp, data) {\n                      if (err) {return done (err);}\n                      assert.equal(data.name, 'larry');\n                      assert.equal(data.id, 1);\n                      assert.equal(data.pets.length, 0);\n                      sailsApp.models.user.findOne({id: 1}).populate('pets').exec(function(err, user) {\n                        if (err) {return done (err);}\n                        assert(user);\n                        assert.equal(user.name, 'larry');\n                        assert.equal(user.id, 1);\n                        assert.equal(user.pets.length, 0);\n                        return done();\n                      });\n                    });\n                  });\n                });\n              });\n            });\n\n          });\n\n          describe('many-to-many :: ', function() {\n\n            describe('a post request to /:model with an array specified for a collection attribute', function() {\n\n              it('should return JSON for the new record including the associated collection', function(done) {\n                sailsApp.models.pet.create({name: 'spot'}).meta({fetch: true}).exec(function(err, spot) {\n                  sailsApp.request('post /user', {name: 'will', animalFriends: [spot.id]}, function (err, resp, data) {\n                    if (err) {return done (err);}\n                    assert.equal(data.name, 'will');\n                    assert.equal(data.id, 1);\n                    assert.equal(data.animalFriends.length, 1);\n                    assert.equal(data.animalFriends[0].name, 'spot');\n                    return done();\n                  });\n                });\n              });\n            });\n\n\n            describe('a get request to /:model/:parentid/:association for a plural association', function() {\n\n              describe('where a single child instance exists', function() {\n\n                it('should return JSON for the specified collection of the test model', function(done) {\n                  sailsApp.models.pet.create({name: 'spot'}).meta({fetch: true}).exec(function(err, spot) {\n                    sailsApp.models.user.create({name: 'will', animalFriends: [spot.id]}).meta({fetch: true}).exec(function(err, will) {\n                      if (err) {return done (err);}\n                      sailsApp.request('get /user/1/animalFriends', function (err, resp, data) {\n                        if (err) {return done (err);}\n                        assert.equal(data.length, 1);\n                        assert.equal(data[0].name, 'spot');\n                        assert.equal(data[0].id, 1);\n                        return done();\n                      });\n                    });\n                  });\n                });\n\n              });\n\n              describe('where a 40 instances exist, and no limit is given', function() {\n\n                it('should return JSON for 30 records of the specified collection of the test model (since the default limit is 30)', function(done) {\n                  var instancesToCreate = _.map(_.range(1,41), function(i) {\n                    return { name: 'pet_' + i };\n                  });\n                  sailsApp.models.pet.createEach(instancesToCreate).meta({fetch: true}).exec(function(err, pets) {\n                    sailsApp.models.user.create({name: 'will', animalFriends: _.pluck(pets, 'id')}).meta({fetch: true}).exec(function(err, will) {\n                      if (err) {return done (err);}\n                      sailsApp.request('get /user/1/animalFriends', function (err, resp, data) {\n                        if (err) {return done (err);}\n                        assert.equal(data.length, 30);\n                        return done();\n                      });\n                    });\n                  });\n                });\n\n              });\n\n\n              describe('where a 40 instances exist, and a limit of 35 is given', function() {\n\n                it('should return JSON for 35 records of the specified collection of the test model', function(done) {\n                  var instancesToCreate = _.map(_.range(1,41), function(i) {\n                    return { name: 'pet_' + i };\n                  });\n                  sailsApp.models.pet.createEach(instancesToCreate).meta({fetch: true}).exec(function(err, pets) {\n                    sailsApp.models.user.create({name: 'will', animalFriends: _.pluck(pets, 'id')}).meta({fetch: true}).exec(function(err, will) {\n                      if (err) {return done (err);}\n                      sailsApp.request('get /user/1/animalFriends?limit=35', function (err, resp, data) {\n                        if (err) {return done (err);}\n                        assert.equal(data.length, 35);\n                        return done();\n                      });\n                    });\n                  });\n                });\n\n              });\n\n            });\n\n            describe('a get request to /:model/:parentid/:association for a plural association with no associated records', function() {\n\n              it('should return JSON for the specified collection of the test model', function(done) {\n                sailsApp.models.user.create({name: 'will'}).meta({fetch: true}).exec(function(err, will) {\n                  if (err) {return done (err);}\n                  sailsApp.request('get /user/1/animalFriends', function (err, resp, data) {\n                    if (err) {return done (err);}\n                    assert.equal(data.length, 0);\n                    return done();\n                  });\n                });\n              });\n            });\n\n            describe('a put request to /:model/:parentid/:association/:id', function() {\n\n              it('should return JSON for an instance of the test model, with its collection updated', function(done) {\n                sailsApp.models.user.create({name: 'ira'}).exec(function(err) {\n                  if (err) {return done (err);}\n                  sailsApp.models.pet.create({name: 'flipper'}).exec(function(err) {\n                    if (err) {return done (err);}\n                    sailsApp.request('put /user/1/animalFriends/1', function (err, resp, data) {\n                      if (err) {return done (err);}\n                      assert.equal(data.name, 'ira');\n                      assert.equal(data.id, 1);\n                      assert.equal(data.animalFriends.length, 1);\n                      assert.equal(data.animalFriends[0].name, 'flipper');\n                      sailsApp.models.user.findOne({id: 1}).populate('animalFriends').exec(function(err, user) {\n                        if (err) {return done (err);}\n                        assert(user);\n                        assert.equal(user.name, 'ira');\n                        assert.equal(user.id, 1);\n                        assert.equal(user.animalFriends.length, 1);\n                        assert.equal(user.animalFriends[0].name, 'flipper');\n                        return done();\n                      });\n                    });\n                  });\n                });\n              });\n            });\n\n            describe('a put request to /:model/:parentid/:association (with empty array)', function() {\n\n              it('should return JSON for an instance of the test model, with its collection replaced', function(done) {\n                sailsApp.models.user.create({name: 'ira', id: 1}).exec(function(err) {\n                  if (err) {return done (err);}\n                  sailsApp.models.pet.create({name: 'flipper', id: 1, humanFriends: [1]}).exec(function(err) {\n                    if (err) {return done (err);}\n                    sailsApp.request('put /user/1/animalFriends', [], function (err, resp, data) {\n                      if (err) {return done (err);}\n                      assert.equal(data.name, 'ira');\n                      assert.equal(data.animalFriends.length, 0);\n                      sailsApp.models.pet.findOne({id: 1}).populate('humanFriends').exec(function(err, pet) {\n                        if (err) {return done (err);}\n                        assert(pet);\n                        assert.equal(pet.name, 'flipper');\n                        assert.equal(pet.id, 1);\n                        assert.equal(pet.humanFriends.length, 0);\n                        return done();\n                      });\n                    });\n                  });\n                });\n              });\n            });\n\n            describe('a put request to /:model/:parentid/:association (with new array)', function() {\n\n              it('should return JSON for an instance of the test model, with its collection replaced', function(done) {\n                sailsApp.models.user.create({name: 'zooey'}).exec(function(err) {\n                  if (err) {return done (err);}\n                  sailsApp.models.pet.createEach([{name: 'ralph', id: 1, humanFriends: [1]}, {name: 'fiona', id: 2}]).exec(function(err) {\n                    if (err) {return done (err);}\n                    sailsApp.request('put /user/1/animalFriends', [2], function (err, resp, data) {\n                      if (err) {return done (err);}\n                      assert.equal(data.name, 'zooey');\n                      assert.equal(data.animalFriends.length, 1);\n                      assert.equal(data.animalFriends[0].id, 2);\n                      assert.equal(data.animalFriends[0].name, 'fiona');\n                      sailsApp.models.pet.findOne({id: 2}).populate('humanFriends').exec(function(err, pet) {\n                        if (err) {return done (err);}\n                        assert(pet);\n                        assert.equal(pet.name, 'fiona');\n                        assert.equal(pet.id, 2);\n                        assert.equal(pet.humanFriends.length, 1);\n                        assert.equal(pet.humanFriends[0].id, 1);\n                        return done();\n                      });\n                    });\n                  });\n                });\n              });\n            });\n\n            describe('a delete request to /:model/:parentid/:association/:id', function() {\n\n              it('should return JSON for an instance of the test model, with its collection updated', function(done) {\n                sailsApp.models.pet.create({name: 'alice'}).meta({fetch: true}).exec(function(err, alice) {\n                  sailsApp.models.user.create({name: 'larry', animalFriends: [alice.id]}).meta({fetch: true}).exec(function(err) {\n                    if (err) {return done (err);}\n                    sailsApp.request('delete /user/1/animalFriends/1', function (err, resp, data) {\n                      if (err) {return done (err);}\n                      assert.equal(data.name, 'larry');\n                      assert.equal(data.id, 1);\n                      assert.equal(data.animalFriends.length, 0);\n                      sailsApp.models.user.findOne({id: 1}).populate('animalFriends').exec(function(err, user) {\n                        if (err) {return done (err);}\n                        assert(user);\n                        assert.equal(user.name, 'larry');\n                        assert.equal(user.id, 1);\n                        assert.equal(user.animalFriends.length, 0);\n                        return done();\n                      });\n                    });\n                  });\n                });\n              });\n            });\n\n          });\n\n\n\n        });\n\n        describe('with a custom parseBlueprintOptions for all blueprints', function() {\n\n          before(function() {\n            extraSailsConfig = {\n              blueprints: {\n                parseBlueprintOptions: function(req) {\n                  var queryOptions = req._sails.hooks.blueprints.parseBlueprintOptions(req);\n                  if (queryOptions.populates.pets) {\n                    queryOptions.populates.pets.limit = 1;\n                  }\n                  return queryOptions;\n                }\n              },\n              routes: {\n                'GET /yolo/:id': 'user/findOne',\n              },\n              orm: {\n                moduleDefinitions: {\n                  models: {\n                    user: {\n                      attributes: {\n                        name: 'string',\n                        pets: {\n                          collection: 'pet',\n                          via: 'owner'\n                        }\n                      }\n                    },\n                    pet: {\n                      attributes: {\n                        name: 'string',\n                        owner: {\n                          model: 'user'\n                        }\n                      }\n                    }\n                  },\n                }\n              }\n            };\n          });\n\n          after(function() {\n            extraSailsConfig = {};\n          });\n\n          it('the custom `parseBlueprintOptions` should be applied to the `find` blueprint', function(done) {\n\n            sailsApp.models.pet.createEach([{name: 'alice'}, {name: 'rex'}]).meta({fetch: true}).exec(function(err, pets) {\n              if (err) {return done(err);}\n              sailsApp.models.user.create({name: 'bill', pets: _.pluck(pets, sailsApp.models.pet.primaryKey)}).exec(function(err, bill) {\n                if (err) {return done(err);}\n                sailsApp.request('get /user/' + bill[sailsApp.models.user.primaryKey], function (err, resp, data) {\n                  if (err) {return done (err);}\n                  assert.equal(data.name, 'bill');\n                  assert(data.pets, 'Record should have `pets` key, but none was found.  Full record: ' + util.inspect(data, {depth: null}));\n                  assert.equal(data.pets.length, 1);\n                  return done();\n                });\n              });\n            });\n\n          });\n\n          it('the custom `parseBlueprintOptions` should be applied to the `create` blueprint', function(done) {\n\n            sailsApp.models.pet.createEach([{name: 'june'}, {name: 'jane'}]).meta({fetch: true}).exec(function(err, pets) {\n              if (err) {return done(err);}\n              sailsApp.request('post /user', {name: 'bob', pets: _.pluck(pets, sailsApp.models.pet.primaryKey)}, function (err, resp, data) {\n                if (err) {return done (err);}\n                assert.equal(data.name, 'bob');\n                assert(data.pets, 'Record should have `pets` key, but none was found.  Full record: ' + util.inspect(data, {depth: null}));\n                assert.equal(data.pets.length, 1);\n                return done();\n              });\n            });\n\n          });\n\n          it('the custom `parseBlueprintOptions` should be applied to a user-defined (i.e. not shadow) route', function(done) {\n\n            sailsApp.models.pet.createEach([{name: 'lolly'}, {name: 'dolly'}]).meta({fetch: true}).exec(function(err, pets) {\n              if (err) {return done(err);}\n              sailsApp.models.user.create({name: 'bruce', pets: _.pluck(pets, sailsApp.models.pet.primaryKey)}).exec(function(err, bruce) {\n                if (err) {return done(err);}\n                sailsApp.request('get /yolo/' + bruce[sailsApp.models.user.primaryKey], function (err, resp, data) {\n                  if (err) {return done (err);}\n                  assert.equal(data.name, 'bruce');\n                  assert(data.pets, 'Record should have `pets` key, but none was found.  Full record: ' + util.inspect(data, {depth: null}));\n                  assert.equal(data.pets.length, 1);\n                  return done();\n                });\n              });\n            });\n\n\n          });\n\n        });\n\n        describe('with a custom parseBlueprintOptions that disables auto-population (tests #4138)', function() {\n\n          before(function() {\n            extraSailsConfig = {\n              hooks: {\n                pubsub: undefined\n              },\n              blueprints: {\n                parseBlueprintOptions: function(req) {\n                  var queryOptions = req._sails.hooks.blueprints.parseBlueprintOptions(req);\n\n                  if (!req.param('populate', false) && !queryOptions.alias) {\n                    queryOptions.populates = {};\n                  }\n                  return queryOptions;\n                }\n              },\n              orm: {\n                moduleDefinitions: {\n                  models: {\n                    user: {\n                      attributes: {\n                        name: 'string',\n                        pets: {\n                          collection: 'pet',\n                          via: 'owner'\n                        }\n                      }\n                    },\n                    pet: {\n                      attributes: {\n                        name: 'string',\n                        owner: {\n                          model: 'user'\n                        }\n                      }\n                    }\n                  },\n                }\n              }\n            };\n          });\n\n          after(function() {\n            extraSailsConfig = {};\n          });\n\n          it('the delete blueprint should not cause any errors', function(done) {\n            sailsApp.models.pet.createEach([{name: 'alice'}, {name: 'rex'}]).meta({fetch: true}).exec(function(err, pets) {\n              if (err) {return done(err);}\n              sailsApp.models.user.create({name: 'bill', pets: _.pluck(pets, sailsApp.models.pet.primaryKey)}).exec(function(err, bill) {\n                if (err) {return done(err);}\n                sailsApp.request('delete /user/' + bill[sailsApp.models.user.primaryKey], function (err, resp, data) {\n                  if (err) {return done (err);}\n                  return done();\n                });\n              });\n            });\n\n          });\n\n        });\n\n        describe('with a custom parseBlueprintOptions for a specific route', function() {\n\n          before(function() {\n            extraSailsConfig = {\n              routes: {\n                'GET /user/:id': {\n                  action: 'user/findOne',\n                  parseBlueprintOptions: function(req) {\n                    var queryOptions = req._sails.hooks.blueprints.parseBlueprintOptions(req);\n                    queryOptions.populates.pets.limit = 1;\n                    return queryOptions;\n                  }\n                }\n              },\n              orm: {\n                moduleDefinitions: {\n                  models: {\n                    user: {\n                      attributes: {\n                        name: 'string',\n                        pets: {\n                          collection: 'pet',\n                          via: 'owner'\n                        }\n                      }\n                    },\n                    pet: {\n                      attributes: {\n                        name: 'string',\n                        owner: {\n                          model: 'user'\n                        }\n                      }\n                    }\n                  },\n                }\n              }\n            };\n          });\n\n          after(function() {\n            extraSailsConfig = {};\n          });\n\n          it('the custom `parseBlueprintOptions` should be applied to the specific route', function(done) {\n\n            sailsApp.models.pet.createEach([{name: 'alice'}, {name: 'rex'}]).meta({fetch: true}).exec(function(err, pets) {\n              if (err) {return done(err);}\n              sailsApp.models.user.create({name: 'bill', pets: _.pluck(pets, sailsApp.models.pet.primaryKey)}).exec(function(err, bill) {\n                if (err) {return done(err);}\n                sailsApp.request('get /user/' + bill[sailsApp.models.user.primaryKey], function (err, resp, data) {\n                  if (err) {return done (err);}\n                  assert.equal(data.name, 'bill');\n                  assert(data.pets, 'Record should have `pets` key, but none was found.  Full record: ' + util.inspect(data, {depth: null}));\n                  assert.equal(data.pets.length, 1);\n                  return done();\n                });\n              });\n            });\n\n          });\n\n          it('the custom `parseBlueprintOptions` should NOT be applied to a different route blueprint', function(done) {\n\n            sailsApp.models.pet.createEach([{name: 'june'}, {name: 'jane'}]).meta({fetch: true}).exec(function(err, pets) {\n              if (err) {return done(err);}\n              sailsApp.request('post /user', {name: 'bob', pets: _.pluck(pets, sailsApp.models.pet.primaryKey)}, function (err, resp, data) {\n                if (err) {return done (err);}\n                assert.equal(data.name, 'bob');\n                assert(data.pets, 'Record should have `pets` key, but none was found.  Full record: ' + util.inspect(data, {depth: null}));\n                assert.equal(data.pets.length, 2);\n                return done();\n              });\n            });\n\n          });\n\n\n        });\n\n      });\n\n      describe('using query string params :: ', function() {\n\n        describe('with the `find` blueprint :: ', function() {\n\n          describe('filtering :: ', function() {\n\n            before(function() {\n              extraSailsConfig = {\n                orm: {\n                  moduleDefinitions: {\n                    models: {\n                      user: {\n                        attributes: {\n                          name: 'string'\n                        }\n                      }\n                    }\n                  }\n                }\n              };\n            });\n\n            after(function() {\n              extraSailsConfig = {};\n            });\n\n            it('a get request to /:model?name=scott should respond with the correctly filtered instances', function(done) {\n              sailsApp.models.user.createEach([{name: 'scott'}, {name: 'mike'}]).exec(function(err) {\n                if (err) {return done(err);}\n                sailsApp.request('get /user?name=scott', function (err, resp, data) {\n                  assert(!err, err);\n                  assert.equal(data.length, 1);\n                  assert.equal(data[0].name, 'scott');\n                  done();\n                });\n              });\n            });\n\n            it('a get request to /:model?where={...} should respond with the correctly filtered instances', function(done) {\n              sailsApp.models.user.createEach([{name: 'scott'}, {name: 'mike'}, {name: 'rachael'}, {name: 'cody'}, {name: 'irl'}]).exec(function(err) {        if (err) {return done(err);}\n                sailsApp.request('get /user?where={\"name\": {\">\": \"irl\"}}', function (err, resp, data) {\n                  assert(!err, err);\n                  assert.equal(data.length, 3);\n                  var names = _.pluck(data, 'name');\n                  assert(_.contains(names, 'scott'));\n                  assert(_.contains(names, 'mike'));\n                  assert(_.contains(names, 'rachael'));\n                  done();\n                });\n              });\n            });\n\n\n          });\n\n          describe('using sort, skip and limit in the query string :: ', function() {\n\n            before(function() {\n              extraSailsConfig = {\n                orm: {\n                  moduleDefinitions: {\n                    models: {\n                      user: {\n                        attributes: {\n                          name: 'string'\n                        }\n                      }\n                    }\n                  }\n                }\n              };\n            });\n\n            after(function() {\n              extraSailsConfig = {};\n            });\n\n            it('a get request to /:model?sort=name%20asc&limit=2&skip=1 should respond with the correctly filtered instances', function(done) {\n              sailsApp.models.user.createEach([{name: 'scott'}, {name: 'mike'}, {name: 'rachael'}, {name: 'cody'}, {name: 'irl'}]).exec(function(err) {\n                if (err) {return done(err);}\n                sailsApp.request('get /user?sort=name%20asc&limit=2&skip=1', function (err, resp, data) {\n                  assert(!err, err);\n                  assert.equal(data.length, 2);\n                  assert.equal(data[0].name, 'irl');\n                  assert.equal(data[1].name, 'mike');\n                  done();\n                });\n              });\n            });\n\n            it('a get request to /:model?sort=name%20desc&limit=2&skip=1 should respond with the correctly filtered instances', function(done) {\n              sailsApp.models.user.createEach([{name: 'scott'}, {name: 'mike'}, {name: 'rachael'}, {name: 'cody'}, {name: 'irl'}]).exec(function(err) {\n                if (err) {return done(err);}\n                sailsApp.request('get /user?sort=name%20desc&limit=2&skip=1', function (err, resp, data) {\n                  assert(!err, err);\n                  assert.equal(data.length, 2);\n                  assert.equal(data[0].name, 'rachael');\n                  assert.equal(data[1].name, 'mike');\n                  done();\n                });\n              });\n            });\n\n            it('a get request to /:model?sort={\"name\":-1}&limit=2&skip=1 should respond with the correctly filtered instances', function(done) {\n              sailsApp.models.user.createEach([{name: 'scott'}, {name: 'mike'}, {name: 'rachael'}, {name: 'cody'}, {name: 'irl'}]).exec(function(err) {\n                if (err) {return done(err);}\n                sailsApp.request('get /user?sort={\"name\":-1}&limit=2&skip=1', function (err, resp, data) {\n                  assert(!err, err);\n                  assert.equal(data.length, 2);\n                  assert.equal(data[0].name, 'rachael');\n                  assert.equal(data[1].name, 'mike');\n                  done();\n                });\n              });\n            });\n\n          });\n\n          describe('using `select` and `omit` in the query string', function() {\n\n            before(function() {\n              extraSailsConfig = {\n                orm: {\n                  moduleDefinitions: {\n                    models: {\n                      user: {\n                        attributes: {\n                          name: 'string',\n                          favoriteColor: 'string',\n                          luckyNumber: 'number'\n                        }\n                      }\n                    }\n                  }\n                }\n              };\n            });\n\n            after(function() {\n              extraSailsConfig = {};\n            });\n\n            it('a get request to /:model?select=name, luckyNumber should respond with the correctly projected instances', function(done) {\n              sailsApp.models.user.createEach([\n                {name: 'scott', favoriteColor: 'grey', luckyNumber: 3},\n                {name: 'mike', favoriteColor: 'blue', luckyNumber: 25},\n                {name: 'rachael', favoriteColor: 'red', luckyNumber: 12},\n                {name: 'cody', favoriteColor: 'blue', luckyNumber: 9},\n                {name: 'irl', favoriteColor: 'black', luckyNumber: 66}\n              ]).exec(function(err) {\n                if (err) {return done(err);}\n                sailsApp.request('get /user?select=name, luckyNumber', function (err, resp, data) {\n                  assert(!err, err);\n                  assert.equal(data.length, 5);\n                  _.each(data, function(row) {\n                    assert(row.name);\n                    assert(row.luckyNumber);\n                    assert(!row.favoriteColor, 'Got favoriteColor for `' + row.name + '`, even though it wasn\\'t selected!');\n                  });\n                  done();\n                });\n              });\n            });\n\n            it('a get request to /:model?omit=favoriteColor, luckyNumber should respond with the correctly projected instances', function(done) {\n              sailsApp.models.user.createEach([\n                {name: 'scott', favoriteColor: 'grey', luckyNumber: 3},\n                {name: 'mike', favoriteColor: 'blue', luckyNumber: 25},\n                {name: 'rachael', favoriteColor: 'red', luckyNumber: 12},\n                {name: 'cody', favoriteColor: 'blue', luckyNumber: 9},\n                {name: 'irl', favoriteColor: 'black', luckyNumber: 66}\n              ]).exec(function(err) {\n                if (err) {return done(err);}\n                sailsApp.request('get /user?omit=favoriteColor, luckyNumber', function (err, resp, data) {\n                  assert(!err, err);\n                  assert.equal(data.length, 5);\n                  _.each(data, function(row) {\n                    assert(row.name);\n                    assert(!row.luckyNumber, 'Got luckyNumber for `' + row.name + '`, even though it was omitted!');\n                    assert(!row.favoriteColor, 'Got favoriteColor for `' + row.name + '`, even though it was omitted!');\n                  });\n                  done();\n                });\n              });\n            });\n\n          });\n\n        });\n\n        describe('with the `populate` blueprint :: ', function() {\n\n          describe('filtering :: ', function() {\n\n            before(function() {\n              extraSailsConfig = {\n                orm: {\n                  moduleDefinitions: {\n                    models: {\n                      user: {\n                        attributes: {\n                          name: 'string',\n                          pets: {\n                            collection: 'pet',\n                            via: 'owner'\n                          }\n                        }\n                      },\n                      pet: {\n                        attributes: {\n                          name: 'string',\n                          owner: {\n                            model: 'user'\n                          }\n                        }\n                      }\n                    }\n                  }\n                }\n              };\n            });\n\n            after(function() {\n              extraSailsConfig = {};\n            });\n\n            it('a get request to /:model/:id/:association?name=alice should respond with the correctly filtered association records', function(done) {\n              sailsApp.models.user.create({name: 'scott'}).meta({fetch: true}).exec(function(err, scott) {\n                sailsApp.models.pet.createEach([{name: 'alice', owner: scott.id}, {name: 'mojo', owner: scott.id}]).exec(function(err) {\n                  if (err) {return done(err);}\n                  sailsApp.request('get /user/'+scott.id+'/pets?name=alice', function (err, resp, data) {\n                    assert(!err, err);\n                    assert.equal(data.length, 1);\n                    assert.equal(data[0].name, 'alice');\n                    done();\n                  });\n                });\n              });\n            });\n\n            it('a get request to /:model/:id/:association?where={...} should respond with the correctly filtered association records', function(done) {\n              sailsApp.models.user.create({name: 'scott'}).meta({fetch: true}).exec(function(err, scott) {\n                sailsApp.models.pet.createEach([{name: 'alice', owner: scott.id}, {name: 'mojo', owner: scott.id}, {name: 'bert', owner: scott.id}]).exec(function(err) {\n                  if (err) {return done(err);}\n                  sailsApp.request('get /user/'+scott.id+'/pets?where={\"name\":{\">\":\"alice\"}}', function (err, resp, data) {\n                    assert(!err, err);\n                    assert.equal(data.length, 2);\n                    var names = _.pluck(data, 'name');\n                    assert(_.contains(names, 'bert'));\n                    assert(_.contains(names, 'mojo'));\n                    done();\n                  });\n                });\n              });\n            });\n\n          });\n\n          describe('using sort, skip and limit in the query string :: ', function() {\n\n            before(function() {\n              extraSailsConfig = {\n                orm: {\n                  moduleDefinitions: {\n                    models: {\n                      user: {\n                        attributes: {\n                          name: 'string',\n                          pets: {\n                            collection: 'pet',\n                            via: 'owner'\n                          }\n                        }\n                      },\n                      pet: {\n                        attributes: {\n                          name: 'string',\n                          owner: {\n                            model: 'user'\n                          }\n                        }\n                      }\n                    }\n                  }\n                }\n              };\n            });\n\n            after(function() {\n              extraSailsConfig = {};\n            });\n\n            it('a get request to /:model/:id/:association?sort=name%20asc&limit=2&skip=1 should respond with the correctly filtered association records', function(done) {\n              sailsApp.models.user.create({name: 'scott'}).meta({fetch: true}).exec(function(err, scott) {\n                sailsApp.models.pet.createEach([{name: 'alice', owner: scott.id}, {name: 'mojo', owner: scott.id}, {name: 'bert', owner: scott.id}, {name: 'bandit', owner: scott.id}]).exec(function(err) {\n                  if (err) {return done(err);}\n                  sailsApp.request('get /user/'+scott.id+'/pets?sort=name%20asc&limit=2&skip=1', function (err, resp, data) {\n                    assert(!err, err);\n                    assert.equal(data.length, 2);\n                    var names = _.pluck(data, 'name');\n                    assert(_.contains(names, 'bandit'));\n                    assert(_.contains(names, 'bert'));\n                    done();\n                  });\n                });\n              });\n            });\n\n            it('a get request to /:model/:id/:association?sort=name%desc&limit=1&skip=1 should respond with the correctly filtered association records', function(done) {\n              sailsApp.models.user.create({name: 'scott'}).meta({fetch: true}).exec(function(err, scott) {\n                sailsApp.models.pet.createEach([{name: 'alice', owner: scott.id}, {name: 'mojo', owner: scott.id}, {name: 'bert', owner: scott.id}, {name: 'bandit', owner: scott.id}]).exec(function(err) {\n                  if (err) {return done(err);}\n                  sailsApp.request('get /user/'+scott.id+'/pets?sort=name%20desc&limit=1&skip=1', function (err, resp, data) {\n                    assert(!err, err);\n                    assert.equal(data.length, 1);\n                    var names = _.pluck(data, 'name');\n                    assert(_.contains(names, 'bert'));\n                    done();\n                  });\n                });\n              });\n            });\n\n            it('a get request to /:model/:id/:association?sort={\"name\":-1}&limit=2&skip=1 should respond with the correctly filtered association records', function(done) {\n              sailsApp.models.user.create({name: 'scott'}).meta({fetch: true}).exec(function(err, scott) {\n                sailsApp.models.pet.createEach([{name: 'alice', owner: scott.id}, {name: 'mojo', owner: scott.id}, {name: 'bert', owner: scott.id}, {name: 'bandit', owner: scott.id}]).exec(function(err) {\n                  if (err) {return done(err);}\n                  sailsApp.request('get /user/'+scott.id+'/pets?sort={\"name\":-1}&limit=1&skip=1', function (err, resp, data) {\n                    assert(!err, err);\n                    assert.equal(data.length, 1);\n                    var names = _.pluck(data, 'name');\n                    assert(_.contains(names, 'bert'));\n                    done();\n                  });\n                });\n              });\n            });\n\n          });\n\n          describe('using `select` and `omit` in the query string', function() {\n\n            before(function() {\n              extraSailsConfig = {\n                orm: {\n                  moduleDefinitions: {\n                    models: {\n                      user: {\n                        attributes: {\n                          name: 'string',\n                          pets: {\n                            collection: 'pet',\n                            via: 'owner'\n                          }\n                        }\n                      },\n                      pet: {\n                        attributes: {\n                          name: 'string',\n                          animal: 'string',\n                          age: 'number',\n                          owner: {\n                            model: 'user'\n                          }\n                        }\n                      }\n                    }\n                  }\n                }\n              };\n            });\n\n            after(function() {\n              extraSailsConfig = {};\n            });\n\n            it('a get request to /:model/:id/:association?select=name, luckyNumber should respond with the correctly projected association records', function(done) {\n              sailsApp.models.user.create({name: 'scott'}).meta({fetch: true}).exec(function(err, scott) {\n                sailsApp.models.pet.createEach([\n                  {name: 'alice', animal: 'cat', age: 3, owner: scott.id},\n                  {name: 'bandit', animal: 'dog', age: 25, owner: scott.id},\n                  {name: 'bert', animal: 'cat', age: 12, owner: scott.id},\n                  {name: 'mojo', animal: 'cat', age: 9, owner: scott.id},\n                  {name: 'rex', animal: 'cheetah', age: 66, owner: scott.id}\n                ]).exec(function(err) {\n                  if (err) {return done(err);}\n                  sailsApp.request('get /user/' + scott.id + '/pets?select=name, age', function (err, resp, data) {\n                    assert(!err, err);\n                    assert.equal(data.length, 5);\n                    _.each(data, function(row) {\n                      assert(row.name);\n                      assert(row.age);\n                      assert(!row.animal, 'Got animal for `' + row.name + '`, even though it wasn\\'t selected!');\n                    });\n                    done();\n                  });\n                });\n              });\n            });\n\n            it('a get request to /:model/:id/:association?omit=age, animal should respond with the correctly projected association records', function(done) {\n              sailsApp.models.user.create({name: 'scott'}).meta({fetch: true}).exec(function(err, scott) {\n                sailsApp.models.pet.createEach([\n                  {name: 'alice', animal: 'cat', age: 3, owner: scott.id},\n                  {name: 'bandit', animal: 'dog', age: 25, owner: scott.id},\n                  {name: 'bert', animal: 'cat', age: 12, owner: scott.id},\n                  {name: 'mojo', animal: 'cat', age: 9, owner: scott.id},\n                  {name: 'rex', animal: 'cheetah', age: 66, owner: scott.id}\n                ]).exec(function(err) {\n                  if (err) {return done(err);}\n                  sailsApp.request('get /user/' + scott.id + '/pets?omit=age, animal', function (err, resp, data) {\n                    assert(!err, err);\n                    assert.equal(data.length, 5);\n                    _.each(data, function(row) {\n                      assert(row.name);\n                      assert(!row.age, 'Got age for `' + row.name + '`, even though it was omitted!');\n                      assert(!row.animal, 'Got animal for `' + row.name + '`, even though it was omitted!');\n                    });\n                    done();\n                  });\n                });\n              });\n            });\n\n          });\n\n        });\n\n      });\n\n      describe('after reloading actions :: ', function() {\n\n        before(function() {\n          extraSailsConfig = {\n            orm: {\n              moduleDefinitions: {\n                models: {\n                  user: {\n                    attributes: {\n                      name: 'string'\n                    }\n                  }\n                }\n              }\n            }\n          };\n        });\n\n        after(function() {\n          extraSailsConfig = {};\n        });\n\n        it('should still respond to RESTful blueprint requests correctly :: ', function(done) {\n          sailsApp.models.user.createEach([{name: 'scott'}, {name: 'mike'}]).exec(function(err) {\n            if (err) {return done(err);}\n            sailsApp.reloadActions(function(err) {\n              if (err) {return done(err);}\n              sailsApp.request('get /user', function (err, resp, data) {\n                assert(!err, err);\n                assert.equal(data.length, 2);\n                done();\n              });\n            });\n          });\n        });\n\n      });\n\n      describe('with pluralize turned on :: ', function() {\n\n        before(function() {\n          extraSailsConfig = {\n            blueprints: {\n              pluralize: true\n            },\n            orm: {\n              moduleDefinitions: {\n                models: {\n                  user: {},\n                  quiz: {}\n                }\n              }\n            }\n          };\n        });\n\n        after(function() {\n          extraSailsConfig = {};\n        });\n\n        it('should bind blueprint actions to plural controller names', function(done) {\n          sailsApp.models.user.create({}).exec(function(err) {\n            if (err) {return done (err);}\n            sailsApp.request('get /users', function (err, resp, data) {\n              assert(!err, err);\n              assert.equal(data.length, 1);\n              assert.equal(data[0].id, 1);\n              done();\n            });\n          });\n        });\n\n        it('should bind blueprint actions to plural controller names (quiz => quizzes)', function(done) {\n          sailsApp.models.quiz.create({}).exec(function(err) {\n            if (err) {return done (err);}\n            sailsApp.request('get /quizzes', function (err, resp, data) {\n              assert(!err, err);\n              assert.equal(data.length, 1);\n              assert.equal(data[0].id, 1);\n              done();\n            });\n          });\n        });\n\n        it('should not bind blueprint actions to singular controller names', function(done) {\n          sailsApp.models.user.create({}).exec(function(err) {\n            if (err) {return done (err);}\n            sailsApp.request('get /user', function (err, resp, data) {\n              assert(err);\n              assert.equal(err.status, 404);\n              done();\n            });\n          });\n        });\n\n      });\n\n      describe('with `prefix` option set to \\'/api\\' :: ', function() {\n\n        before(function() {\n          extraSailsConfig = {\n            blueprints: {\n              prefix: '/api'\n            },\n            orm: {\n              moduleDefinitions: {\n                models: {\n                  user: {\n                    attributes: {\n                      name: 'string'\n                    }\n                  }\n                }\n              }\n            }\n          };\n        });\n\n        after(function() {\n          extraSailsConfig = {};\n        });\n\n        describe('a get request to /api/:model', function() {\n\n          it('should return JSON for all of the instances of the test model', function(done) {\n            sailsApp.models.user.create({name: 'joy'}).exec(function(err) {\n              if (err) {return done (err);}\n              sailsApp.request('get /api/user', function (err, resp, data) {\n                assert(!err, err);\n                assert.equal(data.length, 1);\n                assert.equal(data[0].name, 'joy');\n                assert.equal(data[0].id, 1);\n                done();\n              });\n            });\n          });\n        });\n\n        describe('a get request to /:model', function() {\n\n          it('should return a 404', function(done) {\n            sailsApp.request('get /user', function (err, resp, data) {\n              assert(err);\n              assert.equal(err.status, 404);\n              done();\n            });\n          });\n        });\n\n      });\n\n      describe('with `restPrefix` option set to \\'/v1\\' :: ', function() {\n\n        before(function() {\n          extraSailsConfig = {\n            blueprints: {\n              restPrefix: '/v1'\n            },\n            orm: {\n              moduleDefinitions: {\n                models: {\n                  user: {\n                    attributes: {\n                      name: 'string'\n                    }\n                  }\n                }\n              }\n            }\n          };\n        });\n\n        after(function() {\n          extraSailsConfig = {};\n        });\n\n        describe('a get request to /v1/:model', function() {\n\n          it('should return JSON for all of the instances of the test model', function(done) {\n            sailsApp.models.user.create({name: 'wanda'}).exec(function(err) {\n              if (err) {return done (err);}\n              sailsApp.request('get /v1/user', function (err, resp, data) {\n                assert(!err, err);\n                assert.equal(data.length, 1);\n                assert.equal(data[0].name, 'wanda');\n                assert.equal(data[0].id, 1);\n                done();\n              });\n            });\n          });\n        });\n\n        describe('a get request to /:model', function() {\n\n          it('should return a 404', function(done) {\n            sailsApp.request('get /user', function (err, resp, data) {\n              assert(err);\n              assert.equal(err.status, 404);\n              done();\n            });\n          });\n        });\n\n      });\n\n      describe('with `prefix` option set to \\'api\\' and `restPrefix` option set to \\'/v1\\' :: ', function() {\n\n        before(function() {\n          extraSailsConfig = {\n            blueprints: {\n              prefix: '/api',\n              restPrefix: '/v1'\n            },\n            orm: {\n              moduleDefinitions: {\n                models: {\n                  user: {\n                    attributes: {\n                      name: 'string'\n                    }\n                  }\n                }\n              }\n            }\n          };\n        });\n\n        after(function() {\n          extraSailsConfig = {};\n        });\n\n        describe('a get request to /api/v1/:model', function() {\n\n          it('should return JSON for all of the instances of the test model', function(done) {\n            sailsApp.models.user.create({name: 'ron'}).exec(function(err) {\n              if (err) {return done (err);}\n              sailsApp.request('get /api/v1/user', function (err, resp, data) {\n                assert(!err, err);\n                assert.equal(data.length, 1);\n                assert.equal(data[0].name, 'ron');\n                assert.equal(data[0].id, 1);\n                done();\n              });\n            });\n          });\n        });\n\n        describe('a get request to /:model', function() {\n\n          it('should return a 404', function(done) {\n            sailsApp.request('get /user', function (err, resp, data) {\n              assert(err);\n              assert.equal(err.status, 404);\n              done();\n            });\n          });\n        });\n\n        describe('a get request to /api/:model', function() {\n\n          it('should return a 404', function(done) {\n            sailsApp.request('get /api/user', function (err, resp, data) {\n              assert(err);\n              assert.equal(err.status, 404);\n              done();\n            });\n          });\n        });\n\n        describe('a get request to /v1/:model', function() {\n\n          it('should return a 404', function(done) {\n            sailsApp.request('get /v1/user', function (err, resp, data) {\n              assert(err);\n              assert.equal(err.status, 404);\n              done();\n            });\n          });\n        });\n\n      });\n\n      describe('overriding blueprints :: ', function() {\n\n        before(function() {\n          extraSailsConfig = {\n            orm: {\n              moduleDefinitions: {\n                models: {\n                  user: {},\n                },\n              }\n            },\n            controllers: {\n              moduleDefinitions: {\n                'user/find': function(req, res) {\n                  return res.send('find dem users!');\n                }\n              }\n            }\n          };\n        });\n\n        after(function() {\n          extraSailsConfig = {};\n        });\n\n        it('if a `:model.find` action is explicitly added, it should be used in response to `GET /:model`', function(done) {\n          sailsApp.models.user.create({name: 'al'}).exec(function(err) {\n            if (err) {return done (err);}\n            sailsApp.request('get /user', function (err, resp, data) {\n              assert(!err, err);\n              assert.equal(data, 'find dem users!');\n              done();\n            });\n          });\n        });\n\n\n      });\n\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/integration/hook.blueprints.shortcut.routes.test.js",
    "content": "/**\n * Test dependencies\n */\n\nvar util = require('util');\nvar assert = require('assert');\nvar tmp = require('tmp');\nvar _ = require('@sailshq/lodash');\nvar Filesystem = require('machinepack-fs');\n\nvar appHelper = require('./helpers/appHelper');\n\nvar Sails = require('../../lib').constructor;\n\n/**\n * Errors\n */\nvar Err = {\n  badResponse: function(response) {\n    return 'Wrong server response!  Response :::\\n' + util.inspect(response.body);\n  }\n};\n\n\ndescribe('blueprints :: ', function() {\n\n  var curDir, tmpDir, sailsApp;\n\n  var extraSailsConfig = {};\n\n  describe('shortcut routes :: ', function() {\n\n    describe('when turned off globaly :: ', function() {\n\n      before(function(done) {\n        // Cache the current working directory.\n        curDir = process.cwd();\n        // Create a temp directory.\n        tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n        // Switch to the temp directory.\n        process.chdir(tmpDir.name);\n        appHelper.linkDeps(tmpDir.name);\n        (new Sails()).load({\n          hooks: {\n            grunt: false, views: false, policies: false, pubsub: false, i18n: false\n          },\n          orm: {\n            moduleDefinitions: {\n              models: {\n                user: {\n                  attributes: {\n                    name: 'string',\n                    pets: {\n                      collection: 'pet',\n                      via: 'owner'\n                    }\n                  }\n                },\n                pet: {\n                  attributes: {\n                    name: 'string',\n                    owner: {\n                      model: 'user'\n                    }\n                  }\n                }\n              },\n            }\n          },\n          models: {\n            migrate: 'drop',\n            schema: true,\n            attributes: {\n              createdAt: { type: 'number', autoCreatedAt: true, },\n              updatedAt: { type: 'number', autoUpdatedAt: true, },\n              // id: { type: 'string', unique: true, columnName: '_id'},\n              id: { type: 'number', autoIncrement: true}\n            }\n          },\n          blueprints: {\n            rest: false,\n            shortcuts: false,\n            actions: false\n          },\n          log: {level: 'error'}\n        }, function(err, _sails) {\n          if (err) { return done(err); }\n          sailsApp = _sails;\n          return done();\n        });\n      });\n\n      after(function(done) {\n        extraSailsConfig = {};\n        sailsApp.lower(function() {\n          process.chdir(curDir);\n          return done();\n        });\n      });\n\n      it('a get request to /:model/find should return a 404', function(done) {\n        sailsApp.models.user.create({name: 'al'}).exec(function(err) {\n          if (err) {return done (err);}\n          sailsApp.request('get /user/find', function (err, resp, data) {\n            assert(err);\n            assert.equal(err.status, 404);\n            done();\n          });\n        });\n      });\n\n    });\n\n    describe('when turned off for a specific controller :: ', function() {\n\n      before(function(done) {\n        // Cache the current working directory.\n        curDir = process.cwd();\n        // Create a temp directory.\n        tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n        // Switch to the temp directory.\n        process.chdir(tmpDir.name);\n        appHelper.linkDeps(tmpDir.name);\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'api/controllers/UserController.js',\n          string: 'module.exports = { _config: { shortcuts: false } }'\n        }).execSync();\n\n        (new Sails()).load({\n          hooks: {\n            grunt: false, views: false, policies: false, pubsub: false, i18n: false\n          },\n          orm: {\n            moduleDefinitions: {\n              models: {\n                user: {\n                  attributes: {\n                    name: 'string',\n                    pets: {\n                      collection: 'pet',\n                      via: 'owner'\n                    }\n                  }\n                },\n                pet: {\n                  attributes: {\n                    name: 'string',\n                    owner: {\n                      model: 'user'\n                    }\n                  }\n                }\n              },\n            }\n          },\n          models: {\n            migrate: 'drop',\n            schema: true,\n            attributes: {\n              createdAt: { type: 'number', autoCreatedAt: true, },\n              updatedAt: { type: 'number', autoUpdatedAt: true, },\n              // id: { type: 'string', unique: true, columnName: '_id'},\n              id: { type: 'number', autoIncrement: true}\n            }\n          },\n          blueprints: {\n            rest: false,\n            actions: false\n          },\n          log: {level: 'error'}\n        }, function(err, _sails) {\n          if (err) { return done(err); }\n          sailsApp = _sails;\n          return done();\n        });\n      });\n\n      after(function(done) {\n        extraSailsConfig = {};\n        sailsApp.lower(function() {\n          process.chdir(curDir);\n          return done();\n        });\n      });\n\n      it('a get request to the /:model/find with REST disabled should return a 404', function(done) {\n        sailsApp.models.user.create({name: 'al'}).exec(function(err) {\n          if (err) {return done (err);}\n          sailsApp.request('get /user/find', function (err, resp, data) {\n            assert(err);\n            assert.equal(err.status, 404);\n            done();\n          });\n        });\n      });\n\n      it('a get request to the /:model/find with REST enabled should return JSON for all of the instances of the test model', function(done) {\n        sailsApp.models.pet.create({name: 'rex'}).exec(function(err) {\n          if (err) {return done (err);}\n          sailsApp.request('get /pet/find', function (err, resp, data) {\n            assert(!err, err);\n            assert.equal(data.length, 1);\n            assert.equal(data[0].name, 'rex');\n            assert.equal(data[0].id, 1);\n            done();\n          });\n        });\n      });\n\n    });\n\n    describe('when turned on :: ', function() {\n\n      beforeEach(function(done) {\n        // Cache the current working directory.\n        curDir = process.cwd();\n        // Create a temp directory.\n        tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n        // Switch to the temp directory.\n        process.chdir(tmpDir.name);\n        appHelper.linkDeps(tmpDir.name);\n        (new Sails()).load(_.merge({\n          hooks: {\n            grunt: false, views: false, policies: false, pubsub: false, i18n: false\n          },\n          orm: {\n            moduleDefinitions: {\n              models: { 'user': {} }\n            }\n          },\n          models: {\n            migrate: 'drop',\n            schema: true,\n            attributes: {\n              createdAt: { type: 'number', autoCreatedAt: true, },\n              updatedAt: { type: 'number', autoUpdatedAt: true, },\n              // id: { type: 'string', unique: true, columnName: '_id'},\n              id: { type: 'number', autoIncrement: true}\n            }\n          },\n          blueprints: {\n            rest: false,\n            actions: false\n          },\n          log: {level: 'error'}\n        }, extraSailsConfig), function(err, _sails) {\n          if (err) { return done(err); }\n          sailsApp = _sails;\n          return done();\n        });\n      });\n\n      afterEach(function(done) {\n        sailsApp.lower(function() {\n          process.chdir(curDir);\n          return done();\n        });\n      });\n\n      describe('basic usage :: ', function() {\n\n        before(function() {\n          extraSailsConfig = {\n            orm: {\n              moduleDefinitions: {\n                models: {\n                  user: {\n                    attributes: {\n                      name: 'string',\n                      pets: {\n                        collection: 'pet',\n                        via: 'owner'\n                      }\n                    }\n                  },\n                  pet: {\n                    attributes: {\n                      name: 'string',\n                      owner: {\n                        model: 'user'\n                      }\n                    }\n                  }\n                },\n              }\n            }\n          };\n        });\n\n        after(function() {\n          extraSailsConfig = {};\n        });\n\n        describe('a get request to /:model/find', function() {\n\n          it('should return JSON for all of the instances of the test model', function(done) {\n            sailsApp.models.user.create({name: 'al'}).exec(function(err) {\n              if (err) {return done (err);}\n              sailsApp.request('get /user/find', function (err, resp, data) {\n                assert(!err, err);\n                assert.equal(data.length, 1);\n                assert.equal(data[0].name, 'al');\n                assert.equal(data[0].id, 1);\n                done();\n              });\n            });\n          });\n        });\n\n\n        describe('a get request to /:model/find/:id', function() {\n\n          it('should return JSON for the requested instance of the test model', function(done) {\n            sailsApp.models.user.create({name: 'ron'}).exec(function(err) {\n              if (err) {return done (err);}\n              sailsApp.request('get /user/find/1', function (err, resp, data) {\n                assert(!err, err);\n                assert.equal(data.name, 'ron');\n                assert.equal(data.id, 1);\n                done();\n              });\n            });\n          });\n        });\n\n        describe('a get request to /:model/update/:id?name=foo', function() {\n\n          it('should return JSON for an updated instance of the test model', function(done) {\n            sailsApp.models.user.create({name: 'dave'}).exec(function(err) {\n              if (err) {return done (err);}\n              sailsApp.request('get /user/update/1?name=bob', function (err, resp, data) {\n                if (err) {return done (err);}\n                assert.equal(data.name, 'bob');\n                assert.equal(data.id, 1);\n                sailsApp.models.user.findOne({id: 1}).exec(function(err, user) {\n                  if (err) {return done (err);}\n                  assert(user);\n                  assert.equal(user.name, 'bob');\n                  return done();\n                });\n              });\n            });\n          });\n        });\n\n        describe('a get request to /:model/create?name=foo', function() {\n\n          it('should return JSON for a newly created instance of the test model', function(done) {\n            sailsApp.request('get /user/create?name=joe', function (err, resp, data) {\n              assert(!err, err);\n              assert.equal(data.name, 'joe');\n              assert.equal(data.id, 1);\n              sailsApp.models.user.findOne({id: 1}).exec(function(err, user) {\n                if (err) {return done (err);}\n                assert(user);\n                assert.equal(user.name, 'joe');\n                return done();\n              });\n            });\n          });\n        });\n\n        describe('a get request to /:model/create?name=foo&pets=[1]', function() {\n\n          it('should return JSON for the new record including the associated collection', function(done) {\n            sailsApp.models.pet.create({name: 'spot'}).meta({fetch: true}).exec(function(err, spot) {\n              sailsApp.request('get /user/create?name=will&pets=[1]', function (err, resp, data) {\n                if (err) {return done (err);}\n                assert.equal(data.name, 'will');\n                assert.equal(data.id, 1);\n                assert.equal(data.pets.length, 1);\n                assert.equal(data.pets[0].name, 'spot');\n                return done();\n              });\n            });\n          });\n        });\n\n\n        describe('a get request to /:model/destroy/1', function() {\n\n          it('should return JSON for the deleted instance of the test model', function(done) {\n            sailsApp.models.user.create({name: 'bubba'}).exec(function(err) {\n              if (err) {return done (err);}\n              sailsApp.request('get /user/destroy/1', function (err, resp, data) {\n                assert(!err, err);\n                assert.equal(data.name, 'bubba');\n                assert.equal(data.id, 1);\n                sailsApp.models.user.findOne({id: 1}).exec(function(err, user) {\n                  if (err) {return done (err);}\n                  assert(!user);\n                  return done();\n                });\n              });\n            });\n          });\n        });\n\n        describe('associations :: ', function() {\n\n          describe('a get request to /:model/:parentid/:association', function() {\n\n            it('should return JSON for the specified collection of the test model', function(done) {\n              sailsApp.models.pet.create({name: 'spot'}).meta({fetch: true}).exec(function(err, spot) {\n                sailsApp.models.user.create({name: 'will', pets: [spot.id]}).meta({fetch: true}).exec(function(err) {\n                  if (err) {return done (err);}\n                  sailsApp.request('get /user/1/pets', function (err, resp, data) {\n                    if (err) {return done (err);}\n                    assert.equal(data.length, 1);\n                    assert.equal(data[0].name, 'spot');\n                    assert.equal(data[0].id, 1);\n                    assert.equal(data[0].owner, 1);\n                    return done();\n                  });\n                });\n              });\n            });\n          });\n\n          describe('a get request to /:model/:parentid/:association/:id', function() {\n\n            it('should return JSON for the specified instance in the collection of the test model', function(done) {\n              sailsApp.models.pet.createEach([{name: 'bubbles'}, {name: 'dempsey'}]).meta({fetch: true}).exec(function(err, pets) {\n                sailsApp.models.user.create({name: 'roger', pets: _.pluck(pets,'id')}).meta({fetch: true}).exec(function(err) {\n                  if (err) {return done (err);}\n                  sailsApp.request('get /user/1/pets/2', function (err, resp, data) {\n                    if (err) {\n                      if (err.status && err.status === 404) {\n                        return done();\n                      }\n                      return done(new Error('Should have responded with a 404 error, but instead got:' + util.inspect(err, {depth: null})));\n                    }\n                    return done(new Error('Should have responded with a 404 error, but instead got:' + util.inspect(data, {depth: null})));\n                  });\n                });\n              });\n            });\n          });\n\n          describe('a get request to /:model/:parentid/:association/add/:id', function() {\n\n            it('should return JSON for an instance of the test model, with its collection updated', function(done) {\n              sailsApp.models.user.create({name: 'ira'}).exec(function(err) {\n                if (err) {return done (err);}\n                sailsApp.models.pet.create({name: 'flipper'}).exec(function(err) {\n                  if (err) {return done (err);}\n                  sailsApp.request('get /user/1/pets/add/1', function (err, resp, data) {\n                    if (err) {return done (err);}\n                    assert.equal(data.name, 'ira');\n                    assert.equal(data.id, 1);\n                    assert.equal(data.pets.length, 1);\n                    assert.equal(data.pets[0].name, 'flipper');\n                    sailsApp.models.user.findOne({id: 1}).populate('pets').exec(function(err, user) {\n                      if (err) {return done (err);}\n                      assert(user);\n                      assert.equal(user.name, 'ira');\n                      assert.equal(user.id, 1);\n                      assert.equal(user.pets.length, 1);\n                      assert.equal(user.pets[0].name, 'flipper');\n                      return done();\n                    });\n                  });\n                });\n              });\n            });\n          });\n\n          describe('a get request to /:model/:parentid/:association/remove/:id', function() {\n\n            it('should return JSON for an instance of the test model, with its collection updated', function(done) {\n              sailsApp.models.pet.create({name: 'alice'}).meta({fetch: true}).exec(function(err, alice) {\n                sailsApp.models.user.create({name: 'larry', pets: [alice.id]}).meta({fetch: true}).exec(function(err) {\n                  if (err) {return done (err);}\n                  sailsApp.request('get /user/1/pets/remove/1', function (err, resp, data) {\n                    if (err) {return done (err);}\n                    assert.equal(data.name, 'larry');\n                    assert.equal(data.id, 1);\n                    assert.equal(data.pets.length, 0);\n                    sailsApp.models.user.findOne({id: 1}).populate('pets').exec(function(err, user) {\n                      if (err) {return done (err);}\n                      assert(user);\n                      assert.equal(user.name, 'larry');\n                      assert.equal(user.id, 1);\n                      assert.equal(user.pets.length, 0);\n                      return done();\n                    });\n                  });\n                });\n              });\n            });\n          });\n\n\n          describe('a put request to /:model/:parentid/:association/replace (with empty array)', function() {\n\n            it('should return JSON for an instance of the test model, with its collection replaced', function(done) {\n              sailsApp.models.user.create({name: 'ira', id: 1}).exec(function(err) {\n                if (err) {return done (err);}\n                sailsApp.models.pet.create({name: 'flipper', id: 1, owner: 1}).exec(function(err) {\n                  if (err) {return done (err);}\n                  sailsApp.request('get /user/1/pets/replace?pets=[]', function (err, resp, data) {\n                    if (err) {return done (err);}\n                    assert.equal(data.name, 'ira');\n                    assert.equal(data.pets.length, 0);\n                    sailsApp.models.pet.findOne({id: 1}).populate('owner').exec(function(err, pet) {\n                      if (err) {return done (err);}\n                      assert(pet);\n                      assert.equal(pet.name, 'flipper');\n                      assert.equal(pet.id, 1);\n                      assert.equal(pet.owner, null);\n                      return done();\n                    });\n                  });\n                });\n              });\n            });\n          });\n\n          describe('a get request to /:model/:parentid/:association/replace (with new array)', function() {\n\n            it('should return JSON for an instance of the test model, with its collection replaced', function(done) {\n              sailsApp.models.user.create({name: 'zooey'}).exec(function(err) {\n                if (err) {return done (err);}\n                sailsApp.models.pet.createEach([{name: 'ralph', id: 1, owner: 1}, {name: 'fiona', id: 2}]).exec(function(err) {\n                  if (err) {return done (err);}\n                  sailsApp.request('get /user/1/pets/replace?pets=[2]', function (err, resp, data) {\n                    if (err) {return done (err);}\n                    assert.equal(data.name, 'zooey');\n                    assert.equal(data.pets.length, 1);\n                    assert.equal(data.pets[0].id, 2);\n                    assert.equal(data.pets[0].name, 'fiona');\n                    sailsApp.models.pet.findOne({id: 2}).populate('owner').exec(function(err, pet) {\n                      if (err) {return done (err);}\n                      assert(pet);\n                      assert.equal(pet.name, 'fiona');\n                      assert.equal(pet.id, 2);\n                      assert.equal(pet.owner.name, 'zooey');\n                      assert.equal(pet.owner.id, 1);\n                      return done();\n                    });\n                  });\n                });\n              });\n            });\n          });\n\n        });\n\n      });\n\n      describe('filtering in the query string :: ', function() {\n\n        before(function() {\n          extraSailsConfig = {\n            orm: {\n              moduleDefinitions: {\n                models: {\n                  user: {\n                    attributes: {\n                      name: 'string'\n                    }\n                  }\n                }\n              }\n            }\n          };\n        });\n\n        after(function() {\n          extraSailsConfig = {};\n        });\n\n        it('a get request to /:model/find?name=scott should respond with the correctly filtered instances', function(done) {\n          sailsApp.models.user.createEach([{name: 'scott'}, {name: 'mike'}]).exec(function(err) {\n            if (err) {return done(err);}\n            sailsApp.request('get /user/find?name=scott', function (err, resp, data) {\n              assert(!err, err);\n              assert.equal(data.length, 1);\n              assert.equal(data[0].name, 'scott');\n              done();\n            });\n          });\n        });\n\n        it('a get request to /:model/find?where={...} should respond with the correctly filtered instances', function(done) {\n          sailsApp.models.user.createEach([{name: 'scott'}, {name: 'mike'}, {name: 'rachael'}, {name: 'cody'}, {name: 'irl'}]).exec(function(err) {        if (err) {return done(err);}\n            sailsApp.request('get /user/find?where={\"name\": {\">\": \"irl\"}}', function (err, resp, data) {\n              assert(!err, err);\n              assert.equal(data.length, 3);\n              var names = _.pluck(data, 'name');\n              assert(_.contains(names, 'scott'));\n              assert(_.contains(names, 'mike'));\n              assert(_.contains(names, 'rachael'));\n              done();\n            });\n          });\n        });\n\n\n      });\n\n      describe('using sort, skip and limit in the query string :: ', function() {\n\n        before(function() {\n          extraSailsConfig = {\n            orm: {\n              moduleDefinitions: {\n                models: {\n                  user: {\n                    attributes: {\n                      name: 'string'\n                    }\n                  }\n                }\n              }\n            }\n          };\n        });\n\n        after(function() {\n          extraSailsConfig = {};\n        });\n\n        it('a get request to /:model/find?sort=name%20asc&limit=2&skip=1 should respond with the correctly filtered instances', function(done) {\n          sailsApp.models.user.createEach([{name: 'scott'}, {name: 'mike'}, {name: 'rachael'}, {name: 'cody'}, {name: 'irl'}]).exec(function(err) {\n            if (err) {return done(err);}\n            sailsApp.request('get /user/find?sort=name%20asc&limit=2&skip=1', function (err, resp, data) {\n              assert(!err, err);\n              assert.equal(data.length, 2);\n              assert.equal(data[0].name, 'irl');\n              assert.equal(data[1].name, 'mike');\n              done();\n            });\n          });\n        });\n\n        it('a get request to /:model/find?sort=name%20desc&limit=2&skip=1 should respond with the correctly filtered instances', function(done) {\n          sailsApp.models.user.createEach([{name: 'scott'}, {name: 'mike'}, {name: 'rachael'}, {name: 'cody'}, {name: 'irl'}]).exec(function(err) {\n            if (err) {return done(err);}\n            sailsApp.request('get /user/find?sort=name%20desc&limit=2&skip=1', function (err, resp, data) {\n              assert(!err, err);\n              assert.equal(data.length, 2);\n              assert.equal(data[0].name, 'rachael');\n              assert.equal(data[1].name, 'mike');\n              done();\n            });\n          });\n        });\n\n        it('a get request to /:model/find?sort={\"name\":-1}&limit=2&skip=1 should respond with the correctly filtered instances', function(done) {\n          sailsApp.models.user.createEach([{name: 'scott'}, {name: 'mike'}, {name: 'rachael'}, {name: 'cody'}, {name: 'irl'}]).exec(function(err) {\n            if (err) {return done(err);}\n            sailsApp.request('get /user/find?sort={\"name\":-1}&limit=2&skip=1', function (err, resp, data) {\n              assert(!err, err);\n              assert.equal(data.length, 2);\n              assert.equal(data[0].name, 'rachael');\n              assert.equal(data[1].name, 'mike');\n              done();\n            });\n          });\n        });\n\n      });\n\n      describe('after reloading actions :: ', function() {\n\n        before(function() {\n          extraSailsConfig = {\n            orm: {\n              moduleDefinitions: {\n                models: {\n                  user: {\n                    attributes: {\n                      name: 'string'\n                    }\n                  }\n                }\n              }\n            }\n          };\n        });\n\n        after(function() {\n          extraSailsConfig = {};\n        });\n\n        it('should still respond to RESTful blueprint requests correctly :: ', function(done) {\n          sailsApp.models.user.createEach([{name: 'scott'}, {name: 'mike'}]).exec(function(err) {\n            if (err) {return done(err);}\n            sailsApp.reloadActions(function(err) {\n              if (err) {return done(err);}\n              sailsApp.request('get /user/find', function (err, resp, data) {\n                assert(!err, err);\n                assert.equal(data.length, 2);\n                done();\n              });\n            });\n          });\n        });\n\n      });\n\n      describe('with pluralize turned on :: ', function() {\n\n        before(function() {\n          extraSailsConfig = {\n            blueprints: {\n              pluralize: true\n            },\n            orm: {\n              moduleDefinitions: {\n                models: {\n                  user: {},\n                  quiz: {}\n                }\n              }\n            }\n          };\n        });\n\n        after(function() {\n          extraSailsConfig = {};\n        });\n\n        it('should bind blueprint actions to plural controller names', function(done) {\n          sailsApp.models.user.create({}).exec(function(err) {\n            if (err) {return done (err);}\n            sailsApp.request('get /users/find', function (err, resp, data) {\n              assert(!err, err);\n              assert.equal(data.length, 1);\n              assert.equal(data[0].id, 1);\n              done();\n            });\n          });\n        });\n\n        it('should bind blueprint actions to plural controller names (quiz => quizzes)', function(done) {\n          sailsApp.models.quiz.create({}).exec(function(err) {\n            if (err) {return done (err);}\n            sailsApp.request('get /quizzes/find', function (err, resp, data) {\n              assert(!err, err);\n              assert.equal(data.length, 1);\n              assert.equal(data[0].id, 1);\n              done();\n            });\n          });\n        });\n\n        it('should not bind blueprint actions to singular controller names', function(done) {\n          sailsApp.models.user.create({}).exec(function(err) {\n            if (err) {return done (err);}\n            sailsApp.request('get /user/find', function (err, resp, data) {\n              assert(err);\n              assert.equal(err.status, 404);\n              done();\n            });\n          });\n        });\n\n      });\n\n      describe('with `prefix` option set to \\'/api\\' :: ', function() {\n\n        before(function() {\n          extraSailsConfig = {\n            blueprints: {\n              prefix: '/api'\n            },\n            orm: {\n              moduleDefinitions: {\n                models: {\n                  user: {\n                    attributes: {\n                      name: 'string'\n                    }\n                  }\n                }\n              }\n            }\n          };\n        });\n\n        after(function() {\n          extraSailsConfig = {};\n        });\n\n        describe('a get request to /api/:model/find', function() {\n\n          it('should return JSON for all of the instances of the test model', function(done) {\n            sailsApp.models.user.create({name: 'joy'}).exec(function(err) {\n              if (err) {return done (err);}\n              sailsApp.request('get /api/user/find', function (err, resp, data) {\n                assert(!err, err);\n                assert.equal(data.length, 1);\n                assert.equal(data[0].name, 'joy');\n                assert.equal(data[0].id, 1);\n                done();\n              });\n            });\n          });\n        });\n\n        describe('a get request to /:model/find', function() {\n\n          it('should return a 404', function(done) {\n            sailsApp.request('get /user/find', function (err, resp, data) {\n              assert(err);\n              assert.equal(err.status, 404);\n              done();\n            });\n          });\n        });\n\n      });\n\n      describe('with `restPrefix` option set to \\'/v1\\' :: ', function() {\n\n        before(function() {\n          extraSailsConfig = {\n            blueprints: {\n              restPrefix: '/v1'\n            },\n            orm: {\n              moduleDefinitions: {\n                models: {\n                  user: {\n                    attributes: {\n                      name: 'string'\n                    }\n                  }\n                }\n              }\n            }\n          };\n        });\n\n        after(function() {\n          extraSailsConfig = {};\n        });\n\n        describe('a get request to /v1/:model/find', function() {\n\n          it('should return a 404 (rest prefix should have no effect on shortcut routes', function(done) {\n            sailsApp.request('get /v1/user/find', function (err, resp, data) {\n              assert(err);\n              assert.equal(err.status, 404);\n              done();\n            });\n          });\n\n\n        });\n\n        describe('a get request to /:model/find', function() {\n\n          it('should return JSON for all of the instances of the test model', function(done) {\n            sailsApp.models.user.create({name: 'al'}).exec(function(err) {\n              if (err) {return done (err);}\n              sailsApp.request('get /user/find', function (err, resp, data) {\n                assert(!err, err);\n                assert.equal(data.length, 1);\n                assert.equal(data[0].name, 'al');\n                assert.equal(data[0].id, 1);\n                done();\n              });\n            });\n          });\n        });\n\n      });\n\n      describe('overriding blueprints :: ', function() {\n\n        before(function() {\n          extraSailsConfig = {\n            orm: {\n              moduleDefinitions: {\n                models: {\n                  user: {},\n                },\n              }\n            },\n            controllers: {\n              moduleDefinitions: {\n                'user/find': function(req, res) {\n                  return res.send('find dem users!');\n                }\n              }\n            }\n          };\n        });\n\n        after(function() {\n          extraSailsConfig = {};\n        });\n\n        it('if a `:model.find` action is explicitly added, it should be used in response to `GET /:model/find`', function(done) {\n          sailsApp.models.user.create({name: 'al'}).exec(function(err) {\n            if (err) {return done (err);}\n            sailsApp.request('get /user/find', function (err, resp, data) {\n              assert(!err, err);\n              assert.equal(data, 'find dem users!');\n              done();\n            });\n          });\n        });\n\n\n      });\n\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/integration/hook.cors.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar assert = require('assert');\nvar httpHelper = require('./helpers/httpHelper.js');\nvar appHelper = require('./helpers/appHelper');\nvar path = require('path');\nvar fs = require('fs');\nvar _ = require('@sailshq/lodash');\nvar tmp = require('tmp');\n\nvar Sails = require('../../lib').constructor;\n\n\ndescribe('CORS config ::', function() {\n\n  var setups = {\n    'with default settings': {\n      expectations: [\n        {\n          route: 'PUT /no-cors-config',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: null\n        },\n        {\n          route: 'OPTIONS /no-cors-config',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: null\n        },\n        {\n          route: 'PUT /cors-true',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: {\n           'access-control-allow-origin': '*',\n          }\n        },\n        {\n          route: 'OPTIONS /cors-true',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': '*',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n          }\n        },\n        {\n          route: 'OPTIONS /cors-true',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'POST'},\n          response_status: 200,\n          response_headers: null\n        },\n        {\n          route: 'PUT /origin-example-com',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /origin-example-com',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /origin-example-com',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'POST'},\n          response_status: 200,\n          response_headers: null\n        },\n        {\n          route: 'PUT /origin-example-com',\n          request_headers: {origin: 'http://somewhere.com'},\n          response_status: 200,\n          response_headers: null\n        },\n        {\n          route: 'OPTIONS /origin-example-com',\n          request_headers: {origin: 'http://somewhere.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: null\n        },\n        {\n          route: 'OPTIONS /origin-example-com',\n          request_headers: {origin: 'http://somewhere.com', 'access-control-request-method': 'POST'},\n          response_status: 200,\n          response_headers: null\n        },\n        {\n          route: 'PUT /origin-example-com-somewhere-com',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /origin-example-com-somewhere-com',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /origin-example-com-somewhere-com',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'POST'},\n          response_status: 200,\n          response_headers: null\n        },\n        {\n          route: 'PUT /origin-example-com-somewhere-com',\n          request_headers: {origin: 'http://somewhere.com'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://somewhere.com',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /origin-example-com-somewhere-com',\n          request_headers: {origin: 'http://somewhere.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://somewhere.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /origin-example-com-somewhere-com',\n          request_headers: {origin: 'http://somewhere.com', 'access-control-request-method': 'POST'},\n          response_status: 200,\n          response_headers: null\n        },\n        {\n          route: 'PUT /origin-example-com-somewhere-com-array',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /origin-example-com-somewhere-com-array',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /origin-example-com-somewhere-com-array',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'POST'},\n          response_status: 200,\n          response_headers: null\n        },\n        {\n          route: 'PUT /origin-example-com-somewhere-com-array',\n          request_headers: {origin: 'http://somewhere.com'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://somewhere.com',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /origin-example-com-somewhere-com-array',\n          request_headers: {origin: 'http://somewhere.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://somewhere.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /origin-example-com-somewhere-com-array',\n          request_headers: {origin: 'http://somewhere.com', 'access-control-request-method': 'POST'},\n          response_status: 200,\n          response_headers: null\n        },\n        {\n          route: 'PUT /all-methods-origin-example-com',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /all-methods-origin-example-com',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'POST /all-methods-origin-example-com',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /all-methods-origin-example-com',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'POST'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'vary': 'Origin'\n          }\n        },\n\n        {\n          route: 'DELETE /unsafe',\n          request_headers: {origin: 'http://foobar.com'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://foobar.com',\n            'access-control-allow-credentials': 'true',\n            'vary': 'Origin'\n          }\n        },\n\n        {\n          route: 'OPTIONS /unsafe',\n          request_headers: {origin: 'http://foobar.com', 'access-control-request-method': 'DELETE'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://foobar.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-credentials': 'true',\n            'access-control-allow-headers': 'content-type',\n            'vary': 'Origin'\n          }\n        },\n\n      ]\n    },\n\n    'with `allRoutes: true`': {\n      sailsCorsConfig: {allRoutes: true},\n      expectations: [\n        {\n          route: 'PUT /no-cors-config',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': '*',\n          }\n        },\n        {\n          route: 'OPTIONS /no-cors-config',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': '*',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n          }\n        },\n        {\n          route: 'PUT /cors-true',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: {\n           'access-control-allow-origin': '*',\n          }\n        },\n        {\n          route: 'OPTIONS /cors-true',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': '*',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n          }\n        },\n        {\n          route: 'OPTIONS /cors-true',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'POST'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': '*',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n          }\n        },\n      ],\n    },\n    'with `allRoutes: true`, `allowCredentials: true`, `allowAnyOriginWithCredentialsUnsafe: true`': {\n      sailsCorsConfig: {allRoutes: true, allowCredentials: true, allowAnyOriginWithCredentialsUnsafe: true},\n      expectations: [\n        {\n          route: 'PUT /no-cors-config',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin':'http://example.com',\n            'access-control-allow-methods': undefined,\n            'access-control-allow-headers': undefined,\n            'access-control-allow-credentials': 'true',\n            'access-control-exposed-headers': undefined,\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /no-cors-config',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin':'http://example.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'access-control-allow-credentials': 'true',\n            'access-control-exposed-headers': undefined,\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'PUT /cors-true',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin':'http://example.com',\n            'access-control-allow-methods': undefined,\n            'access-control-allow-headers': undefined,\n            'access-control-allow-credentials': 'true',\n            'access-control-exposed-headers': undefined,\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /cors-true',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin':'http://example.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'access-control-allow-credentials': 'true',\n            'access-control-exposed-headers': undefined,\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /cors-true',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'POST'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin':'http://example.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'access-control-allow-credentials': 'true',\n            'access-control-exposed-headers': undefined,\n            'vary': 'Origin'\n          }\n        },\n      ]\n    },\n    'with `allRoutes: true`, `origin: http://example.com`': {\n      sailsCorsConfig: {allRoutes: true, allowOrigins: 'http://example.com'},\n      expectations: [\n        {\n          route: 'PUT /no-cors-config',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /no-cors-config',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'PUT /cors-true',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: {\n           'access-control-allow-origin': 'http://example.com',\n           'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /cors-true',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /cors-true',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'POST'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'PUT /cors-true',\n          request_headers: {origin: 'http://somewhere.com'},\n          response_status: 200,\n          response_headers: null\n        },\n      ]\n    },\n    'with `allRoutes: true`, `origin: http://example.com, http://somewhere.com`': {\n      sailsCorsConfig: {allRoutes: true, allowOrigins: 'http://example.com, http://somewhere.com'},\n      expectations: [\n        {\n          route: 'PUT /no-cors-config',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /no-cors-config',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'PUT /cors-true',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: {\n           'access-control-allow-origin': 'http://example.com',\n           'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /cors-true',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /cors-true',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'POST'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'PUT /cors-true',\n          request_headers: {origin: 'http://somewhere.com'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://somewhere.com',\n            'vary': 'Origin'\n          }\n        },\n      ]\n    },\n    'with `allRoutes: true`, `origin: [\\'http://example.com\\', \\'http://somewhere.com\\']`': {\n      sailsCorsConfig: {allRoutes: true, allowOrigins: ['http://example.com', 'http://somewhere.com']},\n      expectations: [\n        {\n          route: 'PUT /no-cors-config',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /no-cors-config',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'PUT /cors-true',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: {\n           'access-control-allow-origin': 'http://example.com',\n           'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /cors-true',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'OPTIONS /cors-true',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'POST'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'access-control-allow-methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n            'access-control-allow-headers': 'content-type',\n            'vary': 'Origin'\n          }\n        },\n        {\n          route: 'PUT /cors-true',\n          request_headers: {origin: 'http://somewhere.com'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://somewhere.com',\n            'vary': 'Origin'\n          }\n        },\n      ]\n    },\n    'with `allowRequestHeaders: \\'x-foo-bar\\'`, `allowResponseHeaders: \\'x-owl-hoot\\'`, `allowRequestMethods: \\'PUT,POST\\'`': {\n      sailsCorsConfig: {allowRequestHeaders: 'x-foo-bar', allowResponseHeaders: 'x-owl-hoot', allowRequestMethods: 'PUT,POST'},\n      expectations: [\n        {\n          route: 'PUT /no-cors-config',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: null\n        },\n        {\n          route: 'PUT /cors-true',\n          request_headers: {origin: 'http://example.com'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': '*',\n          }\n        },\n        {\n          route: 'OPTIONS /cors-true',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': '*',\n            'access-control-allow-methods': 'PUT,POST',\n            'access-control-allow-headers': 'x-foo-bar',\n            'access-control-expose-headers': 'x-owl-hoot',\n          }\n        },\n        {\n          route: 'OPTIONS /origin-example-com',\n          request_headers: {origin: 'http://example.com', 'access-control-request-method': 'PUT'},\n          response_status: 200,\n          response_headers: {\n            'access-control-allow-origin': 'http://example.com',\n            'access-control-allow-methods': 'PUT,POST',\n            'access-control-allow-headers': 'x-foo-bar',\n            'access-control-expose-headers': 'x-owl-hoot',\n            'vary': 'Origin'\n          }\n        },\n      ]\n    }\n  };\n\n  var only = _.findKey(setups, function(setup, name) {\n    if (name.indexOf('only.') === 0) {\n      return name;\n    }\n  });\n\n  if (only) {\n    setups = (function(){\n      var onlySetup = setups[only];\n      newSetups = {};\n      newSetups[only.replace(/^only\\./,'')] = onlySetup;\n      return newSetups;\n    })();\n  }\n\n  _.each(setups, function(setup, name) {\n    if (name.indexOf('skip.') === 0) {\n      return;\n    }\n    describe(name, function() {\n      var sailsApp;\n\n      before(function(done) {\n        (new Sails()).load({\n            hooks: {grunt: false, views: false, blueprints: false, policies: false, i18n: false},\n            log: {level: 'error'},\n            security: {\n              cors: _.cloneDeep(setup.sailsCorsConfig)\n            },\n            routes: {\n              'PUT /no-cors-config': function(req, res){return res.ok();},\n              'PUT /cors-true': {cors: true, target: function(req, res){return res.ok();}},\n              'PUT /origin-example-com': {cors: {allowOrigins: 'http://example.com'}, target: function(req, res){return res.ok();}},\n              'PUT /origin-example-com-somewhere-com': {cors: {allowOrigins: 'http://example.com, http://somewhere.com'}, target: function(req, res){return res.ok();}},\n              'PUT /origin-example-com-somewhere-com-array': {cors: {allowOrigins: ['http://example.com', 'http://somewhere.com']}, target: function(req, res){return res.ok();}},\n              '/all-methods-origin-example-com': {cors: {allowOrigins: 'http://example.com'}, target: function(req, res){return res.ok();}},\n              '/unsafe': {cors: {allowOrigins: '*', allowCredentials: true, allowAnyOriginWithCredentialsUnsafe: true}, target: function(req, res){return res.ok();}},\n            }\n          }, function(err, _sails) {\n            sailsApp = _sails;\n            return done(err);\n          }\n        );\n      });\n\n      after(function(done) {\n        sailsApp.lower(done);\n      });\n\n      _.each(setup.expectations, function(expectation) {\n        var routeParts = expectation.route.split(' ');\n        var method = routeParts[0];\n        var path = routeParts[1];\n        describe('a ' + method.toUpperCase() + ' request to ' + path + ' using ' + JSON.stringify(expectation.request_headers), function() {\n\n          var responseHolder = {};\n          before(function(done) {\n            sailsApp.request({\n              url: path,\n              method: method,\n              headers: expectation.request_headers\n            }, function (err, response, data) {\n              if (err) {return done(err);}\n              responseHolder.response = response;\n              return done();\n            });\n          });\n\n          it('should respond with status code ' + expectation.response_status, function() {\n            assert.equal(responseHolder.response.statusCode, expectation.response_status);\n          });\n\n          var expectedHeaders = _.extend({}, {\n            'access-control-allow-origin': undefined,\n            'access-control-allow-methods': undefined,\n            'access-control-allow-headers': undefined,\n            'access-control-allow-credentials': undefined,\n            'access-control-exposed-headers': undefined,\n            'vary': undefined\n          }, expectation.response_headers || {});\n\n          expectHeaders(responseHolder, expectedHeaders);\n\n        });\n      });\n\n    });\n\n    describe(name + ' (with deprecated config)', function() {\n      var sailsApp;\n\n      before(function(done) {\n        (new Sails()).load({\n            hooks: {grunt: false, views: false, blueprints: false, policies: false, i18n: false},\n            log: {level: 'silent'},\n            cors: _.cloneDeep(setup.sailsCorsConfig),\n            routes: {\n              'PUT /no-cors-config': function(req, res){\n                return res.ok();\n              },\n              'PUT /cors-true': {cors: true, target: function(req, res){return res.ok();}},\n              'PUT /origin-example-com': {cors: {origin: 'http://example.com'}, target: function(req, res){return res.ok();}},\n              'PUT /origin-example-com-somewhere-com': {cors: {origin: 'http://example.com, http://somewhere.com'}, target: function(req, res){return res.ok();}},\n              'PUT /origin-example-com-somewhere-com-array': {cors: {origin: ['http://example.com', 'http://somewhere.com']}, target: function(req, res){return res.ok();}},\n              '/all-methods-origin-example-com': {cors: {origin: 'http://example.com'}, target: function(req, res){return res.ok();}},\n              '/unsafe': {cors: {origin: '*', allowCredentials: true, allowAnyOriginWithCredentialsUnsafe: true}, target: function(req, res){return res.ok();}},\n            }\n          }, function(err, _sails) {\n            sailsApp = _sails;\n            return done(err);\n          }\n        );\n      });\n\n      after(function(done) {\n        sailsApp.lower(done);\n      });\n\n      _.each(setup.expectations, function(expectation) {\n        var routeParts = expectation.route.split(' ');\n        var method = routeParts[0];\n        var path = routeParts[1];\n        describe('a ' + method.toUpperCase() + ' request to ' + path + ' using ' + JSON.stringify(expectation.request_headers), function() {\n\n          var responseHolder = {};\n          before(function(done) {\n            sailsApp.request({\n              url: path,\n              method: method,\n              headers: expectation.request_headers\n            }, function (err, response, data) {\n              if (err) {return done(err);}\n              responseHolder.response = response;\n              return done();\n            });\n          });\n\n          it('should respond with status code ' + expectation.response_status, function() {\n            assert.equal(responseHolder.response.statusCode, expectation.response_status);\n          });\n\n          var expectedHeaders = _.extend({}, {\n            'access-control-allow-origin': undefined,\n            'access-control-allow-methods': undefined,\n            'access-control-allow-headers': undefined,\n            'access-control-allow-credentials': undefined,\n            'access-control-exposed-headers': undefined,\n            'vary': undefined\n          }, expectation.response_headers || {});\n\n          expectHeaders(responseHolder, expectedHeaders);\n\n        });\n      });\n\n    });\n  });\n\n  describe('with invalid global CORS config ({allowOrigins: \\'*\\', allowCredentials: true})', function() {\n\n    it('should fail to lift', function(done) {\n      (new Sails()).load({\n          hooks: {grunt: false, views: false, blueprints: false, policies: false, i18n: false},\n          log: {level: 'silent'},\n          cors: {allowOrigins: '*', allowCredentials: true},\n        }, function(err, _sails) {\n          if (err) {return done();}\n          return done(new Error('Sails should have failed to lift with invalid global CORS config!'));\n        }\n      );\n    });\n\n  });\n\n  describe('with invalid global CORS config ({allowOrigins: 666})', function() {\n\n    it('should fail to lift', function(done) {\n      (new Sails()).load({\n          hooks: {grunt: false, views: false, blueprints: false, policies: false, i18n: false},\n          log: {level: 'silent'},\n          cors: {allowOrigins: 666},\n        }, function(err, _sails) {\n          if (err) {return done();}\n          return done(new Error('Sails should have failed to lift with invalid global CORS config!'));\n        }\n      );\n    });\n  });\n\n  describe('with invalid global CORS config ({allowOrigins: [\\'localboast.yarg\\']})', function() {\n\n    it('should fail to lift', function(done) {\n      (new Sails()).load({\n          hooks: {grunt: false, views: false, blueprints: false, policies: false, i18n: false},\n          log: {level: 'silent'},\n          cors: {allowOrigins: ['localboast.yarg']},\n        }, function(err, _sails) {\n          if (err) {return done();}\n          return done(new Error('Sails should have failed to lift with invalid global CORS config!'));\n        }\n      );\n    });\n  });\n\n  describe('with invalid global CORS config ({allowOrigins: [\\'http://localboast.com:80\\']})', function() {\n\n    it('should fail to lift', function(done) {\n      (new Sails()).load({\n          hooks: {grunt: false, views: false, blueprints: false, policies: false, i18n: false},\n          log: {level: 'silent'},\n          cors: {allowOrigins: ['http://localboast.com:80']},\n        }, function(err, _sails) {\n          if (err) {return done();}\n          return done(new Error('Sails should have failed to lift with invalid global CORS config!'));\n        }\n      );\n    });\n  });\n\n  describe('with invalid global CORS config ({allowOrigins: [\\'https://localboast.com:443\\']})', function() {\n\n    it('should fail to lift', function(done) {\n      (new Sails()).load({\n          hooks: {grunt: false, views: false, blueprints: false, policies: false, i18n: false},\n          log: {level: 'silent'},\n          cors: {allowOrigins: ['https://localboast.com:443']},\n        }, function(err, _sails) {\n          if (err) {return done();}\n          return done(new Error('Sails should have failed to lift with invalid global CORS config!'));\n        }\n      );\n    });\n  });\n\n  describe('with invalid global CORS config ({allowOrigins: [\\'\\']})', function() {\n\n    it('should fail to lift', function(done) {\n      (new Sails()).load({\n          hooks: {grunt: false, views: false, blueprints: false, policies: false, i18n: false},\n          log: {level: 'silent'},\n          cors: {allowOrigins: ['']},\n        }, function(err, _sails) {\n          if (err) {return done();}\n          return done(new Error('Sails should have failed to lift with invalid global CORS config!'));\n        }\n      );\n    });\n  });\n\n  describe('with invalid global CORS config ({allowOrigins: [666]})', function() {\n\n    it('should fail to lift', function(done) {\n      (new Sails()).load({\n          hooks: {grunt: false, views: false, blueprints: false, policies: false, i18n: false},\n          log: {level: 'silent'},\n          cors: {allowOrigins: [666]},\n        }, function(err, _sails) {\n          if (err) {return done();}\n          return done(new Error('Sails should have failed to lift with invalid global CORS config!'));\n        }\n      );\n    });\n  });\n\n  describe('with invalid route CORS config ({allRoutes: true, origin: \\'*\\', allowCredentials: true})', function() {\n\n    it('should fail to lift', function(done) {\n      (new Sails()).load({\n          hooks: {grunt: false, views: false, blueprints: false, policies: false, i18n: false},\n          log: {level: 'silent'},\n          routes: {\n            '/invalid': {cors: {allowOrigins: '*', allowCredentials: true}}\n          }\n        }, function(err, _sails) {\n          if (err) {return done();}\n          return done(new Error('Sails should have failed to lift with invalid route CORS config!'));\n        }\n      );\n    });\n\n  });\n\n  describe('with invalid route CORS config ({allowOrigins: [666]})', function() {\n\n    it('should fail to lift', function(done) {\n      (new Sails()).load({\n          hooks: {grunt: false, views: false, blueprints: false, policies: false, i18n: false},\n          log: {level: 'silent'},\n          routes: {\n            '/invalid': {cors: {allowOrigins: [666]}}\n          }\n        }, function(err, _sails) {\n          if (err) {return done();}\n          return done(new Error('Sails should have failed to lift with invalid route CORS config!'));\n        }\n      );\n    });\n\n  });\n\n  describe('with invalid route CORS config ({allowOrigins: 666})', function() {\n\n    it('should fail to lift', function(done) {\n      (new Sails()).load({\n          hooks: {grunt: false, views: false, blueprints: false, policies: false, i18n: false},\n          log: {level: 'silent'},\n          routes: {\n            '/invalid': {cors: {allowOrigins: 666}}\n          }\n        }, function(err, _sails) {\n          if (err) {return done();}\n          return done(new Error('Sails should have failed to lift with invalid route CORS config!'));\n        }\n      );\n    });\n\n  });\n\n  describe('with invalid route CORS config ({allowOrigins: [\\'blah\\']})', function() {\n\n    it('should fail to lift', function(done) {\n      (new Sails()).load({\n          hooks: {grunt: false, views: false, blueprints: false, policies: false, i18n: false},\n          log: {level: 'silent'},\n          routes: {\n            '/invalid': {cors: {allowOrigins: ['blah']}}\n          }\n        }, function(err, _sails) {\n          if (err) {return done();}\n          return done(new Error('Sails should have failed to lift with invalid route CORS config!'));\n        }\n      );\n    });\n\n  });\n\n}); //</describe('CORS config ::')>\n\n\nfunction makeRequest(options, responseHolder, sailsApp) {\n  return function(done) {\n    sailsApp.request(options, function (err, response, data) {\n      if (err) {return done(err);}\n      responseHolder.response = response;\n      return done();\n    });\n  };\n}\n\nfunction expectHeaders(responseHolder, headers) {\n  _.each(headers, function(val, header) {\n    if (_.isUndefined(val)) {\n      it('`' + header + '` should be undefined', function(){ assert(_.isUndefined(responseHolder.response.headers[header]), 'Got `' + responseHolder.response.headers[header] + '` instead'); });\n    } else {\n      it('`' + header + '` should be `' + val + '`', function(){ assert.equal(responseHolder.response.headers[header], val); });\n    }\n  });\n\n}\n\n\n"
  },
  {
    "path": "test/integration/hook.csrf.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar assert = require('assert');\nvar fs = require('fs');\nvar path = require('path');\nvar _ = require('@sailshq/lodash');\nvar tmp = require('tmp');\nvar Sails = require('../../lib').constructor;\nvar httpHelper = require('./helpers/httpHelper.js');\nvar appHelper = require('./helpers/appHelper');\n\n\n\ndescribe('CSRF ::', function() {\n\n  describe('Basic CSRF config ::', function() {\n\n    var sailsConfig = {};\n\n    var sailsApp;\n    beforeEach(function(done) {\n      var _config = _.merge({\n        hooks: {grunt: false, views: false, blueprints: false, policies: false, i18n: false},\n        log: {level: 'error'},\n        routes: {\n          '/csrfToken': {action: 'security/grant-csrf-token'},\n          'ALL /viewtest/csrf': function(req, res) {\n            var template = _.template('csrf=\\'<%-_csrf%>\\'');\n            res.send(template(res.locals));\n          },\n          'GET /user': function(req, res) {\n            return res.sendStatus(200);\n          },\n          'POST /user': function(req, res) {\n            return res.status(201).send();\n          },\n          'POST /user/:id': function(req, res) {\n            return res.sendStatus(200);\n          }\n        }\n      }, sailsConfig);\n      (new Sails()).load(_config, function(err, _sails) {\n          sailsApp = _sails;\n          return done(err);\n        }\n      );\n    });\n\n    afterEach(function(done) {\n      sailsApp.lower(done);\n    });\n\n    describe('with CSRF set to `false`', function() {\n\n      before(function() {\n        sailsConfig = {};\n      });\n\n      it('a blank CSRF token should be present in view locals', function(done) {\n        sailsApp.request({url: '/viewtest/csrf', method: 'get'}, function(err, response) {\n          if (err) {\n            return done(err);\n          }\n          assert(response.body.indexOf('csrf=\\'\\'') !== -1, response.body);\n          done();\n        });\n      });\n\n    });\n\n    describe('with CSRF set to `true`', function() {\n\n      before(function() {\n\n        sailsConfig = {\n          security: {\n            csrf: true\n          }\n        };\n\n      });\n\n      it('a HEAD request to a route with the CSRF used as a view local should not result in an error', function(done) {\n        sailsApp.request({url: '/viewtest/csrf', method: 'head'}, function(err, response) {\n          if (err) {\n            return done(err);\n          }\n          done();\n        });\n      });\n\n      it('an OPTIONS request to a route with the CSRF used as a view local should not result in an error', function(done) {\n        sailsApp.request({url: '/viewtest/csrf', method: 'options'}, function(err, response) {\n          if (err) {\n            return done(err);\n          }\n          done();\n        });\n      });\n\n      it('a CSRF token should be present in view locals', function(done) {\n        sailsApp.request({url: '/viewtest/csrf', method: 'get'}, function(err, response) {\n          if (err) {\n            return done(err);\n          }\n          assert(response.body.match(/csrf='.{36}'/), response.body);\n          done();\n        });\n      });\n\n      it('a request to /csrfToken should respond with a _csrf token', function(done) {\n        sailsApp.request({url: '/csrftoken', method: 'get'}, function(err, response) {\n          if (err) {\n            return done(err);\n          }\n          assert(response.body._csrf, response.body);\n          return done();\n        });\n      });\n\n      it('a POST request without a CSRF token should result in a 403 response', function(done) {\n\n        sailsApp.request({url: '/user', method: 'post'}, function(err, response) {\n\n          if (err && err.status === 403) {\n            return done();\n          }\n          done(new Error('Expected a 403 error, instead got: ' + (err || response.body)));\n\n        });\n\n      });\n\n      it('a POST request with a valid CSRF token should result in a 201 response', function(done) {\n\n        sailsApp.request({url: '/csrftoken', method: 'get'}, function(err, response) {\n          if (err) {\n            return done(err);\n          }\n          try {\n            var body = response.body;\n            var sid = response.headers['set-cookie'][0].split(';')[0].substr(10);\n            sailsApp.request({\n              method: 'post',\n              url: '/user',\n              headers: {\n                'Content-type': 'application/json',\n                'cookie': 'sails.sid=' + sid\n              },\n              data: {_csrf: body._csrf}\n            }, function(err, response) {\n\n              if (err) {\n                return done(err);\n              }\n\n              assert.equal(response.statusCode, 201);\n              done();\n\n            });\n          } catch (e) {\n            done(e);\n          }\n        });\n\n      });\n    });\n\n    describe('with CSRF set to true, blacklisting \\'POST /foo/:id, POST /bar/:id?, /user\\'}', function() {\n\n      before(function() {\n\n        sailsConfig = {\n          security: {\n            csrf: true\n          },\n          routes: {\n            // Note -- since we don't actually define a target for these, requests that pass CSRF should return a 404.\n            'POST /foo/:id': {csrf: false},\n            '/bar/:id?': {csrf: false},\n            '/user': {csrf: false}\n          }\n        };\n\n      });\n\n      it('a POST request on /user without a CSRF token should result in a 201 response', function(done) {\n        sailsApp.request({\n          method: 'post',\n          url: '/user'\n        }, function(err, response) {\n          if (err) {\n            return done(err);\n          }\n          assert.equal(response.statusCode, 201);\n          done();\n        });\n      });\n\n      it('a POST request on /foo/12 without a CSRF token should result in a 404 response', function(done) {\n        sailsApp.request({url: '/foo/12', method: 'post'}, function(err, response) {\n          if (err && err.status === 404) {\n            return done();\n          }\n          done(new Error('Expected a 404 error, instead got: ' + (err || response.body)));\n        });\n      });\n\n      it('a POST request on /foo/12?abc=123 without a CSRF token should result in a 404 response', function(done) {\n        sailsApp.request({url: '/foo/12?abc=123', method: 'post'}, function(err, response) {\n          if (err && err.status === 404) {\n            return done();\n          }\n          done(new Error('Expected a 404 error, instead got: ' + (err || response.body)));\n        });\n      });\n\n      it('a POST request on /bar/12 without a CSRF token should result in a 404 response', function(done) {\n        sailsApp.request({url: '/bar/12', method: 'post'}, function(err, response) {\n          if (err && err.status === 404) {\n            return done();\n          }\n          done(new Error('Expected a 404 error, instead got: ' + (err || response.body)));\n        });\n      });\n\n      it('a POST request on /bar/12?abc=123 without a CSRF token should result in a 404 response', function(done) {\n        sailsApp.request({url: '/bar/12?abc=123', method: 'post'}, function(err, response) {\n          if (err && err.status === 404) {\n            return done();\n          }\n          done(new Error('Expected a 404 error, instead got: ' + (err || response.body)));\n        });\n      });\n\n      it('a POST request on /bar without a CSRF token should result in a 404 response', function(done) {\n        sailsApp.request({url: '/bar', method: 'post'}, function(err, response) {\n          if (err && err.status === 404) {\n            return done();\n          }\n          done(new Error('Expected a 404 error, instead got: ' + (err || response.body)));\n        });\n      });\n\n      it('a POST request on /bar?abc=123 without a CSRF token should result in a 404 response', function(done) {\n        sailsApp.request({url: '/bar?abc=123', method: 'post'}, function(err, response) {\n          if (err && err.status === 404) {\n            return done();\n          }\n          done(new Error('Expected a 404 error, instead got: ' + (err || response.body)));\n        });\n      });\n\n      it('a PUT request on /foo/12 without a CSRF token should result in a 403 response', function(done) {\n        sailsApp.request({url: '/foo/12', method: 'put'}, function(err, response) {\n          if (err && err.status === 403) {\n            return done();\n          }\n          done(new Error('Expected a 403 error, instead got: ' + (err || response.body)));\n        });\n      });\n\n      it('a POST request on /test without a CSRF token should result in a 403 response', function(done) {\n        sailsApp.request({url: '/test', method: 'post'}, function(err, response) {\n          if (err && err.status === 403) {\n            return done();\n          }\n          done(new Error('Expected a 403 error, instead got: ' + (err || response.body)));\n        });\n      });\n\n      it('a POST request on /foo without a CSRF token should result in a 403 response', function(done) {\n        sailsApp.request({url: '/foo', method: 'post'}, function(err, response) {\n          if (err && err.status === 403) {\n            return done();\n          }\n          done(new Error('Expected a 403 error, instead got: ' + (err || response.body)));\n        });\n      });\n\n    });\n\n    describe('with CSRF set to true, blacklisting \\'POST /user\\\\/\\\\d+/\\'', function() {\n\n      before(function() {\n\n        sailsConfig = {\n          security: {\n            csrf: true\n          },\n          routes: {\n            'POST r|user/\\\\d+|': {csrf: false}\n          }\n        };\n\n      });\n\n      it('a POST request on /user/1 without a CSRF token should result in a 200 response', function(done) {\n        sailsApp.request({url: '/user/1', method: 'post'}, function(err, response) {\n          if (err) {\n            return done(err);\n          }\n          assert.equal(response.statusCode, 200);\n          done();\n        });\n      });\n\n      it('a PUT request on /user/1 without a CSRF token should result in a 403 response', function(done) {\n        sailsApp.request({url: '/user/1', method: 'put'}, function(err, response) {\n          if (err && err.status === 403) {\n            return done();\n          }\n          done(new Error('Expected a 403 error, instead got: ' + (err || response.body)));\n        });\n      });\n\n      it('a POST request on /user/a without a CSRF token should result in a 403 response', function(done) {\n        sailsApp.request({url: '/user/a', method: 'post'}, function(err, response) {\n          if (err && err.status === 403) {\n            return done();\n          }\n          done(new Error('Expected a 403 error, instead got: ' + (err || response.body)));\n        });\n      });\n\n      it('a POST request on /user without a CSRF token should result in a 403 response', function(done) {\n        sailsApp.request({url: '/user', method: 'post'}, function(err, response) {\n          if (err && err.status === 403) {\n            return done();\n          }\n          done(new Error('Expected a 403 error, instead got: ' + (err || response.body)));\n        });\n      });\n\n    });\n\n    describe('with CSRF set to false, whitelisting \\'/user\\\\/\\\\d+/\\'', function() {\n\n      before(function() {\n\n        sailsConfig = {\n          security: {\n            csrf: false\n          },\n          routes: {\n            'r|user/\\\\d+|': {csrf: true}\n          }\n        };\n\n      });\n\n      it('a POST request on /user/1 without a CSRF token should result in a 403 response', function(done) {\n        sailsApp.request({url: '/user/1', method: 'post'}, function(err, response) {\n          if (err && err.status === 403) {\n            return done();\n          }\n          done(new Error('Expected a 403 error, instead got: ' + (err || response.body)));\n        });\n      });\n\n      it('a POST request on /user/a without a CSRF token should result in a 200 response', function(done) {\n        sailsApp.request({url: '/user/a', method: 'post'}, function(err, response) {\n          if (err) {\n            return done(err);\n          }\n          assert.equal(response.statusCode, 200);\n          done();\n        });\n      });\n\n      it('a POST request on /user without a CSRF token should result in a 201 response', function(done) {\n        sailsApp.request({url: '/user', method: 'post'}, function(err, response) {\n          if (err) {\n            return done(err);\n          }\n          assert.equal(response.statusCode, 201);\n          done();\n        });\n      });\n\n    });\n\n\n    describe('with CSRF set to true and sessions disabled', function() {\n\n      before(function() {\n\n        sailsConfig = {\n          security: {\n            csrf: false\n          },\n          hooks: {session: false},\n          routes: {\n            'GET /user': {csrf: true, target: function(req, res) {\n              return res.sendStatus(200);\n            }},\n            'POST /user': {csrf: true, target: function(req, res) {\n              return res.status(201).send();\n            }}\n          }\n        };\n\n      });\n\n      it('a GET request on /user should result in a 200 response', function(done) {\n        sailsApp.request({url: '/user', method: 'get'}, function(err, response) {\n          if (err) {\n            return done(err);\n          }\n          assert.equal(response.statusCode, 200);\n          done();\n        });\n\n      });\n\n      it('a POST request on /user without a CSRF token should result in a 403 response', function(done) {\n        sailsApp.request({url: '/user', method: 'post'}, function(err, response) {\n          assert(err);\n          assert.equal(err.status, 403);\n          done();\n        });\n\n      });\n\n    });\n\n  }); //</describe('CSRF config ::')>\n\n  describe('With CSRF set to `true` globally and the session hook disabled :: ', function() {\n    it('should fail to lift', function(done) {\n      (new Sails()).load({security: {csrf: true}, hooks: {session: false}}, function(err, _sails) {\n          if (err) { return done(); }\n          _sails.lower(function() {\n            return done(new Error('Sails lifted successfully, but it should have failed!'));\n          });\n        }\n      );\n    });\n  });\n\n});\n"
  },
  {
    "path": "test/integration/hook.helpers.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar assert = require('assert');\nvar tmp = require('tmp');\nvar _ = require('@sailshq/lodash');\nvar async = require('async');\n\nvar Filesystem = require('machinepack-fs');\n\nvar Sails = require('../../lib').constructor;\n\ntmp.setGracefulCleanup();\n\n\ndescribe('helpers :: ', function() {\n\n  describe('basic usage :: ', function() {\n\n    var curDir, tmpDir, sailsApp;\n\n    before(function(done) {\n      // Cache the current working directory.\n      curDir = process.cwd();\n      // Create a temp directory.\n      tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n\n      Filesystem.writeSync({\n        force: true,\n        destination: 'api/helpers/greet.js',\n        string: 'module.exports = { inputs: { name: { example: \\'bob\\', required: true } }, exits: { success: { outputExample: \\'Hi, Bob!\\'} }, fn: function (inputs, exits) { return exits.success(\\'Hi, \\' + inputs.name + \\'!\\'); } }'\n      }).execSync();\n\n      (new Sails()).load({\n        hooks: {\n          grunt: false, views: false, pubsub: false\n        },\n        log: {level: 'silent'},\n        helpers: {\n          moduleDefinitions: {\n            ucase: { sync: true, inputs: { string: { example: 'Hi, Bob!', required: true } }, exits: { success: { outputExample: 'HI, BOB!'} }, fn: function(inputs, exits) { return exits.success(inputs.string.toUpperCase()); } }\n          },\n        }\n      }, function(err, _sailsApp) {\n        if (err) { return done(err); }\n        sailsApp = _sailsApp;\n        return done();\n      });\n    });\n\n    after(function(done) {\n      process.chdir(curDir);\n      if (sailsApp) {sailsApp.lower(done);}\n      else {\n        return done();\n      }\n    });\n\n    it('should load helpers from disk and merge them with programmatically added helpers', function() {\n      assert.equal(_.keys(sailsApp.helpers).length, 2);\n    });\n\n    it('should load helpers correctly', function() {\n      assert(_.isFunction(sailsApp.helpers.greet));\n      assert(_.isFunction(sailsApp.helpers.greet.with));\n      assert(_.isFunction(sailsApp.helpers.ucase));\n      assert(_.isFunction(sailsApp.helpers.ucase.with));\n    });\n\n    it('should support \"serial\" and \"natural\" usage out of the box, and `.with()` too', function(done) {\n      var result1 = sailsApp.helpers.ucase('Hi, Glen!');\n      var result2 = sailsApp.helpers.ucase.with({ string: 'Hi, Glen!' });\n      assert.equal(result1, 'HI, GLEN!');\n      assert.equal(result2, result1);\n\n      sailsApp.helpers.greet('Glen').switch({\n        error: done,\n        success: function (result3) {\n          sailsApp.helpers.greet.with({ name: 'Glen' }).exec(function(err, result4) {\n            if (err) { return done(err); }\n            try {\n              assert.equal(result3, 'Hi, Glen!');\n              assert.equal(result4, result3);\n            } catch (err) { return done(err); }\n            return done();\n          });//_∏_\n        }//>\n      });//_∏_\n    });\n\n    it('should support customization', function(done) {\n      sailsApp.helpers.greet.customize({ arginStyle: 'named', execStyle: 'natural' })({ name: 'Glen' }).then(function(result1){\n        assert.equal(result1, 'Hi, Glen!');\n        var result2 = sailsApp.helpers.ucase.customize({ arginStyle: 'serial', execStyle: 'deferred' })('Hi, Glen!').now();\n        var result3 = sailsApp.helpers.ucase.customize({ arginStyle: 'serial', execStyle: 'deferred' }).with({ string: 'Hi, Glen!' }).now();\n        assert.equal(result2, 'HI, GLEN!');\n        assert.equal(result3, result2);\n        return done();\n      }).catch (function(err){ return done(err); });//_∏_\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/integration/hook.i18n.test.js",
    "content": "/**\n * Test dependencies\n */\n\nvar assert = require('assert');\nvar httpHelper = require('./helpers/httpHelper.js');\nvar appHelper = require('./helpers/appHelper');\nvar path = require('path');\nvar fs = require('fs');\n\n\n\n\n//  ██╗ ██╗ █████╗ ███╗   ██╗    ██╗  ██╗ ██████╗  ██████╗ ██╗  ██╗\n//  ██║███║██╔══██╗████╗  ██║    ██║  ██║██╔═══██╗██╔═══██╗██║ ██╔╝\n//  ██║╚██║╚█████╔╝██╔██╗ ██║    ███████║██║   ██║██║   ██║█████╔╝\n//  ██║ ██║██╔══██╗██║╚██╗██║    ██╔══██║██║   ██║██║   ██║██╔═██╗\n//  ██║ ██║╚█████╔╝██║ ╚████║    ██║  ██║╚██████╔╝╚██████╔╝██║  ██╗\n//  ╚═╝ ╚═╝ ╚════╝ ╚═╝  ╚═══╝    ╚═╝  ╚═╝ ╚═════╝  ╚═════╝ ╚═╝  ╚═╝\n//\n//   ██╗ ██████╗ ██╗   ██╗███████╗██████╗  █████╗ ██╗     ██╗     ██╗\n//  ██╔╝██╔═══██╗██║   ██║██╔════╝██╔══██╗██╔══██╗██║     ██║     ╚██╗\n//  ██║ ██║   ██║██║   ██║█████╗  ██████╔╝███████║██║     ██║      ██║\n//  ██║ ██║   ██║╚██╗ ██╔╝██╔══╝  ██╔══██╗██╔══██║██║     ██║      ██║\n//  ╚██╗╚██████╔╝ ╚████╔╝ ███████╗██║  ██║██║  ██║███████╗███████╗██╔╝\n//   ╚═╝ ╚═════╝   ╚═══╝  ╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝╚══════╝╚═╝\n//\ndescribe('i18n ::', function() {\n\n  var appName = 'testApp';\n\n  var sailsApp;\n  before(function(done) {\n    appHelper.build(done);\n  });\n\n  beforeEach(function(done) {\n    appHelper.lift({\n      log: { level: 'silent' },\n      routes: {\n        '/test_req_getlocale': function(req, res) {\n          res.send(req.getLocale());\n        },\n        '/test_req_setlocale': function(req, res) {\n          req.setLocale('es');\n          res.send(req.i18n.__('Welcome'));\n        },\n        '/test_sails_getlocale': function(req, res) {\n          res.send(req._sails.hooks.i18n.getLocale());\n        },\n        '/test_sails_setlocale': function(req, res) {\n          res.send(req._sails.__('Welcome'));\n        },\n\n      }\n    }, function(err, sails) {\n      if (err) {\n        return done(err);\n      }\n      sailsApp = sails;\n      return done();\n    });\n  });\n\n  afterEach(function(done) {\n    sailsApp.lower(done);\n  });\n\n  after(function() {\n    process.chdir('../');\n    appHelper.teardown();\n  });\n\n  describe('with locales generate by sails-generate-backend', function() {\n    it('should say \"Welcome\" by default', function() {\n      assert.equal(sailsApp.__('Welcome'), 'Welcome');\n    });\n\n    it('should work using `i18n()` as well as `__()`', function() {\n      assert.equal(sailsApp.i18n('Welcome'), 'Welcome');\n    });\n\n    it('should say \"Welcome\" in English', function() {\n      sailsApp.hooks.i18n.setLocale('en');\n      assert.equal(sailsApp.__('Welcome'), 'Welcome');\n    });\n\n    it('should say \"Bienvenido\" in Spanish', function() {\n      sailsApp.hooks.i18n.setLocale('es');\n      assert.equal(sailsApp.__('Welcome'), 'Bienvenido');\n    });\n\n    it('should support `req.getLocale()` to get the current locale.', function(done) {\n      // sailsApp.hooks.i18n.setLocale('es');\n      sailsApp.request({url: 'GET /test_req_getlocale', headers: {'Accept-language': 'es'}}, function(err, res, body) {\n        if (err) { return done(err); }\n        assert.equal(body, 'es');\n        return done();\n      });\n    });\n\n    it('should support `req.setLocale()` to set the current locale.', function(done) {\n      sailsApp.request('GET /test_req_setlocale', function(err, res, body) {\n        if (err) { return done(err); }\n        assert.equal(body, 'Bienvenido');\n        return done();\n      });\n    });\n\n    it('should support `sails.hooks.i18n.getLocale()` to get the current locale.', function(done) {\n      sailsApp.hooks.i18n.setLocale('es');\n      sailsApp.request('GET /test_sails_getlocale', function(err, res, body) {\n        if (err) { return done(err); }\n        assert.equal(body, 'es');\n        return done();\n      });\n    });\n\n    it('should support `sails.hooks.i18n.setLocale()` to set the current locale.', function(done) {\n      sailsApp.hooks.i18n.setLocale('fr');\n      sailsApp.request('GET /test_sails_setlocale', function(err, res, body) {\n        if (err) { return done(err); }\n        assert.equal(body, 'Bienvenue');\n        return done();\n      });\n    });\n\n    it('should say \"Bienvenue\" in French', function() {\n      sailsApp.hooks.i18n.setLocale('fr');\n      assert.equal(sailsApp.__('Welcome'), 'Bienvenue');\n    });\n\n    it('should say \"Willkommen\" in German', function() {\n      sailsApp.hooks.i18n.setLocale('de');\n      assert.equal(sailsApp.__('Welcome'), 'Willkommen');\n    });\n  });\n\n});//</describe i18n tests>\n\n\n\n\n//  ██╗ ██╗ █████╗ ███╗   ██╗     ██████╗ ██████╗ ███╗   ██╗███████╗██╗ ██████╗\n//  ██║███║██╔══██╗████╗  ██║    ██╔════╝██╔═══██╗████╗  ██║██╔════╝██║██╔════╝\n//  ██║╚██║╚█████╔╝██╔██╗ ██║    ██║     ██║   ██║██╔██╗ ██║█████╗  ██║██║  ███╗\n//  ██║ ██║██╔══██╗██║╚██╗██║    ██║     ██║   ██║██║╚██╗██║██╔══╝  ██║██║   ██║\n//  ██║ ██║╚█████╔╝██║ ╚████║    ╚██████╗╚██████╔╝██║ ╚████║██║     ██║╚██████╔╝\n//  ╚═╝ ╚═╝ ╚════╝ ╚═╝  ╚═══╝     ╚═════╝ ╚═════╝ ╚═╝  ╚═══╝╚═╝     ╚═╝ ╚═════╝\n//\ndescribe('i18n Config ::', function() {\n\n  var appName = 'testApp';\n  var sailsApp;\n\n  describe('with locales generate by config', function() {\n\n    before(function (done) {\n      appHelper.build(function(err) {\n        if (err) {return done(err);}\n        var config = 'module.exports.i18n = { locales: [\\'en\\', \\'de\\'], defaultLocale: \\'de\\' };';\n        fs.writeFileSync(path.resolve('../', appName, 'config/i18n.js'), config);\n        appHelper.lift({\n          log: {level: 'silent'}\n        }, function(err, sails) {\n          if (err) {\n            return done(err);\n          }\n          sailsApp = sails;\n          return done();\n        });\n      });\n    });\n\n    after(function(done) {\n      sailsApp.lower(function() {\n        process.chdir('../');\n        appHelper.teardown();\n        return done();\n      });\n    });\n\n    it('should say \"Willkommen\" by defaultLocale', function() {\n      //see https://github.com/balderdashy/sails-generate-backend/pull/10\n      assert(sailsApp.__('Welcome') === 'Willkommen');\n    });\n\n    it('should autoupdate the file', function(done) {\n      sailsApp.__('Login');\n      fs.readFile(path.resolve('../', appName, 'config/locales/de.json'), 'utf8', function read(err, data) {\n        if (err) {\n          return done(err);\n        }\n\n        var de = JSON.parse(data);\n        assert(de['Login'] === 'Login');\n        return done();\n\n      });\n    });\n  });\n\n});\n\n\n"
  },
  {
    "path": "test/integration/hook.policies.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar assert = require('assert');\nvar tmp = require('tmp');\nvar _ = require('@sailshq/lodash');\nvar async = require('async');\n\nvar Filesystem = require('machinepack-fs');\n\nvar Sails = require('../../lib').constructor;\n\ntmp.setGracefulCleanup();\n\n\ndescribe('policies :: ', function() {\n\n  describe('basic usage :: ', function() {\n\n    var curDir, tmpDir;\n\n    before(function() {\n      // Cache the current working directory.\n      curDir = process.cwd();\n      // Create a temp directory.\n      tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n\n      Filesystem.writeSync({\n        force: true,\n        destination: 'api/policies/err.js',\n        string: 'module.exports = function(req, res, next) {return res.serverError(\\'Test Error\\');}'\n      }).execSync();\n\n    });\n\n    after(function() {\n      process.chdir(curDir);\n    });\n\n    it('should load policies from disk and merge them with programmatically added policies', function(done) {\n\n      (new Sails()).load({\n        hooks: {\n          grunt: false, views: false, pubsub: false\n        },\n        blueprints: {\n          actions: false,\n          rest: false,\n          shortcuts: false\n        },\n        log: {level: 'silent'},\n        controllers: {},\n        routes: {},\n        policies: {\n          moduleDefinitions: {\n            'foo': function(req, res, next) {return res.serverError('foo');}\n          },\n        }\n      }, function(err, sailsApp) {\n        if (err) { return done(err); }\n        assert.equal(_.keys(sailsApp.hooks.policies.middleware).length, 2);\n        assert(sailsApp.hooks.policies.middleware.foo);\n        assert(sailsApp.hooks.policies.middleware.err);\n        return done();\n      });\n    });\n\n    describe('error policies :: ', function() {\n\n      var sailsApp;\n      var policyMap = {};\n\n      beforeEach(function(done) {\n        (new Sails()).load({\n          hooks: {\n            grunt: false, views: false, pubsub: false\n          },\n          blueprints: {\n            actions: false,\n            rest: false,\n            shortcuts: false\n          },\n          log: {level: 'silent'},\n          controllers: {\n            moduleDefinitions: {\n              'user': function(req, res) { return res.send('user'); },\n              'user/foo': function(req, res) { return res.send('user.foo'); },\n              'user/foo/bar': function(req, res) { return res.send('user.foo.bar'); }\n            }\n          },\n          routes: {\n            '/user': 'user',\n            '/user-foo': 'user/foo',\n            '/user-foo-bar': 'user/foo/bar'\n          },\n          policies: policyMap\n\n\n        }, function(err, _sails) {\n          if (err) { return done(err); }\n          sailsApp = _sails;\n          return done();\n        });\n      });\n\n      afterEach(function(done){\n        if (sailsApp) {sailsApp.lower(done);}\n        else {\n          return done();\n        }\n      });\n\n      describe('with a single, defined \"error\" policy mapped to user/*', function() {\n\n        before(function() {\n          policyMap = { 'user/*': ['err'] };\n        });\n\n        it('the policy should apply to all user/* actions', function(done) {\n\n          async.each(['/user', '/user-foo', '/user-foo-bar'], function(url, cb) {\n            sailsApp.request({\n              url: url,\n              method: 'GET'\n            }, function (err, response, data) {\n              if (!err) {\n                return cb(new Error('For URL ' + url + ', expected server error, got: ' + data));\n              }\n              assert.equal(err.body, 'Test Error');\n              return cb();\n            });\n\n          }, function (err) {\n            if (err) {return done(err);}\n            return done();\n          });\n        });\n\n      });\n\n      describe('with a `false` policy mapped to user/*', function() {\n\n        before(function() {\n          policyMap = { 'user/*': false };\n        });\n\n        it('the policy should apply to all user/* actions', function(done) {\n\n          async.each(['/user', '/user-foo', '/user-foo-bar'], function(url, cb) {\n            sailsApp.request({\n              url: url,\n              method: 'GET'\n            }, function (err, response, data) {\n              if (!err) {\n                return cb(new Error('For URL ' + url + ', expected server error, got: ' + data));\n              }\n              assert.equal(err.status, 403);\n              return cb();\n            });\n\n          }, function (err) {\n            if (err) {return done(err);}\n            return done();\n          });\n        });\n\n      });\n\n      describe('with a defined \"error\" policy mapped to user/* and a \"blank\" policy mapped to user/foo', function() {\n\n        before(function() {\n          policyMap = {\n            'user/*': ['err'],\n            'user/Foo': [] // <-- Note the uppercase F -- this should not matter.\n          };\n        });\n\n        it('the policy should apply to actions `user` and `user/foo/bar`', function(done) {\n\n          async.each(['/user', '/user-foo-bar'], function(url, cb) {\n            sailsApp.request({\n              url: url,\n              method: 'GET'\n            }, function (err, response, data) {\n              if (!err) {\n                return cb(new Error('For URL ' + url + ', expected server error, got: ' + data));\n              }\n              assert.equal(err.body, 'Test Error');\n              return cb();\n            });\n\n          }, function (err) {\n            if (err) {return done(err);}\n            return done();\n          });\n        });\n\n        it('the policy should NOT apply to actions `user/foo`', function(done) {\n\n          sailsApp.request({\n            url: '/user-foo',\n            method: 'GET'\n          }, function (err, response, data) {\n            if (err) {\n              return done(new Error('For URL /user-foo, expected \"user-foo\", got: ' + err));\n            }\n            assert.equal(data, 'user.foo');\n            return done();\n          });\n\n        });\n\n      });\n\n      describe('with a defined \"error\" policy mapped to user/* and a `true` policy mapped to user/foo', function() {\n\n        before(function() {\n          policyMap = {\n            'user/*': ['err'],\n            'user/foo': true\n          };\n        });\n\n        it('the policy should apply to actions `user` and `user/foo/bar`', function(done) {\n\n          async.each(['/user', '/user-foo-bar'], function(url, cb) {\n            sailsApp.request({\n              url: url,\n              method: 'GET'\n            }, function (err, response, data) {\n              if (!err) {\n                return cb(new Error('For URL ' + url + ', expected server error, got: ' + data));\n              }\n              assert.equal(err.body, 'Test Error');\n              return cb();\n            });\n\n          }, function (err) {\n            if (err) {return done(err);}\n            return done();\n          });\n        });\n\n        it('the policy should NOT apply to actions `user/foo`', function(done) {\n\n          sailsApp.request({\n            url: '/user-foo',\n            method: 'GET'\n          }, function (err, response, data) {\n            if (err) {\n              return cb(new Error('For URL ' + url + ', expected \"user-foo\", got: ' + err));\n            }\n            assert.equal(data, 'user.foo');\n            return done();\n          });\n\n        });\n\n      });\n\n\n      describe('with a defined \"error\" policy mapped to * and a `true` policy mapped to user/foo', function() {\n\n        before(function() {\n          policyMap = {\n            '*': ['err'],\n            'user/foo': true\n          };\n        });\n\n        it('the policy should apply to actions `user` and `user/foo/bar`', function(done) {\n\n          async.each(['/user', '/user-foo-bar'], function(url, cb) {\n            sailsApp.request({\n              url: url,\n              method: 'GET'\n            }, function (err, response, data) {\n              if (!err) {\n                return cb(new Error('For URL ' + url + ', expected server error, got: ' + data));\n              }\n              assert.equal(err.body, 'Test Error');\n              return cb();\n            });\n\n          }, function (err) {\n            if (err) {return done(err);}\n            return done();\n          });\n        });\n\n        it('the policy should NOT apply to actions `user/foo`', function(done) {\n\n          sailsApp.request({\n            url: '/user-foo',\n            method: 'GET'\n          }, function (err, response, data) {\n            if (err) {\n              return done(new Error('For URL /user-foo, expected \"user-foo\", got: ' + err));\n            }\n            assert.equal(data, 'user.foo');\n            return done();\n          });\n\n        });\n\n      });\n\n      describe('with a defined \"error\" policy mapped to user/* and a \"blank\" policy mapped to user/foo/*', function() {\n\n        before(function() {\n          policyMap = {\n            'user/*': ['err'],\n            'user/foo/*': []\n          };\n        });\n\n        it('the policy should apply to actions `user`', function(done) {\n\n          async.each(['/user'], function(url, cb) {\n            sailsApp.request({\n              url: url,\n              method: 'GET'\n            }, function (err, response, data) {\n              if (!err) {\n                return cb(new Error('For URL ' + url + ', expected server error, got: ' + data));\n              }\n              assert.equal(err.body, 'Test Error');\n              return cb();\n            });\n\n          }, function (err) {\n            if (err) {return done(err);}\n            return done();\n          });\n        });\n\n        it('the policy should NOT apply to actions `user/foo`', function(done) {\n\n          sailsApp.request({\n            url: '/user-foo',\n            method: 'GET'\n          }, function (err, response, data) {\n            if (err) {\n              return cb(new Error('For URL ' + url + ', expected \"user-foo\", got: ' + err));\n            }\n            assert.equal(data, 'user.foo');\n            return done();\n          });\n\n        });\n\n        it('the policy should NOT apply to actions `user/foo/bar`', function(done) {\n\n          sailsApp.request({\n            url: '/user-foo-bar',\n            method: 'GET'\n          }, function (err, response, data) {\n            if (err) {\n              return cb(new Error('For URL ' + url + ', expected \"user-foo-bar\", got: ' + err));\n            }\n            assert.equal(data, 'user.foo.bar');\n            return done();\n          });\n\n        });\n\n      });\n\n\n    });\n\n    describe('pass-thru policies', function() {\n\n      var sailsApp;\n      var policyMap = {};\n\n      beforeEach(function(done) {\n        (new Sails()).load({\n          hooks: {\n            grunt: false, views: false, pubsub: false\n          },\n          blueprints: {\n            actions: false,\n            rest: false,\n            shortcuts: false\n          },\n          log: {level: 'silent'},\n          controllers: {\n            moduleDefinitions: {\n              'user': function(req, res) { return res.json({action: 'user', animals: {cat: req.options.cat, owl: req.options.owl}}); },\n              'user/foo': function(req, res) { return res.json({action: 'user.foo', animals: {cat: req.options.cat, owl: req.options.owl}}); },\n              'user/foo/bar': function(req, res) { return res.json({action: 'user.foo.bar', animals: {cat: req.options.cat, owl: req.options.owl}}); },\n              'user/foo/baz': function(req, res) { throw new Error('should never be reached!'); }\n            }\n          },\n          routes: {\n            '/user': 'user',\n            '/user-foo': 'user/foo',\n            '/user-foo-bar': 'user/foo/bar',\n            '/user-foo-baz': 'user/foo/baz'\n          },\n          policies: _.extend({\n            moduleDefinitions: {\n              'add-owl': function(req, res, next) {req.options.owl = 'hoot'; return next();},\n              'add-cat': function(req, res, next) {req.options.cat = 'meow'; return next();}\n            },\n          },policyMap)\n\n\n        }, function(err, _sails) {\n          if (err) { return done(err); }\n          sailsApp = _sails;\n          return done();\n        });\n      });\n\n      afterEach(function(done){\n        if (sailsApp) {sailsApp.lower(done);}\n        else {\n          return done();\n        }\n      });\n\n      describe('with a single, defined \"pass-thru\" policy mapped to user.*', function() {\n\n        before(function() {\n          policyMap = { 'user/*': ['add-owl'] };\n        });\n\n        it('the policy should apply to all user/* actions', function(done) {\n\n          async.each(['/user', '/user-foo', '/user-foo-bar'], function(url, cb) {\n            sailsApp.request({\n              url: url,\n              method: 'GET'\n            }, function (err, response, data) {\n              if (err) {\n                return cb(new Error('For URL ' + url + ', expected successful response, got: ' + err));\n              }\n              assert.equal(data.action, url.substr(1).replace(/-/g,'.'));\n              assert.equal(data.animals.owl, 'hoot');\n              assert(_.isUndefined(data.animals.cat));\n              return cb();\n            });\n\n          }, function (err) {\n            if (err) {return done(err);}\n            return done();\n          });\n        });\n\n      });\n\n      describe('with two defined \"pass-thru\" policies chained to user/*', function() {\n\n        before(function() {\n          policyMap = { 'user/*': ['add-owl', 'add-cat'] };\n        });\n\n        it('the policies should apply to all user/* actions', function(done) {\n\n          async.each(['/user', '/user-foo', '/user-foo-bar'], function(url, cb) {\n            sailsApp.request({\n              url: url,\n              method: 'GET'\n            }, function (err, response, data) {\n              if (err) {\n                return cb(new Error('For URL ' + url + ', expected successful response, got: ' + err));\n              }\n              assert.equal(data.action, url.substr(1).replace(/-/g,'.'));\n              assert.equal(data.animals.owl, 'hoot');\n              assert.equal(data.animals.cat, 'meow');\n              return cb();\n            });\n\n          }, function (err) {\n            if (err) {return done(err);}\n            return done();\n          });\n        });\n\n      });\n\n      describe('with the \"add-owl\" policy on user/*, and \"add-cat\" on user/foo/bar', function() {\n\n        before(function() {\n          policyMap = { 'user/*': ['add-owl'], 'user/foo/bar': ['add-cat'] };\n        });\n\n        it('the \"add-owl\" policy (and NOT the \"add-cat\" policy) should apply to the \"user\" and \"user/foo\" actions', function(done) {\n\n          async.each(['/user', '/user-foo'], function(url, cb) {\n            sailsApp.request({\n              url: url,\n              method: 'GET'\n            }, function (err, response, data) {\n              if (err) {\n                return cb(new Error('For URL ' + url + ', expected successful response, got: ' + err));\n              }\n              assert.equal(data.action, url.substr(1).replace(/-/g,'.'));\n              assert.equal(data.animals.owl, 'hoot');\n              assert(_.isUndefined(data.animals.cat));\n              return cb();\n            });\n\n          }, function (err) {\n            if (err) {return done(err);}\n            return done();\n          });\n        });\n\n        it('the \"add-cat\" policy (and NOT the \"add-owl\" policy) should apply to the \"user/foo/bar\" action', function(done) {\n\n          sailsApp.request({\n            url: '/user-foo-bar',\n            method: 'GET'\n          }, function (err, response, data) {\n            if (err) {\n              return cb(new Error('For URL ' + url + ', expected successful response, got: ' + err));\n            }\n            assert.equal(data.action, 'user.foo.bar');\n            assert.equal(data.animals.cat, 'meow');\n            assert(_.isUndefined(data.animals.owl));\n            return done();\n          });\n\n        });\n\n      });\n\n      describe('(using controller config) with the \"add-owl\" policy on user/*, and \"add-cat\" on user/foo/bar', function() {\n\n        before(function() {\n          policyMap = {\n            'User': {\n              '*': 'add-OWL', // Capitalization shouldn't matter for policy name...\n              'foo': ['add-cat']\n            },\n            'user/FooController': {\n              '*': false,\n              'Bar': 'add-cat' // Capitalization shouldn't matter for action name...\n            }\n          };\n        });\n\n        it('the \"add-owl\" policy (and NOT the \"add-cat\" policy) should apply to the \"user\" action', function(done) {\n\n          sailsApp.request({\n            url: '/user',\n            method: 'GET'\n          }, function (err, response, data) {\n            if (err) {\n              return done(new Error('For URL \\'/user\\', expected successful response, got: ' + err));\n            }\n            assert.equal(data.action, 'user');\n            assert.equal(data.animals.owl, 'hoot');\n            assert(_.isUndefined(data.animals.cat));\n            return done();\n          });\n\n        });\n\n        it('the \"add-cat\" policy (and NOT the \"add-owl\" policy) should apply to the \"user/foo\" and \"user/foo/bar\" actions', function(done) {\n\n          async.each(['/user-foo', '/user-foo-bar'], function(url, cb) {\n            sailsApp.request({\n              url: url,\n              method: 'GET'\n            }, function (err, response, data) {\n              if (err) {\n                return cb(new Error('For URL ' + url + ', expected successful response, got: ' + err));\n              }\n              assert.equal(data.action, url.substr(1).replace(/-/g,'.'));\n              assert.equal(data.animals.cat, 'meow');\n              assert(_.isUndefined(data.animals.owl));\n              return cb();\n            });\n\n          }, function (err) {\n            if (err) {return done(err);}\n            return done();\n          });\n\n        });\n\n        it('the \"user/foo/baz\" route should always return \"Forbidden\"', function(done) {\n\n          sailsApp.request({\n            url: '/user-foo-baz',\n            method: 'GET'\n          }, function (err, response, data) {\n            if (!err) {\n              return done(new Error('For URL \\'/user/foo/baz\\', expected server error, got: ' + data));\n            }\n            assert.equal(err.status, 403);\n            return done();\n          });\n\n        });\n\n      });\n\n    });\n\n\n    describe('Adding policies directly to routes', function() {\n\n      var sailsApp;\n      var policyMap = {};\n\n      before(function(done) {\n        (new Sails()).load({\n          hooks: {\n            grunt: false, views: false, pubsub: false\n          },\n          blueprints: {\n            actions: false,\n            rest: false,\n            shortcuts: false\n          },\n          log: {level: 'silent'},\n          controllers: {\n            moduleDefinitions: {\n              'user': function(req, res) { return res.json({action: 'user', foo: req.options.foo, animals: {cat: req.options.cat, owl: req.options.owl}}); },\n              'user/foo': function(req, res) { return res.json({action: 'user/foo', foo: req.options.foo,  animals: {cat: req.options.cat, owl: req.options.owl}}); },\n              'user/foo/bar': function(req, res) { return res.json({action: 'user/foo/bar', foo: req.options.foo, animals: {cat: req.options.cat, owl: req.options.owl}}); }\n            }\n          },\n          routes: {\n            '/user': [{ policy: 'add-owl', foo: 'bar' }, 'user'],\n            '/user-foo': [{ policy: 'add-cat' }, 'user/foo'],\n            '/user-foo-bar': [ { policy: 'add-owl'}, { policy: 'add-cat' }, 'user/foo/bar']\n          },\n          policies: _.extend({\n            moduleDefinitions: {\n              'add-owl': function(req, res, next) {req.options.owl = 'hoot'; return next();},\n              'add-cat': function(req, res, next) {req.options.cat = 'meow'; return next();}\n            },\n          },policyMap)\n\n\n        }, function(err, _sails) {\n          if (err) { return done(err); }\n          sailsApp = _sails;\n          return done();\n        });\n      });\n\n      after(function(done){\n        if (sailsApp) {sailsApp.lower(done);}\n        else {\n          return done();\n        }\n      });\n\n      it('should add the correct policy to `/user` and retain extra options', function(done) {\n\n        sailsApp.request({\n          url: '/user',\n          method: 'GET'\n        }, function (err, response, data) {\n          if (err) {\n            return cb(new Error('For URL ' + url + ', expected successful response, got: ' + err));\n          }\n          assert.equal(data.action, 'user');\n          assert.equal(data.animals.owl, 'hoot');\n          assert(_.isUndefined(data.animals.cat));\n          assert.equal(data.foo, 'bar');\n          return done();\n        });\n\n      });\n\n      it('should add the correct policy to `/user-foo`', function(done) {\n\n        sailsApp.request({\n          url: '/user-foo',\n          method: 'GET'\n        }, function (err, response, data) {\n          if (err) {\n            return cb(new Error('For URL ' + url + ', expected successful response, got: ' + err));\n          }\n          assert.equal(data.action, 'user/foo');\n          assert.equal(data.animals.cat, 'meow');\n          assert(_.isUndefined(data.animals.owl));\n          assert(_.isUndefined(data.foo));\n          return done();\n        });\n\n      });\n\n      it('should add the correct policies to `/user-foo-bar`', function(done) {\n\n        sailsApp.request({\n          url: '/user-foo-bar',\n          method: 'GET'\n        }, function (err, response, data) {\n          if (err) {\n            return cb(new Error('For URL ' + url + ', expected successful response, got: ' + err));\n          }\n          assert.equal(data.action, 'user/foo/bar');\n          assert.equal(data.animals.cat, 'meow');\n          assert.equal(data.animals.owl, 'hoot');\n          assert(_.isUndefined(data.foo));\n          return done();\n        });\n\n      });\n\n    });\n\n\n  });\n\n  describe('with invalid configuration :: ', function() {\n\n    describe('when a non-function policy is specified on disk', function() {\n\n      var curDir, tmpDir;\n\n      before(function(done) {\n\n        // Cache the current working directory.\n        curDir = process.cwd();\n        // Create a temp directory.\n        tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n        // Switch to the temp directory.\n        process.chdir(tmpDir.name);\n\n        Filesystem.writeSync({\n          force: true,\n          destination: 'api/policies/err.js',\n          string: 'module.exports = {\"foo\": \"bar\"}'\n        }).execSync();\n\n        return done();\n      });\n\n      after(function() {\n        process.chdir(curDir);\n      });\n\n      it('Sails should fail to lift', function(done) {\n\n        (new Sails()).load({\n          hooks: {\n            grunt: false, views: false, pubsub: false\n          },\n          log: {level: 'silent'}\n        }, function(err, sailsApp) {\n          if (!err) {\n            sailsApp.lower(function() {\n              return done(new Error('Expected error lifting, but didn\\'t get one!'));\n            });\n          }\n          return done();\n        });\n\n      });\n\n    });\n\n    describe('when a non-function policy is specified programmatically', function() {\n\n      it('Sails should fail to lift', function(done) {\n\n        (new Sails()).load({\n          hooks: {\n            grunt: false, views: false, pubsub: false, orm: false\n          },\n          log: {level: 'silent'},\n          policies: {\n            moduleDefinitions: {\n              foo: 'bar'\n            }\n          }\n        }, function(err, sailsApp) {\n          if (!err) {\n            sailsApp.lower(function() {\n              return done(new Error('Expected error lifting, but didn\\'t get one!'));\n            });\n          }\n          return done();\n        });\n\n      });\n\n    });\n\n\n  });\n\n});\n"
  },
  {
    "path": "test/integration/hook.pubsub.modelEvents.noSubscribers.test.js",
    "content": "/**\n * Test dependencies\n */\nvar util = require('util');\nvar assert = require('assert');\nvar socketHelper = require('./helpers/socketHelper.js');\nvar appHelper = require('./helpers/appHelper');\n\n\n\n/**\n * Errors\n */\nvar Err = {\n  badResponse: function(response) {\n    return 'Wrong server response!  Response :::\\n' + util.inspect(response);\n  }\n};\n\n\n\n\ndescribe('pubsub :: ', function() {\n\n  var sailsprocess;\n  var socket1;\n  var socket2;\n  var appName = 'testApp';\n\n  describe('Model events', function() {\n\n    describe('when no one is subscribed to user #1 and User has no watchers ', function() {\n\n      before(function(done) {\n        appHelper.buildAndLiftWithTwoSockets(appName, function(err, sails, _socket1, _socket2) {\n          if (err) {\n            throw new Error(err);\n          }\n          sailsprocess = sails;\n          socket1 = _socket1;\n          socket2 = _socket2;\n          done();\n        });\n      });\n\n      after(function(done) {\n        socket1.disconnect();\n        socket2.disconnect();\n        process.chdir('../');\n        appHelper.teardown();\n        if (sailsprocess) {\n          return sailsprocess.lower(function() {\n            setTimeout(done, 100);\n          });\n        }\n        return done();\n      });\n\n      this.slow(3000);\n\n      afterEach(function(done) {\n        socket2.removeAllListeners();\n        done();\n      });\n\n      it('a post request to /user should result in no `user` events being received', function(done) {\n\n        socket2.on('user', function(message) {\n          assert(false, 'User event received by socket 2 when it should not have been!');\n        });\n        socket1.post('/user', {\n          name: 'scott'\n        });\n        setTimeout(done, 1000);\n\n      });\n\n      it('updating the user via PUT /user/1 should result in no `user` events being received', function(done) {\n\n        socket2.on('user', function(message) {\n          assert(false, 'User event received by socket 2 when it should not have been!');\n        });\n\n        socket1.put('/user/1', {\n          name: 'joe'\n        });\n        setTimeout(done, 1000);\n\n      });\n\n      it('adding a pet to the user via POST /pet should result in no `user` events being received', function(done) {\n\n        socket2.on('user', function(message) {\n          assert(false, 'User event received by socket 2 when it should not have been!');\n        });\n\n        socket1.post('/pet', {\n          name: 'rex',\n          owner: 1\n        });\n        setTimeout(done, 1000);\n\n      });\n\n      it('removing a pet from the user via DELETE /pet/1 should result in no `user` events being received', function(done) {\n\n        socket2.on('user', function(message) {\n          assert(false, 'User event received by socket 2 when it should not have been!');\n        });\n\n        socket1.delete('/pet/1');\n        setTimeout(done, 1000);\n\n      });\n\n      it('deleting the user via DELETE /user/1 should result in no `user` events being received', function(done) {\n\n        socket2.on('user', function(message) {\n          assert(false, 'User event received by socket 2 when it should not have been!');\n        });\n\n        socket1.delete('/user/1');\n        setTimeout(done, 1000);\n\n      });\n\n    });\n\n  });\n});\n"
  },
  {
    "path": "test/integration/hook.pubsub.modelEvents.subscribers.test.js",
    "content": "/**\n * Test dependencies\n */\n\nvar util = require('util');\nvar path = require('path');\nvar _ = require('@sailshq/lodash');\nvar assert = require('assert');\nvar async = require('async');\nvar socketHelper = require('./helpers/socketHelper.js');\nvar appHelper = require('./helpers/appHelper');\nvar fs = require('fs-extra');\n\n\n/**\n * Errors\n */\nvar Err = {\n  badResponse: function(response) {\n    return 'Wrong server response!  Response :::\\n' + util.inspect(response);\n  }\n};\n\n\ndescribe('pubsub :: ', function() {\n\n  describe('Model events', function() {\n\n    describe('when a socket is watching Users ', function() {\n      var socket1;\n      var socket2;\n      var appName = 'testApp';\n      var sailsApp;\n      var bootstrapModels = {};\n      var bootstrappedData = {};\n\n      before(appName, function(done) {\n        appHelper.build(done);\n      });\n\n      beforeEach(function (done) {\n        appHelper.liftWithTwoSockets({\n          log: {level: 'warn'},\n          models: {\n            migrate: 'drop'\n          }\n        }, function(err, sails, _socket1, _socket2) {\n          if (err) {\n            return done(err);\n          }\n          sailsApp = sails;\n          socket1 = _socket1;\n          socket2 = _socket2;\n\n          async.eachSeries(_.keys(bootstrapModels), function(model, nextModel) {\n            sailsApp.models[model].createEach(bootstrapModels[model]).meta({fetch: true}).exec(function(err, records) {\n              if (err) {\n                return nextModel(err);\n              }\n              bootstrappedData[model] = records;\n              return nextModel();\n            });\n          }, function(err) {\n            if (err) {return done(err);}\n            // Subscribe to all users and new user notifications.\n            socket1.get('/user', function(body, jwr) {\n              if (jwr.error) { return done(new Error('Error in tests.  Details:' + JSON.stringify(jwr))); }\n              // Subscribe to all pets and new pet notifications.\n              socket1.get('/pet', function(body, jwr) {\n                if (jwr.error) { return done(new Error('Error in tests.  Details:' + JSON.stringify(jwr))); }\n                done();\n              });\n            });\n          });\n        });\n      });\n\n      afterEach(function(done) {\n        bootstrapModels = {};\n        bootstrappedData = {};\n        socket1.removeAllListeners();\n        socket2.removeAllListeners();\n        var dir = path.resolve('.tmp', 'localDiskDb');\n        if (fs.existsSync(dir)) {\n          fs.removeSync(dir);\n        }\n        setTimeout(function(){sailsApp.lower(done);},100);\n      });\n\n      after(function(done) {\n        // Add a delay before killing the app to account for any queries that\n        // haven't been run by the blueprints yet; otherwise they might casue an error\n        setTimeout(function() {\n          process.chdir('../');\n          appHelper.teardown(appName);\n          return done();\n        }, 500);\n\n      });//</after>\n\n      //   ██████╗██████╗ ███████╗ █████╗ ████████╗███████╗\n      //  ██╔════╝██╔══██╗██╔════╝██╔══██╗╚══██╔══╝██╔════╝\n      //  ██║     ██████╔╝█████╗  ███████║   ██║   █████╗\n      //  ██║     ██╔══██╗██╔══╝  ██╔══██║   ██║   ██╔══╝\n      //  ╚██████╗██║  ██║███████╗██║  ██║   ██║   ███████╗\n      //   ╚═════╝╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝   ╚═╝   ╚══════╝\n\n      describe('creating a new user with POST /user', function() {\n        it('should cause a `created` notification to be received by all `user` subscribers', function(done) {\n\n          expectNotifications({\n            user: {\n              created: {\n                verb: 'created',\n                id: 1,\n                'data.name': 'bert'\n              }\n            }\n          }, done);\n\n          socket2.post('/user', { name: 'bert' }, function (body, jwr) {\n            if (jwr.error) { return done(jwr.error); }\n            // Otherwise, the event handler above should fire (or this test will time out and fail).\n          });\n\n        });\n      });\n\n      describe('creating a new pet with POST /pet that includes a value for a singular association in a one-to-many relationship', function () {\n\n        before(function() {\n          bootstrapModels = {\n            user: [{name: 'bob'}]\n          };\n        });\n\n        it('should cause an `addedTo` notification to be received by all subscribers to the child record', function(done) {\n\n          expectNotifications({\n            pet: {\n              created: {\n                verb: 'created',\n                id: 1,\n                'data.name': 'alice'\n              }\n            },\n            user: {\n              addedTo: {\n                verb: 'addedTo',\n                id: 1,\n                addedId: 1,\n                attribute: 'pets'\n              }\n            }\n          }, done);\n\n          socket2.post('/pet', { name: 'alice', owner: 1 }, function (body, jwr) {\n            if (jwr.error) { return done(jwr.error); }\n            // Otherwise, the event handler above should fire (or this test will time out and fail).\n          });\n\n\n        });\n      });\n\n      describe('creating a new user with POST /user that includes a value for a plural association in a many-to-one relationship', function () {\n\n        describe('and the other side was not already linked to a record', function() {\n\n          before(function() {\n            bootstrapModels = {\n              pet: [{ name: 'alice'}, {name: 'mr. bailey'}, {name: 'tex'}]\n            };\n          });\n\n          it('should cause an `updated` notification to be received by all subscribers to the child record', function(done) {\n\n            expectNotifications({\n              user: {\n                created: {\n                  verb: 'created',\n                  id: 1,\n                  'data.name': 'bert'\n                }\n              },\n              pet: {\n                updatedAlice: {\n                  verb: 'updated',\n                  id: 1,\n                  'data.owner': 1\n                },\n                updatedBailey: {\n                  verb: 'updated',\n                  id: 2,\n                  'data.owner': 1\n                },\n                updatedTex: {\n                  verb: 'updated',\n                  id: 3,\n                  'data.owner': 1\n                }\n              }\n            }, done);\n\n            socket2.post('/user', { name: 'bert', pets: [1, 2, 3] }, function (body, jwr) {\n              if (jwr.error) { return done(jwr.error); }\n              // Otherwise, the event handler above should fire (or this test will time out and fail).\n            });\n\n          });\n\n        });\n\n        describe('and the other side was already linked to a record', function() {\n\n          before(function() {\n            bootstrapModels = {\n              user: [{ name: 'bert' }],\n              pet: [{ name: 'alice', owner: 1}, {name: 'mr. bailey'}, {name: 'tex', owner: 1}]\n            };\n          });\n\n          it('should cause an `updated` notification to be received by all subscribers to the child record, and a `removedFrom` notification to be received by all subscribers to the child\\'s former parent record', function(done) {\n\n            expectNotifications({\n              user: {\n                created: {\n                  verb: 'created',\n                  id: 2,\n                  'data.name': 'ernie'\n                },\n                removedAlice: {\n                  verb: 'removedFrom',\n                  id: 1,\n                  removedId: 1,\n                  attribute: 'pets'\n                },\n                removedText: {\n                  verb: 'removedFrom',\n                  id: 1,\n                  removedId: 3,\n                  attribute: 'pets'\n                }\n              },\n              pet: {\n                updatedAlice: {\n                  verb: 'updated',\n                  id: 1,\n                  'data.owner': 2\n                },\n                updatedBailey: {\n                  verb: 'updated',\n                  id: 2,\n                  'data.owner': 2\n                },\n                updatedTex: {\n                  verb: 'updated',\n                  id: 3,\n                  'data.owner': 2\n                }\n              }\n            }, done);\n\n            socket2.post('/user', { name: 'ernie', pets: [1, 2, 3] }, function (body, jwr) {\n              if (jwr.error) { return done(jwr.error); }\n              // Otherwise, the event handler above should fire (or this test will time out and fail).\n            });\n          });\n\n        });\n\n      });\n\n\n      describe('creating a new user with POST /user that includes a value for a plural association in a many-to-many relationship', function () {\n\n        before(function() {\n          bootstrapModels = {\n            pet: [{ name: 'alice' }, {name: 'mr. bailey'}, {name: 'tex' }]\n          };\n        });\n\n        it('should cause an `addedTo` notification to be received by all subscribers to the child record', function(done) {\n\n          expectNotifications({\n            user: {\n              created: {\n                verb: 'created',\n                id: 1,\n                'data.name': 'bert'\n              }\n            },\n            pet: {\n              addedToAlice: {\n                verb: 'addedTo',\n                attribute: 'vets',\n                id: 1,\n                addedId: 1\n              },\n              addedToBailey: {\n                verb: 'addedTo',\n                attribute: 'vets',\n                id: 2,\n                addedId: 1\n              },\n              addedToTex: {\n                verb: 'addedTo',\n                attribute: 'vets',\n                id: 3,\n                addedId: 1\n              }\n            }\n          }, done);\n\n          socket2.post('/user', { name: 'bert', patients: [1, 2, 3] }, function (body, jwr) {\n            if (jwr.error) { return done(jwr.error); }\n            // Otherwise, the event handler above should fire (or this test will time out and fail).\n          });\n\n        });\n\n      });\n\n      //  ██╗   ██╗██████╗ ██████╗  █████╗ ████████╗███████╗\n      //  ██║   ██║██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔════╝\n      //  ██║   ██║██████╔╝██║  ██║███████║   ██║   █████╗\n      //  ██║   ██║██╔═══╝ ██║  ██║██╔══██║   ██║   ██╔══╝\n      //  ╚██████╔╝██║     ██████╔╝██║  ██║   ██║   ███████╗\n      //   ╚═════╝ ╚═╝     ╚═════╝ ╚═╝  ╚═╝   ╚═╝   ╚══════╝\n      //\n\n      describe('updating a record with PATCH /user', function() {\n\n        before(function() {\n          bootstrapModels = {\n            user: [{ name: 'bert' }]\n          };\n        });\n\n        it('should cause an `updated` notification to be received by all subscribers to the parent record', function(done) {\n\n          expectNotifications({\n            user: {\n              updated: {\n                verb: 'updated',\n                id: 1,\n                'data.name': 'ernie',\n                'previous.name': 'bert'\n              }\n            }\n          }, done);\n\n          socket2.patch('/user/1', { name: 'ernie' }, function (body, jwr) {\n            if (jwr.error) { return done(jwr.error); }\n            // Otherwise, the event handler above should fire (or this test will time out and fail).\n          });\n\n\n        });\n\n      });\n\n      describe('updating a record with PUT /pet with a new value for a singular association', function() {\n\n        describe('where the previous value was `null`', function() {\n\n          before(function() {\n            bootstrapModels = {\n              user: [{ name: 'bert' }],\n              pet: [{name: 'alice', owner: null}]\n            };\n          });\n\n          it('should cause an `addedTo` notification to be sent to all subscribers to the new parent record', function(done) {\n\n            expectNotifications({\n              pet: {\n                updated: {\n                  verb: 'updated',\n                  id: 1,\n                  'data.owner': 1,\n                  'previous.owner': null\n                }\n              },\n              user: {\n                addedTo: {\n                  verb: 'addedTo',\n                  id: 1,\n                  attribute: 'pets',\n                  addedId: 1\n                }\n              }\n            }, done);\n\n            socket2.patch('/pet/1', { owner: 1 }, function (body, jwr) {\n              if (jwr.error) { return done(jwr.error); }\n              // Otherwise, the event handler above should fire (or this test will time out and fail).\n            });\n\n          });\n\n\n        });\n\n        describe('where the previous value was not `null`', function() {\n\n          before(function() {\n            bootstrapModels = {\n              user: [{ name: 'bert' }, {name: 'ernie'}],\n              pet: [{name: 'alice', owner: 1}]\n            };\n          });\n\n          it('should cause a `removedFrom` notification to be sent to all subscribers to the old parent record', function(done) {\n\n            expectNotifications({\n              pet: {\n                updated: {\n                  verb: 'updated',\n                  id: 1,\n                  'data.owner': 2,\n                  'previous.owner.user_id': 1\n                }\n              },\n              user: {\n                addedTo: {\n                  verb: 'addedTo',\n                  id: 2,\n                  attribute: 'pets',\n                  addedId: 1\n                },\n                removedFrom: {\n                  verb: 'removedFrom',\n                  id: 1,\n                  attribute: 'pets',\n                  removedId: 1\n                }\n              }\n\n            }, done);\n\n            socket2.patch('/pet/1', { owner: 2 }, function (body, jwr) {\n              if (jwr.error) { return done(jwr.error); }\n              // Otherwise, the event handler above should fire (or this test will time out and fail).\n            });\n\n          });\n\n        });\n\n      });\n\n      //   █████╗ ██████╗ ██████╗\n      //  ██╔══██╗██╔══██╗██╔══██╗\n      //  ███████║██║  ██║██║  ██║\n      //  ██╔══██║██║  ██║██║  ██║\n      //  ██║  ██║██████╔╝██████╔╝\n      //  ╚═╝  ╚═╝╚═════╝ ╚═════╝\n      //\n\n      describe('adding a pet to a user with PUT /user/1/pets/1 where pets->owner is a many-to-one relationship', function () {\n\n        describe('and the other side was not already linked to a record', function() {\n\n          before(function() {\n            bootstrapModels = {\n              user: [{name: 'bert'}],\n              pet: [{ name: 'alice'}]\n            };\n          });\n\n          it('should cause an `addedTo` notification to be received by all subscribers to the parent record, and an `updated` notification to be received by all subscribers to the child record', function(done) {\n\n            expectNotifications({\n              user: {\n                addedTo: {\n                  verb: 'addedTo',\n                  id: 1,\n                  attribute: 'pets',\n                  addedId: 1\n                }\n              },\n              pet: {\n                updatedAlice: {\n                  verb: 'updated',\n                  id: 1,\n                  'data.owner': 1\n                }\n              }\n            }, done);\n\n            socket2.put('/user/1/pets/1', {}, function (body, jwr) {\n              if (jwr.error) { return done(jwr.error); }\n              // Otherwise, the event handler above should fire (or this test will time out and fail).\n            });\n\n          });\n\n        });\n\n        describe('and the other side was already linked to a record', function() {\n\n          before(function() {\n            bootstrapModels = {\n              user: [{ name: 'bert' }, { name: 'ernie' }],\n              pet: [{ name: 'alice', owner: 1}]\n            };\n          });\n\n          it('should cause an `updated` notification to be received by all subscribers to the child record, and a `removedFrom` notification to be received by all subscribers to the child\\'s former parent record', function(done) {\n\n            expectNotifications({\n              user: {\n                addedTo: {\n                  verb: 'addedTo',\n                  id: 2,\n                  attribute: 'pets',\n                  addedId: 1\n                },\n                removedFrom: {\n                  verb: 'removedFrom',\n                  id: 1,\n                  removedId: 1,\n                  attribute: 'pets'\n                }\n              },\n              pet: {\n                updatedAlice: {\n                  verb: 'updated',\n                  id: 1,\n                  'data.owner': 2\n                }\n              }\n            }, done);\n\n            socket2.put('/user/2/pets/1', {}, function (body, jwr) {\n              if (jwr.error) { return done(jwr.error); }\n              // Otherwise, the event handler above should fire (or this test will time out and fail).\n            });\n          });\n\n        });\n\n      });\n\n\n      describe('adding a patient to a user with PUT /user/1/patients/1 where patients->vets is a many-to-many relationship', function () {\n\n        before(function() {\n          bootstrapModels = {\n            user: [{name: 'bert'}],\n            pet: [{ name: 'alice' }]\n          };\n        });\n\n        it('should cause an `addedTo` notification to be received by all subscribers to the child record', function(done) {\n\n          expectNotifications({\n            pet: {\n              addedTo: {\n                verb: 'addedTo',\n                id: 1,\n                attribute: 'vets',\n                addedId: 1\n              }\n            },\n            user: {\n              addedTo: {\n                verb: 'addedTo',\n                id: 1,\n                attribute: 'patients',\n                addedId: 1\n              }\n            }\n\n          }, done);\n\n          socket2.put('/user/1/patients/1', {}, function (body, jwr) {\n            if (jwr.error) { return done(jwr.error); }\n            // Otherwise, the event handler above should fire (or this test will time out and fail).\n          });\n\n        });\n\n      });\n\n      //  ██████╗ ███████╗███╗   ███╗ ██████╗ ██╗   ██╗███████╗\n      //  ██╔══██╗██╔════╝████╗ ████║██╔═══██╗██║   ██║██╔════╝\n      //  ██████╔╝█████╗  ██╔████╔██║██║   ██║██║   ██║█████╗\n      //  ██╔══██╗██╔══╝  ██║╚██╔╝██║██║   ██║╚██╗ ██╔╝██╔══╝\n      //  ██║  ██║███████╗██║ ╚═╝ ██║╚██████╔╝ ╚████╔╝ ███████╗\n      //  ╚═╝  ╚═╝╚══════╝╚═╝     ╚═╝ ╚═════╝   ╚═══╝  ╚══════╝\n      //\n\n      describe('removing a pet from a user with DELETE /user/1/pets/1 where pets->owner is a many-to-one relationship', function () {\n\n        before(function() {\n          bootstrapModels = {\n            user: [{ name: 'bert' }],\n            pet: [{ name: 'alice', owner: 1}]\n          };\n        });\n\n        it('should cause a `removedFrom` notification to be received by all subscribers to the parent record, and an `updated` notification to be received by all subscribers to the child record', function(done) {\n\n          expectNotifications({\n            user: {\n              removedFrom: {\n                verb: 'removedFrom',\n                id: 1,\n                attribute: 'pets',\n                removedId: 1\n              },\n            },\n            pet: {\n              updatedAlice: {\n                verb: 'updated',\n                id: 1,\n                'data.owner': null\n              }\n            }\n          }, done);\n\n          socket2.delete('/user/1/pets/1', {}, function (body, jwr) {\n            if (jwr.error) { return done(jwr.error); }\n            // Otherwise, the event handler above should fire (or this test will time out and fail).\n          });\n        });\n\n      });\n\n      describe('removing a patient from a user with DELETE /user/1/patients/1 where patients->vets is a many-to-many relationship', function () {\n\n        before(function() {\n          bootstrapModels = {\n            user: [{name: 'bert'}],\n            pet: [{ name: 'alice', vets: [1] }]\n          };\n        });\n\n        it('should cause a `removedFrom` notification to be received by all subscribers to the child record', function(done) {\n\n          expectNotifications({\n            pet: {\n              removedFrom: {\n                verb: 'removedFrom',\n                id: 1,\n                attribute: 'vets',\n                removedId: 1\n              }\n            },\n            user: {\n              removedFrom: {\n                verb: 'removedFrom',\n                id: 1,\n                attribute: 'patients',\n                removedId: 1\n              }\n            }\n\n          }, done);\n\n          socket2.delete('/user/1/patients/1', {}, function (body, jwr) {\n            if (jwr.error) { return done(jwr.error); }\n            // Otherwise, the event handler above should fire (or this test will time out and fail).\n          });\n\n        });\n\n      });\n\n      //  ██████╗ ███████╗██████╗ ██╗      █████╗  ██████╗███████╗\n      //  ██╔══██╗██╔════╝██╔══██╗██║     ██╔══██╗██╔════╝██╔════╝\n      //  ██████╔╝█████╗  ██████╔╝██║     ███████║██║     █████╗\n      //  ██╔══██╗██╔══╝  ██╔═══╝ ██║     ██╔══██║██║     ██╔══╝\n      //  ██║  ██║███████╗██║     ███████╗██║  ██║╚██████╗███████╗\n      //  ╚═╝  ╚═╝╚══════╝╚═╝     ╚══════╝╚═╝  ╚═╝ ╚═════╝╚══════╝\n      //\n\n      describe('replacing pets of a user with PUT /user/1/pets where pets->owner is a many-to-one relationship', function () {\n\n        describe('and some of the replacement pets were already linked to owners', function() {\n\n          before(function() {\n            bootstrapModels = {\n              user: [{ name: 'bert' }, { name: 'ernie' }],\n              pet: [{ name: 'alice', owner: 1}, {name: 'mr. bailey', owner: 2}, {name: 'tex'}]\n            };\n          });\n\n          it('should cause an `updated` notification to be received by all subscribers to the replacement child records, an `addedTo` notification to be received by all subscribers to the new parent record, a `removedFrom` notification to be received by all subscribers to the new parent record (about replaced children) and a `removedFrom` notification to be received by all subscribers to any \"stolen\" child\\'s former parent record', function(done) {\n\n            expectNotifications({\n              user: {\n                addedMrBailey: {\n                  verb: 'addedTo',\n                  id: 1,\n                  attribute: 'pets',\n                  addedId: 2\n                },\n                addedTex: {\n                  verb: 'addedTo',\n                  id: 1,\n                  attribute: 'pets',\n                  addedId: 3\n                },\n                removedAlice: {\n                  verb: 'removedFrom',\n                  id: 1,\n                  removedId: 1,\n                  attribute: 'pets'\n                },\n                removedBailey: {\n                  verb: 'removedFrom',\n                  id: 2,\n                  removedId: 2,\n                  attribute: 'pets'\n                },\n              },\n              pet: {\n                updatedAlice: {\n                  verb: 'updated',\n                  id: 1,\n                  'data.owner': null\n                },\n                updatedBailey: {\n                  verb: 'updated',\n                  id: 2,\n                  'data.owner': 1\n                },\n                updatedTex: {\n                  verb: 'updated',\n                  id: 3,\n                  'data.owner': 1\n                },\n\n              }\n            }, done);\n\n            socket2.put('/user/1/pets', [2,3], function (body, jwr) {\n              if (jwr.error) { return done(jwr.error); }\n              // Otherwise, the event handler above should fire (or this test will time out and fail).\n            });\n          });\n\n        });\n\n      });\n\n\n      describe('replacing patients of a user with PUT /user/1/patients where patients->vets is a many-to-many relationship', function () {\n\n        before(function() {\n          bootstrapModels = {\n            pet: [{ name: 'alice' }, { name: 'mr. bailey'}, {name: 'tex'}],\n            user: [{name: 'bert', patients: [1,2]}],\n          };\n        });\n\n        it('should cause an `updated` notification to be received by all subscribers to the replacement child records, an `addedTo` notification to be received by all subscribers to the new parent record, and a `removedFrom` notification to be received by all subscribers to the new parent record (about replaced children)', function(done) {\n\n          expectNotifications({\n            pet: {\n              addedTex: {\n                verb: 'addedTo',\n                id: 3,\n                attribute: 'vets',\n                addedId: 1\n              },\n              removedAlice: {\n                verb: 'removedFrom',\n                id: 1,\n                attribute: 'vets',\n                removedId: 1\n              }\n            },\n            user: {\n              addedTex: {\n                verb: 'addedTo',\n                id: 1,\n                attribute: 'patients',\n                addedId: 3\n              },\n              removedAlice: {\n                verb: 'removedFrom',\n                id: 1,\n                attribute: 'patients',\n                removedId: 1\n              }\n            }\n\n          }, done);\n\n          socket2.put('/user/1/patients', [2,3], function (body, jwr) {\n            if (jwr.error) { return done(jwr.error); }\n            // Otherwise, the event handler above should fire (or this test will time out and fail).\n          });\n\n        });\n\n      });\n\n      //  ██████╗ ███████╗███████╗████████╗██████╗  ██████╗ ██╗   ██╗\n      //  ██╔══██╗██╔════╝██╔════╝╚══██╔══╝██╔══██╗██╔═══██╗╚██╗ ██╔╝\n      //  ██║  ██║█████╗  ███████╗   ██║   ██████╔╝██║   ██║ ╚████╔╝\n      //  ██║  ██║██╔══╝  ╚════██║   ██║   ██╔══██╗██║   ██║  ╚██╔╝\n      //  ██████╔╝███████╗███████║   ██║   ██║  ██║╚██████╔╝   ██║\n      //  ╚═════╝ ╚══════╝╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝    ╚═╝\n      //\n\n      describe('destroying a user with DELETE /user/1 where the user has pets and pets->owner is a many-to-one relationship', function () {\n\n        before(function() {\n          bootstrapModels = {\n            user: [{ name: 'bert' }],\n            pet: [{ name: 'alice', owner: 1}]\n          };\n        });\n\n        it('should cause a `destroyed` notification to be received by all subscribers to the parent record, and an `updated` notification to be received by all subscribers to the child records', function(done) {\n\n          expectNotifications({\n            user: {\n              destroyed: {\n                verb: 'destroyed',\n                id: 1,\n              },\n            },\n            pet: {\n              updatedAlice: {\n                verb: 'updated',\n                id: 1,\n                'data.owner': null\n              }\n            }\n          }, done);\n\n          socket2.delete('/user/1', {}, function (body, jwr) {\n            if (jwr.error) { return done(jwr.error); }\n            // Otherwise, the event handler above should fire (or this test will time out and fail).\n          });\n        });\n\n      });\n\n      describe('destroying a user with DELETE /user/1 where the user has patients and patients->vets is a many-to-many relationship', function () {\n\n        before(function() {\n          bootstrapModels = {\n            user: [{name: 'bert'}],\n            pet: [{ name: 'alice', vets: [1] }]\n          };\n        });\n\n        it('should cause a `removedFrom` notification to be received by all subscribers to the child record', function(done) {\n\n          expectNotifications({\n            user: {\n              destroyed: {\n                verb: 'destroyed',\n                id: 1\n              }\n            },\n            pet: {\n              removedFrom: {\n                verb: 'removedFrom',\n                id: 1,\n                attribute: 'vets',\n                removedId: 1\n              }\n            }\n\n          }, done);\n\n          socket2.delete('/user/1', {}, function (body, jwr) {\n            if (jwr.error) { return done(jwr.error); }\n            // Otherwise, the event handler above should fire (or this test will time out and fail).\n          });\n\n        });\n\n      });\n\n      function expectNotifications(notifications, done) {\n        var checklist = {};\n        var errored = false;\n        _.each(notifications, function(modelNotifications, model) {\n          _.each(modelNotifications, function(validator, identifier) {\n            checklist[model + '.' + identifier] = false;\n          });\n          socket1.on(model, function(notification){\n            // console.log(notification);\n            if (errored) {return;}\n            try {\n              if (!_.any(modelNotifications, function(validator, identifier) {\n                if (_.all(validator, function(val, path) {\n                  return _.get(notification, path) === val;\n                })) {\n                  if (checklist[model + '.' + identifier] === true) {\n                    errored = true;\n                    throw new Error('Got duplicate `' + identifier + '` notification for model `' + model + '`' );\n                  }\n                  checklist[model + '.' + identifier] = true;\n                  if (_.all(checklist, function(flag) {\n                    return flag === true;\n                  })) {\n                    done();\n                  }\n                  return true;\n                }\n              })) {\n                throw new Error('Unexpected `' + model + '` notification: ' + util.inspect(notification, {depth: null}));\n              }\n            } catch (e) {\n              errored = true;\n              return done(e);\n            }\n          });\n        });\n      }\n\n    });//</describe>\n  });//</describe :: Model events>\n});//</describe :: pubsub>\n"
  },
  {
    "path": "test/integration/hook.sockets.interpreter.test.js",
    "content": "/**\n * Test dependencies\n */\n\nvar util = require('util');\nvar assert = require('assert');\nvar socketHelper = require('./helpers/socketHelper.js');\nvar appHelper = require('./helpers/appHelper');\n\n\n\ndescribe('hook:sockets :: ', function() {\n\n  var sailsApp;\n  var socket1;\n  var socket2;\n  var appName = 'testApp';\n\n  describe('interpreter', function() {\n\n    before(function(done) {\n      appHelper.buildAndLiftWithTwoSockets(appName, {\n        silly: false\n      }, function(err, sails, _socket1, _socket2) {\n        if (err) { return done(err); }\n\n        if (!_socket1 || !_socket2) {\n          return done(new Error('Failed to connect test sockets'));\n        }\n        sailsApp = sails;\n        socket1 = _socket1;\n        socket2 = _socket2;\n        done();\n      });\n    });\n\n    after(function(done) {\n      socket1.disconnect();\n      socket2.disconnect();\n      process.chdir('../');\n      appHelper.teardown();\n\n      sailsApp.lower(done);\n    });\n\n    afterEach(function(done) {\n      socket1.removeAllListeners();\n      socket2.removeAllListeners();\n      done();\n    });\n\n    describe('basic usage', function() {\n\n      it('should probably be tested using a different helper...');\n      // TODO: use new sails.io.js client to perform these tests\n      // see http://github.com/balderdashy/sails.io.js\n    });\n\n  });\n});\n"
  },
  {
    "path": "test/integration/hook.userconfig.test.js",
    "content": "/**\n * Test dependencies\n */\nvar assert = require('assert');\nvar httpHelper = require('./helpers/httpHelper.js');\nvar appHelper = require('./helpers/appHelper');\nvar util = require('util');\nvar path = require('path');\nvar fs = require('fs-extra');\nvar Sails = require('../../lib/app');\nvar async = require('async');\n\n\n\n\ndescribe('hooks :: ', function() {\n\n  describe('userconfig hook', function() {\n    var appName = 'testApp';\n\n    before(function(done) {\n      appHelper.teardown();\n      async.series([\n        function(cb) {fs.outputFile(path.resolve(__dirname,'../../testApp/config/abc.js'), 'module.exports = {\"foo\":\"goo\"};', cb);},\n        function(cb) {fs.outputFile(path.resolve(__dirname,'../../testApp/config/foo/bar.js'), 'module.exports = {\"foo\":\"bar\", \"abc\":123, \"betty\": \"boop\"};', cb);},\n        function(cb) {fs.outputFile(path.resolve(__dirname,'../../testApp/config/lara/bar.js'), 'module.exports = {\"horse\":\"neigh\", \"pig\": \"oink\", \"betty\": \"spaghetti\"};', cb);},\n        function(cb) {fs.outputFile(path.resolve(__dirname,'../../testApp/config/env/development.js'), 'module.exports = {\"cat\":\"meow\"};', cb);},\n        function(cb) {fs.outputFile(path.resolve(__dirname,'../../testApp/config/env/development/config.js'), 'module.exports = {\"owl\":\"hoot\"};', cb);},\n        function(cb) {fs.outputFile(path.resolve(__dirname,'../../testApp/config/env/test-development.js'), 'module.exports = {\"duck\":\"quack\"};', cb);},\n        function(cb) {fs.outputFile(path.resolve(__dirname,'../../testApp/config/env/test-development/config.js'), 'module.exports = {\"dog\":\"woof\"};', cb);},\n        function(cb) {process.chdir('testApp'); cb();}\n      ], done);\n\n    });\n\n\n    describe('with default options', function() {\n\n      var sailsApp;\n\n      it('should merge config options regardless of file structure', function(done) {\n\n        sailsApp = Sails();\n        sailsApp.load({hooks:{grunt:false, pubsub: false}}, function(err, sails) {\n          if (err) { return callback(err); }\n          assert.equal(sails.config.foo, 'bar');\n          assert.equal(sails.config.abc, 123);\n          assert.equal(sails.config.horse, 'neigh');\n          assert.equal(sails.config.pig, 'oink');\n          assert.equal(sails.config.betty, 'spaghetti');\n          assert.equal(typeof(sails.config.bar), 'undefined');\n          return done();\n        });\n\n      });\n\n      after(function (done){\n        sailsApp.lower(done);\n      });\n\n    });\n\n    describe('in development environment', function() {\n\n      var sails;\n      before(function(done) {\n        sails = Sails();\n        sails.load({hooks:{grunt:false, pubsub: false}}, done);\n      });\n\n      it('should load config from config/env/development.js', function() {\n        assert.equal(sails.config.cat, 'meow');\n      });\n\n      it('should load config from config/env/development/** files', function() {\n        assert.equal(sails.config.owl, 'hoot');\n      });\n\n      it('should not load config from config/env/test-development/** files', function() {\n        assert(!sails.config.dog);\n      });\n\n      it('should not load config from config/env/test-development.js', function() {\n        assert(!sails.config.duck);\n      });\n\n      after(function (done){\n        sails.lower(done);\n      });\n\n    });\n\n    describe('in test-development environment', function() {\n\n      var sails;\n      before(function(done) {\n        sails = Sails();\n        sails.load({hooks:{grunt:false, pubsub: false}, environment: 'test-development'}, done);\n      });\n\n      it('should load config from config/env/test-development.js', function() {\n        assert.equal(sails.config.duck, 'quack');\n      });\n\n      it('should load config from config/env/test-development/** files', function() {\n        assert.equal(sails.config.dog, 'woof');\n      });\n\n      it('should not load config from config/env/development/** files', function() {\n        assert(!sails.config.owl);\n      });\n\n      it('should not load config from config/env/development.js', function() {\n        assert(!sails.config.cat);\n      });\n\n      after(function (done){\n        sails.lower(done);\n      });\n\n    });\n\n    after(function() {\n      process.chdir('../');\n      appHelper.teardown();\n    });\n\n  });\n});\n"
  },
  {
    "path": "test/integration/hook.views.test.js",
    "content": "/**\n * Test dependencies\n */\n\nvar util = require('util');\nvar assert = require('assert');\nvar httpHelper = require('./helpers/httpHelper.js');\nvar appHelper = require('./helpers/appHelper');\nvar _ = require('@sailshq/lodash');\nvar Filesystem = require('machinepack-fs');\nvar tmp = require('tmp');\n\nvar Sails = require('../../lib').constructor;\n\ntmp.setGracefulCleanup();\n\n\n\n\ndescribe('hooks :: ', function() {\n\n  describe('views hook', function() {\n\n    var curDir, tmpDir, sailsApp;\n    var sailsConfig = {};\n    var filesToWrite = {};\n\n    afterEach(function(done) {\n      sailsApp.lower(function() {\n        process.chdir(curDir);\n        return done();\n      });\n    });\n\n    beforeEach(function(done) {\n      // Cache the current working directory.\n      curDir = process.cwd();\n      // Create a temp directory.\n      tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n      // Write a layout file for each test.\n      Filesystem.writeSync({\n        force: true,\n        destination: 'views/layout.ejs',\n        string: '<!DOCTYPE html><html><head><!-- default layout --></head><body><%- body %></body></html>'\n      }).execSync();\n      // Write out any files specific to this test.\n      _.each(filesToWrite, function(data, filename) {\n        Filesystem.writeSync({\n          force: true,\n          destination: filename,\n          string: data\n        }).execSync();\n      });\n      // Merge the default config with any config specific to this test.\n      var _config = _.merge({\n        port: 1342,\n        hooks: {grunt: false, blueprints: false, policies: false, pubsub: false},\n        log: {level: 'error'},\n      }, sailsConfig);\n      // Lift Sails for this test.\n      (new Sails()).lift(_config, function(err, _sails) {\n          sailsApp = _sails;\n          return done(err);\n        }\n      );\n    });\n\n    afterEach(function(done) {\n      sailsApp.lower(function() {\n        process.chdir(curDir);\n        return done();\n      });\n    });\n\n\n    describe('using res.view', function () {\n\n      before(function() {\n        sailsConfig = {\n          hooks: {i18n: false},\n          routes: {\n            '/resView': function(req, res) {\n              return res.view('homepage');\n            }\n          }\n        };\n        filesToWrite = {\n          'views/homepage.ejs': '<!-- Default home page -->'\n        };\n      });\n\n      it('should respond to a get request to localhost:1342 with the requested page wrapped in the default layout', function(done) {\n\n        httpHelper.testRoute('get', 'resView', function(err, response) {\n          if (err) {\n            return done(new Error(err));\n          }\n          assert.equal(response.body, '<!DOCTYPE html><html><head><!-- default layout --></head><body><!-- Default home page --></body></html>');\n          done();\n        });\n      });\n\n    });\n\n    describe('using res.view with i18n', function () {\n\n      before(function() {\n        sailsConfig = {\n\n          // We must set i18n.locales because otherwise, the hook will be skipped.\n          i18n: { locales: ['en', 'es'] },\n\n          routes: {\n            '/resView': function(req, res) {\n              return res.view('homepage');\n            }\n          }\n\n        };\n        filesToWrite = {\n          'views/homepage.ejs': '<%= __(\\'Welcome\\') + i18n(\\'Welcome\\') %>',\n          'config/locales/es.json': '{\"Welcome\":\"Bienvenido\"}'\n        };\n      });\n\n      it('should respond to a get request to localhost:1342 with the requested page wrapped in the default layout', function(done) {\n\n        httpHelper.testRoute(\n          'get',\n          {url: 'resView', headers: {'accept-language': 'es'}},\n          function(err, response) {\n            if (err) {\n              return done(err);\n            }\n\n            if (response.statusCode !== 200) {\n              return done(new Error('Should have gotten 200 status code, but instead got '+response.statusCode+' with a response body of: '+util.inspect(response.body, {depth:null})+''));\n            }\n\n            try {\n              assert.equal(response.body, '<!DOCTYPE html><html><head><!-- default layout --></head><body>BienvenidoBienvenido</body></html>');\n            } catch (e) { return done(e); }\n\n            done();\n          });\n      });\n\n    });\n\n    describe('using exposeLocalsToBrowser', function () {\n\n      describe('with CSRF enabled', function() {\n\n        before(function() {\n          sailsConfig = {\n            hooks: {i18n: false},\n            security: {\n              csrf: true\n            },\n            routes: {\n              '/expose-locals': function(req, res) {\n                return res.view('show-locals', {foo: 'bar', abc: 123});\n              }\n            }\n          };\n          filesToWrite = {\n            'views/show-locals.ejs': '<%- exposeLocalsToBrowser() %>',\n          };\n        });\n\n        it('should respond to a get request to localhost:1342 with a page containing a script exposing locals, including a csrf token', function(done) {\n\n          httpHelper.testRoute('get', 'expose-locals', function(err, response) {\n            if (err) {\n              return done(new Error(err));\n            }\n            assert(response.body.indexOf('foo: unescape(\\'bar\\')') > -1);\n            assert(response.body.indexOf('abc: unescape(123)') > -1);\n            assert(response.body.indexOf('_csrf: unescape') > -1);\n            done();\n          });\n        });\n\n      });\n\n      describe('with CSRF disabled', function() {\n\n        before(function() {\n          sailsConfig = {\n            hooks: {i18n: false},\n            routes: {\n              '/expose-locals': function(req, res) {\n                return res.view('show-locals', {foo: 'bar', abc: 123});\n              }\n            }\n          };\n          filesToWrite = {\n            'views/show-locals.ejs': '<%- exposeLocalsToBrowser() %>',\n          };\n        });\n\n        it('should respond to a get request to localhost:1342 with a page containing a script exposing locals, including a csrf token', function(done) {\n\n          httpHelper.testRoute('get', 'expose-locals', function(err, response) {\n            if (err) {\n              return done(new Error(err));\n            }\n            assert(response.body.indexOf('foo: unescape(\\'bar\\')') > -1);\n            assert(response.body.indexOf('abc: unescape(123)') > -1);\n            assert(response.body.indexOf('_csrf: unescape') < 0);\n            done();\n          });\n        });\n\n\n      });\n    });\n\n    describe('using res.view with `sails.config.views.layout = false`', function () {\n\n      before(function() {\n        sailsConfig = {\n          hooks: {i18n: false},\n          routes: {\n            '/resView': function(req, res) {\n              return res.view('homepage');\n            }\n          },\n          views: {\n            layout: false\n          }\n        };\n        filesToWrite = {\n          'views/homepage.ejs': '<!-- Default home page -->'\n        };\n      });\n\n      it('should respond to a get request to localhost:1342 with the requested page NOT wrapped in the default layout', function(done) {\n\n        httpHelper.testRoute('get', 'resView', function(err, response) {\n          if (err) {\n            return done(new Error(err));\n          }\n          assert.equal(response.body, '<!-- Default home page -->');\n          done();\n        });\n      });\n\n    });\n\n\n    describe('using res.view with {layout: false} in locals', function () {\n\n      before(function() {\n        sailsConfig = {\n          hooks: {i18n: false},\n          routes: {\n            '/resView': function(req, res) {\n              return res.view('homepage', {layout: false});\n            }\n          }\n        };\n        filesToWrite = {\n          'views/homepage.ejs': '<!-- Default home page -->'\n        };\n      });\n\n      it('should respond to a get request to localhost:1342 with the requested page NOT wrapped in the default layout', function(done) {\n\n        httpHelper.testRoute('get', 'resView', function(err, response) {\n          if (err) {\n            return done(new Error(err));\n          }\n          assert.equal(response.body, '<!-- Default home page -->');\n          done();\n        });\n      });\n\n    });\n\n    describe('using res.view with an alternate layout', function () {\n\n      before(function() {\n        sailsConfig = {\n          hooks: {i18n: false},\n          routes: {\n            '/resView': function(req, res) {\n              return res.view('homepage', {layout: 'alt-layout'});\n            },\n          }\n        };\n        filesToWrite = {\n          'views/homepage.ejs': '<!-- Default home page -->',\n          'views/alt-layout.ejs': '<FOO><%-body%></FOO>',\n        };\n      });\n\n      it('should respond to a get request to localhost:1342 with the requested page wrapped in the alternate layout', function(done) {\n\n        httpHelper.testRoute('get', 'resView', function(err, response) {\n          if (err) {\n            return done(new Error(err));\n          }\n          assert.equal(response.body, '<FOO><!-- Default home page --></FOO>');\n          done();\n        });\n      });\n\n    });\n\n    describe('using res.view with an alternate extension for EJS', function () {\n      var nunjucks = require('nunjucks');\n      before(function() {\n        sailsConfig = {\n          hooks: {i18n: false},\n          routes: {\n            '/resView': function(req, res) {\n              return res.view('homepage', {boss: 'llama'});\n            },\n          },\n          views: {\n            extension: 'foo',\n          }\n        };\n        filesToWrite = {\n          'views/layout.foo': '<!DOCTYPE html><html><head><!-- default layout --></head><body><%- body %></body></html>',\n          'views/homepage.foo': '<!-- vars like a <%= boss %> -->',\n        };\n      });\n\n      it('should respond to a get request to localhost:1342 with the correct content', function(done) {\n\n        httpHelper.testRoute('get', 'resView', function(err, response) {\n          if (err) {\n            return done(new Error(err));\n          }\n          assert.equal(response.body, '<!DOCTYPE html><html><head><!-- default layout --></head><body><!-- vars like a llama --></body></html>');\n          done();\n        });\n      });\n\n    });\n\n    describe('using res.view with an alternate render fn', function () {\n      var nunjucks = require('nunjucks');\n      before(function() {\n        sailsConfig = {\n          hooks: {i18n: false},\n          routes: {\n            '/resView': function(req, res) {\n              return res.view('homepage', {boss: 'dinosaur'});\n            },\n          },\n          views: {\n            layout: false,\n            extension: 'html',\n            getRenderFn: function() {\n              var env = nunjucks.configure({\n                tags: {\n                  variableStart: '<$',\n                  variableEnd: '$>',\n                }\n              });\n              return env.render.bind(env);\n            }\n          }\n        };\n        filesToWrite = {\n          'views/homepage.html': '<!-- vars like a <$ boss $> -->',\n        };\n      });\n\n      it('should respond to a get request to localhost:1342 with the correct content', function(done) {\n\n        httpHelper.testRoute('get', 'resView', function(err, response) {\n          if (err) {\n            return done(new Error(err));\n          }\n          assert.equal(response.body, '<!-- vars like a dinosaur -->');\n          done();\n        });\n      });\n\n    });\n\n\n    describe('using partials', function () {\n\n      describe('with cacheing turned off', function() {\n\n        before(function() {\n          sailsConfig = {\n            hooks: {i18n: false},\n            routes: {\n              '/partials': function(req, res) {\n                return res.view('test-partials');\n              },\n            }\n          };\n          filesToWrite = {\n            'views/test-partials.ejs': '<BLAP><%- partial(\\'./partials/outer.ejs\\') %></BLAP>',\n            'views/partials/outer.ejs': '<FOO><%- partial(\\'./nested/inner.ejs\\') %></FOO>',\n            'views/partials/nested/inner.ejs': '<BAR>BAZ!</BAR>'\n          };\n        });\n\n        it('should respond to a get request to localhost:1342 with the correct content, and respond differently to a subsequent request after changing the file contents', function(done) {\n\n          httpHelper.testRoute('get', 'partials', function(err, response) {\n            if (err) {\n              return done(new Error(err));\n            }\n            assert.equal(response.body, '<!DOCTYPE html><html><head><!-- default layout --></head><body><BLAP><FOO><BAR>BAZ!</BAR></FOO></BLAP></body></html>');\n            filesToWrite = {\n              'views/layout.ejs': '<ZAP><%- body %></ZAP>',\n              'views/test-partials.ejs': '<APPLE><%- partial(\\'./partials/outer.ejs\\') %></APPLE>',\n              'views/partials/outer.ejs': '<ORANGE><%- partial(\\'./nested/inner.ejs\\') %></ORANGE>',\n              'views/partials/nested/inner.ejs': '<BANANA>TADA!</BANANA>'\n            };\n            _.each(filesToWrite, function(data, filename) {\n              Filesystem.writeSync({\n                force: true,\n                destination: filename,\n                string: data\n              }).execSync();\n            });\n            httpHelper.testRoute('get', 'partials', function(err, response) {\n              if (err) {\n                return done(new Error(err));\n              }\n              assert.equal(response.body, '<ZAP><APPLE><ORANGE><BANANA>TADA!</BANANA></ORANGE></APPLE></ZAP>');\n              done();\n            });\n          });\n        });\n\n      });\n\n      describe('with cacheing turned on', function() {\n\n        before(function() {\n          sailsConfig = {\n            hooks: {i18n: false},\n            routes: {\n              '/partials': function(req, res) {\n                return res.view('test-partials', {cache: true});\n              },\n            }\n          };\n          filesToWrite = {\n            'views/test-partials.ejs': '<BLAP><%- partial(\\'./partials/outer.ejs\\') %></BLAP>',\n            'views/partials/outer.ejs': '<FOO><%- partial(\\'./nested/inner.ejs\\') %></FOO>',\n            'views/partials/nested/inner.ejs': '<BAR>BAZ!</BAR>'\n          };\n        });\n\n        it('should respond to a get request to localhost:1342 with the correct content, and respond the same way to a subsequent request after changing the file contents', function(done) {\n\n          httpHelper.testRoute('get', 'partials', function(err, response) {\n            if (err) {\n              return done(new Error(err));\n            }\n            assert.equal(response.body, '<!DOCTYPE html><html><head><!-- default layout --></head><body><BLAP><FOO><BAR>BAZ!</BAR></FOO></BLAP></body></html>');\n            filesToWrite = {\n              'views/layout.ejs': '<ZAP><%- body %></ZAP>',\n              'views/test-partials.ejs': '<APPLE><%- partial(\\'./partials/outer.ejs\\') %></APPLE>',\n              'views/partials/outer.ejs': '<ORANGE><%- partial(\\'./nested/inner.ejs\\') %></ORANGE>',\n              'views/partials/nested/inner.ejs': '<BANANA>TADA!</BANANA>'\n            };\n            _.each(filesToWrite, function(data, filename) {\n              Filesystem.writeSync({\n                force: true,\n                destination: filename,\n                string: data\n              }).execSync();\n            });\n            httpHelper.testRoute('get', 'partials', function(err, response) {\n              if (err) {\n                return done(new Error(err));\n              }\n              assert.equal(response.body, '<!DOCTYPE html><html><head><!-- default layout --></head><body><BLAP><FOO><BAR>BAZ!</BAR></FOO></BLAP></body></html>');\n              done();\n            });\n          });\n        });\n\n      });\n\n    });\n\n    describe('using renderView', function () {\n\n      before(function() {\n        sailsConfig = {\n          hooks: {i18n: false},\n          routes: {\n            '/renderView': function(req, res) {\n              req._sails.renderView('homepage', {}, function(err, html) {\n                return res.send(html);\n              });\n            }\n          }\n        };\n        filesToWrite = {\n          'views/homepage.ejs': '<!-- Default home page -->'\n        };\n      });\n\n      it('should respond to a get request to localhost:1342 with welcome page', function(done) {\n\n        httpHelper.testRoute('get', 'renderView', function(err, response) {\n          if (err) {\n            return done(new Error(err));\n          }\n          assert.equal(response.body, '<!DOCTYPE html><html><head><!-- default layout --></head><body><!-- Default home page --></body></html>');\n          done();\n        });\n      });\n\n    });\n\n    describe('using renderView (with i18n disabled)', function () {\n\n      before(function() {\n        sailsConfig = {\n          hooks: {i18n: false},\n          routes: {\n            '/renderView': function(req, res) {\n              req._sails.renderView('homepage', {}, function(err, html) {\n                return res.send(html);\n              });\n            }\n          },\n          hooks: {\n            i18n: false\n          }\n        };\n        filesToWrite = {\n          'views/homepage.ejs': '<!-- Default home page -->'\n        };\n      });\n\n      it('should respond to a get request to localhost:1342 with welcome page', function(done) {\n\n        httpHelper.testRoute('get', 'renderView', function(err, response) {\n          if (err) {\n            return done(new Error(err));\n          }\n          assert.equal(response.body, '<!DOCTYPE html><html><head><!-- default layout --></head><body><!-- Default home page --></body></html>');\n          done();\n        });\n      });\n\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/integration/hooks.user.test.js",
    "content": "/**\n * Test dependencies\n */\nvar assert = require('assert');\nvar httpHelper = require('./helpers/httpHelper.js');\nvar appHelper = require('./helpers/appHelper');\nvar util = require('util');\nvar path = require('path');\nvar fs = require('fs-extra');\nvar _  = require('@sailshq/lodash');\n\ndescribe('hooks :: ', function() {\n\n  var sailsprocess;\n\n  describe('defining a user hook', function() {\n    var appName = 'testApp';\n\n    before(function() {\n      appHelper.teardown();\n    });\n\n    describe('in api/hooks/shout', function(){\n\n      before(function(done) {\n        fs.mkdirs(path.resolve(__dirname, '../..', appName, 'api/hooks'), function(err) {\n          if (err) {return done(err);}\n          fs.copySync(path.resolve(__dirname, 'fixtures/hooks/installable/shout/index.js'), path.resolve(__dirname,'../../testApp/api/hooks/shout/index.js'));\n          process.chdir(path.resolve(__dirname, '../..', appName));\n          done();\n        });\n      });\n\n      after(function() {\n        process.chdir('../');\n        // Sleep for 500ms--otherwise we get timing errors for this test on Windows\n        setTimeout(function() {\n          appHelper.teardown();\n        }, 500);\n      });\n\n      describe('with default settings', function() {\n\n        var sails;\n\n        before(function(done) {\n          appHelper.liftQuiet({hooks: {pubsub: false}}, function(err, _sails) {\n            if (err) {return done(err);}\n            sails = _sails;\n            return done();\n          });\n        });\n\n        after(function(done) {\n          sails.lower(function(){setTimeout(done, 100);});\n        });\n\n        it('should install a hook into `sails.hooks.shout`', function() {\n\n          assert(sails.hooks.shout);\n\n        });\n\n        it('should use merge the default hook config', function() {\n\n          assert(sails.config.shout.phrase === 'make it rain', sails.config.shout.phrase);\n\n        });\n\n        it('should bind a /shout route that responds with the default phrase', function(done) {\n          httpHelper.testRoute('GET', 'shout', function(err, resp, body) {\n            assert(body === 'make it rain');\n            return done();\n          });\n        });\n\n      });\n\n      describe('with hooks.shout set to boolean false', function() {\n\n        var sails;\n\n        before(function(done) {\n          appHelper.liftQuiet({hooks: {shout: false, pubsub: false}}, function(err, _sails) {\n            if (err) {return done(err);}\n            sails = _sails;\n            return done();\n          });\n        });\n\n        after(function(done) {\n          sails.lower(function(){setTimeout(done, 100);});\n        });\n\n        it('should not install a hook into `sails.hooks.shout`', function() {\n\n          assert(_.isUndefined(sails.hooks.shout));\n\n        });\n\n      });\n\n\n      describe('with hooks.shout set to the string \"false\"', function() {\n\n        var sails;\n\n        before(function(done) {\n          appHelper.liftQuiet({hooks: {shout: 'false', pubsub: false}}, function(err, _sails) {\n            if (err) {return done(err);}\n            sails = _sails;\n            return done();\n          });\n        });\n\n        after(function(done) {\n          sails.lower(function(){setTimeout(done, 100);});\n        });\n\n        it('should not install a hook into `sails.hooks.shout`', function() {\n\n          assert(_.isUndefined(sails.hooks.shout));\n\n        });\n\n      });\n\n      describe('with hook-level config options', function() {\n\n        var sails;\n\n        before(function(done) {\n          appHelper.liftQuiet({shout: {phrase: 'yolo'}, hooks:{pubsub: false}}, function(err, _sails) {\n            if (err) {return done(err);}\n            sails = _sails;\n            return done();\n          });\n        });\n\n        after(function(done) {\n          sails.lower(function(){setTimeout(done, 100);});\n        });\n\n        it('should bind a /shout route that responds with the configured phrase', function(done) {\n          httpHelper.testRoute('GET', 'shout', function(err, resp, body) {\n            assert(body === 'yolo');\n            return done();\n          });\n        });\n\n      });\n\n    });\n\n    if (Number(process.version.match(/^v(\\d+\\.\\d+)/)[1]) >= 7.6) {\n\n      describe('with an asynchronous initialize function', function() {\n\n        before(function(done) {\n          fs.mkdirs(path.resolve(__dirname, '../..', appName, 'api/hooks'), function(err) {\n            if (err) {return done(err);}\n            fs.copySync(path.resolve(__dirname, 'fixtures/hooks/installable/async/index.js.txt'), path.resolve(__dirname,'../../testApp/api/hooks/async/index.js'));\n            process.chdir(path.resolve(__dirname, '../..', appName));\n            done();\n          });\n        });\n\n        after(function(done) {\n          process.chdir('../');\n          // Sleep for 500ms--otherwise we get timing errors for this test on Windows\n          setTimeout(function() {\n            appHelper.teardown();\n            return done();\n          }, 500);\n        });\n\n        describe('that runs succesfully', function() {\n\n          var sails;\n\n          before(function(done) {\n            appHelper.liftQuiet({hooks: {pubsub: false}}, function(err, _sails) {\n              if (err) {return done(err);}\n              sails = _sails;\n              return done();\n            });\n          });\n\n          after(function(done) {\n            sails.lower(done, 100);\n          });\n\n          it('should run the initialize function successfully', function() {\n\n            assert.equal(sails.hooks.async.val, 'foo');\n\n          });\n\n        });\n\n        describe('that has an error', function() {\n\n          var sails;\n\n          after(function(done) {\n            if (!sails) { return done(); }\n            sails.lower(function(){setTimeout(function() {\n              process.chdir('../');\n              // Sleep for 500ms--otherwise we get timing errors for this test on Windows\n              setTimeout(function() {\n                appHelper.teardown();\n                return done();\n              }, 500);\n            }, 100);});\n          });\n\n          it('should handle the error gracefully', function(done) {\n\n            appHelper.liftQuiet({hooks: {pubsub: false}, custom: {reject: true}}, function(err, _sails) {\n              if (err) {\n                assert.equal(err, 'foo');\n                return done();\n              }\n              sails = _sails;\n              return done(new Error('Should have failed to lift!'));\n            });\n\n          });\n\n        });\n\n      });\n\n    }\n\n\n  });\n\n\n\n});\n"
  },
  {
    "path": "test/integration/lift.https.test.js",
    "content": "var assert = require('assert');\nvar fs = require('fs');\nvar request = require('@sailshq/request');\nvar appHelper = require('./helpers/appHelper');\nvar path = require('path');\n\ndescribe('Starting HTTPS sails server with lift', function() {\n\n  var appName = 'testApp';\n\n  before(function(done) {\n    appHelper.build(done);\n  });\n\n  after(function() {\n    process.chdir('../');\n    appHelper.teardown();\n  });\n\n\n  describe('using sails.config.ssl.key and sails.config.ssl.cert', function() {\n\n    var sailsServer;\n\n    before(function() {\n      fs.writeFileSync(path.resolve('../', appName, 'config/env/development.js'), \"module.exports = {ssl: {key: require('fs').readFileSync('\"+require('path').resolve(__dirname, 'cert','sailstest-key.pem').replace(/\\\\/g,'\\\\\\\\')+\"'), cert: require('fs').readFileSync('\"+require('path').resolve(__dirname, 'cert','sailstest-cert.pem').replace(/\\\\/g,'\\\\\\\\')+\"')}};\");\n    });\n\n    after(function(done) {\n      if (sailsServer) {\n        return sailsServer.lower(function(){setTimeout(done, 100);});\n      }\n      return done();\n    });\n\n    it('should start server without error', function(done) {\n      appHelper.lift(function(err, _sailsServer) {\n        assert(!err);\n        sailsServer = _sailsServer;\n        return done();\n      });\n\n    });\n\n    it('should respond to a request to port 1342 with a 200 status code', function(done) {\n      if (!sailsServer) {return done('Bailing due to previous test failure!');}\n\n      request.get({\n        url:'https://localhost:1342/',\n        ca: require('fs').readFileSync(require('path').resolve(__dirname, 'cert','sailstest-cert.pem')),\n      }, function(err, response) {\n        assert(!err);\n        assert.equal(response.statusCode, 200);\n        return done();\n      });\n\n    });\n  });\n\n  describe('using sails.config.ssl = true and sails.config.http.serverOptions', function() {\n\n    var sailsServer;\n\n    before(function() {\n      fs.writeFileSync(path.resolve('../', appName, 'config/env/development.js'), \"module.exports = {ssl: true, http: {serverOptions: { key: require('fs').readFileSync('\"+require('path').resolve(__dirname, 'cert','sailstest-key.pem').replace(/\\\\/g,'\\\\\\\\')+\"'), cert: require('fs').readFileSync('\"+require('path').resolve(__dirname, 'cert','sailstest-cert.pem').replace(/\\\\/g,'\\\\\\\\')+\"')}}};\");\n    });\n\n    after(function(done) {\n      if (sailsServer) {\n        return sailsServer.lower(function(){setTimeout(done, 100);});\n      }\n      return done();\n    });\n\n    it('should start server without error', function(done) {\n      appHelper.lift(function(err, _sailsServer) {\n        assert(!err);\n        sailsServer = _sailsServer;\n        return done();\n      });\n\n    });\n\n    it('should respond to a request to port 1342 with a 200 status code', function(done) {\n      if (!sailsServer) {return done('Bailing due to previous test failure!');}\n\n      request.get({\n        url:'https://localhost:1342/',\n        ca: require('fs').readFileSync(require('path').resolve(__dirname, 'cert','sailstest-cert.pem')),\n      }, function(err, response) {\n        assert(!err);\n        assert.equal(response.statusCode, 200);\n        return done();\n      });\n\n    });\n  });\n});\n"
  },
  {
    "path": "test/integration/lift.lower.test.js",
    "content": "var assert = require('assert');\nvar Sails = require('../../lib/app');\nvar async = require('async');\nvar _ = require('@sailshq/lodash');\n\ndescribe('sails being lifted and lowered (e.g in a test framework)', function() {\n\n  it('should clean up event listeners', function(done) {\n\n    // Get a list of all the current listeners on the process.\n    // Note that Mocha adds some listeners, so these might not all be empty arrays!\n    var beforeListeners = {\n      sigusr2: process.listeners('SIGUSR2'),\n      sigint: process.listeners('SIGINT'),\n      sigterm: process.listeners('SIGTERM'),\n      exit: process.listeners('exit')\n    };\n\n    // Lift and lower 15 Sails apps in a row, to simulate a testing environment\n    async.forEachOfSeries(Array(15), function(undef, i, cb) {\n      var sailsServer = null;\n      Sails().lift({\n        port: 1342,\n        environment: process.env.TEST_ENV,\n        log: {\n          level: 'error'\n        },\n        globals: false,\n        hooks: {\n          grunt: false,\n          i18n: false,\n          session: false\n        }\n      }, function(err, sails) {\n        if (err) {\n          return cb(err);\n        }\n        setTimeout(function() {\n          sails.lower(function(){setTimeout(cb, 100);});\n        });\n\n      });\n\n    }, function(err) {\n      if (err) {\n        return done(err);\n      }\n      // Check that we have the same # of listeners as before--that is,\n      // that all listeners that were added when the apps were initialized\n      // were subsequently removed when they were lowered.\n      assert.equal(beforeListeners.sigusr2.length,\n        process.listeners('SIGUSR2').length);\n      assert.equal(beforeListeners.sigterm.length,\n        process.listeners('SIGTERM').length);\n      assert.equal(beforeListeners.exit.length,\n        process.listeners('exit').length);\n      assert.equal(beforeListeners.sigint.length,\n        process.listeners('SIGINT').length);\n      return done();\n    });\n\n  }); //</should clean up event listeners>\n\n  describe('with NODE_ENV set and Sails environment not configured', function() {\n\n    var sailsApp;\n    var originalNodeEnv;\n\n    before(function() {\n      originalNodeEnv = process.env.NODE_ENV;\n      process.env.NODE_ENV = 'foobar';\n    });\n\n    after(function(done) {\n      if (_.isUndefined(originalNodeEnv)) {\n        delete process.env.NODE_ENV;\n      } else {\n        process.env.NODE_ENV = originalNodeEnv;\n      }\n      if (sailsApp) {\n        return sailsApp.lower(done);\n      }\n      else {\n        return done();\n      }\n    });\n\n    it('should change the Sails environment to match NODE_ENV it the Sails environment is not explicitly configured', function(done) {\n\n      // Save reference to original NODE_ENV.\n      var originalNodeEnv = process.env.NODE_ENV;\n      process.env.NODE_ENV = 'foobar';\n\n      // Load `app0` deep in the `'cenote'`\n      Sails().load({\n        log: {\n          level: 'error'\n        },\n        globals: false,\n        hooks: {\n          grunt: false,\n          i18n: false,\n          session: false\n        }\n      }, function(err, _sailsApp) {\n        if (err) { return done(err); }\n\n        sailsApp = _sailsApp;\n\n        // Assert that NODE_ENV is unchanged.\n        assert.equal('foobar', process.env.NODE_ENV);\n\n        // Assert that Sails environment has been changed to match NODE_ENV\n        assert.equal('foobar', sailsApp.config.environment);\n\n        return done();\n\n      });\n\n    });\n\n  });\n\n  describe('with Sails environment configured but no NODE_ENV set', function() {\n\n    var sailsApp;\n    var originalNodeEnv;\n\n    before(function() {\n      originalNodeEnv = process.env.NODE_ENV;\n      delete process.env.NODE_ENV;\n    });\n\n    after(function(done) {\n      if (_.isUndefined(originalNodeEnv)) {\n        delete process.env.NODE_ENV;\n      } else {\n        process.env.NODE_ENV = originalNodeEnv;\n      }\n      if (sailsApp) {\n        return sailsApp.lower(done);\n      }\n      else {\n        return done();\n      }\n    });\n\n    it('should not change the NODE_ENV env variable to match the configured Sails environment, or vice versa', function(done) {\n\n      // Load `app0` deep in the `'cenote'`\n      Sails().load({\n        environment: 'cenote',\n        log: {\n          level: 'error'\n        },\n        globals: false,\n        hooks: {\n          grunt: false,\n          i18n: false,\n          session: false\n        }\n      }, function(err, _sailsApp) {\n\n        if (err) { return done(err); }\n\n        sailsApp = _sailsApp;\n\n        // Assert that NODE_ENV is unchanged.\n        assert(typeof process.env.NODE_ENV === 'undefined');\n\n        // Assert that sails config is unchanged.\n        assert.equal(sailsApp.config.environment, 'cenote');\n\n        return done();\n\n      });//</app0.load()>\n\n    });\n\n  });\n\n  describe('with both NODE_ENV set and Sails environment configured', function() {\n\n    var sailsApp;\n    var originalNodeEnv;\n\n    before(function() {\n      originalNodeEnv = process.env.NODE_ENV;\n      process.env.NODE_ENV = 'foobar';\n    });\n\n    after(function(done) {\n      if (_.isUndefined(originalNodeEnv)) {\n        delete process.env.NODE_ENV;\n      } else {\n        process.env.NODE_ENV = originalNodeEnv;\n      }\n      if (sailsApp) {\n        return sailsApp.lower(done);\n      }\n      else {\n        return done();\n      }\n    });\n\n    it('should not change the NODE_ENV env variable to match the configured Sails environment, or vice versa', function(done) {\n\n      // Load `app0` deep in the `'cenote'`\n      Sails().load({\n        environment: 'cenote',\n        log: {\n          level: 'error'\n        },\n        globals: false,\n        hooks: {\n          grunt: false,\n          i18n: false,\n          session: false\n        }\n      }, function(err, _sailsApp) {\n\n        if (err) { return done(err); }\n\n        sailsApp = _sailsApp;\n\n        // Assert that NODE_ENV is unchanged.\n        assert.equal('foobar', process.env.NODE_ENV);\n\n        // Assert that sails config is unchanged.\n        assert.equal(sailsApp.config.environment, 'cenote');\n\n        return done();\n\n      });//</app0.load()>\n\n    });\n\n  });\n\n  describe('with Sails environment set to `production`, and the Node environment is `undefined`', function() {\n\n    var sailsApp;\n    var originalNodeEnv;\n\n    before(function() {\n      originalNodeEnv = process.env.NODE_ENV;\n      delete process.env.NODE_ENV;\n    });\n\n    after(function(done) {\n      if (_.isUndefined(originalNodeEnv)) {\n        delete process.env.NODE_ENV;\n      } else {\n        process.env.NODE_ENV = originalNodeEnv;\n      }\n      if (sailsApp) {\n        return sailsApp.lower(done);\n      }\n      else {\n        return done();\n      }\n    });\n\n    it('should change NODE_ENV to production and log a warning', function(done) {\n\n      var debugs = [];\n      var customLogger = {\n        level: 'debug',\n        custom: {\n          log: function(){},\n          warn: function(){},\n          debug: function(msg) {debugs.push(msg);}\n        },\n        colors: { warn: '' },\n        prefixTheme: 'abbreviated'\n      };\n\n      // Load `app0` deep in the `'cenote'`\n      Sails().load({\n        environment: 'production',\n        log: customLogger,\n        globals: false,\n        hooks: {\n          grunt: false,\n          i18n: false,\n          session: false,\n          sockets: false\n        }\n      }, function(err, _sailsApp) {\n\n        if (err) { return done(err); }\n        sailsApp = _sailsApp;\n\n        // Assert that NODE_ENV is changed.\n        assert.equal(process.env.NODE_ENV, 'production');\n\n        // Assert that sails config is unchanged.\n        assert.equal(sailsApp.config.environment, 'production');\n\n        assert (_.any(debugs, function(debug) {\n          return debug.indexOf('Detected Sails environment is \"production\", but NODE_ENV is `undefined`.') > -1;\n        }), 'Did not log a warning about NODE_ENV being undefined while sails environment is `production`!');\n        assert (_.any(debugs, function(debug) {\n          return debug.indexOf('Automatically setting the NODE_ENV environment variable to \"production\".') > -1;\n        }), 'Did not log a warning about NODE_ENV being set to `production`!');\n\n        return done();\n\n      });//</app0.load()>\n\n    });\n\n  });\n\n  describe('with Sails environment set to `production`, and the Node environment is `development`', function() {\n\n    var sailsApp;\n    var originalNodeEnv;\n\n    before(function() {\n      originalNodeEnv = process.env.NODE_ENV;\n      process.env.NODE_ENV = 'development';\n    });\n\n    after(function(done) {\n      if (_.isUndefined(originalNodeEnv)) {\n        delete process.env.NODE_ENV;\n      } else {\n        process.env.NODE_ENV = originalNodeEnv;\n      }\n      if (sailsApp) {\n        return sailsApp.lower(done);\n      }\n      else {\n        return done();\n      }\n    });\n\n    it('should fail to lift sails', function(done) {\n\n      // Load `app0` deep in the `'cenote'`\n      Sails().load({\n        environment: 'production',\n        log: {level: 'silent'},\n        globals: false,\n        hooks: {\n          grunt: false,\n          i18n: false,\n          session: false,\n          sockets: false\n        }\n      }, function(err, _sailsApp) {\n        if (!err) { return done(new Error('Sails should have failed to lift!')); }\n        assert.equal(err.code, 'E_INVALID_NODE_ENV');\n\n        return done();\n\n      });//</app0.load()>\n\n    });\n\n  });\n\n\n});\n"
  },
  {
    "path": "test/integration/lift.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar path = require('path');\nvar util = require('util');\nvar tmp = require('tmp');\nvar request = require('@sailshq/request');\nvar assert = require('assert');\nvar _ = require('@sailshq/lodash');\nvar MProcess = require('machinepack-process');\nvar Filesystem = require('machinepack-fs');\nvar testSpawningSailsChildProcessInCwd = require('../helpers/test-spawning-sails-child-process-in-cwd');\nvar testSpawningSailsLiftChildProcessInCwd = require('../helpers/test-spawning-sails-lift-child-process-in-cwd');\nvar appHelper = require('./helpers/appHelper');\n\ntmp.setGracefulCleanup();\n\n\n\ndescribe('Starting sails server with `sails lift`, `sails console` or `node app.js`', function() {\n\n  // Track the location of the Sails CLI, as well as the current working directory\n  // before we stop hopping about all over the place.\n  var originalCwd = process.cwd();\n  var pathToSailsCLI = path.resolve(__dirname, '../../bin/sails.js');\n\n\n  describe('in the directory of a newly-generated sails app', function() {\n\n    var pathToTestApp;\n\n    before(function(done) {\n      // Create a temp directory.\n      var tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n      pathToTestApp = path.resolve(tmpDir.name, 'testApp');\n      // Create a new Sails app.\n      MProcess.executeCommand({\n        command: util.format('node %s new %s --fast --traditional --without=lodash,async', pathToSailsCLI, 'testApp'),\n      }).exec(function(err) {\n        if (err) {return done(err);}\n        appHelper.linkDeps(pathToTestApp);\n        appHelper.linkSails(pathToTestApp);\n        return done();\n      });\n    });\n\n\n    // And CD in.\n    before(function (){\n      process.chdir(pathToTestApp);\n      Filesystem.writeSync({\n        force: true,\n        destination: 'api/controllers/getconf.js',\n        string: 'module.exports = function (req, res) { return res.json(sails.config); }'\n      }).execSync();\n      Filesystem.writeSync({\n        force: true,\n        destination: 'config/routes.js',\n        string: 'module.exports.routes = { \\'get /getconf\\': \\'getconf\\' };'\n      }).execSync();\n\n    });\n\n    // Test `sails lift` in the CWD with env vars for config.\n    describe('running `sails lift`', function (){\n      testSpawningSailsLiftChildProcessInCwd({\n        pathToSailsCLI: pathToSailsCLI,\n        liftCliArgs: ['--hooks.pubsub=false'],\n        envVars: _.extend({ 'sails_foo__bar': '{\"abc\": 123}'}, process.env),\n        httpRequestInstructions: {\n          method: 'GET',\n          uri: 'http://localhost:1337/getconf',\n        },\n        fnWithAdditionalTests: function (){\n          it('should humanize the config passed in via env vars', function (done){\n            request({\n              method: 'GET',\n              uri: 'http://localhost:1337/getconf',\n            }, function(err, response, body) {\n              if (err) { return done(err); }\n\n              try {\n\n                assert.equal(response.statusCode, 200);\n\n                try {\n                  body = JSON.parse(body);\n                } catch(e){\n                  throw new Error('Could not parse as JSON: '+e.stack+'\\nHere is what I attempted to parse: '+util.inspect(body, {depth:null})+'');\n                }\n\n                assert.equal(body.foo && body.foo.bar && body.foo.bar.abc, 123);\n\n              } catch (e) { return done(e); }\n\n              return done();\n            });\n          });\n        }\n      });\n    });\n\n    // Test `node app.js` in the CWD with env vars for config.\n    describe('running `node app.js`', function (){\n\n      testSpawningSailsChildProcessInCwd({\n        cliArgs: ['app.js', '--hooks.pubsub=false'],\n        envVars: _.extend({ 'sails_foo__bar': '{\"abc\": 123}'}, process.env),\n        fnWithAdditionalTests: function (){\n          it('should humanize the config passed in via env vars', function (done){\n            request({\n              method: 'GET',\n              uri: 'http://localhost:1337/getconf',\n            }, function(err, response, body) {\n              if (err) { return done(err); }\n              try {\n\n                assert.equal(response.statusCode, 200);\n\n                try {\n                  body = JSON.parse(body);\n                } catch(e){\n                  throw new Error('Could not parse as JSON: '+e.stack+'\\nHere is what I attempted to parse: '+util.inspect(body, {depth:null})+'');\n                }\n\n                assert.equal(body.foo && body.foo.bar && body.foo.bar.abc, 123);\n\n              } catch (e) { return done(e); }\n              return done();\n            });\n          });\n        }\n      });\n\n    });\n\n    // Test `sails console` in the CWD with env vars for config.\n    describe('running `sails console`', function (){\n\n      testSpawningSailsChildProcessInCwd({\n        cliArgs: [pathToSailsCLI, 'console', '--hooks.pubsub=false'],\n        envVars: _.extend({ 'sails_foo__bar': '{\"abc\": 123}'}, process.env),\n        fnWithAdditionalTests: function (){\n          it('should humanize the config passed in via env vars', function (done){\n            request({\n              method: 'GET',\n              uri: 'http://localhost:1337/getconf',\n            }, function(err, response, body) {\n              if (err) { return done(err); }\n              try {\n\n                assert.equal(response.statusCode, 200);\n\n                try {\n                  body = JSON.parse(body);\n                } catch(e){\n                  throw new Error('Could not parse as JSON: '+e.stack+'\\nHere is what I attempted to parse: '+util.inspect(body, {depth:null})+'');\n                }\n\n                assert.equal(body.foo && body.foo.bar && body.foo.bar.abc, 123);\n\n              } catch (e) { return done(e); }\n              return done();\n            });\n          });\n        }\n      });\n\n    });\n\n    // Test `sails lift --port=1492` in the CWD.\n    describe('running `sails lift --port=1492`', function (){\n      testSpawningSailsLiftChildProcessInCwd({\n        pathToSailsCLI: pathToSailsCLI,\n        liftCliArgs: [\n          '--port=1492',\n          '--hooks.pubsub=false'\n        ],\n        httpRequestInstructions: {\n          method: 'GET',\n          uri: 'http://localhost:1492/getconf',\n        },\n        fnWithAdditionalTests: function (){\n          it('should NOT be able to contact localhost:1337 anymore', function (done){\n            request({\n              method: 'GET',\n              uri: 'http://localhost:1337',\n            }, function(err, response, body) {\n              if (err) { return done(); }\n              return done(new Error('Should not be able to communicate with locahost:1337 anymore.... Here is the response we received:'+util.inspect(response,{depth:null})+'\\n\\n* * Troublehooting * *\\n Perhaps the Sails app running in the child process was not properly cleaned up when it received SIGTERM?  Or could be a problem with the tests.  Find out all this and more after you fix it.'));\n            });\n          });\n        }\n      });\n    });\n\n\n    // And CD back to where we were before.\n    after(function () {\n      process.chdir(originalCwd);\n    });\n\n  });//</in the directory of a newly-generated sails app>\n\n\n\n\n\n\n  describe('in an empty directory', function() {\n\n    var pathToEmptyDirectory;\n\n    before(function() {\n      // Create a temp directory.\n      var tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n      pathToEmptyDirectory = tmpDir.name;\n    });\n\n    // And CD in.\n    before(function (){\n      process.chdir(pathToEmptyDirectory);\n    });\n\n    // Now inject a describe block that tests lifing Sails in the CWD using\n    // our wonderful little helper: \"testSpawningSailsLiftChildProcessInCwd()\".\n    describe('running `sails lift`', function (){\n      testSpawningSailsLiftChildProcessInCwd({\n        pathToSailsCLI: pathToSailsCLI,\n        liftCliArgs: ['--hooks.pubsub=false'],\n        httpRequestInstructions: {\n          method: 'GET',\n          uri: 'http://localhost:1337',\n          expectedStatusCode: 404\n        }\n      });\n    });\n\n    // And CD back to whererever we were before.\n    after(function () {\n      process.chdir(originalCwd);\n    });\n\n  });//</in an empty directory>\n\n\n});\n"
  },
  {
    "path": "test/integration/middleware.404.test.js",
    "content": "var _ = require('@sailshq/lodash');\nvar request = require('@sailshq/request');\nvar Sails = require('../../lib').Sails;\nvar assert = require('assert');\nvar fs = require('fs-extra');\nvar request = require('@sailshq/request');\nvar appHelper = require('./helpers/appHelper');\nvar path = require('path');\n\ndescribe('middleware :: ', function() {\n\n  describe('404 :: ', function() {\n\n    var appName = 'testApp';\n    var sailsApp;\n\n    before(function(done) {\n      appHelper.build(function(err) {\n        if (err) {return done(err);}\n        fs.writeFileSync(path.resolve('..', appName, 'views', '404.ejs'), 'no file here bruh!');\n        return done();\n      });\n    });\n\n    after(function() {\n      process.chdir('../');\n      appHelper.teardown();\n    });\n\n    describe('with no custom 404 handler installed', function() {\n\n      before(function(done) {\n        appHelper.lift({\n          hooks: {\n            pubsub: false\n          }\n        }, function(err, _sailsApp) {\n          if (err) { return done(err); }\n          sailsApp = _sailsApp;\n          return done();\n        });\n      });\n\n      it('the default 404 handler should respond to a request for an unbound URL', function(done) {\n\n        request(\n          {\n            method: 'GET',\n            uri: 'http://localhost:1342/nothing',\n            headers: {\n              'Accept': 'text/html'\n            }\n          },\n          function(err, response, body) {\n            if (err) { return done(err); }\n            assert.equal(response.statusCode, 404);\n            assert(body.match('<html>'));\n            assert(body.match('no file here bruh!'));\n            return done();\n          }\n        );\n\n      });\n\n\n      after(function(done) {\n        sailsApp.lower(done);\n      });\n\n    });\n\n    describe('with a custom 404 handler installed', function() {\n\n      before(function(done) {\n        appHelper.lift({\n          hooks: {\n            pubsub: false\n          },\n          http: {\n            middleware: {\n              order: [\n                'startRequestTimer',\n                'cookieParser',\n                'session',\n                'bodyParser',\n                'handleBodyParserError',\n                'compress',\n                'methodOverride',\n                'poweredBy',\n                '$custom',\n                'router',\n                'www',\n                'favicon',\n                'notfound'\n              ],\n              notfound: function (req, res) {\n                return res.send('custom nada bro');\n              }\n            }\n          }\n        }, function(err, _sailsApp) {\n          if (err) { return done(err); }\n          sailsApp = _sailsApp;\n          return done();\n        });\n      });\n\n      it('the custom 404 handler should respond to a request for an unbound URL', function(done) {\n\n        request(\n          {\n            method: 'GET',\n            uri: 'http://localhost:1342/nothing',\n            headers: {\n              'Accept': 'text/html'\n            }\n          },\n          function(err, response, body) {\n            if (err) { return done(err); }\n            assert.equal(response.statusCode, 200);\n            assert.equal(body, 'custom nada bro');\n            return done();\n          }\n        );\n\n      });\n\n\n      after(function(done) {\n        sailsApp.lower(done);\n      });\n\n    });\n\n    describe('with 404 left out of a custom middleware order', function() {\n\n      before(function(done) {\n        appHelper.lift({\n          hooks: {\n            pubsub: false\n          },\n          http: {\n            middleware: {\n              order: [\n                'startRequestTimer',\n                'cookieParser',\n                'session',\n                'bodyParser',\n                'handleBodyParserError',\n                'compress',\n                'methodOverride',\n                'poweredBy',\n                '$custom',\n                'router',\n                'www',\n                'favicon'\n              ]\n            }\n          }\n        }, function(err, _sailsApp) {\n          if (err) { return done(err); }\n          sailsApp = _sailsApp;\n          return done();\n        });\n      });\n\n      it('the default 404 handler should still respond to a request for an unbound URL', function(done) {\n\n        request(\n          {\n            method: 'GET',\n            uri: 'http://localhost:1342/nothing',\n            headers: {\n              'Accept': 'text/html'\n            }\n          },\n          function(err, response, body) {\n            if (err) { return done(err); }\n            assert.equal(response.statusCode, 404);\n            assert(body.match('<html>'));\n            assert(body.match('no file here bruh!'));\n            return done();\n          }\n        );\n\n      });\n\n      after(function(done) {\n        sailsApp.lower(done);\n      });\n\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/integration/middleware.500.test.js",
    "content": "var _ = require('@sailshq/lodash');\nvar request = require('@sailshq/request');\nvar Sails = require('../../lib').Sails;\nvar assert = require('assert');\nvar fs = require('fs-extra');\nvar request = require('@sailshq/request');\nvar appHelper = require('./helpers/appHelper');\nvar path = require('path');\n\ndescribe('middleware :: ', function() {\n\n  describe('500 :: ', function() {\n\n    var appName = 'testApp';\n    var sailsApp;\n\n    before(function(done) {\n      appHelper.build(function(err) {\n        if (err) {return done(err);}\n        fs.writeFileSync(path.resolve('..', appName, 'views', '500.ejs'), 'bogus err bruh!');\n        fs.writeFileSync(path.resolve('..', appName, 'config', 'routes.js'), 'module.exports.routes = { \\'/err\\': function (req, res) {throw new Error(\\'errrr\\');} };');\n        return done();\n      });\n    });\n\n    after(function() {\n      process.chdir('../');\n      appHelper.teardown();\n    });\n\n    describe('with no custom 500 handler installed', function() {\n\n      before(function(done) {\n        appHelper.lift({\n          hooks: {\n            pubsub: false\n          }\n        }, function(err, _sailsApp) {\n          if (err) { return done(err); }\n          sailsApp = _sailsApp;\n          return done();\n        });\n      });\n\n      it('the default 500 handler should respond to a request that causes an error', function(done) {\n\n        request(\n          {\n            method: 'GET',\n            uri: 'http://localhost:1342/err',\n            headers: {\n              'Accept': 'text/html'\n            }\n          },\n          function(err, response, body) {\n            if (err) { return done(err); }\n            assert.equal(response.statusCode, 500);\n            assert(body.match('<html>'));\n            assert(body.match('bogus err bruh!'));\n            return done();\n          }\n        );\n\n      });\n\n\n      after(function(done) {\n        sailsApp.lower(done);\n      });\n\n    });\n\n    describe('with a custom 500 handler installed', function() {\n\n      before(function(done) {\n        appHelper.lift({\n          hooks: {\n            pubsub: false\n          },\n          http: {\n            middleware: {\n              order: [\n                'startRequestTimer',\n                'cookieParser',\n                'session',\n                'bodyParser',\n                'handleBodyParserError',\n                'compress',\n                'methodOverride',\n                'poweredBy',\n                '$custom',\n                'router',\n                'www',\n                'favicon',\n                'err'\n              ],\n              err: function (err, req, res, next) {\n                return res.send('custom err bro');\n              }\n            }\n          }\n        }, function(err, _sailsApp) {\n          if (err) { return done(err); }\n          sailsApp = _sailsApp;\n          return done();\n        });\n      });\n\n      it('the custom 500 handler should respond to a request that causes an error', function(done) {\n\n        request(\n          {\n            method: 'GET',\n            uri: 'http://localhost:1342/err',\n            headers: {\n              'Accept': 'text/html'\n            }\n          },\n          function(err, response, body) {\n            if (err) { return done(err); }\n            assert.equal(response.statusCode, 200);\n            assert.equal(body, 'custom err bro');\n            return done();\n          }\n        );\n\n      });\n\n\n      after(function(done) {\n        sailsApp.lower(done);\n      });\n\n    });\n\n    describe('with 500 left out of a custom middleware order', function() {\n\n      before(function(done) {\n        appHelper.lift({\n          hooks: {\n            pubsub: false\n          },\n          http: {\n            middleware: {\n              order: [\n                'startRequestTimer',\n                'cookieParser',\n                'session',\n                'bodyParser',\n                'handleBodyParserError',\n                'compress',\n                'methodOverride',\n                'poweredBy',\n                '$custom',\n                'router',\n                'www',\n                'favicon'\n              ]\n            }\n          }\n        }, function(err, _sailsApp) {\n          if (err) { return done(err); }\n          sailsApp = _sailsApp;\n          return done();\n        });\n      });\n\n      it('the default 500 handler should respond to a request that causes an error', function(done) {\n\n        request(\n          {\n            method: 'GET',\n            uri: 'http://localhost:1342/err',\n            headers: {\n              'Accept': 'text/html'\n            }\n          },\n          function(err, response, body) {\n            if (err) { return done(err); }\n            assert.equal(response.statusCode, 500);\n            assert(body.match('<html>'));\n            assert(body.match('bogus err bruh!'));\n            return done();\n          }\n        );\n\n      });\n\n      after(function(done) {\n        sailsApp.lower(done);\n      });\n\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/integration/middleware.compression.test.js",
    "content": "var _ = require('@sailshq/lodash');\nvar request = require('@sailshq/request');\nvar Sails = require('../../lib').Sails;\nvar assert = require('assert');\n\n\n\n\n\n\n\n\n\ndescribe('middleware :: ', function() {\n\n  describe('compression :: ', function() {\n\n    // Source text (must be > 1024 bytes to trigger compression)\n    var lipsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras fringilla mollis sapien sed consequat. Cras vestibulum iaculis rhoncus. Vestibulum et auctor dolor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc vitae risus sit amet massa lacinia luctus. Quisque auctor hendrerit fermentum. Nullam neque diam, condimentum a nisi eu, ornare porttitor nisl. Praesent porttitor augue turpis, eu consectetur ligula suscipit eget. Aliquam placerat turpis ut varius vestibulum. Pellentesque ligula velit, molestie vel purus sollicitudin, elementum rutrum odio. Ut ultricies convallis leo. Integer id nisl vel tellus laoreet iaculis sed et ipsum. In malesuada sem vitae porttitor sollicitudin. Maecenas sodales est eu augue auctor, in accumsan dolor lacinia. Proin at euismod nibh, eu congue velit. Vestibulum risus velit, vulputate in dui in, commodo sodales elit. Curabitur consectetur justo tincidunt odio imperdiet blandit. Etiam gravida eu ante commodo viverra. Ut sed dapibus purus, eu vulputate neque. Maecenas suscipit felis ac sapien iaculis tempor. Etiam quis vulputate turpis. Cras at nulla lectus. Vestibulum non magna sem. Aliquam tristique lacinia ligula, non interdum justo scelerisque vitae. Praesent molestie eu nibh vel volutpat. Pellentesque ut lacus a tortor lacinia condimentum. Quisque blandit facilisis nunc sed tempus. Praesent dapibus leo at enim mollis, tristique facilisis turpis aliquam. Vestibulum tempus felis ac arcu rhoncus, in efficitur elit sodales. Suspendisse eu odio odio. Vestibulum tempus elementum massa, et rutrum risus ultricies ut. Ut ac mattis nulla. Aenean tristique sollicitudin metus. Morbi massa purus, hendrerit non placerat non, imperdiet nec turpis. Nulla et ultrices metus. Nulla eget congue urna, ut rutrum enim. Aenean rutrum dui massa, non luctus urna dignissim vel. Morbi a suscipit ligula. Nunc laoreet nisi eleifend tortor volutpat finibus vel nec risus. Fusce maximus non sem vel mattis. Etiam iaculis, turpis at sollicitudin blandit, massa mi finibus nunc, nec auctor ex nisl sed.';\n\n\n    describe('In the production environment', function() {\n\n      // Lift a Sails instance in production mode\n      var app = Sails();\n      var originalNodeEnv;\n\n      before(function() {\n        originalNodeEnv = process.env.NODE_ENV;\n        process.env.NODE_ENV = 'production';\n      });\n\n      after(function() {\n        process.env.NODE_ENV = originalNodeEnv;\n      });\n\n      before(function (done){\n        app.lift({\n          globals: false,\n          port: 1535,\n          environment: 'production',\n          log: {level: 'silent'},\n          hooks: {session: false, grunt: false, pubsub: false, sockets: false},\n          routes: {\n            '/test': function(req, res) {\n              return res.send(lipsum);\n            }\n          }\n        }, done);\n      });\n\n\n      it('responses should be compressed', function(done) {\n        var rawLen = 0, res = '';\n        request(\n          {\n            method: 'GET',\n            uri: 'http://localhost:1535/test',\n            gzip: true\n          }\n        )\n        .on('data', function(data) {\n          // decompressed data as it is received\n          res += data;\n        })\n        .on('response', function(response) {\n          // unmodified http.IncomingMessage object\n          response.on('data', function(data) {\n            rawLen += data.length;\n          });\n        })\n        .on('end', function(err) {\n          if (err) {return done(err);}\n          assert.equal(res, lipsum);\n          assert(rawLen < lipsum.length, 'Expected length of raw response data (' + rawLen.toString() + ') to be < length of source data (' + lipsum.length.toString() + ').');\n          return done(err);\n        });\n\n      });\n\n      after(function(done) {\n        app.lower(done);\n      });\n\n    });\n\n    describe('In the development environment', function() {\n\n      // Lift a Sails instance in production mode\n      var app = Sails();\n      before(function (done){\n        app.lift({\n          globals: false,\n          port: 1535,\n          environment: 'development',\n          log: {level: 'silent'},\n          hooks: {session: false, grunt: false},\n          routes: {\n            '/test': function(req, res) {\n              res.send(lipsum);\n            }\n          }\n        }, done);\n      });\n\n\n      it('responses should not be compressed', function(done) {\n        var rawLen = 0, res = '';\n        request(\n          {\n            method: 'GET',\n            uri: 'http://localhost:1535/test',\n            gzip: true\n          }\n        )\n        .on('data', function(data) {\n          // decompressed data as it is received\n          res += data;\n        })\n        .on('response', function(response) {\n          // unmodified http.IncomingMessage object\n          response.on('data', function(data) {\n            rawLen += data.length;\n          });\n        })\n        .on('end', function(err) {\n          if (err) {return done(err);}\n          assert.equal(res, lipsum);\n          assert.equal(rawLen, lipsum.length, 'Expected length of raw response data (' + rawLen.toString() + ') to be equal to length of source data (' + lipsum.length.toString() + ').');\n          return done(err);\n        });\n\n      });\n\n      after(function(done) {\n        app.lower(done);\n      });\n\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/integration/middleware.cookieParser.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar request = require('@sailshq/request');\nvar Sails = require('../../lib').Sails;\nvar assert = require('assert');\n\n\n\n\n\n\n\ndescribe('middleware :: ', function() {\n\n  describe('cookie parser :: ', function() {\n\n    describe('http requests :: ', function() {\n\n      describe('with a valid session secret', function() {\n\n        // Lift a Sails instance in production mode\n        var app = Sails();\n        before(function (done){\n          app.lift({\n            globals: false,\n            port: 1535,\n            environment: 'development',\n            log: {level: 'silent'},\n            session: {\n              secret: 'abc123'\n            },\n            hooks: {grunt: false, pubsub: false},\n            routes: {\n              '/test': function(req, res) {\n                res.json({\n                  cookies: req.cookies,\n                  signedCookies: req.signedCookies\n                });\n              }\n            }\n          }, done);\n        });\n\n        it('when sending a request with a Cookie: header, req.cookies and req.signedCookies should be populated', function(done) {\n          var rawLen = 0, res = '';\n          request(\n            {\n              method: 'GET',\n              uri: 'http://localhost:1535/test',\n              headers: {\n                'cookie': 'foo=bar; owl=s%3Ahoot.v0ELGJM%2B8t4aP0YeUpcC31OKnAQ%2BqUTf%2F4WaLaaosJg; abc=123',\n              }\n            },\n            function(err, response, body) {\n              if(err){ return done(err); }\n              body = JSON.parse(body);\n              assert(body.cookies);\n              assert(body.signedCookies);\n              assert.equal(body.cookies.foo, 'bar');\n              assert.equal(body.cookies.abc, '123');\n              assert.equal(body.signedCookies.owl, 'hoot');\n              assert(!body.cookies.owl);\n              return done();\n            }\n          );\n        });\n\n        after(function(done) {\n          app.lower(done);\n        });\n      });\n\n      describe('with no session secret and session hook disabled', function() {\n\n        // Lift a Sails instance in production mode\n        var app = Sails();\n        before(function (done){\n          app.lift({\n            globals: false,\n            port: 1535,\n            environment: 'development',\n            log: {level: 'silent'},\n            session: {\n              secret: null\n            },\n            hooks: {\n              session: false,\n              grunt: false,\n              pubsub: false\n            },\n            routes: {\n              '/test': function(req, res) {\n                res.json({\n                  cookies: req.cookies,\n                  signedCookies: req.signedCookies\n                });\n              }\n            }\n          }, done);\n        });\n\n        it('when sending a request with a Cookie: header, req.cookies and req.signedCookies should be populated', function(done) {\n          var rawLen = 0, res = '';\n          request(\n            {\n              method: 'GET',\n              uri: 'http://localhost:1535/test',\n              headers: {\n                'cookie': 'foo=bar; owl=s%3Ahoot.v0ELGJM%2B8t4aP0YeUpcC31OKnAQ%2BqUTf%2F4WaLaaosJg; abc=123',\n              }\n            },\n            function(err, response, body) {\n              if(err){ return done(err); }\n              body = JSON.parse(body);\n              assert(body.cookies);\n              assert(body.signedCookies);\n              assert.equal(body.cookies.foo, 'bar');\n              assert.equal(body.cookies.abc, '123');\n              assert.equal(body.cookies.owl, 's:hoot.v0ELGJM+8t4aP0YeUpcC31OKnAQ+qUTf/4WaLaaosJg');\n              assert(!body.signedCookies.owl);\n              return done();\n            }\n          );\n        });\n\n        after(function(done) {\n          app.lower(done);\n        });\n\n      });\n\n      describe('with an invalid session secret and session hook disabled', function() {\n\n        var app = Sails();\n\n        it('should throw an error when lifting Sails', function(done) {\n\n          app.lift({\n            globals: false,\n            port: 1535,\n            environment: 'development',\n            log: {level: 'silent'},\n            session: {\n              secret: 12345\n            },\n            hooks: {\n              session: false,\n              grunt: false,\n              pubsub: false\n            },\n            routes: {\n              '/test': function(req, res) {\n                res.json({\n                  cookies: req.cookies,\n                  signedCookies: req.signedCookies\n                });\n              }\n            }\n          }, function(err) {\n            if (!err) {return done(new Error('Should have thrown an error!'));}\n            return done();\n          });\n\n        });\n\n        after(function(done) {\n          app.lower(done);\n        });\n\n      });\n\n    });\n\n    describe('virtual requests :: ', function() {\n\n      describe('with a valid session secret', function() {\n\n        // Lift a Sails instance in production mode\n        var app = Sails();\n        before(function (done){\n          app.load({\n            globals: false,\n            environment: 'development',\n            log: {level: 'silent'},\n            session: {\n              secret: 'abc123'\n            },\n            hooks: {\n              http: false,\n              views: false,\n              sockets: false,\n              pubsub: false\n            },\n            routes: {\n              '/test': function(req, res) {\n                res.json({\n                  cookies: req.cookies,\n                  signedCookies: req.signedCookies\n                });\n              }\n            }\n          }, done);\n        });\n\n        it('when sending a request with a Cookie: header, req.cookies and req.signedCookies should be populated', function(done) {\n          var rawLen = 0, res = '';\n          app.request(\n            {\n              method: 'GET',\n              url: '/test',\n              headers: {\n                'cookie': 'foo=bar; owl=s%3Ahoot.v0ELGJM%2B8t4aP0YeUpcC31OKnAQ%2BqUTf%2F4WaLaaosJg; abc=123',\n              }\n            },\n            function(err, response, body) {\n              if(err){ return done(err); }\n              assert(body.cookies);\n              assert(body.signedCookies);\n              assert.equal(body.cookies.foo, 'bar');\n              assert.equal(body.cookies.abc, '123');\n              assert.equal(body.signedCookies.owl, 'hoot');\n              assert(!body.cookies.owl);\n              return done();\n            }\n          );\n        });\n\n        after(function(done) {\n          app.lower(done);\n        });\n\n      });\n\n      describe('with no session secret and session hook disabled', function() {\n\n        // Lift a Sails instance in production mode\n        var app = Sails();\n        before(function (done){\n          app.load({\n            globals: false,\n            environment: 'development',\n            log: {level: 'silent'},\n            session: {\n              secret: null\n            },\n            hooks: {\n              session: false,\n              http: false,\n              views: false,\n              sockets: false,\n              pubsub: false\n            },\n            routes: {\n              '/test': function(req, res) {\n                return res.json({\n                  cookies: req.cookies,\n                  signedCookies: req.signedCookies\n                });\n              }\n            }\n          }, done);\n        });\n\n\n        it('when sending a request with a Cookie: header, req.cookies and req.signedCookies should be populated', function(done) {\n          var rawLen = 0, res = '';\n          app.request(\n            {\n              method: 'GET',\n              url: '/test',\n              headers: {\n                'cookie': 'foo=bar; owl=s%3Ahoot.v0ELGJM%2B8t4aP0YeUpcC31OKnAQ%2BqUTf%2F4WaLaaosJg; abc=123',\n              }\n            },\n            function(err, response, body) {\n              if(err){ return done(err); }\n              assert(body.cookies);\n              assert(body.signedCookies);\n              assert.equal(body.cookies.foo, 'bar');\n              assert.equal(body.cookies.abc, '123');\n              assert.equal(body.cookies.owl, 's:hoot.v0ELGJM+8t4aP0YeUpcC31OKnAQ+qUTf/4WaLaaosJg');\n              assert(!body.signedCookies.owl);\n              return done();\n            }\n          );\n        });\n\n        after(function(done) {\n          app.lower(done);\n        });\n\n      });\n\n      describe('with an invalid session secret and session hook disabled', function() {\n\n        var app = Sails();\n\n        it('should throw an error when lifting Sails', function(done) {\n\n          app.load({\n            globals: false,\n            environment: 'development',\n            log: {level: 'silent'},\n            session: {\n              secret: 12345\n            },\n            hooks: {\n              session: false,\n              http: false,\n              views: false,\n              sockets: false,\n              pubsub: false\n            },\n            routes: {\n              '/test': function(req, res) {\n                res.json({\n                  cookies: req.cookies,\n                  signedCookies: req.signedCookies\n                });\n              }\n            }\n          }, function(err) {\n            if (!err) {return done(new Error('Should have thrown an error!'));}\n            return done();\n          });\n\n        });\n\n        after(function(done) {\n          app.lower(done);\n        });\n\n      });\n\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/integration/middleware.favicon.test.js",
    "content": "var _ = require('@sailshq/lodash');\nvar request = require('@sailshq/request');\nvar Sails = require('../../lib').Sails;\nvar assert = require('assert');\nvar fs = require('fs-extra');\nvar request = require('@sailshq/request');\nvar appHelper = require('./helpers/appHelper');\nvar path = require('path');\n\ndescribe('middleware :: ', function() {\n\n  describe('favicon :: ', function() {\n\n    var appName = 'testApp';\n    var sailsApp;\n\n    describe('with no favicon file in the assets folder', function() {\n\n      before(function(done) {\n        appHelper.build(done);\n      });\n\n      before(function(done) {\n        appHelper.lift({\n          hooks: {\n            pubsub: false\n          }\n        }, function(err, _sailsApp) {\n          if (err) { return done(err); }\n          sailsApp = _sailsApp;\n          return done();\n        });\n      });\n\n      it('the default sailboat favicon should be provided', function(done) {\n\n        var default_favicon = fs.readFileSync(path.resolve(__dirname, '../../lib/hooks/http/default-favicon.ico'));\n        request(\n          {\n            method: 'GET',\n            uri: 'http://localhost:1342/favicon.ico',\n          },\n          function(err, response, body) {\n            if (err) { return done(err); }\n            assert.equal(response.statusCode, 200);\n            assert.equal(default_favicon.toString('utf-8'), body);\n            return done();\n          }\n        );\n\n      });\n\n      after(function() {\n        process.chdir('../');\n        appHelper.teardown();\n      });\n\n      after(function(done) {\n        sailsApp.lower(done);\n      });\n\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/integration/middleware.handleBodyParserError.test.js",
    "content": "var _ = require('@sailshq/lodash');\nvar request = require('@sailshq/request');\nvar Sails = require('../../lib').Sails;\nvar assert = require('assert');\nvar fs = require('fs-extra');\nvar request = require('@sailshq/request');\nvar appHelper = require('./helpers/appHelper');\nvar path = require('path');\n\ndescribe('middleware :: ', function() {\n\n  describe('handleBodyParserError :: ', function() {\n\n    var appName = 'testApp';\n    var sailsApp;\n\n    before(function(done) {\n      appHelper.build(done);\n    });\n\n    after(function() {\n      process.chdir('../');\n      appHelper.teardown();\n    });\n\n    describe('default handleBodyParserError middleware', function() {\n\n      before(function(done) {\n        appHelper.lift({\n          hooks: {\n            pubsub: false\n          }\n        }, function(err, _sailsApp) {\n          if (err) { return done(err); }\n          sailsApp = _sailsApp;\n          return done();\n        });\n      });\n\n      it('should handle body parser errors', function(done) {\n\n        request(\n          {\n            method: 'POST',\n            uri: 'http://localhost:1342/nothing',\n            headers: {\n              'Content-type': 'application/json'\n            },\n            body: '{ foo:'\n          },\n          function(err, response, body) {\n            if (err) { return done(err); }\n            assert(body.match('Unable to parse HTTP body'));\n            return done();\n          }\n        );\n\n      });\n\n      after(function(done) {\n        sailsApp.lower(done);\n      });\n\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/integration/middleware.sails.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar request = require('@sailshq/request');\nvar Sails = require('../../lib').Sails;\nvar assert = require('assert');\n\n\n\n\n\ndescribe('middleware :: ', function() {\n\n  describe('sails :: ', function() {\n\n    describe('http requests :: ', function() {\n\n      var sid;\n\n      // Lift a Sails instance.\n      var app = Sails();\n      before(function (done){\n        app.lift({\n          globals: false,\n          port: 1535,\n          environment: 'development',\n          log: {level: 'silent'},\n          session: {\n            secret: 'abc123'\n          },\n          hooks: {\n            grunt: false,\n            request: false,\n            pubsub: false\n          },\n          routes: {\n            '/test': function(req, res) {\n              var defined = (req._sails !== undefined) ? 'defined' : 'undefined';\n              res.send('req._sails is ' + defined);\n            }\n          }\n        }, done);\n      });\n\n\n      it('req._sails should be set if request hook is disabled', function(done) {\n\n        request({\n          method: 'GET',\n          uri: 'http://localhost:1535/test',\n        }, function(err, response, body) {\n          if (err) { return done(err); }\n          assert.equal(body, 'req._sails is defined');\n          return done();\n        });\n      });\n\n      after(function(done) {\n        return app.lower(done);\n      });\n\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/integration/middleware.session.redis.test.js",
    "content": "var _ = require('@sailshq/lodash');\nvar request = require('@sailshq/request');\nvar Sails = require('../../lib').Sails;\nvar assert = require('assert');\nvar cookie = require('cookie');\nvar tmp = require('tmp');\nvar path = require('path');\nvar fs = require('fs-extra');\n\nif (process.env.TEST_REDIS_SESSION) {\n\n  describe('middleware :: ', function() {\n\n    describe('session :: ', function() {\n\n      describe('with redis adapter ::', function() {\n\n        var curDir, tmpDir;\n\n        before(function() {\n          // Cache the current working directory.\n          curDir = process.cwd();\n          // Create a temp directory.\n          tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n          // Switch to the temp directory.\n          process.chdir(tmpDir.name);\n          // Ensure a symlink to the connect-redis adapter.\n          fs.ensureSymlinkSync(path.resolve(__dirname, '..', '..', 'node_modules', 'connect-redis'), path.resolve(tmpDir.name, 'node_modules', 'connect-redis'));\n        });\n\n        after(function() {\n          process.chdir(curDir);\n        });\n\n        it('should fail to lift if the Redis server can\\'t be reached', function(done) {\n\n          var app = Sails();\n          app.lift({\n\n            globals: false,\n            environment: 'development',\n            log: {level: 'silent'},\n            session: {\n              secret: 'abc123',\n              adapter: 'connect-redis',\n              port: 6300\n            },\n            hooks: {grunt: false},\n            routes: {\n              '/test': function(req, res) {\n                var count = req.session.count || 1;\n                req.session.count = count + 1;\n                return res.send('Count is ' + count);\n              }\n            }\n\n          }, function(err) {\n            if (err && err.code === 'ECONNREFUSED') {\n              return done();\n            }\n            else if (err) {\n              return done(err);\n            }\n            else {\n              return done(new Error('Expected an error, but Sails appears to have lifted!'));\n            }\n          });\n        });\n\n        describe('http requests :: ', function() {\n\n          var sid;\n\n          // Lift two Sails instances connected to the same Redis server\n          var app1 = Sails();\n          var app2 = Sails();\n          before(function (done){\n\n            var liftOptions = {\n              globals: false,\n              environment: 'development',\n              log: {level: 'silent'},\n              session: {\n                secret: 'abc123',\n                pass: 'secret',\n                db: 3,\n                adapter: 'connect-redis',\n                port: 6380\n              },\n              hooks: {grunt: false},\n              routes: {\n                '/test': function(req, res) {\n                  var count = req.session.count || 1;\n                  req.session.count = count + 1;\n                  return res.send('Count is ' + count);\n                }\n              }\n            };\n\n            app1.lift(_.extend({port: 1535}, _.cloneDeep(liftOptions)), function(err) {\n              if (err) {return done(err);}\n              app2.lift(_.extend({port: 1536}, _.cloneDeep(liftOptions)), function(err) {\n                if (err) {return done(err);}\n                return done();\n              });\n            });\n          });\n\n          it('a server responses should supply a cookie with a session ID', function(done) {\n            request(\n              {\n                method: 'GET',\n                uri: 'http://localhost:1535/test',\n              },\n              function(err, response, body) {\n                if (err) {return done(err);}\n                assert.equal(body, 'Count is 1');\n                assert(response.headers['set-cookie']);\n                var cookies = require('cookie').parse(response.headers['set-cookie'][0]);\n                assert(cookies['sails.sid']);\n                sid = cookies['sails.sid'];\n                return done();\n              }\n            );\n          });\n\n          it('a subsequent request to a different app sharing the same session store, with the same cookie, should retrieve the same session', function(done) {\n\n            request(\n              {\n                method: 'GET',\n                uri: 'http://localhost:1536/test',\n                headers: {\n                  Cookie: 'sails.sid=' + sid\n                }\n              },\n              function(err, response, body) {\n                if (err) {return done(err);}\n                assert.equal(body, 'Count is 2');\n                return done();\n              }\n            );\n\n          });\n\n          after(function(done) {\n            return app1.lower(function(err) {if(err) {return done(err);} app2.lower(done);});\n          });\n\n\n        });\n\n        describe('virtual requests :: ', function() {\n\n          var sid;\n\n          // Lift two Sails instances connected to the same Redis server\n          var app1 = Sails();\n          var app2 = Sails();\n          before(function (done){\n\n            var liftOptions = {\n              globals: false,\n              environment: 'development',\n              log: {level: 'silent'},\n              session: {\n                secret: 'abc123',\n                adapter: 'connect-redis',\n                pass: 'secret',\n                db: 3,\n                port: 6380\n              },\n              hooks: {grunt: false},\n              routes: {\n                '/test': function(req, res) {\n                  var count = req.session.count || 1;\n                  req.session.count = count + 1;\n                  return res.send('Count is ' + count);\n                }\n              }\n            };\n\n            app1.lift(_.extend({port: 1535}, _.cloneDeep(liftOptions)), function(err) {\n              if (err) {return done(err);}\n              app2.lift(_.extend({port: 1536}, _.cloneDeep(liftOptions)), function(err) {\n                if (err) {return done(err);}\n                return done();\n              });\n            });\n          });\n\n          it('a server responses should supply a cookie with a session ID', function(done) {\n            app1.request(\n              {\n                method: 'GET',\n                url: '/test',\n              },\n              function(err, response, body) {\n                if (err) {return done(err);}\n                assert.equal(body, 'Count is 1');\n                assert(response.headers['set-cookie']);\n                var cookies = require('cookie').parse(response.headers['set-cookie'][0]);\n                assert(cookies['sails.sid']);\n                sid = cookies['sails.sid'];\n                return done();\n              }\n            );\n          });\n\n          it('a subsequent request to a different app sharing the same session store, with the same cookie, should retrieve the same session', function(done) {\n\n            app2.request(\n              {\n                method: 'GET',\n                url: '/test',\n                headers: {\n                  Cookie: 'sails.sid=' + sid\n                }\n              },\n              function(err, response, body) {\n                if (err) {return done(err);}\n                assert.equal(body, 'Count is 2');\n                return done();\n              }\n            );\n\n          });\n\n          after(function(done) {\n            return app1.lower(function(err) {if(err) {return done(err);} app2.lower(done);});\n          });\n\n\n        });\n\n      });\n\n    });\n\n  });\n}\n"
  },
  {
    "path": "test/integration/middleware.session.test.js",
    "content": "var _ = require('@sailshq/lodash');\nvar request = require('@sailshq/request');\nvar Sails = require('../../lib').Sails;\nvar assert = require('assert');\nvar cookie = require('cookie');\nvar tmp = require('tmp');\nvar path = require('path');\nvar fs = require('fs');\n\n\n\ndescribe('middleware :: ', function() {\n\n  describe('session :: ', function() {\n\n    describe('with invalid `cookie.secure` setting', function() {\n\n      it('should throw an error', function(done) {\n\n        var app = Sails();\n        app.lift({\n          globals: false,\n          port: 1535,\n          environment: 'development',\n          log: {level: 'silent'},\n          session: {\n            cookie: {\n              secure: 'true'\n            }\n          },\n          hooks: {grunt: false, pubsub: false},\n        }, function(err, _app) {\n\n          if (err && err.code && err.code === 'E_SESSION_BAD_COOKIE_SECURE') {\n            return done();\n          }\n\n          if (err) {\n            return done(err);\n          }\n\n          _app.lower(function(err) {\n            if (err) {\n              return done(new Error('App lifted when it should have failed with E_SESSION_BAD_COOKIE_SECURE.  Additionally, an error occurred while lowering: ' + util.inspect(err)));\n            }\n            return done(new Error('App lifted when it should have failed with E_SESSION_BAD_COOKIE_SECURE'));\n          });\n\n        });\n\n      });\n\n    });\n\n    describe('http requests :: ', function() {\n\n      describe('with a valid session secret', function() {\n\n        describe('using built-in (memory) store', function() {\n\n          var sid;\n\n          // Lift a Sails instance in production mode\n          var app = Sails();\n          before(function (done){\n            app.lift({\n              globals: false,\n              port: 1535,\n              environment: 'development',\n              log: {level: 'silent'},\n              session: {\n                secret: 'abc123'\n              },\n              hooks: {grunt: false, pubsub: false},\n              routes: {\n                '/test': function(req, res) {\n                  var count = req.session.count || 1;\n                  req.session.count = count + 1;\n                  return res.send('Count is ' + count);\n                }\n              }\n            }, done);\n          });\n\n          it('a server responses should supply a cookie with a session ID', function(done) {\n\n            request(\n              {\n                method: 'GET',\n                uri: 'http://localhost:1535/test',\n              },\n              function(err, response, body) {\n                assert.equal(body, 'Count is 1');\n                assert(response.headers['set-cookie']);\n                var cookies = require('cookie').parse(response.headers['set-cookie'][0]);\n                assert(cookies['sails.sid']);\n                sid = cookies['sails.sid'];\n                return done();\n              }\n            );\n          });\n\n          it('a subsequent request using that session ID in a \"Cookie\" header should use the same session', function(done) {\n\n            request(\n              {\n                method: 'GET',\n                uri: 'http://localhost:1535/test',\n                headers: {\n                  Cookie: 'sails.sid=' + sid\n                }\n              },\n              function(err, response, body) {\n                assert.equal(body, 'Count is 2');\n                return done();\n              }\n            );\n\n          });\n\n          after(function(done) {\n            return app.lower(done);\n          });\n        });\n\n        describe('using 3rd-party (file) store', function() {\n\n          var curDir, tmpDir, sailsApp;\n          var sid;\n\n          // Lift a Sails instance in production mode\n          var app = Sails();\n          before(function (done){\n\n            // Cache the current working directory.\n            curDir = process.cwd();\n            // Create a temp directory.\n            tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n            // Switch to the temp directory.\n            process.chdir(tmpDir.name);\n\n            app.lift({\n              globals: false,\n              port: 1535,\n              environment: 'development',\n              log: {level: 'silent'},\n              session: {\n                secret: 'abc123',\n                adapter: require('session-file-store'),\n                // adapter: require(path.resolve(__dirname, '..', '..', 'session-file-store')),\n                path: './my-session-files'\n              },\n              hooks: {grunt: false, pubsub: false},\n              routes: {\n                '/test': function(req, res) {\n                  var count = req.session.count || 1;\n                  req.session.count = count + 1;\n                  return res.send('Count is ' + count);\n                }\n              }\n            }, done);\n          });\n\n          it('should use the 3rd-party adapter', function() {\n            assert(fs.existsSync(path.resolve(tmpDir.name, 'my-session-files')));\n          });\n\n          it('a server responses should supply a cookie with a session ID', function(done) {\n\n            request(\n              {\n                method: 'GET',\n                uri: 'http://localhost:1535/test',\n              },\n              function(err, response, body) {\n                assert.equal(body, 'Count is 1');\n                assert(response.headers['set-cookie']);\n                var cookies = require('cookie').parse(response.headers['set-cookie'][0]);\n                assert(cookies['sails.sid']);\n                sid = cookies['sails.sid'];\n                return done();\n              }\n            );\n          });\n\n          it('a subsequent request using that session ID in a \"Cookie\" header should use the same session', function(done) {\n\n            request(\n              {\n                method: 'GET',\n                uri: 'http://localhost:1535/test',\n                headers: {\n                  Cookie: 'sails.sid=' + sid\n                }\n              },\n              function(err, response, body) {\n                assert.equal(body, 'Count is 2');\n                return done();\n              }\n            );\n\n          });\n\n          after(function(done) {\n            process.chdir(curDir);\n            return app.lower(done);\n          });\n        });\n\n      });\n\n\n\n\n\n      describe('with an invalid session secret', function() {\n\n        var app = Sails();\n\n        it('should throw an error when lifting Sails', function(done) {\n\n          app.lift({\n            globals: false,\n            port: 1535,\n            environment: 'development',\n            log: {level: 'silent'},\n            session: {\n              secret: 12345\n            },\n            hooks: {grunt: false},\n            routes: {\n              '/test': function(req, res) {\n                res.json({\n                  cookies: req.cookies,\n                  signedCookies: req.signedCookies\n                });\n              }\n            }\n          }, function(err) {\n            if (!err) {return done(new Error('Should have thrown an error!'));}\n            return done();\n          });\n\n        });\n\n        after(function(done) {\n          return app.lower(done);\n        });\n\n      });\n\n      describe('requesting a route with default `isSessionDisabled` setting', function() {\n\n        // Lift a Sails instance in production mode\n        var app = Sails();\n        before(function (done){\n          app.lift({\n            globals: false,\n            port: 1535,\n            environment: 'development',\n            log: {level: 'silent'},\n            session: {\n              secret: 'abc123'\n            },\n            hooks: {grunt: false},\n            routes: {\n              '/sails.io.js': function(req, res) {\n                return res.status(200).send();\n              }\n            }\n          }, done);\n        });\n\n        describe('static asset', function() {\n\n          it('there should be no `set-cookie` header in the response', function(done) {\n\n            request(\n              {\n                method: 'GET',\n                uri: 'http://localhost:1535/sails.io.js',\n              },\n              function(err, response, body) {\n                assert.equal(response.statusCode, 200);\n                assert(_.isUndefined(response.headers['set-cookie']));\n                return done();\n              }\n            );\n          });\n\n        });\n\n        after(function(done) {\n          return app.lower(done);\n        });\n\n      });\n\n      describe('requesting a route with custom `isSessionDisabled` setting', function() {\n\n        var fooRegexp = require('path-to-regexp')('/foo/:id/bar/');\n        // Lift a Sails instance in production mode\n        var app = Sails();\n        before(function (done){\n          app.lift({\n            globals: false,\n            port: 1535,\n            environment: 'development',\n            log: {level: 'silent'},\n            session: {\n              secret: 'abc123',\n              isSessionDisabled: function(req) {\n                var path = req.path;\n                var method = req.method;\n                var CRUD = ['GET', 'PUT', 'POST', 'PATCH', 'DELETE'];\n                if (\n                  (path === '/test' && _.contains(CRUD, method)) ||\n                  (path === '/bar' && method === 'POST') ||\n                  (path === '/baz') ||\n                  (path.match(fooRegexp))\n                ) {\n                    return true;\n                  }\n              }\n            },\n            hooks: {grunt: false},\n            routes: {\n              '/test': function(req, res) {\n                return res.status(200).send();\n              },\n              '/bar': function(req, res) {\n                return res.status(200).send();\n              },\n              '/baz': function(req, res) {\n                return res.status(200).send();\n              },\n              '/foo/123/bar': function(req, res) {\n                return res.status(200).send();\n              },\n              '/sails.io.js': function(req, res) {\n                return res.status(200).send();\n              }\n\n\n            }\n          }, done);\n        });\n\n        describe('static path (blank verb)', function() {\n\n          it('there should be no `set-cookie` header in the response when requesting via GET', function(done) {\n\n            request(\n              {\n                method: 'GET',\n                uri: 'http://localhost:1535/test',\n              },\n              function(err, response, body) {\n                assert.equal(response.statusCode, 200);\n                assert(_.isUndefined(response.headers['set-cookie']));\n                return done();\n              }\n            );\n          });\n\n          it('there should be a `set-cookie` header in the response when requesting via HEAD', function(done) {\n\n            request(\n              {\n                method: 'HEAD',\n                uri: 'http://localhost:1535/test',\n              },\n              function(err, response, body) {\n                assert.equal(response.statusCode, 200);\n                assert(response.headers['set-cookie']);\n                return done();\n              }\n            );\n          });\n\n        });\n\n\n        describe('static path (ALL verb)', function() {\n\n          it('there should be no `set-cookie` header in the response when requesting via GET', function(done) {\n\n            request(\n              {\n                method: 'GET',\n                uri: 'http://localhost:1535/baz',\n              },\n              function(err, response, body) {\n                assert.equal(response.statusCode, 200);\n                assert(_.isUndefined(response.headers['set-cookie']));\n                return done();\n              }\n            );\n          });\n\n          it('there should be no `set-cookie` header in the response when requesting via HEAD', function(done) {\n\n            request(\n              {\n                method: 'HEAD',\n                uri: 'http://localhost:1535/baz',\n              },\n              function(err, response, body) {\n                assert.equal(response.statusCode, 200);\n                assert(_.isUndefined(response.headers['set-cookie']));\n                return done();\n              }\n            );\n          });\n        });\n\n        describe('static path (POST only)', function() {\n\n          it('there should be no `set-cookie` header in the response when requesting via POST', function(done) {\n\n            request(\n              {\n                method: 'POST',\n                uri: 'http://localhost:1535/bar',\n              },\n              function(err, response, body) {\n                assert.equal(response.statusCode, 200);\n                assert(_.isUndefined(response.headers['set-cookie']));\n                return done();\n              }\n            );\n          });\n\n          it('there SHOULD be a `set-cookie` header in the response when requesting via GET', function(done) {\n\n            request(\n              {\n                method: 'GET',\n                uri: 'http://localhost:1535/bar',\n              },\n              function(err, response, body) {\n                assert.equal(response.statusCode, 200);\n                assert(response.headers['set-cookie']);\n                return done();\n              }\n            );\n          });\n\n        });\n\n        describe('dynamic path', function() {\n\n          it('there should be no `set-cookie` header in the response', function(done) {\n\n            request(\n              {\n                method: 'GET',\n                uri: 'http://localhost:1535/foo/123/bar',\n              },\n              function(err, response, body) {\n                assert.equal(response.statusCode, 200);\n                assert(_.isUndefined(response.headers['set-cookie']));\n                return done();\n              }\n            );\n          });\n\n        });\n\n        describe('static asset', function() {\n\n          it('there SHOULD be a `set-cookie` header in the response', function(done) {\n\n            request(\n              {\n                method: 'GET',\n                uri: 'http://localhost:1535/sails.io.js',\n              },\n              function(err, response, body) {\n                assert.equal(response.statusCode, 200);\n                assert(response.headers['set-cookie']);\n                return done();\n              }\n            );\n          });\n\n        });\n\n        after(function(done) {\n          return app.lower(done);\n        });\n\n      });\n\n    });\n\n    describe('virtual requests :: ', function() {\n\n      describe('with a valid session secret', function() {\n\n        var sid;\n\n        // Lift a Sails instance in production mode\n        var app = Sails();\n        before(function (done){\n          app.load({\n            globals: false,\n            environment: 'development',\n            log: {level: 'silent'},\n            session: {\n              secret: 'abc123'\n            },\n            routes: {\n              '/test': function(req, res) {\n                var count = req.session.count || 1;\n                req.session.count = count + 1;\n                res.send('Count is ' + count);\n              }\n            }\n          }, done);\n        });\n\n\n        it('a server responses should supply a cookie with a session ID', function(done) {\n\n          app.request(\n            {\n              method: 'GET',\n              url: '/test',\n            },\n            function(err, response, body) {\n              assert.equal(body, 'Count is 1');\n              assert(response.headers['set-cookie']);\n              var cookies = require('cookie').parse(response.headers['set-cookie'][0]);\n              assert(cookies['sails.sid']);\n              sid = cookies['sails.sid'];\n              return done();\n            }\n          );\n        });\n\n        it('a subsequent request using that session ID in a \"Cookie\" header should use the same session', function(done) {\n\n          app.request(\n            {\n              method: 'GET',\n              url: '/test',\n              headers: {\n                Cookie: 'sails.sid=' + sid\n              }\n            },\n            function(err, response, body) {\n              assert.equal(body, 'Count is 2');\n              return done();\n            }\n          );\n\n        });\n\n        after(function(done) {\n          return app.lower(done);\n        });\n\n      });\n\n      describe('requesting a route disabled by sails.config.session.isSessionDisabled', function() {\n\n        // Lift a Sails instance in production mode\n        var app = Sails();\n        before(function (done){\n          app.lift({\n            globals: false,\n            port: 1535,\n            environment: 'development',\n            log: {level: 'silent'},\n            session: {\n              secret: 'abc123',\n              isSessionDisabled: function(req) {\n                var CRUD = ['GET', 'PUT', 'POST', 'PATCH', 'DELETE'];\n                return (req.path === '/test' && _.contains(CRUD, req.method))\n              }\n            },\n            hooks: {grunt: false},\n            routes: {\n              '/test': function(req, res) {\n                if (_.isUndefined(req.session)) {\n                  return res.status(200).send();\n                }\n                return res.status(500).send();\n              }\n            }\n          }, done);\n        });\n\n        it('there should be no `set-cookie` header in the response', function(done) {\n\n          request(\n            {\n              method: 'GET',\n              uri: 'http://localhost:1535/test',\n            },\n            function(err, response, body) {\n              assert.equal(response.statusCode, 200);\n              assert(_.isUndefined(response.headers['set-cookie']));\n              return done();\n            }\n          );\n        });\n\n        after(function(done) {\n          return app.lower(done);\n        });\n\n      });\n\n\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/integration/middleware.startRequestTimer.test.js",
    "content": "var _ = require('@sailshq/lodash');\nvar request = require('@sailshq/request');\nvar Sails = require('../../lib').Sails;\nvar assert = require('assert');\nvar fs = require('fs-extra');\nvar request = require('@sailshq/request');\nvar appHelper = require('./helpers/appHelper');\nvar path = require('path');\n\ndescribe('middleware :: ', function() {\n\n  describe('startRequestTimer :: ', function() {\n\n    var appName = 'testApp';\n    var sailsApp;\n\n    before(function(done) {\n      appHelper.build(done);\n    });\n\n    after(function() {\n      process.chdir('../');\n      appHelper.teardown();\n    });\n\n    describe('default startRequestTimer middleware', function() {\n\n      before(function(done) {\n        appHelper.lift({\n          hooks: {\n            pubsub: false\n          },\n          routes: {\n            '/time': function(req, res) {\n              assert(req._startTime);\n              assert(req._startTime instanceof Date);\n              res.send();\n            }\n          }\n        }, function(err, _sailsApp) {\n          if (err) { return done(err); }\n          sailsApp = _sailsApp;\n          return done();\n        });\n      });\n\n      it('should add a _startTime to the request object', function(done) {\n\n        request(\n          {\n            method: 'GET',\n            uri: 'http://localhost:1342/time',\n          },\n          function(err, response, body) {\n            return done(err);\n          }\n        );\n\n      });\n\n      after(function(done) {\n        sailsApp.lower(done);\n      });\n\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/integration/middleware.static.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar _ = require('@sailshq/lodash');\nvar request = require('@sailshq/request');\nvar Sails = require('../../lib').Sails;\nvar assert = require('assert');\nvar fs = require('fs-extra');\nvar request = require('@sailshq/request');\nvar appHelper = require('./helpers/appHelper');\nvar path = require('path');\n\n\n\n\n\ndescribe('middleware :: ', function() {\n\n  describe('static :: ', function() {\n\n    var appName = 'testApp';\n    var customFaviconPath = path.resolve(__dirname, 'fixtures/favicon.ico');\n    var test_file;\n\n    before(function(done) {\n      appHelper.build(function(err) {\n        if (err) {return done(err);}\n        fs.copySync(customFaviconPath, path.resolve('../', appName, '.tmp/public/test.txt'));\n        fs.copySync(customFaviconPath, path.resolve('../', appName, '.tmp/public/test.png'));\n        fs.copySync(customFaviconPath, path.resolve('../', appName, '.tmp/public/test.woff'));\n        test_file = fs.readFileSync(customFaviconPath);\n        return done();\n      });\n    });\n\n\n\n\n\n    describe('with a test.txt, test.png and test.woff file in the .tmp/public folder', function() {\n\n      var sailsApp;\n      before(function(done) {\n\n        appHelper.lift(function(err, _sailsApp) {\n          assert(!err);\n          sailsApp = _sailsApp;\n          return done();\n        });\n      });\n\n      it('a request to /test.txt should provide the file with the correct content-type header', function(done) {\n\n        request(\n          {\n            method: 'GET',\n            uri: 'http://localhost:1342/test.txt',\n          },\n          function(err, response, body) {\n            assert.equal(test_file.toString('utf-8'), body);\n            assert.equal(response.headers['content-type'], 'text/plain; charset=UTF-8');\n            return done();\n          }\n        );\n\n      });\n\n      it('a request to /test.png should provide the file with the correct content-type header', function(done) {\n\n        request(\n          {\n            method: 'GET',\n            uri: 'http://localhost:1342/test.png',\n          },\n          function(err, response, body) {\n            assert.equal(test_file.toString('utf-8'), body);\n            assert.equal(response.headers['content-type'], 'image/png');\n            return done();\n          }\n        );\n\n      });\n\n      it('a request to /test.woff should provide the file with the correct content-type header', function(done) {\n\n        request(\n          {\n            method: 'GET',\n            uri: 'http://localhost:1342/test.woff',\n          },\n          function(err, response, body) {\n            assert.equal(test_file.toString('utf-8'), body);\n            assert.equal(response.headers['content-type'], 'font/woff');\n            return done();\n          }\n        );\n\n      });\n\n      after(function(done) {\n        sailsApp.lower(done);\n      });\n\n    });\n\n\n\n\n    describe('with cache time set to 5000 ms', function() {\n\n      var sailsApp;\n      before(function(done) {\n\n        appHelper.lift({\n          http: {\n            cache: 5000\n          }\n        },\n        function(err, _sailsApp) {\n          assert(!err);\n          sailsApp = _sailsApp;\n          return done();\n        });\n      });\n\n      it('a request to /test.txt should provide the file and a correct cache-control header', function(done) {\n        request(\n          {\n            method: 'GET',\n            uri: 'http://localhost:1342/test.txt',\n          },\n          function(err, response, body) {\n            assert.equal(test_file.toString('utf-8'), body);\n            assert.equal(response.headers['content-type'], 'text/plain; charset=UTF-8');\n            assert.equal(response.headers['cache-control'], 'public, max-age=5');\n            return done();\n          }\n        );\n      });\n\n      after(function(done) {\n        sailsApp.lower(done);\n      });\n\n    });\n\n    after(function() {\n      process.chdir('../');\n      appHelper.teardown();\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/integration/new.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar assert  = require('assert');\nvar fs    = require('fs-extra');\nvar exec  = require('child_process').exec;\nvar _   = require('@sailshq/lodash');\nvar appHelper = require('./helpers/appHelper');\nvar path = require('path');\nvar util  = require('util');\n\n\n\n\n/**\n * Module errors\n */\n\ndescribe('New app generator', function() {\n  var sailsbin = path.resolve('./bin/sails.js');\n  var appName = 'testApp';\n  var defaultTemplateLang = 'ejs';\n\n  this.slow(1000);\n\n  beforeEach(function(done) {\n    fs.exists(appName, function(exists) {\n      if (exists) {\n        fs.removeSync(appName);\n      }\n      done();\n    });\n  });\n\n  afterEach(function(done) {\n    fs.exists(appName, function(exists) {\n      if (exists) {\n        fs.removeSync(appName);\n      }\n      done();\n    });\n  });\n\n  describe('sails new <appname>', function() {\n\n    it('should create new, liftable app in new folder', function(done) {\n      exec('node '+ sailsbin + ' new ' + appName + ' --fast --traditional --without=lodash,async', function(err) {\n        if (err) { return done(new Error(err)); }\n        appHelper.lift({log:{level:'silent'}}, function(err, sailsApp) {\n          if (err) {return done(err);}\n          sailsApp.lower(done);\n        });\n      });\n    });\n\n    it('should not overwrite a folder', function(done) {\n      fs.mkdir(appName, function(err) {\n        if (err) { return done(new Error(err)); }\n        fs.writeFile(path.resolve(appName, 'test'), '', function(err) {\n          if (err) { return done(new Error(err)); }\n          exec('node '+ sailsbin + ' new ' + appName + ' --fast --traditional', function(err, dumb, result) {\n            // In Node v0.10.x on some environments (like in Appveyor), this just\n            // returns an Error in `err` instead of a result, so account for that.\n            if (process.versions.node.split('.')[0] === '0' && process.versions.node.split('.')[1] === '10' && err) {\n              return done();\n            }\n            assert(result.indexOf('error') > -1, 'Should have received an error, but instead got: ' + result);\n            done();\n          });\n        });\n      });\n    });\n  });\n\n  describe('sails generate new <appname>', function() {\n\n    it('should create new app', function(done) {\n      exec('node '+ sailsbin + ' generate new ' + appName + ' --fast --traditional --without=lodash,async', function(err) {\n        if (err) { return done(new Error(err)); }\n        appHelper.lift({log:{level:'silent'}}, function(err, sailsApp) {\n          if (err) {return done(err);}\n          sailsApp.lower(done);\n        });\n      });\n    });\n\n    it('should not overwrite a folder', function(done) {\n      fs.mkdir(appName, function(err) {\n        if (err) { return done(new Error(err)); }\n        fs.writeFile(path.resolve(appName, 'test'), '', function(err) {\n          if (err) { return done(new Error(err)); }\n          exec('node '+ sailsbin + ' generate new ' + appName + ' --fast --traditional', function(err, dumb, result) {\n            // In Node v0.10.x on some environments (like in Appveyor), this just\n            // returns an Error in `err` instead of a result, so account for that.\n            if (process.versions.node.split('.')[0] === '0' && process.versions.node.split('.')[1] === '10' && err) {\n              return done();\n            }\n            assert(result.indexOf('error') > -1, 'Should have received an error, but instead got: ' + result);\n            done();\n          });\n        });\n      });\n    });\n  });\n\n  describe('sails new .', function() {\n\n    it('should create new app in existing folder', function(done) {\n\n      // make app folder and move into directory\n      fs.mkdirSync(appName);\n      process.chdir(appName);\n\n      exec( 'node '+ path.resolve('..', sailsbin) + ' new . --fast --traditional --without=lodash,async', function(err) {\n        if (err) { return done(new Error(err)); }\n\n        // move from app to its parent directory\n        process.chdir('../');\n\n        done();\n      });\n    });\n\n    it('should not overwrite a folder', function(done) {\n      // make app folder and move into directory\n      fs.mkdirSync(appName);\n      process.chdir(appName);\n      fs.mkdirSync('test');\n      exec( 'node ' + path.resolve('..', sailsbin) + ' new . --fast --traditional --without=lodash,async', function(err, dumb, result) {\n        // move from app to its parent directory\n        process.chdir('../');\n        // In Node v0.10.x on some environments (like in Appveyor), this just\n        // returns an Error in `err` instead of a result, so account for that.\n        if (process.versions.node.split('.')[0] === '0' && process.versions.node.split('.')[1] === '10' && err) {\n          return done();\n        }\n        assert(result.indexOf('error') > -1, 'Should have received an error, but instead got: ' + result);\n        done();\n      });\n    });\n  });\n\n});\n"
  },
  {
    "path": "test/integration/router.params.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar assert = require('assert');\nvar httpHelper = require('./helpers/httpHelper.js');\nvar appHelper = require('./helpers/appHelper');\n\n\n\n\nvar Err = {\n  badResponse: function(response) {\n    return 'Wrong server response!  Response :::\\n' + util.inspect(response.body);\n  }\n};\n\n\n\n\ndescribe('router :: ', function() {\n  describe('Parameters', function() {\n    var appName = 'testApp';\n\n    before(function(done) {\n      appHelper.build(done);\n    });\n\n    beforeEach(function(done) {\n      appHelper.lift({verbose: false}, function(err, sails) {\n        if (err) {throw new Error(err);}\n        sailsprocess = sails;\n        setTimeout(done, 100);\n      });\n    });\n\n    afterEach(function(done) {\n      sailsprocess.lower(function(){setTimeout(done, 100);});\n    });\n\n    after(function() {\n      process.chdir('../');\n      appHelper.teardown();\n    });\n\n    describe('\"length\" param', function() {\n\n      before(function(){\n        require('fs').writeFileSync('config/routes.js', 'module.exports.routes = {\"/testLength\": function(req,res){res.send(req.param(\"length\"));}};');\n      });\n\n      it('when sent as a query param, should respond with the correct value of `length`', function(done) {\n        httpHelper.testRoute('get', 'testLength?length=long', function(err, response) {\n          if (err) { return done(err); }\n          assert(response.body==='long', Err.badResponse(response));\n          done();\n        });\n\n      });\n\n      it('when sent as a body param, should respond with the correct value of `length`', function(done) {\n        httpHelper.testRoute('post', {url: 'testLength', json: {length: 'short'}}, function(err, response) {\n          if (err) { return done(err); }\n          assert(response.body==='short', Err.badResponse(response));\n          done();\n        });\n\n      });\n\n    });\n\n    describe('\"touch\" param (with no value)', function() {\n\n      before(function(){\n        require('fs').writeFileSync('config/routes.js', 'module.exports.routes = {\"/testTouch\": function(req,res){res.send(typeof req.param(\"touch\") !== \"undefined\");}};');\n      });\n\n      it('when sent as a query param, should respond with a truthy value', function(done) {\n        httpHelper.testRoute('get', 'testTouch?touch', function(err, response) {\n          if (err) { return done(err); }\n          assert(response.body==='true', Err.badResponse(response));\n          done();\n        });\n\n      });\n\n    });\n\n    describe('req.param() precedence', function() {\n\n      before(function(){\n        require('fs').writeFileSync('config/routes.js', 'module.exports.routes = {\"/test/:foo\": function(req,res){res.json(req.param(\"foo\"));}, \"/test\": function(req,res){res.json(req.param(\"foo\"));}};');\n      });\n\n      it('when sent a value is specified in the query, body and route, route param should take precedence', function(done) {\n        httpHelper.testRoute('post', {url: 'test/abc?foo=123', json: {foo: 666}}, function(err, response) {\n          if (err) { return done(err); }\n          assert(response.body==='abc', Err.badResponse(response));\n          done();\n        });\n      });\n\n      it('when sent a value is specified in the query and body, body should take precedence', function(done) {\n        httpHelper.testRoute('post', {url: 'test?foo=123', json: {foo: 666}}, function(err, response) {\n          if (err) { return done(err); }\n          assert(response.body===666, Err.badResponse(response));\n          done();\n        });\n      });\n\n    });\n\n    describe('req.param() defaults', function() {\n\n      before(function(){\n        require('fs').writeFileSync('config/routes.js', 'module.exports.routes = {\"/test\": function(req,res){res.json(req.param(\"foo\", \"bar\"));}, \"/none\": function(req,res){res.json(req.param(\"foo\"));}};');\n      });\n\n      it('when a value for a param is specified, that value should be used instead of the default', function(done) {\n        httpHelper.testRoute('post', {url: 'test/?foo=123'}, function(err, response) {\n          if (err) { return done(err); }\n          assert(response.body==='\"123\"', Err.badResponse(response));\n          done();\n        });\n      });\n\n      it('when no value for a param is specified, the default should be used', function(done) {\n        httpHelper.testRoute('post', {url: 'test'}, function(err, response) {\n          if (err) { return done(err); }\n          assert(response.body==='\"bar\"', Err.badResponse(response));\n          done();\n        });\n      });\n\n      it('when no value for a param is specified, and there is no default, the param should be undefined', function(done) {\n        httpHelper.testRoute('post', {url: 'none'}, function(err, response) {\n          if (err) { return done(err); }\n          assert(response.body==='', Err.badResponse(response));\n          done();\n        });\n      });\n\n\n    });\n\n    describe('req.params.allParams', function() {\n\n      before(function(){\n        require('fs').writeFileSync('config/routes.js', 'module.exports.routes = {\"/testParams/:foo\": function(req,res){res.json(req.allParams());}};');\n      });\n\n      it('should return the correct param values, accounting for precedence', function(done) {\n        httpHelper.testRoute('post', {url: 'testParams/abc?foo=123&baz=999&bar=555&touch', json: {bar: 666}}, function(err, response) {\n          if (err) { return done(err); }\n          assert.equal(response.body.foo, 'abc');\n          assert.equal(response.body.bar, 666);\n          assert.equal(response.body.baz, 999);\n          assert.equal(response.body.touch, '');\n          done();\n        });\n\n      });\n\n    });\n\n\n\n  });\n\n});\n"
  },
  {
    "path": "test/integration/router.specifiedRoutes.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar assert = require('assert');\nvar httpHelper = require('./helpers/httpHelper.js');\nvar appHelper = require('./helpers/appHelper');\n\n\n\n\nvar Err = {\n  badResponse: function(response) {\n    return 'Wrong server response!  Response :::\\n' + util.inspect(response.body);\n  }\n};\n\n\n\n\ndescribe('router :: ', function() {\n  describe('Specified routes', function() {\n    var appName = 'testApp';\n\n    before(function(done) {\n      appHelper.build(done);\n    });\n\n    beforeEach(function(done) {\n      appHelper.lift({verbose: false}, function(err, sails) {\n        if (err) {throw new Error(err);}\n        sailsprocess = sails;\n        setTimeout(done, 100);\n      });\n    });\n\n    afterEach(function(done) {\n      sailsprocess.lower(function(){setTimeout(done, 100);});\n    });\n\n    after(function() {\n      process.chdir('../');\n      appHelper.teardown();\n    });\n\n    describe('an options request', function() {\n      before(function() {\n        httpHelper.writeRoutes({\n          '/*': {\n            cors: true,\n          },\n          '/testRoute': {\n            action: 'test/verb',\n          },\n        });\n      });\n\n      it('should respond to OPTIONS requests', function(done) {\n        httpHelper.testRoute('options', {\n          url: 'testRoute',\n          headers: {\n            'Access-Control-Request-Method': 'post',\n            Origin: 'https://foo.shyp.com'\n          },\n        }, function(err, response, body) {\n          assert.equal(response.statusCode, 200);\n          assert.equal(response.headers['access-control-allow-origin'], '*');\n          done();\n        });\n      });\n    });\n\n    describe('with an unspecified http method', function() {\n\n      before(function() {\n        httpHelper.writeRoutes({\n          '/testRoute': {\n           action: 'test/verb'\n          }\n        });\n      });\n\n      it('should respond to get requests', function(done) {\n\n\n        httpHelper.testRoute('get', 'testRoute', function(err, response) {\n          if (err) { return done(err); }\n\n          assert(response.body === 'get', Err.badResponse(response));\n          done();\n        });\n      });\n\n      it('should respond to post requests', function(done) {\n\n        httpHelper.testRoute('post', 'testRoute', function(err, response) {\n          if (err) { return done(err); }\n\n          assert(response.body === 'post', Err.badResponse(response));\n          done();\n        });\n      });\n\n      it('should respond to put requests', function(done) {\n\n        httpHelper.testRoute('put', 'testRoute', function(err, response) {\n          if (err) { return done(err); }\n\n          assert(response.body === 'put', Err.badResponse(response));\n          done();\n        });\n      });\n\n      it('should respond to delete requests', function(done) {\n\n        httpHelper.testRoute('del', 'testRoute', function(err, response) {\n          if (err) { return done(err); }\n\n          assert(response.body === 'delete', Err.badResponse(response));\n          done();\n        });\n      });\n    });\n\n    describe('with get http method specified', function() {\n\n      before(function() {\n        httpHelper.writeRoutes({\n          'get /testRoute': {\n            action: 'test/verb'\n          }\n        });\n      });\n\n      it('should respond to get requests', function(done) {\n\n        httpHelper.testRoute('get', 'testRoute', function(err, response) {\n          if (err) { return done(err); }\n\n          assert(response.body === 'get', Err.badResponse(response));\n          done();\n        });\n      });\n\n      it('shouldn\\'t respond to post requests', function(done) {\n\n        httpHelper.testRoute('post', 'testRoute', function(err, response) {\n          if (err) { return done(err); }\n\n          assert(response.body !== 'post', Err.badResponse(response));\n          done();\n        });\n      });\n    });\n\n    describe('with post http method specified', function() {\n\n      before(function() {\n        httpHelper.writeRoutes({\n          'post /testRoute': {\n            action: 'test/verb'\n          }\n        });\n      });\n\n      it('should respond to post requests', function(done) {\n\n        httpHelper.testRoute('post', 'testRoute', function(err, response) {\n          if (err) { return done(err); }\n\n          assert(response.body === 'post', Err.badResponse(response));\n          done();\n        });\n      });\n    });\n\n    describe('with put http method specified', function() {\n\n      before(function() {\n        httpHelper.writeRoutes({\n          'put /testRoute': {\n            action: 'test/verb'\n          }\n        });\n      });\n\n      it('should respond to put requests', function(done) {\n\n        httpHelper.testRoute('put', 'testRoute', function(err, response) {\n          if (err) { return done(err); }\n\n          assert(response.body === 'put', Err.badResponse(response));\n          done();\n        });\n      });\n    });\n\n    describe('with delete http method specified', function() {\n\n      before(function(){\n        httpHelper.writeRoutes({\n          'delete /testRoute': {\n            action: 'test/verb'\n          }\n        });\n      });\n\n      it('should respond to delete requests', function(done) {\n\n\n        httpHelper.testRoute('del', 'testRoute', function(err, response) {\n          if (err) { return done(err); }\n\n          assert(response.body === 'delete', Err.badResponse(response));\n          done();\n        });\n      });\n    });\n\n    describe('with dynamic url paths specified', function() {\n\n      before(function() {\n        httpHelper.writeRoutes({\n          'get /test/:category/:size': {\n            action: 'test/dynamic'\n          }\n        });\n      });\n\n      it('should respond to requests that match the url pattern', function(done) {\n\n        httpHelper.testRoute('get', 'test/shirts/large', function(err, response) {\n          if (err) { return done(err); }\n          var body = JSON.parse(response.body);\n          assert.equal(body.category, 'shirts');\n          assert.equal(body.size, 'large');\n          done();\n        });\n      });\n    });\n\n    describe('should be case-insensitive', function() {\n\n      before(function() {\n        httpHelper.writeRoutes({\n          'get /testRoute': {\n            action: 'test/verb'\n          }\n        });\n      });\n\n      it('', function(done) {\n        httpHelper.testRoute('get', 'tEStrOutE', function(err, response) {\n          if (err) { return done(err); }\n\n          assert(response.body === 'get', Err.badResponse(response));\n          done();\n        });\n      });\n    });\n\n    describe('should accept case-insensitive controller key', function() {\n\n      before(function() {\n        httpHelper.writeRoutes({\n          'get /testRoute': {\n            action: 'tEsT/verb'\n          }\n        });\n      });\n\n      it('', function(done) {\n        httpHelper.testRoute('get', 'testRoute', function(err, response) {\n          if (err) { return done(err); }\n\n          assert(response.body === 'get', Err.badResponse(response));\n          done();\n        });\n      });\n    });\n\n    describe('should accept case-insensitive action key', function() {\n\n      before(function(){\n        httpHelper.writeRoutes({\n          'get /testRoute': {\n            action: 'test/capiTalleTTers'\n          }\n        });\n      });\n\n      it('', function(done) {\n        httpHelper.testRoute('get', 'testRoute', function(err, response) {\n          if (err) { return done(err); }\n\n          assert(response.body === 'CapitalLetters', Err.badResponse(response));\n          done();\n        });\n      });\n    });\n\n    describe('regex routes - get r|^/\\\\\\\\d+/(\\\\\\\\w+)/(\\\\\\\\w+)$|foo,bar', function() {\n\n      before(function(){\n        require('fs').writeFileSync('config/routes.js', 'module.exports.routes = {\"r|^/\\\\\\\\d+/(\\\\\\\\w+)/(\\\\\\\\w+)$|foo,bar\": function(req,res){res.json({foo:req.param(\"foo\"),bar:req.param(\"bar\")});}};');\n      });\n\n      it('should match /123/abc/def and put \"abc\" and \"def\" in \"foo\" and \"bar\" params', function(done) {\n        httpHelper.testRoute('get', '123/abc/def', function(err, response) {\n          if (err) { return done(err); }\n          var body = JSON.parse(response.body);\n          assert(body.foo==='abc', Err.badResponse(response));\n          assert(body.bar==='def', Err.badResponse(response));\n          done();\n        });\n\n      });\n\n      it('should match /9/fizzle/fazzle and put \"fizzle\" and \"fazzle\" in \"foo\" and \"bar\" params', function(done) {\n        httpHelper.testRoute('get', '9/fizzle/fazzle', function(err, response) {\n          if (err) {return done(new Error(err));}\n          var body = JSON.parse(response.body);\n          try {\n            assert.equal(body.foo, 'fizzle', Err.badResponse(response));\n            assert.equal(body.bar, 'fazzle', Err.badResponse(response));\n          }\n          catch (e) { return done(e); }\n          done();\n        });\n\n      });\n\n    });\n\n    describe('skipAssets', function() {\n\n      before(function(){\n        httpHelper.writeRoutes({\n          '/*': {\n            skipAssets: true,\n            action: 'test/index'\n          }\n        });\n      });\n\n      it('should match /foo', function(done) {\n\n        httpHelper.testRoute('get', 'foo', function(err, response) {\n          if (err) { return done(new Error(err)); }\n          try { assert.equal(response.body, 'index', Err.badResponse(response)); }\n          catch (e) { return done(e); }\n          done();\n        });\n\n      });\n\n      it('should match /foo?abc=1.2.3', function(done) {\n\n        httpHelper.testRoute('get', 'foo?abc=1.2.3', function(err, response) {\n          if (err) { return done(err); }\n          try { assert.equal(response.body, 'index', Err.badResponse(response)); }\n          catch (e) { return done(e); }\n          done();\n        });\n\n      });\n\n      it('should match /foo.bar/baz?abc=1.2.3', function(done) {\n\n        httpHelper.testRoute('get', 'foo.bar/baz?abc=1.2.3', function(err, response) {\n          if (err) { return done(err); }\n          try { assert.equal(response.body, 'index', Err.badResponse(response)); }\n          catch (e) { return done(e); }\n          done();\n        });\n\n      });\n\n      it('should not match /foo.js', function(done) {\n\n        httpHelper.testRoute('get', 'foo.js', function(err, response) {\n          if (err) { return done(err); }\n          assert(response.statusCode === 404, Err.badResponse(response));\n          done();\n        });\n\n      });\n\n      it('should not match /js/dependencies/pretend.js', function(done) {\n\n        httpHelper.testRoute('get', 'js/dependencies/pretend.js', function(err, response) {\n          if (err) { return done(err); }\n          assert(response.statusCode === 404, Err.badResponse(response));\n          done();\n        });\n\n      });\n\n      it('should not match /js/dependencies/pretend.io.js', function(done) {\n\n        httpHelper.testRoute('get', 'js/dependencies/pretend.io.js', function(err, response) {\n          if (err) { return done(err); }\n          assert(response.statusCode === 404, Err.badResponse(response));\n          done();\n        });\n\n      });\n\n      it('should not match /styles/pretendporter.css', function(done) {\n\n        httpHelper.testRoute('get', 'styles/pretendporter.css', function(err, response) {\n          if (err) { return done(err); }\n          assert(response.statusCode === 404, Err.badResponse(response));\n          done();\n        });\n\n      });\n\n      it('should not match /foo.bar/foo.js', function(done) {\n\n        httpHelper.testRoute('get', 'foo.js', function(err, response) {\n          if (err) { return done(err); }\n          assert(response.statusCode === 404, Err.badResponse(response));\n          done();\n        });\n\n      });\n\n    });\n\n    describe('skipRegex /abc/', function() {\n\n      before(function(){\n        var ROUTES_FILE_CONTENTS = 'module.exports.routes = {\\'/*\\': {skipRegex: /abc/,action: \\'test/index\\'}};';\n        require('fs').writeFileSync('config/routes.js', ROUTES_FILE_CONTENTS);\n      });\n\n      it('should match /foo', function(done) {\n\n        httpHelper.testRoute('get', 'foo', function(err, response) {\n          if (err) { return done(err); }\n          try { assert.equal(response.body, 'index', Err.badResponse(response)); }\n          catch (e) { return done(e); }\n          done();\n        });\n\n      });\n\n      it('should not match /fooabcbar', function(done) {\n\n        httpHelper.testRoute('get', 'fooabcbar', function(err, response) {\n          if (err) { return done(err); }\n          assert(response.statusCode === 404, Err.badResponse(response));\n          done();\n        });\n\n      });\n\n    });\n\n    describe('skipRegex [/abc/, /def/]', function() {\n\n      before(function(){\n        require('fs').writeFileSync('config/routes.js', 'module.exports.routes = {\\'/*\\': {skipRegex: [/abc/,/def/],action: \\'test/index\\'}};');\n      });\n\n      it('should match /foo', function(done) {\n\n        httpHelper.testRoute('get', 'foo', function(err, response) {\n          if (err) { return done(err); }\n          try { assert.equal(response.body, 'index', Err.badResponse(response)); }\n          catch (e) { return done(e); }\n          done();\n        });\n\n      });\n\n      it('should not match /fooabcbar', function(done) {\n\n        httpHelper.testRoute('get', 'fooabcbar', function(err, response) {\n          if (err) { return done(err); }\n          assert(response.statusCode === 404, Err.badResponse(response));\n          done();\n        });\n\n      });\n\n      it('should not match /foodefbar', function(done) {\n\n        httpHelper.testRoute('get', 'foodefbar', function(err, response) {\n          if (err) { return done(err); }\n          assert(response.statusCode === 404, Err.badResponse(response));\n          done();\n        });\n\n      });\n\n    });\n\n    if (Number(process.version.match(/^v(\\d+\\.\\d+)/)[1]) >= 7.6) {\n\n      describe('Async route handlers :: ', function() {\n\n        describe('ad-hoc functions', function() {\n\n          before(function() {\n            require('fs').writeFileSync('config/routes.js', 'module.exports.routes = {\"/testasync\": async function(req, res, next) { throw new Error(\"foo!\"); } }');\n          });\n\n          it('should handle uncaught promises correctly', function(done) {\n            httpHelper.testRoute('get', 'testasync', function(err, response) {\n              if (err) { return done(err); }\n              assert(response.statusCode === 500);\n              done();\n            });\n\n          });\n\n        });\n\n        describe('actions', function() {\n\n          before(function() {\n            require('fs').writeFileSync('config/routes.js', 'module.exports.routes = {\"/testasyncerror\": \"asynctesterr\", \"/testasyncok\": \"asynctestok\" }');\n            require('fs').writeFileSync('api/controllers/asynctesterr.js', 'module.exports = async function(req, res, next) { throw new Error(\"foo!\"); }');\n            require('fs').writeFileSync('api/controllers/asynctestok.js', 'module.exports = async function(req, res, next) { var dumb = function() { return new Promise (function(resolve, reject) { setTimeout(function(){ return resolve(\"foo\")}, 100);}); }; var val = await dumb();  return res.send(val) }');\n          });\n\n          it('should handle responses correctly', function(done) {\n            httpHelper.testRoute('get', 'testasyncok', function(err, response) {\n              if (err) { return done(err); }\n              assert(response.statusCode === 200);\n              assert(response.body === 'foo');\n              done();\n            });\n\n          });\n\n          it('should handle uncaught promises correctly', function(done) {\n            httpHelper.testRoute('get', 'testasyncerror', function(err, response) {\n              if (err) { return done(err); }\n              assert(response.statusCode === 500);\n              done();\n            });\n\n          });\n\n        });\n\n\n      });\n\n\n    }\n\n\n  });\n});\n"
  },
  {
    "path": "test/integration/router.viewRendering.test.js",
    "content": "/**\n * Test dependencies\n */\n\nvar assert = require('assert');\nvar httpHelper = require('./helpers/httpHelper.js');\nvar appHelper = require('./helpers/appHelper');\nvar _ = require('@sailshq/lodash');\nvar fs = require('fs');\n\n\n\n\n\n\ndescribe('router :: ', function() {\n  describe('View routes', function() {\n    var appName = 'testApp';\n\n    before(function(done) {\n      appHelper.build(function() {\n        fs.writeFileSync('config/extraroutes.js', 'module.exports.routes = ' + JSON.stringify({\n          '/testView': {\n            view: 'viewtest/index'\n          },\n          '/app': {\n            view: 'app'\n          },\n          '/user': {\n            view: 'app/user/homepage'\n          }\n        }));\n        return done();\n      });\n    });\n\n    beforeEach(function(done) {\n      appHelper.lift({\n        verbose: false\n      }, function(err, sails) {\n        if (err) {\n          throw new Error(err);\n        }\n        sailsprocess = sails;\n        return done();\n      });\n    });\n\n    afterEach(function(done) {\n      sailsprocess.lower(done);\n    });\n\n    after(function() {\n      process.chdir('../');\n      appHelper.teardown();\n    });\n\n    describe('with default routing', function() {\n\n      it('should respond to a get request to localhost:1342 with welcome page', function(done) {\n\n        httpHelper.testRoute('get', '', function(err, response) {\n          if (err) {\n            return done(new Error(err));\n          }\n\n          assert(response.body.indexOf('not found') < 0);\n          assert(response.body.indexOf('<!-- Default home page -->') > -1);\n          done();\n        });\n      });\n\n      it('should wrap the view in the default layout', function(done) {\n\n        httpHelper.testRoute('get', '', function(err, response) {\n          if (err) {\n            return done(new Error(err));\n          }\n          assert(response.body.indexOf('<html>') > -1);\n          done();\n        });\n      });\n\n    });\n\n    describe('with specified routing using the \"view:\" syntax', function() {\n\n      it('route with config {view: \"app\"} should respond to a get request with the \"app/index.ejs\" view if \"app.ejs\" does not exist', function(done) {\n\n        httpHelper.testRoute('get', 'app', function(err, response) {\n          if (err) {\n            return done(new Error(err));\n          }\n          assert(response.body.indexOf('not found') < 0);\n          assert(response.body.indexOf('App index file') > -1);\n          done();\n        });\n      });\n\n      it('route with config {view: \"viewtest/index\"} should respond to a get request with \"viewtest/index.ejs\"', function(done) {\n\n        httpHelper.testRoute('get', 'testView', function(err, response) {\n          if (err) {\n            return done(new Error(err));\n          }\n          assert(response.body.indexOf('not found') < 0);\n          assert(response.body.indexOf('indexView') > -1);\n          done();\n        });\n      });\n\n      it('route with config {view: \"app/user/homepage\"} should respond to a get request with \"app/user/homepage.ejs\"', function(done) {\n\n        httpHelper.testRoute('get', 'user', function(err, response) {\n          if (err) {\n            return done(new Error(err));\n          }\n          assert(response.body.indexOf('not found') < 0);\n          assert(response.body.indexOf('I\\'m deeply nested!') > -1);\n          done();\n        });\n      });\n\n\n    });\n\n    xdescribe('with no specified routing', function() {\n\n      before(function() {\n        httpHelper.writeRoutes({});\n      });\n\n      it('should respond to get request to :controller with the template at views/:controller/index.ejs', function(done) {\n\n        // Empty router file\n\n        httpHelper.testRoute('get', 'viewTest', function(err, response) {\n          if (err) {\n            return done(new Error(err));\n          }\n\n          assert(response.body.indexOf('indexView') !== -1, response.body);\n          done();\n        });\n      });\n\n      it('should respond to get request to :controller/:action with the template at views/:controller/:action.ejs', function(done) {\n\n        httpHelper.testRoute('get', 'viewTest/create', function(err, response) {\n          if (err) {\n            return done(new Error(err));\n          }\n\n          assert(response.body.indexOf('createView') !== -1);\n          done();\n        });\n      });\n\n      it('should merge config.views.locals into the view locals', function(done) {\n\n        httpHelper.testRoute('get', 'viewTest/viewOptions', function(err, response) {\n          if (err) {\n            return done(new Error(err));\n          }\n\n          assert(response.body.indexOf('!bar!') !== -1);\n          done();\n        });\n      });\n\n      it('should allow config.views.locals to be overridden', function(done) {\n\n        httpHelper.testRoute('get', 'viewTest/viewOptionsOverride', function(err, response) {\n          if (err) {\n            return done(new Error(err));\n          }\n          assert(response.body.indexOf('!baz!') !== -1);\n          done();\n        });\n      });\n\n\n    });\n  });\n});\n"
  },
  {
    "path": "test/integration/www.test.js",
    "content": "/**\n * Test dependencies\n */\n\nvar assert = require('assert');\nvar fs = require('fs-extra');\nvar exec = require('child_process').exec;\nvar path = require('path');\nvar spawn = require('child_process').spawn;\nvar tmp = require('tmp');\n\n// Make existsSync not crash on older versions of Node\nfs.existsSync = fs.existsSync || require('path').existsSync;\n\ndescribe('Running sails www', function() {\n  var sailsBin = path.resolve('./bin/sails.js');\n  var appName = 'testApp';\n\n  before(function() {\n    if (fs.existsSync(appName)) {\n      fs.removeSync(appName);\n    }\n  });\n\n  describe('in an empty directory', function() {\n\n    before(function() {\n      // Make empty folder and move into it\n      fs.mkdirSync('empty');\n      process.chdir('empty');\n      sailsBin = path.resolve('..', sailsBin);\n    });\n\n    // TODO: run tests in here\n\n    after(function() {\n      // Delete empty folder and move out of it\n      process.chdir('../');\n      fs.rmdirSync('empty');\n      sailsBin = path.resolve(sailsBin);\n    });\n\n  });\n\n  describe('in a sails app directory', function() {\n\n    var sailsChildProc;\n    var curDir;\n    var tmpDir;\n\n    before(function(done) {\n      // Cache the current working directory.\n      curDir = process.cwd();\n\n      // Create a temp directory.\n      tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n\n      return done();\n    });\n\n    after(function(done) {\n      process.chdir(curDir);\n      return done();\n    });\n\n    it('should start server without error', function(done) {\n\n      exec('node ' + sailsBin + ' new ' + appName + ' --fast --traditional --without=lodash,async', function(err) {\n        if (err) { return done(new Error(err)); }\n\n        // Move into app directory\n        process.chdir(appName);\n        sailsBin = path.resolve('..', sailsBin);\n\n        sailsChildProc = spawn('node', [sailsBin, 'www']);\n\n        // Any output from stderr is considered an error by this test.\n        sailsChildProc.stderr.on('data', function(data) {\n          return done(data);\n        });\n\n        sailsChildProc.stdout.on('data', function(data) {\n          var dataString = data + '';\n          assert(dataString.indexOf('error') === -1);\n          sailsChildProc.stdout.removeAllListeners('data');\n          // Move out of app directory\n          process.chdir('../');\n          sailsChildProc.kill();\n          return done();\n        });\n      });\n    });\n\n  });\n\n  describe('with command line arguments', function() {\n\n    var sailsChildProc;\n    var curDir;\n    var tmpDir;\n\n    beforeEach(function(done) {\n      // Cache the current working directory.\n      curDir = process.cwd();\n\n      // Create a temp directory.\n      tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n\n      // Create a new Sails app in the temp directory.\n      exec('node ' + sailsBin + ' new ' + appName + ' --fast --traditional --without=lodash,async', function(err) {\n        if (err) { return done(new Error(err)); }\n        process.chdir(path.resolve(tmpDir.name, appName));\n        return done();\n      });\n\n    });\n\n    afterEach(function(done) {\n      sailsChildProc.stderr.removeAllListeners('data');\n      process.chdir(curDir);\n      sailsChildProc.kill();\n      return done();\n    });\n\n    it('--dev should execute grunt build', function(done) {\n\n      // Change environment to production in config file\n      fs.writeFileSync('config/application.js', 'module.exports = ' + JSON.stringify({\n        appName: 'Sails Application',\n        port: 1342,\n        environment: 'production',\n        log: {\n          level: 'info'\n        }\n      }));\n\n      sailsChildProc = spawn('node', [sailsBin, 'www', '--dev']);\n\n      sailsChildProc.stdout.on('data', function(data) {\n        var dataString = data + '';\n        if (dataString.indexOf('`grunt build`') !== -1) {\n          return done();\n        }\n      });\n    });\n\n\n    it('--prod should execute grunt buildProd', function(done) {\n\n      // Overrwrite session config file\n      // to set session adapter:null ( to prevent warning message from appearing on command line )\n      fs.writeFileSync('config/session.js', 'module.exports.session = { adapter: null }');\n\n      sailsChildProc = spawn('node', [sailsBin, 'www', '--prod']);\n\n      sailsChildProc.stdout.on('data', function(data) {\n        var dataString = data + '';\n        if (dataString.indexOf('`grunt buildProd`') !== -1) {\n\n          return done();\n        }\n      });\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/mocha.opts",
    "content": "--reporter spec\n--recursive\n--slow 2000\n--timeout 18000\n"
  },
  {
    "path": "test/unit/App.prototype.load.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar assert = require('assert');\nvar util = require('util');\nvar async = require('async');\nvar Sails = require('../../lib').constructor;\n\n\ndescribe('App', function (){\n\n  var app;\n\n  describe('.prototype.load', function () {\n\n    it('should return app instance (so it can be chained w/ other prototypal methods)', function (done) {\n      app = new Sails();\n      var returnValue;\n\n\n      async.auto({\n\n        loadSails: function (next) {\n          returnValue = app.load({\n            globals: false,\n            loadHooks: [\n              'moduleloader',\n              'userconfig',\n              'http',\n              'views'\n            ]\n          }, function onLoaded (err){\n            if (err) return next(err);\n\n            // Return value should === `app` whenever sails finishes loading\n            assert.equal(app, returnValue);\n\n            next();\n          });\n\n          // Return value should === `app` immediately\n          assert.equal(app, returnValue);\n        },\n\n        // Return value should === `app` later on\n        laterOn: function (next) {\n          setTimeout(function (){\n            assert.equal(app, returnValue);\n            next();\n          }, 150);\n        }\n      }, done);\n\n    });\n  });\n\n\n\n\n  it('should initialize w/ the session hook', function (done) {\n\n    app = new Sails();\n    app.load({\n      globals: false,\n      loadHooks: [\n        'moduleloader',\n        'userconfig',\n        'http',\n        'session',\n        'views'\n      ]\n    }, done);\n  });\n\n});\n\n"
  },
  {
    "path": "test/unit/README.md",
    "content": "# Unit tests\n\nUnit tests shouldn't lift the server (i.e. bind an HTTP server or WebSocket server to a port). Instead, they should bootstrap the minimal set of necessary components to test a particular method (or sometimes a group of methods, if it makes more sense.)  The goal is to identify future breaking changes and isolate _exactly what broke_.  This makes would-be issues easier to spot in advance, and real bugs easier to track down after the fact.\n\n\n## What _Not_ To Test\nSince unit tests are more implementation-specific, we shouldn't unit test parts of Sails which are currently in flux or likely to change.\n\n## How Can I Help?\n\n\n#### Unit tests for the hook loader\n\n1. If we run the hook loader with options for conditionally loading hooks, the server should start with the correct hooks applied.\n2. The hook loader should fail if a hook has other hooks as dependencies, but those dependencies are omitted.\n3. If a graph of circular dependencies is passed into the hook loader, it should fail.\n\n> No one is currently working on this\n\n\n\n#### Unit tests for each core hook\n\n1. initialize()\n2. loadModules()\n3. configure()\n3. Other important methods (hook-specific)\n\n> No one is currently working on this\n"
  },
  {
    "path": "test/unit/app.getRouteFor.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar assert = require('assert');\nvar util = require('util');\nvar Sails = require('../../lib').constructor;\n\n\ndescribe('app.getRouteFor()', function (){\n\n  var app;\n  before(function (done){\n    app = new Sails();\n    app.load({\n      globals: false,\n      loadHooks: [],\n      log: {\n        level: 'error'\n      },\n      routes: {\n        'get /signup': 'PageController.signup',\n        'post /signup': 'UserController.signup',\n        'post /*': 'UserController.signup',\n        'get /': { controller: 'PageController', action: 'homepage' },\n        'get /home': 'index',\n        'get /about': { target: 'PageController.about' },\n        'get /admin': { target: 'PageController.adminDashboard' },\n        'get /badmin': { target: 'PageController.admndashboard' },\n        'get /wolves': 'WolfController.find',\n        'get /wolves/:id': { target: 'WolfController.findOne' },\n        'post /wolves': { controller: 'WolfController', action: 'create' },\n        'options /wolves/test': { target: 'WolfController.CreaTe' },\n        'get /my-machineFn': { action: 'machines/machinefn' },\n        'get /my-page': { view: 'somepage' },\n        'get /cause-trouble': [{ policy: 'be-good'}, { action: 'trouble/cause'}],\n        'put /cause-more-trouble': [{ policy: 'be-good'}, { controller: 'TroubleController', action: 'cause-more' }],\n      },\n      controllers: {\n        moduleDefinitions: {\n          'machines/machinefn': {\n            fn: function () {}\n          }\n        }\n      }\n    }, done);\n  });\n\n\n  it('should return appropriate route info dictionary with simplified usage', function () {\n    var route = app.getRouteFor('PageController.signup');\n    assert.equal(route.method, 'get');\n    assert.equal(route.url, '/signup');\n  });\n\n  it('should return appropriate route info dictionary with expanded usage', function () {\n    var route = app.getRouteFor({ target: 'PageController.signup' });\n    assert.equal(route.method, 'get');\n    assert.equal(route.url, '/signup');\n  });\n\n  it('should return the _first_ matching route', function () {\n    var route = app.getRouteFor('UserController.signup');\n    assert.equal(route.method, 'post');\n    assert.equal(route.url, '/signup');\n  });\n\n  it('should work with new action target syntax', function() {\n    var route = app.getRouteFor('user/signup');\n    assert.equal(route.method, 'post');\n    assert.equal(route.url, '/signup');\n  });\n\n  it('should work with strings without dots or slashes', function() {\n    var route = app.getRouteFor('index');\n    assert.equal(route.method, 'get');\n    assert.equal(route.url, '/home');\n  });\n\n  it('should throw usage error (i.e. `e.code===\\'E_NOT_FOUND\\'`) if target to search is not found', function () {\n    try {\n      app.getRouteFor('JuiceController.makeJuice');\n      assert(false, 'Should have thrown an error');\n    }\n    catch (e) {\n      if (e.code !== 'E_NOT_FOUND') {\n        assert(false, 'Should have thrown an error w/ code === \"E_NOT_FOUND\", instead got: ' + util.inspect(e));\n      }\n    }\n  });\n\n  it('should throw usage error (i.e. `e.code===\\'E_USAGE\\'`) if target to search for not specified or is invalid', function (){\n    try {\n      app.getRouteFor();\n      assert(false, 'Should have thrown an error');\n    }\n    catch (e) {\n      if (e.code !== 'E_USAGE') { assert(false, 'Should have thrown an error w/ code === \"E_USAGE\"'); }\n    }\n\n    try {\n      app.getRouteFor(3235);\n      assert(false, 'Should have thrown an error');\n    }\n    catch (e) {\n      if (e.code !== 'E_USAGE') { assert(false, 'Should have thrown an error w/ code === \"E_USAGE\"'); }\n    }\n\n    try {\n      app.getRouteFor({ x: 32, y: 49 });\n      assert(false, 'Should have thrown an error');\n    }\n    catch (e) {\n      if (e.code !== 'E_USAGE') { assert(false, 'Should have thrown an error w/ code === \"E_USAGE\"'); }\n    }\n\n    try {\n      app.getRouteFor(function(){});\n      assert(false, 'Should have thrown an error');\n    }\n    catch (e) {\n      if (e.code !== 'E_USAGE') { assert(false, 'Should have thrown an error w/ code === \"E_USAGE\"'); }\n    }\n\n  });\n\n  it('should be able to match different syntaxes (routes that specify separate controller+action, or specifically specify a target)', function (){\n\n    assert.equal( app.getRouteFor({controller: 'WolfController', action: 'find'}).url, '/wolves' );\n    assert.equal( app.getRouteFor({controller: 'WolfController', action: 'find'}).method, 'get' );\n\n    assert.equal( app.getRouteFor({controller: 'wolf', action: 'find'}).url, '/wolves' );\n    assert.equal( app.getRouteFor({controller: 'wolf', action: 'find'}).method, 'get' );\n\n    assert.equal( app.getRouteFor({target: {controller: 'wolf', action: 'find'}}).url, '/wolves' );\n    assert.equal( app.getRouteFor({target: {controller: 'wolf', action: 'find'}}).method, 'get' );\n\n    assert.equal( app.getRouteFor('WolfController.find').url, '/wolves' );\n    assert.equal( app.getRouteFor('WolfController.find').method, 'get' );\n\n    assert.equal( app.getRouteFor({target: 'WolfController.find'}).url, '/wolves' );\n    assert.equal( app.getRouteFor({target: 'WolfController.find'}).method, 'get' );\n\n    assert.equal( app.getRouteFor('WolfController.findOne').url, '/wolves/:id' );\n    assert.equal( app.getRouteFor('WolfController.findOne').method, 'get' );\n\n    assert.equal( app.getRouteFor('WolfController.create').url, '/wolves' );\n    assert.equal( app.getRouteFor('WolfController.create').method, 'post' );\n\n    assert.equal( app.getRouteFor('machines/machinefn').url, '/my-machineFn' );\n    assert.equal( app.getRouteFor('machines/machinefn').method, 'get' );\n\n    assert.equal( app.getRouteFor('trouble/cause').url, '/cause-trouble' );\n    assert.equal( app.getRouteFor('trouble/cause').method, 'get' );\n\n    assert.equal( app.getRouteFor('trouble/cause-more').url, '/cause-more-trouble' );\n    assert.equal( app.getRouteFor('trouble/cause-more').method, 'put' );\n\n  });\n\n  it('should be case-insensitive regarding controller / action names', function (){\n    assert.equal( app.getRouteFor('WolfController.CreaTe').url, '/wolves' );\n    assert.equal( app.getRouteFor('WolfController.CreaTe').method, 'post' );\n\n    assert.equal( app.getRouteFor({controller: 'WOLF', action: 'finD'}).url, '/wolves' );\n    assert.equal( app.getRouteFor({controller: 'WOLF', action: 'finD'}).method, 'get' );\n  });\n\n});\n\n"
  },
  {
    "path": "test/unit/app.getUrlFor.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar assert = require('assert');\nvar util = require('util');\nvar Sails = require('../../lib').constructor;\n\n\ndescribe('app.getUrlFor()', function (){\n\n  var app;\n  before(function (done){\n    app = new Sails();\n    app.load({\n      globals: false,\n      loadHooks: [],\n      routes: {\n        'get /signup': 'PageController.signup',\n        'post /login': 'UserController.login',\n        'get /login': 'PageController.login',\n        'post /*': 'UserController.login'\n      },\n      controllers: {\n        moduleDefinitions: {\n          'page/signup': function() {},\n          'page/login': function() {},\n          'user/login': function() {}\n        }\n      }\n    }, done);\n  });\n\n\n  it('should return appropriate route URL with simplified usage', function () {\n    assert.equal( app.getUrlFor('PageController.signup'), '/signup' );\n  });\n\n  it('should return appropriate route URL with expanded usage', function () {\n    assert.equal( app.getUrlFor({ target: 'PageController.login' }), '/login' );\n  });\n\n  it('should return the _first_ matching route URL for the given target', function () {\n    assert.equal( app.getUrlFor('UserController.login'), '/login' );\n  });\n\n});\n\n"
  },
  {
    "path": "test/unit/app.initializeHooks.test.js",
    "content": "/**\n * Module dependencies\n */\nvar should = require('should');\nvar assert = require('assert');\nvar _ = require('@sailshq/lodash');\n\nvar constants = require('../fixtures/constants');\nvar customHooks = require('../fixtures/customHooks');\n\nvar $Sails = require('../helpers/sails');\n\n// TIP:\n//\n// To get a hold of the `sails` instance as a closure variable\n// (i.e. if you're tired of using the mocha context):\n// var sails;\n// $Sails.get(function (_sails) { sails = _sails; });\n\n\ndescribe('app.initializeHooks()', function() {\n\n  describe('with no hooks', function() {\n    var sails = $Sails.load.withAllHooksDisabled();\n    it('hooks should be exposed on the `sails` global', function() {\n      sails.hooks.should.be.an.Object;\n    });\n  });\n\n\n\n  describe('with all core hooks and default config', function() {\n    var sails = $Sails.load();\n    it('should expose hooks on the `sails` global', function() {\n      sails.hooks.should.be.an.Object;\n    });\n    it('should expose at least the expected core hooks', function() {\n      var intersection = _.intersection(_.keys(sails.hooks), _.keys(constants.EXPECTED_DEFAULT_HOOKS));\n\n      // If i18n is missing, that might be ok-- but just check to be sure sails.config.i18n.locales is `[]`.\n      // (i.e. it must have turned itself off)\n      if (!_.contains(intersection, 'i18n')) {\n        assert(_.isEqual(sails.config.i18n.locales, []), 'i18n.locales config must be [] in this situation');\n      }\n\n      assert.deepEqual(intersection, _.without(_.keys(constants.EXPECTED_DEFAULT_HOOKS), 'i18n'),  'Missing expected default hooks');\n    });\n  });\n\n  describe('with the grunt hook set to boolean false', function() {\n    var sails = $Sails.load({hooks: {grunt: false}});\n    it('should expose hooks on the `sails` global', function() {\n      sails.hooks.should.be.an.Object;\n    });\n    it('should expose all the core hooks except for Grunt', function() {\n      var intersection = _.intersection(_.keys(sails.hooks), _.keys(constants.EXPECTED_DEFAULT_HOOKS));\n      assert.deepEqual(intersection, _.without(_.keys(constants.EXPECTED_DEFAULT_HOOKS), 'grunt', 'i18n'),  'Missing expected default hooks');\n    });\n  });\n\n  describe('with the grunt hook set to the string \"false\"', function() {\n    var sails = $Sails.load({hooks: {grunt: \"false\"}});\n    it('should expose hooks on the `sails` global', function() {\n      sails.hooks.should.be.an.Object;\n    });\n    it('should expose all the core hooks except for Grunt', function() {\n      var intersection = _.intersection(_.keys(sails.hooks), _.keys(constants.EXPECTED_DEFAULT_HOOKS));\n      assert.deepEqual(intersection, _.without(_.keys(constants.EXPECTED_DEFAULT_HOOKS), 'grunt', 'i18n'),  'Missing expected default hooks');\n    });\n  });\n\n\n\n\n  describe('configured with a custom hook called `noop`', function() {\n    var sails = $Sails.load({\n      hooks: {\n        noop: customHooks.NOOP\n      }\n    });\n\n    it('should expose `noop`', function() {\n      sails.hooks.should.have\n        .property('noop');\n    });\n    it('should also expose the expected core hooks', function() {\n      var intersection = _.intersection(Object.keys(sails.hooks), _.keys(constants.EXPECTED_DEFAULT_HOOKS));\n      assert.deepEqual(intersection, _.without(_.keys(constants.EXPECTED_DEFAULT_HOOKS), 'i18n'),  'Missing expected default hooks');\n    });\n  });\n\n\n\n  describe('configured with a hook (`noop2`), but not its dependency (`noop`)', function() {\n    var sails = $Sails.load.expectFatalError({\n      hooks: {\n\n        // This forced failure is only temporary--\n        // very hard to test right now as things stand.\n        whadga: function(sails) {\n          throw 'temporary forced failure to simulate dependency issue';\n        },\n\n        noop2: customHooks.NOOP2\n      }\n    });\n  });\n\n\n\n  describe('configured with a hook that always throws', function() {\n    var sails = $Sails.load.expectFatalError({\n      hooks: {\n        // This forced failure is only temporary--\n        // very hard to test right now as things stand.\n        badHook: customHooks.SPOILED_HOOK\n      }\n    });\n  });\n\n\n  describe('configured with a custom hook with a `defaults` object', function() {\n    var sails = $Sails.load({\n      hooks: {\n        defaults_obj: customHooks.DEFAULTS_OBJ\n      },\n      inky: {\n        pinky: 'boo'\n      }\n    });\n\n    it('should add a `foo` key to sails config', function() {\n      assert(sails.config.foo === 'bar');\n    });\n    it('should add an `inky.dinky` key to sails config', function() {\n      assert(sails.config.inky.dinky === 'doo');\n    });\n    it('should keep the existing `inky.pinky` key to sails config', function() {\n      assert(sails.config.inky.pinky === 'boo');\n    });\n\n  });\n\n  describe('configured with a custom hook with a `defaults` function', function() {\n    var sails = $Sails.load({\n      hooks: {\n        defaults_fn: customHooks.DEFAULTS_FN\n      },\n      inky: {\n        pinky: 'boo'\n      }\n    });\n\n    it('should add a `foo` key to sails config', function() {\n      assert(sails.config.foo === 'bar');\n    });\n    it('should add an `inky.dinky` key to sails config', function() {\n      assert(sails.config.inky.dinky === 'doo');\n    });\n    it('should keep the existing `inky.pinky` key to sails config', function() {\n      assert(sails.config.inky.pinky === 'boo');\n    });\n\n  });\n\n  describe('configured with a custom hook with a `configure` function', function() {\n    var sails = $Sails.load({\n      hooks: {\n        config_fn: customHooks.CONFIG_FN\n      },\n      testConfig: 'oh yeah!'\n    });\n\n    it('should add a `hookConfigLikeABoss` key to sails config', function() {\n      assert(sails.config.hookConfigLikeABoss === 'oh yeah!');\n    });\n\n  });\n\n  describe('configured with a custom hook with an `initialize` function', function() {\n    var sails = $Sails.load({\n      hooks: {\n        init_fn: customHooks.INIT_FN\n      }\n    });\n\n    it('should add a `hookInitLikeABoss` key to sails config', function() {\n      assert(sails.config.hookInitLikeABoss === true);\n    });\n\n  });\n\n\n  describe('configured with a custom hook with a `routes` object', function() {\n    var sails = $Sails.load({\n      hooks: {\n        routes: customHooks.ROUTES\n      },\n      routes: {\n        \"GET /foo\": function(req, res, next) {sails.config.foo += \"b\"; return next();}\n      }\n    });\n\n    it('should add two `/foo` routes to the sails config', function() {\n      var fooRoutes = 0;\n      _.each(sails.router._privateRouter.stack, function(stack){\n        if(stack.route.path === '/foo' && stack.route.methods.get === true){\n          fooRoutes += 1;\n        }\n      });\n      assert(fooRoutes === 3);\n    });\n\n    it('should bind the routes in the correct order', function(done) {\n      sails.request({\n        method: 'get',\n        url: '/foo'\n      }, function (err, res, body) {\n        if (err) return done(err);\n        assert.equal(res.statusCode, 200);\n        assert.equal(body, 'abc');\n        return done();\n      });\n    });\n\n  });\n\n  describe('configured with a custom hook with advanced routing', function() {\n    var sails = $Sails.load({\n      hooks: {\n        advanced_routes: customHooks.ADVANCED_ROUTES\n      },\n      routes: {\n        \"GET /foo\": function(req, res, next) {sails.config.foo += \"c\"; return next();}\n      }\n    });\n\n    it('should add four `/foo` routes to the sails config', function() {\n      var fooRoutes = 0;\n      _.each(sails.router._privateRouter.stack, function(stack){\n        if(stack.route.path === '/foo' && stack.route.methods.get === true){\n          fooRoutes += 1;\n        }\n      });\n      assert(fooRoutes === 5);\n    });\n\n    it('should bind the routes in the correct order', function(done) {\n      sails.request({\n        method: 'get',\n        url: '/foo'\n      }, function (err, res, body) {\n        if (err) return done(err);\n        assert.equal(res.statusCode, 200);\n        assert.equal(body, 'abcde');\n        return done();\n      });\n    });\n\n  });\n\n  // describe('configured with a circular hook dependency', function () {\n\n  //  // NOTE #1: not currently implemented\n  //  // NOTE #2: not currently possible\n  //  // (should be possible after merging @ragulka's PR)\n  //  // $Sails.load();\n\n  //  it('should throw a fatal error');\n  // });\n\n\n});\n"
  },
  {
    "path": "test/unit/app.lower.test.js",
    "content": "\nvar assert = require('assert');\nvar async = require('async');\nvar Sails = require('../../lib').constructor;\n\ndescribe('app.lower', function (){\n\n  it('should clean up event listeners', function (done) {\n\n    // Get a list of all the current listeners on the process.\n    // Note that Mocha adds some listeners, so these might not all be empty arrays!\n    var beforeListeners = {\n      sigusr2: process.listeners('SIGUSR2'),\n      sigint: process.listeners('SIGINT'),\n      sigterm: process.listeners('SIGTERM'),\n      exit: process.listeners('exit')\n    };\n\n    // Lift and lower 15 Sails apps in a row, to simulate a testing environment\n    async.eachSeries(Array(15), function(i, cb) {\n      var app = new Sails();\n      var options = {\n        hooks: {i18n: false},\n        globals: false,\n        log: {\n          level: 'error'\n        }\n      };\n\n      async.series([\n        function(cb) {\n          app.load(options, cb);\n        },\n        app.initialize,\n        app.lower\n      ], cb);\n\n    }, function(err) {\n      if (err) {return done(err);}\n      // Check that we have the same # of listeners as before--that is,\n      // that all listeners that were added when the apps were initialized\n      // were subsequently removed when they were lowered.\n      assert.equal(beforeListeners.sigusr2.length,\n                   process.listeners('SIGUSR2').length);\n      assert.equal(beforeListeners.sigint.length,\n                   process.listeners('SIGINT').length);\n      assert.equal(beforeListeners.sigterm.length,\n                   process.listeners('SIGTERM').length);\n      assert.equal(beforeListeners.exit.length,\n                   process.listeners('exit').length);\n      return done();\n    });\n\n  });\n\n});\n\n"
  },
  {
    "path": "test/unit/app.registerAction.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar assert = require('assert');\nvar _ = require('@sailshq/lodash');\n\nvar Sails = require('../../lib').constructor;\n\ndescribe('sails.registerAction() :: ', function() {\n\n  var sailsApp;\n  before(function(done) {\n    (new Sails()).load({\n      hooks: {grunt: false, views: false, blueprints: false, policies: false, i18n: false},\n      log: {level: 'error'},\n      routes: {\n        '/foo': {}\n      }\n    }, function(err, _sails) {\n      if (err) { return done(err); }\n      sailsApp = _sails;\n      return done();\n    });\n  });\n\n  after(function(done) {\n    sailsApp.lower(done);\n  });\n\n  it('should allow registering a new action at runtime, if it doesn\\'t conflict with an existing action', function() {\n    sailsApp.registerAction(function(req, res) {return res.ok('ok!');}, 'new-action');\n    assert(_.isFunction(sailsApp._actions['new-action']), 'registerAction() succeeded, but could not find the registered action in the sails._actions dictionary!');\n  });\n\n  it('should allow not registering a new action at runtime, if it conflicts with an existing action', function() {\n    try {\n      sailsApp.registerAction(function(req, res) {return res.ok('ok!');}, 'top-level-standalone-fn');\n      sailsApp.registerAction(function(req, res) {return res.ok('not ok!');}, 'top-level-standalone-fn');\n    } catch (err) {\n      assert.equal(err.code, 'E_CONFLICT');\n      assert.equal(err.identity, 'top-level-standalone-fn');\n      return;\n    }\n    throw new Error('Expected an E_CONFLICT error, but didn\\'t get one!');\n  });\n\n});\n\n"
  },
  {
    "path": "test/unit/app.reloadActions.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar assert = require('assert');\nvar tmp = require('tmp');\nvar _ = require('@sailshq/lodash');\n\nvar Filesystem = require('machinepack-fs');\n\nvar Sails = require('../../lib').constructor;\n\ntmp.setGracefulCleanup();\n\ndescribe('sails.reloadActions ::', function() {\n\n  describe('basic usage ::', function() {\n\n    var curDir, tmpDir, sailsApp;\n\n    var userHookStuff = 'foo';\n\n    before(function(done) {\n      // Cache the current working directory.\n      curDir = process.cwd();\n      // Create a temp directory.\n      tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n      // Create a top-level legacy controller file.\n      Filesystem.writeSync({\n        force: true,\n        destination: 'api/controllers/TopLevelController.js',\n        string: 'module.exports = { fnAction: function (req, res) { res.send(\\'fn controller action!\\'); } };'\n      }).execSync();\n\n      // Load the Sails app.\n      (new Sails()).load({\n        globals: { sails: true, models: false, _: false, async: false, services: false },\n        hooks: {\n          grunt: false, views: false, blueprints: false, policies: false, pubsub: false, i18n: false,\n          myHook: function() {return {initialize: function(cb) {this.registerActions(cb);}, registerActions: function(cb) {sails.registerAction(function(){}, 'custom-action'); return cb();}};}\n        },\n        log: {level: 'error'}\n      }, function(err, _sails) {\n        sailsApp = _sails;\n        assert(sailsApp._actions['toplevel/fnaction'], 'Expected to find a `toplevel/fnaction` action, but didn\\'t.');\n        assert(sailsApp._actions['custom-action'], 'Expected to find a `custom-action` action, but didn\\'t.');\n        assert(!sailsApp._actions['toplevel/machineaction'], 'Didn\\'t expect `toplevel/machineaction` action to exist!');\n        assert(!sailsApp._actions['nested/standalone-action'], 'Didn\\'t expect `nested/standalone-action` action to exist!');\n        return done(err);\n      });\n    });\n\n    after(function(done) {\n      sailsApp.lower(function() {\n        process.chdir(curDir);\n        return done();\n      });\n    });\n\n    it('should reload all modules when no `hooksToSkip` is provided', function(done) {\n      userHookStuff = 'bar';\n      Filesystem.writeSync({\n        force: true,\n        destination: 'api/controllers/TopLevelController.js',\n        string: 'module.exports = { fnAction: function (req, res) { res.send(\\'fn controller action!\\'); }, machineAction: { fn: function (inputs, exits) { exits.success(\\'machine!\\'); } } };'\n      }).execSync();\n      Filesystem.writeSync({\n        force: true,\n        destination: 'api/controllers/nested/standalone-action.js',\n        string: 'module.exports = function (req, res) { res.send(\\'standalone action!\\'); };'\n      }).execSync();\n      sailsApp.reloadActions(function(err) {\n        if (err) {return done(err);}\n        assert(sailsApp._actions['toplevel/fnaction'], 'Expected to find a `toplevel/fnaction` action, but didn\\'t.');\n        assert(sailsApp._actions['toplevel/machineaction'], 'Expected to find a `toplevel/machineaction` action, but didn\\'t.');\n        assert(sailsApp._actions['nested/standalone-action'], 'Expected to find a `nested/standalone-action` action, but didn\\'t.');\n        assert(sailsApp._actions['custom-action'], 'Expected to find a `custom-action` action, but didn\\'t.');\n        return done();\n      });\n    });\n\n    it('should skip modules for hooks listed in `hooksToSkip`', function(done) {\n      sailsApp.reloadActions({hooksToSkip: ['myHook']}, function(err) {\n        if (err) {return done(err);}\n        assert(!sailsApp._actions['custom-action'], 'Expected to not find a `custom-action` action, but did!');\n        return done();\n      });\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/unit/bootstrap.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar assert = require('assert');\n\nvar Sails = require('root-require')('lib/app');\n\n\ndescribe('bootstrap', function (){\n\n  it('should pass the proper untampered-with error from the bootstrap to the callback of sails.lift()', function (done) {\n\n    var ERROR = 'oh no I forgot my keys';\n    var bootstrapWasFired;\n\n    Sails().lift({\n      globals: false,\n      log: { level: 'silent' },\n      loadHooks: false,\n      bootstrap: function (cb) {\n        bootstrapWasFired = true;\n        cb(ERROR);\n      }\n    }, function (err) {\n      if (!bootstrapWasFired) {\n        return done(new Error('Should have called the bootstrap function'));\n      }\n      if (!err) {\n        return done(new Error('Should have passed an error to the callback of sails.lift()'));\n      }\n\n      assert.deepEqual(err, ERROR, 'Error should be exactly the same as it was when passed from the bootstrap function');\n      return done();\n    });\n  });\n\n  it('if the bootstrap THROWS, Sails should pass the proper untampered-with error to the callback of sails.lift()', function (done) {\n\n    var ERROR = 'oh no I forgot my keys';\n\n    Sails().lift({\n      globals: false,\n      log: { level: 'silent' },\n      loadHooks: false,\n      bootstrap: function (cb) {\n        bootstrapWasFired = true;\n        throw ERROR;\n      }\n    }, function (err) {\n      if (!bootstrapWasFired) {\n        return done(new Error('Should have called the bootstrap function'));\n      }\n      if (!err) {\n        return done(new Error('Should have passed an error to the callback of sails.lift()'));\n      }\n\n      assert.deepEqual(err, ERROR, 'Error should be exactly the same as it was when passed from the bootstrap function');\n      return done();\n    });\n  });\n\n\n  it('if the bootstrap throws AFTER triggering its callback, Sails should log an error');\n\n  it('should log an error if the bootstrap\\'s callback is called twice');\n});\n"
  },
  {
    "path": "test/unit/controller.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar assert = require('assert');\nvar tmp = require('tmp');\nvar _ = require('@sailshq/lodash');\n\nvar Filesystem = require('machinepack-fs');\n\nvar Sails = require('../../lib').constructor;\n\ntmp.setGracefulCleanup();\n\n/**\n * Errors\n */\n\nvar Err = {\n  badResponse: function(response) {\n    return 'Wrong server response!  Response :::\\n' + util.inspect(response.body);\n  }\n};\n\n\n\n/**\n * Tests\n */\n\ndescribe('controllers :: ', function() {\n  describe('with valid actions', function() {\n\n    var curDir, tmpDir, sailsApp;\n    var warn;\n    var warnings = [];\n\n    before(function(done) {\n      // Cache the current working directory.\n      curDir = process.cwd();\n      // Create a temp directory.\n      tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n      // Create a top-level legacy controller file.\n      Filesystem.writeSync({\n        force: true,\n        destination: 'api/controllers/TopLevelLegacyController.js',\n        string: 'module.exports = { fnAction: function (req, res) { res.send(\\'legacy fn action!\\'); }, machineAction: { exits: {success: {outputExample: \\'abc123\\'} }, fn: function (inputs, exits) { exits.success(\\'legacy machine action!\\'); } }, underscore_action: function(req, res) { return res.send(); }, \\'action-with-dashes\\': function(req, res) {  return res.send(); } };'\n      }).execSync();\n      // Create a top-level action file with a req/res function.\n      Filesystem.writeSync({\n        force: true,\n        destination: 'api/controllers/top-level-standalone-fn.js',\n        string: 'module.exports = function (req, res) { res.send(\\'top level standalone fn!\\'); };'\n      }).execSync();\n      // Create a top-level action file with a machine.\n      Filesystem.writeSync({\n        force: true,\n        destination: 'api/controllers/top-level-standalone-machine.js',\n        string: 'module.exports = { exits: {success: {outputExample: \\'abc123\\'} },  fn: function (inputs, exits) { exits.success(\\'top level standalone machine!\\'); } };'\n      }).execSync();\n      // Create a nested legacy controller file.\n      Filesystem.writeSync({\n        force: true,\n        destination: 'api/controllers/someFolder/someOtherFolder/NestedLegacyController.js',\n        string: 'module.exports = { fnAction: function (req, res) { res.send(\\'nested legacy fn action!\\'); }, machineAction: { exits: {success: {outputExample: \\'abc123\\'} },  fn: function (inputs, exits) { exits.success(\\'nested legacy machine action!\\'); } } };'\n      }).execSync();\n      // Create a nested legacy controller file, with dots in the subdirectory.\n      Filesystem.writeSync({\n        force: true,\n        destination: 'api/controllers/some.folder/some.other.folder/NestedLegacyController.js',\n        string: 'module.exports = { fnAction: function (req, res) { res.send(\\'nested legacy fn action!\\'); }, machineAction: { exits: {success: {outputExample: \\'abc123\\'} },  fn: function (inputs, exits) { exits.success(\\'nested legacy machine action!\\'); } } };'\n      }).execSync();\n      // Create a nested action file with a machine.\n      Filesystem.writeSync({\n        force: true,\n        destination: 'api/controllers/someFolder/someOtherFolder/nested-standalone-machine.js',\n        string: 'module.exports = { exits: {success: {outputExample: \\'abc123\\'} },  fn: function (inputs, exits) { exits.success(\\'nested standalone machine!\\'); } };'\n      }).execSync();\n      // Create an invalid legacy controller (doesn't contain a dictionary)\n      Filesystem.writeSync({\n        force: true,\n        destination: 'api/controllers/LegacyControllerWithFn.js',\n        string: 'module.exports = function (req, res) { return res.send(\\'garbage\\'); };'\n      }).execSync();\n      // Create an invalid action (doesn't contain a machine)\n      Filesystem.writeSync({\n        force: true,\n        destination: 'api/controllers/invalid-action.js',\n        string: 'module.exports = {};'\n      }).execSync();\n      // Create an invalid file (doesn't conform to naming conventions)\n      Filesystem.writeSync({\n        force: true,\n        destination: 'api/controllers/invalidLyNamed-fileController.js',\n        string: 'module.exports = {};'\n      }).execSync();\n\n      // Write a routes.js file\n      Filesystem.writeSync({\n        force: true,\n        destination: 'config/routes.js',\n        string: 'module.exports.routes = ' + JSON.stringify({\n          'POST /route1': 'TopLevelLegacyController.fnAction',\n          'POST /route1a': 'TopLevelLegacy.fnAction',\n          'POST /route2': 'TopLevelLegacyController.machineAction',\n          'POST /route3': {\n             controller: 'TopLevelLegacyController',\n             action: 'fnAction',\n          },\n          'POST /route4': {\n            action: 'toplevellegacy/fnAction',\n          },\n          'POST /route5': {\n            action: 'top-level-standalone-fn'\n          },\n          'POST /route6': {\n            action: 'somefolder/someotherfolder/nestedlegacy/fnaction'\n          },\n          'POST /route6a': {\n            action: 'some/folder/some/other/folder/nestedlegacy/fnaction'\n          },\n          'POST /route7': {\n            action: 'somefolder/someotherfolder/nested-standalone-machine'\n          },\n          'POST /warn1': {\n            controller: 'somefolder/someotherfolder/NestedLegacyController',\n            action: 'machineaction'\n          },\n          'POST /warn2': {\n            controller: 'somefolder/someotherfolder/NestedLegacy',\n            action: 'machineaction'\n          },\n          'POST /warn3': 'somefolder/someotherfolder/NestedLegacyController.machineAction',\n          'POST /warn4': 'some/unknown/action',\n          'POST /warn5': {\n            controller: 'UnknownController',\n            action: 'unknown/action'\n          },\n\n        })\n      }).execSync();\n\n      // Load the Sails app.\n      (new Sails()).load({hooks: {security: false, grunt: false, views: false, blueprints: false, policies: false, pubsub: false}, log: {level: 'error'}}, function(err, _sails) {\n        sailsApp = _sails;\n        return done(err);\n      });\n    });\n\n    after(function(done) {\n      sailsApp.lower(function() {\n        process.chdir(curDir);\n        return done();\n      });\n    });\n\n    it('should load all of the valid controller actions', function() {\n      var expectedActions = [\n        'toplevellegacy/fnaction',\n        'toplevellegacy/machineaction',\n        'toplevellegacy/underscore_action',\n        'toplevellegacy/action-with-dashes',\n        'top-level-standalone-fn',\n        'top-level-standalone-machine',\n        'somefolder/someotherfolder/nestedlegacy/fnaction',\n        'somefolder/someotherfolder/nestedlegacy/machineaction',\n        'some/folder/some/other/folder/nestedlegacy/fnaction',\n        'some/folder/some/other/folder/nestedlegacy/machineaction',\n        'somefolder/someotherfolder/nested-standalone-machine'\n      ];\n      var unexpectedActions = _.difference(_.keys(sailsApp._actions), expectedActions);\n      assert(!unexpectedActions.length, 'Loaded unexpected actions:\\n' + util.inspect(unexpectedActions));\n      _.each(expectedActions, function(expectedAction) {\n        assert(sailsApp._actions[expectedAction], 'Did not load expected action `' + expectedAction + '`');\n        assert(_.isFunction(sailsApp._actions[expectedAction]), 'Expected action `' + expectedAction + '` loaded, but instead of a function it\\'s a ' + typeof(sailsApp._actions[expectedAction]));\n      });\n    });\n\n    it('should bind a route using \\'TopLevelLegacyController/fnAction\\'', function(done) {\n      sailsApp.request('POST /route1', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data, 'legacy fn action!');\n        done();\n      });\n    });\n\n    it('should bind a route using \\'TopLevelLegacy/fnAction\\'', function(done) {\n      sailsApp.request('POST /route1a', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data, 'legacy fn action!');\n        done();\n      });\n    });\n\n    it('should bind a route using \\'TopLevelLegacyController/machineAction\\'', function(done) {\n      sailsApp.request('POST /route2', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data, 'legacy machine action!');\n        done();\n      });\n    });\n\n    it('should bind a route using {controller: \\'TopLevelLegacyController\\', action: \\'fnAction\\'}', function(done) {\n      sailsApp.request('POST /route3', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data, 'legacy fn action!');\n        done();\n      });\n    });\n\n    it('should bind a route using {action: \\'toplevellegacy/fnAction\\'}', function(done) {\n      sailsApp.request('POST /route4', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data, 'legacy fn action!');\n        done();\n      });\n    });\n\n    it('should bind a route using {action: \\'top-level-standalone-fn\\'}', function(done) {\n      sailsApp.request('POST /route5', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data, 'top level standalone fn!');\n        done();\n      });\n    });\n\n    it('should bind a route using {action: \\'somefolder/someotherfolder/nestedlegacy/fnaction\\'}', function(done) {\n      sailsApp.request('POST /route6', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data, 'nested legacy fn action!');\n        done();\n      });\n    });\n\n    it('should bind a route using {action: \\'some/folder/some/other/folder/nestedlegacy/fnaction\\'}', function(done) {\n      sailsApp.request('POST /route6', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data, 'nested legacy fn action!');\n        done();\n      });\n    });\n\n    it('should bind a route using {action: \\'somefolder/someotherfolder/nested-standalone-machine\\'}', function(done) {\n      sailsApp.request('POST /route7', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data, 'nested standalone machine!');\n        done();\n      });\n    });\n\n    it('should bind a route (under protest) using {controller: \\'somefolder/someotherfolder/NestedLegacyController\\', action: \\'machineaction\\'}', function(done) {\n      sailsApp.request('POST /warn1', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data, 'nested legacy machine action!');\n        done();\n      });\n    });\n\n    it('should bind a route (under protest) using {controller: \\'NestedLegacy\\', action: \\'machineaction\\'}', function(done) {\n      sailsApp.request('POST /warn2', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data, 'nested legacy machine action!');\n        done();\n      });\n    });\n\n    it('should bind a route (under protest) using \\'somefolder/someotherfolder/NestedLegacyController.machineAction\\'', function(done) {\n      sailsApp.request('POST /warn3', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data, 'nested legacy machine action!');\n        done();\n      });\n    });\n\n    it('should return a shallow clone of the actions dictionary when `sails.getActions` is called', function() {\n      var actions = sailsApp.getActions();\n      assert(actions !== sailsApp._actions, 'sails.getActions is supposed to return a shallow clone, but got an exact reference!');\n      var expectedActions = [\n        'toplevellegacy/fnaction',\n        'toplevellegacy/machineaction',\n        'toplevellegacy/underscore_action',\n        'toplevellegacy/action-with-dashes',\n        'top-level-standalone-fn',\n        'top-level-standalone-machine',\n        'somefolder/someotherfolder/nestedlegacy/fnaction',\n        'somefolder/someotherfolder/nestedlegacy/machineaction',\n        'some/folder/some/other/folder/nestedlegacy/fnaction',\n        'some/folder/some/other/folder/nestedlegacy/machineaction',\n        'somefolder/someotherfolder/nested-standalone-machine'\n      ];\n      var unexpectedActions = _.difference(_.keys(actions), expectedActions);\n      assert(!unexpectedActions.length, 'Loaded unexpected actions:\\n' + util.inspect(unexpectedActions));\n      _.each(expectedActions, function(expectedAction) {\n        assert(actions[expectedAction], 'Did not load expected action `' + expectedAction + '`');\n        assert(_.isFunction(actions[expectedAction]), 'Expected action `' + expectedAction + '` loaded, but instead of a function it\\'s a ' + typeof(actions[expectedAction]));\n      });\n\n    });\n\n  });\n\n  describe('with conflicting actions in api/controllers', function() {\n\n    var curDir, tmpDir, sailsApp;\n    var warn;\n    var warnings = [];\n\n    before(function(done) {\n      // Cache the current working directory.\n      curDir = process.cwd();\n      // Create a temp directory.\n      tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n      // Create a top-level legacy controller file.\n      Filesystem.writeSync({\n        force: true,\n        destination: 'api/controllers/TopLevelController.js',\n        string: 'module.exports = { fnAction: function (req, res) { res.send(\\'fn controller action!\\'); } };'\n      }).execSync();\n      // Create a top-level action file with a req/res function.\n      Filesystem.writeSync({\n        force: true,\n        destination: 'api/controllers/toplevel/fnaction.js',\n        string: 'module.exports = function (req, res) { res.send(\\'standalone fn!\\'); };'\n      }).execSync();\n\n      return done();\n\n    });\n\n    after(function() {\n      process.chdir(curDir);\n    });\n\n    it('should fail to load sails', function(done) {\n      // Load the Sails app.\n      (new Sails()).load({hooks: {grunt: false, views: false, blueprints: false, policies: false, pubsub: false}, log: {level: 'error'}}, function(err, _sails) {\n        if (!err) {\n          _sails.lower(function() {\n            return done(new Error('Should have thrown an error!'));\n          });\n        }\n        assert.equal(err.code, 'E_CONFLICT');\n        assert.equal(err.identity, 'toplevel/fnaction');\n        return done();\n      });\n\n    });\n\n  });\n\n  describe('With a controller with `Controller` in the name', function() {\n\n    var curDir, tmpDir, sailsApp;\n    var warn;\n    var warnings = [];\n\n    before(function(done) {\n      // Cache the current working directory.\n      curDir = process.cwd();\n      // Create a temp directory.\n      tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n      // Switch to the temp directory.\n      process.chdir(tmpDir.name);\n      // Create a top-level legacy controller file with `Controller` in the name.\n      Filesystem.writeSync({\n        force: true,\n        destination: 'api/controllers/MicroControllerController.js',\n        string: 'module.exports = { \\'check\\': function(req, res) {  return res.send(\\'mate\\'); } };'\n      }).execSync();\n\n      // Write a routes.js file\n      Filesystem.writeSync({\n        force: true,\n        destination: 'config/routes.js',\n        string: 'module.exports.routes = ' + JSON.stringify({\n          'GET /microcontroller/:id/check':  'MicroControllerController.check',\n          'GET /microcontroller/:id/check2': { controller: 'MicroControllerController', action: 'check' },\n          'GET /microcontroller/:id/check3': 'microcontroller/check'\n        })\n      }).execSync();\n\n      // Load the Sails app.\n      (new Sails()).load({hooks: {security: false, grunt: false, views: false, blueprints: false, policies: false, pubsub: false}, log: {level: 'error'}}, function(err, _sails) {\n        sailsApp = _sails;\n        return done(err);\n      });\n    });\n\n    after(function(done) {\n      sailsApp.lower(function() {\n        process.chdir(curDir);\n        return done();\n      });\n    });\n\n    it('should bind a route using \\'MicroControllerController.check\\'', function(done) {\n      sailsApp.request('GET /microcontroller/123/check', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data, 'mate');\n        done();\n      });\n    });\n\n    it('should bind a route using { controller: \\'MicroControllerController\\', action: \\'check\\' }', function(done) {\n      sailsApp.request('GET /microcontroller/123/check2', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data, 'mate');\n        done();\n      });\n    });\n\n    it('should bind a route using \\'microcontroller/check\\'', function(done) {\n      sailsApp.request('GET /microcontroller/123/check3', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(data, 'mate');\n        done();\n      });\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/unit/req.errors.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar assert = require('assert');\nvar _ = require('@sailshq/lodash');\nvar $Sails = require('../helpers/sails');\n\n\ndescribe('request that causes an error', function (){\n\n  var sails = $Sails.load({\n    globals: false,\n    log: { level: 'silent' },\n    loadHooks: [\n      'moduleloader',\n      'userconfig',\n      'responses'\n    ]\n  });\n  var saveServerError;\n\n  // Restore the default error handler after tests that change it\n  afterEach(function () {\n    if (saveServerError) {\n      sails.registry.responses.serverError = saveServerError;\n      saveServerError = undefined;\n    }\n  });\n\n  it('should return the expected error when something throws', function (done) {\n\n    var ERROR = 'oh no I forgot my keys';\n\n    sails.get('/errors/1', function (req, res) {\n      throw ERROR;\n    });\n\n    sails.request('GET /errors/1', {}, function (err) {\n      assert.deepEqual(500, err.status);\n      assert.deepEqual(ERROR, err.body);\n      done();\n    });\n\n  });\n\n  it('should call the `res.serverError()` handler when something throws and the \"responses\" hook is enabled, and the error should emerge, untampered-with', function (done) {\n\n    var ERROR = 'oh no I forgot my keys';\n    var CHECKPOINT = 'made it';\n\n    saveServerError = sails.registry.responses.serverError;\n    sails.registry.responses.serverError = function (err) {\n      assert.deepEqual(ERROR, err);\n      this.res.status(500).send(CHECKPOINT);\n    };\n\n    sails.get('/errors/2', function (req, res) {\n      throw ERROR;\n    });\n\n    sails.request('GET /errors/2', {}, function (err) {\n      assert.deepEqual(CHECKPOINT, err.body);\n      done();\n    });\n\n  });\n\n  it('should return the expected error when something throws an Error object', function (done) {\n\n    var MESSAGE = 'oh no I forgot my keys again';\n    var ERROR = new Error(MESSAGE);\n\n    ERROR.toJSON = function() {\n      return {\n        message: MESSAGE,\n        stack: this.stack\n      };\n    };\n\n    sails.get('/errors/3', function (req, res) {\n      throw ERROR;\n    });\n\n    sails.request('GET /errors/3', {}, function (err) {\n      assert.deepEqual(err.status, 500);\n      assert.deepEqual(typeof(err.body), 'object');\n      assert.deepEqual(err.body.message, MESSAGE);\n      done();\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/unit/req.session.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar assert = require('assert');\nvar util = require('util');\nvar _ = require('@sailshq/lodash');\nvar async = require('async');\nvar Sails = require('../../lib').Sails;\n\n\ndescribe('req.session', function (){\n\n  var app;\n\n  before(function (done){\n    app = Sails();\n    app.load({\n      globals: false,\n      loadHooks: [\n        'moduleloader',\n        'userconfig',\n        'session'\n      ],\n      session: {\n        adapter: 'memory',\n        name: 'sails.sid',\n        secret: 'af9442683372850a85a87150c47b4a31'\n      }\n    }, done);\n  });\n\n\n  describe('when responding to a virtual request',function (){\n\n    var doesSessionExist;\n    var isSessionAnObject;\n    var doesTestPropertyStillExist;\n\n    before(function setupTestRoute(){\n      app.post('/sessionTest', function (req, res){\n        doesSessionExist = !!req.session;\n        isSessionAnObject = _.isObject(req.session);\n        req.session.something = 'some string';\n        res.send();\n      });\n\n      app.get('/sessionTest', function (req, res){\n        doesSessionExist = !!req.session;\n        isSessionAnObject = _.isObject(req.session);\n        doesTestPropertyStillExist = req.session.something === 'some string';\n        res.send();\n      });\n\n      app.get('/sails.io.js', function (req, res){\n        doesSessionExist = !!req.session;\n        res.send();\n      });\n\n    });\n\n    describe('with routes disabled by the default `isSessionDisabled` function', function() {\n\n      it('should not exist', function (done) {\n        app.request({\n          url: '/sails.io.js',\n          method: 'GET',\n          params: {},\n          headers: {}\n        }, function (err, res, body){\n          if (err) return done(err);\n          if (res.statusCode !== 200) return done(new Error('Expected 200 status code'));\n          if (doesSessionExist) return done(new Error('req.session should not exist.'));\n          if (res.headers['set-cookie']) return done(new Error('Should not have a `set-cookie` header in the response.'));\n          return done();\n        });\n      });\n\n    });\n\n    describe('with routes NOT disabled by the default `isSessionDisabled` function', function() {\n\n      it('should exist', function (done) {\n        app.request({\n          url: '/sessionTest',\n          method: 'POST',\n          params: {},\n          headers: {}\n        }, function (err, res, body){\n          if (err) return done(err);\n          if (res.statusCode !== 200) return done(new Error('Expected 200 status code'));\n          if (!doesSessionExist) return done(new Error('req.session should exist.'));\n          if (!isSessionAnObject) return done(new Error('req.session should be an object.'));\n          return done();\n        });\n      });\n\n\n      //\n      // To test:\n      //\n      // DEBUG=express-session mocha test/unit/req.session.test.js -b -g 'should persist'\n      //\n\n      it('should persist data between requests', function (done){\n        app.request({\n          url: '/sessionTest',\n          method: 'POST',\n          params: {},\n          headers: {}\n        }, function (err, clientRes, body){\n          if (err) return done(err);\n          if (clientRes.statusCode !== 200) return done(new Error('Expected 200 status code'));\n          if (!doesSessionExist) return done(new Error('req.session should exist.'));\n          if (!isSessionAnObject) return done(new Error('req.session should be an object.'));\n\n          app.request({\n            url: '/sessionTest',\n            method: 'GET',\n            params: {},\n            headers: {\n              cookie: clientRes.headers['set-cookie']\n            }\n          }, function (err, clientRes, body){\n            if (err) return done(err);\n            if (clientRes.statusCode !== 200) return done(new Error('Expected 200 status code'));\n            if (!doesSessionExist) return done(new Error('req.session should exist.'));\n            if (!isSessionAnObject) return done(new Error('req.session should be an object.'));\n            if (!doesTestPropertyStillExist) return done(new Error('`req.session.something` should still exist for subsequent requests.'));\n            return done();\n          });\n        });\n      });\n\n    });\n\n\n\n  });\n\n  after(function (done) {\n    done();\n  });\n\n});\n\n"
  },
  {
    "path": "test/unit/req.test.js",
    "content": "/**\n * Module dependencies\n */\nvar assert = require('assert');\nvar should = require('should');   // https://github.com/visionmedia/should.js/\n\n\nvar buildReq = require('root-require')('lib/router/req');\n\n\n/**\n * This mocked implementation of `req` forms the basis for\n * Sails' transport-agnostic support of Connect/Express\n * middleware.\n */\ndescribe('Base Request (`req`)', function (){\n\n  describe('with empty request', function() {\n\n    var req;\n\n\n    // Mock the request object.\n    before(function (){\n      req = buildReq();\n      req.should.be.an.Object;\n      this.req = req;\n    });\n\n\n    it('.body', function () {\n      req.body.should.be.an.Object;\n      req.body.should.be.empty;\n    });\n\n    it('.params', function () {\n      req.params.should.be.an.Array;\n      req.params.should.be.empty;\n    });\n\n    it('.query', function (){\n      req.query.should.be.an.Object;\n      req.query.should.be.empty;\n    });\n\n    it('.param()', function () {\n      should(req.param('foo'))\n        .not.be.ok;\n    });\n  });\n\n\n  describe('with url /hello?abc=123&foo=bar', function() {\n\n    var req;\n\n\n    // Mock the request object.\n    before(function (){\n      req = buildReq({url: '/hello?abc=123&foo=bar'});\n      req.should.be.an.Object;\n      this.req = req;\n    });\n\n\n    it('.body', function () {\n      req.body.should.be.an.Object;\n      req.body.should.be.empty;\n    });\n\n    it('.params', function () {\n      req.params.should.be.an.Array;\n      req.params.should.be.empty;\n    });\n\n    it('.query', function (){\n      req.query.should.be.an.Object;\n      req.query.should.have.property('abc', '123');\n      req.query.should.have.property('foo', 'bar');\n    });\n\n    it('.param()', function () {\n      should(req.param('abc')).equal('123');\n      should(req.param('foo')).equal('bar');\n    });\n\n    it('.path', function() {\n      req.path.should.be.an.String;\n      req.path.should.equal('/hello');\n    });\n\n    it('.url', function() {\n      req.url.should.be.an.String;\n      req.url.should.equal('/hello?abc=123&foo=bar');\n    });\n\n    it('.originalUrl', function() {\n      req.originalUrl.should.be.an.String;\n      req.originalUrl.should.equal('/hello?abc=123&foo=bar');\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/unit/res.test.js",
    "content": "/**\n * Module dependencies\n */\nvar assert = require('assert');\nvar should = require('should');   // https://github.com/visionmedia/should.js/\n\n\nvar buildRes = require('root-require')('lib/router/res');\n\n\n/**\n * This mocked implementation of `res` forms the basis for\n * Sails' transport-agnostic support of Connect/Express\n * middleware.\n */\ndescribe('Base Response (`res`)', function (){\n\n  describe('header handling', function() {\n    var ourCustomMime = 'application/vnd.sails.test.v1+json'\n\n    it(\"should set a content-type when we send a JS object but don't set content-type\", function () {\n      var res = buildRes()\n      var resBodyData = {foo: 'bar'}\n      res.status(200).send(resBodyData)\n      res.headers['content-type'].should.equal('application/json')\n    });\n\n    it('should not overwrite our content-type header when we send a JS object', function () {\n      var res = buildRes()\n      var resBodyData = {foo: 'bar'}\n      res.set('content-type', ourCustomMime) // set our own content-type!\n      res.status(200).send(resBodyData)\n      res.headers['content-type'].should.equal(ourCustomMime)\n    });\n\n    it(\"should NOT automatically set a content-type when we send a string but don't set content-type\", function () {\n      var res = buildRes()\n      var resBodyDataString = 'some plain text'\n      res.status(200).send(resBodyDataString)\n      should(res.headers['content-type']).be.empty\n    });\n\n    it('should not overwrite our content-type header when we send a string', function () {\n      var res = buildRes()\n      var resBodyDataString = '<some xml=\"\"></some>'\n      res.set('content-type', ourCustomMime) // set our own content-type!\n      res.status(200).send(resBodyDataString)\n      res.headers['content-type'].should.equal(ourCustomMime)\n    });\n\n  });\n\n});\n"
  },
  {
    "path": "test/unit/router.bind.test.js",
    "content": "/**\n * Module dependencies\n */\nvar supertest = require('supertest');\nvar assert = require('assert');\n\nvar $Sails = require('../helpers/sails');\nvar $Router = require('../helpers/router');\n\n// Middleware fixtures\nvar RESPOND = require('../fixtures/middleware');\n\n\ndescribe('Router.bind', function() {\n\n  var sails = $Sails.load.withAllHooksDisabled();\n\n  it('Should not allow routes with :length as a parameter', function() {\n    assert.throws(function() {\n      this.sails.router.bind('get /foo/:length', RESPOND.HELLO);\n    });\n    assert.throws(function() {\n      this.sails.router.bind('get /foo/:length/foo', RESPOND.HELLO);\n    });\n  });\n\n  $Router.bind('get /foo', RESPOND.HELLO)\n    .expectBoundRoute({\n      path: '/foo',\n      method: 'get'\n    })\n    .test(function() {\n      $Router.bind('get /FOO', RESPOND.GOODBYE)\n        .expectBoundRoute({\n          path: '/FOO',\n          method: 'get'\n        }).test(function() {\n          it('should send expected response (get /foo)', function(done) {\n            this.sails.request({\n              url: '/foo',\n              method: 'get'\n            }, function(err, res, body) {\n              if (err) {\n                return done(err);\n              }\n              res.statusCode.should.be.equal(200);\n              body.should.be.equal('hello world!');\n              return done();\n            });\n          });\n          it('router should not be case-sensitive', function(done) {\n            this.sails.request({\n              url: '/FOO',\n              method: 'get'\n            }, function(err, res, body) {\n              if (err) {\n                return done(err);\n              }\n              res.statusCode.should.be.equal(200);\n              body.should.be.equal('hello world!');\n              return done();\n            });\n          });\n        });\n    });\n\n  $Router.bind('get /footarg', {\n      target: RESPOND.HELLO\n    })\n    .expectBoundRoute({\n      path: '/footarg',\n      method: 'get'\n    })\n    .test(function() {\n      it('should send expected response (get /footarg)', function(done) {\n        this.sails.request({\n          url: '/footarg',\n          method: 'get'\n        }, function(err, res, body) {\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          body.should.be.equal('hello world!');\n          return done();\n        });\n      });\n    });\n\n  $Router.bind('get /foofn', {\n      fn: RESPOND.HELLO\n    })\n    .expectBoundRoute({\n      path: '/foofn',\n      method: 'get'\n    })\n    .test(function() {\n      it('should send expected response (get /foofn)', function(done) {\n        this.sails.request({\n          url: '/foofn',\n          method: 'get'\n        }, function(err, res, body) {\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          body.should.be.equal('hello world!');\n          return done();\n        });\n      });\n    });\n\n\n  $Router.bind('/bar', RESPOND.HELLO)\n    .expectBoundRoute({\n      path: '/bar',\n      method: 'get'\n    })\n    .expectBoundRoute({\n      path: '/bar',\n      method: 'put'\n    })\n    .expectBoundRoute({\n      path: '/bar',\n      method: 'post'\n    })\n    .expectBoundRoute({\n      path: '/bar',\n      method: 'patch'\n    })\n    .expectBoundRoute({\n      path: '/bar',\n      method: 'delete'\n    })\n    .test(function() {\n      it('should send expected response (get /bar)', function(done) {\n        this.sails.request({\n          url: '/bar',\n          method: 'get'\n        }, function(err, res, body) {\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          body.should.be.equal('hello world!');\n          return done();\n        });\n      });\n    })\n    .test(function() {\n      it('should send expected response (post /bar)', function(done) {\n        this.sails.request({\n          url: '/bar',\n          method: 'post'\n        }, function(err, res, body) {\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          body.should.be.equal('hello world!');\n          return done();\n        });\n      });\n    })\n    .test(function() {\n      it('should send expected response (put /bar)', function(done) {\n        this.sails.request({\n          url: '/bar',\n          method: 'put'\n        }, function(err, res, body) {\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          body.should.be.equal('hello world!');\n          return done();\n        });\n      });\n    })\n    .test(function() {\n      it('should send expected response (delete /bar)', function(done) {\n        this.sails.request({\n          url: '/bar',\n          method: 'delete'\n        }, function(err, res, body) {\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          body.should.be.equal('hello world!');\n          return done();\n        });\n      });\n    })\n    .test(function() {\n      it('should send expected response (patch /bar)', function(done) {\n        this.sails.request({\n          url: '/bar',\n          method: 'patch'\n        }, function(err, res, body) {\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          body.should.be.equal('hello world!');\n          return done();\n        });\n      });\n    })\n    .test(function() {\n      it('should send expected response (options /bar)', function(done) {\n        var sails = this.sails;\n        this.sails.request({\n          url: '/bar',\n          method: 'options'\n        }, function(err, res, body) {\n          // console.dir(sails.router._privateRouter, {depth: null});\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          return done();\n        });\n      });\n    })\n    .test(function() {\n      it('should send a 404 response (copy /bar)', function(done) {\n        this.sails.request({\n          url: '/bar',\n          method: 'copy'\n        }, function(err, res, body) {\n          err.status.should.be.equal(404);\n          return done();\n        });\n      });\n    });\n\n  $Router.bind('all /boop', RESPOND.HELLO)\n    .expectBoundRoute({\n      path: '/boop',\n      method: 'get'\n    })\n    .expectBoundRoute({\n      path: '/boop',\n      method: 'put'\n    })\n    .expectBoundRoute({\n      path: '/boop',\n      method: 'post'\n    })\n    .expectBoundRoute({\n      path: '/boop',\n      method: 'patch'\n    })\n    .expectBoundRoute({\n      path: '/boop',\n      method: 'delete'\n    })\n    .test(function() {\n      it('should send expected response (get /boop)', function(done) {\n        this.sails.request({\n          url: '/boop',\n          method: 'get'\n        }, function(err, res, body) {\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          body.should.be.equal('hello world!');\n          return done();\n        });\n      });\n    })\n    .test(function() {\n      it('should send expected response (post /boop)', function(done) {\n        this.sails.request({\n          url: '/boop',\n          method: 'post'\n        }, function(err, res, body) {\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          body.should.be.equal('hello world!');\n          return done();\n        });\n      });\n    })\n    .test(function() {\n      it('should send expected response (put /boop)', function(done) {\n        this.sails.request({\n          url: '/boop',\n          method: 'put'\n        }, function(err, res, body) {\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          body.should.be.equal('hello world!');\n          return done();\n        });\n      });\n    })\n    .test(function() {\n      it('should send expected response (delete /boop)', function(done) {\n        this.sails.request({\n          url: '/boop',\n          method: 'delete'\n        }, function(err, res, body) {\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          body.should.be.equal('hello world!');\n          return done();\n        });\n      });\n    })\n    .test(function() {\n      it('should send expected response (patch /boop)', function(done) {\n        this.sails.request({\n          url: '/boop',\n          method: 'patch'\n        }, function(err, res, body) {\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          body.should.be.equal('hello world!');\n          return done();\n        });\n      });\n    })\n    .test(function() {\n      it('should send expected response (options /boop)', function(done) {\n        this.sails.request({\n          url: '/boop',\n          method: 'options'\n        }, function(err, res, body) {\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          body.should.be.equal('hello world!');\n          return done();\n        });\n      });\n    })\n    .test(function() {\n      it('should send expected response (options /boop)', function(done) {\n        this.sails.request({\n          url: '/boop',\n          method: 'copy'\n        }, function(err, res, body) {\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          body.should.be.equal('hello world!');\n          return done();\n        });\n      });\n    });\n\n\n  $Router.bind('options /blap', RESPOND.GOODBYE)\n    .expectBoundRoute({\n      path: '/boop',\n      method: 'options'\n    })\n    .test(function() {\n      it('should send expected response (options /blap)', function(done) {\n        this.sails.request({\n          url: '/blap',\n          method: 'options'\n        }, function(err, res, body) {\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          body.should.be.equal('goodbye world!');\n          return done();\n        });\n      });\n    });\n\n\n  $Router.bind('post /bar_baz_beezzz', RESPOND.HELLO_500)\n    .expectBoundRoute({\n      path: '/bar_baz_beezzz',\n      method: 'post'\n    })\n    .test(function() {\n      it('should send expected response (post /bar_baz_beezzz)', function(done) {\n        this.sails.request({\n          url: '/bar_baz_beezzz',\n          method: 'post'\n        }, function(err, res, body) {\n          err.status.should.be.equal(500);\n          err.body.should.be.equal('hello world!');\n          return done();\n        });\n      });\n    });\n\n\n\n  $Router.bind('patch /user', RESPOND.JSON_HELLO)\n    .expectBoundRoute({\n      path: '/user',\n      method: 'patch'\n    })\n    .test(function() {\n      it('should send expected response (patch /user)', function(done) {\n        this.sails.request({\n          url: '/user',\n          method: 'patch'\n        }, function(err, res, body) {\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          body.should.be.instanceOf(Object);\n          body.hello.should.be.equal('world');\n          return done();\n        });\n      });\n    });\n\n  $Router.bind('   /whitespace1 ', RESPOND.JSON_HELLO)\n    .expectBoundRoute({\n      path: '/whitespace1',\n      method: 'get'\n    })\n    .test(function() {\n      it('should send expected response (get /whitespace1)', function(done) {\n        this.sails.request({\n          url: '/whitespace1',\n          method: 'get'\n        }, function(err, res, body) {\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          body.should.be.instanceOf(Object);\n          body.hello.should.be.equal('world');\n          return done();\n        });\n      });\n    });\n\n  $Router.bind('  GET   /whitespace2 ', RESPOND.JSON_HELLO)\n    .expectBoundRoute({\n      path: '/whitespace',\n      method: 'get'\n    })\n    .test(function() {\n      it('should send expected response (get /whitespace2)', function(done) {\n        this.sails.request({\n          url: '/whitespace2',\n          method: 'get'\n        }, function(err, res, body) {\n          if (err) {\n            return done(err);\n          }\n          res.statusCode.should.be.equal(200);\n          body.should.be.instanceOf(Object);\n          body.hello.should.be.equal('world');\n          return done();\n        });\n      });\n      it('should send 404 response (put /whitespace2)', function(done) {\n        this.sails.request({\n          url: '/whitespace2',\n          method: 'put'\n        }, function(err, res, body) {\n          if (err && err.status === 404) {\n            return done();\n          } else if (err) {\n            return done(err);\n          } else {\n            return done(new Error('Should have returned 404!'));\n          }\n        });\n      });\n\n    });\n\n\n  $Router\n    .test(function() {\n      it('should respond with 404 handler', function(done) {\n        this.sails.request({\n          url: '/something_undefined',\n          method: 'get'\n        }, function(err, res, body) {\n          err.status.should.be.equal(404);\n          return done();\n        });\n      });\n    });\n\n\n  $Router.bind('post /something_that_throws', RESPOND.SOMETHING_THAT_THROWS)\n    .test(function() {\n      it('should respond with 500 handler if something throws', function(done) {\n        this.sails.request({\n          url: '/something_that_throws',\n          method: 'post'\n        }, function(err, res, body) {\n          err.status.should.be.equal(500);\n          return done();\n        });\n      });\n    });\n\n});\n"
  },
  {
    "path": "test/unit/router.ordering.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar util = require('util');\nvar assert = require('assert');\nvar tmp = require('tmp');\nvar _ = require('@sailshq/lodash');\n\nvar Sails = require('../../lib').constructor;\n\ntmp.setGracefulCleanup();\n\n/**\n * Errors\n */\n\nvar Err = {\n  badResponse: function(response) {\n    return 'Wrong server response!  Response :::\\n' + util.inspect(response.body);\n  }\n};\n\n\n\n/**\n * Tests\n */\n\ndescribe('route ordering :: ', function() {\n\n  var curDir, tmpDir, sailsApp;\n\n  var testRoutes = {\n    '/*': function(req, res) {res.json({route: '/*', params: req.params});},\n    'GET /*': function(req, res) {res.json({route: 'GET /*', params: req.params});},\n    '/*/baz': function(req, res) {res.json({route: '/*/baz', params: req.params});},\n    '/*/baz/*': function(req, res) {res.json({route: '/*/baz/*', params: req.params});},\n    '/*/bar/baz': function(req, res) {res.json({route: '/*/bar/baz', params: req.params});},\n    '/:foo/*': function(req, res) {res.json({route: '/:foo/*', params: req.params});},\n    '/:foo/:bar/*': function(req, res) {res.json({route: '/:foo/:bar/*', params: req.params});},\n    '/:foo/:bar/:baz': function(req, res) {res.json({route: '/:foo/:bar/:baz', params: req.params});},\n    '/:foo/:bar/baz': function(req, res) {res.json({route: '/:foo/:bar/baz', params: req.params});},\n    '/:foo/:bar': function(req, res) {res.json({route: '/:foo/:bar', params: req.params});},\n    '/:foo/bar/:baz': function(req, res) {res.json({route: '/:foo/bar/:baz', params: req.params});},\n    '/:foo/bar/baz': function(req, res) {res.json({route: '/:foo/bar/baz', params: req.params});},\n    '/:foo/bar': function(req, res) {res.json({route: '/:foo/bar', params: req.params});},\n    '/:foo': function(req, res) {res.json({route: '/:foo', params: req.params});},\n    '/foo/*': function(req, res) {res.json({route: '/foo/*', params: req.params});},\n    '/foo/*/baz': function(req, res) {res.json({route: '/foo/*/baz', params: req.params});},\n    '/foo/:bar/:baz': function(req, res) {res.json({route: '/foo/:bar/:baz', params: req.params});},\n    '/foo/:bar/baz': function(req, res) {res.json({route: '/foo/:bar/baz', params: req.params});},\n    '/foo/:bar': function(req, res) {res.json({route: '/foo/:bar', params: req.params});},\n    '/foo/bar/*': function(req, res) {res.json({route: '/foo/bar/*', params: req.params});},\n    'GET /foo/bar/*': function(req, res) {res.json({route: 'GET /foo/bar/*', params: req.params});},\n    '/foo/bar/:baz': function(req, res) {res.json({route: '/foo/bar/:baz', params: req.params});},\n    'GET /foo/bar/:baz': function(req, res) {res.json({route: 'GET /foo/bar/:baz', params: req.params});},\n    '/foo/bar/baz': function(req, res) {res.json({route: '/foo/bar/baz', params: req.params});},\n    '/foo/bar': function(req, res) {res.json({route: '/foo/bar', params: req.params});},\n    '/foo': function(req, res) {res.json({route: '/foo', params: req.params});},\n    'GET /foo': function(req, res) {res.json({route: 'GET /foo', params: req.params});}\n  };\n\n  before(function(done) {\n    // Cache the current working directory.\n    curDir = process.cwd();\n    // Create a temp directory.\n    tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});\n    // Switch to the temp directory.\n    process.chdir(tmpDir.name);\n    var sails = new Sails();\n    (new Sails()).load({\n      loadHooks: [],\n      routes: testRoutes\n    }, function(err, _sails) {\n      if (err) { return done(err); }\n      sailsApp = _sails;\n      return done();\n    });\n  });\n\n  after(function(done) {\n    sailsApp.lower(function() {\n      process.chdir(curDir);\n      return done();\n    });\n  });\n\n  it('should bind the routes in the correct order', function(){\n    var sortedRoutes = sailsApp.router.getSortedRouteAddresses();\n    assert(_.isEqual(sortedRoutes, ['GET /foo', '/foo', '/foo/bar', '/foo/bar/baz', 'GET /foo/bar/:baz', '/foo/bar/:baz', 'GET /foo/bar/*', '/foo/bar/*', '/foo/:bar', '/foo/:bar/baz', '/foo/:bar/:baz', '/foo/*/baz', '/foo/*', '/:foo/bar', '/:foo/bar/baz', '/:foo/bar/:baz', '/:foo/:bar/baz', '/*/bar/baz', '/*/baz/*', '/*/baz', '/:foo', '/:foo/:bar', '/:foo/:bar/:baz', '/:foo/:bar/*', '/:foo/*', 'GET /*', '/*']), sortedRoutes);\n  });\n\n  var testRequests = {\n    'GET /foo': 'GET /foo',\n    '/foo': 'POST /foo',\n    '/foo/bar': 'GET /foo/bar',\n    '/foo/bar/baz': 'GET /foo/bar/baz',\n    'GET /foo/bar/:baz': 'GET /foo/bar/xxx',\n    '/foo/bar/:baz': 'POST /foo/bar/xxx',\n    'GET /foo/bar/*': 'GET /foo/bar/xxx/yyy',\n    '/foo/bar/*': 'POST /foo/bar/xxx/yyy',\n    '/foo/:bar': 'GET /foo/xxx',\n    '/foo/:bar/baz': 'GET /foo/xxx/baz',\n    '/foo/:bar/:baz': 'GET /foo/xxx/yyy',\n    '/foo/*/baz': 'GET /foo/xxx/yyy/zzz/baz',\n    '/foo/*': 'GET /foo/xxx/yyy/zzz',\n    '/:foo/bar': 'GET /xxx/bar',\n    '/:foo/bar/baz': 'GET /xxx/bar/baz',\n    '/:foo/bar/:baz': 'GET /xxx/bar/yyy',\n    '/:foo/:bar/baz': 'GET /xxx/yyy/baz',\n    '/*/bar/baz': '/xxx/yyy/bar/baz',\n    '/*/baz/*': '/xxx/baz/yyy',\n    '/*/baz': '/xxx/yyy/zzz/baz',\n    '/:foo': 'GET /xxx',\n    '/:foo/:bar': 'GET /xxx/yyy',\n    '/:foo/:bar/:baz': 'GET /xxx/yyy/zzz',\n    '/:foo/:bar/*': 'GET /xxx/yyy/zzz/owl'\n  };\n\n  _.each(testRequests, function(request, expectedRoute) {\n    it('a request to `' + request + '` should be handled by the `' + expectedRoute + '` route', function(done) {\n      sailsApp.request(request, {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.equal(data.route, expectedRoute, 'The `' + data.route + '` route handled it instead!');\n        done();\n      });\n    });\n  });\n\n});\n"
  },
  {
    "path": "test/unit/router.test.js",
    "content": "/**\n * Module dependencies\n */\nvar should = require('should');\n\nvar $Sails = require('../helpers/sails');\n\n\ndescribe('`sails.router`', function() {\n\n    var sails = $Sails.load.withAllHooksDisabled();\n\n\n\n    it('should be exposed on the `sails` global', function () {\n        sails\n            .router\n            ._privateRouter\n            .stack\n                .should.be.ok;\n    });\n});\n"
  },
  {
    "path": "test/unit/router.unbind.test.js",
    "content": "/**\n * Module dependencies\n */\nvar $Sails = require('root-require')('test/helpers/sails');\nvar $Router = require('root-require')('test/helpers/router');\n\n\n\ndescribe('sails.router.unbind', function (){\n\n  var sails = $Sails.load.withAllHooksDisabled();\n\n\n  $Router.unbind('get /foo')\n  .shouldDelete({\n    path: '/foo',\n    method: 'get'\n  });\n\n\n\n  $Router.unbind('post /bar_baz_beezzz')\n  .shouldDelete({\n    path: '/bar_baz_beezzz',\n    method: 'post'\n  });\n\n\n\n  $Router.unbind('patch /user')\n  .shouldDelete({\n    path: '/user',\n    method: 'patch'\n  });\n\n});\n\n\n\n\n\n"
  },
  {
    "path": "test/unit/virtual-request-interpreter.test.js",
    "content": "/**\n * Module dependencies\n */\n\nvar assert = require('assert');\nvar $Sails = require('../helpers/sails');\n\n\ndescribe('virtual request interpreter', function (){\n\n  var app = $Sails.load({\n    globals: false,\n    log: { level: 'silent' },\n    loadHooks: [\n      'moduleloader',\n      'userconfig',\n      'responses'\n    ]\n  });\n\n\n  //  ██████╗ ███████╗███████╗   ██████╗ ███████╗██████╗ ██╗██████╗ ███████╗ ██████╗████████╗ ██╗██╗\n  //  ██╔══██╗██╔════╝██╔════╝   ██╔══██╗██╔════╝██╔══██╗██║██╔══██╗██╔════╝██╔════╝╚══██╔══╝██╔╝╚██╗\n  //  ██████╔╝█████╗  ███████╗   ██████╔╝█████╗  ██║  ██║██║██████╔╝█████╗  ██║        ██║   ██║  ██║\n  //  ██╔══██╗██╔══╝  ╚════██║   ██╔══██╗██╔══╝  ██║  ██║██║██╔══██╗██╔══╝  ██║        ██║   ██║  ██║\n  //  ██║  ██║███████╗███████║██╗██║  ██║███████╗██████╔╝██║██║  ██║███████╗╚██████╗   ██║   ╚██╗██╔╝\n  //  ╚═╝  ╚═╝╚══════╝╚══════╝╚═╝╚═╝  ╚═╝╚══════╝╚═════╝ ╚═╝╚═╝  ╚═╝╚══════╝ ╚═════╝   ╚═╝    ╚═╝╚═╝\n  //\n  describe('res.redirect()', function (){\n\n    it('should support creamy vanilla usage', function (done) {\n      app.get('/res_redirect/1', function (req, res) {\n        return res.redirect('/foo/bar');\n      });\n      app.request('GET /res_redirect/1', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(resp.headers.Location, '/foo/bar');\n        assert.deepEqual(302, resp.statusCode);\n        done();\n      });\n    });\n\n    it('should honor .status()', function (done) {\n      app.get('/res_redirect/2', function (req, res) {\n        return res.status(301).redirect('/foo/baz');\n      });\n      app.request('GET /res_redirect/2', {}, function (err, resp, data) {\n        assert(!err, err);\n        assert.deepEqual(resp.headers.Location, '/foo/baz');\n        assert.deepEqual(301, resp.statusCode);\n        done();\n      });\n    });\n\n    it('should NO LONGER ALLOW a status code to be passed as the first argument', function (done) {\n      app.get('/res_redirect/3', function (req, res) {\n        try {\n          return res.redirect(301, '/foo/baz');\n        } catch(e) {\n          // Go ahead and throw this again on purpose (so that we test the rest\n          // of the unhandled error flow for the VR interpreter)\n          throw e;\n        }\n      });\n      app.request('GET /res_redirect/3', {}, function (err, resp, data) {\n        try {\n          assert(err);\n          assert(err.message.match(/Error: The 2\\-ary usage of \\`res\\.redirect\\(\\)\\` is no longer supported/), 'Unexpected error message: '+err.message);\n        } catch (e) { return done(e); }\n        return done();\n      });\n    });\n\n    it('should NO LONGER ALLOW the status code being passed in as the second argument EITHER', function (done) {\n      app.get('/res_redirect/4', function (req, res) {\n        try {\n          return res.redirect('/foo/baz', 301);\n        } catch(e) {\n          // Go ahead and throw this again on purpose (so that we test the rest\n          // of the unhandled error flow for the VR interpreter)\n          throw e;\n        }\n      });\n      app.request('GET /res_redirect/4', {}, function (err, resp, data) {\n        try {\n          assert(err);\n          assert(err.message.match(/Error: The 2\\-ary usage of \\`res\\.redirect\\(\\)\\` is no longer supported/), 'Unexpected error message: '+err.message);\n        } catch (e) { return done(e); }\n        return done();\n      });\n    });\n\n  });//</describe: res.redirect()>\n\n\n\n\n\n\n\n  //  ██████╗ ███████╗███████╗   ███████╗███████╗███╗   ██╗██████╗  ██╗██╗        ██╗\n  //  ██╔══██╗██╔════╝██╔════╝   ██╔════╝██╔════╝████╗  ██║██╔══██╗██╔╝╚██╗       ██║\n  //  ██████╔╝█████╗  ███████╗   ███████╗█████╗  ██╔██╗ ██║██║  ██║██║  ██║    ████████╗\n  //  ██╔══██╗██╔══╝  ╚════██║   ╚════██║██╔══╝  ██║╚██╗██║██║  ██║██║  ██║    ██╔═██╔═╝\n  //  ██║  ██║███████╗███████║██╗███████║███████╗██║ ╚████║██████╔╝╚██╗██╔╝    ██████║\n  //  ╚═╝  ╚═╝╚══════╝╚══════╝╚═╝╚══════╝╚══════╝╚═╝  ╚═══╝╚═════╝  ╚═╝╚═╝     ╚═════╝\n  //\n  //  ██████╗ ███████╗███████╗        ██╗███████╗ ██████╗ ███╗   ██╗ ██╗██╗\n  //  ██╔══██╗██╔════╝██╔════╝        ██║██╔════╝██╔═══██╗████╗  ██║██╔╝╚██╗\n  //  ██████╔╝█████╗  ███████╗        ██║███████╗██║   ██║██╔██╗ ██║██║  ██║\n  //  ██╔══██╗██╔══╝  ╚════██║   ██   ██║╚════██║██║   ██║██║╚██╗██║██║  ██║\n  //  ██║  ██║███████╗███████║██╗╚█████╔╝███████║╚██████╔╝██║ ╚████║╚██╗██╔╝\n  //  ╚═╝  ╚═╝╚══════╝╚══════╝╚═╝ ╚════╝ ╚══════╝ ╚═════╝ ╚═╝  ╚═══╝ ╚═╝╚═╝\n  //\n  // For reference, here is the actual behavior when testing w/ express over http:\n  // ```\n  // return res.send();       // 122b\n  // return res.send('');     // 200b (empty body, content length ==> 0)\n  // return res.send(0);      // XXXXXXXX WARNING (because deprecated usage-- it sees it as status code only)\n  // return res.send(null);   // 163b   (empty body, content length ==> 0)\n  // return res.send(false);  // 215b  (sends down the string `false`, content len ==> 5)\n  // return res.send(true);   // same as false basically\n  // return res.send(45);     // XXXXXXXX WARNING (because deprecated usage-- it sees it as status code only)\n  // return res.send(\"\");     // exactly like `''` above\n  //\n  // return res.json();       // 154b\n  // return res.json('');     // 212b (body is `\"\"`- content length ==> 2)\n  // return res.json(0);      // (content length ==> 1)\n  // return res.json(null);   // null - 214b   (empty body, content length ==> 4)\n  // return res.json('null'); // \"null\" -> 216b   (empty body, content length ==> 6)\n  // return res.json(false);  // 215b  (sends down the string `false`, content len ==> 5)\n  // return res.json(true);   // same as false basically\n  // return res.json(45);     // 212b (content length ==> 2)\n  // return res.json('45');   // \"45\" content len ==> 4\n  // return res.json(\"\");     // exactly like `''` above\n  // ```\n\n\n  describe('sending back a string', function (){\n    describe('using res.send()', function (){\n      it('should be the body', function (done) {\n        app.get('/res_sending_back_a_string/1', function (req, res) {\n          return res.send('foo');\n        });\n        app.request('GET /res_sending_back_a_string/1', {}, function (err, resp, data) {\n          try {\n            assert(!err, err);\n            assert.deepEqual(200, resp.statusCode);\n            assert.strictEqual(resp.body, 'foo');\n            assert.strictEqual(data, 'foo');\n          } catch (e) { return done(e); }\n          done();\n        });\n      });\n      it('should be the body, even if it is empty string', function (done) {\n        app.get('/res_sending_back_a_string/1/B', function (req, res) {\n          return res.send('');\n        });\n        app.request('GET /res_sending_back_a_string/1/B', {}, function (err, resp, data) {\n          try {\n            assert(!err, err);\n            assert.deepEqual(200, resp.statusCode);\n            assert.strictEqual(resp.body, undefined);\n            assert.strictEqual(data, undefined);\n          } catch (e) { return done(e); }\n          done();\n        });\n      });\n    });//</describe using res.send()>\n    describe('using res.json()', function (){\n      it('should be the body', function (done) {\n        app.get('/res_sending_back_a_string/2', function (req, res) {\n          return res.json('foo');\n        });\n        app.request('GET /res_sending_back_a_string/2', {}, function (err, resp, data) {\n          try {\n            assert(!err, err);\n            assert.deepEqual(200, resp.statusCode);\n            assert.strictEqual(resp.body, 'foo');\n            assert.strictEqual(data, 'foo');\n          } catch (e) { return done(e); }\n          done();\n        });\n      });\n      it('should be the body, even if empty string', function (done) {\n        app.get('/res_sending_back_a_string/2/B', function (req, res) {\n          return res.json('');\n        });\n        app.request('GET /res_sending_back_a_string/2/B', {}, function (err, resp, data) {\n          try {\n            assert(!err, err);\n            assert.deepEqual(200, resp.statusCode);\n            assert.strictEqual(resp.body, '');\n            assert.strictEqual(data, '');\n          } catch (e) { return done(e); }\n          done();\n        });\n      });\n\n      it('should stay wrapped in quotes if it was wrapped in quotes', function (done) {\n        app.get('/res_sending_back_a_string/3', function (req, res) {\n          return res.json('\"foo\"');\n        });\n        app.request('GET /res_sending_back_a_string/3', {}, function (err, resp, data) {\n          try {\n            assert(!err, err);\n            assert.deepEqual(200, resp.statusCode);\n            assert.strictEqual(resp.body, '\"foo\"');\n            assert.strictEqual(data, '\"foo\"');\n          } catch (e) { return done(e); }\n          done();\n        });\n      });\n\n      it('should stay wrapped in quotes if it was wrapped in quotes, even if it empty string wrapped in quotes', function (done) {\n        app.get('/res_sending_back_a_string/3/b', function (req, res) {\n          return res.json('\"\"');\n        });\n        app.request('GET /res_sending_back_a_string/3/b', {}, function (err, resp, data) {\n          try {\n            assert(!err, err);\n            assert.deepEqual(200, resp.statusCode);\n            assert.strictEqual(resp.body, '\"\"');\n            assert.strictEqual(data, '\"\"');\n          } catch (e) { return done(e); }\n          done();\n        });\n      });\n    });//</describe using res.json()>\n  });//</describe: sending back a string >\n\n\n  describe('sending back a number', function (){\n    describe('using res.send()', function (){\n      it('should be the body, and NOT interpreted as a status code', function (done) {\n        app.get('/res_sending_back_a_number/1', function (req, res) {\n          return res.send(45);\n        });\n        app.request('GET /res_sending_back_a_number/1', {}, function (err, resp, data) {\n          try {\n            assert(!err, err);\n            assert.deepEqual(200, resp.statusCode);\n            assert.strictEqual(resp.body, 45);\n            assert.strictEqual(data, 45);\n          } catch (e) { return done(e); }\n          done();\n        });\n      });\n      it('should be the body, and NOT interpreted as a status code, even when zero is used', function (done) {\n        app.get('/res_sending_back_a_number/1/B', function (req, res) {\n          return res.send(0);\n        });\n        app.request('GET /res_sending_back_a_number/1/B', {}, function (err, resp, data) {\n          try {\n            assert(!err, err);\n            assert.deepEqual(200, resp.statusCode);\n            assert.strictEqual(resp.body, 0);\n            assert.strictEqual(data, 0);\n          } catch (e) { return done(e); }\n          done();\n        });\n      });\n    });//</describe using res.send()>\n    describe('using res.json()', function (){\n      it('should be the body, and NOT interpreted as a status code', function (done) {\n        app.get('/res_sending_back_a_number/2', function (req, res) {\n          return res.json(45);\n        });\n        app.request('GET /res_sending_back_a_number/2', {}, function (err, resp, data) {\n          try {\n            assert(!err, err);\n            assert.deepEqual(200, resp.statusCode);\n            assert.strictEqual(resp.body, 45);\n            assert.strictEqual(data, 45);\n          } catch (e) { return done(e); }\n          done();\n        });\n      });\n\n      it('should be the body, and NOT interpreted as a status code, even when zero is used', function (done) {\n        app.get('/res_sending_back_a_number/2/B', function (req, res) {\n          return res.json(0);\n        });\n        app.request('GET /res_sending_back_a_number/2/B', {}, function (err, resp, data) {\n          try {\n            assert(!err, err);\n            assert.deepEqual(200, resp.statusCode);\n            assert.strictEqual(resp.body, 0);\n            assert.strictEqual(data, 0);\n          } catch (e) { return done(e); }\n          done();\n        });\n      });\n\n      it('should stay a string, if it was wrapped in quotes', function (done) {\n        app.get('/res_sending_back_a_number/3', function (req, res) {\n          return res.json('45');\n        });\n        app.request('GET /res_sending_back_a_number/3', {}, function (err, resp, data) {\n          try {\n            assert(!err, err);\n            assert.deepEqual(200, resp.statusCode);\n            assert.strictEqual(resp.body, '45');\n            assert.strictEqual(data, '45');\n          } catch (e) { return done(e); }\n          done();\n        });\n      });\n\n      it('should stay a string, if it was wrapped in quotes, even if it is zero', function (done) {\n        app.get('/res_sending_back_a_number/3/B', function (req, res) {\n          return res.json('0');\n        });\n        app.request('GET /res_sending_back_a_number/3/B', {}, function (err, resp, data) {\n          try {\n            assert(!err, err);\n            assert.deepEqual(200, resp.statusCode);\n            assert.strictEqual(resp.body, '0');\n            assert.strictEqual(data, '0');\n          } catch (e) { return done(e); }\n          done();\n        });\n      });\n    });//</describe using res.json()>\n  });//</describe: sending back a number >\n\n\n\n\n\n  describe('sending back `null`', function (){\n    describe('using res.send()', function (){\n      it('should be the body', function (done) {\n        app.get('/res_sending_back_the_null_literal/1', function (req, res) {\n          return res.send(null);\n        });\n        app.request('GET /res_sending_back_the_null_literal/1', {}, function (err, resp, data) {\n          try {\n            assert(!err, err);\n            assert.deepEqual(200, resp.statusCode);\n            assert.strictEqual(resp.body, null);\n            assert.strictEqual(data, null);\n          } catch (e) { return done(e); }\n          done();\n        });\n      });\n    });//</describe using res.send()>\n    describe('using res.json()', function (){\n      it('should be the body', function (done) {\n        app.get('/res_sending_back_the_null_literal/2', function (req, res) {\n          return res.json(null);\n        });\n        app.request('GET /res_sending_back_the_null_literal/2', {}, function (err, resp, data) {\n          try {\n            assert(!err, err);\n            assert.deepEqual(200, resp.statusCode);\n            assert.strictEqual(resp.body, null);\n            assert.strictEqual(data, null);\n          } catch (e) { return done(e); }\n          done();\n        });\n      });\n\n      it('should stay a string, if it was wrapped in quotes', function (done) {\n        app.get('/res_sending_back_the_null_literal/3', function (req, res) {\n          return res.json('null');\n        });\n        app.request('GET /res_sending_back_the_null_literal/3', {}, function (err, resp, data) {\n          try {\n            assert(!err, err);\n            assert.deepEqual(200, resp.statusCode);\n            assert.strictEqual(resp.body, 'null');\n            assert.strictEqual(data, 'null');\n          } catch (e) { return done(e); }\n          done();\n        });\n      });\n    });//</describe using res.json()>\n  });//</describe: sending back `null` >\n\n\n\n  describe('sending back a boolean', function (){\n    describe('using res.send()', function (){\n      describe('`true`', function (){\n        it('should be the body', function (done) {\n          app.get('/res_sending_back_a_boolean/1', function (req, res) {\n            return res.send(true);\n          });\n          app.request('GET /res_sending_back_a_boolean/1', {}, function (err, resp, data) {\n            try {\n              assert(!err, err);\n              assert.deepEqual(200, resp.statusCode);\n              assert.strictEqual(resp.body, true);\n              assert.strictEqual(data, true);\n            } catch (e) { return done(e); }\n            done();\n          });\n        });\n      });//</describe: `true`>\n      describe('`false`', function (){\n        it('should be the body', function (done) {\n          app.get('/res_sending_back_a_boolean/2', function (req, res) {\n            return res.send(false);\n          });\n          app.request('GET /res_sending_back_a_boolean/2', {}, function (err, resp, data) {\n            try {\n              assert(!err, err);\n              assert.deepEqual(200, resp.statusCode);\n              assert.strictEqual(resp.body, false);\n              assert.strictEqual(data, false);\n            } catch (e) { return done(e); }\n            done();\n          });\n        });\n      });//</describe: `false`>\n    });//</describe using res.send()>\n    describe('using res.json()', function (){\n      describe('`true`', function (){\n        it('should be the body', function (done) {\n          app.get('/res_sending_back_a_boolean/3', function (req, res) {\n            return res.json(true);\n          });\n          app.request('GET /res_sending_back_a_boolean/3', {}, function (err, resp, data) {\n            try {\n              assert(!err, err);\n              assert.deepEqual(200, resp.statusCode);\n              assert.strictEqual(resp.body, true);\n              assert.strictEqual(data, true);\n            } catch (e) { return done(e); }\n            done();\n          });\n        });\n\n        it('should stay a string, if it was wrapped in quotes', function (done) {\n          app.get('/res_sending_back_a_boolean/4', function (req, res) {\n            return res.json('true');\n          });\n          app.request('GET /res_sending_back_a_boolean/4', {}, function (err, resp, data) {\n            try {\n              assert(!err, err);\n              assert.deepEqual(200, resp.statusCode);\n              assert.strictEqual(resp.body, 'true');\n              assert.strictEqual(data, 'true');\n            } catch (e) { return done(e); }\n            done();\n          });\n        });\n      });//</describe: `true`>\n      describe('`false`', function (){\n        it('should be the body', function (done) {\n          app.get('/res_sending_back_a_boolean/5', function (req, res) {\n            return res.json(false);\n          });\n          app.request('GET /res_sending_back_a_boolean/5', {}, function (err, resp, data) {\n            try {\n              assert(!err, err);\n              assert.deepEqual(200, resp.statusCode);\n              assert.strictEqual(resp.body, false);\n              assert.strictEqual(data, false);\n            } catch (e) { return done(e); }\n            done();\n          });\n        });\n\n        it('should stay a string, if it was wrapped in quotes', function (done) {\n          app.get('/res_sending_back_a_boolean/6', function (req, res) {\n            return res.json('false');\n          });\n          app.request('GET /res_sending_back_a_boolean/6', {}, function (err, resp, data) {\n            try {\n              assert(!err, err);\n              assert.deepEqual(200, resp.statusCode);\n              assert.strictEqual(resp.body, 'false');\n              assert.strictEqual(data, 'false');\n            } catch (e) { return done(e); }\n            done();\n          });\n        });\n      });//</describe: `false`>\n\n    });//</describe using res.json()>\n  });//</describe: sending back a boolean >\n\n});//</describe: VR interpreter>\n"
  }
]