diff --git a/.gitignore b/.gitignore index b3fa805..c85e9a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,15 @@ -Properties -bin -obj +Properties/ +bin/ +obj/ *.suo *.user *.sublime* -node_modules \ No newline at end of file +node_modules/ +.vs +*.dat +.idea +*.tmp +coverage/ +typings/DefinitelyTyped/**/*.d.ts +.tscache +dist/test/ \ No newline at end of file diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..dce4b6a --- /dev/null +++ b/.jshintrc @@ -0,0 +1,22 @@ +{ + "bitwise": false, + "curly": false, + "eqeqeq": false, + "forin": false, + "freeze": true, + "immed": false, + "newcap": true, + "noarg": true, + "noempty": true, + "plusplus": false, + "undef": true, + "unused": true, + "maxcomplexity": 40, + "asi": false, + "boss": true, + "debug": false, + "eqnull": false, + "evil": false, + + "node": true +} \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..c2fc0aa --- /dev/null +++ b/.npmignore @@ -0,0 +1,20 @@ +.travis.yml +test/ +dist/test/ +example/ +doc/ +benchmarks/ +.settings/ +.git/ +.idea/ +.vs/ +.tscache/ +obj/ +bin/ +CHANGELOG +Gruntfile.js +*.njsproj +.jshintrc +.gitattributes +.gitignore +typings/DefinitelyTyped \ No newline at end of file diff --git a/.settings/launch.json b/.settings/launch.json new file mode 100644 index 0000000..4d7318c --- /dev/null +++ b/.settings/launch.json @@ -0,0 +1,31 @@ +{ + "version": "0.1.0", + "configurations": [ + { + "name": "Run Tests", + "type": "node", + "program": "node_modules/mocha/bin/mocha", + "stopOnEntry": false, + "args": ["--debug"], + "cwd": ".", + "runtimeExecutable": null, + "env": { } + }, + { + "name": "Run Benchmark", + "type": "node", + "program": "benchmarks/mongodb.js", + "stopOnEntry": true, + "args": ["--debug"], + "cwd": ".", + "runtimeExecutable": null, + "env": { } + }, + { + "name": "Attach", + "type": "node", + "address": "localhost", + "port": 5858 + } + ] +} diff --git a/.settings/tasks.json b/.settings/tasks.json new file mode 100644 index 0000000..e59acfd --- /dev/null +++ b/.settings/tasks.json @@ -0,0 +1,82 @@ +// Available variables which can be used inside of strings. +// ${workspaceRoot}: the root folder of the team +// ${file}: the current opened file +// ${fileBasename}: the current opened file's basename +// ${fileDirname}: the current opened file's dirname +// ${fileExtname}: the current opened file's extension +// ${cwd}: the current working directory of the spawned process +{ + "version": "0.1.0", + "command": "npm", + "showOutput": "silent", + + "windows": { + "command": "npm.cmd" + }, + + "tasks": [ + { + "taskName": "build", + "args": ["run"], + "isBuildCommand": true, + "problemMatcher": ["$tsc", { + "fileLocation": "relative", + "owner": "typescript", + "pattern": { + "regexp": "^([^(]+)\\((\\d+,\\d+)\\)\\: (error|warning) TS(\\d+): (.*)$", + "file": 1, + "location": 2, + "code": 3, + "severity": 4, + "message": 5 + } + }, { + "fileLocation": "relative", + "owner": "typescript", + "pattern": { + "regexp": "^(error|warning) TS(\\d+): File '([^']+)'.*$", + "file": 3, + "location": 2, + "code": 2, + "severity": 1, + "message": 3 + } + }] + }, + { + "taskName": "test", + "args": [], + "isTestCommand": true, + "problemMatcher": { + "owner": "mocha", + "pattern": { + "regexp": "^\\s*(\\d+)\\)\\s*(.+)\\:$", + "file": 1, + "message": 2 + } + } + }, + { + "taskName": "lint", + "args": ["run", "lint", "--", "${file}", "--exclude"], + "problemMatcher": "$jshint" + }, + { + "taskName": "install", + "args": [] + }, + { + "taskName": "update", + "args": [] + }, + { + "taskName": "benchmark", + "args": ["run"], + "showOutput": "always" + }, + { + "taskName": "publish", + "args": [] + } + ] +} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 8a6bd3e..55b5b04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,33 @@ language: node_js node_js: - - "0.8" - "0.10" - - "0.11" services: - mongodb + env: - - CI_SERVER=1 \ No newline at end of file + - CI_SERVER=1 + +addons: + code_climate: + repo_token: 9c90177b42d39905ca635b1f6226580dab5799f87f172b66bab4e8df77b67a13 + +before_install: + - sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 + - echo 'deb http://repo.mongodb.org/apt/ubuntu precise/mongodb-org/3.0 multiverse' | sudo tee /etc/apt/sources.list.d/mongodb-org-3.0.list + - echo 'deb http://repo.mongodb.org/apt/ubuntu precise/mongodb-org/3.1 multiverse' | sudo tee /etc/apt/sources.list.d/mongodb-org-3.1.list + - sudo apt-get update + - sudo apt-get install -y mongodb-org mongodb-org-server mongodb-org-shell mongodb-org-tools + +before_script: + - "until nc -z localhost 27017; do echo Waiting for MongoDB; sleep 1; done" + - "mongo --version" + - gulp build + +script: + - gulp ci + +after_script: + - gulp coverage + - coveralls < coverage/lcov.info + - codeclimate < coverage/lcov.info \ No newline at end of file diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..6ad3734 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,21 @@ +# v4.0.0 + - Removed automatic schema extensions + This was done for two reasons, firstly it simplifies the process of loading documents from the database a small amount. + The bigger reason is pure performance. Not only do we cut down on the number of properties which need to be defined + on new instances (if they aren't in the static schema) but by preventing modifications to the object structure we + prevent V8 from de-optimizing the code. + - Instance original object is no longer cloned + This should result in a small performance boost and shouldn't break any existing code (unless it was doing weird stuff + with the private API... you haven't been messing with the private API have you?) + - Removed support for the event emitter implementation + With the adoption of promises, its use has become less pronounced and its removal will further simplify the codebase + and help boost performance. + - All hook signatures have been changed + The first argument has become the value which was previously `this`. This should enable more explicit code and support + for TypeScript. + - Model options `preprocessors` renamed to `transforms` + Makes it a little bit more clear what these are to be used for + - Caching infrastructure updated + We've separated the cache implementations (get/set/clear) from the logic dictating how to cache something (can/key) + to make implementing the two parts easier. The cache implementation is now set on the core while the model provides + the cache controller dictating how caching is performed. \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..0c0b54f --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,137 @@ +var tsconfig = require('./tsconfig.json'); + +module.exports = function (grunt) { + "use strict"; + + grunt.initConfig({ + ts: { + options: tsconfig.compilerOptions, + + dev: { + src: tsconfig.files, + options: { + watch: 'lib', + fast: 'never' + } + }, + test: { + src: tsconfig.files, + options: { + sourceMap: true, + fast: 'never' + } + }, + release: { + src: ["index.ts", "lib/**/*.ts"], + options: { + sourceMap: false, + fast: 'never' + } + } + }, + mochacli: { + options: { + require: ["test/support/chai", "test/support/config"], + files: "test/*.js", + timeout: '10s' + }, + + default: { + + } + }, + mocha_istanbul: { + coverage: { + src: 'test', + root: 'lib', + options: { + mask: '*.js', + reportFormats: ['lcovonly', 'html'], + timeout: '10s', + require: ["test/support/chai", "test/support/config"], + check: { + lines: 75, + statements: 75 + } + } + }, + + coveralls: { + src: 'test', + root: 'lib', + options: { + mask: '*.js', + coverage: true, + reportFormats: ['lcovonly'], + timeout: '10s', + require: ["test/support/chai", "test/support/config"] + } + } + }, + + clean: { + definitions: ["*.d.ts", "!iridium.d.ts", "!_references.d.ts", "benchmarks/**/*.d.ts", "example/**/*.d.ts", "lib/**/*.d.ts", "test/**/*.d.ts"], + sourceMaps: ["*.map", "benchmarks/**/*.map", "example/**/*.map", "lib/**/*.map", "test/**/*.map"], + compiledFiles: ["*.js", "!Gruntfile.js", "benchmarks/**/*.js", "example/**/*.js", "lib/**/*.js", "test/**/*.js"], + coverage: ["coverage"] + }, + + _release: { + options: { + tagName: "v<%= version %>", + commitMessage: "v<%= version %>" + } + }, + + 'string-replace': { + packageDependencies: { + files: { "_references.d.ts": "_references.d.ts" }, + options: { + replacements: [{ + pattern: /(\/\/\/ )/gi, + replacement: '//$1' + }] + } + }, + developmentDependencies: { + files: { "_references.d.ts": "_references.d.ts" }, + options: { + replacements: [{ + pattern: /\/\/(\/\/\/ )/gi, + replacement: '$1' + }] + } + } + } + }); + + grunt.event.on('coverage', function (lcovcontent, done) { + require('coveralls').handleInput(lcovcontent, function (err) { + if (err) return done(err); + done(); + }); + }); + + grunt.loadNpmTasks("grunt-ts"); + grunt.loadNpmTasks("grunt-mocha-cli"); + grunt.loadNpmTasks("grunt-mocha-istanbul"); + grunt.loadNpmTasks("grunt-contrib-clean"); + grunt.loadNpmTasks("grunt-release"); + grunt.loadNpmTasks("grunt-string-replace"); + + grunt.renameTask('release', '_release'); + + grunt.registerTask("default", ["clean", "ts:dev"]); + grunt.registerTask("test", ["clean", "ts:test", "mochacli"]); + grunt.registerTask("coverage", ["clean", "ts:test", "mocha_istanbul:coverage"]); + grunt.registerTask("coveralls", ["clean", "ts:test", "mocha_istanbul:coveralls"]); + grunt.registerTask("build", ["clean", "ts:release"]); + grunt.registerTask("build:package", ["clean", "ts:release", "string-replace:packageDependencies"]); + grunt.registerTask("clean:package", ["string-replace:developmentDependencies"]); + + grunt.registerTask("release", ["build:package", "_release", "clean:package"]); + grunt.registerTask("release:prerelease", ["build:package", "_release:prerelease", "clean:package"]); + grunt.registerTask("release:patch", ["build:package", "_release:patch", "clean:package"]); + grunt.registerTask("release:minor", ["build:package", "_release:minor", "clean:package"]); + grunt.registerTask("release:major", ["build:package", "_release:major", "clean:package"]); +}; \ No newline at end of file diff --git a/Iridium.njsproj b/Iridium.njsproj index 23b2921..e8711c9 100644 --- a/Iridium.njsproj +++ b/Iridium.njsproj @@ -3,64 +3,148 @@ Debug 2.0 - {a2511e8c-33be-4b8d-8203-70d403aa446a} + {e9a19872-d84f-418a-8332-3ded543fb1fa} ShowAllFiles - index.js + node_modules\mocha\bin\mocha . . {3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{349c5851-65df-11da-9384-00065b846f21};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD} + true + CommonJS + true 11.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + C:\Program Files\iojs\iojs.exe + False + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + Mocha + + + Code + Mocha + + + Code + Mocha + + + Code + Mocha + + + Mocha + + + Code + Mocha + + + Mocha + + + Code + Mocha + + + Mocha + + + + Mocha + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + False + + diff --git a/Iridium.sln b/Iridium.sln index 9bd8174..3e496f8 100644 --- a/Iridium.sln +++ b/Iridium.sln @@ -1,9 +1,9 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.21005.1 +# Visual Studio 14 +VisualStudioVersion = 14.0.22609.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "Iridium", "Iridium.njsproj", "{A2511E8C-33BE-4B8D-8203-70D403AA446A}" +Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "Iridium", "Iridium.njsproj", "{E9A19872-D84F-418A-8332-3DED543FB1FA}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -11,10 +11,10 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A2511E8C-33BE-4B8D-8203-70D403AA446A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A2511E8C-33BE-4B8D-8203-70D403AA446A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A2511E8C-33BE-4B8D-8203-70D403AA446A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A2511E8C-33BE-4B8D-8203-70D403AA446A}.Release|Any CPU.Build.0 = Release|Any CPU + {E9A19872-D84F-418A-8332-3DED543FB1FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9A19872-D84F-418A-8332-3DED543FB1FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9A19872-D84F-418A-8332-3DED543FB1FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9A19872-D84F-418A-8332-3DED543FB1FA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/README.md b/README.md index d6786d4..79195e2 100644 --- a/README.md +++ b/README.md @@ -1,489 +1,268 @@ -# Iridium [![Build Status](https://travis-ci.org/SierraSoftworks/Iridium.png?branch=master)](https://travis-ci.org/SierraSoftworks/Iridium) [![](https://badge.fury.io/js/iridium.png)](https://npmjs.org/package/iridium) -**A bare metal ORM for MongoDB** +# Iridium +**A High Performance, IDE Friendly ODM for MongoDB** - +[![NPM Module](https://badge.fury.io/js/iridium.png)](https://npmjs.org/package/iridium) +[![Build Status](https://travis-ci.org/SierraSoftworks/Iridium.png?branch=master)](https://travis-ci.org/SierraSoftworks/Iridium) +[![Coverage Status](https://coveralls.io/repos/SierraSoftworks/Iridium/badge.svg?branch=master)](https://coveralls.io/r/SierraSoftworks/Iridium?branch=typescript) +[![Code Climate](https://codeclimate.com/github/SierraSoftworks/Iridium/badges/gpa.svg)](https://codeclimate.com/github/SierraSoftworks/Iridium) +[![Test Coverage](https://codeclimate.com/github/SierraSoftworks/Iridium/badges/coverage.svg)](https://codeclimate.com/github/SierraSoftworks/Iridium) -Iridium was designed to alleviate many of the issues often present in modern ORMs, especially those designed for NoSQL datastores like MongoDB. Namely, these include a high level of bloat and an excessive amount of documentation - vastly raising the barrier to entry. On the flip side of the coin, they also tend to abstract core database functionality away from the developer to the extent that they end up jumping through unnecessary hoops just to get the results they're looking for. -Iridium hopes to solve these issues by providing a bare bones ORM targeted at power users, and those looking for an exceptionally low overhead. It provides much of the indispensable functionality found in ORMs without the fluff. +Iridium is designed to offer a high performance, easy to use and above all, editor friendly ODM for MongoDB on Node.js. +Rather than adopting the "re-implement everything" approach often favoured by ODMs like Mongoose and friends, requiring +you to learn an entirely new API and locking you into a specific coding style, Iridium tries to offer an incredibly +lightweight implementation which makes your life easier where it counts and gets out of your way when you want to do +anything more complex. -## Features - - **Crazy Lightweight** - We've build Iridium from the ground up to minimize its memory footprint and performance overhead, we're fairly confident that you'll be hard pressed to find an ORM as fast with the same number of features. Don't believe us? Take a look at the Benchmarks section where we pit it against the *Native MongoDB Driver!*. - - **Flexible Schema Validation** - MongoDB's greatest strength is its ability to support dynamic schemas, we think that's a great idea but sometimes it's necessary to be able to validate aspects of your models. That's where Iridium's validation framework comes in - with an intuitive schema design framework with support for optional and dynamic fields, you'll never find yourself stuck again. - - **Powerful Transforms** - Anyone familiar with MongoDB knows the headaches that ObjectID causes due to its custom datatype. We also know that sometimes custom datatypes are unavoidable, or preferable for storage - though not necessarily ideal for processing. Iridium allows you to define a set of up-down transforms which are applied to parts of your model so that your code doesn't need to worry about these inconsistencies, and you can get down to writing the code you want to. - - **Express Support** - Everyone who has written code using Node.js knows about Express, to help make your life easier we've included support right out of the box for Express. - - **Powerful Models** - Iridium's models are designed to exist as individual files or modules within your application, this helps simplify management of your models and separates database design code from your application code. In addition to this, Iridium supports virtual properties, extension methods, transforms, client side property renaming and validations in an easy to understand and implement package. - - **Caching Support** - High performance web applications depend on accessing your data as quickly as possible, Iridium provides support for automated inline caching through any key-value store, allowing you to ensure that you can build the fastest application possible. - - **Plugin Framework** - Iridium allows the creation and use of plugins which can extend models and reduce duplicated code across models for common behavioural use cases. Plugins can provide custom validation, manipulate models at creation time and have the opportunity to extend instances when they are created. - - **Automatic Query Generation** - We understand that sometimes you don't want to structure your own queries - it's a hassle which you could do without especially when working with arrays. Thankfully, Iridium includes a powerful differential query generator which automatically generates the query necessary to store your changes without you raising a finger. - - **[A+ Promises](https://github.com/petkaantonov/bluebird) Built In** - We know how horrible it is having to manually wrap your favourite libraries before you can use them with promises, so we've decided to include support for the incredibly fast [Bluebird](https://github.com/petkaantonov/bluebird) promises library out of the box! (Iridium actually uses it internally as the primary handler, delegating back to callbacks for compatibility, don't tell anybody.) - -## Installation -Iridium is available using *npm*, which means you can install it locally using `npm install iridium` or add it to your project's *package.json* file to have it installed automatically whenever you run `npm install`. - -We make use of the [Semantic Versioning](http://semver.org/) guidelines for our versioning system, as such we highly recommend you stick to a single major version of Iridium when developing an application. This can easily be handled through your *package.json* file by doing the following. +It also means that, if you're familiar with the MongoDB CLI you should find working with Iridium very natural, with all database +methods returning promises for their results and sensible, type annotated results being provided if you wish to make use of them. -```javascript -{ - // ... - "dependencies": { - "iridium": "4.x" - } -} -``` - -## Example -```javascript -var iridium = require('iridium'); - -var database = new iridium({ - database: 'demo' -}); - -database.register('User', new iridium.Model(database, 'user', { - firstname: String, - lastname: String, - since: Date, - clown: Boolean, - houses: [{ - address: String, - colour: /Red|White|Blue|Green|Pink/ - }] -})); - -database.connect(function(err, db) { - if(err) throw err; - - // at this point database == db - - db.User.create({ - firstname: 'Billy', - lastname: 'Bob', - since: new Date(), - clown: true, - houses: [ - { address: 'The middle of nowhere', colour: 'Red' } - ] - }, function(err, user) { - if(err) throw err; - - console.log(JSON.stringify(user)); - }); -}); -``` - -## Benchmarks -Since Iridium claims to be ultra-lightweight, we thought we'd share some benchmarks with you - keep in mind that in the interest of fairness we are doing our best to compare apples with apples here, so when benchmarking against the MongoDB native driver we are running Iridium queries with `{ wrap: false }`, which disables wrapping of documents in `Instance` objects - though they still pass through the validation and preprocessing frameworks, and trigger hooks. - -Keep in mind that benchmarks only tell half of the story, performance will vary depending on how your application is configured, the system you run it on (these benchmarks were conducted on an ANCIENT laptop that was lying around) and a number of other factors. - -### Iridium vs. MongoDB Driver -As you can see, with `wrap: false`, Iridium is within a few milliseconds of the native MongoDB driver in many queries, despite adding an additional layer of security (validation) and ease of use (preprocessing). You can view the source code for this benchmark under *benchmarks/mongodb.js*. - -``` -> MongoDB 10000 Inserts { w: 1 } -> => 652ms -> Iridium 10000 Inserts { w: 1, wrap: false } -> => 725ms -> MongoDB find() -> => 230ms -> Iridium find() { wrap: false } -> => 311ms -> MongoDB remove() -> => 216ms -> Iridium remove() -> => 213ms -``` - -## Core -The Iridium core (that sounds WAY cooler than I intended when I came up with the name) is where you create a database connection and register any models to be used by the database. Registration of models is optional, however it makes accessing them easier. +## Features + - **Built with TypeScript** and designed for ease of use, you'll be hard pressed to find another Node.js ORM as easy to pick + up and learn when combined with a supporting editor. + - **Promises Throughout** mean that you can easily start working with your favourite A+ compliant promise library and writing + more readable code. Don't think promises are the way forward? Stick with callbacks, Iridium supports both. + - **Fully Tested** with over 300 unit tests and over 95% test coverage, helping to ensure that Iridium always behaves the way + it's supposed to. + - **Decorator Support** helps describe your classes in an easy to understand and maintain manner, embrace ES7 with strong fallbacks + on ES5 compliant approaches. + - **Fall into Success** with Iridium's carefully designed API - which is structured to help ensure your code remains maintainable + regardless of how large your project becomes. + +## Requirements +Iridium is built on top of a number of very modern technologies, including TypeScript 1.5, JavaScript ES6 (though we do compile +to ES5 for compatibility with existing Node.js implementations) and the latest MongoDB Node.js Driver (version 2.0). + +For starters, you will need to be running MongoDB 2.6 or later in order to use Iridium - however we recommend you use MongoDB 3.0 +due to the various performance improvements they've made. If you're working with TypeScript, you will also need to use the 1.5 +compiler or risk having the Iridium type definitions break your project. + +## Using Iridium +Rather than opt of the usual "Look how quickly you can do something" approach, we thought it might be useful to see +an example which covers most of the stuff you'd need to do in Iridium. This example covers defining your own document +interfaces, a custom schema and instance type which provides some additional methods. + +You'll notice that the `House` class extends Iridium's `Instance` class, which gives it methods like `save()` as well +as change tracking when calling `save()` on the instance. If you'd prefer a lighter approach or to use your own home-grown +implementation then you can do so by taking a look at the [Custom Instances](#custom-instances) section. -When using Iridium, you are required to instantiate a core with a settings object which describes the database server you want to connect to. This is done by calling the core's constructor and passing an object similar to the following. +```typescript +/// +import {Core, Model, Instance, Collection, Index, Property, ObjectID} Iridium from 'iridium'; -```javascript -{ - host: 'localhost', // Optional - port: 27018, // Optional - username: '', // Optional - password: '', // Optional - database: 'iridium' +interface Colour { + r: number; + g: number; + b: number; } -``` - -Alternatively, you can pass in a standard MongoDB URI like `mongodb://username:password@localhost:27018/iridium` in place of the configuration object, allowing Iridium to easily be used with MongoS. - -Once you've got a core, you need to connect it to the database. This is done by calling the core's *connect* method and giving it a callback function. - - -### Registering Models -Models can be registered with the Iridium core to provide quick access from anywhere with access to the database instance. It has the added benefit of enabling IntelliSense for these models when accessed from the database object. - -You are required to give each model a name by which they may be accessed, convention states that these names should begin with a capital letter to indicate that they are constructors. - -```javascript -db.register('MyModel', require('./models/MyModel.js')); -db.MyModel.get(function(err, instance) { - -}); -``` - -You can also chain `register` calls to quickly load all your different models using a single line of code. - -```javascript -db.register('Model1', require('./models/Model1.js')).register('Model2', require('./models/Model2.js')).connect(function(err, db) { - if(err) throw err; -}); -``` - -## Database API -We've tried to keep the Iridium API as similar to the original MongoDB Native Driver's API as possible, while still keeping it extremely easy to use. The following are a bunch of TypeScript definitions showing the different overloads available to you for Iridium's methods. -If you don't understand TypeScript then it's easiest to think of it this way. You can use any method available from the MongoDB Native Driver using (usually) the same arguments, except the callbacks are entirely optional and a promise is always returned. On methods which select items, you can simply provide the `_id` field's value to have it automatically converted and wrapped, so doing something like `users.find('spartan563')` is entirely fine. - -```typescript -interface FindFunction { - (): promise; - (callback: function): promise; - (identifier: any): promise; - (identifier: any, callback: function): promise; - (identifier: any, options: object, callback: function): promise; - (conditions: object): promise; - (conditions: object, callback: function): promise; - (conditions: object, options: object, callback: function): promise; +interface Car { + make: string; + model: string; + colour: Colour; } -interface InsertFunction { - (document: object): promise; - (document: object, callback: function): promise; - (document: object, options: object): promise; - (document: object, options: object, callback: function): promise; - (documents: [object]): promise; - (documents: [object], callback: function): promise; - (documents: [object], options: object): promise; - (documents: [object], options: object, callback: function): promise; +interface HouseDocument { + _id?: string; + name: string; + + cars?: Car[]; } -interface UpdateFunction { - (conditions: object, changes: object): promise; - (conditions: object, changes: object, callback: function): promise; - (conditions: object, changes: object, options: object): promise; - (conditions: object, changes: object, options: object, callback: function): promise; +@Index({ name: 1 }) +@Collection('houses') +class House extends Instance implements HouseDocument { + @ObjectID _id: string; + @Property(/^.+$/) + name: string; + + @Property([{ + make: String, + model: String, + colour: { + r: Number, + g: Number, + b: Number + } + }]) + cars: Car[]; + + static onCreating(doc: HouseDocument) { + doc.cars = doc.cars || []; + } + + addCar(make: string, model: string, colour: Colour) { + this.cars.push({ + make: make, + model: model, + colour: colour + }); + } + + get numberOfCars() { + return this.cars.length; + } } -interface EnsureIndexFunction { - (index: object): promise; - (index: object, options: object): promise; - (index: object, callback: function): promise; - (index: object, options: object, callback: function): promise; +class MyDatabase extends Core { + Houses = new Model(this, House); } -var find: FindFunction; -var findOne: FindFunction; -var get: FindFunction = findOne; - -var insert: InsertFunction; -var create: InsertFunction = insert; -var update: UpdateFunction; - -var count: FindFunction; -var remove: FindFunction; - -var aggregate: function; - -var ensureIndex: EnsureIndexFunction; +var myDb = new MyDatabase({ database: 'houses_test' }); + +myDb.connect().then(() => myDb.Houses.insert({ + name: 'My House', + cars: [{ + make: 'Audi', + model: 'A4', + colour: { r: 0, g: 0, b: 0 } + }] + })) + .then(() => myDb.Houses.get()) + .then((house) => { + house.addCar('Audi', 'S4', { r: 255, g: 255, b: 255 }); + return house.save(); + }) + .then(() => myDb.close()); ``` -## Models -Iridium has been designed to make it as easy as possible to create and manage your models. To support this, models are designed to be stored within their own files - separating them from one another and keeping things logical. - -Each model file should export a function which accepts a reference to an Iridium Core instance, and returns the result of a model construction call. +## Defining a Model +Iridium models are created with a reference to their Core (which provides the database connection) and an `InstanceType` which +is composed of a constructor function as well as a number of static properties providing configuration information for the instance. +**JavaScript** ```javascript -var Model = require('iridium').Model; -module.exports = function(db) { - var schema = { - name: String, - email: /.+@.+\.\w+/ - }; - - var options = { - methods: { +new Model(core, InstanceType); +``` - }, - virtuals: { +**TypeScript** +```typescript +new Model(core, InstanceType); +``` - }, - preprocessors: [], - hooks: { +If you're working with TypeScript, you can provide an interface for the document structure used by the database, which will allow you +to get useful type hints when you are creating documents. You can also provide the `InstanceType` to provide useful type information +for any instances which are retrieved from the database. This information is carried through all promises and callbacks you will use +within Iridium and it makes your life significantly easier. - } - }; +Typically you will expose your models as variables on a custom Core implementation like this. - return new Model(db, 'collectionName', schema, options); -}; +```typescript +class MyCore extends Core { + MyModel = new Model(this, MyInstanceType); +} ``` -### Validation -Iridium now (as of **v2.11.0**) uses [skmatc](https://sierrasoftworks.com/skmatc)(pronounced schematic) for schema validation, it allows you to quickly and easily write complex schemas in a readable manner and provides a powerful extensibility framework which you can use if you require more complex validation logic. +## The InstanceType Constructor +The InstanceType constructor is responsible for creating objects which represent a document retrieved from the database. It also provides +a number of configuration details which are used to determine how Iridium works with the model. -```javascript -var schema = { - username: /\w[\w\d_]{7,}/, - email: String, - dateOfBirth: Date, - - sessions: [{ - id: String, - started: Date, - valid: Boolean - }] -}; -``` +There are two approaches to defining an instance constructor - the first is to create a true wrapper like the one provided by `Iridium.Instance` +which offers helper methods like `save()` and `remove()`, which comes in very handy for writing concise descriptive code, while the other approach +is to simply return the document received from the database - great for performance or security purposes. -### Methods -Methods allow you to provide instance specific functionality in the form of callable methods on a model's instances. These methods have access to the instance, and allow you to call them directly as 1st class members. +**TypeScript** +```typescript +interface Document { + _id?: string; +} -```javascript -var options = { - methods: { - checkPassword: function(password) { - return hash(password) == this.passwordHash; - } - } -}; +class InstanceType { + constructor(model: Model, document: Document, isNew: boolean = true, isPartial: boolean = false) { + + } + + _id: string; + + static schema: Iridium.Schema = { + _id: false + }; + + static collection = 'myCollection'; +} ``` -### Virtuals -Virtuals work similarly to methods, however they represent Getter/Setter properties which also behave as first class members. The idea is that they allow you to access information which can be gathered from an instance but which you do not necessarilly wish to store in the database. - +**JavaScript** ```javascript -var options = { - virtuals: { - forAPI: function() { - return { - id: this.id, - name: this.name - }; - }, - password: { - get: function() { return this.passwordHash; }, - set: function(value) { this.passwordHash = hash(value); } - } - } -}; -``` - -As you can see, virtuals can either be pure getters (in which case they should not make any changes to the instance) or Getter/Setter pairs, allowing instance values to be modified. - -If you have no need for accessing the original database value - and it can be converted between forms losslessly, it may be preferable to make use of the preprocessor framework, as it will remove the overhead of calling a conversion function on each property access. - -### Hooks -Hooks allow you to implement custom behaviour on certain events, one of the most common being the creation of a new instance. A good example of their use is the creation of a new user, where you usually receive their requested password directly. By using a hook, you can automatically convert their password into a hashed form upon creation, and save yourself the headache. +module.exports = function(model, document, isNew, isPartial) { + +} -```javascript -var options = { - hooks: { - creating: function() { - if(this.password) { - this.passwordHash = hash(this.password); - delete this.password; - } - } - } +module.exports.collection = 'myCollection'; +module.exports.schema = { + _id: false }; ``` -Other uses include the creation of default properties on models, using the *creating* hook to set the defaults if they are not present. - -Keep in mind that all hooks support a *done* callback if you wish to perform any kind of asynchronous operation before completing. This allows web requests, file system operations or even other database operations to be performed safely from within the hook. - -#### Available Hooks - - **creating([done])** - Called before an object is first stored in the database, `this` is set to the contents of the object being stored - allowing modification of the object prior to its insertion into the database. - - **saving(changes[, done])** - Called before an exisiting object is updated in the database (not called for `Model.update()`) with `this` set to the instance of the object being updated and the first argument always being the object changeset (MongoDB update syntax), allowing you to perform custom updates each time an object is saved. - - **retrieved([done])** - Called after an object has been retrieved from the database, but before it is wrapped into an Instance. The hook's `this` is set to the database document received - and will not have undergone any preprocessing yet. - - **ready([done])** - Called after an object has undergone preprocessing and has been wrapped into an Instance, allowing you to set non-persistent properties on the object (for example, retrieval time for a cache). +### Configuration Options +As we mentioned, configuration of a model is conducted through static properties on its constructor. These configuration options include +the `schema` which is used to validate that all data inserted into the database through Iridium meets certain conditions, the `collection` +which specifies the name of the MongoDB collection into which the documents are stashed and a couple of others worth noting. -### Events -Are you a fan of using EventEmitters? Are you too cool for school? No worries, we've got you covered. You can consume events on both models and instances using Node.js' standard event consumption functions, they behave much the same as their hook cousins with the exception that there is no way to safely manipulate the object (their call order is non-deterministic). - -```javascript -// Called for all validation, hook and database errors -model.on('error', function error(err) { }); -// Called when an object is being created in the database -model.on('creating', function creating(document) { }); -// Called when an object is being saved to the database -model.on('saving', function saving(instance, changes) { }); -// Called when an object is retrieved from the database/cache -model.on('retrieved', function retrieved(instance) { }); -// Called when a new instance becomes ready -model.on('ready', function ready(instance) { }); - -instance.on('error', function error(err) { }); -instance.on('creating', function creating(document) { }); -instance.on('saving', function saving(instance, changes) { }); -instance.on('retrieved', function retrieved(instance) { }); -instance.on('ready', function ready(instance) { }); -``` +#### Schema +Iridium uses [Skmatc](https://github.com/SierraSoftworks/Skmatc) for schema validation, you can read more about it on its project page but +we'll give a quick rundown of the way you make use of it here. -## Instances -An instance represents a database object retrieved by Iridium, and will inherit behaviour from the model it was created to represent. In addition to this, an instance has access to a few functions for performing operations which pertain directly to that instance, including the following. +The model's schema is defined using an object in which keys represent their document property counterparts while the values represent a validation +rule. -```javascript -// Saves any changes made to the instance (only affects properties in the schema, or already retrieved from the DB) -Instance.save().then(function complete(instance) { }, function error(err) { }); -Instance.save(function callback(err, instance) {}); - -// Executes the requested MongoDB changes on the current instance ({ $push: { sessions: 'session_key' }} etc.) -Instance.save(mongoChanges).then(function complete(instance) { }, function error(err) { }); -Instance.save(mongoChanges, function callback(err, instance) {}); - -// Used for manipulating specific array elements, you can use extraConditions to select the array element to manipulate -// For example Instance.save({ array: { $elemMatch: { id: 1 }}}, { $inc: { 'array.$.hits': 1 }}); -Instance.save(extraConditions, mongoChanges).then(function complete(instance) { }, function error(err) { }); -Instance.save(extraConditions, mongoChanges, function callback(err,instance) {}) - -// Updates the instance's data to match the latest available data from the database -Instance.update().then(function complete(instance) { }, function error(err) { }); -Instance.update(function callback(err, instance) {}); - -// Removes the instance from the database -Instance.remove().then(function complete() { }, function error(err) { }); -Instance.remove(function callback(err) {}); +**TypeScript** +```typescript +class InstanceType { + _id: string; + email: string; + + static schema: Iridium.Schema = { + _id: false, + email: /^.+@.+$/ + }; +} ``` -### Helpers -The Instance object has a number of helper properties and functions available which prove useful in common use cases. These include the `document` virtual property which returns the underlying document which the Instance represents - allowing you to easily perform JSON serialization. Keep in mind that this document will have passed through the preprocessing framework. This property will be transparently overridden by Instances who's schema defines a `document` property. - -You will also find the `select` and `first` methods which can be used to filter an array or map for items which match certain criteria. They are based on [lodash](http://lodash.org)'s _.filter and _.first methods and by default will bind `this` to the Instance on which they are called. - +**JavaScript** ```javascript -console.log(JSON.stringify(Instance.document)); - -var session = Instance.first(Instance.sessions, function test(session) { - return session.id == 'abcdef...'; -}); +function InstanceType() {} -var comments = Instance.select(Instance.comments, function test(comment) { - return comment.by = 'username'; -}); +InstanceType.schema = { + _id: false, + email: /^.+@.+$/ +}; ``` -### Differential Queries -In **v2.9.4** we added a powerful new differential query generator (codename Omnom) which allows you to easily make changes to your instances in code, and have Iridium handle the task of converting those changes to the most efficient query possible when you want to store those changes in MongoDB. - -Omnom allows you to do many things without needing to worry about how they are persisted to the database. These are some of the things that Omnom is well suited to handling. - - - Change properties or their children - - Change the value of an array's element or its children - - Remove elements from an array - - Add elements to the end of an array - - Selectively replacing an array's elements - -Unfortunately, there are a few limitations imposed by the way MongoDB handles queries - so when working with Iridium and Omnom we recommend you try to avoid doing the following. - - - Removing elements from an array while adding/changing others (will result in the array being replaced) - - Inserting elements at the front of an array (consider reversing the array using a Concoction if you want a stack implementation that is fast) - -## Caching Framework -Our caching framework allows basic queries to be served against a high performance cache, offloading requests from your database server and allowing you to more easily develop high performance applications that scale well to user demand. -Your cache will **only** be tried for `Model.get` and `Model.findOne` requests for which the cache's `valid()` function returns true, allowing you to implement any basic cache structure you wish - including compound caches should you wish. +### The Iridium Instance Class +Instead of implementing your own instance constructor every time, Iridium offers a powerful and tested instance base class which provides +a number of useful helper methods and a diff algorithm allowing you to make changes in a POCO manner. -By default Iridium doesn't cache anything, implementing a no-op cache, but you can easily configure your own caching plugin on a per-model basis by following this example. +To use it, simply inherit from it (if you need any computed properties or custom methods) or provide it as your instance type when instantiating +the model. -```javascript -function MemoryCache() { - this.cache = {}; +**TypeScript** +```typescript +class InstanceType extends Iridium.Instance { + _id: string; } -// Tells Iridium whether it can use the cache for objects that match these conditions -MemoryCache.prototype.valid = function valid(conditions) { - return conditions && conditions._id; -}; - -MemoryCache.prototype.store = function store(conditions, document, callback) { - // Conditions are null when storing on an insert, otherwise they represent the conditions - // that resulted in the object being retrieved from the database. - var id = JSON.stringify(document._id); - this.cache[id] = document; - callback(); -}; - -MemoryCache.prototype.fetch = function fetch(id, callback) { - var id = JSON.stringify(conditions._id); - callback(this.cache[id]); -}; - -MemoryCache.prototype.drop = function drop(conditions, callback) { - var id = JSON.stringify(conditions._id); - if(this.cache[id]) delete this.cache[id]; - callback(); -}; +new Iridium.Model(core, InstanceType); ``` -## Preprocessing Framework -The preprocessing framework allows Iridium to convert values from a form better suited to your database into a form more suitable for your application in an entirely transparent manner. This is acomplished through the use of a number of preprocessors which run when retrieving an object from the database, their order is reversed when pushing an object to the database. - -It has been moved into its own module, Concoction, which can be used outside of Iridium - and allows you to easily extend or replace it if you wish. For more information on Concoction, visit its [project page](https://github.com/SierraSoftworks/Concoction). - -### Conversions -The transforms framework provides a low-level means to convert from one value type to another by means of up/down conversion functions. The up functions are used to convert the value before it is sent UPstream to the database, while down functions are used to convert the database value into the DOWNstream value used by your application. - +**JavaScript** ```javascript -var Concoction = require('concoction'); -var model = new Model(db, 'modelName', modelSchema, { - preprocessors: [new Concoction.Convert({ - property: { - apply: function apply(value) { return convertedValueForDatabase; }, - reverse: function reverse(value) { return convertedValueFromDatabase } - } - })] - }); -``` +function InstanceType() { + Iridium.Instance.apply(this, arguments); +} -### Renames -The renames framework allows you to access properties in a manner better suited to your application while keeping the same schema on the database side. This comes in handy when using the *_id* field for fields such as a user's username. +require('util').inherits(InstanceType, Iridium.Instance); -```javascript -var Concoction = require('concoction'); -var model = new Model(db, 'modelName', modelSchema, { - preprocessors: [new Concoction.Rename({ - _id: 'id' - })] - }); +new Iridium.Model(core, InstanceType); ``` -## Plugins -Iridium allows you to use plugins which extend the functionality provided by a number of Iridium's components. These plugins can add extra functions for models and instances as well has allowing hooks to be added automatically. Plugins are imported using the `db.register(plugin)` method overload (similar to the way models are loaded), and are declared using the following layout. +If you've used the `Iridium.Instance` constructor then you'll have a couple of useful helper methods available to you. These include `save()`, `refresh()`, +`update()`, `remove()` and `delete()` which do more or less what it says on the can - `refresh` and `update` are synonyms for one another as are `remove` and +`delete`. -```javascript -module.exports = { - newModel: function newModel(db, collection, schema, options) { - this.collection = collection.toLowerCase(); - this.schema._id = String, - this.options.preprocessors = []; - }, - newInstance: function newInstance(model, document, isNew) { - Object.defineProperty(this, 'id', { - get: function() { return document._id; }, - enumerable: false - }); - } -}; -``` +You'll also find `first()` and `select()` which allow you to select the first, or all, entr(y|ies) in a collection which match a predicate - ensuring that `this` +maps to the instance itself within the predicate - helping to make comparisons somewhat easier within JavaScript ES5. -## Thanks To -I'd also like to thank [dresende](https://github.com/dresende) and [dxg](https://github.com/dxg) from the [node-orm2](https://github.com/dresende/node-orm2) project for getting me introduced to Node and giving me many of the ideas for how a good ORM should be structured. If you're looking for an easy to use and more fully featured ORM with support for SQL and NoSQL databases, I'd seriously suggest giving [node-orm2](https://github.com/dresende/node-orm2) a try. +### Custom Instances +If you decide to implement your own instance constructor then this is the part you'll be interested in. \ No newline at end of file diff --git a/README.old.md b/README.old.md new file mode 100644 index 0000000..8b30d76 --- /dev/null +++ b/README.old.md @@ -0,0 +1,497 @@ +# Iridium [![Build Status](https://travis-ci.org/SierraSoftworks/Iridium.png?branch=master)](https://travis-ci.org/SierraSoftworks/Iridium) [![Coverage Status](https://coveralls.io/repos/SierraSoftworks/Iridium/badge.svg?branch=master)](https://coveralls.io/r/SierraSoftworks/Iridium?branch=typescript) [![](https://badge.fury.io/js/iridium.png)](https://npmjs.org/package/iridium) [![Code Climate](https://codeclimate.com/github/SierraSoftworks/Iridium/badges/gpa.svg)](https://codeclimate.com/github/SierraSoftworks/Iridium) [![Test Coverage](https://codeclimate.com/github/SierraSoftworks/Iridium/badges/coverage.svg)](https://codeclimate.com/github/SierraSoftworks/Iridium) +**A bare metal ODM for MongoDB** + +## Version 5 Alpha +The v5.x implementation of Iridium is not yet finished. While the API is relatively final there are still major architectural changes being made on a daily basis as we prepare v5 for stable release. +Until then, we recommend you stick with Iridium v4.x in your applications, the documentation below covers the v4.x API and will be updated to match the v5.x API when we are ready for release. + +## Introduction +Iridium isn't your traditional JavaScript ORM, it's the ORM I've always wanted and I hope you'll enjoy using it as much +as I do. With Iridium, your models are simply JavaScript classes (if you're using ES6 or TypeScript) which means that +you don't need to learn a new DSL just to define your database structure and that your favourite editor will be able +to understand your code and provide useful suggestions. + +Iridium was designed to alleviate many of the issues often present in modern ORMs, especially those designed for NoSQL datastores like MongoDB. Namely, these include a high level of bloat and an excessive amount of documentation - vastly raising the barrier to entry. On the flip side of the coin, they also tend to abstract core database functionality away from the developer to the extent that they end up jumping through unnecessary hoops just to get the results they're looking for. + +Iridium hopes to solve these issues by providing a bare bones ORM targeted at power users, and those looking for an exceptionally low overhead. It provides much of the indispensable functionality found in ORMs without the fluff. + +## Features + - **Crazy Lightweight** + We've build Iridium from the ground up to minimize its memory footprint and performance overhead, we're fairly confident that you'll be hard pressed to find an ORM as fast with the same number of features. Don't believe us? Take a look at the Benchmarks section where we pit it against the *Native MongoDB Driver!*. + - **Flexible Schema Validation** + MongoDB's greatest strength is its ability to support dynamic schemas, we think that's a great idea but sometimes it's necessary to be able to validate aspects of your models. That's where Iridium's validation framework comes in - with an intuitive schema design framework with support for optional and dynamic fields, you'll never find yourself stuck again. + - **Powerful Transforms** + Anyone familiar with MongoDB knows the headaches that ObjectID causes due to its custom datatype. We also know that sometimes custom datatypes are unavoidable, or preferable for storage - though not necessarily ideal for processing. Iridium allows you to define a set of up-down transforms which are applied to parts of your model so that your code doesn't need to worry about these inconsistencies, and you can get down to writing the code you want to. + - **Express Support** + Everyone who has written code using Node.js knows about Express, to help make your life easier we've included support right out of the box for Express. + - **Powerful Models** + Iridium's models are designed to exist as individual files or modules within your application, this helps simplify management of your models and separates database design code from your application code. In addition to this, Iridium supports virtual properties, extension methods, transforms, client side property renaming and validations in an easy to understand and implement package. + - **Caching Support** + High performance web applications depend on accessing your data as quickly as possible, Iridium provides support for automated inline caching through any key-value store, allowing you to ensure that you can build the fastest application possible. + - **Plugin Framework** + Iridium allows the creation and use of plugins which can extend models and reduce duplicated code across models for common behavioural use cases. Plugins can provide custom validation, manipulate models at creation time and have the opportunity to extend instances when they are created. + - **Automatic Query Generation** + We understand that sometimes you don't want to structure your own queries - it's a hassle which you could do without especially when working with arrays. Thankfully, Iridium includes a powerful differential query generator which automatically generates the query necessary to store your changes without you raising a finger. + - **[A+ Promises](https://github.com/petkaantonov/bluebird) Built In** + We know how horrible it is having to manually wrap your favourite libraries before you can use them with promises, so we've decided to include support for the incredibly fast [Bluebird](https://github.com/petkaantonov/bluebird) promises library out of the box! (Iridium actually uses it internally as the primary handler, delegating back to callbacks for compatibility, don't tell anybody.) + +## Installation +Iridium is available using *npm*, which means you can install it locally using `npm install iridium` or add it to your project's *package.json* file to have it installed automatically whenever you run `npm install`. + +We make use of the [Semantic Versioning](http://semver.org/) guidelines for our versioning system, as such we highly recommend you stick to a single major version of Iridium when developing an application. This can easily be handled through your *package.json* file by doing the following. + +```javascript +{ + // ... + "dependencies": { + "iridium": "4.x" + } +} +``` + +## Example +```javascript +var iridium = require('iridium'); + +var database = new iridium({ + database: 'demo' +}); + +database.register('User', new iridium.Model(database, 'user', { + firstname: String, + lastname: String, + since: Date, + clown: Boolean, + houses: [{ + address: String, + colour: /Red|White|Blue|Green|Pink/ + }] +})); + +database.connect(function(err, db) { + if(err) throw err; + + // at this point database == db + + db.User.create({ + firstname: 'Billy', + lastname: 'Bob', + since: new Date(), + clown: true, + houses: [ + { address: 'The middle of nowhere', colour: 'Red' } + ] + }, function(err, user) { + if(err) throw err; + + console.log(JSON.stringify(user)); + }); +}); +``` + +## Benchmarks +Since Iridium claims to be ultra-lightweight, we thought we'd share some benchmarks with you - keep in mind that in the interest of fairness we are doing our best to compare apples with apples here, so when benchmarking against the MongoDB native driver we are running Iridium queries with `{ wrap: false }`, which disables wrapping of documents in `Instance` objects - though they still pass through the validation and preprocessing frameworks, and trigger hooks. + +Keep in mind that benchmarks only tell half of the story, performance will vary depending on how your application is configured, the system you run it on (these benchmarks were conducted on an ANCIENT laptop that was lying around) and a number of other factors. + +### Iridium vs. MongoDB Driver +As you can see, with `wrap: false`, Iridium is within a few milliseconds of the native MongoDB driver in many queries, despite adding an additional layer of security (validation) and ease of use (preprocessing). You can view the source code for this benchmark under *benchmarks/mongodb.js*. + +``` +> MongoDB 10000 Inserts { w: 1 } +> => 652ms +> Iridium 10000 Inserts { w: 1, wrap: false } +> => 725ms +> MongoDB find() +> => 230ms +> Iridium find() { wrap: false } +> => 311ms +> MongoDB remove() +> => 216ms +> Iridium remove() +> => 213ms +``` + +## Core +The Iridium core (that sounds WAY cooler than I intended when I came up with the name) is where you create a database connection and register any models to be used by the database. Registration of models is optional, however it makes accessing them easier. + +When using Iridium, you are required to instantiate a core with a settings object which describes the database server you want to connect to. This is done by calling the core's constructor and passing an object similar to the following. + +```javascript +{ + host: 'localhost', // Optional + port: 27018, // Optional + username: '', // Optional + password: '', // Optional + database: 'iridium' +} +``` + +Alternatively, you can pass in a standard MongoDB URI like `mongodb://username:password@localhost:27018/iridium` in place of the configuration object, allowing Iridium to easily be used with MongoS. + +Once you've got a core, you need to connect it to the database. This is done by calling the core's *connect* method and giving it a callback function. + + +### Registering Models +Models can be registered with the Iridium core to provide quick access from anywhere with access to the database instance. It has the added benefit of enabling IntelliSense for these models when accessed from the database object. + +You are required to give each model a name by which they may be accessed, convention states that these names should begin with a capital letter to indicate that they are constructors. + +```javascript +db.register('MyModel', require('./models/MyModel.js')); + +db.MyModel.get(function(err, instance) { + +}); +``` + +You can also chain `register` calls to quickly load all your different models using a single line of code. + +```javascript +db.register('Model1', require('./models/Model1.js')).register('Model2', require('./models/Model2.js')).connect(function(err, db) { + if(err) throw err; +}); +``` + +## Database API +We've tried to keep the Iridium API as similar to the original MongoDB Native Driver's API as possible, while still keeping it extremely easy to use. The following are a bunch of TypeScript definitions showing the different overloads available to you for Iridium's methods. +If you don't understand TypeScript then it's easiest to think of it this way. You can use any method available from the MongoDB Native Driver using (usually) the same arguments, except the callbacks are entirely optional and a promise is always returned. On methods which select items, you can simply provide the `_id` field's value to have it automatically converted and wrapped, so doing something like `users.find('spartan563')` is entirely fine. + +```typescript +interface FindFunction { + (): promise; + (callback: function): promise; + (identifier: any): promise; + (identifier: any, callback: function): promise; + (identifier: any, options: object, callback: function): promise; + (conditions: object): promise; + (conditions: object, callback: function): promise; + (conditions: object, options: object, callback: function): promise; +} + +interface InsertFunction { + (document: object): promise; + (document: object, callback: function): promise; + (document: object, options: object): promise; + (document: object, options: object, callback: function): promise; + (documents: [object]): promise; + (documents: [object], callback: function): promise; + (documents: [object], options: object): promise; + (documents: [object], options: object, callback: function): promise; +} + +interface UpdateFunction { + (conditions: object, changes: object): promise; + (conditions: object, changes: object, callback: function): promise; + (conditions: object, changes: object, options: object): promise; + (conditions: object, changes: object, options: object, callback: function): promise; +} + +interface EnsureIndexFunction { + (index: object): promise; + (index: object, options: object): promise; + (index: object, callback: function): promise; + (index: object, options: object, callback: function): promise; +} + +var find: FindFunction; +var findOne: FindFunction; +var get: FindFunction = findOne; + +var insert: InsertFunction; +var create: InsertFunction = insert; +var update: UpdateFunction; + +var count: FindFunction; +var remove: FindFunction; + +var aggregate: function; + +var ensureIndex: EnsureIndexFunction; +``` + +## Models +Iridium has been designed to make it as easy as possible to create and manage your models. To support this, models are designed to be stored within their own files - separating them from one another and keeping things logical. + +Each model file should export a function which accepts a reference to an Iridium Core instance, and returns the result of a model construction call. + +```javascript +var Model = require('iridium').Model; +module.exports = function(db) { + var schema = { + name: String, + email: /.+@.+\.\w+/ + }; + + var options = { + methods: { + + }, + virtuals: { + + }, + preprocessors: [], + hooks: { + + } + }; + + return new Model(db, 'collectionName', schema, options); +}; +``` + +### Validation +Iridium now (as of **v2.11.0**) uses [skmatc](https://sierrasoftworks.com/skmatc)(pronounced schematic) for schema validation, it allows you to quickly and easily write complex schemas in a readable manner and provides a powerful extensibility framework which you can use if you require more complex validation logic. + +```javascript +var schema = { + username: /\w[\w\d_]{7,}/, + email: String, + dateOfBirth: Date, + + sessions: [{ + id: String, + started: Date, + valid: Boolean + }] +}; +``` + +### Methods +Methods allow you to provide instance specific functionality in the form of callable methods on a model's instances. These methods have access to the instance, and allow you to call them directly as 1st class members. + +```javascript +var options = { + methods: { + checkPassword: function(password) { + return hash(password) == this.passwordHash; + } + } +}; +``` + +### Virtuals +Virtuals work similarly to methods, however they represent Getter/Setter properties which also behave as first class members. The idea is that they allow you to access information which can be gathered from an instance but which you do not necessarilly wish to store in the database. + +```javascript +var options = { + virtuals: { + forAPI: function() { + return { + id: this.id, + name: this.name + }; + }, + password: { + get: function() { return this.passwordHash; }, + set: function(value) { this.passwordHash = hash(value); } + } + } +}; +``` + +As you can see, virtuals can either be pure getters (in which case they should not make any changes to the instance) or Getter/Setter pairs, allowing instance values to be modified. + +If you have no need for accessing the original database value - and it can be converted between forms losslessly, it may be preferable to make use of the preprocessor framework, as it will remove the overhead of calling a conversion function on each property access. + +### Hooks +Hooks allow you to implement custom behaviour on certain events, one of the most common being the creation of a new instance. A good example of their use is the creation of a new user, where you usually receive their requested password directly. By using a hook, you can automatically convert their password into a hashed form upon creation, and save yourself the headache. + +```javascript +var options = { + hooks: { + creating: function() { + if(this.password) { + this.passwordHash = hash(this.password); + delete this.password; + } + } + } +}; +``` + +Other uses include the creation of default properties on models, using the *creating* hook to set the defaults if they are not present. + +Keep in mind that all hooks support a *done* callback if you wish to perform any kind of asynchronous operation before completing. This allows web requests, file system operations or even other database operations to be performed safely from within the hook. + +#### Available Hooks + - **creating([done])** + Called before an object is first stored in the database, `this` is set to the contents of the object being stored - allowing modification of the object prior to its insertion into the database. + - **saving(changes[, done])** + Called before an exisiting object is updated in the database (not called for `Model.update()`) with `this` set to the instance of the object being updated and the first argument always being the object changeset (MongoDB update syntax), allowing you to perform custom updates each time an object is saved. + - **retrieved([done])** + Called after an object has been retrieved from the database, but before it is wrapped into an Instance. The hook's `this` is set to the database document received - and will not have undergone any preprocessing yet. + - **ready([done])** + Called after an object has undergone preprocessing and has been wrapped into an Instance, allowing you to set non-persistent properties on the object (for example, retrieval time for a cache). + +### Events +Are you a fan of using EventEmitters? Are you too cool for school? No worries, we've got you covered. You can consume events on both models and instances using Node.js' standard event consumption functions, they behave much the same as their hook cousins with the exception that there is no way to safely manipulate the object (their call order is non-deterministic). + +```javascript +// Called for all validation, hook and database errors +model.on('error', function error(err) { }); +// Called when an object is being created in the database +model.on('creating', function creating(document) { }); +// Called when an object is being saved to the database +model.on('saving', function saving(instance, changes) { }); +// Called when an object is retrieved from the database/cache +model.on('retrieved', function retrieved(instance) { }); +// Called when a new instance becomes ready +model.on('ready', function ready(instance) { }); + +instance.on('error', function error(err) { }); +instance.on('creating', function creating(document) { }); +instance.on('saving', function saving(instance, changes) { }); +instance.on('retrieved', function retrieved(instance) { }); +instance.on('ready', function ready(instance) { }); +``` + +## Instances +An instance represents a database object retrieved by Iridium, and will inherit behaviour from the model it was created to represent. In addition to this, an instance has access to a few functions for performing operations which pertain directly to that instance, including the following. + +```javascript +// Saves any changes made to the instance (only affects properties in the schema, or already retrieved from the DB) +Instance.save().then(function complete(instance) { }, function error(err) { }); +Instance.save(function callback(err, instance) {}); + +// Executes the requested MongoDB changes on the current instance ({ $push: { sessions: 'session_key' }} etc.) +Instance.save(mongoChanges).then(function complete(instance) { }, function error(err) { }); +Instance.save(mongoChanges, function callback(err, instance) {}); + +// Used for manipulating specific array elements, you can use extraConditions to select the array element to manipulate +// For example Instance.save({ array: { $elemMatch: { id: 1 }}}, { $inc: { 'array.$.hits': 1 }}); +Instance.save(extraConditions, mongoChanges).then(function complete(instance) { }, function error(err) { }); +Instance.save(extraConditions, mongoChanges, function callback(err,instance) {}) + +// Updates the instance's data to match the latest available data from the database +Instance.update().then(function complete(instance) { }, function error(err) { }); +Instance.update(function callback(err, instance) {}); + +// Removes the instance from the database +Instance.remove().then(function complete() { }, function error(err) { }); +Instance.remove(function callback(err) {}); +``` + +### Helpers +The Instance object has a number of helper properties and functions available which prove useful in common use cases. These include the `document` virtual property which returns the underlying document which the Instance represents - allowing you to easily perform JSON serialization. Keep in mind that this document will have passed through the preprocessing framework. This property will be transparently overridden by Instances who's schema defines a `document` property. + +You will also find the `select` and `first` methods which can be used to filter an array or map for items which match certain criteria. They are based on [lodash](http://lodash.org)'s _.filter and _.first methods and by default will bind `this` to the Instance on which they are called. + +```javascript +console.log(JSON.stringify(Instance.document)); + +var session = Instance.first(Instance.sessions, function test(session) { + return session.id == 'abcdef...'; +}); + +var comments = Instance.select(Instance.comments, function test(comment) { + return comment.by = 'username'; +}); +``` + +### Differential Queries +In **v2.9.4** we added a powerful new differential query generator (codename Omnom) which allows you to easily make changes to your instances in code, and have Iridium handle the task of converting those changes to the most efficient query possible when you want to store those changes in MongoDB. + +Omnom allows you to do many things without needing to worry about how they are persisted to the database. These are some of the things that Omnom is well suited to handling. + + - Change properties or their children + - Change the value of an array's element or its children + - Remove elements from an array + - Add elements to the end of an array + - Selectively replacing an array's elements + +Unfortunately, there are a few limitations imposed by the way MongoDB handles queries - so when working with Iridium and Omnom we recommend you try to avoid doing the following. + + - Removing elements from an array while adding/changing others (will result in the array being replaced) + - Inserting elements at the front of an array (consider reversing the array using a Concoction if you want a stack implementation that is fast) + +## Caching Framework +Our caching framework allows basic queries to be served against a high performance cache, offloading requests from your database server and allowing you to more easily develop high performance applications that scale well to user demand. + +Your cache will **only** be tried for `Model.get` and `Model.findOne` requests for which the cache's `valid()` function returns true, allowing you to implement any basic cache structure you wish - including compound caches should you wish. + +By default Iridium doesn't cache anything, implementing a no-op cache, but you can easily configure your own caching plugin on a per-model basis by following this example. + +```javascript +function MemoryCache() { + this.cache = {}; +} + +// Tells Iridium whether it can use the cache for objects that match these conditions +MemoryCache.prototype.valid = function valid(conditions) { + return conditions && conditions._id; +}; + +MemoryCache.prototype.store = function store(conditions, document, callback) { + // Conditions are null when storing on an insert, otherwise they represent the conditions + // that resulted in the object being retrieved from the database. + var id = JSON.stringify(document._id); + this.cache[id] = document; + callback(); +}; + +MemoryCache.prototype.fetch = function fetch(id, callback) { + var id = JSON.stringify(conditions._id); + callback(this.cache[id]); +}; + +MemoryCache.prototype.drop = function drop(conditions, callback) { + var id = JSON.stringify(conditions._id); + if(this.cache[id]) delete this.cache[id]; + callback(); +}; +``` + +## Preprocessing Framework +The preprocessing framework allows Iridium to convert values from a form better suited to your database into a form more suitable for your application in an entirely transparent manner. This is acomplished through the use of a number of preprocessors which run when retrieving an object from the database, their order is reversed when pushing an object to the database. + +It has been moved into its own module, Concoction, which can be used outside of Iridium - and allows you to easily extend or replace it if you wish. For more information on Concoction, visit its [project page](https://github.com/SierraSoftworks/Concoction). + +### Conversions +The transforms framework provides a low-level means to convert from one value type to another by means of up/down conversion functions. The up functions are used to convert the value before it is sent UPstream to the database, while down functions are used to convert the database value into the DOWNstream value used by your application. + +```javascript +var Concoction = require('concoction'); +var model = new Model(db, 'modelName', modelSchema, { + preprocessors: [new Concoction.Convert({ + property: { + apply: function apply(value) { return convertedValueForDatabase; }, + reverse: function reverse(value) { return convertedValueFromDatabase } + } + })] + }); +``` + +### Renames +The renames framework allows you to access properties in a manner better suited to your application while keeping the same schema on the database side. This comes in handy when using the *_id* field for fields such as a user's username. + +```javascript +var Concoction = require('concoction'); +var model = new Model(db, 'modelName', modelSchema, { + preprocessors: [new Concoction.Rename({ + _id: 'id' + })] + }); +``` + +## Plugins +Iridium allows you to use plugins which extend the functionality provided by a number of Iridium's components. These plugins can add extra functions for models and instances as well has allowing hooks to be added automatically. Plugins are imported using the `db.register(plugin)` method overload (similar to the way models are loaded), and are declared using the following layout. + +```javascript +module.exports = { + newModel: function newModel(db, collection, schema, options) { + this.collection = collection.toLowerCase(); + this.schema._id = String, + this.options.preprocessors = []; + }, + newInstance: function newInstance(model, document, isNew) { + Object.defineProperty(this, 'id', { + get: function() { return document._id; }, + enumerable: false + }); + } +}; +``` + +## Thanks To +I'd also like to thank [dresende](https://github.com/dresende) and [dxg](https://github.com/dxg) from the [node-orm2](https://github.com/dresende/node-orm2) project for getting me introduced to Node and giving me many of the ideas for how a good ORM should be structured. If you're looking for an easy to use and more fully featured ORM with support for SQL and NoSQL databases, I'd seriously suggest giving [node-orm2](https://github.com/dresende/node-orm2) a try. diff --git a/_references.d.ts b/_references.d.ts new file mode 100644 index 0000000..4995816 --- /dev/null +++ b/_references.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/benchmarks/mongodb.js b/benchmarks/mongodb.js deleted file mode 100644 index 99db7f6..0000000 --- a/benchmarks/mongodb.js +++ /dev/null @@ -1,143 +0,0 @@ -var async = require('async'), - MongoClient = require('mongodb').MongoClient, - Iridium = require('../'); - -var objects = []; -for(var i = 0; i < 10000; i++) - objects.push({ - name: 'John', - surname: 'Doe', - birthday: new Date() - }); - -var iDB = new Iridium({ - database: 'iridium_bench' -}); - -var model = new Iridium.Model(iDB, 'iridium', { - name: String, - surname: String, - birthday: Date -}); - -var modelWrap = new Iridium.Model(iDB, 'iridiumWrap', { - name: String, - surname: String, - birthday: Date -}); - -iDB.register('model', model); - -function printTime(format, start) { - var ms = (new Date()).getTime() - start.getTime(); - console.log(format, ms.toString() + 'ms'); -} - -MongoClient.connect('mongodb://localhost/iridium_bench', function(err, mDB) { - if(err) throw err; - - iDB.connect(function(err) { - if(err) throw err; - - // Both databases are ready, let's start testing... - - var mDBcol = mDB.collection('mongo'); - async.series([ - function(done) { - mDBcol.remove({}, { w: 1 }, function(err, removed) { - if(err) return done(err); - return done(); - }); - }, - function(done) { - model.remove({}, function(err, removed) { - if(err) return done(err); - return done(); - }); - }, - function(done) { - console.log('MongoDB 10000 Inserts { w: 1 }'); - var start = new Date(); - mDBcol.insert(objects, { w: 1 }, function(err, inserted) { - if(err) return done(err); - printTime(' => %s', start); - return done(); - }); - }, - function(done) { - console.log('Iridium 10000 Inserts { w: 1, wrap: false }'); - var start = new Date(); - model.insert(objects, { w: 1, wrap: false }, function(err, inserted) { - if(err) return done(err); - printTime(' => %s', start); - return done(); - }); - }, - function(done) { - console.log('Iridium 10000 Inserts { w: 1, wrap: true }'); - var start = new Date(); - modelWrap.insert(objects, { w: 1, wrap: true }, function(err, inserted) { - if(err) return done(err); - printTime(' => %s', start); - return done(); - }); - }, - function(done) { - console.log('MongoDB find()'); - var start = new Date(); - mDBcol.find({}).toArray(function(err, results) { - if(err) return done(err); - printTime(' => %s', start); - return done(); - }); - }, - function(done) { - console.log('Iridium find() { wrap: false }'); - var start = new Date(); - model.find({}, { wrap: false }, function(err, results) { - if(err) return done(err); - printTime(' => %s', start); - return done(); - }); - }, - function(done) { - console.log('Iridium find() { wrap: true }'); - var start = new Date(); - modelWrap.find({}, { wrap: true }, function(err, results) { - if(err) return done(err); - printTime(' => %s', start); - return done(); - }); - }, - function(done) { - console.log('MongoDB remove()'); - var start = new Date(); - mDBcol.remove({}, function(err, results) { - if(err) return done(err); - printTime(' => %s', start); - return done(); - }); - }, - function(done) { - console.log('Iridium remove()'); - var start = new Date(); - model.remove(function(err, results) { - if(err) return done(err); - printTime(' => %s', start); - return done(); - }); - }, - function(done) { - modelWrap.remove(function(err, results) { - if(err) return done(err); - return done(); - }); - } - ], function(err) { - if(err) throw err; - - mDB.close(); - iDB.disconnect(); - }); - }); -}); \ No newline at end of file diff --git a/benchmarks/mongodb.ts b/benchmarks/mongodb.ts new file mode 100644 index 0000000..f54ce5c --- /dev/null +++ b/benchmarks/mongodb.ts @@ -0,0 +1,135 @@ +/// +import Iridium = require('../index'); +import Bluebird = require('bluebird'); +import MongoDB = require('mongodb'); +import _ = require('lodash'); +import crypto = require('crypto'); + +var intensity = 1000; +var samples = 3; + +interface UserDocument { + _id: string; +} + +class User { + _id: string; +} + +class WrappedUser extends Iridium.Instance { + _id: string; +} + +class IridiumDB extends Iridium.Core { + constructor() { + super({ database: 'test' }); + } + + User = new Iridium.Model(this,(model, doc) => doc, 'iridium', { + _id: false + }); + + UserWrapped = new Iridium.Model(this, WrappedUser, 'iridiumWrapped', { + _id: false + }); +} + +console.log("Running benchmark with intensity of %d, %d samples", intensity, samples); + +var results: { [name: string]: number } = {}; +function benchmark(name: string, prepare: (objects: UserDocument[]) => Bluebird, run: (objects: UserDocument[]) => Bluebird, compareTo?: string) { + return Bluebird.resolve(new Array(samples)).map(() => { + var objects: UserDocument[] = new Array(intensity); + for (var i = 0; i < objects.length; i++) + objects[i] = { _id: crypto.pseudoRandomBytes(16).toString('hex') }; + + return Bluebird.resolve().then(() => prepare(objects)).then(() => { + var start = new Date(); + return Bluebird.resolve().then(() => run(objects)).then(() => { + var time = new Date().valueOf() - start.valueOf(); + return time; + }); + }) + }, { concurency: 1 }).then(times => { + results[name] = _.reduce(times,(x, y) => x + y, 0) / times.length; + console.log("%s: %dms", name, results[name]); + if (compareTo) { + if (Math.abs(results[name] - results[compareTo]) / results[compareTo] < 0.1); + else if (results[name] > results[compareTo]) console.log(" - %dx slower than %s",(results[name] / results[compareTo]).toPrecision(2), compareTo); + else if (results[name] < results[compareTo]) console.log(" - %dx faster than %s",(results[name] / results[compareTo]).toPrecision(2), compareTo); + } + + return results[name]; + }); +} + +var iDB = new IridiumDB(); +iDB.connect() + .then(() => iDB.User.remove()) + .then(() => iDB.UserWrapped.remove()) + .then(() => { + return new Bluebird((resolve, reject) => { + iDB.connection.collection('mongodb').remove((err) => { + if (err) return reject(err); + return resolve(null); + }); + }); +}) + .then(() => benchmark("MongoDB insert()",() => { + return new Bluebird((resolve, reject) => { + iDB.connection.collection('mongodb').remove({},(err) => { + if (err) return reject(err); + return resolve(null); + }); + }); +},(objects) => { + return new Bluebird((resolve, reject) => { + iDB.connection.collection('mongodb').insert(objects,(err, objects) => { + if (err) return reject(err); + return resolve(objects); + }); + }); + })) + .then(() => benchmark("Iridium insert()",() => iDB.User.remove(),(objects) => iDB.User.insert(objects), "MongoDB insert()")) + .then(() => benchmark("Iridium Instances insert()",() => iDB.UserWrapped.remove(),(objects) => iDB.UserWrapped.insert(objects), "MongoDB insert()")) + + .then(() => benchmark("MongoDB find()",() => null,() => { + return new Bluebird((resolve, reject) => { + iDB.connection.collection('mongodb').find().toArray((err, objects: any) => { + if (err) return reject(err); + return resolve(objects); + }); + }); +})) + .then(() => benchmark("Iridium find()",() => null,() => iDB.User.find().toArray(), "MongoDB find()")) + .then(() => benchmark("Iridium Instances find()",() => null,() => iDB.UserWrapped.find().toArray(), "MongoDB find()")) + + .then(() => { + return new Bluebird((resolve, reject) => { + iDB.connection.collection('mongodb').remove((err, objects: any) => { + if (err) return reject(err); + return resolve(objects); + }); + }); +}) + .then(() => benchmark("MongoDB remove()",(objects) => { + return new Bluebird((resolve, reject) => { + iDB.connection.collection('mongodb').insert(objects,(err, objects) => { + if (err) return reject(err); + return resolve(objects); + }); + }); +},() => { + return new Bluebird((resolve, reject) => { + iDB.connection.collection('mongodb').remove((err, objects: any) => { + if (err) return reject(err); + return resolve(objects); + }); + }); + })) + .then(() => iDB.User.remove()) + .then(() => benchmark("Iridium remove()",(objects) => iDB.User.insert(objects),() => iDB.User.remove(), "MongoDB remove()")) + .then(() => iDB.UserWrapped.remove()) + + .catch((err) => console.error(err)) + .finally(() => iDB.close()); \ No newline at end of file diff --git a/build/build.js b/build/build.js new file mode 100644 index 0000000..6fa1421 --- /dev/null +++ b/build/build.js @@ -0,0 +1,31 @@ +var gulp = require('gulp'), + typescript = require('gulp-typescript'), + sourcemaps = require('gulp-sourcemaps'); + +var paths = require('./paths'); + +var tsProject = { + module: 'commonjs', + target: 'es5', + typescript: require('typescript') +}; + +gulp.task('build', ['build-lib', 'build-tests']); + +function build(files) { + var tsResult = gulp.src(files, { base: paths.projectRoot }) + .pipe(sourcemaps.init()) + .pipe(typescript(tsProject)); + + return tsResult.js + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest(paths.distFolder)); +} + +gulp.task('build-lib', function () { + return build(paths.buildFiles); +}); + +gulp.task('build-tests', function () { + return build(paths.testFiles); +}); \ No newline at end of file diff --git a/build/ci.js b/build/ci.js new file mode 100644 index 0000000..e70b436 --- /dev/null +++ b/build/ci.js @@ -0,0 +1,22 @@ +var gulp = require('gulp'); +var runSequence = require('run-sequence'); +var mocha = require('gulp-mocha'); +var plumber = require('gulp-plumber'); +var path = require('path'); + +var paths = require('./paths'); + +gulp.task('ci', function () { + return runSequence('build', 'ci-test'); +}); + +gulp.task('ci-test', function () { + return gulp.src(paths.builtTestFiles) + .pipe(plumber()) + .pipe(mocha({ + require: paths.testSupportFiles.map(function (file) { return path.resolve(path.dirname(__dirname), file); }), + timeout: 10000 + })) + .once('end', function () { process.exit(); }) + .once('error', function () { process.exit(1); }); +}); \ No newline at end of file diff --git a/build/clean.js b/build/clean.js new file mode 100644 index 0000000..14480ae --- /dev/null +++ b/build/clean.js @@ -0,0 +1,8 @@ +var gulp = require('gulp'); +var del = require('del'); + +var paths = require('./paths'); + +gulp.task('clean', function (done) { + del(paths.cleanFiles, done); +}); \ No newline at end of file diff --git a/build/coverage.js b/build/coverage.js new file mode 100644 index 0000000..416beb0 --- /dev/null +++ b/build/coverage.js @@ -0,0 +1,30 @@ +var gulp = require('gulp'), + plumber = require('gulp-plumber'), + istanbul = require('gulp-istanbul'), + mocha = require('gulp-mocha'), + path = require('path'); + +var paths = require('./paths'); + +gulp.task('coverage', function (cb) { + gulp.src(paths.builtFiles) + .pipe(istanbul()) + .pipe(istanbul.hookRequire()) + .on('finish', function () { + gulp.src(paths.builtTestFiles) + .pipe(plumber()) + .pipe(mocha({ + require: paths.testSupportFiles.map(function (file) { return path.resolve(path.dirname(__dirname), file); }), + timeout: 10000 + })) + .pipe(istanbul.writeReports({ + dir: paths.coverageFolder, + reporters: ['lcov', 'json', 'html'], + reportOpts: { dir: paths.coverageFolder } + })) + .pipe(istanbul.enforceThresholds({ thresholds: { global: 90 } })) + .once('end', function () { cb(); process.exit(); }) + .once('error', function (err) { cb(err); process.exit(1); }); + }); +}); + \ No newline at end of file diff --git a/build/default.js b/build/default.js new file mode 100644 index 0000000..c4b969e --- /dev/null +++ b/build/default.js @@ -0,0 +1,6 @@ +var gulp = require('gulp'), + runSequence = require('run-sequence'); + +gulp.task('default', function () { + return runSequence('clean', 'build', 'test', 'watch'); +}); \ No newline at end of file diff --git a/build/paths.js b/build/paths.js new file mode 100644 index 0000000..6d5cb9f --- /dev/null +++ b/build/paths.js @@ -0,0 +1,14 @@ +var path = require('path'); +module.exports = { + distFolder: path.resolve(__dirname, '../dist'), + projectRoot: path.dirname(__dirname), + coverageFolder: path.resolve(__dirname, '../coverage'), + + buildFiles: ["lib/**/*.ts", "index.ts"], + testFiles: ["test/**/*.ts"], + cleanFiles: ["coverage", "dist"], + + builtTestFiles: 'dist/test/*.js', + builtFiles: ["dist/lib/**/*.js", "dist/index.js"], + testSupportFiles: ['dist/test/support/chai'] +}; \ No newline at end of file diff --git a/build/publish.js b/build/publish.js new file mode 100644 index 0000000..aba16fd --- /dev/null +++ b/build/publish.js @@ -0,0 +1,24 @@ +var gulp = require('gulp'), + runSequence = require('run-sequence'), + replace = require('gulp-replace'), + spawn = require('child_process').spawn; + +gulp.task('publish', function () { + return runSequence('publish-prep', 'publish-action', 'publish-clean'); +}); + +gulp.task('publish-prep', function () { + return gulp.src('_references.d.ts') + .pipe(replace(/^\/{3}.*tsd\.d\.ts.*$/, '//$0')) + .pipe(gulp.dest('.')); +}); + +gulp.task('publish-action', function (done) { + spawn('npm', ['publish'], { stdio: 'inherit' }).on('close', done); +}); + +gulp.task('publish-clean', function () { + return gulp.src('_references.d.ts') + .pipe(replace(/^\/{2}(\/{3}.*tsd\.d\.ts.*)$/, '$1')) + .pipe(gulp.dest('.')); +}); \ No newline at end of file diff --git a/build/test.js b/build/test.js new file mode 100644 index 0000000..64b8974 --- /dev/null +++ b/build/test.js @@ -0,0 +1,13 @@ +var gulp = require('gulp'), + mocha = require('gulp-mocha'), + path = require('path'); + +var paths = require('./paths'); + +gulp.task('test', function () { + return gulp.src(paths.builtTestFiles) + .pipe(mocha({ + require: paths.testSupportFiles.map(function (file) { return path.resolve(path.dirname(__dirname), file); }), + timeout: 10000 + })); +}); \ No newline at end of file diff --git a/build/version.js b/build/version.js new file mode 100644 index 0000000..0b6910e --- /dev/null +++ b/build/version.js @@ -0,0 +1,61 @@ +var gulp = require('gulp'), + bump = require('gulp-bump'), + gutil = require('gulp-util'), + git = require('gulp-git'), + minimist = require('minimist'), + semver = require('semver'), + runSequence = require('run-sequence'), + fs = require('fs'); + +function getPackageJsonVersion() { + //We parse the json file instead of using require because require caches multiple calls so the version number won't be updated + return JSON.parse(fs.readFileSync('./package.json', 'utf8')).version; +} + +gulp.task('version-bump', function () { + var args = minimist(process.argv); + + var options = {}; + if (semver.valid(args.version)) options.version = args.version; + else options.type = args.version; + + return gulp.src(['./package.json']) + .pipe(bump(options).on('error', gutil.log)) + .pipe(gulp.dest('./')); +}); + +gulp.task('version-commit', function () { + var version = getPackageJsonVersion(); + return gulp.src('.') + .pipe(git.commit('Version ' + version)); +}); + +gulp.task('version-push', function (cb) { + git.push('origin', 'master', cb); +}); + +gulp.task('version-tag', function (cb) { + var version = getPackageJsonVersion(); + git.tag(version, 'Version ' + version, function (error) { + if (error) { + return cb(error); + } + git.push('origin', 'master', { args: '--tags' }, cb); + }); +}); + +gulp.task('version', function (callback) { + runSequence( + 'version-bump', + 'version-commit', + 'version-push', + 'version-tag', + function (error) { + if (error) { + console.log(error.message); + } else { + console.log('Version set and comitted successfully'); + } + callback(error); + }); +}); \ No newline at end of file diff --git a/build/watch.js b/build/watch.js new file mode 100644 index 0000000..9479025 --- /dev/null +++ b/build/watch.js @@ -0,0 +1,13 @@ +var gulp = require('gulp'); +var runSequence = require('run-sequence'); +var paths = require('./paths.js'); + +gulp.task('watch', function () { + gulp.watch(paths.buildFiles, function () { + return runSequence('build-lib', 'test'); + }); + + gulp.watch(paths.testFiles, function () { + return runSequence('build-tests', 'test'); + }); +}); \ No newline at end of file diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..117df64 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,25 @@ +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +var Core_1 = require('./lib/Core'); +exports.Core = Core_1.default; +var Model_1 = require('./lib/Model'); +exports.Model = Model_1.default; +var Instance_1 = require('./lib/Instance'); +exports.Instance = Instance_1.default; +__export(require('./lib/Decorators')); +__export(require('./lib/Plugins')); +__export(require('./lib/Schema')); +__export(require('./lib/Cache')); +__export(require('./lib/CacheDirector')); +__export(require('./lib/ModelOptions')); +__export(require('./lib/Configuration')); +__export(require('./lib/Hooks')); +var MemoryCache_1 = require('./lib/caches/MemoryCache'); +exports.MemoryCache = MemoryCache_1.default; +var NoOpCache_1 = require('./lib/caches/NoOpCache'); +exports.NoOpCache = NoOpCache_1.default; +var IDDirector_1 = require('./lib/cacheControllers/IDDirector'); +exports.CacheOnID = IDDirector_1.default; + +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map new file mode 100644 index 0000000..ef316a3 --- /dev/null +++ b/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["index.ts"],"names":[],"mappings":";;;AAAA,qBAAiB,YAAY,CAAC,CAAA;AAGtB,YAAI,kBAHkB;AAC9B,sBAAkB,aAAa,CAAC,CAAA;AAElB,aAAK,mBAFa;AAChC,yBAAqB,gBAAgB,CAAC,CAAA;AACjB,gBAAQ,sBADS;AAGtC,iBAAc,kBAAkB,CAAC,EAAA;AAEjC,iBAAc,eAAe,CAAC,EAAA;AAC9B,iBAAc,cAAc,CAAC,EAAA;AAC7B,iBAAc,aAAa,CAAC,EAAA;AAC5B,iBAAc,qBAAqB,CAAC,EAAA;AACpC,iBAAc,oBAAoB,CAAC,EAAA;AACnC,iBAAc,qBAAqB,CAAC,EAAA;AACpC,iBAAc,aAAa,CAAC,EAAA;AAE5B,4BAAwB,0BAA0B,CAAC,CAAA;AAE3C,mBAAW,yBAFgC;AACnD,0BAAsB,wBAAwB,CAAC,CAAA;AAC1B,iBAAS,uBADiB;AAG/C,2BAAuB,mCAAmC,CAAC,CAAA;AACrC,iBAAS,wBAD4B;AAC1B","file":"index.js","sourcesContent":["import Core from './lib/Core';\r\nimport Model from './lib/Model';\r\nimport Instance from './lib/Instance';\r\nexport {Core, Model, Instance};\r\n\r\nexport * from './lib/Decorators';\r\n\r\nexport * from './lib/Plugins';\r\nexport * from './lib/Schema';\r\nexport * from './lib/Cache';\r\nexport * from './lib/CacheDirector';\r\nexport * from './lib/ModelOptions';\r\nexport * from './lib/Configuration';\r\nexport * from './lib/Hooks';\r\n\r\nimport MemoryCache from './lib/caches/MemoryCache';\r\nimport NoOpCache from './lib/caches/NoOpCache';\r\nexport {MemoryCache, NoOpCache};\r\n\r\nimport IDDirector from './lib/cacheControllers/IDDirector';\r\nexport {IDDirector as CacheOnID};"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/Cache.js b/dist/lib/Cache.js new file mode 100644 index 0000000..d1bbc7d --- /dev/null +++ b/dist/lib/Cache.js @@ -0,0 +1,3 @@ + + +//# sourceMappingURL=../lib/Cache.js.map \ No newline at end of file diff --git a/dist/lib/Cache.js.map b/dist/lib/Cache.js.map new file mode 100644 index 0000000..8fe1036 --- /dev/null +++ b/dist/lib/Cache.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/Cache.ts"],"names":[],"mappings":"AAOC","file":"lib/Cache.js","sourcesContent":["/// \r\nimport Bluebird = require('bluebird');\r\n\r\nexport interface Cache {\r\n set(key: string, value: T): void;\r\n get(key: string): Bluebird;\r\n clear(key: string): void\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/CacheDirector.js b/dist/lib/CacheDirector.js new file mode 100644 index 0000000..fa0218f --- /dev/null +++ b/dist/lib/CacheDirector.js @@ -0,0 +1,3 @@ +/// + +//# sourceMappingURL=../lib/CacheDirector.js.map \ No newline at end of file diff --git a/dist/lib/CacheDirector.js.map b/dist/lib/CacheDirector.js.map new file mode 100644 index 0000000..a6557ff --- /dev/null +++ b/dist/lib/CacheDirector.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/CacheDirector.ts"],"names":[],"mappings":"AAAA,AACA,4CAD4C;AAO3C","file":"lib/CacheDirector.js","sourcesContent":["/// \r\nexport interface CacheDirector {\r\n valid(object: T): boolean;\r\n buildKey(object: T): string;\r\n\r\n validQuery(conditions: any): boolean;\r\n buildQueryKey(conditions: any): string;\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/Configuration.js b/dist/lib/Configuration.js new file mode 100644 index 0000000..01a23ff --- /dev/null +++ b/dist/lib/Configuration.js @@ -0,0 +1,3 @@ +/// + +//# sourceMappingURL=../lib/Configuration.js.map \ No newline at end of file diff --git a/dist/lib/Configuration.js.map b/dist/lib/Configuration.js.map new file mode 100644 index 0000000..f15a39c --- /dev/null +++ b/dist/lib/Configuration.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/Configuration.ts"],"names":[],"mappings":"AAAA,AACA,4CAD4C;AAS3C","file":"lib/Configuration.js","sourcesContent":["/// \r\nexport interface Configuration {\r\n host?: string;\r\n port?: number;\r\n hosts?: { address: string; port?: number }[];\r\n database?: string;\r\n username?: string;\r\n password?: string;\r\n [key:string]: any;\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/Core.js b/dist/lib/Core.js new file mode 100644 index 0000000..f39bfbb --- /dev/null +++ b/dist/lib/Core.js @@ -0,0 +1,169 @@ +/// +var Bluebird = require('bluebird'); +var MongoDB = require('mongodb'); +var _ = require('lodash'); +var Express_1 = require('./middleware/Express'); +var NoOpCache_1 = require('./caches/NoOpCache'); +var mongoConnectAsyc = Bluebird.promisify(MongoDB.MongoClient.connect); +var Core = (function () { + function Core(uri, config) { + this._plugins = []; + this._cache = new NoOpCache_1.default(); + var args = Array.prototype.slice.call(arguments, 0); + uri = config = null; + for (var i = 0; i < args.length; i++) { + if (typeof args[i] == 'string') + uri = args[i]; + else if (typeof args[i] == 'object') + config = args[i]; + } + if (!uri && !config) + throw new Error("Expected either a URI or config object to be supplied when initializing Iridium"); + this._url = uri; + this._config = config; + } + Object.defineProperty(Core.prototype, "plugins", { + /** + * Gets the plugins registered with this Iridium Core + * @returns {[Iridium.Plugin]} + */ + get: function () { + return this._plugins; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Core.prototype, "settings", { + /** + * Gets the configuration specified in the construction of this + * Iridium Core. + * @returns {Iridium.Configuration} + */ + get: function () { + return this._config; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Core.prototype, "connection", { + /** + * Gets the currently active database connection for this Iridium + * Core. + * @returns {MongoDB.Db} + */ + get: function () { + return this._connection; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Core.prototype, "url", { + /** + * Gets the URL used to connect to MongoDB + * @returns {String} + */ + get: function () { + var _this = this; + if (this._url) + return this._url; + var url = 'mongodb://'; + if (this._config.username) { + url += this._config.username; + if (this._config.password) + url += ':' + this._config.password; + url += '@'; + } + var hosts = []; + if (this._config.host) { + if (this._config.port) + hosts.push(this._config.host + ':' + this._config.port); + else + hosts.push(this._config.host); + } + if (this._config.hosts) { + _.each(this._config.hosts, function (host) { + if (host.port) + hosts.push(host.address + ':' + host.port); + else if (_this._config.port) + hosts.push(host.address + ':' + _this._config.port); + else + hosts.push(host.address); + }); + } + if (hosts.length) + url += _.uniq(hosts).join(','); + else + url += 'localhost'; + url += '/' + this._config.database; + return url; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Core.prototype, "cache", { + /** + * Gets the cache used to store objects retrieved from the database for performance reasons + * @returns {cache} + */ + get: function () { + return this._cache; + }, + set: function (value) { + this._cache = value; + }, + enumerable: true, + configurable: true + }); + /** + * Registers a new plugin with this Iridium Core + * @param {Iridium.Plugin} plugin The plugin to register with this Iridium Core + * @returns {Iridium.Core} + */ + Core.prototype.register = function (plugin) { + this.plugins.push(plugin); + return this; + }; + /** + * Connects to the database server specified in the provided configuration + * @param {function(Error, Iridium.Core)} [callback] A callback to be triggered once the connection is established. + * @returns {Promise} + */ + Core.prototype.connect = function (callback) { + var self = this; + return Bluebird.bind(this).then(function () { + if (self._connection) + return self._connection; + return mongoConnectAsyc(self.url); + }).then(function (db) { + self._connection = db; + return self; + }).nodeify(callback); + }; + /** + * Closes the active database connection + * @type {Promise} + */ + Core.prototype.close = function () { + var self = this; + return Bluebird.bind(this).then(function () { + if (!self._connection) + return this; + var conn = self._connection; + self._connection = null; + conn.close(); + return this; + }); + }; + /** + * Provides an express middleware which can be used to set the req.db property + * to the current Iridium instance. + * @returns {Iridium.ExpressMiddleware} + */ + Core.prototype.express = function () { + return Express_1.default(this); + }; + return Core; +})(); +exports.default = Core; + +//# sourceMappingURL=../lib/Core.js.map \ No newline at end of file diff --git a/dist/lib/Core.js.map b/dist/lib/Core.js.map new file mode 100644 index 0000000..5ed979e --- /dev/null +++ b/dist/lib/Core.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/Core.ts"],"names":["Core","Core.constructor","Core.plugins","Core.settings","Core.connection","Core.url","Core.cache","Core.register","Core.connect","Core.close","Core.express"],"mappings":"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","file":"lib/Core.js","sourcesContent":["/// \r\nimport Bluebird = require('bluebird');\r\nimport MongoDB = require('mongodb');\r\nimport _ = require('lodash');\r\nimport http = require('http');\r\nimport events = require('events');\r\n\r\nimport {Configuration} from './Configuration';\r\nimport {Plugin} from './Plugins';\r\nimport Model from './Model';\r\nimport Instance from './Instance';\r\n\r\nimport {MiddlewareFactory} from './Middleware';\r\nimport * as ExpressMiddleware from './middleware/Express';\r\nimport ExpressMiddlewareFactory from './middleware/Express';\r\n\r\nimport {Cache} from './Cache';\r\nimport NoOpCache from './caches/NoOpCache';\r\nimport MemoryCache from './caches/MemoryCache';\r\n\r\nvar mongoConnectAsyc = Bluebird.promisify(MongoDB.MongoClient.connect);\r\n\r\nexport default class Core {\r\n /**\r\n * Creates a new Iridium Core instance connected to the specified MongoDB instance\r\n * @param {Iridium.IridiumConfiguration} config The config object defining the database to connect to\r\n * @constructs Core\r\n */\r\n constructor(config: Configuration);\r\n /**\r\n * Creates a new Iridium Core instance connected to the specified MongoDB instance\r\n * @param {String} url The URL of the MongoDB instance to connect to\r\n * @param {Iridium.IridiumConfiguration} config The config object made available as settings\r\n * @constructs Core\r\n */\r\n constructor(uri: string, config?: Configuration);\r\n constructor(uri: string | Configuration, config?: Configuration) {\r\n\r\n var args = Array.prototype.slice.call(arguments, 0);\r\n uri = config = null;\r\n for (var i = 0; i < args.length; i++) {\r\n if (typeof args[i] == 'string')\r\n uri = args[i];\r\n else if (typeof args[i] == 'object')\r\n config = args[i];\r\n }\r\n\r\n if (!uri && !config) throw new Error(\"Expected either a URI or config object to be supplied when initializing Iridium\");\r\n\r\n this._url = uri;\r\n this._config = config;\r\n }\r\n \r\n private _plugins: Plugin[] = [];\r\n private _url: string;\r\n private _config: Configuration;\r\n private _connection: MongoDB.Db;\r\n private _cache: Cache = new NoOpCache();\r\n \r\n /**\r\n * Gets the plugins registered with this Iridium Core\r\n * @returns {[Iridium.Plugin]}\r\n */\r\n get plugins(): Plugin[] {\r\n return this._plugins;\r\n }\r\n\r\n /**\r\n * Gets the configuration specified in the construction of this\r\n * Iridium Core.\r\n * @returns {Iridium.Configuration}\r\n */\r\n get settings(): Configuration {\r\n return this._config;\r\n }\r\n\r\n /**\r\n * Gets the currently active database connection for this Iridium\r\n * Core.\r\n * @returns {MongoDB.Db}\r\n */\r\n get connection(): MongoDB.Db {\r\n return this._connection;\r\n }\r\n\r\n /**\r\n * Gets the URL used to connect to MongoDB\r\n * @returns {String}\r\n */\r\n get url(): string {\r\n if (this._url) return this._url;\r\n var url: string = 'mongodb://';\r\n\r\n if (this._config.username) {\r\n url += this._config.username;\r\n if (this._config.password)\r\n url += ':' + this._config.password;\r\n url += '@';\r\n }\r\n\r\n var hosts = [];\r\n\r\n if (this._config.host) {\r\n if (this._config.port)\r\n hosts.push(this._config.host + ':' + this._config.port);\r\n else\r\n hosts.push(this._config.host);\r\n }\r\n\r\n if (this._config.hosts) {\r\n _.each(this._config.hosts, (host) => {\r\n if (host.port)\r\n hosts.push(host.address + ':' + host.port);\r\n else if(this._config.port)\r\n hosts.push(host.address + ':' + this._config.port);\r\n else\r\n hosts.push(host.address);\r\n });\r\n }\r\n\r\n if (hosts.length)\r\n url += _.uniq(hosts).join(',');\r\n else\r\n url += 'localhost';\r\n\r\n url += '/' + this._config.database;\r\n\r\n return url;\r\n }\r\n\r\n /**\r\n * Gets the cache used to store objects retrieved from the database for performance reasons\r\n * @returns {cache}\r\n */\r\n get cache(): Cache {\r\n return this._cache;\r\n }\r\n\r\n set cache(value: Cache) {\r\n this._cache = value;\r\n }\r\n\r\n /**\r\n * Registers a new plugin with this Iridium Core\r\n * @param {Iridium.Plugin} plugin The plugin to register with this Iridium Core\r\n * @returns {Iridium.Core}\r\n */\r\n register(plugin: Plugin): Core {\r\n this.plugins.push(plugin);\r\n return this;\r\n }\r\n\r\n /**\r\n * Connects to the database server specified in the provided configuration\r\n * @param {function(Error, Iridium.Core)} [callback] A callback to be triggered once the connection is established.\r\n * @returns {Promise}\r\n */\r\n connect(callback?: (err: Error, core: Core) => any): Bluebird {\r\n var self = this;\r\n return Bluebird.bind(this).then(function() {\r\n if (self._connection) return self._connection;\r\n return mongoConnectAsyc(self.url);\r\n }).then(function(db: MongoDB.Db) {\r\n self._connection = db;\r\n return self;\r\n }).nodeify(callback);\r\n }\r\n\r\n /**\r\n * Closes the active database connection\r\n * @type {Promise}\r\n */\r\n close(): Bluebird {\r\n var self = this;\r\n return Bluebird.bind(this).then(function() {\r\n if (!self._connection) return this;\r\n var conn: MongoDB.Db = self._connection;\r\n self._connection = null;\r\n conn.close();\r\n return this;\r\n });\r\n }\r\n\r\n /**\r\n * Provides an express middleware which can be used to set the req.db property\r\n * to the current Iridium instance.\r\n * @returns {Iridium.ExpressMiddleware}\r\n */\r\n express(): ExpressMiddleware.ExpressMiddleware {\r\n return ExpressMiddlewareFactory(this);\r\n }\r\n}\r\n"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/Cursor.js b/dist/lib/Cursor.js new file mode 100644 index 0000000..f0482b0 --- /dev/null +++ b/dist/lib/Cursor.js @@ -0,0 +1,144 @@ +var Bluebird = require('bluebird'); +var Cursor = (function () { + /** + * Creates a new Iridium cursor which wraps a MongoDB cursor object + * @param {Model} model The Iridium model that this cursor belongs to + * @param {Object} conditions The conditions that resulte in this cursor being created + * @param {MongoDB.Cursor} cursor The MongoDB native cursor object to be wrapped + * @constructor + */ + function Cursor(model, conditions, cursor) { + this.model = model; + this.conditions = conditions; + this.cursor = cursor; + } + /** + * Counts the number of documents which are matched by this cursor + * @param {function(Error, Number)} callback A callback which is triggered when the result is available + * @return {Promise} A promise which will resolve with the number of documents matched by this cursor + */ + Cursor.prototype.count = function (callback) { + var _this = this; + return new Bluebird(function (resolve, reject) { + _this.cursor.count(true, function (err, count) { + if (err) + return reject(err); + return resolve(count); + }); + }).nodeify(callback); + }; + /** + * Runs the specified handler over each instance in the query results + * @param {function(Instance)} handler The handler which is triggered for each element in the query + * @param {function(Error)} callback A callback which is triggered when all operations have been dispatched + * @return {Promise} A promise which is resolved when all operations have been dispatched + */ + Cursor.prototype.forEach = function (handler, callback) { + var _this = this; + var helpers = this.model.helpers; + return new Bluebird(function (resolve, reject) { + _this.cursor.forEach(function (item) { + _this.model.handlers.documentReceived(_this.conditions, item, function () { return helpers.wrapDocument.apply(helpers, arguments); }).then(handler); + }, function (err) { + if (err) + return reject(err); + return resolve(null); + }); + }).nodeify(callback); + }; + /** + * Runs the specified transform over each instance in the query results and returns the resulting transformed objects + * @param {function(Instance): TResult} transform A handler which is used to transform the result objects + * @param {function(Error, TResult[])} callback A callback which is triggered when the transformations are completed + * @return {Promise} A promise which is fulfilled with the results of the transformations + */ + Cursor.prototype.map = function (transform, callback) { + var _this = this; + var helpers = this.model.helpers; + return new Bluebird(function (resolve, reject) { + var promises = []; + _this.cursor.forEach(function (item) { + promises.push(_this.model.handlers.documentReceived(_this.conditions, item, function () { return helpers.wrapDocument.apply(helpers, arguments); }) + .then(transform)); + }, function (err) { + if (err) + return reject(err); + return resolve(Bluebird.all(promises)); + }); + }).nodeify(callback); + }; + /** + * Retrieves all matching instances and returns them in an array + * @param {function(Error, TInstance[])} callback A callback which is triggered with the resulting instances + * @return {Promise} A promise which resolves with the instances returned by the query + */ + Cursor.prototype.toArray = function (callback) { + var _this = this; + var helpers = this.model.helpers; + return new Bluebird(function (resolve, reject) { + _this.cursor.toArray(function (err, results) { + if (err) + return reject(err); + return resolve(results); + }); + }).map(function (document) { + return _this.model.handlers.documentReceived(_this.conditions, document, function () { return helpers.wrapDocument.apply(helpers, arguments); }); + }).nodeify(callback); + }; + /** + * Retrieves the next item in the results list + * @param {function(Error, TInstance)} callback A callback which is triggered when the next item becomes available + * @return {Promise} A promise which is resolved with the next item + */ + Cursor.prototype.next = function (callback) { + var _this = this; + return new Bluebird(function (resolve, reject) { + _this.cursor.next(function (err, result) { + if (err) + return reject(err); + return resolve(result); + }); + }).then(function (document) { + if (!document) + return Bluebird.resolve(null); + return _this.model.handlers.documentReceived(_this.conditions, document, function (document, isNew, isPartial) { return _this.model.helpers.wrapDocument(document, isNew, isPartial); }); + }).nodeify(callback); + }; + /** + * Returns a new cursor which behaves the same as this one did before any results were retrieved + * @return {Cursor} The new cursor which starts at the beginning of the results + */ + Cursor.prototype.rewind = function () { + this.cursor.rewind(); + return this; + }; + /** + * Returns a new cursor which sorts its results by the given index expression + * @param {model.IndexSpecification} sortExpression The index expression dictating the sort order and direction to use + * @return {Cursor} The new cursor which sorts its results by the sortExpression + */ + Cursor.prototype.sort = function (sortExpression) { + return new Cursor(this.model, this.conditions, this.cursor.sort(sortExpression)); + }; + /** + * Returns a new cursor which limits the number of returned results + * @param {Number} limit The maximum number of results to return + * @return {Cursor} The new cursor which will return a maximum number of results + */ + Cursor.prototype.limit = function (limit) { + return new Cursor(this.model, this.conditions, this.cursor.limit(limit)); + }; + /** + * Returns a new cursor which skips a number of results before it begins + * returning any. + * @param {Number} skip The number of results to skip before the cursor beings returning + * @return {Cursor} The new cursor which skips a number of results + */ + Cursor.prototype.skip = function (skip) { + return new Cursor(this.model, this.conditions, this.cursor.skip(skip)); + }; + return Cursor; +})(); +exports.default = Cursor; + +//# sourceMappingURL=../lib/Cursor.js.map \ No newline at end of file diff --git a/dist/lib/Cursor.js.map b/dist/lib/Cursor.js.map new file mode 100644 index 0000000..f201972 --- /dev/null +++ b/dist/lib/Cursor.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/Cursor.ts"],"names":["Cursor","Cursor.constructor","Cursor.count","Cursor.forEach","Cursor.map","Cursor.toArray","Cursor.next","Cursor.rewind","Cursor.sort","Cursor.limit","Cursor.skip"],"mappings":"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","file":"lib/Cursor.js","sourcesContent":["/// \r\nimport Model from './Model';\r\nimport General = require('./General');\r\nimport MongoDB = require('mongodb');\r\nimport Bluebird = require('bluebird');\r\nimport * as Index from './Index';\r\n\r\nexport default class Cursor {\r\n /**\r\n * Creates a new Iridium cursor which wraps a MongoDB cursor object\r\n * @param {Model} model The Iridium model that this cursor belongs to\r\n * @param {Object} conditions The conditions that resulte in this cursor being created\r\n * @param {MongoDB.Cursor} cursor The MongoDB native cursor object to be wrapped\r\n * @constructor\r\n */\r\n constructor(private model: Model, private conditions: any, public cursor: MongoDB.Cursor) {\r\n\r\n }\r\n\r\n /**\r\n * Counts the number of documents which are matched by this cursor\r\n * @param {function(Error, Number)} callback A callback which is triggered when the result is available\r\n * @return {Promise} A promise which will resolve with the number of documents matched by this cursor\r\n */\r\n count(callback?: General.Callback): Bluebird {\r\n return new Bluebird((resolve, reject) => {\r\n this.cursor.count(true,(err, count) => {\r\n if (err) return reject(err);\r\n return resolve(count);\r\n });\r\n }).nodeify(callback);\r\n }\r\n\r\n /**\r\n * Runs the specified handler over each instance in the query results\r\n * @param {function(Instance)} handler The handler which is triggered for each element in the query\r\n * @param {function(Error)} callback A callback which is triggered when all operations have been dispatched\r\n * @return {Promise} A promise which is resolved when all operations have been dispatched\r\n */\r\n forEach(handler: (instance: TInstance) => void, callback?: General.Callback): Bluebird {\r\n var helpers = this.model.helpers;\r\n return new Bluebird((resolve, reject) => {\r\n this.cursor.forEach((item: TDocument) => {\r\n this.model.handlers.documentReceived(this.conditions, item, function () { return helpers.wrapDocument.apply(helpers, arguments); }).then(handler);\r\n },(err) => {\r\n if (err) return reject(err);\r\n return resolve(null);\r\n });\r\n }).nodeify(callback);\r\n }\r\n\r\n /**\r\n * Runs the specified transform over each instance in the query results and returns the resulting transformed objects\r\n * @param {function(Instance): TResult} transform A handler which is used to transform the result objects\r\n * @param {function(Error, TResult[])} callback A callback which is triggered when the transformations are completed\r\n * @return {Promise} A promise which is fulfilled with the results of the transformations\r\n */\r\n map(transform: (instance: TInstance) => TResult | Bluebird, callback?: General.Callback): Bluebird {\r\n var helpers = this.model.helpers;\r\n return new Bluebird((resolve, reject) => {\r\n var promises: Bluebird[] = [];\r\n this.cursor.forEach((item: TDocument) => {\r\n promises.push(this.model.handlers.documentReceived(this.conditions, item, function () { return helpers.wrapDocument.apply(helpers, arguments); })\r\n .then(<(instance) => TResult>transform));\r\n },(err) => {\r\n if (err) return reject(err);\r\n return resolve(Bluebird.all(promises));\r\n });\r\n }).nodeify(callback);\r\n }\r\n\r\n /**\r\n * Retrieves all matching instances and returns them in an array\r\n * @param {function(Error, TInstance[])} callback A callback which is triggered with the resulting instances\r\n * @return {Promise} A promise which resolves with the instances returned by the query\r\n */\r\n toArray(callback?: General.Callback): Bluebird {\r\n var helpers = this.model.helpers;\r\n return new Bluebird((resolve, reject) => {\r\n this.cursor.toArray((err, results: any[]) => {\r\n if (err) return reject(err);\r\n return resolve(results);\r\n });\r\n }).map((document) => {\r\n return this.model.handlers.documentReceived(this.conditions, document, function () { return helpers.wrapDocument.apply(helpers, arguments); });\r\n }).nodeify(callback);\r\n }\r\n\r\n /**\r\n * Retrieves the next item in the results list\r\n * @param {function(Error, TInstance)} callback A callback which is triggered when the next item becomes available\r\n * @return {Promise} A promise which is resolved with the next item\r\n */\r\n next(callback?: General.Callback): Bluebird {\r\n return new Bluebird((resolve, reject) => {\r\n this.cursor.next((err, result: any) => {\r\n if (err) return reject(err);\r\n return resolve(result);\r\n });\r\n }).then((document) => {\r\n if (!document) return Bluebird.resolve(null);\r\n return this.model.handlers.documentReceived(this.conditions, document,(document, isNew?, isPartial?) => this.model.helpers.wrapDocument(document, isNew, isPartial));\r\n }).nodeify(callback);\r\n }\r\n\r\n /**\r\n * Returns a new cursor which behaves the same as this one did before any results were retrieved\r\n * @return {Cursor} The new cursor which starts at the beginning of the results\r\n */\r\n rewind(): Cursor {\r\n this.cursor.rewind();\r\n return this;\r\n }\r\n\r\n /**\r\n * Returns a new cursor which sorts its results by the given index expression\r\n * @param {model.IndexSpecification} sortExpression The index expression dictating the sort order and direction to use\r\n * @return {Cursor} The new cursor which sorts its results by the sortExpression\r\n */\r\n sort(sortExpression: Index.IndexSpecification): Cursor {\r\n return new Cursor(this.model, this.conditions, this.cursor.sort(sortExpression));\r\n }\r\n\r\n /**\r\n * Returns a new cursor which limits the number of returned results\r\n * @param {Number} limit The maximum number of results to return\r\n * @return {Cursor} The new cursor which will return a maximum number of results\r\n */\r\n limit(limit: number): Cursor {\r\n return new Cursor(this.model, this.conditions, this.cursor.limit(limit));\r\n }\r\n\r\n /**\r\n * Returns a new cursor which skips a number of results before it begins\r\n * returning any.\r\n * @param {Number} skip The number of results to skip before the cursor beings returning\r\n * @return {Cursor} The new cursor which skips a number of results\r\n */\r\n skip(skip: number): Cursor {\r\n return new Cursor(this.model, this.conditions, this.cursor.skip(skip));\r\n }\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/Decorators.js b/dist/lib/Decorators.js new file mode 100644 index 0000000..c1d0be0 --- /dev/null +++ b/dist/lib/Decorators.js @@ -0,0 +1,63 @@ +/// +var MongoDB = require('mongodb'); +var _ = require('lodash'); +var skmatc = require('skmatc'); +function Collection(name) { + return function (target) { + target.collection = name; + }; +} +exports.Collection = Collection; +function Index(spec, options) { + return function (target) { + target.indexes = (target.indexes || []).concat({ spec: spec, options: options || {} }); + }; +} +exports.Index = Index; +function Validate(forType, validate) { + return function (target) { + target.validators = (target.validators || []).concat(skmatc.create(function (schema) { return schema === forType; }, validate)); + }; +} +exports.Validate = Validate; +function Property() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i - 0] = arguments[_i]; + } + var name = null, asType = false, required = true; + if (args.length > 1 && typeof args[args.length - 1] === 'boolean') + required = args.pop(); + return function (target, property) { + if (!property) + name = args.shift(); + else { + name = property; + target = target.constructor; + } + asType = args.pop() || false; + target.schema = _.clone(target.schema || {}); + if (!required && typeof asType !== 'boolean') + target.schema[name] = { $required: required, $type: asType }; + else + target.schema[name] = asType; + }; +} +exports.Property = Property; +function Transform(fromDB, toDB) { + return function (target, property) { + target.constructor.transforms = _.clone(target.constructor.transforms || {}); + target.constructor.transforms[property] = { + fromDB: fromDB, + toDB: toDB + }; + }; +} +exports.Transform = Transform; +function ObjectID(target, name) { + target.constructor.schema = _.clone(target.constructor.schema || {}); + target.constructor.schema[name] = MongoDB.ObjectID; +} +exports.ObjectID = ObjectID; + +//# sourceMappingURL=../lib/Decorators.js.map \ No newline at end of file diff --git a/dist/lib/Decorators.js.map b/dist/lib/Decorators.js.map new file mode 100644 index 0000000..cc8c35f --- /dev/null +++ b/dist/lib/Decorators.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/Decorators.ts"],"names":["Collection","Index","Validate","Property","Transform","ObjectID"],"mappings":"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","file":"lib/Decorators.js","sourcesContent":["/// \r\nimport MongoDB = require('mongodb');\r\nimport _ = require('lodash');\r\nimport skmatc = require('skmatc');\r\nimport Instance from './Instance';\r\nimport {Index, IndexSpecification} from './Index';\r\nimport {Schema} from './Schema';\r\nimport InstanceImplementation from './InstanceInterface';\r\n\r\nexport function Collection(name: string) {\r\n\treturn function(target: InstanceImplementation) {\r\n\t\ttarget.collection = name;\r\n\t};\r\n}\r\n\r\nexport function Index(spec: IndexSpecification, options?: MongoDB.IndexOptions) {\r\n\treturn function(target: InstanceImplementation) {\r\n\t\ttarget.indexes = (target.indexes || []).concat({ spec: spec, options: options || {} });\r\n\t}\r\n}\r\n\r\nexport function Validate(forType: any, validate: (schema: any, data: any, path: string) => Skmatc.Result) {\r\n\treturn function(target: InstanceImplementation) {\r\n\t\ttarget.validators = (target.validators || []).concat(skmatc.create(schema => schema === forType, validate));\r\n\t}\r\n}\r\n\r\nexport function Property(asType: any, required?: boolean): (target: { constructor: Function }, name: string) => void;\r\nexport function Property(name: string, asType: any, required?: boolean): (target: Function) => void;\r\nexport function Property(...args: any[]): (target: any, name?: string) => void {\r\n\tlet name = null,\r\n\t\tasType = false,\r\n\t\trequired = true;\r\n\t\r\n\tif (args.length > 1 && typeof args[args.length - 1] === 'boolean')\r\n\t\trequired = args.pop();\r\n\t\r\n\treturn function(target: any, property?: string) {\r\n\t\tif (!property) name = args.shift();\r\n\t\telse {\r\n\t\t\tname = property;\r\n\t\t\ttarget = target.constructor;\r\n\t\t}\r\n\t\tasType = args.pop() || false;\r\n\t\t\r\n\t\ttarget.schema = _.clone(target.schema || {});\r\n\t\tif(!required && typeof asType !== 'boolean') target.schema[name] = { $required: required, $type: asType };\r\n\t\telse target.schema[name] = asType;\r\n\t}\r\n}\r\n\r\nexport function Transform(fromDB: (value: any) => any, toDB: (value: any) => any) {\r\n\treturn function(target: any, property: string) {\r\n\t\ttarget.constructor.transforms = _.clone(target.constructor.transforms || {})\r\n\t\ttarget.constructor.transforms[property] = {\r\n\t\t\tfromDB: fromDB,\r\n\t\t\ttoDB: toDB\r\n\t\t};\r\n\t};\r\n}\r\n\r\nexport function ObjectID(target: { constructor: typeof Instance }, name: string) {\r\n\ttarget.constructor.schema = _.clone(target.constructor.schema || {});\r\n\ttarget.constructor.schema[name] = MongoDB.ObjectID;\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/General.js b/dist/lib/General.js new file mode 100644 index 0000000..f88be71 --- /dev/null +++ b/dist/lib/General.js @@ -0,0 +1,3 @@ +/// + +//# sourceMappingURL=../lib/General.js.map \ No newline at end of file diff --git a/dist/lib/General.js.map b/dist/lib/General.js.map new file mode 100644 index 0000000..fd4eed7 --- /dev/null +++ b/dist/lib/General.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/General.ts"],"names":[],"mappings":"AAAA,AACA,4CAD4C;AAoB3C","file":"lib/General.js","sourcesContent":["/// \r\nexport interface Callback {\r\n (err: Error, object?: T): void;\r\n}\r\n\r\nexport interface Predicate {\r\n (object: T, key?: string): boolean;\r\n}\r\n\r\nexport interface PropertyGetter {\r\n (): T;\r\n}\r\n\r\nexport interface PropertySetter {\r\n (value: T): void;\r\n}\r\n\r\nexport interface Property {\r\n get?: PropertyGetter;\r\n set?: PropertySetter;\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/Hooks.js b/dist/lib/Hooks.js new file mode 100644 index 0000000..117b52f --- /dev/null +++ b/dist/lib/Hooks.js @@ -0,0 +1,3 @@ + + +//# sourceMappingURL=../lib/Hooks.js.map \ No newline at end of file diff --git a/dist/lib/Hooks.js.map b/dist/lib/Hooks.js.map new file mode 100644 index 0000000..91cecf9 --- /dev/null +++ b/dist/lib/Hooks.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/Hooks.ts"],"names":[],"mappings":"AAQC","file":"lib/Hooks.js","sourcesContent":["/// \r\nimport instance = require('./Instance');\r\n\r\nexport interface Hooks {\r\n onCreating? (document: TDocument): void;\r\n onRetrieved? (document: TDocument): void;\r\n onReady? (instance: TInstance): void;\r\n onSaving? (instance: TInstance, changes: any): void;\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/Index.js b/dist/lib/Index.js new file mode 100644 index 0000000..f99f4da --- /dev/null +++ b/dist/lib/Index.js @@ -0,0 +1,3 @@ + + +//# sourceMappingURL=../lib/Index.js.map \ No newline at end of file diff --git a/dist/lib/Index.js.map b/dist/lib/Index.js.map new file mode 100644 index 0000000..4b508f3 --- /dev/null +++ b/dist/lib/Index.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/Index.ts"],"names":[],"mappings":"AAUC","file":"lib/Index.js","sourcesContent":["/// \r\nimport MongoDB = require('mongodb');\r\n\r\nexport interface Index {\r\n spec: IndexSpecification;\r\n options?: MongoDB.IndexOptions;\r\n}\r\n\r\nexport interface IndexSpecification {\r\n [key: string]: number;\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/Instance.js b/dist/lib/Instance.js new file mode 100644 index 0000000..91dd02a --- /dev/null +++ b/dist/lib/Instance.js @@ -0,0 +1,256 @@ +var _ = require('lodash'); +var MongoDB = require('mongodb'); +var Bluebird = require('bluebird'); +var skmatc = require('skmatc'); +var Instance = (function () { + /** + * Creates a new instance which represents the given document as a type of model + * @param {model.Model} model The model that the document represents + * @param {TSchema} document The document which should be wrapped by this instance + * @param {Boolean} isNew Whether the document is new (doesn't exist in the database) or not + * @param {Boolean} isPartial Whether the document has only a subset of its fields populated + * @description + * This class will be subclassed automatically by Iridium to create a model specific instance + * which takes advantage of some of v8's optimizations to boost performance significantly. + * The instance returned by the model, and all of this instance's methods, will be of type + * TInstance - which should represent the merger of TSchema and IInstance for best results. + */ + function Instance(model, document, isNew, isPartial) { + var _this = this; + if (isNew === void 0) { isNew = true; } + if (isPartial === void 0) { isPartial = false; } + this._model = model; + this._isNew = !!isNew; + this._isPartial = isPartial; + this._original = document; + this._modified = _.cloneDeep(document); + _.each(model.core.plugins, function (plugin) { + if (plugin.newInstance) + plugin.newInstance(_this, model); + }); + } + Object.defineProperty(Instance.prototype, "document", { + /** + * Gets the underlying document representation of this instance + */ + get: function () { + return this._modified; + }, + enumerable: true, + configurable: true + }); + Instance.prototype.save = function () { + var _this = this; + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i - 0] = arguments[_i]; + } + var callback = null; + var changes = null; + var conditions = {}; + Array.prototype.slice.call(args, 0).reverse().forEach(function (arg) { + if (typeof arg == 'function') + callback = arg; + else if (typeof arg == 'object') { + if (!changes) + changes = arg; + else + conditions = arg; + } + }); + return Bluebird.resolve().then(function () { + conditions = _.cloneDeep(conditions); + _.merge(conditions, { _id: _this._modified._id }); + conditions = _this._model.helpers.transformToDB(conditions); + if (!changes) { + var validation = _this._model.helpers.validate(_this._modified); + if (validation.failed) + return Bluebird.reject(validation.error).bind(_this).nodeify(callback); + var original = _.cloneDeep(_this._original); + var modified = _.cloneDeep(_this._modified); + changes = _this._model.helpers.diff(original, modified); + } + if (!_.keys(changes).length) + return null; + return changes; + }).then(function (changes) { + if (!changes && !_this._isNew) + return changes; + return _this._model.handlers.savingDocument(_this, changes).then(function () { return changes; }); + }).then(function (changes) { + if (!changes && !_this._isNew) + return false; + if (_this._isNew) { + return new Bluebird(function (resolve, reject) { + _this._model.collection.insertOne(_this._modified, { w: 'majority' }, function (err, doc) { + if (err) + return reject(err); + return resolve(!!doc); + }); + }); + } + else { + return new Bluebird(function (resolve, reject) { + _this._model.collection.updateOne(conditions, changes, { w: 'majority' }, function (err, changed) { + if (err) + return reject(err); + return resolve(changed); + }); + }); + } + }).then(function (changed) { + conditions = _this._model.helpers.convertToDB({ _id: _this._modified._id }); + if (!changed) { + return _.cloneDeep(_this._modified); + } + return new Bluebird(function (resolve, reject) { + _this._model.collection.findOne(conditions, function (err, latest) { + if (err) + return reject(err); + return resolve(latest); + }); + }); + }).then(function (latest) { + return _this._model.handlers.documentReceived(conditions, latest, function (value) { + _this._isPartial = false; + _this._isNew = false; + _this._original = value; + _this._modified = _.clone(value); + return _this; + }); + }).nodeify(callback); + }; + /** + * Updates this instance to match the latest document available in the backing collection + * @param {function(Error, IInstance)} callback A callback which is triggered when the update completes + * @returns {Promise} + */ + Instance.prototype.update = function (callback) { + return this.refresh(callback); + }; + /** + * Updates this instance to match the latest document available in the backing collection + * @param {function(Error, IInstance)} callback A callback which is triggered when the update completes + * @returns {Promise} + */ + Instance.prototype.refresh = function (callback) { + var _this = this; + var conditions = { _id: this._original._id }; + return Bluebird.resolve().then(function () { + return new Bluebird(function (resolve, reject) { + _this._model.collection.findOne(conditions, function (err, doc) { + if (err) + return reject(err); + return resolve(doc); + }); + }); + }).then(function (newDocument) { + if (!newDocument) { + _this._isPartial = true; + _this._isNew = true; + _this._original = _.cloneDeep(_this._modified); + return _this; + } + return _this._model.handlers.documentReceived(conditions, newDocument, function (doc) { + _this._isNew = false; + _this._isPartial = false; + _this._original = doc; + _this._modified = _.cloneDeep(doc); + return _this; + }); + }).nodeify(callback); + }; + /** + * Removes this instance's document from the backing collection + * @param {function(Error, IInstance)} callback A callback which is triggered when the operation completes + * @returns {Promise} + */ + Instance.prototype.delete = function (callback) { + return this.remove(callback); + }; + /** + * Removes this instance's document from the backing collection + * @param {function(Error, IInstance)} callback A callback which is triggered when the operation completes + * @returns {Promise} + */ + Instance.prototype.remove = function (callback) { + var _this = this; + var conditions = { _id: this._original._id }; + return Bluebird.resolve().then(function () { + if (_this._isNew) + return 0; + return new Bluebird(function (resolve, reject) { + _this._model.collection.remove(conditions, { w: 'majority' }, function (err, removed) { + if (err) + return reject(err); + return resolve(removed); + }); + }); + }).then(function (removed) { + if (removed) + return _this._model.cache.clear(conditions); + return false; + }).then(function () { + _this._isNew = true; + return _this; + }).nodeify(callback); + }; + Instance.prototype.first = function (collection, predicate) { + var _this = this; + var result = null; + _.each(collection, function (value, key) { + if (predicate.call(_this, value, key)) { + result = value; + return false; + } + }); + return result; + }; + Instance.prototype.select = function (collection, predicate) { + var _this = this; + var isArray = Array.isArray(collection); + var results = isArray ? [] : {}; + _.each(collection, function (value, key) { + if (predicate.call(_this, value, key)) { + if (isArray) + results.push(value); + else + results[key] = value; + } + }); + return results; + }; + /** + * Gets the JSON representation of this instance + * @returns {TDocument} + */ + Instance.prototype.toJSON = function () { + return this.document; + }; + /** + * Gets a string representation of this instance + * @returns {String} + */ + Instance.prototype.toString = function () { + return JSON.stringify(this.document, null, 2); + }; + Instance.schema = { + _id: MongoDB.ObjectID + }; + Instance.validators = [ + skmatc.create(function (schema) { return schema === MongoDB.ObjectID; }, function (schema, data) { + return this.assert(!data || data instanceof MongoDB.ObjectID); + }, { name: 'ObjectID validation' }) + ]; + Instance.transforms = { + _id: { + fromDB: function (value) { return value._bsontype == 'ObjectID' ? new MongoDB.ObjectID(value.id).toHexString() : value; }, + toDB: function (value) { return value && typeof value === 'string' ? new MongoDB.ObjectID(value) : value; } + } + }; + Instance.indexes = []; + return Instance; +})(); +exports.default = Instance; + +//# sourceMappingURL=../lib/Instance.js.map \ No newline at end of file diff --git a/dist/lib/Instance.js.map b/dist/lib/Instance.js.map new file mode 100644 index 0000000..f29897d --- /dev/null +++ b/dist/lib/Instance.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/Instance.ts"],"names":["Instance","Instance.constructor","Instance.document","Instance.save","Instance.update","Instance.refresh","Instance.delete","Instance.remove","Instance.first","Instance.select","Instance.toJSON","Instance.toString"],"mappings":"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","file":"lib/Instance.js","sourcesContent":["/// \r\nimport Core from './Core';\r\nimport Model from './Model';\r\nimport {Plugin} from './Plugins';\r\nimport {CacheDirector} from './CacheDirector';\r\nimport * as General from './General';\r\nimport * as ModelInterfaces from './ModelInterfaces';\r\nimport * as Index from './Index';\r\nimport {Schema} from './Schema';\r\n\r\nimport _ = require('lodash');\r\nimport MongoDB = require('mongodb');\r\nimport Bluebird = require('bluebird');\r\nimport skmatc = require('skmatc');\r\n\r\nexport default class Instance {\r\n /**\r\n * Creates a new instance which represents the given document as a type of model\r\n * @param {model.Model} model The model that the document represents\r\n * @param {TSchema} document The document which should be wrapped by this instance\r\n * @param {Boolean} isNew Whether the document is new (doesn't exist in the database) or not\r\n * @param {Boolean} isPartial Whether the document has only a subset of its fields populated\r\n * @description\r\n * This class will be subclassed automatically by Iridium to create a model specific instance\r\n * which takes advantage of some of v8's optimizations to boost performance significantly.\r\n * The instance returned by the model, and all of this instance's methods, will be of type\r\n * TInstance - which should represent the merger of TSchema and IInstance for best results.\r\n */\r\n constructor(model: Model, document: TDocument, isNew: boolean = true, isPartial: boolean = false) {\r\n this._model = model;\r\n\r\n this._isNew = !!isNew;\r\n this._isPartial = isPartial;\r\n this._original = document;\r\n this._modified = _.cloneDeep(document);\r\n\r\n _.each(model.core.plugins,(plugin: Plugin) => {\r\n if (plugin.newInstance) plugin.newInstance(this, model);\r\n });\r\n }\r\n\r\n private _isNew: boolean;\r\n private _isPartial: boolean;\r\n private _model: Model;\r\n private _original: TDocument;\r\n private _modified: TDocument;\r\n\r\n /**\r\n * Gets the underlying document representation of this instance\r\n */\r\n get document(): TDocument {\r\n return this._modified;\r\n }\r\n\r\n [name: string]: any;\r\n\r\n static onCreating: (document: { _id?: any }) => void;\r\n static onRetrieved: (document: { _id?: any }) => void;\r\n static onReady: (instance: Instance<{ _id?: any }, Instance<{ _id?: any }, any>>) => void;\r\n static onSaving: (instance: Instance<{ _id?: any }, Instance<{ _id?: any }, any>>, changes: any) => void;\r\n \r\n static collection: string;\r\n \r\n static schema: Schema = {\r\n _id: MongoDB.ObjectID\r\n };\r\n \r\n static validators: Skmatc.Validator[] = [\r\n skmatc.create(schema => schema === MongoDB.ObjectID, function(schema, data) {\r\n return this.assert(!data || data instanceof MongoDB.ObjectID);\r\n }, { name: 'ObjectID validation' })\r\n ];\r\n \r\n static transforms: { [property: string]: { fromDB: (value: any) => any; toDB: (value: any) => any; } } = {\r\n _id: {\r\n fromDB: value => value._bsontype == 'ObjectID' ? new MongoDB.ObjectID(value.id).toHexString() : value,\r\n toDB: value => value && typeof value === 'string' ? new MongoDB.ObjectID(value) : value\r\n }\r\n };\r\n \r\n static cache: CacheDirector;\r\n static indexes: (Index.Index | Index.IndexSpecification)[] = [];\r\n static identifier: {\r\n apply(fromSource: any): any;\r\n reverse(toSource: any): any;\r\n };\r\n \r\n /**\r\n * Saves any changes to this instance, using the built in diff algorithm to write the update query.\r\n * @param {function(Error, IInstance)} callback A callback which is triggered when the save operation completes\r\n * @returns {Promise}\r\n */\r\n save(callback?: General.Callback): Bluebird;\r\n /**\r\n * Saves the given changes to this instance and updates the instance to match the latest database document.\r\n * @param {Object} changes The MongoDB changes object to be used when updating this instance\r\n * @param {function(Error, IInstance)} callback A callback which is triggered when the save operation completes\r\n * @returns {Promise}\r\n */\r\n save(changes: Object, callback?: General.Callback): Bluebird;\r\n /**\r\n * Saves the given changes to this instance and updates the instance to match the latest database document.\r\n * @param {Object} conditions The conditions under which the update will take place - these will be merged with an _id query\r\n * @param {Object} changes The MongoDB changes object to be used when updating this instance\r\n * @param {function(Error, IInstance)} callback A callback which is triggered when the save operation completes\r\n * @returns {Promise}\r\n */\r\n save(conditions: Object, changes: Object, callback?: General.Callback): Bluebird;\r\n save(...args: any[]): Bluebird {\r\n var callback: General.Callback = null;\r\n var changes: any = null;\r\n var conditions: any = {};\r\n\r\n Array.prototype.slice.call(args, 0).reverse().forEach((arg) => {\r\n if (typeof arg == 'function') callback = arg;\r\n else if (typeof arg == 'object') {\r\n if (!changes) changes = arg;\r\n else conditions = arg;\r\n }\r\n });\r\n\r\n return Bluebird.resolve().then(() => {\r\n conditions = _.cloneDeep(conditions);\r\n _.merge(conditions, { _id: this._modified._id });\r\n \r\n conditions = this._model.helpers.transformToDB(conditions);\r\n \r\n if (!changes) {\r\n var validation = this._model.helpers.validate(this._modified);\r\n if (validation.failed) return Bluebird.reject(validation.error).bind(this).nodeify(callback);\r\n\r\n var original = _.cloneDeep(this._original);\r\n var modified = _.cloneDeep(this._modified);\r\n\r\n changes = this._model.helpers.diff(original, modified);\r\n }\r\n\r\n if (!_.keys(changes).length) return null;\r\n\r\n return changes;\r\n }).then((changes) => {\r\n if (!changes && !this._isNew) return changes;\r\n return this._model.handlers.savingDocument(this, changes).then(() => changes);\r\n }).then((changes) => {\r\n if (!changes && !this._isNew) return false;\r\n\r\n if (this._isNew) {\r\n return new Bluebird((resolve, reject) => {\r\n this._model.collection.insertOne(this._modified, { w: 'majority' }, (err, doc) => {\r\n if (err) return reject(err);\r\n return resolve(!!doc);\r\n });\r\n });\r\n } else {\r\n return new Bluebird((resolve: (changed: boolean) => void, reject) => {\r\n this._model.collection.updateOne(conditions, changes, { w: 'majority' }, (err: Error, changed: boolean) => {\r\n if (err) return reject(err);\r\n return resolve(changed);\r\n });\r\n });\r\n }\r\n }).then((changed: boolean) => {\r\n conditions = this._model.helpers.convertToDB({ _id: this._modified._id });\r\n if (!changed) {\r\n return _.cloneDeep(this._modified);\r\n }\r\n\r\n return new Bluebird((resolve, reject) => {\r\n this._model.collection.findOne(conditions,(err: Error, latest) => {\r\n if (err) return reject(err);\r\n return resolve(latest);\r\n });\r\n });\r\n }).then((latest: TDocument) => {\r\n return this._model.handlers.documentReceived(conditions, latest,(value) => {\r\n this._isPartial = false;\r\n this._isNew = false;\r\n this._original = value;\r\n this._modified = _.clone(value);\r\n return this;\r\n });\r\n }).nodeify(callback);\r\n }\r\n\r\n /**\r\n * Updates this instance to match the latest document available in the backing collection\r\n * @param {function(Error, IInstance)} callback A callback which is triggered when the update completes\r\n * @returns {Promise}\r\n */\r\n update(callback?: General.Callback): Bluebird {\r\n return this.refresh(callback);\r\n }\r\n\r\n /**\r\n * Updates this instance to match the latest document available in the backing collection\r\n * @param {function(Error, IInstance)} callback A callback which is triggered when the update completes\r\n * @returns {Promise}\r\n */\r\n refresh(callback?: General.Callback): Bluebird {\r\n var conditions = { _id: this._original._id };\r\n\r\n return Bluebird.resolve().then(() => {\r\n return new Bluebird((resolve, reject) => {\r\n this._model.collection.findOne(conditions,(err: Error, doc: any) => {\r\n if (err) return reject(err);\r\n return resolve(doc);\r\n });\r\n });\r\n }).then((newDocument) => {\r\n if (!newDocument) {\r\n this._isPartial = true;\r\n this._isNew = true;\r\n this._original = _.cloneDeep(this._modified);\r\n return >this;\r\n }\r\n\r\n return this._model.handlers.documentReceived(conditions, newDocument, (doc) => {\r\n this._isNew = false;\r\n this._isPartial = false;\r\n this._original = doc;\r\n this._modified = _.cloneDeep(doc);\r\n\r\n return this;\r\n });\r\n }).nodeify(callback);\r\n }\r\n\r\n /**\r\n * Removes this instance's document from the backing collection\r\n * @param {function(Error, IInstance)} callback A callback which is triggered when the operation completes\r\n * @returns {Promise}\r\n */\r\n delete(callback?: General.Callback): Bluebird {\r\n return this.remove(callback);\r\n }\r\n\r\n /**\r\n * Removes this instance's document from the backing collection\r\n * @param {function(Error, IInstance)} callback A callback which is triggered when the operation completes\r\n * @returns {Promise}\r\n */\r\n remove(callback?: General.Callback): Bluebird {\r\n var conditions = { _id: this._original._id };\r\n\r\n return Bluebird.resolve().then(() => {\r\n if (this._isNew) return 0;\r\n return new Bluebird((resolve, reject) => {\r\n this._model.collection.remove(conditions, { w: 'majority' },(err: Error, removed?: any) => {\r\n if (err) return reject(err);\r\n return resolve(removed);\r\n });\r\n });\r\n }).then((removed) => {\r\n if (removed) return this._model.cache.clear(conditions);\r\n return false;\r\n }).then(() => {\r\n this._isNew = true;\r\n return this;\r\n }).nodeify(callback);\r\n }\r\n\r\n /**\r\n * Retrieves the first element in an enumerable collection which matches the predicate\r\n * @param {any[]} collection The collection from which to retrieve the element\r\n * @param {function(any, Number): Boolean} predicate The function which determines whether to select an element\r\n * @returns {any}\r\n */\r\n first(collection: T[], predicate: General.Predicate): T;\r\n /**\r\n * Retrieves the first element in an enumerable collection which matches the predicate\r\n * @param {Object} collection The collection from which to retrieve the element\r\n * @param {function(any, String): Boolean} predicate The function which determines whether to select an element\r\n * @returns {any}\r\n */\r\n first(collection: { [key: string]: T }, predicate: General.Predicate): T;\r\n first(collection: T[]| { [key: string]: T }, predicate: General.Predicate): T {\r\n var result = null;\r\n\r\n _.each(collection,(value: T, key) => {\r\n if (predicate.call(this, value, key)) {\r\n result = value;\r\n return false;\r\n }\r\n });\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Retrieves a number of elements from an enumerable collection which match the predicate\r\n * @param {any[]} collection The collection from which elements will be plucked\r\n * @param {function(any, Number): Boolean} predicate The function which determines the elements to be plucked\r\n * @returns {any[]}\r\n */\r\n select(collection: T[], predicate: General.Predicate): T[];\r\n /**\r\n * Retrieves a number of elements from an enumerable collection which match the predicate\r\n * @param {Object} collection The collection from which elements will be plucked\r\n * @param {function(any, String): Boolean} predicate The function which determines the elements to be plucked\r\n * @returns {Object}\r\n */\r\n select(collection: { [key: string]: T }, predicate: General.Predicate): { [key: string]: T };\r\n select(collection: T[]| { [key: string]: T }, predicate: General.Predicate): any {\r\n var isArray = Array.isArray(collection);\r\n var results: any = isArray ? [] : {};\r\n\r\n _.each(collection,(value: T, key) => {\r\n if (predicate.call(this, value, key)) {\r\n if (isArray) results.push(value);\r\n else results[key] = value;\r\n }\r\n });\r\n\r\n return results;\r\n }\r\n\r\n /**\r\n * Gets the JSON representation of this instance\r\n * @returns {TDocument}\r\n */\r\n toJSON(): any {\r\n return this.document;\r\n }\r\n\r\n /**\r\n * Gets a string representation of this instance\r\n * @returns {String}\r\n */\r\n toString(): string {\r\n return JSON.stringify(this.document, null, 2);\r\n }\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/InstanceInterface.js b/dist/lib/InstanceInterface.js new file mode 100644 index 0000000..430cbbd --- /dev/null +++ b/dist/lib/InstanceInterface.js @@ -0,0 +1,3 @@ + + +//# sourceMappingURL=../lib/InstanceInterface.js.map \ No newline at end of file diff --git a/dist/lib/InstanceInterface.js.map b/dist/lib/InstanceInterface.js.map new file mode 100644 index 0000000..5661b62 --- /dev/null +++ b/dist/lib/InstanceInterface.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/InstanceInterface.ts"],"names":[],"mappings":"AAwBC","file":"lib/InstanceInterface.js","sourcesContent":["/// \r\nimport Iridium from './Core';\r\nimport {Schema} from './Schema';\r\nimport Model from './Model';\r\nimport * as Index from './Index';\r\nimport {CacheDirector} from './CacheDirector';\r\n\r\nexport default InstanceImplementation;\r\n\r\ninterface InstanceImplementation {\r\n new (model: Model, doc: TDocument, isNew?: boolean, isPartial?: boolean): TInstance;\r\n \r\n collection: string;\r\n schema: Schema;\r\n indexes?: (Index.Index | Index.IndexSpecification)[];\r\n \r\n onCreating? (document: TDocument): void;\r\n onRetrieved? (document: TDocument): void;\r\n onReady? (instance: TInstance): void;\r\n onSaving? (instance: TInstance, changes: any): void;\r\n\r\n cache?: CacheDirector;\r\n validators?: Skmatc.Validator[];\r\n transforms?: { [property: string]: { fromDB: (value: any) => any; toDB: (value: any) => any; } };\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/Middleware.js b/dist/lib/Middleware.js new file mode 100644 index 0000000..33be524 --- /dev/null +++ b/dist/lib/Middleware.js @@ -0,0 +1,3 @@ + + +//# sourceMappingURL=../lib/Middleware.js.map \ No newline at end of file diff --git a/dist/lib/Middleware.js.map b/dist/lib/Middleware.js.map new file mode 100644 index 0000000..4742047 --- /dev/null +++ b/dist/lib/Middleware.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/Middleware.ts"],"names":[],"mappings":"AAKC","file":"lib/Middleware.js","sourcesContent":["/// \r\nimport Core from './Core';\r\n\r\nexport interface MiddlewareFactory {\r\n (core: Core): T;\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/Model.js b/dist/lib/Model.js new file mode 100644 index 0000000..8256fa7 --- /dev/null +++ b/dist/lib/Model.js @@ -0,0 +1,483 @@ +var Bluebird = require('bluebird'); +var _ = require('lodash'); +var Core_1 = require('./Core'); +var Instance_1 = require('./Instance'); +var Cursor_1 = require('./Cursor'); +var ModelCache_1 = require('./ModelCache'); +var ModelHelpers_1 = require('./ModelHelpers'); +var ModelHandlers_1 = require('./ModelHandlers'); +var ModelSpecificInstance_1 = require('./ModelSpecificInstance'); +/** + * An Iridium Model which represents a structured MongoDB collection + * @class + */ +var Model = (function () { + /** + * Creates a new Iridium model representing a given ISchema and backed by a collection whose name is specified + * @param {Iridium} core The Iridium core that this model should use for database access + * @param {ModelInterfaces.InstanceImplementation} instanceType The class which will be instantiated for each document retrieved from the database + * @returns {Model} + * @constructor + */ + function Model(core, instanceType) { + this._hooks = {}; + if (!(core instanceof Core_1.default)) + throw new Error("You failed to provide a valid Iridium core for this model"); + if (typeof instanceType != 'function') + throw new Error("You failed to provide a valid instance constructor for this model"); + if (typeof instanceType.collection != 'string' || !instanceType.collection) + throw new Error("You failed to provide a valid collection name for this model"); + if (!_.isPlainObject(instanceType.schema) || instanceType.schema._id === undefined) + throw new Error("You failed to provide a valid schema for this model"); + this._core = core; + this.loadExternal(instanceType); + this.onNewModel(); + this.loadInternal(); + } + Model.prototype.loadExternal = function (instanceType) { + this._collection = instanceType.collection; + this._schema = instanceType.schema; + this._hooks = instanceType; + this._cacheDirector = instanceType.cache; + this._transforms = instanceType.transforms || {}; + this._validators = instanceType.validators || []; + this._indexes = instanceType.indexes || []; + if (instanceType.prototype instanceof Instance_1.default) + this._Instance = ModelSpecificInstance_1.default(this, instanceType); + else + this._Instance = instanceType.bind(undefined, this); + }; + Model.prototype.loadInternal = function () { + this._cache = new ModelCache_1.default(this); + this._helpers = new ModelHelpers_1.default(this); + this._handlers = new ModelHandlers_1.default(this); + }; + Model.prototype.onNewModel = function () { + var _this = this; + this._core.plugins.forEach(function (plugin) { return plugin.newModel && plugin.newModel(_this); }); + }; + Object.defineProperty(Model.prototype, "helpers", { + /** + * Provides helper methods used by Iridium for common tasks + * @returns {ModelHelpers} + */ + get: function () { + return this._helpers; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Model.prototype, "handlers", { + /** + * Provides helper methods used by Iridium for hook delegation and common processes + * @returns {ModelHandlers} + */ + get: function () { + return this._handlers; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Model.prototype, "hooks", { + /** + * Gets the even hooks subscribed on this model for a number of different state changes + * @returns {Hooks} + */ + get: function () { + return this._hooks; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Model.prototype, "schema", { + /** + * Gets the ISchema dictating the data structure represented by this model + * @public + * @returns {schema} + */ + get: function () { + return this._schema; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Model.prototype, "core", { + /** + * Gets the Iridium core that this model is associated with + * @public + * @returns {Iridium} + */ + get: function () { + return this._core; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Model.prototype, "collection", { + /** + * Gets the underlying MongoDB collection from which this model's documents are retrieved + * @public + * @returns {Collection} + */ + get: function () { + if (!this.core.connection) + throw new Error("Iridium Core not connected to a database."); + return this.core.connection.collection(this._collection); + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Model.prototype, "collectionName", { + /** + * Gets the name of the underlying MongoDB collection from which this model's documents are retrieved + * @public + */ + get: function () { + return this._collection; + }, + /** + * Sets the name of the underlying MongoDB collection from which this model's documents are retrieved + * @public + */ + set: function (value) { + this._collection = value; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Model.prototype, "cacheDirector", { + /** + * Gets the cache controller which dictates which queries will be cached, and under which key + * @public + * @returns {CacheDirector} + */ + get: function () { + return this._cacheDirector; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Model.prototype, "cache", { + /** + * Gets the cache responsible for storing objects for quick retrieval under certain conditions + * @public + * @returns {ModelCache} + */ + get: function () { + return this._cache; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Model.prototype, "Instance", { + /** + * Gets the constructor responsible for creating instances for this model + */ + get: function () { + return this._Instance; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Model.prototype, "transforms", { + get: function () { + return this._transforms; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Model.prototype, "validators", { + get: function () { + return this._validators; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Model.prototype, "indexes", { + get: function () { + return this._indexes; + }, + enumerable: true, + configurable: true + }); + Model.prototype.find = function (conditions, fields) { + conditions = conditions || {}; + fields = fields || {}; + if (!_.isPlainObject(conditions)) + conditions = { _id: conditions }; + conditions = this._helpers.convertToDB(conditions); + var cursor = this.collection.find(conditions, { + fields: fields + }); + return new Cursor_1.default(this, conditions, cursor); + }; + Model.prototype.get = function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i - 0] = arguments[_i]; + } + return this.findOne.apply(this, args); + }; + Model.prototype.findOne = function () { + var _this = this; + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i - 0] = arguments[_i]; + } + var conditions = null; + var options = null; + var callback = null; + for (var argI = 0; argI < args.length; argI++) { + if (typeof args[argI] == 'function') + callback = callback || args[argI]; + else if (_.isPlainObject(args[argI])) { + if (conditions) + options = args[argI]; + else + conditions = args[argI]; + } + else + conditions = { _id: args[argI] }; + } + conditions = conditions || {}; + options = options || {}; + _.defaults(options, { + cache: true + }); + return Bluebird.resolve().bind(this).then(function () { + conditions = _this._helpers.convertToDB(conditions); + return _this._cache.get(conditions); + }).then(function (cachedDocument) { + if (cachedDocument) + return cachedDocument; + return new Bluebird(function (resolve, reject) { + _this.collection.findOne(conditions, { + fields: options.fields, + skip: options.skip, + sort: options.sort, + limit: options.limit + }, function (err, result) { + if (err) + return reject(err); + return resolve(result); + }); + }); + }).then(function (document) { + if (!document) + return null; + return _this._handlers.documentReceived(conditions, document, function (document, isNew, isPartial) { return _this._helpers.wrapDocument(document, isNew, isPartial); }, options); + }).nodeify(callback); + }; + Model.prototype.create = function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i - 0] = arguments[_i]; + } + return this.insert.apply(this, args); + }; + Model.prototype.insert = function (objs) { + var _this = this; + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + var objects; + var options = {}; + var callback = null; + if (typeof args[0] == 'function') + callback = args[0]; + else { + options = args[0]; + callback = args[1]; + } + if (Array.isArray(objs)) + objects = objs; + else + objects = [objs]; + options = options || {}; + _.defaults(options, { + w: 'majority', + forceServerObjectId: true + }); + return Bluebird.resolve().then(function () { + var queryOptions = { w: options.w, upsert: options.upsert, new: true }; + if (options.upsert) { + var docs = _this._handlers.creatingDocuments(objects); + return docs.map(function (object) { + return new Bluebird(function (resolve, reject) { + _this.collection.findAndModify({ _id: object._id }, ["_id"], object, queryOptions, function (err, result) { + if (err) + return reject(err); + return resolve(result); + }); + }); + }); + } + else + return _this._handlers.creatingDocuments(objects).then(function (objects) { return _.chunk(objects, 1000); }).map(function (objects) { + return new Bluebird(function (resolve, reject) { + _this.collection.insertMany(objects, queryOptions, function (err, result) { + if (err) + return reject(err); + return resolve(result.ops); + }); + }); + }).then(function (results) { return _.flatten(results); }); + }).map(function (inserted) { + return _this._handlers.documentReceived(null, inserted, function (document, isNew, isPartial) { return _this._helpers.wrapDocument(document, isNew, isPartial); }, { cache: options.cache }); + }).then(function (results) { + if (Array.isArray(objs)) + return results; + return results[0]; + }).nodeify(callback); + }; + Model.prototype.update = function (conditions, changes, options, callback) { + var _this = this; + if (typeof options == 'function') { + callback = options; + options = {}; + } + options = options || {}; + if (!_.isPlainObject(conditions)) + conditions = { + _id: conditions + }; + _.defaults(options, { + w: 'majority', + multi: true + }); + return Bluebird.resolve().then(function () { + conditions = _this._helpers.convertToDB(conditions); + return new Bluebird(function (resolve, reject) { + _this.collection.updateMany(conditions, changes, options, function (err, response) { + if (err) + return reject(err); + // New MongoDB 2.6+ response type + if (response.result && response.result.nModified !== undefined) + return resolve(response.result.nModified); + // Legacy response type + return resolve(response.result.n); + }); + }); + }).nodeify(callback); + }; + Model.prototype.count = function (conds, callback) { + var _this = this; + var conditions = conds; + if (typeof conds == 'function') { + callback = conds; + conditions = {}; + } + conditions = conditions || {}; + if (!_.isPlainObject(conditions)) + conditions = { + _id: conditions + }; + return Bluebird.resolve().then(function () { + conditions = _this._helpers.convertToDB(conditions); + return new Bluebird(function (resolve, reject) { + _this.collection.count(conditions, function (err, results) { + if (err) + return reject(err); + return resolve(results); + }); + }); + }).nodeify(callback); + }; + Model.prototype.remove = function (conds, options, callback) { + var _this = this; + var conditions = conds; + if (typeof options === 'function') { + callback = options; + options = {}; + } + if (typeof conds == 'function') { + callback = conds; + options = {}; + conditions = {}; + } + conditions = conditions || {}; + options = options || {}; + _.defaults(options, { + w: 'majority' + }); + if (!_.isPlainObject(conditions)) + conditions = { + _id: conditions + }; + return Bluebird.resolve().then(function () { + conditions = _this._helpers.convertToDB(conditions); + return new Bluebird(function (resolve, reject) { + _this.collection.remove(conditions, options, function (err, response) { + if (err) + return reject(err); + return resolve(response.result.n); + }); + }); + }).then(function (count) { + if (count === 1) + _this._cache.clear(conditions); + return Bluebird.resolve(count); + }).nodeify(callback); + }; + Model.prototype.ensureIndex = function (specification, options, callback) { + var _this = this; + if (typeof options == 'function') { + callback = options; + options = {}; + } + return new Bluebird(function (resolve, reject) { + _this.collection.ensureIndex(specification, options, function (err, name) { + if (err) + return reject(err); + return resolve(name); + }); + }).nodeify(callback); + }; + /** + * Ensures that all indexes defined in the model's options are created + * @param {function(Error, String[])} callback A callback which is triggered when the operation completes + * @returns {Promise} The names of the indexes + */ + Model.prototype.ensureIndexes = function (callback) { + var _this = this; + return Bluebird.resolve(this._indexes).map(function (index) { + return _this.ensureIndex(index.spec || index, index.options || {}); + }).nodeify(callback); + }; + Model.prototype.dropIndex = function (specification, callback) { + var _this = this; + var index; + if (typeof (specification) === 'string') + index = specification; + else { + index = _(specification).map(function (direction, key) { return key + '_' + direction; }).reduce(function (x, y) { return x + '_' + y; }); + } + return new Bluebird(function (resolve, reject) { + _this.collection.dropIndex(index, function (err, result) { + if (err) + return reject(err); + return resolve(!!result.ok); + }); + }).nodeify(callback); + }; + /** + * Removes all indexes (except for _id) from the collection + * @param {function(Error, Boolean)} callback A callback which is triggered when the operation completes + * @returns {Promise} Whether the indexes were dropped + */ + Model.prototype.dropIndexes = function (callback) { + var _this = this; + return new Bluebird(function (resolve, reject) { + _this.collection.dropAllIndexes(function (err, count) { + if (err) + return reject(err); + return resolve(count); + }); + }).nodeify(callback); + }; + return Model; +})(); +exports.default = Model; + +//# sourceMappingURL=../lib/Model.js.map \ No newline at end of file diff --git a/dist/lib/Model.js.map b/dist/lib/Model.js.map new file mode 100644 index 0000000..f618f12 --- /dev/null +++ b/dist/lib/Model.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/Model.ts"],"names":["Model","Model.constructor","Model.loadExternal","Model.loadInternal","Model.onNewModel","Model.helpers","Model.handlers","Model.hooks","Model.schema","Model.core","Model.collection","Model.collectionName","Model.cacheDirector","Model.cache","Model.Instance","Model.transforms","Model.validators","Model.indexes","Model.find","Model.get","Model.findOne","Model.create","Model.insert","Model.update","Model.count","Model.remove","Model.ensureIndex","Model.ensureIndexes","Model.dropIndex","Model.dropIndexes"],"mappings":"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","file":"lib/Model.js","sourcesContent":["/// \r\nimport MongoDB = require('mongodb');\r\nimport Bluebird = require('bluebird');\r\nimport util = require('util');\r\nimport _ = require('lodash');\r\n\r\nimport Core from './Core';\r\nimport Instance from './Instance';\r\nimport {Schema} from './Schema';\r\nimport {Hooks} from './Hooks';\r\nimport {Plugin} from './Plugins';\r\nimport {Cache} from './Cache';\r\nimport {CacheDirector} from './CacheDirector';\r\nimport * as General from './General';\r\nimport Cursor from './Cursor';\r\nimport * as Index from './Index';\r\nimport * as ModelOptions from './ModelOptions';\r\n\r\nimport noOpCache from './caches/NoOpCache';\r\nimport memoryCache from './caches/MemoryCache';\r\nimport idCacheController from './cacheControllers/IDDirector';\r\n\r\nimport Omnom from './utils/Omnom';\r\nimport ModelCache from './ModelCache';\r\nimport ModelHelpers from './ModelHelpers';\r\nimport ModelHandlers from './ModelHandlers';\r\nimport * as ModelInterfaces from './ModelInterfaces';\r\nimport ModelSpecificInstance from './ModelSpecificInstance';\r\nimport InstanceImplementation from './InstanceInterface';\r\n\r\n/**\r\n * An Iridium Model which represents a structured MongoDB collection\r\n * @class\r\n */\r\nexport default class Model {\r\n /**\r\n * Creates a new Iridium model representing a given ISchema and backed by a collection whose name is specified\r\n * @param {Iridium} core The Iridium core that this model should use for database access\r\n * @param {ModelInterfaces.InstanceImplementation} instanceType The class which will be instantiated for each document retrieved from the database\r\n * @returns {Model}\r\n * @constructor\r\n */\r\n constructor(core: Core, instanceType: InstanceImplementation) {\r\n if (!(core instanceof Core)) throw new Error(\"You failed to provide a valid Iridium core for this model\");\r\n if (typeof instanceType != 'function') throw new Error(\"You failed to provide a valid instance constructor for this model\");\r\n if (typeof instanceType.collection != 'string' || !instanceType.collection) throw new Error(\"You failed to provide a valid collection name for this model\");\r\n if (!_.isPlainObject(instanceType.schema) || instanceType.schema._id === undefined) throw new Error(\"You failed to provide a valid schema for this model\");\r\n \r\n this._core = core;\r\n \r\n this.loadExternal(instanceType);\r\n this.onNewModel();\r\n this.loadInternal();\r\n }\r\n \r\n private loadExternal(instanceType: InstanceImplementation) {\r\n this._collection = instanceType.collection;\r\n this._schema = instanceType.schema;\r\n this._hooks = instanceType;\r\n this._cacheDirector = instanceType.cache;\r\n this._transforms = instanceType.transforms || {};\r\n this._validators = instanceType.validators || [];\r\n this._indexes = instanceType.indexes || [];\r\n\r\n if ((instanceType).prototype instanceof Instance)\r\n this._Instance = ModelSpecificInstance(this, instanceType);\r\n else\r\n this._Instance = instanceType.bind(undefined, this);\r\n }\r\n \r\n private loadInternal() {\r\n this._cache = new ModelCache(this);\r\n this._helpers = new ModelHelpers(this);\r\n this._handlers = new ModelHandlers(this);\r\n }\r\n \r\n private onNewModel() {\r\n this._core.plugins.forEach(plugin => plugin.newModel && plugin.newModel(this));\r\n }\r\n\r\n private _helpers: ModelHelpers;\r\n /**\r\n * Provides helper methods used by Iridium for common tasks\r\n * @returns {ModelHelpers}\r\n */\r\n get helpers(): ModelHelpers {\r\n return this._helpers;\r\n }\r\n\r\n private _handlers: ModelHandlers;\r\n /**\r\n * Provides helper methods used by Iridium for hook delegation and common processes\r\n * @returns {ModelHandlers}\r\n */\r\n get handlers(): ModelHandlers {\r\n return this._handlers;\r\n }\r\n \r\n private _hooks: Hooks = {};\r\n \r\n /**\r\n * Gets the even hooks subscribed on this model for a number of different state changes\r\n * @returns {Hooks}\r\n */\r\n get hooks(): Hooks {\r\n return this._hooks;\r\n }\r\n\r\n private _schema: Schema;\r\n /**\r\n * Gets the ISchema dictating the data structure represented by this model\r\n * @public\r\n * @returns {schema}\r\n */\r\n get schema(): Schema {\r\n return this._schema;\r\n }\r\n\r\n private _core: Core;\r\n /**\r\n * Gets the Iridium core that this model is associated with\r\n * @public\r\n * @returns {Iridium}\r\n */\r\n get core(): Core {\r\n return this._core;\r\n }\r\n\r\n private _collection: string;\r\n /**\r\n * Gets the underlying MongoDB collection from which this model's documents are retrieved\r\n * @public\r\n * @returns {Collection}\r\n */\r\n get collection(): MongoDB.Collection {\r\n if (!this.core.connection) throw new Error(\"Iridium Core not connected to a database.\");\r\n return this.core.connection.collection(this._collection);\r\n }\r\n \r\n /**\r\n * Gets the name of the underlying MongoDB collection from which this model's documents are retrieved\r\n * @public\r\n */\r\n get collectionName(): string {\r\n return this._collection;\r\n }\r\n\r\n /**\r\n * Sets the name of the underlying MongoDB collection from which this model's documents are retrieved\r\n * @public\r\n */\r\n set collectionName(value: string) {\r\n this._collection = value;\r\n }\r\n\r\n private _cacheDirector: CacheDirector;\r\n /**\r\n * Gets the cache controller which dictates which queries will be cached, and under which key\r\n * @public\r\n * @returns {CacheDirector}\r\n */\r\n get cacheDirector(): CacheDirector {\r\n return this._cacheDirector;\r\n }\r\n\r\n private _cache: ModelCache;\r\n /**\r\n * Gets the cache responsible for storing objects for quick retrieval under certain conditions\r\n * @public\r\n * @returns {ModelCache}\r\n */\r\n get cache(): ModelCache {\r\n return this._cache;\r\n }\r\n\r\n private _Instance: ModelInterfaces.ModelSpecificInstanceConstructor;\r\n\r\n /**\r\n * Gets the constructor responsible for creating instances for this model\r\n */\r\n get Instance(): ModelInterfaces.ModelSpecificInstanceConstructor {\r\n return this._Instance;\r\n }\r\n \r\n private _transforms: { [property: string]: { fromDB: (value: any) => any; toDB: (value: any) => any; } };\r\n \r\n get transforms() {\r\n return this._transforms;\r\n }\r\n \r\n private _validators: Skmatc.Validator[];\r\n \r\n get validators() {\r\n return this._validators;\r\n }\r\n \r\n private _indexes: (Index.Index | Index.IndexSpecification)[];\r\n \r\n get indexes() {\r\n return this._indexes;\r\n }\r\n \r\n /**\r\n * Retrieves all documents in the collection and wraps them as instances\r\n * @param {function(Error, TInstance[])} callback An optional callback which will be triggered when results are available\r\n * @returns {Promise}\r\n */\r\n find(): Cursor;\r\n /**\r\n * Returns all documents in the collection which match the conditions and wraps them as instances\r\n * @param {Object} conditions The MongoDB query dictating which documents to return\r\n * @returns {Promise}\r\n */\r\n find(conditions: { _id?: any, [key: string]: any } | any): Cursor;\r\n /**\r\n * Returns all documents in the collection which match the conditions\r\n * @param {Object} conditions The MongoDB query dictating which documents to return\r\n * @param {Object} fields The fields to include or exclude from the document\r\n * @returns {Promise}\r\n */\r\n find(conditions: { _id?: any, [key: string]: any } | any, fields: { [name: string]: number }): Cursor;\r\n find(conditions?: { _id?: any, [key: string]: any } | any, fields?: any): Cursor {\r\n conditions = conditions || {};\r\n fields = fields || {};\r\n\r\n if (!_.isPlainObject(conditions)) conditions = { _id: conditions };\r\n conditions = this._helpers.convertToDB(conditions);\r\n\r\n var cursor = this.collection.find(conditions, {\r\n fields: fields\r\n });\r\n\r\n return new Cursor(this, conditions, cursor);\r\n }\r\n\r\n /**\r\n * Retrieves a single document from the collection and wraps it as an instance\r\n * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available\r\n * @returns {Promise}\r\n */\r\n get(callback?: General.Callback): Bluebird;\r\n /**\r\n * Retrieves a single document from the collection with the given ID and wraps it as an instance\r\n * @param {any} id The document's unique _id field value in downstream format\r\n * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available\r\n * @returns {Promise}\r\n */\r\n get(id: any, callback?: General.Callback): Bluebird;\r\n /**\r\n * Retrieves a single document from the collection which matches the conditions\r\n * @param {Object} conditions The MongoDB query dictating which document to return\r\n * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available\r\n * @returns {Promise}\r\n */\r\n get(conditions: { _id?: any, [key: string]: any }, callback?: General.Callback): Bluebird;\r\n /**\r\n * Retrieves a single document from the collection with the given ID and wraps it as an instance\r\n * @param {any} id The document's unique _id field value in downstream format\r\n * @param {QueryOptions} options The options dictating how this function behaves\r\n * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available\r\n * @returns {Promise}\r\n */\r\n get(id: any, options: ModelOptions.QueryOptions, callback?: General.Callback): Bluebird;\r\n /**\r\n * Retrieves a single document from the collection which matches the conditions\r\n * @param {Object} conditions The MongoDB query dictating which document to return\r\n * @param {QueryOptions} options The options dictating how this function behaves\r\n * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available\r\n * @returns {Promise}\r\n */\r\n get(conditions: { _id?: any, [key: string]: any }, options: ModelOptions.QueryOptions, callback?: General.Callback): Bluebird;\r\n get(...args: any[]): Bluebird {\r\n return this.findOne.apply(this, args);\r\n }\r\n\r\n /**\r\n * Retrieves a single document from the collection and wraps it as an instance\r\n * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available\r\n * @returns {Promise}\r\n */\r\n findOne(callback?: General.Callback): Bluebird;\r\n /**\r\n * Retrieves a single document from the collection with the given ID and wraps it as an instance\r\n * @param {any} id The document's unique _id field value in downstream format\r\n * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available\r\n * @returns {Promise}\r\n */\r\n findOne(id: any, callback?: General.Callback): Bluebird;\r\n /**\r\n * Retrieves a single document from the collection which matches the conditions\r\n * @param {Object} conditions The MongoDB query dictating which document to return\r\n * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available\r\n * @returns {Promise}\r\n */\r\n findOne(conditions: { _id?: any, [key: string]: any }, callback?: General.Callback): Bluebird;\r\n /**\r\n * Retrieves a single document from the collection with the given ID and wraps it as an instance\r\n * @param {any} id The document's unique _id field value in downstream format\r\n * @param {QueryOptions} options The options dictating how this function behaves\r\n * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available\r\n * @returns {Promise}\r\n */\r\n findOne(id: any, options: ModelOptions.QueryOptions, callback?: General.Callback): Bluebird;\r\n /**\r\n * Retrieves a single document from the collection which matches the conditions\r\n * @param {Object} conditions The MongoDB query dictating which document to return\r\n * @param {QueryOptions} options The options dictating how this function behaves\r\n * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available\r\n * @returns {Promise}\r\n */\r\n findOne(conditions: { _id?: any, [key: string]: any }, options: ModelOptions.QueryOptions, callback?: General.Callback): Bluebird;\r\n findOne(...args: any[]): Bluebird {\r\n var conditions: { _id?: any, [key: string]: any } = null;\r\n var options: ModelOptions.QueryOptions = null;\r\n var callback: General.Callback = null;\r\n\r\n for (var argI = 0; argI < args.length; argI++) {\r\n if (typeof args[argI] == 'function') callback = callback || args[argI];\r\n else if (_.isPlainObject(args[argI])) {\r\n if (conditions) options = args[argI];\r\n else conditions = args[argI];\r\n }\r\n else conditions = { _id: args[argI] };\r\n }\r\n\r\n conditions = conditions || {};\r\n options = options || {};\r\n\r\n _.defaults(options, {\r\n cache: true\r\n });\r\n\r\n return Bluebird.resolve().bind(this).then(() => {\r\n conditions = this._helpers.convertToDB(conditions);\r\n\r\n return this._cache.get(conditions);\r\n }).then((cachedDocument: TDocument) => {\r\n if (cachedDocument) return cachedDocument;\r\n return new Bluebird((resolve, reject) => {\r\n this.collection.findOne(conditions, {\r\n fields: options.fields,\r\n skip: options.skip,\r\n sort: options.sort,\r\n limit: options.limit\r\n },(err, result) => {\r\n if (err) return reject(err);\r\n return resolve(result);\r\n });\r\n });\r\n }).then((document: TDocument) => {\r\n if (!document) return null;\r\n return this._handlers.documentReceived(conditions, document,(document, isNew?, isPartial?) => this._helpers.wrapDocument(document, isNew, isPartial), options);\r\n }).nodeify(callback);\r\n }\r\n\r\n /**\r\n * Inserts an object into the collection after validating it against this model's schema\r\n * @param {Object} object The object to insert into the collection\r\n * @param {function(Error, TInstance)} callback A callback which is triggered when the operation completes\r\n * @returns {Promise}\r\n */\r\n create(objects: TDocument, callback?: General.Callback): Bluebird;\r\n /**\r\n * Inserts an object into the collection after validating it against this model's schema\r\n * @param {Object} object The object to insert into the collection\r\n * @param {CreateOptions} options The options dictating how this function behaves\r\n * @param {function(Error, TInstance)} callback A callback which is triggered when the operation completes\r\n * @returns {Promise}\r\n */\r\n create(objects: TDocument, options: ModelOptions.CreateOptions, callback?: General.Callback): Bluebird;\r\n /**\r\n * Inserts the objects into the collection after validating them against this model's schema\r\n * @param {Object[]} objects The objects to insert into the collection\r\n * @param {function(Error, TInstance)} callback A callback which is triggered when the operation completes\r\n * @returns {Promise}\r\n */\r\n create(objects: TDocument[], callback?: General.Callback): Bluebird;\r\n /**\r\n * Inserts the objects into the collection after validating them against this model's schema\r\n * @param {Object[]} objects The objects to insert into the collection\r\n * @param {CreateOptions} options The options dictating how this function behaves\r\n * @param {function(Error, TInstance)} callback A callback which is triggered when the operation completes\r\n * @returns {Promise}\r\n */\r\n create(objects: TDocument[], options: ModelOptions.CreateOptions, callback?: General.Callback): Bluebird;\r\n create(...args: any[]): Bluebird {\r\n return this.insert.apply(this, args);\r\n }\r\n\r\n /**\r\n * Inserts an object into the collection after validating it against this model's schema\r\n * @param {Object} object The object to insert into the collection\r\n * @param {function(Error, TInstance)} callback A callback which is triggered when the operation completes\r\n * @returns {Promise}\r\n */\r\n insert(objects: TDocument, callback?: General.Callback): Bluebird;\r\n /**\r\n * Inserts an object into the collection after validating it against this model's schema\r\n * @param {Object} object The object to insert into the collection\r\n * @param {CreateOptions} options The options dictating how this function behaves\r\n * @param {function(Error, TInstance)} callback A callback which is triggered when the operation completes\r\n * @returns {Promise}\r\n */\r\n insert(objects: TDocument, options: ModelOptions.CreateOptions, callback?: General.Callback): Bluebird;\r\n /**\r\n * Inserts the objects into the collection after validating them against this model's schema\r\n * @param {Object[]} objects The objects to insert into the collection\r\n * @param {function(Error, TInstance[])} callback A callback which is triggered when the operation completes\r\n * @returns {Promise}\r\n */\r\n insert(objects: TDocument[], callback?: General.Callback): Bluebird;\r\n /**\r\n * Inserts the objects into the collection after validating them against this model's schema\r\n * @param {Object[]} objects The objects to insert into the collection\r\n * @param {CreateOptions} options The options dictating how this function behaves\r\n * @param {function(Error, TInstance[])} callback A callback which is triggered when the operation completes\r\n * @returns {Promise}\r\n */\r\n insert(objects: TDocument[], options: ModelOptions.CreateOptions, callback?: General.Callback): Bluebird;\r\n insert(objs: TDocument | TDocument[], ...args: any[]): Bluebird {\r\n var objects: TDocument[];\r\n var options: ModelOptions.CreateOptions = {};\r\n var callback: General.Callback = null;\r\n if (typeof args[0] == 'function') callback = args[0];\r\n else {\r\n options = args[0];\r\n callback = args[1];\r\n }\r\n\r\n if (Array.isArray(objs))\r\n objects = objs;\r\n else\r\n objects = [objs];\r\n\r\n options = options || {};\r\n _.defaults(options, {\r\n w: 'majority',\r\n forceServerObjectId: true\r\n });\r\n\r\n return Bluebird.resolve().then(() => {\r\n var queryOptions = { w: options.w, upsert: options.upsert, new: true };\r\n\r\n if (options.upsert) {\r\n var docs = this._handlers.creatingDocuments(objects);\r\n return docs.map((object: { _id: any; }) => {\r\n return new Bluebird((resolve, reject) => {\r\n this.collection.findAndModify({ _id: object._id }, [\"_id\"], object, queryOptions,(err, result) => {\r\n if (err) return reject(err);\r\n return resolve(result);\r\n });\r\n });\r\n });\r\n }\r\n else\r\n return this._handlers.creatingDocuments(objects).then(objects => _.chunk(objects, 1000)).map((objects: any[]) => {\r\n return new Bluebird((resolve, reject) => {\r\n this.collection.insertMany(objects, queryOptions,(err, result) => {\r\n if (err) return reject(err);\r\n return resolve(result.ops);\r\n });\r\n });\r\n }).then(results => _.flatten(results));\r\n }).map((inserted: any) => {\r\n return this._handlers.documentReceived(null, inserted,(document, isNew?, isPartial?) => this._helpers.wrapDocument(document, isNew, isPartial), { cache: options.cache });\r\n }).then((results: TInstance[]) => {\r\n if (Array.isArray(objs)) return results;\r\n return results[0];\r\n }).nodeify(callback);\r\n }\r\n\r\n /**\r\n * Updates the documents in the backing collection which match the conditions using the given update instructions\r\n * @param {Object} conditions The conditions which determine which documents will be updated\r\n * @param {Object} changes The changes to make to the documents\r\n * @param {function(Error, Number)} callback A callback which is triggered when the operation completes\r\n */\r\n update(conditions: { _id?: any, [key: string]: any } | any, changes: any, callback?: General.Callback): Bluebird;\r\n /**\r\n * Updates the documents in the backing collection which match the conditions using the given update instructions\r\n * @param {Object} conditions The conditions which determine which documents will be updated\r\n * @param {Object} changes The changes to make to the documents\r\n * @param {UpdateOptions} options The options which dictate how this function behaves\r\n * @param {function(Error, Number)} callback A callback which is triggered when the operation completes\r\n */\r\n update(conditions: { _id?: any, [key: string]: any } | any, changes: any, options: ModelOptions.UpdateOptions, callback?: General.Callback): Bluebird;\r\n update(conditions: { _id?: any, [key: string]: any } | any, changes: any, options?: ModelOptions.UpdateOptions, callback?: General.Callback): Bluebird {\r\n if (typeof options == 'function') {\r\n callback = >options;\r\n options = {};\r\n }\r\n \r\n options = options || {};\r\n\r\n if (!_.isPlainObject(conditions)) conditions = {\r\n _id: conditions\r\n };\r\n\r\n _.defaults(options, {\r\n w: 'majority',\r\n multi: true\r\n });\r\n\r\n return Bluebird.resolve().then(() => {\r\n conditions = this._helpers.convertToDB(conditions);\r\n\r\n return new Bluebird((resolve, reject) => {\r\n this.collection.updateMany(conditions, changes, options,(err, response) => {\r\n if (err) return reject(err);\r\n\r\n // New MongoDB 2.6+ response type\r\n if (response.result && response.result.nModified !== undefined) return resolve(response.result.nModified);\r\n\r\n // Legacy response type\r\n return resolve(response.result.n);\r\n });\r\n })\r\n }).nodeify(callback);\r\n }\r\n\r\n /**\r\n * Counts the number of documents in the collection\r\n * @param {function(Error, Number)} callback A callback which is triggered when the operation completes\r\n * @returns {Promise}\r\n */\r\n count(callback?: General.Callback): Bluebird;\r\n /**\r\n * Counts the number of documents in the collection which match the conditions provided\r\n * @param {Object} conditions The conditions which determine whether an object is counted or not\r\n * @param {function(Error, Number)} callback A callback which is triggered when the operation completes\r\n * @returns {Promise}\r\n */\r\n count(conditions: { _id?: any, [key: string]: any } | any, callback?: General.Callback): Bluebird;\r\n count(conds?: any, callback?: General.Callback): Bluebird {\r\n var conditions: { _id?: any, [key: string]: any } = <{ _id?: any, [key: string]: any }>conds;\r\n if (typeof conds == 'function') {\r\n callback = >conds;\r\n conditions = {};\r\n }\r\n\r\n conditions = conditions || {};\r\n\r\n if (!_.isPlainObject(conditions)) conditions = {\r\n _id: conditions\r\n };\r\n\r\n return Bluebird.resolve().then(() => {\r\n conditions = this._helpers.convertToDB(conditions);\r\n\r\n return new Bluebird((resolve, reject) => {\r\n this.collection.count(conditions,(err, results) => {\r\n if (err) return reject(err);\r\n return resolve(results);\r\n });\r\n });\r\n }).nodeify(callback);\r\n }\r\n\r\n /**\r\n * Removes all documents from the collection\r\n * @param {function(Error, Number)} callback A callback which is triggered when the operation completes\r\n * @returns {Promise}\r\n */\r\n remove(callback?: General.Callback): Bluebird;\r\n /**\r\n * Removes all documents from the collection which match the conditions\r\n * @param {Object} conditions The conditions determining whether an object is removed or not\r\n * @param {function(Error, Number)} callback A callback which is triggered when the operation completes\r\n * @returns {Promise}\r\n */\r\n remove(conditions: { _id?: any, [key: string]: any } | any, callback?: General.Callback): Bluebird;\r\n /**\r\n * Removes all documents from the collection which match the conditions\r\n * @param {Object} conditions The conditions determining whether an object is removed or not\r\n * @param {Object} options The options controlling the way in which the function behaves\r\n * @param {function(Error, Number)} callback A callback which is triggered when the operation completes\r\n * @returns {Promise}\r\n */\r\n remove(conditions: { _id?: any, [key: string]: any }, options: ModelOptions.RemoveOptions, callback?: General.Callback): Bluebird;\r\n remove(conds?: any, options?: ModelOptions.RemoveOptions, callback?: General.Callback): Bluebird {\r\n var conditions: { _id?: any, [key: string]: any } = <{ _id?: any, [key: string]: any }>conds;\r\n \r\n if (typeof options === 'function') {\r\n callback = >options;\r\n options = {};\r\n }\r\n \r\n if (typeof conds == 'function') {\r\n callback = >conds;\r\n options = {};\r\n conditions = {};\r\n }\r\n\r\n conditions = conditions || {};\r\n options = options || {};\r\n \r\n _.defaults(options, {\r\n w: 'majority'\r\n });\r\n\r\n if (!_.isPlainObject(conditions)) conditions = {\r\n _id: conditions\r\n };\r\n\r\n return Bluebird.resolve().then(() => {\r\n conditions = this._helpers.convertToDB(conditions);\r\n\r\n return new Bluebird((resolve, reject) => {\r\n this.collection.remove(conditions, options,(err, response) => {\r\n if (err) return reject(err);\r\n return resolve(response.result.n);\r\n });\r\n });\r\n }).then((count) => {\r\n if (count === 1) this._cache.clear(conditions);\r\n return Bluebird.resolve(count);\r\n }).nodeify(callback);\r\n }\r\n\r\n /**\r\n * Ensures that the given index is created for the collection\r\n * @param {Object} specification The index specification object used by MongoDB\r\n * @param {function(Error, String)} callback A callback which is triggered when the operation completes\r\n * @returns {Promise} The name of the index\r\n */\r\n ensureIndex(specification: Index.IndexSpecification, callback?: General.Callback): Bluebird;\r\n /**\r\n * Ensures that the given index is created for the collection\r\n * @param {Object} specification The index specification object used by MongoDB\r\n * @param {MongoDB.IndexOptions} options The options dictating how the index is created and behaves\r\n * @param {function(Error, String)} callback A callback which is triggered when the operation completes\r\n * @returns {Promise} The name of the index\r\n */\r\n ensureIndex(specification: Index.IndexSpecification, options: MongoDB.IndexOptions, callback?: General.Callback): Bluebird;\r\n ensureIndex(specification: Index.IndexSpecification, options?: MongoDB.IndexOptions, callback?: General.Callback): Bluebird {\r\n if (typeof options == 'function') {\r\n callback = >options;\r\n options = {};\r\n }\r\n\r\n return new Bluebird((resolve, reject) => {\r\n this.collection.ensureIndex(specification, options,(err, name: any) => {\r\n if (err) return reject(err);\r\n return resolve(name);\r\n });\r\n }).nodeify(callback);\r\n }\r\n\r\n /**\r\n * Ensures that all indexes defined in the model's options are created\r\n * @param {function(Error, String[])} callback A callback which is triggered when the operation completes\r\n * @returns {Promise} The names of the indexes\r\n */\r\n ensureIndexes(callback?: General.Callback): Bluebird {\r\n return Bluebird.resolve(this._indexes).map((index: Index.Index | Index.IndexSpecification) => {\r\n return this.ensureIndex((index).spec || index,(index).options || {});\r\n }).nodeify(callback);\r\n }\r\n\r\n /**\r\n * Drops the index with the specified name if it exists in the collection\r\n * @param {String} name The name of the index to remove\r\n * @param {function(Error, Boolean)} callback A callback which is triggered when the operation completes\r\n * @returns {Promise} Whether the index was dropped\r\n */\r\n dropIndex(name: string, callback?: General.Callback): Bluebird;\r\n /**\r\n * Drops the index if it exists in the collection\r\n * @param {IndexSpecification} index The index to remove\r\n * @param {function(Error, Boolean)} callback A callback which is triggered when the operation completes\r\n * @returns {Promise} Whether the index was dropped\r\n */\r\n dropIndex(index: Index.IndexSpecification, callback?: General.Callback): Bluebird;\r\n dropIndex(specification: string | Index.IndexSpecification, callback?: General.Callback): Bluebird {\r\n var index: string;\r\n\r\n if (typeof (specification) === 'string') index = specification;\r\n else {\r\n index = _(specification).map((direction, key) => key + '_' + direction).reduce((x, y) => x + '_' + y);\r\n }\r\n\r\n return new Bluebird((resolve, reject) => {\r\n this.collection.dropIndex(index,(err, result: { ok: number }) => {\r\n if (err) return reject(err);\r\n return resolve(!!result.ok);\r\n });\r\n }).nodeify(callback);\r\n }\r\n\r\n /**\r\n * Removes all indexes (except for _id) from the collection\r\n * @param {function(Error, Boolean)} callback A callback which is triggered when the operation completes\r\n * @returns {Promise} Whether the indexes were dropped\r\n */\r\n dropIndexes(callback?: General.Callback): Bluebird {\r\n return new Bluebird((resolve, reject) => {\r\n this.collection.dropAllIndexes((err, count) => {\r\n if (err) return reject(err);\r\n return resolve(count);\r\n });\r\n }).nodeify(callback);\r\n }\r\n}\r\n"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/ModelCache.js b/dist/lib/ModelCache.js new file mode 100644 index 0000000..3f69829 --- /dev/null +++ b/dist/lib/ModelCache.js @@ -0,0 +1,25 @@ +var Bluebird = require('bluebird'); +var ModelCache = (function () { + function ModelCache(model) { + this.model = model; + } + ModelCache.prototype.set = function (value) { + if (!this.model.cacheDirector || !this.model.cacheDirector.valid(value)) + return; + this.model.core.cache.set(this.model.cacheDirector.buildKey(value), value); + }; + ModelCache.prototype.get = function (conditions) { + if (!this.model.cacheDirector || !this.model.cacheDirector.validQuery(conditions)) + return Bluebird.resolve(null); + return this.model.core.cache.get(this.model.cacheDirector.buildQueryKey(conditions)); + }; + ModelCache.prototype.clear = function (conditions) { + if (!this.model.cacheDirector || !this.model.cacheDirector.validQuery(conditions)) + return; + this.model.core.cache.clear(this.model.cacheDirector.buildQueryKey(conditions)); + }; + return ModelCache; +})(); +exports.default = ModelCache; + +//# sourceMappingURL=../lib/ModelCache.js.map \ No newline at end of file diff --git a/dist/lib/ModelCache.js.map b/dist/lib/ModelCache.js.map new file mode 100644 index 0000000..d549311 --- /dev/null +++ b/dist/lib/ModelCache.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/ModelCache.ts"],"names":["ModelCache","ModelCache.constructor","ModelCache.set","ModelCache.get","ModelCache.clear"],"mappings":"AAEA,IAAO,QAAQ,WAAW,UAAU,CAAC,CAAC;AAEtC;IACIA,oBAAmBA,KAAqBA;QAArBC,UAAKA,GAALA,KAAKA,CAAgBA;IAExCA,CAACA;IAEDD,wBAAGA,GAAHA,UAAOA,KAAQA;QACXE,EAAEA,CAACA,CAACA,CAACA,IAAIA,CAACA,KAAKA,CAACA,aAAaA,IAAIA,CAACA,IAAIA,CAACA,KAAKA,CAACA,aAAaA,CAACA,KAAKA,CAACA,KAAKA,CAACA,CAACA;YAACA,MAAMA,CAACA;QAChFA,IAAIA,CAACA,KAAKA,CAACA,IAAIA,CAACA,KAAKA,CAACA,GAAGA,CAACA,IAAIA,CAACA,KAAKA,CAACA,aAAaA,CAACA,QAAQA,CAACA,KAAKA,CAACA,EAAEA,KAAKA,CAACA,CAACA;IAC/EA,CAACA;IAEDF,wBAAGA,GAAHA,UAAOA,UAAeA;QAClBG,EAAEA,CAACA,CAACA,CAACA,IAAIA,CAACA,KAAKA,CAACA,aAAaA,IAAIA,CAACA,IAAIA,CAACA,KAAKA,CAACA,aAAaA,CAACA,UAAUA,CAACA,UAAUA,CAACA,CAACA;YAACA,MAAMA,CAACA,QAAQA,CAACA,OAAOA,CAAIA,IAAIA,CAACA,CAACA;QACpHA,MAAMA,CAACA,IAAIA,CAACA,KAAKA,CAACA,IAAIA,CAACA,KAAKA,CAACA,GAAGA,CAAIA,IAAIA,CAACA,KAAKA,CAACA,aAAaA,CAACA,aAAaA,CAACA,UAAUA,CAACA,CAACA,CAACA;IAC5FA,CAACA;IAEDH,0BAAKA,GAALA,UAAMA,UAAeA;QACjBI,EAAEA,CAACA,CAACA,CAACA,IAAIA,CAACA,KAAKA,CAACA,aAAaA,IAAIA,CAACA,IAAIA,CAACA,KAAKA,CAACA,aAAaA,CAACA,UAAUA,CAACA,UAAUA,CAACA,CAACA;YAACA,MAAMA,CAACA;QAC1FA,IAAIA,CAACA,KAAKA,CAACA,IAAIA,CAACA,KAAKA,CAACA,KAAKA,CAACA,IAAIA,CAACA,KAAKA,CAACA,aAAaA,CAACA,aAAaA,CAACA,UAAUA,CAACA,CAACA,CAACA;IACpFA,CAACA;IACLJ,iBAACA;AAADA,CAnBA,AAmBCA,IAAA;AAnBD,4BAmBC,CAAA","file":"lib/ModelCache.js","sourcesContent":["/// \r\nimport Model from './Model';\r\nimport Bluebird = require('bluebird');\r\n\r\nexport default class ModelCache {\r\n constructor(public model: Model) {\r\n\r\n }\r\n\r\n set(value: T): void {\r\n if (!this.model.cacheDirector || !this.model.cacheDirector.valid(value)) return;\r\n this.model.core.cache.set(this.model.cacheDirector.buildKey(value), value);\r\n }\r\n\r\n get(conditions: any): Bluebird {\r\n if (!this.model.cacheDirector || !this.model.cacheDirector.validQuery(conditions)) return Bluebird.resolve(null);\r\n return this.model.core.cache.get(this.model.cacheDirector.buildQueryKey(conditions));\r\n }\r\n\r\n clear(conditions: any): void {\r\n if (!this.model.cacheDirector || !this.model.cacheDirector.validQuery(conditions)) return;\r\n this.model.core.cache.clear(this.model.cacheDirector.buildQueryKey(conditions));\r\n }\r\n}\r\n"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/ModelHandlers.js b/dist/lib/ModelHandlers.js new file mode 100644 index 0000000..0ab058d --- /dev/null +++ b/dist/lib/ModelHandlers.js @@ -0,0 +1,57 @@ +var _ = require('lodash'); +var Bluebird = require('bluebird'); +var ModelHandlers = (function () { + function ModelHandlers(model) { + this.model = model; + } + ModelHandlers.prototype.documentReceived = function (conditions, result, wrapper, options) { + var _this = this; + if (options === void 0) { options = {}; } + _.defaults(options, { + cache: true, + partial: false + }); + return Bluebird.resolve(result).then(function (target) { + return Bluebird.resolve().then(function () { + // Cache the document if caching is enabled + if (_this.model.core.cache && options.cache && !options.fields) { + _this.model.cache.set(target); // Does not block execution pipeline - fire and forget + } + // Trigger the received hook + if (_this.model.hooks.onRetrieved) + _this.model.hooks.onRetrieved(target); + // Wrap the document and trigger the ready hook + var wrapped = wrapper(target, false, !!options.fields); + if (_this.model.hooks.onReady && wrapped instanceof _this.model.Instance) + _this.model.hooks.onReady(wrapped); + return wrapped; + }); + }); + }; + ModelHandlers.prototype.creatingDocuments = function (documents) { + var _this = this; + return Bluebird.all(documents.map(function (document) { + return Bluebird.resolve().then(function () { + if (_this.model.hooks.onCreating) + _this.model.hooks.onCreating(document); + document = _this.model.helpers.convertToDB(document); + var validation = _this.model.helpers.validate(document); + if (validation.failed) + return Bluebird.reject(validation.error); + return document; + }); + })); + }; + ModelHandlers.prototype.savingDocument = function (instance, changes) { + var _this = this; + return Bluebird.resolve().then(function () { + if (_this.model.hooks.onSaving) + _this.model.hooks.onSaving(instance, changes); + return instance; + }); + }; + return ModelHandlers; +})(); +exports.default = ModelHandlers; + +//# sourceMappingURL=../lib/ModelHandlers.js.map \ No newline at end of file diff --git a/dist/lib/ModelHandlers.js.map b/dist/lib/ModelHandlers.js.map new file mode 100644 index 0000000..fded2bf --- /dev/null +++ b/dist/lib/ModelHandlers.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/ModelHandlers.ts"],"names":["ModelHandlers","ModelHandlers.constructor","ModelHandlers.documentReceived","ModelHandlers.creatingDocuments","ModelHandlers.savingDocument"],"mappings":"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","file":"lib/ModelHandlers.js","sourcesContent":["/// \r\nimport Core from './Core';\r\nimport {Schema} from './Schema';\r\nimport Model from './Model';\r\nimport ModelCache from './ModelCache';\r\nimport * as ModelOptions from './ModelOptions';\r\n\r\nimport _ = require('lodash');\r\nimport MongoDB = require('mongodb');\r\nimport Bluebird = require('bluebird');\r\n\r\nexport default class ModelHandlers {\r\n constructor(public model: Model) {\r\n\r\n }\r\n\r\n documentReceived(conditions: any,\r\n result: TDocument,\r\n wrapper: (document: TDocument, isNew?: boolean, isPartial?: boolean) => TResult,\r\n options: ModelOptions.QueryOptions = {}): Bluebird {\r\n _.defaults(options, {\r\n cache: true,\r\n partial: false\r\n });\r\n\r\n return Bluebird.resolve(result).then((target: any) => {\r\n return >Bluebird.resolve().then(() => {\r\n\r\n // Cache the document if caching is enabled\r\n if (this.model.core.cache && options.cache && !options.fields) {\r\n this.model.cache.set(target); // Does not block execution pipeline - fire and forget\r\n }\r\n \r\n // Trigger the received hook\r\n if (this.model.hooks.onRetrieved) this.model.hooks.onRetrieved(target);\r\n\r\n // Wrap the document and trigger the ready hook\r\n let wrapped: TResult = wrapper(target, false, !!options.fields);\r\n\r\n if (this.model.hooks.onReady && wrapped instanceof this.model.Instance) this.model.hooks.onReady(wrapped);\r\n return wrapped;\r\n });\r\n });\r\n }\r\n\r\n creatingDocuments(documents: TDocument[]): Bluebird {\r\n return Bluebird.all(documents.map((document: any) => {\r\n return Bluebird.resolve().then(() => {\r\n if (this.model.hooks.onCreating) this.model.hooks.onCreating(document);\r\n document = this.model.helpers.convertToDB(document);\r\n let validation: Skmatc.Result = this.model.helpers.validate(document);\r\n if (validation.failed) return Bluebird.reject(validation.error);\r\n \r\n return document;\r\n });\r\n }));\r\n }\r\n\r\n savingDocument(instance: TInstance, changes: any): Bluebird {\r\n return Bluebird.resolve().then(() => {\r\n if (this.model.hooks.onSaving) this.model.hooks.onSaving(instance, changes);\r\n return instance;\r\n });\r\n }\r\n}\r\n"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/ModelHelpers.js b/dist/lib/ModelHelpers.js new file mode 100644 index 0000000..a8d4e9c --- /dev/null +++ b/dist/lib/ModelHelpers.js @@ -0,0 +1,65 @@ +var skmatc = require('skmatc'); +var Omnom_1 = require('./utils/Omnom'); +var _ = require('lodash'); +var ModelHelpers = (function () { + function ModelHelpers(model) { + var _this = this; + this.model = model; + this._validator = new skmatc(model.schema); + model.validators.forEach(function (validator) { return _this._validator.register(validator); }); + } + /** + * Validates a document to ensure that it matches the model's ISchema requirements + * @param {any} document The document to validate against the ISchema + * @returns {SkmatcCore.IResult} The result of the validation + */ + ModelHelpers.prototype.validate = function (document) { + return this._validator.validate(document); + }; + /** + * Wraps the given document in an instance wrapper for use throughout the application + * @param {any} document The document to be wrapped as an instance + * @param {Boolean} isNew Whether the instance originated from the database or was created by the application + * @param {Boolean} isPartial Whether the document supplied contains all information present in the database + * @returns {any} An instance which wraps this document + */ + ModelHelpers.prototype.wrapDocument = function (document, isNew, isPartial) { + return new this.model.Instance(document, isNew, isPartial); + }; + /** + * Converts the given document to its database form into a form + * using the transforms defined on the model. + * @param {any} document The document to be converted + * @returns {any} A new document cloned from the original and transformed + */ + ModelHelpers.prototype.transformToDB = function (document) { + for (var property in this.model.transforms) + if (document.hasOwnProperty(property)) + document[property] = this.model.transforms[property].toDB(document[property]); + return document; + }; + /** + * Converts the given document to its database form into a form + * using the transforms defined on the model. + * @param {any} document The document to be converted + * @returns {any} A new document cloned from the original and transformed + */ + ModelHelpers.prototype.convertToDB = function (document) { + var doc = _.cloneDeep(document); + return this.transformToDB(doc); + }; + /** + * Performs a diff operation between two documents and creates a MongoDB changes object to represent the differences + * @param {any} original The original document prior to changes being made + * @param {any} modified The document after changes were made + */ + ModelHelpers.prototype.diff = function (original, modified) { + var omnom = new Omnom_1.default(); + omnom.diff(original, modified); + return omnom.changes; + }; + return ModelHelpers; +})(); +exports.default = ModelHelpers; + +//# sourceMappingURL=../lib/ModelHelpers.js.map \ No newline at end of file diff --git a/dist/lib/ModelHelpers.js.map b/dist/lib/ModelHelpers.js.map new file mode 100644 index 0000000..051c1b1 --- /dev/null +++ b/dist/lib/ModelHelpers.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/ModelHelpers.ts"],"names":["ModelHelpers","ModelHelpers.constructor","ModelHelpers.validate","ModelHelpers.wrapDocument","ModelHelpers.transformToDB","ModelHelpers.convertToDB","ModelHelpers.diff"],"mappings":"AAGA,IAAO,MAAM,WAAW,QAAQ,CAAC,CAAC;AAClC,sBAAkB,eAAe,CAAC,CAAA;AAClC,IAAO,CAAC,WAAW,QAAQ,CAAC,CAAC;AAG7B;IACIA,sBAAmBA,KAAkCA;QADzDC,iBA8DCA;QA7DsBA,UAAKA,GAALA,KAAKA,CAA6BA;QACjDA,IAAIA,CAACA,UAAUA,GAAGA,IAAIA,MAAMA,CAACA,KAAKA,CAACA,MAAMA,CAACA,CAACA;QAC3CA,KAAKA,CAACA,UAAUA,CAACA,OAAOA,CAACA,UAAAA,SAASA,IAAIA,OAAAA,KAAIA,CAACA,UAAUA,CAACA,QAAQA,CAACA,SAASA,CAACA,EAAnCA,CAAmCA,CAACA,CAACA;IAC/EA,CAACA;IAIDD;;;;OAIGA;IACHA,+BAAQA,GAARA,UAASA,QAAmBA;QACxBE,MAAMA,CAACA,IAAIA,CAACA,UAAUA,CAACA,QAAQA,CAACA,QAAQA,CAACA,CAACA;IAC9CA,CAACA;IAEDF;;;;;;OAMGA;IACHA,mCAAYA,GAAZA,UAAaA,QAAmBA,EAAEA,KAAeA,EAAEA,SAAmBA;QAClEG,MAAMA,CAACA,IAAIA,IAAIA,CAACA,KAAKA,CAACA,QAAQA,CAACA,QAAQA,EAAEA,KAAKA,EAAEA,SAASA,CAACA,CAACA;IAC/DA,CAACA;IAEDH;;;;;OAKGA;IACHA,oCAAaA,GAAbA,UAAiBA,QAAWA;QACxBI,GAAGA,CAACA,CAACA,GAAGA,CAACA,QAAQA,IAAIA,IAAIA,CAACA,KAAKA,CAACA,UAAUA,CAACA;YACvCA,EAAEA,CAAAA,CAACA,QAAQA,CAACA,cAAcA,CAACA,QAAQA,CAACA,CAACA;gBACjCA,QAAQA,CAACA,QAAQA,CAACA,GAAGA,IAAIA,CAACA,KAAKA,CAACA,UAAUA,CAACA,QAAQA,CAACA,CAACA,IAAIA,CAACA,QAAQA,CAACA,QAAQA,CAACA,CAACA,CAACA;QACtFA,MAAMA,CAACA,QAAQA,CAACA;IACpBA,CAACA;IAEDJ;;;;;OAKGA;IACHA,kCAAWA,GAAXA,UAAeA,QAAWA;QACtBK,IAAIA,GAAGA,GAAMA,CAACA,CAACA,SAASA,CAACA,QAAQA,CAACA,CAACA;QACnCA,MAAMA,CAACA,IAAIA,CAACA,aAAaA,CAACA,GAAGA,CAACA,CAACA;IACnCA,CAACA;IAEDL;;;;OAIGA;IACHA,2BAAIA,GAAJA,UAAKA,QAAmBA,EAAEA,QAAmBA;QACzCM,IAAIA,KAAKA,GAAGA,IAAIA,eAAKA,EAAEA,CAACA;QACxBA,KAAKA,CAACA,IAAIA,CAACA,QAAQA,EAAEA,QAAQA,CAACA,CAACA;QAC/BA,MAAMA,CAACA,KAAKA,CAACA,OAAOA,CAACA;IACzBA,CAACA;IACLN,mBAACA;AAADA,CA9DA,AA8DCA,IAAA;AA9DD,8BA8DC,CAAA","file":"lib/ModelHelpers.js","sourcesContent":["/// \r\nimport MongoDB = require('mongodb');\r\nimport Model from './Model';\r\nimport skmatc = require('skmatc');\r\nimport Omnom from './utils/Omnom';\r\nimport _ = require('lodash');\r\nimport Bluebird = require('bluebird');\r\n\r\nexport default class ModelHelpers {\r\n constructor(public model: Model) {\r\n this._validator = new skmatc(model.schema);\r\n model.validators.forEach(validator => this._validator.register(validator));\r\n }\r\n\r\n private _validator: Skmatc.Skmatc;\r\n\r\n /**\r\n * Validates a document to ensure that it matches the model's ISchema requirements\r\n * @param {any} document The document to validate against the ISchema\r\n * @returns {SkmatcCore.IResult} The result of the validation\r\n */\r\n validate(document: TDocument): Skmatc.Result {\r\n return this._validator.validate(document);\r\n }\r\n\r\n /**\r\n * Wraps the given document in an instance wrapper for use throughout the application\r\n * @param {any} document The document to be wrapped as an instance\r\n * @param {Boolean} isNew Whether the instance originated from the database or was created by the application\r\n * @param {Boolean} isPartial Whether the document supplied contains all information present in the database\r\n * @returns {any} An instance which wraps this document\r\n */\r\n wrapDocument(document: TDocument, isNew?: boolean, isPartial?: boolean): TInstance {\r\n return new this.model.Instance(document, isNew, isPartial);\r\n }\r\n \r\n /**\r\n * Converts the given document to its database form into a form\r\n * using the transforms defined on the model.\r\n * @param {any} document The document to be converted\r\n * @returns {any} A new document cloned from the original and transformed\r\n */\r\n transformToDB(document: T): T {\r\n for (var property in this.model.transforms)\r\n if(document.hasOwnProperty(property))\r\n document[property] = this.model.transforms[property].toDB(document[property]);\r\n return document;\r\n }\r\n \r\n /**\r\n * Converts the given document to its database form into a form\r\n * using the transforms defined on the model.\r\n * @param {any} document The document to be converted\r\n * @returns {any} A new document cloned from the original and transformed\r\n */\r\n convertToDB(document: T): T {\r\n var doc: T = _.cloneDeep(document);\r\n return this.transformToDB(doc);\r\n }\r\n\r\n /**\r\n * Performs a diff operation between two documents and creates a MongoDB changes object to represent the differences\r\n * @param {any} original The original document prior to changes being made\r\n * @param {any} modified The document after changes were made\r\n */\r\n diff(original: TDocument, modified: TDocument): any {\r\n var omnom = new Omnom();\r\n omnom.diff(original, modified);\r\n return omnom.changes;\r\n }\r\n}\r\n"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/ModelInterfaces.js b/dist/lib/ModelInterfaces.js new file mode 100644 index 0000000..f833bad --- /dev/null +++ b/dist/lib/ModelInterfaces.js @@ -0,0 +1,3 @@ +/// + +//# sourceMappingURL=../lib/ModelInterfaces.js.map \ No newline at end of file diff --git a/dist/lib/ModelInterfaces.js.map b/dist/lib/ModelInterfaces.js.map new file mode 100644 index 0000000..f6543fd --- /dev/null +++ b/dist/lib/ModelInterfaces.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/ModelInterfaces.ts"],"names":[],"mappings":"AAAA,AACA,4CAD4C;AAG3C","file":"lib/ModelInterfaces.js","sourcesContent":["/// \r\nexport interface ModelSpecificInstanceConstructor {\r\n new (doc: TDocument, isNew?: boolean, isPartial?: boolean): TInstance;\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/ModelOptions.js b/dist/lib/ModelOptions.js new file mode 100644 index 0000000..d024eab --- /dev/null +++ b/dist/lib/ModelOptions.js @@ -0,0 +1,3 @@ + + +//# sourceMappingURL=../lib/ModelOptions.js.map \ No newline at end of file diff --git a/dist/lib/ModelOptions.js.map b/dist/lib/ModelOptions.js.map new file mode 100644 index 0000000..dd4b25b --- /dev/null +++ b/dist/lib/ModelOptions.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/ModelOptions.ts"],"names":[],"mappings":"AAyCC","file":"lib/ModelOptions.js","sourcesContent":["/// \r\nimport MongoDB = require('mongodb');\r\nimport Index = require('./Index');\r\nimport Hooks = require('./Hooks');\r\nimport {CacheDirector} from './CacheDirector';\r\nimport * as General from './General';\r\n\r\nexport interface QueryOptions {\r\n cache?: boolean;\r\n fields?: { [name: string]: number };\r\n limit?: number;\r\n skip?: number;\r\n sort?: Index.IndexSpecification;\r\n}\r\n\r\nexport interface CreateOptions {\r\n w?: any;\r\n wtimeout?: number;\r\n j?: number;\r\n serializeFunctions?: boolean;\r\n forceServerObjectId?: boolean;\r\n upsert?: boolean;\r\n cache?: boolean;\r\n}\r\n\r\nexport interface UpdateOptions {\r\n w?: any;\r\n wtimeout?: number;\r\n j?: boolean;\r\n upsert?: boolean;\r\n}\r\n\r\nexport interface RemoveOptions {\r\n w?: any;\r\n wtimeout?: number;\r\n j?: boolean;\r\n single?: boolean;\r\n}\r\n\r\nexport interface Transforms {\r\n [property: string]: { fromDB: (value: any) => any; toDB: (value: any) => any; };\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/ModelSpecificInstance.js b/dist/lib/ModelSpecificInstance.js new file mode 100644 index 0000000..2be7304 --- /dev/null +++ b/dist/lib/ModelSpecificInstance.js @@ -0,0 +1,35 @@ +var util = require('util'); +var _ = require('lodash'); +function ModelSpecificInstance(model, instanceType) { + var constructor = function (doc, isNew, isPartial) { + instanceType.call(this, model, doc, isNew, isPartial); + }; + util.inherits(constructor, instanceType); + _.each(Object.keys(model.schema), function (property) { + if (model.transforms.hasOwnProperty(property)) { + return Object.defineProperty(constructor.prototype, property, { + get: function () { + return model.transforms[property].fromDB(this._modified._id); + }, + set: function (value) { + this._modified._id = model.transforms[property].toDB(value); + }, + enumerable: true, + configurable: true + }); + } + Object.defineProperty(constructor.prototype, property, { + get: function () { + return this._modified[property]; + }, + set: function (value) { + this._modified[property] = value; + }, + enumerable: true + }); + }); + return constructor; +} +exports.default = ModelSpecificInstance; + +//# sourceMappingURL=../lib/ModelSpecificInstance.js.map \ No newline at end of file diff --git a/dist/lib/ModelSpecificInstance.js.map b/dist/lib/ModelSpecificInstance.js.map new file mode 100644 index 0000000..aadeb31 --- /dev/null +++ b/dist/lib/ModelSpecificInstance.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/ModelSpecificInstance.ts"],"names":["ModelSpecificInstance"],"mappings":"AAGA,IAAO,IAAI,WAAW,MAAM,CAAC,CAAC;AAC9B,IAAO,CAAC,WAAW,QAAQ,CAAC,CAAC;AAE7B,+BAA0F,KAAkC,EAAE,YAA0D;IACpLA,IAAIA,WAAWA,GAAGA,UAAUA,GAAcA,EAAEA,KAAeA,EAAEA,SAAmBA;QAC5E,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;IAC1D,CAAC,CAACA;IAEFA,IAAIA,CAACA,QAAQA,CAACA,WAAWA,EAAEA,YAAYA,CAACA,CAACA;IAEzCA,CAACA,CAACA,IAAIA,CAACA,MAAMA,CAACA,IAAIA,CAACA,KAAKA,CAACA,MAAMA,CAACA,EAACA,UAACA,QAAQA;QACtCA,EAAEA,CAACA,CAACA,KAAKA,CAACA,UAAUA,CAACA,cAAcA,CAACA,QAAQA,CAACA,CAACA,CAACA,CAACA;YAC5CA,MAAMA,CAACA,MAAMA,CAACA,cAAcA,CAACA,WAAWA,CAACA,SAASA,EAAEA,QAAQA,EAAEA;gBAC1DA,GAAGA,EAAEA;oBACD,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBACjE,CAAC;gBACDA,GAAGA,EAAEA,UAAUA,KAAKA;oBAChB,IAAI,CAAC,SAAS,CAAC,GAAG,GAAG,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAChE,CAAC;gBACDA,UAAUA,EAAEA,IAAIA;gBAChBA,YAAYA,EAAEA,IAAIA;aACrBA,CAACA,CAACA;QACPA,CAACA;QAEDA,MAAMA,CAACA,cAAcA,CAACA,WAAWA,CAACA,SAASA,EAAEA,QAAQA,EAAEA;YACnDA,GAAGA,EAAEA;gBACD,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;YACpC,CAAC;YACDA,GAAGA,EAAEA,UAAUA,KAAKA;gBAChB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC;YACrC,CAAC;YACDA,UAAUA,EAAEA,IAAIA;SACnBA,CAACA,CAACA;IACPA,CAACA,CAACA,CAACA;IAEHA,MAAMA,CAAMA,WAAWA,CAACA;AAC5BA,CAACA;AAjCD,uCAiCC,CAAA","file":"lib/ModelSpecificInstance.js","sourcesContent":["/// \r\nimport Model from './Model';\r\nimport InstanceImplementation from './InstanceInterface';\r\nimport util = require('util');\r\nimport _ = require('lodash');\r\n\r\nexport default function ModelSpecificInstance(model: Model, instanceType: InstanceImplementation): new (doc: TDocument, isNew?: boolean, isPartial?: boolean) => TInstance {\r\n var constructor = function (doc: TDocument, isNew?: boolean, isPartial?: boolean) {\r\n instanceType.call(this, model, doc, isNew, isPartial);\r\n };\r\n\r\n util.inherits(constructor, instanceType);\r\n\r\n _.each(Object.keys(model.schema),(property) => {\r\n if (model.transforms.hasOwnProperty(property)) {\r\n return Object.defineProperty(constructor.prototype, property, {\r\n get: function () {\r\n return model.transforms[property].fromDB(this._modified._id);\r\n },\r\n set: function (value) {\r\n this._modified._id = model.transforms[property].toDB(value);\r\n },\r\n enumerable: true,\r\n configurable: true\r\n });\r\n }\r\n\r\n Object.defineProperty(constructor.prototype, property, {\r\n get: function () {\r\n return this._modified[property];\r\n },\r\n set: function (value) {\r\n this._modified[property] = value;\r\n },\r\n enumerable: true\r\n });\r\n });\r\n\r\n return constructor;\r\n}\r\n"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/Plugins.js b/dist/lib/Plugins.js new file mode 100644 index 0000000..e1ca0cd --- /dev/null +++ b/dist/lib/Plugins.js @@ -0,0 +1,3 @@ + + +//# sourceMappingURL=../lib/Plugins.js.map \ No newline at end of file diff --git a/dist/lib/Plugins.js.map b/dist/lib/Plugins.js.map new file mode 100644 index 0000000..fa9051a --- /dev/null +++ b/dist/lib/Plugins.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/Plugins.ts"],"names":[],"mappings":"AAQC","file":"lib/Plugins.js","sourcesContent":["/// \r\nimport core = require('./Core');\r\nimport Model from './Model';\r\n\r\nexport interface Plugin {\r\n newModel? (model: Model);\r\n newInstance? (instance: any, model: Model);\r\n validate?: Skmatc.Validator | Skmatc.Validator[];\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/Schema.js b/dist/lib/Schema.js new file mode 100644 index 0000000..cdd60f9 --- /dev/null +++ b/dist/lib/Schema.js @@ -0,0 +1,3 @@ +/// + +//# sourceMappingURL=../lib/Schema.js.map \ No newline at end of file diff --git a/dist/lib/Schema.js.map b/dist/lib/Schema.js.map new file mode 100644 index 0000000..7b02e00 --- /dev/null +++ b/dist/lib/Schema.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/Schema.ts"],"names":[],"mappings":"AAAA,AACA,4CAD4C;AAI3C","file":"lib/Schema.js","sourcesContent":["/// \r\nexport interface Schema {\r\n _id: boolean | any;\r\n [key:string]: any;\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/cacheControllers/IDDirector.js b/dist/lib/cacheControllers/IDDirector.js new file mode 100644 index 0000000..ce59a8a --- /dev/null +++ b/dist/lib/cacheControllers/IDDirector.js @@ -0,0 +1,25 @@ +var MongoDB = require('mongodb'); +var IDCacheDirector = (function () { + function IDCacheDirector() { + } + IDCacheDirector.prototype.valid = function (object) { + return !!object._id; + }; + IDCacheDirector.prototype.buildKey = function (object) { + if (object._id._bsontype == 'ObjectID') + return new MongoDB.ObjectID(object._id.id).toHexString(); + return object._id; + }; + IDCacheDirector.prototype.validQuery = function (conditions) { + return !!conditions._id; + }; + IDCacheDirector.prototype.buildQueryKey = function (conditions) { + if (conditions._id._bsontype == 'ObjectID') + return new MongoDB.ObjectID(conditions._id.id).toHexString(); + return conditions._id; + }; + return IDCacheDirector; +})(); +exports.default = IDCacheDirector; + +//# sourceMappingURL=../../lib/cacheControllers/IDDirector.js.map \ No newline at end of file diff --git a/dist/lib/cacheControllers/IDDirector.js.map b/dist/lib/cacheControllers/IDDirector.js.map new file mode 100644 index 0000000..ceaafbb --- /dev/null +++ b/dist/lib/cacheControllers/IDDirector.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/cacheControllers/IDDirector.ts"],"names":["IDCacheDirector","IDCacheDirector.constructor","IDCacheDirector.valid","IDCacheDirector.buildKey","IDCacheDirector.validQuery","IDCacheDirector.buildQueryKey"],"mappings":"AAEA,IAAO,OAAO,WAAW,SAAS,CAAC,CAAC;AAEpC;IAAAA;IAoBAC,CAACA;IAnBGD,+BAAKA,GAALA,UAAMA,MAAoBA;QACtBE,MAAMA,CAACA,CAACA,CAACA,MAAMA,CAACA,GAAGA,CAACA;IACxBA,CAACA;IAEDF,kCAAQA,GAARA,UAASA,MAAoBA;QACzBG,EAAEA,CAACA,CAACA,MAAMA,CAACA,GAAGA,CAACA,SAASA,IAAIA,UAAUA,CAACA;YACnCA,MAAMA,CAACA,IAAIA,OAAOA,CAACA,QAAQA,CAACA,MAAMA,CAACA,GAAGA,CAACA,EAAEA,CAACA,CAACA,WAAWA,EAAEA,CAACA;QAC7DA,MAAMA,CAACA,MAAMA,CAACA,GAAGA,CAACA;IACtBA,CAACA;IAEDH,oCAAUA,GAAVA,UAAWA,UAAUA;QACjBI,MAAMA,CAACA,CAACA,CAACA,UAAUA,CAACA,GAAGA,CAACA;IAC5BA,CAACA;IAEDJ,uCAAaA,GAAbA,UAAcA,UAAUA;QACpBK,EAAEA,CAACA,CAACA,UAAUA,CAACA,GAAGA,CAACA,SAASA,IAAIA,UAAUA,CAACA;YACvCA,MAAMA,CAACA,IAAIA,OAAOA,CAACA,QAAQA,CAACA,UAAUA,CAACA,GAAGA,CAACA,EAAEA,CAACA,CAACA,WAAWA,EAAEA,CAACA;QACjEA,MAAMA,CAACA,UAAUA,CAACA,GAAGA,CAACA;IAC1BA,CAACA;IACLL,sBAACA;AAADA,CApBA,AAoBCA,IAAA;AApBD,iCAoBC,CAAA","file":"lib/cacheControllers/IDDirector.js","sourcesContent":["/// \r\nimport {CacheDirector} from '../CacheDirector';\r\nimport MongoDB = require('mongodb');\r\n\r\nexport default class IDCacheDirector implements CacheDirector{\r\n valid(object: { _id: any }) {\r\n return !!object._id;\r\n }\r\n\r\n buildKey(object: { _id: any }) {\r\n if (object._id._bsontype == 'ObjectID')\r\n return new MongoDB.ObjectID(object._id.id).toHexString();\r\n return object._id;\r\n }\r\n\r\n validQuery(conditions) {\r\n return !!conditions._id;\r\n }\r\n\r\n buildQueryKey(conditions) {\r\n if (conditions._id._bsontype == 'ObjectID')\r\n return new MongoDB.ObjectID(conditions._id.id).toHexString();\r\n return conditions._id;\r\n }\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/caches/MemoryCache.js b/dist/lib/caches/MemoryCache.js new file mode 100644 index 0000000..eb3fc28 --- /dev/null +++ b/dist/lib/caches/MemoryCache.js @@ -0,0 +1,24 @@ +/// +var Bluebird = require('bluebird'); +var MemoryCache = (function () { + function MemoryCache() { + this.cache = {}; + } + MemoryCache.prototype.set = function (key, value) { + this.cache[key] = value; + return Bluebird.resolve(value); + }; + MemoryCache.prototype.get = function (key) { + return Bluebird.resolve(this.cache[key]); + }; + MemoryCache.prototype.clear = function (key) { + var has = this.cache.hasOwnProperty(key); + if (has) + delete this.cache[key]; + return Bluebird.resolve(has); + }; + return MemoryCache; +})(); +exports.default = MemoryCache; + +//# sourceMappingURL=../../lib/caches/MemoryCache.js.map \ No newline at end of file diff --git a/dist/lib/caches/MemoryCache.js.map b/dist/lib/caches/MemoryCache.js.map new file mode 100644 index 0000000..5b4ed7e --- /dev/null +++ b/dist/lib/caches/MemoryCache.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/caches/MemoryCache.ts"],"names":["MemoryCache","MemoryCache.constructor","MemoryCache.set","MemoryCache.get","MemoryCache.clear"],"mappings":"AAAA,AACA,+CAD+C;AAC/C,IAAO,QAAQ,WAAW,UAAU,CAAC,CAAC;AAGtC;IAAAA;QACYC,UAAKA,GAAQA,EAAEA,CAACA;IAgB5BA,CAACA;IAdGD,yBAAGA,GAAHA,UAAOA,GAAWA,EAAEA,KAAQA;QACxBE,IAAIA,CAACA,KAAKA,CAACA,GAAGA,CAACA,GAAGA,KAAKA,CAACA;QACxBA,MAAMA,CAACA,QAAQA,CAACA,OAAOA,CAACA,KAAKA,CAACA,CAACA;IACnCA,CAACA;IAEDF,yBAAGA,GAAHA,UAAOA,GAAWA;QACdG,MAAMA,CAACA,QAAQA,CAACA,OAAOA,CAACA,IAAIA,CAACA,KAAKA,CAACA,GAAGA,CAACA,CAACA,CAACA;IAC7CA,CAACA;IAEDH,2BAAKA,GAALA,UAAMA,GAAWA;QACbI,IAAIA,GAAGA,GAAGA,IAAIA,CAACA,KAAKA,CAACA,cAAcA,CAACA,GAAGA,CAACA,CAACA;QACzCA,EAAEA,CAAAA,CAACA,GAAGA,CAACA;YAACA,OAAOA,IAAIA,CAACA,KAAKA,CAACA,GAAGA,CAACA,CAACA;QAC/BA,MAAMA,CAACA,QAAQA,CAACA,OAAOA,CAACA,GAAGA,CAACA,CAACA;IACjCA,CAACA;IACLJ,kBAACA;AAADA,CAjBA,AAiBCA,IAAA;AAjBD,6BAiBC,CAAA","file":"lib/caches/MemoryCache.js","sourcesContent":["/// \r\nimport Bluebird = require('bluebird');\r\nimport {Cache} from '../Cache';\r\n\r\nexport default class MemoryCache implements Cache {\r\n private cache: any = {};\r\n\r\n set(key: string, value: T): Bluebird {\r\n this.cache[key] = value;\r\n return Bluebird.resolve(value);\r\n }\r\n\r\n get(key: string): Bluebird {\r\n return Bluebird.resolve(this.cache[key]);\r\n }\r\n\r\n clear(key: string) : Bluebird {\r\n var has = this.cache.hasOwnProperty(key);\r\n if(has) delete this.cache[key];\r\n return Bluebird.resolve(has);\r\n }\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/caches/NoOpCache.js b/dist/lib/caches/NoOpCache.js new file mode 100644 index 0000000..f9c4789 --- /dev/null +++ b/dist/lib/caches/NoOpCache.js @@ -0,0 +1,18 @@ +var Bluebird = require('bluebird'); +var NoOpCache = (function () { + function NoOpCache() { + } + NoOpCache.prototype.set = function (key, object) { + return Bluebird.resolve(object); + }; + NoOpCache.prototype.get = function (key) { + return Bluebird.resolve(); + }; + NoOpCache.prototype.clear = function (key) { + return Bluebird.resolve(false); + }; + return NoOpCache; +})(); +exports.default = NoOpCache; + +//# sourceMappingURL=../../lib/caches/NoOpCache.js.map \ No newline at end of file diff --git a/dist/lib/caches/NoOpCache.js.map b/dist/lib/caches/NoOpCache.js.map new file mode 100644 index 0000000..2faf674 --- /dev/null +++ b/dist/lib/caches/NoOpCache.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/caches/NoOpCache.ts"],"names":["NoOpCache","NoOpCache.constructor","NoOpCache.set","NoOpCache.get","NoOpCache.clear"],"mappings":"AAEA,IAAO,QAAQ,WAAW,UAAU,CAAC,CAAC;AAEtC;IAAAA;IAYAC,CAACA;IAXGD,uBAAGA,GAAHA,UAAOA,GAAWA,EAAEA,MAASA;QACzBE,MAAMA,CAACA,QAAQA,CAACA,OAAOA,CAACA,MAAMA,CAACA,CAACA;IACpCA,CAACA;IAEDF,uBAAGA,GAAHA,UAAOA,GAAWA;QACdG,MAAMA,CAACA,QAAQA,CAACA,OAAOA,EAAEA,CAACA;IAC9BA,CAACA;IAEDH,yBAAKA,GAALA,UAAMA,GAAWA;QACbI,MAAMA,CAACA,QAAQA,CAACA,OAAOA,CAACA,KAAKA,CAACA,CAACA;IACnCA,CAACA;IACLJ,gBAACA;AAADA,CAZA,AAYCA,IAAA;AAZD,2BAYC,CAAA","file":"lib/caches/NoOpCache.js","sourcesContent":["/// \r\nimport {Cache} from '../Cache';\r\nimport Bluebird = require('bluebird');\r\n\r\nexport default class NoOpCache implements Cache {\r\n set(key: string, object: T): Bluebird {\r\n return Bluebird.resolve(object);\r\n }\r\n\r\n get(key: string): Bluebird {\r\n return Bluebird.resolve();\r\n }\r\n\r\n clear(key: string): Bluebird {\r\n return Bluebird.resolve(false);\r\n }\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/middleware/Express.js b/dist/lib/middleware/Express.js new file mode 100644 index 0000000..5404bb0 --- /dev/null +++ b/dist/lib/middleware/Express.js @@ -0,0 +1,13 @@ +function ExpressMiddlewareFactory(core) { + return function (req, res, next) { + core.connect().then(function () { + Object.defineProperty(req, 'db', { + get: function () { return core; } + }); + next(); + }).catch(next); + }; +} +exports.default = ExpressMiddlewareFactory; + +//# sourceMappingURL=../../lib/middleware/Express.js.map \ No newline at end of file diff --git a/dist/lib/middleware/Express.js.map b/dist/lib/middleware/Express.js.map new file mode 100644 index 0000000..6096e2e --- /dev/null +++ b/dist/lib/middleware/Express.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/middleware/Express.ts"],"names":["ExpressMiddlewareFactory"],"mappings":"AAKA,kCAAiD,IAAU;IACvDA,MAAMA,CAACA,UAAUA,GAAuBA,EAAEA,GAAwBA,EAAEA,IAA0CA;QAC1G,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC;YAChB,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE;gBAC7B,GAAG,EAAE,cAAa,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;aACnC,CAAC,CAAC;YACH,IAAI,EAAE,CAAC;QACX,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC,CAACA;AACNA,CAACA;AATD,0CASC,CAAA;AAIA","file":"lib/middleware/Express.js","sourcesContent":["/// \r\nimport http = require('http');\r\nimport {MiddlewareFactory} from '../Middleware';\r\nimport Core from '../Core';\r\n\r\nexport default function ExpressMiddlewareFactory(core: Core): ExpressMiddleware {\r\n return function (req: http.ServerRequest, res: http.ServerResponse, next:(err?: Error, route?: String) => void) {\r\n core.connect().then(function() {\r\n Object.defineProperty(req, 'db', {\r\n get: function() { return core; }\r\n });\r\n next();\r\n }).catch(next);\r\n };\r\n}\r\n\r\nexport interface ExpressMiddleware {\r\n (req: http.ServerRequest, res: http.ServerResponse, next:(err?: Error, route?: String) => void);\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/dist/lib/utils/Omnom.js b/dist/lib/utils/Omnom.js new file mode 100644 index 0000000..11727bd --- /dev/null +++ b/dist/lib/utils/Omnom.js @@ -0,0 +1,187 @@ +/// +var _ = require('lodash'); +var MongoDB = require('mongodb'); +var Omnom = (function () { + function Omnom(options) { + if (options === void 0) { options = {}; } + this.options = options; + this._changes = {}; + } + Object.defineProperty(Omnom.prototype, "changes", { + get: function () { + return this._changes; + }, + enumerable: true, + configurable: true + }); + Omnom.prototype.diff = function (original, modified) { + this.onObject(original, modified); + return this; + }; + Omnom.diff = function (original, modified, options) { + return new Omnom(options).diff(original, modified).changes; + }; + Omnom.prototype.onObject = function (original, modified, changePath) { + if (original === undefined || original === null) + return (original !== modified) && this.set(changePath, modified); + if (typeof original == 'number' && typeof modified == 'number' && original !== modified) { + if (this.options.atomicNumbers) + return this.inc(changePath, modified - original); + return this.set(changePath, modified); + } + if (Array.isArray(original) && Array.isArray(modified)) + return this.onArray(original, modified, changePath); + if (original instanceof MongoDB.ObjectID && modified instanceof MongoDB.ObjectID) + return !original.equals(modified) && this.set(changePath, modified); + if (!_.isPlainObject(original) || !_.isPlainObject(modified)) + return !_.isEqual(original, modified) && this.set(changePath, modified); + _.each(modified, function (value, key) { + // Handle array diffs in their own special way + if (Array.isArray(value) && Array.isArray(original[key])) + this.onArray(original[key], value, this.resolve(changePath, key)); + else + this.onObject(original[key], value, this.resolve(changePath, key)); + }, this); + // Unset removed properties + _.each(original, function (value, key) { + if (modified[key] === undefined || modified[key] === null) + return this.unset(this.resolve(changePath, key)); + }, this); + }; + Omnom.prototype.onArray = function (original, modified, changePath) { + var _this = this; + var i, j; + // Check if we can get from original => modified using just pulls + if (original.length > modified.length) { + var pulls = []; + for (i = 0, j = 0; i < original.length && j < modified.length; i++) { + if (this.almostEqual(original[i], modified[j])) + j++; + else + pulls.push(original[i]); + } + for (; i < original.length; i++) + pulls.push(original[i]); + if (j === modified.length) { + if (pulls.length === 1) + return this.pull(changePath, pulls[0]); + // We can complete using just pulls + return pulls.forEach(function (pull) { return _this.pull(changePath, pull); }); + } + else + return this.set(changePath, modified); + } + // Check if we can get from original => modified using just pushes + if (original.length < modified.length) { + var canPush = true; + for (i = 0; i < original.length; i++) + if (this.almostEqual(original[i], modified[i]) < 1) { + canPush = false; + break; + } + if (canPush) { + for (i = original.length; i < modified.length; i++) + this.push(changePath, modified[i]); + return; + } + } + // Otherwise, we need to use $set to generate the new array + // Check how many manipulations would need to be performed, if it's more than half the array size + // then rather re-create the array + var sets = []; + var partials = []; + for (i = 0; i < modified.length; i++) { + var equality = this.almostEqual(original[i], modified[i]); + if (equality === 0) + sets.push(i); + else if (equality < 1) + partials.push(i); + } + if (sets.length > modified.length / 2) + return this.set(changePath, modified); + for (i = 0; i < sets.length; i++) + this.set(this.resolve(changePath, sets[i].toString()), modified[sets[i]]); + for (i = 0; i < partials.length; i++) + this.onObject(original[partials[i]], modified[partials[i]], this.resolve(changePath, partials[i].toString())); + }; + Omnom.prototype.set = function (path, value) { + if (!this.changes.$set) + this.changes.$set = {}; + this.changes.$set[path] = value; + }; + Omnom.prototype.unset = function (path) { + if (!this.changes.$unset) + this.changes.$unset = {}; + this.changes.$unset[path] = 1; + }; + Omnom.prototype.inc = function (path, value) { + if (!this.changes.$inc) + this.changes.$inc = {}; + this.changes.$inc[path] = value; + }; + Omnom.prototype.push = function (path, value) { + if (!this.changes.$push) + this.changes.$push = {}; + if (this.changes.$push[path]) { + if (this.changes.$push[path].$each) + this.changes.$push[path].$each.push(value); + else + this.changes.$push[path] = { $each: [this.changes.$push[path], value] }; + } + else + this.changes.$push[path] = value; + }; + Omnom.prototype.pull = function (path, value) { + if (!this.changes.$pull) + this.changes.$pull = {}; + if (this.changes.$pullAll && this.changes.$pullAll[path]) { + return this.changes.$pullAll[path].push(value); + } + if (this.changes.$pull[path]) { + this.pullAll(path, [this.changes.$pull[path], value]); + delete this.changes.$pull[path]; + if (_.keys(this.changes.$pull).length === 0) + delete this.changes.$pull; + return; + } + this.changes.$pull[path] = value; + }; + Omnom.prototype.pullAll = function (path, values) { + if (!this.changes.$pullAll) + this.changes.$pullAll = {}; + this.changes.$pullAll[path] = values; + }; + Omnom.prototype.resolve = function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i - 0] = arguments[_i]; + } + var validArguments = []; + args.forEach(function (arg) { + if (arg) + validArguments.push(arg); + }); + return validArguments.join('.'); + }; + Omnom.prototype.almostEqual = function (o1, o2) { + if (!_.isPlainObject(o1) || !_.isPlainObject(o2)) + return o1 == o2 ? 1 : 0; + var o1i, o1k = Object.keys(o1); + var o2k = Object.keys(o2); + var commonKeys = []; + for (o1i = 0; o1i < o1k.length; o1i++) + if (~o2k.indexOf(o1k[o1i])) + commonKeys.push(o1k[o1i]); + var totalKeys = o1k.length + o2k.length - commonKeys.length; + var keysDifference = totalKeys - commonKeys.length; + var requiredChanges = 0; + for (var i = 0; i < commonKeys.length; i++) + if (this.almostEqual(o1[commonKeys[i]], o2[commonKeys[i]]) < 1) + requiredChanges++; + return 1 - (keysDifference / totalKeys) - (requiredChanges / commonKeys.length); + }; + return Omnom; +})(); +exports.default = Omnom; + +//# sourceMappingURL=../../lib/utils/Omnom.js.map \ No newline at end of file diff --git a/dist/lib/utils/Omnom.js.map b/dist/lib/utils/Omnom.js.map new file mode 100644 index 0000000..d7ec881 --- /dev/null +++ b/dist/lib/utils/Omnom.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["lib/utils/Omnom.ts"],"names":["Omnom","Omnom.constructor","Omnom.changes","Omnom.diff","Omnom.onObject","Omnom.onArray","Omnom.set","Omnom.unset","Omnom.inc","Omnom.push","Omnom.pull","Omnom.pullAll","Omnom.resolve","Omnom.almostEqual"],"mappings":"AAAA,AACA,+CAD+C;AAC/C,IAAO,CAAC,WAAW,QAAQ,CAAC,CAAC;AAC7B,IAAO,OAAO,WAAW,SAAS,CAAC,CAAC;AAEpC;IACIA,eAAmBA,OAEbA;QAFMC,uBAENA,GAFMA,YAENA;QAFaA,YAAOA,GAAPA,OAAOA,CAEpBA;QACFA,IAAIA,CAACA,QAAQA,GAAGA,EAAEA,CAACA;IACvBA,CAACA;IAUDD,sBAAIA,0BAAOA;aAAXA;YAQIE,MAAMA,CAACA,IAAIA,CAACA,QAAQA,CAACA;QACzBA,CAACA;;;OAAAF;IAMDA,oBAAIA,GAAJA,UAAKA,QAAaA,EAAEA,QAAaA;QAC7BG,IAAIA,CAACA,QAAQA,CAACA,QAAQA,EAAEA,QAAQA,CAACA,CAACA;QAClCA,MAAMA,CAACA,IAAIA,CAACA;IAChBA,CAACA;IAcMH,UAAIA,GAAXA,UAAYA,QAAaA,EAAEA,QAAaA,EAAEA,OAEzCA;QACGG,MAAMA,CAACA,IAAIA,KAAKA,CAACA,OAAOA,CAACA,CAACA,IAAIA,CAACA,QAAQA,EAAEA,QAAQA,CAACA,CAACA,OAAOA,CAACA;IAC/DA,CAACA;IAMOH,wBAAQA,GAAhBA,UAAiBA,QAAaA,EAAEA,QAAaA,EAAEA,UAAmBA;QAC9DI,EAAEA,CAACA,CAACA,QAAQA,KAAKA,SAASA,IAAIA,QAAQA,KAAKA,IAAIA,CAACA;YAC5CA,MAAMA,CAACA,CAACA,QAAQA,KAAKA,QAAQA,CAACA,IAAIA,IAAIA,CAACA,GAAGA,CAACA,UAAUA,EAAEA,QAAQA,CAACA,CAACA;QAErEA,EAAEA,CAACA,CAACA,OAAOA,QAAQA,IAAIA,QAAQA,IAAIA,OAAOA,QAAQA,IAAIA,QAAQA,IAAIA,QAAQA,KAAKA,QAAQA,CAACA,CAACA,CAACA;YACtFA,EAAEA,CAACA,CAACA,IAAIA,CAACA,OAAOA,CAACA,aAAaA,CAACA;gBAACA,MAAMA,CAACA,IAAIA,CAACA,GAAGA,CAACA,UAAUA,EAAEA,QAAQA,GAAGA,QAAQA,CAACA,CAACA;YACjFA,MAAMA,CAACA,IAAIA,CAACA,GAAGA,CAACA,UAAUA,EAAEA,QAAQA,CAACA,CAACA;QAC1CA,CAACA;QAEDA,EAAEA,CAACA,CAACA,KAAKA,CAACA,OAAOA,CAACA,QAAQA,CAACA,IAAIA,KAAKA,CAACA,OAAOA,CAACA,QAAQA,CAACA,CAACA;YACnDA,MAAMA,CAACA,IAAIA,CAACA,OAAOA,CAACA,QAAQA,EAAEA,QAAQA,EAAEA,UAAUA,CAACA,CAACA;QAExDA,EAAEA,CAACA,CAACA,QAAQA,YAAYA,OAAOA,CAACA,QAAQA,IAAIA,QAAQA,YAAYA,OAAOA,CAACA,QAAQA,CAACA;YAC7EA,MAAMA,CAACA,CAACA,QAAQA,CAACA,MAAMA,CAACA,QAAQA,CAACA,IAAIA,IAAIA,CAACA,GAAGA,CAACA,UAAUA,EAAEA,QAAQA,CAACA,CAACA;QAExEA,EAAEA,CAACA,CAACA,CAACA,CAACA,CAACA,aAAaA,CAACA,QAAQA,CAACA,IAAIA,CAACA,CAACA,CAACA,aAAaA,CAACA,QAAQA,CAACA,CAACA;YACzDA,MAAMA,CAACA,CAACA,CAACA,CAACA,OAAOA,CAACA,QAAQA,EAAEA,QAAQA,CAACA,IAAIA,IAAIA,CAACA,GAAGA,CAACA,UAAUA,EAAEA,QAAQA,CAACA,CAACA;QAE5EA,CAACA,CAACA,IAAIA,CAACA,QAAQA,EAAEA,UAAUA,KAAKA,EAAEA,GAAGA;YACjC,8CAA8C;YAC9C,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;YAG5H,IAAI;gBAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;QAC5E,CAAC,EAAEA,IAAIA,CAACA,CAACA;QAETA,2BAA2BA;QAC3BA,CAACA,CAACA,IAAIA,CAACA,QAAQA,EAAEA,UAAUA,KAAKA,EAAEA,GAAGA;YACjC,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,SAAS,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC;gBAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;QAChH,CAAC,EAAEA,IAAIA,CAACA,CAACA;IACbA,CAACA;IAEOJ,uBAAOA,GAAfA,UAAgBA,QAAeA,EAAEA,QAAeA,EAAEA,UAAkBA;QAApEK,iBA8DCA;QA7DGA,IAAIA,CAACA,EAAEA,CAACA,CAACA;QAETA,iEAAiEA;QACjEA,EAAEA,CAACA,CAACA,QAAQA,CAACA,MAAMA,GAAGA,QAAQA,CAACA,MAAMA,CAACA,CAACA,CAACA;YACpCA,IAAIA,KAAKA,GAAGA,EAAEA,CAACA;YACfA,GAAGA,CAACA,CAACA,CAACA,GAAGA,CAACA,EAAEA,CAACA,GAAGA,CAACA,EAAEA,CAACA,GAAGA,QAAQA,CAACA,MAAMA,IAAIA,CAACA,GAAGA,QAAQA,CAACA,MAAMA,EAAEA,CAACA,EAAEA,EAAEA,CAACA;gBACjEA,EAAEA,CAACA,CAACA,IAAIA,CAACA,WAAWA,CAACA,QAAQA,CAACA,CAACA,CAACA,EAAEA,QAAQA,CAACA,CAACA,CAACA,CAACA,CAACA;oBAACA,CAACA,EAAEA,CAACA;gBACpDA,IAAIA;oBAACA,KAAKA,CAACA,IAAIA,CAACA,QAAQA,CAACA,CAACA,CAACA,CAACA,CAACA;YACjCA,CAACA;YAEDA,GAAGA,CAACA,CAACA,EAAEA,CAACA,GAAGA,QAAQA,CAACA,MAAMA,EAAEA,CAACA,EAAEA;gBAC3BA,KAAKA,CAACA,IAAIA,CAACA,QAAQA,CAACA,CAACA,CAACA,CAACA,CAACA;YAE5BA,EAAEA,CAACA,CAACA,CAACA,KAAKA,QAAQA,CAACA,MAAMA,CAACA,CAACA,CAACA;gBACxBA,EAAEA,CAACA,CAACA,KAAKA,CAACA,MAAMA,KAAKA,CAACA,CAACA;oBAACA,MAAMA,CAACA,IAAIA,CAACA,IAAIA,CAACA,UAAUA,EAAEA,KAAKA,CAACA,CAACA,CAACA,CAACA,CAACA;gBAC/DA,mCAAmCA;gBACnCA,MAAMA,CAACA,KAAKA,CAACA,OAAOA,CAACA,UAACA,IAAIA,IAAKA,OAAAA,KAAIA,CAACA,IAAIA,CAACA,UAAUA,EAAEA,IAAIA,CAACA,EAA3BA,CAA2BA,CAACA,CAACA;YAChEA,CAACA;YAIDA,IAAIA;gBAACA,MAAMA,CAACA,IAAIA,CAACA,GAAGA,CAACA,UAAUA,EAAEA,QAAQA,CAACA,CAACA;QAC/CA,CAACA;QAEDA,kEAAkEA;QAClEA,EAAEA,CAACA,CAACA,QAAQA,CAACA,MAAMA,GAAGA,QAAQA,CAACA,MAAMA,CAACA,CAACA,CAACA;YACpCA,IAAIA,OAAOA,GAAGA,IAAIA,CAACA;YACnBA,GAAGA,CAACA,CAACA,CAACA,GAAGA,CAACA,EAAEA,CAACA,GAAGA,QAAQA,CAACA,MAAMA,EAAEA,CAACA,EAAEA;gBAChCA,EAAEA,CAACA,CAACA,IAAIA,CAACA,WAAWA,CAACA,QAAQA,CAACA,CAACA,CAACA,EAAEA,QAAQA,CAACA,CAACA,CAACA,CAACA,GAAGA,CAACA,CAACA,CAACA,CAACA;oBACjDA,OAAOA,GAAGA,KAAKA,CAACA;oBAChBA,KAAKA,CAACA;gBACVA,CAACA;YAELA,EAAEA,CAACA,CAACA,OAAOA,CAACA,CAACA,CAACA;gBACVA,GAAGA,CAACA,CAACA,CAACA,GAAGA,QAAQA,CAACA,MAAMA,EAAEA,CAACA,GAAGA,QAAQA,CAACA,MAAMA,EAAEA,CAACA,EAAEA;oBAC9CA,IAAIA,CAACA,IAAIA,CAACA,UAAUA,EAAEA,QAAQA,CAACA,CAACA,CAACA,CAACA,CAACA;gBACvCA,MAAMA,CAACA;YACXA,CAACA;QACLA,CAACA;QAEDA,2DAA2DA;QAE3DA,iGAAiGA;QACjGA,kCAAkCA;QAElCA,IAAIA,IAAIA,GAAGA,EAAEA,CAACA;QACdA,IAAIA,QAAQA,GAAGA,EAAEA,CAACA;QAClBA,GAAGA,CAACA,CAACA,CAACA,GAAGA,CAACA,EAAEA,CAACA,GAAGA,QAAQA,CAACA,MAAMA,EAAEA,CAACA,EAAEA,EAAEA,CAACA;YACnCA,IAAIA,QAAQA,GAAGA,IAAIA,CAACA,WAAWA,CAACA,QAAQA,CAACA,CAACA,CAACA,EAAEA,QAAQA,CAACA,CAACA,CAACA,CAACA,CAACA;YAC1DA,EAAEA,CAACA,CAACA,QAAQA,KAAKA,CAACA,CAACA;gBAACA,IAAIA,CAACA,IAAIA,CAACA,CAACA,CAACA,CAACA;YACjCA,IAAIA,CAACA,EAAEA,CAACA,CAACA,QAAQA,GAAGA,CAACA,CAACA;gBAACA,QAAQA,CAACA,IAAIA,CAACA,CAACA,CAACA,CAACA;QAC5CA,CAACA;QAEDA,EAAEA,CAACA,CAACA,IAAIA,CAACA,MAAMA,GAAGA,QAAQA,CAACA,MAAMA,GAAGA,CAACA,CAACA;YAClCA,MAAMA,CAACA,IAAIA,CAACA,GAAGA,CAACA,UAAUA,EAAEA,QAAQA,CAACA,CAACA;QAE1CA,GAAGA,CAACA,CAACA,CAACA,GAAGA,CAACA,EAAEA,CAACA,GAAGA,IAAIA,CAACA,MAAMA,EAAEA,CAACA,EAAEA;YAC5BA,IAAIA,CAACA,GAAGA,CAACA,IAAIA,CAACA,OAAOA,CAACA,UAAUA,EAAEA,IAAIA,CAACA,CAACA,CAACA,CAACA,QAAQA,EAAEA,CAACA,EAAEA,QAAQA,CAACA,IAAIA,CAACA,CAACA,CAACA,CAACA,CAACA,CAACA;QAE9EA,GAAGA,CAACA,CAACA,CAACA,GAAGA,CAACA,EAAEA,CAACA,GAAGA,QAAQA,CAACA,MAAMA,EAAEA,CAACA,EAAEA;YAChCA,IAAIA,CAACA,QAAQA,CAACA,QAAQA,CAACA,QAAQA,CAACA,CAACA,CAACA,CAACA,EAAEA,QAAQA,CAACA,QAAQA,CAACA,CAACA,CAACA,CAACA,EAAEA,IAAIA,CAACA,OAAOA,CAACA,UAAUA,EAAEA,QAAQA,CAACA,CAACA,CAACA,CAACA,QAAQA,EAAEA,CAACA,CAACA,CAACA;IACtHA,CAACA;IAEOL,mBAAGA,GAAXA,UAAYA,IAAYA,EAAEA,KAAUA;QAChCM,EAAEA,CAACA,CAACA,CAACA,IAAIA,CAACA,OAAOA,CAACA,IAAIA,CAACA;YACnBA,IAAIA,CAACA,OAAOA,CAACA,IAAIA,GAAGA,EAAEA,CAACA;QAE3BA,IAAIA,CAACA,OAAOA,CAACA,IAAIA,CAACA,IAAIA,CAACA,GAAGA,KAAKA,CAACA;IACpCA,CAACA;IAEON,qBAAKA,GAAbA,UAAcA,IAAYA;QACtBO,EAAEA,CAACA,CAACA,CAACA,IAAIA,CAACA,OAAOA,CAACA,MAAMA,CAACA;YACrBA,IAAIA,CAACA,OAAOA,CAACA,MAAMA,GAAGA,EAAEA,CAACA;QAE7BA,IAAIA,CAACA,OAAOA,CAACA,MAAMA,CAACA,IAAIA,CAACA,GAAGA,CAACA,CAACA;IAClCA,CAACA;IAEOP,mBAAGA,GAAXA,UAAYA,IAAYA,EAAEA,KAAaA;QACnCQ,EAAEA,CAACA,CAACA,CAACA,IAAIA,CAACA,OAAOA,CAACA,IAAIA,CAACA;YACnBA,IAAIA,CAACA,OAAOA,CAACA,IAAIA,GAAGA,EAAEA,CAACA;QAE3BA,IAAIA,CAACA,OAAOA,CAACA,IAAIA,CAACA,IAAIA,CAACA,GAAGA,KAAKA,CAACA;IACpCA,CAACA;IAEOR,oBAAIA,GAAZA,UAAaA,IAAYA,EAAEA,KAAUA;QACjCS,EAAEA,CAACA,CAACA,CAACA,IAAIA,CAACA,OAAOA,CAACA,KAAKA,CAACA;YACpBA,IAAIA,CAACA,OAAOA,CAACA,KAAKA,GAAGA,EAAEA,CAACA;QAE5BA,EAAEA,CAACA,CAACA,IAAIA,CAACA,OAAOA,CAACA,KAAKA,CAACA,IAAIA,CAACA,CAACA,CAACA,CAACA;YAC3BA,EAAEA,CAACA,CAACA,IAAIA,CAACA,OAAOA,CAACA,KAAKA,CAACA,IAAIA,CAACA,CAACA,KAAKA,CAACA;gBAC/BA,IAAIA,CAACA,OAAOA,CAACA,KAAKA,CAACA,IAAIA,CAACA,CAACA,KAAKA,CAACA,IAAIA,CAACA,KAAKA,CAACA,CAACA;YAC/CA,IAAIA;gBACAA,IAAIA,CAACA,OAAOA,CAACA,KAAKA,CAACA,IAAIA,CAACA,GAAGA,EAAEA,KAAKA,EAAEA,CAACA,IAAIA,CAACA,OAAOA,CAACA,KAAKA,CAACA,IAAIA,CAACA,EAAEA,KAAKA,CAACA,EAAEA,CAACA;QAChFA,CAACA;QAACA,IAAIA;YAACA,IAAIA,CAACA,OAAOA,CAACA,KAAKA,CAACA,IAAIA,CAACA,GAAGA,KAAKA,CAACA;IAC5CA,CAACA;IAEOT,oBAAIA,GAAZA,UAAaA,IAAYA,EAAEA,KAAUA;QACjCU,EAAEA,CAACA,CAACA,CAACA,IAAIA,CAACA,OAAOA,CAACA,KAAKA,CAACA;YACpBA,IAAIA,CAACA,OAAOA,CAACA,KAAKA,GAAGA,EAAEA,CAACA;QAE5BA,EAAEA,CAACA,CAACA,IAAIA,CAACA,OAAOA,CAACA,QAAQA,IAAIA,IAAIA,CAACA,OAAOA,CAACA,QAAQA,CAACA,IAAIA,CAACA,CAACA,CAACA,CAACA;YACvDA,MAAMA,CAACA,IAAIA,CAACA,OAAOA,CAACA,QAAQA,CAACA,IAAIA,CAACA,CAACA,IAAIA,CAACA,KAAKA,CAACA,CAACA;QACnDA,CAACA;QAEDA,EAAEA,CAACA,CAACA,IAAIA,CAACA,OAAOA,CAACA,KAAKA,CAACA,IAAIA,CAACA,CAACA,CAACA,CAACA;YAC3BA,IAAIA,CAACA,OAAOA,CAACA,IAAIA,EAAEA,CAACA,IAAIA,CAACA,OAAOA,CAACA,KAAKA,CAACA,IAAIA,CAACA,EAAEA,KAAKA,CAACA,CAACA,CAACA;YACtDA,OAAOA,IAAIA,CAACA,OAAOA,CAACA,KAAKA,CAACA,IAAIA,CAACA,CAACA;YAChCA,EAAEA,CAACA,CAACA,CAACA,CAACA,IAAIA,CAACA,IAAIA,CAACA,OAAOA,CAACA,KAAKA,CAACA,CAACA,MAAMA,KAAKA,CAACA,CAACA;gBACxCA,OAAOA,IAAIA,CAACA,OAAOA,CAACA,KAAKA,CAACA;YAC9BA,MAAMA,CAACA;QACXA,CAACA;QAEDA,IAAIA,CAACA,OAAOA,CAACA,KAAKA,CAACA,IAAIA,CAACA,GAAGA,KAAKA,CAACA;IACrCA,CAACA;IAEOV,uBAAOA,GAAfA,UAAgBA,IAAYA,EAAEA,MAAaA;QACvCW,EAAEA,CAACA,CAACA,CAACA,IAAIA,CAACA,OAAOA,CAACA,QAAQA,CAACA;YACvBA,IAAIA,CAACA,OAAOA,CAACA,QAAQA,GAAGA,EAAEA,CAACA;QAE/BA,IAAIA,CAACA,OAAOA,CAACA,QAAQA,CAACA,IAAIA,CAACA,GAAGA,MAAMA,CAACA;IACzCA,CAACA;IAEOX,uBAAOA,GAAfA;QAAgBY,cAAOA;aAAPA,WAAOA,CAAPA,sBAAOA,CAAPA,IAAOA;YAAPA,6BAAOA;;QACnBA,IAAIA,cAAcA,GAAGA,EAAEA,CAACA;QACxBA,IAAIA,CAACA,OAAOA,CAACA,UAAUA,GAAGA;YACtB,EAAE,CAAC,CAAC,GAAG,CAAC;gBAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACtC,CAAC,CAACA,CAACA;QACHA,MAAMA,CAACA,cAAcA,CAACA,IAAIA,CAACA,GAAGA,CAACA,CAACA;IACpCA,CAACA;IAGOZ,2BAAWA,GAAnBA,UAAoBA,EAAOA,EAAEA,EAAOA;QAChCa,EAAEA,CAACA,CAACA,CAACA,CAACA,CAACA,aAAaA,CAACA,EAAEA,CAACA,IAAIA,CAACA,CAACA,CAACA,aAAaA,CAACA,EAAEA,CAACA,CAACA;YAACA,MAAMA,CAACA,EAAEA,IAAIA,EAAEA,GAAGA,CAACA,GAAGA,CAACA,CAACA;QAE1EA,IAAIA,GAAGA,EAAEA,GAAGA,GAAGA,MAAMA,CAACA,IAAIA,CAACA,EAAEA,CAACA,CAACA;QAC/BA,IAAIA,GAAGA,GAAGA,MAAMA,CAACA,IAAIA,CAACA,EAAEA,CAACA,CAACA;QAE1BA,IAAIA,UAAUA,GAAGA,EAAEA,CAACA;QACpBA,GAAGA,CAACA,CAACA,GAAGA,GAAGA,CAACA,EAAEA,GAAGA,GAAGA,GAAGA,CAACA,MAAMA,EAAEA,GAAGA,EAAEA;YACjCA,EAAEA,CAACA,CAACA,CAACA,GAAGA,CAACA,OAAOA,CAACA,GAAGA,CAACA,GAAGA,CAACA,CAACA,CAACA;gBAACA,UAAUA,CAACA,IAAIA,CAACA,GAAGA,CAACA,GAAGA,CAACA,CAACA,CAACA;QAE1DA,IAAIA,SAASA,GAAGA,GAAGA,CAACA,MAAMA,GAAGA,GAAGA,CAACA,MAAMA,GAAGA,UAAUA,CAACA,MAAMA,CAACA;QAC5DA,IAAIA,cAAcA,GAAGA,SAASA,GAAGA,UAAUA,CAACA,MAAMA,CAACA;QAEnDA,IAAIA,eAAeA,GAAGA,CAACA,CAACA;QACxBA,GAAGA,CAACA,CAACA,GAAGA,CAACA,CAACA,GAAGA,CAACA,EAAEA,CAACA,GAAGA,UAAUA,CAACA,MAAMA,EAAEA,CAACA,EAAEA;YACtCA,EAAEA,CAACA,CAACA,IAAIA,CAACA,WAAWA,CAACA,EAAEA,CAACA,UAAUA,CAACA,CAACA,CAACA,CAACA,EAAEA,EAAEA,CAACA,UAAUA,CAACA,CAACA,CAACA,CAACA,CAACA,GAAGA,CAACA,CAACA;gBAACA,eAAeA,EAAEA,CAACA;QAEtFA,MAAMA,CAACA,CAACA,GAAGA,CAACA,cAAcA,GAAGA,SAASA,CAACA,GAAGA,CAACA,eAAeA,GAAGA,UAAUA,CAACA,MAAMA,CAACA,CAACA;IACpFA,CAACA;IACLb,YAACA;AAADA,CAhPA,AAgPCA,IAAA;AAhPD,uBAgPC,CAAA","file":"lib/utils/Omnom.js","sourcesContent":["/// \r\nimport _ = require('lodash');\r\nimport MongoDB = require('mongodb');\r\n\r\nexport default class Omnom {\r\n constructor(public options: {\r\n atomicNumbers?: boolean;\r\n } = {}) {\r\n this._changes = {};\r\n }\r\n\r\n private _changes: {\r\n $set?: any;\r\n $unset?: any;\r\n $inc?: any;\r\n $push?: any;\r\n $pull?: any;\r\n $pullAll?: any;\r\n };\r\n get changes(): {\r\n $set?: any;\r\n $unset?: any;\r\n $inc?: any;\r\n $push?: any;\r\n $pull?: any;\r\n $pullAll?: any;\r\n } {\r\n return this._changes;\r\n }\r\n\r\n diff(original: number, modified: number): Omnom;\r\n diff(original: [any], modified: any[]): Omnom;\r\n diff(original: MongoDB.ObjectID, modified: MongoDB.ObjectID): Omnom;\r\n diff(original: Object, modified: Object): Omnom;\r\n diff(original: any, modified: any): Omnom {\r\n this.onObject(original, modified);\r\n return this;\r\n }\r\n\r\n static diff(original: number, modified: number, options?: {\r\n atomicNumbers?: boolean;\r\n });\r\n static diff(original: [any], modified: any[], options?: {\r\n atomicNumbers?: boolean;\r\n });\r\n static diff(original: MongoDB.ObjectID, modified: MongoDB.ObjectID, options?: {\r\n atomicNumbers?: boolean;\r\n });\r\n static diff(original: Object, modified: Object, options?: {\r\n atomicNumbers?: boolean;\r\n });\r\n static diff(original: any, modified: any, options?: {\r\n atomicNumbers?: boolean;\r\n }) {\r\n return new Omnom(options).diff(original, modified).changes;\r\n }\r\n\r\n private onObject(original: number, modified: number, changePath?: string);\r\n private onObject(original: [any], modified: any[], changePath?: string);\r\n private onObject(original: MongoDB.ObjectID, modified: MongoDB.ObjectID, changePath?: string);\r\n private onObject(original: Object, modified: Object, changePath?: string);\r\n private onObject(original: any, modified: any, changePath?: string) {\r\n if (original === undefined || original === null)\r\n return (original !== modified) && this.set(changePath, modified);\r\n\r\n if (typeof original == 'number' && typeof modified == 'number' && original !== modified) {\r\n if (this.options.atomicNumbers) return this.inc(changePath, modified - original);\r\n return this.set(changePath, modified);\r\n }\r\n\r\n if (Array.isArray(original) && Array.isArray(modified))\r\n return this.onArray(original, modified, changePath);\r\n\r\n if (original instanceof MongoDB.ObjectID && modified instanceof MongoDB.ObjectID)\r\n return !original.equals(modified) && this.set(changePath, modified);\r\n\r\n if (!_.isPlainObject(original) || !_.isPlainObject(modified))\r\n return !_.isEqual(original, modified) && this.set(changePath, modified);\r\n\r\n _.each(modified, function (value, key) {\r\n // Handle array diffs in their own special way\r\n if (Array.isArray(value) && Array.isArray(original[key])) this.onArray(original[key], value, this.resolve(changePath, key));\r\n\r\n // Otherwise, just keep going\r\n else this.onObject(original[key], value, this.resolve(changePath, key));\r\n }, this);\r\n\r\n // Unset removed properties\r\n _.each(original, function (value, key) {\r\n if (modified[key] === undefined || modified[key] === null) return this.unset(this.resolve(changePath, key));\r\n }, this);\r\n }\r\n\r\n private onArray(original: [any], modified: [any], changePath: string) {\r\n var i, j;\r\n\r\n // Check if we can get from original => modified using just pulls\r\n if (original.length > modified.length) {\r\n var pulls = [];\r\n for (i = 0, j = 0; i < original.length && j < modified.length; i++) {\r\n if (this.almostEqual(original[i], modified[j])) j++;\r\n else pulls.push(original[i]);\r\n }\r\n\r\n for (; i < original.length; i++)\r\n pulls.push(original[i]);\r\n\r\n if (j === modified.length) {\r\n if (pulls.length === 1) return this.pull(changePath, pulls[0]);\r\n // We can complete using just pulls\r\n return pulls.forEach((pull) => this.pull(changePath, pull));\r\n }\r\n\r\n // If we have a smaller target array than our source, we will need to re-create it\r\n // regardless (if we want to do so in a single operation anyway)\r\n else return this.set(changePath, modified);\r\n }\r\n\r\n // Check if we can get from original => modified using just pushes\r\n if (original.length < modified.length) {\r\n var canPush = true;\r\n for (i = 0; i < original.length; i++)\r\n if (this.almostEqual(original[i], modified[i]) < 1) {\r\n canPush = false;\r\n break;\r\n }\r\n\r\n if (canPush) {\r\n for (i = original.length; i < modified.length; i++)\r\n this.push(changePath, modified[i]);\r\n return;\r\n }\r\n }\r\n\r\n // Otherwise, we need to use $set to generate the new array\r\n\r\n // Check how many manipulations would need to be performed, if it's more than half the array size\r\n // then rather re-create the array\r\n\r\n var sets = [];\r\n var partials = [];\r\n for (i = 0; i < modified.length; i++) {\r\n var equality = this.almostEqual(original[i], modified[i]);\r\n if (equality === 0) sets.push(i);\r\n else if (equality < 1) partials.push(i);\r\n }\r\n\r\n if (sets.length > modified.length / 2)\r\n return this.set(changePath, modified);\r\n\r\n for (i = 0; i < sets.length; i++)\r\n this.set(this.resolve(changePath, sets[i].toString()), modified[sets[i]]);\r\n\r\n for (i = 0; i < partials.length; i++)\r\n this.onObject(original[partials[i]], modified[partials[i]], this.resolve(changePath, partials[i].toString()));\r\n }\r\n\r\n private set(path: string, value: any) {\r\n if (!this.changes.$set)\r\n this.changes.$set = {};\r\n\r\n this.changes.$set[path] = value;\r\n }\r\n\r\n private unset(path: string) {\r\n if (!this.changes.$unset)\r\n this.changes.$unset = {};\r\n\r\n this.changes.$unset[path] = 1;\r\n }\r\n\r\n private inc(path: string, value: number) {\r\n if (!this.changes.$inc)\r\n this.changes.$inc = {};\r\n\r\n this.changes.$inc[path] = value;\r\n }\r\n\r\n private push(path: string, value: any) {\r\n if (!this.changes.$push)\r\n this.changes.$push = {};\r\n\r\n if (this.changes.$push[path]) {\r\n if (this.changes.$push[path].$each)\r\n this.changes.$push[path].$each.push(value);\r\n else\r\n this.changes.$push[path] = { $each: [this.changes.$push[path], value] };\r\n } else this.changes.$push[path] = value;\r\n }\r\n\r\n private pull(path: string, value: any) {\r\n if (!this.changes.$pull)\r\n this.changes.$pull = {};\r\n\r\n if (this.changes.$pullAll && this.changes.$pullAll[path]) {\r\n return this.changes.$pullAll[path].push(value);\r\n }\r\n\r\n if (this.changes.$pull[path]) {\r\n this.pullAll(path, [this.changes.$pull[path], value]);\r\n delete this.changes.$pull[path];\r\n if (_.keys(this.changes.$pull).length === 0)\r\n delete this.changes.$pull;\r\n return;\r\n }\r\n\r\n this.changes.$pull[path] = value;\r\n }\r\n\r\n private pullAll(path: string, values: any[]) {\r\n if (!this.changes.$pullAll)\r\n this.changes.$pullAll = {};\r\n\r\n this.changes.$pullAll[path] = values;\r\n }\r\n\r\n private resolve(...args) {\r\n var validArguments = [];\r\n args.forEach(function (arg) {\r\n if (arg) validArguments.push(arg);\r\n });\r\n return validArguments.join('.');\r\n }\r\n\r\n private almostEqual(o1: Object, o2: Object);\r\n private almostEqual(o1: any, o2: any) {\r\n if (!_.isPlainObject(o1) || !_.isPlainObject(o2)) return o1 == o2 ? 1 : 0;\r\n\r\n var o1i, o1k = Object.keys(o1);\r\n var o2k = Object.keys(o2);\r\n\r\n var commonKeys = [];\r\n for (o1i = 0; o1i < o1k.length; o1i++)\r\n if (~o2k.indexOf(o1k[o1i])) commonKeys.push(o1k[o1i]);\r\n\r\n var totalKeys = o1k.length + o2k.length - commonKeys.length;\r\n var keysDifference = totalKeys - commonKeys.length;\r\n\r\n var requiredChanges = 0;\r\n for (var i = 0; i < commonKeys.length; i++)\r\n if (this.almostEqual(o1[commonKeys[i]], o2[commonKeys[i]]) < 1) requiredChanges++;\r\n\r\n return 1 - (keysDifference / totalKeys) - (requiredChanges / commonKeys.length);\r\n }\r\n}"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/doc/Migrating from 4.x to 5.x.md b/doc/Migrating from 4.x to 5.x.md new file mode 100644 index 0000000..840d71f --- /dev/null +++ b/doc/Migrating from 4.x to 5.x.md @@ -0,0 +1,145 @@ +# Migrating from v4.x to v5.x +There have been some major architectural changes to Iridium between v4.x and v5.x - most significant being the switch +to TypeScript over JavaScript. This serves two primary purposes, the first being that we can finally provide true +IntelliSense support without having to hack around the limitations of individual editors, and the second being that +both Iridium and applications you write which make use of it are far less likely to contain errors as a result of +poor API usage. + +Part of this transition required changes to the way certain things are handled by Iridium, those changes are detailed +in the [Architectural Changes](#architectural_changes) section. + +## First things first +This isn't a small update (like the one from v2.x to v3.x or v3.x to v4.x) and will **definitely** require changes to your application +if you wish to make use of it. We will be supporting the 3.x branch for a short time, providing bug and security fixes +as necessary, however new features will not be backported. + +We recommend that you **do not upgrade existing applications** to the 4.x branch unless you've got a lot of time on your +hands or are using TypeScript already. If however you are set on doing so, you will need to change the following things. + +## The Performance Difference +With the new architecture it is a lot easier to reason about how V8's compiler will optimize objects, helping to make +sure that performance is significantly better than Iridium's already high level. + +Running the benchmark scripts from v4.x and v5.x, (which run the same combination of 10 000 inserts, finds and removes) +we saw the following results. + +### Iridium 4.x Benchmark +``` +MongoDB 10000 Inserts { w: 1 } + => 412ms +Iridium 10000 Inserts { w: 1, wrap: false } + => 1124ms (2.7x slower) +Iridium 10000 Inserts { w: 1, wrap: true } + => 1700ms (4.2x slower) + +MongoDB find() + => 83ms +Iridium find() { wrap: false } + => 216ms (2.6x slower) +Iridium find() { wrap: true } + => 669ms (8.06x slower) + +MongoDB remove() + => 151ms +Iridium remove() + => 147ms (about the same) +``` + +### Iridium 5.x Benchmark +``` +Running benchmark with intensity of 10000 +MongoDB inserting: 368ms +Iridium inserting: 920ms (2.5x slower) +Iridium Instances inserting: 1151ms (3.1x slower) + +MongoDB finding: 87ms +Iridium finding: 268ms (3.1x slower) +Iridium Instances finding: 344ms (4.0x slower) + +MongoDB removing: 182ms +Iridium Instances removing: 159ms (about the same) +Iridium removing: 158ms (about the same) +``` + +As you can see, with the exception of the unwrapped find() method, we see an across-the-board improvement in performance +of up to 100% (most significant when wrapping documents in the new Instance type). We're very happy with these gains, and +keep in mind that these v5.x benchmarks were run before any optmization work has begun on the 5.x branch - things are +definitely looking good. + +## Architectural Changes +The biggest change between v4.x and v5.x is that Iridium is now primarily developed in TypeScript. Yes, you read that +correctly, [TypeScript](http://www.typescriptlang.org). I'll be perfectly honest with you, it was my intention to do +so from the get go but when I started Iridium it simply wasn't mature enough (v0.9 at the time) to support many of the +features I wanted to implement. + +With the latest version of TypeScript (v1.4) the building blocks were finally available and so I set about rewriting +Iridium the way I wanted to write it initially. This has meant a couple of changes to the way you do certain things +which you may like or dislike depending on how you decide to use Iridium. + +The biggest difference is that the `Instance` type which used to wrap documents to provide methods such as `save()` +has now been decoupled from the `Model` type (which wraps a MongoDB collection). This means that it is now possible +to develop your own custom instance types to replace Iridum's default one if you wish, but it also is the result of +a change in the way Models are defined. + +In Iridium v4.x you would define a model's properties and methods through the options object like this: + +```javascript +var model = new Iridium.Model(core, 'collection', { + id: false, + fullname: String +}, { + methods: { + jump: function() { } + }, + virtuals: { + avatar: function() { }, + password: { + get: function() { }, + set: function(value) { } + } + } +}); +``` + +This worked well from a JavaScript perspective but made it incredibly difficult for your IDE to figure out what was +going on. For starters, the `avatar()` function's `this` at design time would point to the `virtuals: {}` object +and there was no tangible relationship between the created instances and the methods, virtuals or properties defined +in the model. + +In Iridium v5.x we've changed things up a bit, using TypeScript generics you can now specify an interface which describes +the documents present in the collection, as well as a class which will be used to wrap those documents. It's actually +easier than you'd think since Iridium provides a number of useful helpers. + +```typescript +interface CollectionDocument { + id: string; + fullname: string; +} + +class CollectionInstance extends Iridium.Instance { + id: string; + fullname: string; + jump() { } + get avatar() { } + get password() { } + set password() { } +} + +var model = new Iridium.Model(core, CollectionInstance, 'collection', { + id: false, + fullname: String +}); +``` + +So, what's changed? Well, we've defined an interface which describes our collection documents - this allows your IDE +to provide contextual completion on any method which expects a document property. +So `model.create({ id: 'test', fullname: 'Test User' })` for example will provide hints for `id` and `fullname` - making +your life significantly easier. + +We've also defined a class called `CollectionInstance` which extends the default Iridium Instance type (to gain `save()` +and friends) which is then passed into our model's constructor in the `instanceType` field. From there on everything is +pretty much identical to Iridium 4.x - with the obvious removal of the `methods` and `virtuals` options since they are +no longer required. + +The best part is, you still get to keep all the same great performance you got with Iridium 4.x - with a massive boost +in developer productivity as a result of having inline assistance from your IDE. diff --git a/example/IntelliSense.js b/example/IntelliSense.js deleted file mode 100644 index 4bd967d..0000000 --- a/example/IntelliSense.js +++ /dev/null @@ -1,31 +0,0 @@ -var Iridium = require('../index'); - -var db = new Iridium({ - database: 'iridium_test' -}); - -db.register('Test', new Iridium.Model(db, 'test', { - name: String, - birthday: Date -}, { - virtuals: { - age: function() { - /// - } - }, - methods: { - greet: function() { - console.log('Hello!'); - } - } -})); - -var x = new db.Test.Instance({ name: 'test', birthday: new Date() }); -x.name.toUpperCase(); -x.birthday.toDateString(); - -db.Test.insert({}, function(err, instance) { - /// - instance.name = "Demo"; - instance.greet(); -}); \ No newline at end of file diff --git a/example/IntelliSense.ts b/example/IntelliSense.ts new file mode 100644 index 0000000..b30ae8f --- /dev/null +++ b/example/IntelliSense.ts @@ -0,0 +1,91 @@ + +/// +import Iridium = require('iridium'); + +interface UserDoc { + _id?: string; + username: string; + fullname: string; + email: string; + dateOfBirth: Date; + passwordHash: string; + joined?: Date; +} + +class User extends Iridium.Instance implements UserDoc, Iridium.Hooks { + _id: string; + username: string; + fullname: string; + email: string; + dateOfBirth: Date; + passwordHash: string; + joined: Date; + + changePassword(newPassword: string) { + this.passwordHash = newPassword.toLowerCase(); + } + + static onCreating(doc: UserDoc) { + doc.joined = new Date(); + } +} + +class MyDB extends Iridium.Core { + Users = new Iridium.Model(this, User, "users", { + _id: false, + username: /^[a-z][a-z0-9_]{7,}$/, + fullname: String, + email: String, + dateOfBirth: Date, + passwordHash: String, + joined: Date + }, { + indexes: [ + { email: 1 } + ] + }); + + PlainUsers = new Iridium.Model(this,(model, doc) => doc, "users", { + _id: false, + username: /^[a-z][a-z0-9_]{7,}$/, + fullname: String, + email: String, + dateOfBirth: Date, + passwordHash: String, + joined: Date + }, { + indexes: [ + { email: 1 } + ] + }); +} + +var db = new MyDB("mongodb://localhost/test"); + +db.connect().then(function () { + db.Users.insert({ fullname: 'test', username: 'test', passwordHash: 'test', email: 'test@test.com', dateOfBirth: new Date() }).then(function (user) { + user.fullname; + user.dateOfBirth.getTime(); + }); + + db.Users.insert([{ fullname: 'test', username: 'test', passwordHash: 'test', email: 'test@test.com', dateOfBirth: new Date() }]).then(function (users) { + users[0].fullname; + }); + + db.Users.findOne().then(function (instance) { + instance.save().then(function (i) { + i.remove().then(function (i) { + i.username = 'test'; + return i.save(); + }); + }); + }); + + db.Users.count().then(function (count) { + count.toPrecision(2); + }); + + db.PlainUsers.get().then(function (plainUser) { + plainUser.username; + }); +}); \ No newline at end of file diff --git a/example/LowercaseCollectionsPlugin.js b/example/LowercaseCollectionsPlugin.js deleted file mode 100644 index df8b598..0000000 --- a/example/LowercaseCollectionsPlugin.js +++ /dev/null @@ -1,9 +0,0 @@ -var Database = require('iridium'); - -var Plugin = { - newModel: function(db, db, collection, schema, options) { - this.collection = collection.toLowerCase(); - } -}; - -module.exports = Plugin; \ No newline at end of file diff --git a/example/ModelHookPlugin.ts b/example/ModelHookPlugin.ts new file mode 100644 index 0000000..c7604e5 --- /dev/null +++ b/example/ModelHookPlugin.ts @@ -0,0 +1,9 @@ +import Iridium = require('../index'); + +export = LowercaseCollectionsPlugin; + +class LowercaseCollectionsPlugin implements Iridium.Plugin { + newModel(model: Iridium.Model) { + model.collectionName = model.collectionName.toLowerCase(); + } +} \ No newline at end of file diff --git a/example/StringCaseValidationPlugin.js b/example/StringCaseValidationPlugin.js deleted file mode 100644 index 19e6c21..0000000 --- a/example/StringCaseValidationPlugin.js +++ /dev/null @@ -1,18 +0,0 @@ -var Database = require('iridium'), - skmatc = require('skmatc'); - -var Plugin = { - validate: [ - skmatc.Validator.module(function(schema) { - return schema == "Uppercase"; - }, function(schema, data, path) { - return this.assert(value.toUpperCase() == value); - }), - skmatc.Validator.module(function(schema) { - return schema == "Lowercase"; - }, function(schema, data, path) { - return this.assert(value.toLowerCase() == value); - })] -}; - -module.exports = Plugin; \ No newline at end of file diff --git a/example/UserModel.js b/example/UserModel.js deleted file mode 100644 index 4243ad6..0000000 --- a/example/UserModel.js +++ /dev/null @@ -1,168 +0,0 @@ -/// - -var _ = require('lodash'); -var Database = require('../index.js'); -var Model = Database.Model; -var Concoction = require('concoction'); - -module.exports = function (db) { - /// Configure the User model to use the given database - /// The database connection to use - /// - - "use strict"; - var database = db; - - var options = { - virtuals: { - API: function () { - var $ = this; - - return { - username: $.username, - fullname: $.fullname, - email: $.email, - banned: $.banned, - statistics: $.statistics, - skill: { - level: $.skill.level, - xp: $.skill.xp - }, - friends: $.friends, - pending_messages: $.pending_messages, - last_seen: $.last_seen - }; - } - }, - methods: { - setPassword: function (newPassword, callback) { - /// Updates the user's stored password hash - /// The new password to use for the user - /// A function to be called once the user's password has been updated - - var passwordTest = /(?=^.{8,}$)((?=.*\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[^A-Za-z0-9])(?=.*[a-z])|(?=.*[^A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9]))^.*/; - if (!passwordTest.test(newPassword || '')) return callback(new Error('Password didn\'t meet the minimum safe password requirements. Passwords should be at least 8 characters long, and contain at least 3 of the following categories: lowercase letters, uppercase letters, numbers, characters')); - - var hashed = require('crypto').createHash('sha512').update(database.settings.security.salt).update(newPassword).digest('hex'); - this.password = hashed; - this.save(callback); - }, - checkPassword: function (password) { - /// Checks whether a given password is correct for a user's account - /// The password to validate against the user's password hash. - /// - - var hashed = require('crypto').createHash('sha512').update(database.settings.security.salt).update(password).digest('hex'); - return hashed == this.password; - }, - addFriend: function (friend, callback) { - this.save({ $push: { friends: friend } }, callback); - }, - updateLevel: function () { - /// Update's the user's current level based on the amount of XP they have. Doesn't save the user instance. - - // Amount of XP required per level starts at 1200, doubles for each consecutive level - // tf. XP_n = XP_nm1 + 1200 * 2^n - - var remainingXP = this.skill.xp; - - var previousLevelXP = 0; - var levelXP = 1200; - var level = 0; - - for(; remainingXP >= levelXP; level++, previousLevelXP = levelXP, remainingXP -= levelXP, levelXP += 1200 * Math.pow(2, level)); - - this.skill.level = level; - this.skill.current_level = previousLevelXP; - this.skill.next_level = levelXP; - } - }, - hooks: { - creating: function (done) { - var item = this; - - item._id = item.username; - if (item.username) delete item.username; - - var passwordTest = /(?=^.{8,}$)((?=.*\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[^A-Za-z0-9])(?=.*[a-z])|(?=.*[^A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9]))^.*/; - - if (!passwordTest.test(item.password || '')) return done('Password didn\'t meet the minimum safe password requirements. Passwords should be at least 8 characters long, and contain at least 3 of the following categories: lowercase letters, uppercase letters, numbers, characters'); - - item.password = require('crypto').createHash('sha512').update(database.settings.security.salt).update(item.password).digest('hex'); - - _.defaults(item, { - type: "Player", - banned: false, - statistics: { - won: 0, - drawn: 0, - lost: 0, - incomplete: 0 - }, - skill: { - matchmaking: 0, - trend: 0, - level: 0, - xp: 0, - current_level: 0, - next_level: 1200 - }, - friends: [], - friend_requests: [], - pending_messages: [], - sessions: [], - last_seen: new Date() - }); - - done(); - } - }, - preprocessors: [ - new Concoction.Rename({ - _id: 'username' - }) - ], - indexes: [ - [{ email: 1 }, { unique: true, background: true }], - [{ type: 1 }, { background: true }], - [{ sessions: 1 }, { sparse: true, background: true }], - [{ 'skill.xp': -1 }, { background: true }] - ] - }; - - var schema = { - _id: /[a-z0-9]+(_[a-z0-9]+)*/, - fullname: String, - email: String, - password: String, - type: /Player|Moderator|Admin/, - banned: Boolean, - statistics: { - won: Number, - drawn: Number, - lost: Number, - incomplete: Number - }, - skill: { - matchmaking: Number, - trend: Number, - level: Number, - xp: Number, - current_level: Number, - next_level: Number - }, - friends: [String], - pending_messages: [{ - from: String, - time: Date, - message: String, - group: { $type: String, $required: false }, - game: { $type: String, $required: false } - }], - sessions: [String], - friend_requests: [String], - last_seen: Date - }; - - return new Model(db, 'user', schema, options); -}; diff --git a/example/UserModel.ts b/example/UserModel.ts new file mode 100644 index 0000000..c935ad9 --- /dev/null +++ b/example/UserModel.ts @@ -0,0 +1,221 @@ +/// +/// + +import _ = require('lodash'); +import Promise = require('bluebird'); +import * as Iridium from 'iridium'; +import {Index, Property} from 'iridium'; + +var settings: any = {}; + +export interface UserDocument { + _id: string; + fullname: string; + email: string; + password: string; + type: string; + banned: boolean; + statistics: { + won: number; + drawn: number; + lost: number; + incomplete: number; + }; + skill: { + matchmaking: number; + trend: number; + level: number; + xp: number; + current_level: number; + next_level: number; + }; + friends: string[]; + + pending_messages: { + from: string; + time: Date; + message: string; + group?: string; + game?: string; + }[]; + sessions: string[]; + friend_requests: string[]; + last_seen: Date; +} + +@Iridium.Collection('user') +@Index({ email: 1 }, { unique: true }) +@Index({ sessions: 1 }, { sparse: true }) +@Index({ 'skill.xp': -1 }, { background: true }) +export class User extends Iridium.Instance implements UserDocument { + @Iridium.Property(/^[a-z][a-z0-9_]{7,}$/) _id: string; + get username() { + return this._id; + } + + @Property(String) fullname: string; + @Property(/^.+@.+$/) email: string; + @Property(String) password: string; + @Property(/Player|Moderator|Admin/) type: string; + @Property(Boolean) banned: boolean; + + @Property({ + won: Number, + drawn: Number, + lost: Number, + incomplete: Number + }) + statistics: { + won: number; + drawn: number; + lost: number; + incomplete: number; + }; + + @Property({ + matchmaking: Number, + trend: Number, + level: Number, + xp: Number, + current_level: Number, + next_level: Number + }) + skill: { + matchmaking: number; + trend: number; + level: number; + xp: number; + current_level: number; + next_level: number; + }; + + @Property([String]) + friends: string[]; + + @Property([{ + from: String, + time: Date, + message: String, + group: { $required: false, $type: String }, + game: { $required: false, $type: String } + }]) + pending_messages: { + from: string; + time: Date; + message: string; + group?: string; + game?: string; + }[]; + + @Property([String]) + sessions: string[]; + + @Property([String]) + friend_requests: string[]; + + @Property(Date) + last_seen: Date; + + static onCreating(user: UserDocument) { + var passwordTest = /(?=^.{8,}$)((?=.*\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[^A-Za-z0-9])(?=.*[a-z])|(?=.*[^A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9]))^.*/; + + if (!passwordTest.test(user.password || '')) return Promise.reject(new Error('Password didn\'t meet the minimum safe password requirements. Passwords should be at least 8 characters long, and contain at least 3 of the following categories: lowercase letters, uppercase letters, numbers, characters')); + + user.password = require('crypto').createHash('sha512').update(settings.security.salt).update(user.password).digest('hex'); + + _.defaults(user, { + type: "Player", + banned: false, + statistics: { + won: 0, + drawn: 0, + lost: 0, + incomplete: 0 + }, + skill: { + matchmaking: 0, + trend: 0, + level: 0, + xp: 0, + current_level: 0, + next_level: 1200 + }, + friends: [], + friend_requests: [], + pending_messages: [], + sessions: [], + last_seen: new Date() + }); + } + + get API() { + var $ = this; + + return { + username: $.username, + fullname: $.fullname, + email: $.email, + banned: $.banned, + statistics: $.statistics, + skill: { + level: $.skill.level, + xp: $.skill.xp + }, + friends: $.friends, + pending_messages: $.pending_messages, + last_seen: $.last_seen + }; + } + + setPassword(newPassword: string, callback: (err?: Error, user?: User) => void) { + /// Updates the user's stored password hash + /// The new password to use for the user + /// A function to be called once the user's password has been updated + + var passwordTest = /(?=^.{8,}$)((?=.*\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[^A-Za-z0-9])(?=.*[a-z])|(?=.*[^A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9]))^.*/; + if (!passwordTest.test(newPassword || '')) return callback(new Error('Password didn\'t meet the minimum safe password requirements. Passwords should be at least 8 characters long, and contain at least 3 of the following categories: lowercase letters, uppercase letters, numbers, characters')); + + var hashed = require('crypto').createHash('sha512').update(settings.security.salt).update(newPassword).digest('hex'); + this.password = hashed; + this.save(callback); + } + checkPassword(password: string): boolean { + /// Checks whether a given password is correct for a user's account + /// The password to validate against the user's password hash. + /// + + var hashed = require('crypto').createHash('sha512').update(settings.security.salt).update(password).digest('hex'); + return hashed == this.password; + } + addFriend(friend: string, callback: (err?: Error, user?: User) => void) { + this.save({ $push: { friends: friend } }, callback); + } + updateLevel() { + /// Update's the user's current level based on the amount of XP they have. Doesn't save the user instance. + + // Amount of XP required per level starts at 1200, doubles for each consecutive level + // tf. XP_n = XP_nm1 + 1200 * 2^n + + var remainingXP = this.skill.xp; + + var previousLevelXP = 0; + var levelXP = 1200; + var level = 0; + + for (; remainingXP >= levelXP; level++ , previousLevelXP = levelXP, remainingXP -= levelXP, levelXP += 1200 * Math.pow(2, level)); + + this.skill.level = level; + this.skill.current_level = previousLevelXP; + this.skill.next_level = levelXP; + } +} + +export function Users(core: Iridium.Core): Iridium.Model { + return new Iridium.Model(core, User); +} + +var usrModel = Users(null); +usrModel.findOne().then(function (user) { + if (user.checkPassword("test")) return true; + return false; +}); diff --git a/example/ValidationPlugin.ts b/example/ValidationPlugin.ts new file mode 100644 index 0000000..2d0b639 --- /dev/null +++ b/example/ValidationPlugin.ts @@ -0,0 +1,12 @@ +/// +import Iridium = require('../index'); +import Skmatc = require('skmatc'); + +export = StringCaseValidationPlugin; + +class StringCaseValidationPlugin implements Iridium.Plugin { + validate = [ + Skmatc.create((schema) => schema === "Lowercase", function (schema, data, path) { return this.assert(data.toLowerCase() == data) }), + Skmatc.create((schema) => schema === "Uppercase", function (schema, data, path) { return this.assert(data.toUpperCase() == data) }) + ]; +} diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..c9ef621 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1 @@ +require('require-dir')('build'); \ No newline at end of file diff --git a/index.js b/index.js deleted file mode 100644 index d1ef699..0000000 --- a/index.js +++ /dev/null @@ -1,6 +0,0 @@ -var Database = require('./lib/Database.js'); -var Concoction = require('concoction'); - -Database.Concoction = Concoction; - -(require.modules || {}).iridium = (require.modules || {}).index = module.exports = Database; \ No newline at end of file diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..5e7f8df --- /dev/null +++ b/index.ts @@ -0,0 +1,21 @@ +import Core from './lib/Core'; +import Model from './lib/Model'; +import Instance from './lib/Instance'; +export {Core, Model, Instance}; + +export * from './lib/Decorators'; + +export * from './lib/Plugins'; +export * from './lib/Schema'; +export * from './lib/Cache'; +export * from './lib/CacheDirector'; +export * from './lib/ModelOptions'; +export * from './lib/Configuration'; +export * from './lib/Hooks'; + +import MemoryCache from './lib/caches/MemoryCache'; +import NoOpCache from './lib/caches/NoOpCache'; +export {MemoryCache, NoOpCache}; + +import IDDirector from './lib/cacheControllers/IDDirector'; +export {IDDirector as CacheOnID}; \ No newline at end of file diff --git a/iridium.d.ts b/iridium.d.ts new file mode 100644 index 0000000..2f0553d --- /dev/null +++ b/iridium.d.ts @@ -0,0 +1,5 @@ +/// +declare module 'iridium' { + import Iridium = require('iridium/index'); + export = Iridium; +} \ No newline at end of file diff --git a/lib/.gitignore b/lib/.gitignore new file mode 100644 index 0000000..69a24fb --- /dev/null +++ b/lib/.gitignore @@ -0,0 +1,2 @@ +*.js +*.map \ No newline at end of file diff --git a/test/.gitignore b/lib/.npmignore similarity index 100% rename from test/.gitignore rename to lib/.npmignore diff --git a/lib/Cache.ts b/lib/Cache.ts new file mode 100644 index 0000000..6208825 --- /dev/null +++ b/lib/Cache.ts @@ -0,0 +1,8 @@ +/// +import Bluebird = require('bluebird'); + +export interface Cache { + set(key: string, value: T): void; + get(key: string): Bluebird; + clear(key: string): void +} \ No newline at end of file diff --git a/lib/CacheDirector.ts b/lib/CacheDirector.ts new file mode 100644 index 0000000..6173380 --- /dev/null +++ b/lib/CacheDirector.ts @@ -0,0 +1,8 @@ +/// +export interface CacheDirector { + valid(object: T): boolean; + buildKey(object: T): string; + + validQuery(conditions: any): boolean; + buildQueryKey(conditions: any): string; +} \ No newline at end of file diff --git a/lib/Configuration.ts b/lib/Configuration.ts new file mode 100644 index 0000000..443d0ab --- /dev/null +++ b/lib/Configuration.ts @@ -0,0 +1,10 @@ +/// +export interface Configuration { + host?: string; + port?: number; + hosts?: { address: string; port?: number }[]; + database?: string; + username?: string; + password?: string; + [key:string]: any; +} \ No newline at end of file diff --git a/lib/Core.ts b/lib/Core.ts new file mode 100644 index 0000000..d6822bb --- /dev/null +++ b/lib/Core.ts @@ -0,0 +1,192 @@ +/// +import Bluebird = require('bluebird'); +import MongoDB = require('mongodb'); +import _ = require('lodash'); +import http = require('http'); +import events = require('events'); + +import {Configuration} from './Configuration'; +import {Plugin} from './Plugins'; +import Model from './Model'; +import Instance from './Instance'; + +import {MiddlewareFactory} from './Middleware'; +import * as ExpressMiddleware from './middleware/Express'; +import ExpressMiddlewareFactory from './middleware/Express'; + +import {Cache} from './Cache'; +import NoOpCache from './caches/NoOpCache'; +import MemoryCache from './caches/MemoryCache'; + +var mongoConnectAsyc = Bluebird.promisify(MongoDB.MongoClient.connect); + +export default class Core { + /** + * Creates a new Iridium Core instance connected to the specified MongoDB instance + * @param {Iridium.IridiumConfiguration} config The config object defining the database to connect to + * @constructs Core + */ + constructor(config: Configuration); + /** + * Creates a new Iridium Core instance connected to the specified MongoDB instance + * @param {String} url The URL of the MongoDB instance to connect to + * @param {Iridium.IridiumConfiguration} config The config object made available as settings + * @constructs Core + */ + constructor(uri: string, config?: Configuration); + constructor(uri: string | Configuration, config?: Configuration) { + + var args = Array.prototype.slice.call(arguments, 0); + uri = config = null; + for (var i = 0; i < args.length; i++) { + if (typeof args[i] == 'string') + uri = args[i]; + else if (typeof args[i] == 'object') + config = args[i]; + } + + if (!uri && !config) throw new Error("Expected either a URI or config object to be supplied when initializing Iridium"); + + this._url = uri; + this._config = config; + } + + private _plugins: Plugin[] = []; + private _url: string; + private _config: Configuration; + private _connection: MongoDB.Db; + private _cache: Cache = new NoOpCache(); + + /** + * Gets the plugins registered with this Iridium Core + * @returns {[Iridium.Plugin]} + */ + get plugins(): Plugin[] { + return this._plugins; + } + + /** + * Gets the configuration specified in the construction of this + * Iridium Core. + * @returns {Iridium.Configuration} + */ + get settings(): Configuration { + return this._config; + } + + /** + * Gets the currently active database connection for this Iridium + * Core. + * @returns {MongoDB.Db} + */ + get connection(): MongoDB.Db { + return this._connection; + } + + /** + * Gets the URL used to connect to MongoDB + * @returns {String} + */ + get url(): string { + if (this._url) return this._url; + var url: string = 'mongodb://'; + + if (this._config.username) { + url += this._config.username; + if (this._config.password) + url += ':' + this._config.password; + url += '@'; + } + + var hosts = []; + + if (this._config.host) { + if (this._config.port) + hosts.push(this._config.host + ':' + this._config.port); + else + hosts.push(this._config.host); + } + + if (this._config.hosts) { + _.each(this._config.hosts, (host) => { + if (host.port) + hosts.push(host.address + ':' + host.port); + else if(this._config.port) + hosts.push(host.address + ':' + this._config.port); + else + hosts.push(host.address); + }); + } + + if (hosts.length) + url += _.uniq(hosts).join(','); + else + url += 'localhost'; + + url += '/' + this._config.database; + + return url; + } + + /** + * Gets the cache used to store objects retrieved from the database for performance reasons + * @returns {cache} + */ + get cache(): Cache { + return this._cache; + } + + set cache(value: Cache) { + this._cache = value; + } + + /** + * Registers a new plugin with this Iridium Core + * @param {Iridium.Plugin} plugin The plugin to register with this Iridium Core + * @returns {Iridium.Core} + */ + register(plugin: Plugin): Core { + this.plugins.push(plugin); + return this; + } + + /** + * Connects to the database server specified in the provided configuration + * @param {function(Error, Iridium.Core)} [callback] A callback to be triggered once the connection is established. + * @returns {Promise} + */ + connect(callback?: (err: Error, core: Core) => any): Bluebird { + var self = this; + return Bluebird.bind(this).then(function() { + if (self._connection) return self._connection; + return mongoConnectAsyc(self.url); + }).then(function(db: MongoDB.Db) { + self._connection = db; + return self; + }).nodeify(callback); + } + + /** + * Closes the active database connection + * @type {Promise} + */ + close(): Bluebird { + var self = this; + return Bluebird.bind(this).then(function() { + if (!self._connection) return this; + var conn: MongoDB.Db = self._connection; + self._connection = null; + conn.close(); + return this; + }); + } + + /** + * Provides an express middleware which can be used to set the req.db property + * to the current Iridium instance. + * @returns {Iridium.ExpressMiddleware} + */ + express(): ExpressMiddleware.ExpressMiddleware { + return ExpressMiddlewareFactory(this); + } +} diff --git a/lib/Cursor.ts b/lib/Cursor.ts new file mode 100644 index 0000000..9d3a6c7 --- /dev/null +++ b/lib/Cursor.ts @@ -0,0 +1,142 @@ +/// +import Model from './Model'; +import General = require('./General'); +import MongoDB = require('mongodb'); +import Bluebird = require('bluebird'); +import * as Index from './Index'; + +export default class Cursor { + /** + * Creates a new Iridium cursor which wraps a MongoDB cursor object + * @param {Model} model The Iridium model that this cursor belongs to + * @param {Object} conditions The conditions that resulte in this cursor being created + * @param {MongoDB.Cursor} cursor The MongoDB native cursor object to be wrapped + * @constructor + */ + constructor(private model: Model, private conditions: any, public cursor: MongoDB.Cursor) { + + } + + /** + * Counts the number of documents which are matched by this cursor + * @param {function(Error, Number)} callback A callback which is triggered when the result is available + * @return {Promise} A promise which will resolve with the number of documents matched by this cursor + */ + count(callback?: General.Callback): Bluebird { + return new Bluebird((resolve, reject) => { + this.cursor.count(true,(err, count) => { + if (err) return reject(err); + return resolve(count); + }); + }).nodeify(callback); + } + + /** + * Runs the specified handler over each instance in the query results + * @param {function(Instance)} handler The handler which is triggered for each element in the query + * @param {function(Error)} callback A callback which is triggered when all operations have been dispatched + * @return {Promise} A promise which is resolved when all operations have been dispatched + */ + forEach(handler: (instance: TInstance) => void, callback?: General.Callback): Bluebird { + var helpers = this.model.helpers; + return new Bluebird((resolve, reject) => { + this.cursor.forEach((item: TDocument) => { + this.model.handlers.documentReceived(this.conditions, item, function () { return helpers.wrapDocument.apply(helpers, arguments); }).then(handler); + },(err) => { + if (err) return reject(err); + return resolve(null); + }); + }).nodeify(callback); + } + + /** + * Runs the specified transform over each instance in the query results and returns the resulting transformed objects + * @param {function(Instance): TResult} transform A handler which is used to transform the result objects + * @param {function(Error, TResult[])} callback A callback which is triggered when the transformations are completed + * @return {Promise} A promise which is fulfilled with the results of the transformations + */ + map(transform: (instance: TInstance) => TResult | Bluebird, callback?: General.Callback): Bluebird { + var helpers = this.model.helpers; + return new Bluebird((resolve, reject) => { + var promises: Bluebird[] = []; + this.cursor.forEach((item: TDocument) => { + promises.push(this.model.handlers.documentReceived(this.conditions, item, function () { return helpers.wrapDocument.apply(helpers, arguments); }) + .then(<(instance) => TResult>transform)); + },(err) => { + if (err) return reject(err); + return resolve(Bluebird.all(promises)); + }); + }).nodeify(callback); + } + + /** + * Retrieves all matching instances and returns them in an array + * @param {function(Error, TInstance[])} callback A callback which is triggered with the resulting instances + * @return {Promise} A promise which resolves with the instances returned by the query + */ + toArray(callback?: General.Callback): Bluebird { + var helpers = this.model.helpers; + return new Bluebird((resolve, reject) => { + this.cursor.toArray((err, results: any[]) => { + if (err) return reject(err); + return resolve(results); + }); + }).map((document) => { + return this.model.handlers.documentReceived(this.conditions, document, function () { return helpers.wrapDocument.apply(helpers, arguments); }); + }).nodeify(callback); + } + + /** + * Retrieves the next item in the results list + * @param {function(Error, TInstance)} callback A callback which is triggered when the next item becomes available + * @return {Promise} A promise which is resolved with the next item + */ + next(callback?: General.Callback): Bluebird { + return new Bluebird((resolve, reject) => { + this.cursor.next((err, result: any) => { + if (err) return reject(err); + return resolve(result); + }); + }).then((document) => { + if (!document) return Bluebird.resolve(null); + return this.model.handlers.documentReceived(this.conditions, document,(document, isNew?, isPartial?) => this.model.helpers.wrapDocument(document, isNew, isPartial)); + }).nodeify(callback); + } + + /** + * Returns a new cursor which behaves the same as this one did before any results were retrieved + * @return {Cursor} The new cursor which starts at the beginning of the results + */ + rewind(): Cursor { + this.cursor.rewind(); + return this; + } + + /** + * Returns a new cursor which sorts its results by the given index expression + * @param {model.IndexSpecification} sortExpression The index expression dictating the sort order and direction to use + * @return {Cursor} The new cursor which sorts its results by the sortExpression + */ + sort(sortExpression: Index.IndexSpecification): Cursor { + return new Cursor(this.model, this.conditions, this.cursor.sort(sortExpression)); + } + + /** + * Returns a new cursor which limits the number of returned results + * @param {Number} limit The maximum number of results to return + * @return {Cursor} The new cursor which will return a maximum number of results + */ + limit(limit: number): Cursor { + return new Cursor(this.model, this.conditions, this.cursor.limit(limit)); + } + + /** + * Returns a new cursor which skips a number of results before it begins + * returning any. + * @param {Number} skip The number of results to skip before the cursor beings returning + * @return {Cursor} The new cursor which skips a number of results + */ + skip(skip: number): Cursor { + return new Cursor(this.model, this.conditions, this.cursor.skip(skip)); + } +} \ No newline at end of file diff --git a/lib/Database.js b/lib/Database.js deleted file mode 100644 index 11d4ca6..0000000 --- a/lib/Database.js +++ /dev/null @@ -1,171 +0,0 @@ -var _ = require('lodash'), - fn = require('functionality'), - Promise = require('bluebird'), - MongoClient = Promise.promisifyAll(require('mongodb').MongoClient); - -var IridiumModel = require('./Model.js'), - IridiumInstance = require('./Instance.js'); - -(require.modules || {}).IridiumDatabase = module.exports = IridiumDatabase; - -function IridiumDatabase(uri, config) { - /// Creates a new Iridium instance - /// A MongoDB URI which can be used to connect to the database - /// A configuration object describing the details of the database to connect to and which becomes available as db.settings - - "use strict"; - if(!(this instanceof IridiumDatabase)) return new IridiumDatabase(config); - - this.connection = null; - this.models = {}; - this.plugins = []; - - var args = Array.prototype.slice.call(arguments, 0); - uri = config = null; - for(var i = 0; i < args.length; i++) { - if(typeof args[i] == 'string') - uri = args[i]; - else if(typeof args[i] == 'object') - config = args[i]; - } - - if(!uri && !config) throw new Error("Expected either a URI or config object to be supplied when initializing Iridium"); - - if(uri) { - Object.defineProperty(this, 'uri', { - get: function () { - return config; - }, - enumerable: false - }); - } - if(config) { - Object.defineProperty(this, 'settings', { - get: function () { - return config; - }, - enumerable: false - }); - } -} - -IridiumDatabase.Model = IridiumModel; -IridiumDatabase.Instance = IridiumInstance; - -IridiumDatabase.prototype = { - get uri() { - /// Gets a URL which can be used to connect to a MongoDB instance based on the configuration - /// - - "use strict"; - var uri = 'mongodb://'; - if (this.settings.username && this.settings.password) - uri += this.settings.username + ':' + this.settings.password + '@'; - uri += this.settings.host || 'localhost'; - if (this.settings.port) - uri += ':' + this.settings.port; - uri += '/' + this.settings.database; - - return uri; - }, - get db() { - /// Gets the MongoDB database connection - - "use strict"; - if (!this.connection) throw new Error('Database connection not yet established'); - return this.connection; - } -}; - -IridiumDatabase.prototype.connect = function connect(callback) { - /// Connects to the database server specified in the provided configuration - /// A function to be called when the connection is completed, called immediately if one is already open - - "use strict"; - return Promise.bind(this).then(function() { - if(this.connection) return this.connection; - return MongoClient.connectAsync(this.uri); - }).then(function(db) { - this.connection = db; - return this; - }).nodeify(callback); -}; - -IridiumDatabase.prototype.close = IridiumDatabase.prototype.disconnect = function disconnect() { - /// Closes the active database connection - - "use strict"; - return Promise.bind(this).then(function() { - if(!this.connection) return this; - var conn = this.connection; - this.connection = conn; - conn.close(); - return this; - }); - }; - -IridiumDatabase.prototype.express = function express() { - /// Creates an Express Middleware which will make this database wrapper available through the req.db property - /// - - "use strict"; - - return (function(req, res, next) { - /// Express Middleware which will make this database wrapper available through the req.db property - /// Node Request object - /// Node Response object - /// Callback used to continue to the next step in the Express request pipeline - - this.connect().then(function(db) { - req.db = db; - return next(); - }).error(function(err) { - return next(err); - }); - }).bind(this); -}; - -IridiumDatabase.prototype.register = function register() { - /// - /// Registers a plugin with the ORM, allowing extended functionality - /// The plugin - /// - /// - /// Registers a model with the ORM (not entirely necessary) - /// The name of the model to register with the current connection - /// The model to register with the database connection - /// - /// - /// Registers a model with the ORM (not entirely necessary) - /// The name of the model to register with the ORM - /// A function which creates a model for a given database connection - /// - "use strict"; - - var args = Array.prototype.slice.call(arguments, 0); - - if(args.length === 1) { - // Plugin registration - var plugin = args[0]; - - this.plugins.push(plugin); - } else if(args.length === 2) { - // Model registration - var name = args[0]; - var model = args[1]; - - if (model.isModel) - this.models[name] = model; - else this.models[name] = model(this); - - Object.defineProperty(this, name, { - get: function () { - /// - return this.models[name]; - }, - enumerable: true - }); - } - - return this; -}; diff --git a/lib/Decorators.ts b/lib/Decorators.ts new file mode 100644 index 0000000..90ce0ec --- /dev/null +++ b/lib/Decorators.ts @@ -0,0 +1,65 @@ +/// +import MongoDB = require('mongodb'); +import _ = require('lodash'); +import skmatc = require('skmatc'); +import Instance from './Instance'; +import {Index, IndexSpecification} from './Index'; +import {Schema} from './Schema'; +import InstanceImplementation from './InstanceInterface'; + +export function Collection(name: string) { + return function(target: InstanceImplementation) { + target.collection = name; + }; +} + +export function Index(spec: IndexSpecification, options?: MongoDB.IndexOptions) { + return function(target: InstanceImplementation) { + target.indexes = (target.indexes || []).concat({ spec: spec, options: options || {} }); + } +} + +export function Validate(forType: any, validate: (schema: any, data: any, path: string) => Skmatc.Result) { + return function(target: InstanceImplementation) { + target.validators = (target.validators || []).concat(skmatc.create(schema => schema === forType, validate)); + } +} + +export function Property(asType: any, required?: boolean): (target: { constructor: Function }, name: string) => void; +export function Property(name: string, asType: any, required?: boolean): (target: Function) => void; +export function Property(...args: any[]): (target: any, name?: string) => void { + let name = null, + asType = false, + required = true; + + if (args.length > 1 && typeof args[args.length - 1] === 'boolean') + required = args.pop(); + + return function(target: any, property?: string) { + if (!property) name = args.shift(); + else { + name = property; + target = target.constructor; + } + asType = args.pop() || false; + + target.schema = _.clone(target.schema || {}); + if(!required && typeof asType !== 'boolean') target.schema[name] = { $required: required, $type: asType }; + else target.schema[name] = asType; + } +} + +export function Transform(fromDB: (value: any) => any, toDB: (value: any) => any) { + return function(target: any, property: string) { + target.constructor.transforms = _.clone(target.constructor.transforms || {}) + target.constructor.transforms[property] = { + fromDB: fromDB, + toDB: toDB + }; + }; +} + +export function ObjectID(target: { constructor: typeof Instance }, name: string) { + target.constructor.schema = _.clone(target.constructor.schema || {}); + target.constructor.schema[name] = MongoDB.ObjectID; +} \ No newline at end of file diff --git a/lib/General.ts b/lib/General.ts new file mode 100644 index 0000000..0fe4bb1 --- /dev/null +++ b/lib/General.ts @@ -0,0 +1,21 @@ +/// +export interface Callback { + (err: Error, object?: T): void; +} + +export interface Predicate { + (object: T, key?: string): boolean; +} + +export interface PropertyGetter { + (): T; +} + +export interface PropertySetter { + (value: T): void; +} + +export interface Property { + get?: PropertyGetter; + set?: PropertySetter; +} \ No newline at end of file diff --git a/lib/Hooks.ts b/lib/Hooks.ts new file mode 100644 index 0000000..dbe3bb6 --- /dev/null +++ b/lib/Hooks.ts @@ -0,0 +1,9 @@ +/// +import instance = require('./Instance'); + +export interface Hooks { + onCreating? (document: TDocument): void; + onRetrieved? (document: TDocument): void; + onReady? (instance: TInstance): void; + onSaving? (instance: TInstance, changes: any): void; +} \ No newline at end of file diff --git a/lib/Index.ts b/lib/Index.ts new file mode 100644 index 0000000..350bd73 --- /dev/null +++ b/lib/Index.ts @@ -0,0 +1,11 @@ +/// +import MongoDB = require('mongodb'); + +export interface Index { + spec: IndexSpecification; + options?: MongoDB.IndexOptions; +} + +export interface IndexSpecification { + [key: string]: number; +} \ No newline at end of file diff --git a/lib/Instance.js b/lib/Instance.js deleted file mode 100644 index 2e70491..0000000 --- a/lib/Instance.js +++ /dev/null @@ -1,374 +0,0 @@ -var ObjectID = require('mongodb').ObjectID, - _ = require('lodash'), - EventEmitter = require('events').EventEmitter, - debug = require('debug')('iridium:Instance'), - fn = require('functionality'), - Promise = require('bluebird'), - - inherit = require('./utils/Inherit.js'), - diff = require('./utils/diff.js'); - - -(require.modules || {}).IridiumInstance = module.exports = IridiumInstance; - -function IridiumInstance(model, doc, isNew, isPartial) { - /// - /// Creates a new wrapper around a database document - /// The model for which the instance should be created - /// The document from the database which is to be wrapped - /// - /// - /// Creates a new wrapper around a database document - /// The model for which the instance should be created - /// The document from the database which is to be wrapped - /// The model for which the instance should be created - /// The document from the database which is to be wrapped - /// - /// Updates this object from the database, bringing it up to date - /// - /// - /// Updates this object from the database, bringing it up to date - /// A function to be called once the update is complete - /// - - return this.promise.then(function() { - var conditions = this.model.uniqueConditions(this.original); - return this.findOne(conditions).bind(this).then(function(latest) { - if(!latest) { - this.state.isPartial = false; - this.state.isNew = true; - this.state.original = _.cloneDeep(this.modified); - return this.context; - } - return this.model.onRetrieved(conditions, latest, null, { wrap: false }).bind(this).then(function(value) { - this.model.fromSource(value); - this.state.isNew = false; - this.state.isPartial = false; - this.state.original = _.cloneDeep(value); - this.state.modified = _.cloneDeep(value); - this.context.__extendSchema(); - this.context.emit('retrieved', this.context); - return this.context; - }).bind(this); - }); - }).catch(function(err) { - this.context.emit('error', err); - return Promise.reject(err); - }).nodeify(this.callback); -}).compile(); - - -IridiumInstance.prototype.delete = IridiumInstance.prototype.remove = fn.first(function() { - this.promise = Promise.bind(this, this.context); - - this.state = this.context.__state; - this.model = this.state.model; - this.original = this.state.original; - this.modified = this.state.modified; - this.remove = Promise.promisify(this.model.collection.remove, this.model.collection); -}).on(fn.opt(Function), function(callback) { - this.callback = callback; -}).then(function() { - /// - /// Updates this object from the database, bringing it up to date - /// - /// - /// Updates this object from the database, bringing it up to date - /// A function to be called once the update is complete - /// - - return this.promise.then(function() { - if(this.state.isNew) return 0; - - var conditions = this.model.uniqueConditions(this.modified); - this.model.cache.drop(conditions); - this.context.emit('removing', this.context); - return this.remove(conditions, { w: 1 }).bind(this); - }).then(function(removed) { - this.state.isNew = !!removed; - return this.context; - }).catch(function(err) { - this.context.emit('error', err); - return Promise.reject(err); - }).nodeify(this.callback); -}).compile(); - -IridiumInstance.prototype.select = function(collection, filter) { - /// - /// Finds elements in the array for which the filter function returns truey - /// The array to search through for matches - /// A function called with the array's element and its index/key for filtering pursposes - /// - /// - /// - /// Finds elements in the array for which the filter function returns truey - /// The array to search through for matches - /// A function called with the array's element and its index/key for filtering pursposes - /// - /// - - var isArray = Array.isArray(collection); - var results = isArray ? [] : {}; - - _.each(collection, function(value, key) { - if(filter.call(this, value, key)) { - if(isArray) results.push(value); - else results[key] = value; - } - }, this); - - return results; -}; - -IridiumInstance.prototype.first = function (collection, filter) { - /// - /// Finds the first element in the object for which the filter function returns truey - /// The array to search through for matches - /// A function called with the array's element and its index/key for filtering pursposes - /// - /// - /// - /// Finds the first element in the array for which the filter function returns truey - /// The array to search through for matches - /// A function called with the array's element and its index/key for filtering pursposes - /// - /// - - var result; - - _.each(collection, function (value, key) { - if (filter.call(this, value, key)) { - result = value; - return false; - } - }, this); - - return result; -}; - -IridiumInstance.prototype.__extendSchema = function() { - var $ = this; - - var schema = {}, property; - - for(property in this.__state.modified) - schema[property] = false; - - for(property in this.__state.model.schema) - if(schema[property]) delete schema[property]; - - for(var targetProperty in schema) { - if(!$[targetProperty] && !$.hasOwnProperty(targetProperty)) - (function(targetProperty) { - Object.defineProperty($, targetProperty, { - get: function() { - /// Get the most recent value for this field - return $.__state.modified[targetProperty]; - }, - set: function(value) { - /// Set the value of this field. Changes may be committed by calling save() on this instance. - $.__state.modified[targetProperty] = value; - }, - enumerable: true - }); - })(targetProperty); - } -}; - -IridiumInstance.forModel = function(model) { - /// Creates an instance wrapper for the specified model - /// The model which the instance wraps - - function IridiumModelInstance(doc, isNew, isPartial) { - /// Creates a new model instance for the specified document - /// The document which the instance should wrap - /// Whether or not the document was sourced from the database - /// Whether or not the document represents a partial version of the database document - - IridiumInstance.call(this, model, doc, isNew, isPartial); - } - - inherit(IridiumModelInstance, IridiumInstance); - - _.each(model.schema, function(validator, name) { - Object.defineProperty(IridiumModelInstance.prototype, name, { - get: function() { - return this.__state.modified[name] === undefined ? null : this.__state.modified[name]; - }, - set: function(value) { - this.__state.modified[name] = value; - }, - enumerable: true - }); - }); - - _.each(model.options.virtuals, function(property, name) { - if('function' == typeof property) - Object.defineProperty(IridiumModelInstance.prototype, name, { - get: property, - enumerable: true - }); - else - Object.defineProperty(IridiumModelInstance.prototype, name, { - get: property.get, - set: property.set, - enumerable: true - }); - }); - - _.each(model.options.methods, function(method, name) { - Object.defineProperty(IridiumModelInstance.prototype, name, { - value: method, - enumerable: false - }); - }); - - return IridiumModelInstance; -}; - -IridiumInstance.diff = diff; \ No newline at end of file diff --git a/lib/Instance.ts b/lib/Instance.ts new file mode 100644 index 0000000..07a1397 --- /dev/null +++ b/lib/Instance.ts @@ -0,0 +1,332 @@ +/// +import Core from './Core'; +import Model from './Model'; +import {Plugin} from './Plugins'; +import {CacheDirector} from './CacheDirector'; +import * as General from './General'; +import * as ModelInterfaces from './ModelInterfaces'; +import * as Index from './Index'; +import {Schema} from './Schema'; + +import _ = require('lodash'); +import MongoDB = require('mongodb'); +import Bluebird = require('bluebird'); +import skmatc = require('skmatc'); + +export default class Instance { + /** + * Creates a new instance which represents the given document as a type of model + * @param {model.Model} model The model that the document represents + * @param {TSchema} document The document which should be wrapped by this instance + * @param {Boolean} isNew Whether the document is new (doesn't exist in the database) or not + * @param {Boolean} isPartial Whether the document has only a subset of its fields populated + * @description + * This class will be subclassed automatically by Iridium to create a model specific instance + * which takes advantage of some of v8's optimizations to boost performance significantly. + * The instance returned by the model, and all of this instance's methods, will be of type + * TInstance - which should represent the merger of TSchema and IInstance for best results. + */ + constructor(model: Model, document: TDocument, isNew: boolean = true, isPartial: boolean = false) { + this._model = model; + + this._isNew = !!isNew; + this._isPartial = isPartial; + this._original = document; + this._modified = _.cloneDeep(document); + + _.each(model.core.plugins,(plugin: Plugin) => { + if (plugin.newInstance) plugin.newInstance(this, model); + }); + } + + private _isNew: boolean; + private _isPartial: boolean; + private _model: Model; + private _original: TDocument; + private _modified: TDocument; + + /** + * Gets the underlying document representation of this instance + */ + get document(): TDocument { + return this._modified; + } + + [name: string]: any; + + static onCreating: (document: { _id?: any }) => void; + static onRetrieved: (document: { _id?: any }) => void; + static onReady: (instance: Instance<{ _id?: any }, Instance<{ _id?: any }, any>>) => void; + static onSaving: (instance: Instance<{ _id?: any }, Instance<{ _id?: any }, any>>, changes: any) => void; + + static collection: string; + + static schema: Schema = { + _id: MongoDB.ObjectID + }; + + static validators: Skmatc.Validator[] = [ + skmatc.create(schema => schema === MongoDB.ObjectID, function(schema, data) { + return this.assert(!data || data instanceof MongoDB.ObjectID); + }, { name: 'ObjectID validation' }) + ]; + + static transforms: { [property: string]: { fromDB: (value: any) => any; toDB: (value: any) => any; } } = { + _id: { + fromDB: value => value._bsontype == 'ObjectID' ? new MongoDB.ObjectID(value.id).toHexString() : value, + toDB: value => value && typeof value === 'string' ? new MongoDB.ObjectID(value) : value + } + }; + + static cache: CacheDirector; + static indexes: (Index.Index | Index.IndexSpecification)[] = []; + static identifier: { + apply(fromSource: any): any; + reverse(toSource: any): any; + }; + + /** + * Saves any changes to this instance, using the built in diff algorithm to write the update query. + * @param {function(Error, IInstance)} callback A callback which is triggered when the save operation completes + * @returns {Promise} + */ + save(callback?: General.Callback): Bluebird; + /** + * Saves the given changes to this instance and updates the instance to match the latest database document. + * @param {Object} changes The MongoDB changes object to be used when updating this instance + * @param {function(Error, IInstance)} callback A callback which is triggered when the save operation completes + * @returns {Promise} + */ + save(changes: Object, callback?: General.Callback): Bluebird; + /** + * Saves the given changes to this instance and updates the instance to match the latest database document. + * @param {Object} conditions The conditions under which the update will take place - these will be merged with an _id query + * @param {Object} changes The MongoDB changes object to be used when updating this instance + * @param {function(Error, IInstance)} callback A callback which is triggered when the save operation completes + * @returns {Promise} + */ + save(conditions: Object, changes: Object, callback?: General.Callback): Bluebird; + save(...args: any[]): Bluebird { + var callback: General.Callback = null; + var changes: any = null; + var conditions: any = {}; + + Array.prototype.slice.call(args, 0).reverse().forEach((arg) => { + if (typeof arg == 'function') callback = arg; + else if (typeof arg == 'object') { + if (!changes) changes = arg; + else conditions = arg; + } + }); + + return Bluebird.resolve().then(() => { + conditions = _.cloneDeep(conditions); + _.merge(conditions, { _id: this._modified._id }); + + conditions = this._model.helpers.transformToDB(conditions); + + if (!changes) { + var validation = this._model.helpers.validate(this._modified); + if (validation.failed) return Bluebird.reject(validation.error).bind(this).nodeify(callback); + + var original = _.cloneDeep(this._original); + var modified = _.cloneDeep(this._modified); + + changes = this._model.helpers.diff(original, modified); + } + + if (!_.keys(changes).length) return null; + + return changes; + }).then((changes) => { + if (!changes && !this._isNew) return changes; + return this._model.handlers.savingDocument(this, changes).then(() => changes); + }).then((changes) => { + if (!changes && !this._isNew) return false; + + if (this._isNew) { + return new Bluebird((resolve, reject) => { + this._model.collection.insertOne(this._modified, { w: 'majority' }, (err, doc) => { + if (err) return reject(err); + return resolve(!!doc); + }); + }); + } else { + return new Bluebird((resolve: (changed: boolean) => void, reject) => { + this._model.collection.updateOne(conditions, changes, { w: 'majority' }, (err: Error, changed: boolean) => { + if (err) return reject(err); + return resolve(changed); + }); + }); + } + }).then((changed: boolean) => { + conditions = this._model.helpers.convertToDB({ _id: this._modified._id }); + if (!changed) { + return _.cloneDeep(this._modified); + } + + return new Bluebird((resolve, reject) => { + this._model.collection.findOne(conditions,(err: Error, latest) => { + if (err) return reject(err); + return resolve(latest); + }); + }); + }).then((latest: TDocument) => { + return this._model.handlers.documentReceived(conditions, latest,(value) => { + this._isPartial = false; + this._isNew = false; + this._original = value; + this._modified = _.clone(value); + return this; + }); + }).nodeify(callback); + } + + /** + * Updates this instance to match the latest document available in the backing collection + * @param {function(Error, IInstance)} callback A callback which is triggered when the update completes + * @returns {Promise} + */ + update(callback?: General.Callback): Bluebird { + return this.refresh(callback); + } + + /** + * Updates this instance to match the latest document available in the backing collection + * @param {function(Error, IInstance)} callback A callback which is triggered when the update completes + * @returns {Promise} + */ + refresh(callback?: General.Callback): Bluebird { + var conditions = { _id: this._original._id }; + + return Bluebird.resolve().then(() => { + return new Bluebird((resolve, reject) => { + this._model.collection.findOne(conditions,(err: Error, doc: any) => { + if (err) return reject(err); + return resolve(doc); + }); + }); + }).then((newDocument) => { + if (!newDocument) { + this._isPartial = true; + this._isNew = true; + this._original = _.cloneDeep(this._modified); + return >this; + } + + return this._model.handlers.documentReceived(conditions, newDocument, (doc) => { + this._isNew = false; + this._isPartial = false; + this._original = doc; + this._modified = _.cloneDeep(doc); + + return this; + }); + }).nodeify(callback); + } + + /** + * Removes this instance's document from the backing collection + * @param {function(Error, IInstance)} callback A callback which is triggered when the operation completes + * @returns {Promise} + */ + delete(callback?: General.Callback): Bluebird { + return this.remove(callback); + } + + /** + * Removes this instance's document from the backing collection + * @param {function(Error, IInstance)} callback A callback which is triggered when the operation completes + * @returns {Promise} + */ + remove(callback?: General.Callback): Bluebird { + var conditions = { _id: this._original._id }; + + return Bluebird.resolve().then(() => { + if (this._isNew) return 0; + return new Bluebird((resolve, reject) => { + this._model.collection.remove(conditions, { w: 'majority' },(err: Error, removed?: any) => { + if (err) return reject(err); + return resolve(removed); + }); + }); + }).then((removed) => { + if (removed) return this._model.cache.clear(conditions); + return false; + }).then(() => { + this._isNew = true; + return this; + }).nodeify(callback); + } + + /** + * Retrieves the first element in an enumerable collection which matches the predicate + * @param {any[]} collection The collection from which to retrieve the element + * @param {function(any, Number): Boolean} predicate The function which determines whether to select an element + * @returns {any} + */ + first(collection: T[], predicate: General.Predicate): T; + /** + * Retrieves the first element in an enumerable collection which matches the predicate + * @param {Object} collection The collection from which to retrieve the element + * @param {function(any, String): Boolean} predicate The function which determines whether to select an element + * @returns {any} + */ + first(collection: { [key: string]: T }, predicate: General.Predicate): T; + first(collection: T[]| { [key: string]: T }, predicate: General.Predicate): T { + var result = null; + + _.each(collection,(value: T, key) => { + if (predicate.call(this, value, key)) { + result = value; + return false; + } + }); + + return result; + } + + /** + * Retrieves a number of elements from an enumerable collection which match the predicate + * @param {any[]} collection The collection from which elements will be plucked + * @param {function(any, Number): Boolean} predicate The function which determines the elements to be plucked + * @returns {any[]} + */ + select(collection: T[], predicate: General.Predicate): T[]; + /** + * Retrieves a number of elements from an enumerable collection which match the predicate + * @param {Object} collection The collection from which elements will be plucked + * @param {function(any, String): Boolean} predicate The function which determines the elements to be plucked + * @returns {Object} + */ + select(collection: { [key: string]: T }, predicate: General.Predicate): { [key: string]: T }; + select(collection: T[]| { [key: string]: T }, predicate: General.Predicate): any { + var isArray = Array.isArray(collection); + var results: any = isArray ? [] : {}; + + _.each(collection,(value: T, key) => { + if (predicate.call(this, value, key)) { + if (isArray) results.push(value); + else results[key] = value; + } + }); + + return results; + } + + /** + * Gets the JSON representation of this instance + * @returns {TDocument} + */ + toJSON(): any { + return this.document; + } + + /** + * Gets a string representation of this instance + * @returns {String} + */ + toString(): string { + return JSON.stringify(this.document, null, 2); + } +} \ No newline at end of file diff --git a/lib/InstanceInterface.ts b/lib/InstanceInterface.ts new file mode 100644 index 0000000..96a9045 --- /dev/null +++ b/lib/InstanceInterface.ts @@ -0,0 +1,25 @@ +/// +import Iridium from './Core'; +import {Schema} from './Schema'; +import Model from './Model'; +import * as Index from './Index'; +import {CacheDirector} from './CacheDirector'; + +export default InstanceImplementation; + +interface InstanceImplementation { + new (model: Model, doc: TDocument, isNew?: boolean, isPartial?: boolean): TInstance; + + collection: string; + schema: Schema; + indexes?: (Index.Index | Index.IndexSpecification)[]; + + onCreating? (document: TDocument): void; + onRetrieved? (document: TDocument): void; + onReady? (instance: TInstance): void; + onSaving? (instance: TInstance, changes: any): void; + + cache?: CacheDirector; + validators?: Skmatc.Validator[]; + transforms?: { [property: string]: { fromDB: (value: any) => any; toDB: (value: any) => any; } }; +} \ No newline at end of file diff --git a/lib/Middleware.ts b/lib/Middleware.ts new file mode 100644 index 0000000..dc6001b --- /dev/null +++ b/lib/Middleware.ts @@ -0,0 +1,6 @@ +/// +import Core from './Core'; + +export interface MiddlewareFactory { + (core: Core): T; +} \ No newline at end of file diff --git a/lib/Model.js b/lib/Model.js deleted file mode 100644 index e436aae..0000000 --- a/lib/Model.js +++ /dev/null @@ -1,672 +0,0 @@ -var _ = require('lodash'), - async = require('async'), - debug = require('debug')('iridium:Model'), - ObjectID = require('mongodb').ObjectID, - EventEmitter = require('events').EventEmitter, - Concoction = require('concoction'), - skmatc = require('skmatc'), - fn = require('functionality'), - Promise = require('bluebird'), - - inherit = require('./utils/Inherit.js'), - Database = require('./Database.js'), - Instance = require('./Instance.js'), - NoOpCache = require('./caches/NoOpCache.js'); - -// Fix for IntelliSense support in Node.js Tools for Visual Studio -(require.modules || {}).IridiumModel = module.exports = IridiumModel; - -function IridiumModel(database, collection, schema, options) { - /// Creates a new model around the specified database - /// The database wrapper on which this model will operate - /// The name of the database collection in which objects of this type are stored - /// A JSON representation of the database schema - /// Additional options configuring the behaviour of this model's instances - - if(!(this instanceof IridiumModel)) return new IridiumModel(database, collection, schema, options); - - EventEmitter.call(this); - - // Don't throw exceptions which are sent via the 'error' event - this.on('error', function(err) { - debug('error: %s', err.message); - }); - - if (!options) options = {}; - - _.defaults(options, { - hooks: {}, - preprocessors: [ - new Concoction.Rename({ - _id: 'id' - }), - new Concoction.Convert({ - id: { - apply: function (value) { return (value && value.id) ? new ObjectID(value.id).toHexString() : value; }, - reverse: function (value) { - if(value === null || value === undefined) return undefined; - if(value && /^[a-f0-9]{24}$/.test(value)) return ObjectID.createFromHexString(value); - return value; - } - } - }) - ] - }); - - Object.defineProperty(this, 'preprocessor', { - value: new Concoction(options.preprocessors) - }); - - Object.defineProperty(this, 'collection', { - get: function () { - var actualCollection = database.db.collection(collection); - return Promise.promisifyAll(actualCollection, actualCollection); - }, - set: function(value) { collection = value; }, - enumerable: false - }); - - Object.defineProperty(this, 'database', { - get: function() { return database; }, - set: function(value) { database = value; }, - enumerable: false - }); - - Object.defineProperty(this, 'schema', { - get: function() { return schema; }, - enumerable: false - }); - - var schemaValidator = new skmatc(schema); - Object.defineProperty(this, 'schemaValidator', { - get: function() { return schemaValidator; }, - enumerable: false - }); - - Object.defineProperty(this, 'options', { - get: function () { return options; }, - enumerable: false - }); - - Object.defineProperty(this, 'isModel', { - value: true, - enumerable: false - }); - - var _instance = null; - Object.defineProperty(this, 'Instance', { - get: function () { return _instance = _instance || Instance.forModel(this); }, - enumerable: false - }); - - var _cache = options.cache; - Object.defineProperty(this, 'cache', { - get: function () { return _cache = _cache || new NoOpCache(); }, - enumerable: false - }); - - - var i; - for(i = 0; i < database.plugins.length; i++) { - if(database.plugins[i].validate) { - var validators = database.plugins[i].validate; - - if(Array.isArray(validators)) - _.forEach(validators, function(validator) { this.schemaValidator.register(validator); }, this); - else - this.schemaValidator.register(validators); - } - } - - for(i = 0; i < database.plugins.length; i++) { - if(database.plugins[i].newModel) database.plugins[i].newModel.call(this, database, collection, schema, options); - } -} - -inherit(IridiumModel, EventEmitter); - - -IridiumModel.prototype.fromSource = function(document) { - /// Applies the model's preprocessors to convert the document from the source - /// The object to apply the preprocessors to - - this.preprocessor.apply(document); -}; - - -IridiumModel.prototype.toSource = function(document) { - /// Applies the model's preprocessors to convert the document from the source - /// The object to apply the preprocessors to - - this.preprocessor.reverse(document); -}; - - -IridiumModel.prototype.uniqueConditions = function(document) { - /// Gets a set of MongoDB conditions which uniquely identify the given document for this model in source form/summary> - /// The document to find the unique conditions for - /// - - var testDoc = _.cloneDeep(document); - this.toSource(testDoc); - - var conditions = { - _id: testDoc._id - }; - return conditions; -}; - - -IridiumModel.prototype.downstreamID = function(id) { - /// - /// Gets the downstream _id field's identifier after preprocessing - /// - /// - /// - /// Gets the set of conditions representing the downstream _id field for the given downstream identifier - /// The identifier to create the conditions from - /// - /// - - var test_doc = { - _id: true - }; - - this.fromSource(test_doc); - - var _id = null; - for(var k in test_doc) - if(test_doc[k] === true) { - _id = k; - break; - } - - if(id) { - var conditions = {}; - conditions[_id] = id; - return conditions; - } else return _id; -}; - - -IridiumModel.prototype.wrap = function (document, isNew, isPartial) { - /// - /// Wraps the given database object in this model's Instance wrapper - /// The database object to be wrapped by this model - /// - /// - /// - /// Wraps the given database object in this model's Instance wrapper - /// The database object to be wrapped by this model - /// Whether or not this instance is new (not in the database) - /// - /// - /// - /// Wraps the given database object in this model's Instance wrapper - /// The database object to be wrapped by this model - /// Whether or not this instance is new (not in the database) - /// Whether or not this instance is only a partial representation of the database version - /// - /// - - return new this.Instance(document, isNew, isPartial); -}; - - -IridiumModel.prototype.onRetrieved = function(conditions, results, wrapper, options) { - /// - ///Handles any post-receive hooks and the wrapping of objects from the database - ///The conditions which resulted in the object being retrieved - ///The objects retrieved from the database - ///The function to be called once the objects have been wrapped - ///A function which converts the retrieved objects prior to submission - ///A set of options determining how to handle the retrieved object - /// - /// - ///Handles any post-receive hooks and the wrapping of objects from the database - ///The conditions which resulted in the object being retrieved - ///The objects retrieved from the database - ///The function to be called once the objects have been wrapped - ///A function which converts the retrieved objects prior to submission - ///A set of options determining how to handle the retrieved object - /// - - var $ = this; - - wrapper = wrapper || this.wrap.bind(this); - options = options || {}; - - _.defaults(options, { - wrap: true, - cache: true, - partial: false - }); - - var returnArray = Array.isArray(results); - if(!returnArray) results = [results]; - - function doHook(hook, target) { - return Promise.resolve().then(function() { - if(!hook) return; - if(hook.length === 0) return hook.call(target); - return Promise.fromNode(hook.bind(target)); - }); - } - - return Promise.bind(this, results).map(function(target) { - return doHook(this.options.hooks.retrieved, target).bind(this).then(function() { - this.emit('retrieved', target); - }).then(function() { - if(this.cache && options.cache && !options.partial) { - var cacheDoc = _.cloneDeep(target); - this.cache.store(conditions, cacheDoc, function() { }); - } - }).then(function() { - var wrapped = target; - if(options.wrap) wrapped = wrapper(target, false, options.partial); - else this.fromSource(wrapped); - - return doHook(this.options.hooks.ready, wrapped).then(function() { - return wrapped; - }); - }); - }).then(function(results) { - if(returnArray) return results; - return results[0]; - }); -}; - -IridiumModel.prototype.onCreating = function(documents) { - /// - ///Handles any pre-creation hooks for the model - ///The documents being created - prior to any transformations being applied - /// - - function doHook(hook, target) { - return Promise.resolve(target).then(function(target) { - if(!hook) return; - if(!hook.length) return hook.call(target); - return Promise.fromNode(hook.bind(target)); - }); - } - - return Promise.resolve(documents).bind(this).map(function(document) { - this.emit('creating', document); - return doHook(this.options.hooks.creating || this.options.hooks.beforeCreate, document).bind(this).then(function() { - var validation = this.schemaValidator.validate(document); - if(validation.failed) throw validation.error; - this.toSource(document); - return document; - }); - }); -}; - -IridiumModel.prototype.onSaving = function(instance, changes) { - /// - /// Handles any pre-save hooks for the model - /// The instance being saved to the database - /// The MongoDB changes object being applied to the database document - /// - - function doHook(hook, target, arg) { - return Promise.resolve().then(function() { - if(!hook) return; - if(hook.length === 0) return hook.call(target, arg); - return Promise.fromNode(hook.bind(target, arg)); - }); - } - - return doHook(this.options.hooks.saving, instance, changes).bind(this).then(function() { - this.emit('saving', instance, changes); - }).error(function(err) { - this.emit('error', err); - }); -}; - - -/** - * Finds a number of documents in the collection which match the given criteria. - * - * find([conditions, [options]], [callback]) : promise - * - * `conditions` may be either a well-formed object representing the query passed - * to MongoDB or a value which can be coerced into an `{ _id: '' }` selector through - * the transforms engine. - * - * `options` allows you to specify different options including the following: - * ``` - * { - * wrap: true, // Whether to wrap results in Iridium Instances - * cache: true, // Whether to attempt to retrieve the document from the cache - * skip: 0, // The number of documents to skip - * limit: 1, // The maximum number of documents to return - * sort: { _id: 1 }, // The sort order to use for documents - * fields: { _id: 1 }, // The fields to return (or exclude) from the document (disables cache storage) - * } - * ``` - * - * `callback` is optional and should be of the form `function (err, result) {}` - */ -IridiumModel.prototype.find = fn.first(function() { - this.promise = Promise.resolve().bind(this); -}) -.on(fn.not(Object), fn.gobble(), function(id) { - this.args[0] = this.context.downstreamID(id); - this.retry(); -}) -.on(fn.opt(Function), function(callback) { - this.conditions = {}; - this.options = { wrap: true, cache: true }; - this.callback = callback; -}).on(Object, fn.opt(Function), function(conditions, callback) { - this.conditions = conditions; - this.options = { wrap: true }; - this.callback = callback; -}).on(Object, Object, fn.opt(Function), function(conditions, options, callback) { - this.conditions = conditions; - this.options = options; - this.callback = callback; - - _.defaults(this.options, { - wrap: true - }); -}).then(function() { - return this.promise.then(function() { - if(this.options.fields) - this.context.toSource(this.options.fields); - - if (!_.isPlainObject(this.conditions)) this.conditions = this.context.downstreamID(this.conditions); - this.context.toSource(this.conditions); - - var cursor = this.context.collection.find(this.conditions, { limit: this.options.limit, sort: this.options.sort, skip: this.options.skip, fields: this.options.fields }); - cursor = Promise.promisifyAll(cursor); - - return cursor.toArrayAsync().bind(this).then(function(results) { - if(!results || !results.length) return []; - return this.context.onRetrieved(this.conditions, results, null, { wrap: this.options.wrap, cache: false, partial: !!this.options.fields }); - }); - }).error(function(err) { - this.context.emit('error', err); - return Promise.reject(err); - }).nodeify(this.callback); -}).compile(); - - -/** - * Finds a single document in the collection which matches the given criteria. - * - * findOne([conditions, [options]], [callback]) : promise - * - * `conditions` may be either a well-formed object representing the query passed - * to MongoDB or a value which can be coerced into an `{ _id: '' }` selector through - * the transforms engine. - * - * `options` allows you to specify different options including the following: - * ``` - * { - * wrap: true, // Whether to wrap results in Iridium Instances - * cache: true, // Whether to attempt to retrieve the document from the cache - * skip: 0, // The number of documents to skip - * sort: { _id: 1 }, // The sort order to use for documents - * fields: { _id: 1 }, // The fields to return (or exclude) from the document (disables cache storage) - * } - * ``` - * - * `callback` is optional and should be of the form `function (err, result) {}` - */ -IridiumModel.prototype.findOne = IridiumModel.prototype.get = fn.first(function() { - this.promise = Promise.resolve().bind(this); - this.cacheFetch = Promise.promisify(this.context.cache.fetch, this.context.cache); -}) -.on(fn.not(Object), fn.gobble(), function(id) { - this.args[0] = this.context.downstreamID(id); - this.retry(); -}) -.on(fn.opt(Function), function(callback) { - this.conditions = {}; - this.options = { wrap: true, cache: true }; - this.callback = callback; -}) -.on(Object, fn.opt(Function), function(conditions, callback) { - this.conditions = conditions; - this.options = { wrap: true, cache: true }; - this.callback = callback; -}) -.on(Object, Object, fn.opt(Function), function(conditions, options, callback) { - this.conditions = conditions; - this.options = options; - this.callback = callback; - - _.defaults(this.options, { - wrap: true, - cache: true - }); -}).then(function() { - return this.promise.then(function() { - this.context.toSource(this.conditions); - - if(this.options.fields) - this.context.toSource(this.options.fields); - - if(this.options.cache && this.context.cache && this.context.cache.valid(this.conditions)) - return this.cacheFetch(this.conditions).bind(this).then(function(doc) { - if(doc) return Promise.resolve(doc); - else return Promise.reject(null); - }); - return Promise.reject(null); - }).catch(function() { - return this.context.collection.findOneAsync(this.conditions, { sort: this.options.sort, skip: this.options.skip, fields: this.options.fields }).bind(this); - }).then(function(doc) { - if(!doc) return null; - return this.context.onRetrieved(this.conditions, doc, null, { wrap: this.options.wrap, cache: this.options.cache, partial: !!this.options.fields }).bind(this); - }).catch(function(err) { - this.context.emit('error', err); - return Promise.reject(err); - }).nodeify(this.callback); -}).compile(); - - -IridiumModel.prototype.insert = IridiumModel.prototype.create = fn.first(function() { - this.promise = Promise.bind(this); -}) -.on(Object, fn.gobble(), function(object) { - this.returnArray = false; - this.args[0] = [object]; - this.retry(); -}) -.on([Object], fn.opt(Function), function(objects, callback) { - this.objects = objects; - this.returnArray = this.returnArray === undefined ? true : this.returnArray; - this.options = { - wrap: true, - w: 1 - }; - this.callback = callback; -}) -.on([Object], Object, fn.opt(Function), function(objects, options, callback) { - this.objects = objects; - this.options = options; - this.returnArray = this.returnArray === undefined ? true : this.returnArray; - this.callback = callback; - - _.defaults(this.options, { - wrap: true, - w: 1 - }); -}).then(function() { - return this.promise.then(function() { - var queryOptions = { w: this.options.w, upsert: !!this.options.upsert, new: true }; - var objects = this.context.onCreating(this.objects).bind(this); - var inserted; - if(queryOptions.upsert) - inserted = objects.map(function(object) { - return this.context.collection.findAndModifyAsync({ _id: object._id }, { _id: 1 }, object, queryOptions).then(function(result) { return result[0]; }).bind(this); - }); - else - inserted = objects.then(function(resolvedObjects) { - return this.context.collection.insertAsync(resolvedObjects, queryOptions).bind(this); - }); - - return inserted; - }).then(function(inserted) { - return this.context.onRetrieved(null, inserted, null, { wrap: this.options.wrap, cache: this.options.cache }).bind(this); - }).then(function(results) { - if(!Array.isArray(results)) results = [results]; - if(!this.returnArray) return results[0]; - return results; - }).catch(function(err) { - this.context.emit('error', err); - return Promise.reject(err); - }).nodeify(this.callback); -}).compile(); - - -IridiumModel.prototype.update = fn.first(function() { - this.promise = Promise.resolve().bind(this); -}).on(Object, Object, fn.opt(Function), function(conditions, changes, callback) { - this.conditions = conditions; - this.changes = changes; - this.options = { - w: 1, - multi: true - }; - this.callback = callback; -}).on(Object, Object, Object, fn.opt(Function), function(conditions, changes, options, callback) { - this.conditions = conditions; - this.changes = changes; - this.options = options; - - _.defaults(this.options, { - w: 1, - multi: true - }); - this.callback = callback; -}).then(function() { - this.context.toSource(this.conditions); - - return this.promise.then(function() { - return this.context.collection.updateAsync(this.conditions, this.changes, this.options).bind(this); - }).catch(function(err) { - this.context.emit('error', err); - return Promise.reject(err); - }).nodeify(this.callback); -}).compile(); - - -IridiumModel.prototype.count = fn.first(function() { - this.promise = Promise.resolve().bind(this); -}).on(fn.not(Object), fn.gobble(), function(conditions, callback) { - this.args[0] = this.context.downstreamID(id); - this.retry(); -}).on(fn.opt(Function), function(callback) { - this.conditions = {}; - this.callback = callback; -}).on(Object, fn.opt(Function), function(conditions, callback) { - this.conditions = conditions; - this.callback = callback; -}).then(function() { - this.context.toSource(this.conditions); - - return this.promise.then(function() { - return this.context.collection.countAsync(this.conditions); - }).catch(function(err) { - this.context.emit('error', err); - return Promise.reject(err); - }).nodeify(this.callback); -}).compile(); - - -IridiumModel.prototype.remove = fn.first(function() { - this.promise = Promise.resolve().bind(this); - this.cacheDrop = Promise.promisify(this.context.cache.drop, this.context.cache); -}).on(fn.not(Object), fn.gobble(), function(conditions, callback) { - this.args[0] = this.context.downstreamID(id); - this.retry(); -}).on(fn.opt(Function), function(callback) { - this.conditions = {}; - this.options = { - w: 1 - }; - this.callback = callback; -}).on(Object, fn.opt(Function), function(conditions, callback) { - this.conditions = conditions; - this.options = { - w: 1 - }; - this.callback = callback; -}).on(Object, Object, fn.opt(Function), function(conditions, options, callback) { - this.conditions = conditions; - this.options = options; - _.defaults(this.options, { - w: 1 - }); - this.callback = callback; -}).then(function() { - this.context.toSource(this.conditions); - - return this.promise.then(function() { - return this.context.collection.removeAsync(this.conditions, this.options); - }).then(function(count) { - if(this.context.cache && this.context.cache.valid(this.conditions)) - return this.cacheDrop(this.conditions).then(function() { return count; }); - return count; - }).catch(function(err) { - this.context.emit('error', err); - return Promise.reject(err); - }).nodeify(this.callback); -}).compile(); - - -IridiumModel.prototype.aggregate = function (chain, callback) { - /// - /// Allows you to execute aggregation queries using MongoDB's aggregation framework - /// The aggregation toolchain to run - /// A function to be called once aggregation has been completed - /// - - return this.collection.aggregateAsync(chain).nodeify(callback); -}; - - -IridiumModel.prototype.ensureIndex = fn.first(function() { -}).on(Object, fn.opt(Function), function(spec, callback) { - return this.collection.ensureIndexAsync(spec, {}).nodeify(callback); -}).on(Object, Object, fn.opt(Function), function(spec, options, callback) { - return this.collection.ensureIndexAsync(spec, options).nodeify(callback); -}).compile(); - - -IridiumModel.prototype.setupIndexes = function (callback) { - /// - /// Configures indexes defined for this model - /// - /// - /// Configures indexes defined for this model - /// A function to be called once all index creations have been requested - /// - - var $ = this; - - if (!this.options.indexes || this.options.indexes.length === 0) return Promise.resolve([]); - - return Promise.bind(this, this.options.indexes).map(function(index) { - return this.ensureIndex(index[0], index[1]); - }); -}; - - -/** - * INTELLISENSE STUFF - */ - -function Model_InstanceCallback_Single(err, instance) { - /// A function that is executed upon insertion of a single document - /// An error object representing a problem inserting the document - /// The document which was inserted into the database -} - -function Model_InstanceCallback_Multiple(err, instances) { - /// A function that is executed upon insertion of multiple documents - /// An error object representing a problem inserting the documents - /// The documents which were inserted into the database -} \ No newline at end of file diff --git a/lib/Model.ts b/lib/Model.ts new file mode 100644 index 0000000..ddc724d --- /dev/null +++ b/lib/Model.ts @@ -0,0 +1,703 @@ +/// +import MongoDB = require('mongodb'); +import Bluebird = require('bluebird'); +import util = require('util'); +import _ = require('lodash'); + +import Core from './Core'; +import Instance from './Instance'; +import {Schema} from './Schema'; +import {Hooks} from './Hooks'; +import {Plugin} from './Plugins'; +import {Cache} from './Cache'; +import {CacheDirector} from './CacheDirector'; +import * as General from './General'; +import Cursor from './Cursor'; +import * as Index from './Index'; +import * as ModelOptions from './ModelOptions'; + +import noOpCache from './caches/NoOpCache'; +import memoryCache from './caches/MemoryCache'; +import idCacheController from './cacheControllers/IDDirector'; + +import Omnom from './utils/Omnom'; +import ModelCache from './ModelCache'; +import ModelHelpers from './ModelHelpers'; +import ModelHandlers from './ModelHandlers'; +import * as ModelInterfaces from './ModelInterfaces'; +import ModelSpecificInstance from './ModelSpecificInstance'; +import InstanceImplementation from './InstanceInterface'; + +/** + * An Iridium Model which represents a structured MongoDB collection + * @class + */ +export default class Model { + /** + * Creates a new Iridium model representing a given ISchema and backed by a collection whose name is specified + * @param {Iridium} core The Iridium core that this model should use for database access + * @param {ModelInterfaces.InstanceImplementation} instanceType The class which will be instantiated for each document retrieved from the database + * @returns {Model} + * @constructor + */ + constructor(core: Core, instanceType: InstanceImplementation) { + if (!(core instanceof Core)) throw new Error("You failed to provide a valid Iridium core for this model"); + if (typeof instanceType != 'function') throw new Error("You failed to provide a valid instance constructor for this model"); + if (typeof instanceType.collection != 'string' || !instanceType.collection) throw new Error("You failed to provide a valid collection name for this model"); + if (!_.isPlainObject(instanceType.schema) || instanceType.schema._id === undefined) throw new Error("You failed to provide a valid schema for this model"); + + this._core = core; + + this.loadExternal(instanceType); + this.onNewModel(); + this.loadInternal(); + } + + private loadExternal(instanceType: InstanceImplementation) { + this._collection = instanceType.collection; + this._schema = instanceType.schema; + this._hooks = instanceType; + this._cacheDirector = instanceType.cache; + this._transforms = instanceType.transforms || {}; + this._validators = instanceType.validators || []; + this._indexes = instanceType.indexes || []; + + if ((instanceType).prototype instanceof Instance) + this._Instance = ModelSpecificInstance(this, instanceType); + else + this._Instance = instanceType.bind(undefined, this); + } + + private loadInternal() { + this._cache = new ModelCache(this); + this._helpers = new ModelHelpers(this); + this._handlers = new ModelHandlers(this); + } + + private onNewModel() { + this._core.plugins.forEach(plugin => plugin.newModel && plugin.newModel(this)); + } + + private _helpers: ModelHelpers; + /** + * Provides helper methods used by Iridium for common tasks + * @returns {ModelHelpers} + */ + get helpers(): ModelHelpers { + return this._helpers; + } + + private _handlers: ModelHandlers; + /** + * Provides helper methods used by Iridium for hook delegation and common processes + * @returns {ModelHandlers} + */ + get handlers(): ModelHandlers { + return this._handlers; + } + + private _hooks: Hooks = {}; + + /** + * Gets the even hooks subscribed on this model for a number of different state changes + * @returns {Hooks} + */ + get hooks(): Hooks { + return this._hooks; + } + + private _schema: Schema; + /** + * Gets the ISchema dictating the data structure represented by this model + * @public + * @returns {schema} + */ + get schema(): Schema { + return this._schema; + } + + private _core: Core; + /** + * Gets the Iridium core that this model is associated with + * @public + * @returns {Iridium} + */ + get core(): Core { + return this._core; + } + + private _collection: string; + /** + * Gets the underlying MongoDB collection from which this model's documents are retrieved + * @public + * @returns {Collection} + */ + get collection(): MongoDB.Collection { + if (!this.core.connection) throw new Error("Iridium Core not connected to a database."); + return this.core.connection.collection(this._collection); + } + + /** + * Gets the name of the underlying MongoDB collection from which this model's documents are retrieved + * @public + */ + get collectionName(): string { + return this._collection; + } + + /** + * Sets the name of the underlying MongoDB collection from which this model's documents are retrieved + * @public + */ + set collectionName(value: string) { + this._collection = value; + } + + private _cacheDirector: CacheDirector; + /** + * Gets the cache controller which dictates which queries will be cached, and under which key + * @public + * @returns {CacheDirector} + */ + get cacheDirector(): CacheDirector { + return this._cacheDirector; + } + + private _cache: ModelCache; + /** + * Gets the cache responsible for storing objects for quick retrieval under certain conditions + * @public + * @returns {ModelCache} + */ + get cache(): ModelCache { + return this._cache; + } + + private _Instance: ModelInterfaces.ModelSpecificInstanceConstructor; + + /** + * Gets the constructor responsible for creating instances for this model + */ + get Instance(): ModelInterfaces.ModelSpecificInstanceConstructor { + return this._Instance; + } + + private _transforms: { [property: string]: { fromDB: (value: any) => any; toDB: (value: any) => any; } }; + + get transforms() { + return this._transforms; + } + + private _validators: Skmatc.Validator[]; + + get validators() { + return this._validators; + } + + private _indexes: (Index.Index | Index.IndexSpecification)[]; + + get indexes() { + return this._indexes; + } + + /** + * Retrieves all documents in the collection and wraps them as instances + * @param {function(Error, TInstance[])} callback An optional callback which will be triggered when results are available + * @returns {Promise} + */ + find(): Cursor; + /** + * Returns all documents in the collection which match the conditions and wraps them as instances + * @param {Object} conditions The MongoDB query dictating which documents to return + * @returns {Promise} + */ + find(conditions: { _id?: any, [key: string]: any } | any): Cursor; + /** + * Returns all documents in the collection which match the conditions + * @param {Object} conditions The MongoDB query dictating which documents to return + * @param {Object} fields The fields to include or exclude from the document + * @returns {Promise} + */ + find(conditions: { _id?: any, [key: string]: any } | any, fields: { [name: string]: number }): Cursor; + find(conditions?: { _id?: any, [key: string]: any } | any, fields?: any): Cursor { + conditions = conditions || {}; + fields = fields || {}; + + if (!_.isPlainObject(conditions)) conditions = { _id: conditions }; + conditions = this._helpers.convertToDB(conditions); + + var cursor = this.collection.find(conditions, { + fields: fields + }); + + return new Cursor(this, conditions, cursor); + } + + /** + * Retrieves a single document from the collection and wraps it as an instance + * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available + * @returns {Promise} + */ + get(callback?: General.Callback): Bluebird; + /** + * Retrieves a single document from the collection with the given ID and wraps it as an instance + * @param {any} id The document's unique _id field value in downstream format + * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available + * @returns {Promise} + */ + get(id: any, callback?: General.Callback): Bluebird; + /** + * Retrieves a single document from the collection which matches the conditions + * @param {Object} conditions The MongoDB query dictating which document to return + * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available + * @returns {Promise} + */ + get(conditions: { _id?: any, [key: string]: any }, callback?: General.Callback): Bluebird; + /** + * Retrieves a single document from the collection with the given ID and wraps it as an instance + * @param {any} id The document's unique _id field value in downstream format + * @param {QueryOptions} options The options dictating how this function behaves + * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available + * @returns {Promise} + */ + get(id: any, options: ModelOptions.QueryOptions, callback?: General.Callback): Bluebird; + /** + * Retrieves a single document from the collection which matches the conditions + * @param {Object} conditions The MongoDB query dictating which document to return + * @param {QueryOptions} options The options dictating how this function behaves + * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available + * @returns {Promise} + */ + get(conditions: { _id?: any, [key: string]: any }, options: ModelOptions.QueryOptions, callback?: General.Callback): Bluebird; + get(...args: any[]): Bluebird { + return this.findOne.apply(this, args); + } + + /** + * Retrieves a single document from the collection and wraps it as an instance + * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available + * @returns {Promise} + */ + findOne(callback?: General.Callback): Bluebird; + /** + * Retrieves a single document from the collection with the given ID and wraps it as an instance + * @param {any} id The document's unique _id field value in downstream format + * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available + * @returns {Promise} + */ + findOne(id: any, callback?: General.Callback): Bluebird; + /** + * Retrieves a single document from the collection which matches the conditions + * @param {Object} conditions The MongoDB query dictating which document to return + * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available + * @returns {Promise} + */ + findOne(conditions: { _id?: any, [key: string]: any }, callback?: General.Callback): Bluebird; + /** + * Retrieves a single document from the collection with the given ID and wraps it as an instance + * @param {any} id The document's unique _id field value in downstream format + * @param {QueryOptions} options The options dictating how this function behaves + * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available + * @returns {Promise} + */ + findOne(id: any, options: ModelOptions.QueryOptions, callback?: General.Callback): Bluebird; + /** + * Retrieves a single document from the collection which matches the conditions + * @param {Object} conditions The MongoDB query dictating which document to return + * @param {QueryOptions} options The options dictating how this function behaves + * @param {function(Error, TInstance)} callback An optional callback which will be triggered when a result is available + * @returns {Promise} + */ + findOne(conditions: { _id?: any, [key: string]: any }, options: ModelOptions.QueryOptions, callback?: General.Callback): Bluebird; + findOne(...args: any[]): Bluebird { + var conditions: { _id?: any, [key: string]: any } = null; + var options: ModelOptions.QueryOptions = null; + var callback: General.Callback = null; + + for (var argI = 0; argI < args.length; argI++) { + if (typeof args[argI] == 'function') callback = callback || args[argI]; + else if (_.isPlainObject(args[argI])) { + if (conditions) options = args[argI]; + else conditions = args[argI]; + } + else conditions = { _id: args[argI] }; + } + + conditions = conditions || {}; + options = options || {}; + + _.defaults(options, { + cache: true + }); + + return Bluebird.resolve().bind(this).then(() => { + conditions = this._helpers.convertToDB(conditions); + + return this._cache.get(conditions); + }).then((cachedDocument: TDocument) => { + if (cachedDocument) return cachedDocument; + return new Bluebird((resolve, reject) => { + this.collection.findOne(conditions, { + fields: options.fields, + skip: options.skip, + sort: options.sort, + limit: options.limit + },(err, result) => { + if (err) return reject(err); + return resolve(result); + }); + }); + }).then((document: TDocument) => { + if (!document) return null; + return this._handlers.documentReceived(conditions, document,(document, isNew?, isPartial?) => this._helpers.wrapDocument(document, isNew, isPartial), options); + }).nodeify(callback); + } + + /** + * Inserts an object into the collection after validating it against this model's schema + * @param {Object} object The object to insert into the collection + * @param {function(Error, TInstance)} callback A callback which is triggered when the operation completes + * @returns {Promise} + */ + create(objects: TDocument, callback?: General.Callback): Bluebird; + /** + * Inserts an object into the collection after validating it against this model's schema + * @param {Object} object The object to insert into the collection + * @param {CreateOptions} options The options dictating how this function behaves + * @param {function(Error, TInstance)} callback A callback which is triggered when the operation completes + * @returns {Promise} + */ + create(objects: TDocument, options: ModelOptions.CreateOptions, callback?: General.Callback): Bluebird; + /** + * Inserts the objects into the collection after validating them against this model's schema + * @param {Object[]} objects The objects to insert into the collection + * @param {function(Error, TInstance)} callback A callback which is triggered when the operation completes + * @returns {Promise} + */ + create(objects: TDocument[], callback?: General.Callback): Bluebird; + /** + * Inserts the objects into the collection after validating them against this model's schema + * @param {Object[]} objects The objects to insert into the collection + * @param {CreateOptions} options The options dictating how this function behaves + * @param {function(Error, TInstance)} callback A callback which is triggered when the operation completes + * @returns {Promise} + */ + create(objects: TDocument[], options: ModelOptions.CreateOptions, callback?: General.Callback): Bluebird; + create(...args: any[]): Bluebird { + return this.insert.apply(this, args); + } + + /** + * Inserts an object into the collection after validating it against this model's schema + * @param {Object} object The object to insert into the collection + * @param {function(Error, TInstance)} callback A callback which is triggered when the operation completes + * @returns {Promise} + */ + insert(objects: TDocument, callback?: General.Callback): Bluebird; + /** + * Inserts an object into the collection after validating it against this model's schema + * @param {Object} object The object to insert into the collection + * @param {CreateOptions} options The options dictating how this function behaves + * @param {function(Error, TInstance)} callback A callback which is triggered when the operation completes + * @returns {Promise} + */ + insert(objects: TDocument, options: ModelOptions.CreateOptions, callback?: General.Callback): Bluebird; + /** + * Inserts the objects into the collection after validating them against this model's schema + * @param {Object[]} objects The objects to insert into the collection + * @param {function(Error, TInstance[])} callback A callback which is triggered when the operation completes + * @returns {Promise} + */ + insert(objects: TDocument[], callback?: General.Callback): Bluebird; + /** + * Inserts the objects into the collection after validating them against this model's schema + * @param {Object[]} objects The objects to insert into the collection + * @param {CreateOptions} options The options dictating how this function behaves + * @param {function(Error, TInstance[])} callback A callback which is triggered when the operation completes + * @returns {Promise} + */ + insert(objects: TDocument[], options: ModelOptions.CreateOptions, callback?: General.Callback): Bluebird; + insert(objs: TDocument | TDocument[], ...args: any[]): Bluebird { + var objects: TDocument[]; + var options: ModelOptions.CreateOptions = {}; + var callback: General.Callback = null; + if (typeof args[0] == 'function') callback = args[0]; + else { + options = args[0]; + callback = args[1]; + } + + if (Array.isArray(objs)) + objects = objs; + else + objects = [objs]; + + options = options || {}; + _.defaults(options, { + w: 'majority', + forceServerObjectId: true + }); + + return Bluebird.resolve().then(() => { + var queryOptions = { w: options.w, upsert: options.upsert, new: true }; + + if (options.upsert) { + var docs = this._handlers.creatingDocuments(objects); + return docs.map((object: { _id: any; }) => { + return new Bluebird((resolve, reject) => { + this.collection.findAndModify({ _id: object._id }, ["_id"], object, queryOptions,(err, result) => { + if (err) return reject(err); + return resolve(result); + }); + }); + }); + } + else + return this._handlers.creatingDocuments(objects).then(objects => _.chunk(objects, 1000)).map((objects: any[]) => { + return new Bluebird((resolve, reject) => { + this.collection.insertMany(objects, queryOptions,(err, result) => { + if (err) return reject(err); + return resolve(result.ops); + }); + }); + }).then(results => _.flatten(results)); + }).map((inserted: any) => { + return this._handlers.documentReceived(null, inserted,(document, isNew?, isPartial?) => this._helpers.wrapDocument(document, isNew, isPartial), { cache: options.cache }); + }).then((results: TInstance[]) => { + if (Array.isArray(objs)) return results; + return results[0]; + }).nodeify(callback); + } + + /** + * Updates the documents in the backing collection which match the conditions using the given update instructions + * @param {Object} conditions The conditions which determine which documents will be updated + * @param {Object} changes The changes to make to the documents + * @param {function(Error, Number)} callback A callback which is triggered when the operation completes + */ + update(conditions: { _id?: any, [key: string]: any } | any, changes: any, callback?: General.Callback): Bluebird; + /** + * Updates the documents in the backing collection which match the conditions using the given update instructions + * @param {Object} conditions The conditions which determine which documents will be updated + * @param {Object} changes The changes to make to the documents + * @param {UpdateOptions} options The options which dictate how this function behaves + * @param {function(Error, Number)} callback A callback which is triggered when the operation completes + */ + update(conditions: { _id?: any, [key: string]: any } | any, changes: any, options: ModelOptions.UpdateOptions, callback?: General.Callback): Bluebird; + update(conditions: { _id?: any, [key: string]: any } | any, changes: any, options?: ModelOptions.UpdateOptions, callback?: General.Callback): Bluebird { + if (typeof options == 'function') { + callback = >options; + options = {}; + } + + options = options || {}; + + if (!_.isPlainObject(conditions)) conditions = { + _id: conditions + }; + + _.defaults(options, { + w: 'majority', + multi: true + }); + + return Bluebird.resolve().then(() => { + conditions = this._helpers.convertToDB(conditions); + + return new Bluebird((resolve, reject) => { + this.collection.updateMany(conditions, changes, options,(err, response) => { + if (err) return reject(err); + + // New MongoDB 2.6+ response type + if (response.result && response.result.nModified !== undefined) return resolve(response.result.nModified); + + // Legacy response type + return resolve(response.result.n); + }); + }) + }).nodeify(callback); + } + + /** + * Counts the number of documents in the collection + * @param {function(Error, Number)} callback A callback which is triggered when the operation completes + * @returns {Promise} + */ + count(callback?: General.Callback): Bluebird; + /** + * Counts the number of documents in the collection which match the conditions provided + * @param {Object} conditions The conditions which determine whether an object is counted or not + * @param {function(Error, Number)} callback A callback which is triggered when the operation completes + * @returns {Promise} + */ + count(conditions: { _id?: any, [key: string]: any } | any, callback?: General.Callback): Bluebird; + count(conds?: any, callback?: General.Callback): Bluebird { + var conditions: { _id?: any, [key: string]: any } = <{ _id?: any, [key: string]: any }>conds; + if (typeof conds == 'function') { + callback = >conds; + conditions = {}; + } + + conditions = conditions || {}; + + if (!_.isPlainObject(conditions)) conditions = { + _id: conditions + }; + + return Bluebird.resolve().then(() => { + conditions = this._helpers.convertToDB(conditions); + + return new Bluebird((resolve, reject) => { + this.collection.count(conditions,(err, results) => { + if (err) return reject(err); + return resolve(results); + }); + }); + }).nodeify(callback); + } + + /** + * Removes all documents from the collection + * @param {function(Error, Number)} callback A callback which is triggered when the operation completes + * @returns {Promise} + */ + remove(callback?: General.Callback): Bluebird; + /** + * Removes all documents from the collection which match the conditions + * @param {Object} conditions The conditions determining whether an object is removed or not + * @param {function(Error, Number)} callback A callback which is triggered when the operation completes + * @returns {Promise} + */ + remove(conditions: { _id?: any, [key: string]: any } | any, callback?: General.Callback): Bluebird; + /** + * Removes all documents from the collection which match the conditions + * @param {Object} conditions The conditions determining whether an object is removed or not + * @param {Object} options The options controlling the way in which the function behaves + * @param {function(Error, Number)} callback A callback which is triggered when the operation completes + * @returns {Promise} + */ + remove(conditions: { _id?: any, [key: string]: any }, options: ModelOptions.RemoveOptions, callback?: General.Callback): Bluebird; + remove(conds?: any, options?: ModelOptions.RemoveOptions, callback?: General.Callback): Bluebird { + var conditions: { _id?: any, [key: string]: any } = <{ _id?: any, [key: string]: any }>conds; + + if (typeof options === 'function') { + callback = >options; + options = {}; + } + + if (typeof conds == 'function') { + callback = >conds; + options = {}; + conditions = {}; + } + + conditions = conditions || {}; + options = options || {}; + + _.defaults(options, { + w: 'majority' + }); + + if (!_.isPlainObject(conditions)) conditions = { + _id: conditions + }; + + return Bluebird.resolve().then(() => { + conditions = this._helpers.convertToDB(conditions); + + return new Bluebird((resolve, reject) => { + this.collection.remove(conditions, options,(err, response) => { + if (err) return reject(err); + return resolve(response.result.n); + }); + }); + }).then((count) => { + if (count === 1) this._cache.clear(conditions); + return Bluebird.resolve(count); + }).nodeify(callback); + } + + /** + * Ensures that the given index is created for the collection + * @param {Object} specification The index specification object used by MongoDB + * @param {function(Error, String)} callback A callback which is triggered when the operation completes + * @returns {Promise} The name of the index + */ + ensureIndex(specification: Index.IndexSpecification, callback?: General.Callback): Bluebird; + /** + * Ensures that the given index is created for the collection + * @param {Object} specification The index specification object used by MongoDB + * @param {MongoDB.IndexOptions} options The options dictating how the index is created and behaves + * @param {function(Error, String)} callback A callback which is triggered when the operation completes + * @returns {Promise} The name of the index + */ + ensureIndex(specification: Index.IndexSpecification, options: MongoDB.IndexOptions, callback?: General.Callback): Bluebird; + ensureIndex(specification: Index.IndexSpecification, options?: MongoDB.IndexOptions, callback?: General.Callback): Bluebird { + if (typeof options == 'function') { + callback = >options; + options = {}; + } + + return new Bluebird((resolve, reject) => { + this.collection.ensureIndex(specification, options,(err, name: any) => { + if (err) return reject(err); + return resolve(name); + }); + }).nodeify(callback); + } + + /** + * Ensures that all indexes defined in the model's options are created + * @param {function(Error, String[])} callback A callback which is triggered when the operation completes + * @returns {Promise} The names of the indexes + */ + ensureIndexes(callback?: General.Callback): Bluebird { + return Bluebird.resolve(this._indexes).map((index: Index.Index | Index.IndexSpecification) => { + return this.ensureIndex((index).spec || index,(index).options || {}); + }).nodeify(callback); + } + + /** + * Drops the index with the specified name if it exists in the collection + * @param {String} name The name of the index to remove + * @param {function(Error, Boolean)} callback A callback which is triggered when the operation completes + * @returns {Promise} Whether the index was dropped + */ + dropIndex(name: string, callback?: General.Callback): Bluebird; + /** + * Drops the index if it exists in the collection + * @param {IndexSpecification} index The index to remove + * @param {function(Error, Boolean)} callback A callback which is triggered when the operation completes + * @returns {Promise} Whether the index was dropped + */ + dropIndex(index: Index.IndexSpecification, callback?: General.Callback): Bluebird; + dropIndex(specification: string | Index.IndexSpecification, callback?: General.Callback): Bluebird { + var index: string; + + if (typeof (specification) === 'string') index = specification; + else { + index = _(specification).map((direction, key) => key + '_' + direction).reduce((x, y) => x + '_' + y); + } + + return new Bluebird((resolve, reject) => { + this.collection.dropIndex(index,(err, result: { ok: number }) => { + if (err) return reject(err); + return resolve(!!result.ok); + }); + }).nodeify(callback); + } + + /** + * Removes all indexes (except for _id) from the collection + * @param {function(Error, Boolean)} callback A callback which is triggered when the operation completes + * @returns {Promise} Whether the indexes were dropped + */ + dropIndexes(callback?: General.Callback): Bluebird { + return new Bluebird((resolve, reject) => { + this.collection.dropAllIndexes((err, count) => { + if (err) return reject(err); + return resolve(count); + }); + }).nodeify(callback); + } +} diff --git a/lib/ModelCache.ts b/lib/ModelCache.ts new file mode 100644 index 0000000..919e9fd --- /dev/null +++ b/lib/ModelCache.ts @@ -0,0 +1,24 @@ +/// +import Model from './Model'; +import Bluebird = require('bluebird'); + +export default class ModelCache { + constructor(public model: Model) { + + } + + set(value: T): void { + if (!this.model.cacheDirector || !this.model.cacheDirector.valid(value)) return; + this.model.core.cache.set(this.model.cacheDirector.buildKey(value), value); + } + + get(conditions: any): Bluebird { + if (!this.model.cacheDirector || !this.model.cacheDirector.validQuery(conditions)) return Bluebird.resolve(null); + return this.model.core.cache.get(this.model.cacheDirector.buildQueryKey(conditions)); + } + + clear(conditions: any): void { + if (!this.model.cacheDirector || !this.model.cacheDirector.validQuery(conditions)) return; + this.model.core.cache.clear(this.model.cacheDirector.buildQueryKey(conditions)); + } +} diff --git a/lib/ModelHandlers.ts b/lib/ModelHandlers.ts new file mode 100644 index 0000000..9f7da3b --- /dev/null +++ b/lib/ModelHandlers.ts @@ -0,0 +1,65 @@ +/// +import Core from './Core'; +import {Schema} from './Schema'; +import Model from './Model'; +import ModelCache from './ModelCache'; +import * as ModelOptions from './ModelOptions'; + +import _ = require('lodash'); +import MongoDB = require('mongodb'); +import Bluebird = require('bluebird'); + +export default class ModelHandlers { + constructor(public model: Model) { + + } + + documentReceived(conditions: any, + result: TDocument, + wrapper: (document: TDocument, isNew?: boolean, isPartial?: boolean) => TResult, + options: ModelOptions.QueryOptions = {}): Bluebird { + _.defaults(options, { + cache: true, + partial: false + }); + + return Bluebird.resolve(result).then((target: any) => { + return >Bluebird.resolve().then(() => { + + // Cache the document if caching is enabled + if (this.model.core.cache && options.cache && !options.fields) { + this.model.cache.set(target); // Does not block execution pipeline - fire and forget + } + + // Trigger the received hook + if (this.model.hooks.onRetrieved) this.model.hooks.onRetrieved(target); + + // Wrap the document and trigger the ready hook + let wrapped: TResult = wrapper(target, false, !!options.fields); + + if (this.model.hooks.onReady && wrapped instanceof this.model.Instance) this.model.hooks.onReady(wrapped); + return wrapped; + }); + }); + } + + creatingDocuments(documents: TDocument[]): Bluebird { + return Bluebird.all(documents.map((document: any) => { + return Bluebird.resolve().then(() => { + if (this.model.hooks.onCreating) this.model.hooks.onCreating(document); + document = this.model.helpers.convertToDB(document); + let validation: Skmatc.Result = this.model.helpers.validate(document); + if (validation.failed) return Bluebird.reject(validation.error); + + return document; + }); + })); + } + + savingDocument(instance: TInstance, changes: any): Bluebird { + return Bluebird.resolve().then(() => { + if (this.model.hooks.onSaving) this.model.hooks.onSaving(instance, changes); + return instance; + }); + } +} diff --git a/lib/ModelHelpers.ts b/lib/ModelHelpers.ts new file mode 100644 index 0000000..64f63bf --- /dev/null +++ b/lib/ModelHelpers.ts @@ -0,0 +1,71 @@ +/// +import MongoDB = require('mongodb'); +import Model from './Model'; +import skmatc = require('skmatc'); +import Omnom from './utils/Omnom'; +import _ = require('lodash'); +import Bluebird = require('bluebird'); + +export default class ModelHelpers { + constructor(public model: Model) { + this._validator = new skmatc(model.schema); + model.validators.forEach(validator => this._validator.register(validator)); + } + + private _validator: Skmatc.Skmatc; + + /** + * Validates a document to ensure that it matches the model's ISchema requirements + * @param {any} document The document to validate against the ISchema + * @returns {SkmatcCore.IResult} The result of the validation + */ + validate(document: TDocument): Skmatc.Result { + return this._validator.validate(document); + } + + /** + * Wraps the given document in an instance wrapper for use throughout the application + * @param {any} document The document to be wrapped as an instance + * @param {Boolean} isNew Whether the instance originated from the database or was created by the application + * @param {Boolean} isPartial Whether the document supplied contains all information present in the database + * @returns {any} An instance which wraps this document + */ + wrapDocument(document: TDocument, isNew?: boolean, isPartial?: boolean): TInstance { + return new this.model.Instance(document, isNew, isPartial); + } + + /** + * Converts the given document to its database form into a form + * using the transforms defined on the model. + * @param {any} document The document to be converted + * @returns {any} A new document cloned from the original and transformed + */ + transformToDB(document: T): T { + for (var property in this.model.transforms) + if(document.hasOwnProperty(property)) + document[property] = this.model.transforms[property].toDB(document[property]); + return document; + } + + /** + * Converts the given document to its database form into a form + * using the transforms defined on the model. + * @param {any} document The document to be converted + * @returns {any} A new document cloned from the original and transformed + */ + convertToDB(document: T): T { + var doc: T = _.cloneDeep(document); + return this.transformToDB(doc); + } + + /** + * Performs a diff operation between two documents and creates a MongoDB changes object to represent the differences + * @param {any} original The original document prior to changes being made + * @param {any} modified The document after changes were made + */ + diff(original: TDocument, modified: TDocument): any { + var omnom = new Omnom(); + omnom.diff(original, modified); + return omnom.changes; + } +} diff --git a/lib/ModelInterfaces.ts b/lib/ModelInterfaces.ts new file mode 100644 index 0000000..4378b88 --- /dev/null +++ b/lib/ModelInterfaces.ts @@ -0,0 +1,4 @@ +/// +export interface ModelSpecificInstanceConstructor { + new (doc: TDocument, isNew?: boolean, isPartial?: boolean): TInstance; +} \ No newline at end of file diff --git a/lib/ModelOptions.ts b/lib/ModelOptions.ts new file mode 100644 index 0000000..3def824 --- /dev/null +++ b/lib/ModelOptions.ts @@ -0,0 +1,42 @@ +/// +import MongoDB = require('mongodb'); +import Index = require('./Index'); +import Hooks = require('./Hooks'); +import {CacheDirector} from './CacheDirector'; +import * as General from './General'; + +export interface QueryOptions { + cache?: boolean; + fields?: { [name: string]: number }; + limit?: number; + skip?: number; + sort?: Index.IndexSpecification; +} + +export interface CreateOptions { + w?: any; + wtimeout?: number; + j?: number; + serializeFunctions?: boolean; + forceServerObjectId?: boolean; + upsert?: boolean; + cache?: boolean; +} + +export interface UpdateOptions { + w?: any; + wtimeout?: number; + j?: boolean; + upsert?: boolean; +} + +export interface RemoveOptions { + w?: any; + wtimeout?: number; + j?: boolean; + single?: boolean; +} + +export interface Transforms { + [property: string]: { fromDB: (value: any) => any; toDB: (value: any) => any; }; +} \ No newline at end of file diff --git a/lib/ModelSpecificInstance.ts b/lib/ModelSpecificInstance.ts new file mode 100644 index 0000000..209f182 --- /dev/null +++ b/lib/ModelSpecificInstance.ts @@ -0,0 +1,40 @@ +/// +import Model from './Model'; +import InstanceImplementation from './InstanceInterface'; +import util = require('util'); +import _ = require('lodash'); + +export default function ModelSpecificInstance(model: Model, instanceType: InstanceImplementation): new (doc: TDocument, isNew?: boolean, isPartial?: boolean) => TInstance { + var constructor = function (doc: TDocument, isNew?: boolean, isPartial?: boolean) { + instanceType.call(this, model, doc, isNew, isPartial); + }; + + util.inherits(constructor, instanceType); + + _.each(Object.keys(model.schema),(property) => { + if (model.transforms.hasOwnProperty(property)) { + return Object.defineProperty(constructor.prototype, property, { + get: function () { + return model.transforms[property].fromDB(this._modified._id); + }, + set: function (value) { + this._modified._id = model.transforms[property].toDB(value); + }, + enumerable: true, + configurable: true + }); + } + + Object.defineProperty(constructor.prototype, property, { + get: function () { + return this._modified[property]; + }, + set: function (value) { + this._modified[property] = value; + }, + enumerable: true + }); + }); + + return constructor; +} diff --git a/lib/Plugins.ts b/lib/Plugins.ts new file mode 100644 index 0000000..59c130a --- /dev/null +++ b/lib/Plugins.ts @@ -0,0 +1,9 @@ +/// +import core = require('./Core'); +import Model from './Model'; + +export interface Plugin { + newModel? (model: Model); + newInstance? (instance: any, model: Model); + validate?: Skmatc.Validator | Skmatc.Validator[]; +} \ No newline at end of file diff --git a/lib/Schema.ts b/lib/Schema.ts new file mode 100644 index 0000000..d96924f --- /dev/null +++ b/lib/Schema.ts @@ -0,0 +1,5 @@ +/// +export interface Schema { + _id: boolean | any; + [key:string]: any; +} \ No newline at end of file diff --git a/lib/cacheControllers/IDDirector.ts b/lib/cacheControllers/IDDirector.ts new file mode 100644 index 0000000..293bf30 --- /dev/null +++ b/lib/cacheControllers/IDDirector.ts @@ -0,0 +1,25 @@ +/// +import {CacheDirector} from '../CacheDirector'; +import MongoDB = require('mongodb'); + +export default class IDCacheDirector implements CacheDirector{ + valid(object: { _id: any }) { + return !!object._id; + } + + buildKey(object: { _id: any }) { + if (object._id._bsontype == 'ObjectID') + return new MongoDB.ObjectID(object._id.id).toHexString(); + return object._id; + } + + validQuery(conditions) { + return !!conditions._id; + } + + buildQueryKey(conditions) { + if (conditions._id._bsontype == 'ObjectID') + return new MongoDB.ObjectID(conditions._id.id).toHexString(); + return conditions._id; + } +} \ No newline at end of file diff --git a/lib/caches/MemoryCache.ts b/lib/caches/MemoryCache.ts new file mode 100644 index 0000000..9ffd135 --- /dev/null +++ b/lib/caches/MemoryCache.ts @@ -0,0 +1,22 @@ +/// +import Bluebird = require('bluebird'); +import {Cache} from '../Cache'; + +export default class MemoryCache implements Cache { + private cache: any = {}; + + set(key: string, value: T): Bluebird { + this.cache[key] = value; + return Bluebird.resolve(value); + } + + get(key: string): Bluebird { + return Bluebird.resolve(this.cache[key]); + } + + clear(key: string) : Bluebird { + var has = this.cache.hasOwnProperty(key); + if(has) delete this.cache[key]; + return Bluebird.resolve(has); + } +} \ No newline at end of file diff --git a/lib/caches/NoOpCache.js b/lib/caches/NoOpCache.js deleted file mode 100644 index ab98073..0000000 --- a/lib/caches/NoOpCache.js +++ /dev/null @@ -1,37 +0,0 @@ -module.exports = NoOpCache; - -function NoOpCache(options) { - /// Creates a new cache which performs no caching of instances - /// Options dictating the configuration of this cache -} - -NoOpCache.prototype.valid = function(conditions) { - /// Determines whether or not an object with the given conditions can be retrieved from this cache - /// The conditions for which the document was retrieved - /// -}; - -NoOpCache.prototype.store = function(conditions, document, callback) { - /// Stores a document in the cache for future access - /// The conditions that resulted in this object being stored, null for insertions - /// The database object to store in the cache - /// A function which is called once the document has been stored - - return callback && callback(); -}; - -NoOpCache.prototype.fetch = function(conditions, callback) { - /// Fetches the document with the matching id from the cache - /// The conditions used to select the object to be returned from the cache - /// A function to call with the retrieved value - - return callback && callback(null); -}; - -NoOpCache.prototype.drop = function(conditions, callback) { - /// Removes the document with the matching id from the cache - /// The conditions used to select the object to be removed from the cache - /// A function to call once the document has been removed from the cache - - return callback && callback(null); -}; \ No newline at end of file diff --git a/lib/caches/NoOpCache.ts b/lib/caches/NoOpCache.ts new file mode 100644 index 0000000..0f5f469 --- /dev/null +++ b/lib/caches/NoOpCache.ts @@ -0,0 +1,17 @@ +/// +import {Cache} from '../Cache'; +import Bluebird = require('bluebird'); + +export default class NoOpCache implements Cache { + set(key: string, object: T): Bluebird { + return Bluebird.resolve(object); + } + + get(key: string): Bluebird { + return Bluebird.resolve(); + } + + clear(key: string): Bluebird { + return Bluebird.resolve(false); + } +} \ No newline at end of file diff --git a/lib/middleware/Express.ts b/lib/middleware/Express.ts new file mode 100644 index 0000000..af39cdc --- /dev/null +++ b/lib/middleware/Express.ts @@ -0,0 +1,19 @@ +/// +import http = require('http'); +import {MiddlewareFactory} from '../Middleware'; +import Core from '../Core'; + +export default function ExpressMiddlewareFactory(core: Core): ExpressMiddleware { + return function (req: http.ServerRequest, res: http.ServerResponse, next:(err?: Error, route?: String) => void) { + core.connect().then(function() { + Object.defineProperty(req, 'db', { + get: function() { return core; } + }); + next(); + }).catch(next); + }; +} + +export interface ExpressMiddleware { + (req: http.ServerRequest, res: http.ServerResponse, next:(err?: Error, route?: String) => void); +} \ No newline at end of file diff --git a/lib/utils/Inherit.js b/lib/utils/Inherit.js deleted file mode 100644 index 728fa16..0000000 --- a/lib/utils/Inherit.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = function(target, inherits) { - /// Inherits properties from one class into a child - /// The target class which should inherit properties of its parent - /// The parent class from which to inherit - - var targetProto = function() {}; - targetProto.prototype = inherits.prototype; - targetProto.constructor = targetProto; - - target.prototype = new targetProto(); - target.prototype.constructor = target; - inherits.prototype.constructor = inherits; -}; \ No newline at end of file diff --git a/lib/utils/Omnom.ts b/lib/utils/Omnom.ts new file mode 100644 index 0000000..86d33fd --- /dev/null +++ b/lib/utils/Omnom.ts @@ -0,0 +1,245 @@ +/// +import _ = require('lodash'); +import MongoDB = require('mongodb'); + +export default class Omnom { + constructor(public options: { + atomicNumbers?: boolean; + } = {}) { + this._changes = {}; + } + + private _changes: { + $set?: any; + $unset?: any; + $inc?: any; + $push?: any; + $pull?: any; + $pullAll?: any; + }; + get changes(): { + $set?: any; + $unset?: any; + $inc?: any; + $push?: any; + $pull?: any; + $pullAll?: any; + } { + return this._changes; + } + + diff(original: number, modified: number): Omnom; + diff(original: [any], modified: any[]): Omnom; + diff(original: MongoDB.ObjectID, modified: MongoDB.ObjectID): Omnom; + diff(original: Object, modified: Object): Omnom; + diff(original: any, modified: any): Omnom { + this.onObject(original, modified); + return this; + } + + static diff(original: number, modified: number, options?: { + atomicNumbers?: boolean; + }); + static diff(original: [any], modified: any[], options?: { + atomicNumbers?: boolean; + }); + static diff(original: MongoDB.ObjectID, modified: MongoDB.ObjectID, options?: { + atomicNumbers?: boolean; + }); + static diff(original: Object, modified: Object, options?: { + atomicNumbers?: boolean; + }); + static diff(original: any, modified: any, options?: { + atomicNumbers?: boolean; + }) { + return new Omnom(options).diff(original, modified).changes; + } + + private onObject(original: number, modified: number, changePath?: string); + private onObject(original: [any], modified: any[], changePath?: string); + private onObject(original: MongoDB.ObjectID, modified: MongoDB.ObjectID, changePath?: string); + private onObject(original: Object, modified: Object, changePath?: string); + private onObject(original: any, modified: any, changePath?: string) { + if (original === undefined || original === null) + return (original !== modified) && this.set(changePath, modified); + + if (typeof original == 'number' && typeof modified == 'number' && original !== modified) { + if (this.options.atomicNumbers) return this.inc(changePath, modified - original); + return this.set(changePath, modified); + } + + if (Array.isArray(original) && Array.isArray(modified)) + return this.onArray(original, modified, changePath); + + if (original instanceof MongoDB.ObjectID && modified instanceof MongoDB.ObjectID) + return !original.equals(modified) && this.set(changePath, modified); + + if (!_.isPlainObject(original) || !_.isPlainObject(modified)) + return !_.isEqual(original, modified) && this.set(changePath, modified); + + _.each(modified, function (value, key) { + // Handle array diffs in their own special way + if (Array.isArray(value) && Array.isArray(original[key])) this.onArray(original[key], value, this.resolve(changePath, key)); + + // Otherwise, just keep going + else this.onObject(original[key], value, this.resolve(changePath, key)); + }, this); + + // Unset removed properties + _.each(original, function (value, key) { + if (modified[key] === undefined || modified[key] === null) return this.unset(this.resolve(changePath, key)); + }, this); + } + + private onArray(original: [any], modified: [any], changePath: string) { + var i, j; + + // Check if we can get from original => modified using just pulls + if (original.length > modified.length) { + var pulls = []; + for (i = 0, j = 0; i < original.length && j < modified.length; i++) { + if (this.almostEqual(original[i], modified[j])) j++; + else pulls.push(original[i]); + } + + for (; i < original.length; i++) + pulls.push(original[i]); + + if (j === modified.length) { + if (pulls.length === 1) return this.pull(changePath, pulls[0]); + // We can complete using just pulls + return pulls.forEach((pull) => this.pull(changePath, pull)); + } + + // If we have a smaller target array than our source, we will need to re-create it + // regardless (if we want to do so in a single operation anyway) + else return this.set(changePath, modified); + } + + // Check if we can get from original => modified using just pushes + if (original.length < modified.length) { + var canPush = true; + for (i = 0; i < original.length; i++) + if (this.almostEqual(original[i], modified[i]) < 1) { + canPush = false; + break; + } + + if (canPush) { + for (i = original.length; i < modified.length; i++) + this.push(changePath, modified[i]); + return; + } + } + + // Otherwise, we need to use $set to generate the new array + + // Check how many manipulations would need to be performed, if it's more than half the array size + // then rather re-create the array + + var sets = []; + var partials = []; + for (i = 0; i < modified.length; i++) { + var equality = this.almostEqual(original[i], modified[i]); + if (equality === 0) sets.push(i); + else if (equality < 1) partials.push(i); + } + + if (sets.length > modified.length / 2) + return this.set(changePath, modified); + + for (i = 0; i < sets.length; i++) + this.set(this.resolve(changePath, sets[i].toString()), modified[sets[i]]); + + for (i = 0; i < partials.length; i++) + this.onObject(original[partials[i]], modified[partials[i]], this.resolve(changePath, partials[i].toString())); + } + + private set(path: string, value: any) { + if (!this.changes.$set) + this.changes.$set = {}; + + this.changes.$set[path] = value; + } + + private unset(path: string) { + if (!this.changes.$unset) + this.changes.$unset = {}; + + this.changes.$unset[path] = 1; + } + + private inc(path: string, value: number) { + if (!this.changes.$inc) + this.changes.$inc = {}; + + this.changes.$inc[path] = value; + } + + private push(path: string, value: any) { + if (!this.changes.$push) + this.changes.$push = {}; + + if (this.changes.$push[path]) { + if (this.changes.$push[path].$each) + this.changes.$push[path].$each.push(value); + else + this.changes.$push[path] = { $each: [this.changes.$push[path], value] }; + } else this.changes.$push[path] = value; + } + + private pull(path: string, value: any) { + if (!this.changes.$pull) + this.changes.$pull = {}; + + if (this.changes.$pullAll && this.changes.$pullAll[path]) { + return this.changes.$pullAll[path].push(value); + } + + if (this.changes.$pull[path]) { + this.pullAll(path, [this.changes.$pull[path], value]); + delete this.changes.$pull[path]; + if (_.keys(this.changes.$pull).length === 0) + delete this.changes.$pull; + return; + } + + this.changes.$pull[path] = value; + } + + private pullAll(path: string, values: any[]) { + if (!this.changes.$pullAll) + this.changes.$pullAll = {}; + + this.changes.$pullAll[path] = values; + } + + private resolve(...args) { + var validArguments = []; + args.forEach(function (arg) { + if (arg) validArguments.push(arg); + }); + return validArguments.join('.'); + } + + private almostEqual(o1: Object, o2: Object); + private almostEqual(o1: any, o2: any) { + if (!_.isPlainObject(o1) || !_.isPlainObject(o2)) return o1 == o2 ? 1 : 0; + + var o1i, o1k = Object.keys(o1); + var o2k = Object.keys(o2); + + var commonKeys = []; + for (o1i = 0; o1i < o1k.length; o1i++) + if (~o2k.indexOf(o1k[o1i])) commonKeys.push(o1k[o1i]); + + var totalKeys = o1k.length + o2k.length - commonKeys.length; + var keysDifference = totalKeys - commonKeys.length; + + var requiredChanges = 0; + for (var i = 0; i < commonKeys.length; i++) + if (this.almostEqual(o1[commonKeys[i]], o2[commonKeys[i]]) < 1) requiredChanges++; + + return 1 - (keysDifference / totalKeys) - (requiredChanges / commonKeys.length); + } +} \ No newline at end of file diff --git a/lib/utils/String.js b/lib/utils/String.js deleted file mode 100644 index c7d91ce..0000000 --- a/lib/utils/String.js +++ /dev/null @@ -1,147 +0,0 @@ -/* - Copyright (c) 2009, CodePlex Foundation - All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, are permitted - provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this list of conditions - and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions - and the following disclaimer in the documentation and/or other materials provided with the distribution. - - * Neither the name of CodePlex Foundation nor the names of its contributors may be used to endorse or - promote products derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY EXPRESS OR IMPLIED - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN - IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -(function () { - - $type = String; - $type.__typeName = 'String'; - $type.__class = true; - - $prototype = $type.prototype; - $prototype.endsWith = function String$endsWith(suffix) { - /// Determines whether the end of this instance matches the specified string. - /// A string to compare to. - /// true if suffix matches the end of this instance; otherwise, false. - return (this.substr(this.length - suffix.length) === suffix); - } - - $prototype.startsWith = function String$startsWith(prefix) { - /// Determines whether the beginning of this instance matches the specified string. - /// The String to compare. - /// true if prefix matches the beginning of this string; otherwise, false. - return (this.substr(0, prefix.length) === prefix); - } - - $prototype.trim = function String$trim() { - /// Removes all leading and trailing white-space characters from the current String object. - /// The string that remains after all white-space characters are removed from the start and end of the current String object. - return this.replace(/^\s+|\s+$/g, ''); - } - - $prototype.trimEnd = function String$trimEnd() { - /// Removes all trailing white spaces from the current String object. - /// The string that remains after all white-space characters are removed from the end of the current String object. - return this.replace(/\s+$/, ''); - } - - $prototype.trimStart = function String$trimStart() { - /// Removes all leading white spaces from the current String object. - /// The string that remains after all white-space characters are removed from the start of the current String object. - return this.replace(/^\s+/, ''); - } - - $type.format = function String$format(format, args) { - /// Replaces the format items in a specified String with the text equivalents of the values of corresponding object instances. The invariant culture will be used to format dates and numbers. - /// A format string. - /// The objects to format. - /// A copy of format in which the format items have been replaced by the string equivalent of the corresponding instances of object arguments. - return String._toFormattedString(false, arguments); - } - - $type._toFormattedString = function String$_toFormattedString(useLocale, args) { - var result = ''; - var format = args[0]; - - for (var i = 0; ;) { - // Find the next opening or closing brace - var open = format.indexOf('{', i); - var close = format.indexOf('}', i); - if ((open < 0) && (close < 0)) { - // Not found: copy the end of the string and break - result += format.slice(i); - break; - } - if ((close > 0) && ((close < open) || (open < 0))) { - - if (format.charAt(close + 1) !== '}') { - throw new Error('format stringFormatBraceMismatch'); - } - - result += format.slice(i, close + 1); - i = close + 2; - continue; - } - - // Copy the string before the brace - result += format.slice(i, open); - i = open + 1; - - // Check for double braces (which display as one and are not arguments) - if (format.charAt(i) === '{') { - result += '{'; - i++; - continue; - } - - if (close < 0) throw new Error('format stringFormatBraceMismatch'); - - - // Find the closing brace - - // Get the string between the braces, and split it around the ':' (if any) - var brace = format.substring(i, close); - var colonIndex = brace.indexOf(':'); - var argNumber = parseInt((colonIndex < 0) ? brace : brace.substring(0, colonIndex), 10) + 1; - - if (isNaN(argNumber)) throw new Error('format stringFormatInvalid'); - - var argFormat = (colonIndex < 0) ? '' : brace.substring(colonIndex + 1); - - var arg = args[argNumber]; - if (typeof (arg) === "undefined" || arg === null) { - arg = ''; - } - - // If it has a toFormattedString method, call it. Otherwise, call toString() - if (arg.toFormattedString) { - result += arg.toFormattedString(argFormat); - } - else if (useLocale && arg.localeFormat) { - result += arg.localeFormat(argFormat); - } - else if (arg.format) { - result += arg.format(argFormat); - } - else - result += arg.toString(); - - i = close + 1; - } - - return result; - } - -})(); \ No newline at end of file diff --git a/lib/utils/diff.js b/lib/utils/diff.js deleted file mode 100644 index f59d5ef..0000000 --- a/lib/utils/diff.js +++ /dev/null @@ -1,200 +0,0 @@ -var _ = require('lodash'), - ObjectID = require('mongodb').ObjectID; - -module.exports = diff; - -function diff(original, modified) { - var omnom = new Omnom(); - - omnom.diff(original, modified); - - return omnom.changes; -} - -function Omnom(options) { - this.options = options; - this.changes = {}; -} - -Omnom.prototype.diff = function(original, modified) { - this.onObject(original, modified); -}; - -Omnom.prototype.onObject = function(original, modified, changePath) { - if(original === undefined || original === null) - return (original !== modified) && this.set(changePath, modified); - - if(typeof original == 'number' && typeof modified == 'number' && original !== modified) - return this.set(changePath, modified); - - if(Array.isArray(original) && Array.isArray(modified)) - return this.onArray(original, modified, changePath); - - if(original instanceof ObjectID && modified instanceof ObjectID) - return !original.equals(modified) && this.set(changePath, modified); - - if(!_.isPlainObject(original) || !_.isPlainObject(modified)) - return !_.isEqual(original, modified) && this.set(changePath, modified); - - _.each(modified, function(value, key) { - // Handle array diffs in their own special way - if(Array.isArray(value) && Array.isArray(original[key])) this.onArray(original[key], value, resolve(changePath, key)); - - // Otherwise, just keep going - else this.onObject(original[key], value, resolve(changePath, key)); - }, this); - - // Unset removed properties - _.each(original, function(value, key) { - if(modified[key] === undefined || modified[key] === null) return this.unset(resolve(changePath, key)); - }, this); -}; - -Omnom.prototype.onArray = function(original, modified, changePath) { - var i,j; - - // Check if we can get from original => modified using just pulls - if(original.length > modified.length) { - var pulls = []; - for(i = 0, j = 0; i < original.length && j < modified.length; i++) { - if(almostEqual(original[i], modified[j])) j++; - else pulls.push(original[i]); - } - - for(; i < original.length; i++) - pulls.push(original[i]); - - if(j === modified.length) { - if(pulls.length === 1) return this.pull(changePath, pulls[0]); - // We can complete using just pulls - return this.pullAll(changePath, pulls); - } - - // If we have a smaller target array than our source, we will need to re-create it - // regardless (if we want to do so in a single operation anyway) - else return this.set(changePath, modified); - } - - // Check if we can get from original => modified using just pushes - if(original.length < modified.length) { - var canPush = true; - for(i = 0; i < original.length; i++) - if(almostEqual(original[i], modified[i]) < 1) { - canPush = false; - break; - } - - if(canPush) { - for(i = original.length; i < modified.length; i++) - this.push(changePath, modified[i]); - return; - } - } - - // Otherwise, we need to use $set to generate the new array - - // Check how many manipulations would need to be performed, if it's more than half the array size - // then rather re-create the array - - var sets = []; - var partials = []; - for(i = 0; i < modified.length; i++) { - var equality = almostEqual(original[i], modified[i]); - if(equality === 0) sets.push(i); - else if(equality < 1) partials.push(i); - } - - if(sets.length > modified.length / 2) - return this.set(changePath, modified); - - for(i = 0; i < sets.length; i++) - this.set(resolve(changePath, sets[i].toString()), modified[sets[i]]); - - for(i = 0; i < partials.length; i++) - this.onObject(original[partials[i]], modified[partials[i]], resolve(changePath, partials[i].toString())); -}; - -Omnom.prototype.set = function(path, value) { - if(!this.changes.$set) - this.changes.$set = {}; - - this.changes.$set[path] = value; -}; - -Omnom.prototype.unset = function(path, value) { - if(!this.changes.$unset) - this.changes.$unset = {}; - - this.changes.$unset[path] = 1; -}; - -Omnom.prototype.inc = function(path, value) { - if(!this.changes.$inc) - this.changes.$inc = {}; - - this.changes.$inc[path] = value; -}; - -Omnom.prototype.push = function(path, value) { - if(!this.changes.$push) - this.changes.$push = {}; - - if(this.changes.$push[path]) { - if(this.changes.$push[path].$each) - this.changes.$push[path].$each.push(value); - else - this.changes.$push[path] = { $each: [this.changes.$push[path], value] }; - } else this.changes.$push[path] = value; -}; - -Omnom.prototype.pull = function(path, value) { - if(!this.changes.$pull) - this.changes.$pull = {}; - - if(this.changes.$pullAll && this.changes.$pullAll[path]) { - return this.changes.$pullAll[path].push(value); - } - - if(this.changes.$pull[path]) { - this.pullAll(path, [this.changes.$pull[path], value]); - delete this.changes.$pull[path]; - return; - } - - this.changes.$pull[path] = value; -}; - -Omnom.prototype.pullAll = function(path, values) { - if(!this.changes.$pullAll) - this.changes.$pullAll = {}; - - this.changes.$pullAll[path] = values; -}; - -function resolve() { - var validArguments = []; - Array.prototype.forEach.call(arguments, function(arg) { - if(arg) validArguments.push(arg); - }); - return validArguments.join('.'); -} - -var almostEqual = function (o1, o2) { - if(!_.isPlainObject(o1) || !_.isPlainObject(o2)) return o1 == o2 ? 1 : 0; - - var o1i, o1k = Object.keys(o1); - var o2i, o2k = Object.keys(o2); - - var commonKeys = []; - for(o1i = 0; o1i < o1k.length; o1i++) - if(~o2k.indexOf(o1k[o1i])) commonKeys.push(o1k[o1i]); - - var totalKeys = o1k.length + o2k.length - commonKeys.length; - var keysDifference = totalKeys - commonKeys.length; - - var requiredChanges = 0; - for(var i = 0; i < commonKeys.length; i++) - if(almostEqual(o1[commonKeys[i]], o2[commonKeys[i]]) < 1) requiredChanges++; - - return 1 - (keysDifference / totalKeys) - (requiredChanges / commonKeys.length); -}; \ No newline at end of file diff --git a/package.json b/package.json index eacf2dc..82baaea 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,14 @@ { "name": "iridium", - "version": "4.0.1", + "version": "5.5.1", "author": "Benjamin Pannell ", "description": "A custom lightweight ORM for MongoDB designed for power-users", + "license": "MIT", "homepage": "https://sierrasoftworks.com/iridium", - "repository": "https://github.com/sierrasoftworks/iridium", + "repository": { + "url": "https://github.com/sierrasoftworks/iridium", + "type": "git" + }, "contributors": [ { "name": "Benjamin Pannell", @@ -12,29 +16,53 @@ } ], "licence": "MIT", - "main": "./index", + "main": "./dist/index.js", "scripts": { - "test": "mocha" + "test": "grunt test", + "coverage": "grunt coverage", + "lint": "jshint lib/", + "benchmark": "node ./benchmarks/mongodb.js", + "build": "grunt build" + }, + "typescript": { + "definition": "iridium.d.ts" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" }, "dependencies": { - "mongodb": "~1.4", - "skmatc": "1.x", - "lodash": "*", - "concoction": "~1.0", - "debug": "1.x", - "bluebird": "2.x", - "async": "~0.9", - "functionality": "2.x" + "bluebird": "^2.9.25", + "lodash": "^3.7.0", + "mongodb": "^2.0.28", + "skmatc": "^1.1.2" }, "devDependencies": { - "mocha": "2.x", - "chai": "1.x", - "chai-as-promised": "4.x", - "chai-fuzzy": "1.x", - "underscore": "1.8.3" + "chai": "^2.2.0", + "chai-as-promised": "^5.0.0", + "chai-fuzzy": "^1.5.0", + "codeclimate-test-reporter": "~0.0.4", + "coveralls": "^2.11.2", + "minimist": "^1.1.1", + "semver": "^4.3.6", + "gulp": "^3.9.0", + "del": "^1.2.0", + "require-dir": "~0.3.0", + "run-sequence": "^1.1.0", + "gulp-typescript": "^2.7.6", + "gulp-sourcemaps": "^1.5.2", + "gulp-plumber": "^1.0.1", + "gulp-mocha": "^2.1.1", + "gulp-util": "^3.0.5", + "gulp-bump": "~0.3.1", + "gulp-git": "^1.2.4", + "gulp-istanbul": "~0.10.0", + "gulp-replace": "~0.5.3", + "istanbul": "~0.3.13", + "jshint": "^2.7.0", + "mocha": "^2.2.4", + "tick": "~0.1.1", + "typescript": "^1.5.0-beta", + "underscore": "^1.8.3" }, "keywords": [ "mongodb", @@ -42,7 +70,6 @@ "odm", "iridium", "validation", - "transformation", "preprocessing" ] } diff --git a/test/Cache.ts b/test/Cache.ts new file mode 100644 index 0000000..762e510 --- /dev/null +++ b/test/Cache.ts @@ -0,0 +1,176 @@ +/// +import * as Iridium from '../index'; + +interface Document { + _id?: string; +} + +class Instance extends Iridium.Instance { + static collection = 'test'; + static schema: Iridium.Schema = { _id: false }; + static cache = new Iridium.CacheOnID(); + + _id: string; +} + +describe("Cache",() => { + describe("implementations",() => { + + describe("NoOp",() => { + let noOpCache = new Iridium.NoOpCache(); + + it("should pretend to cache objects",() => { + return chai.expect(noOpCache.set("test", {})).to.eventually.be.eql({}); + }); + + it("should return undefined for keys which were cached",() => { + return chai.expect(noOpCache.get("test")).to.eventually.be.undefined; + }); + + it("should return undefined for keys which were not cached",() => { + return chai.expect(noOpCache.get("uncached")).to.eventually.be.undefined; + }); + + it("should report that objects were not in the cache when removing them",() => { + return chai.expect(noOpCache.clear("test")).to.eventually.be.false; + }); + }); + + describe("Memory",() => { + let memCache = new Iridium.MemoryCache(); + + it("should cache objects",() => { + return chai.expect(memCache.set("test", {})).to.eventually.be.eql({}); + }); + + it("should return cached objects when requested",() => { + return chai.expect(memCache.get("test")).to.eventually.be.eql({}); + }); + + it("should return undefined if the object does not exist in the cache",() => { + return chai.expect(memCache.get("uncached")).to.eventually.be.undefined; + }); + + it("should report if an object was present in the cache when removed",() => { + return chai.expect(memCache.clear("test")).to.eventually.be.true; + }); + + it("should report if an object was not present in the cache when removed",() => { + return chai.expect(memCache.clear("uncached")).to.eventually.be.false; + }); + + it("should actually remove an object from the cache",() => { + return chai.expect(memCache.get("test")).to.eventually.be.undefined; + }); + }); + + }); + + describe("controllers",() => { + + describe("CacheOnID",() => { + let director = new Iridium.CacheOnID(); + + it("should only report that objects with an _id field are cacheable",() => { + chai.expect(director.valid({ _id: 'test' })).to.be.true; + chai.expect(director.valid({ noID: 'test' })).to.be.false; + }); + + it("should generate a key based on the object's ID",() => { + chai.expect(director.buildKey({ _id: 'test' })).to.be.equal('test'); + }); + + it("should only report that queries which specify the _id field are usable",() => { + chai.expect(director.validQuery({ _id: 'test' })).to.be.true; + chai.expect(director.validQuery({ notID: 'test' })).to.be.false; + }); + + it("should generate a key based on the query ID",() => { + chai.expect(director.buildQueryKey({ _id: 'test' })).to.be.equal('test'); + }); + }); + + }); + + describe("integration",() => { + let core = new Iridium.Core({ + database: 'test' + }); + + let model = new Iridium.Model(core, Instance); + + before(() => core.connect().then(() => { + core.cache = new Iridium.MemoryCache(); + }).then(() => model.create({}))); + + after(() => core.close()); + + describe("cache",() => { + it("should be set on the Iridium Core",() => { + core.cache = new Iridium.MemoryCache(); + }); + + it("should be available through the Iridium Core",() => { + chai.expect(core.cache).to.be.instanceOf(Iridium.MemoryCache); + }); + }); + + describe("director",() => { + it("should be available through the model's cache field",() => { + chai.expect(model.cacheDirector).to.be.instanceOf(Iridium.CacheOnID); + }); + }); + + describe("should be populated",() => { + beforeEach(() => core.cache = new Iridium.MemoryCache()); + + it("when a single document is retrieved",() => { + return model.get().then((instance) => + chai.expect(core.cache.get(instance._id) + ).to.eventually.exist); + }); + + it("when an instance is modified",() => { + return model.get().then((instance) => { + core.cache = new Iridium.MemoryCache(); + return instance.save(); + }).then((instance) => chai.expect(core.cache.get(instance._id)).to.eventually.exist); + }); + }); + + describe("should be hit",() => { + let instanceID; + beforeEach(() => core.connect().then(() => model.remove()).then(() => model.insert({})).then(() => { + core.cache = new Iridium.MemoryCache(); + }).then(() => model.get()).then(instance => { + instanceID = instance._id; + // Remove the instance from the database and put it back into the cache + return instance.remove().then(() => { + return model.cache.set(instance.document); + }); + })); + + it("when a single document is retrieved",() => { + return chai.expect(model.get(instanceID)).to.eventually.exist; + }); + + it("when a document is requested which matches the conditions",() => { + return chai.expect(model.get({ _id: instanceID })).to.eventually.exist; + }); + }); + + describe("should be cleaned",() => { + beforeEach(() => model.insert({}).then(() => model.get())); + + it("when an instance is removed",() => { + return model.get().then(instance => instance.remove()).then(instance => chai.expect(core.cache.get(instance._id)).to.eventually.be.undefined); + }); + + it("when remove is called with compatible conditions",() => { + return model.get().then(instance => { + return model.remove({ _id: instance._id }).then(() => instance); + }).then(instance => chai.expect(core.cache.get(instance._id)).to.eventually.be.undefined); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/Core.ts b/test/Core.ts new file mode 100644 index 0000000..c2b9718 --- /dev/null +++ b/test/Core.ts @@ -0,0 +1,232 @@ +/// +import * as Iridium from '../index'; + +class InheritedCore extends Iridium.Core { + theAnswer = 42; +} + +class InheritedCoreWithCustomConstructor extends Iridium.Core { + constructor() { + super("mongodb://localhost/test"); + } +} + +describe("Core",() => { + describe("constructor",() => { + it("should accept a URI string",() => { + let core = new Iridium.Core("mongodb://localhost/test"); + chai.expect(core.url).to.equal("mongodb://localhost/test"); + }); + + it("should accept a configuration object",() => { + new Iridium.Core({ + database: 'test' + }); + }); + + it("should throw an error if no URI or configuration object was provided",() => { + chai.expect(() => new Iridium.Core('')).to.throw("Expected either a URI or config object to be supplied when initializing Iridium"); + }); + + describe("should correctly convert the configuration object into a URI string", () => { + it("when only a single host is specified",() => { + let core = new Iridium.Core({ + host: 'localhost', + port: 27016, + database: 'test', + username: 'user', + password: 'password' + }); + + chai.expect(core.url).to.equal("mongodb://user:password@localhost:27016/test"); + }); + + it("when only a single host is specified with no port",() => { + let core = new Iridium.Core({ + host: 'localhost', + database: 'test', + username: 'user', + password: 'password' + }); + + chai.expect(core.url).to.equal("mongodb://user:password@localhost/test"); + }); + + it("when multiple hosts are specified",() => { + let core = new Iridium.Core({ + hosts: [{ address: 'localhost' }, { address: '127.0.0.1' }], + database: 'test', + port: 27016, + username: 'user', + password: 'password' + }); + + chai.expect(core.url).to.equal("mongodb://user:password@localhost:27016,127.0.0.1:27016/test"); + }); + + it("when multiple hosts are specified with no port",() => { + let core = new Iridium.Core({ + hosts: [{ address: 'localhost' }, { address: '127.0.0.1' }], + database: 'test', + username: 'user', + password: 'password' + }); + + chai.expect(core.url).to.equal("mongodb://user:password@localhost,127.0.0.1/test"); + }); + + it("when multiple hosts are specified with different ports",() => { + let core = new Iridium.Core({ + hosts: [{ address: 'localhost', port: 27016 }, { address: '127.0.0.1', port: 27017 }], + database: 'test', + username: 'user', + password: 'password' + }); + + chai.expect(core.url).to.equal("mongodb://user:password@localhost:27016,127.0.0.1:27017/test"); + }); + + it("when a combination of single and multiple hosts is specified",() => { + let core = new Iridium.Core({ + host: 'localhost', + port: 27016, + hosts: [{ address: 'localhost', port: 27017 }, { address: '127.0.0.1', port: 27018 }], + database: 'test', + username: 'user', + password: 'password' + }); + + chai.expect(core.url).to.equal("mongodb://user:password@localhost:27016,localhost:27017,127.0.0.1:27018/test"); + }); + + it("when a combination of single and multiple hosts is specified and there are duplicates",() => { + let core = new Iridium.Core({ + host: 'localhost', + port: 27016, + hosts: [{ address: 'localhost', port: 27016 }, { address: '127.0.0.1', port: 27017 }], + database: 'test', + username: 'user', + password: 'password' + }); + + chai.expect(core.url).to.equal("mongodb://user:password@localhost:27016,127.0.0.1:27017/test"); + }); + }); + + it("should make logical assumptions about the default host",() => { + let core = new Iridium.Core({ + database: 'test' + }); + + chai.expect(core.url).to.equal("mongodb://localhost/test"); + }); + }); + + describe("plugins",() => { + let core = new Iridium.Core({ + database: 'test' + }); + + let plugin = { + newModel: (model) => { + + } + }; + + it("should be registered through the register method",() => { + chai.expect(core.register(plugin)).to.equal(core); + }); + it("should then be available through the plugins collection",() => { + chai.expect(core.plugins).to.contain(plugin); + }); + }); + + describe("middleware",() => { + let core = new Iridium.Core({ + database: 'test' + }); + + it("should have an Express provider",() => { + chai.expect(core.express).to.exist.and.be.a('function'); + chai.expect(core.express()).to.exist.and.be.a('function'); + }); + }); + + describe("cache",() => { + let core = new Iridium.Core({ + database: 'test' + }); + + it("should have a default no-op cache provider",() => { + chai.expect(core.cache).to.exist; + core.cache.set("test", true); + chai.expect(core.cache.get("test")).to.eventually.not.exist; + }); + }); + + describe("settings",() => { + it("should be exposed via the settings property",() => { + let core = new Iridium.Core({ database: 'test' }); + chai.expect(core.settings).to.exist.and.eql({ database: 'test' }); + }); + }); + + describe("connect",() => { + let core: Iridium.Core; + if (!process.env.CI_SERVER) + it("should return a rejection if the connection fails",() => { + core = new Iridium.Core("mongodb://0.0.0.0/test"); + return chai.expect(core.connect()).to.be.rejected; + }); + else it.skip("should return a rejection if the connection fails"); + + it("should open a connection to the correct database and return the core",() => { + core = new Iridium.Core("mongodb://localhost/test"); + return chai.expect(core.connect()).to.eventually.exist.and.equal(core); + }); + + it("should then be able to close the connection",() => { + return core.close(); + }); + }); + + describe("close",() => { + let core = new Iridium.Core("mongodb://localhost/test"); + + it("should not fail if called when not connected",() => { + return core.close(); + }); + + it("should chain promises",() => { + chai.expect(core.close()).to.eventually.equal(core); + }); + }); + + describe("inheritance",() => { + it("should allow a class to extend the core",() => { + chai.expect(InheritedCore).to.exist; + chai.expect(new InheritedCore("mongodb://localhost/test")).to.be.an.instanceof(Iridium.Core); + }); + + it("should pass through constructor arguments to the core",() => { + let core = new InheritedCore({ + database: 'test' + }); + + chai.expect(core.url).to.equal("mongodb://localhost/test"); + }); + + it("should pass through the properties of the object",() => { + let core = new InheritedCore({ + database: 'test' + }); + + chai.expect(core.theAnswer).to.equal(42); + }); + + it("should support custom constructors",() => { + let core = new InheritedCoreWithCustomConstructor(); + chai.expect(core.url).to.equal("mongodb://localhost/test"); + }); + }); +}); diff --git a/test/Decorators.ts b/test/Decorators.ts new file mode 100644 index 0000000..b440b5c --- /dev/null +++ b/test/Decorators.ts @@ -0,0 +1,157 @@ +/// +import * as Iridium from '../index'; +import skmatc = require('skmatc'); +import MongoDB = require('mongodb'); + +interface TestDocument { + _id?: string; + name: string; + email: string; +} + +function VersionValidator(schema, data) { + return this.assert(/^\d+\.\d+\.\d+(?:-.+)?$/.test(data)); +} + +@Iridium.Collection('test') +@Iridium.Index({ name: 1 }) +@Iridium.Index({ email: 1 }, { background: true }) +@Iridium.Validate('version', VersionValidator) +@Iridium.Property('version', 'version') +@Iridium.Property('optional2', Boolean, false) +class Test extends Iridium.Instance implements TestDocument { + @Iridium.ObjectID + _id: string; + + @Iridium.Property(String) + name: string; + + @Iridium.Property(/^.+@.+$/) + @Iridium.Transform(email => email.toLowerCase().trim(), email => email.toLowerCase().trim()) + email: string; + + version: string; + + @Iridium.Property(Boolean, false) + optional1: boolean; + optional2: boolean; +} + +describe("Decorators", () => { + describe("Collection", () => { + it("should populate the collection static field", () => { + chai.expect(Test.collection).to.equal('test'); + }); + + it("should not pollute the parent's collection property", () => { + chai.expect(Iridium.Instance.collection).to.not.exist; + }); + }); + + describe("Index", () => { + it("should populate the constructor's indexes property with index objects", () => { + chai.expect(Test.indexes).to.exist.and.have.length(2); + }); + + it("should support just spec indexes", () => { + chai.expect(Test.indexes).to.containOneLike({ spec: { name: 1 }, options: {} }); + }); + + it("should support indexes with both a spec and options", () => { + chai.expect(Test.indexes).to.containOneLike({ spec: { email: 1 }, options: { background: true }}); + }); + + it("should not pollute the parent's index object", () => { + chai.expect(Iridium.Instance.indexes).to.exist.and.have.length(0); + }); + }); + + describe("Validate", () => { + it("should populate the constructor's valdiators property", () => { + chai.expect(Test.validators).to.exist.and.have.length(Iridium.Instance.validators.length + 1); + }); + + it("should create a valid Skmatc validator instance", () => { + let s = new skmatc(Test.schema); + + for (let i = 0; i < Test.validators.length; i++) { + chai.expect(Test.validators[i]).to.be.a('function'); + s.register(Test.validators[i]); + } + + chai.expect(s.validate({ + name: 'Test', + email: 'test@test.com', + version: '1.0.0' + })).to.exist.and.have.property('failures').eql([]); + }); + + it("should correctly include the validations", () => { + let s = new skmatc(Test.schema); + + for (let i = 0; i < Test.validators.length; i++) { + chai.expect(Test.validators[i]).to.be.a('function'); + s.register(Test.validators[i]); + } + + chai.expect(s.validate({ + name: 'Test', + email: 'test@test.com', + version: 'a1.0.0' + })).to.exist.and.have.property('failures').not.eql([]); + }); + + it("should not pollute the parent's validators object", () => { + chai.expect(Iridium.Instance.validators).to.exist.and.have.length(1); + }); + }); + + describe("ObjectID", () => { + it("should populate the constructor's schema object", () => { + chai.expect(Test.schema).to.exist.and.have.property('_id').and.eql(MongoDB.ObjectID); + }); + }); + + describe("Property", () => { + it("should populate the constructor's schema object when applied to properties", () => { + chai.expect(Test.schema).to.exist.and.have.property('name', String); + chai.expect(Test.schema).to.exist.and.have.property('email').and.eql(/^.+@.+$/); + }); + + it("should populate the constructor's schema object when to the constructor", () => { + chai.expect(Test.schema).to.exist.and.have.property('version', 'version'); + }); + + it("should correctly handle optional properties defined on instance fields", () => { + chai.expect(Test.schema).to.exist.and.have.property('optional1').eql({ $required: false, $type: Boolean }); + }); + + it("should correctly handle optional properties defined on the constructor", () => { + chai.expect(Test.schema).to.exist.and.have.property('optional2').eql({ $required: false, $type: Boolean }); + }); + + it("should not pollute the parent's schema object", () => { + chai.expect(Iridium.Instance.schema).to.exist.and.not.have.property('name'); + chai.expect(Iridium.Instance.schema).to.exist.and.not.have.property('email'); + chai.expect(Iridium.Instance.schema).to.exist.and.not.have.property('version'); + chai.expect(Iridium.Instance.schema).to.exist.and.not.have.property('optional1'); + chai.expect(Iridium.Instance.schema).to.exist.and.not.have.property('optional2'); + }); + }); + + describe("Transform", () => { + it("should not remove existing entries in the transforms object", () => { + chai.expect(Test.transforms).to.exist.and.have.property('_id').with.property('fromDB').which.is.a('function'); + chai.expect(Test.transforms).to.exist.and.have.property('_id').with.property('toDB').which.is.a('function'); + }); + + it("should populate the constructor's transforms object", () => { + chai.expect(Test.transforms).to.exist.and.have.property('email').with.property('fromDB').which.is.a('function'); + chai.expect(Test.transforms).to.exist.and.have.property('email').with.property('toDB').which.is.a('function'); + }); + + it("should not pollute the parent's transforms object", () => { + chai.expect(Iridium.Instance.transforms).to.exist.and.not.have.property('email'); + }); + }); +}); \ No newline at end of file diff --git a/test/Hooks.ts b/test/Hooks.ts new file mode 100644 index 0000000..6d0a7d4 --- /dev/null +++ b/test/Hooks.ts @@ -0,0 +1,163 @@ +/// +import * as Iridium from '../index'; +import Events = require('events'); +import Promise = require('bluebird'); + +interface TestDocument { + id?: string; + answer: number; +} + +let hookEmitter = new Events.EventEmitter(); + +class Test extends Iridium.Instance { + static collection = 'test'; + static schema: Iridium.Schema = { + _id: false, + answer: Number + }; + + id: string; + answer: number; + + static onCreating(document: TestDocument) { + hookEmitter.emit('creating', document); + } + + static onReady(instance: Test) { + hookEmitter.emit('ready', instance); + } + + static onRetrieved(document: TestDocument) { + hookEmitter.emit('retrieved', document); + } + + static onSaving(instance: Test, changes: any) { + hookEmitter.emit('saving', instance, changes); + } +} + +describe("Hooks", function () { + this.timeout(500); + + let core = new Iridium.Core({ database: 'test' }); + let model = new Iridium.Model(core, Test); + + beforeEach(() => core.connect().then(() => model.remove()).then(() => model.insert({ answer: 10 }))); + afterEach(() => model.remove()); + after(() => core.close()); + + describe("creating",() => { + it("should be called when a document is being created",(done) => { + hookEmitter.once('creating',() => done()); + model.insert({ answer: 11 }); + }); + + it("should be passed the document being created",() => { + let result: Promise; + + hookEmitter.once('creating',(document) => { + result = Promise.resolve().then(() => { + chai.expect(document).to.eql({ answer: 11 }); + }); + }); + + return model.insert({ answer: 11 }).then(() => chai.expect(result).to.exist).then(() => result); + }); + }); + + describe("ready",() => { + it("should be called when an instance is prepared",() => { + let result: Promise; + + hookEmitter.once('ready',() => { + result = Promise.resolve(); + }); + + return model.get().then(() => chai.expect(result).to.exist).then(() => result); + }); + + it("should be passed the instance which was created",() => { + let result: Promise; + + hookEmitter.once('ready',(instance) => { + result = Promise.resolve().then(() => { + chai.expect(instance).to.be.an.instanceof(model.Instance); + }); + }); + + return model.get().then(() => chai.expect(result).to.exist).then(() => result); + }); + }); + + describe("retreived",() => { + it("should be called when a document is being retrieved",() => { + let result: Promise; + + hookEmitter.once('retrieved',() => { + result = Promise.resolve(); + }); + + return model.get().then(() => chai.expect(result).to.exist).then(() => result); + }); + + it("should be passed the document being retrieved",() => { + let result: Promise; + + hookEmitter.once('retrieved',(document) => { + result = Promise.resolve().then(() => { + chai.expect(document).to.have.property('answer', 10); + }); + }); + + return model.get().then(() => chai.expect(result).to.exist).then(() => result); + }); + }); + + describe("saving",() => { + it("should be triggered when save() is called on an instance",() => { + let result: Promise; + + hookEmitter.once('saving',() => { + result = Promise.resolve(); + }); + + return model.get().then((instance) => { + instance.answer++; + return instance.save(); + }).then(() => chai.expect(result).to.exist).then(() => result); + }); + + it("should be passed the instance being saved",() => { + let result: Promise; + + hookEmitter.once('saving',(instance) => { + result = Promise.resolve().then(() => { + chai.expect(instance).to.be.an.instanceof(model.Instance); + }); + }); + + return model.get().then((instance) => { + instance.answer++; + return instance.save(); + }).then(() => chai.expect(result).to.exist).then(() => result); + }); + + it("should be passed the changes being made to the instance",() => { + let result: Promise; + + hookEmitter.once('saving',(instance, changes) => { + result = Promise.resolve().then(() => { + chai.expect(changes).to.eql({ + $set: { answer: instance.answer } + }); + }); + }); + + return model.get().then((instance) => { + instance.answer++; + return instance.save(); + }).then(() => chai.expect(result).to.exist).then(() => result); + }); + }); +}); \ No newline at end of file diff --git a/test/Instance.ts b/test/Instance.ts new file mode 100644 index 0000000..c09e31b --- /dev/null +++ b/test/Instance.ts @@ -0,0 +1,453 @@ +/// +import * as Iridium from '../index'; +import MongoDB = require('mongodb'); + +interface TestDocument { + _id?: string; + answer: number; + lots?: number[]; + less?: { [key: string]: number }; +} + +class Test extends Iridium.Instance implements TestDocument { + static collection = 'test'; + static schema: Iridium.Schema = { + _id: false, + answer: Number, + lots: { $required: false, $type: [Number] }, + less: { $required: false, $propertyType: Number } + }; + + _id: string; + answer: number; + lots: number[]; + less: { [key: string]: number }; + + test() { + return true; + } + + get ansqr() { + return this.answer * this.answer; + } +} + +class TestDB extends Iridium.Core { + constructor() { + super("mongodb://localhost/test"); + } + + Test = new Iridium.Model(this, Test); +} + +describe("Instance",() => { + let core = new TestDB(); + + before(() => core.connect()); + after(() => core.close()); + + beforeEach(() => core.Test.remove()); + + it("should default to isNew",() => { + let instance = new core.Test.Instance({ + answer: 42 + }); + + chai.expect(instance).to.have.property("_isNew", true); + }); + + it("should default to !isPartial",() => { + let instance = new core.Test.Instance({ + answer: 42 + }); + + chai.expect(instance).to.have.property("_isPartial", false); + }); + + it("should expose the latest document values",() => { + let instance = core.Test.helpers.wrapDocument({ + _id: 'aaaaaa', + answer: 2 + }); + + chai.expect(instance).to.exist; + chai.expect(instance.answer).to.be.equal(2); + chai.expect(instance._id).to.be.equal('aaaaaa'); + }); + + describe("methods",() => { + it("should expose save()",() => { + chai.expect(core.Test.helpers.wrapDocument({ id: '1', answer: 2 }).save).to.exist.and.be.a('function'); + }); + + it("should expose update()",() => { + chai.expect(core.Test.helpers.wrapDocument({ id: '1', answer: 2 }).update).to.exist.and.be.a('function'); + }); + + it("should expose refresh()",() => { + chai.expect(core.Test.helpers.wrapDocument({ id: '1', answer: 2 }).refresh).to.exist.and.be.a('function'); + }); + + it("should expose delete()",() => { + chai.expect(core.Test.helpers.wrapDocument({ id: '1', answer: 2 }).delete).to.exist.and.be.a('function'); + }); + + it("should expose remove()",() => { + chai.expect(core.Test.helpers.wrapDocument({ id: '1', answer: 2 }).remove).to.exist.and.be.a('function'); + }); + + it("should override toJSON()",() => { + chai.expect(core.Test.helpers.wrapDocument({ id: '1', answer: 2 }).toJSON()).to.eql({ id: '1', answer: 2 }); + }); + + it("should override toString()",() => { + chai.expect(core.Test.helpers.wrapDocument({ id: '1', answer: 2 }).toString()).to.eql(JSON.stringify({ id: '1', answer: 2 }, null, 2)); + }); + }); + + describe("properties",() => { + it("should expose document",() => { + chai.expect(core.Test.helpers.wrapDocument({ id: '1', answer: 2 }).document).to.eql({ id: '1', answer: 2 }); + }); + }); + + it("should expose additional getters and setters",() => { + let instance = core.Test.helpers.wrapDocument({ + id: 'aaaaaa', + answer: 2 + }); + + chai.expect(instance).to.exist; + chai.expect(instance.ansqr).to.exist.and.be.equal(4); + }); + + it("should expose additional methods",() => { + let instance = core.Test.helpers.wrapDocument({ + id: 'aaaaaa', + answer: 2 + }); + + chai.expect(instance).to.exist; + chai.expect(instance.test).to.exist.and.be.a('function'); + }); + + describe("should handle _id in a special manner",() => { + beforeEach(() => core.Test.remove().then(() => core.Test.insert({ answer: 42 }))); + afterEach(() => core.Test.remove()); + + it("get should transform ObjectIDs into hex strings",() => { + return core.Test.get().then(instance => { + chai.expect((instance.document._id)._bsontype).to.equal('ObjectID'); + chai.expect(instance._id).to.be.a('string').with.length(24); + }); + }); + + it("set should transform hex strings into ObjectIDs by default",() => { + return core.Test.get().then(instance => { + instance._id = "aaaaaaaaaaaaaaaaaaaaaaaa"; + chai.expect(new MongoDB.ObjectID(instance.document._id).toHexString()).to.equal('aaaaaaaaaaaaaaaaaaaaaaaa'); + }); + }); + }); + + describe("save()",() => { + + beforeEach(() => core.Test.remove()); + + it("should avoid making calls to the database if no changes were made to the instance",() => { + let update = core.Test.collection.update; + core.Test.collection.update = () => { + chai.assert.fail(); + }; + + return core.Test.insert({ + answer: 1 + }).then(() => chai.expect(core.Test.get().then((instance) => { + return instance.save().then(() => { + core.Test.collection.update = update; + }); + }))); + }); + + it("should insert the instance if it is not present in the database",() => { + let instance = new core.Test.Instance({ + answer: 1 + }); + + chai.expect((instance)._isNew).to.be.true; + return chai.expect(instance.save().then(() => chai.expect(core.Test.get(instance._id)).to.eventually.have.property('answer', instance.answer))).to.eventually.be.ok; + }); + + it("should automatically generate the update query if one was not provided",() => { + return core.Test.insert({ + answer: 1 + }).then(() => chai.expect(core.Test.get().then((instance) => { + instance.answer = 42; + return instance.save().then(() => core.Test.get(instance._id)); + })).to.eventually.have.property('answer', 42)); + }); + + it("should allow you to specify a custom update query",() => { + return core.Test.insert({ + answer: 1 + }) + .then(() => core.Test.get()) + .then((instance) => chai.expect(instance.save({ $set: { answer: 10 } })).to.eventually.have.property('answer', 10)); + }); + + it("should allow you tp specify a custom update query and conditions for the update",() => { + return core.Test.insert({ + answer: 1 + }) + .then(() => core.Test.get()) + .then((instance) => chai.expect(instance.save({ answer: { $lt: 5 } }, { $set: { answer: 10 } })).to.eventually.have.property('answer', 10)); + }); + + it("should return a promise for the instance",() => { + return core.Test.insert({ + answer: 1 + }) + .then(() => core.Test.get()) + .then((instance) => chai.expect(instance.save()).to.eventually.equal(instance)); + }); + + it("should allow the use of a callback instead of promises",(done) => { + core.Test.insert({ + answer: 1 + }) + .then(() => core.Test.get()) + .then((instance) => { + instance.save((err, result) => { + if (err) return done(err); + chai.expect(result).to.equal(instance); + return done(); + }); + }); + }); + }); + + describe("update()",() => { + beforeEach(() => core.Test.remove().then(() => core.Test.insert({ answer: 1 }))); + + it("should not replace the instance",() => { + return core.Test.get().then((instance) => chai.expect(instance.update()).to.eventually.equal(instance)); + }); + + it("should update the instance's properties",() => { + return chai.expect(core.Test.get().then((instance) => { + return core.Test.update({ _id: instance._id }, { + $set: { answer: 10 } + }).then(() => instance.update()); + })).to.eventually.have.property('answer', 10); + }); + + it("should set _isNew to true if the instance was removed from the database",() => { + return core.Test.get().then(instance => { + core.Test.remove().then(() => instance.update()).then(() => chai.expect((instance)._isNew).to.be.true); + }); + }); + + it("should return a promise for the instance",() => { + return core.Test.get().then((instance) => { + core.Test.update({ _id: instance._id }, { + $set: { answer: 10 } + }).then(() => chai.expect(instance.update()).to.eventually.equal(instance)); + }); + }); + + it("should allow the use of a callback instead of promises",(done) => { + core.Test.get().then((instance) => { + instance.update((err, result) => { + if (err) return done(err); + chai.expect(result).to.equal(instance); + return done(); + }); + }); + }); + }); + + describe("refresh()",() => { + beforeEach(() => core.Test.remove().then(() => core.Test.insert({ answer: 1 }))); + + it("should not replace the instance",() => { + return core.Test.get().then((instance) => chai.expect(instance.update()).to.eventually.equal(instance)); + }); + + it("should update the instance's properties",() => { + return chai.expect(core.Test.get().then((instance) => { + return core.Test.update({ _id: instance._id }, { + $set: { answer: 10 } + }).then(() => instance.refresh()); + })).to.eventually.have.property('answer', 10); + }); + + it("should set _isNew to true if the instance was removed from the database",() => { + return core.Test.get().then(instance => { + core.Test.remove().then(() => instance.refresh()).then(() => chai.expect((instance)._isNew).to.be.true); + }); + }); + + it("should return a promise for the instance",() => { + return core.Test.get().then((instance) => { + core.Test.update({ _id: instance._id }, { + $set: { answer: 10 } + }).then(() => chai.expect(instance.refresh()).to.eventually.equal(instance)); + }); + }); + + it("should allow the use of a callback instead of promises",(done) => { + core.Test.get().then((instance) => { + instance.refresh((err, result) => { + if (err) return done(err); + chai.expect(result).to.equal(instance); + return done(); + }); + }); + }); + }); + + describe("remove()",() => { + beforeEach(() => core.Test.remove().then(() => core.Test.insert({ answer: 1 }))); + + it("should remove the document from the database",() => { + return chai.expect(core.Test.get().then((instance) => instance.remove()).then(() => core.Test.get())).to.eventually.be.null; + }); + + it("should set the instance's isNew property to true",() => { + return chai.expect(core.Test.get().then((instance) => instance.remove())).to.eventually.have.property('_isNew', true); + }); + + it("should return a promise for the instance",() => { + return core.Test.get().then((instance) => chai.expect(instance.remove()).to.eventually.equal(instance)); + }); + + it("shouldn't mind if the object has already been removed",() => { + return core.Test.get().then(instance => { + return chai.expect(core.Test.remove().then(() => instance.remove())).to.eventually.not.be.rejected; + }); + }); + + it("should be a no-op if the object is marked as _isNew",() => { + return core.Test.get().then(instance => { + let newInstance = new core.Test.Instance(instance.document); + return newInstance.remove(); + }).then(() => chai.expect(core.Test.count()).to.eventually.equal(1)); + }); + + it("should allow the use of a callback instead of promises",(done) => { + core.Test.get().then((instance) => { + instance.remove((err, result) => { + if (err) return done(err); + chai.expect(result).to.equal(instance); + return done(); + }); + }); + }); + }); + + describe("delete()",() => { + beforeEach(() => core.Test.remove().then(() => core.Test.insert({ answer: 1 }))); + + it("should remove the document from the database",() => { + return chai.expect(core.Test.get().then((instance) => instance.delete()).then(() => core.Test.get())).to.eventually.be.null; + }); + + it("should set the instance's isNew property to true",() => { + return chai.expect(core.Test.get().then((instance) => instance.delete())).to.eventually.have.property('_isNew', true); + }); + + it("should return a promise for the instance",() => { + return core.Test.get().then((instance) => chai.expect(instance.delete()).to.eventually.equal(instance)); + }); + + it("shouldn't mind if the object has already been removed",() => { + return core.Test.get().then(instance => { + return chai.expect(core.Test.remove().then(() => instance.delete())).to.eventually.not.be.rejected; + }); + }); + + it("should be a no-op if the object is marked as _isNew",() => { + return core.Test.get().then(instance => { + let newInstance = new core.Test.Instance(instance.document); + return newInstance.delete(); + }).then(() => chai.expect(core.Test.count()).to.eventually.equal(1)); + }); + + it("should allow the use of a callback instead of promises",(done) => { + core.Test.get().then((instance) => { + instance.delete((err, result) => { + if (err) return done(err); + chai.expect(result).to.equal(instance); + return done(); + }); + }); + }); + }); + + describe("first()",() => { + beforeEach(() => core.Test.remove().then(() => core.Test.insert({ answer: 1, lots: [1, 2, 3, 4], less: { 'a': 1, 'b': 2 } }))); + + it("should return the first object which matches the predicate over an array",() => { + return chai.expect(core.Test.get().then(instance => instance.first(instance.lots, lot => lot == 2))).to.eventually.equal(2); + }); + + it("should return the first object which matches the predicate over an object",() => { + return chai.expect(core.Test.get().then(instance => instance.first(instance.less, (value, key) => key == 'a'))).to.eventually.equal(1); + }); + + it("should return null if no item was found",() => { + return chai.expect(core.Test.get().then(instance => instance.first(instance.lots, lot => lot > 100))).to.eventually.be.null; + }); + }); + + describe("select()",() => { + beforeEach(() => core.Test.remove().then(() => core.Test.insert({ answer: 1, lots: [1, 2, 3, 4], less: { 'a': 1, 'b': 2 } }))); + + it("should return the objects which match the predicate over an array",() => { + return chai.expect(core.Test.get().then(instance => instance.select(instance.lots, lot => lot > 2))).to.eventually.eql([3, 4]); + }); + + it("should return the properties which match the predicate over an object",() => { + return chai.expect(core.Test.get().then(instance => instance.select(instance.less,(value, key) => key == 'a'))).to.eventually.eql({ 'a': 1 }); + }); + + it("should return an empty array if no items matched over an array",() => { + return chai.expect(core.Test.get().then(instance => instance.select(instance.lots, lot => lot > 100))).to.eventually.be.eql([]); + }); + + it("should return an empty object if no items matched over an object",() => { + return chai.expect(core.Test.get().then(instance => instance.select(instance.less, lot => lot > 100))).to.eventually.be.eql({}); + }); + }); + + describe("modifications", () => { + beforeEach(() => core.Test.remove().then(() => core.Test.insert({ answer: 1, lots: [1, 2, 3, 4], less: { 'a': 1, 'b': 2 } }))); + + it("should correctly diff simple property changes", () => { + return core.Test.get().then(instance => { + instance.answer = 2; + return instance.save(); + }).then(instance => { + chai.expect(instance).to.have.property('answer', 2); + }); + }); + + it("should correctly diff deep property changes", () => { + return core.Test.get().then(instance => { + instance.less['a'] = 2; + return instance.save(); + }).then(instance => { + chai.expect(instance).to.have.property('less').eql({ a: 2, b: 2 }); + }); + }); + + it("should correctly diff array operations", () => { + return core.Test.get().then(instance => { + instance.lots.push(5); + return instance.save(); + }).then(instance => { + chai.expect(instance).to.have.property('lots').eql([1,2,3,4,5]); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/Iridium.ts b/test/Iridium.ts new file mode 100644 index 0000000..96443d2 --- /dev/null +++ b/test/Iridium.ts @@ -0,0 +1,16 @@ +/// +import * as Iridium from '../index'; + +describe("Iridium",() => { + it("should expose the Core",() => { + chai.expect(Iridium.Core).to.exist.and.be.a('function'); + }); + + it("should expose the Model constructor", () => { + chai.expect(Iridium.Model).to.exist.and.be.a('function'); + }); + + it("should expose the default Instance class",() => { + chai.expect(Iridium.Instance).to.exist.and.be.a('function'); + }); +}); \ No newline at end of file diff --git a/test/Middleware.ts b/test/Middleware.ts new file mode 100644 index 0000000..04689c2 --- /dev/null +++ b/test/Middleware.ts @@ -0,0 +1,40 @@ +/// +import * as Iridium from '../index'; + +describe("Middleware",() => { + let core = new Iridium.Core({ + database: 'test' + }); + + describe("Express",() => { + beforeEach(() => core.close()); + + it("should be available through Core.express()",() => { + chai.expect(core.express).to.exist.and.be.a('function'); + }); + + it("should return a function",() => { + chai.expect(core.express()).to.exist.and.be.a('function'); + }); + + it("which sets req.db to the core instance",(done) => { + let req: any = {}; + let res: any = {}; + core.express()(req, res,(err) => { + if (err) return done(err); + chai.expect(req.db).to.exist.and.be.an.instanceof(Iridium.Core); + return done(); + }); + }); + + it("which checks that the core is connected",(done) => { + let req: any = {}; + let res: any = {}; + core.express()(req, res,(err) => { + if (err) return done(err); + chai.expect(core.connection).to.exist; + return done(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/Model.ts b/test/Model.ts new file mode 100644 index 0000000..0995fd9 --- /dev/null +++ b/test/Model.ts @@ -0,0 +1,923 @@ +/// +import * as Iridium from '../index'; +import MongoDB = require('mongodb'); +import Cursor from '../lib/Cursor'; +import Promise = require('bluebird'); +import _ = require('lodash'); + +interface TestDocument { + _id?: string; + answer: number; +} + +class Test extends Iridium.Instance implements TestDocument { + static collection = 'test'; + static schema: Iridium.Schema = { + _id: MongoDB.ObjectID, + answer: Number + }; + + _id: string; + answer: number; +} + +class TestWithCustomID extends Test { + static transforms: Iridium.Transforms = { + _id: { + fromDB: x => x * 10, + toDB: x => x / 10 + } + }; +} + +describe("Model",() => { + let core = new Iridium.Core({ database: 'test' }); + + before(() => core.connect()); + + describe("constructor", () => { + function createInstanceImplementation(properties): any { + let fn = function() { return {}; }; + _.merge(fn, properties); + return fn; + } + + it("should throw an error if you don't provide a valid core",() => { + chai.expect(() => { + new Iridium.Model(null, createInstanceImplementation({ + collection: 'test', + schema: { _id: false } + })) + }).to.throw("You failed to provide a valid Iridium core for this model"); + }); + + it("should throw an error if you don't provide a valid instanceType",() => { + chai.expect(() => { + new Iridium.Model(core, null) + }).to.throw("You failed to provide a valid instance constructor for this model"); + }); + + it("should throw an error if you don't provide a collection name",() => { + chai.expect(() => { + new Iridium.Model(core,createInstanceImplementation({ + schema: { _id: false } + })) + }).to.throw("You failed to provide a valid collection name for this model"); + + chai.expect(() => { + new Iridium.Model(core, createInstanceImplementation({ + collection: '', + schema: { _id: false } + })) + }).to.throw("You failed to provide a valid collection name for this model"); + + chai.expect(() => { + new Iridium.Model(core,createInstanceImplementation({ + collection: 4, + schema: { _id: false } + })) + }).to.throw("You failed to provide a valid collection name for this model"); + }); + + it("should throw an error if you don't provide a valid schema",() => { + chai.expect(() => { + new Iridium.Model(core, createInstanceImplementation({ + collection: 'test' + })) + }).to.throw("You failed to provide a valid schema for this model"); + + chai.expect(() => { + new Iridium.Model(core,createInstanceImplementation({ + collection: 'test', + schema: { id: false } + })) + }).to.throw("You failed to provide a valid schema for this model"); + }); + + it("should correctly set the core",() => { + chai.expect(new Iridium.Model(core, createInstanceImplementation({ + collection: 'test', + schema: { _id: false } + })).core).to.equal(core); + }); + + it("should correctly set the collectionName",() => { + chai.expect(new Iridium.Model(core, createInstanceImplementation({ + collection: 'test', + schema: { _id: false } + })).collectionName).to.equal('test'); + }); + + it("should correctly set the schema",() => { + chai.expect(new Iridium.Model(core, createInstanceImplementation({ + collection: 'test', + schema: { _id: false } + })).schema).to.eql({ _id: false }); + }); + }); + + describe("methods",() => { + let test = new Iridium.Model(core, Test); + + it("should expose create()",() => chai.expect(test.create).to.exist.and.be.a('function')); + it("should expose insert()",() => chai.expect(test.insert).to.exist.and.be.a('function')); + it("should expose remove()",() => chai.expect(test.remove).to.exist.and.be.a('function')); + it("should expose findOne()",() => chai.expect(test.findOne).to.exist.and.be.a('function')); + it("should expose get()",() => chai.expect(test.get).to.exist.and.be.a('function')); + it("should expose find()",() => chai.expect(test.find).to.exist.and.be.a('function')); + it("should expose count()",() => chai.expect(test.count).to.exist.and.be.a('function')); + it("should expose ensureIndex()",() => chai.expect(test.ensureIndex).to.exist.and.be.a('function')); + it("should expose ensureIndexes()",() => chai.expect(test.ensureIndexes).to.exist.and.be.a('function')); + it("should expose dropIndex()",() => chai.expect(test.dropIndex).to.exist.and.be.a('function')); + it("should expose dropIndexes()",() => chai.expect(test.dropIndexes).to.exist.and.be.a('function')); + }); + + describe("properties",() => { + let test = new Iridium.Model(core, Test); + + it("should expose core",() => { + chai.expect(test).to.have.property('core'); + chai.expect(test.core).to.equal(core); + }); + it("should expose collection",() => { + chai.expect(test).to.have.property('collection'); + }); + it("should expose collectionName",() => { + chai.expect(test).to.have.property('collectionName'); + chai.expect(test.collectionName).to.equal('test'); + test.collectionName = 'changed'; + chai.expect(test.collectionName).to.equal('changed'); + }); + it("should expose schema",() => chai.expect(test).to.have.property('schema')); + it("should expose helpers",() => chai.expect(test).to.have.property('helpers')); + it("should expose handlers",() => chai.expect(test).to.have.property('handlers')); + it("should expose cache",() => chai.expect(test).to.have.property('cache')); + it("should expose cacheDirector",() => chai.expect(test).to.have.property('cacheDirector')); + it("should expose transforms",() => chai.expect(test).to.have.property('transforms')); + it("should expose indexes",() => chai.expect(test).to.have.property('indexes')); + it("should expose Instance",() => chai.expect(test.Instance).to.exist.and.be.a('function')); + }); + + describe("collection",() => { + it("should throw an error if you attempt to access it before connecting to the database",() => { + let model = new Iridium.Model(new Iridium.Core('mongodb://localhost/test'), Test); + chai.expect(() => model.collection).to.throw("Iridium Core not connected to a database."); + }); + + it("should return a MongoDB DB object",() => { + chai.expect(core.connection).to.exist.and.be.an.instanceof(MongoDB.Db); + }); + }); + + describe("create()",() => { + let model = new Iridium.Model(core, Test); + + before(() => { + return core.connect() + }); + + after(() => { + return model.remove().then(() => core.close()); + }); + + it("should exist",() => { + chai.expect(model.create).to.exist.and.be.a('function'); + }); + + it("should allow the insertion of a single document",() => { + return chai.expect(model.create({ answer: 10 })).to.eventually.be.ok; + }); + + it("should return a document if a single document is inserted",() => { + return chai.expect(model.create({ answer: 10 })).to.eventually.have.property('answer', 10); + }); + + it("should allow the insertion of multiple documents",() => { + return chai.expect(model.create([ + { answer: 11 }, + { answer: 12 }, + { answer: 13 } + ])).to.eventually.exist.and.have.lengthOf(3); + }); + + it("should allow you to provide options to control the creation",() => { + return chai.expect(model.create({ answer: 14 }, { upsert: true })).to.eventually.exist; + }); + + it("should return an error if you don't meet the schema validation requirements",() => { + return chai.expect(model.create({ answer: 'wrong' })).to.eventually.be.rejected; + }); + + it("should support a callback style instead of promises",(done) => { + model.create({ answer: 15 },(err, inserted) => { + if (err) return done(err); + chai.expect(inserted).to.exist.and.have.property('answer', 15); + return done(); + }); + }); + }); + + describe("insert()",() => { + let model = new Iridium.Model(core, Test); + + before(() => { + return core.connect() + }); + + after(() => { + return model.remove().then(() => core.close()); + }); + + it("should exist",() => { + chai.expect(model.insert).to.exist.and.be.a('function'); + }); + + it("should allow the insertion of a single document",() => { + return chai.expect(model.insert({ answer: 10 })).to.eventually.be.ok; + }); + + it("should return a document if a single document is inserted",() => { + return chai.expect(model.insert({ answer: 10 })).to.eventually.have.property('answer', 10); + }); + + it("should allow the insertion of multiple documents",() => { + return chai.expect(model.insert([ + { answer: 11 }, + { answer: 12 }, + { answer: 13 } + ])).to.eventually.exist.and.have.lengthOf(3); + }); + + it("should allow you to provide options to control the creation",() => { + return chai.expect(model.insert({ answer: 14 }, { upsert: true })).to.eventually.exist; + }); + + it("should return an error if you don't meet the schema validation requirements",() => { + return chai.expect(model.insert({ answer: 'wrong' })).to.eventually.be.rejected; + }); + + it("should support a callback style instead of promises",(done) => { + model.insert({ answer: 15 },(err, inserted) => { + if (err) return done(err); + chai.expect(inserted).to.exist.and.have.property('answer', 15); + return done(); + }); + }); + }); + + describe("remove()",() => { + let model = new Iridium.Model(core, Test); + + beforeEach(() => { + return core.connect().then(() => model.remove()).then(() => model.insert([ + { answer: 10 }, + { answer: 11 }, + { answer: 12 }, + { answer: 13 }, + { answer: 14 } + ])); + }); + + after(() => { + return model.remove().then(() => core.close()); + }); + + it("should exist",() => { + chai.expect(model.remove).to.exist.and.be.a('function'); + }); + + it("should allow the removal of documents matching a query",() => { + return chai.expect(model.remove({ answer: 10 })).to.eventually.equal(1); + }); + + it("should allow just the ID to be specified",() => { + return model.get().then(instance => { + return chai.expect(model.remove(instance._id)).to.eventually.exist.and.equal(1); + }); + }); + + it("should allow the removal of all documents",() => { + return chai.expect(model.remove()).to.eventually.equal(5); + }); + + it("should support a callback style instead of promises",(done) => { + model.remove((err, removed) => { + if (err) return done(err); + chai.expect(removed).to.exist.and.equal(5); + return done(); + }); + }); + + it("should support a callback style instead of promises when conditions are specified",(done) => { + model.remove({ answer: 10 }, (err, removed) => { + if (err) return done(err); + chai.expect(removed).to.exist.and.equal(1); + return done(); + }); + }); + + it("should support a callback style instead of promises when options are specified",(done) => { + model.remove({ answer: 10 }, { w: 1 }, (err, removed) => { + if (err) return done(err); + chai.expect(removed).to.exist.and.equal(1); + return done(); + }); + }); + }); + + describe("findOne()",() => { + let model = new Iridium.Model(core, Test); + + before(() => { + return core.connect().then(() => model.remove()).then(() => model.insert([ + { answer: 10 }, + { answer: 11 }, + { answer: 12 }, + { answer: 13 }, + { answer: 14 } + ])); + }); + + after(() => { + return model.remove().then(() => core.close()); + }); + + it("should exist",() => { + chai.expect(model.findOne).to.exist.and.be.a('function'); + }); + + it("should support retrieving an random document",() => { + return chai.expect(model.findOne()).to.eventually.exist.and.have.property('answer').is.a('number'); + }); + + it("should support a query which returns nothing",() => { + return chai.expect(model.findOne({ nothing: true })).to.eventually.not.exist; + }); + + it("should support retrieving a document using its ID",() => { + return chai.expect(model.findOne().then((doc) => model.findOne(doc._id))).to.eventually.exist.and.have.property('answer').is.a('number'); + }); + + it("should retrieve the correct document by its ID",() => { + return model.findOne().then((doc) => { + return chai.expect(model.findOne(doc._id)).to.eventually.exist.and.have.property('_id', doc._id); + }); + }); + + it("should support retrieving a document using a selector query",() => { + return chai.expect(model.findOne({ answer: 10 })).to.eventually.exist.and.have.property('answer', 10); + }); + + it("should support passing options to control the query",() => { + return chai.expect(model.findOne({}, { + sort: { answer: -1 } + })).to.eventually.exist.and.have.property('answer', 14); + }); + + it("should allow you to limit the returned fields",() => { + return chai.expect(model.findOne({}, { + fields: { answer: 0 } + }).then((instance) => instance.answer)).to.eventually.be.undefined; + }); + + it("should support a callback style instead of promises",(done) => { + model.findOne((err, doc) => { + if (err) return done(err); + chai.expect(doc).to.exist.and.have.property('answer'); + return done(); + }); + }); + }); + + describe("get()",() => { + let model = new Iridium.Model(core, Test); + + before(() => { + return core.connect().then(() => model.remove()).then(() => model.insert([ + { answer: 10 }, + { answer: 11 }, + { answer: 12 }, + { answer: 13 }, + { answer: 14 } + ])); + }); + + after(() => { + return model.remove().then(() => core.close()); + }); + + it("should exist",() => { + chai.expect(model.get).to.exist.and.be.a('function'); + }); + + it("should support retrieving an random document",() => { + return chai.expect(model.get()).to.eventually.exist.and.have.property('answer').is.a('number'); + }); + + it("should support a query which returns nothing",() => { + return chai.expect(model.get({ nothing: true })).to.eventually.not.exist; + }); + + it("should support retrieving a document using its ID",() => { + return chai.expect(model.get().then((doc) => model.get(doc._id))).to.eventually.exist.and.have.property('answer').is.a('number'); + }); + + it("should retrieve the correct document by its ID",() => { + return model.get().then((doc) => { + return chai.expect(model.get(doc._id)).to.eventually.exist.and.have.property('_id', doc._id); + }); + }); + + it("should support retrieving a document using a selector query",() => { + return chai.expect(model.get({ answer: 10 })).to.eventually.exist.and.have.property('answer', 10); + }); + + it("should support passing options to control the query",() => { + return chai.expect(model.get({}, { + sort: { answer: -1 } + })).to.eventually.exist.and.have.property('answer', 14); + }); + + it("should allow you to limit the returned fields",() => { + return chai.expect(model.get({}, { + fields: { answer: 0 } + }).then((instance) => instance.answer)).to.eventually.be.undefined; + }); + + it("should support a callback style instead of promises",(done) => { + model.get((err, doc) => { + if (err) return done(err); + chai.expect(doc).to.exist.and.have.property('answer'); + return done(); + }); + }); + }); + + describe("find()",() => { + let model = new Iridium.Model(core, Test); + + before(() => { + return core.connect().then(() => model.remove()).then(() => model.insert([ + { answer: 10 }, + { answer: 11 }, + { answer: 12 }, + { answer: 13 }, + { answer: 14 } + ])); + }); + + after(() => { + return model.remove().then(() => core.close()); + }); + + it("should exist",() => { + chai.expect(model.find).to.exist.and.be.a('function'); + }); + + it("should return a cursor object",() => { + chai.expect(model.find()).to.be.an.instanceof(Cursor); + }); + + describe("each()",() => { + it("should call the handler with each document",() => { + return chai.expect(model.find().forEach((instance) => { + chai.expect(instance).to.exist; + })).to.eventually.not.be.rejected; + }); + + it("should return a promise immediately",() => { + chai.expect(model.find().forEach(i => { })).to.be.instanceof(Promise); + }); + + it("should resolve the promise after all handlers have been dispatched",() => { + let count = 0; + return chai.expect(model.find().forEach((instance) => { + count++; + }).then(() => chai.expect(count).to.not.equal(5)).then(() => Promise.delay(10)).then(() => count)).to.eventually.equal(5); + }); + + it("should be capable of functioning correctly with empty result sets",() => { + return chai.expect(model.find({ nothing: true }).forEach((instance) => { + chai.assert.fail(); + })).to.eventually.not.be.rejected; + }); + + it("should support using callbacks instead of promises",(done) => { + let count = 0; + model.find().forEach(i => count++,(err) => { + if (err) return done(err); + Promise.delay(10).then(() => chai.expect(count).to.eql(5)).then(() => done()); + }); + }); + }); + + describe("map()",() => { + it("should call the handler with documents",() => { + return chai.expect(model.find().map((instance) => { + chai.expect(instance).to.exist; + })).to.eventually.not.be.rejected; + }); + + it("should return the values from of each iteration",() => { + let count = 0; + return chai.expect(model.find().map((instance) => { + return count++; + })).to.eventually.be.eql([0, 1, 2, 3, 4]); + }); + + it("should return its result promise immediately",() => { + chai.expect(model.find().map(i => i)).to.be.instanceof(Promise); + }); + + it("should only resolve its result promise after all results have been resolved",() => { + let count = 0; + return chai.expect(model.find().map((instance) => { + return count++; + }).then(() => count)).to.eventually.equal(5); + }); + + it("should be capable of functioning correctly with empty result sets",() => { + return chai.expect(model.find({ nothing: true }).map((instance) => { + chai.assert.fail(); + })).to.eventually.eql([]); + }); + + it("should support using callbacks instead of promises",(done) => { + let count = 0; + model.find().map(i => count++,(err, results) => { + if (err) return done(err); + chai.expect(results).to.eql([0, 1, 2, 3, 4]); + return done(); + }); + }); + }); + + describe("toArray()",() => { + it("should return all documents",() => { + return chai.expect(model.find().toArray()).to.eventually.exist.and.have.length(5); + }); + + it("should be capable of functioning correctly with empty result sets",() => { + return chai.expect(model.find({ nothing: true }).toArray()).to.eventually.eql([]); + }); + + it("should support a callback style instead of promises",(done) => { + model.find().toArray((err, docs) => { + if (err) return done(err); + chai.expect(docs).to.exist.and.have.length(5); + return done(); + }); + }); + }); + + describe("next()",() => { + it("should return a promise",() => { + chai.expect(model.find().next()).to.be.an.instanceof(Promise); + }); + + it("which should resolve to the next instance in the query",() => { + return chai.expect(model.find().next()).to.eventually.be.an.instanceof(model.Instance); + }); + + it("should be capable of functioning correctly with empty result sets",() => { + return chai.expect(model.find({ nothing: true }).next()).to.eventually.not.exist; + }); + + it("should support using callbacks instead of promises",(done) => { + model.find().next((err, instance) => { + if (err) return done(err); + chai.expect(instance).to.be.an.instanceof(model.Instance); + return done(); + }); + }); + }); + + describe("rewind()",() => { + it("should return a new cursor",() => { + chai.expect(model.find().rewind()).to.be.an.instanceof(Cursor); + }); + + it("which should start returning items from the start of the query",() => { + let cursor = model.find(); + return cursor.next().then(firstItem => cursor.rewind().next().then(rewoundItem => chai.expect(firstItem.document).to.eql(rewoundItem.document))); + }); + + it("should carry through any other attributes",() => { + let cursor = model.find().sort({ answer: -1 }).limit(2); + return chai.expect(cursor.toArray().then(() => cursor.rewind().map(i => i.answer))).to.eventually.eql([14, 13]); + }); + }); + + describe("count()",() => { + it("should return a promise",() => { + chai.expect(model.find().count()).to.be.instanceof(Promise); + }); + + it("should resolve the promise with the number of documents which match the query",() => { + return chai.expect(model.find().count()).to.eventually.be.equal(5); + }); + + it("should support using callbacks instead of promises",(done) => { + model.find().count((err, count) => { + if (err) return done(err); + chai.expect(count).to.equal(5); + return done(); + }); + }); + }); + + describe("limit()",() => { + it("should return a new cursor",() => { + chai.expect(model.find().limit(1)).to.be.instanceof(Cursor); + }); + + it("which should impose the limit",() => { + return chai.expect(model.find().limit(2).toArray()).to.eventually.have.length(2); + }); + }); + + describe("skip()",() => { + it("should return a new cursor",() => { + chai.expect(model.find().skip(1)).to.be.instanceof(Cursor); + }); + + it("which should impose the limit",() => { + return chai.expect(model.find().skip(2).count()).to.eventually.be.equal(3); + }); + }); + + describe("sort()",() => { + it("should return a new cursor",() => { + chai.expect(model.find().sort({ answer: 1 })).to.be.instanceof(Cursor); + }); + + it("which should perform the sort",() => { + return chai.expect(model.find().sort({ answer: -1 }).map(i => i.answer)).to.eventually.eql([14, 13, 12, 11, 10]); + }); + }); + + describe("filtering",() => { + it("should allow filtering using a selector",() => { + return chai.expect(model.find({ answer: 10 }).toArray()).to.eventually.exist.and.have.length(1); + }); + + it("should allow an ID to be specified directly",() => { + return model.find({ answer: 10 }).next().then((instance) => chai.expect(model.find(instance._id).count()).to.eventually.equal(1)); + }); + + it("should transform the conditions",() => { + return model.get().then(instance => chai.expect(model.find({ + _id: instance._id + }).count()).to.eventually.equal(1)); + }); + + it("should allow the returned fields to be restricted",() => { + return chai.expect(model.find({}, { answer: 0 }).map(i => i.answer)).to.eventually.eql([undefined, undefined, undefined, undefined, undefined]); + }); + }); + }); + + describe("count()",() => { + let model = new Iridium.Model(core, Test); + + before(() => { + return core.connect().then(() => model.remove()).then(() => model.insert([ + { answer: 10 }, + { answer: 11 }, + { answer: 12 }, + { answer: 13 }, + { answer: 14 } + ])); + }); + + after(() => { + return model.remove().then(() => core.close()); + }); + + it("should exist",() => { + chai.expect(model.count).to.exist.and.be.a('function'); + }); + + it("should select all documents by default",() => { + return chai.expect(model.count()).to.eventually.exist.and.equal(5); + }); + + it("should allow just the ID to be specified",() => { + return model.get().then(instance => { + return chai.expect(model.count(instance._id)).to.eventually.exist.and.equal(1); + }); + }); + + it("should allow filtering using a selector",() => { + return chai.expect(model.count({ answer: 10 })).to.eventually.exist.and.equal(1); + }); + + it("should support a callback style instead of promises",(done) => { + model.count((err, docs) => { + if (err) return done(err); + chai.expect(docs).to.exist.and.equal(5); + return done(); + }); + }); + }); + + describe("update()",() => { + let model = new Iridium.Model(core, Test); + + beforeEach(() => { + return core.connect().then(() => model.remove()).then(() => model.insert([ + { answer: 10 }, + { answer: 11 }, + { answer: 12 }, + { answer: 13 }, + { answer: 14 } + ])); + }); + + after(() => { + return model.remove().then(() => core.close()); + }); + + it("should exist",() => { + chai.expect(model.update).to.exist.and.be.a('function'); + }); + + it("should use multi update by default",() => { + return chai.expect(model.update({ _id: { $exists: true } }, { $inc: { answer: 1 } })).to.eventually.equal(5); + }); + + it("should allow just the ID to be specified",() => { + return model.get().then(instance => { + return chai.expect(model.update(instance._id, { $inc: { answer: 1 } })).to.eventually.equal(1); + }); + }); + + it("should allow filtering using a selector",() => { + return chai.expect(model.update({ answer: 10 }, { $inc: { answer: 1 } })).to.eventually.equal(1); + }); + + it("should support a callback style instead of promises",(done) => { + model.update({}, { $inc: { answer: 1 } }, (err, docs) => { + if (err) return done(err); + chai.expect(docs).to.equal(5); + return done(); + }); + }); + }); + + describe("ensureIndex()",() => { + let model = new Iridium.Model(core, Test); + + beforeEach(() => { + return core.connect().then(() => model.remove()).then(() => model.insert([ + { answer: 10 }, + { answer: 11 }, + { answer: 12 }, + { answer: 13 }, + { answer: 14 } + ])); + }); + + afterEach(() => { + return model.remove().then(() => model.dropIndexes()).then(() => core.close()); + }); + + it("should exist",() => { + chai.expect(model.ensureIndex).to.exist.and.be.a('function'); + }); + + it("should allow the creation of indexes",() => { + return chai.expect(model.ensureIndex({ answer: 1 }, { unique: true })).to.eventually.exist; + }); + + it("should allow the use of callbacks instead of promises",(done) => { + model.ensureIndex({ answer: 1 },(err, index) => { + if (err) return done(err); + chai.expect(index).to.exist; + return done(); + }); + }); + }); + + describe("ensureIndexes()", () => { + let model = null; + + before(() => { + Test.indexes = [{ answer: 1 }]; + model = new Iridium.Model(core, Test); + return core.connect().then(() => model.remove()).then(() => model.insert([ + { answer: 10 }, + { answer: 11 }, + { answer: 12 }, + { answer: 13 }, + { answer: 14 } + ])); + }); + + after(() => { + Test.indexes = []; + return model.remove().then(() => model.dropIndexes()).then(() => core.close()); + }); + + it("should exist",() => { + chai.expect(model.ensureIndexes).to.exist.and.be.a('function'); + }); + + it("should configure all indexes defined in the model's options", () => { + console.log(model.indexes); + return chai.expect(model.ensureIndexes()).to.eventually.exist.and.have.length(1); + }); + }); + + describe("dropIndex()",() => { + let model = new Iridium.Model(core, Test); + + beforeEach(() => { + return core.connect().then(() => model.remove()).then(() => model.insert([ + { answer: 10 }, + { answer: 11 }, + { answer: 12 }, + { answer: 13 }, + { answer: 14 } + ])).then(() => model.ensureIndex({ answer: 1 })); + }); + + afterEach(() => { + return model.remove().then(() => model.dropIndexes()).then(() => core.close()); + }); + + it("should exist",() => { + chai.expect(model.dropIndex).to.exist.and.be.a('function'); + }); + + it("should remove the specified index",() => { + return chai.expect(model.dropIndex('answer_1')).to.eventually.be.true; + }); + + it("should remove the specified index using its definition",() => { + return chai.expect(model.dropIndex({ answer: 1 })).to.eventually.be.true; + }); + + it("should support removing a compound indexe using its definition",() => { + return chai.expect(model.ensureIndex({ _id: 1, answer: 1 }).then(() => model.dropIndex({ _id: 1, answer: 1 }))).to.eventually.be.true; + }); + + it("should allow the use of callbacks instead of promises",(done) => { + model.dropIndex({ answer: 1 },(err, index) => { + if (err) return done(err); + chai.expect(index).to.be.true; + return done(); + }); + }); + }); + + describe("dropIndexes()",() => { + let model = new Iridium.Model(core, Test); + + before(() => { + return core.connect().then(() => model.remove()).then(() => model.insert([ + { answer: 10 }, + { answer: 11 }, + { answer: 12 }, + { answer: 13 }, + { answer: 14 } + ])).then(() => model.ensureIndex({ answer: 1 })); + }); + + after(() => { + return model.remove().then(() => model.dropIndexes()).then(() => core.close()); + }); + + it("should exist",() => { + chai.expect(model.dropIndexes).to.exist.and.be.a('function'); + }); + + it("should remove all non-_id indexes on the collection",() => { + return chai.expect(model.dropIndexes()).to.eventually.be.true; + }); + }); + + describe("identifier transforms", () => { + it("should have a default converter", () => { + let model = new Iridium.Model(core, Test); + chai.expect(model.transforms).to.exist.and.have.property('_id').with.property('fromDB').which.is.a('function'); + chai.expect(model.transforms).to.exist.and.have.property('_id').with.property('toDB').which.is.a('function'); + }); + + it("should have a default ObjectID to String converter", () => { + let model = new Iridium.Model(core, Test); + chai.expect(model.transforms['_id'].fromDB(MongoDB.ObjectID.createFromHexString('aaaaaaaaaaaaaaaaaaaaaaaa'))).to.eql('aaaaaaaaaaaaaaaaaaaaaaaa'); + chai.expect(model.transforms['_id'].toDB('aaaaaaaaaaaaaaaaaaaaaaaa')).to.eql(MongoDB.ObjectID.createFromHexString('aaaaaaaaaaaaaaaaaaaaaaaa')); + }); + + it("should allow you to specify a custom converter by providing a property on the class", () => { + let model = new Iridium.Model(core, TestWithCustomID); + + chai.expect(model.transforms['_id']).to.exist.and.have.property('fromDB').which.is.a('function'); + chai.expect(model.transforms['_id']).to.exist.and.have.property('toDB').which.is.a('function'); + + chai.expect(model.transforms['_id'].fromDB(12)).to.eql(120); + chai.expect(model.transforms['_id'].toDB(120)).to.eql(12); + }); + }); +}); \ No newline at end of file diff --git a/test/Omnom.ts b/test/Omnom.ts new file mode 100644 index 0000000..f569a69 --- /dev/null +++ b/test/Omnom.ts @@ -0,0 +1,207 @@ +/// +import Omnom from '../lib/utils/Omnom'; +import MongoDB = require('mongodb'); + +describe("Omnom",() => { + it("should correctly diff basic objects",() => { + let oldObject = { + a: 1, + b: 'test', + c: 2, + d: 'constant', + e: 'old' + }; + + let newObject = { + a: 3, + b: 'tested', + c: 2, + d: 'constant', + f: 'new' + }; + + let expectedDiff = { + $set: { a: 3, b: 'tested', f: 'new' }, + $unset: { e: 1 } + }; + + chai.expect(Omnom.diff(oldObject, newObject)).to.exist.and.be.eql(expectedDiff); + }); + + it("should correctly diff basic objects with atomic number changes",() => { + let oldObject = { + a: 1, + b: 'test', + c: 2, + d: 'constant', + e: 'old' + }; + + let newObject = { + a: 3, + b: 'tested', + c: 2, + d: 'constant', + f: 'new' + }; + + let expectedDiff = { + $set: { b: 'tested', f: 'new' }, + $inc: { a: 2 }, + $unset: { e: 1 } + }; + + chai.expect(Omnom.diff(oldObject, newObject, { atomicNumbers: true })).to.exist.and.be.eql(expectedDiff); + }); + + it('should correctly diff complex objects', function () { + let oldObject = { + a: { value: 1 }, + b: { value1: 1, value2: 1 }, + c: { value: 2 }, + d: { value: {} }, + e: { value: true } + }; + + let newObject = { + a: { value: 3 }, + b: { value1: 'tested', value2: 2 }, + c: { value: 2 }, + d: { value: {} }, + e: { value2: false } + }; + + let expectedDiff = { + $set: { 'a.value': 3, 'b.value1': 'tested', 'b.value2': 2, 'e.value2': false }, + $unset: { 'e.value': 1 } + }; + + chai.expect(Omnom.diff(oldObject, newObject)).to.exist.and.be.eql(expectedDiff); + }); + + it('should correctly diff ObjectIDs', function () { + let oldID = new MongoDB.ObjectID(); + let newID = MongoDB.ObjectID.createFromHexString(oldID.toHexString()); + + let oldObject = { _id: oldID }; + let newObject = { _id: newID }; + let expectedDiff = { + + }; + + chai.expect(Omnom.diff(oldObject, newObject)).to.exist.and.be.eql(expectedDiff); + + newID = new MongoDB.ObjectID(); + + oldObject = { _id: oldID }; + newObject = { _id: newID }; + expectedDiff = { + $set: { _id: newID } + }; + + chai.expect(Omnom.diff(oldObject, newObject)).to.exist.and.be.eql(expectedDiff); + }); + + describe('arrays', function () { + it('should correctly handle two pure arrays',() => { + let oldObject = [1, 2, 3]; + let newObject = [4, 5, 6]; + let expectedDiff = { + $set: { undefined: [4, 5, 6] } + }; + + chai.expect(Omnom.diff(oldObject, newObject)).to.exist.and.be.eql(expectedDiff); + }); + + it('should correctly handle arrays which can be pulled', function () { + let oldObject = { a: [1, 2, 3, 4], b: [1, 2, 3, 4], c: [1,2,3,4,5] }; + let newObject = { a: [1, 3, 4], b: [1, 3], c: [1] }; + let expectedDiff = { + $pull: { a: 2 }, + $pullAll: { b: [2, 4], c: [2,3,4,5] } + }; + + chai.expect(Omnom.diff(oldObject, newObject)).to.exist.and.be.eql(expectedDiff); + }); + + it('should correctly handle arrays which can be pushed', function () { + let oldObject = { a: [1, 2, 3, 4], b: [1, 2, 3, 4], c: [1] }; + let newObject = { a: [1, 2, 3, 4, 5], b: [1, 2, 3, 4, 5, 6], c: [1,2,3,4,5] }; + let expectedDiff = { + $push: { a: 5, b: { $each: [5, 6] }, c: { $each: [2, 3, 4, 5] } } + }; + + chai.expect(Omnom.diff(oldObject, newObject)).to.exist.and.be.eql(expectedDiff); + }); + + it('should correctly handle arrays which should be replaced', function () { + let oldObject = { a: [1, 2], b: [1, 2, 3] }; + let newObject = { a: [5, 4, 3], b: [5, 4, 3, 2] }; + let expectedDiff = { + $set: { + a: [5, 4, 3], + b: [5, 4, 3, 2] + } + }; + + chai.expect(Omnom.diff(oldObject, newObject)).to.exist.and.be.eql(expectedDiff); + }); + + it("should correctly handle arrays which shrink in size and can't be pulled", () => { + let oldObject = { a: [1, 2, 3, 4], b: [1, 2, 3, 4] }; + let newObject = { a: [1, 3, 5], b: [1, 3] }; + let expectedDiff = { + $set: { a: [1,3,5] }, + $pullAll: { b: [2, 4] } + }; + + chai.expect(Omnom.diff(oldObject, newObject)).to.exist.and.be.eql(expectedDiff); + }); + + it("should correctly handle arrays which can be partially modified", function () { + let oldObject = { a: [1, 2, 3, 4], b: [1, 2, 3, 4] }; + let newObject = { a: [1, 2, 5, 4, 5], b: [1, 2, 5, 4, 5, 6] }; + let expectedDiff = { + $set: { + 'a.2': 5, + 'a.4': 5, + 'b.2': 5, + 'b.4': 5, + 'b.5': 6 + } + }; + + chai.expect(Omnom.diff(oldObject, newObject)).to.exist.and.be.eql(expectedDiff); + }); + + it("should correctly diff array elements as objects", function () { + let postDate = new Date(); + let oldObject = { + comments: [ + { id: 1, title: 'Title 1', text: 'test text 1', posted: postDate }, + { id: 2, title: 'Title 2', text: 'test text 2', posted: postDate }, + { id: 3, title: 'Title 3', text: 'test text 3', posted: postDate } + ] + }; + + let newDate = new Date(postDate.getTime() + 50); + let newObject = { + comments: [ + { id: 1, title: 'Title 1', text: 'tested text 1', posted: postDate }, + { id: 2, title: 'Title 2', text: 'tested text 2', posted: postDate }, + { id: 3, title: 'Title 3', text: 'test text 3', posted: newDate } + ] + }; + + let expectedDiff = { + $set: { + 'comments.0.text': 'tested text 1', + 'comments.1.text': 'tested text 2', + 'comments.2.posted': newDate + } + }; + + chai.expect(Omnom.diff(oldObject, newObject)).to.exist.and.be.eql(expectedDiff); + }); + }); +}); \ No newline at end of file diff --git a/test/Plugins.ts b/test/Plugins.ts new file mode 100644 index 0000000..7128b8f --- /dev/null +++ b/test/Plugins.ts @@ -0,0 +1,134 @@ +/// +import * as Iridium from '../index'; +import Skmatc = require('skmatc'); + +class Test extends Iridium.Instance { + static collection = 'test'; + static schema: Iridium.Schema = { + _id: false + }; + + _id: string; +} + +describe("Plugins",() => { + let core: Iridium.Core; + beforeEach(() => { + core = new Iridium.Core({ database: 'test' }); + }); + + describe("newModel",() => { + it("should allow a plugin not to define a handler",() => { + core.register({ + newInstance: (instance, model) => { }, + validate: [] + }); + }); + + it("should allow a plugin to define a handler",() => { + core.register({ + newModel: (model) => { }, + newInstance: (instance, model) => { }, + validate: [] + }); + }); + + it("should be called when a new model is created",() => { + let wasCalled = false; + core.register({ + newModel: (model) => { + wasCalled = true; + } + }); + let model = new Iridium.Model(core, Test); + chai.expect(wasCalled).to.be.true; + }); + + it("should be able to make modifications to the model",() => { + core.register({ + newModel: (model) => { + model.collectionName = 'changed'; + } + }); + + let model = new Iridium.Model(core, Test); + chai.expect(model.collectionName).to.exist.and.be.equal('changed'); + }); + }); + + describe("newInstance",() => { + it("should allow a plugin not to define a handler",() => { + core.register({ + newModel: (model) => { }, + validate: [] + }); + }); + + it("should allow a plugin to define a handler",() => { + core.register({ + newModel: (model) => { }, + newInstance: (instance, model) => { }, + validate: [] + }); + }); + + it("should be called when an instance is instantiated",() => { + let wasCalled = false; + core.register({ + newModel: (model) => { }, + newInstance: (instance, model) => { + wasCalled = true; + }, + validate: [] + }); + + let model = new Iridium.Model(core, Test); + let instance = new model.Instance({}); + chai.expect(wasCalled).to.be.true; + }); + + it("should not be called when the instance doesn't inherit from Iridium.Instance",() => { + let wasCalled = false; + core.register({ + newModel: (model) => { }, + newInstance: (instance, model) => { + wasCalled = true; + }, + validate: [] + }); + + let instanceImplementation: any = function() { return {}; }; + instanceImplementation.collection = 'test'; + instanceImplementation.schema = { + _id: false + }; + + let model = new Iridium.Model(core, instanceImplementation); + let instance = new model.Instance({}); + chai.expect(wasCalled).to.be.false; + }); + }); + + describe("validators",() => { + it("should allow a plugin to not define validators",() => { + core.register({ + newModel: (model) => { }, + newInstance: (instance, model) => { } + }); + }); + + it("should allow a plugin to define a single validator",() => { + core.register({ + newInstance: (instance, model) => { }, + validate: Skmatc.create((schema) => schema == 'Test', (schema, data) => this.assert(data == 'test')) + }); + }); + + it("should allow a plugin to define multiple validators",() => { + core.register({ + newInstance: (instance, model) => { }, + validate: [Skmatc.create((schema) => schema == 'Test',(schema, data) => this.assert(data == 'test'))] + }); + }); + }); +}); \ No newline at end of file diff --git a/test/Transforms.ts b/test/Transforms.ts new file mode 100644 index 0000000..3e0f15a --- /dev/null +++ b/test/Transforms.ts @@ -0,0 +1,34 @@ +/// +import * as Iridium from '../index'; + +interface Document { + name: string; + email: string; +} + +class Person extends Iridium.Instance { + static collection = 'test'; + static schema: Iridium.Schema = { + _id: false, + name: String, + email: String + }; + + static transforms: Iridium.Transforms = { + email: { + fromDB: x => x, + toDB: x => x.toLowerCase().trim() + } + }; + + name: string; + email: string; +} + +describe("Transforms", () => { + describe("should be applied", () => { + it("when creating a new object"); + it("when creating a new object, after onCreating"); + it("when creating a new object, before validation"); + }); +}); \ No newline at end of file diff --git a/test/Validation.ts b/test/Validation.ts new file mode 100644 index 0000000..ef13860 --- /dev/null +++ b/test/Validation.ts @@ -0,0 +1,231 @@ +/// +import * as Iridium from '../index'; +import skmatc = require('skmatc'); + +interface Document { + name: string; + dateOfBirth: Date; + siblings: { + name: string; + related: boolean; + ageDifference: number; + }[]; +} + +@Iridium.Validate('Over18', function(schema, data) { + return this.assert(data.getTime && data.getTime() < (new Date().getTime() - 365 * 86400 * 18)); +}) +class Person extends Iridium.Instance { + static collection = 'test'; + static schema: Iridium.Schema = { + _id: false, + name: String, + dateOfBirth: 'Over18', + siblings: [{ + name: String, + related: Boolean, + ageDifference: Number + }] + }; + + name: string; + dateOfBirth: Date; + siblings: { + name: string; + related: boolean; + ageDifference: number; + }[]; +} + +describe("Validation", () => { + let core = new Iridium.Core({ database: 'test' }); + let model = new Iridium.Model(core, Person); + + before(() => core.connect()); + + after(() => model.remove().then(() => core.close())); + + beforeEach(() => model.remove()); + + describe("custom validators", () => { + it("should successfully validate documents which are valid", () => { + return chai.expect(model.insert({ + name: 'John', + dateOfBirth: new Date('1993-02-14T00:00:00.000Z'), + siblings: [{ + name: 'Jane', + related: true, + ageDifference: -2 + }] + })).to.eventually.be.ok; + }); + + it("should fail to validate documents which are invalid", () => { + return chai.expect(model.insert({ + name: 'John', + dateOfBirth: new Date('2013-02-14T00:00:00.000Z'), + siblings: [{ + name: 'Jane', + related: true, + ageDifference: -2 + }] + })).to.eventually.be.ok; + }); + }); + + describe("inserting", () => { + it("should successfully validate single documents which match the schema", () => { + return chai.expect(model.insert({ + name: 'John', + dateOfBirth: new Date('1993-02-14T00:00:00.000Z'), + siblings: [{ + name: 'Jane', + related: true, + ageDifference: -2 + }] + })).to.eventually.be.ok; + }); + + it("should fail to validate single documents which do not match the schema", () => { + return chai.expect(model.insert({ + name: 'John', + dateOfBirth: 0, + siblings: [{ + name: 'Jane', + related: true, + ageDifference: -2 + }] + })).to.eventually.be.rejected; + }); + + it("should not insert a document into the database if it fails validation", () => { + return model.insert({ + name: 'John', + dateOfBirth: 0, + siblings: [{ + name: 'Jane', + related: true, + ageDifference: -2 + }] + }).catch(() => chai.expect(model.findOne({ dateOfBirth: 0 })).to.eventually.be.null); + }); + + it("should successfully validate multiple documents which match the schema", () => { + return chai.expect(model.insert([{ + name: 'Frank', + dateOfBirth: new Date('1993-02-14T00:00:00.000Z'), + siblings: [{ + name: 'Francie', + related: false, + ageDifference: -2 + }] + }, { + name: 'Jack', + dateOfBirth: new Date('1993-02-14T00:00:00.000Z'), + siblings: [{ + name: 'Jill', + related: true, + ageDifference: 2 + }] + }])).to.eventually.be.ok; + }); + + it("should fail to validate multiple documents which do not match the schema", () => { + return chai.expect(model.insert([{ + name: 'Frank', + dateOfBirth: new Date('1993-02-14T00:00:00.000Z'), + siblings: [{ + name: 'Francie', + related: 'related', + ageDifference: -2 + }] + }, { + name: 5, + dateOfBirth: new Date(), + siblings: [{ + name: 'Jill', + related: true, + ageDifference: 2 + }] + }])).to.eventually.be.rejected; + }); + + it("should fail to validate multiple documents where some do not match the schema", () => { + return chai.expect(model.insert([{ + name: 'Frank', + dateOfBirth: new Date(), + siblings: [{ + name: 'Francie', + related: 'related', + ageDifference: -2 + }] + }, { + name: 'Jack', + dateOfBirth: new Date('1993-02-14T00:00:00.000Z'), + siblings: [{ + name: 'Jill', + related: true, + ageDifference: 2 + }] + }])).to.eventually.be.rejected; + }); + + it("should fail to validate multiple documents where some do not match the schema", () => { + return model.insert([{ + name: 'Frank', + dateOfBirth: new Date(), + siblings: [{ + name: 'Francie', + related: 'related', + ageDifference: -2 + }] + }, { + name: 'Jack', + dateOfBirth: new Date('1993-02-14T00:00:00.000Z'), + siblings: [{ + name: 'Jill', + related: true, + ageDifference: 2 + }] + }]).catch(() => chai.expect(model.findOne({ 'siblings.related': 'related' })).to.eventually.be.null); + }); + }); + + describe("instances", () => { + beforeEach(() => model.remove().then(() => model.insert({ + name: 'Frank', + dateOfBirth: new Date('1993-02-14T00:00:00.000Z'), + siblings: [] + }))); + + it("should validate documents when you attempt to change them", () => { + return chai.expect(model.get({ name: 'Frank' }).then((frank) => { + frank.siblings.push({ name: 'Francette', related: true, ageDifference: 0 }); + return frank.save(); + })).to.eventually.be.ok; + }); + + it("should fail validation if the document does not match the schema", () => { + return chai.expect(model.get({ name: 'Frank' }).then((frank) => { + frank.siblings.push({ name: 'Francette', related: 'related', ageDifference: 0 }); + return frank.save(); + })).to.eventually.be.rejected; + }); + + it("should not change the document in the database if validation fails", () => { + return chai.expect(model.get({ name: 'Frank' }).then((frank) => { + frank.siblings.push({ name: 'Francette', related: 'related', ageDifference: 0 }); + return frank.save(); + }).catch(() => model.get({ name: 'Frank', 'siblings.related': 'related' }))).to.eventually.be.null; + }); + + it("should not reverse the changes made to the instance if validation fails", () => { + let staticFrank: Person; + return chai.expect(model.get({ name: 'Frank' }).then((frank) => { + staticFrank = frank; + frank.siblings.push({ name: 'Francette', related: 'related', ageDifference: 0 }); + return frank.save(); + }).catch(() => chai.expect(staticFrank).to.have.property('siblings').with.length(1))).to.eventually.be.ok; + }); + }); +}); \ No newline at end of file diff --git a/test/bugs.js b/test/bugs.js deleted file mode 100644 index 6940d87..0000000 --- a/test/bugs.js +++ /dev/null @@ -1,30 +0,0 @@ -describe('bugs', function () { - "use strict"; - var db = new Database(config); - - before(function () { - return db.connect(); - }); - - after(function() { - return db.disconnect(); - }); - - it("shouldn't attempt to change an ObjectID when saving an instance", function() { - var model = new Model(db, 'model', { - id: false, - data: [String] - }, { - - }); - - return model.remove().then(function() { - return model.create({ data: ['testing'] }); - }).then(function(instance) { - instance.data.push('tested'); - return instance.save(); - }).then(function(instance) { - instance.data.should.be.like(['testing', 'tested']); - }); - }); -}); \ No newline at end of file diff --git a/test/cache.js b/test/cache.js deleted file mode 100644 index 012c068..0000000 --- a/test/cache.js +++ /dev/null @@ -1,82 +0,0 @@ -var EventEmitter = require('events').EventEmitter; - -function EventEmitterCache() { - this.cache = {}; -} - -EventEmitterCache.prototype.__proto__ = EventEmitter.prototype; -EventEmitterCache.prototype.valid = function(conditions) { - return conditions && conditions._id; -}; -EventEmitterCache.prototype.store = function(conditions, document, callback) { - this.emit('store'); - var id = JSON.stringify(document._id); - this.cache[id] = document; - callback(); -}; -EventEmitterCache.prototype.fetch = function(document, callback) { - var id = JSON.stringify(document._id); - if(this.cache[id]) this.emit('fetched'); - callback(this.cache[id]); -}; -EventEmitterCache.prototype.drop = function(document, callback) { - var id = JSON.stringify(document._id); - if(this.cache[id]) { - delete this.cache[id]; - this.emit('dropped'); - } - callback(); -}; - -describe('orm', function () { - "use strict"; - - describe('Model', function () { - var db = new Database(config); - - before(function () { - return db.connect(); - }); - - describe('cache', function() { - var model = null; - - before(function() { - model = new Model(db, 'model', { - name: /.+/ - }, { - preprocessors: [new Concoction.Rename({ _id: 'name' })], - cache: new EventEmitterCache() - }); - - return model.remove().then(function() { - return model.create({ name: 'Demo1' }); - }); - }); - - describe('findOne', function() { - it('should store newly retrieved documents in the cache', function() { - var stored = false; - - model.cache.once('store', function() { stored = true; }); - - return model.findOne('Demo1').then(function(instance) { - should.exist(instance); - stored.should.be.true; - }); - }); - - it('should fetch retrieved documents from the cache', function() { - var fetched = false; - - model.cache.once('fetched', function() { fetched = true; }); - - return model.findOne('Demo1').then(function(instance) { - should.exist(instance); - fetched.should.be.true; - }); - }); - }); - }); - }); -}); diff --git a/test/callbacks.js b/test/callbacks.js deleted file mode 100644 index 261894c..0000000 --- a/test/callbacks.js +++ /dev/null @@ -1,74 +0,0 @@ -describe('orm', function () { - "use strict"; - - describe('Callbacks', function () { - var db = new Database(config); - - before(function (done) { - db.connect(done); - }); - - after(function() { - db.disconnect(); - }); - - describe('database', function() { - var model = null; - - before(function(done) { - model = new Model(db, 'model', { - name: /.+/ - }, { - preprocessors: [new Concoction.Rename({ _id: 'name' })] - }); - - model.remove(function(err) { - if(err) return done(err); - model.create({ name: 'Demo1' }, function(err) { - if(err) return done(err); - model.create({ name: 'Demo2' }, done); - }); - }); - }); - - describe('find', function() { - it('should be able to get all objects', function(done) { - return model.find(function(err, objs) { - if(err) return done(err); - should.exist(objs); - objs.length.should.equal(2); - return done(); - }); - }); - - it('should not throw an error if it couldn\'t find an object', function(done) { - return model.find({ name: 'NotFound' }, function(err, objs) { - if(err) return done(err); - should.exist(objs); - objs.length.should.equal(0); - return done(); - }); - }); - }); - - describe('findOne', function() { - it('should be able to get a single object', function(done) { - return model.findOne(function(err, obj) { - if(err) return done(err); - should.exist(obj); - obj.name.should.eql('Demo1'); - return done(); - }); - }); - - it('should not throw an error if it couldn\'t find an object', function(done) { - return model.findOne({ name: 'NotFound' }, function(err, obj) { - if(err) return done(err); - should.not.exist(obj); - return done(); - }); - }); - }); - }); - }); -}); diff --git a/test/diff.js b/test/diff.js deleted file mode 100644 index a848a32..0000000 --- a/test/diff.js +++ /dev/null @@ -1,155 +0,0 @@ -var diff = require('../lib/utils/diff'), - ObjectID = require('mongodb').ObjectID; - -describe('diff', function() { - it('should correctly diff basic objects', function() { - var o1 = { - a: 1, - b: 'test', - c: 2, - d: 'constant', - e: 'old' - }; - - var o2 = { - a: 3, - b: 'tested', - c: 2, - d: 'constant', - f: 'new' - }; - - var expected = { - $set: { a: 3, b: 'tested', f: 'new' }, - $unset: { e: 1 } - }; - - diff(o1, o2).should.eql(expected); - }); - - it('should correctly diff complex objects', function() { - var o1 = { - a: { value: 1 }, - b: { value1: 1, value2: 1 }, - c: { value: 2 }, - d: { value: {} }, - e: { value: true } - }; - - var o2 = { - a: { value: 3 }, - b: { value1: 'tested', value2: 2 }, - c: { value: 2 }, - d: { value: {} }, - e: { value2: false } - }; - - var expected = { - $set: { 'a.value': 3, 'b.value1': 'tested', 'b.value2': 2, 'e.value2': false }, - $unset: { 'e.value': 1 } - }; - - diff(o1, o2).should.eql(expected); - }); - - it('should correctly diff ObjectIDs', function() { - var o1 = new ObjectID(); - var o2 = ObjectID.createFromHexString(o1.toHexString()); - - var a1 = { _id: o1 }; - var a2 = { _id: o2 }; - var expected = { - - }; - - diff(a1, a2).should.eql(expected); - - o2 = new ObjectID(); - - var a1 = { _id: o1 }; - var a2 = { _id: o2 }; - var expected = { - $set: { _id: o2 } - }; - - diff(a1, a2).should.eql(expected); - }); - - describe('arrays', function() { - it('should correctly handle arrays which can be pulled', function() { - var a1 = { a: [1,2,3,4], b: [1,2,3,4] }; - var a2 = { a: [1,3,4], b: [1,3] }; - var expected = { - $pull: { a: 2 }, - $pullAll: { b: [2,4] } - }; - - diff(a1, a2).should.eql(expected); - }); - - it('should correctly handle arrays which can be pushed', function() { - var a1 = { a: [1,2,3,4], b: [1,2,3,4] }; - var a2 = { a: [1,2,3,4,5], b: [1,2,3,4,5,6] }; - var expected = { - $push: { a: 5, b: { $each: [5,6] }} - }; - - diff(a1, a2).should.eql(expected); - }); - - it('should correctly handle arrays which should be replaced', function() { - var a1 = { a: [1,2], b: [1,2,3] }; - var a2 = { a: [5,4,3], b: [5,4,3,2] }; - var expected = { - $set: { - a: [5,4,3], - b: [5,4,3,2] - } - }; - - diff(a1, a2).should.eql(expected); - }); - - it("should correctly handle arrays which can be partially modified", function() { - var a1 = { a: [1,2,3,4], b: [1,2,3,4] }; - var a2 = { a: [1,2,5,4,5], b: [1,2,5,4,5,6] }; - var expected = { - $set: { - 'a.2': 5, - 'a.4': 5, - 'b.2': 5, - 'b.4': 5, - 'b.5': 6 - } - }; - - diff(a1, a2).should.eql(expected); - }); - - it("should correctly diff array elements as objects", function() { - var postDate = new Date(); - var a1 = { comments: [ - { id: 1, title: 'Title 1', text: 'test text 1', posted: postDate }, - { id: 2, title: 'Title 2', text: 'test text 2', posted: postDate }, - { id: 3, title: 'Title 3', text: 'test text 3', posted: postDate } - ]}; - - var newDate = new Date(postDate.getTime() + 50); - var a2 = { comments: [ - { id: 1, title: 'Title 1', text: 'tested text 1', posted: postDate }, - { id: 2, title: 'Title 2', text: 'tested text 2', posted: postDate }, - { id: 3, title: 'Title 3', text: 'test text 3', posted: newDate } - ]}; - - var expected = { - $set: { - 'comments.0.text': 'tested text 1', - 'comments.1.text': 'tested text 2', - 'comments.2.posted': newDate - } - }; - - diff(a1, a2).should.eql(expected); - }); - }); -}); \ No newline at end of file diff --git a/test/find.js b/test/find.js deleted file mode 100644 index 209c3b3..0000000 --- a/test/find.js +++ /dev/null @@ -1,166 +0,0 @@ -describe('orm', function () { - "use strict"; - - describe('Model', function () { - var db = new Database(config); - - before(function () { - return db.connect(); - }); - - after(function() { - db.disconnect(); - }); - - describe('database', function() { - var model = null; - - before(function() { - model = new Model(db, 'model', { - name: /.+/ - }, { - preprocessors: [new Concoction.Rename({ _id: 'name' })] - }); - - return model.remove().then(function() { - return model.create({ name: 'Demo1' }); - }).then(function() { - return model.create({ name: 'Demo2' }); - }); - }); - - describe('find', function() { - it('should be able to get all objects', function() { - return model.find().then(function(objs) { - should.exist(objs); - objs.length.should.equal(2); - }); - }); - - it('should correctly wrap individual objects', function() { - return model.find().then(function(objs) { - should.exist(objs); - objs.length.should.equal(2); - objs[0].should.have.property('document'); - objs[1].should.have.property('document'); - }); - }); - - it('should not throw an error if it couldn\'t find an object', function() { - return model.find({ name: 'NotFound' }).then(function(objs) { - should.exist(objs); - objs.length.should.equal(0); - }); - }); - - describe('with default model', function() { - before(function() { - model = new Model(db, 'model', { - name: /.+/ - }, { - - }); - - return model.remove().then(function() { - return model.create({ - name: 'Demo1' - }); - }).then(function() { - return model.create({ - name: 'Demo2' - }); - }); - }); - - it('should correctly be able to find using conditions', function() { - return model.find({ name: 'Demo1' }).then(function(objs) { - should.exist(objs); - objs[0].should.have.property('name').and.eql('Demo1'); - }); - }); - }); - - describe('with wrap:false', function() { - before(function() { - model = new Model(db, 'model', { - name: /.+/ - }, { - - }); - - return model.remove().then(function() { - return model.create({ - name: 'Demo1' - }); - }).then(function() { - return model.create({ - name: 'Demo2' - }); - }); - }); - - it('should return the raw document', function() { - return model.find({ name: 'Demo1' }, { wrap: false }).then(function(docs) { - should.exist(docs); - docs.length.should.equal(1); - docs[0].should.have.ownProperty('id'); - docs[0].should.have.ownProperty('name'); - docs[0].name.should.eql('Demo1'); - }); - }); - }); - - describe('with partial results', function() { - before(function() { - model = new Model(db, 'model', { - name: /.+/, - description: String - }, { - - }); - - return model.remove().then(function() { - return model.create({ - name: 'Demo1', - description: 'Demo 1' - }); - }).then(function() { - return model.create({ - name: 'Demo2', - description: 'Demo 2' - }); - }); - }); - - it('should return just the selected fields', function() { - return model.find({ }, { fields: { id: 1, name: 1 }}).then(function(docs) { - should.exist(docs); - docs.length.should.equal(2); - var doc = docs[0]; - doc.document.should.have.ownProperty('name'); - doc.document.should.not.have.ownProperty('description'); - }); - }); - - it('should set the isPartial flag on its instances', function() { - return model.find({ name: 'Demo1' }, { fields: { id: 1, name: 1 }}).then(function(docs) { - should.exist(docs); - docs.length.should.equal(1); - docs[0].__state.isPartial.should.be.true; - }); - }); - - it('should work with wrap: false', function() { - return model.find({ name: 'Demo1' }, { wrap: false, fields: { id: 1, name: 1 }}).then(function(docs) { - should.exist(docs); - docs.length.should.equal(1); - docs[0].should.have.ownProperty('name'); - docs[0].name.should.eql('Demo1'); - docs[0].should.not.have.ownProperty('description'); - }); - }); - }); - }); - }); - }); -}); diff --git a/test/findOne.js b/test/findOne.js deleted file mode 100644 index c7b509e..0000000 --- a/test/findOne.js +++ /dev/null @@ -1,167 +0,0 @@ -describe('orm', function () { - "use strict"; - - describe('Model', function () { - var db = new Database(config); - - before(function () { - return db.connect(); - }); - - after(function() { - db.disconnect(); - }); - - describe('database', function() { - var model = null; - - before(function() { - model = new Model(db, 'model', { - name: /.+/ - }, { - preprocessors: [new Concoction.Rename({ _id: 'name' })] - }); - - return model.remove().then(function() { - return model.create({ name: 'Demo1' }); - }); - }); - - describe('findOne', function() { - it('should be able to locate a single object', function() { - return model.findOne({ name: 'Demo1' }).then(function(obj) { - should.exist(obj); - obj.should.have.property('name', 'Demo1'); - }); - }); - - it('should be able to infer the _id field', function() { - return model.findOne('Demo1').then(function(obj) { - should.exist(obj); - obj.should.have.property('name', 'Demo1'); - }); - }); - - it('should not throw an error if it couldn\'t find an object', function() { - return model.findOne({ name: 'NotFound' }).then(function(obj) { - should.not.exist(obj); - }); - }); - - describe('promises', function() { - it('should work if a value is found', function() { - return model.findOne('Demo1').then(function(result) { - should.exist(result); - }); - }); - - it('should work if a value is not found', function() { - return model.findOne('NotFound').then(function(result) { - should.not.exist(result); - }); - }); - }); - - describe('with default model', function() { - before(function() { - model = new Model(db, 'model', { - name: /.+/ - }, { - - }); - - return model.remove().then(function() { - return model.create({ - name: 'Demo1' - }); - }); - }); - - var d1instance = null; - - it('should correctly be able to find using conditions', function() { - return model.findOne({ name: 'Demo1' }).then(function(obj) { - should.exist(obj); - obj.should.have.property('name', 'Demo1'); - d1instance = obj; - }); - }); - - it('should correctly infer the _id field and convert conditions', function() { - return model.findOne(d1instance.id).then(function(obj) { - should.exist(obj); - obj.should.have.property('id'); - obj.id.should.be.a('string'); - obj.should.have.property('name', 'Demo1'); - }); - }); - }); - - describe('with wrap:false', function() { - before(function() { - model = new Model(db, 'model', { - name: /.+/ - }, { - - }); - - return model.remove().then(function() { - return model.create({ - name: 'Demo1' - }); - }); - }); - - it('should return the raw document', function() { - return model.findOne({ name: 'Demo1' }, { wrap: false }).then(function(doc) { - should.exist(doc); - doc.should.have.ownProperty('id'); - doc.should.have.ownProperty('name', 'Demo1'); - }); - }); - }); - - describe('with partial results', function() { - before(function() { - model = new Model(db, 'model', { - name: /.+/, - description: String - }, { - - }); - - return model.remove().then(function() { - return model.create({ - name: 'Demo1', - description: 'Demonstration 1' - }); - }); - }); - - it('should return just the selected fields', function() { - return model.findOne({ name: 'Demo1' }, { fields: { id: 1, name: 1 }}).then(function(doc) { - should.exist(doc); - doc.document.should.have.ownProperty('name', 'Demo1'); - doc.document.should.not.have.ownProperty('description'); - }); - }); - - it('should set the isPartial flag on its instances', function() { - return model.findOne({ name: 'Demo1' }, { fields: { id: 1, name: 1 }}).then(function(doc) { - should.exist(doc); - doc.__state.isPartial.should.be.true; - }); - }); - - it('should work with wrap: false', function() { - return model.findOne({ name: 'Demo1' }, { wrap: false, fields: { id: 1, name: 1 }}).then(function(doc) { - should.exist(doc); - doc.should.have.ownProperty('name', 'Demo1'); - doc.should.not.have.ownProperty('description'); - }); - }); - }); - }); - }); - }); -}); diff --git a/test/hooks.js b/test/hooks.js deleted file mode 100644 index 620f89e..0000000 --- a/test/hooks.js +++ /dev/null @@ -1,158 +0,0 @@ -describe('hooks', function() { - var db = new Database(config); - - before(function() { - return db.connect(); - }); - - after(function() { - db.disconnect(); - }); - - describe('creating', function() { - function createModel(hook) { - return new Model(db, 'hooks', { - data: String, - created: Date - }, { - hooks: { - creating: hook - } - }); - } - - it('should correctly call the synchronous overload', function() { - var hookCalled = false; - var hookTarget = null; - - var model = createModel(function() { - this.created = new Date(); - hookCalled = true; - this.should.eql(hookTarget); - }); - - hookTarget = { - data: 'Testing' - }; - - return model.create(hookTarget).then(function(created) { - should.exist(created.created); - hookCalled.should.be.true; - }); - }); - - it('should not support an asynchronous overload', function() { - var hookCalled = false; - - var model = createModel(function(done) { - should.fail('asynchronous overload should not be supported'); - }); - - return model.insert({ data: 'Testing' }).then(function(created) { - should.fail('asynchronous overload should not be supported'); - }, function(err) { - return Promise.resolve(); - }); - }); - - it('should convey errors in the synchronous overload', function() { - var model = createModel(function() { - throw new Error('Should fail'); - }); - - return model.insert({ data: 'Testing' }).then(function(inserted) { - should.fail(); - }, function(err) { - err.message.should.eql('Should fail'); - return Promise.resolve(); - }); - }); - }); - - describe('retrieved', function() { - function createModel(hook) { - return new Model(db, 'hooks', { - data: String - }, { - hooks: { - retrieved: hook - } - }); - } - - it('should correctly call before wrapping', function() { - var expected = { data: 'Demo' }; - var hookCalled = false; - var model = createModel(function() { - this.should.eql(expected); - hookCalled = true; - }); - - return model.create(expected).then(function(inserted) { - should.exist(inserted); - hookCalled.should.be.true; - }); - }); - - it('should allow preprocessing of properties', function() { - var expected = { data: 'Demo' }; - var hookCalled = false; - var model = createModel(function() { - this.should.eql(expected); - this.lastAccess = new Date(); - hookCalled = true; - }); - - return model.create(expected).then(function(inserted) { - should.exist(inserted); - hookCalled.should.be.true; - should.exist(inserted.lastAccess); - }); - }); - }); - - describe('ready', function() { - function createModel(hook) { - return new Model(db, 'hooks', { - data: String - }, { - hooks: { - ready: hook - } - }); - } - - it('should correctly call after wrapping', function() { - var expected = { data: 'Demo' }; - var hookCalled = false; - var model = createModel(function() { - should.exist(this.document); - this.document.should.eql(expected); - hookCalled = true; - }); - - return model.create(expected).then(function(inserted) { - should.exist(inserted); - hookCalled.should.be.true; - }); - }); - - it('should allow postprocessing of Instance', function() { - var expected = { data: 'Demo' }; - var hookCalled = false; - var model = createModel(function() { - should.exist(this.document); - this.document.should.eql(expected); - this.lastAccess = new Date(); - hookCalled = true; - }); - - return model.create(expected).then(function(inserted) { - should.exist(inserted); - inserted.document.should.eql(expected); - hookCalled.should.be.true; - should.exist(inserted.lastAccess); - }); - }); - }); -}); \ No newline at end of file diff --git a/test/insertion.js b/test/insertion.js deleted file mode 100644 index ea8471a..0000000 --- a/test/insertion.js +++ /dev/null @@ -1,101 +0,0 @@ -describe('orm', function () { - "use strict"; - - describe('Model', function () { - var db = new Database(config); - - before(function () { - return db.connect(); - }); - - after(function() { - db.disconnect(); - }); - - describe('database', function() { - var model = null; - - before(function() { - model = new Model(db, 'model', { - name: /.+/ - }, { - preprocessors: [new Concoction.Rename({ _id: 'name' })] - }); - - return model.remove(); - }); - - describe('insertion', function() { - - it('should allow a single object to be inserted', function() { - return model.create({ - name: 'Demo1' - }).then(function(instance) { - instance.should.have.property('name', 'Demo1'); - }); - }); - - it('should have created the instance in the database', function() { - return model.count({ name: 'Demo1' }).then(function(number) { - number.should.eql(1); - }); - }); - - it('should allow multiple objects to be inserted', function() { - return model.create([{ - name: 'Demo2' - }, { - name: 'Demo3' - }]).then(function(instances) { - Array.isArray(instances).should.be.true; - instances[0].should.have.property('name', 'Demo2'); - instances[1].should.have.property('name', 'Demo3'); - }); - }); - - it('should pass along DB errors', function() { - return model.create({ - name: 'Demo1' - }).then(function(inserted) { - should.not.exist(inserted); - }, function(err) { - return Promise.resolve(); - }); - }); - - it('should support upsert operations on new documents', function() { - return model.create({ - name: 'Demo4', - upserted: true - }, { upsert: true }).then(function(updated) { - should.exist(updated); - updated.should.have.property('upserted').and.equal(true); - }); - }); - - it('should support upsert operations on existing documents', function() { - return model.create({ - name: 'Demo1', - upserted: true - }, { upsert: true }).then(function(updated) { - should.exist(updated); - updated.should.have.property('upserted').and.equal(true); - }); - }); - - it('should support upsert operations with multiple documents', function() { - return model.create([{ - name: 'Demo1', - upserted: true - },{ - name: 'Demo5', - upserted: true - }], { upsert: true }).then(function(updated) { - should.exist(updated); - updated.length.should.equal(2); - }); - }); - }); - }); - }); -}); diff --git a/test/instance_db.js b/test/instance_db.js deleted file mode 100644 index b42dabf..0000000 --- a/test/instance_db.js +++ /dev/null @@ -1,144 +0,0 @@ -describe('orm', function () { - "use strict"; - - describe('Instance', function () { - var db = new Database(config); - - var model = new Model(db, 'instances', { - username: String, - age: Number, - sessions: [{ - id: String, - expires: Date - }] - }, { - preprocessors: [ - new Concoction.Rename({ '_id': 'username' }) - ] - }); - - before(function () { - return db.connect().then(function() { - return model.remove(); - }); - }); - - after(function() { - db.disconnect(); - }); - - describe('database', function () { - describe('save', function() { - it('should correctly store changes made to the instance', function() { - return model.create({ - username: 'billy', - age: 10, - sessions: [{ - id: 'aaaa', - expires: new Date() - }] - }).then(function(original) { - original.age = 12; - - original.sessions.push({ - id: 'bbbb', - expires: new Date() - }); - - return original.save().then(function(updated) { - updated.should.equal(original); - updated.age.should.eql(12); - updated.sessions.length.toString().should.eql('2'); - }); - }); - }); - - it('should allow passing of specific change-sets', function() { - return model.create({ - username: 'bob', - age: 12, - sessions: [{ - id: 'aaaa', - expires: new Date() - }] - }).then(function(instance) { - return instance.save({ $set: { sessions: [] }, $inc: { age: 1 }}); - }).then(function(instance) { - instance.age.should.eql(13); - instance.sessions.should.eql([]); - }); - }); - - it('should allow conditions and changesets to be used', function() { - return model.create({ - username: 'sally', - age: 15, - sessions: [{ - id: 'aaaa', - expires: new Date() - }] - }).then(function(instance) { - return instance.save({ age: 14 }, { $inc: { age: 1 }}); - }).then(function(instance) { - instance.age.should.eql(15); - return instance.save({ age: 15 }, { $inc: { age: 1 }}); - }).then(function(instance) { - instance.age.should.eql(16); - }); - }); - }); - - describe('update', function() { - it('should retrieve the latest version of the model from the database', function() { - var billy1, billy2; - return model.findOne('billy').then(function(instance) { - billy1 = instance; - return model.findOne('billy'); - }).then(function(instance) { - billy2 = instance; - billy1.age++; - return billy1.save(); - }).then(function() { - billy2.age.should.equal(billy1.age - 1); - return billy2.update(); - }).then(function() { - billy2.age.should.equal(billy1.age); - }); - }); - }); - - describe('remove', function() { - it('should remove instances from the database', function() { - return model.get('billy').then(function(instance) { - should.exist(instance); - return instance.remove(); - }).then(function(instance) { - instance.__state.isNew.should.be.true; - return model.get('billy'); - }).then(function(instance) { - should.not.exist(instance); - }); - }); - - it('should set instance isNew to true after removal', function() { - return model.create({ - username: 'billy', - age: 10, - sessions: [{ - id: 'aaaa', - expires: new Date() - }] - }).then(function(original) { - should.exist(original); - - original.__state.isNew.should.be.false; - - return original.remove(); - }).then(function(removed) { - removed.__state.isNew.should.be.true; - }); - }); - }); - }); - }); -}); \ No newline at end of file diff --git a/test/instance_helpers.js b/test/instance_helpers.js deleted file mode 100644 index 7f41d8f..0000000 --- a/test/instance_helpers.js +++ /dev/null @@ -1,115 +0,0 @@ -describe('orm', function () { - "use strict"; - - describe('Instance', function () { - var db = { - plugins: [] - }; - - describe('helpers', function () { - - describe('document', function() { - it('should return the instance backing document', function() { - var model = new Model(db, 'model', {}, { - preprocessors: [] - }); - - var i = new model.Instance({ - id: 'custom_id', - name: 'name' - }); - - i.document.should.eql(i.__state.modified); - }); - }); - - describe('select', function() { - it('should filter arrays by the selection function', function () { - var model = new Model(db, 'model', {}, { - preprocessors: [] - }); - - var i = new model.Instance({ - id: 'custom_id', - name: 'name', - sessions: [ - 'aaaa', - 'bbbb', - 'cccc', - 'dddd', - 'eeee' - ] - }); - - i.select(i.sessions, function(value) { - return value == 'aaaa'; - }).should.eql(['aaaa']); - }); - - it('should correctly respond to preprocessing changes', function() { - var model = new Model(db, 'model', {}, { - preprocessors: [] - }); - - var i = new model.Instance({ - id: 'custom_id', - name: 'name', - sessions: { - 'aaaa': { expires: new Date() }, - 'bbbb': { expires: new Date() }, - 'cccc': { expires: new Date() } - } - }); - - i.select(i.sessions, function(value, key) { - return key == 'aaaa'; - }).should.eql({ 'aaaa': i.sessions.aaaa }); - }); - }); - - describe('first', function() { - it('should filter arrays by the selection function', function () { - var model = new Model(db, 'model', {}, { - preprocessors: [] - }); - - var i = new model.Instance({ - id: 'custom_id', - name: 'name', - sessions: [ - 'aaaa', - 'bbbb', - 'cccc', - 'dddd', - 'eeee' - ] - }); - - i.first(i.sessions, function(value) { - return value == 'aaaa'; - }).should.eql('aaaa'); - }); - - it('should correctly respond to preprocessing changes', function() { - var model = new Model(db, 'model', {}, { - preprocessors: [] - }); - - var i = new model.Instance({ - id: 'custom_id', - name: 'name', - sessions: { - 'aaaa': { expires: new Date() }, - 'bbbb': { expires: new Date() }, - 'cccc': { expires: new Date() } - } - }); - - i.first(i.sessions, function(value, key) { - return key == 'aaaa'; - }).should.eql(i.sessions.aaaa); - }); - }); - }); - }); -}); diff --git a/test/instance_setup.js b/test/instance_setup.js deleted file mode 100644 index b18caa7..0000000 --- a/test/instance_setup.js +++ /dev/null @@ -1,167 +0,0 @@ -describe('orm', function () { - "use strict"; - - describe('Instance', function () { - var db = { - plugins: [] - }; - - describe('setup', function () { - - describe('document', function() { - it('should present all properties of the received document', function () { - var model = new Model(db, 'model', { - }, { - preprocessors: [] - }) - - var i = new model.Instance({ - id: 'custom_id', - name: 'name' - }); - - i.should.have.property('id', 'custom_id'); - i.should.have.property('name', 'name'); - }); - - it('should correctly respond to preprocessing changes', function() { - var model = new Model(db, 'model', { - pretty: String - },{ - preprocessors: [ - new Concoction.Rename({ - uglyName: 'pretty' - }) - ] - }); - - var i = new model.Instance({ - _id: 'custom_id', - uglyName: 'value' - }); - - i.should.have.property('pretty', 'value'); - }); - }); - - describe('schema', function() { - it("should add all root level schema nodes as getter/setters to the model's instance prototype", function() { - var model = new Model(db, 'model', { - name: String - }); - - model.Instance.prototype.hasOwnProperty('name').should.be.true; - }); - - it('should allow a custom schema', function () { - var model = new Model(db, 'model', { - name: String, - age: { type: Number, required: false } - }, { - preprocessors: [] - }); - - var i = new model.Instance({ - id: 'custom_id', - name: 'name' - }); - - i.should.have.property('id', 'custom_id'); - i.should.have.property('name', 'name'); - i.should.have.property('age').and.eql(null); - }); - - }); - - describe('methods', function() { - it("should be added to the instance's prototype", function() { - var model = new Model(db, 'model', {}, { - methods: { - testMethod: function() { } - } - }); - - model.Instance.prototype.should.respondTo('testMethod'); - }); - - it('should receive all passed arguments', function() { - var model = new Model(db, 'model', {}, { - methods: { - testMethod: function(a,b,c,d) { - a.should.eql('a'); - b.should.eql('b'); - c.should.eql('c'); - d.should.eql('d'); - } - } - }); - - var instance = new model.Instance({}); - instance.testMethod('a','b','c','d'); - }); - - it("should have `this` set to the instance they're called on", function() { - var instance = null; - var model = new Model(db, 'model', {}, { - methods: { - testMethod: function() { - this.should.equal(instance); - } - } - }); - - instance = new model.Instance({}); - instance.testMethod(); - }); - }); - - describe('virtuals', function() { - it('should create basic getters', function() { - var model = new Model(db, 'model', {}, { - virtuals: { - fullname: function () { return this.firstname + ' ' + this.lastname; } - } - }); - - model.Instance.prototype.should.have.ownProperty('fullname'); - - var i = new model.Instance({ - _id: 'custom_id', - firstname: 'Billy', - lastname: 'Bob' - }); - - i.fullname.should.equal('Billy Bob'); - }); - - it('should create getter + setters', function() { - var model = new Model(db, 'model', {}, { - virtuals: { - fullname: { - get: function () { return this.firstname + ' ' + this.lastname; }, - set: function(value) { - this.firstname = value.split(' ')[0]; - this.lastname = value.split(' ')[1]; - } - } - } - }); - - model.Instance.prototype.should.have.ownProperty('fullname'); - - var i = new model.Instance({ - _id: 'custom_id', - firstname: 'Billy', - lastname: 'Bob' - }); - - i.fullname.should.equal('Billy Bob'); - - i.fullname = 'Sally Jane'; - i.firstname.should.equal('Sally'); - i.lastname.should.equal('Jane'); - }); - }); - }); - }); -}); \ No newline at end of file diff --git a/test/mocha.opts b/test/mocha.opts index 918a405..54e3460 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,2 +1,3 @@ --require test/support/chai ---require test/support/config \ No newline at end of file +--require test/support/config +--timeout 10s \ No newline at end of file diff --git a/test/model.js b/test/model.js deleted file mode 100644 index cde6fea..0000000 --- a/test/model.js +++ /dev/null @@ -1,75 +0,0 @@ -describe('orm', function () { - "use strict"; - - describe('Model', function () { - var db = new Database(config); - - before(function () { - return db.connect(); - }); - - after(function() { - db.disconnect(); - }); - describe('constructor', function () { - it('should allow a new model to be created', function () { - var model = new Model(db, 'model', { - - }, { - - }); - }); - - it('should return an object with isModel set to true', function() { - var model = new Model(db, 'model', {}, {}); - model.isModel.should.be.true; - }); - - it('should return a new instance even if called without the new keyword', function() { - var $ = {}; - var model = Model.call($, db, 'model', {}, {}); - (model instanceof Model).should.be.true; - }); - - it('should provide the full model API', function() { - var functions = [ - 'wrap', - 'toSource', - 'fromSource', - 'find', - 'findOne', - 'get', - 'insert', - 'create', - 'update', - 'count', - 'remove', - 'aggregate', - 'ensureIndex', - 'setupIndexes' - ]; - - var properties = [ - 'preprocessor', - 'options', - 'schema', - 'schemaValidator', - 'database', - 'collection' - ]; - - var model = new Model(db, 'model', { - - }, { - - }); - - for(var i = 0; i < functions.length; i++) - model.should.respondTo(functions[i]); - - for(var i = 0; i < properties.length; i++) - model.should.have.property(properties[i]); - }); - }); - }); -}); diff --git a/test/plugins.js b/test/plugins.js deleted file mode 100644 index f09207e..0000000 --- a/test/plugins.js +++ /dev/null @@ -1,71 +0,0 @@ -var skmatc = require('skmatc'); - -describe('plugins', function() { - describe('custom validation', function() { - var plugin = { - validate: skmatc.Validator.module(function(schema) { return schema == 'Positive'; }, - function(schema, data, path) { return this.assert(data >= 0); }) - }; - - it('should correctly validate models upon creation', function() { - var model = new Model({ - plugins: [plugin], - db: { - collection: function() { return null; } - } - }, 'collection', - { name: String, age: 'Positive' }, - {}); - - return model.insert({ - name: 'test', age: -1 - }).then(function(instance) { - should.fail(); - }, function(err) { - should.exist(err); - err.isValidationError.should.be.true; - return Promise.resolve(); - }); - }); - }); - - describe('model creation', function() { - var plugin = { - newModel: function(db, collection, schema, options) { - options.done(); - } - }; - - it('should correctly trigger the newModel hook', function(done) { - var model = new Model({ - plugins: [plugin], - db: { - collection: function() { return null; } - } - }, 'collection', - { name: String, age: 'Positive' }, - { done: done }); - }); - }); - - describe('instance creation', function() { - var plugin = { - newInstance: function(model, doc, isNew) { - model.options.done(); - } - }; - - it('should correctly trigger the newInstance hook', function(done) { - var model = new Model({ - plugins: [plugin] - }, 'collection', - { name: String, age: 'Positive' }, - { done: done, preprocessors: [] }); - - var instance = new Instance(model, { - name: 'Test', - age: 10 - }); - }); - }); -}); \ No newline at end of file diff --git a/test/support/chai.js b/test/support/chai.js deleted file mode 100644 index e8664bc..0000000 --- a/test/support/chai.js +++ /dev/null @@ -1,12 +0,0 @@ -var chai = require('chai'); -var chaiAsPromised = require('chai-as-promised'); -var chaiFuzzy = require('chai-fuzzy'); -var Promise = require('bluebird'); - -var should = chai.should(); -chai.use(chaiAsPromised); -chai.use(chaiFuzzy); - -global.chai = chai; -global.should = should; -global.Promise = Promise; \ No newline at end of file diff --git a/test/support/chai.ts b/test/support/chai.ts new file mode 100644 index 0000000..7a1bb08 --- /dev/null +++ b/test/support/chai.ts @@ -0,0 +1,13 @@ +/// +import chai = require('chai'); +import chaiAsPromised = require('chai-as-promised'); +import Bluebird = require('bluebird'); +var chaiFuzzy = require('chai-fuzzy'); + +Bluebird.longStackTraces(); + +chai.use(chaiAsPromised); +chai.use(chaiFuzzy); + +global.chai = chai; +global.expect = chai.expect; \ No newline at end of file diff --git a/test/support/config.js b/test/support/config.js deleted file mode 100644 index f4c1c9b..0000000 --- a/test/support/config.js +++ /dev/null @@ -1,9 +0,0 @@ -var Iridium = require('../../index.js'), - config = require('./config.json'), - Concoction = require('concoction');; - -global.Iridium = global.Database = Iridium; -global.Model = Iridium.Model; -global.Instance = Iridium.Instance; -global.Concoction = Concoction; -global.config = config; \ No newline at end of file diff --git a/test/support/config.json b/test/support/config.json deleted file mode 100644 index d288564..0000000 --- a/test/support/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "host": "localhost", - "username": "iridium", - "password": "", - "database": "iridium" -} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e19341e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "version": true, + "module": "commonjs", + "target": "es5", + "sourceMap": true, + "declaration": false + } +} \ No newline at end of file diff --git a/tsd.json b/tsd.json new file mode 100644 index 0000000..b146a54 --- /dev/null +++ b/tsd.json @@ -0,0 +1,33 @@ +{ + "version": "v4", + "repo": "borisyankov/DefinitelyTyped", + "ref": "master", + "path": "typings/DefinitelyTyped", + "bundle": "typings/DefinitelyTyped/tsd.d.ts", + "installed": { + "mongodb/mongodb.d.ts": { + "commit": "95d860879fb90c405aeb13eea7b8564f9c0df2bf" + }, + "bluebird/bluebird.d.ts": { + "commit": "95d860879fb90c405aeb13eea7b8564f9c0df2bf" + }, + "lodash/lodash.d.ts": { + "commit": "95d860879fb90c405aeb13eea7b8564f9c0df2bf" + }, + "node/node.d.ts": { + "commit": "95d860879fb90c405aeb13eea7b8564f9c0df2bf" + }, + "mocha/mocha.d.ts": { + "commit": "cb08e83dd87ace1202d1032a8f275b3efacde5c3" + }, + "chai-fuzzy/chai-fuzzy.d.ts": { + "commit": "cb08e83dd87ace1202d1032a8f275b3efacde5c3" + }, + "chai/chai.d.ts": { + "commit": "cb08e83dd87ace1202d1032a8f275b3efacde5c3" + }, + "chai-as-promised/chai-as-promised.d.ts": { + "commit": "cb08e83dd87ace1202d1032a8f275b3efacde5c3" + } + } +} diff --git a/typings/DefinitelyTyped/bluebird/bluebird.d.ts b/typings/DefinitelyTyped/bluebird/bluebird.d.ts new file mode 100644 index 0000000..1952551 --- /dev/null +++ b/typings/DefinitelyTyped/bluebird/bluebird.d.ts @@ -0,0 +1,721 @@ +// Type definitions for bluebird 2.0.0 +// Project: https://github.com/petkaantonov/bluebird +// Definitions by: Bart van der Schoor +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +// ES6 model with generics overload was sourced and trans-multiplied from es6-promises.d.ts +// By: Campredon + +// Warning: recommended to use `tsc > v0.9.7` (critical bugs in earlier generic code): +// - https://github.com/borisyankov/DefinitelyTyped/issues/1563 + +// Note: replicate changes to all overloads in both definition and test file +// Note: keep both static and instance members inline (so similar) + +// TODO fix remaining TODO annotations in both definition and test + +// TODO verify support to have no return statement in handlers to get a Bluebird (more overloads?) + +declare class Bluebird implements Promise.Thenable, Promise.Inspection { + /** + * Create a new promise. The passed in function will receive functions `resolve` and `reject` as its arguments which can be called to seal the fate of the created promise. + */ + constructor(callback: (resolve: (thenable: Promise.Thenable) => void, reject: (error: any) => void) => void); + constructor(callback: (resolve: (result: R) => void, reject: (error: any) => void) => void); + constructor(callback: (resolve: (result: R | Promise.Thenable) => void, reject: (error: any) => void) => void); + + /** + * Promises/A+ `.then()` with progress handler. Returns a new promise chained from this promise. The new promise will be rejected or resolved dedefer on the passed `fulfilledHandler`, `rejectedHandler` and the state of this promise. + */ + then(onFulfill: (value: R) => Promise.Thenable, onReject: (error: any) => Promise.Thenable, onProgress?: (note: any) => any): Bluebird; + then(onFulfill: (value: R) => Promise.Thenable, onReject?: (error: any) => U, onProgress?: (note: any) => any): Bluebird; + then(onFulfill: (value: R) => U, onReject: (error: any) => Promise.Thenable, onProgress?: (note: any) => any): Bluebird; + then(onFulfill?: (value: R) => U, onReject?: (error: any) => U, onProgress?: (note: any) => any): Bluebird; + then(onFulfill?: (value: R) => U | Promise.Thenable, onReject?: (error: any) => U | Promise.Thenable, onProgress?: (note: any) => any): Bluebird; + + /** + * This is a catch-all exception handler, shortcut for calling `.then(null, handler)` on this promise. Any exception happening in a `.then`-chain will propagate to nearest `.catch` handler. + * + * Alias `.caught();` for compatibility with earlier ECMAScript version. + */ + catch(onReject?: (error: any) => Promise.Thenable): Bluebird; + caught(onReject?: (error: any) => Promise.Thenable): Bluebird; + + catch(onReject?: (error: any) => U): Bluebird; + caught(onReject?: (error: any) => U): Bluebird; + + catch(onReject?: (error: any) => U | Promise.Thenable): Bluebird; + caught(onReject?: (error: any) => U | Promise.Thenable): Bluebird; + + /** + * This extends `.catch` to work more like catch-clauses in languages like Java or C#. Instead of manually checking `instanceof` or `.name === "SomeError"`, you may specify a number of error constructors which are eligible for this catch handler. The catch handler that is first met that has eligible constructors specified, is the one that will be called. + * + * This method also supports predicate-based filters. If you pass a predicate function instead of an error constructor, the predicate will receive the error as an argument. The return result of the predicate will be used determine whether the error handler should be called. + * + * Alias `.caught();` for compatibility with earlier ECMAScript version. + */ + catch(predicate: (error: any) => boolean, onReject: (error: any) => Promise.Thenable): Bluebird; + caught(predicate: (error: any) => boolean, onReject: (error: any) => Promise.Thenable): Bluebird; + + catch(predicate: (error: any) => boolean, onReject: (error: any) => U): Bluebird; + caught(predicate: (error: any) => boolean, onReject: (error: any) => U): Bluebird; + + catch(ErrorClass: Function, onReject: (error: any) => Promise.Thenable): Bluebird; + caught(ErrorClass: Function, onReject: (error: any) => Promise.Thenable): Bluebird; + + catch(ErrorClass: Function, onReject: (error: any) => U): Bluebird; + caught(ErrorClass: Function, onReject: (error: any) => U): Bluebird; + + /** + * Like `.catch` but instead of catching all types of exceptions, it only catches those that don't originate from thrown errors but rather from explicit rejections. + */ + error(onReject: (reason: any) => Promise.Thenable): Bluebird; + error(onReject: (reason: any) => U): Bluebird; + + /** + * Pass a handler that will be called regardless of this promise's fate. Returns a new promise chained from this promise. There are special semantics for `.finally()` in that the final value cannot be modified from the handler. + * + * Alias `.lastly();` for compatibility with earlier ECMAScript version. + */ + finally(handler: () => Promise.Thenable): Bluebird; + finally(handler: () => U): Bluebird; + + lastly(handler: () => Promise.Thenable): Bluebird; + lastly(handler: () => U): Bluebird; + + /** + * Create a promise that follows this promise, but is bound to the given `thisArg` value. A bound promise will call its handlers with the bound value set to `this`. Additionally promises derived from a bound promise will also be bound promises with the same `thisArg` binding as the original promise. + */ + bind(thisArg: any): Bluebird; + + /** + * Like `.then()`, but any unhandled rejection that ends up here will be thrown as an error. + */ + done(onFulfilled: (value: R) => Promise.Thenable, onRejected: (error: any) => Promise.Thenable, onProgress?: (note: any) => any): void; + done(onFulfilled: (value: R) => Promise.Thenable, onRejected?: (error: any) => U, onProgress?: (note: any) => any): void; + done(onFulfilled: (value: R) => U, onRejected: (error: any) => Promise.Thenable, onProgress?: (note: any) => any): void; + done(onFulfilled?: (value: R) => U, onRejected?: (error: any) => U, onProgress?: (note: any) => any): void; + + /** + * Like `.finally()`, but not called for rejections. + */ + tap(onFulFill: (value: R) => Promise.Thenable): Bluebird; + tap(onFulfill: (value: R) => U): Bluebird; + + /** + * Shorthand for `.then(null, null, handler);`. Attach a progress handler that will be called if this promise is progressed. Returns a new promise chained from this promise. + */ + progressed(handler: (note: any) => any): Bluebird; + + /** + * Same as calling `Promise.delay(this, ms)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + delay(ms: number): Bluebird; + + /** + * Returns a promise that will be fulfilled with this promise's fulfillment value or rejection reason. However, if this promise is not fulfilled or rejected within `ms` milliseconds, the returned promise is rejected with a `Promise.TimeoutError` instance. + * + * You may specify a custom error message with the `message` parameter. + */ + timeout(ms: number, message?: string): Bluebird; + + /** + * Register a node-style callback on this promise. When this promise is is either fulfilled or rejected, the node callback will be called back with the node.js convention where error reason is the first argument and success value is the second argument. The error argument will be `null` in case of success. + * Returns back this promise instead of creating a new one. If the `callback` argument is not a function, this method does not do anything. + */ + nodeify(callback: (err: any, value?: R) => void): Bluebird; + nodeify(...sink: any[]): void; + + /** + * Marks this promise as cancellable. Promises by default are not cancellable after v0.11 and must be marked as such for `.cancel()` to have any effect. Marking a promise as cancellable is infectious and you don't need to remark any descendant promise. + */ + cancellable(): Bluebird; + + /** + * Cancel this promise. The cancellation will propagate to farthest cancellable ancestor promise which is still pending. + * + * That ancestor will then be rejected with a `CancellationError` (get a reference from `Promise.CancellationError`) object as the rejection reason. + * + * In a promise rejection handler you may check for a cancellation by seeing if the reason object has `.name === "Cancel"`. + * + * Promises are by default not cancellable. Use `.cancellable()` to mark a promise as cancellable. + */ + // TODO what to do with this? + cancel(): Bluebird; + + /** + * Like `.then()`, but cancellation of the the returned promise or any of its descendant will not propagate cancellation to this promise or this promise's ancestors. + */ + fork(onFulfilled: (value: R) => Promise.Thenable, onRejected: (error: any) => Promise.Thenable, onProgress?: (note: any) => any): Bluebird; + fork(onFulfilled: (value: R) => Promise.Thenable, onRejected?: (error: any) => U, onProgress?: (note: any) => any): Bluebird; + fork(onFulfilled: (value: R) => U, onRejected: (error: any) => Promise.Thenable, onProgress?: (note: any) => any): Bluebird; + fork(onFulfilled?: (value: R) => U, onRejected?: (error: any) => U, onProgress?: (note: any) => any): Bluebird; + + /** + * Create an uncancellable promise based on this promise. + */ + uncancellable(): Bluebird; + + /** + * See if this promise can be cancelled. + */ + isCancellable(): boolean; + + /** + * See if this `promise` has been fulfilled. + */ + isFulfilled(): boolean; + + /** + * See if this `promise` has been rejected. + */ + isRejected(): boolean; + + /** + * See if this `promise` is still defer. + */ + isPending(): boolean; + + /** + * See if this `promise` is resolved -> either fulfilled or rejected. + */ + isResolved(): boolean; + + /** + * Get the fulfillment value of the underlying promise. Throws if the promise isn't fulfilled yet. + * + * throws `TypeError` + */ + value(): R; + + /** + * Get the rejection reason for the underlying promise. Throws if the promise isn't rejected yet. + * + * throws `TypeError` + */ + reason(): any; + + /** + * Synchronously inspect the state of this `promise`. The `PromiseInspection` will represent the state of the promise as snapshotted at the time of calling `.inspect()`. + */ + inspect(): Promise.Inspection; + + /** + * This is a convenience method for doing: + * + * + * promise.then(function(obj){ + * return obj[propertyName].call(obj, arg...); + * }); + * + */ + call(propertyName: string, ...args: any[]): Bluebird; + + /** + * This is a convenience method for doing: + * + * + * promise.then(function(obj){ + * return obj[propertyName]; + * }); + * + */ + // TODO find way to fix get() + // get(propertyName: string): Bluebird; + + /** + * Convenience method for: + * + * + * .then(function() { + * return value; + * }); + * + * + * in the case where `value` doesn't change its value. That means `value` is bound at the time of calling `.return()` + * + * Alias `.thenReturn();` for compatibility with earlier ECMAScript version. + */ + return(): Bluebird; + thenReturn(): Bluebird; + return(value: U): Bluebird; + thenReturn(value: U): Bluebird; + + /** + * Convenience method for: + * + * + * .then(function() { + * throw reason; + * }); + * + * Same limitations apply as with `.return()`. + * + * Alias `.thenThrow();` for compatibility with earlier ECMAScript version. + */ + throw(reason: Error): Bluebird; + thenThrow(reason: Error): Bluebird; + + /** + * Convert to String. + */ + toString(): string; + + /** + * This is implicitly called by `JSON.stringify` when serializing the object. Returns a serialized representation of the `Promise`. + */ + toJSON(): Object; + + /** + * Like calling `.then`, but the fulfillment value or rejection reason is assumed to be an array, which is flattened to the formal parameters of the handlers. + */ + // TODO how to model instance.spread()? like Q? + spread(onFulfill: Function, onReject?: (reason: any) => Promise.Thenable): Bluebird; + spread(onFulfill: Function, onReject?: (reason: any) => U): Bluebird; + /* + // TODO or something like this? + spread(onFulfill: (...values: W[]) => Promise.Thenable, onReject?: (reason: any) => Promise.Thenable): Bluebird; + spread(onFulfill: (...values: W[]) => Promise.Thenable, onReject?: (reason: any) => U): Bluebird; + spread(onFulfill: (...values: W[]) => U, onReject?: (reason: any) => Promise.Thenable): Bluebird; + spread(onFulfill: (...values: W[]) => U, onReject?: (reason: any) => U): Bluebird; + */ + /** + * Same as calling `Promise.all(thisPromise)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + // TODO type inference from array-resolving promise? + all(): Bluebird; + + /** + * Same as calling `Promise.props(thisPromise)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + // TODO how to model instance.props()? + props(): Bluebird; + + /** + * Same as calling `Promise.settle(thisPromise)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + // TODO type inference from array-resolving promise? + settle(): Bluebird[]>; + + /** + * Same as calling `Promise.any(thisPromise)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + // TODO type inference from array-resolving promise? + any(): Bluebird; + + /** + * Same as calling `Promise.some(thisPromise)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + // TODO type inference from array-resolving promise? + some(count: number): Bluebird; + + /** + * Same as calling `Promise.race(thisPromise, count)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + // TODO type inference from array-resolving promise? + race(): Bluebird; + + /** + * Same as calling `Promise.map(thisPromise, mapper)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + // TODO type inference from array-resolving promise? + map(mapper: (item: Q, index: number, arrayLength: number) => Promise.Thenable): Bluebird; + map(mapper: (item: Q, index: number, arrayLength: number) => U): Bluebird; + map(mapper: (item: Q, index: number, arrayLength: number) => U, options: { concurency?: number }): Bluebird; + + /** + * Same as calling `Promise.reduce(thisPromise, Function reducer, initialValue)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + // TODO type inference from array-resolving promise? + reduce(reducer: (memo: U, item: Q, index: number, arrayLength: number) => Promise.Thenable, initialValue?: U): Bluebird; + reduce(reducer: (memo: U, item: Q, index: number, arrayLength: number) => U, initialValue?: U): Bluebird; + + /** + * Same as calling ``Promise.filter(thisPromise, filterer)``. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + // TODO type inference from array-resolving promise? + filter(filterer: (item: U, index: number, arrayLength: number) => Promise.Thenable): Bluebird; + filter(filterer: (item: U, index: number, arrayLength: number) => boolean): Bluebird; + + /** + * Start the chain of promises with `Promise.try`. Any synchronous exceptions will be turned into rejections on the returned promise. + * + * Note about second argument: if it's specifically a true array, its values become respective arguments for the function call. Otherwise it is passed as is as the first argument for the function call. + * + * Alias for `attempt();` for compatibility with earlier ECMAScript version. + */ + static try(fn: () => Promise.Thenable, args?: any[], ctx?: any): Bluebird; + static try(fn: () => R, args?: any[], ctx?: any): Bluebird; + + static attempt(fn: () => Promise.Thenable, args?: any[], ctx?: any): Bluebird; + static attempt(fn: () => R, args?: any[], ctx?: any): Bluebird; + + /** + * Returns a new function that wraps the given function `fn`. The new function will always return a promise that is fulfilled with the original functions return values or rejected with thrown exceptions from the original function. + * This method is convenient when a function can sometimes return synchronously or throw synchronously. + */ + static method(fn: Function): Function; + + /** + * Create a promise that is resolved with the given `value`. If `value` is a thenable or promise, the returned promise will assume its state. + */ + static resolve(): Bluebird; + static resolve(value: Promise.Thenable): Bluebird; + static resolve(value: R): Bluebird; + + /** + * Create a promise that is rejected with the given `reason`. + */ + static reject(reason: any): Bluebird; + static reject(reason: any): Bluebird; + + /** + * Create a promise with undecided fate and return a `PromiseResolver` to control it. See resolution?: Promise(#promise-resolution). + */ + static defer(): Promise.Resolver; + + /** + * Cast the given `value` to a trusted promise. If `value` is already a trusted `Promise`, it is returned as is. If `value` is not a thenable, a fulfilled is: Promise returned with `value` as its fulfillment value. If `value` is a thenable (Promise-like object, like those returned by jQuery's `$.ajax`), returns a trusted that: Promise assimilates the state of the thenable. + */ + static cast(value: Promise.Thenable): Bluebird; + static cast(value: R): Bluebird; + + /** + * Sugar for `Promise.resolve(undefined).bind(thisArg);`. See `.bind()`. + */ + static bind(thisArg: any): Bluebird; + + /** + * See if `value` is a trusted Promise. + */ + static is(value: any): boolean; + + /** + * Call this right after the library is loaded to enabled long stack traces. Long stack traces cannot be disabled after being enabled, and cannot be enabled after promises have alread been created. Long stack traces imply a substantial performance penalty, around 4-5x for throughput and 0.5x for latency. + */ + static longStackTraces(): void; + + /** + * Returns a promise that will be fulfilled with `value` (or `undefined`) after given `ms` milliseconds. If `value` is a promise, the delay will start counting down when it is fulfilled and the returned promise will be fulfilled with the fulfillment value of the `value` promise. + */ + // TODO enable more overloads + static delay(value: Promise.Thenable, ms: number): Bluebird; + static delay(value: R, ms: number): Bluebird; + static delay(ms: number): Bluebird; + + /** + * Returns a function that will wrap the given `nodeFunction`. Instead of taking a callback, the returned function will return a promise whose fate is decided by the callback behavior of the given node function. The node function should conform to node.js convention of accepting a callback as last argument and calling that callback with error as the first argument and success value on the second argument. + * + * If the `nodeFunction` calls its callback with multiple success values, the fulfillment value will be an array of them. + * + * If you pass a `receiver`, the `nodeFunction` will be called as a method on the `receiver`. + */ + static promisify(func: (callback: (err:any, result: T) => void) => void, receiver?: any): () => Bluebird; + static promisify(func: (arg1: A1, callback: (err: any, result: T) => void) => void, receiver?: any): (arg1: A1) => Bluebird; + static promisify(func: (arg1: A1, arg2: A2, callback: (err: any, result: T) => void) => void, receiver?: any): (arg1: A1, arg2: A2) => Bluebird; + static promisify(func: (arg1: A1, arg2: A2, arg3: A3, callback: (err: any, result: T) => void) => void, receiver?: any): (arg1: A1, arg2: A2, arg3: A3) => Bluebird; + static promisify(func: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, callback: (err: any, result: T) => void) => void, receiver?: any): (arg1: A1, arg2: A2, arg3: A3, arg4: A4) => Bluebird; + static promisify(func: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, callback: (err: any, result: T) => void) => void, receiver?: any): (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) => Bluebird; + static promisify(nodeFunction: Function, receiver?: any): Function; + + /** + * Promisifies the entire object by going through the object's properties and creating an async equivalent of each function on the object and its prototype chain. The promisified method name will be the original method name postfixed with `Async`. Returns the input object. + * + * Note that the original methods on the object are not overwritten but new methods are created with the `Async`-postfix. For example, if you `promisifyAll()` the node.js `fs` object use `fs.statAsync()` to call the promisified `stat` method. + */ + // TODO how to model promisifyAll? + static promisifyAll(target: Object): Object; + + /** + * Returns a function that can use `yield` to run asynchronous code synchronously. This feature requires the support of generators which are drafted in the next version of the language. Node version greater than `0.11.2` is required and needs to be executed with the `--harmony-generators` (or `--harmony`) command-line switch. + */ + // TODO fix coroutine GeneratorFunction + static coroutine(generatorFunction: Function): Function; + + /** + * Spawn a coroutine which may yield promises to run asynchronous code synchronously. This feature requires the support of generators which are drafted in the next version of the language. Node version greater than `0.11.2` is required and needs to be executed with the `--harmony-generators` (or `--harmony`) command-line switch. + */ + // TODO fix spawn GeneratorFunction + static spawn(generatorFunction: Function): Bluebird; + + /** + * This is relevant to browser environments with no module loader. + * + * Release control of the `Promise` namespace to whatever it was before this library was loaded. Returns a reference to the library namespace so you can attach it to something else. + */ + static noConflict(): typeof Promise; + + /** + * Add `handler` as the handler to call when there is a possibly unhandled rejection. The default handler logs the error stack to stderr or `console.error` in browsers. + * + * Passing no value or a non-function will have the effect of removing any kind of handling for possibly unhandled rejections. + */ + static onPossiblyUnhandledRejection(handler: (reason: any) => any): void; + + /** + * Given an array, or a promise of an array, which contains promises (or a mix of promises and values) return a promise that is fulfilled when all the items in the array are fulfilled. The promise's fulfillment value is an array with fulfillment values at respective positions to the original array. If any promise in the array rejects, the returned promise is rejected with the rejection reason. + */ + // TODO enable more overloads + // promise of array with promises of value + static all(values: Promise.Thenable[]>): Bluebird; + // promise of array with values + static all(values: Promise.Thenable): Bluebird; + // array with promises of value + static all(values: Promise.Thenable[]): Bluebird; + // array with values + static all(values: R[]): Bluebird; + + /** + * Like ``Promise.all`` but for object properties instead of array items. Returns a promise that is fulfilled when all the properties of the object are fulfilled. The promise's fulfillment value is an object with fulfillment values at respective keys to the original object. If any promise in the object rejects, the returned promise is rejected with the rejection reason. + * + * If `object` is a trusted `Promise`, then it will be treated as a promise for object rather than for its properties. All other objects are treated for their properties as is returned by `Object.keys` - the object's own enumerable properties. + * + * *The original object is not modified.* + */ + // TODO verify this is correct + // trusted promise for object + static props(object: Bluebird): Bluebird; + // object + static props(object: Object): Bluebird; + + /** + * Given an array, or a promise of an array, which contains promises (or a mix of promises and values) return a promise that is fulfilled when all the items in the array are either fulfilled or rejected. The fulfillment value is an array of ``PromiseInspection`` instances at respective positions in relation to the input array. + * + * *original: The array is not modified. The input array sparsity is retained in the resulting array.* + */ + // promise of array with promises of value + static settle(values: Promise.Thenable[]>): Bluebird[]>; + // promise of array with values + static settle(values: Promise.Thenable): Bluebird[]>; + // array with promises of value + static settle(values: Promise.Thenable[]): Bluebird[]>; + // array with values + static settle(values: R[]): Bluebird[]>; + + /** + * Like `Promise.some()`, with 1 as `count`. However, if the promise fulfills, the fulfillment value is not an array of 1 but the value directly. + */ + // promise of array with promises of value + static any(values: Promise.Thenable[]>): Bluebird; + // promise of array with values + static any(values: Promise.Thenable): Bluebird; + // array with promises of value + static any(values: Promise.Thenable[]): Bluebird; + // array with values + static any(values: R[]): Bluebird; + + /** + * Given an array, or a promise of an array, which contains promises (or a mix of promises and values) return a promise that is fulfilled or rejected as soon as a promise in the array is fulfilled or rejected with the respective rejection reason or fulfillment value. + * + * **Note** If you pass empty array or a sparse array with no values, or a promise/thenable for such, it will be forever pending. + */ + // promise of array with promises of value + static race(values: Promise.Thenable[]>): Bluebird; + // promise of array with values + static race(values: Promise.Thenable): Bluebird; + // array with promises of value + static race(values: Promise.Thenable[]): Bluebird; + // array with values + static race(values: R[]): Bluebird; + + /** + * Initiate a competetive race between multiple promises or values (values will become immediately fulfilled promises). When `count` amount of promises have been fulfilled, the returned promise is fulfilled with an array that contains the fulfillment values of the winners in order of resolution. + * + * If too many promises are rejected so that the promise can never become fulfilled, it will be immediately rejected with an array of rejection reasons in the order they were thrown in. + * + * *The original array is not modified.* + */ + // promise of array with promises of value + static some(values: Promise.Thenable[]>, count: number): Bluebird; + // promise of array with values + static some(values: Promise.Thenable, count: number): Bluebird; + // array with promises of value + static some(values: Promise.Thenable[], count: number): Bluebird; + // array with values + static some(values: R[], count: number): Bluebird; + + /** + * Like `Promise.all()` but instead of having to pass an array, the array is generated from the passed variadic arguments. + */ + // variadic array with promises of value + static join(...values: Promise.Thenable[]): Bluebird; + // variadic array with values + static join(...values: R[]): Bluebird; + + /** + * Map an array, or a promise of an array, which contains a promises (or a mix of promises and values) with the given `mapper` function with the signature `(item, index, arrayLength)` where `item` is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well. + * + * If the `mapper` function returns promises or thenables, the returned promise will wait for all the mapped results to be resolved as well. + * + * *The original array is not modified.* + */ + // promise of array with promises of value + static map(values: Promise.Thenable[]>, mapper: (item: R, index: number, arrayLength: number) => Promise.Thenable): Bluebird; + static map(values: Promise.Thenable[]>, mapper: (item: R, index: number, arrayLength: number) => U): Bluebird; + + // promise of array with values + static map(values: Promise.Thenable, mapper: (item: R, index: number, arrayLength: number) => Promise.Thenable): Bluebird; + static map(values: Promise.Thenable, mapper: (item: R, index: number, arrayLength: number) => U): Bluebird; + + // array with promises of value + static map(values: Promise.Thenable[], mapper: (item: R, index: number, arrayLength: number) => Promise.Thenable): Bluebird; + static map(values: Promise.Thenable[], mapper: (item: R, index: number, arrayLength: number) => U): Bluebird; + + // array with values + static map(values: R[], mapper: (item: R, index: number, arrayLength: number) => Promise.Thenable): Bluebird; + static map(values: R[], mapper: (item: R, index: number, arrayLength: number) => U): Bluebird; + + /** + * Reduce an array, or a promise of an array, which contains a promises (or a mix of promises and values) with the given `reducer` function with the signature `(total, current, index, arrayLength)` where `item` is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well. + * + * If the reducer function returns a promise or a thenable, the result for the promise is awaited for before continuing with next iteration. + * + * *The original array is not modified. If no `intialValue` is given and the array doesn't contain at least 2 items, the callback will not be called and `undefined` is returned. If `initialValue` is given and the array doesn't have at least 1 item, `initialValue` is returned.* + */ + // promise of array with promises of value + static reduce(values: Promise.Thenable[]>, reducer: (total: U, current: R, index: number, arrayLength: number) => Promise.Thenable, initialValue?: U): Bluebird; + static reduce(values: Promise.Thenable[]>, reducer: (total: U, current: R, index: number, arrayLength: number) => U, initialValue?: U): Bluebird; + + // promise of array with values + static reduce(values: Promise.Thenable, reducer: (total: U, current: R, index: number, arrayLength: number) => Promise.Thenable, initialValue?: U): Bluebird; + static reduce(values: Promise.Thenable, reducer: (total: U, current: R, index: number, arrayLength: number) => U, initialValue?: U): Bluebird; + + // array with promises of value + static reduce(values: Promise.Thenable[], reducer: (total: U, current: R, index: number, arrayLength: number) => Promise.Thenable, initialValue?: U): Bluebird; + static reduce(values: Promise.Thenable[], reducer: (total: U, current: R, index: number, arrayLength: number) => U, initialValue?: U): Bluebird; + + // array with values + static reduce(values: R[], reducer: (total: U, current: R, index: number, arrayLength: number) => Promise.Thenable, initialValue?: U): Bluebird; + static reduce(values: R[], reducer: (total: U, current: R, index: number, arrayLength: number) => U, initialValue?: U): Bluebird; + + /** + * Filter an array, or a promise of an array, which contains a promises (or a mix of promises and values) with the given `filterer` function with the signature `(item, index, arrayLength)` where `item` is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well. + * + * The return values from the filtered functions are coerced to booleans, with the exception of promises and thenables which are awaited for their eventual result. + * + * *The original array is not modified. + */ + // promise of array with promises of value + static filter(values: Promise.Thenable[]>, filterer: (item: R, index: number, arrayLength: number) => Promise.Thenable): Bluebird; + static filter(values: Promise.Thenable[]>, filterer: (item: R, index: number, arrayLength: number) => boolean): Bluebird; + + // promise of array with values + static filter(values: Promise.Thenable, filterer: (item: R, index: number, arrayLength: number) => Promise.Thenable): Bluebird; + static filter(values: Promise.Thenable, filterer: (item: R, index: number, arrayLength: number) => boolean): Bluebird; + + // array with promises of value + static filter(values: Promise.Thenable[], filterer: (item: R, index: number, arrayLength: number) => Promise.Thenable): Bluebird; + static filter(values: Promise.Thenable[], filterer: (item: R, index: number, arrayLength: number) => boolean): Bluebird; + + // array with values + static filter(values: R[], filterer: (item: R, index: number, arrayLength: number) => Promise.Thenable): Bluebird; + static filter(values: R[], filterer: (item: R, index: number, arrayLength: number) => boolean): Bluebird; +} + +declare module Promise { + export interface RangeError extends Error { + } + export interface CancellationError extends Error { + } + export interface TimeoutError extends Error { + } + export interface TypeError extends Error { + } + export interface RejectionError extends Error { + } + export interface OperationalError extends Error { + } + + // Ideally, we'd define e.g. "export class RangeError extends Error {}", + // but as Error is defined as an interface (not a class), TypeScript doesn't + // allow extending Error, only implementing it. + // However, if we want to catch() only a specific error type, we need to pass + // a constructor function to it. So, as a workaround, we define them here as such. + export function RangeError(): RangeError; + export function CancellationError(): CancellationError; + export function TimeoutError(): TimeoutError; + export function TypeError(): TypeError; + export function RejectionError(): RejectionError; + export function OperationalError(): OperationalError; + + export interface Thenable { + then(onFulfilled: (value: R) => Thenable, onRejected: (error: any) => Thenable): Thenable; + then(onFulfilled: (value: R) => Thenable, onRejected?: (error: any) => U): Thenable; + then(onFulfilled: (value: R) => U, onRejected: (error: any) => Thenable): Thenable; + then(onFulfilled?: (value: R) => U, onRejected?: (error: any) => U): Thenable; + } + + export interface Resolver { + /** + * Returns a reference to the controlled promise that can be passed to clients. + */ + promise: Bluebird; + + /** + * Resolve the underlying promise with `value` as the resolution value. If `value` is a thenable or a promise, the underlying promise will assume its state. + */ + resolve(value: R): void; + resolve(): void; + + /** + * Reject the underlying promise with `reason` as the rejection reason. + */ + reject(reason: any): void; + + /** + * Progress the underlying promise with `value` as the progression value. + */ + progress(value: any): void; + + /** + * Gives you a callback representation of the `PromiseResolver`. Note that this is not a method but a property. The callback accepts error object in first argument and success values on the 2nd parameter and the rest, I.E. node js conventions. + * + * If the the callback is called with multiple success values, the resolver fullfills its promise with an array of the values. + */ + // TODO specify resolver callback + callback: (err: any, value: R, ...values: R[]) => void; + } + + export interface Inspection { + /** + * See if the underlying promise was fulfilled at the creation time of this inspection object. + */ + isFulfilled(): boolean; + + /** + * See if the underlying promise was rejected at the creation time of this inspection object. + */ + isRejected(): boolean; + + /** + * See if the underlying promise was defer at the creation time of this inspection object. + */ + isPending(): boolean; + + /** + * Get the fulfillment value of the underlying promise. Throws if the promise wasn't fulfilled at the creation time of this inspection object. + * + * throws `TypeError` + */ + value(): R; + + /** + * Get the rejection reason for the underlying promise. Throws if the promise wasn't rejected at the creation time of this inspection object. + * + * throws `TypeError` + */ + reason(): any; + } + + /** + * Changes how bluebird schedules calls a-synchronously. + * + * @param scheduler Should be a function that asynchronously schedules + * the calling of the passed in function + */ + export function setScheduler(scheduler: (callback: (...args: any[]) => void) => void): void; +} + +declare module 'bluebird' { + export = Bluebird; +} diff --git a/typings/DefinitelyTyped/chai-as-promised/chai-as-promised.d.ts b/typings/DefinitelyTyped/chai-as-promised/chai-as-promised.d.ts new file mode 100644 index 0000000..20b697b --- /dev/null +++ b/typings/DefinitelyTyped/chai-as-promised/chai-as-promised.d.ts @@ -0,0 +1,35 @@ +// Type definitions for chai-as-promised +// Project: https://github.com/domenic/chai-as-promised/ +// Definitions by: jt000 +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +/// + +declare module 'chai-as-promised' { + import chai = require('chai'); + + function chaiAsPromised(chai: any, utils: any): void; + + export = chaiAsPromised; +} + +declare module chai { + + interface LanguageChains { + become(expected: any): Expect; + eventually: Expect; + rejected: Expect; + rejectedWith(expected: any): Expect; + notify(fn: Function): Expect; + } + + interface Assert { + eventually: Assert; + isFulfilled(promise: any, message?: string): void; + becomes(promise: any, expected: any, message?: string): void; + doesNotBecome(promise: any, expected: any, message?: string): void; + isRejected(promise: any, message?: string): void; + isRejected(promise: any, expected: any, message?: string): void; + isRejected(promise: any, match: RegExp, message?: string): void; + } +} \ No newline at end of file diff --git a/typings/DefinitelyTyped/chai-fuzzy/chai-fuzzy.d.ts b/typings/DefinitelyTyped/chai-fuzzy/chai-fuzzy.d.ts new file mode 100644 index 0000000..f080fbc --- /dev/null +++ b/typings/DefinitelyTyped/chai-fuzzy/chai-fuzzy.d.ts @@ -0,0 +1,26 @@ +// Type definitions for chai-fuzzy 1.3.0 +// Project: http://chaijs.com/plugins/chai-fuzzy +// Definitions by: Bart van der Schoor +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +/// + +declare module chai { + interface LanguageChains { + like(exp:any, msg?:string); + notLike(exp:any, msg?:string); + containOneLike(exp:any, msg?:string); + notContainOneLike(exp:any, msg?:string); + jsonOf(exp:any, msg?:string); + notJsonOf(exp:any, msg?:string); + } + + interface Assert { + like(act:any, exp:any, msg?:string); + notLike(act:any, exp:any, msg?:string); + containOneLike(act:any, exp:any, msg?:string); + notContainOneLike(act:any, exp:any, msg?:string); + jsonOf(act:any, exp:any, msg?:string); + notJsonOf(act:any, exp:any, msg?:string); + } +} diff --git a/typings/DefinitelyTyped/chai/chai.d.ts b/typings/DefinitelyTyped/chai/chai.d.ts new file mode 100644 index 0000000..4b9d313 --- /dev/null +++ b/typings/DefinitelyTyped/chai/chai.d.ts @@ -0,0 +1,285 @@ +// Type definitions for chai 2.0.0 +// Project: http://chaijs.com/ +// Definitions by: Jed Hunsaker , Bart van der Schoor +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +declare module chai { + export class AssertionError { + constructor(message: string, _props?: any, ssf?: Function); + name: string; + message: string; + showDiff: boolean; + stack: string; + } + + export function expect(target: any, message?: string): Expect; + + export var assert: Assert; + export var config: Config; + + export interface Config { + includeStack: boolean; + } + + // Provides a way to extend the internals of Chai + function use(fn: (chai: any, utils: any) => void): any; + + interface ExpectStatic { + (target: any): Expect; + } + + interface Assertions { + attr(name: string, value?: string): any; + css(name: string, value?: string): any; + data(name: string, value?: string): any; + class(className: string): any; + id(id: string): any; + html(html: string): any; + text(text: string): any; + value(value: string): any; + visible: any; + hidden: any; + selected: any; + checked: any; + disabled: any; + empty: any; + exist: any; + } + + interface Expect extends LanguageChains, NumericComparison, TypeComparison, Assertions { + not: Expect; + deep: Deep; + a: TypeComparison; + an: TypeComparison; + include: Include; + contain: Include; + ok: Expect; + true: Expect; + false: Expect; + null: Expect; + undefined: Expect; + exist: Expect; + empty: Expect; + arguments: Expect; + Arguments: Expect; + equal: Equal; + equals: Equal; + eq: Equal; + eql: Equal; + eqls: Equal; + property: Property; + ownProperty: OwnProperty; + haveOwnProperty: OwnProperty; + length: Length; + lengthOf: Length; + match(RegularExpression: RegExp, message?: string): Expect; + string(string: string, message?: string): Expect; + keys: Keys; + key(string: string): Expect; + throw: Throw; + throws: Throw; + Throw: Throw; + respondTo(method: string, message?: string): Expect; + itself: Expect; + satisfy(matcher: Function, message?: string): Expect; + closeTo(expected: number, delta: number, message?: string): Expect; + members: Members; + } + + interface LanguageChains { + to: Expect; + be: Expect; + been: Expect; + is: Expect; + that: Expect; + which: Expect; + and: Expect; + have: Expect; + has: Expect; + with: Expect; + at: Expect; + of: Expect; + same: Expect; + } + + interface NumericComparison { + above: NumberComparer; + gt: NumberComparer; + greaterThan: NumberComparer; + least: NumberComparer; + gte: NumberComparer; + below: NumberComparer; + lt: NumberComparer; + lessThan: NumberComparer; + most: NumberComparer; + lte: NumberComparer; + within(start: number, finish: number, message?: string): Expect; + } + + interface NumberComparer { + (value: number, message?: string): Expect; + } + + interface TypeComparison { + (type: string, message?: string): Expect; + instanceof: InstanceOf; + instanceOf: InstanceOf; + } + + interface InstanceOf { + (constructor: Object, message?: string): Expect; + } + + interface Deep { + equal: Equal; + property: Property; + } + + interface Equal { + (value: any, message?: string): Expect; + } + + interface Property { + (name: string, value?: any, message?: string): Expect; + } + + interface OwnProperty { + (name: string, message?: string): Expect; + } + + interface Length extends LanguageChains, NumericComparison { + (length: number, message?: string): Expect; + } + + interface Include { + (value: Object, message?: string): Expect; + (value: string, message?: string): Expect; + (value: number, message?: string): Expect; + keys: Keys; + members: Members; + } + + interface Keys { + (...keys: string[]): Expect; + (keys: any[]): Expect; + } + + interface Members { + (set: any[], message?: string): Expect; + } + + interface Throw { + (): Expect; + (expected: string, message?: string): Expect; + (expected: RegExp, message?: string): Expect; + (constructor: Error, expected?: string, message?: string): Expect; + (constructor: Error, expected?: RegExp, message?: string): Expect; + (constructor: Function, expected?: string, message?: string): Expect; + (constructor: Function, expected?: RegExp, message?: string): Expect; + } + + export interface Assert { + (express: any, msg?: string):void; + + fail(actual?: any, expected?: any, msg?: string, operator?: string):void; + + ok(val: any, msg?: string):void; + notOk(val: any, msg?: string):void; + + equal(act: any, exp: any, msg?: string):void; + notEqual(act: any, exp: any, msg?: string):void; + + strictEqual(act: any, exp: any, msg?: string):void; + notStrictEqual(act: any, exp: any, msg?: string):void; + + deepEqual(act: any, exp: any, msg?: string):void; + notDeepEqual(act: any, exp: any, msg?: string):void; + + isTrue(val: any, msg?: string):void; + isFalse(val: any, msg?: string):void; + + isNull(val: any, msg?: string):void; + isNotNull(val: any, msg?: string):void; + + isUndefined(val: any, msg?: string):void; + isDefined(val: any, msg?: string):void; + + isFunction(val: any, msg?: string):void; + isNotFunction(val: any, msg?: string):void; + + isObject(val: any, msg?: string):void; + isNotObject(val: any, msg?: string):void; + + isArray(val: any, msg?: string):void; + isNotArray(val: any, msg?: string):void; + + isString(val: any, msg?: string):void; + isNotString(val: any, msg?: string):void; + + isNumber(val: any, msg?: string):void; + isNotNumber(val: any, msg?: string):void; + + isBoolean(val: any, msg?: string):void; + isNotBoolean(val: any, msg?: string):void; + + typeOf(val: any, type: string, msg?: string):void; + notTypeOf(val: any, type: string, msg?: string):void; + + instanceOf(val: any, type: Function, msg?: string):void; + notInstanceOf(val: any, type: Function, msg?: string):void; + + include(exp: string, inc: any, msg?: string):void; + include(exp: any[], inc: any, msg?: string):void; + + notInclude(exp: string, inc: any, msg?: string):void; + notInclude(exp: any[], inc: any, msg?: string):void; + + match(exp: any, re: RegExp, msg?: string):void; + notMatch(exp: any, re: RegExp, msg?: string):void; + + property(obj: Object, prop: string, msg?: string):void; + notProperty(obj: Object, prop: string, msg?: string):void; + deepProperty(obj: Object, prop: string, msg?: string):void; + notDeepProperty(obj: Object, prop: string, msg?: string):void; + + propertyVal(obj: Object, prop: string, val: any, msg?: string):void; + propertyNotVal(obj: Object, prop: string, val: any, msg?: string):void; + + deepPropertyVal(obj: Object, prop: string, val: any, msg?: string):void; + deepPropertyNotVal(obj: Object, prop: string, val: any, msg?: string):void; + + lengthOf(exp: any, len: number, msg?: string):void; + //alias frenzy + throw(fn: Function, msg?: string):void; + throw(fn: Function, regExp: RegExp):void; + throw(fn: Function, errType: Function, msg?: string):void; + throw(fn: Function, errType: Function, regExp: RegExp):void; + + throws(fn: Function, msg?: string):void; + throws(fn: Function, regExp: RegExp):void; + throws(fn: Function, errType: Function, msg?: string):void; + throws(fn: Function, errType: Function, regExp: RegExp):void; + + Throw(fn: Function, msg?: string):void; + Throw(fn: Function, regExp: RegExp):void; + Throw(fn: Function, errType: Function, msg?: string):void; + Throw(fn: Function, errType: Function, regExp: RegExp):void; + + doesNotThrow(fn: Function, msg?: string):void; + doesNotThrow(fn: Function, regExp: RegExp):void; + doesNotThrow(fn: Function, errType: Function, msg?: string):void; + doesNotThrow(fn: Function, errType: Function, regExp: RegExp):void; + + operator(val: any, operator: string, val2: any, msg?: string):void; + closeTo(act: number, exp: number, delta: number, msg?: string):void; + + sameMembers(set1: any[], set2: any[], msg?: string):void; + includeMembers(set1: any[], set2: any[], msg?: string):void; + + ifError(val: any, msg?: string):void; + } +} + +declare module "chai" { + export = chai; +} diff --git a/typings/DefinitelyTyped/lodash/lodash.d.ts b/typings/DefinitelyTyped/lodash/lodash.d.ts new file mode 100644 index 0000000..0a9fb3d --- /dev/null +++ b/typings/DefinitelyTyped/lodash/lodash.d.ts @@ -0,0 +1,6431 @@ +// Type definitions for Lo-Dash +// Project: http://lodash.com/ +// Definitions by: Brian Zengel +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +declare var _: _.LoDashStatic; + +declare module _ { + interface LoDashStatic { + /** + * Creates a lodash object which wraps the given value to enable intuitive method chaining. + * + * In addition to Lo-Dash methods, wrappers also have the following Array methods: + * concat, join, pop, push, reverse, shift, slice, sort, splice, and unshift + * + * Chaining is supported in custom builds as long as the value method is implicitly or + * explicitly included in the build. + * + * The chainable wrapper functions are: + * after, assign, bind, bindAll, bindKey, chain, compact, compose, concat, countBy, + * createCallback, curry, debounce, defaults, defer, delay, difference, filter, flatten, + * forEach, forEachRight, forIn, forInRight, forOwn, forOwnRight, functions, groupBy, + * indexBy, initial, intersection, invert, invoke, keys, map, max, memoize, merge, min, + * object, omit, once, pairs, partial, partialRight, pick, pluck, pull, push, range, reject, + * remove, rest, reverse, shuffle, slice, sort, sortBy, splice, tap, throttle, times, + * toArray, transform, union, uniq, unshift, unzip, values, where, without, wrap, and zip + * + * The non-chainable wrapper functions are: + * clone, cloneDeep, contains, escape, every, find, findIndex, findKey, findLast, + * findLastIndex, findLastKey, has, identity, indexOf, isArguments, isArray, isBoolean, + * isDate, isElement, isEmpty, isEqual, isFinite, isFunction, isNaN, isNull, isNumber, + * isObject, isPlainObject, isRegExp, isString, isUndefined, join, lastIndexOf, mixin, + * noConflict, parseInt, pop, random, reduce, reduceRight, result, shift, size, some, + * sortedIndex, runInContext, template, unescape, uniqueId, and value + * + * The wrapper functions first and last return wrapped values when n is provided, otherwise + * they return unwrapped values. + * + * Explicit chaining can be enabled by using the _.chain method. + **/ + (value: number): LoDashWrapper; + (value: string): LoDashWrapper; + (value: boolean): LoDashWrapper; + (value: Array): LoDashArrayWrapper; + (value: T): LoDashObjectWrapper; + (value: any): LoDashWrapper; + + /** + * The semantic version number. + **/ + VERSION: string; + + /** + * An object used to flag environments features. + **/ + support: Support; + + /** + * By default, the template delimiters used by Lo-Dash are similar to those in embedded Ruby + * (ERB). Change the following template settings to use alternative delimiters. + **/ + templateSettings: TemplateSettings; + } + + /** + * By default, the template delimiters used by Lo-Dash are similar to those in embedded Ruby + * (ERB). Change the following template settings to use alternative delimiters. + **/ + interface TemplateSettings { + /** + * The "escape" delimiter. + **/ + escape?: RegExp; + + /** + * The "evaluate" delimiter. + **/ + evaluate?: RegExp; + + /** + * An object to import into the template as local variables. + **/ + imports?: Dictionary; + + /** + * The "interpolate" delimiter. + **/ + interpolate?: RegExp; + + /** + * Used to reference the data object in the template text. + **/ + variable?: string; + } + + /** + * An object used to flag environments features. + **/ + interface Support { + /** + * Detect if an arguments object’s [[Class]] is resolvable (all but Firefox < 4, IE < 9). + **/ + argsClass: boolean; + + /** + * Detect if arguments objects are Object objects (all but Narwhal and Opera < 10.5). + **/ + argsObject: boolean; + + /** + * Detect if name or message properties of Error.prototype are enumerable by default. + * (IE < 9, Safari < 5.1) + **/ + enumErrorProps: boolean; + + /** + * Detect if Function#bind exists and is inferred to be fast (all but V8). + **/ + fastBind: boolean; + + /** + * Detect if functions can be decompiled by Function#toString (all but PS3 and older Opera + * mobile browsers & avoided in Windows 8 apps). + **/ + funcDecomp: boolean; + + /** + * Detect if Function#name is supported (all but IE). + **/ + funcNames: boolean; + + /** + * Detect if arguments object indexes are non-enumerable (Firefox < 4, IE < 9, PhantomJS, + * Safari < 5.1). + **/ + nonEnumArgs: boolean; + + /** + * Detect if properties shadowing those on Object.prototype are non-enumerable. + * + * In IE < 9 an objects own properties, shadowing non-enumerable ones, are made + * non-enumerable as well (a.k.a the JScript [[DontEnum]] bug). + **/ + nonEnumShadows: boolean; + + /** + * Detect if own properties are iterated after inherited properties (all but IE < 9). + **/ + ownLast: boolean; + + /** + * Detect if Array#shift and Array#splice augment array-like objects correctly. + * + * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array shift() and splice() + * functions that fail to remove the last element, value[0], of array-like objects even + * though the length property is set to 0. The shift() method is buggy in IE 8 compatibility + * mode, while splice() is buggy regardless of mode in IE < 9 and buggy in compatibility mode + * in IE 9. + **/ + spliceObjects: boolean; + + /** + * Detect lack of support for accessing string characters by index. + * + * IE < 8 can't access characters by index and IE 8 can only access characters by index on + * string literals. + **/ + unindexedChars: boolean; + } + + interface LoDashWrapperBase { + /** + * Produces the toString result of the wrapped value. + * @return Returns the string result. + **/ + toString(): string; + + /** + * Extracts the wrapped value. + * @return The wrapped value. + **/ + valueOf(): T; + + /** + * @see valueOf + **/ + value(): T; + } + + interface LoDashWrapper extends LoDashWrapperBase> { } + + interface LoDashObjectWrapper extends LoDashWrapperBase> { } + + interface LoDashArrayWrapper extends LoDashWrapperBase> { + concat(...items: T[]): LoDashArrayWrapper; + join(seperator?: string): LoDashWrapper; + pop(): LoDashWrapper; + push(...items: T[]): void; + reverse(): LoDashArrayWrapper; + shift(): LoDashWrapper; + slice(start: number, end?: number): LoDashArrayWrapper; + sort(compareFn?: (a: T, b: T) => number): LoDashArrayWrapper; + splice(start: number): LoDashArrayWrapper; + splice(start: number, deleteCount: number, ...items: any[]): LoDashArrayWrapper; + unshift(...items: any[]): LoDashWrapper; + } + + //_.chain + interface LoDashStatic { + /** + * Creates a lodash object that wraps the given value with explicit method chaining enabled. + * @param value The value to wrap. + * @return The wrapper object. + **/ + chain(value: number): LoDashWrapper; + chain(value: string): LoDashWrapper; + chain(value: boolean): LoDashWrapper; + chain(value: Array): LoDashArrayWrapper; + chain(value: T): LoDashObjectWrapper; + chain(value: any): LoDashWrapper; + } + + interface LoDashWrapperBase { + /** + * Enables explicit method chaining on the wrapper object. + * @see _.chain + * @return The wrapper object. + **/ + chain(): TWrapper; + } + + //_.tap + interface LoDashStatic { + /** + * Invokes interceptor with the value as the first argument and then returns value. The + * purpose of this method is to "tap into" a method chain in order to perform operations on + * intermediate results within the chain. + * @param value The value to provide to interceptor + * @param interceptor The function to invoke. + * @return value + **/ + tap( + value: T, + interceptor: (value: T) => void): T; + } + + interface LoDashWrapperBase { + /** + * @see _.tap + **/ + tap(interceptor: (value: T) => void): TWrapper; + } + + /********* + * Arrays * + **********/ + + //_.compact + interface LoDashStatic { + /** + * Returns a copy of the array with all falsy values removed. In JavaScript, false, null, 0, "", + * undefined and NaN are all falsy. + * @param array Array to compact. + * @return (Array) Returns a new array of filtered values. + **/ + compact(array: Array): T[]; + + /** + * @see _.compact + **/ + compact(array: List): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.compact + **/ + compact(): LoDashArrayWrapper; + } + + //_.chunk + interface LoDashStatic { + /** + * Creates an array of elements split into groups the length of size. If collection can’t be split evenly, the final chunk will be the remaining elements. + * @param array The array to process. + * @param size The length of each chunk. + * @return Returns the new array containing chunks. + */ + chunk(array: T[], size?: number): T[][]; + } + + //_.difference + interface LoDashStatic { + /** + * Creates an array excluding all values of the provided arrays using strict equality for comparisons + * , i.e. ===. + * @param array The array to process + * @param others The arrays of values to exclude. + * @return Returns a new array of filtered values. + **/ + difference( + array: Array, + ...others: Array[]): T[]; + /** + * @see _.difference + **/ + difference( + array: List, + ...others: List[]): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.difference + **/ + difference( + ...others: Array[]): LoDashArrayWrapper; + /** + * @see _.difference + **/ + difference( + ...others: List[]): LoDashArrayWrapper; + } + + //_.findIndex + interface LoDashStatic { + /** + * This method is like _.find except that it returns the index of the first element that passes + * the callback check, instead of the element itself. + * @param array The array to search. + * @param {(Function|Object|string)} callback The function called per iteration. If a property name or object is provided it will be + * used to create a ".pluck" or ".where" style callback, respectively. + * @param thisArg The this binding of callback. + * @return Returns the index of the found element, else -1. + **/ + findIndex( + array: Array, + callback: ListIterator, + thisArg?: any): number; + + /** + * @see _.findIndex + **/ + findIndex( + array: List, + callback: ListIterator, + thisArg?: any): number; + + /** + * @see _.findIndex + **/ + findIndex( + array: Array, + pluckValue: string): number; + + /** + * @see _.findIndex + **/ + findIndex( + array: List, + pluckValue: string): number; + + /** + * @see _.findIndex + **/ + findIndex( + array: Array, + whereDictionary: W): number; + + /** + * @see _.findIndex + **/ + findIndex( + array: List, + whereDictionary: W): number; + } + + //_.findLastIndex + interface LoDashStatic { + /** + * This method is like _.findIndex except that it iterates over elements of a collection from right to left. + * @param array The array to search. + * @param {(Function|Object|string)} callback The function called per iteration. If a property name or object is provided it will be + * used to create a ".pluck" or ".where" style callback, respectively. + * @param thisArg The this binding of callback. + * @return Returns the index of the found element, else -1. + **/ + findLastIndex( + array: Array, + callback: ListIterator, + thisArg?: any): number; + + /** + * @see _.findLastIndex + **/ + findLastIndex( + array: List, + callback: ListIterator, + thisArg?: any): number; + + /** + * @see _.findLastIndex + **/ + findLastIndex( + array: Array, + pluckValue: string): number; + + /** + * @see _.findLastIndex + **/ + findLastIndex( + array: List, + pluckValue: string): number; + + /** + * @see _.findLastIndex + **/ + findLastIndex( + array: Array, + whereDictionary: Dictionary): number; + + /** + * @see _.findLastIndex + **/ + findLastIndex( + array: List, + whereDictionary: Dictionary): number; + } + + //_.first + interface LoDashStatic { + /** + * Gets the first element or first n elements of an array. If a callback is provided + * elements at the beginning of the array are returned as long as the callback returns + * truey. The callback is bound to thisArg and invoked with three arguments; (value, + * index, array). + * + * If a property name is provided for callback the created "_.pluck" style callback + * will return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return ] + * true for elements that have the properties of the given object, else false. + * @param array Retrieves the first element of this array. + * @return Returns the first element of `array`. + **/ + first(array: Array): T; + + /** + * @see _.first + **/ + first(array: List): T; + + /** + * @see _.first + * @param n The number of elements to return. + **/ + first( + array: Array, + n: number): T[]; + + /** + * @see _.first + * @param n The number of elements to return. + **/ + first( + array: List, + n: number): T[]; + + /** + * @see _.first + * @param callback The function called per element. + * @param [thisArg] The this binding of callback. + **/ + first( + array: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.first + * @param callback The function called per element. + * @param [thisArg] The this binding of callback. + **/ + first( + array: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.first + * @param pluckValue "_.pluck" style callback value + **/ + first( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.first + * @param pluckValue "_.pluck" style callback value + **/ + first( + array: List, + pluckValue: string): T[]; + + /** + * @see _.first + * @param whereValue "_.where" style callback value + **/ + first( + array: Array, + whereValue: W): T[]; + + /** + * @see _.first + * @param whereValue "_.where" style callback value + **/ + first( + array: List, + whereValue: W): T[]; + + /** + * @see _.first + **/ + head(array: Array): T; + + /** + * @see _.first + **/ + head(array: List): T; + + /** + * @see _.first + **/ + head( + array: Array, + n: number): T[]; + + /** + * @see _.first + **/ + head( + array: List, + n: number): T[]; + + /** + * @see _.first + **/ + head( + array: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.first + **/ + head( + array: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.first + **/ + head( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.first + **/ + head( + array: List, + pluckValue: string): T[]; + + /** + * @see _.first + **/ + head( + array: Array, + whereValue: W): T[]; + + /** + * @see _.first + **/ + head( + array: List, + whereValue: W): T[]; + + /** + * @see _.first + **/ + take(array: Array): T; + + /** + * @see _.first + **/ + take(array: List): T; + + /** + * @see _.first + **/ + take( + array: Array, + n: number): T[]; + + /** + * @see _.first + **/ + take( + array: List, + n: number): T[]; + + /** + * @see _.first + **/ + take( + array: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.first + **/ + take( + array: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.first + **/ + take( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.first + **/ + take( + array: List, + pluckValue: string): T[]; + + /** + * @see _.first + **/ + take( + array: Array, + whereValue: W): T[]; + + /** + * @see _.first + **/ + take( + array: List, + whereValue: W): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.first + **/ + first(): T; + + /** + * @see _.first + * @param n The number of elements to return. + **/ + first(n: number): LoDashArrayWrapper; + + /** + * @see _.first + * @param callback The function called per element. + * @param [thisArg] The this binding of callback. + **/ + first( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.first + * @param pluckValue "_.pluck" style callback value + **/ + first(pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.first + * @param whereValue "_.where" style callback value + **/ + first(whereValue: W): LoDashArrayWrapper; + + /** + * @see _.first + **/ + head(): T; + + /** + * @see _.first + * @param n The number of elements to return. + **/ + head(n: number): LoDashArrayWrapper; + + /** + * @see _.first + * @param callback The function called per element. + * @param [thisArg] The this binding of callback. + **/ + head( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.first + * @param pluckValue "_.pluck" style callback value + **/ + head(pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.first + * @param whereValue "_.where" style callback value + **/ + head(whereValue: W): LoDashArrayWrapper; + + /** + * @see _.first + **/ + take(): T; + + /** + * @see _.first + * @param n The number of elements to return. + **/ + take(n: number): LoDashArrayWrapper; + + /** + * @see _.first + * @param callback The function called per element. + * @param [thisArg] The this binding of callback. + **/ + take( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.first + * @param pluckValue "_.pluck" style callback value + **/ + take(pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.first + * @param whereValue "_.where" style callback value + **/ + take(whereValue: W): LoDashArrayWrapper; + } + + //_.flatten + interface LoDashStatic { + /** + * Flattens a nested array (the nesting can be to any depth). If isShallow is truey, the + * array will only be flattened a single level. If a callback is provided each element of + * the array is passed through the callback before flattening. The callback is bound to + * thisArg and invoked with three arguments; (value, index, array). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param array The array to flatten. + * @param shallow If true then only flatten one level, optional, default = false. + * @return `array` flattened. + **/ + flatten(array: Array, isShallow?: boolean): T[]; + + /** + * @see _.flatten + **/ + flatten(array: List, isShallow?: boolean): T[]; + + /** + * @see _.flatten + **/ + flatten( + array: Array, + isShallow: boolean, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.flatten + **/ + flatten( + array: List, + isShallow: boolean, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.flatten + **/ + flatten( + array: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.flatten + **/ + flatten( + array: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.flatten + **/ + flatten( + array: Array, + isShallow: boolean, + whereValue: W): T[]; + + /** + * @see _.flatten + **/ + flatten( + array: List, + isShallow: boolean, + whereValue: W): T[]; + + /** + * @see _.flatten + **/ + flatten( + array: Array, + whereValue: W): T[]; + + /** + * @see _.flatten + **/ + flatten( + array: List, + whereValue: W): T[]; + + /** + * @see _.flatten + **/ + flatten( + array: Array, + isShallow: boolean, + pluckValue: string): T[]; + + /** + * @see _.flatten + **/ + flatten( + array: List, + isShallow: boolean, + pluckValue: string): T[]; + + /** + * @see _.flatten + **/ + flatten( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.flatten + **/ + flatten( + array: List, + pluckValue: string): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.flatten + **/ + flatten(isShallow?: boolean): LoDashArrayWrapper; + + /** + * @see _.flatten + **/ + flatten( + isShallow: boolean, + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.flatten + **/ + flatten( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.flatten + **/ + flatten( + isShallow: boolean, + pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.flatten + **/ + flatten( + pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.flatten + **/ + flatten( + isShallow: boolean, + whereValue: W): LoDashArrayWrapper; + + /** + * @see _.flatten + **/ + flatten( + whereValue: W): LoDashArrayWrapper; + } + + //_.indexOf + interface LoDashStatic { + /** + * Gets the index at which the first occurrence of value is found using strict equality + * for comparisons, i.e. ===. If the array is already sorted providing true for fromIndex + * will run a faster binary search. + * @param array The array to search. + * @param value The value to search for. + * @param fromIndex The index to search from. + * @return The index of `value` within `array`. + **/ + indexOf( + array: Array, + value: T): number; + + /** + * @see _.indexOf + **/ + indexOf( + array: List, + value: T): number; + + /** + * @see _.indexOf + * @param fromIndex The index to search from + **/ + indexOf( + array: Array, + value: T, + fromIndex: number): number; + + /** + * @see _.indexOf + * @param fromIndex The index to search from + **/ + indexOf( + array: List, + value: T, + fromIndex: number): number; + + /** + * @see _.indexOf + * @param isSorted True to perform a binary search on a sorted array. + **/ + indexOf( + array: Array, + value: T, + isSorted: boolean): number; + + /** + * @see _.indexOf + * @param isSorted True to perform a binary search on a sorted array. + **/ + indexOf( + array: List, + value: T, + isSorted: boolean): number; + } + + //_.initial + interface LoDashStatic { + /** + * Gets all but the last element or last n elements of an array. If a callback is provided + * elements at the end of the array are excluded from the result as long as the callback + * returns truey. The callback is bound to thisArg and invoked with three arguments; + * (value, index, array). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param array The array to query. + * @param n Leaves this many elements behind, optional. + * @return Returns everything but the last `n` elements of `array`. + **/ + initial( + array: Array): T[]; + + /** + * @see _.initial + **/ + initial( + array: List): T[]; + + /** + * @see _.initial + * @param n The number of elements to exclude. + **/ + initial( + array: Array, + n: number): T[]; + + /** + * @see _.initial + * @param n The number of elements to exclude. + **/ + initial( + array: List, + n: number): T[]; + + /** + * @see _.initial + * @param callback The function called per element + **/ + initial( + array: Array, + callback: ListIterator): T[]; + + /** + * @see _.initial + * @param callback The function called per element + **/ + initial( + array: List, + callback: ListIterator): T[]; + + /** + * @see _.initial + * @param pluckValue _.pluck style callback + **/ + initial( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.initial + * @param pluckValue _.pluck style callback + **/ + initial( + array: List, + pluckValue: string): T[]; + + /** + * @see _.initial + * @param whereValue _.where style callback + **/ + initial( + array: Array, + whereValue: W): T[]; + + /** + * @see _.initial + * @param whereValue _.where style callback + **/ + initial( + array: List, + whereValue: W): T[]; + } + + //_.intersection + interface LoDashStatic { + /** + * Creates an array of unique values present in all provided arrays using strict + * equality for comparisons, i.e. ===. + * @param arrays The arrays to inspect. + * @return Returns an array of composite values. + **/ + intersection(...arrays: Array[]): T[]; + + /** + * @see _.intersection + **/ + intersection(...arrays: List[]): T[]; + } + + //_.last + interface LoDashStatic { + /** + * Gets the last element or last n elements of an array. If a callback is provided + * elements at the end of the array are returned as long as the callback returns truey. + * The callback is bound to thisArg and invoked with three arguments; (value, index, array). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param array The array to query. + * @return Returns the last element(s) of array. + **/ + last(array: Array): T; + + /** + * @see _.last + **/ + last(array: List): T; + + /** + * @see _.last + * @param n The number of elements to return + **/ + last( + array: Array, + n: number): T[]; + + /** + * @see _.last + * @param n The number of elements to return + **/ + last( + array: List, + n: number): T[]; + + /** + * @see _.last + * @param callback The function called per element + **/ + last( + array: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.last + * @param callback The function called per element + **/ + last( + array: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.last + * @param pluckValue _.pluck style callback + **/ + last( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.last + * @param pluckValue _.pluck style callback + **/ + last( + array: List, + pluckValue: string): T[]; + + /** + * @see _.last + * @param whereValue _.where style callback + **/ + last( + array: Array, + whereValue: W): T[]; + + /** + * @see _.last + * @param whereValue _.where style callback + **/ + last( + array: List, + whereValue: W): T[]; + } + + //_.lastIndexOf + interface LoDashStatic { + /** + * Gets the index at which the last occurrence of value is found using strict equality + * for comparisons, i.e. ===. If fromIndex is negative, it is used as the offset from the + * end of the collection. + * @param array The array to search. + * @param value The value to search for. + * @param fromIndex The index to search from. + * @return The index of the matched value or -1. + **/ + lastIndexOf( + array: Array, + value: T, + fromIndex?: number): number; + + /** + * @see _.lastIndexOf + **/ + lastIndexOf( + array: List, + value: T, + fromIndex?: number): number; + } + + //_.pull + interface LoDashStatic { + /** + * Removes all provided values from the given array using strict equality for comparisons, + * i.e. ===. + * @param array The array to modify. + * @param values The values to remove. + * @return array. + **/ + pull( + array: Array, + ...values: any[]): any[]; + + /** + * @see _.pull + **/ + pull( + array: List, + ...values: any[]): any[]; + } + + //_.range + interface LoDashStatic { + /** + * Creates an array of numbers (positive and/or negative) progressing from start up + * to but not including end. If start is less than stop a zero-length range is created + * unless a negative step is specified. + * @param start The start of the range. + * @param end The end of the range. + * @param step The value to increment or decrement by. + * @return Returns a new range array. + **/ + range( + start: number, + stop: number, + step?: number): number[]; + + /** + * @see _.range + * @param end The end of the range. + * @return Returns a new range array. + * @note If start is not specified the implementation will never pull the step (step = arguments[2] || 0) + **/ + range(stop: number): number[]; + } + + //_.remove + interface LoDashStatic { + /** + * Removes all elements from an array that the callback returns truey for and returns + * an array of removed elements. The callback is bound to thisArg and invoked with three + * arguments; (value, index, array). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param array The array to modify. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return A new array of removed elements. + **/ + remove( + array: Array, + callback?: ListIterator, + thisArg?: any): any[]; + + /** + * @see _.remove + **/ + remove( + array: List, + callback?: ListIterator, + thisArg?: any): any[]; + + /** + * @see _.remove + * @param pluckValue _.pluck style callback + **/ + remove( + array: Array, + pluckValue?: string): any[]; + + /** + * @see _.remove + * @param pluckValue _.pluck style callback + **/ + remove( + array: List, + pluckValue?: string): any[]; + + /** + * @see _.remove + * @param whereValue _.where style callback + **/ + remove( + array: Array, + wherealue?: Dictionary): any[]; + + /** + * @see _.remove + * @param whereValue _.where style callback + **/ + remove( + array: List, + wherealue?: Dictionary): any[]; + } + + //_.rest + interface LoDashStatic { + /** + * The opposite of _.initial this method gets all but the first element or first n elements of + * an array. If a callback function is provided elements at the beginning of the array are excluded + * from the result as long as the callback returns truey. The callback is bound to thisArg and + * invoked with three arguments; (value, index, array). + * + * If a property name is provided for callback the created "_.pluck" style callback will return + * the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return true + * for elements that have the properties of the given object, else false. + * @param array The array to query. + * @param {(Function|Object|number|string)} [callback=1] The function called per element or the number + * of elements to exclude. If a property name or object is provided it will be used to create a + * ".pluck" or ".where" style callback, respectively. + * @param {*} [thisArg] The this binding of callback. + * @return Returns a slice of array. + **/ + rest(array: Array): T[]; + + /** + * @see _.rest + **/ + rest(array: List): T[]; + + /** + * @see _.rest + **/ + rest( + array: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.rest + **/ + rest( + array: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.rest + **/ + rest( + array: Array, + n: number): T[]; + + /** + * @see _.rest + **/ + rest( + array: List, + n: number): T[]; + + /** + * @see _.rest + **/ + rest( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.rest + **/ + rest( + array: List, + pluckValue: string): T[]; + + /** + * @see _.rest + **/ + rest( + array: Array, + whereValue: W): T[]; + + /** + * @see _.rest + **/ + rest( + array: List, + whereValue: W): T[]; + + /** + * @see _.rest + **/ + drop(array: Array): T[]; + + /** + * @see _.rest + **/ + drop(array: List): T[]; + + /** + * @see _.rest + **/ + drop( + array: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.rest + **/ + drop( + array: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.rest + **/ + drop( + array: Array, + n: number): T[]; + + /** + * @see _.rest + **/ + drop( + array: List, + n: number): T[]; + + /** + * @see _.rest + **/ + drop( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.rest + **/ + drop( + array: List, + pluckValue: string): T[]; + + /** + * @see _.rest + **/ + drop( + array: Array, + whereValue: W): T[]; + + /** + * @see _.rest + **/ + drop( + array: List, + whereValue: W): T[]; + + /** + * @see _.rest + **/ + tail(array: Array): T[]; + + /** + * @see _.rest + **/ + tail(array: List): T[]; + + /** + * @see _.rest + **/ + tail( + array: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.rest + **/ + tail( + array: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.rest + **/ + tail( + array: Array, + n: number): T[]; + + /** + * @see _.rest + **/ + tail( + array: List, + n: number): T[]; + + /** + * @see _.rest + **/ + tail( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.rest + **/ + tail( + array: List, + pluckValue: string): T[]; + + /** + * @see _.rest + **/ + tail( + array: Array, + whereValue: W): T[]; + + /** + * @see _.rest + **/ + tail( + array: List, + whereValue: W): T[]; + } + + //_.sortedIndex + interface LoDashStatic { + /** + * Uses a binary search to determine the smallest index at which a value should be inserted + * into a given sorted array in order to maintain the sort order of the array. If a callback + * is provided it will be executed for value and each element of array to compute their sort + * ranking. The callback is bound to thisArg and invoked with one argument; (value). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param array The sorted list. + * @param value The value to determine its index within `list`. + * @param callback Iterator to compute the sort ranking of each value, optional. + * @return The index at which value should be inserted into array. + **/ + sortedIndex( + array: Array, + value: T, + callback?: (x: T) => TSort, + thisArg?: any): number; + + /** + * @see _.sortedIndex + **/ + sortedIndex( + array: List, + value: T, + callback?: (x: T) => TSort, + thisArg?: any): number; + + /** + * @see _.sortedIndex + * @param pluckValue the _.pluck style callback + **/ + sortedIndex( + array: Array, + value: T, + pluckValue: string): number; + + /** + * @see _.sortedIndex + * @param pluckValue the _.pluck style callback + **/ + sortedIndex( + array: List, + value: T, + pluckValue: string): number; + + /** + * @see _.sortedIndex + * @param pluckValue the _.where style callback + **/ + sortedIndex( + array: Array, + value: T, + whereValue: W): number; + + /** + * @see _.sortedIndex + * @param pluckValue the _.where style callback + **/ + sortedIndex( + array: List, + value: T, + whereValue: W): number; + } + + //_.union + interface LoDashStatic { + /** + * Creates an array of unique values, in order, of the provided arrays using strict + * equality for comparisons, i.e. ===. + * @param arrays The arrays to inspect. + * @return Returns an array of composite values. + **/ + union(...arrays: Array[]): T[]; + + /** + * @see _.union + **/ + union(...arrays: List[]): T[]; + } + + //_.uniq + interface LoDashStatic { + /** + * Creates a duplicate-value-free version of an array using strict equality for comparisons, + * i.e. ===. If the array is sorted, providing true for isSorted will use a faster algorithm. + * If a callback is provided each element of array is passed through the callback before + * uniqueness is computed. The callback is bound to thisArg and invoked with three arguments; + * (value, index, array). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param array Array to remove duplicates from. + * @param isSorted True if `array` is already sorted, optiona, default = false. + * @param iterator Transform the elements of `array` before comparisons for uniqueness. + * @param context 'this' object in `iterator`, optional. + * @return Copy of `array` where all elements are unique. + **/ + uniq(array: Array, isSorted?: boolean): T[]; + + /** + * @see _.uniq + **/ + uniq(array: List, isSorted?: boolean): T[]; + + /** + * @see _.uniq + **/ + uniq( + array: Array, + isSorted: boolean, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.uniq + **/ + uniq( + array: List, + isSorted: boolean, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.uniq + **/ + uniq( + array: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.uniq + **/ + uniq( + array: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + uniq( + array: Array, + isSorted: boolean, + pluckValue: string): T[]; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + uniq( + array: List, + isSorted: boolean, + pluckValue: string): T[]; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + uniq( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + uniq( + array: List, + pluckValue: string): T[]; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + uniq( + array: Array, + isSorted: boolean, + whereValue: W): T[]; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + uniq( + array: List, + isSorted: boolean, + whereValue: W): T[]; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + uniq( + array: Array, + whereValue: W): T[]; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + uniq( + array: List, + whereValue: W): T[]; + + /** + * @see _.uniq + **/ + unique(array: Array, isSorted?: boolean): T[]; + + /** + * @see _.uniq + **/ + unique(array: List, isSorted?: boolean): T[]; + + /** + * @see _.uniq + **/ + unique( + array: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.uniq + **/ + unique( + array: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.uniq + **/ + unique( + array: Array, + isSorted: boolean, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.uniq + **/ + unique( + array: List, + isSorted: boolean, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + unique( + array: Array, + isSorted: boolean, + pluckValue: string): T[]; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + unique( + array: List, + isSorted: boolean, + pluckValue: string): T[]; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + unique( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + unique( + array: List, + pluckValue: string): T[]; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + unique( + array: Array, + whereValue?: W): T[]; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + unique( + array: List, + whereValue?: W): T[]; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + unique( + array: Array, + isSorted: boolean, + whereValue?: W): T[]; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + unique( + array: List, + isSorted: boolean, + whereValue?: W): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.uniq + **/ + uniq(isSorted?: boolean): LoDashArrayWrapper; + + /** + * @see _.uniq + **/ + uniq( + isSorted: boolean, + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.uniq + **/ + uniq( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + uniq( + isSorted: boolean, + pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + uniq(pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + uniq( + isSorted: boolean, + whereValue: W): LoDashArrayWrapper; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + uniq( + whereValue: W): LoDashArrayWrapper; + + /** + * @see _.uniq + **/ + unique(isSorted?: boolean): LoDashArrayWrapper; + + /** + * @see _.uniq + **/ + unique( + isSorted: boolean, + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.uniq + **/ + unique( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + unique( + isSorted: boolean, + pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + unique(pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + unique( + isSorted: boolean, + whereValue: W): LoDashArrayWrapper; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + unique( + whereValue: W): LoDashArrayWrapper; + } + + //_.without + interface LoDashStatic { + /** + * Creates an array excluding all provided values using strict equality for comparisons, i.e. ===. + * @param array The array to filter. + * @param values The value(s) to exclude. + * @return A new array of filtered values. + **/ + without( + array: Array, + ...values: T[]): T[]; + + /** + * @see _.without + **/ + without( + array: List, + ...values: T[]): T[]; + } + + //_.xor + interface LoDashStatic { + /** + * Creates an array that is the symmetric difference of the provided arrays. + * @param array The array to process + * @param others The arrays of values to calculate the symmetric difference. + * @return Returns a new array of filtered values. + **/ + xor( + array: Array, + ...others: Array[]): T[]; + /** + * @see _.xor + **/ + xor( + array: List, + ...others: List[]): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.xor + **/ + xor( + ...others: Array[]): LoDashArrayWrapper; + /** + * @see _.xor + **/ + xor( + ...others: List[]): LoDashArrayWrapper; + } + + //_.zip + interface LoDashStatic { + /** + * Creates an array of grouped elements, the first of which contains the first + * elements of the given arrays, the second of which contains the second elements + * of the given arrays, and so on. + * @param arrays Arrays to process. + * @return A new array of grouped elements. + **/ + zip(...arrays: any[][]): any[][]; + + /** + * @see _.zip + **/ + zip(...arrays: any[]): any[]; + + /** + * @see _.zip + **/ + unzip(...arrays: any[][]): any[][]; + + /** + * @see _.zip + **/ + unzip(...arrays: any[]): any[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.zip + **/ + zip(...arrays: any[][]): _.LoDashArrayWrapper; + + /** + * @see _.zip + **/ + unzip(...arrays: any[]): _.LoDashArrayWrapper; + } + + //_.zipObject + interface LoDashStatic { + /** + * Creates an object composed from arrays of keys and values. Provide either a single + * two dimensional array, i.e. [[key1, value1], [key2, value2]] or two arrays, one of + * keys and one of corresponding values. + * @param keys The array of keys. + * @param values The array of values. + * @return An object composed of the given keys and corresponding values. + **/ + zipObject( + keys: List, + values: List): TResult; + + /** + * @see _.object + **/ + object( + keys: List, + values: List): TResult; + } + + /* ************* + * Collections * + ************* */ + + //_.at + interface LoDashStatic { + /** + * Creates an array of elements from the specified indexes, or keys, of the collection. + * Indexes may be specified as individual arguments or as arrays of indexes. + * @param collection The collection to iterate over. + * @param indexes The indexes of collection to retrieve, specified as individual indexes or + * arrays of indexes. + * @return A new array of elements corresponding to the provided indexes. + **/ + at( + collection: Array, + indexes: number[]): T[]; + + /** + * @see _.at + **/ + at( + collection: List, + indexes: number[]): T[]; + + /** + * @see _.at + **/ + at( + collection: Dictionary, + indexes: number[]): T[]; + + /** + * @see _.at + **/ + at( + collection: Array, + ...indexes: number[]): T[]; + + /** + * @see _.at + **/ + at( + collection: List, + ...indexes: number[]): T[]; + + /** + * @see _.at + **/ + at( + collection: Dictionary, + ...indexes: number[]): T[]; + } + + //_.contains + interface LoDashStatic { + /** + * Checks if a given value is present in a collection using strict equality for comparisons, + * i.e. ===. If fromIndex is negative, it is used as the offset from the end of the collection. + * @param collection The collection to iterate over. + * @param target The value to check for. + * @param fromIndex The index to search from. + * @return True if the target element is found, else false. + **/ + contains( + collection: Array, + target: T, + fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + contains( + collection: List, + target: T, + fromIndex?: number): boolean; + + /** + * @see _.contains + * @param dictionary The dictionary to iterate over. + * @param key The key in the dictionary to search for. + **/ + contains( + dictionary: Dictionary, + key: string, + fromIndex?: number): boolean; + + /** + * @see _.contains + * @param searchString the string to search + * @param targetString the string to search for + **/ + contains( + searchString: string, + targetString: string, + fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + include( + collection: Array, + target: T, + fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + include( + collection: List, + target: T, + fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + include( + dictionary: Dictionary, + key: string, + fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + include( + searchString: string, + targetString: string, + fromIndex?: number): boolean; + } + + //_.countBy + interface LoDashStatic { + /** + * Creates an object composed of keys generated from the results of running each element + * of collection through the callback. The corresponding value of each key is the number + * of times the key was returned by the callback. The callback is bound to thisArg and + * invoked with three arguments; (value, index|key, collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return Returns the composed aggregate object. + **/ + countBy( + collection: Array, + callback?: ListIterator, + thisArg?: any): Dictionary; + + /** + * @see _.countBy + * @param callback Function name + **/ + countBy( + collection: List, + callback?: ListIterator, + thisArg?: any): Dictionary; + + /** + * @see _.countBy + * @param callback Function name + **/ + countBy( + collection: Dictionary, + callback?: ListIterator, + thisArg?: any): Dictionary; + + /** + * @see _.countBy + * @param callback Function name + **/ + countBy( + collection: Array, + callback: string, + thisArg?: any): Dictionary; + + /** + * @see _.countBy + * @param callback Function name + **/ + countBy( + collection: List, + callback: string, + thisArg?: any): Dictionary; + + /** + * @see _.countBy + * @param callback Function name + **/ + countBy( + collection: Dictionary, + callback: string, + thisArg?: any): Dictionary; + } + + interface LoDashArrayWrapper { + /** + * @see _.countBy + **/ + countBy( + callback?: ListIterator, + thisArg?: any): LoDashObjectWrapper>; + + /** + * @see _.countBy + * @param callback Function name + **/ + countBy( + callback: string, + thisArg?: any): LoDashObjectWrapper>; + } + + //_.every + interface LoDashStatic { + /** + * Checks if the given callback returns truey value for all elements of a collection. + * The callback is bound to thisArg and invoked with three arguments; (value, index|key, + * collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return True if all elements passed the callback check, else false. + **/ + every( + collection: Array, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.every + * @param pluckValue _.pluck style callback + **/ + every( + collection: List, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.every + * @param pluckValue _.pluck style callback + **/ + every( + collection: Dictionary, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.every + * @param pluckValue _.pluck style callback + **/ + every( + collection: Array, + pluckValue: string): boolean; + + /** + * @see _.every + * @param pluckValue _.pluck style callback + **/ + every( + collection: List, + pluckValue: string): boolean; + + /** + * @see _.every + * @param pluckValue _.pluck style callback + **/ + every( + collection: Dictionary, + pluckValue: string): boolean; + + /** + * @see _.every + * @param whereValue _.where style callback + **/ + every( + collection: Array, + whereValue: W): boolean; + + /** + * @see _.every + * @param whereValue _.where style callback + **/ + every( + collection: List, + whereValue: W): boolean; + + /** + * @see _.every + * @param whereValue _.where style callback + **/ + every( + collection: Dictionary, + whereValue: W): boolean; + + /** + * @see _.every + **/ + all( + collection: Array, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.every + **/ + all( + collection: List, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.every + **/ + all( + collection: Dictionary, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.every + * @param pluckValue _.pluck style callback + **/ + all( + collection: Array, + pluckValue: string): boolean; + + /** + * @see _.every + * @param pluckValue _.pluck style callback + **/ + all( + collection: List, + pluckValue: string): boolean; + + /** + * @see _.every + * @param pluckValue _.pluck style callback + **/ + all( + collection: Dictionary, + pluckValue: string): boolean; + + /** + * @see _.every + * @param whereValue _.where style callback + **/ + all( + collection: Array, + whereValue: W): boolean; + + /** + * @see _.every + * @param whereValue _.where style callback + **/ + all( + collection: List, + whereValue: W): boolean; + + /** + * @see _.every + * @param whereValue _.where style callback + **/ + all( + collection: Dictionary, + whereValue: W): boolean; + } + + //_.filter + interface LoDashStatic { + /** + * Iterates over elements of a collection, returning an array of all elements the + * callback returns truey for. The callback is bound to thisArg and invoked with three + * arguments; (value, index|key, collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param context The this binding of callback. + * @return Returns a new array of elements that passed the callback check. + **/ + filter( + collection: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.filter + **/ + filter( + collection: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.filter + **/ + filter( + collection: Dictionary, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + filter( + collection: Array, + pluckValue: string): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + filter( + collection: List, + pluckValue: string): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + filter( + collection: Dictionary, + pluckValue: string): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + filter( + collection: Array, + whereValue: W): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + filter( + collection: List, + whereValue: W): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + filter( + collection: Dictionary, + whereValue: W): T[]; + + /** + * @see _.filter + **/ + select( + collection: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.filter + **/ + select( + collection: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.filter + **/ + select( + collection: Dictionary, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + select( + collection: Array, + pluckValue: string): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + select( + collection: List, + pluckValue: string): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + select( + collection: Dictionary, + pluckValue: string): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + select( + collection: Array, + whereValue: W): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + select( + collection: List, + whereValue: W): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + select( + collection: Dictionary, + whereValue: W): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.filter + **/ + filter( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + filter( + pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + filter( + whereValue: W): LoDashArrayWrapper; + + /** + * @see _.filter + **/ + select( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + select( + pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + select( + whereValue: W): LoDashArrayWrapper; + } + + interface LoDashObjectWrapper { + /** + * @see _.filter + **/ + filter( + callback: ObjectIterator, + thisArg?: any): LoDashObjectWrapper; + } + + //_.find + interface LoDashStatic { + /** + * Iterates over elements of a collection, returning the first element that the callback + * returns truey for. The callback is bound to thisArg and invoked with three arguments; + * (value, index|key, collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param collection Searches for a value in this list. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return The found element, else undefined. + **/ + find( + collection: Array, + callback: ListIterator, + thisArg?: any): T; + + /** + * @see _.find + **/ + find( + collection: List, + callback: ListIterator, + thisArg?: any): T; + + /** + * @see _.find + **/ + find( + collection: Dictionary, + callback: ListIterator, + thisArg?: any): T; + + /** + * @see _.find + * @param _.pluck style callback + **/ + find( + collection: Array, + whereValue: W): T; + + /** + * @see _.find + * @param _.pluck style callback + **/ + find( + collection: List, + whereValue: W): T; + + /** + * @see _.find + * @param _.pluck style callback + **/ + find( + collection: Dictionary, + whereValue: W): T; + + /** + * @see _.find + * @param _.where style callback + **/ + find( + collection: Array, + pluckValue: string): T; + + /** + * @see _.find + * @param _.where style callback + **/ + find( + collection: List, + pluckValue: string): T; + + /** + * @see _.find + * @param _.where style callback + **/ + find( + collection: Dictionary, + pluckValue: string): T; + + /** + * @see _.find + **/ + detect( + collection: Array, + callback: ListIterator, + thisArg?: any): T; + + /** + * @see _.find + **/ + detect( + collection: List, + callback: ListIterator, + thisArg?: any): T; + + /** + * @see _.find + **/ + detect( + collection: Dictionary, + callback: ListIterator, + thisArg?: any): T; + + /** + * @see _.find + * @param _.pluck style callback + **/ + detect( + collection: Array, + whereValue: W): T; + + /** + * @see _.find + * @param _.pluck style callback + **/ + detect( + collection: List, + whereValue: W): T; + + /** + * @see _.find + * @param _.pluck style callback + **/ + detect( + collection: Dictionary, + whereValue: W): T; + + /** + * @see _.find + * @param _.where style callback + **/ + detect( + collection: Array, + pluckValue: string): T; + + /** + * @see _.find + * @param _.where style callback + **/ + detect( + collection: List, + pluckValue: string): T; + + /** + * @see _.find + * @param _.where style callback + **/ + detect( + collection: Dictionary, + pluckValue: string): T; + + /** + * @see _.find + **/ + findWhere( + collection: Array, + callback: ListIterator, + thisArg?: any): T; + + /** + * @see _.find + **/ + findWhere( + collection: List, + callback: ListIterator, + thisArg?: any): T; + + /** + * @see _.find + **/ + findWhere( + collection: Dictionary, + callback: ListIterator, + thisArg?: any): T; + + /** + * @see _.find + * @param _.pluck style callback + **/ + findWhere( + collection: Array, + whereValue: W): T; + + /** + * @see _.find + * @param _.pluck style callback + **/ + findWhere( + collection: List, + whereValue: W): T; + + /** + * @see _.find + * @param _.pluck style callback + **/ + findWhere( + collection: Dictionary, + whereValue: W): T; + + /** + * @see _.find + * @param _.where style callback + **/ + findWhere( + collection: Array, + pluckValue: string): T; + + /** + * @see _.find + * @param _.where style callback + **/ + findWhere( + collection: List, + pluckValue: string): T; + + /** + * @see _.find + * @param _.where style callback + **/ + findWhere( + collection: Dictionary, + pluckValue: string): T; + } + + interface LoDashArrayWrapper { + /** + * @see _.find + */ + find( + callback: ListIterator, + thisArg?: any): T; + /** + * @see _.find + * @param _.where style callback + */ + find( + whereValue: W): T; + + /** + * @see _.find + * @param _.where style callback + */ + find( + pluckValue: string): T; + } + + //_.findLast + interface LoDashStatic { + /** + * This method is like _.find except that it iterates over elements of a collection from + * right to left. + * @param collection Searches for a value in this list. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return The found element, else undefined. + **/ + findLast( + collection: Array, + callback: ListIterator, + thisArg?: any): T; + + /** + * @see _.find + **/ + findLast( + collection: List, + callback: ListIterator, + thisArg?: any): T; + + /** + * @see _.find + **/ + findLast( + collection: Dictionary, + callback: ListIterator, + thisArg?: any): T; + + /** + * @see _.find + * @param _.pluck style callback + **/ + findLast( + collection: Array, + whereValue: W): T; + + /** + * @see _.find + * @param _.pluck style callback + **/ + findLast( + collection: List, + whereValue: W): T; + + /** + * @see _.find + * @param _.pluck style callback + **/ + findLast( + collection: Dictionary, + whereValue: W): T; + + /** + * @see _.find + * @param _.where style callback + **/ + findLast( + collection: Array, + pluckValue: string): T; + + /** + * @see _.find + * @param _.where style callback + **/ + findLast( + collection: List, + pluckValue: string): T; + + /** + * @see _.find + * @param _.where style callback + **/ + findLast( + collection: Dictionary, + pluckValue: string): T; + } + + interface LoDashArrayWrapper { + /** + * @see _.findLast + */ + findLast( + callback: ListIterator, + thisArg?: any): T; + /** + * @see _.findLast + * @param _.where style callback + */ + findLast( + whereValue: W): T; + + /** + * @see _.findLast + * @param _.where style callback + */ + findLast( + pluckValue: string): T; + } + + //_.forEach + interface LoDashStatic { + /** + * Iterates over elements of a collection, executing the callback for each element. + * The callback is bound to thisArg and invoked with three arguments; (value, index|key, + * collection). Callbacks may exit iteration early by explicitly returning false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + **/ + forEach( + collection: Array, + callback: ListIterator, + thisArg?: any): Array; + + /** + * @see _.forEach + **/ + forEach( + collection: List, + callback: ListIterator, + thisArg?: any): List; + + /** + * @see _.forEach + **/ + forEach( + object: Dictionary, + callback: ObjectIterator, + thisArg?: any): Dictionary; + + /** + * @see _.each + **/ + forEach( + object: T, + callback: ObjectIterator, + thisArg?: any): T + + /** + * @see _.forEach + **/ + each( + collection: Array, + callback: ListIterator, + thisArg?: any): Array; + + /** + * @see _.forEach + **/ + each( + collection: List, + callback: ListIterator, + thisArg?: any): List; + + /** + * @see _.forEach + * @param object The object to iterate over + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + **/ + each( + object: Dictionary, + callback: ObjectIterator, + thisArg?: any): Dictionary; + + /** + * @see _.each + **/ + each( + object: T, + callback: ObjectIterator, + thisArg?: any): T + } + + interface LoDashArrayWrapper { + /** + * @see _.forEach + **/ + forEach( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.forEach + **/ + each( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + } + + interface LoDashObjectWrapper { + /** + * @see _.forEach + **/ + forEach( + callback: ObjectIterator, + thisArg?: any): LoDashObjectWrapper; + + /** + * @see _.forEach + **/ + each( + callback: ObjectIterator, + thisArg?: any): LoDashObjectWrapper; + } + + //_.forEachRight + interface LoDashStatic { + /** + * This method is like _.forEach except that it iterates over elements of a + * collection from right to left. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + **/ + forEachRight( + collection: Array, + callback: ListIterator, + thisArg?: any): Array; + + /** + * @see _.forEachRight + **/ + forEachRight( + collection: List, + callback: ListIterator, + thisArg?: any): List; + + /** + * @see _.forEachRight + **/ + forEachRight( + object: Dictionary, + callback: ObjectIterator, + thisArg?: any): Dictionary; + + /** + * @see _.forEachRight + **/ + eachRight( + collection: Array, + callback: ListIterator, + thisArg?: any): Array; + + /** + * @see _.forEachRight + **/ + eachRight( + collection: List, + callback: ListIterator, + thisArg?: any): List; + + /** + * @see _.forEachRight + * @param object The object to iterate over + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + **/ + eachRight( + object: Dictionary, + callback: ObjectIterator, + thisArg?: any): Dictionary; + } + + interface LoDashArrayWrapper { + /** + * @see _.forEachRight + **/ + forEachRight( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.forEachRight + **/ + eachRight( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + } + + interface LoDashObjectWrapper { + /** + * @see _.forEachRight + **/ + forEachRight( + callback: ObjectIterator, + thisArg?: any): LoDashObjectWrapper>; + + /** + * @see _.forEachRight + * @param object The object to iterate over + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + **/ + eachRight( + callback: ObjectIterator, + thisArg?: any): LoDashObjectWrapper>; + } + + //_.groupBy + interface LoDashStatic { + /** + * Creates an object composed of keys generated from the results of running each element + * of a collection through the callback. The corresponding value of each key is an array + * of the elements responsible for generating the key. The callback is bound to thisArg + * and invoked with three arguments; (value, index|key, collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return Returns the composed aggregate object. + **/ + groupBy( + collection: Array, + callback?: ListIterator, + thisArg?: any): Dictionary; + + /** + * @see _.groupBy + **/ + groupBy( + collection: List, + callback?: ListIterator, + thisArg?: any): Dictionary; + + /** + * @see _.groupBy + * @param pluckValue _.pluck style callback + **/ + groupBy( + collection: Array, + pluckValue: string): Dictionary; + + /** + * @see _.groupBy + * @param pluckValue _.pluck style callback + **/ + groupBy( + collection: List, + pluckValue: string): Dictionary; + + /** + * @see _.groupBy + * @param whereValue _.where style callback + **/ + groupBy( + collection: Array, + whereValue: W): Dictionary; + + /** + * @see _.groupBy + * @param whereValue _.where style callback + **/ + groupBy( + collection: List, + whereValue: W): Dictionary; + + /** + * @see _.groupBy + **/ + groupBy( + collection: Dictionary, + callback?: ListIterator, + thisArg?: any): Dictionary; + + /** + * @see _.groupBy + * @param pluckValue _.pluck style callback + **/ + groupBy( + collection: Dictionary, + pluckValue: string): Dictionary; + + /** + * @see _.groupBy + * @param whereValue _.where style callback + **/ + groupBy( + collection: Dictionary, + whereValue: W): Dictionary; + } + + interface LoDashArrayWrapper { + /** + * @see _.groupBy + **/ + groupBy( + callback: ListIterator, + thisArg?: any): _.LoDashObjectWrapper<_.Dictionary>; + + /** + * @see _.groupBy + **/ + groupBy( + pluckValue: string): _.LoDashObjectWrapper<_.Dictionary>; + + /** + * @see _.groupBy + **/ + groupBy( + whereValue: W): _.LoDashObjectWrapper<_.Dictionary>; + } + + interface LoDashObjectWrapper { + /** + * @see _.groupBy + **/ + groupBy( + callback: ListIterator, + thisArg?: any): _.LoDashObjectWrapper<_.Dictionary>; + + /** + * @see _.groupBy + **/ + groupBy( + pluckValue: string): _.LoDashObjectWrapper<_.Dictionary>; + + /** + * @see _.groupBy + **/ + groupBy( + whereValue: W): _.LoDashObjectWrapper<_.Dictionary>; + } + + //_.indexBy + interface LoDashStatic { + /** + * Creates an object composed of keys generated from the results of running each element + * of the collection through the given callback. The corresponding value of each key is + * the last element responsible for generating the key. The callback is bound to thisArg + * and invoked with three arguments; (value, index|key, collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return Returns the composed aggregate object. + **/ + indexBy( + list: Array, + iterator: ListIterator, + context?: any): Dictionary; + + /** + * @see _.indexBy + **/ + indexBy( + list: List, + iterator: ListIterator, + context?: any): Dictionary; + + /** + * @see _.indexBy + * @param pluckValue _.pluck style callback + **/ + indexBy( + collection: Array, + pluckValue: string): Dictionary; + + /** + * @see _.indexBy + * @param pluckValue _.pluck style callback + **/ + indexBy( + collection: List, + pluckValue: string): Dictionary; + + /** + * @see _.indexBy + * @param whereValue _.where style callback + **/ + indexBy( + collection: Array, + whereValue: W): Dictionary; + + /** + * @see _.indexBy + * @param whereValue _.where style callback + **/ + indexBy( + collection: List, + whereValue: W): Dictionary; + } + + //_.invoke + interface LoDashStatic { + /** + * Invokes the method named by methodName on each element in the collection returning + * an array of the results of each invoked method. Additional arguments will be provided + * to each invoked method. If methodName is a function it will be invoked for, and this + * bound to, each element in the collection. + * @param collection The collection to iterate over. + * @param methodName The name of the method to invoke. + * @param args Arguments to invoke the method with. + **/ + invoke( + collection: Array, + methodName: string, + ...args: any[]): any; + + /** + * @see _.invoke + **/ + invoke( + collection: List, + methodName: string, + ...args: any[]): any; + + /** + * @see _.invoke + **/ + invoke( + collection: Dictionary, + methodName: string, + ...args: any[]): any; + + /** + * @see _.invoke + **/ + invoke( + collection: Array, + method: Function, + ...args: any[]): any; + + /** + * @see _.invoke + **/ + invoke( + collection: List, + method: Function, + ...args: any[]): any; + + /** + * @see _.invoke + **/ + invoke( + collection: Dictionary, + method: Function, + ...args: any[]): any; + } + + //_.map + interface LoDashStatic { + /** + * Creates an array of values by running each element in the collection through the callback. + * The callback is bound to thisArg and invoked with three arguments; (value, index|key, + * collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will return + * the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return true + * for elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param theArg The this binding of callback. + * @return The mapped array result. + **/ + map( + collection: Array, + callback: ListIterator, + thisArg?: any): TResult[]; + + /** + * @see _.map + **/ + map( + collection: List, + callback: ListIterator, + thisArg?: any): TResult[]; + + /** + * @see _.map + * @param object The object to iterate over. + * @param callback The function called per iteration. + * @param thisArg `this` object in `iterator`, optional. + * @return The mapped object result. + **/ + map( + object: Dictionary, + callback: ObjectIterator, + thisArg?: any): TResult[]; + + /** + * @see _.map + * @param pluckValue _.pluck style callback + **/ + map( + collection: Array, + pluckValue: string): TResult[]; + + /** + * @see _.map + * @param pluckValue _.pluck style callback + **/ + map( + collection: List, + pluckValue: string): TResult[]; + + /** + * @see _.map + **/ + collect( + collection: Array, + callback: ListIterator, + thisArg?: any): TResult[]; + + /** + * @see _.map + **/ + collect( + collection: List, + callback: ListIterator, + thisArg?: any): TResult[]; + + /** + * @see _.map + **/ + collect( + object: Dictionary, + callback: ObjectIterator, + thisArg?: any): TResult[]; + + /** + * @see _.map + **/ + collect( + collection: Array, + pluckValue: string): TResult[]; + + /** + * @see _.map + **/ + collect( + collection: List, + pluckValue: string): TResult[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.map + **/ + map( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.map + * @param pluckValue _.pluck style callback + **/ + map( + pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.map + **/ + collect( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.map + **/ + collect( + pluckValue: string): LoDashArrayWrapper; + } + + interface LoDashObjectWrapper { + /** + * @see _.map + **/ + map( + callback: ObjectIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.map + **/ + collect( + callback: ObjectIterator, + thisArg?: any): LoDashArrayWrapper; + } + + //_.max + interface LoDashStatic { + /** + * Retrieves the maximum value of a collection. If the collection is empty or falsey -Infinity is + * returned. If a callback is provided it will be executed for each value in the collection to + * generate the criterion by which the value is ranked. The callback is bound to thisArg and invoked + * with three arguments; (value, index, collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will return the + * property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return true for + * elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return Returns the maximum value. + **/ + max( + collection: Array, + callback?: ListIterator, + thisArg?: any): T; + + /** + * @see _.max + **/ + max( + collection: List, + callback?: ListIterator, + thisArg?: any): T; + + /** + * @see _.max + **/ + max( + collection: Dictionary, + callback?: ListIterator, + thisArg?: any): T; + + /** + * @see _.max + * @param pluckValue _.pluck style callback + **/ + max( + collection: Array, + pluckValue: string): T; + + /** + * @see _.max + * @param pluckValue _.pluck style callback + **/ + max( + collection: List, + pluckValue: string): T; + + /** + * @see _.max + * @param pluckValue _.pluck style callback + **/ + max( + collection: Dictionary, + pluckValue: string): T; + + /** + * @see _.max + * @param whereValue _.where style callback + **/ + max( + collection: Array, + whereValue: W): T; + + /** + * @see _.max + * @param whereValue _.where style callback + **/ + max( + collection: List, + whereValue: W): T; + + /** + * @see _.max + * @param whereValue _.where style callback + **/ + max( + collection: Dictionary, + whereValue: W): T; + } + + interface LoDashArrayWrapper { + /** + * @see _.max + **/ + max( + callback?: ListIterator, + thisArg?: any): LoDashWrapper; + + /** + * @see _.max + * @param pluckValue _.pluck style callback + **/ + max( + pluckValue: string): LoDashWrapper; + + /** + * @see _.max + * @param whereValue _.where style callback + **/ + max( + whereValue: W): LoDashWrapper; + } + + //_.min + interface LoDashStatic { + /** + * Retrieves the minimum value of a collection. If the collection is empty or falsey + * Infinity is returned. If a callback is provided it will be executed for each value + * in the collection to generate the criterion by which the value is ranked. The callback + * is bound to thisArg and invoked with three arguments; (value, index, collection). + * + * If a property name is provided for callback the created "_.pluck" style callback + * will return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will + * return true for elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return Returns the maximum value. + **/ + min( + collection: Array, + callback?: ListIterator, + thisArg?: any): T; + + /** + * @see _.min + **/ + min( + collection: List, + callback?: ListIterator, + thisArg?: any): T; + + /** + * @see _.min + **/ + min( + collection: Dictionary, + callback?: ListIterator, + thisArg?: any): T; + + /** + * @see _.min + * @param pluckValue _.pluck style callback + **/ + min( + collection: Array, + pluckValue: string): T; + + /** + * @see _.min + * @param pluckValue _.pluck style callback + **/ + min( + collection: List, + pluckValue: string): T; + + /** + * @see _.min + * @param pluckValue _.pluck style callback + **/ + min( + collection: Dictionary, + pluckValue: string): T; + + /** + * @see _.min + * @param whereValue _.where style callback + **/ + min( + collection: Array, + whereValue: W): T; + + /** + * @see _.min + * @param whereValue _.where style callback + **/ + min( + collection: List, + whereValue: W): T; + + /** + * @see _.min + * @param whereValue _.where style callback + **/ + min( + collection: Dictionary, + whereValue: W): T; + } + + interface LoDashArrayWrapper { + /** + * @see _.min + **/ + min( + callback?: ListIterator, + thisArg?: any): LoDashWrapper; + + /** + * @see _.min + * @param pluckValue _.pluck style callback + **/ + min( + pluckValue: string): LoDashWrapper; + + /** + * @see _.min + * @param whereValue _.where style callback + **/ + min( + whereValue: W): LoDashWrapper; + } + + //_.pluck + interface LoDashStatic { + /** + * Retrieves the value of a specified property from all elements in the collection. + * @param collection The collection to iterate over. + * @param property The property to pluck. + * @return A new array of property values. + **/ + pluck( + collection: Array, + property: string): any[]; + + /** + * @see _.pluck + **/ + pluck( + collection: List, + property: string): any[]; + + /** + * @see _.pluck + **/ + pluck( + collection: Dictionary, + property: string): any[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.pluck + **/ + pluck( + property: string): LoDashArrayWrapper; + } + + interface LoDashObjectWrapper { + /** + * @see _.pluck + **/ + pluck( + property: string): LoDashArrayWrapper; + } + + //_.reduce + interface LoDashStatic { + /** + * Reduces a collection to a value which is the accumulated result of running each + * element in the collection through the callback, where each successive callback execution + * consumes the return value of the previous execution. If accumulator is not provided the + * first element of the collection will be used as the initial accumulator value. The callback + * is bound to thisArg and invoked with four arguments; (accumulator, value, index|key, collection). + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param accumulator Initial value of the accumulator. + * @param thisArg The this binding of callback. + * @return Returns the accumulated value. + **/ + reduce( + collection: Array, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + reduce( + collection: List, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + reduce( + collection: Dictionary, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + reduce( + collection: Array, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + reduce( + collection: List, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + reduce( + collection: Dictionary, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + collection: Array, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + collection: List, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + collection: Dictionary, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + collection: Array, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + collection: List, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + collection: Dictionary, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + collection: Array, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + collection: List, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + collection: Dictionary, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + collection: Array, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + collection: List, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + collection: Dictionary, + callback: MemoIterator, + thisArg?: any): TResult; + } + + interface LoDashArrayWrapper { + /** + * @see _.reduce + **/ + reduce( + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + reduce( + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + callback: MemoIterator, + thisArg?: any): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.reduce + **/ + reduce( + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + reduce( + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + callback: MemoIterator, + thisArg?: any): TResult; + } + + //_.reduceRight + interface LoDashStatic { + /** + * This method is like _.reduce except that it iterates over elements of a collection from + * right to left. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param accumulator Initial value of the accumulator. + * @param thisArg The this binding of callback. + * @return The accumulated value. + **/ + reduceRight( + collection: Array, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + reduceRight( + collection: List, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + reduceRight( + collection: Dictionary, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + reduceRight( + collection: Array, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + reduceRight( + collection: List, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + reduceRight( + collection: Dictionary, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + foldr( + collection: Array, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + foldr( + collection: List, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + foldr( + collection: Dictionary, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + foldr( + collection: Array, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + foldr( + collection: List, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + foldr( + collection: Dictionary, + callback: MemoIterator, + thisArg?: any): TResult; + } + + //_.reject + interface LoDashStatic { + /** + * The opposite of _.filter this method returns the elements of a collection that + * the callback does not return truey for. + * + * If a property name is provided for callback the created "_.pluck" style callback + * will return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will + * return true for elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return A new array of elements that failed the callback check. + **/ + reject( + collection: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.reject + **/ + reject( + collection: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.reject + **/ + reject( + collection: Dictionary, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.reject + * @param pluckValue _.pluck style callback + **/ + reject( + collection: Array, + pluckValue: string): T[]; + + /** + * @see _.reject + * @param pluckValue _.pluck style callback + **/ + reject( + collection: List, + pluckValue: string): T[]; + + /** + * @see _.reject + * @param pluckValue _.pluck style callback + **/ + reject( + collection: Dictionary, + pluckValue: string): T[]; + + /** + * @see _.reject + * @param whereValue _.where style callback + **/ + reject( + collection: Array, + whereValue: W): T[]; + + /** + * @see _.reject + * @param whereValue _.where style callback + **/ + reject( + collection: List, + whereValue: W): T[]; + + /** + * @see _.reject + * @param whereValue _.where style callback + **/ + reject( + collection: Dictionary, + whereValue: W): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.reject + **/ + reject( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.reject + * @param pluckValue _.pluck style callback + **/ + reject(pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.reject + * @param whereValue _.where style callback + **/ + reject(whereValue: W): LoDashArrayWrapper; + } + + //_.sample + interface LoDashStatic { + /** + * Retrieves a random element or n random elements from a collection. + * @param collection The collection to sample. + * @return Returns the random sample(s) of collection. + **/ + sample(collection: Array): T; + + /** + * @see _.sample + **/ + sample(collection: List): T; + + /** + * @see _.sample + **/ + sample(collection: Dictionary): T; + + /** + * @see _.sample + * @param n The number of elements to sample. + **/ + sample(collection: Array, n: number): T[]; + + /** + * @see _.sample + * @param n The number of elements to sample. + **/ + sample(collection: List, n: number): T[]; + + /** + * @see _.sample + * @param n The number of elements to sample. + **/ + sample(collection: Dictionary, n: number): T[]; + } + + //_.shuffle + interface LoDashStatic { + /** + * Creates an array of shuffled values, using a version of the Fisher-Yates shuffle. + * See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle. + * @param collection The collection to shuffle. + * @return Returns a new shuffled collection. + **/ + shuffle(collection: Array): T[]; + + /** + * @see _.shuffle + **/ + shuffle(collection: List): T[]; + + /** + * @see _.shuffle + **/ + shuffle(collection: Dictionary): T[]; + } + + //_.size + interface LoDashStatic { + /** + * Gets the size of the collection by returning collection.length for arrays and array-like + * objects or the number of own enumerable properties for objects. + * @param collection The collection to inspect. + * @return collection.length + **/ + size(collection: Array): number; + + /** + * @see _.size + **/ + size(collection: List): number; + + /** + * @see _.size + * @param object The object to inspect + * @return The number of own enumerable properties. + **/ + size(object: T): number; + + /** + * @see _.size + * @param aString The string to inspect + * @return The length of aString + **/ + size(aString: string): number; + } + + //_.some + interface LoDashStatic { + /** + * Checks if the callback returns a truey value for any element of a collection. The function + * returns as soon as it finds a passing value and does not iterate over the entire collection. + * The callback is bound to thisArg and invoked with three arguments; (value, index|key, collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will return + * the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return true for + * elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return True if any element passed the callback check, else false. + **/ + some( + collection: Array, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.some + **/ + some( + collection: List, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.some + **/ + some( + collection: Dictionary, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.some + **/ + some( + collection: {}, + callback?: ListIterator<{}, boolean>, + thisArg?: any): boolean; + + /** + * @see _.some + * @param pluckValue _.pluck style callback + **/ + some( + collection: Array, + pluckValue: string): boolean; + + /** + * @see _.some + * @param pluckValue _.pluck style callback + **/ + some( + collection: List, + pluckValue: string): boolean; + + /** + * @see _.some + * @param pluckValue _.pluck style callback + **/ + some( + collection: Dictionary, + pluckValue: string): boolean; + + /** + * @see _.some + * @param whereValue _.where style callback + **/ + some( + collection: Array, + whereValue: W): boolean; + + /** + * @see _.some + * @param whereValue _.where style callback + **/ + some( + collection: List, + whereValue: W): boolean; + + /** + * @see _.some + * @param whereValue _.where style callback + **/ + some( + collection: Dictionary, + whereValue: W): boolean; + + /** + * @see _.some + **/ + any( + collection: Array, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.some + **/ + any( + collection: List, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.some + **/ + any( + collection: Dictionary, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.some + **/ + any( + collection: {}, + callback?: ListIterator<{}, boolean>, + thisArg?: any): boolean; + + /** + * @see _.some + * @param pluckValue _.pluck style callback + **/ + any( + collection: Array, + pluckValue: string): boolean; + + /** + * @see _.some + * @param pluckValue _.pluck style callback + **/ + any( + collection: List, + pluckValue: string): boolean; + + /** + * @see _.some + * @param pluckValue _.pluck style callback + **/ + any( + collection: Dictionary, + pluckValue: string): boolean; + + /** + * @see _.some + * @param whereValue _.where style callback + **/ + any( + collection: Array, + whereValue: W): boolean; + + /** + * @see _.some + * @param whereValue _.where style callback + **/ + any( + collection: List, + whereValue: W): boolean; + + /** + * @see _.some + * @param whereValue _.where style callback + **/ + any( + collection: Dictionary, + whereValue: W): boolean; + } + + //_.sortBy + interface LoDashStatic { + /** + * Creates an array of elements, sorted in ascending order by the results of running each + * element in a collection through the callback. This method performs a stable sort, that + * is, it will preserve the original sort order of equal elements. The callback is bound + * to thisArg and invoked with three arguments; (value, index|key, collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return A new array of sorted elements. + **/ + sortBy( + collection: Array, + callback?: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.sortBy + **/ + sortBy( + collection: List, + callback?: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.sortBy + * @param pluckValue _.pluck style callback + **/ + sortBy( + collection: Array, + pluckValue: string): T[]; + + /** + * @see _.sortBy + * @param pluckValue _.pluck style callback + **/ + sortBy( + collection: List, + pluckValue: string): T[]; + + /** + * @see _.sortBy + * @param whereValue _.where style callback + **/ + sortBy( + collection: Array, + whereValue: W): T[]; + + /** + * @see _.sortBy + * @param whereValue _.where style callback + **/ + sortBy( + collection: List, + whereValue: W): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.sortBy + **/ + sortBy( + callback?: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.sortBy + * @param pluckValue _.pluck style callback + **/ + sortBy(pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.sortBy + * @param whereValue _.where style callback + **/ + sortBy(whereValue: W): LoDashArrayWrapper; + } + + //_.toArray + interface LoDashStatic { + /** + * Converts the collection to an array. + * @param collection The collection to convert. + * @return The new converted array. + **/ + toArray(collection: Array): T[]; + + /** + * @see _.toArray + **/ + toArray(collection: List): T[]; + + /** + * @see _.toArray + **/ + toArray(collection: Dictionary): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.toArray + **/ + toArray(): LoDashArrayWrapper; + } + + interface LoDashObjectWrapper { + /** + * @see _.toArray + **/ + toArray(): LoDashArrayWrapper; + } + + //_.where + interface LoDashStatic { + /** + * Performs a deep comparison of each element in a collection to the given properties + * object, returning an array of all elements that have equivalent property values. + * @param collection The collection to iterate over. + * @param properties The object of property values to filter by. + * @return A new array of elements that have the given properties. + **/ + where( + list: Array, + properties: U): T[]; + + /** + * @see _.where + **/ + where( + list: List, + properties: U): T[]; + + /** + * @see _.where + **/ + where( + list: Dictionary, + properties: U): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.where + **/ + where(properties: U): LoDashArrayWrapper; + } + + /************* + * Functions * + *************/ + + //_.after + interface LoDashStatic { + /** + * Creates a function that executes func, with the this binding and arguments of the + * created function, only after being called n times. + * @param n The number of times the function must be called before func is executed. + * @param func The function to restrict. + * @return The new restricted function. + **/ + after( + n: number, + func: Function): Function; + } + + interface LoDashWrapper { + /** + * @see _.after + **/ + after(func: Function): LoDashObjectWrapper; + } + + //_.bind + interface LoDashStatic { + /** + * Creates a function that, when called, invokes func with the this binding of thisArg + * and prepends any additional bind arguments to those provided to the bound function. + * @param func The function to bind. + * @param thisArg The this binding of func. + * @param args Arguments to be partially applied. + * @return The new bound function. + **/ + bind( + func: Function, + thisArg: any, + ...args: any[]): () => any; + } + + interface LoDashObjectWrapper { + /** + * @see _.bind + **/ + bind( + thisArg: any, + ...args: any[]): LoDashObjectWrapper<() => any>; + } + + //_.bindAll + interface LoDashStatic { + /** + * Binds methods of an object to the object itself, overwriting the existing method. Method + * names may be specified as individual arguments or as arrays of method names. If no method + * names are provided all the function properties of object will be bound. + * @param object The object to bind and assign the bound methods to. + * @param methodNames The object method names to bind, specified as individual method names + * or arrays of method names. + * @return object + **/ + bindAll( + object: T, + ...methodNames: string[]): T; + } + + interface LoDashObjectWrapper { + /** + * @see _.bindAll + **/ + bindAll(...methodNames: string[]): LoDashWrapper; + } + + //_.bindKey + interface LoDashStatic { + /** + * Creates a function that, when called, invokes the method at object[key] and prepends any + * additional bindKey arguments to those provided to the bound function. This method differs + * from _.bind by allowing bound functions to reference methods that will be redefined or don't + * yet exist. See http://michaux.ca/articles/lazy-function-definition-pattern. + * @param object The object the method belongs to. + * @param key The key of the method. + * @param args Arguments to be partially applied. + * @return The new bound function. + **/ + bindKey( + object: T, + key: string, + ...args: any[]): Function; + } + + interface LoDashObjectWrapper { + /** + * @see _.bindKey + **/ + bindKey( + key: string, + ...args: any[]): LoDashObjectWrapper; + } + + //_.compose + interface LoDashStatic { + /** + * Creates a function that is the composition of the provided functions, where each function + * consumes the return value of the function that follows. For example, composing the functions + * f(), g(), and h() produces f(g(h())). Each function is executed with the this binding of the + * composed function. + * @param funcs Functions to compose. + * @return The new composed function. + **/ + compose(...funcs: Function[]): Function; + } + + interface LoDashObjectWrapper { + /** + * @see _.compose + **/ + compose(...funcs: Function[]): LoDashObjectWrapper; + } + + //_.createCallback + interface LoDashStatic { + /** + * Produces a callback bound to an optional thisArg. If func is a property name the created + * callback will return the property value for a given element. If func is an object the created + * callback will return true for elements that contain the equivalent object properties, + * otherwise it will return false. + * @param func The value to convert to a callback. + * @param thisArg The this binding of the created callback. + * @param argCount The number of arguments the callback accepts. + * @return A callback function. + **/ + createCallback( + func: string, + thisArg?: any, + argCount?: number): () => any; + + /** + * @see _.createCallback + **/ + createCallback( + func: Dictionary, + thisArg?: any, + argCount?: number): () => boolean; + } + + interface LoDashWrapper { + /** + * @see _.createCallback + **/ + createCallback( + thisArg?: any, + argCount?: number): LoDashObjectWrapper<() => any>; + } + + interface LoDashObjectWrapper { + /** + * @see _.createCallback + **/ + createCallback( + thisArg?: any, + argCount?: number): LoDashObjectWrapper<() => any>; + } + + //_.curry + interface LoDashStatic { + /** + * Creates a function which accepts one or more arguments of func that when invoked either + * executes func returning its result, if all func arguments have been provided, or returns + * a function that accepts one or more of the remaining func arguments, and so on. The arity + * of func can be specified if func.length is not sufficient. + * @param func The function to curry. + * @param arity The arity of func. + * @return The new curried function. + **/ + curry( + func: Function, + arity?: number): Function; + } + + interface LoDashObjectWrapper { + /** + * @see _.curry + **/ + curry(arity?: number): LoDashObjectWrapper; + } + + //_.debounce + interface LoDashStatic { + /** + * Creates a function that will delay the execution of func until after wait milliseconds have + * elapsed since the last time it was invoked. Provide an options object to indicate that func + * should be invoked on the leading and/or trailing edge of the wait timeout. Subsequent calls + * to the debounced function will return the result of the last func call. + * + * Note: If leading and trailing options are true func will be called on the trailing edge of + * the timeout only if the the debounced function is invoked more than once during the wait + * timeout. + * @param func The function to debounce. + * @param wait The number of milliseconds to delay. + * @param options The options object. + * @param options.leading Specify execution on the leading edge of the timeout. + * @param options.maxWait The maximum time func is allowed to be delayed before it’s called. + * @param options.trailing Specify execution on the trailing edge of the timeout. + * @return The new debounced function. + **/ + debounce( + func: T, + wait: number, + options?: DebounceSettings): T; + } + + interface LoDashObjectWrapper { + /** + * @see _.debounce + **/ + debounce( + wait: number, + options?: DebounceSettings): LoDashObjectWrapper; + } + + interface DebounceSettings { + /** + * Specify execution on the leading edge of the timeout. + **/ + leading?: boolean; + + /** + * The maximum time func is allowed to be delayed before it’s called. + **/ + maxWait?: number; + + /** + * Specify execution on the trailing edge of the timeout. + **/ + trailing?: boolean; + } + + //_.defer + interface LoDashStatic { + /** + * Defers executing the func function until the current call stack has cleared. Additional + * arguments will be provided to func when it is invoked. + * @param func The function to defer. + * @param args Arguments to invoke the function with. + * @return The timer id. + **/ + defer( + func: Function, + ...args: any[]): number; + } + + interface LoDashObjectWrapper { + /** + * @see _.defer + **/ + defer(...args: any[]): LoDashWrapper; + } + + //_.delay + interface LoDashStatic { + /** + * Executes the func function after wait milliseconds. Additional arguments will be provided + * to func when it is invoked. + * @param func The function to delay. + * @param wait The number of milliseconds to delay execution. + * @param args Arguments to invoke the function with. + * @return The timer id. + **/ + delay( + func: Function, + wait: number, + ...args: any[]): number; + } + + interface LoDashObjectWrapper { + /** + * @see _.delay + **/ + delay( + wait: number, + ...args: any[]): LoDashWrapper; + } + + //_.memoize + interface LoDashStatic { + /** + * Creates a function that memoizes the result of func. If resolver is provided it will be + * used to determine the cache key for storing the result based on the arguments provided to + * the memoized function. By default, the first argument provided to the memoized function is + * used as the cache key. The func is executed with the this binding of the memoized function. + * The result cache is exposed as the cache property on the memoized function. + * @param func Computationally expensive function that will now memoized results. + * @param resolver Hash function for storing the result of `fn`. + * @return Returns the new memoizing function. + **/ + memoize( + func: T, + resolver?: Function): T; + } + + //_.once + interface LoDashStatic { + /** + * Creates a function that is restricted to execute func once. Repeat calls to the function + * will return the value of the first call. The func is executed with the this binding of the + * created function. + * @param func Function to only execute once. + * @return The new restricted function. + **/ + once(func: T): T; + } + + //_.partial + interface LoDashStatic { + /** + * Creates a function that, when called, invokes func with any additional partial arguments + * prepended to those provided to the new function. This method is similar to _.bind except + * it does not alter the this binding. + * @param func The function to partially apply arguments to. + * @param args Arguments to be partially applied. + * @return The new partially applied function. + **/ + partial( + func: Function, + ...args: any[]): Function; + } + + //_.partialRight + interface LoDashStatic { + /** + * This method is like _.partial except that partial arguments are appended to those provided + * to the new function. + * @param func The function to partially apply arguments to. + * @param args Arguments to be partially applied. + * @return The new partially applied function. + **/ + partialRight( + func: Function, + ...args: any[]): Function; + } + + //_.throttle + interface LoDashStatic { + /** + * Creates a function that, when executed, will only call the func function at most once per + * every wait milliseconds. Provide an options object to indicate that func should be invoked + * on the leading and/or trailing edge of the wait timeout. Subsequent calls to the throttled + * function will return the result of the last func call. + * + * Note: If leading and trailing options are true func will be called on the trailing edge of + * the timeout only if the the throttled function is invoked more than once during the wait timeout. + * @param func The function to throttle. + * @param wait The number of milliseconds to throttle executions to. + * @param options The options object. + * @param options.leading Specify execution on the leading edge of the timeout. + * @param options.trailing Specify execution on the trailing edge of the timeout. + * @return The new throttled function. + **/ + throttle( + func: T, + wait: number, + options?: ThrottleSettings): T; + } + + interface ThrottleSettings { + + /** + * If you'd like to disable the leading-edge call, pass this as false. + **/ + leading?: boolean; + + /** + * If you'd like to disable the execution on the trailing-edge, pass false. + **/ + trailing?: boolean; + } + + //_.wrap + interface LoDashStatic { + /** + * Creates a function that provides value to the wrapper function as its first argument. + * Additional arguments provided to the function are appended to those provided to the + * wrapper function. The wrapper is executed with the this binding of the created function. + * @param value The value to wrap. + * @param wrapper The wrapper function. + * @return The new function. + **/ + wrap( + value: any, + wrapper: (func: Function, ...args: any[]) => any): Function; + } + + /************* + * Objects * + *************/ + + //_.assign + interface LoDashStatic { + /** + * Assigns own enumerable properties of source object(s) to the destination object. Subsequent + * sources will overwrite property assignments of previous sources. If a callback is provided + * it will be executed to produce the assigned values. The callback is bound to thisArg and + * invoked with two arguments; (objectValue, sourceValue). + * @param object The destination object. + * @param s1-8 The source object(s) + * @param callback The function to customize merging properties. + * @param thisArg The this binding of callback. + * @return The destination object. + **/ + assign( + object: T, + s1: S1, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.assign + **/ + assign( + object: T, + s1: S1, + s2: S2, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.assign + **/ + assign( + object: T, + s1: S1, + s2: S2, + s3: S3, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.assign + **/ + assign( + object: T, + s1: S1, + s2: S2, + s3: S3, + s4: S4, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.assign + **/ + extend( + object: T, + s1: S1, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.assign + **/ + extend( + object: T, + s1: S1, + s2: S2, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.assign + **/ + extend( + object: T, + s1: S1, + s2: S2, + s3: S3, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.assign + **/ + extend( + object: T, + s1: S1, + s2: S2, + s3: S3, + s4: S4, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + } + + interface LoDashObjectWrapper { + /** + * @see _.assign + **/ + assign( + s1: S1, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + + /** + * @see _.assign + **/ + assign( + s1: S1, + s2: S2, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + /** + * @see _.assign + **/ + assign( + s1: S1, + s2: S2, + s3: S3, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + /** + * @see _.assign + **/ + assign( + s1: S1, + s2: S2, + s3: S3, + s4: S4, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + /** + * @see _.assign + **/ + assign( + s1: S1, + s2: S2, + s3: S3, + s4: S4, + s5: S5, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + + /** + * @see _.assign + **/ + extend( + s1: S1, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + + /** + * @see _.assign + **/ + extend( + s1: S1, + s2: S2, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + /** + * @see _.assign + **/ + extend( + s1: S1, + s2: S2, + s3: S3, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + /** + * @see _.assign + **/ + extend( + s1: S1, + s2: S2, + s3: S3, + s4: S4, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + /** + * @see _.assign + **/ + extend( + s1: S1, + s2: S2, + s3: S3, + s4: S4, + s5: S5, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + + } + + //_.clone + interface LoDashStatic { + /** + * Creates a clone of value. If deep is true nested objects will also be cloned, otherwise + * they will be assigned by reference. If a callback is provided it will be executed to produce + * the cloned values. If the callback returns undefined cloning will be handled by the method + * instead. The callback is bound to thisArg and invoked with one argument; (value). + * @param value The value to clone. + * @param deep Specify a deep clone. + * @param callback The function to customize cloning values. + * @param thisArg The this binding of callback. + * @return The cloned value. + **/ + clone( + value: T, + deep?: boolean, + callback?: (value: any) => any, + thisArg?: any): T; + } + + //_.cloneDeep + interface LoDashStatic { + /** + * Creates a deep clone of value. If a callback is provided it will be executed to produce the + * cloned values. If the callback returns undefined cloning will be handled by the method instead. + * The callback is bound to thisArg and invoked with one argument; (value). + * + * Note: This method is loosely based on the structured clone algorithm. Functions and DOM nodes + * are not cloned. The enumerable properties of arguments objects and objects created by constructors + * other than Object are cloned to plain Object objects. + * See http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm. + * @param value The value to clone. + * @param callback The function to customize cloning values. + * @param thisArg The this binding of callback. + * @return The cloned value. + **/ + cloneDeep( + value: T, + callback?: (value: any) => any, + thisArg?: any): T; + } + + //_.defaults + interface LoDashStatic { + /** + * Assigns own enumerable properties of source object(s) to the destination object for all + * destination properties that resolve to undefined. Once a property is set, additional defaults + * of the same property will be ignored. + * @param object The destination object. + * @param sources The source objects. + * @return The destination object. + **/ + defaults( + object: T, + ...sources: any[]): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.defaults + **/ + defaults(...sources: any[]): LoDashObjectWrapper + } + + //_.findKey + interface LoDashStatic { + /** + * This method is like _.findIndex except that it returns the key of the first element that + * passes the callback check, instead of the element itself. + * @param object The object to search. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return The key of the found element, else undefined. + **/ + findKey( + object: any, + callback: (value: any) => boolean, + thisArg?: any): string; + + /** + * @see _.findKey + * @param pluckValue _.pluck style callback + **/ + findKey( + object: any, + pluckValue: string): string; + + /** + * @see _.findKey + * @param whereValue _.where style callback + **/ + findKey, T>( + object: T, + whereValue: W): string; + } + + //_.findLastKey + interface LoDashStatic { + /** + * This method is like _.findKey except that it iterates over elements of a collection in the opposite order. + * @param object The object to search. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return The key of the found element, else undefined. + **/ + findLastKey( + object: any, + callback: (value: any) => boolean, + thisArg?: any): string; + + /** + * @see _.findLastKey + * @param pluckValue _.pluck style callback + **/ + findLastKey( + object: any, + pluckValue: string): string; + + /** + * @see _.findLastKey + * @param whereValue _.where style callback + **/ + findLastKey, T>( + object: T, + whereValue: W): string; + } + + //_.forIn + interface LoDashStatic { + /** + * Iterates over own and inherited enumerable properties of an object, executing the callback for + * each property. The callback is bound to thisArg and invoked with three arguments; (value, key, + * object). Callbacks may exit iteration early by explicitly returning false. + * @param object The object to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return object + **/ + forIn( + object: Dictionary, + callback?: ObjectIterator, + thisArg?: any): Dictionary; + + /** + * @see _.forIn + **/ + forIn( + object: T, + callback?: ObjectIterator, + thisArg?: any): T; + } + + interface LoDashObjectWrapper { + /** + * @see _.forIn + **/ + forIn( + callback: ObjectIterator, + thisArg?: any): _.LoDashObjectWrapper; + } + + //_.forInRight + interface LoDashStatic { + /** + * This method is like _.forIn except that it iterates over elements of a collection in the + * opposite order. + * @param object The object to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return object + **/ + forInRight( + object: Dictionary, + callback?: ObjectIterator, + thisArg?: any): Dictionary; + + /** + * @see _.forInRight + **/ + forInRight( + object: T, + callback?: ObjectIterator, + thisArg?: any): T; + } + + interface LoDashObjectWrapper { + /** + * @see _.forInRight + **/ + forInRight( + callback: ObjectIterator, + thisArg?: any): _.LoDashObjectWrapper; + } + + //_.forOwn + interface LoDashStatic { + /** + * Iterates over own enumerable properties of an object, executing the callback for each + * property. The callback is bound to thisArg and invoked with three arguments; (value, key, + * object). Callbacks may exit iteration early by explicitly returning false. + * @param object The object to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return object + **/ + forOwn( + object: Dictionary, + callback?: ObjectIterator, + thisArg?: any): Dictionary; + + /** + * @see _.forOwn + **/ + forOwn( + object: T, + callback?: ObjectIterator, + thisArg?: any): T; + } + + interface LoDashObjectWrapper { + /** + * @see _.forOwn + **/ + forOwn( + callback: ObjectIterator, + thisArg?: any): _.LoDashObjectWrapper; + } + + //_.forOwnRight + interface LoDashStatic { + /** + * This method is like _.forOwn except that it iterates over elements of a collection in the + * opposite order. + * @param object The object to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return object + **/ + forOwnRight( + object: Dictionary, + callback?: ObjectIterator, + thisArg?: any): Dictionary; + /** + * @see _.forOwnRight + **/ + forOwnRight( + object: T, + callback?: ObjectIterator, + thisArg?: any): T; + } + + interface LoDashObjectWrapper { + /** + * @see _.forOwnRight + **/ + forOwnRight( + callback: ObjectIterator, + thisArg?: any): _.LoDashObjectWrapper; + } + + //_.functions + interface LoDashStatic { + /** + * Creates a sorted array of property names of all enumerable properties, own and inherited, of + * object that have function values. + * @param object The object to inspect. + * @return An array of property names that have function values. + **/ + functions(object: any): string[]; + + /** + * @see _functions + **/ + methods(object: any): string[]; + } + + interface LoDashObjectWrapper { + /** + * @see _.functions + **/ + functions(): _.LoDashArrayWrapper; + + /** + * @see _.functions + **/ + methods(): _.LoDashArrayWrapper; + } + + //_.has + interface LoDashStatic { + /** + * Checks if the specified object property exists and is a direct property, instead of an + * inherited property. + * @param object The object to check. + * @param property The property to check for. + * @return True if key is a direct property, else false. + **/ + has(object: any, property: string): boolean; + } + + //_.invert + interface LoDashStatic { + /** + * Creates an object composed of the inverted keys and values of the given object. + * @param object The object to invert. + * @return The created inverted object. + **/ + invert(object: any): any; + } + + //_.isArguments + interface LoDashStatic { + /** + * Checks if value is an arguments object. + * @param value The value to check. + * @return True if the value is an arguments object, else false. + **/ + isArguments(value: any): boolean; + } + + //_.isArray + interface LoDashStatic { + /** + * Checks if value is an array. + * @param value The value to check. + * @return True if the value is an array, else false. + **/ + isArray(value: any): boolean; + } + + //_.isBoolean + interface LoDashStatic { + /** + * Checks if value is a boolean value. + * @param value The value to check. + * @return True if the value is a boolean value, else false. + **/ + isBoolean(value: any): boolean; + } + + //_.isDate + interface LoDashStatic { + /** + * Checks if value is a date. + * @param value The value to check. + * @return True if the value is a date, else false. + **/ + isDate(value: any): boolean; + } + + //_.isElement + interface LoDashStatic { + /** + * Checks if value is a DOM element. + * @param value The value to check. + * @return True if the value is a DOM element, else false. + **/ + isElement(value: any): boolean; + } + + //_.isEmpty + interface LoDashStatic { + /** + * Checks if value is empty. Arrays, strings, or arguments objects with a length of 0 and objects + * with no own enumerable properties are considered "empty". + * @param value The value to inspect. + * @return True if the value is empty, else false. + **/ + isEmpty(value: any[]): boolean; + + /** + * @see _.isEmpty + **/ + isEmpty(value: Dictionary): boolean; + + /** + * @see _.isEmpty + **/ + isEmpty(value: string): boolean; + + /** + * @see _.isEmpty + **/ + isEmpty(value: any): boolean; + } + + //_.isEqual + interface LoDashStatic { + /** + * Performs a deep comparison between two values to determine if they are equivalent to each + * other. If a callback is provided it will be executed to compare values. If the callback + * returns undefined comparisons will be handled by the method instead. The callback is bound to + * thisArg and invoked with two arguments; (a, b). + * @param a The value to compare. + * @param b The other value to compare. + * @param callback The function to customize comparing values. + * @param thisArg The this binding of callback. + * @return True if the values are equivalent, else false. + **/ + isEqual( + a: any, + b: any, + callback?: (a: any, b: any) => boolean, + thisArg?: any): boolean; + } + + //_.isFinite + interface LoDashStatic { + /** + * Checks if value is, or can be coerced to, a finite number. + * + * Note: This is not the same as native isFinite which will return true for booleans and empty + * strings. See http://es5.github.io/#x15.1.2.5. + * @param value The value to check. + * @return True if the value is finite, else false. + **/ + isFinite(value: any): boolean; + } + + //_.isFunction + interface LoDashStatic { + /** + * Checks if value is a function. + * @param value The value to check. + * @return True if the value is a function, else false. + **/ + isFunction(value: any): boolean; + } + + //_.isNaN + interface LoDashStatic { + /** + * Checks if value is NaN. + * + * Note: This is not the same as native isNaN which will return true for undefined and other + * non-numeric values. See http://es5.github.io/#x15.1.2.4. + * @param value The value to check. + * @return True if the value is NaN, else false. + **/ + isNaN(value: any): boolean; + } + + //_.isNull + interface LoDashStatic { + /** + * Checks if value is null. + * @param value The value to check. + * @return True if the value is null, else false. + **/ + isNull(value: any): boolean; + } + + //_.isNumber + interface LoDashStatic { + /** + * Checks if value is a number. + * + * Note: NaN is considered a number. See http://es5.github.io/#x8.5. + * @param value The value to check. + * @return True if the value is a number, else false. + **/ + isNumber(value: any): boolean; + } + + //_.isObject + interface LoDashStatic { + /** + * Checks if value is the language type of Object. (e.g. arrays, functions, objects, regexes, + * new Number(0), and new String('')) + * @param value The value to check. + * @return True if the value is an object, else false. + **/ + isObject(value: any): boolean; + } + + //_.isPlainObject + interface LoDashStatic { + /** + * Checks if value is an object created by the Object constructor. + * @param value The value to check. + * @return True if value is a plain object, else false. + **/ + isPlainObject(value: any): boolean; + } + + //_.isRegExp + interface LoDashStatic { + /** + * Checks if value is a regular expression. + * @param value The value to check. + * @return True if the value is a regular expression, else false. + **/ + isRegExp(value: any): boolean; + } + + //_.isString + interface LoDashStatic { + /** + * Checks if value is a string. + * @param value The value to check. + * @return True if the value is a string, else false. + **/ + isString(value: any): boolean; + } + + //_.isUndefined + interface LoDashStatic { + /** + * Checks if value is undefined. + * @param value The value to check. + * @return True if the value is undefined, else false. + **/ + isUndefined(value: any): boolean; + } + + //_.keys + interface LoDashStatic { + /** + * Creates an array composed of the own enumerable property names of an object. + * @param object The object to inspect. + * @return An array of property names. + **/ + keys(object: any): string[]; + } + + interface LoDashObjectWrapper { + /** + * @see _.keys + **/ + keys(): LoDashArrayWrapper + } + + //_.mapValues + interface LoDashStatic { + /** + * Creates an object with the same keys as object and values generated by running each own + * enumerable property of object through the callback. The callback is bound to thisArg and + * invoked with three arguments; (value, key, object). + * + * If a property name is provided for callback the created "_.pluck" style callback will return + * the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return true + * for elements that have the properties of the given object, else false. + * + * @param object The object to iterate over. + * @param callback The function called per iteration. + * @param thisArg `this` object in `iterator`, optional. + * @return Returns a new object with values of the results of each callback execution. + */ + mapValues(obj: Dictionary, callback: ObjectIterator, thisArg?: any): Dictionary; + mapValues(obj: Dictionary, where: Dictionary): Dictionary; + mapValues(obj: T, pluck: string): TMapped; + mapValues(obj: T, callback: ObjectIterator, thisArg?: any): T; + } + + //_.merge + interface LoDashStatic { + /** + * Recursively merges own enumerable properties of the source object(s), that don't resolve + * to undefined into the destination object. Subsequent sources will overwrite property + * assignments of previous sources. If a callback is provided it will be executed to produce + * the merged values of the destination and source properties. If the callback returns undefined + * merging will be handled by the method instead. The callback is bound to thisArg and invoked + * with two arguments; (objectValue, sourceValue). + * @param object The destination object. + * @param s1-8 The source object(s) + * @param callback The function to customize merging properties. + * @param thisArg The this binding of callback. + * @return The destination object. + **/ + merge( + object: T, + s1: S1, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.merge + **/ + merge( + object: T, + s1: S1, + s2: S2, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.merge + **/ + merge( + object: T, + s1: S1, + s2: S2, + s3: S3, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.merge + **/ + merge( + object: T, + s1: S1, + s2: S2, + s3: S3, + s4: S4, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + } + + //_.omit + interface LoDashStatic { + /** + * Creates a shallow clone of object excluding the specified properties. Property names may be + * specified as individual arguments or as arrays of property names. If a callback is provided + * it will be executed for each property of object omitting the properties the callback returns + * truey for. The callback is bound to thisArg and invoked with three arguments; (value, key, + * object). + * @param object The source object. + * @param keys The properties to omit. + * @return An object without the omitted properties. + **/ + omit( + object: T, + ...keys: string[]): Omitted; + + /** + * @see _.omit + **/ + omit( + object: T, + keys: string[]): Omitted; + + /** + * @see _.omit + **/ + omit( + object: T, + callback: ObjectIterator, + thisArg?: any): Omitted; + } + + interface LoDashObjectWrapper { + /** + * @see _.omit + **/ + omit( + ...keys: string[]): LoDashObjectWrapper; + + /** + * @see _.omit + **/ + omit( + keys: string[]): LoDashObjectWrapper; + + /** + * @see _.omit + **/ + omit( + callback: ObjectIterator, + thisArg?: any): LoDashObjectWrapper; + } + + //_.pairs + interface LoDashStatic { + /** + * Creates a two dimensional array of an object’s key-value pairs, + * i.e. [[key1, value1], [key2, value2]]. + * @param object The object to inspect. + * @return Aew array of key-value pairs. + **/ + pairs(object: any): any[][]; + } + + interface LoDashObjectWrapper { + /** + * @see _.pairs + **/ + pairs(): LoDashArrayWrapper; + } + + //_.picks + interface LoDashStatic { + /** + * Creates a shallow clone of object composed of the specified properties. Property names may be + * specified as individual arguments or as arrays of property names. If a callback is provided + * it will be executed for each property of object picking the properties the callback returns + * truey for. The callback is bound to thisArg and invoked with three arguments; (value, key, + * object). + * @param object Object to strip unwanted key/value pairs. + * @param keys Property names to pick + * @return An object composed of the picked properties. + **/ + pick( + object: T, + ...keys: string[]): Picked; + + /** + * @see _.pick + **/ + pick( + object: T, + keys: string[]): Picked; + + /** + * @see _.pick + **/ + pick( + object: T, + callback: ObjectIterator, + thisArg?: any): Picked; + } + + //_.transform + interface LoDashStatic { + /** + * An alternative to _.reduce this method transforms object to a new accumulator object which is + * the result of running each of its elements through a callback, with each callback execution + * potentially mutating the accumulator object. The callback is bound to thisArg and invoked with + * four arguments; (accumulator, value, key, object). Callbacks may exit iteration early by + * explicitly returning false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param accumulator The custom accumulator value. + * @param thisArg The this binding of callback. + * @return The accumulated value. + **/ + transform( + collection: Array, + callback: MemoVoidIterator, + accumulator: Acc, + thisArg?: any): Acc; + + /** + * @see _.transform + **/ + transform( + collection: List, + callback: MemoVoidIterator, + accumulator: Acc, + thisArg?: any): Acc; + + /** + * @see _.transform + **/ + transform( + collection: Dictionary, + callback: MemoVoidIterator, + accumulator: Acc, + thisArg?: any): Acc; + + /** + * @see _.transform + **/ + transform( + collection: Array, + callback?: MemoVoidIterator, + thisArg?: any): Acc; + + /** + * @see _.transform + **/ + transform( + collection: List, + callback?: MemoVoidIterator, + thisArg?: any): Acc; + + /** + * @see _.transform + **/ + transform( + collection: Dictionary, + callback?: MemoVoidIterator, + thisArg?: any): Acc; + } + + //_.values + interface LoDashStatic { + /** + * Creates an array composed of the own enumerable property values of object. + * @param object The object to inspect. + * @return Returns an array of property values. + **/ + values(object: any): any[]; + } + + /********** + * String * + **********/ + + interface LoDashStatic { + camelCase(str?: string): string; + capitalize(str?: string): string; + deburr(str?: string): string; + endsWith(str?: string, target?: string, position?: number): boolean; + escape(str?: string): string; + escapeRegExp(str?: string): string; + kebabCase(str?: string): string; + pad(str?: string, length?: number, chars?: string): string; + padLeft(str?: string, length?: number, chars?: string): string; + padRight(str?: string, length?: number, chars?: string): string; + repeat(str?: string, n?: number): string; + snakeCase(str?: string): string; + startCase(str?: string): string; + startsWith(str?: string, target?: string, position?: number): boolean; + trim(str?: string, chars?: string): string; + trimLeft(str?: string, chars?: string): string; + trimRight(str?: string, chars?: string): string; + trunc(str?: string, len?: number): string; + trunc(str?: string, options?: { length?: number; omission?: string; separator?: string }): string; + trunc(str?: string, options?: { length?: number; omission?: string; separator?: RegExp }): string; + words(str?: string, pattern?: string): string[]; + words(str?: string, pattern?: RegExp): string[]; + } + + //_.parseInt + interface LoDashStatic { + /** + * Converts the given value into an integer of the specified radix. If radix is undefined or 0 a + * radix of 10 is used unless the value is a hexadecimal, in which case a radix of 16 is used. + * + * Note: This method avoids differences in native ES3 and ES5 parseInt implementations. See + * http://es5.github.io/#E. + * @param value The value to parse. + * @param radix The radix used to interpret the value to parse. + * @return The new integer value. + **/ + parseInt(value: string, radix?: number): number; + } + + /************* + * Utilities * + *************/ + //_.escape + interface LoDashStatic { + /** + * Converts the characters &, <, >, ", and ' in string to their corresponding HTML entities. + * @param string The string to escape. + * @return The escaped string. + **/ + escape(str: string): string; + } + + //_.identity + interface LoDashStatic { + /** + * This method returns the first argument provided to it. + * @param value Any value. + * @return value. + **/ + identity(value: T): T; + } + + //_.mixin + interface LoDashStatic { + /** + * Adds function properties of a source object to the lodash function and chainable wrapper. + * @param object The object of function properties to add to lodash. + **/ + mixin(object: Dictionary<(value: any) => any>): void; + } + + //_.noConflict + interface LoDashStatic { + /** + * Reverts the '_' variable to its previous value and returns a reference to the lodash function. + * @return The lodash function. + **/ + noConflict(): typeof _; + } + + //_.property + interface LoDashStatic { + /** + * # Ⓢ + * Creates a "_.pluck" style function, which returns the key value of a given object. + * @param key (string) + * @return the value of that key on the object + **/ + property(key: string): (obj: T) => RT; + } + + //_.random + interface LoDashStatic { + /** + * Produces a random number between min and max (inclusive). If only one argument is provided a + * number between 0 and the given number will be returned. If floating is truey or either min or + * max are floats a floating-point number will be returned instead of an integer. + * @param max The maximum possible value. + * @param floating Specify returning a floating-point number. + * @return A random number. + **/ + random(max: number, floating?: boolean): number; + + /** + * @see _.random + * @param min The minimum possible value. + * @return A random number between `min` and `max`. + **/ + random(min: number, max: number, floating?: boolean): number; + } + + //_.result + interface LoDashStatic { + /** + * Resolves the value of property on object. If property is a function it will be invoked with + * the this binding of object and its result returned, else the property value is returned. If + * object is falsey then undefined is returned. + * @param object The object to inspect. + * @param property The property to get the value of. + * @return The resolved value. + **/ + result(object: any, property: string): any; + } + + //_.runInContext + interface LoDashStatic { + /** + * Create a new lodash function using the given context object. + * @param context The context object + * @returns The lodash function. + **/ + runInContext(context: any): typeof _; + } + + //_.template + interface LoDashStatic { + /** + * A micro-templating method that handles arbitrary delimiters, preserves whitespace, and + * correctly escapes quotes within interpolated code. + * + * Note: In the development build, _.template utilizes sourceURLs for easier debugging. See + * http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl + * + * For more information on precompiling templates see: + * http://lodash.com/#custom-builds + * + * For more information on Chrome extension sandboxes see: + * http://developer.chrome.com/stable/extensions/sandboxingEval.html + * @param text The template text. + * @param data The data object used to populate the text. + * @param options The options object. + * @param options.escape The "escape" delimiter. + * @param options.evaluate The "evaluate" delimiter. + * @param options.import An object to import into the template as local variables. + * @param options.interpolate The "interpolate" delimiter. + * @param sourceURL The sourceURL of the template’s compiled source. + * @param variable The data object variable name. + * @return Returns the compiled Lo-Dash HTML template or a TemplateExecutor if no data is passed. + **/ + template( + text: string): TemplateExecutor; + + /** + * @see _.template + **/ + template( + text: string, + data: any, + options?: TemplateSettings, + sourceURL?: string, + variable?: string): any /* string or TemplateExecutor*/; + } + + interface TemplateExecutor { + (...data: any[]): string; + source: string; + } + + //_.times + interface LoDashStatic { + /** + * Executes the callback n times, returning an array of the results of each callback execution. + * The callback is bound to thisArg and invoked with one argument; (index). + * @param n The number of times to execute the callback. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + **/ + times( + n: number, + callback: (num: number) => TResult, + context?: any): TResult[]; + } + + //_.unescape + interface LoDashStatic { + /** + * The inverse of _.escape this method converts the HTML entities &, <, >, ", and + * ' in string to their corresponding characters. + * @param string The string to unescape. + * @return The unescaped string. + **/ + unescape( + string: string): string; + } + + //_.uniqueId + interface LoDashStatic { + /** + * Generates a unique ID. If prefix is provided the ID will be appended to it. + * @param prefix The value to prefix the ID with. + * @return Returns the unique ID. + **/ + uniqueId(prefix?: string): string; + } + + //_.noop + interface LoDashStatic { + /** + * A no-operation function. + **/ + noop(): void; + } + + //_.constant + interface LoDashStatic { + /** + * Creates a function that returns value.. + **/ + constant(value: T): () => T; + } + + //_.create + interface LoDashStatic { + /** + * Creates an object that inherits from the given prototype object. If a properties object is provided its own enumerable properties are assigned to the created object. + * @param prototype The object to inherit from. + * @param properties The properties to assign to the object. + */ + create(prototype: Object, properties?: Object): Object; + } + + interface ListIterator { + (value: T, index: number, list: T[]): TResult; + } + + interface ObjectIterator { + (element: T, key: string, list: any): TResult; + } + + interface MemoVoidIterator { + (prev: TResult, curr: T, indexOrKey: any, list?: T[]): void; + } + interface MemoIterator { + (prev: TResult, curr: T, indexOrKey: any, list?: T[]): TResult; + } + /* + interface MemoListIterator { + (prev: TResult, curr: T, index: number, list?: T[]): TResult; + } + interface MemoObjectIterator { + (prev: TResult, curr: T, index: string, object?: Dictionary): TResult; + } + */ + + //interface Collection {} + + // Common interface between Arrays and jQuery objects + interface List { + [index: number]: T; + length: number; + } + + interface Dictionary { + [index: string]: T; + } +} + +declare module "lodash" { + export = _; +} diff --git a/typings/DefinitelyTyped/mocha/mocha.d.ts b/typings/DefinitelyTyped/mocha/mocha.d.ts new file mode 100644 index 0000000..3f5d3e5 --- /dev/null +++ b/typings/DefinitelyTyped/mocha/mocha.d.ts @@ -0,0 +1,147 @@ +// Type definitions for mocha 2.0.1 +// Project: http://mochajs.org/ +// Definitions by: Kazi Manzur Rashid , otiai10 , jt000 +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +interface Mocha { + // Setup mocha with the given setting options. + setup(options: MochaSetupOptions): Mocha; + + //Run tests and invoke `fn()` when complete. + run(callback?: () => void): void; + + // Set reporter as function + reporter(reporter: () => void): Mocha; + + // Set reporter, defaults to "dot" + reporter(reporter: string): Mocha; + + // Enable growl support. + growl(): Mocha +} + +interface MochaSetupOptions { + //milliseconds to wait before considering a test slow + slow?: number; + + // timeout in milliseconds + timeout?: number; + + // ui name "bdd", "tdd", "exports" etc + ui?: string; + + //array of accepted globals + globals?: any[]; + + // reporter instance (function or string), defaults to `mocha.reporters.Dot` + reporter?: any; + + // bail on the first test failure + bail?: boolean; + + // ignore global leaks + ignoreLeaks?: boolean; + + // grep string or regexp to filter tests with + grep?: any; +} + +interface MochaDone { + (error?: Error): void; +} + +declare var mocha: Mocha; + +declare var describe : { + (description: string, spec: () => void): void; + only(description: string, spec: () => void): void; + skip(description: string, spec: () => void): void; + timeout(ms: number): void; +} + +// alias for `describe` +declare var context : { + (contextTitle: string, spec: () => void): void; + only(contextTitle: string, spec: () => void): void; + skip(contextTitle: string, spec: () => void): void; + timeout(ms: number): void; +} + +declare var it: { + (expectation: string, assertion?: () => void): void; + (expectation: string, assertion?: (done: MochaDone) => void): void; + only(expectation: string, assertion?: () => void): void; + only(expectation: string, assertion?: (done: MochaDone) => void): void; + skip(expectation: string, assertion?: () => void): void; + skip(expectation: string, assertion?: (done: MochaDone) => void): void; + timeout(ms: number): void; +}; + +declare function before(action: () => void): void; + +declare function before(action: (done: MochaDone) => void): void; + +declare function setup(action: () => void): void; + +declare function setup(action: (done: MochaDone) => void): void; + +declare function after(action: () => void): void; + +declare function after(action: (done: MochaDone) => void): void; + +declare function teardown(action: () => void): void; + +declare function teardown(action: (done: MochaDone) => void): void; + +declare function beforeEach(action: () => void): void; + +declare function beforeEach(action: (done: MochaDone) => void): void; + +declare function suiteSetup(action: () => void): void; + +declare function suiteSetup(action: (done: MochaDone) => void): void; + +declare function afterEach(action: () => void): void; + +declare function afterEach(action: (done: MochaDone) => void): void; + +declare function suiteTeardown(action: () => void): void; + +declare function suiteTeardown(action: (done: MochaDone) => void): void; + +declare module "mocha" { + + class Mocha { + constructor(options?: { + grep?: RegExp; + ui?: string; + reporter?: string; + timeout?: number; + bail?: boolean; + }); + + bail(value?: boolean): Mocha; + addFile(file: string): Mocha; + reporter(value: string): Mocha; + ui(value: string): Mocha; + grep(value: string): Mocha; + grep(value: RegExp): Mocha; + invert(): Mocha; + ignoreLeaks(value: boolean): Mocha; + checkLeaks(): Mocha; + growl(): Mocha; + globals(value: string): Mocha; + globals(values: string[]): Mocha; + useColors(value: boolean): Mocha; + useInlineDiffs(value: boolean): Mocha; + timeout(value: number): Mocha; + slow(value: number): Mocha; + enableTimeouts(value: boolean): Mocha; + asyncOnly(value: boolean): Mocha; + noHighlighting(value: boolean): Mocha; + + run(onComplete?: (failures: number) => void): void; + } + + export = Mocha; +} diff --git a/typings/DefinitelyTyped/mongodb/mongodb.d.ts b/typings/DefinitelyTyped/mongodb/mongodb.d.ts new file mode 100644 index 0000000..c11f497 --- /dev/null +++ b/typings/DefinitelyTyped/mongodb/mongodb.d.ts @@ -0,0 +1,493 @@ +// Type definitions for MongoDB +// Project: https://github.com/mongodb/node-mongodb-native +// Definitions by: Boris Yankov +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +// Documentation : http://mongodb.github.io/node-mongodb-native/ + +/// + +declare module "mongodb" { + + // Class documentation : http://mongodb.github.io/node-mongodb-native/api-generated/mongoclient.html + export class MongoClient{ + constructor(serverConfig: any, options: any); + + static connect(uri: string, options: any, callback: (err: Error, db: Db) => void): void; + static connect(uri: string, callback: (err: Error, db: Db) => void): void; + } + + // Class documentation : http://mongodb.github.io/node-mongodb-native/api-generated/server.html + export class Server { + constructor (host: string, port: number, opts?: ServerOptions); + + public connect(): any; + } + + // Class documentation : http://mongodb.github.io/node-mongodb-native/api-generated/db.html + export class Db { + constructor (databaseName: string, serverConfig: Server, dbOptions?: DbCreateOptions); + + public db(dbName: string): Db; + + public open(callback: (err : Error, db : Db) => void ): void; + public close(forceClose?: boolean, callback?: (err: Error, result: any) => void ): void; + public admin(callback: (err: Error, result: any) => void ): any; + public collectionsInfo(collectionName: string, callback?: (err: Error, result: any) => void ): void; + public collectionNames(collectionName: string, options: any, callback?: (err: Error, result: any) => void ): void; + + public collection(collectionName: string): Collection; + public collection(collectionName: string, callback: (err: Error, collection: Collection) => void ): Collection; + public collection(collectionName: string, options: MongoCollectionOptions, callback: (err: Error, collection: Collection) => void ): Collection; + + public collections(callback: (err: Error, collections: Collection[]) => void ): void; + public eval(code: any, parameters: any[], options?: any, callback?: (err: Error, result: any) => void ): void; + //public dereference(dbRef: DbRef, callback: (err: Error, result: any) => void): void; + + public logout(options: any, callback?: (err: Error, result: any) => void ): void; + public logout(callback: (err: Error, result: any) => void ): void; + + public authenticate(userName: string, password: string, callback?: (err: Error, result: any) => void ): void; + public authenticate(userName: string, password: string, options: any, callback?: (err: Error, result: any) => void ): void; + + public addUser(username: string, password: string, callback?: (err: Error, result: any) => void ): void; + public addUser(username: string, password: string, options: any, callback?: (err: Error, result: any) => void ): void; + + public removeUser(username: string, callback?: (err: Error, result: any) => void ): void; + public removeUser(username: string, options: any, callback?: (err: Error, result: any) => void ): void; + + public createCollection(collectionName: string, callback?: (err: Error, result: Collection) => void ): void; + public createCollection(collectionName: string, options: CollectionCreateOptions, callback?: (err: Error, result: any) => void ): void; + + public command(selector: Object, callback?: (err: Error, result: any) => void ): void; + public command(selector: Object, options: any, callback?: (err: Error, result: any) => void ): void; + + public dropCollection(collectionName: string, callback?: (err: Error, result: any) => void ): void; + public renameCollection(fromCollection: string, toCollection: string, callback?: (err: Error, result: any) => void ): void; + + public lastError(options: Object, connectionOptions: any, callback: (err: Error, result: any) => void ): void; + public previousError(options: Object, callback: (err: Error, result: any) => void ): void; + + // error = lastError + // lastStatus = lastError + + public executeDbCommand(command_hash: any, callback?: (err: Error, result: any) => void ): void; + public executeDbCommand(command_hash: any, options: any, callback?: (err: Error, result: any) => void ): void; + + public executeDbAdminCommand(command_hash: any, callback?: (err: Error, result: any) => void ): void; + public executeDbAdminCommand(command_hash: any, options: any, callback?: (err: Error, result: any) => void ): void; + + public resetErrorHistory(callback?: (err: Error, result: any) => void ): void; + public resetErrorHistory(options: any, callback?: (err: Error, result: any) => void ): void; + + public createIndex(collectionName: any, fieldOrSpec: any, options: IndexOptions, callback: Function): void; + public ensureIndex(collectionName: any, fieldOrSpec: any, options: IndexOptions, callback: Function): void; + + public cursorInfo(options: any, callback: Function): void; + + public dropIndex(collectionName: string, indexName: string, callback: Function): void; + public reIndex(collectionName: string, callback: Function): void; + public indexInformation(collectionName: string, options: any, callback: Function): void; + public dropDatabase(callback: (err: Error, result: any) => void ): void; + + public stats(options: any, callback: Function): void; + public _registerHandler(db_command: any, raw: any, connection: any, exhaust: any, callback: Function): void; + public _reRegisterHandler(newId: any, object: any, callback: Function): void; + public _callHandler(id: any, document: any, err: any): any; + public _hasHandler(id: any): any; + public _removeHandler(id: any): any; + public _findHandler(id: any): { id: string; callback: Function; }; + public __executeQueryCommand(self: any, db_command: any, options: any, callback: any): void; + + public DEFAULT_URL: string; + + public connect(url: string, options: { uri_decode_auth?: boolean; }, callback: (err: Error, result: any) => void ): void; + + public addListener(event: string, handler:(param: any) => any): any; + } + + // Class documentation : http://mongodb.github.io/node-mongodb-native/api-bson-generated/objectid.html + // Last update: doc. version 1.3.13 (28.08.2013) + export class ObjectID { + constructor (s?: string); + + // Returns the ObjectID id as a 24 byte hex string representation + public toHexString() : string; + + // Compares the equality of this ObjectID with otherID. + public equals(otherID: ObjectID) : boolean; + + // Returns the generation date (accurate up to the second) that this ID was generated. + public getTimestamp(): Date; + + // Creates an ObjectID from a second based number, with the rest of the ObjectID zeroed out. Used for comparisons or sorting the ObjectID. + // time – an integer number representing a number of seconds. + public static createFromTime(time: number): ObjectID; + + // Creates an ObjectID from a hex string representation of an ObjectID. + // hexString – create a ObjectID from a passed in 24 byte hexstring. + public static createFromHexString(hexString: string): ObjectID; + } + + // Class documentation : http://mongodb.github.io/node-mongodb-native/api-bson-generated/binary.html + export class Binary { + constructor (buffer: Buffer, subType?: number); + + // Updates this binary with byte_value + put(byte_value: any): void; + + // Writes a buffer or string to the binary + write(buffer: any, offset: number): void; + + // Reads length bytes starting at position. + read(position: number, length: number): Buffer; + + // Returns the value of this binary as a string. + value(): string; + + // The length of the binary. + length(): number; + } + + export interface SocketOptions { + //= set seconds before connection times out default:0 + timeout?: number; + //= Disables the Nagle algorithm default:true + noDelay?: boolean; + //= Set if keepAlive is used default:0 , which means no keepAlive, set higher than 0 for keepAlive + keepAlive?: number; + //= ‘ascii’|’utf8’|’base64’ default:null + encoding?: string; + } + + export interface ServerOptions { + // - to reconnect automatically, default:false + auto_reconnect?: boolean; + // - specify the number of connections in the pool default:1 + poolSize?: number; + // - a collection of pr socket settings + socketOptions?: any; + } + + export interface PKFactory { + counter: number; + createPk: () => number; + } + + // See : http://mongodb.github.io/node-mongodb-native/api-generated/db.html + // Current definition by documentation version 1.3.13 (28.08.2013) + export interface DbCreateOptions { + // the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = ‘majority’ or tag acknowledges the write. + w?: any; + + // set the timeout for waiting for write concern to finish (combines with w option). + wtimeout?: number; + + // write waits for fsync before returning. default:false. + fsync?: boolean; + + // write waits for journal sync before returning. default:false. + journal?: boolean; + + // the prefered read preference. use 'ReadPreference' class. + readPreference?: string; + + // use c++ bson parser. default:false. + native_parser?: boolean; + + // force server to create _id fields instead of client. default:false. + forceServerObjectId?: boolean; + + // custom primary key factory to generate _id values (see Custom primary keys). + pkFactory?: PKFactory; + + // serialize functions. default:false. + serializeFunctions?: boolean; + + // peform operations using raw bson buffers. default:false. + raw?: boolean; + + // record query statistics during execution. default:false. + recordQueryStats?: boolean; + + // number of miliseconds between retries. default:5000. + retryMiliSeconds?: number; + + // number of retries off connection. default:5. + numberOfRetries?: number; + + // an object representing a logger that you want to use, needs to support functions debug, log, error. default:null. + logger?: Object + + // force setting of SlaveOk flag on queries (only use when explicitly connecting to a secondary server). default:null. + slaveOk?: number; + + // when deserializing a Long will fit it into a Number if it’s smaller than 53 bits. default:true. + promoteLongs?: boolean; + } + + export class ReadPreference { + public static PRIMARY: string; + public static PRIMARY_PREFERRED: string; + public static SECONDARY: string; + public static SECONDARY_PREFERRED: string; + public static NEAREST: string; + } + + // See : http://mongodb.github.io/node-mongodb-native/api-generated/collection.html + // Current definition by documentation version 1.3.13 (28.08.2013) + export interface CollectionCreateOptions { + // the prefered read preference. use 'ReadPreference' class. + readPreference?: string; + + // Allow reads from secondaries. default:false. + slaveOk?: boolean; + + // serialize functions on the document. default:false. + serializeFunctions?: boolean; + + // perform all operations using raw bson objects. default:false. + raw?: boolean; + + // object overriding the basic ObjectID primary key generation. + pkFactory?: PKFactory; + } + + // Documentation: http://docs.mongodb.org/manual/reference/command/collStats/ + export interface CollStats { + // Namespace. + ns: string; + + // Number of documents. + count: number; + + // Collection size in bytes. + size: number; + + // Average object size in bytes. + avgObjSize: number; + + // (Pre)allocated space for the collection in bytes. + storageSize: number; + + // Number of extents (contiguously allocated chunks of datafile space). + numExtents: number; + + // Number of indexes. + nindexes: number; + + // Size of the most recently created extent in bytes. + lastExtentSize: number; + + // Padding can speed up updates if documents grow. + paddingFactor: number; + flags: number; + + // Total index size in bytes. + totalIndexSize: number; + + // Size of specific indexes in bytes. + indexSizes: { + _id_: number; + username: number; + }; + } + + // Documentation : http://mongodb.github.io/node-mongodb-native/api-generated/collection.html + export interface Collection { + new (db: Db, collectionName: string, pkFactory?: Object, options?: CollectionCreateOptions): Collection; // is this right? + + insert(query: any, callback: (err: Error, result: any) => void): void; + insert(query: any, options: { safe?: any; continueOnError?: boolean; keepGoing?: boolean; serializeFunctions?: boolean; }, callback: (err: Error, result: any) => void): void; + insertOne(doc: any, callback: (err: Error, result: any) => void): void; + insertOne(doc: any, options: { w?: any; wtimeout?: number; j?: boolean; serializeFunctions?: boolean; forceServerObjectId?: boolean }, callback: (err: Error, result: any) => void): void; + insertMany(docs: any[], callback: (err: Error, result: any) => void): void; + insertMany(docs: any[], options: { w?: any; wtimeout?: number; j?: boolean; serializeFunctions?: boolean; forceServerObjectId?: boolean }, callback: (err: Error, result: any) => void): void; + + remove(selector: Object, callback?: (err: Error, result: any) => void): void; + remove(selector: Object, options: { safe?: any; single?: boolean; }, callback?: (err: Error, result: any) => void): void; + + rename(newName: String, callback?: (err: Error, result: any) => void): void; + + save(doc: any, callback : (err: Error, result: any) => void): void; + save(doc: any, options: { safe: any; }, callback : (err: Error, result: any) => void): void; + + update(selector: Object, document: any, callback?: (err: Error, result: any) => void): void; + update(selector: Object, document: any, options: { safe?: boolean; upsert?: any; multi?: boolean; serializeFunctions?: boolean; }, callback: (err: Error, result: any) => void): void; + updateOne(selector: Object, document: any, callback?: (err: Error, result: any) => void): void; + updateOne(selector: Object, document: any, options: { w?: any; wtimeout?: number; j?: boolean; }, callback: (err: Error, result: any) => void): void; + updateMany(selector: Object, document: any, callback?: (err: Error, result: any) => void): void; + updateMany(selector: Object, document: any, options: { w?: any; wtimeout?: number; j?: boolean; }, callback: (err: Error, result: any) => void): void; + + distinct(key: string, query: Object, callback: (err: Error, result: any) => void): void; + distinct(key: string, query: Object, options: { readPreference: string; }, callback: (err: Error, result: any) => void): void; + + count(callback: (err: Error, result: any) => void): void; + count(query: Object, callback: (err: Error, result: any) => void): void; + count(query: Object, options: { readPreference: string; }, callback: (err: Error, result: any) => void): void; + + drop(callback?: (err: Error, result: any) => void): void; + + findAndModify(query: Object, sort: any[], doc: Object, callback: (err: Error, result: any) => void): void; + findAndModify(query: Object, sort: any[], doc: Object, options: { safe?: any; remove?: boolean; upsert?: boolean; new?: boolean; }, callback: (err: Error, result: any) => void): void; + + findAndRemove(query : Object, sort? : any[], callback?: (err: Error, result: any) => void): void; + findAndRemove(query : Object, sort? : any[], options?: { safe: any; }, callback?: (err: Error, result: any) => void): void; + + find(callback?: (err: Error, result: Cursor) => void): Cursor; + find(selector: Object, callback?: (err: Error, result: Cursor) => void): Cursor; + find(selector: Object, fields: any, callback?: (err: Error, result: Cursor) => void): Cursor; + find(selector: Object, options: CollectionFindOptions, callback?: (err: Error, result: Cursor) => void): Cursor; + find(selector: Object, fields: any, options: CollectionFindOptions, callback?: (err: Error, result: Cursor) => void): Cursor; + find(selector: Object, fields: any, skip: number, limit: number, callback?: (err: Error, result: Cursor) => void): Cursor; + find(selector: Object, fields: any, skip: number, limit: number, timeout: number, callback?: (err: Error, result: Cursor) => void): Cursor; + + findOne(callback?: (err: Error, result: any) => void): Cursor; + findOne(selector: Object, callback?: (err: Error, result: any) => void): Cursor; + findOne(selector: Object, fields: any, callback?: (err: Error, result: any) => void): Cursor; + findOne(selector: Object, options: CollectionFindOptions, callback?: (err: Error, result: any) => void): Cursor; + findOne(selector: Object, fields: any, options: CollectionFindOptions, callback?: (err: Error, result: any) => void): Cursor; + findOne(selector: Object, fields: any, skip: number, limit: number, callback?: (err: Error, result: any) => void): Cursor; + findOne(selector: Object, fields: any, skip: number, limit: number, timeout: number, callback?: (err: Error, result: any) => void): Cursor; + + createIndex(fieldOrSpec: any, callback: (err: Error, indexName: string) => void): void; + createIndex(fieldOrSpec: any, options: IndexOptions, callback: (err: Error, indexName: string) => void): void; + + ensureIndex(fieldOrSpec: any, callback: (err: Error, indexName: string) => void): void; + ensureIndex(fieldOrSpec: any, options: IndexOptions, callback: (err: Error, indexName: string) => void): void; + + indexInformation(options: any, callback: Function): void; + dropIndex(name: string, callback: Function): void; + dropAllIndexes(callback: Function): void; + // dropIndexes = dropAllIndexes + + reIndex(callback: Function): void; + mapReduce(map: Function, reduce: Function, options: MapReduceOptions, callback: Function): void; + group(keys: Object, condition: Object, initial: Object, reduce: Function, finalize: Function, command: boolean, options: {readPreference: string}, callback: Function): void; + options(callback: Function): void; + isCapped(callback: Function): void; + indexExists(indexes: string, callback: Function): void; + geoNear(x: number, y: number, callback: Function): void; + geoNear(x: number, y: number, options: Object, callback: Function): void; + geoHaystackSearch(x: number, y: number, callback: Function): void; + geoHaystackSearch(x: number, y: number, options: Object, callback: Function): void; + indexes(callback: Function): void; + aggregate(pipeline: any[], callback: (err: Error, results: any) => void): void; + aggregate(pipeline: any[], options: {readPreference: string}, callback: (err: Error, results: any) => void): void; + stats(options: {readPreference: string; scale: number}, callback: (err: Error, results: CollStats) => void): void; + stats(callback: (err: Error, results: CollStats) => void): void; + + hint: any; + } + + export interface MapReduceOptions { + out?: Object; + query?: Object; + sort?: Object; + limit?: number; + keeptemp?: boolean; + finalize?: any; + scope?: Object; + jsMode?: boolean; + verbose?: boolean; + readPreference?: string; + } + + export interface IndexOptions { + w?: any; + wtimeout?: number; + fsync?: boolean; + journal?: boolean; + unique?: boolean; + sparse?: boolean; + background?: boolean; + dropDups?: boolean; + min?: number; + max?: number; + v?: number; + expireAfterSeconds?: number; + name?: string; + } + + // Class documentation : http://mongodb.github.io/node-mongodb-native/api-generated/cursor.html + // Last update: doc. version 1.3.13 (29.08.2013) + export class Cursor { + // INTERNAL TYPE + // constructor (db: Db, collection: Collection, selector, fields, skip, limit, sort, hint, explain, snapshot, timeout, tailable, batchSize, slaveOk, raw, read, returnKey, maxScan, min, max, showDiskLoc, comment, awaitdata, numberOfRetries, dbName, tailableRetryInterval, exhaust, partial); + // constructor(db: Db, collection: Collection, selector, fields, options); + + rewind() : Cursor; + toArray(callback: (err: Error, results: any[]) => any) : void; + each(callback: (err: Error, item: any) => void): void; + forEach(iterator: (document: any) => void, callback: (err: Error) => void): void; + count(applySkipLimit: boolean, callback: (err: Error, count: number) => void) : void; + + sort(keyOrList: any, callback? : (err: Error, result: any) => void): Cursor; + + // this determines how the results are sorted. "asc", "ascending" or 1 for asceding order while "desc", "desceding or -1 for descending order. Note that the strings are case insensitive. + sort(keyOrList: String, direction : string, callback : (err: Error, result: any) => void): Cursor; + limit(limit: number, callback?: (err: Error, result: any) => void): Cursor; + setReadPreference(preference: string, callback?: Function): Cursor; + skip(skip: number, callback?: (err: Error, result: any) => void): Cursor; + batchSize(batchSize: number, callback?: (err: Error, result: any) => void): Cursor; + + nextObject(callback: (err: Error, doc: any) => void): void; + next(callback: (err: Error, doc: any) => void): void; + explain(callback: (err: Error, result: any) => void) : void; + + stream(): CursorStream; + + close(callback: (err: Error, result: any) => void) : void; + isClosed(): boolean; + + public static INIT: number; + public static OPEN: number; + public static CLOSED: number; + public static GET_MORE: number; + } + + // Class documentation : http://mongodb.github.io/node-mongodb-native/api-generated/cursorstream.html + // Last update: doc. version 1.3.13 (29.08.2013) + export class CursorStream { + constructor(cursor: Cursor); + + public pause(): any; + public resume(): any; + public destroy(): any; + } + + export interface CollectionFindOptions { + limit?: number; + sort?: any; + fields?: Object; + skip?: number; + hint?: Object; + explain?: boolean; + snapshot?: boolean; + timeout?: boolean; + tailtable?: boolean; + tailableRetryInterval?: number; + numberOfRetries?: number; + awaitdata?: boolean; + oplogReplay?: boolean; + exhaust?: boolean; + batchSize?: number; + returnKey?: boolean; + maxScan?: number; + min?: number; + max?: number; + showDiskLoc?: boolean; + comment?: String; + raw?: boolean; + readPreference?: String; + partial?: boolean; + } + + export interface MongoCollectionOptions { + safe?: any; + serializeFunctions?: any; + raw?: boolean; + pkFactory?: any; + readPreference?: string; + } +} diff --git a/typings/DefinitelyTyped/node/node.d.ts b/typings/DefinitelyTyped/node/node.d.ts new file mode 100644 index 0000000..999ca9a --- /dev/null +++ b/typings/DefinitelyTyped/node/node.d.ts @@ -0,0 +1,1406 @@ +// Type definitions for Node.js v0.12.0 +// Project: http://nodejs.org/ +// Definitions by: Microsoft TypeScript , DefinitelyTyped +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +/************************************************ +* * +* Node.js v0.12.0 API * +* * +************************************************/ + +/************************************************ +* * +* GLOBAL * +* * +************************************************/ +declare var process: NodeJS.Process; +declare var global: any; + +declare var __filename: string; +declare var __dirname: string; + +declare function setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer; +declare function clearTimeout(timeoutId: NodeJS.Timer): void; +declare function setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer; +declare function clearInterval(intervalId: NodeJS.Timer): void; +declare function setImmediate(callback: (...args: any[]) => void, ...args: any[]): any; +declare function clearImmediate(immediateId: any): void; + +declare var require: { + (id: string): any; + resolve(id:string): string; + cache: any; + extensions: any; + main: any; +}; + +declare var module: { + exports: any; + require(id: string): any; + id: string; + filename: string; + loaded: boolean; + parent: any; + children: any[]; +}; + +// Same as module.exports +declare var exports: any; +declare var SlowBuffer: { + new (str: string, encoding?: string): Buffer; + new (size: number): Buffer; + new (size: Uint8Array): Buffer; + new (array: any[]): Buffer; + prototype: Buffer; + isBuffer(obj: any): boolean; + byteLength(string: string, encoding?: string): number; + concat(list: Buffer[], totalLength?: number): Buffer; +}; + + +// Buffer class +interface Buffer extends NodeBuffer {} +declare var Buffer: { + new (str: string, encoding?: string): Buffer; + new (size: number): Buffer; + new (size: Uint8Array): Buffer; + new (array: any[]): Buffer; + prototype: Buffer; + isBuffer(obj: any): boolean; + byteLength(string: string, encoding?: string): number; + concat(list: Buffer[], totalLength?: number): Buffer; +}; + +/************************************************ +* * +* GLOBAL INTERFACES * +* * +************************************************/ +declare module NodeJS { + export interface ErrnoException extends Error { + errno?: any; + code?: string; + path?: string; + syscall?: string; + } + + export interface EventEmitter { + addListener(event: string, listener: Function): EventEmitter; + on(event: string, listener: Function): EventEmitter; + once(event: string, listener: Function): EventEmitter; + removeListener(event: string, listener: Function): EventEmitter; + removeAllListeners(event?: string): EventEmitter; + setMaxListeners(n: number): void; + listeners(event: string): Function[]; + emit(event: string, ...args: any[]): boolean; + } + + export interface ReadableStream extends EventEmitter { + readable: boolean; + read(size?: number): any; + setEncoding(encoding: string): void; + pause(): void; + resume(): void; + pipe(destination: T, options?: { end?: boolean; }): T; + unpipe(destination?: T): void; + unshift(chunk: string): void; + unshift(chunk: Buffer): void; + wrap(oldStream: ReadableStream): ReadableStream; + } + + export interface WritableStream extends EventEmitter { + writable: boolean; + write(buffer: Buffer, cb?: Function): boolean; + write(str: string, cb?: Function): boolean; + write(str: string, encoding?: string, cb?: Function): boolean; + end(): void; + end(buffer: Buffer, cb?: Function): void; + end(str: string, cb?: Function): void; + end(str: string, encoding?: string, cb?: Function): void; + } + + export interface ReadWriteStream extends ReadableStream, WritableStream {} + + export interface Process extends EventEmitter { + stdout: WritableStream; + stderr: WritableStream; + stdin: ReadableStream; + argv: string[]; + execPath: string; + abort(): void; + chdir(directory: string): void; + cwd(): string; + env: any; + exit(code?: number): void; + getgid(): number; + setgid(id: number): void; + setgid(id: string): void; + getuid(): number; + setuid(id: number): void; + setuid(id: string): void; + version: string; + versions: { + http_parser: string; + node: string; + v8: string; + ares: string; + uv: string; + zlib: string; + openssl: string; + }; + config: { + target_defaults: { + cflags: any[]; + default_configuration: string; + defines: string[]; + include_dirs: string[]; + libraries: string[]; + }; + variables: { + clang: number; + host_arch: string; + node_install_npm: boolean; + node_install_waf: boolean; + node_prefix: string; + node_shared_openssl: boolean; + node_shared_v8: boolean; + node_shared_zlib: boolean; + node_use_dtrace: boolean; + node_use_etw: boolean; + node_use_openssl: boolean; + target_arch: string; + v8_no_strict_aliasing: number; + v8_use_snapshot: boolean; + visibility: string; + }; + }; + kill(pid: number, signal?: string): void; + pid: number; + title: string; + arch: string; + platform: string; + memoryUsage(): { rss: number; heapTotal: number; heapUsed: number; }; + nextTick(callback: Function): void; + umask(mask?: number): number; + uptime(): number; + hrtime(time?:number[]): number[]; + + // Worker + send?(message: any, sendHandle?: any): void; + } + + export interface Timer { + ref() : void; + unref() : void; + } +} + +/** + * @deprecated + */ +interface NodeBuffer { + [index: number]: number; + write(string: string, offset?: number, length?: number, encoding?: string): number; + toString(encoding?: string, start?: number, end?: number): string; + toJSON(): any; + length: number; + copy(targetBuffer: Buffer, targetStart?: number, sourceStart?: number, sourceEnd?: number): number; + slice(start?: number, end?: number): Buffer; + readUInt8(offset: number, noAsset?: boolean): number; + readUInt16LE(offset: number, noAssert?: boolean): number; + readUInt16BE(offset: number, noAssert?: boolean): number; + readUInt32LE(offset: number, noAssert?: boolean): number; + readUInt32BE(offset: number, noAssert?: boolean): number; + readInt8(offset: number, noAssert?: boolean): number; + readInt16LE(offset: number, noAssert?: boolean): number; + readInt16BE(offset: number, noAssert?: boolean): number; + readInt32LE(offset: number, noAssert?: boolean): number; + readInt32BE(offset: number, noAssert?: boolean): number; + readFloatLE(offset: number, noAssert?: boolean): number; + readFloatBE(offset: number, noAssert?: boolean): number; + readDoubleLE(offset: number, noAssert?: boolean): number; + readDoubleBE(offset: number, noAssert?: boolean): number; + writeUInt8(value: number, offset: number, noAssert?: boolean): void; + writeUInt16LE(value: number, offset: number, noAssert?: boolean): void; + writeUInt16BE(value: number, offset: number, noAssert?: boolean): void; + writeUInt32LE(value: number, offset: number, noAssert?: boolean): void; + writeUInt32BE(value: number, offset: number, noAssert?: boolean): void; + writeInt8(value: number, offset: number, noAssert?: boolean): void; + writeInt16LE(value: number, offset: number, noAssert?: boolean): void; + writeInt16BE(value: number, offset: number, noAssert?: boolean): void; + writeInt32LE(value: number, offset: number, noAssert?: boolean): void; + writeInt32BE(value: number, offset: number, noAssert?: boolean): void; + writeFloatLE(value: number, offset: number, noAssert?: boolean): void; + writeFloatBE(value: number, offset: number, noAssert?: boolean): void; + writeDoubleLE(value: number, offset: number, noAssert?: boolean): void; + writeDoubleBE(value: number, offset: number, noAssert?: boolean): void; + fill(value: any, offset?: number, end?: number): void; +} + +/************************************************ +* * +* MODULES * +* * +************************************************/ +declare module "buffer" { + export var INSPECT_MAX_BYTES: number; +} + +declare module "querystring" { + export function stringify(obj: any, sep?: string, eq?: string): string; + export function parse(str: string, sep?: string, eq?: string, options?: { maxKeys?: number; }): any; + export function escape(str: string): string; + export function unescape(str: string): string; +} + +declare module "events" { + export class EventEmitter implements NodeJS.EventEmitter { + static listenerCount(emitter: EventEmitter, event: string): number; + + addListener(event: string, listener: Function): EventEmitter; + on(event: string, listener: Function): EventEmitter; + once(event: string, listener: Function): EventEmitter; + removeListener(event: string, listener: Function): EventEmitter; + removeAllListeners(event?: string): EventEmitter; + setMaxListeners(n: number): void; + listeners(event: string): Function[]; + emit(event: string, ...args: any[]): boolean; + } +} + +declare module "http" { + import events = require("events"); + import net = require("net"); + import stream = require("stream"); + + export interface Server extends events.EventEmitter { + listen(port: number, hostname?: string, backlog?: number, callback?: Function): Server; + listen(path: string, callback?: Function): Server; + listen(handle: any, listeningListener?: Function): Server; + close(cb?: any): Server; + address(): { port: number; family: string; address: string; }; + maxHeadersCount: number; + } + export interface ServerRequest extends events.EventEmitter, stream.Readable { + method: string; + url: string; + headers: any; + trailers: string; + httpVersion: string; + setEncoding(encoding?: string): void; + pause(): void; + resume(): void; + connection: net.Socket; + } + export interface ServerResponse extends events.EventEmitter, stream.Writable { + // Extended base methods + write(buffer: Buffer): boolean; + write(buffer: Buffer, cb?: Function): boolean; + write(str: string, cb?: Function): boolean; + write(str: string, encoding?: string, cb?: Function): boolean; + write(str: string, encoding?: string, fd?: string): boolean; + + writeContinue(): void; + writeHead(statusCode: number, reasonPhrase?: string, headers?: any): void; + writeHead(statusCode: number, headers?: any): void; + statusCode: number; + setHeader(name: string, value: string): void; + sendDate: boolean; + getHeader(name: string): string; + removeHeader(name: string): void; + write(chunk: any, encoding?: string): any; + addTrailers(headers: any): void; + + // Extended base methods + end(): void; + end(buffer: Buffer, cb?: Function): void; + end(str: string, cb?: Function): void; + end(str: string, encoding?: string, cb?: Function): void; + end(data?: any, encoding?: string): void; + } + export interface ClientRequest extends events.EventEmitter, stream.Writable { + // Extended base methods + write(buffer: Buffer): boolean; + write(buffer: Buffer, cb?: Function): boolean; + write(str: string, cb?: Function): boolean; + write(str: string, encoding?: string, cb?: Function): boolean; + write(str: string, encoding?: string, fd?: string): boolean; + + write(chunk: any, encoding?: string): void; + abort(): void; + setTimeout(timeout: number, callback?: Function): void; + setNoDelay(noDelay?: boolean): void; + setSocketKeepAlive(enable?: boolean, initialDelay?: number): void; + + // Extended base methods + end(): void; + end(buffer: Buffer, cb?: Function): void; + end(str: string, cb?: Function): void; + end(str: string, encoding?: string, cb?: Function): void; + end(data?: any, encoding?: string): void; + } + export interface ClientResponse extends events.EventEmitter, stream.Readable { + statusCode: number; + httpVersion: string; + headers: any; + trailers: any; + setEncoding(encoding?: string): void; + pause(): void; + resume(): void; + } + + export interface AgentOptions { + /** + * Keep sockets around in a pool to be used by other requests in the future. Default = false + */ + keepAlive?: boolean; + /** + * When using HTTP KeepAlive, how often to send TCP KeepAlive packets over sockets being kept alive. Default = 1000. + * Only relevant if keepAlive is set to true. + */ + keepAliveMsecs?: number; + /** + * Maximum number of sockets to allow per host. Default for Node 0.10 is 5, default for Node 0.12 is Infinity + */ + maxSockets?: number; + /** + * Maximum number of sockets to leave open in a free state. Only relevant if keepAlive is set to true. Default = 256. + */ + maxFreeSockets?: number; + } + + export class Agent { + maxSockets: number; + sockets: any; + requests: any; + + constructor(opts?: AgentOptions); + + /** + * Destroy any sockets that are currently in use by the agent. + * It is usually not necessary to do this. However, if you are using an agent with KeepAlive enabled, + * then it is best to explicitly shut down the agent when you know that it will no longer be used. Otherwise, + * sockets may hang open for quite a long time before the server terminates them. + */ + destroy(): void; + } + + export var STATUS_CODES: { + [errorCode: number]: string; + [errorCode: string]: string; + }; + export function createServer(requestListener?: (request: ServerRequest, response: ServerResponse) =>void ): Server; + export function createClient(port?: number, host?: string): any; + export function request(options: any, callback?: Function): ClientRequest; + export function get(options: any, callback?: Function): ClientRequest; + export var globalAgent: Agent; +} + +declare module "cluster" { + import child = require("child_process"); + import events = require("events"); + + export interface ClusterSettings { + exec?: string; + args?: string[]; + silent?: boolean; + } + + export class Worker extends events.EventEmitter { + id: string; + process: child.ChildProcess; + suicide: boolean; + send(message: any, sendHandle?: any): void; + kill(signal?: string): void; + destroy(signal?: string): void; + disconnect(): void; + } + + export var settings: ClusterSettings; + export var isMaster: boolean; + export var isWorker: boolean; + export function setupMaster(settings?: ClusterSettings): void; + export function fork(env?: any): Worker; + export function disconnect(callback?: Function): void; + export var worker: Worker; + export var workers: Worker[]; + + // Event emitter + export function addListener(event: string, listener: Function): void; + export function on(event: string, listener: Function): any; + export function once(event: string, listener: Function): void; + export function removeListener(event: string, listener: Function): void; + export function removeAllListeners(event?: string): void; + export function setMaxListeners(n: number): void; + export function listeners(event: string): Function[]; + export function emit(event: string, ...args: any[]): boolean; +} + +declare module "zlib" { + import stream = require("stream"); + export interface ZlibOptions { chunkSize?: number; windowBits?: number; level?: number; memLevel?: number; strategy?: number; dictionary?: any; } + + export interface Gzip extends stream.Transform { } + export interface Gunzip extends stream.Transform { } + export interface Deflate extends stream.Transform { } + export interface Inflate extends stream.Transform { } + export interface DeflateRaw extends stream.Transform { } + export interface InflateRaw extends stream.Transform { } + export interface Unzip extends stream.Transform { } + + export function createGzip(options?: ZlibOptions): Gzip; + export function createGunzip(options?: ZlibOptions): Gunzip; + export function createDeflate(options?: ZlibOptions): Deflate; + export function createInflate(options?: ZlibOptions): Inflate; + export function createDeflateRaw(options?: ZlibOptions): DeflateRaw; + export function createInflateRaw(options?: ZlibOptions): InflateRaw; + export function createUnzip(options?: ZlibOptions): Unzip; + + export function deflate(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function deflateRaw(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function gzip(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function gunzip(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function inflate(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function inflateRaw(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function unzip(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + + // Constants + export var Z_NO_FLUSH: number; + export var Z_PARTIAL_FLUSH: number; + export var Z_SYNC_FLUSH: number; + export var Z_FULL_FLUSH: number; + export var Z_FINISH: number; + export var Z_BLOCK: number; + export var Z_TREES: number; + export var Z_OK: number; + export var Z_STREAM_END: number; + export var Z_NEED_DICT: number; + export var Z_ERRNO: number; + export var Z_STREAM_ERROR: number; + export var Z_DATA_ERROR: number; + export var Z_MEM_ERROR: number; + export var Z_BUF_ERROR: number; + export var Z_VERSION_ERROR: number; + export var Z_NO_COMPRESSION: number; + export var Z_BEST_SPEED: number; + export var Z_BEST_COMPRESSION: number; + export var Z_DEFAULT_COMPRESSION: number; + export var Z_FILTERED: number; + export var Z_HUFFMAN_ONLY: number; + export var Z_RLE: number; + export var Z_FIXED: number; + export var Z_DEFAULT_STRATEGY: number; + export var Z_BINARY: number; + export var Z_TEXT: number; + export var Z_ASCII: number; + export var Z_UNKNOWN: number; + export var Z_DEFLATED: number; + export var Z_NULL: number; +} + +declare module "os" { + export function tmpdir(): string; + export function hostname(): string; + export function type(): string; + export function platform(): string; + export function arch(): string; + export function release(): string; + export function uptime(): number; + export function loadavg(): number[]; + export function totalmem(): number; + export function freemem(): number; + export function cpus(): { model: string; speed: number; times: { user: number; nice: number; sys: number; idle: number; irq: number; }; }[]; + export function networkInterfaces(): any; + export var EOL: string; +} + +declare module "https" { + import tls = require("tls"); + import events = require("events"); + import http = require("http"); + + export interface ServerOptions { + pfx?: any; + key?: any; + passphrase?: string; + cert?: any; + ca?: any; + crl?: any; + ciphers?: string; + honorCipherOrder?: boolean; + requestCert?: boolean; + rejectUnauthorized?: boolean; + NPNProtocols?: any; + SNICallback?: (servername: string) => any; + } + + export interface RequestOptions { + host?: string; + hostname?: string; + port?: number; + path?: string; + method?: string; + headers?: any; + auth?: string; + agent?: any; + pfx?: any; + key?: any; + passphrase?: string; + cert?: any; + ca?: any; + ciphers?: string; + rejectUnauthorized?: boolean; + } + + export interface Agent { + maxSockets: number; + sockets: any; + requests: any; + } + export var Agent: { + new (options?: RequestOptions): Agent; + }; + export interface Server extends tls.Server { } + export function createServer(options: ServerOptions, requestListener?: Function): Server; + export function request(options: RequestOptions, callback?: (res: http.ClientResponse) =>void ): http.ClientRequest; + export function get(options: RequestOptions, callback?: (res: http.ClientResponse) =>void ): http.ClientRequest; + export var globalAgent: Agent; +} + +declare module "punycode" { + export function decode(string: string): string; + export function encode(string: string): string; + export function toUnicode(domain: string): string; + export function toASCII(domain: string): string; + export var ucs2: ucs2; + interface ucs2 { + decode(string: string): string; + encode(codePoints: number[]): string; + } + export var version: any; +} + +declare module "repl" { + import stream = require("stream"); + import events = require("events"); + + export interface ReplOptions { + prompt?: string; + input?: NodeJS.ReadableStream; + output?: NodeJS.WritableStream; + terminal?: boolean; + eval?: Function; + useColors?: boolean; + useGlobal?: boolean; + ignoreUndefined?: boolean; + writer?: Function; + } + export function start(options: ReplOptions): events.EventEmitter; +} + +declare module "readline" { + import events = require("events"); + import stream = require("stream"); + + export interface ReadLine extends events.EventEmitter { + setPrompt(prompt: string, length: number): void; + prompt(preserveCursor?: boolean): void; + question(query: string, callback: Function): void; + pause(): void; + resume(): void; + close(): void; + write(data: any, key?: any): void; + } + export interface ReadLineOptions { + input: NodeJS.ReadableStream; + output: NodeJS.WritableStream; + completer?: Function; + terminal?: boolean; + } + export function createInterface(options: ReadLineOptions): ReadLine; +} + +declare module "vm" { + export interface Context { } + export interface Script { + runInThisContext(): void; + runInNewContext(sandbox?: Context): void; + } + export function runInThisContext(code: string, filename?: string): void; + export function runInNewContext(code: string, sandbox?: Context, filename?: string): void; + export function runInContext(code: string, context: Context, filename?: string): void; + export function createContext(initSandbox?: Context): Context; + export function createScript(code: string, filename?: string): Script; +} + +declare module "child_process" { + import events = require("events"); + import stream = require("stream"); + + export interface ChildProcess extends events.EventEmitter { + stdin: stream.Writable; + stdout: stream.Readable; + stderr: stream.Readable; + pid: number; + kill(signal?: string): void; + send(message: any, sendHandle?: any): void; + disconnect(): void; + } + + export function spawn(command: string, args?: string[], options?: { + cwd?: string; + stdio?: any; + custom?: any; + env?: any; + detached?: boolean; + }): ChildProcess; + export function exec(command: string, options: { + cwd?: string; + stdio?: any; + customFds?: any; + env?: any; + encoding?: string; + timeout?: number; + maxBuffer?: number; + killSignal?: string; + }, callback: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; + export function exec(command: string, callback: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; + export function execFile(file: string, + callback?: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; + export function execFile(file: string, args?: string[], + callback?: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; + export function execFile(file: string, args?: string[], options?: { + cwd?: string; + stdio?: any; + customFds?: any; + env?: any; + encoding?: string; + timeout?: number; + maxBuffer?: string; + killSignal?: string; + }, callback?: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; + export function fork(modulePath: string, args?: string[], options?: { + cwd?: string; + env?: any; + encoding?: string; + }): ChildProcess; + export function execSync(command: string, options?: { + cwd?: string; + input?: string|Buffer; + stdio?: any; + env?: any; + uid?: number; + gid?: number; + timeout?: number; + maxBuffer?: number; + killSignal?: string; + encoding?: string; + }): ChildProcess; + export function execFileSync(command: string, args?: string[], options?: { + cwd?: string; + input?: string|Buffer; + stdio?: any; + env?: any; + uid?: number; + gid?: number; + timeout?: number; + maxBuffer?: number; + killSignal?: string; + encoding?: string; + }): ChildProcess; +} + +declare module "url" { + export interface Url { + href: string; + protocol: string; + auth: string; + hostname: string; + port: string; + host: string; + pathname: string; + search: string; + query: any; // string | Object + slashes: boolean; + hash?: string; + path?: string; + } + + export interface UrlOptions { + protocol?: string; + auth?: string; + hostname?: string; + port?: string; + host?: string; + pathname?: string; + search?: string; + query?: any; + hash?: string; + path?: string; + } + + export function parse(urlStr: string, parseQueryString?: boolean , slashesDenoteHost?: boolean ): Url; + export function format(url: UrlOptions): string; + export function resolve(from: string, to: string): string; +} + +declare module "dns" { + export function lookup(domain: string, family: number, callback: (err: Error, address: string, family: number) =>void ): string; + export function lookup(domain: string, callback: (err: Error, address: string, family: number) =>void ): string; + export function resolve(domain: string, rrtype: string, callback: (err: Error, addresses: string[]) =>void ): string[]; + export function resolve(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; + export function resolve4(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; + export function resolve6(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; + export function resolveMx(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; + export function resolveTxt(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; + export function resolveSrv(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; + export function resolveNs(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; + export function resolveCname(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; + export function reverse(ip: string, callback: (err: Error, domains: string[]) =>void ): string[]; +} + +declare module "net" { + import stream = require("stream"); + + export interface Socket extends stream.Duplex { + // Extended base methods + write(buffer: Buffer): boolean; + write(buffer: Buffer, cb?: Function): boolean; + write(str: string, cb?: Function): boolean; + write(str: string, encoding?: string, cb?: Function): boolean; + write(str: string, encoding?: string, fd?: string): boolean; + + connect(port: number, host?: string, connectionListener?: Function): void; + connect(path: string, connectionListener?: Function): void; + bufferSize: number; + setEncoding(encoding?: string): void; + write(data: any, encoding?: string, callback?: Function): void; + destroy(): void; + pause(): void; + resume(): void; + setTimeout(timeout: number, callback?: Function): void; + setNoDelay(noDelay?: boolean): void; + setKeepAlive(enable?: boolean, initialDelay?: number): void; + address(): { port: number; family: string; address: string; }; + unref(): void; + ref(): void; + + remoteAddress: string; + remotePort: number; + bytesRead: number; + bytesWritten: number; + + // Extended base methods + end(): void; + end(buffer: Buffer, cb?: Function): void; + end(str: string, cb?: Function): void; + end(str: string, encoding?: string, cb?: Function): void; + end(data?: any, encoding?: string): void; + } + + export var Socket: { + new (options?: { fd?: string; type?: string; allowHalfOpen?: boolean; }): Socket; + }; + + export interface Server extends Socket { + listen(port: number, host?: string, backlog?: number, listeningListener?: Function): Server; + listen(path: string, listeningListener?: Function): Server; + listen(handle: any, listeningListener?: Function): Server; + close(callback?: Function): Server; + address(): { port: number; family: string; address: string; }; + maxConnections: number; + connections: number; + } + export function createServer(connectionListener?: (socket: Socket) =>void ): Server; + export function createServer(options?: { allowHalfOpen?: boolean; }, connectionListener?: (socket: Socket) =>void ): Server; + export function connect(options: { allowHalfOpen?: boolean; }, connectionListener?: Function): Socket; + export function connect(port: number, host?: string, connectionListener?: Function): Socket; + export function connect(path: string, connectionListener?: Function): Socket; + export function createConnection(options: { allowHalfOpen?: boolean; }, connectionListener?: Function): Socket; + export function createConnection(port: number, host?: string, connectionListener?: Function): Socket; + export function createConnection(path: string, connectionListener?: Function): Socket; + export function isIP(input: string): number; + export function isIPv4(input: string): boolean; + export function isIPv6(input: string): boolean; +} + +declare module "dgram" { + import events = require("events"); + + interface RemoteInfo { + address: string; + port: number; + size: number; + } + + interface AddressInfo { + address: string; + family: string; + port: number; + } + + export function createSocket(type: string, callback?: (msg: Buffer, rinfo: RemoteInfo) => void): Socket; + + interface Socket extends events.EventEmitter { + send(buf: Buffer, offset: number, length: number, port: number, address: string, callback?: (error: Error, bytes: number) => void): void; + bind(port: number, address?: string, callback?: () => void): void; + close(): void; + address(): AddressInfo; + setBroadcast(flag: boolean): void; + setMulticastTTL(ttl: number): void; + setMulticastLoopback(flag: boolean): void; + addMembership(multicastAddress: string, multicastInterface?: string): void; + dropMembership(multicastAddress: string, multicastInterface?: string): void; + } +} + +declare module "fs" { + import stream = require("stream"); + import events = require("events"); + + interface Stats { + isFile(): boolean; + isDirectory(): boolean; + isBlockDevice(): boolean; + isCharacterDevice(): boolean; + isSymbolicLink(): boolean; + isFIFO(): boolean; + isSocket(): boolean; + dev: number; + ino: number; + mode: number; + nlink: number; + uid: number; + gid: number; + rdev: number; + size: number; + blksize: number; + blocks: number; + atime: Date; + mtime: Date; + ctime: Date; + } + + interface FSWatcher extends events.EventEmitter { + close(): void; + } + + export interface ReadStream extends stream.Readable { + close(): void; + } + export interface WriteStream extends stream.Writable { + close(): void; + } + + export function rename(oldPath: string, newPath: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function renameSync(oldPath: string, newPath: string): void; + export function truncate(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function truncate(path: string, len: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function truncateSync(path: string, len?: number): void; + export function ftruncate(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function ftruncate(fd: number, len: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function ftruncateSync(fd: number, len?: number): void; + export function chown(path: string, uid: number, gid: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function chownSync(path: string, uid: number, gid: number): void; + export function fchown(fd: number, uid: number, gid: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function fchownSync(fd: number, uid: number, gid: number): void; + export function lchown(path: string, uid: number, gid: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function lchownSync(path: string, uid: number, gid: number): void; + export function chmod(path: string, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function chmod(path: string, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function chmodSync(path: string, mode: number): void; + export function chmodSync(path: string, mode: string): void; + export function fchmod(fd: number, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function fchmod(fd: number, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function fchmodSync(fd: number, mode: number): void; + export function fchmodSync(fd: number, mode: string): void; + export function lchmod(path: string, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function lchmod(path: string, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function lchmodSync(path: string, mode: number): void; + export function lchmodSync(path: string, mode: string): void; + export function stat(path: string, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void; + export function lstat(path: string, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void; + export function fstat(fd: number, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void; + export function statSync(path: string): Stats; + export function lstatSync(path: string): Stats; + export function fstatSync(fd: number): Stats; + export function link(srcpath: string, dstpath: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function linkSync(srcpath: string, dstpath: string): void; + export function symlink(srcpath: string, dstpath: string, type?: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function symlinkSync(srcpath: string, dstpath: string, type?: string): void; + export function readlink(path: string, callback?: (err: NodeJS.ErrnoException, linkString: string) => any): void; + export function readlinkSync(path: string): string; + export function realpath(path: string, callback?: (err: NodeJS.ErrnoException, resolvedPath: string) => any): void; + export function realpath(path: string, cache: {[path: string]: string}, callback: (err: NodeJS.ErrnoException, resolvedPath: string) =>any): void; + export function realpathSync(path: string, cache?: {[path: string]: string}): string; + export function unlink(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function unlinkSync(path: string): void; + export function rmdir(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function rmdirSync(path: string): void; + export function mkdir(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function mkdir(path: string, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function mkdir(path: string, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function mkdirSync(path: string, mode?: number): void; + export function mkdirSync(path: string, mode?: string): void; + export function readdir(path: string, callback?: (err: NodeJS.ErrnoException, files: string[]) => void): void; + export function readdirSync(path: string): string[]; + export function close(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function closeSync(fd: number): void; + export function open(path: string, flags: string, callback?: (err: NodeJS.ErrnoException, fd: number) => any): void; + export function open(path: string, flags: string, mode: number, callback?: (err: NodeJS.ErrnoException, fd: number) => any): void; + export function open(path: string, flags: string, mode: string, callback?: (err: NodeJS.ErrnoException, fd: number) => any): void; + export function openSync(path: string, flags: string, mode?: number): number; + export function openSync(path: string, flags: string, mode?: string): number; + export function utimes(path: string, atime: number, mtime: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function utimes(path: string, atime: Date, mtime: Date, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function utimesSync(path: string, atime: number, mtime: number): void; + export function utimesSync(path: string, atime: Date, mtime: Date): void; + export function futimes(fd: number, atime: number, mtime: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function futimes(fd: number, atime: Date, mtime: Date, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function futimesSync(fd: number, atime: number, mtime: number): void; + export function futimesSync(fd: number, atime: Date, mtime: Date): void; + export function fsync(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function fsyncSync(fd: number): void; + export function write(fd: number, buffer: Buffer, offset: number, length: number, position: number, callback?: (err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void): void; + export function writeSync(fd: number, buffer: Buffer, offset: number, length: number, position: number): number; + export function read(fd: number, buffer: Buffer, offset: number, length: number, position: number, callback?: (err: NodeJS.ErrnoException, bytesRead: number, buffer: Buffer) => void): void; + export function readSync(fd: number, buffer: Buffer, offset: number, length: number, position: number): number; + export function readFile(filename: string, encoding: string, callback: (err: NodeJS.ErrnoException, data: string) => void): void; + export function readFile(filename: string, options: { encoding: string; flag?: string; }, callback: (err: NodeJS.ErrnoException, data: string) => void): void; + export function readFile(filename: string, options: { flag?: string; }, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void; + export function readFile(filename: string, callback: (err: NodeJS.ErrnoException, data: Buffer) => void ): void; + export function readFileSync(filename: string, encoding: string): string; + export function readFileSync(filename: string, options: { encoding: string; flag?: string; }): string; + export function readFileSync(filename: string, options?: { flag?: string; }): Buffer; + export function writeFile(filename: string, data: any, callback?: (err: NodeJS.ErrnoException) => void): void; + export function writeFile(filename: string, data: any, options: { encoding?: string; mode?: number; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; + export function writeFile(filename: string, data: any, options: { encoding?: string; mode?: string; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; + export function writeFileSync(filename: string, data: any, options?: { encoding?: string; mode?: number; flag?: string; }): void; + export function writeFileSync(filename: string, data: any, options?: { encoding?: string; mode?: string; flag?: string; }): void; + export function appendFile(filename: string, data: any, options: { encoding?: string; mode?: number; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; + export function appendFile(filename: string, data: any, options: { encoding?: string; mode?: string; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; + export function appendFile(filename: string, data: any, callback?: (err: NodeJS.ErrnoException) => void): void; + export function appendFileSync(filename: string, data: any, options?: { encoding?: string; mode?: number; flag?: string; }): void; + export function appendFileSync(filename: string, data: any, options?: { encoding?: string; mode?: string; flag?: string; }): void; + export function watchFile(filename: string, listener: (curr: Stats, prev: Stats) => void): void; + export function watchFile(filename: string, options: { persistent?: boolean; interval?: number; }, listener: (curr: Stats, prev: Stats) => void): void; + export function unwatchFile(filename: string, listener?: (curr: Stats, prev: Stats) => void): void; + export function watch(filename: string, listener?: (event: string, filename: string) => any): FSWatcher; + export function watch(filename: string, options: { persistent?: boolean; }, listener?: (event: string, filename: string) => any): FSWatcher; + export function exists(path: string, callback?: (exists: boolean) => void): void; + export function existsSync(path: string): boolean; + export function createReadStream(path: string, options?: { + flags?: string; + encoding?: string; + fd?: string; + mode?: number; + bufferSize?: number; + }): ReadStream; + export function createReadStream(path: string, options?: { + flags?: string; + encoding?: string; + fd?: string; + mode?: string; + bufferSize?: number; + }): ReadStream; + export function createWriteStream(path: string, options?: { + flags?: string; + encoding?: string; + string?: string; + }): WriteStream; +} + +declare module "path" { + + export interface ParsedPath { + root: string; + dir: string; + base: string; + ext: string; + name: string; + } + + export function normalize(p: string): string; + export function join(...paths: any[]): string; + export function resolve(...pathSegments: any[]): string; + export function isAbsolute(p: string): boolean; + export function relative(from: string, to: string): string; + export function dirname(p: string): string; + export function basename(p: string, ext?: string): string; + export function extname(p: string): string; + export var sep: string; + export var delimiter: string; + export function parse(p: string): ParsedPath; + export function format(pP: ParsedPath): string; +} + +declare module "string_decoder" { + export interface NodeStringDecoder { + write(buffer: Buffer): string; + detectIncompleteChar(buffer: Buffer): number; + } + export var StringDecoder: { + new (encoding: string): NodeStringDecoder; + }; +} + +declare module "tls" { + import crypto = require("crypto"); + import net = require("net"); + import stream = require("stream"); + + var CLIENT_RENEG_LIMIT: number; + var CLIENT_RENEG_WINDOW: number; + + export interface TlsOptions { + pfx?: any; //string or buffer + key?: any; //string or buffer + passphrase?: string; + cert?: any; + ca?: any; //string or buffer + crl?: any; //string or string array + ciphers?: string; + honorCipherOrder?: any; + requestCert?: boolean; + rejectUnauthorized?: boolean; + NPNProtocols?: any; //array or Buffer; + SNICallback?: (servername: string) => any; + } + + export interface ConnectionOptions { + host?: string; + port?: number; + socket?: net.Socket; + pfx?: any; //string | Buffer + key?: any; //string | Buffer + passphrase?: string; + cert?: any; //string | Buffer + ca?: any; //Array of string | Buffer + rejectUnauthorized?: boolean; + NPNProtocols?: any; //Array of string | Buffer + servername?: string; + } + + export interface Server extends net.Server { + // Extended base methods + listen(port: number, host?: string, backlog?: number, listeningListener?: Function): Server; + listen(path: string, listeningListener?: Function): Server; + listen(handle: any, listeningListener?: Function): Server; + + listen(port: number, host?: string, callback?: Function): Server; + close(): Server; + address(): { port: number; family: string; address: string; }; + addContext(hostName: string, credentials: { + key: string; + cert: string; + ca: string; + }): void; + maxConnections: number; + connections: number; + } + + export interface ClearTextStream extends stream.Duplex { + authorized: boolean; + authorizationError: Error; + getPeerCertificate(): any; + getCipher: { + name: string; + version: string; + }; + address: { + port: number; + family: string; + address: string; + }; + remoteAddress: string; + remotePort: number; + } + + export interface SecurePair { + encrypted: any; + cleartext: any; + } + + export function createServer(options: TlsOptions, secureConnectionListener?: (cleartextStream: ClearTextStream) =>void ): Server; + export function connect(options: TlsOptions, secureConnectionListener?: () =>void ): ClearTextStream; + export function connect(port: number, host?: string, options?: ConnectionOptions, secureConnectListener?: () =>void ): ClearTextStream; + export function connect(port: number, options?: ConnectionOptions, secureConnectListener?: () =>void ): ClearTextStream; + export function createSecurePair(credentials?: crypto.Credentials, isServer?: boolean, requestCert?: boolean, rejectUnauthorized?: boolean): SecurePair; +} + +declare module "crypto" { + export interface CredentialDetails { + pfx: string; + key: string; + passphrase: string; + cert: string; + ca: any; //string | string array + crl: any; //string | string array + ciphers: string; + } + export interface Credentials { context?: any; } + export function createCredentials(details: CredentialDetails): Credentials; + export function createHash(algorithm: string): Hash; + export function createHmac(algorithm: string, key: string): Hmac; + export function createHmac(algorithm: string, key: Buffer): Hmac; + interface Hash { + update(data: any, input_encoding?: string): Hash; + digest(encoding: 'buffer'): Buffer; + digest(encoding: string): any; + digest(): Buffer; + } + interface Hmac { + update(data: any, input_encoding?: string): Hmac; + digest(encoding: 'buffer'): Buffer; + digest(encoding: string): any; + digest(): Buffer; + } + export function createCipher(algorithm: string, password: any): Cipher; + export function createCipheriv(algorithm: string, key: any, iv: any): Cipher; + interface Cipher { + update(data: Buffer): Buffer; + update(data: string, input_encoding?: string, output_encoding?: string): string; + final(): Buffer; + final(output_encoding: string): string; + setAutoPadding(auto_padding: boolean): void; + } + export function createDecipher(algorithm: string, password: any): Decipher; + export function createDecipheriv(algorithm: string, key: any, iv: any): Decipher; + interface Decipher { + update(data: Buffer): Buffer; + update(data: string, input_encoding?: string, output_encoding?: string): string; + final(): Buffer; + final(output_encoding: string): string; + setAutoPadding(auto_padding: boolean): void; + } + export function createSign(algorithm: string): Signer; + interface Signer { + update(data: any): void; + sign(private_key: string, output_format: string): string; + } + export function createVerify(algorith: string): Verify; + interface Verify { + update(data: any): void; + verify(object: string, signature: string, signature_format?: string): boolean; + } + export function createDiffieHellman(prime_length: number): DiffieHellman; + export function createDiffieHellman(prime: number, encoding?: string): DiffieHellman; + interface DiffieHellman { + generateKeys(encoding?: string): string; + computeSecret(other_public_key: string, input_encoding?: string, output_encoding?: string): string; + getPrime(encoding?: string): string; + getGenerator(encoding: string): string; + getPublicKey(encoding?: string): string; + getPrivateKey(encoding?: string): string; + setPublicKey(public_key: string, encoding?: string): void; + setPrivateKey(public_key: string, encoding?: string): void; + } + export function getDiffieHellman(group_name: string): DiffieHellman; + export function pbkdf2(password: string, salt: string, iterations: number, keylen: number, callback: (err: Error, derivedKey: Buffer) => any): void; + export function pbkdf2Sync(password: string, salt: string, iterations: number, keylen: number) : Buffer; + export function randomBytes(size: number): Buffer; + export function randomBytes(size: number, callback: (err: Error, buf: Buffer) =>void ): void; + export function pseudoRandomBytes(size: number): Buffer; + export function pseudoRandomBytes(size: number, callback: (err: Error, buf: Buffer) =>void ): void; +} + +declare module "stream" { + import events = require("events"); + + export interface Stream extends events.EventEmitter { + pipe(destination: T, options?: { end?: boolean; }): T; + } + + export interface ReadableOptions { + highWaterMark?: number; + encoding?: string; + objectMode?: boolean; + } + + export class Readable extends events.EventEmitter implements NodeJS.ReadableStream { + readable: boolean; + constructor(opts?: ReadableOptions); + _read(size: number): void; + read(size?: number): any; + setEncoding(encoding: string): void; + pause(): void; + resume(): void; + pipe(destination: T, options?: { end?: boolean; }): T; + unpipe(destination?: T): void; + unshift(chunk: string): void; + unshift(chunk: Buffer): void; + wrap(oldStream: NodeJS.ReadableStream): NodeJS.ReadableStream; + push(chunk: any, encoding?: string): boolean; + } + + export interface WritableOptions { + highWaterMark?: number; + decodeStrings?: boolean; + } + + export class Writable extends events.EventEmitter implements NodeJS.WritableStream { + writable: boolean; + constructor(opts?: WritableOptions); + _write(data: Buffer, encoding: string, callback: Function): void; + _write(data: string, encoding: string, callback: Function): void; + write(buffer: Buffer, cb?: Function): boolean; + write(str: string, cb?: Function): boolean; + write(str: string, encoding?: string, cb?: Function): boolean; + end(): void; + end(buffer: Buffer, cb?: Function): void; + end(str: string, cb?: Function): void; + end(str: string, encoding?: string, cb?: Function): void; + } + + export interface DuplexOptions extends ReadableOptions, WritableOptions { + allowHalfOpen?: boolean; + } + + // Note: Duplex extends both Readable and Writable. + export class Duplex extends Readable implements NodeJS.ReadWriteStream { + writable: boolean; + constructor(opts?: DuplexOptions); + _write(data: Buffer, encoding: string, callback: Function): void; + _write(data: string, encoding: string, callback: Function): void; + write(buffer: Buffer, cb?: Function): boolean; + write(str: string, cb?: Function): boolean; + write(str: string, encoding?: string, cb?: Function): boolean; + end(): void; + end(buffer: Buffer, cb?: Function): void; + end(str: string, cb?: Function): void; + end(str: string, encoding?: string, cb?: Function): void; + } + + export interface TransformOptions extends ReadableOptions, WritableOptions {} + + // Note: Transform lacks the _read and _write methods of Readable/Writable. + export class Transform extends events.EventEmitter implements NodeJS.ReadWriteStream { + readable: boolean; + writable: boolean; + constructor(opts?: TransformOptions); + _transform(chunk: Buffer, encoding: string, callback: Function): void; + _transform(chunk: string, encoding: string, callback: Function): void; + _flush(callback: Function): void; + read(size?: number): any; + setEncoding(encoding: string): void; + pause(): void; + resume(): void; + pipe(destination: T, options?: { end?: boolean; }): T; + unpipe(destination?: T): void; + unshift(chunk: string): void; + unshift(chunk: Buffer): void; + wrap(oldStream: NodeJS.ReadableStream): NodeJS.ReadableStream; + push(chunk: any, encoding?: string): boolean; + write(buffer: Buffer, cb?: Function): boolean; + write(str: string, cb?: Function): boolean; + write(str: string, encoding?: string, cb?: Function): boolean; + end(): void; + end(buffer: Buffer, cb?: Function): void; + end(str: string, cb?: Function): void; + end(str: string, encoding?: string, cb?: Function): void; + } + + export class PassThrough extends Transform {} +} + +declare module "util" { + export interface InspectOptions { + showHidden?: boolean; + depth?: number; + colors?: boolean; + customInspect?: boolean; + } + + export function format(format: any, ...param: any[]): string; + export function debug(string: string): void; + export function error(...param: any[]): void; + export function puts(...param: any[]): void; + export function print(...param: any[]): void; + export function log(string: string): void; + export function inspect(object: any, showHidden?: boolean, depth?: number, color?: boolean): string; + export function inspect(object: any, options: InspectOptions): string; + export function isArray(object: any): boolean; + export function isRegExp(object: any): boolean; + export function isDate(object: any): boolean; + export function isError(object: any): boolean; + export function inherits(constructor: any, superConstructor: any): void; +} + +declare module "assert" { + function internal (value: any, message?: string): void; + module internal { + export class AssertionError implements Error { + name: string; + message: string; + actual: any; + expected: any; + operator: string; + generatedMessage: boolean; + + constructor(options?: {message?: string; actual?: any; expected?: any; + operator?: string; stackStartFunction?: Function}); + } + + export function fail(actual?: any, expected?: any, message?: string, operator?: string): void; + export function ok(value: any, message?: string): void; + export function equal(actual: any, expected: any, message?: string): void; + export function notEqual(actual: any, expected: any, message?: string): void; + export function deepEqual(actual: any, expected: any, message?: string): void; + export function notDeepEqual(acutal: any, expected: any, message?: string): void; + export function strictEqual(actual: any, expected: any, message?: string): void; + export function notStrictEqual(actual: any, expected: any, message?: string): void; + export var throws: { + (block: Function, message?: string): void; + (block: Function, error: Function, message?: string): void; + (block: Function, error: RegExp, message?: string): void; + (block: Function, error: (err: any) => boolean, message?: string): void; + }; + + export var doesNotThrow: { + (block: Function, message?: string): void; + (block: Function, error: Function, message?: string): void; + (block: Function, error: RegExp, message?: string): void; + (block: Function, error: (err: any) => boolean, message?: string): void; + }; + + export function ifError(value: any): void; + } + + export = internal; +} + +declare module "tty" { + import net = require("net"); + + export function isatty(fd: number): boolean; + export interface ReadStream extends net.Socket { + isRaw: boolean; + setRawMode(mode: boolean): void; + } + export interface WriteStream extends net.Socket { + columns: number; + rows: number; + } +} + +declare module "domain" { + import events = require("events"); + + export class Domain extends events.EventEmitter { + run(fn: Function): void; + add(emitter: events.EventEmitter): void; + remove(emitter: events.EventEmitter): void; + bind(cb: (err: Error, data: any) => any): any; + intercept(cb: (data: any) => any): any; + dispose(): void; + + addListener(event: string, listener: Function): Domain; + on(event: string, listener: Function): Domain; + once(event: string, listener: Function): Domain; + removeListener(event: string, listener: Function): Domain; + removeAllListeners(event?: string): Domain; + } + + export function create(): Domain; +} diff --git a/typings/DefinitelyTyped/tsd.d.ts b/typings/DefinitelyTyped/tsd.d.ts new file mode 100644 index 0000000..6dd6c1e --- /dev/null +++ b/typings/DefinitelyTyped/tsd.d.ts @@ -0,0 +1,8 @@ +/// +/// +/// +/// +/// +/// +/// +/// diff --git a/typings/skmatc/skmatc.d.ts b/typings/skmatc/skmatc.d.ts new file mode 100644 index 0000000..390e0df --- /dev/null +++ b/typings/skmatc/skmatc.d.ts @@ -0,0 +1,65 @@ +declare module "skmatc" { + export = Skmatc.Skmatc; +} + +declare module Skmatc { + export class Skmatc { + constructor(schema: any); + + static validators: Validator[]; + static Validator: typeof Validator; + static Result: typeof Result; + static Failure: typeof Failure; + static create(handles: (schema: any) => boolean, validate: (schema: any, data: any, path: string) => Result, options?: { name?: string }): Validator; + static validate(validators: Validator[], schema: any, data: any, path?: string): Result; + static register(validator: Validator); + + schema: any; + validators: Validator[]; + validate(data: any, path?: string): Result; + register(validator: Validator); + } + + export class Validator { + constructor(skmatc: Skmatc, options?: any); + static create(handles: (schema: any) => boolean, validate: (schema: any, data: any, path: string) => Result, options?: { name?: string }): Validator; + static module(handles: (schema: any) => boolean, validate: (schema: any, data: any, path: string) => Result, options?: { name?: string }): Validator; + + name: string; + skmatc: Skmatc; + handles(schema: any): boolean; + validate(schema: any, data: any, path: string): Result; + assert(schema: any, data: any, path: string, test: boolean, message?: string): Result; + fail(schema: any, data: any, path: string, message?: string): Result; + } + + export class Result { + constructor(failures: Failure[]); + static compound(results: Result[]): Result; + + failures: Failure[]; + success: boolean; + failed: boolean; + message: string; + messages: string[]; + error: Error; + } + + export class Failure { + constructor(validator: Validator, schema: any, data: any, path: string, message?: string); + validator: Validator; + schema: any; + data: any; + path: string; + message: string; + } + + export interface IValidationHandler { + (thisArg: { + validator: IValidationHandler; + skmatc: Skmatc; + fail(message?: string); + assert(test: boolean, message?: string); + }, schema: any, data: any, path: string): Result; + } +} \ No newline at end of file