diff --git a/package.json b/package.json new file mode 100644 index 0000000..ace3571 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "bson", + "version": "1.0.1", + "author": "Marco Paland", + "license": "MIT", + "dependencies": { + "lite-server": "latest", + "typescript": "latest" + }, + "devDependencies": { + "chai": "^3.5.0", + "mocha": "^2.5.2" + }, + "scripts": { + "build": "tsc", + "pretest": "npm run build", + "test": "mocha -R spec ./js/spec/**/*.spec.js", + "dev": "lite-server" + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..8c6e9cb --- /dev/null +++ b/readme.md @@ -0,0 +1,107 @@ +# Ultrafast BSON typescript parser + +This module is an ultrafast serializer and deserializer for the [BSON](http://bsonspec.org) format. +It is written in clean typescript and has no other lib dependencies. +BSON is mainly used as compact transport format for (JSON) objects. + + +### Motivation +I needed a simple, fast and clean (typescript) module to generate and parse BSON for storing JSON objects in files efficiently. +There are some parsers around (2016/06), mainly the primary one of the mongodb project. But I found that it's really not lightweight enough and too slow for mobile usage. +A further requirement was using typed arrays instead of nodejs buffers, to get this baby portable and running in browsers, too. + + +### Design goals: +- Written in typescript +- Fast and lightweight parser +- Very easy to use, just one include module, NO dependencies +- tslint warning free, clean code +- Unit tested +- Rocksolid (I hope so) +- MIT license + + +## Usage +Using this module is rather simple. Copy */src/bson.ts* to your project and import it: + +```typescript +import { BSON } from './bson'; + +// create a test document +let doc = { id: 10, time: new BSON.UTC(), arr: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]) }; + +// serialize the document +let bson = BSON.serialize(doc); + +// and deserialize it, using BSON.UTC objects as time representation +let orig = BSON.deserialize(bson, true); +``` + + +Run the mocha tests with +``` +npm run test +``` + + +## API + +Basically the API consists of just two static methods to serialize/deserialize objects to/from BSON format: + +### BSON serialization and deserialiation + +**`BSON.serialize(object)`** + * @param {Object} object The Javascript object to serialize + * @return {Uint8Array} returns an Uint8Array in BSON format. + Unknown objects are ignored in serialization. + +**`BSON.deserialize(buffer, useUTC)`** + * @param {Uint8Array} buffer An Uint8Array containing the BSON data + * @param {Boolean} useUTC Optional, if set a `BSON.UTC` object is created for 'UTC datetime' instead of a normal JS `Date` object. Defaults to false + * @return {Object} returns the deserialized Javascript object or `undefined` in case of a parsing error (unsupported BSON element etc.) + + +### UTC + +**`bson.ObjectId.isValid(id)`** - Returns true if `id` is a valid number or hexadecimal string representing an ObjectId. +**`bson.ObjectId.createFromHexString(hexString)`** - Returns the ObjectId the `hexString` represents. +**`bson.ObjectId.createFromTime(time)`** - Returns an ObjectId containing the passed time. +* `time` - A Unix timestamp (number of seconds since the epoch). + + +### UUID + +**`bson.ObjectId.isValid(id)`** - Returns true if `id` is a valid number or hexadecimal string representing an ObjectId. +**`bson.ObjectId.createFromHexString(hexString)`** - Returns the ObjectId the `hexString` represents. +**`bson.ObjectId.createFromTime(time)`** - Returns an ObjectId containing the passed time. +* `time` - A Unix timestamp (number of seconds since the epoch). + + +### ObjectId + +**`bson.ObjectId.isValid(id)`** - Returns true if `id` is a valid number or hexadecimal string representing an ObjectId. +**`bson.ObjectId.createFromHexString(hexString)`** - Returns the ObjectId the `hexString` represents. +**`bson.ObjectId.createFromTime(time)`** - Returns an ObjectId containing the passed time. +* `time` - A Unix timestamp (number of seconds since the epoch). + + +### Unsupported elements +The following BSON elements are currently not supported (and lead to a deserialiation error): +- JavaScript code +- Min key +- Max key +- Regular expression (implemented, but untested yet - so don't rely on it) + + +## Caveats +- 64-bit integer BSON values are converted to the Javascript Number type. + However, Javascript supports integer precision up to 2^53 as maximum size. + If a parsed 64-bit integer exceeds this size, floating point rounding errors may occur! + + +## Bottom line +Have fun and report any errors or improvment suggestions please. + + +## License +This BSON module is written under the **MIT** license diff --git a/spec/bson.spec.ts b/spec/bson.spec.ts new file mode 100644 index 0000000..adc2a44 --- /dev/null +++ b/spec/bson.spec.ts @@ -0,0 +1,331 @@ +import {BSON} from "../src/bson"; + +import chai = require('chai'); +let expect = chai.expect; +let assert = chai.assert; + + +describe('BSON', () => { + + class UnknownObj { + _dummy: number = 123; + test() { + } + } + + let serialize_vector = [ + { + obj: { "BSON": ["awesome", 5.05, 1986] }, + bson: "310000000442534f4e002600000002300008000000617765736f6d65000131003333333333331440103200c20700000000", + }, + { + obj: { int32: 10, int64: 1125899906842624, flo: 3.141592653, str: "Hello äöü", utc: new BSON.UTC("2011-10-10T14:48:00Z"), bool: true, date: new Date("2011-10-10T14:48:00Z") }, + bson: "6400000010696e743332000a00000012696e74363400000000000000040001666c6f0038e92f54fb21094002737472000d00000048656c6c6f20c3a4c3b6c3bc00097574630000f94dee3201000008626f6f6c000109646174650000f94dee3201000000" + }, + { + obj: { arr: ["foo", "bar", 100, 1000], ta: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]), obj: { int32: 10, int64: 1125899906842624, flo: 3.141592653 } }, + bson: "7500000004617272002900000002300004000000666f6f00023100040000006261720010320064000000103300e8030000000574610008000000000102030405060708036f626a002c00000010696e743332000a00000012696e74363400000000000000040001666c6f0038e92f54fb2109400000" + }, + { + obj: { da: [[1, 2, 3], [4, 5, 6], [7, 8, 9]], uuid: new BSON.UUID(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])) }, + bson: "80000000046461005c0000000430001a000000103000010000001031000200000010320003000000000431001a000000103000040000001031000500000010320006000000000432001a000000103000070000001031000800000010320009000000000005757569640010000000040102030405060708090a0b0c0d0e0f1000" + }, + { + obj: { id: 123456, sk: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]), pk: new Uint8Array([255, 254, 253, 252, 251, 250, 249, 248]) }, + bson: "2f0000001069640040e2010005736b000800000000010203040506070805706b000800000000fffefdfcfbfaf9f800" + }, + { + obj: { id: 10, obj: new UnknownObj(), str: "Test", n: null, b: true }, + bson: "22000000106964000a00000002737472000500000054657374000a6e000862000100" + } + ]; + + + let deserialize_vector = [ + { + obj: { "BSON": ["awesome", 5.05, 1986] }, + bson: "310000000442534f4e002600000002300008000000617765736f6d65000131003333333333331440103200c20700000000", + }, + { + obj: { int32: 10, int64: 1125899906842624, flo: 3.141592653, str: "Hello äöü", utc: new BSON.UTC("2011-10-10T14:48:00Z"), bool: true, date: new Uint8Array([1,2,3,4]), nu: null }, + bson: "6900000010696e743332000a00000012696e74363400000000000000040001666c6f0038e92f54fb21094002737472000d00000048656c6c6f20c3a4c3b6c3bc00097574630000f94dee3201000008626f6f6c00010564617465000400000000010203040a6e750000" + }, + { + obj: { arr: ["foo", "bar", 100, 1000], ta: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]), obj: { int32: 10, int64: 1125899906842624, flo: 3.141592653 } }, + bson: "7500000004617272002900000002300004000000666f6f00023100040000006261720010320064000000103300e8030000000574610008000000000102030405060708036f626a002c00000010696e743332000a00000012696e74363400000000000000040001666c6f0038e92f54fb2109400000" + }, + { + obj: { da: [[1, 2, 3], [4, 5, 6], [7, 8, 9]], uuid: new BSON.UUID(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])) }, + bson: "80000000046461005c0000000430001a000000103000010000001031000200000010320003000000000431001a000000103000040000001031000500000010320006000000000432001a000000103000070000001031000800000010320009000000000005757569640010000000040102030405060708090a0b0c0d0e0f1000" + }, + { + obj: { id: 123456, sk: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]), pk: new Uint8Array([255, 254, 253, 252, 251, 250, 249, 248]) }, + bson: "2f0000001069640040e2010005736b000800000000010203040506070805706b000800000000fffefdfcfbfaf9f800" + }, + { + obj: { id: 10, oid: new BSON.ObjectId(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])), str: "Test", n: null, b: true }, + bson: "33000000106964000a000000076f6964000102030405060708090a0b0c02737472000500000054657374000a6e000862000100" + } + ]; + + + describe("serialize", function () { + it("checks empty/unknown object", function () { + let bson = BSON.serialize(""); + expect(bin2hex(bson)).to.deep.equal("0500000000"); + bson = BSON.serialize({ obj: new UnknownObj() }); + expect(bin2hex(bson)).to.deep.equal("0500000000"); + }); + + it("checks int32", function () { + let bson = BSON.serialize({ int: 0x1234 }); + expect(bin2hex(bson)).to.deep.equal("0e00000010696e74003412000000"); + }); + + it("checks negative int32", function () { + let bson = BSON.serialize({ int: -10 }); + expect(bin2hex(bson)).to.deep.equal("0e00000010696e7400f6ffffff00"); + }); + + it("checks int64", function () { + let bson = BSON.serialize({ int: 0x1234567890 }); + expect(bin2hex(bson)).to.deep.equal("1200000012696e7400907856341200000000"); + }); + + it("checks negative int64", function () { + let bson = BSON.serialize({ int: -78187493520 }); + expect(bin2hex(bson)).to.deep.equal("1200000012696e74007087a9cbedffffff00"); + }); + + it("checks double (64-bit binary floating point)", function () { + let bson = BSON.serialize({ flo: 3.1415926535 }); + expect(bin2hex(bson)).to.deep.equal("1200000001666c6f0044174154fb21094000"); + }); + + it("checks string", function () { + let bson = BSON.serialize({ str: "Hello World" }); + expect(bin2hex(bson)).to.deep.equal("1a00000002737472000c00000048656c6c6f20576f726c640000"); + }); + + it("checks UTF-8 string", function () { + let bson = BSON.serialize({ str: "\u00C4\u00D6\u00DC\u00DF" }); + expect(bin2hex(bson)).to.deep.equal("17000000027374720009000000c384c396c39cc39f0000"); + }); + + it("checks boolean", function () { + let bson = BSON.serialize({ bool: false }); + expect(bin2hex(bson)).to.deep.equal("0c00000008626f6f6c000000"); + bson = BSON.serialize({ bool: true }); + expect(bin2hex(bson)).to.deep.equal("0c00000008626f6f6c000100"); + }); + + it("checks null", function () { + let bson = BSON.serialize({ nul: null }); + expect(bin2hex(bson)).to.deep.equal("0a0000000a6e756c0000"); + }); + + it("checks binary", function () { + let bson = BSON.serialize({ bin: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0xFF]) }); + expect(bin2hex(bson)).to.deep.equal("190000000562696e000a00000000010203040506070809ff00"); + }); + + it("checks array", function () { + let bson = BSON.serialize({ arr: [0xFA, 0xFB, 0xFC, 0xFD] }); + expect(bin2hex(bson)).to.deep.equal("2b000000046172720021000000103000fa000000103100fb000000103200fc000000103300fd0000000000"); + }); + + it("checks array in array", function () { + let bson = BSON.serialize({ arr: [[0x10, 0x11, 0x12, 0x13], 0xFA, 0xFB, 0xFC, 0xFD] }); + expect(bin2hex(bson)).to.deep.equal("4f000000046172720045000000043000210000001030001000000010310011000000103200120000001033001300000000103100fa000000103200fb000000103300fc000000103400fd0000000000"); + }); + + it("checks object", function () { + let bson = BSON.serialize({ obj: { int: 10, str: "" } }); + expect(bin2hex(bson)).to.deep.equal("22000000036f626a001800000010696e74000a000000027374720001000000000000"); + }); + + it("checks Date", function () { + let bson = BSON.serialize({ dat: new Date("2016-06-25T14:48:11Z") }); + expect(bin2hex(bson)).to.deep.equal("120000000964617400f84308885501000000"); + }); + + it("checks UTC", function () { + let bson = BSON.serialize({ utc1: new BSON.UTC("2016-06-25T14:48:11Z"), utc2: new BSON.UTC("2016-06-25T14:48:11+0200"), utc3: new BSON.UTC([0x3D, 0x53, 0xAE, 0x91, 0x55, 0x01, 0x00, 0x00]) }); + expect(bin2hex(bson)).to.deep.equal("2f000000097574633100f843088855010000097574633200f8669a87550100000975746333003d53ae915501000000"); + }); + + it("checks UUID", function () { + let bson = BSON.serialize({ uuid: new BSON.UUID(new Uint8Array([0x43, 0xab, 0x2e, 0x98, 0x62, 0x3c, 0x03, 0xe8, 0x5f, 0x54, 0x1a, 0x17, 0x45, 0xe0, 0x1b, 0xda])) }); + expect(bin2hex(bson)).to.deep.equal("20000000057575696400100000000443ab2e98623c03e85f541a1745e01bda00"); + }); + + it("checks ObjectId", function () { + let bson = BSON.serialize({ oid: new BSON.ObjectId([0xa8, 0x05, 0x57, 0xf0, 0x5c, 0x6d, 0x7a, 0xd0, 0x9f, 0xa7, 0x35, 0x70]) }); + expect(bin2hex(bson)).to.deep.equal("16000000076f696400a80557f05c6d7ad09fa7357000"); + }); + + it("checks complex objects", function () { + for (let i = 0; i < serialize_vector.length; i++) { + let bson = BSON.serialize(serialize_vector[i].obj); + expect(bin2hex(bson)).to.deep.equal(serialize_vector[i].bson); + } + }); + }); + + + describe("deserialize", function () { + it("checks empty/unknown object", function () { + let obj = BSON.deserialize(hex2bin("0500000000")); + expect(obj).to.deep.equal({ }); + }); + + it("checks int32", function () { + let obj = BSON.deserialize(hex2bin("0e00000010696e74003412000000")); + expect(obj).to.deep.equal({ int: 0x1234 }); + }); + + it("checks negative int32", function () { + let obj = BSON.deserialize(hex2bin("0e00000010696e7400f6ffffff00")); + expect(obj).to.deep.equal({ int: -10 }); + }); + + it("checks int64", function () { + let obj = BSON.deserialize(hex2bin("1200000012696e7400907856341200000000")); + expect(obj).to.deep.equal({ int: 0x1234567890 }); + }); + + it("checks int64 > 2^53", function () { + let obj = BSON.deserialize(hex2bin("1200000012696e7400FFDEBC9A7856341200")); + expect(obj).to.deep.equal({ int: 0x123456789ABCDEFF }); + }); + + it("checks negative int64", function () { + let obj = BSON.deserialize(hex2bin("1200000012696e74007087a9cbedffffff00")); + expect(obj).to.deep.equal({ int: -78187493520 }); + }); + + it("checks double (64-bit binary floating point)", function () { + let obj = BSON.deserialize(hex2bin("1200000001666c6f0044174154fb21094000")); + expect(obj).to.deep.equal({ flo: 3.1415926535 }); + }); + + it("checks string", function () { + let obj = BSON.deserialize(hex2bin("1a00000002737472000c00000048656c6c6f20576f726c640000")); + expect(obj).to.deep.equal({ str: "Hello World" }); + }); + + it("checks UTF-8 string", function () { + let obj = BSON.deserialize(hex2bin("17000000027374720009000000c384c396c39cc39f0000")); + expect(obj).to.deep.equal({ str: "\u00C4\u00D6\u00DC\u00DF" }); + }); + + it("checks boolean", function () { + let obj = BSON.deserialize(hex2bin("0c00000008626f6f6c000000")); + expect(obj).to.deep.equal({ bool: false }); + obj = BSON.deserialize(hex2bin("0c00000008626f6f6c000100")); + expect(obj).to.deep.equal({ bool: true }); + }); + + it("checks null", function () { + let obj = BSON.deserialize(hex2bin("0a0000000a6e756c0000")); + expect(obj).to.deep.equal({ nul: null }); + }); + + it("checks binary", function () { + let obj = BSON.deserialize(hex2bin("190000000562696e000a00000000010203040506070809ff00")); + expect(obj).to.deep.equal({ bin: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0xFF]) }); + }); + + it("checks array", function () { + let obj = BSON.deserialize(hex2bin("2b000000046172720021000000103000fa000000103100fb000000103200fc000000103300fd0000000000")); + expect(obj).to.deep.equal({ arr: [0xFA, 0xFB, 0xFC, 0xFD] }); + }); + + it("checks array in array", function () { + let obj = BSON.deserialize(hex2bin("4f000000046172720045000000043000210000001030001000000010310011000000103200120000001033001300000000103100fa000000103200fb000000103300fc000000103400fd0000000000")); + expect(obj).to.deep.equal({ arr: [[0x10, 0x11, 0x12, 0x13], 0xFA, 0xFB, 0xFC, 0xFD] }); + }); + + it("checks object", function () { + let obj = BSON.deserialize(hex2bin("22000000036f626a001800000010696e74000a000000027374720001000000000000")); + expect(obj).to.deep.equal({ obj: { int: 10, str: "" } }); + }); + + it("checks Date", function () { + let obj = BSON.deserialize(hex2bin("120000000964617400f84308885501000000")); + expect(obj).to.deep.equal({ dat: new Date("2016-06-25T14:48:11Z") }); + }); + + it("checks UTC", function () { + let obj = BSON.deserialize(hex2bin("2f000000097574633100f843088855010000097574633200f8669a87550100000975746333003d53ae915501000000"), true); + expect(obj).to.deep.equal({ utc1: new BSON.UTC("2016-06-25T14:48:11Z"), utc2: new BSON.UTC("2016-06-25T14:48:11+0200"), utc3: new BSON.UTC([0x3D, 0x53, 0xAE, 0x91, 0x55, 0x01, 0x00, 0x00]) }); + }); + + it("checks UUID", function () { + let obj = BSON.deserialize(hex2bin("20000000057575696400100000000443ab2e98623c03e85f541a1745e01bda00")); + expect(obj).to.deep.equal({ uuid: new BSON.UUID(new Uint8Array([0x43, 0xab, 0x2e, 0x98, 0x62, 0x3c, 0x03, 0xe8, 0x5f, 0x54, 0x1a, 0x17, 0x45, 0xe0, 0x1b, 0xda])) }); + obj = BSON.deserialize(hex2bin("2100000005757569640011000000040143ab2e98623c03e85f541a1745e01bda00")); + expect(obj).to.equal(undefined); + }); + + it("checks ObjectId", function () { + let obj = BSON.deserialize(hex2bin("16000000076f696400a80557f05c6d7ad09fa7357000")); + expect(obj).to.deep.equal({ oid: new BSON.ObjectId([0xa8, 0x05, 0x57, 0xf0, 0x5c, 0x6d, 0x7a, 0xd0, 0x9f, 0xa7, 0x35, 0x70]) }); + }); + + it("checks complex objects", function () { + for (let i = 0; i < deserialize_vector.length; i++) { + let bson = BSON.serialize(deserialize_vector[i].obj); + let obj = BSON.deserialize(hex2bin(deserialize_vector[i].bson), true); + expect(obj).to.deep.equal(deserialize_vector[i].obj); + } + }); + + it("checks document too small", function () { + let obj = BSON.deserialize(hex2bin("04000000")); + expect(obj).to.equal(undefined); + }); + + it("checks document termination", function () { + let obj = BSON.deserialize(hex2bin("0c00000008626f6f6c000001")); + expect(obj).to.equal(undefined); + obj = BSON.deserialize(hex2bin("0c00000008626f6f6c0000")); + expect(obj).to.equal(undefined); + }); + + it("checks document size mismatch", function () { + let obj = BSON.deserialize(hex2bin("0d00000008626f6f6c000000")); + expect(obj).to.equal(undefined); + }); + + it("checks illegal keyname", function () { + let obj = BSON.deserialize(hex2bin("0c00000008626f6f6c010100")); + expect(obj).to.equal(undefined); + }); + + it("checks unknown element", function () { + let obj = BSON.deserialize(hex2bin("0c00000018626f6f6c000000")); + expect(obj).to.equal(undefined); + }); + }); + + ///////////////////////////////////////////////////////////////////////////// + + function bin2hex(bin: Uint8Array, uppercase: boolean = false): string { + let hex = uppercase ? "0123456789ABCDEF" : "0123456789abcdef"; + let str = ""; + for (let i = 0, len = bin.length; i < len; i++) { + str += hex.charAt((bin[i] >>> 4) & 0x0f) + hex.charAt(bin[i] & 0x0f); + } + return str; + } + + function hex2bin(hex: string): Uint8Array { + let bin = new Uint8Array(hex.length >>> 1); + for (let i = 0, len = hex.length >>> 1; i < len; i++) { + bin[i] = parseInt(hex.substr(i << 1, 2), 16); + } + return bin; + } + +}); diff --git a/src/bson.ts b/src/bson.ts new file mode 100644 index 0000000..9c80253 --- /dev/null +++ b/src/bson.ts @@ -0,0 +1,615 @@ +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (marco@paland.com) +// 2016, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// This file is part of the mipher crypto library. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Extrem fast BSON implementation in typescript with NO dependencies +// See http://bsonspec.org for details +// Usage: +// import {BSON} from './bson'; +// let obj = { id: 10, time: new BSON.UTC(), arr: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]) }; +// let bson = BSON.serialize(obj); +// let orig = BSON.deserialize(bson); +// +/////////////////////////////////////////////////////////////////////////////// + + +export namespace BSON { + + /** + * BSON module version + */ + const version: string = "1.0.1"; + + + /** + * UUID class + */ + export class UUID { + private _id: Uint8Array; + + constructor(id: Uint8Array | Array) { + this._id = new Uint8Array(id); + } + + buffer() { + return this._id; + } + } + + + /** + * ObjectId class (for mongoDB usage) + */ + export class ObjectId { + private _id: Uint8Array; + + constructor(id: Uint8Array | Array) { + this._id = new Uint8Array(id); + } + + buffer() { + return this._id; + } + } + + + /** + * The UTC class contains the milliseconds since the Unix epoch (1.1.1970 00:00:00 UTC) + */ + export class UTC { + private _time: Uint8Array; + + constructor(time?: Uint8Array | Array | string) { + this._time = (typeof time !== "string") ? new Uint8Array(time || number2long(Date.now())) : number2long(+new Date(time)); + } + + buffer() { + return this._time; + } + + /** + * Convert an (ISO) date string + * @param {String} date (ISO) Date string + */ + fromString(date: string) { + this._time = number2long(+new Date(date)); + } + + /** + * Returns the milliseconds since the Unix epoch (UTC) + */ + toNumber(): number { + return long2number(this._time); + } + + toDate(): Date { + return new Date(long2number(this._time)); + } + } + + + /** + * Private, return the size of the given object + * @param {Object} obj The object to get the size from + * @return {Number} The object size in bytes + */ + function getObjectSize(obj: Object) { + let len = 4 + 1; // handle the obj.length prefix + terminating '0' + for (let key in obj) { + len += getElementSize(key, obj[key]); + } + return len; + } + + + /** + * Private, get the size of the given element + * @param {String} name + * @param {Object} value + * @return {Number} The element size in bytes + */ + function getElementSize(name: string, value: any) { + let len = 1; // always starting with 1 for the data type byte + if (name) { + len += strlen(name) + 1; // cstring: name + '0' termination + } + + if (value === undefined || value === null) { + return len; // just the type byte plus name cstring + } + + switch (value.constructor) { + case String: + return len + 4 + strlen(value) + 1; + + case Number: + if (Math.floor(value) === value) { + if (value <= 2147483647 && value >= -2147483647) + return len + 4; // 32 bit + else + return len + 8; // 64 bit + } + else + return len + 8; // 64 bit double & float + + case Boolean: + return len + 1; + + case Array: + case Object: + return len + getObjectSize(value); + + case Int8Array: + case Uint8Array: + return len + 5 + value.byteLength; + + case Date: + case UTC: + return len + 8; + + case UUID: + return len + 5 + 16; + + case ObjectId: + return len + 12; + + default: + // unsupported type + return 0; + } + } + + + /** + * Serialize an object to BSON format + * @param {Object} object The object to serialize + * @return {Uint8Array} An byte array with the BSON representation + */ + export function serialize(object: any): Uint8Array { + let buffer = new Uint8Array(getObjectSize(object)); + serializeEx(object, buffer); + return buffer; + } + + + /** + * Private, used by serialize() and is called recursively + * @param object + * @param buffer + * @param i + */ + function serializeEx(object: any, buffer: Uint8Array, i: number = 0): number { + i += int32(buffer.length, buffer, i); + + if (object.constructor === Array) { + for (let j = 0, len = object.length; j < len; j++) { + i = packElement(j.toString(), object[j], buffer, i); + } + } + else { + for (let key in object) { + i = packElement(key, object[key], buffer, i); + } + } + buffer[i++] = 0; // terminating zero + return i; + } + + + /** + * Private, assemble BSON cstring element + * @param name + * @param buffer + * @param offset + * @return Element length in bytes + */ + function cstring(name: string, buffer: Uint8Array, offset: number): number { + let cstring = str2bin(name); + let clen = cstring.length; + buffer.set(cstring, offset); + buffer[offset + clen++] = 0; + return clen; + } + + + /** + * Private, assemble BSON int32 element + * @param size + * @param buffer + * @param offset + * @return Element length in bytes + */ + function int32(size: number, buffer: Uint8Array, offset: number): number { + buffer[offset++] = (size) & 0xff; + buffer[offset++] = (size >>> 8) & 0xff; + buffer[offset++] = (size >>> 16) & 0xff; + buffer[offset++] = (size >>> 24) & 0xff; + return 4; + } + + + /** + * Private, assemble BSON elements + * @param name + * @param value + * @param buffer + * @param i + */ + function packElement(name: string, value: any, buffer: Uint8Array, i: number): number { + if (value === undefined || value === null) { + buffer[i++] = 0x0A; // BSON type: Null + i += cstring(name, buffer, i); + return i; + } + switch (value.constructor) { + case String: + buffer[i++] = 0x02; // BSON type: String + i += cstring(name, buffer, i); + let size = cstring(value, buffer, i + 4); + i += int32(size, buffer, i); + return i + size; + + case Number: + if (Math.floor(value) === value) { + if (value <= 2147483647 && value >= -2147483647) { /// = BSON.BSON_INT32_MAX / MIN asf. + buffer[i++] = 0x10; // BSON type: int32 + i += cstring(name, buffer, i); + i += int32(value, buffer, i); + } + else { + buffer[i++] = 0x12; // BSON type: int64 + i += cstring(name, buffer, i); + buffer.set(number2long(value), i); + i += 8; + } + } + else { + // it's a float / double + buffer[i++] = 0x01; // BSON type: 64-bit floating point + i += cstring(name, buffer, i); + let f = new Float64Array([value]); + let d = new Uint8Array(f.buffer); + buffer.set(d, i); + i += 8; + } + return i; + + case Boolean: + buffer[i++] = 0x08; // BSON type: Boolean + i += cstring(name, buffer, i); + buffer[i++] = value ? 1 : 0; + return i; + + case Array: + case Object: + buffer[i++] = value.constructor === Array ? 0x04 : 0x03; // BSON type: Array / Document + i += cstring(name, buffer, i); + let end = serializeEx(value, buffer, i); + int32(end - i, buffer, i); // correct size + return end; + + case Int8Array: + case Uint8Array: + buffer[i++] = 0x05; // BSON type: Binary data + i += cstring(name, buffer, i); + i += int32(value.byteLength, buffer, i); + buffer[i++] = 0; // use generic binary subtype 0 + buffer.set(value, i); + i += value.byteLength; + return i; + + case Date: + buffer[i++] = 0x09; // BSON type: UTC datetime + i += cstring(name, buffer, i); + buffer.set(number2long(value.getTime()), i); + i += 8; + return i; + + case UTC: + buffer[i++] = 0x09; // BSON type: UTC datetime + i += cstring(name, buffer, i); + buffer.set(value.buffer(), i); + i += 8; + return i; + + case UUID: + buffer[i++] = 0x05; // BSON type: Binary data + i += cstring(name, buffer, i); + i += int32(16, buffer, i); + buffer[i++] = 4; // use UUID subtype + buffer.set(value.buffer(), i); + i += 16; + return i; + + case ObjectId: + buffer[i++] = 0x07; // BSON type: ObjectId + i += cstring(name, buffer, i); + buffer.set(value.buffer(), i); + i += 12; + return i; + + case RegExp: + buffer[i++] = 0x0B; // BSON type: Regular expression + i += cstring(name, buffer, i); + i += cstring(value.source, buffer, i); + if (value.global) buffer[i++] = 0x73; // s = 'g' + if (value.ignoreCase) buffer[i++] = 0x69; // i + if (value.multiline) buffer[i++] = 0x6d; // m + buffer[i++] = 0; + return i; + + default: + return i; // unknown type (ignore element) + } + } + + + /** + * Deserialize (parse) BSON data to an object + * @param {Uint8Array} buffer The buffer with BSON data to convert + * @param {Boolean} useUTC Optional, if set an UTC object is created for 'UTC datetime', else an Date object. Defaults to false + * @return {Object} Returns an object or an array + */ + export function deserialize(buffer: Uint8Array, useUTC: boolean = false, i: number = 0, returnArray: boolean = false): Array | Object { + // check size + if (buffer.length < 5) { + // Document error: Size < 5 bytes + return undefined; + } + let size = buffer[i++] | buffer[i++] << 8 | buffer[i++] << 16 | buffer[i++] << 24; + if (size < 5 || size > buffer.length) { + // Document error: Size mismatch + return undefined; + } + if (buffer[buffer.length - 1] !== 0x00) { + // Document error: Missing termination + return undefined; + } + + let object = returnArray ? [] : {}; // needed for type ARRAY recursion later + + for (;;) { + // get element type + let elementType = buffer[i++]; // read type + if (elementType === 0) break; // zero means last byte, exit + + // get element name + let end = i; + for (; buffer[end] !== 0x00 && end < buffer.length; end++); + if (end >= buffer.length - 1) { + // Document error: Illegal key name + return undefined; + } + let name:any = bin2str(buffer.subarray(i, end)); + if (returnArray) { + name = parseInt(name); // convert to number as array index + } + i = ++end; // skip terminating zero + + switch (elementType) { + case 0x01: // BSON type: 64-bit floating point + object[name] = (new Float64Array(buffer.slice(i, i += 8).buffer))[0]; // use slice() here to get a new array + break; + + case 0x02: // BSON type: String + size = buffer[i++] | buffer[i++] << 8 | buffer[i++] << 16 | buffer[i++] << 24; + object[name] = bin2str(buffer.subarray(i, i += size - 1)); + i++; + break; + + case 0x03: // BSON type: Document (Object) + size = buffer[i] | buffer[i + 1] << 8 | buffer[i + 2] << 16 | buffer[i + 3] << 24; + object[name] = deserialize(buffer, useUTC, i, false); // isArray = false => Object + i += size; + break; + + case 0x04: // BSON type: Array + size = buffer[i] | buffer[i + 1] << 8 | buffer[i + 2] << 16 | buffer[i + 3] << 24; // NO 'i' increment since the size bytes are reread during the recursion + object[name] = deserialize(buffer, useUTC, i, true); // pass current index & return an array + i += size; + break; + + case 0x05: // BSON type: Binary data + size = buffer[i++] | buffer[i++] << 8 | buffer[i++] << 16 | buffer[i++] << 24; + if (buffer[i++] === 0x04) { // BSON subtype: UUID + if (size !== 16) { + // Element error: Wrong UUID length + return undefined; + } + object[name] = new UUID(buffer.subarray(i, i += size)); + } + else { + // all other subtypes + object[name] = buffer.slice(i, i += size); // use slice() here to get a new array + } + break; + + case 0x06: // BSON type: Undefined (deprecated) + object[name] = null; + break; + + case 0x07: // BSON type: ObjectId + object[name] = new ObjectId(buffer.subarray(i, i += 12)); + break; + + case 0x08: // BSON type: Boolean + object[name] = buffer[i++] === 1; + break; + + case 0x09: // BSON type: UTC datetime + object[name] = useUTC ? new UTC(buffer.subarray(i, i += 8)) : new Date(long2number(buffer.subarray(i, i += 8))); + break; + + case 0x0A: // BSON type: Null + object[name] = null; + break; + + case 0x0B: // BSON type: RegExp + end = i; + // pattern + while (end < buffer.length && buffer[end++] !== 0x00); + if (end >= buffer.length) { + // Document error: Illegal key name + return undefined; + } + let pat = bin2str(buffer.subarray(i, end)); + i = end; + // flags + while (end < buffer.length && buffer[end++] !== 0x00); + if (end >= buffer.length) { + // Document error: Illegal key name + return undefined; + } + let flags = bin2str(buffer.subarray(i, end)); + i = end; + object[name] = new RegExp(pat, flags); + break; + + case 0x10: // BSON type: 32-bit integer + object[name] = buffer[i++] | buffer[i++] << 8 | buffer[i++] << 16 | buffer[i++] << 24; + break; + + case 0x12: // BSON type: 64-bit integer + object[name] = long2number(buffer.subarray(i, i += 8)); + break; + + default: + // Parsing error: Unknown element + return undefined; + } + } + return object; + } + + + ///////////////////////////////////////////////////////////////////////////// + // H E L P E R + + /** + * Convert a number to a 64 bit integer representation + * @param {Number} value Number to convert + * @return {Uint8Array} Converted number + */ + function number2long(value: number): Uint8Array { + let buf = new Uint8Array(8); + if (Math.floor(value) === value) { + const TWO_PWR_32 = 4294967296; + let lo = (value % TWO_PWR_32) | 0, hi = (value / TWO_PWR_32) | 0; + if (value < 0) { + lo = ~(-value % TWO_PWR_32) | 0, hi = ~(-value / TWO_PWR_32) | 0; + lo = (lo + 1) & 0xffffffff; + if (!lo) hi++; + } + let i = 0; + buf[i++] = (lo & 0xff); buf[i++] = (lo >>> 8) & 0xff; buf[i++] = (lo >>> 16) & 0xff; buf[i++] = (lo >>> 24) & 0xff; + buf[i++] = (hi & 0xff); buf[i++] = (hi >>> 8) & 0xff; buf[i++] = (hi >>> 16) & 0xff; buf[i] = (hi >>> 24) & 0xff; + } + else { // it's a float / double + let f = new Float64Array([value]); + let d = new Uint8Array(f.buffer); + buf.set(d); + } + return buf; + } + + + /** + * Convert 64 bit integer to Number + * @param {Uint8Array} buffer Buffer containing a 64 bit integer as typed array at offset position. LSB is [0], MSB is [7] + * @param {Number} offset Offset in buffer, where the integer starts + * @return {Number} Converted number + */ + function long2number(buffer: Uint8Array, offset: number = 0): number { + const TWO_PWR_32 = 4294967296; + let lo = buffer[offset++] | buffer[offset++] << 8 | buffer[offset++] << 16 | buffer[offset++] << 24; + let hi = buffer[offset++] | buffer[offset++] << 8 | buffer[offset++] << 16 | buffer[offset] << 24; + return hi * TWO_PWR_32 + ((lo >= 0) ? lo : TWO_PWR_32 + lo); + } + + + /** + * Convert a string (UTF-8 encoded) to a byte array + * @param {String} str UTF-8 encoded string + * @return {Uint8Array} Byte array + */ + function str2bin(str: string): Uint8Array { + str = str.replace(/\r\n/g, "\n"); + let bin = [], p = 0; + for (let i = 0, len = str.length; i < len; i++) { + let c = str.charCodeAt(i); + if (c < 128) { + bin[p++] = c; + } else if (c < 2048) { + bin[p++] = (c >>> 6) | 192; + bin[p++] = (c & 63) | 128; + } else { + bin[p++] = (c >>> 12) | 224; + bin[p++] = ((c >>> 6) & 63) | 128; + bin[p++] = (c & 63) | 128; + } + } + return new Uint8Array(bin); + } + + + /** + * Convert a byte array to an UTF-8 string + * @param {Uint8Array} bin UTF-8 text given as array of bytes + * @return {String} UTF-8 Text string + */ + function bin2str(bin: Uint8Array): string { + let str = "", len = bin.length, i = 0, c, c2, c3; + + while (i < len) { + c = bin[i]; + if (c < 128) { + str += String.fromCharCode(c); + i++; + } + else if ((c > 191) && (c < 224)) { + c2 = bin[i + 1]; + str += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 2; + } + else { + c2 = bin[i + 1]; + c3 = bin[i + 2]; + str += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + } + return str; + } + + + /** + * Returns the UTF-8 string length in bytes + * @param {String} Input string + * @return {Number} Stringlength in bytes (not in chars) + */ + function strlen(str: string): number { + return encodeURI(str).split(/%..|./).length - 1; + } + +} // namespace BSON diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5cf044a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "outDir": "js", + "rootDir": "." + }, + "exclude": [ + "js", + "node_modules", + "typings/main", + "typings/main.d.ts" + ], + "filesGlob": [ + "**/*.ts", + "!node_modules/**/*" + ], + "compileOnSave": false, + "atom": { + "rewriteTsconfig": false + } +} diff --git a/typings.json b/typings.json new file mode 100644 index 0000000..3917d0a --- /dev/null +++ b/typings.json @@ -0,0 +1,5 @@ +{ + "name": "BSON", + "globalDependencies": { + } +} diff --git a/typings/assertion-error/assertion-error.d.ts b/typings/assertion-error/assertion-error.d.ts new file mode 100644 index 0000000..a0295bd --- /dev/null +++ b/typings/assertion-error/assertion-error.d.ts @@ -0,0 +1,15 @@ +// Type definitions for assertion-error 1.0.0 +// Project: https://github.com/chaijs/assertion-error +// Definitions by: Bart van der Schoor +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +declare module 'assertion-error' { + class AssertionError implements Error { + constructor(message: string, props?: any, ssf?: Function); + name: string; + message: string; + showDiff: boolean; + stack: string; + } + export = AssertionError; +} diff --git a/typings/chai/chai.d.ts b/typings/chai/chai.d.ts new file mode 100644 index 0000000..8eb0995 --- /dev/null +++ b/typings/chai/chai.d.ts @@ -0,0 +1,414 @@ +// Type definitions for chai 3.4.0 +// Project: http://chaijs.com/ +// Definitions by: Jed Mao , +// Bart van der Schoor , +// Andrew Brown , +// Olivier Chevet , +// Matt Wistrand +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +// + +declare namespace Chai { + + interface ChaiStatic { + expect: ExpectStatic; + should(): Should; + /** + * Provides a way to extend the internals of Chai + */ + use(fn: (chai: any, utils: any) => void): ChaiStatic; + assert: AssertStatic; + config: Config; + AssertionError: typeof AssertionError; + } + + export interface ExpectStatic extends AssertionStatic { + fail(actual?: any, expected?: any, message?: string, operator?: string): void; + } + + export interface AssertStatic extends Assert { + } + + export interface AssertionStatic { + (target: any, message?: string): Assertion; + } + + interface ShouldAssertion { + equal(value1: any, value2: any, message?: string): void; + Throw: ShouldThrow; + throw: ShouldThrow; + exist(value: any, message?: string): void; + } + + interface Should extends ShouldAssertion { + not: ShouldAssertion; + fail(actual: any, expected: any, message?: string, operator?: string): void; + } + + interface ShouldThrow { + (actual: Function): void; + (actual: Function, expected: string|RegExp, message?: string): void; + (actual: Function, constructor: Error|Function, expected?: string|RegExp, message?: string): void; + } + + interface Assertion extends LanguageChains, NumericComparison, TypeComparison { + not: Assertion; + deep: Deep; + any: KeyFilter; + all: KeyFilter; + a: TypeComparison; + an: TypeComparison; + include: Include; + includes: Include; + contain: Include; + contains: Include; + ok: Assertion; + true: Assertion; + false: Assertion; + null: Assertion; + undefined: Assertion; + NaN: Assertion; + exist: Assertion; + empty: Assertion; + arguments: Assertion; + Arguments: Assertion; + equal: Equal; + equals: Equal; + eq: Equal; + eql: Equal; + eqls: Equal; + property: Property; + ownProperty: OwnProperty; + haveOwnProperty: OwnProperty; + ownPropertyDescriptor: OwnPropertyDescriptor; + haveOwnPropertyDescriptor: OwnPropertyDescriptor; + length: Length; + lengthOf: Length; + match: Match; + matches: Match; + string(string: string, message?: string): Assertion; + keys: Keys; + key(string: string): Assertion; + throw: Throw; + throws: Throw; + Throw: Throw; + respondTo: RespondTo; + respondsTo: RespondTo; + itself: Assertion; + satisfy: Satisfy; + satisfies: Satisfy; + closeTo: CloseTo; + approximately: CloseTo; + members: Members; + increase: PropertyChange; + increases: PropertyChange; + decrease: PropertyChange; + decreases: PropertyChange; + change: PropertyChange; + changes: PropertyChange; + extensible: Assertion; + sealed: Assertion; + frozen: Assertion; + oneOf(list: any[], message?: string): Assertion; + } + + interface LanguageChains { + to: Assertion; + be: Assertion; + been: Assertion; + is: Assertion; + that: Assertion; + which: Assertion; + and: Assertion; + has: Assertion; + have: Assertion; + with: Assertion; + at: Assertion; + of: Assertion; + same: Assertion; + } + + 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): Assertion; + } + + interface NumberComparer { + (value: number, message?: string): Assertion; + } + + interface TypeComparison { + (type: string, message?: string): Assertion; + instanceof: InstanceOf; + instanceOf: InstanceOf; + } + + interface InstanceOf { + (constructor: Object, message?: string): Assertion; + } + + interface CloseTo { + (expected: number, delta: number, message?: string): Assertion; + } + + interface Deep { + equal: Equal; + include: Include; + property: Property; + members: Members; + } + + interface KeyFilter { + keys: Keys; + } + + interface Equal { + (value: any, message?: string): Assertion; + } + + interface Property { + (name: string, value?: any, message?: string): Assertion; + } + + interface OwnProperty { + (name: string, message?: string): Assertion; + } + + interface OwnPropertyDescriptor { + (name: string, descriptor: PropertyDescriptor, message?: string): Assertion; + (name: string, message?: string): Assertion; + } + + interface Length extends LanguageChains, NumericComparison { + (length: number, message?: string): Assertion; + } + + interface Include { + (value: Object, message?: string): Assertion; + (value: string, message?: string): Assertion; + (value: number, message?: string): Assertion; + keys: Keys; + members: Members; + any: KeyFilter; + all: KeyFilter; + } + + interface Match { + (regexp: RegExp|string, message?: string): Assertion; + } + + interface Keys { + (...keys: string[]): Assertion; + (keys: any[]): Assertion; + (keys: Object): Assertion; + } + + interface Throw { + (): Assertion; + (expected: string, message?: string): Assertion; + (expected: RegExp, message?: string): Assertion; + (constructor: Error, expected?: string, message?: string): Assertion; + (constructor: Error, expected?: RegExp, message?: string): Assertion; + (constructor: Function, expected?: string, message?: string): Assertion; + (constructor: Function, expected?: RegExp, message?: string): Assertion; + } + + interface RespondTo { + (method: string, message?: string): Assertion; + } + + interface Satisfy { + (matcher: Function, message?: string): Assertion; + } + + interface Members { + (set: any[], message?: string): Assertion; + } + + interface PropertyChange { + (object: Object, prop: string, msg?: string): Assertion; + } + + export interface Assert { + /** + * @param expression Expression to test for truthiness. + * @param message Message to display on error. + */ + (expression: any, message?: string): void; + + fail(actual?: any, expected?: any, msg?: string, operator?: string): void; + + ok(val: any, msg?: string): void; + isOk(val: any, msg?: string): void; + notOk(val: any, msg?: string): void; + isNotOk(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; + + isNotTrue(val: any, msg?: string): void; + isNotFalse(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; + + isNaN(val: any, msg?: string): void; + isNotNaN(val: any, msg?: string): void; + + isAbove(val: number, abv: number, msg?: string): void; + isBelow(val: number, blw: number, msg?: string): void; + + isAtLeast(val: number, atlst: number, msg?: string): void; + isAtMost(val: number, atmst: number, 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; + approximately(act: number, exp: number, delta: number, msg?: string): void; + + sameMembers(set1: any[], set2: any[], msg?: string): void; + sameDeepMembers(set1: any[], set2: any[], msg?: string): void; + includeMembers(superset: any[], subset: any[], msg?: string): void; + + ifError(val: any, msg?: string): void; + + isExtensible(obj: {}, msg?: string): void; + extensible(obj: {}, msg?: string): void; + isNotExtensible(obj: {}, msg?: string): void; + notExtensible(obj: {}, msg?: string): void; + + isSealed(obj: {}, msg?: string): void; + sealed(obj: {}, msg?: string): void; + isNotSealed(obj: {}, msg?: string): void; + notSealed(obj: {}, msg?: string): void; + + isFrozen(obj: Object, msg?: string): void; + frozen(obj: Object, msg?: string): void; + isNotFrozen(obj: Object, msg?: string): void; + notFrozen(obj: Object, msg?: string): void; + + oneOf(inList: any, list: any[], msg?: string): void; + } + + export interface Config { + /** + * Default: false + */ + includeStack: boolean; + + /** + * Default: true + */ + showDiff: boolean; + + /** + * Default: 40 + */ + truncateThreshold: number; + } + + export class AssertionError { + constructor(message: string, _props?: any, ssf?: Function); + name: string; + message: string; + showDiff: boolean; + stack: string; + } +} + +declare var chai: Chai.ChaiStatic; + +declare module "chai" { + export = chai; +} + +interface Object { + should: Chai.Assertion; +} diff --git a/typings/mocha/mocha.d.ts b/typings/mocha/mocha.d.ts new file mode 100644 index 0000000..eb167c8 --- /dev/null +++ b/typings/mocha/mocha.d.ts @@ -0,0 +1,237 @@ +// Type definitions for mocha 2.2.5 +// Project: http://mochajs.org/ +// Definitions by: Kazi Manzur Rashid , otiai10 , jt000 , Vadim Macagon +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +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.Spec` + 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: Mocha.IContextDefinition; +declare var xdescribe: Mocha.IContextDefinition; +// alias for `describe` +declare var context: Mocha.IContextDefinition; +// alias for `describe` +declare var suite: Mocha.IContextDefinition; +declare var it: Mocha.ITestDefinition; +declare var xit: Mocha.ITestDefinition; +// alias for `it` +declare var test: Mocha.ITestDefinition; +declare var specify: Mocha.ITestDefinition; + +declare function before(action: () => void): void; + +declare function before(action: (done: MochaDone) => void): void; + +declare function before(description: string, action: () => void): void; + +declare function before(description: string, 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 after(description: string, action: () => void): void; + +declare function after(description: string, 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 beforeEach(description: string, action: () => void): void; + +declare function beforeEach(description: string, 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 afterEach(description: string, action: () => void): void; + +declare function afterEach(description: string, action: (done: MochaDone) => void): void; + +declare function suiteTeardown(action: () => void): void; + +declare function suiteTeardown(action: (done: MochaDone) => void): void; + +declare class Mocha { + constructor(options?: { + grep?: RegExp; + ui?: string; + reporter?: string; + timeout?: number; + bail?: boolean; + }); + + /** Setup mocha with the given options. */ + setup(options: MochaSetupOptions): Mocha; + bail(value?: boolean): Mocha; + addFile(file: string): Mocha; + /** Sets reporter by name, defaults to "spec". */ + reporter(name: string): Mocha; + /** Sets reporter constructor, defaults to mocha.reporters.Spec. */ + reporter(reporter: (runner: Mocha.IRunner, options: any) => any): Mocha; + ui(value: string): Mocha; + grep(value: string): Mocha; + grep(value: RegExp): Mocha; + invert(): Mocha; + ignoreLeaks(value: boolean): Mocha; + checkLeaks(): Mocha; + /** + * Function to allow assertion libraries to throw errors directly into mocha. + * This is useful when running tests in a browser because window.onerror will + * only receive the 'message' attribute of the Error. + */ + throwError(error: Error): void; + /** Enables growl support. */ + 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; + /** Runs tests and invokes `onComplete()` when finished. */ + run(onComplete?: (failures: number) => void): Mocha.IRunner; +} + +// merge the Mocha class declaration with a module +declare namespace Mocha { + /** Partial interface for Mocha's `Runnable` class. */ + interface IRunnable { + title: string; + fn: Function; + async: boolean; + sync: boolean; + timedOut: boolean; + } + + /** Partial interface for Mocha's `Suite` class. */ + interface ISuite { + parent: ISuite; + title: string; + + fullTitle(): string; + } + + /** Partial interface for Mocha's `Test` class. */ + interface ITest extends IRunnable { + parent: ISuite; + pending: boolean; + + fullTitle(): string; + } + + /** Partial interface for Mocha's `Runner` class. */ + interface IRunner {} + + interface IContextDefinition { + (description: string, spec: () => void): ISuite; + only(description: string, spec: () => void): ISuite; + skip(description: string, spec: () => void): void; + timeout(ms: number): void; + } + + interface ITestDefinition { + (expectation: string, assertion?: () => void): ITest; + (expectation: string, assertion?: (done: MochaDone) => void): ITest; + only(expectation: string, assertion?: () => void): ITest; + only(expectation: string, assertion?: (done: MochaDone) => void): ITest; + skip(expectation: string, assertion?: () => void): void; + skip(expectation: string, assertion?: (done: MochaDone) => void): void; + timeout(ms: number): void; + } + + export module reporters { + export class Base { + stats: { + suites: number; + tests: number; + passes: number; + pending: number; + failures: number; + }; + + constructor(runner: IRunner); + } + + export class Doc extends Base {} + export class Dot extends Base {} + export class HTML extends Base {} + export class HTMLCov extends Base {} + export class JSON extends Base {} + export class JSONCov extends Base {} + export class JSONStream extends Base {} + export class Landing extends Base {} + export class List extends Base {} + export class Markdown extends Base {} + export class Min extends Base {} + export class Nyan extends Base {} + export class Progress extends Base { + /** + * @param options.open String used to indicate the start of the progress bar. + * @param options.complete String used to indicate a complete test on the progress bar. + * @param options.incomplete String used to indicate an incomplete test on the progress bar. + * @param options.close String used to indicate the end of the progress bar. + */ + constructor(runner: IRunner, options?: { + open?: string; + complete?: string; + incomplete?: string; + close?: string; + }); + } + export class Spec extends Base {} + export class TAP extends Base {} + export class XUnit extends Base { + constructor(runner: IRunner, options?: any); + } + } +} + +declare module "mocha" { + export = Mocha; +}