lmdb
Version:
Simple, efficient, scalable, high-performance LMDB interface
432 lines (427 loc) • 11.7 kB
JavaScript
export const SKIP = {};
const DONE = {
value: null,
done: true,
};
const RETURN_DONE = {
// we allow this one to be mutated
value: null,
done: true,
};
if (!Symbol.asyncIterator) {
Symbol.asyncIterator = Symbol.for('Symbol.asyncIterator');
}
const NO_OPTIONS = {};
export class RangeIterable {
constructor(sourceArray) {
if (sourceArray) {
this.iterate = sourceArray[Symbol.iterator].bind(sourceArray);
}
}
map(func) {
let source = this;
let iterable = new RangeIterable();
iterable.iterate = (options = NO_OPTIONS) => {
const { async } = options;
let iterator =
source[async ? Symbol.asyncIterator : Symbol.iterator](options);
if (!async) source.isSync = true;
let i = -1;
return {
next(resolvedResult) {
let result;
do {
let iteratorResult;
try {
if (resolvedResult) {
iteratorResult = resolvedResult;
resolvedResult = null; // don't go in this branch on next iteration
} else {
i++;
iteratorResult = iterator.next();
if (iteratorResult.then) {
if (!async) {
this.throw(
new Error(
'Can not synchronously iterate with promises as iterator results',
),
);
}
return iteratorResult.then(
(iteratorResult) => this.next(iteratorResult),
(error) => {
return this.throw(error);
},
);
}
}
if (iteratorResult.done === true) {
this.done = true;
if (iterable.onDone) iterable.onDone();
return iteratorResult;
}
try {
result = func.call(source, iteratorResult.value, i);
if (result && result.then && async) {
// if async, wait for promise to resolve before returning iterator result
return result.then(
(result) =>
result === SKIP
? this.next()
: {
value: result,
},
(error) => {
if (options.continueOnRecoverableError)
error.continueIteration = true;
return this.throw(error);
},
);
}
} catch (error) {
// if the error came from the user function, we can potentially mark it for continuing iteration
if (options.continueOnRecoverableError)
error.continueIteration = true;
throw error; // throw to next catch to handle
}
} catch (error) {
if (iterable.handleError) {
// if we have handleError, we can use it to further handle errors
try {
result = iterable.handleError(error, i);
} catch (error2) {
return this.throw(error2);
}
} else return this.throw(error);
}
} while (result === SKIP);
if (result === DONE) {
return this.return();
}
return {
value: result,
};
},
return(value) {
if (!this.done) {
RETURN_DONE.value = value;
this.done = true;
if (iterable.onDone) iterable.onDone();
iterator.return?.();
}
return RETURN_DONE;
},
throw(error) {
if (error.continueIteration) {
// if it's a recoverable error, we can return or throw without closing the iterator
if (iterable.returnRecoverableErrors)
try {
return {
value: iterable.returnRecoverableErrors(error),
};
} catch (error) {
// if this throws, we need to go back to closing the iterator
this.return();
throw error;
}
if (options.continueOnRecoverableError) throw error; // throw without closing iterator
}
// else we are done with the iterator (and can throw)
this.return();
throw error;
},
};
};
return iterable;
}
[Symbol.asyncIterator](options) {
if (options) options = { ...options, async: true };
else options = { async: true };
return (this.iterator = this.iterate(options));
}
[Symbol.iterator](options) {
return (this.iterator = this.iterate(options));
}
filter(func) {
let iterable = this.map((element) => {
let result = func(element);
// handle promise
if (result?.then)
return result.then((result) => (result ? element : SKIP));
else return result ? element : SKIP;
});
let iterate = iterable.iterate;
iterable.iterate = (options = NO_OPTIONS) => {
// explicitly prevent continue on recoverable error with filter
if (options.continueOnRecoverableError)
options = { ...options, continueOnRecoverableError: false };
return iterate(options);
};
return iterable;
}
forEach(callback) {
let iterator = (this.iterator = this.iterate());
let result;
while ((result = iterator.next()).done !== true) {
callback(result.value);
}
}
concat(secondIterable) {
let concatIterable = new RangeIterable();
concatIterable.iterate = (options = NO_OPTIONS) => {
let iterator = (this.iterator = this.iterate(options));
let isFirst = true;
function iteratorDone(result) {
if (isFirst) {
try {
isFirst = false;
iterator =
secondIterable[
options.async ? Symbol.asyncIterator : Symbol.iterator
]();
result = iterator.next();
if (concatIterable.onDone) {
if (result.then) {
if (!options.async)
throw new Error(
'Can not synchronously iterate with promises as iterator results',
);
result.then(
(result) => {
if (result.done()) concatIterable.onDone();
},
(error) => {
this.return();
throw error;
},
);
} else if (result.done) concatIterable.onDone();
}
} catch (error) {
this.throw(error);
}
} else {
if (concatIterable.onDone) concatIterable.onDone();
}
return result;
}
return {
next() {
try {
let result = iterator.next();
if (result.then) {
if (!options.async)
throw new Error(
'Can not synchronously iterate with promises as iterator results',
);
return result.then((result) => {
if (result.done) return iteratorDone(result);
return result;
});
}
if (result.done) return iteratorDone(result);
return result;
} catch (error) {
this.throw(error);
}
},
return(value) {
if (!this.done) {
RETURN_DONE.value = value;
this.done = true;
if (concatIterable.onDone) concatIterable.onDone();
iterator.return();
}
return RETURN_DONE;
},
throw(error) {
if (options.continueOnRecoverableError) throw error;
this.return();
throw error;
},
};
};
return concatIterable;
}
flatMap(callback) {
let mappedIterable = new RangeIterable();
mappedIterable.iterate = (options = NO_OPTIONS) => {
let iterator = (this.iterator = this.iterate(options));
let isFirst = true;
let currentSubIterator;
return {
next(resolvedResult) {
try {
do {
if (currentSubIterator) {
let result;
if (resolvedResult) {
result = resolvedResult;
resolvedResult = undefined;
} else result = currentSubIterator.next();
if (result.then) {
if (!options.async)
throw new Error(
'Can not synchronously iterate with promises as iterator results',
);
return result.then((result) => this.next(result));
}
if (!result.done) {
return result;
}
}
let result;
if (resolvedResult != undefined) {
result = resolvedResult;
resolvedResult = undefined;
} else result = iterator.next();
if (result.then) {
if (!options.async)
throw new Error(
'Can not synchronously iterate with promises as iterator results',
);
currentSubIterator = undefined;
return result.then((result) => this.next(result));
}
if (result.done) {
if (mappedIterable.onDone) mappedIterable.onDone();
return result;
}
try {
let value = callback(result.value);
if (value?.then) {
if (!options.async)
throw new Error(
'Can not synchronously iterate with promises as iterator results',
);
return value.then(
(value) => {
if (
Array.isArray(value) ||
value instanceof RangeIterable
) {
currentSubIterator = value[Symbol.iterator]();
return this.next();
} else {
currentSubIterator = null;
return { value };
}
},
(error) => {
if (options.continueOnRecoverableError)
error.continueIteration = true;
this.throw(error);
},
);
}
if (Array.isArray(value) || value instanceof RangeIterable)
currentSubIterator = value[Symbol.iterator]();
else {
currentSubIterator = null;
return { value };
}
} catch (error) {
if (options.continueOnRecoverableError)
error.continueIteration = true;
throw error;
}
} while (true);
} catch (error) {
this.throw(error);
}
},
return() {
if (mappedIterable.onDone) mappedIterable.onDone();
if (currentSubIterator) currentSubIterator.return();
return iterator.return();
},
throw(error) {
if (options.continueOnRecoverableError) throw error;
if (mappedIterable.onDone) mappedIterable.onDone();
if (currentSubIterator) currentSubIterator.return();
this.return();
throw error;
},
};
};
return mappedIterable;
}
slice(start, end) {
let iterable = this.map((element, i) => {
if (i < start) return SKIP;
if (i >= end) {
DONE.value = element;
return DONE;
}
return element;
});
iterable.handleError = (error, i) => {
if (i < start) return SKIP;
if (i >= end) {
return DONE;
}
throw error;
};
return iterable;
}
mapError(catch_callback) {
let iterable = this.map((element) => {
return element;
});
let iterate = iterable.iterate;
iterable.iterate = (options = NO_OPTIONS) => {
// we need to ensure the whole stack
// of iterables is set up to handle recoverable errors and continue iteration
return iterate({ ...options, continueOnRecoverableError: true });
};
iterable.returnRecoverableErrors = catch_callback;
return iterable;
}
next() {
if (!this.iterator) this.iterator = this.iterate();
return this.iterator.next();
}
toJSON() {
if (this.asArray && this.asArray.forEach) {
return this.asArray;
}
const error = new Error(
'Can not serialize async iterables without first calling resolving asArray',
);
error.resolution = this.asArray;
throw error;
//return Array.from(this)
}
get asArray() {
if (this._asArray) return this._asArray;
let promise = new Promise((resolve, reject) => {
let iterator = this.iterate(true);
let array = [];
let iterable = this;
Object.defineProperty(array, 'iterable', { value: iterable });
function next(result) {
while (result.done !== true) {
if (result.then) {
return result.then(next);
} else {
array.push(result.value);
}
result = iterator.next();
}
resolve((iterable._asArray = array));
}
next(iterator.next());
});
promise.iterable = this;
return this._asArray || (this._asArray = promise);
}
resolveData() {
return this.asArray;
}
at(index) {
for (let entry of this) {
if (index-- === 0) return entry;
}
}
}
RangeIterable.prototype.DONE = DONE;