Son CV dans un terminal web en Javascript!
https://terminal-cv.gregandev.fr
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1790 lines
57 KiB
1790 lines
57 KiB
'use strict';
|
|
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
|
|
|
var fs = _interopDefault(require('fs'));
|
|
var path = require('path');
|
|
var EventEmitter = _interopDefault(require('events'));
|
|
var module$1 = require('module');
|
|
var url = require('url');
|
|
var index_js = require('weak-lru-cache/index.js');
|
|
var index_js$1 = require('ordered-binary/index.js');
|
|
var os = _interopDefault(require('os'));
|
|
|
|
const require$1 = module$1.createRequire((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
let nativeMethods, dirName = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.cjs', document.baseURI).href)))).replace(/dist$/, '');
|
|
try {
|
|
console.log(dirName);
|
|
nativeMethods = require$1('node-gyp-build')(dirName);
|
|
if (process.versions.modules == 93)
|
|
require$1('v8').setFlagsFromString('--turbo-fast-api-calls');
|
|
} catch(error) {
|
|
if (process.versions.modules == 93) {
|
|
// use this abi version as the backup version without turbo-fast-api-calls enabled
|
|
Object.defineProperty(process.versions, 'modules', { value: '92' });
|
|
try {
|
|
nativeMethods = require$1('node-gyp-build')(dirName);
|
|
} catch(secondError) {
|
|
throw error
|
|
} finally {
|
|
Object.defineProperty(process.versions, 'modules', { value: '93' });
|
|
}
|
|
} else
|
|
throw error
|
|
}
|
|
const { Env, Cursor, Compression, getBufferForAddress, getAddress } = nativeMethods;
|
|
|
|
let getLastVersion;
|
|
const mapGet = Map.prototype.get;
|
|
const CachingStore = Store => class extends Store {
|
|
constructor(dbName, options) {
|
|
super(dbName, options);
|
|
if (!this.env.cacheCommitter) {
|
|
this.env.cacheCommitter = true;
|
|
this.on('aftercommit', ({ next, last }) => {
|
|
do {
|
|
let store = next.store;
|
|
if (store) {
|
|
if (next.flag & 1)
|
|
next.store.cache.delete(next.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);
|
|
if (entry)
|
|
cache.used(entry, expirationPriority); // this will enter it into the LRFU
|
|
}
|
|
}
|
|
} while (next != last && (next = next.next))
|
|
});
|
|
}
|
|
this.db.cachingDb = this;
|
|
this.cache = new index_js.WeakLRUCache(options.cache);
|
|
}
|
|
get(id, cacheMode) {
|
|
let value = this.cache.getValue(id);
|
|
if (value !== undefined)
|
|
return value
|
|
value = super.get(id);
|
|
if (value && typeof value === 'object' && !cacheMode && typeof id !== 'object') {
|
|
let entry = this.cache.setValue(id, value, this.lastSize >> 10);
|
|
if (this.useVersions) {
|
|
entry.version = getLastVersion();
|
|
}
|
|
}
|
|
return value
|
|
}
|
|
getEntry(id, cacheMode) {
|
|
let entry = this.cache.get(id);
|
|
if (entry)
|
|
return entry
|
|
let value = super.get(id);
|
|
if (value === undefined)
|
|
return
|
|
if (value && typeof value === 'object' && !cacheMode && typeof id !== 'object') {
|
|
entry = this.cache.setValue(id, value, this.lastSize >> 10);
|
|
} else {
|
|
entry = { value };
|
|
}
|
|
if (this.useVersions) {
|
|
entry.version = getLastVersion();
|
|
}
|
|
return entry
|
|
}
|
|
putEntry(id, entry, ifVersion) {
|
|
let result = super.put(id, entry.value, entry.version, ifVersion);
|
|
if (typeof id === 'object')
|
|
return result
|
|
if (result && result.then)
|
|
this.cache.setManually(id, entry); // set manually so we can keep it pinned in memory until it is committed
|
|
else // sync operation, immediately add to cache
|
|
this.cache.set(id, entry);
|
|
}
|
|
put(id, value, version, ifVersion) {
|
|
// if (this.cache.get(id)) // if there is a cache entry, remove it from scheduledEntries and
|
|
let result = super.put(id, value, version, ifVersion);
|
|
if (typeof id !== 'object') {
|
|
// sync operation, immediately add to cache, otherwise keep it pinned in memory until it is committed
|
|
let entry = this.cache.setValue(id, value, result.isSync ? 0 : -1);
|
|
if (version !== undefined)
|
|
entry.version = typeof version === 'object' ? version.version : version;
|
|
}
|
|
return result
|
|
}
|
|
putSync(id, value, version, ifVersion) {
|
|
if (id !== 'object') {
|
|
// sync operation, immediately add to cache, otherwise keep it pinned in memory until it is committed
|
|
if (value && typeof value === 'object') {
|
|
let entry = this.cache.setValue(id, value);
|
|
if (version !== undefined) {
|
|
entry.version = typeof version === 'object' ? version.version : version;
|
|
}
|
|
} else // it is possible that a value used to exist here
|
|
this.cache.delete(id);
|
|
}
|
|
return super.putSync(id, value, version, ifVersion)
|
|
}
|
|
remove(id, ifVersion) {
|
|
this.cache.delete(id);
|
|
return super.remove(id, ifVersion)
|
|
}
|
|
removeSync(id, ifVersion) {
|
|
this.cache.delete(id);
|
|
return super.removeSync(id, ifVersion)
|
|
}
|
|
clear() {
|
|
this.cache.clear();
|
|
super.clear();
|
|
}
|
|
childTransaction(execute) {
|
|
throw new Error('Child transactions are not supported in caching stores')
|
|
}
|
|
};
|
|
function setGetLastVersion(get) {
|
|
getLastVersion = get;
|
|
}
|
|
|
|
const SKIP = {};
|
|
if (!Symbol.asyncIterator) {
|
|
Symbol.asyncIterator = Symbol.for('Symbol.asyncIterator');
|
|
}
|
|
|
|
class ArrayLikeIterable {
|
|
constructor(sourceArray) {
|
|
if (sourceArray) {
|
|
this[Symbol.iterator] = sourceArray[Symbol.iterator].bind(sourceArray);
|
|
}
|
|
}
|
|
map(func) {
|
|
let source = this;
|
|
let result = new ArrayLikeIterable();
|
|
result[Symbol.iterator] = (async) => {
|
|
let iterator = source[Symbol.iterator](async);
|
|
return {
|
|
next(resolvedResult) {
|
|
let result;
|
|
do {
|
|
let iteratorResult;
|
|
if (resolvedResult) {
|
|
iteratorResult = resolvedResult;
|
|
resolvedResult = null; // don't go in this branch on next iteration
|
|
} else {
|
|
iteratorResult = iterator.next();
|
|
if (iteratorResult.then) {
|
|
return iteratorResult.then(iteratorResult => this.next(iteratorResult))
|
|
}
|
|
}
|
|
if (iteratorResult.done === true) {
|
|
this.done = true;
|
|
return iteratorResult
|
|
}
|
|
result = func(iteratorResult.value);
|
|
if (result && result.then) {
|
|
return result.then(result =>
|
|
result == SKIP ?
|
|
this.next() :
|
|
{
|
|
value: result
|
|
})
|
|
}
|
|
} while(result == SKIP)
|
|
return {
|
|
value: result
|
|
}
|
|
},
|
|
return() {
|
|
return iterator.return()
|
|
},
|
|
throw() {
|
|
return iterator.throw()
|
|
}
|
|
}
|
|
};
|
|
return result
|
|
}
|
|
[Symbol.asyncIterator]() {
|
|
return this[Symbol.iterator](true)
|
|
}
|
|
filter(func) {
|
|
return this.map(element => func(element) ? element : SKIP)
|
|
}
|
|
|
|
forEach(callback) {
|
|
let iterator = this[Symbol.iterator]();
|
|
let result;
|
|
while ((result = iterator.next()).done !== true) {
|
|
callback(result.value);
|
|
}
|
|
}
|
|
concat(secondIterable) {
|
|
let concatIterable = new ArrayLikeIterable();
|
|
concatIterable[Symbol.iterator] = (async) => {
|
|
let iterator = this[Symbol.iterator]();
|
|
let isFirst = true;
|
|
let concatIterator = {
|
|
next() {
|
|
let result = iterator.next();
|
|
if (isFirst && result.done) {
|
|
isFirst = false;
|
|
iterator = secondIterable[Symbol.iterator](async);
|
|
return iterator.next()
|
|
}
|
|
return result
|
|
},
|
|
return() {
|
|
return iterator.return()
|
|
},
|
|
throw() {
|
|
return iterator.throw()
|
|
}
|
|
};
|
|
return concatIterator
|
|
};
|
|
return concatIterable
|
|
}
|
|
toJSON() {
|
|
if (this.asArray && this.asArray.forEach) {
|
|
return this.asArray
|
|
}
|
|
throw new Error('Can not serialize async iteratables without first calling resolveJSON')
|
|
//return Array.from(this)
|
|
}
|
|
get asArray() {
|
|
if (this._asArray)
|
|
return this._asArray
|
|
let promise = new Promise((resolve, reject) => {
|
|
let iterator = this[Symbol.iterator](true);
|
|
let array = [];
|
|
let iterable = this;
|
|
function next(result) {
|
|
while (result.done !== true) {
|
|
if (result.then) {
|
|
return result.then(next)
|
|
} else {
|
|
array.push(result.value);
|
|
}
|
|
result = iterator.next();
|
|
}
|
|
array.iterable = iterable;
|
|
resolve(iterable._asArray = array);
|
|
}
|
|
next(iterator.next());
|
|
});
|
|
promise.iterable = this;
|
|
return this._asArray || (this._asArray = promise)
|
|
}
|
|
resolveData() {
|
|
return this.asArray
|
|
}
|
|
}
|
|
|
|
index_js$1.enableNullTermination();
|
|
|
|
const writeUint32Key = (key, target, start) => {
|
|
(target.dataView || (target.dataView = new DataView(target.buffer, 0, target.length))).setUint32(start, key, true);
|
|
return start + 4
|
|
};
|
|
const readUint32Key = (target, start) => {
|
|
return (target.dataView || (target.dataView = new DataView(target.buffer, 0, target.length))).getUint32(start, true)
|
|
};
|
|
const writeBufferKey = (key, target, start) => {
|
|
if (key.length > 1978)
|
|
throw new Error('Key buffer is too long')
|
|
target.set(key, start);
|
|
return key.length + start
|
|
};
|
|
const readBufferKey = (target, start, end) => {
|
|
return Uint8ArraySlice.call(target, start, end)
|
|
};
|
|
|
|
function applyKeyHandling(store) {
|
|
if (store.encoding == 'ordered-binary') {
|
|
store.encoder = store.decoder = {
|
|
encode(value) {
|
|
if (savePosition > 6200)
|
|
allocateSaveBuffer();
|
|
let start = savePosition;
|
|
savePosition = index_js$1.writeKey(value, saveBuffer, start);
|
|
let buffer = saveBuffer.subarray(start, savePosition);
|
|
savePosition = (savePosition + 7) & 0xfffff8;
|
|
return buffer
|
|
},
|
|
decode(buffer, end) { return index_js$1.readKey(buffer, 0, end) },
|
|
writeKey: index_js$1.writeKey,
|
|
readKey: index_js$1.readKey,
|
|
};
|
|
}
|
|
if (store.keyIsUint32) {
|
|
store.writeKey = writeUint32Key;
|
|
store.readKey = readUint32Key;
|
|
} else if (store.keyIsBuffer) {
|
|
store.writeKey = writeBufferKey;
|
|
store.readKey = readBufferKey;
|
|
} else if (store.keyEncoder) {
|
|
store.writeKey = store.keyEncoder.writeKey;
|
|
store.readKey = store.keyEncoder.readKey;
|
|
} else {
|
|
store.writeKey = index_js$1.writeKey;
|
|
store.readKey = index_js$1.readKey;
|
|
}
|
|
}
|
|
|
|
let saveBuffer, saveDataView, saveDataAddress;
|
|
let savePosition = 8000;
|
|
function allocateSaveBuffer() {
|
|
saveBuffer = Buffer.alloc(8192);
|
|
saveBuffer.dataView = saveDataView = new DataView(saveBuffer.buffer, saveBuffer.byteOffset, saveBuffer.byteLength);
|
|
saveBuffer.buffer.address = getAddress(saveBuffer.buffer);
|
|
saveDataAddress = saveBuffer.buffer.address + saveBuffer.byteOffset;
|
|
savePosition = 0;
|
|
|
|
}
|
|
function saveKey(key, writeKey, saveTo) {
|
|
if (savePosition > 6200) {
|
|
allocateSaveBuffer();
|
|
}
|
|
let start = savePosition;
|
|
savePosition = writeKey(key, saveBuffer, start + 4);
|
|
saveDataView.setUint32(start, savePosition - start - 4, true);
|
|
saveTo.saveBuffer = saveBuffer;
|
|
savePosition = (savePosition + 7) & 0xfffff8;
|
|
return start + saveDataAddress
|
|
}
|
|
|
|
const ITERATOR_DONE = { done: true, value: undefined };
|
|
|
|
function addQueryMethods(LMDBStore, {
|
|
getReadTxn, env, keyBytes, keyBytesView, getLastVersion
|
|
}) {
|
|
let renewId = 1;
|
|
LMDBStore.onReadReset = () => renewId++;
|
|
Object.assign(LMDBStore.prototype, {
|
|
getValues(key, options) {
|
|
let defaultOptions = {
|
|
key,
|
|
valuesForKey: true
|
|
};
|
|
if (options && options.snapshot === false)
|
|
throw new Error('Can not disable snapshots for getValues')
|
|
return this.getRange(options ? Object.assign(defaultOptions, options) : defaultOptions)
|
|
},
|
|
getKeys(options) {
|
|
if (!options)
|
|
options = {};
|
|
options.values = false;
|
|
return this.getRange(options)
|
|
},
|
|
getCount(options) {
|
|
if (!options)
|
|
options = {};
|
|
options.onlyCount = true;
|
|
return this.getRange(options)[Symbol.iterator]()
|
|
},
|
|
getKeysCount(options) {
|
|
if (!options)
|
|
options = {};
|
|
options.onlyCount = true;
|
|
options.values = false;
|
|
return this.getRange(options)[Symbol.iterator]()
|
|
},
|
|
getValuesCount(key, options) {
|
|
if (!options)
|
|
options = {};
|
|
options.key = key;
|
|
options.valuesForKey = true;
|
|
options.onlyCount = true;
|
|
return this.getRange(options)[Symbol.iterator]()
|
|
},
|
|
getRange(options) {
|
|
let iterable = new ArrayLikeIterable();
|
|
if (!options)
|
|
options = {};
|
|
let includeValues = options.values !== false;
|
|
let includeVersions = options.versions;
|
|
let valuesForKey = options.valuesForKey;
|
|
let limit = options.limit;
|
|
let db = this.db;
|
|
let snapshot = options.snapshot;
|
|
iterable[Symbol.iterator] = () => {
|
|
let currentKey = valuesForKey ? options.key : options.start;
|
|
const reverse = options.reverse;
|
|
let count = 0;
|
|
let cursor, cursorRenewId;
|
|
let txn;
|
|
let flags = (includeValues ? 0x100 : 0) | (reverse ? 0x400 : 0) |
|
|
(valuesForKey ? 0x800 : 0) | (options.exactMatch ? 0x4000 : 0);
|
|
function resetCursor() {
|
|
try {
|
|
if (cursor)
|
|
finishCursor();
|
|
let writeTxn = env.writeTxn;
|
|
txn = writeTxn || getReadTxn();
|
|
cursor = !writeTxn && db.availableCursor;
|
|
if (cursor) {
|
|
db.availableCursor = null;
|
|
if (db.cursorTxn != txn)
|
|
cursor.renew();
|
|
else// if (db.currentRenewId != renewId)
|
|
flags |= 0x2000;
|
|
} else {
|
|
cursor = new Cursor(db);
|
|
}
|
|
txn.cursorCount = (txn.cursorCount || 0) + 1; // track transaction so we always use the same one
|
|
if (snapshot === false) {
|
|
cursorRenewId = renewId; // use shared read transaction
|
|
txn.renewingCursorCount = (txn.renewingCursorCount || 0) + 1; // need to know how many are renewing cursors
|
|
}
|
|
} catch(error) {
|
|
if (cursor) {
|
|
try {
|
|
cursor.close();
|
|
} catch(error) { }
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
resetCursor();
|
|
let store = this;
|
|
if (options.onlyCount) {
|
|
flags |= 0x1000;
|
|
let count = position(options.offset);
|
|
finishCursor();
|
|
return count
|
|
}
|
|
function position(offset) {
|
|
let keySize = store.writeKey(currentKey, keyBytes, 0);
|
|
let endAddress;
|
|
if (valuesForKey) {
|
|
if (options.start === undefined && options.end === undefined)
|
|
endAddress = 0;
|
|
else {
|
|
let startAddress;
|
|
if (store.encoder.writeKey) {
|
|
startAddress = saveKey(options.start, store.encoder.writeKey, iterable);
|
|
keyBytesView.setFloat64(2000, startAddress, true);
|
|
endAddress = saveKey(options.end, store.encoder.writeKey, iterable);
|
|
} else if ((!options.start || options.start instanceof Uint8Array) && (!options.end || options.end instanceof Uint8Array)) {
|
|
startAddress = saveKey(options.start, index_js$1.writeKey, iterable);
|
|
keyBytesView.setFloat64(2000, startAddress, true);
|
|
endAddress = saveKey(options.end, index_js$1.writeKey, iterable);
|
|
} else {
|
|
throw new Error('Only key-based encoding is supported for start/end values')
|
|
}
|
|
}
|
|
} else
|
|
endAddress = saveKey(options.end, store.writeKey, iterable);
|
|
return cursor.position(flags, offset || 0, keySize, endAddress)
|
|
}
|
|
|
|
function finishCursor() {
|
|
if (txn.isAborted)
|
|
return
|
|
if (cursorRenewId)
|
|
txn.renewingCursorCount--;
|
|
if (--txn.cursorCount <= 0 && txn.onlyCursor) {
|
|
cursor.close();
|
|
txn.abort(); // this is no longer main read txn, abort it now that we are done
|
|
txn.isAborted = true;
|
|
} else {
|
|
if (db.availableCursor || txn != getReadTxn())
|
|
cursor.close();
|
|
else { // try to reuse it
|
|
db.availableCursor = cursor;
|
|
db.cursorTxn = txn;
|
|
}
|
|
}
|
|
}
|
|
return {
|
|
next() {
|
|
let keySize, lastSize;
|
|
if (cursorRenewId && cursorRenewId != renewId) {
|
|
resetCursor();
|
|
keySize = position(0);
|
|
}
|
|
if (count === 0) { // && includeValues) // on first entry, get current value if we need to
|
|
keySize = position(options.offset);
|
|
} else
|
|
keySize = cursor.iterate();
|
|
if (keySize === 0 ||
|
|
(count++ >= limit)) {
|
|
finishCursor();
|
|
return ITERATOR_DONE
|
|
}
|
|
if (!valuesForKey || snapshot === false)
|
|
currentKey = store.readKey(keyBytes, 32, keySize + 32);
|
|
if (includeValues) {
|
|
let value;
|
|
lastSize = keyBytesView.getUint32(0, true);
|
|
if (store.decoder) {
|
|
value = store.decoder.decode(db.unsafeBuffer, lastSize);
|
|
} else if (store.encoding == 'binary')
|
|
value = Uint8ArraySlice.call(db.unsafeBuffer, 0, lastSize);
|
|
else {
|
|
value = store.db.unsafeBuffer.toString('utf8', 0, lastSize);
|
|
if (store.encoding == 'json' && value)
|
|
value = JSON.parse(value);
|
|
}
|
|
if (includeVersions)
|
|
return {
|
|
value: {
|
|
key: currentKey,
|
|
value,
|
|
version: getLastVersion()
|
|
}
|
|
}
|
|
else if (valuesForKey)
|
|
return {
|
|
value
|
|
}
|
|
else
|
|
return {
|
|
value: {
|
|
key: currentKey,
|
|
value,
|
|
}
|
|
}
|
|
} else if (includeVersions) {
|
|
return {
|
|
value: {
|
|
key: currentKey,
|
|
version: getLastVersion()
|
|
}
|
|
}
|
|
} else {
|
|
return {
|
|
value: currentKey
|
|
}
|
|
}
|
|
},
|
|
return() {
|
|
finishCursor();
|
|
return ITERATOR_DONE
|
|
},
|
|
throw() {
|
|
finishCursor();
|
|
return ITERATOR_DONE
|
|
}
|
|
}
|
|
};
|
|
return iterable
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
function when(promise, callback, errback) {
|
|
if (promise && promise.then) {
|
|
return errback ?
|
|
promise.then(callback, errback) :
|
|
promise.then(callback)
|
|
}
|
|
return callback(promise)
|
|
}
|
|
|
|
var backpressureArray;
|
|
|
|
const MAX_KEY_SIZE = 1978;
|
|
const WAITING_OPERATION = 0x400000;
|
|
const BACKPRESSURE_THRESHOLD = 5000000;
|
|
const TXN_DELIMITER = 0x20000000;
|
|
const TXN_COMMITTED = 0x40000000;
|
|
const FAILED_CONDITION = 0x200000;
|
|
|
|
const SYNC_PROMISE_SUCCESS = Promise.resolve(true);
|
|
const SYNC_PROMISE_FAIL = Promise.resolve(false);
|
|
const ABORT = {};
|
|
const CALLBACK_THREW = {};
|
|
SYNC_PROMISE_SUCCESS.isSync = true;
|
|
SYNC_PROMISE_FAIL.isSync = true;
|
|
function addWriteMethods(LMDBStore, { env, fixedBuffer, resetReadTxn, useWritemap, eventTurnBatching, txnStartThreshold, batchStartThreshold, commitDelay }) {
|
|
// stands for write instructions
|
|
var dynamicBytes;
|
|
function allocateInstructionBuffer() {
|
|
dynamicBytes = Buffer.allocUnsafeSlow(0x10000);
|
|
dynamicBytes.uint32 = new Uint32Array(dynamicBytes.buffer, 0, 0x10000 >> 2);
|
|
dynamicBytes.uint32[0] = 0;
|
|
dynamicBytes.float64 = new Float64Array(dynamicBytes.buffer, 0, 0x10000 >> 3);
|
|
dynamicBytes.buffer.address = getAddress(dynamicBytes.buffer);
|
|
dynamicBytes.address = dynamicBytes.buffer.address + dynamicBytes.byteOffset;
|
|
dynamicBytes.position = 0;
|
|
return dynamicBytes
|
|
}
|
|
var outstandingWriteCount = 0;
|
|
var startAddress = 0;
|
|
var writeTxn = null;
|
|
var abortedNonChildTransactionWarn;
|
|
var nextTxnCallbacks = [];
|
|
var commitPromise;
|
|
commitDelay = commitDelay || 0;
|
|
eventTurnBatching = eventTurnBatching === false ? false : true;
|
|
var enqueuedCommit;
|
|
var afterCommitCallbacks = [];
|
|
var beforeCommitCallbacks = [];
|
|
var enqueuedEventTurnBatch;
|
|
var batchDepth = 0;
|
|
var writeBatchStart, outstandingBatchCount;
|
|
txnStartThreshold = txnStartThreshold || 5;
|
|
batchStartThreshold = batchStartThreshold || 1000;
|
|
|
|
allocateInstructionBuffer();
|
|
dynamicBytes.uint32[0] = TXN_DELIMITER | TXN_COMMITTED;
|
|
var txnResolution, lastQueuedResolution, nextResolution = { uint32: dynamicBytes.uint32, flagPosition: 0, };
|
|
var uncommittedResolution = { next: nextResolution };
|
|
var unwrittenResolution = nextResolution;
|
|
function writeInstructions(flags, store, key, value, version, ifVersion) {
|
|
let writeStatus;
|
|
let targetBytes, position;
|
|
let valueBuffer;
|
|
if (flags & 2) {
|
|
// encode first in case we have to write a shared structure
|
|
if (store.encoder) {
|
|
//if (!(value instanceof Uint8Array)) TODO: in a future version, directly store buffers that are provided
|
|
valueBuffer = store.encoder.encode(value);
|
|
if (typeof valueBuffer == 'string')
|
|
valueBuffer = Buffer.from(valueBuffer); // TODO: Would be nice to write strings inline in the instructions
|
|
} else if (typeof value == 'string') {
|
|
valueBuffer = Buffer.from(value); // TODO: Would be nice to write strings inline in the instructions
|
|
} else if (value instanceof Uint8Array)
|
|
valueBuffer = value;
|
|
else
|
|
throw new Error('Invalid value to put in database ' + value + ' (' + (typeof value) +'), consider using encoder')
|
|
}
|
|
if (writeTxn) {
|
|
targetBytes = fixedBuffer;
|
|
position = 0;
|
|
} else {
|
|
if (eventTurnBatching && !enqueuedEventTurnBatch && batchDepth == 0) {
|
|
enqueuedEventTurnBatch = setImmediate(() => {
|
|
try {
|
|
for (let i = 0, l = beforeCommitCallbacks.length; i < l; i++) {
|
|
beforeCommitCallbacks[i]();
|
|
}
|
|
} catch(error) {
|
|
console.error(error);
|
|
}
|
|
enqueuedEventTurnBatch = null;
|
|
finishBatch();
|
|
batchDepth--;
|
|
if (writeBatchStart)
|
|
writeBatchStart(); // TODO: When we support delay start of batch, optionally don't delay this
|
|
});
|
|
commitPromise = null; // reset the commit promise, can't know if it is really a new transaction prior to finishWrite being called
|
|
writeBatchStart = writeInstructions(1, store);
|
|
outstandingBatchCount = 0;
|
|
batchDepth++;
|
|
}
|
|
targetBytes = dynamicBytes;
|
|
position = targetBytes.position;
|
|
}
|
|
let uint32 = targetBytes.uint32, float64 = targetBytes.float64;
|
|
let flagPosition = position << 1; // flagPosition is the 32-bit word starting position
|
|
|
|
// don't increment Position until we are sure we don't have any key writing errors
|
|
uint32[flagPosition + 1] = store.db.dbi;
|
|
if (flags & 4) {
|
|
let keyStartPosition = (position << 3) + 12;
|
|
let endPosition;
|
|
try {
|
|
endPosition = store.writeKey(key, targetBytes, keyStartPosition);
|
|
} catch(error) {
|
|
targetBytes.fill(0, keyStartPosition);
|
|
throw error
|
|
}
|
|
let keySize = endPosition - keyStartPosition;
|
|
if (keySize > MAX_KEY_SIZE) {
|
|
targetBytes.fill(0, keyStartPosition);
|
|
throw new Error('Key size is too large')
|
|
}
|
|
uint32[flagPosition + 2] = keySize;
|
|
position = (endPosition + 16) >> 3;
|
|
if (flags & 2) {
|
|
uint32[(position << 1) - 1] = valueBuffer.length;
|
|
let valueArrayBuffer = valueBuffer.buffer;
|
|
// record pointer to value buffer
|
|
float64[position++] = (valueArrayBuffer.address || (valueArrayBuffer.address = getAddress(valueArrayBuffer))) + valueBuffer.byteOffset;
|
|
if (store.compression && valueBuffer.length >= store.compression.threshold) {
|
|
flags |= 0x100000;
|
|
float64[position] = store.compression.address;
|
|
if (!writeTxn)
|
|
env.compress(targetBytes.buffer.address + (position << 3));
|
|
position++;
|
|
}
|
|
}
|
|
if (ifVersion !== undefined) {
|
|
if (ifVersion === null)
|
|
flags |= 0x10;
|
|
else {
|
|
flags |= 0x100;
|
|
float64[position++] = ifVersion;
|
|
}
|
|
}
|
|
if (version !== undefined) {
|
|
flags |= 0x200;
|
|
float64[position++] = version || 0;
|
|
}
|
|
} else
|
|
position++;
|
|
targetBytes.position = position;
|
|
//console.log('js write', (targetBytes.buffer.address + (flagPosition << 2)).toString(16), flags.toString(16))
|
|
if (writeTxn) {
|
|
uint32[0] = flags;
|
|
env.write(targetBytes.buffer.address);
|
|
return () => (uint32[0] & FAILED_CONDITION) ? SYNC_PROMISE_FAIL : SYNC_PROMISE_SUCCESS
|
|
}
|
|
uint32[position << 1] = 0; // clear out the next slot
|
|
let nextUint32;
|
|
if (position > 0x1e00) { // 61440 bytes
|
|
let lastPosition = position;
|
|
let lastFloat64 = targetBytes.float64;
|
|
let lastUint32 = targetBytes.uint32;
|
|
targetBytes = allocateInstructionBuffer();
|
|
position = targetBytes.position;
|
|
lastFloat64[lastPosition + 1] = targetBytes.buffer.address + position;
|
|
lastUint32[lastPosition << 1] = 3; // pointer instruction
|
|
//console.log('pointer from ', (lastFloat64.buffer.address + (lastPosition << 3)).toString(16), 'to', (targetBytes.buffer.address + position).toString(16), 'flag position', (uint32.buffer.address + (flagPosition << 2)).toString(16))
|
|
nextUint32 = targetBytes.uint32;
|
|
} else
|
|
nextUint32 = uint32;
|
|
let newResolution = store.cache ?
|
|
{
|
|
uint32: nextUint32,
|
|
flagPosition: position << 1,
|
|
flag: 0, // TODO: eventually eliminate this, as we can probably signify success by zeroing the flagPosition
|
|
valueBuffer,
|
|
next: null,
|
|
key,
|
|
store,
|
|
valueSize: 0,
|
|
} :
|
|
{
|
|
uint32: nextUint32,
|
|
flagPosition: position << 1,
|
|
flag: 0, // TODO: eventually eliminate this, as we can probably signify success by zeroing the flagPosition
|
|
valueBuffer,
|
|
next: null,
|
|
};
|
|
let resolution = nextResolution;
|
|
resolution.next = newResolution;
|
|
nextResolution = newResolution;
|
|
let writtenBatchDepth = batchDepth;
|
|
|
|
return () => {
|
|
if (writtenBatchDepth) {
|
|
// if we are in a batch, the transaction can't close, so we do the faster,
|
|
// but non-deterministic updates, knowing that the write thread can
|
|
// just poll for the status change if we miss a status update
|
|
writeStatus = uint32[flagPosition];
|
|
uint32[flagPosition] = flags;
|
|
if (writeBatchStart && !writeStatus) {
|
|
outstandingBatchCount++;
|
|
if (outstandingBatchCount > batchStartThreshold) {
|
|
outstandingBatchCount = 0;
|
|
writeBatchStart();
|
|
writeBatchStart = null;
|
|
}
|
|
}
|
|
} else // otherwise the transaction could end at any time and we need to know the
|
|
// deterministically if it is ending, so we can reset the commit promise
|
|
// so we use the slower atomic operation
|
|
writeStatus = Atomics.or(uint32, flagPosition, flags);
|
|
|
|
outstandingWriteCount++;
|
|
if (writeStatus & TXN_DELIMITER) {
|
|
commitPromise = null;
|
|
queueCommitResolution(resolution);
|
|
if (!startAddress)
|
|
startAddress = uint32.buffer.address + (flagPosition << 2);
|
|
}
|
|
if (writeStatus & WAITING_OPERATION) { // write thread is waiting
|
|
//console.log('resume batch thread', uint32.buffer.address + (flagPosition << 2))
|
|
env.startWriting(0);
|
|
}
|
|
if (outstandingWriteCount > BACKPRESSURE_THRESHOLD) {
|
|
console.log('backpressure');
|
|
if (!backpressureArray)
|
|
backpressureArray = new Int8Array(new SharedArrayBuffer(4), 0, 1);
|
|
Atomics.wait(backpressureArray, 0, 0, 1);
|
|
}
|
|
if (startAddress) {
|
|
if (!enqueuedCommit && txnStartThreshold) {
|
|
enqueuedCommit = commitDelay == 0 ? setImmediate(startWriting) : setTimeout(startWriting, commitDelay);
|
|
} else if (outstandingWriteCount > txnStartThreshold)
|
|
startWriting();
|
|
}
|
|
|
|
if ((outstandingWriteCount & 7) === 0)
|
|
resolveWrites();
|
|
|
|
if (store.cache) {
|
|
resolution.key = key;
|
|
resolution.store = store;
|
|
resolution.valueSize = valueBuffer ? valueBuffer.length : 0;
|
|
}
|
|
resolution.valueBuffer = valueBuffer;
|
|
lastQueuedResolution = resolution;
|
|
|
|
if (ifVersion === undefined) {
|
|
if (writtenBatchDepth > 1)
|
|
return SYNC_PROMISE_SUCCESS // or return undefined?
|
|
if (!commitPromise) {
|
|
commitPromise = new Promise((resolve, reject) => {
|
|
resolution.resolve = resolve;
|
|
resolution.reject = reject;
|
|
});
|
|
}
|
|
return commitPromise
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
resolution.resolve = resolve;
|
|
resolution.reject = reject;
|
|
})
|
|
}
|
|
}
|
|
function startWriting() {
|
|
//console.log('start address ' + startAddress.toString(16), store.name)
|
|
if (enqueuedCommit) {
|
|
clearImmediate(enqueuedCommit);
|
|
enqueuedCommit = null;
|
|
}
|
|
env.startWriting(startAddress, (status) => {
|
|
//console.log('finished batch', store.name)
|
|
if (dynamicBytes.uint32[dynamicBytes.position << 1] & TXN_DELIMITER)
|
|
queueCommitResolution(nextResolution);
|
|
|
|
resolveWrites(true);
|
|
switch (status) {
|
|
case 0: case 1:
|
|
break;
|
|
case 2:
|
|
executeTxnCallbacks();
|
|
console.log('user callback');
|
|
break
|
|
default:
|
|
console.error(status);
|
|
if (commitRejectPromise) {
|
|
commitRejectPromise.reject(status);
|
|
commitRejectPromise = null;
|
|
}
|
|
}
|
|
});
|
|
startAddress = 0;
|
|
}
|
|
|
|
function queueCommitResolution(resolution) {
|
|
if (!resolution.isTxn) {
|
|
resolution.isTxn = true;
|
|
if (txnResolution) {
|
|
txnResolution.nextTxn = resolution;
|
|
outstandingWriteCount = 0;
|
|
}
|
|
else
|
|
txnResolution = resolution;
|
|
}
|
|
}
|
|
function resolveWrites(async) {
|
|
// clean up finished instructions
|
|
let instructionStatus;
|
|
while ((instructionStatus = unwrittenResolution.uint32[unwrittenResolution.flagPosition])
|
|
& 0x10000000) {
|
|
//console.log('instructionStatus: ' + instructionStatus.toString(16))
|
|
if (unwrittenResolution.callbacks) {
|
|
nextTxnCallbacks.push(unwrittenResolution.callbacks);
|
|
unwrittenResolution.callbacks = null;
|
|
}
|
|
if (!unwrittenResolution.isTxn)
|
|
unwrittenResolution.uint32 = null;
|
|
unwrittenResolution.valueBuffer = null;
|
|
unwrittenResolution.flag = instructionStatus;
|
|
outstandingWriteCount--;
|
|
unwrittenResolution = unwrittenResolution.next;
|
|
}
|
|
while (txnResolution &&
|
|
(instructionStatus = txnResolution.uint32[txnResolution.flagPosition] & 0xc0000000)) {
|
|
if (instructionStatus & 0x80000000)
|
|
rejectCommit();
|
|
else if (instructionStatus & TXN_COMMITTED)
|
|
resolveCommit(async);
|
|
}
|
|
}
|
|
|
|
function resolveCommit(async) {
|
|
afterCommit();
|
|
if (async)
|
|
resetReadTxn();
|
|
else
|
|
queueMicrotask(resetReadTxn); // TODO: only do this if there are actually committed writes?
|
|
do {
|
|
if (uncommittedResolution.resolve) {
|
|
let flag = uncommittedResolution.flag;
|
|
if (flag < 0)
|
|
uncommittedResolution.reject(new Error("Error occurred in write"));
|
|
else if (flag & FAILED_CONDITION) {
|
|
uncommittedResolution.resolve(false);
|
|
} else
|
|
uncommittedResolution.resolve(true);
|
|
}
|
|
} while((uncommittedResolution = uncommittedResolution.next) && uncommittedResolution != txnResolution)
|
|
txnResolution = txnResolution.nextTxn;
|
|
}
|
|
var commitRejectPromise;
|
|
function rejectCommit() {
|
|
afterCommit();
|
|
if (!commitRejectPromise) {
|
|
let rejectFunction;
|
|
commitRejectPromise = new Promise((resolve, reject) => rejectFunction = reject);
|
|
commitRejectPromise.reject = rejectFunction;
|
|
}
|
|
do {
|
|
let flag = uncommittedResolution.flag & 0xf;
|
|
let error = new Error("Commit failed (see commitError for details)");
|
|
error.commitError = commitRejectPromise;
|
|
uncommittedResolution.reject(error);
|
|
} while(uncommittedResolution = uncommittedResolution.next && uncommittedResolution != txnResolution)
|
|
txnResolution = txnResolution.nextTxn;
|
|
}
|
|
function atomicStatus(uint32, flagPosition, newStatus) {
|
|
if (batchDepth) {
|
|
// if we are in a batch, the transaction can't close, so we do the faster,
|
|
// but non-deterministic updates, knowing that the write thread can
|
|
// just poll for the status change if we miss a status update
|
|
let writeStatus = uint32[flagPosition];
|
|
uint32[flagPosition] = newStatus;
|
|
return writeStatus
|
|
} else // otherwise the transaction could end at any time and we need to know the
|
|
// deterministically if it is ending, so we can reset the commit promise
|
|
// so we use the slower atomic operation
|
|
return Atomics.or(uint32, flagPosition, newStatus)
|
|
}
|
|
function afterCommit() {
|
|
for (let i = 0, l = afterCommitCallbacks.length; i < l; i++) {
|
|
afterCommitCallbacks[i]({ next: uncommittedResolution, last: unwrittenResolution});
|
|
}
|
|
}
|
|
async function executeTxnCallbacks() {
|
|
env.beginTxn(0);
|
|
env.writeTxn = writeTxn = {};
|
|
let promises;
|
|
let txnCallbacks;
|
|
for (let i = 0, l = nextTxnCallbacks.length; i < l; i++) {
|
|
txnCallbacks = nextTxnCallbacks[i];
|
|
for (let i = 0, l = txnCallbacks.length; i < l; i++) {
|
|
let userTxnCallback = txnCallbacks[i];
|
|
let asChild = userTxnCallback.asChild;
|
|
if (asChild) {
|
|
if (promises) {
|
|
// must complete any outstanding transactions before proceeding
|
|
await Promise.all(promises);
|
|
promises = null;
|
|
}
|
|
env.beginTxn(1); // abortable
|
|
|
|
try {
|
|
let result = userTxnCallback.callback();
|
|
if (result && result.then) {
|
|
await result;
|
|
}
|
|
if (result === ABORT)
|
|
env.abortTxn();
|
|
else
|
|
env.commitTxn();
|
|
txnCallbacks[i] = result;
|
|
} catch(error) {
|
|
env.abortTxn();
|
|
txnError(error, i);
|
|
}
|
|
} else {
|
|
try {
|
|
let result = userTxnCallback();
|
|
txnCallbacks[i] = result;
|
|
if (result && result.then) {
|
|
if (!promises)
|
|
promises = [];
|
|
promises.push(result.catch(() => {}));
|
|
}
|
|
} catch(error) {
|
|
txnError(error, i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
nextTxnCallbacks = [];
|
|
if (promises) { // finish any outstanding commit functions
|
|
await Promise.all(promises);
|
|
}
|
|
env.writeTxn = writeTxn = false;
|
|
return env.commitTxn()
|
|
function txnError(error, i) {
|
|
(txnCallbacks.errors || (txnCallbacks.errors = []))[i] = error;
|
|
txnCallbacks[i] = CALLBACK_THREW;
|
|
}
|
|
}
|
|
function finishBatch() {
|
|
dynamicBytes.uint32[(dynamicBytes.position + 1) << 1] = 0; // clear out the next slot
|
|
let writeStatus = atomicStatus(dynamicBytes.uint32, (dynamicBytes.position++) << 1, 2); // atomically write the end block
|
|
nextResolution.flagPosition += 2;
|
|
if (writeStatus & WAITING_OPERATION) {
|
|
env.startWriting(0);
|
|
}
|
|
}
|
|
Object.assign(LMDBStore.prototype, {
|
|
put(key, value, versionOrOptions, ifVersion) {
|
|
let flags = 15;
|
|
if (typeof versionOrOptions == 'object') {
|
|
if (versionOrOptions.noOverwrite)
|
|
flags |= 0x10;
|
|
if (versionOrOptions.noDupData)
|
|
flags |= 0x20;
|
|
if (versionOrOptions.append)
|
|
flags |= 0x20000;
|
|
if (versionOrOptions.ifVersion != undefined)
|
|
ifVersion = versionsOrOptions.ifVersion;
|
|
versionOrOptions = versionOrOptions.version;
|
|
}
|
|
return writeInstructions(flags, this, key, value, this.useVersions ? versionOrOptions || 0 : undefined, ifVersion)()
|
|
},
|
|
remove(key, ifVersionOrValue) {
|
|
let flags = 13;
|
|
let ifVersion, value;
|
|
if (ifVersionOrValue !== undefined) {
|
|
if (this.useVersions)
|
|
ifVersion = ifVersionOrValue;
|
|
else {
|
|
flags = 14;
|
|
value = ifVersionOrValue;
|
|
}
|
|
}
|
|
return writeInstructions(flags, this, key, value, undefined, ifVersion)()
|
|
},
|
|
ifNoExists(key, callback) {
|
|
return this.ifVersion(key, null, callback)
|
|
},
|
|
|
|
ifVersion(key, version, callback) {
|
|
if (!callback) {
|
|
return new Batch((operations, callback) => {
|
|
let promise = this.ifVersion(key, version, operations);
|
|
if (callback)
|
|
promise.then(callback);
|
|
return promise
|
|
})
|
|
}
|
|
if (writeTxn) {
|
|
if (this.doesExist(key, version)) {
|
|
callback();
|
|
return SYNC_PROMISE_SUCCESS
|
|
}
|
|
return SYNC_PROMISE_FAIL
|
|
}
|
|
let finishStartWrite = writeInstructions(typeof key === 'undefined' ? 1 : 4, this, key, undefined, undefined, version);
|
|
let promise;
|
|
batchDepth += 2;
|
|
if (batchDepth > 2)
|
|
promise = finishStartWrite();
|
|
else {
|
|
writeBatchStart = () => {
|
|
promise = finishStartWrite();
|
|
};
|
|
outstandingBatchCount = 0;
|
|
}
|
|
//console.warn('wrote start of ifVersion', this.path)
|
|
try {
|
|
if (typeof callback === 'function') {
|
|
callback();
|
|
} else {
|
|
for (let i = 0, l = callback.length; i < l; i++) {
|
|
let operation = callback[i];
|
|
this[operation.type](operation.key, operation.value);
|
|
}
|
|
}
|
|
} finally {
|
|
//console.warn('writing end of ifVersion', this.path, (dynamicBytes.buffer.address + ((dynamicBytes.position + 1) << 3)).toString(16))
|
|
if (!promise) {
|
|
finishBatch();
|
|
batchDepth -= 2;
|
|
promise = finishStartWrite(); // finish write once all the operations have been written (and it hasn't been written prematurely)
|
|
writeBatchStart = null;
|
|
} else {
|
|
batchDepth -= 2;
|
|
finishBatch();
|
|
}
|
|
}
|
|
return promise
|
|
},
|
|
batch(callbackOrOperations) {
|
|
return this.ifVersion(undefined, undefined, callbackOrOperations)
|
|
},
|
|
|
|
putSync(key, value, versionOrOptions, ifVersion) {
|
|
if (writeTxn)
|
|
return this.put(key, value, versionOrOptions, ifVersion)
|
|
else
|
|
return this.transactionSync(() =>
|
|
this.put(key, value, versionOrOptions, ifVersion) == SYNC_PROMISE_SUCCESS,
|
|
{ abortable: false })
|
|
},
|
|
removeSync(key, ifVersionOrValue) {
|
|
if (writeTxn)
|
|
return this.remove(key, ifVersionOrValue)
|
|
else
|
|
return this.transactionSync(() =>
|
|
this.remove(key, ifVersionOrValue) == SYNC_PROMISE_SUCCESS,
|
|
{ abortable: false })
|
|
},
|
|
transaction(callback, options) {
|
|
if (options) {
|
|
if (options.synchronousStart)
|
|
return this.transactionSync(callback, options)
|
|
if (options.abortable)
|
|
return this.childTransaction(callback)
|
|
}
|
|
if (writeTxn) {
|
|
// already nested in a transaction, just execute and return
|
|
return callback()
|
|
}
|
|
return this.transactionAsync(callback)
|
|
},
|
|
childTransaction(callback) {
|
|
if (useWritemap)
|
|
throw new Error('Child transactions are not supported in writemap mode')
|
|
if (writeTxn) {
|
|
env.beginTxn(1); // abortable
|
|
try {
|
|
return when(callback(), (result) => {
|
|
if (result === ABORT)
|
|
env.abortTxn();
|
|
else
|
|
env.commitTxn();
|
|
return result
|
|
}, (error) => {
|
|
env.abortTxn();
|
|
throw error
|
|
})
|
|
} catch(error) {
|
|
env.abortTxn();
|
|
throw error
|
|
}
|
|
}
|
|
return this.transactionAsync(callback, true)
|
|
},
|
|
transactionAsync(callback, asChild) {
|
|
let txnIndex;
|
|
let txnCallbacks;
|
|
if (!lastQueuedResolution || !lastQueuedResolution.callbacks) {
|
|
txnCallbacks = [asChild ? { callback, asChild } : callback];
|
|
txnCallbacks.results = writeInstructions(8 | (this.strictAsyncOrder ? 0x100000 : 0), this)();
|
|
lastQueuedResolution.callbacks = txnCallbacks;
|
|
txnIndex = 0;
|
|
} else {
|
|
txnCallbacks = lastQueuedResolution.callbacks;
|
|
txnIndex = txnCallbacks.push(asChild ? { callback, asChild } : callback) - 1;
|
|
}
|
|
return txnCallbacks.results.then((results) => {
|
|
let result = txnCallbacks[txnIndex];
|
|
if (result === CALLBACK_THREW)
|
|
throw txnCallbacks.errors[txnIndex]
|
|
return result
|
|
})
|
|
},
|
|
transactionSync(callback, options) {
|
|
if (writeTxn) {
|
|
if (!useWritemap && !this.cache && !(options && options.abortable === false))
|
|
// already nested in a transaction, execute as child transaction (if possible) and return
|
|
return this.childTransaction(callback)
|
|
let result = callback(); // else just run in current transaction
|
|
if (result == ABORT && !abortedNonChildTransactionWarn) {
|
|
console.warn('Can not abort a transaction inside another transaction with ' + (this.cache ? 'caching enabled' : 'useWritemap enabled'));
|
|
abortedNonChildTransactionWarn = true;
|
|
}
|
|
return result
|
|
}
|
|
try {
|
|
this.transactions++;
|
|
let flags = 0;
|
|
if (!(options && options.abortable === false))
|
|
flags = 1;
|
|
if (!(options && options.synchronousCommit === false))
|
|
flags |= 2;
|
|
env.beginTxn(flags);
|
|
writeTxn = env.writeTxn = {};
|
|
return when(callback(), (result) => {
|
|
try {
|
|
if (result === ABORT)
|
|
env.abortTxn();
|
|
else {
|
|
env.commitTxn();
|
|
resetReadTxn();
|
|
}
|
|
|
|
return result
|
|
} finally {
|
|
env.writeTxn = writeTxn = null;
|
|
}
|
|
}, (error) => {
|
|
try { env.abortTxn(); } catch(e) {}
|
|
env.writeTxn = writeTxn = null;
|
|
throw error
|
|
})
|
|
} catch(error) {
|
|
try { env.abortTxn(); } catch(e) {}
|
|
env.writeTxn = writeTxn = null;
|
|
throw error
|
|
}
|
|
},
|
|
on(event, callback) {
|
|
if (event == 'beforecommit') {
|
|
eventTurnBatching = true;
|
|
beforeCommitCallbacks.push(callback);
|
|
} else if (event == 'aftercommit')
|
|
afterCommitCallbacks.push(callback);
|
|
}
|
|
});
|
|
LMDBStore.prototype.del = LMDBStore.prototype.remove;
|
|
}
|
|
|
|
class Batch extends Array {
|
|
constructor(callback) {
|
|
this.callback = callback;
|
|
}
|
|
put(key, value) {
|
|
this.push({ type: 'put', key, value });
|
|
}
|
|
del(key) {
|
|
this.push({ type: 'del', key });
|
|
}
|
|
clear() {
|
|
this.splice(0, this.length);
|
|
}
|
|
write(callback) {
|
|
this.callback(this, callback);
|
|
}
|
|
}
|
|
|
|
const require$2 = module$1.createRequire((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
setGetLastVersion(getLastVersion$1);
|
|
const Uint8ArraySlice$1 = Uint8Array.prototype.slice;
|
|
const keyBytes = Buffer.allocUnsafeSlow(2048);
|
|
const keyBuffer = keyBytes.buffer;
|
|
const keyBytesView = keyBytes.dataView = new DataView(keyBytes.buffer, 0, 2048); // max key size is actually 1978
|
|
keyBytes.uint32 = new Uint32Array(keyBuffer, 0, 512);
|
|
keyBytes.float64 = new Float64Array(keyBuffer, 0, 256);
|
|
keyBuffer.address = getAddress(keyBuffer);
|
|
const DEFAULT_COMMIT_DELAY = 0;
|
|
|
|
const allDbs = new Map();
|
|
const SYNC_PROMISE_RESULT = Promise.resolve(true);
|
|
const SYNC_PROMISE_FAIL$1 = Promise.resolve(false);
|
|
SYNC_PROMISE_RESULT.isSync = true;
|
|
SYNC_PROMISE_FAIL$1.isSync = true;
|
|
let defaultCompression;
|
|
let lastSize;
|
|
function open(path$1, options) {
|
|
let env = new Env();
|
|
let scheduledTransactions;
|
|
let scheduledOperations;
|
|
let asyncTransactionAfter = true, asyncTransactionStrictOrder;
|
|
let readTxn, writeTxn, readTxnRenewed;
|
|
if (typeof path$1 == 'object' && !options) {
|
|
options = path$1;
|
|
path$1 = options.path;
|
|
}
|
|
let extension = path.extname(path$1);
|
|
let name = path.basename(path$1, extension);
|
|
let is32Bit = os.arch().endsWith('32');
|
|
let remapChunks = (options && options.remapChunks) || ((options && options.mapSize) ?
|
|
(is32Bit && options.mapSize > 0x100000000) : // larger than fits in address space, must use dynamic maps
|
|
is32Bit); // without a known map size, we default to being able to handle large data correctly/well*/
|
|
options = Object.assign({
|
|
path: path$1,
|
|
noSubdir: Boolean(extension),
|
|
isRoot: true,
|
|
maxDbs: 12,
|
|
remapChunks,
|
|
keyBytes,
|
|
// default map size limit of 4 exabytes when using remapChunks, since it is not preallocated and we can
|
|
// make it super huge.
|
|
mapSize: remapChunks ? 0x10000000000000 :
|
|
0x20000, // Otherwise we start small with 128KB
|
|
}, options);
|
|
if (options.asyncTransactionOrder == 'before') {
|
|
console.warn('asyncTransactionOrder: "before" is deprecated');
|
|
asyncTransactionAfter = false;
|
|
} else if (options.asyncTransactionOrder == 'strict') {
|
|
asyncTransactionStrictOrder = true;
|
|
asyncTransactionAfter = false;
|
|
}
|
|
if (!fs.existsSync(options.noSubdir ? path.dirname(path$1) : path$1))
|
|
fs.mkdirSync(options.noSubdir ? path.dirname(path$1) : path$1, { recursive: true });
|
|
if (options.compression) {
|
|
if (options.compression == true) {
|
|
if (defaultCompression)
|
|
options.compression = defaultCompression;
|
|
else
|
|
defaultCompression = options.compression = new Compression({
|
|
threshold: 1000,
|
|
dictionary: fs.readFileSync(new URL('./dict/dict.txt', (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.cjs', document.baseURI).href)).replace(/dist[\\\/]index.cjs$/, ''))),
|
|
});
|
|
defaultCompression.threshold = 1000;
|
|
} else {
|
|
let compressionOptions = Object.assign({
|
|
threshold: 1000,
|
|
dictionary: fs.readFileSync(new URL('./dict/dict.txt', (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.cjs', document.baseURI).href)).replace(/dist[\\\/]index.cjs$/, ''))),
|
|
}, options.compression);
|
|
options.compression = new Compression(compressionOptions);
|
|
options.compression.threshold = compressionOptions.threshold;
|
|
}
|
|
}
|
|
|
|
if (options && options.clearOnStart) {
|
|
console.info('Removing', path$1);
|
|
fs.removeSync(path$1);
|
|
console.info('Removed', path$1);
|
|
}
|
|
let useWritemap = options.useWritemap;
|
|
try {
|
|
env.open(options);
|
|
} catch(error) {
|
|
throw error
|
|
}
|
|
env.readerCheck(); // clear out any stale entries
|
|
function renewReadTxn() {
|
|
if (readTxn)
|
|
readTxn.renew();
|
|
else
|
|
readTxn = env.beginTxn(0x20000);
|
|
readTxnRenewed = setImmediate(resetReadTxn);
|
|
return readTxn
|
|
}
|
|
function resetReadTxn() {
|
|
if (readTxnRenewed) {
|
|
LMDBStore.onReadReset();
|
|
readTxnRenewed = null;
|
|
if (readTxn.cursorCount - (readTxn.renewingCursorCount || 0) > 0) {
|
|
readTxn.onlyCursor = true;
|
|
readTxn = null;
|
|
}
|
|
else
|
|
readTxn.reset();
|
|
}
|
|
}
|
|
class LMDBStore extends EventEmitter {
|
|
constructor(dbName, dbOptions) {
|
|
super();
|
|
if (dbName === undefined)
|
|
throw new Error('Database name must be supplied in name property (may be null for root database)')
|
|
|
|
const openDB = () => {
|
|
try {
|
|
this.db = env.openDbi(Object.assign({
|
|
name: dbName,
|
|
create: true,
|
|
txn: env.writeTxn,
|
|
}, dbOptions));
|
|
this.db.name = dbName || null;
|
|
} catch(error) {
|
|
handleError(error);
|
|
}
|
|
};
|
|
if (dbOptions.compression && !(dbOptions.compression instanceof Compression)) {
|
|
if (dbOptions.compression == true && options.compression)
|
|
dbOptions.compression = options.compression; // use the parent compression if available
|
|
else
|
|
dbOptions.compression = new Compression(Object.assign({
|
|
threshold: 1000,
|
|
dictionary: fs.readFileSync(require$2.resolve('./dict/dict.txt')),
|
|
}), dbOptions.compression);
|
|
}
|
|
|
|
if (dbOptions.dupSort && (dbOptions.useVersions || dbOptions.cache)) {
|
|
throw new Error('The dupSort flag can not be combined with versions or caching')
|
|
}
|
|
openDB();
|
|
resetReadTxn(); // a read transaction becomes invalid after opening another db
|
|
this.name = dbName;
|
|
this.env = env;
|
|
this.reads = 0;
|
|
this.writes = 0;
|
|
this.transactions = 0;
|
|
this.averageTransactionTime = 5;
|
|
if (dbOptions.syncBatchThreshold)
|
|
console.warn('syncBatchThreshold is no longer supported');
|
|
if (dbOptions.immediateBatchThreshold)
|
|
console.warn('immediateBatchThreshold is no longer supported');
|
|
this.commitDelay = DEFAULT_COMMIT_DELAY;
|
|
Object.assign(this, { // these are the options that are inherited
|
|
path: options.path,
|
|
encoding: options.encoding,
|
|
strictAsyncOrder: options.strictAsyncOrder,
|
|
}, dbOptions);
|
|
if (!this.encoding || this.encoding == 'msgpack' || this.encoding == 'cbor') {
|
|
this.encoder = this.decoder = new (this.encoding == 'cbor' ? require$2('cbor-x').Encoder : require$2('msgpackr').Encoder)
|
|
(Object.assign(this.sharedStructuresKey ?
|
|
this.setupSharedStructures() : {
|
|
copyBuffers: true // need to copy any embedded buffers that are found since we use unsafe buffers
|
|
}, options, dbOptions));
|
|
} else if (this.encoding == 'json') {
|
|
this.encoder = {
|
|
encode: JSON.stringify,
|
|
};
|
|
}
|
|
applyKeyHandling(this);
|
|
allDbs.set(dbName ? name + '-' + dbName : name, this);
|
|
}
|
|
openDB(dbName, dbOptions) {
|
|
if (typeof dbName == 'object' && !dbOptions) {
|
|
dbOptions = dbName;
|
|
dbName = options.name;
|
|
} else
|
|
dbOptions = dbOptions || {};
|
|
try {
|
|
return dbOptions.cache ?
|
|
new (CachingStore(LMDBStore))(dbName, dbOptions) :
|
|
new LMDBStore(dbName, dbOptions)
|
|
} catch(error) {
|
|
if (error.message.indexOf('MDB_DBS_FULL') > -1) {
|
|
error.message += ' (increase your maxDbs option)';
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
transactionAsync(callback, asChild) {
|
|
let lastOperation;
|
|
if (scheduledOperations) {
|
|
lastOperation = asyncTransactionAfter ? scheduledOperations.appendAsyncTxn :
|
|
scheduledOperations[asyncTransactionStrictOrder ? scheduledOperations.length - 1 : 0];
|
|
} else {
|
|
scheduledOperations = [];
|
|
scheduledOperations.bytes = 0;
|
|
}
|
|
let transactionSet;
|
|
let transactionSetIndex;
|
|
if (lastOperation === true) { // continue last set of transactions
|
|
transactionSetIndex = scheduledTransactions.length - 1;
|
|
transactionSet = scheduledTransactions[transactionSetIndex];
|
|
} else {
|
|
// for now we signify transactions as a true
|
|
if (asyncTransactionAfter) // by default we add a flag to put transactions after other operations
|
|
scheduledOperations.appendAsyncTxn = true;
|
|
else if (asyncTransactionStrictOrder)
|
|
scheduledOperations.push(true);
|
|
else // in before mode, we put all the async transaction at the beginning
|
|
scheduledOperations.unshift(true);
|
|
if (!scheduledTransactions) {
|
|
scheduledTransactions = [];
|
|
}
|
|
transactionSetIndex = scheduledTransactions.push(transactionSet = []) - 1;
|
|
}
|
|
let index = (transactionSet.push(asChild ?
|
|
{asChild, callback } : callback) - 1) << 1;
|
|
return this.scheduleCommit().results.then((results) => {
|
|
let transactionResults = results.transactionResults[transactionSetIndex];
|
|
let error = transactionResults[index];
|
|
if (error)
|
|
throw error
|
|
return transactionResults[index + 1]
|
|
})
|
|
}
|
|
getSharedBufferForGet(id) {
|
|
let txn = ( (readTxnRenewed ? readTxn : renewReadTxn()));
|
|
lastSize = this.keyIsCompatibility ? txn.getBinaryShared(id) : this.db.get(this.writeKey(id, keyBytes, 0));
|
|
if (lastSize === 0xffffffff) { // not found code
|
|
return //undefined
|
|
}
|
|
return lastSize
|
|
}
|
|
|
|
getSizeBinaryFast(id) {
|
|
(env.writeTxn || (readTxnRenewed ? readTxn : renewReadTxn()));
|
|
lastSize = this.db.getByBinary(this.writeKey(id, keyBytes, 0));
|
|
}
|
|
getString(id) {
|
|
(env.writeTxn || (readTxnRenewed ? readTxn : renewReadTxn()));
|
|
let string = this.db.getStringByBinary(this.writeKey(id, keyBytes, 0));
|
|
if (string)
|
|
lastSize = string.length;
|
|
return string
|
|
}
|
|
getBinaryFast(id) {
|
|
this.getSizeBinaryFast(id);
|
|
return lastSize === 0xffffffff ? undefined : this.db.unsafeBuffer.subarray(0, lastSize)
|
|
}
|
|
getBinary(id) {
|
|
this.getSizeBinaryFast(id);
|
|
return lastSize === 0xffffffff ? undefined : Uint8ArraySlice$1.call(this.db.unsafeBuffer, 0, lastSize)
|
|
}
|
|
get(id) {
|
|
if (this.decoder) {
|
|
this.getSizeBinaryFast(id);
|
|
return lastSize === 0xffffffff ? undefined : this.decoder.decode(this.db.unsafeBuffer, lastSize)
|
|
}
|
|
if (this.encoding == 'binary')
|
|
return this.getBinary(id)
|
|
|
|
let result = this.getString(id);
|
|
if (result) {
|
|
if (this.encoding == 'json')
|
|
return JSON.parse(result)
|
|
}
|
|
return result
|
|
}
|
|
getEntry(id) {
|
|
let value = this.get(id);
|
|
if (value !== undefined) {
|
|
if (this.useVersions)
|
|
return {
|
|
value,
|
|
version: getLastVersion$1(),
|
|
//size: lastSize
|
|
}
|
|
else
|
|
return {
|
|
value,
|
|
//size: lastSize
|
|
}
|
|
}
|
|
}
|
|
resetReadTxn() {
|
|
resetReadTxn();
|
|
}
|
|
doesExist(key, versionOrValue) {
|
|
let txn;
|
|
try {
|
|
if (env.writeTxn) {
|
|
txn = env.writeTxn;
|
|
} else {
|
|
txn = readTxnRenewed ? readTxn : renewReadTxn();
|
|
}
|
|
if (versionOrValue === undefined) {
|
|
this.getSizeBinaryFast(key);
|
|
return lastSize !== 0xffffffff
|
|
}
|
|
else if (this.useVersions) {
|
|
this.getSizeBinaryFast(key);
|
|
return lastSize !== 0xffffffff && matches(getLastVersion$1(), versionOrValue)
|
|
}
|
|
else {
|
|
if (this.encoder) {
|
|
versionOrValue = this.encoder.encode(versionOrValue);
|
|
}
|
|
if (typeof versionOrValue == 'string')
|
|
versionOrValue = Buffer.from(versionOrValue);
|
|
return this.getValuesCount(key, { start: versionOrValue, exactMatch: true}) > 0
|
|
}
|
|
} catch(error) {
|
|
return handleError(error)
|
|
}
|
|
}
|
|
batch(operations) {
|
|
/*if (writeTxn) {
|
|
this.commitBatchNow(operations.map(operation => [this.db, operation.key, operation.value]))
|
|
return Promise.resolve(true)
|
|
}*/
|
|
let scheduledOperations = this.getScheduledOperations();
|
|
for (let operation of operations) {
|
|
let value = operation.value;
|
|
scheduledOperations.push([operation.key, value]);
|
|
scheduledOperations.bytes += operation.key.length + (value && value.length || 0) + 200;
|
|
}
|
|
return this.scheduleCommit().unconditionalResults
|
|
}
|
|
backup(path) {
|
|
return new Promise((resolve, reject) => env.copy(path, false, (error) => {
|
|
if (error) {
|
|
reject(error);
|
|
} else {
|
|
resolve();
|
|
}
|
|
}))
|
|
}
|
|
close() {
|
|
this.db.close();
|
|
if (this.isRoot) {
|
|
if (readTxn) {
|
|
try {
|
|
readTxn.abort();
|
|
} catch(error) {}
|
|
}
|
|
readTxnRenewed = null;
|
|
env.close();
|
|
}
|
|
}
|
|
getStats() {
|
|
try {
|
|
let stats = this.db.stat(readTxnRenewed ? readTxn : renewReadTxn());
|
|
return stats
|
|
}
|
|
catch(error) {
|
|
return handleError(error)
|
|
}
|
|
}
|
|
sync(callback) {
|
|
return env.sync(callback || function(error) {
|
|
if (error) {
|
|
console.error(error);
|
|
}
|
|
})
|
|
}
|
|
deleteDB() {
|
|
this.transactionSync(() =>
|
|
this.db.drop({
|
|
justFreePages: false
|
|
})
|
|
, { abortable: false });
|
|
}
|
|
clear() {
|
|
this.transactionSync(() =>
|
|
this.db.drop({
|
|
justFreePages: true
|
|
})
|
|
, { abortable: false });
|
|
if (this.encoder && this.encoder.structures)
|
|
this.encoder.structures = [];
|
|
|
|
}
|
|
readerCheck() {
|
|
return env.readerCheck()
|
|
}
|
|
readerList() {
|
|
return env.readerList().join('')
|
|
}
|
|
setupSharedStructures() {
|
|
const getStructures = () => {
|
|
let lastVersion; // because we are doing a read here, we may need to save and restore the lastVersion from the last read
|
|
if (this.useVersions)
|
|
lastVersion = getLastVersion$1();
|
|
try {
|
|
let buffer = this.getBinary(this.sharedStructuresKey);
|
|
if (this.useVersions)
|
|
setLastVersion(lastVersion);
|
|
return buffer ? this.encoder.decode(buffer) : []
|
|
} catch(error) {
|
|
return handleError(error)
|
|
}
|
|
};
|
|
return {
|
|
saveStructures: (structures, previousLength) => {
|
|
return this.transactionSync(() => {
|
|
let existingStructuresBuffer = this.getBinary(this.sharedStructuresKey);
|
|
let existingStructures = existingStructuresBuffer ? this.encoder.decode(existingStructuresBuffer) : [];
|
|
if (existingStructures.length != previousLength)
|
|
return false // it changed, we need to indicate that we couldn't update
|
|
this.put(this.sharedStructuresKey, structures);
|
|
}, { abortable: false, synchronousCommit: false })
|
|
},
|
|
getStructures,
|
|
copyBuffers: true // need to copy any embedded buffers that are found since we use unsafe buffers
|
|
}
|
|
}
|
|
}
|
|
// if caching class overrides putSync, don't want to double call the caching code
|
|
const putSync = LMDBStore.prototype.putSync;
|
|
const removeSync = LMDBStore.prototype.removeSync;
|
|
addQueryMethods(LMDBStore, { env, getReadTxn() {
|
|
return readTxnRenewed ? readTxn : renewReadTxn()
|
|
}, saveKey: saveKey$1, keyBytes, keyBytesView, getLastVersion: getLastVersion$1 });
|
|
addWriteMethods(LMDBStore, { env, fixedBuffer: keyBytes, resetReadTxn, ...options });
|
|
return options.cache ?
|
|
new (CachingStore(LMDBStore))(options.name || null, options) :
|
|
new LMDBStore(options.name || null, options)
|
|
function handleError(error, store, txn, retry) {
|
|
try {
|
|
if (writeTxn)
|
|
writeTxn.abort();
|
|
} catch(error) {}
|
|
|
|
if (error.message.startsWith('MDB_') &&
|
|
!(error.message.startsWith('MDB_KEYEXIST') || error.message.startsWith('MDB_NOTFOUND')) ||
|
|
error.message == 'The transaction is already closed.') {
|
|
resetReadTxn(); // separate out cursor-based read txns
|
|
try {
|
|
if (readTxn) {
|
|
readTxn.abort();
|
|
readTxn.isAborted = true;
|
|
}
|
|
} catch(error) {}
|
|
readTxn = null;
|
|
}
|
|
if (error.message.startsWith('MDB_PROBLEM'))
|
|
console.error(error);
|
|
error.message = 'In database ' + name + ': ' + error.message;
|
|
throw error
|
|
}
|
|
}
|
|
|
|
function matches(previousVersion, ifVersion){
|
|
let matches;
|
|
if (previousVersion) {
|
|
if (ifVersion) {
|
|
matches = previousVersion == ifVersion;
|
|
} else {
|
|
matches = false;
|
|
}
|
|
} else {
|
|
matches = !ifVersion;
|
|
}
|
|
return matches
|
|
}
|
|
function getLastEntrySize() {
|
|
return lastSize
|
|
}
|
|
function getLastVersion$1() {
|
|
return keyBytesView.getFloat64(16, true)
|
|
}
|
|
|
|
function setLastVersion(version) {
|
|
return keyBytesView.setFloat64(16, version, true)
|
|
}
|
|
let saveBuffer$1, saveDataView$1, saveDataAddress$1;
|
|
let savePosition$1 = 8000;
|
|
function allocateSaveBuffer$1() {
|
|
saveBuffer$1 = Buffer.alloc(8192);
|
|
saveBuffer$1.dataView = saveDataView$1 = new DataView(saveBuffer$1.buffer, saveBuffer$1.byteOffset, saveBuffer$1.byteLength);
|
|
saveBuffer$1.buffer.address = getAddress(saveBuffer$1.buffer);
|
|
saveDataAddress$1 = saveBuffer$1.buffer.address + saveBuffer$1.byteOffset;
|
|
savePosition$1 = 0;
|
|
|
|
}
|
|
function saveKey$1(key, writeKey, saveTo) {
|
|
if (savePosition$1 > 6200) {
|
|
allocateSaveBuffer$1();
|
|
}
|
|
let start = savePosition$1;
|
|
savePosition$1 = writeKey(key, saveBuffer$1, start + 4);
|
|
saveDataView$1.setUint32(start, savePosition$1 - start - 4, true);
|
|
saveTo.saveBuffer = saveBuffer$1;
|
|
savePosition$1 = (savePosition$1 + 7) & 0xfffff8;
|
|
return start + saveDataAddress$1
|
|
}
|
|
|
|
Object.defineProperty(exports, 'bufferToKeyValue', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return index_js$1.fromBufferKey;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'compareKey', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return index_js$1.compareKeys;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'compareKeys', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return index_js$1.compareKeys;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'keyValueToBuffer', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return index_js$1.toBufferKey;
|
|
}
|
|
});
|
|
exports.ABORT = ABORT;
|
|
exports.allDbs = allDbs;
|
|
exports.getLastEntrySize = getLastEntrySize;
|
|
exports.getLastVersion = getLastVersion$1;
|
|
exports.open = open;
|
|
exports.setLastVersion = setLastVersion;
|
|
//# sourceMappingURL=index.cjs.map
|
|
|