Comparing version 2.3.0-beta2 to 2.3.0-beta3
@@ -0,20 +1,26 @@ | ||
import { env } from 'process'; | ||
import { WeakLRUCache, clearKeptObjects } from './external.js'; | ||
import { FAILED_CONDITION } from './write.js'; | ||
import { FAILED_CONDITION, ABORT } from './write.js'; | ||
import { when } from './util/when.js'; | ||
let getLastVersion; | ||
const mapGet = Map.prototype.get; | ||
export const CachingStore = Store => class extends Store { | ||
export const CachingStore = (Store, env) => { | ||
let childTxnChanges | ||
return class extends Store { | ||
constructor(dbName, options) { | ||
super(dbName, options); | ||
if (!this.env.cacheCommitter) { | ||
this.env.cacheCommitter = true; | ||
if (!env.cacheCommitter) { | ||
env.cacheCommitter = true; | ||
this.on('aftercommit', ({ next, last }) => { | ||
do { | ||
let store = next.store; | ||
let meta = next.meta; | ||
let store = meta && meta.store; | ||
if (store) { | ||
if (next.flag & FAILED_CONDITION) | ||
next.store.cache.delete(next.key); // just delete it from the map | ||
store.cache.delete(meta.key); // just delete it from the map | ||
else { | ||
let expirationPriority = next.valueSize >> 10; | ||
let cache = next.store.cache; | ||
let entry = mapGet.call(cache, next.key); | ||
let expirationPriority = meta.valueSize >> 10; | ||
let cache = store.cache; | ||
let entry = mapGet.call(cache, meta.key); | ||
if (entry) | ||
@@ -84,2 +90,4 @@ cache.used(entry, expirationPriority + 4); // this will enter it into the LRFU (with a little lower priority than a read) | ||
let entry = this.cache.setValue(id, value, !result || result.isSync ? 0 : -1); | ||
if (childTxnChanges) | ||
childTxnChanges.add(id); | ||
if (version !== undefined) | ||
@@ -95,2 +103,4 @@ entry.version = typeof version === 'object' ? version.version : version; | ||
let entry = this.cache.setValue(id, value); | ||
if (childTxnChanges) | ||
childTxnChanges.add(id); | ||
if (version !== undefined) { | ||
@@ -120,5 +130,28 @@ entry.version = typeof version === 'object' ? version.version : version; | ||
} | ||
childTransaction(execute) { | ||
throw new Error('Child transactions are not supported in caching stores'); | ||
childTransaction(callback) { | ||
return super.childTransaction(() => { | ||
let cache = this.cache; | ||
let previousChanges = childTxnChanges; | ||
try { | ||
childTxnChanges = new Set(); | ||
return when(callback(), (result) => { | ||
if (result === ABORT) | ||
return abort(); | ||
childTxnChanges = previousChanges; | ||
return result; | ||
}, abort); | ||
} catch(error) { | ||
abort(error); | ||
} | ||
function abort(error) { | ||
// if the transaction was aborted, remove all affected entries from cache | ||
for (let id of childTxnChanges) | ||
cache.delete(id); | ||
childTxnChanges = previousChanges; | ||
if (error) | ||
throw error; | ||
} | ||
}); | ||
} | ||
}; | ||
}; | ||
@@ -125,0 +158,0 @@ export function setGetLastVersion(get) { |
@@ -13,3 +13,3 @@ import { dirname, default as pathModule } from 'path'; | ||
let nativeAddon = require('node-gyp-build')(dirName); | ||
let nativeAddon = require('node-gyp-build-optional-packages')(dirName); | ||
@@ -16,0 +16,0 @@ let [ majorVersion, minorVersion ] = process.versions.node.split('.') |
@@ -169,4 +169,5 @@ declare namespace lmdb { | ||
* @param action The function to execute within the transaction | ||
* @params flags Additional flags specifying transaction behavior, this is optional and defaults to abortable, synchronous commits that are flushed to disk before returning | ||
**/ | ||
transactionSync<T>(action: () => T): T | ||
transactionSync<T>(action: () => T, flags?: TransactionFlags): T | ||
/** | ||
@@ -273,4 +274,6 @@ * Execute a transaction asyncronously, running all the actions within the action callback in the transaction, | ||
} | ||
/* A special value that can be returned from a transaction to indicate that the transaction should be aborted */ | ||
export const ABORT = 10000000000000 | ||
/* A constant that can be returned from a transaction to indicate that the transaction should be aborted */ | ||
export const ABORT: {}; | ||
/* A constant that can be used as a conditional versions for put and ifVersion to indicate that the write should conditional on the key/entry existing */ | ||
export const IF_EXISTS: number; | ||
class RootDatabase<V = any, K extends Key = Key> extends Database<V, K> { | ||
@@ -321,3 +324,2 @@ /** | ||
readOnly?: boolean | ||
mapAsync?: boolean | ||
maxReaders?: number | ||
@@ -360,2 +362,10 @@ } | ||
} | ||
export enum TransactionFlags { | ||
/* Indicates that the transaction needs to be abortable */ | ||
ABORTABLE = 1, | ||
/* Indicates that the transaction needs to be committed before returning */ | ||
SYNCHRONOUS_COMMIT = 2, | ||
/* Indicates that the function can return before the transaction has been flushed to disk */ | ||
NO_SYNC_FLUSH = 0x10000 | ||
} | ||
class ArrayLikeIterable<T> implements Iterable<T> { | ||
@@ -374,5 +384,7 @@ map<U>(callback: (entry: T) => U): ArrayLikeIterable<U> | ||
export function asBinary(buffer: Uint8Array): Binary | ||
/* Indicates if V8 accelerated functions are enabled. If this is false, some functions will be a little slower, and you may want to npm install --build-from-source to enable maximum performance */ | ||
/* Indicates if V8 accelerated functions are enabled. If this is false, some functions will be a little slower, and you may want to npm install --build-from-source to enable maximum performance */ | ||
export let v8AccelerationEnabled: boolean | ||
/* Return database augmented with methods to better conform to levelup */ | ||
export function levelup(database: Database): Database | ||
} | ||
export = lmdb |
17
index.js
@@ -20,12 +20,17 @@ import EventEmitter from 'events'; | ||
export { ABORT, IF_EXISTS, asBinary } from './write.js'; | ||
import { ABORT, IF_EXISTS, asBinary } from './write.js'; | ||
export { levelup } from './level.js'; | ||
import { levelup } from './level.js'; | ||
export { clearKeptObjects, v8AccelerationEnabled } from './external.js'; | ||
export { open, openAsClass, getLastVersion, setLastVersion, allDbs } from './open.js'; | ||
import { v8AccelerationEnabled } from './external.js'; | ||
export { open, openAsClass, getLastVersion, allDbs } from './open.js'; | ||
import { toBufferKey as keyValueToBuffer, compareKeys as compareKey, fromBufferKey as bufferToKeyValue } from 'ordered-binary'; | ||
import { open, getLastVersion } from './open.js'; | ||
export const ABORTABLE = 1; | ||
export const SYNCHRONOUS_COMMIT = 2; | ||
export const NO_SYNC_FLUSH = 0x10000; | ||
import { open, openAsClass, getLastVersion } from './open.js'; | ||
export const TransactionFlags = { | ||
ABORTABLE: 1, | ||
SYNCHRONOUS_COMMIT: 2, | ||
NO_SYNC_FLUSH: 0x10000, | ||
}; | ||
export default { | ||
open, getLastVersion, compareKey, keyValueToBuffer, bufferToKeyValue, EventEmitter | ||
open, openAsClass, getLastVersion, compareKey, keyValueToBuffer, bufferToKeyValue, ABORT, IF_EXISTS, asBinary, levelup, v8AccelerationEnabled, TransactionFlags | ||
}; |
@@ -239,3 +239,3 @@ import { Compression, getAddress, require, arch, fs, path as pathModule, lmdbError, EventEmitter, MsgpackrEncoder, Env, Dbi, tmpdir, os, native } from './external.js'; | ||
return dbOptions.cache ? | ||
new (CachingStore(LMDBStore))(dbName, dbOptions) : | ||
new (CachingStore(LMDBStore, env))(dbName, dbOptions) : | ||
new LMDBStore(dbName, dbOptions); | ||
@@ -352,3 +352,3 @@ } catch(error) { | ||
}; | ||
let Class = options.cache ? CachingStore(LMDBStore) : LMDBStore; | ||
let Class = options.cache ? CachingStore(LMDBStore, env) : LMDBStore; | ||
return options.asClass ? Class : new Class(options.name || null, options); | ||
@@ -355,0 +355,0 @@ } |
{ | ||
"name": "lmdb", | ||
"author": "Kris Zyp", | ||
"version": "2.3.0-beta2", | ||
"version": "2.3.0-beta3", | ||
"description": "Simple, efficient, scalable, high-performance LMDB interface", | ||
@@ -39,3 +39,2 @@ "license": "MIT", | ||
"/src", | ||
"/prebuilds", | ||
"*.md", | ||
@@ -52,3 +51,3 @@ "/*.js", | ||
"scripts": { | ||
"install": "node-gyp-build", | ||
"install": "node-gyp-build-optional-packages", | ||
"build": "node-gyp configure && node-gyp build && rollup -c", | ||
@@ -58,5 +57,6 @@ "rebuild": "node-gyp build && rollup -c", | ||
"prepare": "rollup -c", | ||
"before-publish": "rollup -c && prebuildify-ci download && prebuildify --target 16.14.0 && set ENABLE_V8_FUNCTIONS=false&& prebuildify --napi --target 16.14.0", | ||
"prebuild-libc": "prebuildify --napi --tag-libc --target 16.14.0", | ||
"prebuildify": "prebuildify", | ||
"before-publish": "rollup -c && prebuildify-ci download && prebuildify --target 16.14.0 && set ENABLE_V8_FUNCTIONS=false&& prebuildify --napi --optional-packages --target 16.14.0 && node util/set-optional-deps.cjs", | ||
"prebuild-libc": "prebuildify --napi --optional-packages --tag-libc --target 16.14.0", | ||
"prebuildify": "prebuildify --target 16.14.2", | ||
"publish-all": "cd prebuilds/win32-x64 && npm publish --otp %OTP% && cd ../darwin-x64 && npm publish --otp %OTP% && cd ../darwin-arm64 && npm publish --otp %OTP% && cd ../linux-x64 && npm publish --otp %OTP% && cd ../linux-arm64 && npm publish --otp %OTP% && cd ../linux-arm && npm publish --otp %OTP% && cd ../.. && npm publish --otp %OTP%", | ||
"recompile": "node-gyp clean && node-gyp configure && node-gyp build", | ||
@@ -74,3 +74,3 @@ "test": "mocha test/**.test.js --recursive && npm run test:types", | ||
"node-addon-api": "^4.3.0", | ||
"node-gyp-build": "^4.2.3", | ||
"node-gyp-build-optional-packages": "^4.3.1", | ||
"ordered-binary": "^1.2.4", | ||
@@ -85,3 +85,3 @@ "weak-lru-cache": "^1.2.2" | ||
"mocha": "^9.1.3", | ||
"prebuildify": "^5.0.0", | ||
"prebuildify": "kriszyp/prebuildify#optional-packages", | ||
"prebuildify-ci": "^1.0.5", | ||
@@ -98,3 +98,11 @@ "rimraf": "^3.0.2", | ||
"test": "tests" | ||
}, | ||
"optionalDependencies": { | ||
"lmdb-darwin-arm64": "2.3.0-beta3", | ||
"lmdb-darwin-x64": "2.3.0-beta3", | ||
"lmdb-linux-arm": "2.3.0-beta3", | ||
"lmdb-linux-arm64": "2.3.0-beta3", | ||
"lmdb-linux-x64": "2.3.0-beta3", | ||
"lmdb-win32-x64": "2.3.0-beta3" | ||
} | ||
} | ||
} |
@@ -28,3 +28,3 @@ [![license](https://img.shields.io/badge/license-MIT-brightgreen)](LICENSE) | ||
``` | ||
`lmdb-js` is based on the Node-API for maximum compatility across all supported Node versions and Deno. It also includes accelerated, high-speed functions for direct V8 interaction that are compiled for, and (automatically) loaded in Node v16. The standard Node-API based functions are used in all other versions and still provide excellent performance, but for absolute maximum performance on older versions of Node, you can use `npm install --build-from-source`. | ||
`lmdb-js` is based on the Node-API for maximum compatility across all supported Node versions and futue Deno versions. It also includes accelerated, high-speed functions for direct V8 interaction that are compiled for, and (automatically) loaded in Node v16. The standard Node-API based functions are used in all other versions and still provide excellent performance, but for absolute maximum performance on older versions of Node, you can use `npm install --build-from-source`. | ||
@@ -450,16 +450,2 @@ In Deno, this package could be directly used from the [deno.land `lmdb` module](https://deno.land/x/lmdb/mod.ts), but Node-API support is currently in-progress, so probably will require Deno v1.22+ (for older versions of Deno, you can use `lmdb-js` v2.2.x). | ||
## Deno | ||
This package is supported on Deno as of v2.1, but there are some requirements and limitations. First, lmdb-js requires FFI (which is currently marked `unstable`) and several permissions to be enabled, so you need to start deno with at least these flags: | ||
``` | ||
deno run --allow-ffi --unstable --allow-write --allow-read --allow-env --allow-net your-app.ts | ||
``` | ||
Deno [currently doesn't provide any infrastructure for binary builds](https://github.com/denoland/deno/issues/12943), so lmdb-js will attempt to download and save the appropriate binary file on its own. If you need to manually specify a path, you can do so with the `LMDB_LIB_PATH` env variable. | ||
You can then use the library with an import of the lmdb-js module from deno.land (insert the version you want): | ||
``` | ||
import { open } from 'https://deno.land/x/lmdb@<version>/mod.ts'; | ||
``` | ||
Also, lmdb-js doesn't support asynchronous transaction callbacks (`transaction` API), which can't be efficiently implemented without a resolution to this [issue](https://github.com/denoland/deno/issues/12946). You can still use the standard asynchronous put and remove methods, which will properly be committed asynchronously, as well as the synchronous transactions. | ||
## LevelUp | ||
@@ -466,0 +452,0 @@ |
91
write.js
@@ -15,2 +15,4 @@ import { getAddress, onExit, native } from './external.js'; | ||
const RESET_BUFFER_MODE = 1024; | ||
const NO_RESOLVE = 16; | ||
const HAS_TXN = 8; | ||
@@ -72,5 +74,8 @@ const SYNC_PROMISE_SUCCESS = Promise.resolve(true); | ||
dynamicBytes.uint32[0] = TXN_DELIMITER | TXN_COMMITTED | TXN_FLUSHED; | ||
var txnResolution, lastQueuedResolution, nextResolution = { uint32: dynamicBytes.uint32, flagPosition: 0, }; | ||
var uncommittedResolution = { next: nextResolution }; | ||
var txnResolution, lastQueuedResolution, nextResolution = { | ||
uint32: dynamicBytes.uint32, flagPosition: 0, flag: 0, valueBuffer: null, next: null, meta: null }; | ||
var uncommittedResolution = { | ||
uint32: null, flagPosition: 0, flag: 0, valueBuffer: null, next: nextResolution, meta: null }; | ||
var unwrittenResolution = nextResolution; | ||
var lastPromisedResolution = uncommittedResolution; | ||
function writeInstructions(flags, store, key, value, version, ifVersion) { | ||
@@ -228,19 +233,9 @@ let writeStatus; | ||
// create the placeholder next resolution | ||
nextResolution = resolution.next = store.cache ? | ||
{ | ||
nextResolution = resolution.next = { // we try keep resolutions exactly the same object type | ||
uint32: nextUint32, | ||
flagPosition: position << 1, | ||
flag: 0, // TODO: eventually eliminate this, as we can probably signify success by zeroing the flagPosition | ||
flag: 0, // TODO: eventually eliminate this, as we can probably signify HAS_TXN/NO_RESOLVE/FAILED_CONDITION in upper bits | ||
valueBuffer: fixedBuffer, // these are all just placeholders so that we have the right hidden class initially allocated | ||
next: null, | ||
key, | ||
store, | ||
valueSize, | ||
} : | ||
{ | ||
uint32: nextUint32, | ||
flagPosition: position << 1, | ||
flag: 0, // TODO: eventually eliminate this, as we can probably signify success by zeroing the flagPosition | ||
valueBuffer: fixedBuffer, // these are all just placeholders so that we have the right hidden class initially allocated | ||
next: null, | ||
meta: null, | ||
}; | ||
@@ -302,5 +297,7 @@ let writtenBatchDepth = batchDepth; | ||
if (store.cache) { | ||
resolution.key = key; | ||
resolution.store = store; | ||
resolution.valueSize = valueBuffer ? valueBuffer.length : 0; | ||
resolution.meta = { | ||
key, | ||
store, | ||
valueSize: valueBuffer ? valueBuffer.length : 0, | ||
}; | ||
} | ||
@@ -314,4 +311,5 @@ resolution.valueBuffer = valueBuffer; | ||
else { | ||
resolution.reject = callback; | ||
resolution.resolve = (value) => callback(null, value); | ||
let meta = resolution.meta || (resolution.meta = {}); | ||
meta.reject = callback; | ||
meta.resolve = (value) => callback(null, value); | ||
return; | ||
@@ -321,9 +319,16 @@ } | ||
if (ifVersion === undefined) { | ||
if (writtenBatchDepth > 1) | ||
if (writtenBatchDepth > 1) { | ||
if (!resolution.flag && !store.cache) | ||
resolution.flag = NO_RESOLVE; | ||
return PROMISE_SUCCESS; // or return undefined? | ||
if (!commitPromise) { | ||
} | ||
if (commitPromise) { | ||
if (!resolution.flag) | ||
resolution.flag = NO_RESOLVE; | ||
} else { | ||
commitPromise = new Promise((resolve, reject) => { | ||
resolution.resolve = resolve; | ||
let meta = resolution.meta || (resolution.meta = {}); | ||
meta.resolve = resolve; | ||
resolve.unconditional = true; | ||
resolution.reject = reject; | ||
meta.reject = reject; | ||
}); | ||
@@ -336,4 +341,5 @@ if (separateFlushed) | ||
lastWritePromise = new Promise((resolve, reject) => { | ||
resolution.resolve = resolve; | ||
resolution.reject = reject; | ||
let meta = resolution.meta || (resolution.meta = {}); | ||
meta.resolve = resolve; | ||
meta.reject = reject; | ||
}); | ||
@@ -398,4 +404,4 @@ if (separateFlushed) | ||
function queueCommitResolution(resolution) { | ||
if (!resolution.isTxn) { | ||
resolution.isTxn = true; | ||
if (!(resolution.flag & HAS_TXN)) { | ||
resolution.flag = HAS_TXN; | ||
if (txnResolution) { | ||
@@ -419,7 +425,14 @@ txnResolution.nextTxn = resolution; | ||
} | ||
if (!unwrittenResolution.isTxn) | ||
outstandingWriteCount--; | ||
if (unwrittenResolution.flag !== HAS_TXN) { | ||
if (unwrittenResolution.flag === NO_RESOLVE && !unwrittenResolution.store) { | ||
// in this case we can completely remove from the linked list, clearing more memory | ||
lastPromisedResolution.next = unwrittenResolution = unwrittenResolution.next; | ||
continue; | ||
} | ||
unwrittenResolution.uint32 = null; | ||
} | ||
unwrittenResolution.valueBuffer = null; | ||
unwrittenResolution.flag = instructionStatus; | ||
outstandingWriteCount--; | ||
lastPromisedResolution = unwrittenResolution; | ||
unwrittenResolution = unwrittenResolution.next; | ||
@@ -443,4 +456,4 @@ } | ||
do { | ||
if (uncommittedResolution.resolve) { | ||
let resolve = uncommittedResolution.resolve; | ||
if (uncommittedResolution.meta && uncommittedResolution.meta.resolve) { | ||
let resolve = uncommittedResolution.meta.resolve; | ||
if (uncommittedResolution.flag & FAILED_CONDITION && !resolve.unconditional) | ||
@@ -463,7 +476,7 @@ resolve(false); | ||
do { | ||
if (uncommittedResolution.reject) { | ||
if (uncommittedResolution.meta && uncommittedResolution.meta.reject) { | ||
let flag = uncommittedResolution.flag & 0xf; | ||
let error = new Error("Commit failed (see commitError for details)"); | ||
error.commitError = commitRejectPromise; | ||
uncommittedResolution.reject(error); | ||
uncommittedResolution.meta.reject(error); | ||
} | ||
@@ -708,4 +721,6 @@ } while((uncommittedResolution = uncommittedResolution.next) && uncommittedResolution != txnResolution) | ||
env.beginTxn(1); // abortable | ||
let callbackDone; | ||
try { | ||
return when(callback(), (result) => { | ||
callbackDone = true; | ||
if (result === ABORT) | ||
@@ -723,3 +738,4 @@ env.abortTxn(); | ||
} catch(error) { | ||
env.abortTxn(); | ||
if (!callbackDone) | ||
env.abortTxn(); | ||
clearWriteTxn(parentTxn); | ||
@@ -762,2 +778,3 @@ throw error; | ||
} | ||
let callbackDone; | ||
try { | ||
@@ -770,2 +787,3 @@ this.transactions++; | ||
try { | ||
callbackDone = true; | ||
if (result === ABORT) | ||
@@ -789,3 +807,4 @@ env.abortTxn(); | ||
} catch(error) { | ||
try { env.abortTxn(); } catch(e) {} | ||
if (!callbackDone) | ||
try { env.abortTxn(); } catch(e) {} | ||
clearWriteTxn(null); | ||
@@ -792,0 +811,0 @@ throw error; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 8 instances in 1 package
5322
1
2300564
12
120
502
11
+ Addedlmdb-darwin-arm64@2.3.0-beta3(transitive)
+ Addedlmdb-darwin-x64@2.3.0-beta3(transitive)
+ Addedlmdb-linux-arm@2.3.0-beta3(transitive)
+ Addedlmdb-linux-arm64@2.3.0-beta3(transitive)
+ Addedlmdb-linux-x64@2.3.0-beta3(transitive)
+ Addedlmdb-win32-x64@2.3.0-beta3(transitive)
+ Addednode-gyp-build-optional-packages@4.3.5(transitive)
- Removednode-gyp-build@^4.2.3
- Removednode-gyp-build@4.8.4(transitive)