diff --git a/README.md b/README.md index f57b3dabbe..406b6e790c 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Here are the main highlights of LMDB, for more, visit http://symas.com/mdb :) * Support for transactions and multiple databases in the same environment * Support for multi-threaded and multi-process use * Zero-copy lookup (memory map) +* Crash-proof design ### Supported platforms @@ -110,6 +111,49 @@ txn.putString(dbi, 2, "Yes, it's this simple!"); txn.commit(); ``` +#### Asynchronous batched operations + +You can batch together a set of operations to be processed asynchronously with `node-lmdb`. Committing multiple operations at once can improve performance, and performing a batch of operations and using sync transactions (slower, but maintains crash-proof integrity) can be efficiently delegated to an asynchronous thread. In addition, writes can be defined as conditional by specifying the required value to match in order for the operation to be performed, to allow for deterministic atomic writes based on prior state. The `batchWrite` method accepts an array of write operation requests, where each operation is an object or array. If it is an object, the supported properties are: +* `db` (required) - The database to write to +* `key` (required) - The key to write +* `value` (optional) - If specified, this is the value to `put` into the entry. If absent or undefined, this write operation will be a delete, and delete this key. This should be a binary/buffer value. +* `ifValue` (optional) - If specified, the write operation (put or delete) will only be performed if the provided `ifValue` matches the existing value for this entry. This should be a binary/buffer value. +* `ifExactMatch` (optional) - If set to true, the conditional write requires that `ifValue` exactly match the existing value, byte for byte and length. By default `ifValue` can be a prefix and only needs to match the number of bytes in `ifValue` (for example if `ifValue` is `Buffer.from([5, 2])`, the conditional write will be performed if the `value` starts with 5, 2). +* `ifKey` (optional) - If specified, indicates the key to use for for matching the conditional value. By default, the key use to match `ifValue` is the same key as the write operation. +* `ifDB` (optional) - If specified, indicates the db to use for for matching the conditional value. By default, the key use to match `ifValue` is the same db as the write operation. + +If the write operation is a specified with an array, the supported elements are: +* A three element array for `put`ing data: `[db, key, value]` (where `value` is a binary/buffer) +* A two element array for `del`eting data: `[db, key]` +* A four element array for conditionally `put`ing or `del`eting data: `[db, key, value, ifValue]` (where `value` and `ifValue` are as specificied in the object definition) + +When `batchWrite` is called, `node-ldmb` will asynchronously create a new write transaction, execute all the operations in the provided array, except for any conditional writes where the condition failed, and commit the transaction, if there were no errors. For conditional writes, if the condition did not match, the write will be skipped, but the transaction will still be committed. However, if any errors occur, the transaction will be aborted. This entire transaction will be created by `node-lmdb` and executed in a separate thread. The callback function will be called once the transaction is finished. It is possible for an explicit write transaction in the main JS thread to block or be blocked by the asynchronous transaction. +For example: +```javascript +env.batchWrite([ + [dbi, key1, Buffer.from("Hello")], // put in key 1 + [dbi, key2, Buffer.from("World")], // put in key 2 + [dbi, key3], // delete any entry from key 3 (can also use null as value to indicate delete) + [dbi, key4, valuePlusOne, oldValue] // you could atomically increment by specifying the require previous state +], options, (error, results) => { + if (error) { + console.error(error); + } else { + // operations finished and transaction was committed + let didWriteToKey4Succeed = results[3] === 0 + } +}) +``` +The callback function will be either be called with an error in the first argument, or an array in the second argument with the results of the operations. The array will be the same length as the array of write operations, with one to one correspondence by position, and each value in the result array will be: +0 - Operation successfully written +1 - Condition not met (only can happen if a condition was provided) +2 - Attempt to delete non-existent key (only can happen if `ignoreNotFound` enabled) + + +The options include all the flags from `put` `options`, and this optional property: +* `progress` - This should be a function, if provided, will be called to report the progress of the write operations, returning the results array, with completion values filled in for completed operations, and all uncompleted operations will correspond to `undefined` in the eleemnt positions in the array. Progress events are best-effort in node; the write operations are performed in a separate thread, and progress events occur if and when node's event queue is free to run them (they are not guaranteed to fire if the main thread is busy). + + ### Basic concepts LMDB has four different entities: diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..66e3bbfb24 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1134 @@ +{ + "name": "node-lmdb", + "version": "0.6.2", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "ajv": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz", + "integrity": "sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "benchmark": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", + "integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=", + "dev": true, + "requires": { + "lodash": "^4.17.4", + "platform": "^1.3.3" + } + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chai": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", + "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", + "dev": true, + "requires": { + "assertion-error": "^1.0.1", + "deep-eql": "^0.1.3", + "type-detect": "^1.0.0" + } + }, + "cli": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", + "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", + "dev": true, + "requires": { + "exit": "0.1.2", + "glob": "^7.1.1" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "^0.1.4" + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-eql": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "0.1.1" + }, + "dependencies": { + "type-detect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "dev": true, + "requires": { + "domelementtype": "~1.1.1", + "entities": "~1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", + "dev": true + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + } + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "entities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "htmlparser2": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "dev": true, + "requires": { + "domelementtype": "1", + "domhandler": "2.3", + "domutils": "1.5", + "entities": "1.0", + "readable-stream": "1.1" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jshint": { + "version": "2.9.7", + "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.7.tgz", + "integrity": "sha512-Q8XN38hGsVQhdlM+4gd1Xl7OB1VieSuCJf+fEJjpo59JH99bVJhXRXAh26qQ15wfdd1VPMuDWNeSWoNl53T4YA==", + "dev": true, + "requires": { + "cli": "~1.0.0", + "console-browserify": "1.1.x", + "exit": "0.1.x", + "htmlparser2": "3.8.x", + "lodash": "~4.17.10", + "minimatch": "~3.0.2", + "shelljs": "0.3.x", + "strip-json-comments": "1.0.x" + } + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", + "dev": true + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "dev": true, + "requires": { + "mime-db": "~1.37.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "dependencies": { + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "nan": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", + "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==" + }, + "node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "dev": true, + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "platform": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", + "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "shelljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-detect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", + "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/package.json b/package.json index 48fec75e07..d969baaa09 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "Matt-Esch", "Oliver Zhou (oliverzy)", "Pascal Berrang (paberr)", - "Raymond Neilson (rneilson)" + "Raymond Neilson (rneilson)", + "Kris Zyp (kriszyp)" ], "license": "MIT", "keywords": [ @@ -37,9 +38,9 @@ }, "gypfile": true, "dependencies": { - "bindings": "^1.2.1", + "bindings": "^1.5.0", "prebuild-install": "^5.2.5", - "nan": "^2.12.0" + "nan": "^2.13.1" }, "devDependencies": { "benchmark": "^2.1.4", diff --git a/src/cursor.cpp b/src/cursor.cpp index 9926856723..2658188df7 100644 --- a/src/cursor.cpp +++ b/src/cursor.cpp @@ -272,7 +272,7 @@ MAKE_GET_FUNC(goToPrevDup, MDB_PREV_DUP); static void fillDataFromArg1(CursorWrap* cw, Nan::NAN_METHOD_ARGS_TYPE info, MDB_val &data) { if (info[1]->IsString()) { - CustomExternalStringResource::writeTo(info[1]->ToString(), &data); + CustomExternalStringResource::writeTo(Local::Cast(info[1]), &data); } else if (node::Buffer::HasInstance(info[1])) { data.mv_size = node::Buffer::Length(info[1]); @@ -350,7 +350,7 @@ NAN_METHOD(CursorWrap::goToDupRange) { return getCommon(info, MDB_GET_BOTH_RANGE, cursorArgToKey<0, 2>, fillDataFromArg1, freeDataFromArg1, nullptr); } -void CursorWrap::setupExports(Handle exports) { +void CursorWrap::setupExports(Local exports) { // CursorWrap: Prepare constructor template Local cursorTpl = Nan::New(CursorWrap::ctor); cursorTpl->SetClassName(Nan::New("Cursor").ToLocalChecked()); @@ -378,5 +378,5 @@ void CursorWrap::setupExports(Handle exports) { cursorTpl->PrototypeTemplate()->Set(Nan::New("del").ToLocalChecked(), Nan::New(CursorWrap::del)); // Set exports - exports->Set(Nan::New("Cursor").ToLocalChecked(), cursorTpl->GetFunction()); + exports->Set(Nan::GetCurrentContext(), Nan::New("Cursor").ToLocalChecked(), cursorTpl->GetFunction(Nan::GetCurrentContext()).ToLocalChecked()); } diff --git a/src/dbi.cpp b/src/dbi.cpp index 449ae7963a..118c320245 100644 --- a/src/dbi.cpp +++ b/src/dbi.cpp @@ -70,12 +70,12 @@ NAN_METHOD(DbiWrap::ctor) { bool needsTransaction = true; bool isOpen = false; - EnvWrap *ew = Nan::ObjectWrap::Unwrap(info[0]->ToObject()); + EnvWrap *ew = Nan::ObjectWrap::Unwrap(Local::Cast(info[0])); if (info[1]->IsObject()) { - Local options = info[1]->ToObject(); - nameIsNull = options->Get(Nan::New("name").ToLocalChecked())->IsNull(); - name = options->Get(Nan::New("name").ToLocalChecked())->ToString(); + Local options = Local::Cast(info[1]); + nameIsNull = options->Get(Nan::GetCurrentContext(), Nan::New("name").ToLocalChecked()).ToLocalChecked()->IsNull(); + name = Local::Cast(options->Get(Nan::GetCurrentContext(), Nan::New("name").ToLocalChecked()).ToLocalChecked()); // Get flags from options @@ -102,14 +102,14 @@ NAN_METHOD(DbiWrap::ctor) { } // Set flags for txn used to open database - Local create = options->Get(Nan::New("create").ToLocalChecked()); - if (create->IsBoolean() ? !create->BooleanValue() : true) { + Local create = options->Get(Nan::GetCurrentContext(), Nan::New("create").ToLocalChecked()).ToLocalChecked(); + if (create->IsBoolean() ? !create->BooleanValue(Nan::GetCurrentContext()).ToChecked() : true) { txnFlags |= MDB_RDONLY; } - auto txnObj = options->Get(Nan::New("txn").ToLocalChecked()); + auto txnObj = options->Get(Nan::GetCurrentContext(), Nan::New("txn").ToLocalChecked()).ToLocalChecked(); if (!txnObj->IsNull() && !txnObj->IsUndefined() && txnObj->IsObject()) { - TxnWrap *tw = Nan::ObjectWrap::Unwrap(txnObj->ToObject()); + TxnWrap *tw = Nan::ObjectWrap::Unwrap(Local::Cast(txnObj)); needsTransaction = false; txn = tw->txn; } @@ -129,7 +129,7 @@ NAN_METHOD(DbiWrap::ctor) { // Open database // NOTE: nullptr in place of the name means using the unnamed database. - rc = mdb_dbi_open(txn, nameIsNull ? nullptr : *String::Utf8Value(name), flags, &dbi); + rc = mdb_dbi_open(txn, nameIsNull ? nullptr : *String::Utf8Value(Isolate::GetCurrent(), name), flags, &dbi); if (rc != 0) { if (needsTransaction) { mdb_txn_abort(txn); @@ -192,16 +192,16 @@ NAN_METHOD(DbiWrap::drop) { // Check if the database should be deleted if (info.Length() == 1 && info[0]->IsObject()) { - Handle options = info[0]->ToObject(); + Local options = Local::Cast(info[0]); // Just free pages - Local opt = options->Get(Nan::New("justFreePages").ToLocalChecked()); - del = opt->IsBoolean() ? !(opt->BooleanValue()) : 1; + Local opt = options->Get(Nan::GetCurrentContext(), Nan::New("justFreePages").ToLocalChecked()).ToLocalChecked(); + del = opt->IsBoolean() ? !(opt->BooleanValue(Nan::GetCurrentContext()).ToChecked()) : 1; // User-supplied txn - auto txnObj = options->Get(Nan::New("txn").ToLocalChecked()); + auto txnObj = options->Get(Nan::GetCurrentContext(), Nan::New("txn").ToLocalChecked()).ToLocalChecked(); if (!txnObj->IsNull() && !txnObj->IsUndefined() && txnObj->IsObject()) { - TxnWrap *tw = Nan::ObjectWrap::Unwrap(txnObj->ToObject()); + TxnWrap *tw = Nan::ObjectWrap::Unwrap(Local::Cast(txnObj)); needsTransaction = false; txn = tw->txn; } @@ -249,17 +249,19 @@ NAN_METHOD(DbiWrap::stat) { return Nan::ThrowError("dbi.stat should be called with a single argument which is a txn."); } - TxnWrap *txn = Nan::ObjectWrap::Unwrap(info[0]->ToObject()); + TxnWrap *txn = Nan::ObjectWrap::Unwrap(Local::Cast(info[0])); MDB_stat stat; mdb_stat(txn->txn, dw->dbi, &stat); + Local context = Nan::GetCurrentContext(); Local obj = Nan::New(); - obj->Set(Nan::New("pageSize").ToLocalChecked(), Nan::New(stat.ms_psize)); - obj->Set(Nan::New("treeDepth").ToLocalChecked(), Nan::New(stat.ms_depth)); - obj->Set(Nan::New("treeBranchPageCount").ToLocalChecked(), Nan::New(stat.ms_branch_pages)); - obj->Set(Nan::New("treeLeafPageCount").ToLocalChecked(), Nan::New(stat.ms_leaf_pages)); - obj->Set(Nan::New("entryCount").ToLocalChecked(), Nan::New(stat.ms_entries)); + obj->Set(context, Nan::New("pageSize").ToLocalChecked(), Nan::New(stat.ms_psize)); + obj->Set(context, Nan::New("treeDepth").ToLocalChecked(), Nan::New(stat.ms_depth)); + obj->Set(context, Nan::New("treeBranchPageCount").ToLocalChecked(), Nan::New(stat.ms_branch_pages)); + obj->Set(context, Nan::New("treeLeafPageCount").ToLocalChecked(), Nan::New(stat.ms_leaf_pages)); + obj->Set(context, Nan::New("entryCount").ToLocalChecked(), Nan::New(stat.ms_entries)); + obj->Set(context, Nan::New("overflowPages").ToLocalChecked(), Nan::New(stat.ms_overflow_pages)); info.GetReturnValue().Set(obj); -} +} \ No newline at end of file diff --git a/src/env.cpp b/src/env.cpp index 218562c7e1..b6fa84de82 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -26,6 +26,7 @@ using namespace v8; using namespace node; +#define IGNORE_NOTFOUND (1) Nan::Persistent EnvWrap::txnCtor; Nan::Persistent EnvWrap::dbiCtor; @@ -79,9 +80,9 @@ NAN_METHOD(EnvWrap::ctor) { template int applyUint32Setting(int (*f)(MDB_env *, T), MDB_env* e, Local options, T dflt, const char* keyName) { int rc; - const Local value = options->Get(Nan::New(keyName).ToLocalChecked()); + const Local value = options->Get(Nan::GetCurrentContext(), Nan::New(keyName).ToLocalChecked()).ToLocalChecked(); if (value->IsUint32()) { - rc = f(e, value->Uint32Value()); + rc = f(e, value->Uint32Value(Nan::GetCurrentContext()).ToChecked()); } else { rc = f(e, dflt); @@ -96,26 +97,182 @@ class SyncWorker : public Nan::AsyncWorker { : Nan::AsyncWorker(callback), env(env) {} void Execute() { - int rc = mdb_env_sync(env, 1); - if (rc != 0) { - SetErrorMessage(mdb_strerror(rc)); - } + int rc = mdb_env_sync(env, 1); + if (rc != 0) { + SetErrorMessage(mdb_strerror(rc)); + } + } + + void HandleOKCallback() { + Nan::HandleScope scope; + v8::Local argv[] = { + Nan::Null() + }; + + callback->Call(1, argv, async_resource); + } + + private: + MDB_env* env; +}; + +struct condition_t { + MDB_val key; + MDB_val data; + MDB_dbi dbi; + bool matchSize; + argtokey_callback_t freeKey; +}; + +struct action_t { + MDB_val key; + MDB_val data; + MDB_dbi dbi; + condition_t *condition; + argtokey_callback_t freeKey; +}; + +class BatchWorker : public Nan::AsyncProgressWorker { + public: + BatchWorker(MDB_env* env, action_t *actions, int actionCount, int putFlags, Nan::Callback *callback, Nan::Callback *progress) + : Nan::AsyncProgressWorker(callback, "node-lmdb:Batch"), + actions(actions), + actionCount(actionCount), + putFlags(putFlags), + env(env), + progress(progress) { + results = new int[actionCount]; + } + + ~BatchWorker() { + for (int i = 0; i < actionCount; i++) { + action_t* action = &actions[i]; + condition_t* condition = action->condition; + if (condition) { + delete condition; + } + } + delete[] actions; + delete[] results; + delete progress; + } + + void Execute(const ExecutionProgress& executionProgress) { + MDB_txn *txn; + int rc = mdb_txn_begin(env, nullptr, 0, &txn); + if (rc != 0) { + return SetErrorMessage(mdb_strerror(rc)); + } + int getCount = 0; + + for (int i = 0; i < actionCount;) { + action_t* action = &actions[i]; + condition_t* condition = action->condition; + if (condition) { + MDB_val value; + rc = mdb_get(txn, condition->dbi, &condition->key, &value); + bool different; + if (condition->data.mv_data == nullptr) { + different = rc != MDB_NOTFOUND; + } else { + if (rc == MDB_NOTFOUND) { + different = true; + } else { + different = (condition->matchSize ? value.mv_size != condition->data.mv_size : value.mv_size < condition->data.mv_size) || + memcmp(value.mv_data, condition->data.mv_data, condition->data.mv_size); + } + } + if (different) { + results[i] = 1; + } else { + // condition matches, remove condition, same as having no condition + condition = nullptr; + results[i] = 0; + } + } else { + results[i] = 0; + } + if (condition) { + rc = 0; // make sure this gets set back to zero, failed conditions shouldn't trigger error + } else { + if (action->data.mv_data == nullptr) { + rc = mdb_del(txn, action->dbi, &action->key, nullptr); + if (rc == MDB_NOTFOUND) { + rc = 0; // ignore not_found errors + results[i] = 2; + } + } else { + rc = mdb_put(txn, action->dbi, &action->key, &action->data, putFlags); + } + } + + if (action->freeKey) { // if we created a key and needs to be cleaned up, do it now + action->freeKey(action->key); + } + if (rc != 0) { + mdb_txn_abort(txn); + return SetErrorMessage(mdb_strerror(rc)); + } + i++; + if (progress) { // let node know that progress updates are available + executionProgress.Send(reinterpret_cast(&i), sizeof(int)); + } + } + + rc = mdb_txn_commit(txn); + if (rc != 0) { + return SetErrorMessage(mdb_strerror(rc)); + } + } + + v8::Local updatedResultsArray(int currentIndex) { + v8::Local resultsArray; + if (hasResultsArray) { + resultsArray = v8::Local::Cast(GetFromPersistent("results")); + } else { + resultsArray = Nan::New(actionCount); + SaveToPersistent("results", resultsArray); + hasResultsArray = true; + } + Local context = Nan::GetCurrentContext(); + for (; resultIndex < currentIndex; resultIndex++) { + resultsArray->Set(context, resultIndex, Nan::New(results[resultIndex])); + } + return resultsArray; + } + + void HandleProgressCallback(const char *data, size_t count) { + Nan::HandleScope scope; + v8::Local argv[] = { + updatedResultsArray(*reinterpret_cast(const_cast(data))) + }; + + progress->Call(1, argv, async_resource); } void HandleOKCallback() { - Nan::HandleScope scope; - v8::Local argv[] = { - Nan::Null() - }; + Nan::HandleScope scope; + v8::Local argv[] = { + Nan::Null(), + updatedResultsArray(actionCount) + }; - callback->Call(1, argv, async_resource); + callback->Call(2, argv, async_resource); } private: MDB_env* env; + int actionCount; + int* results; + int resultIndex = 0; + bool hasResultsArray = false; + action_t* actions; + int putFlags; + Nan::Callback* progress; }; + NAN_METHOD(EnvWrap::open) { Nan::HandleScope scope; @@ -129,8 +286,8 @@ NAN_METHOD(EnvWrap::open) { return Nan::ThrowError("The environment is already closed."); } - Local options = info[0]->ToObject(); - Local path = options->Get(Nan::New("path").ToLocalChecked())->ToString(); + Local options = Local::Cast(info[0]); + Local path = Local::Cast(options->Get(Nan::GetCurrentContext(), Nan::New("path").ToLocalChecked()).ToLocalChecked()); // Parse the maxDbs option rc = applyUint32Setting(&mdb_env_set_maxdbs, ew->env, options, 1, "maxDbs"); @@ -139,10 +296,9 @@ NAN_METHOD(EnvWrap::open) { } // Parse the mapSize option - Local mapSizeOption = options->Get(Nan::New("mapSize").ToLocalChecked()); + Local mapSizeOption = options->Get(Nan::GetCurrentContext(), Nan::New("mapSize").ToLocalChecked()).ToLocalChecked(); if (mapSizeOption->IsNumber()) { - double mapSizeDouble = mapSizeOption->NumberValue(); - size_t mapSizeSizeT = (size_t) mapSizeDouble; + size_t mapSizeSizeT = mapSizeOption->IntegerValue(Nan::GetCurrentContext()).ToChecked(); rc = mdb_env_set_mapsize(ew->env, mapSizeSizeT); if (rc != 0) { return throwLmdbError(rc); @@ -165,16 +321,16 @@ NAN_METHOD(EnvWrap::open) { setFlagFromValue(&flags, MDB_NOSYNC, "noSync", false, options); setFlagFromValue(&flags, MDB_MAPASYNC, "mapAsync", false, options); setFlagFromValue(&flags, MDB_NOLOCK, "unsafeNoLock", false, options); - + if (flags & MDB_NOLOCK) { fprintf(stderr, "You chose to use MDB_NOLOCK which is not officially supported by node-lmdb. You have been warned!\n"); } - + // Set MDB_NOTLS to enable multiple read-only transactions on the same thread (in this case, the nodejs main thread) flags |= MDB_NOTLS; // TODO: make file attributes configurable - rc = mdb_env_open(ew->env, *String::Utf8Value(path), flags, 0664); + rc = mdb_env_open(ew->env, *String::Utf8Value(Isolate::GetCurrent(), path), flags, 0664); if (rc != 0) { mdb_env_close(ew->env); @@ -203,8 +359,7 @@ NAN_METHOD(EnvWrap::resize) { return Nan::ThrowError("Only call env.resize() when there are no active transactions. Please close all transactions before calling env.resize()."); } - double mapSizeDouble = info[0]->NumberValue(); - size_t mapSizeSizeT = (size_t) mapSizeDouble; + size_t mapSizeSizeT = info[0]->IntegerValue(Nan::GetCurrentContext()).ToChecked(); int rc = mdb_env_set_mapsize(ew->env, mapSizeSizeT); if (rc != 0) { return throwLmdbError(rc); @@ -226,55 +381,57 @@ NAN_METHOD(EnvWrap::close) { NAN_METHOD(EnvWrap::stat) { Nan::HandleScope scope; - + // Get the wrapper EnvWrap *ew = Nan::ObjectWrap::Unwrap(info.This()); if (!ew->env) { return Nan::ThrowError("The environment is already closed."); } - + int rc; MDB_stat stat; - + rc = mdb_env_stat(ew->env, &stat); if (rc != 0) { return throwLmdbError(rc); } - + + Local context = Nan::GetCurrentContext(); Local obj = Nan::New(); - obj->Set(Nan::New("pageSize").ToLocalChecked(), Nan::New(stat.ms_psize)); - obj->Set(Nan::New("treeDepth").ToLocalChecked(), Nan::New(stat.ms_depth)); - obj->Set(Nan::New("treeBranchPageCount").ToLocalChecked(), Nan::New(stat.ms_branch_pages)); - obj->Set(Nan::New("treeLeafPageCount").ToLocalChecked(), Nan::New(stat.ms_leaf_pages)); - obj->Set(Nan::New("entryCount").ToLocalChecked(), Nan::New(stat.ms_entries)); + obj->Set(context, Nan::New("pageSize").ToLocalChecked(), Nan::New(stat.ms_psize)); + obj->Set(context, Nan::New("treeDepth").ToLocalChecked(), Nan::New(stat.ms_depth)); + obj->Set(context, Nan::New("treeBranchPageCount").ToLocalChecked(), Nan::New(stat.ms_branch_pages)); + obj->Set(context, Nan::New("treeLeafPageCount").ToLocalChecked(), Nan::New(stat.ms_leaf_pages)); + obj->Set(context, Nan::New("entryCount").ToLocalChecked(), Nan::New(stat.ms_entries)); info.GetReturnValue().Set(obj); } NAN_METHOD(EnvWrap::info) { Nan::HandleScope scope; - + // Get the wrapper EnvWrap *ew = Nan::ObjectWrap::Unwrap(info.This()); if (!ew->env) { return Nan::ThrowError("The environment is already closed."); } - + int rc; MDB_envinfo envinfo; - + rc = mdb_env_info(ew->env, &envinfo); if (rc != 0) { return throwLmdbError(rc); } - + + Local context = Nan::GetCurrentContext(); Local obj = Nan::New(); - obj->Set(Nan::New("mapAddress").ToLocalChecked(), Nan::New((uint64_t) envinfo.me_mapaddr)); - obj->Set(Nan::New("mapSize").ToLocalChecked(), Nan::New(envinfo.me_mapsize)); - obj->Set(Nan::New("lastPageNumber").ToLocalChecked(), Nan::New(envinfo.me_last_pgno)); - obj->Set(Nan::New("lastTxnId").ToLocalChecked(), Nan::New(envinfo.me_last_txnid)); - obj->Set(Nan::New("maxReaders").ToLocalChecked(), Nan::New(envinfo.me_maxreaders)); - obj->Set(Nan::New("numReaders").ToLocalChecked(), Nan::New(envinfo.me_numreaders)); + obj->Set(context, Nan::New("mapAddress").ToLocalChecked(), Nan::New((uint64_t) envinfo.me_mapaddr)); + obj->Set(context, Nan::New("mapSize").ToLocalChecked(), Nan::New(envinfo.me_mapsize)); + obj->Set(context, Nan::New("lastPageNumber").ToLocalChecked(), Nan::New(envinfo.me_last_pgno)); + obj->Set(context, Nan::New("lastTxnId").ToLocalChecked(), Nan::New(envinfo.me_last_txnid)); + obj->Set(context, Nan::New("maxReaders").ToLocalChecked(), Nan::New(envinfo.me_maxreaders)); + obj->Set(context, Nan::New("numReaders").ToLocalChecked(), Nan::New(envinfo.me_numreaders)); info.GetReturnValue().Set(obj); } @@ -337,20 +494,163 @@ NAN_METHOD(EnvWrap::sync) { return; } -void EnvWrap::setupExports(Handle exports) { + +NAN_METHOD(EnvWrap::batchWrite) { + Nan::HandleScope scope; + + EnvWrap *ew = Nan::ObjectWrap::Unwrap(info.This()); + Local context = Nan::GetCurrentContext(); + + if (!ew->env) { + return Nan::ThrowError("The environment is already closed."); + } + v8::Local array = v8::Local::Cast(info[0]); + + int length = array->Length(); + action_t* actions = new action_t[length]; + + int putFlags = 0; + Nan::Callback* callback; + Nan::Callback* progress = nullptr; + Local options = info[1]; + + if (!info[1]->IsNull() && !info[1]->IsUndefined() && info[1]->IsObject() && !info[1]->IsFunction()) { + Local optionsObject = Local::Cast(options); + setFlagFromValue(&putFlags, MDB_NODUPDATA, "noDupData", false, optionsObject); + setFlagFromValue(&putFlags, MDB_NOOVERWRITE, "noOverwrite", false, optionsObject); + setFlagFromValue(&putFlags, MDB_APPEND, "append", false, optionsObject); + setFlagFromValue(&putFlags, MDB_APPENDDUP, "appendDup", false, optionsObject); + + Local progressValue = optionsObject->Get(context, Nan::New("progress").ToLocalChecked()).ToLocalChecked(); + if (progressValue->IsFunction()) { + progress = new Nan::Callback(v8::Local::Cast(progressValue)); + } + callback = new Nan::Callback( + v8::Local::Cast(info[2]) + ); + } else { + callback = new Nan::Callback( + v8::Local::Cast(info[1]) + ); + } + + BatchWorker* worker = new BatchWorker( + ew->env, actions, length, putFlags, callback, progress + ); + int persistedIndex = 0; + bool keyIsValid = false; + NodeLmdbKeyType keyType; + + for (unsigned int i = 0; i < array->Length(); i++) { + if (!array->Get(context, i).ToLocalChecked()->IsObject()) + continue; + action_t* action = &actions[i]; + v8::Local operation = v8::Local::Cast(array->Get(context, i).ToLocalChecked()); + + bool isArray = operation->IsArray(); + DbiWrap *dw = Nan::ObjectWrap::Unwrap(v8::Local::Cast((isArray ? operation->Get(context, 0) : operation->Get(context, Nan::New("db").ToLocalChecked())).ToLocalChecked())); + action->dbi = dw->dbi; + v8::Local key = (isArray ? operation->Get(context, 1) : operation->Get(context, Nan::New("key").ToLocalChecked())).ToLocalChecked(); + + if (!keyIsValid) { + // just execute this the first time so we didn't need to re-execute for each iteration + keyType = inferAndValidateKeyType(key, options, dw->keyType, keyIsValid); + if (!keyIsValid) { + // inferAndValidateKeyType already threw an error + return; + } + } + action->freeKey = argToKey(key, action->key, keyType, keyIsValid); + if (!keyIsValid) { + // argToKey already threw an error + return; + } + // persist the reference until we are done with the operation + worker->SaveToPersistent(persistedIndex++, key); + v8::Local value = (isArray ? operation->Get(context, 2) : operation->Get(context, Nan::New("value").ToLocalChecked())).ToLocalChecked(); + + // check if this is a conditional save + v8::Local ifValue = (isArray ? operation->Get(context, 3) : operation->Get(context, Nan::New("ifValue").ToLocalChecked())).ToLocalChecked(); + if (!ifValue->IsUndefined()) { + condition_t *condition = action->condition = new condition_t(); + + if (ifValue->IsNull()) { + condition->data.mv_data = nullptr; + } else { + condition->data.mv_size = node::Buffer::Length(ifValue); + condition->data.mv_data = node::Buffer::Data(ifValue); + if (!isArray) { + v8::Local ifExactMatch = operation->Get(context, Nan::New("ifExactMatch").ToLocalChecked()).ToLocalChecked(); + if (ifExactMatch->IsTrue()) { + condition->matchSize = true; + } + } + } + if (isArray) { + condition->dbi = action->dbi; + condition->key = action->key; + } else { + v8::Local ifDB = operation->Get(context, Nan::New("ifDB").ToLocalChecked()).ToLocalChecked(); + if (ifDB->IsNullOrUndefined()) { + condition->dbi = action->dbi; + } else { + dw = Nan::ObjectWrap::Unwrap(v8::Local::Cast((isArray ? operation->Get(context, 0) : operation->Get(Nan::GetCurrentContext(), Nan::New("ifDB").ToLocalChecked())).ToLocalChecked())); + condition->dbi = dw->dbi; + } + + v8::Local ifKey = operation->Get(context, Nan::New("ifKey").ToLocalChecked()).ToLocalChecked(); + if (ifKey->IsNullOrUndefined()) { + condition->key = action->key; + } else { + condition->freeKey = argToKey(ifKey, condition->key, keyType, keyIsValid); + if (!keyIsValid) { + // argToKey already threw an error + return; + } + worker->SaveToPersistent(persistedIndex++, ifKey); + } + } + worker->SaveToPersistent(persistedIndex++, ifValue); + } else { + action->condition = nullptr; + } + + if (value->IsNullOrUndefined()) { + action->data.mv_data = nullptr; + } else if (value->IsArrayBufferView()) { + action->data.mv_size = node::Buffer::Length(value); + action->data.mv_data = node::Buffer::Data(value); + // likewise persist value if needed too + worker->SaveToPersistent(persistedIndex++, value); + } else { + return Nan::ThrowError("The value must be a buffer or null/undefined."); + } + } + + worker->SaveToPersistent("env", info.This()); + + Nan::AsyncQueueWorker(worker); + return; +} + + + +void EnvWrap::setupExports(Local exports) { // EnvWrap: Prepare constructor template Local envTpl = Nan::New(EnvWrap::ctor); envTpl->SetClassName(Nan::New("Env").ToLocalChecked()); envTpl->InstanceTemplate()->SetInternalFieldCount(1); // EnvWrap: Add functions to the prototype - envTpl->PrototypeTemplate()->Set(Nan::New("open").ToLocalChecked(), Nan::New(EnvWrap::open)); - envTpl->PrototypeTemplate()->Set(Nan::New("close").ToLocalChecked(), Nan::New(EnvWrap::close)); - envTpl->PrototypeTemplate()->Set(Nan::New("beginTxn").ToLocalChecked(), Nan::New(EnvWrap::beginTxn)); - envTpl->PrototypeTemplate()->Set(Nan::New("openDbi").ToLocalChecked(), Nan::New(EnvWrap::openDbi)); - envTpl->PrototypeTemplate()->Set(Nan::New("sync").ToLocalChecked(), Nan::New(EnvWrap::sync)); - envTpl->PrototypeTemplate()->Set(Nan::New("stat").ToLocalChecked(), Nan::New(EnvWrap::stat)); - envTpl->PrototypeTemplate()->Set(Nan::New("info").ToLocalChecked(), Nan::New(EnvWrap::info)); - envTpl->PrototypeTemplate()->Set(Nan::New("resize").ToLocalChecked(), Nan::New(EnvWrap::resize)); + Isolate *isolate = Isolate::GetCurrent(); + envTpl->PrototypeTemplate()->Set(isolate, "open", Nan::New(EnvWrap::open)); + envTpl->PrototypeTemplate()->Set(isolate, "close", Nan::New(EnvWrap::close)); + envTpl->PrototypeTemplate()->Set(isolate, "beginTxn", Nan::New(EnvWrap::beginTxn)); + envTpl->PrototypeTemplate()->Set(isolate, "openDbi", Nan::New(EnvWrap::openDbi)); + envTpl->PrototypeTemplate()->Set(isolate, "sync", Nan::New(EnvWrap::sync)); + envTpl->PrototypeTemplate()->Set(isolate, "batchWrite", Nan::New(EnvWrap::batchWrite)); + envTpl->PrototypeTemplate()->Set(isolate, "stat", Nan::New(EnvWrap::stat)); + envTpl->PrototypeTemplate()->Set(isolate, "info", Nan::New(EnvWrap::info)); + envTpl->PrototypeTemplate()->Set(isolate, "resize", Nan::New(EnvWrap::resize)); // TODO: wrap mdb_env_copy too // TxnWrap: Prepare constructor template @@ -358,38 +658,38 @@ void EnvWrap::setupExports(Handle exports) { txnTpl->SetClassName(Nan::New("Txn").ToLocalChecked()); txnTpl->InstanceTemplate()->SetInternalFieldCount(1); // TxnWrap: Add functions to the prototype - txnTpl->PrototypeTemplate()->Set(Nan::New("commit").ToLocalChecked(), Nan::New(TxnWrap::commit)); - txnTpl->PrototypeTemplate()->Set(Nan::New("abort").ToLocalChecked(), Nan::New(TxnWrap::abort)); - txnTpl->PrototypeTemplate()->Set(Nan::New("getString").ToLocalChecked(), Nan::New(TxnWrap::getString)); - txnTpl->PrototypeTemplate()->Set(Nan::New("getStringUnsafe").ToLocalChecked(), Nan::New(TxnWrap::getStringUnsafe)); - txnTpl->PrototypeTemplate()->Set(Nan::New("getBinary").ToLocalChecked(), Nan::New(TxnWrap::getBinary)); - txnTpl->PrototypeTemplate()->Set(Nan::New("getBinaryUnsafe").ToLocalChecked(), Nan::New(TxnWrap::getBinaryUnsafe)); - txnTpl->PrototypeTemplate()->Set(Nan::New("getNumber").ToLocalChecked(), Nan::New(TxnWrap::getNumber)); - txnTpl->PrototypeTemplate()->Set(Nan::New("getBoolean").ToLocalChecked(), Nan::New(TxnWrap::getBoolean)); - txnTpl->PrototypeTemplate()->Set(Nan::New("putString").ToLocalChecked(), Nan::New(TxnWrap::putString)); - txnTpl->PrototypeTemplate()->Set(Nan::New("putBinary").ToLocalChecked(), Nan::New(TxnWrap::putBinary)); - txnTpl->PrototypeTemplate()->Set(Nan::New("putNumber").ToLocalChecked(), Nan::New(TxnWrap::putNumber)); - txnTpl->PrototypeTemplate()->Set(Nan::New("putBoolean").ToLocalChecked(), Nan::New(TxnWrap::putBoolean)); - txnTpl->PrototypeTemplate()->Set(Nan::New("del").ToLocalChecked(), Nan::New(TxnWrap::del)); - txnTpl->PrototypeTemplate()->Set(Nan::New("reset").ToLocalChecked(), Nan::New(TxnWrap::reset)); - txnTpl->PrototypeTemplate()->Set(Nan::New("renew").ToLocalChecked(), Nan::New(TxnWrap::renew)); + txnTpl->PrototypeTemplate()->Set(isolate, "commit", Nan::New(TxnWrap::commit)); + txnTpl->PrototypeTemplate()->Set(isolate, "abort", Nan::New(TxnWrap::abort)); + txnTpl->PrototypeTemplate()->Set(isolate, "getString", Nan::New(TxnWrap::getString)); + txnTpl->PrototypeTemplate()->Set(isolate, "getStringUnsafe", Nan::New(TxnWrap::getStringUnsafe)); + txnTpl->PrototypeTemplate()->Set(isolate, "getBinary", Nan::New(TxnWrap::getBinary)); + txnTpl->PrototypeTemplate()->Set(isolate, "getBinaryUnsafe", Nan::New(TxnWrap::getBinaryUnsafe)); + txnTpl->PrototypeTemplate()->Set(isolate, "getNumber", Nan::New(TxnWrap::getNumber)); + txnTpl->PrototypeTemplate()->Set(isolate, "getBoolean", Nan::New(TxnWrap::getBoolean)); + txnTpl->PrototypeTemplate()->Set(isolate, "putString", Nan::New(TxnWrap::putString)); + txnTpl->PrototypeTemplate()->Set(isolate, "putBinary", Nan::New(TxnWrap::putBinary)); + txnTpl->PrototypeTemplate()->Set(isolate, "putNumber", Nan::New(TxnWrap::putNumber)); + txnTpl->PrototypeTemplate()->Set(isolate, "putBoolean", Nan::New(TxnWrap::putBoolean)); + txnTpl->PrototypeTemplate()->Set(isolate, "del", Nan::New(TxnWrap::del)); + txnTpl->PrototypeTemplate()->Set(isolate, "reset", Nan::New(TxnWrap::reset)); + txnTpl->PrototypeTemplate()->Set(isolate, "renew", Nan::New(TxnWrap::renew)); // TODO: wrap mdb_cmp too // TODO: wrap mdb_dcmp too // TxnWrap: Get constructor - EnvWrap::txnCtor.Reset( txnTpl->GetFunction()); + EnvWrap::txnCtor.Reset( txnTpl->GetFunction(Nan::GetCurrentContext()).ToLocalChecked()); // DbiWrap: Prepare constructor template Local dbiTpl = Nan::New(DbiWrap::ctor); dbiTpl->SetClassName(Nan::New("Dbi").ToLocalChecked()); dbiTpl->InstanceTemplate()->SetInternalFieldCount(1); // DbiWrap: Add functions to the prototype - dbiTpl->PrototypeTemplate()->Set(Nan::New("close").ToLocalChecked(), Nan::New(DbiWrap::close)); - dbiTpl->PrototypeTemplate()->Set(Nan::New("drop").ToLocalChecked(), Nan::New(DbiWrap::drop)); - dbiTpl->PrototypeTemplate()->Set(Nan::New("stat").ToLocalChecked(), Nan::New(DbiWrap::stat)); + dbiTpl->PrototypeTemplate()->Set(isolate, "close", Nan::New(DbiWrap::close)); + dbiTpl->PrototypeTemplate()->Set(isolate, "drop", Nan::New(DbiWrap::drop)); + dbiTpl->PrototypeTemplate()->Set(isolate, "stat", Nan::New(DbiWrap::stat)); // TODO: wrap mdb_stat too // DbiWrap: Get constructor - EnvWrap::dbiCtor.Reset( dbiTpl->GetFunction()); + EnvWrap::dbiCtor.Reset( dbiTpl->GetFunction(Nan::GetCurrentContext()).ToLocalChecked()); // Set exports - exports->Set(Nan::New("Env").ToLocalChecked(), envTpl->GetFunction()); + exports->Set(Nan::GetCurrentContext(), Nan::New("Env").ToLocalChecked(), envTpl->GetFunction(Nan::GetCurrentContext()).ToLocalChecked()); } diff --git a/src/misc.cpp b/src/misc.cpp index 850eb6d82a..99f9c88b0b 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -25,24 +25,24 @@ #include #include -static thread_local uint32_t currentUint32Key = 0; - -void setupExportMisc(Handle exports) { +void setupExportMisc(Local exports) { Local versionObj = Nan::New(); int major, minor, patch; char *str = mdb_version(&major, &minor, &patch); - versionObj->Set(Nan::New("versionString").ToLocalChecked(), Nan::New(str).ToLocalChecked()); - versionObj->Set(Nan::New("major").ToLocalChecked(), Nan::New(major)); - versionObj->Set(Nan::New("minor").ToLocalChecked(), Nan::New(minor)); - versionObj->Set(Nan::New("patch").ToLocalChecked(), Nan::New(patch)); + Local context = Nan::GetCurrentContext(); + versionObj->Set(context, Nan::New("versionString").ToLocalChecked(), Nan::New(str).ToLocalChecked()); + versionObj->Set(context, Nan::New("major").ToLocalChecked(), Nan::New(major)); + versionObj->Set(context, Nan::New("minor").ToLocalChecked(), Nan::New(minor)); + versionObj->Set(context, Nan::New("patch").ToLocalChecked(), Nan::New(patch)); - exports->Set(Nan::New("version").ToLocalChecked(), versionObj); + exports->Set(context, Nan::New("version").ToLocalChecked(), versionObj); } void setFlagFromValue(int *flags, int flag, const char *name, bool defaultValue, Local options) { - Local opt = options->Get(Nan::New(name).ToLocalChecked()); - if (opt->IsBoolean() ? opt->BooleanValue() : defaultValue) { + Local context = Nan::GetCurrentContext(); + Local opt = options->Get(context, Nan::New(name).ToLocalChecked()).ToLocalChecked(); + if (opt->IsBoolean() ? opt->BooleanValue(context).ToChecked() : defaultValue) { *flags |= flag; } } @@ -56,7 +56,7 @@ NodeLmdbKeyType keyTypeFromOptions(const Local &val, NodeLmdbKeyType defa return NodeLmdbKeyType::InvalidKey; } - auto obj = val->ToObject(); + auto obj = Local::Cast(val); NodeLmdbKeyType keyType = defaultKeyType; int keyIsUint32 = 0; @@ -71,7 +71,6 @@ NodeLmdbKeyType keyTypeFromOptions(const Local &val, NodeLmdbKeyType defa if (keyIsUint32) { keyType = NodeLmdbKeyType::Uint32Key; - if (keyIsBuffer || keyIsString) { Nan::ThrowError(keySpecificationErrorText); return NodeLmdbKeyType::InvalidKey; @@ -137,7 +136,7 @@ argtokey_callback_t argToKey(const Local &val, MDB_val &key, NodeLmdbKeyT } isValid = true; - CustomExternalStringResource::writeTo(val->ToString(), &key); + CustomExternalStringResource::writeTo(Local::Cast(val), &key); return ([](MDB_val &key) -> void { delete[] (uint16_t*)key.mv_data; }); @@ -149,11 +148,14 @@ argtokey_callback_t argToKey(const Local &val, MDB_val &key, NodeLmdbKeyT } isValid = true; - currentUint32Key = val->Uint32Value(); + uint32_t* uint32Key = new uint32_t; + *uint32Key = val->Uint32Value(Nan::GetCurrentContext()).ToChecked(); key.mv_size = sizeof(uint32_t); - key.mv_data = ¤tUint32Key; + key.mv_data = uint32Key; - return nullptr; + return ([](MDB_val &key) -> void { + delete (uint32_t*)key.mv_data; + }); } else if (keyType == NodeLmdbKeyType::BinaryKey) { if (!node::Buffer::HasInstance(val)) { @@ -244,14 +246,14 @@ Local valToBoolean(MDB_val &data) { void throwLmdbError(int rc) { auto err = Nan::Error(mdb_strerror(rc)); - err.As()->Set(Nan::New("code").ToLocalChecked(), Nan::New(rc)); + err.As()->Set(Nan::GetCurrentContext(), Nan::New("code").ToLocalChecked(), Nan::New(rc)); return Nan::ThrowError(err); } void consoleLog(const char *msg) { Local str = Nan::New("console.log('").ToLocalChecked(); - str = String::Concat(str, Nan::New(msg).ToLocalChecked()); - str = String::Concat(str, Nan::New("');").ToLocalChecked()); + //str = String::Concat(str, Nan::New(msg).ToLocalChecked()); + //str = String::Concat(str, Nan::New("');").ToLocalChecked()); Local