防撤回软件提交
This commit is contained in:
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1717043909505" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1498" fill="currentColor" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M910.222222 853.333333v56.888889h-284.444444v-56.888889h284.444444zM457.102222 72.419556c24.007111 0 47.104 4.778667 68.664889 14.222222 20.992 9.216 39.594667 21.845333 55.296 37.489778 15.644444 15.701333 28.273778 34.304 37.489778 55.352888 9.443556 21.504 14.222222 44.885333 14.222222 69.518223 0 17.976889-2.503111 35.043556-7.395555 51.143111h82.261333c25.713778 0 48.867556 9.784889 66.901333 28.273778 17.806222 18.261333 27.192889 41.358222 27.192889 66.673777V512h-56.888889V395.093333a36.693333 36.693333 0 0 0-7.054222-22.243555l-3.982222-4.721778a34.816 34.816 0 0 0-26.168889-11.093333H548.636444l22.357334-73.443556c3.242667-10.695111 4.892444-22.357333 4.892444-34.588444 0-16.725333-3.185778-32.426667-9.443555-46.705778a123.904 123.904 0 0 0-25.6-37.944889 122.88 122.88 0 0 0-37.944889-25.6 113.379556 113.379556 0 0 0-45.795556-9.443556c-16.099556 0-31.516444 3.185778-45.738666 9.443556a122.88 122.88 0 0 0-37.944889 25.6 123.050667 123.050667 0 0 0-25.656889 37.944889 115.484444 115.484444 0 0 0-9.386667 46.705778c0 11.889778 1.649778 23.210667 5.063111 33.735111l23.779556 74.296889H208.782222a36.238222 36.238222 0 0 0-26.794666 11.264 36.295111 36.295111 0 0 0-11.377778 26.794666v78.620445c0.568889 10.24 2.275556 14.165333 2.730666 15.018666 0.341333-0.227556 2.56-0.625778 5.916445-2.048a521.102222 521.102222 0 0 0 20.081778-9.443555l2.048-0.967111c1.706667-0.796444 4.551111-1.991111 8.988444-3.470222a134.200889 134.200889 0 0 1 106.609778-0.568889c16.497778 6.997333 31.061333 16.839111 43.406222 29.127111a136.305778 136.305778 0 0 1 39.822222 96.995555c0 18.944-3.584 36.864-10.695111 53.191111a137.216 137.216 0 0 1-72.533333 72.021334 135.964444 135.964444 0 0 1-53.532445 10.581333c-18.318222 0-36.465778-4.152889-54.044444-12.401778l-2.389333-0.398222-6.712889-3.015111c-1.706667-0.796444-5.176889-2.389333-10.865778-5.745778-8.704-5.12-12.288-5.290667-12.686222-5.290666 0.398222 0.284444-3.299556 3.128889-6.087111 12.913777v83.000889a34.702222 34.702222 0 0 0 11.150222 26.112 36.977778 36.977778 0 0 0 26.965333 11.093334l360.106667-0.056889v56.888889H208.782222c-22.186667 0-42.666667-7.168-59.619555-20.935112l-7.054223-6.257777A91.306667 91.306667 0 0 1 113.777778 803.214222V713.386667l0.967111-4.096c4.949333-20.707556 14.734222-36.807111 29.184-47.900445a53.418667 53.418667 0 0 1 32.824889-10.979555c13.084444 0 26.567111 4.266667 41.244444 13.027555 2.958222 1.706667 4.721778 2.56 5.632 3.015111l3.527111 0.568889 4.778667 2.446222c10.638222 5.233778 20.935111 7.793778 31.516444 7.793778a78.677333 78.677333 0 0 0 56.718223-23.096889 76.003556 76.003556 0 0 0 23.153777-55.808 78.506667 78.506667 0 0 0-23.153777-56.718222 78.734222 78.734222 0 0 0-56.718223-23.210667c-10.638222 0-20.878222 2.275556-31.288888 6.826667l-1.536 0.682667-1.592889 0.568889a26.396444 26.396444 0 0 0-4.209778 1.592889 426.666667 426.666667 0 0 1-22.869333 10.695111 74.524444 74.524444 0 0 1-36.579556 6.371555 54.272 54.272 0 0 1-39.310222-24.746666c-7.395556-11.093333-11.377778-25.429333-12.231111-45.056L113.777778 473.713778V395.093333c0-25.486222 9.671111-48.696889 27.989333-67.015111a93.184 93.184 0 0 1 67.015111-27.932444h80.497778a165.546667 165.546667 0 0 1-7.793778-51.143111c0-24.632889 4.778667-48.014222 14.222222-69.575111 9.216-20.992 21.788444-39.594667 37.489778-55.296 15.644444-15.644444 34.247111-28.273778 55.296-37.489778a169.528889 169.528889 0 0 1 68.608-14.222222zM910.222222 711.111111v56.888889h-284.444444v-56.888889h284.444444z m0-142.222222v56.888889h-284.444444v-56.888889h284.444444z" p-id="1499"></path></svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,141 @@
|
||||
<style>
|
||||
img.icon {
|
||||
width: 60px;
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
img.icon::after {
|
||||
content: "";
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: url(local://root/src/setting/static/default.png) no-repeat center;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
:host(.highlight-item) .plugin-item-info {
|
||||
animation: highlightAnimation 2s ease-out 1;
|
||||
}
|
||||
|
||||
/* 动画关键帧 */
|
||||
@keyframes highlightAnimation {
|
||||
0% {
|
||||
background-color: orange;
|
||||
box-shadow: 0 0 10px 10px orange;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-color: transparent;
|
||||
box-shadow: 0 0 1px 1px transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.authors {
|
||||
display: inline-flex;
|
||||
gap: 10px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.authors a {
|
||||
color: #81adef;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.manipulate {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.dependencies a {
|
||||
color: green;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.failed,
|
||||
.uninstall,
|
||||
.update,
|
||||
.inactive-tag,
|
||||
.newer-version,
|
||||
.dependencies,
|
||||
.lower4 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host([data-failed]) .failed {
|
||||
display: unset;
|
||||
}
|
||||
|
||||
:host([data-failed]) .success {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host([data-installed='1']) .uninstall {
|
||||
display: unset;
|
||||
}
|
||||
|
||||
:host([data-installed='1']) .install {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host([data-update='1']) .update {
|
||||
display: unset;
|
||||
}
|
||||
|
||||
:host([data-update='1']) .newer-version {
|
||||
display: inline;
|
||||
color: skyblue;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
:host([data-inactive='1']) .inactive-tag {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
:host([data-dependencies='1']) .dependencies {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
:host([data-lower4='1']) .lower4 {
|
||||
display: inline;
|
||||
color: var(--feedback_warning);
|
||||
padding-left: 5px;
|
||||
}
|
||||
</style>
|
||||
<setting-item data-direction="row">
|
||||
<div class="plugin-item-info">
|
||||
<div style="display: flex; gap: 10px; margin-bottom: 10px">
|
||||
<img class="icon" src="local://root/src/setting/static/default.png" />
|
||||
<div>
|
||||
<setting-text>
|
||||
<span class="title" title="插件名">插件名</span>
|
||||
<span class="inactive-tag">(未激活)</span>
|
||||
<span class="lower4" title="manifest版本小于4">(<4)</span>
|
||||
</setting-text>
|
||||
<setting-text data-type="secondary">
|
||||
<span class="description" title="插件描述">插件描述</span>
|
||||
</setting-text>
|
||||
</div>
|
||||
</div>
|
||||
<setting-text data-type="secondary">
|
||||
<div style="display: flex;gap: 5px 30px;flex-wrap: wrap;">
|
||||
<span>版本:<span class="version">版本</span><span class="newer-version"></span>
|
||||
</span>
|
||||
<span>开发:<span class="authors"></span></span>
|
||||
<span>类型:<span class="type"></span></span>
|
||||
<span>平台:<span class="platforms"></span></span>
|
||||
<span class="dependencies">依赖:<span class="items"></span></span>
|
||||
</div>
|
||||
</setting-text>
|
||||
</div>
|
||||
<div class="manipulate">
|
||||
<setting-button class="success update" data-type="primary">更新</setting-button>
|
||||
<setting-button class="success install" data-type="primary">安装</setting-button>
|
||||
<setting-button class="success uninstall" data-type="secondary">卸载</setting-button>
|
||||
<setting-button class="failed retry" data-type="primary">重试</setting-button>
|
||||
<setting-button class="detail" data-type="secondary">详情</setting-button>
|
||||
</div>
|
||||
</setting-item>
|
||||
@@ -0,0 +1,136 @@
|
||||
<setting-section>
|
||||
<style>
|
||||
[class^=list-dialog]::backdrop {
|
||||
background: rgba(0, 0, 100, 0.4);
|
||||
}
|
||||
|
||||
[class^=list-dialog] button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.mirror-select {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.manipulate {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.list-dialog-install {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
margin: 0;
|
||||
border-radius: 5px;
|
||||
width: 50%;
|
||||
background-color: var(--bg_white);
|
||||
color: var(--text_primary)
|
||||
}
|
||||
|
||||
.list-dialog {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
margin: 0;
|
||||
border-radius: 5px;
|
||||
width: 50%;
|
||||
background-color: var(--background-04);
|
||||
color: var(--text_primary)
|
||||
}
|
||||
|
||||
.list-dialog-content {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-direction: column;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
<setting-panel>
|
||||
<setting-list data-direction="column">
|
||||
<setting-item>
|
||||
<input type="text" id="list-search" placeholder="过滤下方插件,支持插件名、描述、作者名、版本号"
|
||||
style="height: 25px; width: 100%; color: inherit" />
|
||||
</setting-item>
|
||||
<setting-item data-direction="row">
|
||||
<div>
|
||||
<setting-text>
|
||||
<span>插件总数:<span class="total-text"></span></span>
|
||||
</setting-text>
|
||||
<setting-text data-type="secondary">
|
||||
刷新会禁用缓存并请求,所以会比较慢,可用来检查更新,建议不要频繁刷新
|
||||
</setting-text>
|
||||
</div>
|
||||
<div>
|
||||
<setting-button data-type="primary" class="refresh-btn">刷新</setting-button>
|
||||
</div>
|
||||
</setting-item>
|
||||
<setting-item data-direction="row">
|
||||
<div>
|
||||
<setting-text>是否使用镜像</setting-text>
|
||||
<setting-text data-type="secondary">设置系统代理时建议关闭</setting-text>
|
||||
</div>
|
||||
<div class="manipulate">
|
||||
<!-- <div>
|
||||
不使用带缓存的镜像
|
||||
<setting-switch class="mirror-no-cache"></setting-switch>
|
||||
</div> -->
|
||||
<setting-switch class="mirror-switch" is-active></setting-switch>
|
||||
<setting-button class="mirror-add-btn" data-type="secondary">修改镜像</setting-button>
|
||||
<!-- <setting-select class="mirror-select" style="width: 150px;">
|
||||
<setting-option data-value="1" is-selected>不使用镜像</setting-option>
|
||||
<setting-option data-value="2">2</setting-option>
|
||||
<setting-option data-value="3">3</setting-option>
|
||||
</setting-select> -->
|
||||
</div>
|
||||
</setting-item>
|
||||
</setting-list>
|
||||
</setting-panel>
|
||||
</setting-section>
|
||||
|
||||
<setting-section data-title="插件列表">
|
||||
<setting-panel>
|
||||
<setting-list data-direction="column" id="plugin-list"> </setting-list>
|
||||
</setting-panel>
|
||||
</setting-section>
|
||||
<dialog class="list-dialog-install">
|
||||
<div style="font-size: large; display: flex; justify-content: space-between">
|
||||
选择包来源
|
||||
<button class="list-dialog-install-btn-close q-button q-button--secondary default-btn"
|
||||
style="width: 25px">×</button>
|
||||
</div>
|
||||
<hr />
|
||||
<div style="color: red;margin-bottom: 10px;">注意:安装/更新 时会删除插件原文件(不包括配置),如果有修改过的文件,请先备份或者使用 git 进行安装/更新</div>
|
||||
<div style="display: flex; justify-content: space-around">
|
||||
<div style="display: flex; flex-direction: column; gap: 10px">
|
||||
<setting-button class="list-dialog-btn-repo" data-type="secondary">使用仓库代码包</button>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 10px">
|
||||
<setting-button class="list-dialog-btn-release" data-type="secondary">使用release包</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="list-dialog">
|
||||
<div style="font-size: large; display: flex; justify-content: space-between">
|
||||
<span class="list-dialog-title">标题</span>
|
||||
<button class="list-dialog-btn-close q-button q-button--secondary default-btn" style="width: 25px">×</button>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="list-dialog-content">
|
||||
<div>content</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div style="display: flex; justify-content: flex-end; gap: 20px;">
|
||||
<setting-button class="list-dialog-btn-confirm" data-type="primary">确认</setting-button>
|
||||
<setting-button class="list-dialog-btn-cancel" data-type="secondary">取消</setting-button>
|
||||
</div>
|
||||
</dialog>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "./manifest_schema.json",
|
||||
"manifest_version": 4,
|
||||
"type": "extension",
|
||||
"name": "插件列表查看",
|
||||
"slug": "list-viewer",
|
||||
"description": "插件列表查看·安装·更新",
|
||||
"icon": "assets/icon.svg",
|
||||
"thumb": "assets/icon.svg",
|
||||
"version": "1.2.1",
|
||||
"authors": [
|
||||
{
|
||||
"name": "ltxhhz",
|
||||
"link": "https://github.com/ltxhhz"
|
||||
}
|
||||
],
|
||||
"platform": [
|
||||
"win32",
|
||||
"linux",
|
||||
"darwin"
|
||||
],
|
||||
"injects": {
|
||||
"main": "./main/index.js",
|
||||
"preload": "./preload/index.js",
|
||||
"renderer": "./renderer/index.js"
|
||||
},
|
||||
"repository": {
|
||||
"repo": "ltxhhz/LL-plugin-list-viewer",
|
||||
"branch": "master"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
"use strict";const r=require("electron"),t=r.contextBridge.exposeInMainWorld;t("ListViewer",{getPkg:(e,i)=>r.ipcRenderer.invoke("LiteLoader.ListViewer.getPkg",e,i),removePkg:e=>r.ipcRenderer.invoke("LiteLoader.ListViewer.removePkg",e),log:(...e)=>r.ipcRenderer.send("LiteLoader.ListViewer.removePkg",...e)});
|
||||
@@ -0,0 +1,857 @@
|
||||
const semver = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
|
||||
const validateAndParse = (version) => {
|
||||
if (typeof version !== "string") {
|
||||
throw new TypeError("Invalid argument expected string");
|
||||
}
|
||||
const match = version.match(semver);
|
||||
if (!match) {
|
||||
throw new Error(`Invalid argument not valid semver ('${version}' received)`);
|
||||
}
|
||||
match.shift();
|
||||
return match;
|
||||
};
|
||||
const isWildcard = (s) => s === "*" || s === "x" || s === "X";
|
||||
const tryParse = (v) => {
|
||||
const n = parseInt(v, 10);
|
||||
return isNaN(n) ? v : n;
|
||||
};
|
||||
const forceType = (a, b) => typeof a !== typeof b ? [String(a), String(b)] : [a, b];
|
||||
const compareStrings = (a, b) => {
|
||||
if (isWildcard(a) || isWildcard(b))
|
||||
return 0;
|
||||
const [ap, bp] = forceType(tryParse(a), tryParse(b));
|
||||
if (ap > bp)
|
||||
return 1;
|
||||
if (ap < bp)
|
||||
return -1;
|
||||
return 0;
|
||||
};
|
||||
const compareSegments = (a, b) => {
|
||||
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
||||
const r = compareStrings(a[i] || "0", b[i] || "0");
|
||||
if (r !== 0)
|
||||
return r;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
const compareVersions = (v1, v2) => {
|
||||
const n1 = validateAndParse(v1);
|
||||
const n2 = validateAndParse(v2);
|
||||
const p1 = n1.pop();
|
||||
const p2 = n2.pop();
|
||||
const r = compareSegments(n1, n2);
|
||||
if (r !== 0)
|
||||
return r;
|
||||
if (p1 && p2) {
|
||||
return compareSegments(p1.split("."), p2.split("."));
|
||||
} else if (p1 || p2) {
|
||||
return p1 ? -1 : 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
const compare = (v1, v2, operator) => {
|
||||
assertValidOperator(operator);
|
||||
const res = compareVersions(v1, v2);
|
||||
return operatorResMap[operator].includes(res);
|
||||
};
|
||||
const operatorResMap = {
|
||||
">": [1],
|
||||
">=": [0, 1],
|
||||
"=": [0],
|
||||
"<=": [-1, 0],
|
||||
"<": [-1],
|
||||
"!=": [-1, 1]
|
||||
};
|
||||
const allowedOperators = Object.keys(operatorResMap);
|
||||
const assertValidOperator = (op) => {
|
||||
if (typeof op !== "string") {
|
||||
throw new TypeError(`Invalid operator type, expected string but got ${typeof op}`);
|
||||
}
|
||||
if (allowedOperators.indexOf(op) === -1) {
|
||||
throw new Error(`Invalid operator, expected one of ${allowedOperators.join("|")}`);
|
||||
}
|
||||
};
|
||||
class Node {
|
||||
value;
|
||||
next;
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
class Queue {
|
||||
#head;
|
||||
#tail;
|
||||
#size;
|
||||
constructor() {
|
||||
this.clear();
|
||||
}
|
||||
enqueue(value) {
|
||||
const node = new Node(value);
|
||||
if (this.#head) {
|
||||
this.#tail.next = node;
|
||||
this.#tail = node;
|
||||
} else {
|
||||
this.#head = node;
|
||||
this.#tail = node;
|
||||
}
|
||||
this.#size++;
|
||||
}
|
||||
dequeue() {
|
||||
const current = this.#head;
|
||||
if (!current) {
|
||||
return;
|
||||
}
|
||||
this.#head = this.#head.next;
|
||||
this.#size--;
|
||||
return current.value;
|
||||
}
|
||||
clear() {
|
||||
this.#head = void 0;
|
||||
this.#tail = void 0;
|
||||
this.#size = 0;
|
||||
}
|
||||
get size() {
|
||||
return this.#size;
|
||||
}
|
||||
*[Symbol.iterator]() {
|
||||
let current = this.#head;
|
||||
while (current) {
|
||||
yield current.value;
|
||||
current = current.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
const AsyncResource = {
|
||||
bind(fn, _type, thisArg) {
|
||||
return fn.bind(thisArg);
|
||||
}
|
||||
};
|
||||
function pLimit(concurrency) {
|
||||
if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
|
||||
throw new TypeError("Expected `concurrency` to be a number from 1 and up");
|
||||
}
|
||||
const queue = new Queue();
|
||||
let activeCount = 0;
|
||||
const next = () => {
|
||||
activeCount--;
|
||||
if (queue.size > 0) {
|
||||
queue.dequeue()();
|
||||
}
|
||||
};
|
||||
const run = async (function_, resolve, arguments_) => {
|
||||
activeCount++;
|
||||
const result = (async () => function_(...arguments_))();
|
||||
resolve(result);
|
||||
try {
|
||||
await result;
|
||||
} catch {
|
||||
}
|
||||
next();
|
||||
};
|
||||
const enqueue = (function_, resolve, arguments_) => {
|
||||
queue.enqueue(
|
||||
AsyncResource.bind(run.bind(void 0, function_, resolve, arguments_))
|
||||
);
|
||||
(async () => {
|
||||
await Promise.resolve();
|
||||
if (activeCount < concurrency && queue.size > 0) {
|
||||
queue.dequeue()();
|
||||
}
|
||||
})();
|
||||
};
|
||||
const generator = (function_, ...arguments_) => new Promise((resolve) => {
|
||||
enqueue(function_, resolve, arguments_);
|
||||
});
|
||||
Object.defineProperties(generator, {
|
||||
activeCount: {
|
||||
get: () => activeCount
|
||||
},
|
||||
pendingCount: {
|
||||
get: () => queue.size
|
||||
},
|
||||
clearQueue: {
|
||||
value() {
|
||||
queue.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
return generator;
|
||||
}
|
||||
const hostReg = /^https?:\/\/[^/]+/;
|
||||
const originMirrors = ["https://mirror.ghproxy.com/", "https://ghproxy.net/", "https://github.moeyy.xyz/"];
|
||||
const thisSlug = "list-viewer";
|
||||
let config;
|
||||
async function initConfig() {
|
||||
const defaultConfig = {
|
||||
inactivePlugins: [],
|
||||
debug: false,
|
||||
useMirror: true,
|
||||
mirrors: {
|
||||
downloadUrl: ["https://cdn.jsdelivr.net/gh"]
|
||||
// rawUrl: []
|
||||
}
|
||||
};
|
||||
config = await LiteLoader.api.config.get(thisSlug, defaultConfig);
|
||||
const save = debounce((obj) => {
|
||||
LiteLoader.api.config.set(thisSlug, obj);
|
||||
}, 1e3);
|
||||
config = deepWatch(config, () => {
|
||||
save(config);
|
||||
});
|
||||
}
|
||||
function getRandomItem(arr) {
|
||||
return arr ? arr[Math.floor(Math.random() * arr.length)] : void 0;
|
||||
}
|
||||
function useMirror(url2, mirror, removeHost = true) {
|
||||
if (/\/gh$/.test(mirror) && mirror.includes("jsdelivr")) {
|
||||
return mirror + url2.replace(hostReg, "").replace("/raw/", "@").replace(/@v(?!v)/, "@vv");
|
||||
} else {
|
||||
return mirror + (removeHost ? url2.replace(hostReg, "") : url2);
|
||||
}
|
||||
}
|
||||
function localFetch(path, plugin = "list-viewer") {
|
||||
return fetch(`local:///${LiteLoader.plugins[plugin].path.plugin.replace(":\\", "://").replaceAll("\\", "/")}/${path.startsWith("/") ? path.slice(1) : path}`);
|
||||
}
|
||||
function fetchWithTimeout(url2, options, timeout = 3e3) {
|
||||
config.debug && console.log("fetchWithTimeout", url2);
|
||||
return new Promise((resolve, reject) => {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => {
|
||||
controller.abort();
|
||||
reject(new Error("请求超时"));
|
||||
}, timeout);
|
||||
fetch(url2, { ...options, signal: controller.signal }).then((response) => {
|
||||
clearTimeout(timeoutId);
|
||||
resolve(response);
|
||||
}).catch((error) => {
|
||||
clearTimeout(timeoutId);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
function debounce(func, delay) {
|
||||
let timeoutId;
|
||||
return function(...args) {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
timeoutId = setTimeout(() => {
|
||||
func(args);
|
||||
}, delay);
|
||||
};
|
||||
}
|
||||
function deepWatch(obj, callback) {
|
||||
const observer = new Proxy(obj, {
|
||||
set(target, key, value, receiver) {
|
||||
const oldValue = target[key];
|
||||
if (oldValue !== value) {
|
||||
callback();
|
||||
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
||||
deepWatch(value, callback);
|
||||
}
|
||||
}
|
||||
return Reflect.set(target, key, value, receiver);
|
||||
}
|
||||
});
|
||||
return observer;
|
||||
}
|
||||
const listUrl = {
|
||||
repo: "LiteLoaderQQNT/Plugin-List",
|
||||
branch: "v4",
|
||||
file: "plugins.json"
|
||||
};
|
||||
const defaultIcon = "local://root/src/setting/static/default.png";
|
||||
const domParser = new DOMParser();
|
||||
const typeMap = {
|
||||
extension: "扩展",
|
||||
theme: "主题",
|
||||
framework: "框架"
|
||||
};
|
||||
let pluginList;
|
||||
let currentItem;
|
||||
let currentManifest;
|
||||
let showDialog;
|
||||
let filterInput;
|
||||
function onSettingWindowCreated(view) {
|
||||
initConfig();
|
||||
localFetch("/assets/view.html").then((e) => e.text()).then(async (res) => {
|
||||
const doms = domParser.parseFromString(res, "text/html");
|
||||
filterInput = doms.querySelector("#list-search");
|
||||
const refreshBtn = doms.querySelector(".refresh-btn");
|
||||
const totalEl = doms.querySelector(".total-text");
|
||||
const dialogInstall = doms.querySelector(".list-dialog-install");
|
||||
const dialogInstallClose = doms.querySelector(".list-dialog-install-btn-close");
|
||||
let resFunc;
|
||||
dialogInstallClose.addEventListener("click", () => {
|
||||
dialogInstall.close();
|
||||
resFunc();
|
||||
});
|
||||
const useRepo = doms.querySelector(".list-dialog-btn-repo");
|
||||
useRepo.addEventListener("click", () => {
|
||||
dialogInstall.close();
|
||||
resFunc(false);
|
||||
});
|
||||
const useRelease = doms.querySelector(".list-dialog-btn-release");
|
||||
useRelease.addEventListener("click", () => {
|
||||
dialogInstall.close();
|
||||
resFunc(true);
|
||||
});
|
||||
let dialogResolve;
|
||||
const dialog = doms.querySelector(".list-dialog");
|
||||
const dialogClose = doms.querySelector(".list-dialog-btn-close");
|
||||
dialogClose.addEventListener("click", () => {
|
||||
dialog.close();
|
||||
dialogResolve();
|
||||
});
|
||||
const dialogTitle = doms.querySelector(".list-dialog-title");
|
||||
const dialogContent = doms.querySelector(".list-dialog-content");
|
||||
const dialogConfirm = doms.querySelector(".list-dialog-btn-confirm");
|
||||
dialogConfirm.addEventListener("click", () => {
|
||||
dialog.close();
|
||||
dialogResolve(true);
|
||||
});
|
||||
const dialogCancel = doms.querySelector(".list-dialog-btn-cancel");
|
||||
dialogCancel.addEventListener("click", () => {
|
||||
dialog.close();
|
||||
dialogResolve(false);
|
||||
});
|
||||
showDialog = (option) => {
|
||||
let dialogInput;
|
||||
dialogTitle.innerText = option.title;
|
||||
if (option.type === "confirm" || option.type === "message") {
|
||||
if (option.message) {
|
||||
dialogContent.innerText = option.message;
|
||||
}
|
||||
if (option.content) {
|
||||
if (typeof option.content === "string") {
|
||||
dialogContent.innerHTML = option.content;
|
||||
} else {
|
||||
dialogContent.replaceChildren(option.content);
|
||||
}
|
||||
}
|
||||
if (option.type === "message") {
|
||||
dialogCancel.style.display = "none";
|
||||
}
|
||||
} else if (option.type === "prompt") {
|
||||
dialogInput = option.textarea ? document.createElement("textarea") : document.createElement("input");
|
||||
dialogInput.placeholder = option.placeholder ?? "请输入内容";
|
||||
dialogInput.value = option.default ?? "";
|
||||
dialogInput.style.width = "100%";
|
||||
dialogInput.style.background = "var(--background_02)";
|
||||
dialogInput.style.color = "var(--bg_white_light)";
|
||||
dialogInput.style.marginTop = "10px";
|
||||
dialogContent.replaceChildren(option.message || "", dialogInput);
|
||||
}
|
||||
dialogConfirm.innerText = option.confirm || "确定";
|
||||
dialogCancel.innerText = option.cancel || "取消";
|
||||
return new Promise((resolve) => {
|
||||
dialog.showModal();
|
||||
dialogResolve = (bool) => {
|
||||
if (option.type === "prompt") {
|
||||
resolve(bool ? dialogInput.value : "");
|
||||
} else {
|
||||
resolve(bool);
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
const mirrorSwitch = doms.querySelector(".mirror-switch");
|
||||
mirrorSwitch.toggleAttribute("is-active", config.useMirror);
|
||||
mirrorSwitch.onclick = () => {
|
||||
const isActive = mirrorSwitch.hasAttribute("is-active");
|
||||
mirrorSwitch.toggleAttribute("is-active", !isActive);
|
||||
config.useMirror = !isActive;
|
||||
};
|
||||
const mirrorAddBtn = doms.querySelector(".mirror-add-btn");
|
||||
mirrorAddBtn.onclick = () => {
|
||||
showDialog({
|
||||
title: "添加镜像",
|
||||
type: "prompt",
|
||||
placeholder: "请输入镜像地址,每行一个",
|
||||
message: `请输入镜像地址,每行一个,如果代理方式是完整url在地址后面,比如
|
||||
https://mirror/https://github.com/xx/xx,则需要写
|
||||
https://mirror/https://github.com/
|
||||
如果代理方式是 path在地址后面,比如
|
||||
https://mirror/xx/xx,则需要写https://mirror
|
||||
jsdelivr镜像直接按默认那个写就行
|
||||
内置三个镜像'https://mirror.ghproxy.com', 'https://ghproxy.net', 'https://github.moeyy.xyz'
|
||||
使用时默认优先使用第一个,如果没有响应才会使用其他镜像`,
|
||||
textarea: true,
|
||||
default: config.mirrors.downloadUrl.join("\n")
|
||||
}).then((res2) => {
|
||||
if (res2) {
|
||||
config.mirrors.downloadUrl = res2.split("\n");
|
||||
}
|
||||
});
|
||||
};
|
||||
doms.body.childNodes.forEach((dom) => {
|
||||
view.appendChild(dom);
|
||||
});
|
||||
const showInstallDialog = () => new Promise((resolve) => {
|
||||
dialogInstall.showModal();
|
||||
resFunc = resolve;
|
||||
});
|
||||
createItemComponent(await localFetch("/assets/list-item.html").then((e) => e.text()), showInstallDialog);
|
||||
const pluginListDom = view.querySelector("#plugin-list");
|
||||
const getList1 = (noCache = false) => {
|
||||
refreshBtn.setAttribute("is-disabled", "");
|
||||
getList(noCache).then(async (list) => {
|
||||
pluginList = list;
|
||||
totalEl.innerText = list.length.toString();
|
||||
const promArr = [];
|
||||
const limit = pLimit(3);
|
||||
list.forEach((plugin, i) => {
|
||||
const dom = document.createElement("plugin-item");
|
||||
pluginListDom.appendChild(dom);
|
||||
promArr.push(
|
||||
limit(async () => {
|
||||
const manifest = await getManifest(plugin, noCache);
|
||||
dom.dataset.index = i + "";
|
||||
config.debug && console.log(plugin, manifest);
|
||||
updateElProp(dom, manifest, plugin.repo);
|
||||
})
|
||||
);
|
||||
});
|
||||
return Promise.all(promArr);
|
||||
}).finally(() => {
|
||||
refreshBtn.removeAttribute("is-disabled");
|
||||
});
|
||||
};
|
||||
refreshBtn.addEventListener("click", () => {
|
||||
pluginListDom.replaceChildren();
|
||||
getList1(true);
|
||||
});
|
||||
getList1();
|
||||
}).catch(console.error);
|
||||
}
|
||||
function createItemComponent(innerHtml, showInstallDialog) {
|
||||
class PluginListClass extends HTMLElement {
|
||||
titleEl;
|
||||
descriptionEl;
|
||||
versionEl;
|
||||
authorsEl;
|
||||
manipulateEl;
|
||||
iconEl;
|
||||
updateBtnEl;
|
||||
installBtnEl;
|
||||
uninstallBtnEl;
|
||||
detailBtnEl;
|
||||
retryBtnEl;
|
||||
typeEl;
|
||||
dependenciesItemsEl;
|
||||
platformsEl;
|
||||
manifest = null;
|
||||
#initPromise;
|
||||
#initPromiseResolve;
|
||||
#initialized = false;
|
||||
constructor() {
|
||||
super();
|
||||
const shadow = this.attachShadow({ mode: "open" });
|
||||
shadow.innerHTML = innerHtml;
|
||||
this.#initPromise = new Promise((resolve) => {
|
||||
this.#initPromiseResolve = resolve;
|
||||
if (this.#initialized)
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
connectedCallback() {
|
||||
this.titleEl = this.shadowRoot.querySelector(".title");
|
||||
this.descriptionEl = this.shadowRoot.querySelector(".description");
|
||||
this.versionEl = this.shadowRoot.querySelector(".version");
|
||||
this.authorsEl = this.shadowRoot.querySelector(".authors");
|
||||
this.manipulateEl = this.shadowRoot.querySelector(".manipulate");
|
||||
this.iconEl = this.shadowRoot.querySelector(".icon");
|
||||
this.typeEl = this.shadowRoot.querySelector(".type");
|
||||
this.dependenciesItemsEl = this.shadowRoot.querySelector(".dependencies>.items");
|
||||
this.platformsEl = this.shadowRoot.querySelector(".platforms");
|
||||
this.updateBtnEl = this.shadowRoot.querySelector(".update");
|
||||
const installEvent = async (update = false) => {
|
||||
currentItem = pluginList[Number(this.dataset.index)];
|
||||
currentManifest = this.manifest;
|
||||
showInstallDialog().then((res) => {
|
||||
if (res !== void 0) {
|
||||
if (update) {
|
||||
this.updateBtnEl.setAttribute("is-disabled", "");
|
||||
this.updateBtnEl.innerText = "安装中...";
|
||||
} else {
|
||||
this.installBtnEl.setAttribute("is-disabled", "");
|
||||
this.installBtnEl.innerText = "安装中...";
|
||||
}
|
||||
install(res).then((res2) => {
|
||||
if (res2.success) {
|
||||
this.dataset.installed = "1";
|
||||
this.dataset.inactive = "1";
|
||||
if (update)
|
||||
delete this.dataset.update;
|
||||
config.inactivePlugins.push(this.manifest.slug);
|
||||
this.updateOpenDirEvent();
|
||||
} else {
|
||||
showDialog({ title: "安装失败", message: res2.message, type: "message" });
|
||||
}
|
||||
}).catch((e) => {
|
||||
showDialog({ title: "安装失败", message: e.message, type: "message" });
|
||||
}).finally(() => {
|
||||
if (update) {
|
||||
this.updateBtnEl.removeAttribute("is-disabled");
|
||||
this.updateBtnEl.innerText = "更新";
|
||||
} else {
|
||||
this.installBtnEl.removeAttribute("is-disabled");
|
||||
this.installBtnEl.innerText = "安装";
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
this.updateBtnEl.addEventListener("click", () => installEvent(true));
|
||||
this.installBtnEl = this.shadowRoot.querySelector(".install");
|
||||
this.installBtnEl.addEventListener("click", () => installEvent());
|
||||
this.uninstallBtnEl = this.shadowRoot.querySelector(".uninstall");
|
||||
this.uninstallBtnEl.addEventListener("click", async () => {
|
||||
config.debug && console.log("uninstall", this.manifest.name);
|
||||
currentItem = pluginList[Number(this.dataset.index)];
|
||||
currentManifest = this.manifest;
|
||||
showDialog({ title: "卸载", message: `确定要卸载插件 ${this.manifest.name} 吗?`, type: "confirm" }).then((e) => {
|
||||
if (e) {
|
||||
this.uninstallBtnEl.innerText = "卸载中...";
|
||||
this.uninstallBtnEl.setAttribute("is-disabled", "");
|
||||
uninstall().then((res) => {
|
||||
if (res.success) {
|
||||
this.uninstallBtnEl.innerText = "卸载";
|
||||
this.uninstallBtnEl.removeAttribute("is-disabled");
|
||||
delete this.dataset.installed;
|
||||
if (config.inactivePlugins.includes(this.manifest.slug)) {
|
||||
this.dataset.inactive = "0";
|
||||
config.inactivePlugins = config.inactivePlugins.filter((e2) => e2 !== this.manifest.slug);
|
||||
}
|
||||
this.updateOpenDirEvent();
|
||||
} else {
|
||||
showDialog({ title: "卸载失败", message: res.message, type: "message" });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
this.retryBtnEl = this.shadowRoot.querySelector(".retry");
|
||||
this.retryBtnEl.addEventListener("click", async () => {
|
||||
this.retryBtnEl.innerText = "重试中...";
|
||||
this.retryBtnEl.setAttribute("is-disabled", "");
|
||||
const manifest = await getManifest(pluginList[Number(this.dataset.index)]);
|
||||
this.manifest = manifest;
|
||||
updateElProp(this, manifest, this.dataset.failed);
|
||||
this.retryBtnEl.innerText = "重试";
|
||||
this.retryBtnEl.removeAttribute("is-disabled");
|
||||
});
|
||||
this.detailBtnEl = this.shadowRoot.querySelector(".detail");
|
||||
this.detailBtnEl.addEventListener("click", async () => {
|
||||
LiteLoader.api.openExternal(`https://github.com/${pluginList[Number(this.dataset.index)].repo}/tree/${pluginList[Number(this.dataset.index)].branch}`);
|
||||
});
|
||||
filterInput.addEventListener("input", () => this.updateHidden());
|
||||
this.updateHidden();
|
||||
this.updateOpenDirEvent();
|
||||
this.#initialized = true;
|
||||
this.#initPromiseResolve?.();
|
||||
}
|
||||
static get observedAttributes() {
|
||||
return ["data-name", "data-version", "data-description", "data-authors", "data-icon", "data-failed", "data-type", "data-dependencies", "data-platforms"];
|
||||
}
|
||||
attributeChangedCallback(name, _oldValue, newValue) {
|
||||
this.#initPromise.then(() => {
|
||||
try {
|
||||
switch (name) {
|
||||
case "data-failed":
|
||||
if (newValue) {
|
||||
this.titleEl.innerText = newValue;
|
||||
this.titleEl.style.color = "red";
|
||||
this.descriptionEl.innerText = "获取失败";
|
||||
} else {
|
||||
this.titleEl.style.removeProperty("color");
|
||||
}
|
||||
break;
|
||||
case "data-name":
|
||||
this.titleEl.innerText = newValue || "插件名";
|
||||
this.titleEl.title = newValue || "";
|
||||
break;
|
||||
case "data-version":
|
||||
this.versionEl.innerText = newValue || "版本";
|
||||
this.versionEl.title = newValue || "";
|
||||
break;
|
||||
case "data-description":
|
||||
this.descriptionEl.innerText = newValue || "插件描述";
|
||||
this.descriptionEl.title = newValue || "";
|
||||
break;
|
||||
case "data-authors": {
|
||||
const arr = newValue === "1" ? this.manifest.authors : [];
|
||||
this.authorsEl.append(
|
||||
...arr.map((author) => {
|
||||
const a = document.createElement("a");
|
||||
a.title = author.link;
|
||||
a.innerText = author.name;
|
||||
a.onclick = () => LiteLoader.api.openExternal(author.link);
|
||||
return a;
|
||||
}).reduce((p, v, i) => {
|
||||
p[i * 2] = v;
|
||||
if (i)
|
||||
p[i * 2 - 1] = " | ";
|
||||
return p;
|
||||
}, [])
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "data-type":
|
||||
this.typeEl.innerText = typeMap[newValue + ""] || "unknown";
|
||||
break;
|
||||
case "data-platforms":
|
||||
this.platformsEl.title = this.platformsEl.innerText = newValue || "";
|
||||
break;
|
||||
case "data-dependencies": {
|
||||
const arr = newValue === "1" ? this.manifest.dependencies : [];
|
||||
this.dependenciesItemsEl.append(
|
||||
...arr.map((e) => {
|
||||
const a = document.createElement("a");
|
||||
a.title = e;
|
||||
a.innerText = e;
|
||||
a.onclick = () => {
|
||||
const item = document.getElementById(`item-${e}`);
|
||||
if (item) {
|
||||
item.scrollIntoView?.();
|
||||
item.classList.add("highlight-item");
|
||||
setTimeout(() => {
|
||||
item.classList.remove("highlight-item");
|
||||
}, 2e3);
|
||||
}
|
||||
};
|
||||
return a;
|
||||
}).reduce((p, v, i) => {
|
||||
p[i * 2] = v;
|
||||
if (i)
|
||||
p[i * 2 - 1] = " | ";
|
||||
return p;
|
||||
}, [])
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "data-icon": {
|
||||
const [src, src1] = (newValue || "").split(",");
|
||||
this.iconEl.src = src || defaultIcon;
|
||||
let num = 0;
|
||||
this.iconEl.addEventListener("error", () => {
|
||||
if (src1 && num < 3) {
|
||||
const iconPath = this.manifest.icon.replace(/^\.?\//, "");
|
||||
switch (num) {
|
||||
case 0:
|
||||
this.iconEl.src = src.replace(iconPath, `src/${iconPath}`);
|
||||
break;
|
||||
case 1:
|
||||
this.iconEl.src = src1;
|
||||
break;
|
||||
case 2:
|
||||
this.iconEl.src = src1.replace(iconPath, `src/${iconPath}`);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this.iconEl.src = defaultIcon;
|
||||
}
|
||||
num++;
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(this.dataset.name || this.dataset.failed, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
updateOpenDirEvent() {
|
||||
if (this.manifest && LiteLoader.plugins[this.manifest.slug] && this.dataset.installed === "1") {
|
||||
this.titleEl.title = "点击打开插件所在目录";
|
||||
this.titleEl.addEventListener("click", () => {
|
||||
LiteLoader.api.openPath(LiteLoader.plugins[this.manifest.slug].path.plugin);
|
||||
});
|
||||
this.titleEl.style.cursor = "pointer";
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 过滤用,判断是否应该隐藏
|
||||
*/
|
||||
updateHidden() {
|
||||
try {
|
||||
const authors = this.dataset.authors === "1" ? this.manifest.authors : [];
|
||||
const str = (this.dataset.name || "") + (this.dataset.version || "") + (this.dataset.description || "") + (this.dataset.version || "") + authors.map((e) => e.name).join("");
|
||||
if (!filterInput.value || str.toLowerCase().includes(filterInput.value.toLowerCase())) {
|
||||
this.hidden = false;
|
||||
} else {
|
||||
this.hidden = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(this.manifest?.slug || this.dataset.name, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define("plugin-item", PluginListClass);
|
||||
return new PluginListClass();
|
||||
}
|
||||
function updateElProp(el, manifest, repo) {
|
||||
if (manifest) {
|
||||
el.id = `item-${manifest.slug}`;
|
||||
el.dataset.name = manifest.name;
|
||||
el.manifest = manifest;
|
||||
el.updateOpenDirEvent();
|
||||
el.dataset.description = manifest.description;
|
||||
el.dataset.lower4 = Number(manifest.manifest_version) >= 4 ? "" : "1";
|
||||
el.dataset.authors = manifest.authors ? "1" : "";
|
||||
el.dataset.platforms = manifest.platform.join("|");
|
||||
el.dataset.installed = LiteLoader.plugins[manifest.slug] ? "1" : "";
|
||||
el.dataset.slug = manifest.slug;
|
||||
el.dataset.icon = getIconUrls(pluginList[Number(el.dataset.index)], manifest).toString();
|
||||
el.dataset.defaultIcon = defaultIcon;
|
||||
el.dataset.type = manifest.type;
|
||||
el.dataset.dependencies = manifest.dependencies ? "1" : "";
|
||||
delete el.dataset.failed;
|
||||
if (LiteLoader.plugins[manifest.slug]) {
|
||||
el.dataset.version = LiteLoader.plugins[manifest.slug].manifest.version;
|
||||
config.debug && console.log(manifest.slug, LiteLoader.plugins[manifest.slug], manifest);
|
||||
el.dataset.update = compare(manifest.version, LiteLoader.plugins[manifest.slug]?.manifest?.version ?? manifest.version, ">") ? "1" : "";
|
||||
el.shadowRoot.querySelector(".newer-version").innerText = `-> ${manifest.version}`;
|
||||
} else {
|
||||
el.dataset.version = manifest.version;
|
||||
}
|
||||
if (config.inactivePlugins.includes(manifest.slug)) {
|
||||
if (!LiteLoader.plugins[manifest.slug]) {
|
||||
el.dataset.inactive = "1";
|
||||
} else {
|
||||
config.inactivePlugins = config.inactivePlugins.filter((v) => v !== manifest.slug);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
el.dataset.failed = repo;
|
||||
}
|
||||
}
|
||||
async function getList(noCache = false, again = false) {
|
||||
let url;
|
||||
if (config.useMirror) {
|
||||
const m = getGithubMirror(!again);
|
||||
url = useMirror(`https://github.com/${listUrl.repo}/raw/${listUrl.branch}/${listUrl.file}`, m || getRandomItem(originMirrors), !!m);
|
||||
} else {
|
||||
url = `https://github.com/${listUrl.repo}/raw/${listUrl.branch}/${listUrl.file}`;
|
||||
}
|
||||
return await fetchWithTimeout(url, {
|
||||
cache: noCache ? "no-cache" : "default"
|
||||
}).then((res) => res.status === 200 ? res.json() : null).catch((err) => {
|
||||
if (again) {
|
||||
console.error(`getList ${url}`, err);
|
||||
return null;
|
||||
} else {
|
||||
console.warn(`getList ${url}`, err);
|
||||
return getList(noCache, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
async function getManifest(item, noCache = false, again = false) {
|
||||
let url;
|
||||
let m = getGithubMirror(!again);
|
||||
if (config.useMirror) {
|
||||
url = useMirror(`https://github.com/${item.repo}/raw/${item.branch}/manifest.json`, m || getRandomItem(originMirrors), !!m);
|
||||
} else {
|
||||
url = `https://github.com/${item.repo}/raw/${item.branch}/manifest.json`;
|
||||
}
|
||||
return await fetchWithTimeout(url, {
|
||||
cache: noCache ? "no-cache" : "default"
|
||||
}).then((res) => {
|
||||
if (res.status === 200) {
|
||||
return res.json();
|
||||
} else {
|
||||
m = getGithubMirror(!again);
|
||||
if (config.useMirror) {
|
||||
url = useMirror(`https://github.com/${item.repo}/raw/${item.branch}/package.json`, m || getRandomItem(originMirrors), !!m);
|
||||
} else {
|
||||
url = `https://github.com/${item.repo}/raw/${item.branch}/package.json`;
|
||||
}
|
||||
return fetchWithTimeout(url, {
|
||||
cache: noCache ? "no-cache" : "default"
|
||||
}).then(async (res1) => {
|
||||
if (res1.status === 200) {
|
||||
const pkg = await res1.json();
|
||||
const obj = pkg.liteloader_manifest;
|
||||
if (obj) {
|
||||
obj.version = pkg.version;
|
||||
obj.description = pkg.description;
|
||||
obj.authors = typeof pkg.author === "string" ? [{ name: pkg.author, link: `https://github.com/${pkg.author}` }] : [pkg.author];
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}).catch((err) => {
|
||||
if (again) {
|
||||
console.error(`getManifest ${url}`, err);
|
||||
return null;
|
||||
} else {
|
||||
console.warn(`getManifest ${url}`, err);
|
||||
return getManifest(item, noCache, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
async function install(release = false) {
|
||||
let url;
|
||||
config.debug && console.log("install", currentItem);
|
||||
if (release) {
|
||||
url = await getLatestReleaseUrl(currentItem);
|
||||
if (!url) {
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
message: "获取release包失败"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
url = getArchiveUrl(currentItem);
|
||||
}
|
||||
if (config.useMirror) {
|
||||
const m = getGithubMirror();
|
||||
return ListViewer.getPkg(currentManifest.slug, useMirror(url, m || getRandomItem(originMirrors), !!m));
|
||||
} else {
|
||||
return ListViewer.getPkg(currentManifest.slug, url);
|
||||
}
|
||||
}
|
||||
function getIconUrls(item, manifest) {
|
||||
if (manifest.icon) {
|
||||
const iconPath = manifest.icon.replace(/^\.?\//, "");
|
||||
const m = getGithubMirror(true);
|
||||
const m1 = getGithubMirror();
|
||||
if (config.useMirror) {
|
||||
return [
|
||||
useMirror(`https://github.com/${item.repo}/raw/${item.branch}/${iconPath}`, m || getRandomItem(originMirrors), !!m),
|
||||
useMirror(`https://github.com/${item.repo}/raw/${item.branch}/${iconPath}`, m1 || getRandomItem(originMirrors), !!m1)
|
||||
];
|
||||
} else {
|
||||
return [`https://github.com/${item.repo}/raw/${item.branch}/${iconPath}`];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
function uninstall() {
|
||||
return ListViewer.removePkg(currentManifest.slug);
|
||||
}
|
||||
function getGithubMirror(first = false) {
|
||||
if (first) {
|
||||
return config.mirrors.downloadUrl[0];
|
||||
}
|
||||
return getRandomItem(config.mirrors.downloadUrl.splice(1));
|
||||
}
|
||||
function getArchiveUrl(item) {
|
||||
return `https://github.com/${item.repo}/archive/refs/heads/${item.branch}.zip`;
|
||||
}
|
||||
async function getLatestReleaseUrl(item) {
|
||||
const url = `https://api.github.com/repos/${item.repo}/releases/latest`;
|
||||
const body = await fetchWithTimeout(url).then((e) => e.json()).catch((err) => {
|
||||
throw new Error(`${err.message}
|
||||
${url}`);
|
||||
});
|
||||
const zipFile = body.assets.find((asset) => asset.name.endsWith(".zip"));
|
||||
return zipFile.browser_download_url;
|
||||
}
|
||||
export {
|
||||
onSettingWindowCreated
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 XiaoHe321
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,90 @@
|
||||
# LiteLoaderQQNT - anti-recall
|
||||
|
||||
LiteLoaderQQNT插件,用于比较完善的防撤回。
|
||||
使用前需要安装[LiteLoaderQQNT](https://github.com/mo-jinran/LiteLoaderQQNT),并在QQNT新版上使用。
|
||||
|
||||
建议加入更新日志频道,接收最新更新和使用提示[https://t.me/llqqnt_xh](https://t.me/llqqnt_xh)。
|
||||
|
||||
## 使用方法
|
||||
|
||||
*建议从`LiteLoaderQQNT`应用商店中直接下载安装,方便快捷。*(新版本1.0`LiteLoaderQQNT`没有插件商店了,请遵循下面的手动安装方法)
|
||||
|
||||
**手动安装**:请前往[版本发布页面](https://github.com/xh321/LiteLoaderQQNT-Anti-Recall/releases)下载最新版压缩包(`qq-anti-recall.zip`),在`LiteLoaderQQNT数据目录/plugins/`下面新建文件夹(名称任意,建议为插件名称),蒋插件压缩包里的内容解压进去,重启QQNT即可。
|
||||
|
||||
直接克隆源码的话,记得需要手动进行`npm install`。
|
||||
|
||||
**版本不兼容提示**:从0.2.29起,插件已适配1.0版本以上`LiteLoaderQQNT`框架,同时不再兼容旧版框架,请遵循[安装方法](https://liteloaderqqnt.github.io/guide/install.html)更新框架。
|
||||
|
||||
**版本提示**:从0.2.35起,自旧版本升级上来的用户会丢失配置和已撤回数据库,需要手动迁移,这是因为之前的数据目录写入错误。迁移过程:把`LiteLoaderQQNT数据目录/qq-recalled-db`文件夹移动到`LiteLoaderQQNT数据目录/anti_recall`文件夹内即可(最终路径:`LiteLoaderQQNT数据目录/anti_recall/qq-recalled-db`。配置文件不建议迁移,可能已经被其他配置文件覆盖,建议重新配置插件(一共也没几个配置项目)。
|
||||
|
||||
注意:如果你是从旧版更新到新版(1.0)框架后,需要把`LiteLoaderQQNT`数据目录中的plugins_data文件夹重命名为data,否则之前的记录和配置文件会丢失。(如果仍然丢失就没办法了)
|
||||
|
||||
|
||||
## 什么情况下反撤回会失效
|
||||
|
||||
1. QQ接到消息时已经被撤回(只接到了撤回的消息)。
|
||||
2. QQ未在线时接到的消息(上线后已经被撤回)。
|
||||
3. 从接到某条消息开始到这条消息撤回,还收到了超过`MaxMsgSaveLimit(内存中消息最多缓存条数)`条消息(可以在配置界面里配置)。
|
||||
4. 插件出现错误。
|
||||
|
||||
## 重启QQ反撤回失效?
|
||||
|
||||
请使用命令行运行QQ,并观察日志输出。
|
||||
|
||||
如果出现:数据库打开失败的提示,并且下面的cause类似于
|
||||
|
||||
```
|
||||
cause: [Error: IO error: LockFile <LiteLoaderQQNT数据目录>\data\qq-recalled-db/LOCK...]
|
||||
```
|
||||
|
||||
可能是你之前的QQ退出不完全,或者你开了两个QQ。
|
||||
|
||||
请完全关闭QQ进程,并重试。若还不行,可能是数据库文件损坏,考虑清空上面的路径(<LiteLoaderQQNT数据目录>\data\qq-recalled-db)中的LOCK文件,若还不行,清空所有文件(会导致之前反撤回内容丢失)。
|
||||
|
||||
|
||||
|
||||
## 太长不看版
|
||||
|
||||
在NTQQ打开期间无论怎样操作,**反撤回均能生效**。
|
||||
|
||||
若开启数据库储存(**默认关闭**,请前往配置界面手动开启),NTQQ重启后可恢复已撤回的消息,目前已测试文本、表情、图片、转发消息均能正常恢复;文件若没下载则不能打开,但还能看见文件消息本身;其他消息类型未测试,不提供保证。
|
||||
|
||||
若不满意撤回的颜色、阴影和提示,也请前往配置界面手动调整。
|
||||
|
||||
若反撤回消息太多,累积在数据库里可能造成加载卡顿,现在已经做了优化,一般来说不需要清理,但如果觉得卡顿,如有必要,还是可以**隔一段时间**通过插件配置界面清理一次已储存的消息。
|
||||
|
||||
## 详细说明
|
||||
|
||||
所有被撤回的消息会被带上红框,下方会出现“已撤回”的小提示(均可通过配置关闭)。**重新进消息界面已撤回的消息仍然可以正常显示。**
|
||||
|
||||
**使用LevelDB进行数据持久化储存**,默认**不**启用,需要手动去设置里开启。如果不开启,重启QQ消息会丢失。若开启,当前版本重启QQ后**已能恢复文字、图片、转发和表情消息**,其他消息不提供保证,若不能恢复请提`ISSUE`。
|
||||
|
||||
`LevelDB`有时候启动的时候会加载失败,导致无法加载反撤回数据库内容。如果发现之前的反撤回失效,重启QQ一般都能解决(目前正在尽力避免这种问题)。
|
||||
|
||||
如果你需要调整撤回样式,请前往插件的设置界面,手动修改调整。
|
||||
|
||||
不建议手动修改配置文件,修改后不会实时生效(需要重启QQ),建议用配置界面修改。
|
||||
|
||||
|
||||
|
||||
现在,即使图片在撤回之前未打开过,撤回后也能继续查看了,并且不止缩略图,点开看也是可以的!即使图片显示为灰色的损坏样式,你也可以双击试试能不能打开看。
|
||||
|
||||
但是如果不是在眼前撤回的,而是撤回后你才点进聊天界面去看的,图片仍然会转圈一段时间(请耐心等待),猜测因为QQ会先检查图片是否存在,撤回后还没下载当然不存在,所以转圈;等插件下载好图片后,需要等待QQ重新检查,才能显示图片。
|
||||
|
||||
图片如果撤回太快,反撤回后有可能会一直加载不出来,这个可能是图片已经从服务器上删除了,这种情况下,**无法恢复**。
|
||||
|
||||
## 原理介绍
|
||||
|
||||
**之前版本(过时):** 为什么在简介里说是简易的呢?因为这个防撤回原理是通过拦截撤回消息的IPC,这样消息就不会被删除。可是,若重新打开消息界面,NTQQ似乎会重新向逻辑层拉取一次消息(旧版QQ的防撤回直接从通信上就拦截了撤回包,所以没有这个问题;而本插件仅拦截到了渲染层的IPC,逻辑层还是一个黑盒),所以导致消息仍然被撤回了。
|
||||
|
||||
**现在版本的变更:** 在接到消息后会将消息存于内存中,进入消息界面后,若发现撤回会拦截撤回包,并显示红色边框以及给文字附加删除线。若重新进入消息页面,会遍历消息列表,若发现撤回的提示,会寻找之前保存过的消息,若找到,则将撤回提示替换为之前保存的消息,并显示红色边框以及给文字附加删除线;若未找到,则仍然显示撤回提示。这样,重进消息界面**仍然可以正确反撤回**(包括打开独立聊天界面**也可以**)。
|
||||
|
||||
- 为避免太占内存,内存中缓存消息的上限默认为10000条。如有需要,可前往插件设置界面修改。这个值的意义是,假设默认最多存10000条,那么总接到消息的10000条之前的消息若被撤回则无法恢复。**不过,若一条消息在撤回后你回去看了,并且反撤回生效(也就是只要你看到了这条消息出现红框和删除线),则这条消息会被额外储存到专门的“已撤回消息数组”中,(若开启数据库,也会同时存进数据库),这个数组没有容量上限,不会被本限制所约束。**
|
||||
- 为避免可能存在的并发问题,消息超过上述限制后,每次接到消息后默认会删除内存缓存的消息中的前500条,直到低于上述上限。这个500条你也可以通过插件设置页面来变更。建议修改为你平均每秒接到的消息数量。
|
||||
- 若开启数据库储存功能,则任何撤回的消息都会存入本地的数据库(位于LLQQNT的插件数据目录(`plugin_data`里),以便于重启后读取已撤回的消息记录。数据库明文储存(后续会推出加密功能),请谨慎保管。
|
||||
|
||||
相关详细的原理和说明请参阅源码。
|
||||
|
||||
## 协议及免责
|
||||
|
||||
MIT | 禁止用于任何非法用途,插件开发属学习与研究目的,仅自用,未提供给任何第三方使用。任何不当使用导致的任何侵权问题责任自负。
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,682 @@
|
||||
const http = require("http");
|
||||
const https = require("https");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { app, ipcMain, dialog } = require("electron");
|
||||
|
||||
var configFilePath = "";
|
||||
var pluginDataDir = path.join(LiteLoader.path.data, "anti_recall");
|
||||
|
||||
const { Level } = require("level");
|
||||
var db = null;
|
||||
|
||||
var sampleConfig = {
|
||||
mainColor: "#ff6d6d",
|
||||
saveDb: false,
|
||||
enableShadow: true,
|
||||
enableTip: true,
|
||||
isAntiRecallSelfMsg: false,
|
||||
maxMsgSaveLimit: 10000,
|
||||
deleteMsgCountPerTime: 500,
|
||||
};
|
||||
|
||||
var nowConfig = {};
|
||||
|
||||
function initConfig() {
|
||||
fs.writeFileSync(
|
||||
configFilePath,
|
||||
JSON.stringify(sampleConfig, null, 2),
|
||||
"utf-8"
|
||||
);
|
||||
}
|
||||
|
||||
function loadConfig() {
|
||||
if (!fs.existsSync(configFilePath)) {
|
||||
initConfig();
|
||||
return sampleConfig;
|
||||
} else {
|
||||
return JSON.parse(fs.readFileSync(configFilePath, "utf-8"));
|
||||
}
|
||||
}
|
||||
|
||||
onLoad();
|
||||
|
||||
async function onLoad() {
|
||||
if (!fs.existsSync(pluginDataDir)) {
|
||||
fs.mkdirSync(pluginDataDir, { recursive: true });
|
||||
}
|
||||
configFilePath = path.join(pluginDataDir, "config.json");
|
||||
nowConfig = loadConfig();
|
||||
|
||||
if (nowConfig.mainColor == null) {
|
||||
nowConfig.mainColor = "#ff6d6d";
|
||||
}
|
||||
if (nowConfig.enableShadow == null) {
|
||||
nowConfig.enableShadow = true;
|
||||
}
|
||||
if (nowConfig.enableTip == null) {
|
||||
nowConfig.enableTip = true;
|
||||
}
|
||||
fs.writeFileSync(configFilePath, JSON.stringify(nowConfig, null, 2), "utf-8");
|
||||
|
||||
ipcMain.handle(
|
||||
"LiteLoader.anti_recall.getNowConfig",
|
||||
async (event, message) => {
|
||||
return nowConfig;
|
||||
}
|
||||
);
|
||||
|
||||
ipcMain.handle("LiteLoader.anti_recall.saveConfig", async (event, config) => {
|
||||
nowConfig = config;
|
||||
sendChatWindowsMessage("LiteLoader.anti_recall.mainWindow.repatchCss");
|
||||
fs.writeFileSync(configFilePath, JSON.stringify(config, null, 2), "utf-8");
|
||||
});
|
||||
|
||||
if (nowConfig.saveDb) {
|
||||
db = new Level(path.join(pluginDataDir, "qq-recalled-db"), {
|
||||
valueEncoding: "json",
|
||||
});
|
||||
|
||||
db.open((e) => {
|
||||
if (e !== undefined && e !== null) {
|
||||
app.whenReady().then(() => {
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: "warning",
|
||||
title: "警告",
|
||||
message:
|
||||
"打开反撤回数据库失败,可能是上次QQ进程未完全退出或同时开启了多个QQ(插件不支持QQ多开,如需多开请关闭保存到数据库功能)。建议关闭QQ并彻底结束QQ进程,再重启QQ,否则反撤回消息无法正常保存(即时反撤回仍生效,只是重启QQ后会丢失)。",
|
||||
buttons: ["继续打开QQ", "关闭QQ"],
|
||||
})
|
||||
.then((r) => {
|
||||
if (r.response == 1) {
|
||||
app.exit();
|
||||
}
|
||||
});
|
||||
});
|
||||
output(
|
||||
"打开数据库失败,可能是QQ进程未完全退出。请查看下面详细错误信息中的cause部分:",
|
||||
e
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
ipcMain.handle("LiteLoader.anti_recall.clearDb", async (event, message) => {
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: "warning",
|
||||
title: "警告",
|
||||
message: "清空所有已储存的撤回消息后不可恢复,是否确认清空?",
|
||||
buttons: ["确定", "取消"],
|
||||
cancelId: 1,
|
||||
})
|
||||
.then(async (idx) => {
|
||||
//第一个按钮
|
||||
if (idx.response == 0) {
|
||||
await db.clear();
|
||||
dialog.showMessageBox({
|
||||
type: "info",
|
||||
title: "提示",
|
||||
message:
|
||||
"清空完毕,之前保存的所有已撤回消息均被删除,重启QQ后就能看见效果。",
|
||||
buttons: ["确定"],
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 老方法,废弃
|
||||
// setTimeout(async () => {
|
||||
// await loadDb()
|
||||
// .catch(async (e) => {
|
||||
// output(
|
||||
// "Error while loading recalled msgs from db: " +
|
||||
// e.toString(),
|
||||
// ", retrying..."
|
||||
// );
|
||||
// await db.open();
|
||||
// await loadDb();
|
||||
// })
|
||||
// .catch((e) => {
|
||||
// output(
|
||||
// "Error while loading recalled msgs from db: " +
|
||||
// e.toString(),
|
||||
// ", stop load."
|
||||
// );
|
||||
// });
|
||||
// }, 100);
|
||||
|
||||
app.on("quit", async () => {
|
||||
output("Closing db...");
|
||||
await db.close();
|
||||
});
|
||||
}
|
||||
|
||||
// 废弃
|
||||
// async function loadDb() {
|
||||
// if (nowConfig.saveDb) {
|
||||
// output("Loading recalled msgs from db...");
|
||||
// var counter = 0;
|
||||
// for await (const value of db.values()) {
|
||||
// counter++;
|
||||
// recalledMsg.push(value);
|
||||
// if (value.msg == null) continue;
|
||||
// for (item of value.msg.elements) {
|
||||
// if (item.picElement != null) {
|
||||
// item.picElement.thumbPath = new Map([
|
||||
// [
|
||||
// 0,
|
||||
// item.picElement.sourcePath
|
||||
// .replace("Ori", "Thumb")
|
||||
// .replace(
|
||||
// item.picElement.md5HexStr,
|
||||
// item.picElement.md5HexStr + "_0"
|
||||
// )
|
||||
// ],
|
||||
// [
|
||||
// 198,
|
||||
// item.picElement.sourcePath
|
||||
// .replace("Ori", "Thumb")
|
||||
// .replace(
|
||||
// item.picElement.md5HexStr,
|
||||
// item.picElement.md5HexStr + "_198"
|
||||
// )
|
||||
// ],
|
||||
// [
|
||||
// 720,
|
||||
// item.picElement.sourcePath
|
||||
// .replace("Ori", "Thumb")
|
||||
// .replace(
|
||||
// item.picElement.md5HexStr,
|
||||
// item.picElement.md5HexStr + "_720"
|
||||
// )
|
||||
// ]
|
||||
// ]);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// output(`Loaded ${counter} msgs.`);
|
||||
// } else {
|
||||
// output("Db saving is disabled, continue.");
|
||||
// }
|
||||
// }
|
||||
|
||||
var msgFlow = [];
|
||||
var recalledMsg = [];
|
||||
|
||||
function request(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const protocol = url.startsWith("https") ? https : http;
|
||||
const req = protocol.get(url);
|
||||
req.on("error", (error) => reject(error));
|
||||
req.on("response", (res) => {
|
||||
// 发生跳转就继续请求
|
||||
if (res.statusCode >= 300 && res.statusCode <= 399) {
|
||||
return resolve(request(res.headers.location));
|
||||
}
|
||||
const chunks = [];
|
||||
res.on("error", (error) => reject(error));
|
||||
res.on("data", (chunk) => chunks.push(chunk));
|
||||
res.on("end", () => resolve(Buffer.concat(chunks)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 下载被撤回的图片(抄自Lite-Tools)
|
||||
async function processPic(msgItem) {
|
||||
msgItem?.elements?.forEach(async (el) => {
|
||||
if (el?.picElement) {
|
||||
const pic = el.picElement;
|
||||
const picName = pic.md5HexStr.toUpperCase();
|
||||
const picUrl = `https://gchat.qpic.cn/gchatpic_new/0/0-0-${picName}/`;
|
||||
output("Download lost pic(s)... url=", picUrl, "msgId=", msgItem.msgId);
|
||||
if (!fs.existsSync(pic.sourcePath)) {
|
||||
output("Download pic:", `${picUrl}0`);
|
||||
const body = await request(`${picUrl}0`);
|
||||
fs.mkdirSync(path.dirname(pic.sourcePath), { recursive: true });
|
||||
fs.writeFileSync(pic.sourcePath, body);
|
||||
} else {
|
||||
output("Pic already existed, skip.", pic.sourcePath);
|
||||
}
|
||||
// 修复本地数据中的错误
|
||||
if (
|
||||
pic?.thumbPath &&
|
||||
(pic.thumbPath instanceof Array || pic.thumbPath instanceof Object)
|
||||
) {
|
||||
pic.thumbPath = new Map([
|
||||
[
|
||||
0,
|
||||
pic.sourcePath
|
||||
.replace("Ori", "Thumb")
|
||||
.replace(pic.md5HexStr, pic.md5HexStr + "_0"),
|
||||
],
|
||||
[
|
||||
198,
|
||||
pic.sourcePath
|
||||
.replace("Ori", "Thumb")
|
||||
.replace(pic.md5HexStr, pic.md5HexStr + "_198"),
|
||||
],
|
||||
[
|
||||
720,
|
||||
pic.sourcePath
|
||||
.replace("Ori", "Thumb")
|
||||
.replace(pic.md5HexStr, pic.md5HexStr + "_720"),
|
||||
],
|
||||
]);
|
||||
}
|
||||
if (
|
||||
pic?.thumbPath &&
|
||||
(pic.thumbPath instanceof Array || pic.thumbPath instanceof Map)
|
||||
) {
|
||||
pic.thumbPath.forEach(async (el, key) => {
|
||||
if (!fs.existsSync(el)) {
|
||||
output("Download thumbs:", `${picUrl}${key}`);
|
||||
const body = await request(`${picUrl}${key}`);
|
||||
fs.mkdirSync(path.dirname(el), { recursive: true });
|
||||
fs.writeFileSync(el, body);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//废弃(老方法)
|
||||
// async function downloadPic(msgList) {
|
||||
// if (msgList == null) return;
|
||||
|
||||
// for (item of msgList.elements) {
|
||||
// if (item.picElement) {
|
||||
// var pic = item.picElement;
|
||||
// var picBasePath = pic.md5HexStr.toUpperCase();
|
||||
// var urlBase = `https://gchat.qpic.cn/gchatpic_new/0/0-0-${picBasePath}/`;
|
||||
|
||||
// output(
|
||||
// `[${pic.md5HexStr
|
||||
// .toUpperCase()
|
||||
// .substring(0, 5)}]Downloading lost pic(s)... ${urlBase}`
|
||||
// );
|
||||
|
||||
// if (!fs.existsSync(pic.sourcePath)) {
|
||||
// const body = await request(`${urlBase}0`);
|
||||
// fs.mkdirSync(path.dirname(pic.sourcePath), { recursive: true });
|
||||
// fs.writeFileSync(pic.sourcePath, body);
|
||||
// } else {
|
||||
// output(
|
||||
// `[${pic.md5HexStr.toUpperCase().substring(0, 5)}] ${
|
||||
// pic.sourcePath
|
||||
// } Pic(s) already existed, skip.`
|
||||
// );
|
||||
// }
|
||||
|
||||
// if (pic.thumbPath instanceof Array) {
|
||||
// pic.thumbPath.forEach(async (value, key) => {
|
||||
// if (!fs.existsSync(value)) {
|
||||
// const body = await request(`${urlBase}${key}`);
|
||||
// fs.mkdirSync(path.dirname(value), { recursive: true });
|
||||
// fs.writeFileSync(value, body);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// output(
|
||||
// `[${pic.md5HexStr
|
||||
// .toUpperCase()
|
||||
// .substring(0, 5)}]Download completed!`
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
function insertDb(msg) {
|
||||
if (db != null) {
|
||||
db.get(msg.id, (error, value) => {
|
||||
if (error.status == 404) {
|
||||
db.put(msg.id, msg, (err) => {
|
||||
if (err) throw err;
|
||||
});
|
||||
} else {
|
||||
if (error) throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function getMsgById(id) {
|
||||
if (db != null) {
|
||||
try {
|
||||
return await db.get(id);
|
||||
} catch (e) {
|
||||
if (e.status != 404) {
|
||||
output(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
output("Warning: Db is null, no previous msg available.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function sendChatWindowsMessage(message) {
|
||||
for (var window of mainWindowObjs) {
|
||||
if (window.isDestroyed()) continue;
|
||||
window.webContents.send(message);
|
||||
}
|
||||
}
|
||||
|
||||
var mainWindowObjs = [];
|
||||
function onBrowserWindowCreated(window) {
|
||||
window.webContents.on("did-stop-loading", () => {
|
||||
//只针对主界面和独立聊天界面生效
|
||||
if (
|
||||
window.webContents.getURL().indexOf("#/main/message") != -1 ||
|
||||
window.webContents.getURL().indexOf("#/chat") != -1
|
||||
) {
|
||||
mainWindowObjs.push(window);
|
||||
// const proxyEvents = new Proxy(
|
||||
// window.webContents._events["-ipc-message"],
|
||||
// {
|
||||
// // 拦截函数调用
|
||||
// apply(target, thisArg, argumentsList) {
|
||||
// if (
|
||||
// argumentsList[3][0]["eventName"] &&
|
||||
// !argumentsList[3][0]["eventName"].includes(
|
||||
// "ns-Logger"
|
||||
// )
|
||||
// ) {
|
||||
// output(JSON.stringify(argumentsList));
|
||||
// }
|
||||
// return target.apply(thisArg, argumentsList);
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
// window.webContents._events["-ipc-message"] = proxyEvents;
|
||||
|
||||
//补充知识:
|
||||
//撤回的原理是,你先发了一条消息,这条消息有一个msgId,然后又撤回了他,那腾讯就会发一条一样msgId的撤回消息包来替换,这样你以后拉取的话,这个msgId只会对应一条撤回提示了;
|
||||
//本插件的原理是,先在内存中临时储存所有消息(1000条上限),然后如果有撤回发生,则将撤回的提示替换为之前保存的消息。
|
||||
|
||||
const original_send =
|
||||
(window.webContents.__qqntim_original_object &&
|
||||
window.webContents.__qqntim_original_object.send) ||
|
||||
window.webContents.send;
|
||||
|
||||
//var myUid = "";
|
||||
const patched_send = async function (channel, ...args) {
|
||||
// output(channel, JSON.stringify(args));
|
||||
// if (db != null) {
|
||||
// db.put("a", { x: 123 }, function (err) {
|
||||
// if (err) throw err;
|
||||
|
||||
// db.get("a", function (err, value) {
|
||||
// console.log(value); // { x: 123 }
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
try {
|
||||
if (args.length >= 2) {
|
||||
//MessageList IPC 中能看到消息全量更新内容,其中包含撤回的提示,但并不包含被撤回的消息(被撤回的已经被替换掉了),需要替换撤回提示为之前保存的消息内容
|
||||
if (
|
||||
args.some(
|
||||
(item) =>
|
||||
item &&
|
||||
item.hasOwnProperty("msgList") &&
|
||||
item.msgList != null &&
|
||||
item.msgList instanceof Array &&
|
||||
item.msgList.length > 0
|
||||
)
|
||||
) {
|
||||
var currentMsgPeer = "";
|
||||
|
||||
//撤回提示所在的msgList下标数组,在后面需要一个个替换为真实的消息
|
||||
var needUpdateIdx = [];
|
||||
for (let idx in args[1].msgList) {
|
||||
var item = args[1].msgList[idx];
|
||||
currentMsgPeer = item.peerUid;
|
||||
if (item.msgType == 5 && item.subMsgType == 4) {
|
||||
if (
|
||||
item.elements[0].grayTipElement != null &&
|
||||
item.elements[0].grayTipElement.revokeElement != null &&
|
||||
(nowConfig.isAntiRecallSelfMsg ||
|
||||
!item.elements[0].grayTipElement.revokeElement
|
||||
.isSelfOperate)
|
||||
) {
|
||||
needUpdateIdx.push(idx);
|
||||
}
|
||||
}
|
||||
// console.log(
|
||||
// item.recallTime,
|
||||
// "<====>",
|
||||
// item.elements,
|
||||
// "<====>",
|
||||
// item.elements[0].grayTipElement.revokeElement
|
||||
// );
|
||||
}
|
||||
|
||||
needUpdateIdx.sort((a, b) => b - a);
|
||||
|
||||
for (var i of needUpdateIdx) {
|
||||
var currMsgId = args[1].msgList[i].msgId;
|
||||
|
||||
//如果之前存了消息
|
||||
var olderMsg = msgFlow.find((i) => i.id == currMsgId);
|
||||
var olderMsgFromRecalledMsg = recalledMsg.find(
|
||||
(i) => i.id == currMsgId
|
||||
);
|
||||
|
||||
var dbMsg = await getMsgById(currMsgId);
|
||||
|
||||
//优先从已保存的撤回的消息中获取
|
||||
if (olderMsgFromRecalledMsg != null) {
|
||||
// original_send.call(
|
||||
// window.webContents,
|
||||
// channel,
|
||||
// {
|
||||
// type: "request",
|
||||
// eventName: "ns-ntApi-2"
|
||||
// },
|
||||
// [
|
||||
// {
|
||||
// cmdName:
|
||||
// "nodeIKernelMsgListener/onRecvMsg",
|
||||
// cmdType: "event",
|
||||
// payload: {
|
||||
// msgList: [
|
||||
// olderMsgFromRecalledMsg.msg
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// );
|
||||
|
||||
await processPic(olderMsgFromRecalledMsg.msg);
|
||||
|
||||
args[1].msgList[i] = olderMsgFromRecalledMsg.msg;
|
||||
|
||||
output(
|
||||
"Detected recall, intercepted and recovered from old msg"
|
||||
);
|
||||
}
|
||||
//如果没有存过,则说明他在消息流里
|
||||
else if (olderMsg != null) {
|
||||
args[1].msgList[i] = olderMsg.msg;
|
||||
|
||||
//没专门存过这条消息到专门的反撤回数组中,就存一下
|
||||
if (olderMsgFromRecalledMsg == null) {
|
||||
recalledMsg.push(olderMsg);
|
||||
}
|
||||
|
||||
await processPic(olderMsg.msg);
|
||||
|
||||
output(
|
||||
"Detected recall, intercepted and recovered from msgFlow"
|
||||
);
|
||||
} else if (dbMsg != null) {
|
||||
args[1].msgList[i] = dbMsg.msg;
|
||||
|
||||
//没专门存过这条消息到专门的反撤回数组中,就存一下
|
||||
if (olderMsgFromRecalledMsg == null) {
|
||||
recalledMsg.push(dbMsg);
|
||||
}
|
||||
|
||||
await processPic(dbMsg.msg);
|
||||
|
||||
output(
|
||||
"Detected recall, intercepted and recovered from dbMsg"
|
||||
);
|
||||
}
|
||||
args[1].msgList[i].isOnlineMsg = true;
|
||||
}
|
||||
original_send.call(
|
||||
window.webContents,
|
||||
"LiteLoader.anti_recall.mainWindow.recallTipList",
|
||||
recalledMsg
|
||||
.filter(
|
||||
(i) => i.sender == currentMsgPeer || i?.sender == null
|
||||
)
|
||||
.map((i) => i.id)
|
||||
);
|
||||
}
|
||||
|
||||
//增量更新 IPC
|
||||
if (
|
||||
args.some(
|
||||
(item) =>
|
||||
item instanceof Array &&
|
||||
item.length > 0 &&
|
||||
item[0] &&
|
||||
item[0].cmdName != null
|
||||
)
|
||||
) {
|
||||
var args1 = args[1][0];
|
||||
if (args1 == null) return;
|
||||
|
||||
//方法一:获取个人信息的IPC,用来获取个人UID,避免防撤回自己的消息
|
||||
// if (args1.cmdName.indexOf("onProfileDetailInfoChanged") != -1) {
|
||||
// myUid = args1.payload.info.uid;
|
||||
// } else
|
||||
//目前采用方法二,直接获取撤回消息中的参数
|
||||
//拦截撤回IPC
|
||||
if (
|
||||
args1.cmdName != null &&
|
||||
(args1.cmdName.indexOf("onMsgInfoListUpdate") != -1 ||
|
||||
args1.cmdName.indexOf("onActiveMsgInfoUpdate") != -1) &&
|
||||
args1.payload != null &&
|
||||
args1.payload.msgList instanceof Array &&
|
||||
args1.payload.msgList[0].msgType == 5 &&
|
||||
args1.payload.msgList[0].subMsgType == 4
|
||||
) {
|
||||
var msgList = args1.payload.msgList[0];
|
||||
|
||||
//不是自己撤回的,才拦截
|
||||
if (
|
||||
msgList.elements[0].grayTipElement != null &&
|
||||
msgList.elements[0].grayTipElement.revokeElement != null &&
|
||||
(nowConfig.isAntiRecallSelfMsg ||
|
||||
!msgList.elements[0].grayTipElement.revokeElement
|
||||
.isSelfOperate)
|
||||
) {
|
||||
original_send.call(
|
||||
window.webContents,
|
||||
"LiteLoader.anti_recall.mainWindow.recallTip",
|
||||
msgList.msgId
|
||||
);
|
||||
|
||||
//如果之前存了消息
|
||||
var olderMsg = msgFlow.find((i) => i.id == msgList.msgId);
|
||||
var olderMsgFromRecalledMsg = recalledMsg.find(
|
||||
(i) => i.id == msgList.msgId
|
||||
);
|
||||
|
||||
//之前存了消息,但是还没有存入专门的反撤回数组中
|
||||
if (olderMsg != null && olderMsgFromRecalledMsg == null) {
|
||||
recalledMsg.push(olderMsg);
|
||||
if (nowConfig.saveDb) {
|
||||
insertDb(olderMsg);
|
||||
}
|
||||
}
|
||||
|
||||
await processPic(olderMsg?.msg);
|
||||
await processPic(olderMsgFromRecalledMsg?.msg);
|
||||
|
||||
args[1][0].cmdName = "none";
|
||||
args[1][0].payload.msgList.pop();
|
||||
|
||||
// console.log(args1.payload);
|
||||
output("Detected recall, intercepted");
|
||||
}
|
||||
}
|
||||
//接到消息
|
||||
else if (
|
||||
(args1.cmdName != null &&
|
||||
args1.payload != null &&
|
||||
(args1.cmdName.indexOf("onRecvMsg") != -1 ||
|
||||
args1.cmdName.indexOf("onRecvActiveMsg") != -1) &&
|
||||
args1.payload.msgList instanceof Array) ||
|
||||
(args1.cmdName.indexOf("onAddSendMsg") != -1 &&
|
||||
args1.payload.msgRecord != null) ||
|
||||
(args1.cmdName.indexOf("onMsgInfoListUpdate") != -1 &&
|
||||
args1.payload.msgList instanceof Array)
|
||||
) {
|
||||
var msgList =
|
||||
args1.payload.msgList instanceof Array
|
||||
? args1.payload.msgList
|
||||
: [args1.payload.msgRecord];
|
||||
|
||||
for (msg of msgList) {
|
||||
var msgId = msg.msgId;
|
||||
|
||||
var olderMsgIdx = msgFlow.findIndex((i) => i.id == msgId);
|
||||
if (olderMsgIdx == -1) {
|
||||
msgFlow.push({});
|
||||
olderMsgIdx = msgFlow.length - 1;
|
||||
}
|
||||
msgFlow[olderMsgIdx] = {
|
||||
id: msgId,
|
||||
sender: msg.peerUid,
|
||||
msg: msg,
|
||||
};
|
||||
|
||||
if (nowConfig.maxMsgSaveLimit == null) {
|
||||
nowConfig.maxMsgSaveLimit = 10000;
|
||||
}
|
||||
if (nowConfig.deleteMsgCountPerTime == null) {
|
||||
nowConfig.deleteMsgCountPerTime = 500;
|
||||
}
|
||||
|
||||
if (msgFlow.length > nowConfig.maxMsgSaveLimit) {
|
||||
msgFlow.splice(0, nowConfig.deleteMsgCountPerTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
output(
|
||||
"NTQQ Anti-Recall Error: ",
|
||||
e,
|
||||
"Please report this to https://github.com/xh321/LiteLoaderQQNT-Anti-Recall/issues, thank you"
|
||||
);
|
||||
}
|
||||
|
||||
return original_send.call(window.webContents, channel, ...args);
|
||||
};
|
||||
if (window.webContents.__qqntim_original_object)
|
||||
window.webContents.__qqntim_original_object.send = patched_send;
|
||||
else window.webContents.send = patched_send;
|
||||
|
||||
output(
|
||||
"NTQQ Anti-Recall loaded for window: " + window.webContents.getURL()
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function output(...args) {
|
||||
console.log("\x1b[32m%s\x1b[0m", "Anti-Recall:", ...args);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
onBrowserWindowCreated,
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"manifest_version": 4,
|
||||
"type": "extension",
|
||||
"name": "防撤回",
|
||||
"slug": "anti_recall",
|
||||
"description": "防止QQNT撤回消息",
|
||||
"version": "0.2.40",
|
||||
"icon": "./icon.png",
|
||||
"authors": [
|
||||
{
|
||||
"name": "XiaoHe321",
|
||||
"link": "https://github.com/xh321"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"repo": "xh321/LiteLoaderQQNT-Anti-Recall",
|
||||
"branch": "master",
|
||||
"release": {
|
||||
"tag": "0.2.40",
|
||||
"file": "qq-anti-recall.zip"
|
||||
}
|
||||
},
|
||||
"platform": ["win32", "darwin", "linux"],
|
||||
"injects": {
|
||||
"main": "./main.js",
|
||||
"preload": "./preload.js",
|
||||
"renderer": "./renderer.js"
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../node-gyp-build/bin.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../node-gyp-build/bin.js" "$@"
|
||||
fi
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../node-gyp-build/optional.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../node-gyp-build/optional.js" "$@"
|
||||
fi
|
||||
Generated
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\node-gyp-build\optional.js" %*
|
||||
Generated
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../node-gyp-build/optional.js" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../node-gyp-build/optional.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../node-gyp-build/optional.js" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../node-gyp-build/optional.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../node-gyp-build/build-test.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../node-gyp-build/build-test.js" "$@"
|
||||
fi
|
||||
Generated
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\node-gyp-build\build-test.js" %*
|
||||
Generated
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../node-gyp-build/build-test.js" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../node-gyp-build/build-test.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../node-gyp-build/build-test.js" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../node-gyp-build/build-test.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
Generated
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\node-gyp-build\bin.js" %*
|
||||
Generated
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../node-gyp-build/bin.js" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../node-gyp-build/bin.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../node-gyp-build/bin.js" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../node-gyp-build/bin.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
Generated
Vendored
+243
@@ -0,0 +1,243 @@
|
||||
{
|
||||
"name": "anti-recall",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"node_modules/abstract-level": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.4.tgz",
|
||||
"integrity": "sha512-eUP/6pbXBkMbXFdx4IH2fVgvB7M0JvR7/lIL33zcs0IBcwjdzSSl31TOJsaCzmKSSDF9h8QYSOJux4Nd4YJqFg==",
|
||||
"dependencies": {
|
||||
"buffer": "^6.0.3",
|
||||
"catering": "^2.1.0",
|
||||
"is-buffer": "^2.0.5",
|
||||
"level-supports": "^4.0.0",
|
||||
"level-transcoder": "^1.0.1",
|
||||
"module-error": "^1.0.1",
|
||||
"queue-microtask": "^1.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/browser-level": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/browser-level/-/browser-level-1.0.1.tgz",
|
||||
"integrity": "sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ==",
|
||||
"dependencies": {
|
||||
"abstract-level": "^1.0.2",
|
||||
"catering": "^2.1.1",
|
||||
"module-error": "^1.0.2",
|
||||
"run-parallel-limit": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/catering": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz",
|
||||
"integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/classic-level": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.4.1.tgz",
|
||||
"integrity": "sha512-qGx/KJl3bvtOHrGau2WklEZuXhS3zme+jf+fsu6Ej7W7IP/C49v7KNlWIsT1jZu0YnfzSIYDGcEWpCa1wKGWXQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"abstract-level": "^1.0.2",
|
||||
"catering": "^2.1.0",
|
||||
"module-error": "^1.0.1",
|
||||
"napi-macros": "^2.2.2",
|
||||
"node-gyp-build": "^4.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/is-buffer": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
|
||||
"integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/level": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/level/-/level-8.0.1.tgz",
|
||||
"integrity": "sha512-oPBGkheysuw7DmzFQYyFe8NAia5jFLAgEnkgWnK3OXAuJr8qFT+xBQIwokAZPME2bhPFzS8hlYcL16m8UZrtwQ==",
|
||||
"dependencies": {
|
||||
"abstract-level": "^1.0.4",
|
||||
"browser-level": "^1.0.1",
|
||||
"classic-level": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/level"
|
||||
}
|
||||
},
|
||||
"node_modules/level-supports": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz",
|
||||
"integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/level-transcoder": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz",
|
||||
"integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==",
|
||||
"dependencies": {
|
||||
"buffer": "^6.0.3",
|
||||
"module-error": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/module-error": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz",
|
||||
"integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/napi-macros": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz",
|
||||
"integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g=="
|
||||
},
|
||||
"node_modules/node-gyp-build": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz",
|
||||
"integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==",
|
||||
"bin": {
|
||||
"node-gyp-build": "bin.js",
|
||||
"node-gyp-build-optional": "optional.js",
|
||||
"node-gyp-build-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/run-parallel-limit": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz",
|
||||
"integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+48
@@ -0,0 +1,48 @@
|
||||
# Changelog
|
||||
|
||||
## [1.0.4] - 2024-01-20
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix TypeScript definitions of `all()` and `nextv()` ([#67](https://github.com/Level/abstract-level/issues/67)) ([`8e85993`](https://github.com/Level/abstract-level/commit/8e85993), [`9f17757`](https://github.com/Level/abstract-level/commit/9f17757)) (Bryan)
|
||||
|
||||
## [1.0.3] - 2022-03-20
|
||||
|
||||
### Added
|
||||
|
||||
- Document error codes of `classic-level` and `many-level` ([#20](https://github.com/Level/abstract-level/issues/20)) ([`4b3464c`](https://github.com/Level/abstract-level/commit/4b3464c)) (Vincent Weevers)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Add hidden `abortOnClose` option to iterators ([`2935180`](https://github.com/Level/abstract-level/commit/2935180)) (Vincent Weevers)
|
||||
- Make internal iterator decoding options enumerable ([`eb08363`](https://github.com/Level/abstract-level/commit/eb08363)) (Vincent Weevers)
|
||||
- Restore Sauce Labs browser tests ([`90b8816`](https://github.com/Level/abstract-level/commit/90b8816)) (Vincent Weevers)
|
||||
|
||||
## [1.0.2] - 2022-03-06
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix TypeScript declaration of chained batch `write()` options ([`392b7f7`](https://github.com/Level/abstract-level/commit/392b7f7)) (Vincent Weevers)
|
||||
- Document the return type of `db.batch()` and add example ([`9739bba`](https://github.com/Level/abstract-level/commit/9739bba)) (Vincent Weevers)
|
||||
|
||||
## [1.0.1] - 2022-02-06
|
||||
|
||||
### Fixed
|
||||
|
||||
- Add `highWaterMarkBytes` option to tests where it matters ([`6b25a91`](https://github.com/Level/abstract-level/commit/6b25a91)) (Vincent Weevers)
|
||||
- Clarify the meaning of `db.status` ([`2e90b05`](https://github.com/Level/abstract-level/commit/2e90b05)) (Vincent Weevers)
|
||||
- Use `new` in README examples ([`379503e`](https://github.com/Level/abstract-level/commit/379503e)) (Vincent Weevers).
|
||||
|
||||
## [1.0.0] - 2022-01-30
|
||||
|
||||
_:seedling: Initial release. If you are upgrading from `abstract-leveldown` please see [`UPGRADING.md`](UPGRADING.md)_
|
||||
|
||||
[1.0.4]: https://github.com/Level/abstract-level/releases/tag/v1.0.4
|
||||
|
||||
[1.0.3]: https://github.com/Level/abstract-level/releases/tag/v1.0.3
|
||||
|
||||
[1.0.2]: https://github.com/Level/abstract-level/releases/tag/v1.0.2
|
||||
|
||||
[1.0.1]: https://github.com/Level/abstract-level/releases/tag/v1.0.1
|
||||
|
||||
[1.0.0]: https://github.com/Level/abstract-level/releases/tag/v1.0.0
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright © 2013 Rod Vagg and the contributors to abstract-level.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Generated
Vendored
+1436
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+479
@@ -0,0 +1,479 @@
|
||||
# Upgrade Guide
|
||||
|
||||
This document describes breaking changes and how to upgrade. For a complete list of changes including minor and patch releases, please refer to the [changelog](CHANGELOG.md).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
<details><summary>Click to expand</summary>
|
||||
|
||||
- [1.0.0](#100)
|
||||
- [1. API parity with `levelup`](#1-api-parity-with-levelup)
|
||||
- [1.1. New: promises](#11-new-promises)
|
||||
- [1.2. New: events](#12-new-events)
|
||||
- [1.3. New: idempotent open](#13-new-idempotent-open)
|
||||
- [1.4. New: deferred open](#14-new-deferred-open)
|
||||
- [1.5. No constructor callback](#15-no-constructor-callback)
|
||||
- [1.6. New: state checks](#16-new-state-checks)
|
||||
- [1.7. New: chained batch length](#17-new-chained-batch-length)
|
||||
- [2. API parity with `level`](#2-api-parity-with-level)
|
||||
- [2.1. For consumers](#21-for-consumers)
|
||||
- [2.2. For implementors](#22-for-implementors)
|
||||
- [2.3. Other notable changes](#23-other-notable-changes)
|
||||
- [3. Streams have moved](#3-streams-have-moved)
|
||||
- [4. Zero-length keys and range options are now valid](#4-zero-length-keys-and-range-options-are-now-valid)
|
||||
- [5. Resources are auto-closed](#5-resources-are-auto-closed)
|
||||
- [5.1. Closing iterators is idempotent](#51-closing-iterators-is-idempotent)
|
||||
- [5.2. Chained batch can be closed](#52-chained-batch-can-be-closed)
|
||||
- [6. Errors now use codes](#6-errors-now-use-codes)
|
||||
- [7. Semi-private properties have been removed](#7-semi-private-properties-have-been-removed)
|
||||
- [8. Changes to test suite](#8-changes-to-test-suite)
|
||||
- [9. Sublevels are builtin](#9-sublevels-are-builtin)
|
||||
|
||||
</details>
|
||||
|
||||
## 1.0.0
|
||||
|
||||
**Introducing `abstract-level`: a fork of [`abstract-leveldown`](https://github.com/Level/abstract-leveldown) that removes the need for [`levelup`](https://github.com/Level/levelup), [`encoding-down`](https://github.com/Level/encoding-down) and more. An `abstract-level` database is a complete solution that doesn't need to be wrapped. It has the same API as `level(up)` including encodings, promises and events. In addition, implementations can now choose to use Uint8Array instead of Buffer. Consumers of an implementation can use both. Sublevels are builtin.**
|
||||
|
||||
We've put together several upgrade guides for different modules. See the [FAQ](https://github.com/Level/community#faq) to find the best upgrade guide for you. This upgrade guide describes how to replace `abstract-leveldown` with `abstract-level`. Implementations that do so, can no longer be wrapped with `levelup`.
|
||||
|
||||
The npm package name is `abstract-level` and the main export is called `AbstractLevel` rather than `AbstractLevelDOWN`. It started using classes. Support of Node.js 10 has been dropped.
|
||||
|
||||
For most folks, a database that upgraded from `abstract-leveldown` to `abstract-level` can be a drop-in replacement for a `level(up)` database (with the exception of stream methods). Let's start this guide there: all methods have been enhanced to reach API parity with `levelup` and `level`.
|
||||
|
||||
### 1. API parity with `levelup`
|
||||
|
||||
#### 1.1. New: promises
|
||||
|
||||
Methods that take a callback now also support promises. They return a promise if no callback is provided, the same as `levelup`. Implementations that override public (non-underscored) methods _must_ do the same and any implementation _should_ do the same for additional methods if any.
|
||||
|
||||
#### 1.2. New: events
|
||||
|
||||
An `abstract-level` database emits the same events as `levelup` would.
|
||||
|
||||
#### 1.3. New: idempotent open
|
||||
|
||||
Opening and closing a database is idempotent and safe, similar to `levelup` but more precise. If `open()` and `close()` are called repeatedly, the last call dictates the final status. Callbacks are not called (or promises not resolved) until any pending state changes are done. Same for events. Unlike on `levelup` it is safe to call `open()` while status is `'closing'`: the database will wait for closing to complete and then reopen. None of these changes are likely to constitute a breaking change; they increase state consistency in edge cases.
|
||||
|
||||
The `open()` method has a new option called `passive`. If set to `true` the call will wait for, but not initiate, opening of the database. To similar effect as `db.once('open', callback)` with added benefit that it also works if the database is already open. Implementations that wrap another database can use the `passive` option to open themselves without taking full control of the database that they wrap.
|
||||
|
||||
#### 1.4. New: deferred open
|
||||
|
||||
Deferred open is built-in. This means a database opens itself a tick after its constructor returns (unless `open()` was called manually). Any operations made until opening has completed are queued up in memory. When opening completes the operations are replayed. If opening failed (and this is a new behavior compared to `levelup`) the operations will yield errors. The `AbstractLevel` class has a new `defer()` method for an implementation to defer custom operations.
|
||||
|
||||
The initial `status` of a database is `'opening'` rather than `'new'`, which no longer exists. Wrapping a database with [`deferred-leveldown`](https://github.com/Level/deferred-leveldown) is not supported and will exhibit undefined behavior.
|
||||
|
||||
Implementations must also accept options for `open()` in their constructor, which was previously done by `levelup`. For example, usage of the [`classic-level`](https://github.com/Level/classic-level) implementation is as follows:
|
||||
|
||||
```js
|
||||
const db = new ClassicLevel('./db', {
|
||||
createIfMissing: false,
|
||||
compression: false
|
||||
})
|
||||
```
|
||||
|
||||
This works by first forwarding options to the `AbstractLevel` constructor, which in turn forwards them to `open(options)`. If `open(options)` is called manually those options will be shallowly merged with options from the constructor:
|
||||
|
||||
```js
|
||||
// Results in { createIfMissing: false, compression: true }
|
||||
await db.open({ compression: true })
|
||||
```
|
||||
|
||||
A database is not "patch-safe". If some form of plugin monkey-patches a database like in the following example, it must now also take the responsibility of deferring the operation (as well as handling promises and callbacks) using `db.defer()`. I.e. this example is incomplete:
|
||||
|
||||
```js
|
||||
function plugin (db) {
|
||||
const original = db.get
|
||||
|
||||
db.get = function (...args) {
|
||||
original.call(this, ...args)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.5. No constructor callback
|
||||
|
||||
The database constructor does not take a callback argument, unlike `levelup`. This goes for `abstract-level` as well as implementations - which is to say, implementors don't have to (and should not) support this old pattern.
|
||||
|
||||
Instead call `db.open()` if you wish to wait for opening (which is not necessary to use the database) or to capture an error. If that's your reason for using the callback and you previously initialized a database like so (simplified):
|
||||
|
||||
```js
|
||||
levelup(function (err, db) {
|
||||
// ..
|
||||
})
|
||||
```
|
||||
|
||||
You must now do:
|
||||
|
||||
```js
|
||||
db.open(function (err) {
|
||||
// ..
|
||||
})
|
||||
```
|
||||
|
||||
Or using promises:
|
||||
|
||||
```js
|
||||
await db.open()
|
||||
```
|
||||
|
||||
#### 1.6. New: state checks
|
||||
|
||||
On any operation, an `abstract-level` database checks if it's open. If not, it will either throw an error (if the relevant API is synchronous) or asynchronously yield an error. For example:
|
||||
|
||||
```js
|
||||
await db.close()
|
||||
|
||||
try {
|
||||
db.iterator()
|
||||
} catch (err) {
|
||||
console.log(err.code) // LEVEL_DATABASE_NOT_OPEN
|
||||
}
|
||||
```
|
||||
|
||||
_Errors now have a `code` property. More on that below\._
|
||||
|
||||
This may be a breaking change downstream because it changes error messages for implementations that had their own safety checks (which will now be ineffective because `abstract-level` checks are performed first) or implicitly relied on `levelup` checks. By safety we mean mainly that yielding a JavaScript error is preferred over segmentation faults, though non-native implementations also benefit from detecting incorrect usage.
|
||||
|
||||
Implementations that have additional methods should add or align their own safety checks for consistency. Like so:
|
||||
|
||||
<details>
|
||||
<summary>Click to expand</summary>
|
||||
|
||||
```js
|
||||
const ModuleError = require('module-error')
|
||||
|
||||
class ExampleLevel extends AbstractLevel {
|
||||
// For brevity this example does not implement promises or encodings
|
||||
approximateSize (start, end, callback) {
|
||||
if (this.status === 'opening') {
|
||||
this.defer(() => this.approximateSize(start, end, callback))
|
||||
} else if (this.status !== 'open') {
|
||||
this.nextTick(callback, new ModuleError('Database is not open', {
|
||||
code: 'LEVEL_DATABASE_NOT_OPEN'
|
||||
}))
|
||||
} else {
|
||||
// ..
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### 1.7. New: chained batch length
|
||||
|
||||
The `AbstractChainedBatch` prototype has a new `length` property that, like a chained batch in `levelup`, returns the number of queued operations in the batch. Implementations should not have to make changes for this unless they monkey-patched public methods of `AbstractChainedBatch`.
|
||||
|
||||
### 2. API parity with `level`
|
||||
|
||||
It was previously necessary to use [`level`](https://github.com/Level/level) to get the "full experience". Or similar modules like [`level-mem`](https://github.com/Level/mem), [`level-rocksdb`](https://github.com/Level/level-rocksdb) and more. These modules combined an `abstract-leveldown` implementation with [`encoding-down`](https://github.com/Level/encoding-down) and [`levelup`](https://github.com/Level/levelup). Encodings are now built-in to `abstract-level`, using [`level-transcoder`](https://github.com/Level/transcoder) rather than [`level-codec`](https://github.com/Level/codec). The main change is that logic from the existing public API has been expanded down into the storage layer.
|
||||
|
||||
The `level` module still has a place, for its support of both Node.js and browsers and for being the main entrypoint into the Level ecosystem. The next major version of `level`, that's v8.0.0, will likely simply export [`classic-level`](https://github.com/Level/classic-level) in Node.js and [`browser-level`](https://github.com/Level/browser-level) in browsers. To differentiate, the text below will refer to the old version as `level@7`.
|
||||
|
||||
#### 2.1. For consumers
|
||||
|
||||
All relevant methods including the database constructor now accept `keyEncoding` and `valueEncoding` options, the same as `level@7`. Read operations now yield strings rather than buffers by default, having the same default `'utf8'` encoding as `level@7` and friends.
|
||||
|
||||
There are a few differences from `level@7` and `encoding-down`. Some breaking:
|
||||
|
||||
- The lesser-used `'ascii'`, `'ucs2'` and `'utf16le'` encodings are not supported
|
||||
- The `'id'` encoding, which was not supported by any active `abstract-leveldown` implementation and aliased as `'none'`, has been removed
|
||||
- The undocumented `encoding` option (as an alias for `valueEncoding`) is not supported.
|
||||
|
||||
And some non-breaking:
|
||||
|
||||
- The `'binary'` encoding has been renamed to `'buffer'`, with `'binary'` as an alias
|
||||
- The `'utf8'` encoding previously did not touch Buffers. Now it will call `buffer.toString('utf8')` for consistency. Consumers can use the `'buffer'` encoding to avoid this conversion.
|
||||
|
||||
If you previously did one of the following (on a database that's defaulting to the `'utf8'` encoding):
|
||||
|
||||
```js
|
||||
await db.put('a', Buffer.from('x'))
|
||||
await db.put('a', Buffer.from('x'), { valueEncoding: 'binary' })
|
||||
```
|
||||
|
||||
Both examples will still work (assuming the buffer contains only UTF8 data) but you should now do:
|
||||
|
||||
```js
|
||||
await db.put('a', Buffer.from('x'), { valueEncoding: 'buffer' })
|
||||
```
|
||||
|
||||
Or use the new `'view'` encoding which accepts Uint8Arrays (and therefore also Buffer):
|
||||
|
||||
```js
|
||||
await db.put('a', new Uint8Array(...), { valueEncoding: 'view' })
|
||||
```
|
||||
|
||||
#### 2.2. For implementors
|
||||
|
||||
_You can skip this section if you're consuming (rather than writing) an `abstract-level` implementation._
|
||||
|
||||
Both the public and private API of `abstract-level` are encoding-aware. This means that private methods receive `keyEncoding` and `valueEncoding` options too, instead of the `keyAsBuffer`, `valueAsBuffer` and `asBuffer` options that `abstract-leveldown` had. Implementations don't need to perform encoding or decoding themselves. In fact they can do less: the `_serializeKey()` and `_serializeValue()` methods are also gone and implementations are less likely to have to convert between strings and buffers.
|
||||
|
||||
For example: a call like `db.put(key, { x: 2 }, { valueEncoding: 'json' })` will encode the `{ x: 2 }` value and might forward it to the private API as `db._put(key, '{"x":2}', { valueEncoding: 'utf8' }, callback)`. Same for the key, omitted for brevity. We say "might" because it depends on the implementation, which can now declare which encodings it supports.
|
||||
|
||||
To first give a concrete example for `get()`, if your implementation previously did:
|
||||
|
||||
```js
|
||||
class ExampleLeveldown extends AbstractLevelDOWN {
|
||||
_get (key, options, callback) {
|
||||
if (options.asBuffer) {
|
||||
this.nextTick(callback, null, Buffer.from('abc'))
|
||||
} else {
|
||||
this.nextTick(callback, null, 'abc')
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You must now do (if still relevant):
|
||||
|
||||
```js
|
||||
class ExampleLevel extends AbstractLevel {
|
||||
_get (key, options, callback) {
|
||||
if (options.valueEncoding === 'buffer') {
|
||||
this.nextTick(callback, null, Buffer.from('abc'))
|
||||
} else {
|
||||
this.nextTick(callback, null, 'abc')
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The encoding options and data received by the private API depend on which encodings it supports. It must declare those via the manifest passed to the `AbstractLevel` constructor. See the [`README`](README.md) for details. For example, an implementation might only support storing data as Uint8Arrays, known here as the `'view'` encoding:
|
||||
|
||||
```js
|
||||
class ExampleLevel extends AbstractLevel {
|
||||
constructor (location, options) {
|
||||
super({ encodings: { view: true } }, options)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The earlier `put()` example would then result in `db._put(key, value, { valueEncoding: 'view' }, callback)` where `value` is a Uint8Array containing JSON in binary form. And the earlier `_get()` example can be simplified to:
|
||||
|
||||
```js
|
||||
class ExampleLevel extends AbstractLevel {
|
||||
_get (key, options, callback) {
|
||||
// No need to check valueEncoding as it's always 'view'
|
||||
this.nextTick(callback, null, new Uint8Array(...))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Implementations can also declare support of multiple encodings; keys and values will then be encoded via the most optimal path. For example:
|
||||
|
||||
```js
|
||||
super({
|
||||
encodings: {
|
||||
view: true,
|
||||
utf8: true
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### 2.3. Other notable changes
|
||||
|
||||
- The `AbstractIterator` constructor now requires an `options` argument, for encoding options
|
||||
- The `AbstractIterator#_seek()` method got a new `options` argument, for a `keyEncoding` option
|
||||
- The `db.supports.bufferKeys` property has been removed. Use `db.supports.encodings.buffer` instead.
|
||||
|
||||
### 3. Streams have moved
|
||||
|
||||
Node.js readable streams must now be created with a new standalone module called [`level-read-stream`](https://github.com/Level/read-stream), rather than database methods like `db.createReadStream()`. Please see its [upgrade guide](https://github.com/Level/read-stream/blob/main/UPGRADING.md#100) for details.
|
||||
|
||||
To offer an alternative to `db.createKeyStream()` and `db.createValueStream()`, two new types of iterators have been added: `db.keys()` and `db.values()`. Their default implementations are functional but implementors may want to override them for optimal performance. The same goes for two new methods on iterators: `nextv()` and `all()`. To achieve this and honor the `limit` option, abstract iterators now count how many items they yielded, which may remove the need for implementations to do so on their own. Please see the README for details.
|
||||
|
||||
### 4. Zero-length keys and range options are now valid
|
||||
|
||||
These keys sort before anything else. Historically they weren't supported for causing segmentation faults in `leveldown`. That doesn't apply to today's codebase. Implementations must now support:
|
||||
|
||||
```js
|
||||
await db.put('', 'example')
|
||||
|
||||
console.log(await db.get('')) // 'example'
|
||||
|
||||
for await (const [key, value] of db.iterator({ lte: '' })) {
|
||||
console.log(value) // 'example'
|
||||
}
|
||||
```
|
||||
|
||||
Same goes for zero-length Buffer and Uint8Array keys. Zero-length keys would previously result in an error and never reach the private API.
|
||||
|
||||
### 5. Resources are auto-closed
|
||||
|
||||
To further improve safety and consistency, additional changes were made that make an `abstract-level` database safer to use than `abstract-leveldown` wrapped with `levelup`.
|
||||
|
||||
#### 5.1. Closing iterators is idempotent
|
||||
|
||||
The `iterator.end()` method has been renamed to `iterator.close()`, with `end()` being an alias until a next major version in the future. The term "close" makes it easier to differentiate between the iterator having reached its natural end (data-wise) versus closing it to cleanup resources. If you previously did:
|
||||
|
||||
```js
|
||||
const iterator = db.iterator()
|
||||
iterator.end(callback)
|
||||
```
|
||||
|
||||
You should now do one of:
|
||||
|
||||
```js
|
||||
iterator.close(callback)
|
||||
await iterator.close()
|
||||
```
|
||||
|
||||
Likewise, in the private API for implementors, `_end()` has been renamed to `_close()` but without an alias. This method is no longer allowed to yield an error.
|
||||
|
||||
On `db.close()`, non-closed iterators are now automatically closed. This may be a breaking change but only if an implementation has (at its own risk) overridden the public `end()` method, because `close()` or `end()` is now an idempotent operation rather than yielding an `end() already called on iterator` error. If a `next()` call is in progress, closing the iterator (or database) will wait for that.
|
||||
|
||||
The error message `cannot call next() after end()` has been replaced with code `LEVEL_ITERATOR_NOT_OPEN`, the error `cannot call seek() after end()` has been removed in favor of a silent return, and `cannot call next() before previous next() has completed` and `cannot call seek() before next() has completed` have been replaced with code `LEVEL_ITERATOR_BUSY`.
|
||||
|
||||
The `next()` method no longer returns `this` (when a callback is provided).
|
||||
|
||||
#### 5.2. Chained batch can be closed
|
||||
|
||||
Chained batch has a new method `close()` which is an idempotent operation and automatically called after `write()` (for backwards compatibility) or on `db.close()`. This to ensure batches can't be used after closing and reopening a db. If a `write()` is in progress, closing will wait for that. If `write()` is never called then `close()` must be. For example:
|
||||
|
||||
```js
|
||||
const batch = db.batch()
|
||||
.put('abc', 'zyz')
|
||||
.del('foo')
|
||||
|
||||
if (someCondition) {
|
||||
await batch.write()
|
||||
} else {
|
||||
// Decided not to commit
|
||||
await batch.close()
|
||||
}
|
||||
|
||||
// In either case this will throw
|
||||
batch.put('more', 'data')
|
||||
```
|
||||
|
||||
These changes could be breaking for an implementation that has (at its own risk) overridden the public `write()` method. In addition, the error message `write() already called on this batch` has been replaced with code `LEVEL_BATCH_NOT_OPEN`.
|
||||
|
||||
An implementation can optionally override `AbstractChainedBatch#_close()` if it has resources to free and wishes to free them earlier than GC would.
|
||||
|
||||
### 6. Errors now use codes
|
||||
|
||||
The [`level-errors`](https://github.com/Level/errors) module as used by `levelup` and friends, is not used or exposed by `abstract-level`. Instead errors thrown or yielded from a database have a `code` property. See the [`README`](./README.md#errors) for details. Going forward, the semver contract will be on `code` and error messages will change without a semver-major bump.
|
||||
|
||||
To minimize breakage, the most used error as yielded by `get()` when an entry is not found, has the same properties that `level-errors` added (`notFound` and `status`) in addition to code `LEVEL_NOT_FOUND`. Those properties will be removed in a future version. Implementations can still yield an error that matches `/NotFound/i.test(err)` or they can start using the code. Either way `abstract-level` will normalize the error.
|
||||
|
||||
If you previously did:
|
||||
|
||||
```js
|
||||
db.get('abc', function (err, value) {
|
||||
if (err && err.notFound) {
|
||||
// Handle missing entry
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
That will still work but it's preferred to do:
|
||||
|
||||
```js
|
||||
db.get('abc', function (err, value) {
|
||||
if (err && err.code === 'LEVEL_NOT_FOUND') {
|
||||
// Handle missing entry
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Or using promises:
|
||||
|
||||
```js
|
||||
try {
|
||||
const value = await db.get('abc')
|
||||
} catch (err) {
|
||||
if (err.code === 'LEVEL_NOT_FOUND') {
|
||||
// Handle missing entry
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Semi-private properties have been removed
|
||||
|
||||
The following properties and methods can no longer be accessed, as they've been removed or replaced with internal [symbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol):
|
||||
|
||||
- `AbstractIterator#_nexting`
|
||||
- `AbstractIterator#_ended`
|
||||
- `AbstractChainedBatch#_written`
|
||||
- `AbstractChainedBatch#_checkWritten()`
|
||||
- `AbstractChainedBatch#_operations`
|
||||
- `AbstractLevel#_setupIteratorOptions()`
|
||||
|
||||
### 8. Changes to test suite
|
||||
|
||||
_You can skip this section if you're consuming (rather than writing) an `abstract-level` implementation._
|
||||
|
||||
The abstract test suite of `abstract-level` has some breaking changes compared to `abstract-leveldown`:
|
||||
|
||||
- Options to skip tests have been removed in favor of `db.supports`
|
||||
- Support of `db.clear()` and `db.getMany()` is now mandatory. The default (slow) implementation of `_clear()` has been removed.
|
||||
- Added tests that `gte` and `lte` range options take precedence over `gt` and `lt` respectively. This is incompatible with [`ltgt`](https://github.com/dominictarr/ltgt) but aligns with `subleveldown`, [`level-option-wrap`](https://github.com/substack/level-option-wrap) and half of `leveldown`. There was no good choice.
|
||||
- The `setUp` and `tearDown` functions have been removed from the test suite and `suite.common()`.
|
||||
- Added ability to access manifests via `testCommon.supports`, by lazily copying it from `testCommon.factory().supports`. This requires that the manifest does not change during the lifetime of a `db`.
|
||||
- Your `factory()` function must now accept an `options` argument.
|
||||
|
||||
Many tests were imported from `levelup`, `encoding-down`, `deferred-leveldown`, `memdown`, `level-js` and `leveldown`. They test the changes described above and improve coverage of existing behavior.
|
||||
|
||||
Lastly, it's recommended to revisit any custom tests of an implementation. In particular if those tests relied upon the previously loose state checking of `abstract-leveldown`. For example, making a `db.put()` call before `db.open()`. Such a test now has a different meaning. The previous meaning can typically be restored by inserting `db.once('open', ...)` or `await db.open()` logic.
|
||||
|
||||
### 9. Sublevels are builtin
|
||||
|
||||
_This section is only relevant if you use [`subleveldown`](https://github.com/Level/subleveldown) (which can not wrap an `abstract-level` database)._
|
||||
|
||||
Sublevels are now builtin. If you previously did:
|
||||
|
||||
```js
|
||||
const sub = require('subleveldown')
|
||||
const example1 = sub(db, 'example1')
|
||||
const example2 = sub(db, 'example2', { valueEncoding: 'json' })
|
||||
```
|
||||
|
||||
You must now do:
|
||||
|
||||
```js
|
||||
const example1 = db.sublevel('example1')
|
||||
const example2 = db.sublevel('example2', { valueEncoding: 'json' })
|
||||
```
|
||||
|
||||
The key structure is equal to that of `subleveldown`. This means that an `abstract-level` sublevel can read sublevels previously created with (and populated by) `subleveldown`. There are some new features:
|
||||
|
||||
- `db.batch(..)` takes a `sublevel` option on operations, to atomically commit data to multiple sublevels
|
||||
- Sublevels support Uint8Array in addition to Buffer
|
||||
- `AbstractLevel#_sublevel()` can be overridden to add additional methods to sublevels.
|
||||
|
||||
To reduce function overloads, the prefix argument (`example1` above) is now required and it's called `name` here. If you previously did one of the following, resulting in an empty name:
|
||||
|
||||
```js
|
||||
subleveldown(db)
|
||||
subleveldown(db, { separator: '@' })
|
||||
```
|
||||
|
||||
You must now use an explicit empty name:
|
||||
|
||||
```js
|
||||
db.sublevel('')
|
||||
db.sublevel('', { separator: '@' })
|
||||
```
|
||||
|
||||
The string shorthand for `{ separator }` has also been removed. If you previously did:
|
||||
|
||||
```js
|
||||
subleveldown(db, 'example', '@')
|
||||
```
|
||||
|
||||
You must now do:
|
||||
|
||||
```js
|
||||
db.sublevel('example', { separator: '@' })
|
||||
```
|
||||
|
||||
Third, the `open` option has been removed. If you need an asynchronous open hook, feel free to open an issue to discuss restoring this API. Should it support promises? Should `abstract-level` support it on any database and not just sublevels?
|
||||
|
||||
Lastly, the error message `Parent database is not open` (courtesy of `subleveldown` which had to check open state to prevent segmentation faults from underlying databases) changed to error code [`LEVEL_DATABASE_NOT_OPEN`](https://github.com/Level/abstract-level#errors) (courtesy of `abstract-level` which does those checks on any database).
|
||||
|
||||
---
|
||||
|
||||
_For earlier releases, before `abstract-level` was forked from `abstract-leveldown` (v7.2.0), please see [the upgrade guide of `abstract-leveldown`](https://github.com/Level/abstract-leveldown/blob/master/UPGRADING.md)._
|
||||
Generated
Vendored
+181
@@ -0,0 +1,181 @@
|
||||
'use strict'
|
||||
|
||||
const { fromCallback } = require('catering')
|
||||
const ModuleError = require('module-error')
|
||||
const { getCallback, getOptions } = require('./lib/common')
|
||||
|
||||
const kPromise = Symbol('promise')
|
||||
const kStatus = Symbol('status')
|
||||
const kOperations = Symbol('operations')
|
||||
const kFinishClose = Symbol('finishClose')
|
||||
const kCloseCallbacks = Symbol('closeCallbacks')
|
||||
|
||||
class AbstractChainedBatch {
|
||||
constructor (db) {
|
||||
if (typeof db !== 'object' || db === null) {
|
||||
const hint = db === null ? 'null' : typeof db
|
||||
throw new TypeError(`The first argument must be an abstract-level database, received ${hint}`)
|
||||
}
|
||||
|
||||
this[kOperations] = []
|
||||
this[kCloseCallbacks] = []
|
||||
this[kStatus] = 'open'
|
||||
this[kFinishClose] = this[kFinishClose].bind(this)
|
||||
|
||||
this.db = db
|
||||
this.db.attachResource(this)
|
||||
this.nextTick = db.nextTick
|
||||
}
|
||||
|
||||
get length () {
|
||||
return this[kOperations].length
|
||||
}
|
||||
|
||||
put (key, value, options) {
|
||||
if (this[kStatus] !== 'open') {
|
||||
throw new ModuleError('Batch is not open: cannot call put() after write() or close()', {
|
||||
code: 'LEVEL_BATCH_NOT_OPEN'
|
||||
})
|
||||
}
|
||||
|
||||
const err = this.db._checkKey(key) || this.db._checkValue(value)
|
||||
if (err) throw err
|
||||
|
||||
const db = options && options.sublevel != null ? options.sublevel : this.db
|
||||
const original = options
|
||||
const keyEncoding = db.keyEncoding(options && options.keyEncoding)
|
||||
const valueEncoding = db.valueEncoding(options && options.valueEncoding)
|
||||
const keyFormat = keyEncoding.format
|
||||
|
||||
// Forward encoding options
|
||||
options = { ...options, keyEncoding: keyFormat, valueEncoding: valueEncoding.format }
|
||||
|
||||
// Prevent double prefixing
|
||||
if (db !== this.db) {
|
||||
options.sublevel = null
|
||||
}
|
||||
|
||||
const mappedKey = db.prefixKey(keyEncoding.encode(key), keyFormat)
|
||||
const mappedValue = valueEncoding.encode(value)
|
||||
|
||||
this._put(mappedKey, mappedValue, options)
|
||||
this[kOperations].push({ ...original, type: 'put', key, value })
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
_put (key, value, options) {}
|
||||
|
||||
del (key, options) {
|
||||
if (this[kStatus] !== 'open') {
|
||||
throw new ModuleError('Batch is not open: cannot call del() after write() or close()', {
|
||||
code: 'LEVEL_BATCH_NOT_OPEN'
|
||||
})
|
||||
}
|
||||
|
||||
const err = this.db._checkKey(key)
|
||||
if (err) throw err
|
||||
|
||||
const db = options && options.sublevel != null ? options.sublevel : this.db
|
||||
const original = options
|
||||
const keyEncoding = db.keyEncoding(options && options.keyEncoding)
|
||||
const keyFormat = keyEncoding.format
|
||||
|
||||
// Forward encoding options
|
||||
options = { ...options, keyEncoding: keyFormat }
|
||||
|
||||
// Prevent double prefixing
|
||||
if (db !== this.db) {
|
||||
options.sublevel = null
|
||||
}
|
||||
|
||||
this._del(db.prefixKey(keyEncoding.encode(key), keyFormat), options)
|
||||
this[kOperations].push({ ...original, type: 'del', key })
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
_del (key, options) {}
|
||||
|
||||
clear () {
|
||||
if (this[kStatus] !== 'open') {
|
||||
throw new ModuleError('Batch is not open: cannot call clear() after write() or close()', {
|
||||
code: 'LEVEL_BATCH_NOT_OPEN'
|
||||
})
|
||||
}
|
||||
|
||||
this._clear()
|
||||
this[kOperations] = []
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
_clear () {}
|
||||
|
||||
write (options, callback) {
|
||||
callback = getCallback(options, callback)
|
||||
callback = fromCallback(callback, kPromise)
|
||||
options = getOptions(options)
|
||||
|
||||
if (this[kStatus] !== 'open') {
|
||||
this.nextTick(callback, new ModuleError('Batch is not open: cannot call write() after write() or close()', {
|
||||
code: 'LEVEL_BATCH_NOT_OPEN'
|
||||
}))
|
||||
} else if (this.length === 0) {
|
||||
this.close(callback)
|
||||
} else {
|
||||
this[kStatus] = 'writing'
|
||||
this._write(options, (err) => {
|
||||
this[kStatus] = 'closing'
|
||||
this[kCloseCallbacks].push(() => callback(err))
|
||||
|
||||
// Emit after setting 'closing' status, because event may trigger a
|
||||
// db close which in turn triggers (idempotently) closing this batch.
|
||||
if (!err) this.db.emit('batch', this[kOperations])
|
||||
|
||||
this._close(this[kFinishClose])
|
||||
})
|
||||
}
|
||||
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
_write (options, callback) {}
|
||||
|
||||
close (callback) {
|
||||
callback = fromCallback(callback, kPromise)
|
||||
|
||||
if (this[kStatus] === 'closing') {
|
||||
this[kCloseCallbacks].push(callback)
|
||||
} else if (this[kStatus] === 'closed') {
|
||||
this.nextTick(callback)
|
||||
} else {
|
||||
this[kCloseCallbacks].push(callback)
|
||||
|
||||
if (this[kStatus] !== 'writing') {
|
||||
this[kStatus] = 'closing'
|
||||
this._close(this[kFinishClose])
|
||||
}
|
||||
}
|
||||
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
_close (callback) {
|
||||
this.nextTick(callback)
|
||||
}
|
||||
|
||||
[kFinishClose] () {
|
||||
this[kStatus] = 'closed'
|
||||
this.db.detachResource(this)
|
||||
|
||||
const callbacks = this[kCloseCallbacks]
|
||||
this[kCloseCallbacks] = []
|
||||
|
||||
for (const cb of callbacks) {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.AbstractChainedBatch = AbstractChainedBatch
|
||||
Generated
Vendored
+490
@@ -0,0 +1,490 @@
|
||||
'use strict'
|
||||
|
||||
const { fromCallback } = require('catering')
|
||||
const ModuleError = require('module-error')
|
||||
const { getOptions, getCallback } = require('./lib/common')
|
||||
|
||||
const kPromise = Symbol('promise')
|
||||
const kCallback = Symbol('callback')
|
||||
const kWorking = Symbol('working')
|
||||
const kHandleOne = Symbol('handleOne')
|
||||
const kHandleMany = Symbol('handleMany')
|
||||
const kAutoClose = Symbol('autoClose')
|
||||
const kFinishWork = Symbol('finishWork')
|
||||
const kReturnMany = Symbol('returnMany')
|
||||
const kClosing = Symbol('closing')
|
||||
const kHandleClose = Symbol('handleClose')
|
||||
const kClosed = Symbol('closed')
|
||||
const kCloseCallbacks = Symbol('closeCallbacks')
|
||||
const kKeyEncoding = Symbol('keyEncoding')
|
||||
const kValueEncoding = Symbol('valueEncoding')
|
||||
const kAbortOnClose = Symbol('abortOnClose')
|
||||
const kLegacy = Symbol('legacy')
|
||||
const kKeys = Symbol('keys')
|
||||
const kValues = Symbol('values')
|
||||
const kLimit = Symbol('limit')
|
||||
const kCount = Symbol('count')
|
||||
|
||||
const emptyOptions = Object.freeze({})
|
||||
const noop = () => {}
|
||||
let warnedEnd = false
|
||||
|
||||
// This class is an internal utility for common functionality between AbstractIterator,
|
||||
// AbstractKeyIterator and AbstractValueIterator. It's not exported.
|
||||
class CommonIterator {
|
||||
constructor (db, options, legacy) {
|
||||
if (typeof db !== 'object' || db === null) {
|
||||
const hint = db === null ? 'null' : typeof db
|
||||
throw new TypeError(`The first argument must be an abstract-level database, received ${hint}`)
|
||||
}
|
||||
|
||||
if (typeof options !== 'object' || options === null) {
|
||||
throw new TypeError('The second argument must be an options object')
|
||||
}
|
||||
|
||||
this[kClosed] = false
|
||||
this[kCloseCallbacks] = []
|
||||
this[kWorking] = false
|
||||
this[kClosing] = false
|
||||
this[kAutoClose] = false
|
||||
this[kCallback] = null
|
||||
this[kHandleOne] = this[kHandleOne].bind(this)
|
||||
this[kHandleMany] = this[kHandleMany].bind(this)
|
||||
this[kHandleClose] = this[kHandleClose].bind(this)
|
||||
this[kKeyEncoding] = options[kKeyEncoding]
|
||||
this[kValueEncoding] = options[kValueEncoding]
|
||||
this[kLegacy] = legacy
|
||||
this[kLimit] = Number.isInteger(options.limit) && options.limit >= 0 ? options.limit : Infinity
|
||||
this[kCount] = 0
|
||||
|
||||
// Undocumented option to abort pending work on close(). Used by the
|
||||
// many-level module as a temporary solution to a blocked close().
|
||||
// TODO (next major): consider making this the default behavior. Native
|
||||
// implementations should have their own logic to safely close iterators.
|
||||
this[kAbortOnClose] = !!options.abortOnClose
|
||||
|
||||
this.db = db
|
||||
this.db.attachResource(this)
|
||||
this.nextTick = db.nextTick
|
||||
}
|
||||
|
||||
get count () {
|
||||
return this[kCount]
|
||||
}
|
||||
|
||||
get limit () {
|
||||
return this[kLimit]
|
||||
}
|
||||
|
||||
next (callback) {
|
||||
let promise
|
||||
|
||||
if (callback === undefined) {
|
||||
promise = new Promise((resolve, reject) => {
|
||||
callback = (err, key, value) => {
|
||||
if (err) reject(err)
|
||||
else if (!this[kLegacy]) resolve(key)
|
||||
else if (key === undefined && value === undefined) resolve()
|
||||
else resolve([key, value])
|
||||
}
|
||||
})
|
||||
} else if (typeof callback !== 'function') {
|
||||
throw new TypeError('Callback must be a function')
|
||||
}
|
||||
|
||||
if (this[kClosing]) {
|
||||
this.nextTick(callback, new ModuleError('Iterator is not open: cannot call next() after close()', {
|
||||
code: 'LEVEL_ITERATOR_NOT_OPEN'
|
||||
}))
|
||||
} else if (this[kWorking]) {
|
||||
this.nextTick(callback, new ModuleError('Iterator is busy: cannot call next() until previous call has completed', {
|
||||
code: 'LEVEL_ITERATOR_BUSY'
|
||||
}))
|
||||
} else {
|
||||
this[kWorking] = true
|
||||
this[kCallback] = callback
|
||||
|
||||
if (this[kCount] >= this[kLimit]) this.nextTick(this[kHandleOne], null)
|
||||
else this._next(this[kHandleOne])
|
||||
}
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
_next (callback) {
|
||||
this.nextTick(callback)
|
||||
}
|
||||
|
||||
nextv (size, options, callback) {
|
||||
callback = getCallback(options, callback)
|
||||
callback = fromCallback(callback, kPromise)
|
||||
options = getOptions(options, emptyOptions)
|
||||
|
||||
if (!Number.isInteger(size)) {
|
||||
this.nextTick(callback, new TypeError("The first argument 'size' must be an integer"))
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
if (this[kClosing]) {
|
||||
this.nextTick(callback, new ModuleError('Iterator is not open: cannot call nextv() after close()', {
|
||||
code: 'LEVEL_ITERATOR_NOT_OPEN'
|
||||
}))
|
||||
} else if (this[kWorking]) {
|
||||
this.nextTick(callback, new ModuleError('Iterator is busy: cannot call nextv() until previous call has completed', {
|
||||
code: 'LEVEL_ITERATOR_BUSY'
|
||||
}))
|
||||
} else {
|
||||
if (size < 1) size = 1
|
||||
if (this[kLimit] < Infinity) size = Math.min(size, this[kLimit] - this[kCount])
|
||||
|
||||
this[kWorking] = true
|
||||
this[kCallback] = callback
|
||||
|
||||
if (size <= 0) this.nextTick(this[kHandleMany], null, [])
|
||||
else this._nextv(size, options, this[kHandleMany])
|
||||
}
|
||||
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
_nextv (size, options, callback) {
|
||||
const acc = []
|
||||
const onnext = (err, key, value) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
} else if (this[kLegacy] ? key === undefined && value === undefined : key === undefined) {
|
||||
return callback(null, acc)
|
||||
}
|
||||
|
||||
acc.push(this[kLegacy] ? [key, value] : key)
|
||||
|
||||
if (acc.length === size) {
|
||||
callback(null, acc)
|
||||
} else {
|
||||
this._next(onnext)
|
||||
}
|
||||
}
|
||||
|
||||
this._next(onnext)
|
||||
}
|
||||
|
||||
all (options, callback) {
|
||||
callback = getCallback(options, callback)
|
||||
callback = fromCallback(callback, kPromise)
|
||||
options = getOptions(options, emptyOptions)
|
||||
|
||||
if (this[kClosing]) {
|
||||
this.nextTick(callback, new ModuleError('Iterator is not open: cannot call all() after close()', {
|
||||
code: 'LEVEL_ITERATOR_NOT_OPEN'
|
||||
}))
|
||||
} else if (this[kWorking]) {
|
||||
this.nextTick(callback, new ModuleError('Iterator is busy: cannot call all() until previous call has completed', {
|
||||
code: 'LEVEL_ITERATOR_BUSY'
|
||||
}))
|
||||
} else {
|
||||
this[kWorking] = true
|
||||
this[kCallback] = callback
|
||||
this[kAutoClose] = true
|
||||
|
||||
if (this[kCount] >= this[kLimit]) this.nextTick(this[kHandleMany], null, [])
|
||||
else this._all(options, this[kHandleMany])
|
||||
}
|
||||
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
_all (options, callback) {
|
||||
// Must count here because we're directly calling _nextv()
|
||||
let count = this[kCount]
|
||||
const acc = []
|
||||
|
||||
const nextv = () => {
|
||||
// Not configurable, because implementations should optimize _all().
|
||||
const size = this[kLimit] < Infinity ? Math.min(1e3, this[kLimit] - count) : 1e3
|
||||
|
||||
if (size <= 0) {
|
||||
this.nextTick(callback, null, acc)
|
||||
} else {
|
||||
this._nextv(size, emptyOptions, onnextv)
|
||||
}
|
||||
}
|
||||
|
||||
const onnextv = (err, items) => {
|
||||
if (err) {
|
||||
callback(err)
|
||||
} else if (items.length === 0) {
|
||||
callback(null, acc)
|
||||
} else {
|
||||
acc.push.apply(acc, items)
|
||||
count += items.length
|
||||
nextv()
|
||||
}
|
||||
}
|
||||
|
||||
nextv()
|
||||
}
|
||||
|
||||
[kFinishWork] () {
|
||||
const cb = this[kCallback]
|
||||
|
||||
// Callback will be null if work was aborted on close
|
||||
if (this[kAbortOnClose] && cb === null) return noop
|
||||
|
||||
this[kWorking] = false
|
||||
this[kCallback] = null
|
||||
|
||||
if (this[kClosing]) this._close(this[kHandleClose])
|
||||
|
||||
return cb
|
||||
}
|
||||
|
||||
[kReturnMany] (cb, err, items) {
|
||||
if (this[kAutoClose]) {
|
||||
this.close(cb.bind(null, err, items))
|
||||
} else {
|
||||
cb(err, items)
|
||||
}
|
||||
}
|
||||
|
||||
seek (target, options) {
|
||||
options = getOptions(options, emptyOptions)
|
||||
|
||||
if (this[kClosing]) {
|
||||
// Don't throw here, to be kind to implementations that wrap
|
||||
// another db and don't necessarily control when the db is closed
|
||||
} else if (this[kWorking]) {
|
||||
throw new ModuleError('Iterator is busy: cannot call seek() until next() has completed', {
|
||||
code: 'LEVEL_ITERATOR_BUSY'
|
||||
})
|
||||
} else {
|
||||
const keyEncoding = this.db.keyEncoding(options.keyEncoding || this[kKeyEncoding])
|
||||
const keyFormat = keyEncoding.format
|
||||
|
||||
if (options.keyEncoding !== keyFormat) {
|
||||
options = { ...options, keyEncoding: keyFormat }
|
||||
}
|
||||
|
||||
const mapped = this.db.prefixKey(keyEncoding.encode(target), keyFormat)
|
||||
this._seek(mapped, options)
|
||||
}
|
||||
}
|
||||
|
||||
_seek (target, options) {
|
||||
throw new ModuleError('Iterator does not support seek()', {
|
||||
code: 'LEVEL_NOT_SUPPORTED'
|
||||
})
|
||||
}
|
||||
|
||||
close (callback) {
|
||||
callback = fromCallback(callback, kPromise)
|
||||
|
||||
if (this[kClosed]) {
|
||||
this.nextTick(callback)
|
||||
} else if (this[kClosing]) {
|
||||
this[kCloseCallbacks].push(callback)
|
||||
} else {
|
||||
this[kClosing] = true
|
||||
this[kCloseCallbacks].push(callback)
|
||||
|
||||
if (!this[kWorking]) {
|
||||
this._close(this[kHandleClose])
|
||||
} else if (this[kAbortOnClose]) {
|
||||
// Don't wait for work to finish. Subsequently ignore the result.
|
||||
const cb = this[kFinishWork]()
|
||||
|
||||
cb(new ModuleError('Aborted on iterator close()', {
|
||||
code: 'LEVEL_ITERATOR_NOT_OPEN'
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
_close (callback) {
|
||||
this.nextTick(callback)
|
||||
}
|
||||
|
||||
[kHandleClose] () {
|
||||
this[kClosed] = true
|
||||
this.db.detachResource(this)
|
||||
|
||||
const callbacks = this[kCloseCallbacks]
|
||||
this[kCloseCallbacks] = []
|
||||
|
||||
for (const cb of callbacks) {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
async * [Symbol.asyncIterator] () {
|
||||
try {
|
||||
let item
|
||||
|
||||
while ((item = (await this.next())) !== undefined) {
|
||||
yield item
|
||||
}
|
||||
} finally {
|
||||
if (!this[kClosed]) await this.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility this class is not (yet) called AbstractEntryIterator.
|
||||
class AbstractIterator extends CommonIterator {
|
||||
constructor (db, options) {
|
||||
super(db, options, true)
|
||||
this[kKeys] = options.keys !== false
|
||||
this[kValues] = options.values !== false
|
||||
}
|
||||
|
||||
[kHandleOne] (err, key, value) {
|
||||
const cb = this[kFinishWork]()
|
||||
if (err) return cb(err)
|
||||
|
||||
try {
|
||||
key = this[kKeys] && key !== undefined ? this[kKeyEncoding].decode(key) : undefined
|
||||
value = this[kValues] && value !== undefined ? this[kValueEncoding].decode(value) : undefined
|
||||
} catch (err) {
|
||||
return cb(new IteratorDecodeError('entry', err))
|
||||
}
|
||||
|
||||
if (!(key === undefined && value === undefined)) {
|
||||
this[kCount]++
|
||||
}
|
||||
|
||||
cb(null, key, value)
|
||||
}
|
||||
|
||||
[kHandleMany] (err, entries) {
|
||||
const cb = this[kFinishWork]()
|
||||
if (err) return this[kReturnMany](cb, err)
|
||||
|
||||
try {
|
||||
for (const entry of entries) {
|
||||
const key = entry[0]
|
||||
const value = entry[1]
|
||||
|
||||
entry[0] = this[kKeys] && key !== undefined ? this[kKeyEncoding].decode(key) : undefined
|
||||
entry[1] = this[kValues] && value !== undefined ? this[kValueEncoding].decode(value) : undefined
|
||||
}
|
||||
} catch (err) {
|
||||
return this[kReturnMany](cb, new IteratorDecodeError('entries', err))
|
||||
}
|
||||
|
||||
this[kCount] += entries.length
|
||||
this[kReturnMany](cb, null, entries)
|
||||
}
|
||||
|
||||
end (callback) {
|
||||
if (!warnedEnd && typeof console !== 'undefined') {
|
||||
warnedEnd = true
|
||||
console.warn(new ModuleError(
|
||||
'The iterator.end() method was renamed to close() and end() is an alias that will be removed in a future version',
|
||||
{ code: 'LEVEL_LEGACY' }
|
||||
))
|
||||
}
|
||||
|
||||
return this.close(callback)
|
||||
}
|
||||
}
|
||||
|
||||
class AbstractKeyIterator extends CommonIterator {
|
||||
constructor (db, options) {
|
||||
super(db, options, false)
|
||||
}
|
||||
|
||||
[kHandleOne] (err, key) {
|
||||
const cb = this[kFinishWork]()
|
||||
if (err) return cb(err)
|
||||
|
||||
try {
|
||||
key = key !== undefined ? this[kKeyEncoding].decode(key) : undefined
|
||||
} catch (err) {
|
||||
return cb(new IteratorDecodeError('key', err))
|
||||
}
|
||||
|
||||
if (key !== undefined) this[kCount]++
|
||||
cb(null, key)
|
||||
}
|
||||
|
||||
[kHandleMany] (err, keys) {
|
||||
const cb = this[kFinishWork]()
|
||||
if (err) return this[kReturnMany](cb, err)
|
||||
|
||||
try {
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
keys[i] = key !== undefined ? this[kKeyEncoding].decode(key) : undefined
|
||||
}
|
||||
} catch (err) {
|
||||
return this[kReturnMany](cb, new IteratorDecodeError('keys', err))
|
||||
}
|
||||
|
||||
this[kCount] += keys.length
|
||||
this[kReturnMany](cb, null, keys)
|
||||
}
|
||||
}
|
||||
|
||||
class AbstractValueIterator extends CommonIterator {
|
||||
constructor (db, options) {
|
||||
super(db, options, false)
|
||||
}
|
||||
|
||||
[kHandleOne] (err, value) {
|
||||
const cb = this[kFinishWork]()
|
||||
if (err) return cb(err)
|
||||
|
||||
try {
|
||||
value = value !== undefined ? this[kValueEncoding].decode(value) : undefined
|
||||
} catch (err) {
|
||||
return cb(new IteratorDecodeError('value', err))
|
||||
}
|
||||
|
||||
if (value !== undefined) this[kCount]++
|
||||
cb(null, value)
|
||||
}
|
||||
|
||||
[kHandleMany] (err, values) {
|
||||
const cb = this[kFinishWork]()
|
||||
if (err) return this[kReturnMany](cb, err)
|
||||
|
||||
try {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
const value = values[i]
|
||||
values[i] = value !== undefined ? this[kValueEncoding].decode(value) : undefined
|
||||
}
|
||||
} catch (err) {
|
||||
return this[kReturnMany](cb, new IteratorDecodeError('values', err))
|
||||
}
|
||||
|
||||
this[kCount] += values.length
|
||||
this[kReturnMany](cb, null, values)
|
||||
}
|
||||
}
|
||||
|
||||
// Internal utility, not typed or exported
|
||||
class IteratorDecodeError extends ModuleError {
|
||||
constructor (subject, cause) {
|
||||
super(`Iterator could not decode ${subject}`, {
|
||||
code: 'LEVEL_DECODE_ERROR',
|
||||
cause
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// To help migrating to abstract-level
|
||||
for (const k of ['_ended property', '_nexting property', '_end method']) {
|
||||
Object.defineProperty(AbstractIterator.prototype, k.split(' ')[0], {
|
||||
get () { throw new ModuleError(`The ${k} has been removed`, { code: 'LEVEL_LEGACY' }) },
|
||||
set () { throw new ModuleError(`The ${k} has been removed`, { code: 'LEVEL_LEGACY' }) }
|
||||
})
|
||||
}
|
||||
|
||||
// Exposed so that AbstractLevel can set these options
|
||||
AbstractIterator.keyEncoding = kKeyEncoding
|
||||
AbstractIterator.valueEncoding = kValueEncoding
|
||||
|
||||
exports.AbstractIterator = AbstractIterator
|
||||
exports.AbstractKeyIterator = AbstractKeyIterator
|
||||
exports.AbstractValueIterator = AbstractValueIterator
|
||||
Generated
Vendored
+818
@@ -0,0 +1,818 @@
|
||||
'use strict'
|
||||
|
||||
const { supports } = require('level-supports')
|
||||
const { Transcoder } = require('level-transcoder')
|
||||
const { EventEmitter } = require('events')
|
||||
const { fromCallback } = require('catering')
|
||||
const ModuleError = require('module-error')
|
||||
const { AbstractIterator } = require('./abstract-iterator')
|
||||
const { DefaultKeyIterator, DefaultValueIterator } = require('./lib/default-kv-iterator')
|
||||
const { DeferredIterator, DeferredKeyIterator, DeferredValueIterator } = require('./lib/deferred-iterator')
|
||||
const { DefaultChainedBatch } = require('./lib/default-chained-batch')
|
||||
const { getCallback, getOptions } = require('./lib/common')
|
||||
const rangeOptions = require('./lib/range-options')
|
||||
|
||||
const kPromise = Symbol('promise')
|
||||
const kLanded = Symbol('landed')
|
||||
const kResources = Symbol('resources')
|
||||
const kCloseResources = Symbol('closeResources')
|
||||
const kOperations = Symbol('operations')
|
||||
const kUndefer = Symbol('undefer')
|
||||
const kDeferOpen = Symbol('deferOpen')
|
||||
const kOptions = Symbol('options')
|
||||
const kStatus = Symbol('status')
|
||||
const kDefaultOptions = Symbol('defaultOptions')
|
||||
const kTranscoder = Symbol('transcoder')
|
||||
const kKeyEncoding = Symbol('keyEncoding')
|
||||
const kValueEncoding = Symbol('valueEncoding')
|
||||
const noop = () => {}
|
||||
|
||||
class AbstractLevel extends EventEmitter {
|
||||
constructor (manifest, options) {
|
||||
super()
|
||||
|
||||
if (typeof manifest !== 'object' || manifest === null) {
|
||||
throw new TypeError("The first argument 'manifest' must be an object")
|
||||
}
|
||||
|
||||
options = getOptions(options)
|
||||
const { keyEncoding, valueEncoding, passive, ...forward } = options
|
||||
|
||||
this[kResources] = new Set()
|
||||
this[kOperations] = []
|
||||
this[kDeferOpen] = true
|
||||
this[kOptions] = forward
|
||||
this[kStatus] = 'opening'
|
||||
|
||||
this.supports = supports(manifest, {
|
||||
status: true,
|
||||
promises: true,
|
||||
clear: true,
|
||||
getMany: true,
|
||||
deferredOpen: true,
|
||||
|
||||
// TODO (next major): add seek
|
||||
snapshots: manifest.snapshots !== false,
|
||||
permanence: manifest.permanence !== false,
|
||||
|
||||
// TODO: remove from level-supports because it's always supported
|
||||
keyIterator: true,
|
||||
valueIterator: true,
|
||||
iteratorNextv: true,
|
||||
iteratorAll: true,
|
||||
|
||||
encodings: manifest.encodings || {},
|
||||
events: Object.assign({}, manifest.events, {
|
||||
opening: true,
|
||||
open: true,
|
||||
closing: true,
|
||||
closed: true,
|
||||
put: true,
|
||||
del: true,
|
||||
batch: true,
|
||||
clear: true
|
||||
})
|
||||
})
|
||||
|
||||
this[kTranscoder] = new Transcoder(formats(this))
|
||||
this[kKeyEncoding] = this[kTranscoder].encoding(keyEncoding || 'utf8')
|
||||
this[kValueEncoding] = this[kTranscoder].encoding(valueEncoding || 'utf8')
|
||||
|
||||
// Add custom and transcoder encodings to manifest
|
||||
for (const encoding of this[kTranscoder].encodings()) {
|
||||
if (!this.supports.encodings[encoding.commonName]) {
|
||||
this.supports.encodings[encoding.commonName] = true
|
||||
}
|
||||
}
|
||||
|
||||
this[kDefaultOptions] = {
|
||||
empty: Object.freeze({}),
|
||||
entry: Object.freeze({
|
||||
keyEncoding: this[kKeyEncoding].commonName,
|
||||
valueEncoding: this[kValueEncoding].commonName
|
||||
}),
|
||||
key: Object.freeze({
|
||||
keyEncoding: this[kKeyEncoding].commonName
|
||||
})
|
||||
}
|
||||
|
||||
// Let subclass finish its constructor
|
||||
this.nextTick(() => {
|
||||
if (this[kDeferOpen]) {
|
||||
this.open({ passive: false }, noop)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
get status () {
|
||||
return this[kStatus]
|
||||
}
|
||||
|
||||
keyEncoding (encoding) {
|
||||
return this[kTranscoder].encoding(encoding != null ? encoding : this[kKeyEncoding])
|
||||
}
|
||||
|
||||
valueEncoding (encoding) {
|
||||
return this[kTranscoder].encoding(encoding != null ? encoding : this[kValueEncoding])
|
||||
}
|
||||
|
||||
open (options, callback) {
|
||||
callback = getCallback(options, callback)
|
||||
callback = fromCallback(callback, kPromise)
|
||||
|
||||
options = { ...this[kOptions], ...getOptions(options) }
|
||||
|
||||
options.createIfMissing = options.createIfMissing !== false
|
||||
options.errorIfExists = !!options.errorIfExists
|
||||
|
||||
const maybeOpened = (err) => {
|
||||
if (this[kStatus] === 'closing' || this[kStatus] === 'opening') {
|
||||
// Wait until pending state changes are done
|
||||
this.once(kLanded, err ? () => maybeOpened(err) : maybeOpened)
|
||||
} else if (this[kStatus] !== 'open') {
|
||||
callback(new ModuleError('Database is not open', {
|
||||
code: 'LEVEL_DATABASE_NOT_OPEN',
|
||||
cause: err
|
||||
}))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
if (options.passive) {
|
||||
if (this[kStatus] === 'opening') {
|
||||
this.once(kLanded, maybeOpened)
|
||||
} else {
|
||||
this.nextTick(maybeOpened)
|
||||
}
|
||||
} else if (this[kStatus] === 'closed' || this[kDeferOpen]) {
|
||||
this[kDeferOpen] = false
|
||||
this[kStatus] = 'opening'
|
||||
this.emit('opening')
|
||||
|
||||
this._open(options, (err) => {
|
||||
if (err) {
|
||||
this[kStatus] = 'closed'
|
||||
|
||||
// Resources must be safe to close in any db state
|
||||
this[kCloseResources](() => {
|
||||
this.emit(kLanded)
|
||||
maybeOpened(err)
|
||||
})
|
||||
|
||||
this[kUndefer]()
|
||||
return
|
||||
}
|
||||
|
||||
this[kStatus] = 'open'
|
||||
this[kUndefer]()
|
||||
this.emit(kLanded)
|
||||
|
||||
// Only emit public event if pending state changes are done
|
||||
if (this[kStatus] === 'open') this.emit('open')
|
||||
|
||||
// TODO (next major): remove this alias
|
||||
if (this[kStatus] === 'open') this.emit('ready')
|
||||
|
||||
maybeOpened()
|
||||
})
|
||||
} else if (this[kStatus] === 'open') {
|
||||
this.nextTick(maybeOpened)
|
||||
} else {
|
||||
this.once(kLanded, () => this.open(options, callback))
|
||||
}
|
||||
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
_open (options, callback) {
|
||||
this.nextTick(callback)
|
||||
}
|
||||
|
||||
close (callback) {
|
||||
callback = fromCallback(callback, kPromise)
|
||||
|
||||
const maybeClosed = (err) => {
|
||||
if (this[kStatus] === 'opening' || this[kStatus] === 'closing') {
|
||||
// Wait until pending state changes are done
|
||||
this.once(kLanded, err ? maybeClosed(err) : maybeClosed)
|
||||
} else if (this[kStatus] !== 'closed') {
|
||||
callback(new ModuleError('Database is not closed', {
|
||||
code: 'LEVEL_DATABASE_NOT_CLOSED',
|
||||
cause: err
|
||||
}))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
if (this[kStatus] === 'open') {
|
||||
this[kStatus] = 'closing'
|
||||
this.emit('closing')
|
||||
|
||||
const cancel = (err) => {
|
||||
this[kStatus] = 'open'
|
||||
this[kUndefer]()
|
||||
this.emit(kLanded)
|
||||
maybeClosed(err)
|
||||
}
|
||||
|
||||
this[kCloseResources](() => {
|
||||
this._close((err) => {
|
||||
if (err) return cancel(err)
|
||||
|
||||
this[kStatus] = 'closed'
|
||||
this[kUndefer]()
|
||||
this.emit(kLanded)
|
||||
|
||||
// Only emit public event if pending state changes are done
|
||||
if (this[kStatus] === 'closed') this.emit('closed')
|
||||
|
||||
maybeClosed()
|
||||
})
|
||||
})
|
||||
} else if (this[kStatus] === 'closed') {
|
||||
this.nextTick(maybeClosed)
|
||||
} else {
|
||||
this.once(kLanded, () => this.close(callback))
|
||||
}
|
||||
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
[kCloseResources] (callback) {
|
||||
if (this[kResources].size === 0) {
|
||||
return this.nextTick(callback)
|
||||
}
|
||||
|
||||
let pending = this[kResources].size
|
||||
let sync = true
|
||||
|
||||
const next = () => {
|
||||
if (--pending === 0) {
|
||||
// We don't have tests for generic resources, so dezalgo
|
||||
if (sync) this.nextTick(callback)
|
||||
else callback()
|
||||
}
|
||||
}
|
||||
|
||||
// In parallel so that all resources know they are closed
|
||||
for (const resource of this[kResources]) {
|
||||
resource.close(next)
|
||||
}
|
||||
|
||||
sync = false
|
||||
this[kResources].clear()
|
||||
}
|
||||
|
||||
_close (callback) {
|
||||
this.nextTick(callback)
|
||||
}
|
||||
|
||||
get (key, options, callback) {
|
||||
callback = getCallback(options, callback)
|
||||
callback = fromCallback(callback, kPromise)
|
||||
options = getOptions(options, this[kDefaultOptions].entry)
|
||||
|
||||
if (this[kStatus] === 'opening') {
|
||||
this.defer(() => this.get(key, options, callback))
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
if (maybeError(this, callback)) {
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
const err = this._checkKey(key)
|
||||
|
||||
if (err) {
|
||||
this.nextTick(callback, err)
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
const keyEncoding = this.keyEncoding(options.keyEncoding)
|
||||
const valueEncoding = this.valueEncoding(options.valueEncoding)
|
||||
const keyFormat = keyEncoding.format
|
||||
const valueFormat = valueEncoding.format
|
||||
|
||||
// Forward encoding options to the underlying store
|
||||
if (options.keyEncoding !== keyFormat || options.valueEncoding !== valueFormat) {
|
||||
// Avoid spread operator because of https://bugs.chromium.org/p/chromium/issues/detail?id=1204540
|
||||
options = Object.assign({}, options, { keyEncoding: keyFormat, valueEncoding: valueFormat })
|
||||
}
|
||||
|
||||
this._get(this.prefixKey(keyEncoding.encode(key), keyFormat), options, (err, value) => {
|
||||
if (err) {
|
||||
// Normalize not found error for backwards compatibility with abstract-leveldown and level(up)
|
||||
if (err.code === 'LEVEL_NOT_FOUND' || err.notFound || /NotFound/i.test(err)) {
|
||||
if (!err.code) err.code = 'LEVEL_NOT_FOUND' // Preferred way going forward
|
||||
if (!err.notFound) err.notFound = true // Same as level-errors
|
||||
if (!err.status) err.status = 404 // Same as level-errors
|
||||
}
|
||||
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
try {
|
||||
value = valueEncoding.decode(value)
|
||||
} catch (err) {
|
||||
return callback(new ModuleError('Could not decode value', {
|
||||
code: 'LEVEL_DECODE_ERROR',
|
||||
cause: err
|
||||
}))
|
||||
}
|
||||
|
||||
callback(null, value)
|
||||
})
|
||||
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
_get (key, options, callback) {
|
||||
this.nextTick(callback, new Error('NotFound'))
|
||||
}
|
||||
|
||||
getMany (keys, options, callback) {
|
||||
callback = getCallback(options, callback)
|
||||
callback = fromCallback(callback, kPromise)
|
||||
options = getOptions(options, this[kDefaultOptions].entry)
|
||||
|
||||
if (this[kStatus] === 'opening') {
|
||||
this.defer(() => this.getMany(keys, options, callback))
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
if (maybeError(this, callback)) {
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
if (!Array.isArray(keys)) {
|
||||
this.nextTick(callback, new TypeError("The first argument 'keys' must be an array"))
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
if (keys.length === 0) {
|
||||
this.nextTick(callback, null, [])
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
const keyEncoding = this.keyEncoding(options.keyEncoding)
|
||||
const valueEncoding = this.valueEncoding(options.valueEncoding)
|
||||
const keyFormat = keyEncoding.format
|
||||
const valueFormat = valueEncoding.format
|
||||
|
||||
// Forward encoding options
|
||||
if (options.keyEncoding !== keyFormat || options.valueEncoding !== valueFormat) {
|
||||
options = Object.assign({}, options, { keyEncoding: keyFormat, valueEncoding: valueFormat })
|
||||
}
|
||||
|
||||
const mappedKeys = new Array(keys.length)
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
const err = this._checkKey(key)
|
||||
|
||||
if (err) {
|
||||
this.nextTick(callback, err)
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
mappedKeys[i] = this.prefixKey(keyEncoding.encode(key), keyFormat)
|
||||
}
|
||||
|
||||
this._getMany(mappedKeys, options, (err, values) => {
|
||||
if (err) return callback(err)
|
||||
|
||||
try {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
if (values[i] !== undefined) {
|
||||
values[i] = valueEncoding.decode(values[i])
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
return callback(new ModuleError(`Could not decode one or more of ${values.length} value(s)`, {
|
||||
code: 'LEVEL_DECODE_ERROR',
|
||||
cause: err
|
||||
}))
|
||||
}
|
||||
|
||||
callback(null, values)
|
||||
})
|
||||
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
_getMany (keys, options, callback) {
|
||||
this.nextTick(callback, null, new Array(keys.length).fill(undefined))
|
||||
}
|
||||
|
||||
put (key, value, options, callback) {
|
||||
callback = getCallback(options, callback)
|
||||
callback = fromCallback(callback, kPromise)
|
||||
options = getOptions(options, this[kDefaultOptions].entry)
|
||||
|
||||
if (this[kStatus] === 'opening') {
|
||||
this.defer(() => this.put(key, value, options, callback))
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
if (maybeError(this, callback)) {
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
const err = this._checkKey(key) || this._checkValue(value)
|
||||
|
||||
if (err) {
|
||||
this.nextTick(callback, err)
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
const keyEncoding = this.keyEncoding(options.keyEncoding)
|
||||
const valueEncoding = this.valueEncoding(options.valueEncoding)
|
||||
const keyFormat = keyEncoding.format
|
||||
const valueFormat = valueEncoding.format
|
||||
|
||||
// Forward encoding options
|
||||
if (options.keyEncoding !== keyFormat || options.valueEncoding !== valueFormat) {
|
||||
options = Object.assign({}, options, { keyEncoding: keyFormat, valueEncoding: valueFormat })
|
||||
}
|
||||
|
||||
const mappedKey = this.prefixKey(keyEncoding.encode(key), keyFormat)
|
||||
const mappedValue = valueEncoding.encode(value)
|
||||
|
||||
this._put(mappedKey, mappedValue, options, (err) => {
|
||||
if (err) return callback(err)
|
||||
this.emit('put', key, value)
|
||||
callback()
|
||||
})
|
||||
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
_put (key, value, options, callback) {
|
||||
this.nextTick(callback)
|
||||
}
|
||||
|
||||
del (key, options, callback) {
|
||||
callback = getCallback(options, callback)
|
||||
callback = fromCallback(callback, kPromise)
|
||||
options = getOptions(options, this[kDefaultOptions].key)
|
||||
|
||||
if (this[kStatus] === 'opening') {
|
||||
this.defer(() => this.del(key, options, callback))
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
if (maybeError(this, callback)) {
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
const err = this._checkKey(key)
|
||||
|
||||
if (err) {
|
||||
this.nextTick(callback, err)
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
const keyEncoding = this.keyEncoding(options.keyEncoding)
|
||||
const keyFormat = keyEncoding.format
|
||||
|
||||
// Forward encoding options
|
||||
if (options.keyEncoding !== keyFormat) {
|
||||
options = Object.assign({}, options, { keyEncoding: keyFormat })
|
||||
}
|
||||
|
||||
this._del(this.prefixKey(keyEncoding.encode(key), keyFormat), options, (err) => {
|
||||
if (err) return callback(err)
|
||||
this.emit('del', key)
|
||||
callback()
|
||||
})
|
||||
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
_del (key, options, callback) {
|
||||
this.nextTick(callback)
|
||||
}
|
||||
|
||||
batch (operations, options, callback) {
|
||||
if (!arguments.length) {
|
||||
if (this[kStatus] === 'opening') return new DefaultChainedBatch(this)
|
||||
if (this[kStatus] !== 'open') {
|
||||
throw new ModuleError('Database is not open', {
|
||||
code: 'LEVEL_DATABASE_NOT_OPEN'
|
||||
})
|
||||
}
|
||||
return this._chainedBatch()
|
||||
}
|
||||
|
||||
if (typeof operations === 'function') callback = operations
|
||||
else callback = getCallback(options, callback)
|
||||
|
||||
callback = fromCallback(callback, kPromise)
|
||||
options = getOptions(options, this[kDefaultOptions].empty)
|
||||
|
||||
if (this[kStatus] === 'opening') {
|
||||
this.defer(() => this.batch(operations, options, callback))
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
if (maybeError(this, callback)) {
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
if (!Array.isArray(operations)) {
|
||||
this.nextTick(callback, new TypeError("The first argument 'operations' must be an array"))
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
if (operations.length === 0) {
|
||||
this.nextTick(callback)
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
const mapped = new Array(operations.length)
|
||||
const { keyEncoding: ke, valueEncoding: ve, ...forward } = options
|
||||
|
||||
for (let i = 0; i < operations.length; i++) {
|
||||
if (typeof operations[i] !== 'object' || operations[i] === null) {
|
||||
this.nextTick(callback, new TypeError('A batch operation must be an object'))
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
const op = Object.assign({}, operations[i])
|
||||
|
||||
if (op.type !== 'put' && op.type !== 'del') {
|
||||
this.nextTick(callback, new TypeError("A batch operation must have a type property that is 'put' or 'del'"))
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
const err = this._checkKey(op.key)
|
||||
|
||||
if (err) {
|
||||
this.nextTick(callback, err)
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
const db = op.sublevel != null ? op.sublevel : this
|
||||
const keyEncoding = db.keyEncoding(op.keyEncoding || ke)
|
||||
const keyFormat = keyEncoding.format
|
||||
|
||||
op.key = db.prefixKey(keyEncoding.encode(op.key), keyFormat)
|
||||
op.keyEncoding = keyFormat
|
||||
|
||||
if (op.type === 'put') {
|
||||
const valueErr = this._checkValue(op.value)
|
||||
|
||||
if (valueErr) {
|
||||
this.nextTick(callback, valueErr)
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
const valueEncoding = db.valueEncoding(op.valueEncoding || ve)
|
||||
|
||||
op.value = valueEncoding.encode(op.value)
|
||||
op.valueEncoding = valueEncoding.format
|
||||
}
|
||||
|
||||
// Prevent double prefixing
|
||||
if (db !== this) {
|
||||
op.sublevel = null
|
||||
}
|
||||
|
||||
mapped[i] = op
|
||||
}
|
||||
|
||||
this._batch(mapped, forward, (err) => {
|
||||
if (err) return callback(err)
|
||||
this.emit('batch', operations)
|
||||
callback()
|
||||
})
|
||||
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
_batch (operations, options, callback) {
|
||||
this.nextTick(callback)
|
||||
}
|
||||
|
||||
sublevel (name, options) {
|
||||
return this._sublevel(name, AbstractSublevel.defaults(options))
|
||||
}
|
||||
|
||||
_sublevel (name, options) {
|
||||
return new AbstractSublevel(this, name, options)
|
||||
}
|
||||
|
||||
prefixKey (key, keyFormat) {
|
||||
return key
|
||||
}
|
||||
|
||||
clear (options, callback) {
|
||||
callback = getCallback(options, callback)
|
||||
callback = fromCallback(callback, kPromise)
|
||||
options = getOptions(options, this[kDefaultOptions].empty)
|
||||
|
||||
if (this[kStatus] === 'opening') {
|
||||
this.defer(() => this.clear(options, callback))
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
if (maybeError(this, callback)) {
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
const original = options
|
||||
const keyEncoding = this.keyEncoding(options.keyEncoding)
|
||||
|
||||
options = rangeOptions(options, keyEncoding)
|
||||
options.keyEncoding = keyEncoding.format
|
||||
|
||||
if (options.limit === 0) {
|
||||
this.nextTick(callback)
|
||||
} else {
|
||||
this._clear(options, (err) => {
|
||||
if (err) return callback(err)
|
||||
this.emit('clear', original)
|
||||
callback()
|
||||
})
|
||||
}
|
||||
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
_clear (options, callback) {
|
||||
this.nextTick(callback)
|
||||
}
|
||||
|
||||
iterator (options) {
|
||||
const keyEncoding = this.keyEncoding(options && options.keyEncoding)
|
||||
const valueEncoding = this.valueEncoding(options && options.valueEncoding)
|
||||
|
||||
options = rangeOptions(options, keyEncoding)
|
||||
options.keys = options.keys !== false
|
||||
options.values = options.values !== false
|
||||
|
||||
// We need the original encoding options in AbstractIterator in order to decode data
|
||||
options[AbstractIterator.keyEncoding] = keyEncoding
|
||||
options[AbstractIterator.valueEncoding] = valueEncoding
|
||||
|
||||
// Forward encoding options to private API
|
||||
options.keyEncoding = keyEncoding.format
|
||||
options.valueEncoding = valueEncoding.format
|
||||
|
||||
if (this[kStatus] === 'opening') {
|
||||
return new DeferredIterator(this, options)
|
||||
} else if (this[kStatus] !== 'open') {
|
||||
throw new ModuleError('Database is not open', {
|
||||
code: 'LEVEL_DATABASE_NOT_OPEN'
|
||||
})
|
||||
}
|
||||
|
||||
return this._iterator(options)
|
||||
}
|
||||
|
||||
_iterator (options) {
|
||||
return new AbstractIterator(this, options)
|
||||
}
|
||||
|
||||
keys (options) {
|
||||
// Also include valueEncoding (though unused) because we may fallback to _iterator()
|
||||
const keyEncoding = this.keyEncoding(options && options.keyEncoding)
|
||||
const valueEncoding = this.valueEncoding(options && options.valueEncoding)
|
||||
|
||||
options = rangeOptions(options, keyEncoding)
|
||||
|
||||
// We need the original encoding options in AbstractKeyIterator in order to decode data
|
||||
options[AbstractIterator.keyEncoding] = keyEncoding
|
||||
options[AbstractIterator.valueEncoding] = valueEncoding
|
||||
|
||||
// Forward encoding options to private API
|
||||
options.keyEncoding = keyEncoding.format
|
||||
options.valueEncoding = valueEncoding.format
|
||||
|
||||
if (this[kStatus] === 'opening') {
|
||||
return new DeferredKeyIterator(this, options)
|
||||
} else if (this[kStatus] !== 'open') {
|
||||
throw new ModuleError('Database is not open', {
|
||||
code: 'LEVEL_DATABASE_NOT_OPEN'
|
||||
})
|
||||
}
|
||||
|
||||
return this._keys(options)
|
||||
}
|
||||
|
||||
_keys (options) {
|
||||
return new DefaultKeyIterator(this, options)
|
||||
}
|
||||
|
||||
values (options) {
|
||||
const keyEncoding = this.keyEncoding(options && options.keyEncoding)
|
||||
const valueEncoding = this.valueEncoding(options && options.valueEncoding)
|
||||
|
||||
options = rangeOptions(options, keyEncoding)
|
||||
|
||||
// We need the original encoding options in AbstractValueIterator in order to decode data
|
||||
options[AbstractIterator.keyEncoding] = keyEncoding
|
||||
options[AbstractIterator.valueEncoding] = valueEncoding
|
||||
|
||||
// Forward encoding options to private API
|
||||
options.keyEncoding = keyEncoding.format
|
||||
options.valueEncoding = valueEncoding.format
|
||||
|
||||
if (this[kStatus] === 'opening') {
|
||||
return new DeferredValueIterator(this, options)
|
||||
} else if (this[kStatus] !== 'open') {
|
||||
throw new ModuleError('Database is not open', {
|
||||
code: 'LEVEL_DATABASE_NOT_OPEN'
|
||||
})
|
||||
}
|
||||
|
||||
return this._values(options)
|
||||
}
|
||||
|
||||
_values (options) {
|
||||
return new DefaultValueIterator(this, options)
|
||||
}
|
||||
|
||||
defer (fn) {
|
||||
if (typeof fn !== 'function') {
|
||||
throw new TypeError('The first argument must be a function')
|
||||
}
|
||||
|
||||
this[kOperations].push(fn)
|
||||
}
|
||||
|
||||
[kUndefer] () {
|
||||
if (this[kOperations].length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const operations = this[kOperations]
|
||||
this[kOperations] = []
|
||||
|
||||
for (const op of operations) {
|
||||
op()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: docs and types
|
||||
attachResource (resource) {
|
||||
if (typeof resource !== 'object' || resource === null ||
|
||||
typeof resource.close !== 'function') {
|
||||
throw new TypeError('The first argument must be a resource object')
|
||||
}
|
||||
|
||||
this[kResources].add(resource)
|
||||
}
|
||||
|
||||
// TODO: docs and types
|
||||
detachResource (resource) {
|
||||
this[kResources].delete(resource)
|
||||
}
|
||||
|
||||
_chainedBatch () {
|
||||
return new DefaultChainedBatch(this)
|
||||
}
|
||||
|
||||
_checkKey (key) {
|
||||
if (key === null || key === undefined) {
|
||||
return new ModuleError('Key cannot be null or undefined', {
|
||||
code: 'LEVEL_INVALID_KEY'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_checkValue (value) {
|
||||
if (value === null || value === undefined) {
|
||||
return new ModuleError('Value cannot be null or undefined', {
|
||||
code: 'LEVEL_INVALID_VALUE'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expose browser-compatible nextTick for dependents
|
||||
// TODO: after we drop node 10, also use queueMicrotask in node
|
||||
AbstractLevel.prototype.nextTick = require('./lib/next-tick')
|
||||
|
||||
const { AbstractSublevel } = require('./lib/abstract-sublevel')({ AbstractLevel })
|
||||
|
||||
exports.AbstractLevel = AbstractLevel
|
||||
exports.AbstractSublevel = AbstractSublevel
|
||||
|
||||
const maybeError = function (db, callback) {
|
||||
if (db[kStatus] !== 'open') {
|
||||
db.nextTick(callback, new ModuleError('Database is not open', {
|
||||
code: 'LEVEL_DATABASE_NOT_OPEN'
|
||||
}))
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const formats = function (db) {
|
||||
return Object.keys(db.supports.encodings)
|
||||
.filter(k => !!db.supports.encodings[k])
|
||||
}
|
||||
RevokeMsgPatcher.v1.8.1/LiteLoaderQQNT/plugins/qq-anti-recall/node_modules/abstract-level/index.d.ts
Generated
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
export {
|
||||
AbstractLevel,
|
||||
AbstractDatabaseOptions,
|
||||
AbstractOpenOptions,
|
||||
AbstractGetOptions,
|
||||
AbstractGetManyOptions,
|
||||
AbstractPutOptions,
|
||||
AbstractDelOptions,
|
||||
AbstractBatchOptions,
|
||||
AbstractBatchOperation,
|
||||
AbstractBatchPutOperation,
|
||||
AbstractBatchDelOperation,
|
||||
AbstractClearOptions
|
||||
} from './types/abstract-level'
|
||||
|
||||
export {
|
||||
AbstractIterator,
|
||||
AbstractIteratorOptions,
|
||||
AbstractSeekOptions,
|
||||
AbstractKeyIterator,
|
||||
AbstractKeyIteratorOptions,
|
||||
AbstractValueIterator,
|
||||
AbstractValueIteratorOptions
|
||||
} from './types/abstract-iterator'
|
||||
|
||||
export {
|
||||
AbstractChainedBatch,
|
||||
AbstractChainedBatchPutOptions,
|
||||
AbstractChainedBatchDelOptions,
|
||||
AbstractChainedBatchWriteOptions
|
||||
} from './types/abstract-chained-batch'
|
||||
|
||||
export {
|
||||
AbstractSublevel,
|
||||
AbstractSublevelOptions
|
||||
} from './types/abstract-sublevel'
|
||||
|
||||
export {
|
||||
NodeCallback
|
||||
} from './types/interfaces'
|
||||
|
||||
export * as Transcoder from 'level-transcoder'
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
'use strict'
|
||||
|
||||
exports.AbstractLevel = require('./abstract-level').AbstractLevel
|
||||
exports.AbstractSublevel = require('./abstract-level').AbstractSublevel
|
||||
exports.AbstractIterator = require('./abstract-iterator').AbstractIterator
|
||||
exports.AbstractKeyIterator = require('./abstract-iterator').AbstractKeyIterator
|
||||
exports.AbstractValueIterator = require('./abstract-iterator').AbstractValueIterator
|
||||
exports.AbstractChainedBatch = require('./abstract-chained-batch').AbstractChainedBatch
|
||||
Generated
Vendored
+124
@@ -0,0 +1,124 @@
|
||||
'use strict'
|
||||
|
||||
const { AbstractIterator, AbstractKeyIterator, AbstractValueIterator } = require('../abstract-iterator')
|
||||
|
||||
const kUnfix = Symbol('unfix')
|
||||
const kIterator = Symbol('iterator')
|
||||
const kHandleOne = Symbol('handleOne')
|
||||
const kHandleMany = Symbol('handleMany')
|
||||
const kCallback = Symbol('callback')
|
||||
|
||||
// TODO: unfix natively if db supports it
|
||||
class AbstractSublevelIterator extends AbstractIterator {
|
||||
constructor (db, options, iterator, unfix) {
|
||||
super(db, options)
|
||||
|
||||
this[kIterator] = iterator
|
||||
this[kUnfix] = unfix
|
||||
this[kHandleOne] = this[kHandleOne].bind(this)
|
||||
this[kHandleMany] = this[kHandleMany].bind(this)
|
||||
this[kCallback] = null
|
||||
}
|
||||
|
||||
[kHandleOne] (err, key, value) {
|
||||
const callback = this[kCallback]
|
||||
if (err) return callback(err)
|
||||
if (key !== undefined) key = this[kUnfix](key)
|
||||
callback(err, key, value)
|
||||
}
|
||||
|
||||
[kHandleMany] (err, entries) {
|
||||
const callback = this[kCallback]
|
||||
if (err) return callback(err)
|
||||
|
||||
for (const entry of entries) {
|
||||
const key = entry[0]
|
||||
if (key !== undefined) entry[0] = this[kUnfix](key)
|
||||
}
|
||||
|
||||
callback(err, entries)
|
||||
}
|
||||
}
|
||||
|
||||
class AbstractSublevelKeyIterator extends AbstractKeyIterator {
|
||||
constructor (db, options, iterator, unfix) {
|
||||
super(db, options)
|
||||
|
||||
this[kIterator] = iterator
|
||||
this[kUnfix] = unfix
|
||||
this[kHandleOne] = this[kHandleOne].bind(this)
|
||||
this[kHandleMany] = this[kHandleMany].bind(this)
|
||||
this[kCallback] = null
|
||||
}
|
||||
|
||||
[kHandleOne] (err, key) {
|
||||
const callback = this[kCallback]
|
||||
if (err) return callback(err)
|
||||
if (key !== undefined) key = this[kUnfix](key)
|
||||
callback(err, key)
|
||||
}
|
||||
|
||||
[kHandleMany] (err, keys) {
|
||||
const callback = this[kCallback]
|
||||
if (err) return callback(err)
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
if (key !== undefined) keys[i] = this[kUnfix](key)
|
||||
}
|
||||
|
||||
callback(err, keys)
|
||||
}
|
||||
}
|
||||
|
||||
class AbstractSublevelValueIterator extends AbstractValueIterator {
|
||||
constructor (db, options, iterator) {
|
||||
super(db, options)
|
||||
this[kIterator] = iterator
|
||||
}
|
||||
}
|
||||
|
||||
for (const Iterator of [AbstractSublevelIterator, AbstractSublevelKeyIterator]) {
|
||||
Iterator.prototype._next = function (callback) {
|
||||
this[kCallback] = callback
|
||||
this[kIterator].next(this[kHandleOne])
|
||||
}
|
||||
|
||||
Iterator.prototype._nextv = function (size, options, callback) {
|
||||
this[kCallback] = callback
|
||||
this[kIterator].nextv(size, options, this[kHandleMany])
|
||||
}
|
||||
|
||||
Iterator.prototype._all = function (options, callback) {
|
||||
this[kCallback] = callback
|
||||
this[kIterator].all(options, this[kHandleMany])
|
||||
}
|
||||
}
|
||||
|
||||
for (const Iterator of [AbstractSublevelValueIterator]) {
|
||||
Iterator.prototype._next = function (callback) {
|
||||
this[kIterator].next(callback)
|
||||
}
|
||||
|
||||
Iterator.prototype._nextv = function (size, options, callback) {
|
||||
this[kIterator].nextv(size, options, callback)
|
||||
}
|
||||
|
||||
Iterator.prototype._all = function (options, callback) {
|
||||
this[kIterator].all(options, callback)
|
||||
}
|
||||
}
|
||||
|
||||
for (const Iterator of [AbstractSublevelIterator, AbstractSublevelKeyIterator, AbstractSublevelValueIterator]) {
|
||||
Iterator.prototype._seek = function (target, options) {
|
||||
this[kIterator].seek(target, options)
|
||||
}
|
||||
|
||||
Iterator.prototype._close = function (callback) {
|
||||
this[kIterator].close(callback)
|
||||
}
|
||||
}
|
||||
|
||||
exports.AbstractSublevelIterator = AbstractSublevelIterator
|
||||
exports.AbstractSublevelKeyIterator = AbstractSublevelKeyIterator
|
||||
exports.AbstractSublevelValueIterator = AbstractSublevelValueIterator
|
||||
Generated
Vendored
+258
@@ -0,0 +1,258 @@
|
||||
'use strict'
|
||||
|
||||
const ModuleError = require('module-error')
|
||||
const { Buffer } = require('buffer') || {}
|
||||
const {
|
||||
AbstractSublevelIterator,
|
||||
AbstractSublevelKeyIterator,
|
||||
AbstractSublevelValueIterator
|
||||
} = require('./abstract-sublevel-iterator')
|
||||
|
||||
const kPrefix = Symbol('prefix')
|
||||
const kUpperBound = Symbol('upperBound')
|
||||
const kPrefixRange = Symbol('prefixRange')
|
||||
const kParent = Symbol('parent')
|
||||
const kUnfix = Symbol('unfix')
|
||||
|
||||
const textEncoder = new TextEncoder()
|
||||
const defaults = { separator: '!' }
|
||||
|
||||
// Wrapped to avoid circular dependency
|
||||
module.exports = function ({ AbstractLevel }) {
|
||||
class AbstractSublevel extends AbstractLevel {
|
||||
static defaults (options) {
|
||||
// To help migrating from subleveldown to abstract-level
|
||||
if (typeof options === 'string') {
|
||||
throw new ModuleError('The subleveldown string shorthand for { separator } has been removed', {
|
||||
code: 'LEVEL_LEGACY'
|
||||
})
|
||||
} else if (options && options.open) {
|
||||
throw new ModuleError('The subleveldown open option has been removed', {
|
||||
code: 'LEVEL_LEGACY'
|
||||
})
|
||||
}
|
||||
|
||||
if (options == null) {
|
||||
return defaults
|
||||
} else if (!options.separator) {
|
||||
return { ...options, separator: '!' }
|
||||
} else {
|
||||
return options
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add autoClose option, which if true, does parent.attachResource(this)
|
||||
constructor (db, name, options) {
|
||||
// Don't forward AbstractSublevel options to AbstractLevel
|
||||
const { separator, manifest, ...forward } = AbstractSublevel.defaults(options)
|
||||
name = trim(name, separator)
|
||||
|
||||
// Reserve one character between separator and name to give us an upper bound
|
||||
const reserved = separator.charCodeAt(0) + 1
|
||||
const parent = db[kParent] || db
|
||||
|
||||
// Keys should sort like ['!a!', '!a!!a!', '!a"', '!aa!', '!b!'].
|
||||
// Use ASCII for consistent length between string, Buffer and Uint8Array
|
||||
if (!textEncoder.encode(name).every(x => x > reserved && x < 127)) {
|
||||
throw new ModuleError(`Prefix must use bytes > ${reserved} < ${127}`, {
|
||||
code: 'LEVEL_INVALID_PREFIX'
|
||||
})
|
||||
}
|
||||
|
||||
super(mergeManifests(parent, manifest), forward)
|
||||
|
||||
const prefix = (db.prefix || '') + separator + name + separator
|
||||
const upperBound = prefix.slice(0, -1) + String.fromCharCode(reserved)
|
||||
|
||||
this[kParent] = parent
|
||||
this[kPrefix] = new MultiFormat(prefix)
|
||||
this[kUpperBound] = new MultiFormat(upperBound)
|
||||
this[kUnfix] = new Unfixer()
|
||||
|
||||
this.nextTick = parent.nextTick
|
||||
}
|
||||
|
||||
prefixKey (key, keyFormat) {
|
||||
if (keyFormat === 'utf8') {
|
||||
return this[kPrefix].utf8 + key
|
||||
} else if (key.byteLength === 0) {
|
||||
// Fast path for empty key (no copy)
|
||||
return this[kPrefix][keyFormat]
|
||||
} else if (keyFormat === 'view') {
|
||||
const view = this[kPrefix].view
|
||||
const result = new Uint8Array(view.byteLength + key.byteLength)
|
||||
|
||||
result.set(view, 0)
|
||||
result.set(key, view.byteLength)
|
||||
|
||||
return result
|
||||
} else {
|
||||
const buffer = this[kPrefix].buffer
|
||||
return Buffer.concat([buffer, key], buffer.byteLength + key.byteLength)
|
||||
}
|
||||
}
|
||||
|
||||
// Not exposed for now.
|
||||
[kPrefixRange] (range, keyFormat) {
|
||||
if (range.gte !== undefined) {
|
||||
range.gte = this.prefixKey(range.gte, keyFormat)
|
||||
} else if (range.gt !== undefined) {
|
||||
range.gt = this.prefixKey(range.gt, keyFormat)
|
||||
} else {
|
||||
range.gte = this[kPrefix][keyFormat]
|
||||
}
|
||||
|
||||
if (range.lte !== undefined) {
|
||||
range.lte = this.prefixKey(range.lte, keyFormat)
|
||||
} else if (range.lt !== undefined) {
|
||||
range.lt = this.prefixKey(range.lt, keyFormat)
|
||||
} else {
|
||||
range.lte = this[kUpperBound][keyFormat]
|
||||
}
|
||||
}
|
||||
|
||||
get prefix () {
|
||||
return this[kPrefix].utf8
|
||||
}
|
||||
|
||||
get db () {
|
||||
return this[kParent]
|
||||
}
|
||||
|
||||
_open (options, callback) {
|
||||
// The parent db must open itself or be (re)opened by the user because
|
||||
// a sublevel should not initiate state changes on the rest of the db.
|
||||
this[kParent].open({ passive: true }, callback)
|
||||
}
|
||||
|
||||
_put (key, value, options, callback) {
|
||||
this[kParent].put(key, value, options, callback)
|
||||
}
|
||||
|
||||
_get (key, options, callback) {
|
||||
this[kParent].get(key, options, callback)
|
||||
}
|
||||
|
||||
_getMany (keys, options, callback) {
|
||||
this[kParent].getMany(keys, options, callback)
|
||||
}
|
||||
|
||||
_del (key, options, callback) {
|
||||
this[kParent].del(key, options, callback)
|
||||
}
|
||||
|
||||
_batch (operations, options, callback) {
|
||||
this[kParent].batch(operations, options, callback)
|
||||
}
|
||||
|
||||
_clear (options, callback) {
|
||||
// TODO (refactor): move to AbstractLevel
|
||||
this[kPrefixRange](options, options.keyEncoding)
|
||||
this[kParent].clear(options, callback)
|
||||
}
|
||||
|
||||
_iterator (options) {
|
||||
// TODO (refactor): move to AbstractLevel
|
||||
this[kPrefixRange](options, options.keyEncoding)
|
||||
const iterator = this[kParent].iterator(options)
|
||||
const unfix = this[kUnfix].get(this[kPrefix].utf8.length, options.keyEncoding)
|
||||
return new AbstractSublevelIterator(this, options, iterator, unfix)
|
||||
}
|
||||
|
||||
_keys (options) {
|
||||
this[kPrefixRange](options, options.keyEncoding)
|
||||
const iterator = this[kParent].keys(options)
|
||||
const unfix = this[kUnfix].get(this[kPrefix].utf8.length, options.keyEncoding)
|
||||
return new AbstractSublevelKeyIterator(this, options, iterator, unfix)
|
||||
}
|
||||
|
||||
_values (options) {
|
||||
this[kPrefixRange](options, options.keyEncoding)
|
||||
const iterator = this[kParent].values(options)
|
||||
return new AbstractSublevelValueIterator(this, options, iterator)
|
||||
}
|
||||
}
|
||||
|
||||
return { AbstractSublevel }
|
||||
}
|
||||
|
||||
const mergeManifests = function (parent, manifest) {
|
||||
return {
|
||||
// Inherit manifest of parent db
|
||||
...parent.supports,
|
||||
|
||||
// Disable unsupported features
|
||||
createIfMissing: false,
|
||||
errorIfExists: false,
|
||||
|
||||
// Unset additional events because we're not forwarding them
|
||||
events: {},
|
||||
|
||||
// Unset additional methods (like approximateSize) which we can't support here unless
|
||||
// the AbstractSublevel class is overridden by an implementation of `abstract-level`.
|
||||
additionalMethods: {},
|
||||
|
||||
// Inherit manifest of custom AbstractSublevel subclass. Such a class is not
|
||||
// allowed to override encodings.
|
||||
...manifest,
|
||||
|
||||
encodings: {
|
||||
utf8: supportsEncoding(parent, 'utf8'),
|
||||
buffer: supportsEncoding(parent, 'buffer'),
|
||||
view: supportsEncoding(parent, 'view')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const supportsEncoding = function (parent, encoding) {
|
||||
// Prefer a non-transcoded encoding for optimal performance
|
||||
return parent.supports.encodings[encoding]
|
||||
? parent.keyEncoding(encoding).name === encoding
|
||||
: false
|
||||
}
|
||||
|
||||
class MultiFormat {
|
||||
constructor (key) {
|
||||
this.utf8 = key
|
||||
this.view = textEncoder.encode(key)
|
||||
this.buffer = Buffer ? Buffer.from(this.view.buffer, 0, this.view.byteLength) : {}
|
||||
}
|
||||
}
|
||||
|
||||
class Unfixer {
|
||||
constructor () {
|
||||
this.cache = new Map()
|
||||
}
|
||||
|
||||
get (prefixLength, keyFormat) {
|
||||
let unfix = this.cache.get(keyFormat)
|
||||
|
||||
if (unfix === undefined) {
|
||||
if (keyFormat === 'view') {
|
||||
unfix = function (prefixLength, key) {
|
||||
// Avoid Uint8Array#slice() because it copies
|
||||
return key.subarray(prefixLength)
|
||||
}.bind(null, prefixLength)
|
||||
} else {
|
||||
unfix = function (prefixLength, key) {
|
||||
// Avoid Buffer#subarray() because it's slow
|
||||
return key.slice(prefixLength)
|
||||
}.bind(null, prefixLength)
|
||||
}
|
||||
|
||||
this.cache.set(keyFormat, unfix)
|
||||
}
|
||||
|
||||
return unfix
|
||||
}
|
||||
}
|
||||
|
||||
const trim = function (str, char) {
|
||||
let start = 0
|
||||
let end = str.length
|
||||
|
||||
while (start < end && str[start] === char) start++
|
||||
while (end > start && str[end - 1] === char) end--
|
||||
|
||||
return str.slice(start, end)
|
||||
}
|
||||
Generated
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
'use strict'
|
||||
|
||||
exports.getCallback = function (options, callback) {
|
||||
return typeof options === 'function' ? options : callback
|
||||
}
|
||||
|
||||
exports.getOptions = function (options, def) {
|
||||
if (typeof options === 'object' && options !== null) {
|
||||
return options
|
||||
}
|
||||
|
||||
if (def !== undefined) {
|
||||
return def
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
Generated
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
'use strict'
|
||||
|
||||
const { AbstractChainedBatch } = require('../abstract-chained-batch')
|
||||
const ModuleError = require('module-error')
|
||||
const kEncoded = Symbol('encoded')
|
||||
|
||||
// Functional default for chained batch, with support of deferred open
|
||||
class DefaultChainedBatch extends AbstractChainedBatch {
|
||||
constructor (db) {
|
||||
super(db)
|
||||
this[kEncoded] = []
|
||||
}
|
||||
|
||||
_put (key, value, options) {
|
||||
this[kEncoded].push({ ...options, type: 'put', key, value })
|
||||
}
|
||||
|
||||
_del (key, options) {
|
||||
this[kEncoded].push({ ...options, type: 'del', key })
|
||||
}
|
||||
|
||||
_clear () {
|
||||
this[kEncoded] = []
|
||||
}
|
||||
|
||||
// Assumes this[kEncoded] cannot change after write()
|
||||
_write (options, callback) {
|
||||
if (this.db.status === 'opening') {
|
||||
this.db.defer(() => this._write(options, callback))
|
||||
} else if (this.db.status === 'open') {
|
||||
if (this[kEncoded].length === 0) this.nextTick(callback)
|
||||
else this.db._batch(this[kEncoded], options, callback)
|
||||
} else {
|
||||
this.nextTick(callback, new ModuleError('Batch is not open: cannot call write() after write() or close()', {
|
||||
code: 'LEVEL_BATCH_NOT_OPEN'
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.DefaultChainedBatch = DefaultChainedBatch
|
||||
Generated
Vendored
+72
@@ -0,0 +1,72 @@
|
||||
'use strict'
|
||||
|
||||
const { AbstractKeyIterator, AbstractValueIterator } = require('../abstract-iterator')
|
||||
|
||||
const kIterator = Symbol('iterator')
|
||||
const kCallback = Symbol('callback')
|
||||
const kHandleOne = Symbol('handleOne')
|
||||
const kHandleMany = Symbol('handleMany')
|
||||
|
||||
class DefaultKeyIterator extends AbstractKeyIterator {
|
||||
constructor (db, options) {
|
||||
super(db, options)
|
||||
|
||||
this[kIterator] = db.iterator({ ...options, keys: true, values: false })
|
||||
this[kHandleOne] = this[kHandleOne].bind(this)
|
||||
this[kHandleMany] = this[kHandleMany].bind(this)
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultValueIterator extends AbstractValueIterator {
|
||||
constructor (db, options) {
|
||||
super(db, options)
|
||||
|
||||
this[kIterator] = db.iterator({ ...options, keys: false, values: true })
|
||||
this[kHandleOne] = this[kHandleOne].bind(this)
|
||||
this[kHandleMany] = this[kHandleMany].bind(this)
|
||||
}
|
||||
}
|
||||
|
||||
for (const Iterator of [DefaultKeyIterator, DefaultValueIterator]) {
|
||||
const keys = Iterator === DefaultKeyIterator
|
||||
const mapEntry = keys ? (entry) => entry[0] : (entry) => entry[1]
|
||||
|
||||
Iterator.prototype._next = function (callback) {
|
||||
this[kCallback] = callback
|
||||
this[kIterator].next(this[kHandleOne])
|
||||
}
|
||||
|
||||
Iterator.prototype[kHandleOne] = function (err, key, value) {
|
||||
const callback = this[kCallback]
|
||||
if (err) callback(err)
|
||||
else callback(null, keys ? key : value)
|
||||
}
|
||||
|
||||
Iterator.prototype._nextv = function (size, options, callback) {
|
||||
this[kCallback] = callback
|
||||
this[kIterator].nextv(size, options, this[kHandleMany])
|
||||
}
|
||||
|
||||
Iterator.prototype._all = function (options, callback) {
|
||||
this[kCallback] = callback
|
||||
this[kIterator].all(options, this[kHandleMany])
|
||||
}
|
||||
|
||||
Iterator.prototype[kHandleMany] = function (err, entries) {
|
||||
const callback = this[kCallback]
|
||||
if (err) callback(err)
|
||||
else callback(null, entries.map(mapEntry))
|
||||
}
|
||||
|
||||
Iterator.prototype._seek = function (target, options) {
|
||||
this[kIterator].seek(target, options)
|
||||
}
|
||||
|
||||
Iterator.prototype._close = function (callback) {
|
||||
this[kIterator].close(callback)
|
||||
}
|
||||
}
|
||||
|
||||
// Internal utilities, should be typed as AbstractKeyIterator and AbstractValueIterator
|
||||
exports.DefaultKeyIterator = DefaultKeyIterator
|
||||
exports.DefaultValueIterator = DefaultValueIterator
|
||||
Generated
Vendored
+108
@@ -0,0 +1,108 @@
|
||||
'use strict'
|
||||
|
||||
const { AbstractIterator, AbstractKeyIterator, AbstractValueIterator } = require('../abstract-iterator')
|
||||
const ModuleError = require('module-error')
|
||||
|
||||
const kNut = Symbol('nut')
|
||||
const kUndefer = Symbol('undefer')
|
||||
const kFactory = Symbol('factory')
|
||||
|
||||
class DeferredIterator extends AbstractIterator {
|
||||
constructor (db, options) {
|
||||
super(db, options)
|
||||
|
||||
this[kNut] = null
|
||||
this[kFactory] = () => db.iterator(options)
|
||||
|
||||
this.db.defer(() => this[kUndefer]())
|
||||
}
|
||||
}
|
||||
|
||||
class DeferredKeyIterator extends AbstractKeyIterator {
|
||||
constructor (db, options) {
|
||||
super(db, options)
|
||||
|
||||
this[kNut] = null
|
||||
this[kFactory] = () => db.keys(options)
|
||||
|
||||
this.db.defer(() => this[kUndefer]())
|
||||
}
|
||||
}
|
||||
|
||||
class DeferredValueIterator extends AbstractValueIterator {
|
||||
constructor (db, options) {
|
||||
super(db, options)
|
||||
|
||||
this[kNut] = null
|
||||
this[kFactory] = () => db.values(options)
|
||||
|
||||
this.db.defer(() => this[kUndefer]())
|
||||
}
|
||||
}
|
||||
|
||||
for (const Iterator of [DeferredIterator, DeferredKeyIterator, DeferredValueIterator]) {
|
||||
Iterator.prototype[kUndefer] = function () {
|
||||
if (this.db.status === 'open') {
|
||||
this[kNut] = this[kFactory]()
|
||||
}
|
||||
}
|
||||
|
||||
Iterator.prototype._next = function (callback) {
|
||||
if (this[kNut] !== null) {
|
||||
this[kNut].next(callback)
|
||||
} else if (this.db.status === 'opening') {
|
||||
this.db.defer(() => this._next(callback))
|
||||
} else {
|
||||
this.nextTick(callback, new ModuleError('Iterator is not open: cannot call next() after close()', {
|
||||
code: 'LEVEL_ITERATOR_NOT_OPEN'
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
Iterator.prototype._nextv = function (size, options, callback) {
|
||||
if (this[kNut] !== null) {
|
||||
this[kNut].nextv(size, options, callback)
|
||||
} else if (this.db.status === 'opening') {
|
||||
this.db.defer(() => this._nextv(size, options, callback))
|
||||
} else {
|
||||
this.nextTick(callback, new ModuleError('Iterator is not open: cannot call nextv() after close()', {
|
||||
code: 'LEVEL_ITERATOR_NOT_OPEN'
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
Iterator.prototype._all = function (options, callback) {
|
||||
if (this[kNut] !== null) {
|
||||
this[kNut].all(callback)
|
||||
} else if (this.db.status === 'opening') {
|
||||
this.db.defer(() => this._all(options, callback))
|
||||
} else {
|
||||
this.nextTick(callback, new ModuleError('Iterator is not open: cannot call all() after close()', {
|
||||
code: 'LEVEL_ITERATOR_NOT_OPEN'
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
Iterator.prototype._seek = function (target, options) {
|
||||
if (this[kNut] !== null) {
|
||||
// TODO: explain why we need _seek() rather than seek() here
|
||||
this[kNut]._seek(target, options)
|
||||
} else if (this.db.status === 'opening') {
|
||||
this.db.defer(() => this._seek(target, options))
|
||||
}
|
||||
}
|
||||
|
||||
Iterator.prototype._close = function (callback) {
|
||||
if (this[kNut] !== null) {
|
||||
this[kNut].close(callback)
|
||||
} else if (this.db.status === 'opening') {
|
||||
this.db.defer(() => this._close(callback))
|
||||
} else {
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.DeferredIterator = DeferredIterator
|
||||
exports.DeferredKeyIterator = DeferredKeyIterator
|
||||
exports.DeferredValueIterator = DeferredValueIterator
|
||||
Generated
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
'use strict'
|
||||
|
||||
const queueMicrotask = require('queue-microtask')
|
||||
|
||||
module.exports = function (fn, ...args) {
|
||||
if (args.length === 0) {
|
||||
queueMicrotask(fn)
|
||||
} else {
|
||||
queueMicrotask(() => fn(...args))
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = process.nextTick
|
||||
Generated
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
'use strict'
|
||||
|
||||
const ModuleError = require('module-error')
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty
|
||||
const rangeOptions = new Set(['lt', 'lte', 'gt', 'gte'])
|
||||
|
||||
module.exports = function (options, keyEncoding) {
|
||||
const result = {}
|
||||
|
||||
for (const k in options) {
|
||||
if (!hasOwnProperty.call(options, k)) continue
|
||||
if (k === 'keyEncoding' || k === 'valueEncoding') continue
|
||||
|
||||
if (k === 'start' || k === 'end') {
|
||||
throw new ModuleError(`The legacy range option '${k}' has been removed`, {
|
||||
code: 'LEVEL_LEGACY'
|
||||
})
|
||||
} else if (k === 'encoding') {
|
||||
// To help migrating to abstract-level
|
||||
throw new ModuleError("The levelup-style 'encoding' alias has been removed, use 'valueEncoding' instead", {
|
||||
code: 'LEVEL_LEGACY'
|
||||
})
|
||||
}
|
||||
|
||||
if (rangeOptions.has(k)) {
|
||||
// Note that we don't reject nullish and empty options here. While
|
||||
// those types are invalid as keys, they are valid as range options.
|
||||
result[k] = keyEncoding.encode(options[k])
|
||||
} else {
|
||||
result[k] = options[k]
|
||||
}
|
||||
}
|
||||
|
||||
result.reverse = !!result.reverse
|
||||
result.limit = Number.isInteger(result.limit) && result.limit >= 0 ? result.limit : -1
|
||||
|
||||
return result
|
||||
}
|
||||
Generated
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"name": "abstract-level",
|
||||
"version": "1.0.4",
|
||||
"description": "Abstract class for a lexicographically sorted key-value database",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"browser": {
|
||||
"./lib/next-tick.js": "./lib/next-tick-browser.js"
|
||||
},
|
||||
"types": "./index.d.ts",
|
||||
"scripts": {
|
||||
"test": "standard && ts-standard *.ts types/*.ts && hallmark && (nyc -s node test/self.js | faucet) && nyc report",
|
||||
"test-browsers": "airtap --coverage test/self.js",
|
||||
"coverage": "nyc report -r lcovonly"
|
||||
},
|
||||
"files": [
|
||||
"abstract-chained-batch.js",
|
||||
"abstract-iterator.js",
|
||||
"abstract-level.js",
|
||||
"index.js",
|
||||
"index.d.ts",
|
||||
"lib",
|
||||
"test",
|
||||
"types",
|
||||
"CHANGELOG.md",
|
||||
"UPGRADING.md"
|
||||
],
|
||||
"dependencies": {
|
||||
"buffer": "^6.0.3",
|
||||
"catering": "^2.1.0",
|
||||
"is-buffer": "^2.0.5",
|
||||
"level-supports": "^4.0.0",
|
||||
"level-transcoder": "^1.0.1",
|
||||
"module-error": "^1.0.1",
|
||||
"queue-microtask": "^1.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.0.0",
|
||||
"@voxpelli/tsconfig": "^4.0.0",
|
||||
"airtap": "^4.0.4",
|
||||
"airtap-playwright": "^1.0.1",
|
||||
"faucet": "^0.0.3",
|
||||
"hallmark": "^4.0.0",
|
||||
"nyc": "^15.1.0",
|
||||
"sinon": "^14.0.0",
|
||||
"standard": "^16.0.4",
|
||||
"tape": "^5.4.0",
|
||||
"ts-standard": "^11.0.0",
|
||||
"typescript": "^4.5.5"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Level/abstract-level.git"
|
||||
},
|
||||
"homepage": "https://github.com/Level/abstract-level",
|
||||
"keywords": [
|
||||
"abstract-level",
|
||||
"level",
|
||||
"leveldb"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+143
@@ -0,0 +1,143 @@
|
||||
'use strict'
|
||||
|
||||
const input = [{ key: '1', value: '1' }, { key: '2', value: '2' }]
|
||||
|
||||
let db
|
||||
|
||||
exports.setup = function (test, testCommon) {
|
||||
test('setup', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
db = testCommon.factory()
|
||||
db.open(function (err) {
|
||||
t.ifError(err, 'no open() error')
|
||||
|
||||
db.batch(input.map(entry => ({ ...entry, type: 'put' })), function (err) {
|
||||
t.ifError(err, 'no batch() error')
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.asyncIterator = function (test, testCommon) {
|
||||
for (const mode of ['iterator', 'keys', 'values']) {
|
||||
test(`for await...of ${mode}()`, async function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const it = db[mode]({ keyEncoding: 'utf8', valueEncoding: 'utf8' })
|
||||
const output = []
|
||||
|
||||
for await (const item of it) {
|
||||
output.push(item)
|
||||
}
|
||||
|
||||
t.same(output, input.map(({ key, value }) => {
|
||||
return mode === 'iterator' ? [key, value] : mode === 'keys' ? key : value
|
||||
}))
|
||||
})
|
||||
|
||||
testCommon.supports.permanence && test(`for await...of ${mode}() (deferred)`, async function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.batch(input.map(entry => ({ ...entry, type: 'put' })))
|
||||
await db.close()
|
||||
|
||||
// Don't await
|
||||
db.open()
|
||||
|
||||
const it = db[mode]({ keyEncoding: 'utf8', valueEncoding: 'utf8' })
|
||||
const output = []
|
||||
|
||||
for await (const item of it) {
|
||||
output.push(item)
|
||||
}
|
||||
|
||||
t.same(output, input.map(({ key, value }) => {
|
||||
return mode === 'iterator' ? [key, value] : mode === 'keys' ? key : value
|
||||
}))
|
||||
|
||||
await db.close()
|
||||
})
|
||||
|
||||
testCommon.supports.snapshots && test(`for await...of ${mode}() (deferred, with snapshot)`, async function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const db = testCommon.factory()
|
||||
const it = db[mode]({ keyEncoding: 'utf8', valueEncoding: 'utf8' })
|
||||
const promise = db.batch(input.map(entry => ({ ...entry, type: 'put' })))
|
||||
const output = []
|
||||
|
||||
for await (const item of it) {
|
||||
output.push(item)
|
||||
}
|
||||
|
||||
t.same(output, [], 'used snapshot')
|
||||
|
||||
// Wait for data to be written
|
||||
await promise
|
||||
|
||||
for await (const item of db[mode]({ keyEncoding: 'utf8', valueEncoding: 'utf8' })) {
|
||||
output.push(item)
|
||||
}
|
||||
|
||||
t.same(output, input.map(({ key, value }) => {
|
||||
return mode === 'iterator' ? [key, value] : mode === 'keys' ? key : value
|
||||
}))
|
||||
|
||||
await db.close()
|
||||
})
|
||||
|
||||
for (const deferred of [false, true]) {
|
||||
test(`for await...of ${mode}() (empty, deferred: ${deferred})`, async function (t) {
|
||||
const db = testCommon.factory()
|
||||
const entries = []
|
||||
|
||||
if (!deferred) await db.open()
|
||||
|
||||
for await (const item of db[mode]({ keyEncoding: 'utf8', valueEncoding: 'utf8' })) {
|
||||
entries.push(item)
|
||||
}
|
||||
|
||||
t.same(entries, [])
|
||||
await db.close()
|
||||
})
|
||||
}
|
||||
|
||||
test(`for await...of ${mode}() does not permit reuse`, async function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const it = db[mode]()
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
for await (const item of it) {
|
||||
t.pass('nexted')
|
||||
}
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
for await (const item of it) {
|
||||
t.fail('should not be called')
|
||||
}
|
||||
} catch (err) {
|
||||
t.is(err.code, 'LEVEL_ITERATOR_NOT_OPEN')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.teardown = function (test, testCommon) {
|
||||
test('teardown', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
db.close(function (err) {
|
||||
t.ifError(err, 'no close() error')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.setup(test, testCommon)
|
||||
exports.asyncIterator(test, testCommon)
|
||||
exports.teardown(test, testCommon)
|
||||
}
|
||||
Generated
Vendored
+320
@@ -0,0 +1,320 @@
|
||||
'use strict'
|
||||
|
||||
const { Buffer } = require('buffer')
|
||||
const { verifyNotFoundError, assertAsync } = require('./util')
|
||||
const { illegalKeys, illegalValues } = require('./util')
|
||||
|
||||
let db
|
||||
|
||||
exports.setUp = function (test, testCommon) {
|
||||
test('setUp db', function (t) {
|
||||
db = testCommon.factory()
|
||||
db.open(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
exports.args = function (test, testCommon) {
|
||||
test('test batch() with missing `value`', assertAsync.ctx(function (t) {
|
||||
t.plan(3)
|
||||
|
||||
db.batch([{ type: 'put', key: 'foo1' }], assertAsync(function (err) {
|
||||
t.is(err && err.code, 'LEVEL_INVALID_VALUE', 'correct error code (callback)')
|
||||
}))
|
||||
|
||||
db.batch([{ type: 'put', key: 'foo1' }]).catch((err) => {
|
||||
t.is(err.code, 'LEVEL_INVALID_VALUE', 'correct error code (promise)')
|
||||
})
|
||||
}))
|
||||
|
||||
test('test batch() with illegal values', assertAsync.ctx(function (t) {
|
||||
t.plan(illegalValues.length * 6)
|
||||
|
||||
for (const { name, value } of illegalValues) {
|
||||
db.batch([{ type: 'put', key: 'foo1', value }], assertAsync(function (err) {
|
||||
t.ok(err, name + ' - has error (callback)')
|
||||
t.ok(err instanceof Error, name + ' - is Error (callback)')
|
||||
t.is(err && err.code, 'LEVEL_INVALID_VALUE', 'correct error code (callback)')
|
||||
}))
|
||||
|
||||
db.batch([{ type: 'put', key: 'foo1', value }]).catch(function (err) {
|
||||
t.ok(err instanceof Error, name + ' - is Error (promise)')
|
||||
t.is(err.code, 'LEVEL_INVALID_VALUE', name + ' - correct error code (promise)')
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
||||
test('test batch() with missing `key`', assertAsync.ctx(function (t) {
|
||||
t.plan(3)
|
||||
|
||||
db.batch([{ type: 'put', value: 'foo1' }], assertAsync(function (err) {
|
||||
t.is(err && err.code, 'LEVEL_INVALID_KEY', 'correct error code (callback)')
|
||||
}))
|
||||
|
||||
db.batch([{ type: 'put', value: 'foo1' }]).catch(function (err) {
|
||||
t.is(err.code, 'LEVEL_INVALID_KEY', 'correct error code (promise)')
|
||||
})
|
||||
}))
|
||||
|
||||
test('test batch() with illegal keys', assertAsync.ctx(function (t) {
|
||||
t.plan(illegalKeys.length * 6)
|
||||
|
||||
for (const { name, key } of illegalKeys) {
|
||||
db.batch([{ type: 'put', key, value: 'foo1' }], assertAsync(function (err) {
|
||||
t.ok(err, name + ' - has error (callback)')
|
||||
t.ok(err instanceof Error, name + ' - is Error (callback)')
|
||||
t.is(err && err.code, 'LEVEL_INVALID_KEY', 'correct error code (callback)')
|
||||
}))
|
||||
|
||||
db.batch([{ type: 'put', key, value: 'foo1' }]).catch(function (err) {
|
||||
t.ok(err instanceof Error, name + ' - is Error (promise)')
|
||||
t.is(err.code, 'LEVEL_INVALID_KEY', name + ' - correct error code (promise)')
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
||||
test('test batch() with missing or incorrect type', assertAsync.ctx(function (t) {
|
||||
t.plan(10)
|
||||
|
||||
db.batch([{ key: 'key', value: 'value' }], assertAsync(function (err) {
|
||||
t.is(err && err.name, 'TypeError')
|
||||
t.is(err && err.message, "A batch operation must have a type property that is 'put' or 'del'", 'correct error message (callback)')
|
||||
}))
|
||||
|
||||
db.batch([{ key: 'key', value: 'value', type: 'foo' }], assertAsync(function (err) {
|
||||
t.is(err && err.name, 'TypeError')
|
||||
t.is(err && err.message, "A batch operation must have a type property that is 'put' or 'del'", 'correct error message (callback)')
|
||||
}))
|
||||
|
||||
db.batch([{ key: 'key', value: 'value' }]).catch(function (err) {
|
||||
t.is(err.name, 'TypeError')
|
||||
t.is(err.message, "A batch operation must have a type property that is 'put' or 'del'", 'correct error message (promise)')
|
||||
})
|
||||
|
||||
db.batch([{ key: 'key', value: 'value', type: 'foo' }]).catch(function (err) {
|
||||
t.is(err.name, 'TypeError')
|
||||
t.is(err.message, "A batch operation must have a type property that is 'put' or 'del'", 'correct error message (promise)')
|
||||
})
|
||||
}))
|
||||
|
||||
test('test batch() with missing or nullish operations', assertAsync.ctx(function (t) {
|
||||
t.plan(13)
|
||||
|
||||
db.batch(assertAsync(function (err) {
|
||||
t.is(err && err.name, 'TypeError')
|
||||
t.is(err && err.message, "The first argument 'operations' must be an array", 'correct error message (callback)')
|
||||
}))
|
||||
|
||||
for (const array of [null, undefined]) {
|
||||
db.batch(array, assertAsync(function (err) {
|
||||
t.is(err && err.name, 'TypeError')
|
||||
t.is(err && err.message, "The first argument 'operations' must be an array", 'correct error message (callback)')
|
||||
}))
|
||||
|
||||
db.batch(array).catch(function (err) {
|
||||
t.is(err.name, 'TypeError')
|
||||
t.is(err.message, "The first argument 'operations' must be an array", 'correct error message (promise)')
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
||||
test('test batch() with null options', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
db.batch([], null, function (err) {
|
||||
t.error(err)
|
||||
})
|
||||
|
||||
db.batch([], null).then(function () {
|
||||
t.pass('resolved')
|
||||
}).catch(t.fail.bind(t))
|
||||
})
|
||||
|
||||
;[null, undefined, 1, true].forEach(function (operation) {
|
||||
const type = operation === null ? 'null' : typeof operation
|
||||
|
||||
test('test batch() with ' + type + ' operation', assertAsync.ctx(function (t) {
|
||||
t.plan(5)
|
||||
|
||||
db.batch([operation], assertAsync(function (err) {
|
||||
t.is(err && err.name, 'TypeError')
|
||||
t.is(err && err.message, 'A batch operation must be an object', 'correct error message (callback)')
|
||||
}))
|
||||
|
||||
db.batch([operation]).catch(function (err) {
|
||||
t.is(err.name, 'TypeError')
|
||||
t.is(err.message, 'A batch operation must be an object', 'correct error message (promise)')
|
||||
})
|
||||
}))
|
||||
})
|
||||
|
||||
test('test batch() with empty array', assertAsync.ctx(function (t) {
|
||||
t.plan(3)
|
||||
|
||||
db.batch([], assertAsync(function (err) {
|
||||
t.error(err, 'no error from batch()')
|
||||
}))
|
||||
|
||||
db.batch([]).then(function () {
|
||||
t.pass('resolved')
|
||||
}).catch(t.fail.bind(t))
|
||||
}))
|
||||
}
|
||||
|
||||
exports.batch = function (test, testCommon) {
|
||||
test('test simple batch()', function (t) {
|
||||
db.batch([{ type: 'put', key: 'foo', value: 'bar' }], function (err) {
|
||||
t.error(err)
|
||||
|
||||
db.get('foo', function (err, value) {
|
||||
t.error(err)
|
||||
t.is(value, 'bar')
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('test simple batch() with promise', async function (t) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
await db.open()
|
||||
await db.batch([{ type: 'put', key: 'foo', value: 'bar' }])
|
||||
|
||||
t.is(await db.get('foo', { valueEncoding: 'utf8' }), 'bar')
|
||||
return db.close()
|
||||
})
|
||||
|
||||
test('test multiple batch()', function (t) {
|
||||
db.batch([
|
||||
{ type: 'put', key: 'foobatch1', value: 'bar1' },
|
||||
{ type: 'put', key: 'foobatch2', value: 'bar2' },
|
||||
{ type: 'put', key: 'foobatch3', value: 'bar3' },
|
||||
{ type: 'del', key: 'foobatch2' }
|
||||
], function (err) {
|
||||
t.error(err)
|
||||
|
||||
let r = 0
|
||||
const done = function () {
|
||||
if (++r === 3) { t.end() }
|
||||
}
|
||||
|
||||
db.get('foobatch1', function (err, value) {
|
||||
t.error(err)
|
||||
t.is(value, 'bar1')
|
||||
done()
|
||||
})
|
||||
|
||||
db.get('foobatch2', function (err, value) {
|
||||
t.ok(err, 'entry not found')
|
||||
t.ok(typeof value === 'undefined', 'value is undefined')
|
||||
t.ok(verifyNotFoundError(err), 'NotFound error')
|
||||
done()
|
||||
})
|
||||
|
||||
db.get('foobatch3', function (err, value) {
|
||||
t.error(err)
|
||||
t.is(value, 'bar3')
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
for (const encoding of ['utf8', 'buffer', 'view']) {
|
||||
if (!testCommon.supports.encodings[encoding]) continue
|
||||
|
||||
// NOTE: adapted from memdown
|
||||
test(`empty values in batch with ${encoding} valueEncoding`, async function (t) {
|
||||
const db = testCommon.factory({ valueEncoding: encoding })
|
||||
const values = ['', Uint8Array.from([]), Buffer.alloc(0)]
|
||||
const expected = encoding === 'utf8' ? values[0] : encoding === 'view' ? values[1] : values[2]
|
||||
|
||||
await db.open()
|
||||
await db.batch(values.map((value, i) => ({ type: 'put', key: String(i), value })))
|
||||
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
const value = await db.get(String(i))
|
||||
|
||||
// Buffer is a Uint8Array, so this is allowed
|
||||
if (encoding === 'view' && Buffer.isBuffer(value)) {
|
||||
t.same(value, values[2])
|
||||
} else {
|
||||
t.same(value, expected)
|
||||
}
|
||||
}
|
||||
|
||||
return db.close()
|
||||
})
|
||||
|
||||
test(`empty keys in batch with ${encoding} keyEncoding`, async function (t) {
|
||||
const db = testCommon.factory({ keyEncoding: encoding })
|
||||
const keys = ['', Uint8Array.from([]), Buffer.alloc(0)]
|
||||
|
||||
await db.open()
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
await db.batch([{ type: 'put', key: keys[i], value: String(i) }])
|
||||
t.same(await db.get(keys[i]), String(i), `got value ${i}`)
|
||||
}
|
||||
|
||||
return db.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.atomic = function (test, testCommon) {
|
||||
test('test batch() is atomic', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
let async = false
|
||||
|
||||
db.batch([
|
||||
{ type: 'put', key: 'foobah1', value: 'bar1' },
|
||||
{ type: 'put', value: 'bar2' },
|
||||
{ type: 'put', key: 'foobah3', value: 'bar3' }
|
||||
], function (err) {
|
||||
t.ok(err, 'should error')
|
||||
t.ok(async, 'callback is asynchronous')
|
||||
|
||||
db.get('foobah1', function (err) {
|
||||
t.ok(err, 'should not be found')
|
||||
})
|
||||
db.get('foobah3', function (err) {
|
||||
t.ok(err, 'should not be found')
|
||||
})
|
||||
})
|
||||
|
||||
async = true
|
||||
})
|
||||
}
|
||||
|
||||
exports.events = function (test, testCommon) {
|
||||
test('test batch([]) (array-form) emits batch event', async function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
|
||||
t.ok(db.supports.events.batch)
|
||||
|
||||
db.on('batch', function (ops) {
|
||||
t.same(ops, [{ type: 'put', key: 456, value: 99, custom: 123 }])
|
||||
})
|
||||
|
||||
await db.batch([{ type: 'put', key: 456, value: 99, custom: 123 }])
|
||||
await db.close()
|
||||
})
|
||||
}
|
||||
|
||||
exports.tearDown = function (test, testCommon) {
|
||||
test('tearDown', function (t) {
|
||||
db.close(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.setUp(test, testCommon)
|
||||
exports.args(test, testCommon)
|
||||
exports.batch(test, testCommon)
|
||||
exports.atomic(test, testCommon)
|
||||
exports.events(test, testCommon)
|
||||
exports.tearDown(test, testCommon)
|
||||
}
|
||||
Generated
Vendored
+295
@@ -0,0 +1,295 @@
|
||||
'use strict'
|
||||
|
||||
let db
|
||||
|
||||
exports.setUp = function (test, testCommon) {
|
||||
test('setUp db', function (t) {
|
||||
db = testCommon.factory()
|
||||
db.open(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
exports.args = function (test, testCommon) {
|
||||
test('test batch has db reference', function (t) {
|
||||
t.ok(db.batch().db === db)
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('test batch#put() with missing, null or undefined `value`', function (t) {
|
||||
t.plan(3 * 2)
|
||||
|
||||
for (const args of [[null], [undefined], []]) {
|
||||
const batch = db.batch()
|
||||
|
||||
try {
|
||||
batch.put('key', ...args)
|
||||
} catch (err) {
|
||||
t.is(err.code, 'LEVEL_INVALID_VALUE', 'correct error code')
|
||||
t.is(batch.length, 0, 'length is not incremented on error')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('test batch#put() with missing, null or undefined `key`', function (t) {
|
||||
t.plan(3 * 2)
|
||||
|
||||
for (const args of [[], [null, 'foo'], [undefined, 'foo']]) {
|
||||
const batch = db.batch()
|
||||
|
||||
try {
|
||||
batch.put(...args)
|
||||
} catch (err) {
|
||||
t.is(err.code, 'LEVEL_INVALID_KEY', 'correct error code')
|
||||
t.is(batch.length, 0, 'length is not incremented on error')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('test batch#del() with missing, null or undefined `key`', function (t) {
|
||||
t.plan(3 * 2)
|
||||
|
||||
for (const args of [[null], [undefined], []]) {
|
||||
const batch = db.batch()
|
||||
|
||||
try {
|
||||
batch.del(...args)
|
||||
} catch (err) {
|
||||
t.is(err.code, 'LEVEL_INVALID_KEY', 'correct error code')
|
||||
t.is(batch.length, 0, 'length is not incremented on error')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('test batch#clear() doesn\'t throw', function (t) {
|
||||
db.batch().clear()
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('test batch#put() after write()', function (t) {
|
||||
const batch = db.batch().put('foo', 'bar')
|
||||
batch.write(function () {})
|
||||
try {
|
||||
batch.put('boom', 'bang')
|
||||
} catch (err) {
|
||||
t.is(err.code, 'LEVEL_BATCH_NOT_OPEN', 'correct error code')
|
||||
return t.end()
|
||||
}
|
||||
t.fail('should have thrown')
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('test batch#del() after write()', function (t) {
|
||||
const batch = db.batch().put('foo', 'bar')
|
||||
batch.write(function () {})
|
||||
try {
|
||||
batch.del('foo')
|
||||
} catch (err) {
|
||||
t.is(err.code, 'LEVEL_BATCH_NOT_OPEN', 'correct error code')
|
||||
return t.end()
|
||||
}
|
||||
t.fail('should have thrown')
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('test batch#clear() after write()', function (t) {
|
||||
const batch = db.batch().put('foo', 'bar')
|
||||
batch.write(function () {})
|
||||
try {
|
||||
batch.clear()
|
||||
} catch (err) {
|
||||
t.is(err.code, 'LEVEL_BATCH_NOT_OPEN', 'correct error code')
|
||||
return t.end()
|
||||
}
|
||||
t.fail('should have thrown')
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('test batch#write() after write()', function (t) {
|
||||
t.plan(1)
|
||||
const batch = db.batch().put('foo', 'bar')
|
||||
batch.write(function () {})
|
||||
batch.write(function (err) {
|
||||
t.is(err && err.code, 'LEVEL_BATCH_NOT_OPEN', 'correct error code')
|
||||
})
|
||||
})
|
||||
|
||||
test('test batch#write() with no operations', function (t) {
|
||||
let async = false
|
||||
|
||||
db.batch().write(function (err) {
|
||||
t.ifError(err, 'no error from write()')
|
||||
t.ok(async, 'callback is asynchronous')
|
||||
t.end()
|
||||
})
|
||||
|
||||
async = true
|
||||
})
|
||||
|
||||
test('test batch#write() with promise and no operations', function (t) {
|
||||
db.batch().write()
|
||||
.then(t.end.bind(t))
|
||||
.catch(t.end.bind(t))
|
||||
})
|
||||
|
||||
test('test twice batch#close() is idempotent', function (t) {
|
||||
const batch = db.batch()
|
||||
batch.close(function () {
|
||||
let async = false
|
||||
|
||||
batch.close(function () {
|
||||
t.ok(async, 'callback is asynchronous')
|
||||
t.end()
|
||||
})
|
||||
|
||||
async = true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.batch = function (test, testCommon) {
|
||||
test('test basic batch', function (t) {
|
||||
db.batch([
|
||||
{ type: 'put', key: 'one', value: '1' },
|
||||
{ type: 'put', key: 'two', value: '2' },
|
||||
{ type: 'put', key: 'three', value: '3' }
|
||||
], function (err) {
|
||||
t.error(err)
|
||||
|
||||
const batch = db.batch()
|
||||
.put('1', 'one')
|
||||
.del('2', 'two')
|
||||
.put('3', 'three')
|
||||
|
||||
t.is(batch.length, 3, 'length was incremented')
|
||||
|
||||
batch.clear()
|
||||
t.is(batch.length, 0, 'length is reset')
|
||||
|
||||
batch.put('one', 'I')
|
||||
.put('two', 'II')
|
||||
.del('three')
|
||||
.put('foo', 'bar')
|
||||
|
||||
t.is(batch.length, 4, 'length was incremented')
|
||||
|
||||
batch.write(function (err) {
|
||||
t.error(err, 'no write() error')
|
||||
|
||||
db.iterator({ keyEncoding: 'utf8', valueEncoding: 'utf8' }).all(function (err, entries) {
|
||||
t.error(err)
|
||||
t.same(entries, [
|
||||
['foo', 'bar'],
|
||||
['one', 'I'],
|
||||
['two', 'II']
|
||||
])
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('test basic batch with promise', function (t) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open(function (err) {
|
||||
t.error(err)
|
||||
|
||||
db.batch()
|
||||
.put('1', 'one')
|
||||
.put('2', 'two')
|
||||
.put('3', 'three')
|
||||
.write().then(function () {
|
||||
db.iterator({ keyEncoding: 'utf8', valueEncoding: 'utf8' }).all(function (err, entries) {
|
||||
t.error(err)
|
||||
t.same(entries, [
|
||||
['1', 'one'],
|
||||
['2', 'two'],
|
||||
['3', 'three']
|
||||
])
|
||||
db.close(t.end.bind(t))
|
||||
})
|
||||
}).catch(t.fail.bind(t))
|
||||
})
|
||||
})
|
||||
|
||||
// NOTE: adapted from levelup
|
||||
test('chained batch with per-operation encoding options', async function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
|
||||
db.once('batch', function (operations) {
|
||||
t.same(operations, [
|
||||
{ type: 'put', key: 'a', value: 'a', valueEncoding: 'json' },
|
||||
{ type: 'put', key: 'b', value: 'b' },
|
||||
{ type: 'put', key: '"c"', value: 'c' },
|
||||
{ type: 'del', key: 'c', keyEncoding: 'json', arbitraryOption: true }
|
||||
])
|
||||
})
|
||||
|
||||
await db.batch()
|
||||
.put('a', 'a', { valueEncoding: 'json' })
|
||||
.put('b', 'b')
|
||||
.put('"c"', 'c')
|
||||
.del('c', { keyEncoding: 'json', arbitraryOption: true })
|
||||
.write()
|
||||
|
||||
t.same(await db.iterator().all(), [
|
||||
['a', '"a"'],
|
||||
['b', 'b']
|
||||
])
|
||||
|
||||
return db.close()
|
||||
})
|
||||
}
|
||||
|
||||
exports.events = function (test, testCommon) {
|
||||
test('test chained batch() emits batch event', async function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
|
||||
t.ok(db.supports.events.batch)
|
||||
|
||||
db.on('batch', function (ops) {
|
||||
t.same(ops, [
|
||||
{ type: 'put', key: 987, value: 'b', custom: 123 },
|
||||
{ type: 'del', key: 216, custom: 999 }
|
||||
])
|
||||
})
|
||||
|
||||
await db.batch().put(987, 'b', { custom: 123 }).del(216, { custom: 999 }).write()
|
||||
await db.close()
|
||||
})
|
||||
|
||||
test('test close() on chained batch event', async function () {
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
|
||||
let promise
|
||||
|
||||
db.on('batch', function () {
|
||||
// Should not interfere with the current write() operation
|
||||
promise = db.close()
|
||||
})
|
||||
|
||||
await db.batch().put('a', 'b').write()
|
||||
await promise
|
||||
})
|
||||
}
|
||||
|
||||
exports.tearDown = function (test, testCommon) {
|
||||
test('tearDown', function (t) {
|
||||
db.close(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.setUp(test, testCommon)
|
||||
exports.args(test, testCommon)
|
||||
exports.batch(test, testCommon)
|
||||
exports.events(test, testCommon)
|
||||
exports.tearDown(test, testCommon)
|
||||
}
|
||||
Generated
Vendored
+274
@@ -0,0 +1,274 @@
|
||||
'use strict'
|
||||
|
||||
const data = (function () {
|
||||
const d = []
|
||||
let i = 0
|
||||
let k
|
||||
for (; i < 100; i++) {
|
||||
k = (i < 10 ? '0' : '') + i
|
||||
d.push({
|
||||
key: k,
|
||||
value: String(Math.random())
|
||||
})
|
||||
}
|
||||
return d
|
||||
}())
|
||||
|
||||
exports.range = function (test, testCommon) {
|
||||
function rangeTest (name, opts, expected) {
|
||||
test('db#clear() with ' + name, function (t) {
|
||||
prepare(t, function (db) {
|
||||
db.clear(opts, function (err) {
|
||||
t.ifError(err, 'no clear error')
|
||||
verify(t, db, expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function prepare (t, callback) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err, 'no open error')
|
||||
|
||||
db.batch(data.map(function (d) {
|
||||
return {
|
||||
type: 'put',
|
||||
key: d.key,
|
||||
value: d.value
|
||||
}
|
||||
}), function (err) {
|
||||
t.ifError(err, 'no batch error')
|
||||
callback(db)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function verify (t, db, expected) {
|
||||
const it = db.iterator({ keyEncoding: 'utf8', valueEncoding: 'utf8' })
|
||||
|
||||
it.all(function (err, entries) {
|
||||
t.ifError(err, 'no all() error')
|
||||
t.is(entries.length, expected.length, 'correct number of entries')
|
||||
t.same(entries, expected.map(kv => [kv.key, kv.value]))
|
||||
|
||||
db.close(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
function exclude (data, start, end, expectedLength) {
|
||||
data = data.slice()
|
||||
const removed = data.splice(start, end - start + 1) // Inclusive
|
||||
if (expectedLength != null) checkLength(removed, expectedLength)
|
||||
return data
|
||||
}
|
||||
|
||||
// For sanity checks on test arguments
|
||||
function checkLength (arr, length) {
|
||||
if (arr.length !== length) {
|
||||
throw new RangeError('Expected ' + length + ' elements, got ' + arr.length)
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
|
||||
rangeTest('full range', {}, [])
|
||||
|
||||
// Reversing has no effect without limit
|
||||
rangeTest('reverse=true', {
|
||||
reverse: true
|
||||
}, [])
|
||||
|
||||
rangeTest('gte=00', {
|
||||
gte: '00'
|
||||
}, [])
|
||||
|
||||
rangeTest('gte=50', {
|
||||
gte: '50'
|
||||
}, data.slice(0, 50))
|
||||
|
||||
rangeTest('lte=50 and reverse=true', {
|
||||
lte: '50',
|
||||
reverse: true
|
||||
}, data.slice(51))
|
||||
|
||||
rangeTest('gte=49.5 (midway)', {
|
||||
gte: '49.5'
|
||||
}, data.slice(0, 50))
|
||||
|
||||
rangeTest('gte=49999 (midway)', {
|
||||
gte: '49999'
|
||||
}, data.slice(0, 50))
|
||||
|
||||
rangeTest('lte=49.5 (midway) and reverse=true', {
|
||||
lte: '49.5',
|
||||
reverse: true
|
||||
}, data.slice(50))
|
||||
|
||||
rangeTest('lt=49.5 (midway) and reverse=true', {
|
||||
lt: '49.5',
|
||||
reverse: true
|
||||
}, data.slice(50))
|
||||
|
||||
rangeTest('lt=50 and reverse=true', {
|
||||
lt: '50',
|
||||
reverse: true
|
||||
}, data.slice(50))
|
||||
|
||||
rangeTest('lte=50', {
|
||||
lte: '50'
|
||||
}, data.slice(51))
|
||||
|
||||
rangeTest('lte=50.5 (midway)', {
|
||||
lte: '50.5'
|
||||
}, data.slice(51))
|
||||
|
||||
rangeTest('lte=50555 (midway)', {
|
||||
lte: '50555'
|
||||
}, data.slice(51))
|
||||
|
||||
rangeTest('lt=50555 (midway)', {
|
||||
lt: '50555'
|
||||
}, data.slice(51))
|
||||
|
||||
rangeTest('gte=50.5 (midway) and reverse=true', {
|
||||
gte: '50.5',
|
||||
reverse: true
|
||||
}, data.slice(0, 51))
|
||||
|
||||
rangeTest('gt=50.5 (midway) and reverse=true', {
|
||||
gt: '50.5',
|
||||
reverse: true
|
||||
}, data.slice(0, 51))
|
||||
|
||||
rangeTest('gt=50 and reverse=true', {
|
||||
gt: '50',
|
||||
reverse: true
|
||||
}, data.slice(0, 51))
|
||||
|
||||
// First key is actually '00' so it should avoid it
|
||||
rangeTest('lte=0', {
|
||||
lte: '0'
|
||||
}, data)
|
||||
|
||||
// First key is actually '00' so it should avoid it
|
||||
rangeTest('lt=0', {
|
||||
lt: '0'
|
||||
}, data)
|
||||
|
||||
rangeTest('gte=30 and lte=70', {
|
||||
gte: '30',
|
||||
lte: '70'
|
||||
}, exclude(data, 30, 70))
|
||||
|
||||
// The gte and lte options should take precedence over gt and lt respectively.
|
||||
rangeTest('test iterator with gte=30 and lte=70 and gt=40 and lt=60', {
|
||||
gte: '30',
|
||||
lte: '70',
|
||||
gt: '40',
|
||||
lt: '60'
|
||||
}, exclude(data, 30, 70))
|
||||
|
||||
// Also test the other way around: if gt and lt were to select a bigger range.
|
||||
rangeTest('test iterator with gte=30 and lte=70 and gt=20 and lt=80', {
|
||||
gte: '30',
|
||||
lte: '70',
|
||||
gt: '20',
|
||||
lt: '80'
|
||||
}, exclude(data, 30, 70))
|
||||
|
||||
rangeTest('gt=29 and lt=71', {
|
||||
gt: '29',
|
||||
lt: '71'
|
||||
}, exclude(data, 30, 70))
|
||||
|
||||
rangeTest('gte=30 and lte=70 and reverse=true', {
|
||||
lte: '70',
|
||||
gte: '30',
|
||||
reverse: true
|
||||
}, exclude(data, 30, 70))
|
||||
|
||||
rangeTest('gt=29 and lt=71 and reverse=true', {
|
||||
lt: '71',
|
||||
gt: '29',
|
||||
reverse: true
|
||||
}, exclude(data, 30, 70))
|
||||
|
||||
rangeTest('limit=20', {
|
||||
limit: 20
|
||||
}, data.slice(20))
|
||||
|
||||
rangeTest('limit=20 and gte=20', {
|
||||
limit: 20,
|
||||
gte: '20'
|
||||
}, exclude(data, 20, 39, 20))
|
||||
|
||||
rangeTest('limit=20 and reverse=true', {
|
||||
limit: 20,
|
||||
reverse: true
|
||||
}, data.slice(0, -20))
|
||||
|
||||
rangeTest('limit=20 and lte=79 and reverse=true', {
|
||||
limit: 20,
|
||||
lte: '79',
|
||||
reverse: true
|
||||
}, exclude(data, 60, 79, 20))
|
||||
|
||||
rangeTest('limit=-1 should clear whole database', {
|
||||
limit: -1
|
||||
}, [])
|
||||
|
||||
rangeTest('limit=0 should not clear anything', {
|
||||
limit: 0
|
||||
}, data)
|
||||
|
||||
rangeTest('lte after limit', {
|
||||
limit: 20,
|
||||
lte: '50'
|
||||
}, data.slice(20))
|
||||
|
||||
rangeTest('lte before limit', {
|
||||
limit: 50,
|
||||
lte: '19'
|
||||
}, data.slice(20))
|
||||
|
||||
rangeTest('gte after database end', {
|
||||
gte: '9a'
|
||||
}, data)
|
||||
|
||||
rangeTest('gt after database end', {
|
||||
gt: '9a'
|
||||
}, data)
|
||||
|
||||
rangeTest('lte after database end and reverse=true', {
|
||||
lte: '9a',
|
||||
reverse: true
|
||||
}, [])
|
||||
|
||||
rangeTest('lte and gte after database and reverse=true', {
|
||||
lte: '9b',
|
||||
gte: '9a',
|
||||
reverse: true
|
||||
}, data)
|
||||
|
||||
rangeTest('lt and gt after database and reverse=true', {
|
||||
lt: '9b',
|
||||
gt: '9a',
|
||||
reverse: true
|
||||
}, data)
|
||||
|
||||
rangeTest('gt greater than lt', {
|
||||
gt: '20',
|
||||
lt: '10'
|
||||
}, data)
|
||||
|
||||
rangeTest('gte greater than lte', {
|
||||
gte: '20',
|
||||
lte: '10'
|
||||
}, data)
|
||||
}
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.range(test, testCommon)
|
||||
}
|
||||
Generated
Vendored
+183
@@ -0,0 +1,183 @@
|
||||
'use strict'
|
||||
|
||||
const isBuffer = require('is-buffer')
|
||||
const { Buffer } = require('buffer')
|
||||
|
||||
exports.args = function (test, testCommon) {
|
||||
test('test clear() with legacy range options', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err)
|
||||
|
||||
try {
|
||||
db.clear({ start: 'foo' }, t.fail.bind(t))
|
||||
} catch (err) {
|
||||
t.is(err.code, 'LEVEL_LEGACY')
|
||||
}
|
||||
|
||||
try {
|
||||
db.clear({ end: 'foo' }).catch(t.fail.bind(t))
|
||||
} catch (err) {
|
||||
t.is(err.code, 'LEVEL_LEGACY')
|
||||
}
|
||||
|
||||
db.close(t.ifError.bind(t))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.clear = function (test, testCommon) {
|
||||
makeTest('string', ['a', 'b'])
|
||||
|
||||
if (testCommon.supports.encodings.buffer) {
|
||||
makeTest('buffer', [Buffer.from('a'), Buffer.from('b')])
|
||||
makeTest('mixed', [Buffer.from('a'), 'b'])
|
||||
|
||||
// These keys would be equal when compared as utf8 strings
|
||||
makeTest('non-utf8 buffer', [Buffer.from('80', 'hex'), Buffer.from('c0', 'hex')])
|
||||
}
|
||||
|
||||
function makeTest (type, keys) {
|
||||
test('test simple clear() on ' + type + ' keys', function (t) {
|
||||
t.plan(8)
|
||||
|
||||
const db = testCommon.factory()
|
||||
const ops = keys.map(function (key) {
|
||||
return { type: 'put', key: key, value: 'foo', keyEncoding: isBuffer(key) ? 'buffer' : 'utf8' }
|
||||
})
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err, 'no open error')
|
||||
|
||||
db.batch(ops, function (err) {
|
||||
t.ifError(err, 'no batch error')
|
||||
|
||||
db.iterator().all(function (err, entries) {
|
||||
t.ifError(err, 'no all() error')
|
||||
t.is(entries.length, keys.length, 'has entries')
|
||||
|
||||
db.clear(function (err) {
|
||||
t.ifError(err, 'no clear error')
|
||||
|
||||
db.iterator().all(function (err, entries) {
|
||||
t.ifError(err, 'no all() error')
|
||||
t.is(entries.length, 0, 'has no entries')
|
||||
|
||||
db.close(function (err) {
|
||||
t.ifError(err, 'no close error')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('test simple clear() on ' + type + ' keys, with promise', function (t) {
|
||||
t.plan(8)
|
||||
|
||||
const db = testCommon.factory()
|
||||
const ops = keys.map(function (key) {
|
||||
return { type: 'put', key: key, value: 'foo', keyEncoding: isBuffer(key) ? 'buffer' : 'utf8' }
|
||||
})
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err, 'no open error')
|
||||
|
||||
db.batch(ops, function (err) {
|
||||
t.ifError(err, 'no batch error')
|
||||
|
||||
db.iterator().all(function (err, entries) {
|
||||
t.ifError(err, 'no all() error')
|
||||
t.is(entries.length, keys.length, 'has entries')
|
||||
|
||||
db.clear().then(function () {
|
||||
t.ifError(err, 'no clear error')
|
||||
|
||||
db.iterator().all(function (err, entries) {
|
||||
t.ifError(err, 'no all() error')
|
||||
t.is(entries.length, 0, 'has no entries')
|
||||
|
||||
db.close(function (err) {
|
||||
t.ifError(err, 'no close error')
|
||||
})
|
||||
})
|
||||
}).catch(t.fail.bind(t))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// NOTE: adapted from levelup
|
||||
for (const deferred of [false, true]) {
|
||||
for (const [gte, keyEncoding] of [['"b"', 'utf8'], ['b', 'json']]) {
|
||||
test(`clear() with ${keyEncoding} encoding (deferred: ${deferred})`, async function (t) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
await db.open()
|
||||
await db.batch([
|
||||
{ type: 'put', key: '"a"', value: 'a' },
|
||||
{ type: 'put', key: '"b"', value: 'b' }
|
||||
])
|
||||
|
||||
if (deferred) {
|
||||
await db.close()
|
||||
t.is(db.status, 'closed')
|
||||
db.open(t.ifError.bind(t))
|
||||
t.is(db.status, 'opening')
|
||||
}
|
||||
|
||||
await db.clear({ gte, keyEncoding })
|
||||
|
||||
const keys = await db.keys().all()
|
||||
t.same(keys, ['"a"'], 'got expected keys')
|
||||
|
||||
return db.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.events = function (test, testCommon) {
|
||||
test('test clear() with options emits clear event', async function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
|
||||
t.ok(db.supports.events.clear)
|
||||
|
||||
db.on('clear', function (options) {
|
||||
t.same(options, { gt: 567, custom: 123 })
|
||||
})
|
||||
|
||||
await db.clear({ gt: 567, custom: 123 })
|
||||
await db.close()
|
||||
})
|
||||
|
||||
test('test clear() without options emits clear event', async function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
|
||||
t.ok(db.supports.events.clear)
|
||||
|
||||
db.on('clear', function (options) {
|
||||
t.same(options, {})
|
||||
})
|
||||
|
||||
await db.clear()
|
||||
await db.close()
|
||||
})
|
||||
}
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.args(test, testCommon)
|
||||
exports.events(test, testCommon)
|
||||
exports.clear(test, testCommon)
|
||||
}
|
||||
Generated
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
'use strict'
|
||||
|
||||
exports.close = function (test, testCommon) {
|
||||
test('test close()', function (t) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err, 'no open() error')
|
||||
|
||||
db.close(function (err) {
|
||||
t.error(err)
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('test close() with promise', function (t) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err, 'no open() error')
|
||||
|
||||
db.close()
|
||||
.then(t.end.bind(t))
|
||||
.catch(t.end.bind(t))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.close(test, testCommon)
|
||||
}
|
||||
Generated
Vendored
+90
@@ -0,0 +1,90 @@
|
||||
'use strict'
|
||||
|
||||
const kNone = Symbol('none')
|
||||
const kProtected = Symbol('protected')
|
||||
|
||||
function testCommon (options) {
|
||||
const factory = options.factory
|
||||
const test = options.test
|
||||
|
||||
if (typeof factory !== 'function') {
|
||||
throw new TypeError('factory must be a function')
|
||||
}
|
||||
|
||||
if (typeof test !== 'function') {
|
||||
throw new TypeError('test must be a function')
|
||||
}
|
||||
|
||||
if (options.legacyRange != null) {
|
||||
throw new Error('The legacyRange option has been removed')
|
||||
}
|
||||
|
||||
let supports = kNone
|
||||
|
||||
return protect(options, {
|
||||
test: test,
|
||||
factory: factory,
|
||||
internals: options.internals || {},
|
||||
|
||||
// Expose manifest through testCommon to more easily skip tests based on
|
||||
// supported features. Use a getter to only create a db once. Implicitly
|
||||
// we also test that the manifest doesn't change after the db constructor.
|
||||
get supports () {
|
||||
if (supports === kNone) this.supports = this.factory().supports
|
||||
return supports
|
||||
},
|
||||
|
||||
// Prefer assigning early via manifest-test unless test.only() is used
|
||||
// in which case we create the manifest on-demand. Copy it to be safe.
|
||||
set supports (value) {
|
||||
if (supports === kNone) supports = JSON.parse(JSON.stringify(value))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = testCommon
|
||||
|
||||
// To help migrating from abstract-leveldown.
|
||||
// Throw if test suite options are used instead of db.supports
|
||||
function protect (options, testCommon) {
|
||||
const legacyOptions = [
|
||||
['createIfMissing', true],
|
||||
['errorIfExists', true],
|
||||
['snapshots', true],
|
||||
['seek', true],
|
||||
['encodings', true],
|
||||
['deferredOpen', true],
|
||||
['streams', true],
|
||||
['clear', true],
|
||||
['getMany', true],
|
||||
['bufferKeys', false],
|
||||
['serialize', false],
|
||||
['idempotentOpen', false],
|
||||
['passiveOpen', false],
|
||||
['openCallback', false]
|
||||
]
|
||||
|
||||
Object.defineProperty(testCommon, kProtected, {
|
||||
value: true
|
||||
})
|
||||
|
||||
for (const [k, exists] of legacyOptions) {
|
||||
const msg = exists ? 'has moved to db.supports' : 'has been removed'
|
||||
|
||||
// Options may be a testCommon instance
|
||||
if (!options[kProtected] && k in options) {
|
||||
throw new Error(`The test suite option '${k}' ${msg}`)
|
||||
}
|
||||
|
||||
Object.defineProperty(testCommon, k, {
|
||||
get () {
|
||||
throw new Error(`The test suite option '${k}' ${msg}`)
|
||||
},
|
||||
set () {
|
||||
throw new Error(`The test suite option '${k}' ${msg}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return testCommon
|
||||
}
|
||||
Generated
Vendored
+329
@@ -0,0 +1,329 @@
|
||||
'use strict'
|
||||
|
||||
const { DeferredIterator } = require('../lib/deferred-iterator')
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
function verifyValues (t, db, entries) {
|
||||
let pendingGets = 3
|
||||
|
||||
for (let k = 1; k <= entries; k++) {
|
||||
db.get('k' + k, { valueEncoding: 'utf8' }, function (err, v) {
|
||||
t.ifError(err, 'no get() error')
|
||||
t.is(v, 'v' + k, 'value is ok')
|
||||
t.is(db.status, 'open', 'status is ok')
|
||||
|
||||
if (--pendingGets <= 0) {
|
||||
db.get('k4', { valueEncoding: 'utf8' }, function (err) {
|
||||
t.ok(err)
|
||||
db.close(t.ifError.bind(t))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: copied from levelup
|
||||
test('deferred open(): put() and get() on new database', function (t) {
|
||||
t.plan(15)
|
||||
|
||||
// Open database without callback, opens in next tick
|
||||
const db = testCommon.factory()
|
||||
|
||||
let pendingPuts = 3
|
||||
|
||||
// Insert 3 values with put(), these should be deferred until the database is actually open
|
||||
for (let k = 1; k <= 3; k++) {
|
||||
db.put('k' + k, 'v' + k, function (err) {
|
||||
t.ifError(err, 'no put() error')
|
||||
|
||||
if (--pendingPuts <= 0) {
|
||||
verifyValues(t, db, 3)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.is(db.status, 'opening')
|
||||
})
|
||||
|
||||
// NOTE: copied from levelup
|
||||
test('deferred open(): batch() on new database', function (t) {
|
||||
t.plan(13)
|
||||
|
||||
// Open database without callback, opens in next tick
|
||||
const db = testCommon.factory()
|
||||
|
||||
// Insert 3 values with batch(), these should be deferred until the database is actually open
|
||||
db.batch([
|
||||
{ type: 'put', key: 'k1', value: 'v1' },
|
||||
{ type: 'put', key: 'k2', value: 'v2' },
|
||||
{ type: 'put', key: 'k3', value: 'v3' }
|
||||
], function (err) {
|
||||
t.ifError(err, 'no batch() error')
|
||||
verifyValues(t, db, 3)
|
||||
})
|
||||
|
||||
t.is(db.status, 'opening')
|
||||
})
|
||||
|
||||
// NOTE: copied from levelup
|
||||
test('deferred open(): chained batch() on new database', function (t) {
|
||||
t.plan(13)
|
||||
|
||||
// Open database without callback, opens in next tick
|
||||
const db = testCommon.factory()
|
||||
|
||||
// Insert 3 values with batch(), these should be deferred until the database is actually open
|
||||
db.batch()
|
||||
.put('k1', 'v1')
|
||||
.put('k2', 'v2')
|
||||
.put('k3', 'v3')
|
||||
.write(function (err) {
|
||||
t.ifError(err, 'no write() error')
|
||||
verifyValues(t, db, 3)
|
||||
})
|
||||
|
||||
t.is(db.status, 'opening')
|
||||
})
|
||||
|
||||
// NOTE: copied from levelup
|
||||
test('deferred open(): put() and get() on reopened database', async function (t) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
await db.close()
|
||||
t.is(db.status, 'closed')
|
||||
|
||||
db.open(() => {})
|
||||
t.is(db.status, 'opening')
|
||||
|
||||
await db.put('beep', 'boop')
|
||||
|
||||
t.is(db.status, 'open')
|
||||
t.is(await db.get('beep', { valueEncoding: 'utf8' }), 'boop')
|
||||
|
||||
await db.close()
|
||||
})
|
||||
|
||||
// NOTE: copied from levelup
|
||||
test('deferred open(): value of queued operation is not stringified', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const db = testCommon.factory({ valueEncoding: 'json' })
|
||||
|
||||
db.put('key', { thing: 2 }, function (err) {
|
||||
t.ifError(err)
|
||||
|
||||
db.get('key', function (err, value) {
|
||||
t.ifError(err)
|
||||
t.same(value, { thing: 2 })
|
||||
db.close(t.ifError.bind(t))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// NOTE: copied from levelup
|
||||
test('deferred open(): key of queued operation is not stringified', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const db = testCommon.factory({ keyEncoding: 'json' })
|
||||
|
||||
db.put({ thing: 2 }, 'value', function (err) {
|
||||
t.ifError(err)
|
||||
|
||||
db.iterator().next(function (err, key, value) {
|
||||
t.ifError(err, 'no next() error')
|
||||
t.same(key, { thing: 2 })
|
||||
db.close(t.ifError.bind(t))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// NOTE: copied from deferred-leveldown
|
||||
test('cannot operate on closed db', function (t) {
|
||||
t.plan(6)
|
||||
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err)
|
||||
|
||||
db.close(function (err) {
|
||||
t.ifError(err)
|
||||
|
||||
db.put('foo', 'bar', function (err) {
|
||||
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
})
|
||||
|
||||
try {
|
||||
db.iterator()
|
||||
} catch (err) {
|
||||
t.is(err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
}
|
||||
|
||||
try {
|
||||
db.keys()
|
||||
} catch (err) {
|
||||
t.is(err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
}
|
||||
|
||||
try {
|
||||
db.values()
|
||||
} catch (err) {
|
||||
t.is(err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// NOTE: copied from deferred-leveldown
|
||||
test('cannot operate on closing db', function (t) {
|
||||
t.plan(6)
|
||||
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err)
|
||||
|
||||
db.close(function (err) {
|
||||
t.ifError(err)
|
||||
})
|
||||
|
||||
db.put('foo', 'bar', function (err) {
|
||||
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
})
|
||||
|
||||
try {
|
||||
db.iterator()
|
||||
} catch (err) {
|
||||
t.is(err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
}
|
||||
|
||||
try {
|
||||
db.keys()
|
||||
} catch (err) {
|
||||
t.is(err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
}
|
||||
|
||||
try {
|
||||
db.values()
|
||||
} catch (err) {
|
||||
t.is(err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// NOTE: copied from deferred-leveldown
|
||||
test('deferred iterator - cannot operate on closed db', function (t) {
|
||||
t.plan(10)
|
||||
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open(function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
db.close(function (err) {
|
||||
t.ifError(err)
|
||||
|
||||
it.next(function (err, key, value) {
|
||||
t.is(err && err.code, 'LEVEL_ITERATOR_NOT_OPEN')
|
||||
})
|
||||
|
||||
it.next().catch(function (err) {
|
||||
t.is(err.code, 'LEVEL_ITERATOR_NOT_OPEN')
|
||||
})
|
||||
|
||||
it.nextv(10, function (err, items) {
|
||||
t.is(err && err.code, 'LEVEL_ITERATOR_NOT_OPEN')
|
||||
})
|
||||
|
||||
it.nextv(10).catch(function (err) {
|
||||
t.is(err.code, 'LEVEL_ITERATOR_NOT_OPEN')
|
||||
})
|
||||
|
||||
it.all(function (err, items) {
|
||||
t.is(err && err.code, 'LEVEL_ITERATOR_NOT_OPEN')
|
||||
})
|
||||
|
||||
it.all().catch(function (err) {
|
||||
t.is(err.code, 'LEVEL_ITERATOR_NOT_OPEN')
|
||||
})
|
||||
|
||||
// Was already closed
|
||||
it.close(function () {
|
||||
t.ifError(err, 'no close() error')
|
||||
})
|
||||
|
||||
it.close().catch(function () {
|
||||
t.fail('no close() error')
|
||||
})
|
||||
|
||||
try {
|
||||
it.seek('foo')
|
||||
} catch (err) {
|
||||
// Should *not* throw
|
||||
t.fail(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const it = db.iterator({ gt: 'foo' })
|
||||
t.ok(it instanceof DeferredIterator)
|
||||
})
|
||||
|
||||
// NOTE: copied from deferred-leveldown
|
||||
test('deferred iterator - cannot operate on closing db', function (t) {
|
||||
t.plan(10)
|
||||
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open(function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
db.close(function (err) {
|
||||
t.ifError(err)
|
||||
})
|
||||
|
||||
it.next(function (err, key, value) {
|
||||
t.is(err && err.code, 'LEVEL_ITERATOR_NOT_OPEN')
|
||||
})
|
||||
|
||||
it.next().catch(function (err) {
|
||||
t.is(err.code, 'LEVEL_ITERATOR_NOT_OPEN')
|
||||
})
|
||||
|
||||
it.nextv(10, function (err) {
|
||||
t.is(err && err.code, 'LEVEL_ITERATOR_NOT_OPEN')
|
||||
})
|
||||
|
||||
it.nextv(10).catch(function (err) {
|
||||
t.is(err.code, 'LEVEL_ITERATOR_NOT_OPEN')
|
||||
})
|
||||
|
||||
it.all(function (err) {
|
||||
t.is(err && err.code, 'LEVEL_ITERATOR_NOT_OPEN')
|
||||
})
|
||||
|
||||
it.all().catch(function (err) {
|
||||
t.is(err.code, 'LEVEL_ITERATOR_NOT_OPEN')
|
||||
})
|
||||
|
||||
// Is already closing
|
||||
it.close(function (err) {
|
||||
t.ifError(err, 'no close() error')
|
||||
})
|
||||
|
||||
it.close().catch(function () {
|
||||
t.fail('no close() error')
|
||||
})
|
||||
|
||||
try {
|
||||
it.seek('foo')
|
||||
} catch (err) {
|
||||
// Should *not* throw
|
||||
t.fail(err)
|
||||
}
|
||||
})
|
||||
|
||||
const it = db.iterator({ gt: 'foo' })
|
||||
t.ok(it instanceof DeferredIterator)
|
||||
})
|
||||
}
|
||||
Generated
Vendored
+105
@@ -0,0 +1,105 @@
|
||||
'use strict'
|
||||
|
||||
const { verifyNotFoundError, illegalKeys, assertAsync } = require('./util')
|
||||
|
||||
let db
|
||||
|
||||
exports.setUp = function (test, testCommon) {
|
||||
test('setUp db', function (t) {
|
||||
db = testCommon.factory()
|
||||
db.open(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
exports.args = function (test, testCommon) {
|
||||
test('test del() with illegal keys', assertAsync.ctx(function (t) {
|
||||
t.plan(illegalKeys.length * 5)
|
||||
|
||||
for (const { name, key } of illegalKeys) {
|
||||
db.del(key, assertAsync(function (err) {
|
||||
t.ok(err instanceof Error, name + ' - is Error (callback)')
|
||||
t.is(err && err.code, 'LEVEL_INVALID_KEY', name + ' - correct error code (callback)')
|
||||
}))
|
||||
|
||||
db.del(key).catch(function (err) {
|
||||
t.ok(err instanceof Error, name + ' - is Error (promise)')
|
||||
t.is(err.code, 'LEVEL_INVALID_KEY', name + ' - correct error code (callback)')
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
exports.del = function (test, testCommon) {
|
||||
test('test simple del()', function (t) {
|
||||
db.put('foo', 'bar', function (err) {
|
||||
t.error(err)
|
||||
db.del('foo', function (err) {
|
||||
t.error(err)
|
||||
db.get('foo', function (err, value) {
|
||||
t.ok(err, 'entry properly deleted')
|
||||
t.ok(typeof value === 'undefined', 'value is undefined')
|
||||
t.ok(verifyNotFoundError(err), 'NotFound error')
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('test simple del() with promise', function (t) {
|
||||
db.put('foo', 'bar', function (err) {
|
||||
t.error(err)
|
||||
db.del('foo').then(function (err) {
|
||||
t.error(err)
|
||||
db.get('foo', function (err, value) {
|
||||
t.ok(err, 'entry properly deleted')
|
||||
t.ok(typeof value === 'undefined', 'value is undefined')
|
||||
t.ok(verifyNotFoundError(err), 'NotFound error')
|
||||
t.end()
|
||||
})
|
||||
}).catch(t.fail.bind(t))
|
||||
})
|
||||
})
|
||||
|
||||
test('test del on non-existent key', function (t) {
|
||||
db.del('blargh', function (err) {
|
||||
t.error(err)
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
|
||||
test('test del on non-existent key, with promise', async function (t) {
|
||||
return db.del('blargh')
|
||||
})
|
||||
}
|
||||
|
||||
exports.events = function (test, testCommon) {
|
||||
test('test del() emits del event', async function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
|
||||
t.ok(db.supports.events.del)
|
||||
|
||||
db.on('del', function (key) {
|
||||
t.is(key, 456)
|
||||
})
|
||||
|
||||
await db.del(456)
|
||||
await db.close()
|
||||
})
|
||||
}
|
||||
|
||||
exports.tearDown = function (test, testCommon) {
|
||||
test('tearDown', function (t) {
|
||||
db.close(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.setUp(test, testCommon)
|
||||
exports.args(test, testCommon)
|
||||
exports.del(test, testCommon)
|
||||
exports.events(test, testCommon)
|
||||
exports.tearDown(test, testCommon)
|
||||
}
|
||||
Generated
Vendored
+255
@@ -0,0 +1,255 @@
|
||||
'use strict'
|
||||
|
||||
const { Buffer } = require('buffer')
|
||||
const textEncoder = new TextEncoder()
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
if (!testCommon.supports.encodings.buffer) return
|
||||
|
||||
// NOTE: adapted from levelup
|
||||
test('test put() and get() with buffer value and buffer valueEncoding', async function (t) {
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
await db.put('test', testBuffer(), { valueEncoding: 'buffer' })
|
||||
t.same(await db.get('test', { valueEncoding: 'buffer' }), testBuffer())
|
||||
return db.close()
|
||||
})
|
||||
|
||||
// NOTE: adapted from levelup
|
||||
test('test put() and get() with buffer value and buffer valueEncoding in factory', async function (t) {
|
||||
const db = testCommon.factory({ valueEncoding: 'buffer' })
|
||||
await db.open()
|
||||
await db.put('test', testBuffer())
|
||||
t.same(await db.get('test'), testBuffer())
|
||||
return db.close()
|
||||
})
|
||||
|
||||
// NOTE: adapted from levelup
|
||||
test('test put() and get() with buffer key and buffer keyEncoding', async function (t) {
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
await db.put(testBuffer(), 'test', { keyEncoding: 'buffer' })
|
||||
t.same(await db.get(testBuffer(), { keyEncoding: 'buffer' }), 'test')
|
||||
return db.close()
|
||||
})
|
||||
|
||||
// NOTE: adapted from levelup
|
||||
test('test put() and get() with buffer key and utf8 keyEncoding', async function (t) {
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
await db.put(Buffer.from('foo🐄'), 'test', { keyEncoding: 'utf8' })
|
||||
t.same(await db.get(Buffer.from('foo🐄'), { keyEncoding: 'utf8' }), 'test')
|
||||
return db.close()
|
||||
})
|
||||
|
||||
// NOTE: adapted from levelup
|
||||
test('test put() and get() with string value and buffer valueEncoding', async function (t) {
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
await db.put('test', 'foo🐄', { valueEncoding: 'buffer' })
|
||||
t.same(await db.get('test', { valueEncoding: 'buffer' }), Buffer.from('foo🐄'))
|
||||
t.same(await db.get('test', { valueEncoding: 'utf8' }), 'foo🐄')
|
||||
return db.close()
|
||||
})
|
||||
|
||||
// NOTE: adapted from memdown
|
||||
test('put() as string, get() as buffer and vice versa', async function (t) {
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
const enc = { keyEncoding: 'buffer', valueEncoding: 'buffer' }
|
||||
const [a, b] = ['🐄', '🐄 says moo']
|
||||
|
||||
const promise1 = db.put(a, a).then(async () => {
|
||||
const value = await db.get(Buffer.from(a), enc)
|
||||
t.same(value, Buffer.from(a), 'got buffer value')
|
||||
})
|
||||
|
||||
const promise2 = db.put(Buffer.from(b), Buffer.from(b), enc).then(async () => {
|
||||
const value = await db.get(b)
|
||||
t.same(value, b, 'got string value')
|
||||
})
|
||||
|
||||
await Promise.all([promise1, promise2])
|
||||
return db.close()
|
||||
})
|
||||
|
||||
// NOTE: adapted from memdown
|
||||
test('put() stringifies input to buffer', async function (t) {
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
await db.put(1, 2)
|
||||
|
||||
const it = db.iterator({ keyEncoding: 'buffer', valueEncoding: 'buffer' })
|
||||
const entries = await it.all()
|
||||
|
||||
t.same(entries[0][0], Buffer.from('1'), 'key was stringified')
|
||||
t.same(entries[0][1], Buffer.from('2'), 'value was stringified')
|
||||
|
||||
return db.close()
|
||||
})
|
||||
|
||||
// NOTE: adapted from memdown
|
||||
test('put() as string, iterate as buffer', async function (t) {
|
||||
const db = testCommon.factory({ keyEncoding: 'utf8', valueEncoding: 'utf8' })
|
||||
await db.open()
|
||||
await db.put('🐄', '🐄')
|
||||
|
||||
const it = db.iterator({ keyEncoding: 'buffer', valueEncoding: 'buffer' })
|
||||
const entries = await it.all()
|
||||
|
||||
t.same(entries, [[Buffer.from('🐄'), Buffer.from('🐄')]])
|
||||
return db.close()
|
||||
})
|
||||
|
||||
// NOTE: adapted from memdown
|
||||
test('put() as buffer, iterate as string', async function (t) {
|
||||
const db = testCommon.factory({ keyEncoding: 'buffer', valueEncoding: 'buffer' })
|
||||
await db.open()
|
||||
await db.put(Buffer.from('🐄'), Buffer.from('🐄'))
|
||||
|
||||
const it = db.iterator({ keyEncoding: 'utf8', valueEncoding: 'utf8' })
|
||||
const entries = await it.all()
|
||||
|
||||
t.same(entries, [['🐄', '🐄']])
|
||||
return db.close()
|
||||
})
|
||||
|
||||
test('put() as view, iterate as view', async function (t) {
|
||||
const db = testCommon.factory({ keyEncoding: 'view', valueEncoding: 'view' })
|
||||
const cow = textEncoder.encode('🐄')
|
||||
await db.open()
|
||||
await db.put(cow, cow)
|
||||
|
||||
const it = db.iterator()
|
||||
const entries = await it.all()
|
||||
const key = Buffer.isBuffer(entries[0][0]) ? Buffer.from(cow) : cow // Valid, Buffer is a Uint8Array
|
||||
const value = Buffer.isBuffer(entries[0][1]) ? Buffer.from(cow) : cow
|
||||
|
||||
t.same(entries, [[key, value]])
|
||||
return db.close()
|
||||
})
|
||||
|
||||
test('put() as string, iterate as view', async function (t) {
|
||||
const db = testCommon.factory({ keyEncoding: 'utf8', valueEncoding: 'utf8' })
|
||||
const cow = textEncoder.encode('🐄')
|
||||
await db.open()
|
||||
await db.put('🐄', '🐄')
|
||||
|
||||
const it = db.iterator({ keyEncoding: 'view', valueEncoding: 'view' })
|
||||
const entries = await it.all()
|
||||
const key = Buffer.isBuffer(entries[0][0]) ? Buffer.from(cow) : cow // Valid, Buffer is a Uint8Array
|
||||
const value = Buffer.isBuffer(entries[0][1]) ? Buffer.from(cow) : cow
|
||||
|
||||
t.same(entries, [[key, value]])
|
||||
return db.close()
|
||||
})
|
||||
|
||||
test('put() as view, iterate as string', async function (t) {
|
||||
const db = testCommon.factory({ keyEncoding: 'view', valueEncoding: 'view' })
|
||||
const cow = textEncoder.encode('🐄')
|
||||
await db.open()
|
||||
await db.put(cow, cow)
|
||||
|
||||
const it = db.iterator({ keyEncoding: 'utf8', valueEncoding: 'utf8' })
|
||||
const entries = await it.all()
|
||||
|
||||
t.same(entries, [['🐄', '🐄']])
|
||||
return db.close()
|
||||
})
|
||||
|
||||
// NOTE: adapted from levelup
|
||||
test('batch() with multiple puts with buffer valueEncoding per batch', async function (t) {
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
await db.batch([
|
||||
{ type: 'put', key: 'foo', value: testBuffer() },
|
||||
{ type: 'put', key: 'bar', value: testBuffer() },
|
||||
{ type: 'put', key: 'baz', value: 'abazvalue' }
|
||||
], { valueEncoding: 'buffer' })
|
||||
|
||||
t.same(await db.get('foo', { valueEncoding: 'buffer' }), testBuffer())
|
||||
t.same(await db.get('bar', { valueEncoding: 'buffer' }), testBuffer())
|
||||
t.same(await db.get('baz', { valueEncoding: 'buffer' }), Buffer.from('abazvalue'))
|
||||
|
||||
return db.close()
|
||||
})
|
||||
|
||||
test('batch() with multiple puts with buffer valueEncoding per operation', async function (t) {
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
await db.batch([
|
||||
{ type: 'put', key: 'foo', value: testBuffer(), valueEncoding: 'buffer' },
|
||||
{ type: 'put', key: 'bar', value: testBuffer(), valueEncoding: 'buffer' },
|
||||
{ type: 'put', key: 'baz', value: 'abazvalue', valueEncoding: 'buffer' }
|
||||
])
|
||||
|
||||
t.same(await db.get('foo', { valueEncoding: 'buffer' }), testBuffer())
|
||||
t.same(await db.get('bar', { valueEncoding: 'buffer' }), testBuffer())
|
||||
t.same(await db.get('baz', { valueEncoding: 'buffer' }), Buffer.from('abazvalue'))
|
||||
|
||||
return db.close()
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test('batch() with buffer encoding in factory', async function (t) {
|
||||
const operations = [{
|
||||
type: 'put',
|
||||
key: Buffer.from([1, 2, 3]),
|
||||
value: Buffer.from([4, 5, 6])
|
||||
}, {
|
||||
type: 'put',
|
||||
key: Buffer.from([7, 8, 9]),
|
||||
value: Buffer.from([10, 11, 12])
|
||||
}]
|
||||
|
||||
const db = testCommon.factory({ keyEncoding: 'buffer', valueEncoding: 'buffer' })
|
||||
await db.open()
|
||||
await db.batch(operations)
|
||||
|
||||
t.same(await db.get(operations[0].key), operations[0].value)
|
||||
t.same(await db.get(operations[1].key), operations[1].value)
|
||||
|
||||
return db.close()
|
||||
})
|
||||
|
||||
for (const keyEncoding of ['buffer', 'view']) {
|
||||
// NOTE: adapted from memdown
|
||||
test(`storage is byte-aware (${keyEncoding} encoding)`, function (t) {
|
||||
const db = testCommon.factory({ keyEncoding })
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err, 'no open error')
|
||||
|
||||
const one = Buffer.from('80', 'hex')
|
||||
const two = Buffer.from('c0', 'hex')
|
||||
|
||||
t.ok(two.toString() === one.toString(), 'would be equal when not byte-aware')
|
||||
t.ok(two.compare(one) > 0, 'but greater when byte-aware')
|
||||
|
||||
db.put(one, 'one', function (err) {
|
||||
t.ifError(err, 'no put() error')
|
||||
|
||||
db.get(one, function (err, value) {
|
||||
t.ifError(err, 'no get() error')
|
||||
t.is(value, 'one', 'value one ok')
|
||||
|
||||
db.put(two, 'two', function (err) {
|
||||
t.ifError(err, 'no put() error')
|
||||
|
||||
db.get(one, function (err, value) {
|
||||
t.ifError(err, 'no get() error')
|
||||
t.is(value, 'one', 'value one did not change')
|
||||
|
||||
db.close(t.end.bind(t))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function testBuffer () {
|
||||
return Buffer.from('0080c0ff', 'hex')
|
||||
}
|
||||
Generated
Vendored
+99
@@ -0,0 +1,99 @@
|
||||
'use strict'
|
||||
|
||||
// NOTE: copied from levelup
|
||||
exports.all = function (test, testCommon) {
|
||||
for (const deferred of [false, true]) {
|
||||
test(`custom encoding: simple-object values (deferred: ${deferred})`, function (t) {
|
||||
run(t, deferred, [
|
||||
{ key: '0', value: 0 },
|
||||
{ key: '1', value: 1 },
|
||||
{ key: 'string', value: 'a string' },
|
||||
{ key: 'true', value: true },
|
||||
{ key: 'false', value: false }
|
||||
])
|
||||
})
|
||||
|
||||
test(`custom encoding: simple-object keys (deferred: ${deferred})`, function (t) {
|
||||
// Test keys that would be considered the same with default utf8 encoding.
|
||||
// Because String([1]) === String(1).
|
||||
run(t, deferred, [
|
||||
{ value: '0', key: [1] },
|
||||
{ value: '1', key: 1 },
|
||||
{ value: 'string', key: 'a string' },
|
||||
{ value: 'true', key: true },
|
||||
{ value: 'false', key: false }
|
||||
])
|
||||
})
|
||||
|
||||
test(`custom encoding: complex-object values (deferred: ${deferred})`, function (t) {
|
||||
run(t, deferred, [{
|
||||
key: '0',
|
||||
value: {
|
||||
foo: 'bar',
|
||||
bar: [1, 2, 3],
|
||||
bang: { yes: true, no: false }
|
||||
}
|
||||
}])
|
||||
})
|
||||
|
||||
test(`custom encoding: complex-object keys (deferred: ${deferred})`, function (t) {
|
||||
// Test keys that would be considered the same with default utf8 encoding.
|
||||
// Because String({}) === String({}) === '[object Object]'.
|
||||
run(t, deferred, [{
|
||||
value: '0',
|
||||
key: {
|
||||
foo: 'bar',
|
||||
bar: [1, 2, 3],
|
||||
bang: { yes: true, no: false }
|
||||
}
|
||||
}, {
|
||||
value: '1',
|
||||
key: {
|
||||
foo: 'different',
|
||||
bar: [1, 2, 3],
|
||||
bang: { yes: true, no: false }
|
||||
}
|
||||
}])
|
||||
})
|
||||
}
|
||||
|
||||
function run (t, deferred, entries) {
|
||||
const customEncoding = {
|
||||
encode: JSON.stringify,
|
||||
decode: JSON.parse,
|
||||
format: 'utf8',
|
||||
type: 'custom'
|
||||
}
|
||||
|
||||
const db = testCommon.factory({
|
||||
keyEncoding: customEncoding,
|
||||
valueEncoding: customEncoding
|
||||
})
|
||||
|
||||
const operations = entries.map(entry => ({ type: 'put', ...entry }))
|
||||
const init = deferred ? (fn) => fn() : db.open.bind(db)
|
||||
|
||||
init(function (err) {
|
||||
t.ifError(err, 'no init() error')
|
||||
|
||||
db.batch(operations, function (err) {
|
||||
t.ifError(err, 'no batch() error')
|
||||
|
||||
let pending = entries.length
|
||||
const next = () => {
|
||||
if (--pending === 0) {
|
||||
db.close(t.end.bind(t))
|
||||
}
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
db.get(entry.key, function (err, value) {
|
||||
t.ifError(err, 'no get() error')
|
||||
t.same(value, entry.value)
|
||||
next()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
'use strict'
|
||||
|
||||
let db
|
||||
let keySequence = 0
|
||||
|
||||
const testKey = () => 'test' + (++keySequence)
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
test('setup', async function (t) {
|
||||
db = testCommon.factory()
|
||||
return db.open()
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test('get() and getMany() forward decode error', function (t) {
|
||||
const key = testKey()
|
||||
const valueEncoding = {
|
||||
encode: (v) => v,
|
||||
decode: (v) => { throw new Error('decode error xyz') },
|
||||
format: 'utf8'
|
||||
}
|
||||
|
||||
db.put(key, 'bar', { valueEncoding }, function (err) {
|
||||
t.ifError(err, 'no put() error')
|
||||
|
||||
db.get(key, { valueEncoding }, function (err, value) {
|
||||
t.is(err && err.code, 'LEVEL_DECODE_ERROR')
|
||||
t.is(err && err.cause && err.cause.message, 'decode error xyz')
|
||||
t.is(value, undefined)
|
||||
|
||||
db.getMany(['other-key', key], { valueEncoding }, function (err, values) {
|
||||
t.is(err && err.code, 'LEVEL_DECODE_ERROR')
|
||||
t.is(err && err.cause && err.cause.message, 'decode error xyz')
|
||||
t.is(values, undefined)
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test('get() and getMany() yield encoding error if stored value is invalid', function (t) {
|
||||
const key = testKey()
|
||||
|
||||
db.put(key, 'this {} is [] not : json', { valueEncoding: 'utf8' }, function (err) {
|
||||
t.ifError(err, 'no put() error')
|
||||
|
||||
db.get(key, { valueEncoding: 'json' }, function (err) {
|
||||
t.is(err && err.code, 'LEVEL_DECODE_ERROR')
|
||||
t.is(err && err.cause.name, 'SyntaxError') // From JSON.parse()
|
||||
|
||||
db.getMany(['other-key', key], { valueEncoding: 'json' }, function (err) {
|
||||
t.is(err && err.code, 'LEVEL_DECODE_ERROR')
|
||||
t.is(err && err.cause.name, 'SyntaxError') // From JSON.parse()
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('teardown', async function (t) {
|
||||
return db.close()
|
||||
})
|
||||
}
|
||||
Generated
Vendored
+88
@@ -0,0 +1,88 @@
|
||||
'use strict'
|
||||
|
||||
// NOTE: copied from levelup
|
||||
exports.all = function (test, testCommon) {
|
||||
for (const deferred of [false, true]) {
|
||||
test(`json encoding: simple-object values (deferred: ${deferred})`, function (t) {
|
||||
run(t, deferred, [
|
||||
{ key: '0', value: 0 },
|
||||
{ key: '1', value: 1 },
|
||||
{ key: '2', value: 'a string' },
|
||||
{ key: '3', value: true },
|
||||
{ key: '4', value: false }
|
||||
])
|
||||
})
|
||||
|
||||
test(`json encoding: simple-object keys (deferred: ${deferred})`, function (t) {
|
||||
run(t, deferred, [
|
||||
{ value: 'string', key: 'a string' },
|
||||
{ value: '0', key: 0 },
|
||||
{ value: '1', key: 1 },
|
||||
{ value: 'false', key: false },
|
||||
{ value: 'true', key: true }
|
||||
])
|
||||
})
|
||||
|
||||
test(`json encoding: complex-object values (deferred: ${deferred})`, function (t) {
|
||||
run(t, deferred, [{
|
||||
key: '0',
|
||||
value: {
|
||||
foo: 'bar',
|
||||
bar: [1, 2, 3],
|
||||
bang: { yes: true, no: false }
|
||||
}
|
||||
}])
|
||||
})
|
||||
|
||||
test(`json encoding: complex-object keys (deferred: ${deferred})`, function (t) {
|
||||
run(t, deferred, [{
|
||||
value: '0',
|
||||
key: {
|
||||
foo: 'bar',
|
||||
bar: [1, 2, 3],
|
||||
bang: { yes: true, no: false }
|
||||
}
|
||||
}])
|
||||
})
|
||||
}
|
||||
|
||||
function run (t, deferred, entries) {
|
||||
const db = testCommon.factory({ keyEncoding: 'json', valueEncoding: 'json' })
|
||||
const operations = entries.map(entry => ({ type: 'put', ...entry }))
|
||||
const init = deferred ? (fn) => fn() : db.open.bind(db)
|
||||
|
||||
init(function (err) {
|
||||
t.ifError(err, 'no init() error')
|
||||
|
||||
db.batch(operations, function (err) {
|
||||
t.ifError(err, 'no batch() error')
|
||||
|
||||
let pending = entries.length + 1
|
||||
const next = () => {
|
||||
if (--pending === 0) db.close(t.end.bind(t))
|
||||
}
|
||||
|
||||
testGet(next)
|
||||
testIterator(next)
|
||||
})
|
||||
})
|
||||
|
||||
function testGet (next) {
|
||||
for (const entry of entries) {
|
||||
db.get(entry.key, function (err, value) {
|
||||
t.ifError(err, 'no get() error')
|
||||
t.same(value, entry.value)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function testIterator (next) {
|
||||
db.iterator().all(function (err, result) {
|
||||
t.ifError(err, 'no all() error')
|
||||
t.same(result, entries.map(kv => [kv.key, kv.value]))
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+141
@@ -0,0 +1,141 @@
|
||||
'use strict'
|
||||
|
||||
let db
|
||||
let keySequence = 0
|
||||
|
||||
const testKey = () => 'test' + (++keySequence)
|
||||
|
||||
// TODO: test encoding options on every method. This is largely
|
||||
// covered (indirectly) by other tests, but a dedicated property-
|
||||
// based test for each would be good to have.
|
||||
exports.all = function (test, testCommon) {
|
||||
test('setup', async function (t) {
|
||||
db = testCommon.factory()
|
||||
return db.open()
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test('encodings default to utf8', function (t) {
|
||||
t.is(db.keyEncoding().commonName, 'utf8')
|
||||
t.is(db.valueEncoding().commonName, 'utf8')
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('can set encoding options in factory', async function (t) {
|
||||
const dbs = []
|
||||
|
||||
for (const name of ['buffer', 'view', 'json']) {
|
||||
if (!testCommon.supports.encodings[name]) continue
|
||||
|
||||
const db1 = testCommon.factory({ keyEncoding: name })
|
||||
const db2 = testCommon.factory({ valueEncoding: name })
|
||||
const db3 = testCommon.factory({ keyEncoding: name, valueEncoding: name })
|
||||
|
||||
t.is(db1.keyEncoding().commonName, name)
|
||||
t.is(db1.keyEncoding(), db1.keyEncoding(name))
|
||||
t.is(db1.valueEncoding().commonName, 'utf8')
|
||||
t.is(db1.valueEncoding(), db1.valueEncoding('utf8'))
|
||||
|
||||
t.is(db2.keyEncoding().commonName, 'utf8')
|
||||
t.is(db2.keyEncoding(), db2.keyEncoding('utf8'))
|
||||
t.is(db2.valueEncoding().commonName, name)
|
||||
t.is(db2.valueEncoding(), db2.valueEncoding(name))
|
||||
|
||||
t.is(db3.keyEncoding().commonName, name)
|
||||
t.is(db3.keyEncoding(), db3.keyEncoding(name))
|
||||
t.is(db3.valueEncoding().commonName, name)
|
||||
t.is(db3.valueEncoding(), db3.valueEncoding(name))
|
||||
|
||||
dbs.push(db1, db2, db3)
|
||||
}
|
||||
|
||||
await Promise.all(dbs.map(db => db.close()))
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
for (const deferred of [false, true]) {
|
||||
test(`default utf8 encoding stringifies numbers (deferred: ${deferred})`, async function (t) {
|
||||
const db = testCommon.factory()
|
||||
if (!deferred) await db.open()
|
||||
await db.put(1, 2)
|
||||
t.is(await db.get(1), '2')
|
||||
return db.close()
|
||||
})
|
||||
}
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test('can decode from string to json', function (t) {
|
||||
const key = testKey()
|
||||
const data = { thisis: 'json' }
|
||||
|
||||
db.put(key, JSON.stringify(data), { valueEncoding: 'utf8' }, function (err) {
|
||||
t.ifError(err, 'no put() error')
|
||||
|
||||
db.get(key, { valueEncoding: 'json' }, function (err, value) {
|
||||
t.ifError(err, 'no get() error')
|
||||
t.same(value, data, 'got parsed object')
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test('can decode from json to string', function (t) {
|
||||
const data = { thisis: 'json' }
|
||||
const key = testKey()
|
||||
|
||||
db.put(key, data, { valueEncoding: 'json' }, function (err) {
|
||||
t.ifError(err, 'no put() error')
|
||||
|
||||
db.get(key, { valueEncoding: 'utf8' }, function (err, value) {
|
||||
t.ifError(err, 'no get() error')
|
||||
t.is(value, JSON.stringify(data), 'got unparsed JSON string')
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test('getMany() skips decoding not-found values', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const valueEncoding = {
|
||||
encode: JSON.stringify,
|
||||
decode (value) {
|
||||
t.is(value, JSON.stringify(data))
|
||||
return JSON.parse(value)
|
||||
},
|
||||
format: 'utf8'
|
||||
}
|
||||
|
||||
const data = { beep: 'boop' }
|
||||
const key = testKey()
|
||||
|
||||
db.put(key, data, { valueEncoding }, function (err) {
|
||||
t.ifError(err, 'no put() error')
|
||||
|
||||
db.getMany([key, testKey()], { valueEncoding }, function (err, values) {
|
||||
t.ifError(err, 'no getMany() error')
|
||||
t.same(values, [data, undefined])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// NOTE: adapted from memdown
|
||||
test('number keys with utf8 encoding', async function (t) {
|
||||
const db = testCommon.factory()
|
||||
const numbers = [-Infinity, 0, 12, 2, +Infinity]
|
||||
|
||||
await db.open()
|
||||
await db.batch(numbers.map(key => ({ type: 'put', key, value: 'value' })))
|
||||
|
||||
const keys = await db.keys({ keyEncoding: 'utf8' }).all()
|
||||
t.same(keys, numbers.map(String), 'sorts lexicographically')
|
||||
|
||||
return db.close()
|
||||
})
|
||||
|
||||
test('teardown', async function (t) {
|
||||
return db.close()
|
||||
})
|
||||
}
|
||||
Generated
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = function (test, testCommon) {
|
||||
test('testCommon.factory() returns valid database', function (t) {
|
||||
t.plan(6)
|
||||
|
||||
const db = testCommon.factory()
|
||||
const kEvent = Symbol('event')
|
||||
|
||||
// Avoid instanceof, for levelup compatibility tests
|
||||
t.is(typeof db, 'object', 'is an object')
|
||||
t.isNot(db, null, 'is not null')
|
||||
t.is(typeof db.open, 'function', 'has open() method')
|
||||
t.is(typeof db.on, 'function', 'has on() method')
|
||||
t.is(typeof db.emit, 'function', 'has emit() method')
|
||||
|
||||
db.once(kEvent, (v) => t.is(v, 'foo', 'got event'))
|
||||
db.emit(kEvent, 'foo')
|
||||
})
|
||||
|
||||
test('testCommon.factory() returns a unique database', function (t) {
|
||||
const db1 = testCommon.factory()
|
||||
const db2 = testCommon.factory()
|
||||
|
||||
t.isNot(db1, db2, 'unique instances')
|
||||
|
||||
function close () {
|
||||
db1.close(function (err) {
|
||||
t.error(err, 'no error while closing db1')
|
||||
db2.close(function (err) {
|
||||
t.error(err, 'no error while closing db2')
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
db1.open(function (err) {
|
||||
t.error(err, 'no error while opening db1')
|
||||
db2.open(function (err) {
|
||||
t.error(err, 'no error while opening db2')
|
||||
db1.put('key', 'value', function (err) {
|
||||
t.error(err, 'put key in db1')
|
||||
db2.get('key', function (err, value) {
|
||||
t.ok(err, 'db2 should be empty')
|
||||
t.is(value, undefined, 'db2 should be empty')
|
||||
close()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
Generated
Vendored
+276
@@ -0,0 +1,276 @@
|
||||
'use strict'
|
||||
|
||||
const { assertAsync, illegalKeys } = require('./util')
|
||||
|
||||
let db
|
||||
|
||||
/**
|
||||
* @param {import('tape')} test
|
||||
*/
|
||||
exports.setUp = function (test, testCommon) {
|
||||
test('setUp db', function (t) {
|
||||
db = testCommon.factory()
|
||||
db.open(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('tape')} test
|
||||
*/
|
||||
exports.args = function (test, testCommon) {
|
||||
test('test getMany() requires an array argument (callback)', assertAsync.ctx(function (t) {
|
||||
// Add 1 assertion for every assertAsync()
|
||||
t.plan(6)
|
||||
|
||||
db.getMany('foo', assertAsync(function (err) {
|
||||
t.is(err.name, 'TypeError')
|
||||
t.is(err && err.message, "The first argument 'keys' must be an array")
|
||||
}))
|
||||
db.getMany('foo', {}, assertAsync(function (err) {
|
||||
t.is(err.name, 'TypeError')
|
||||
t.is(err && err.message, "The first argument 'keys' must be an array")
|
||||
}))
|
||||
}))
|
||||
|
||||
test('test getMany() requires an array argument (promise)', function (t) {
|
||||
t.plan(6)
|
||||
|
||||
db.getMany().catch(function (err) {
|
||||
t.is(err.name, 'TypeError')
|
||||
t.is(err && err.message, "The first argument 'keys' must be an array")
|
||||
})
|
||||
db.getMany('foo').catch(function (err) {
|
||||
t.is(err.name, 'TypeError')
|
||||
t.is(err && err.message, "The first argument 'keys' must be an array")
|
||||
})
|
||||
db.getMany('foo', {}).catch(function (err) {
|
||||
t.is(err.name, 'TypeError')
|
||||
t.is(err && err.message, "The first argument 'keys' must be an array")
|
||||
})
|
||||
})
|
||||
|
||||
test('test getMany() with illegal keys', assertAsync.ctx(function (t) {
|
||||
// Add 1 assertion for every assertAsync()
|
||||
t.plan(illegalKeys.length * 10)
|
||||
|
||||
for (const { name, key } of illegalKeys) {
|
||||
db.getMany([key], assertAsync(function (err) {
|
||||
t.ok(err instanceof Error, name + ' - is Error (callback)')
|
||||
t.is(err && err.code, 'LEVEL_INVALID_KEY', name + ' - correct error code (callback)')
|
||||
}))
|
||||
|
||||
db.getMany(['valid', key], assertAsync(function (err) {
|
||||
t.ok(err instanceof Error, name + ' - is Error (callback, second key)')
|
||||
t.is(err && err.code, 'LEVEL_INVALID_KEY', name + ' - correct error code (callback, second key)')
|
||||
}))
|
||||
|
||||
db.getMany([key]).catch(function (err) {
|
||||
t.ok(err instanceof Error, name + ' - is Error (promise)')
|
||||
t.is(err.code, 'LEVEL_INVALID_KEY', name + ' - correct error code (promise)')
|
||||
})
|
||||
|
||||
db.getMany(['valid', key]).catch(function (err) {
|
||||
t.ok(err instanceof Error, name + ' - is Error (promise, second key)')
|
||||
t.is(err.code, 'LEVEL_INVALID_KEY', name + ' - correct error code (promise, second key)')
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('tape')} test
|
||||
*/
|
||||
exports.getMany = function (test, testCommon) {
|
||||
test('test simple getMany()', function (t) {
|
||||
db.put('foo', 'bar', function (err) {
|
||||
t.error(err)
|
||||
|
||||
function verify (err, values) {
|
||||
t.error(err)
|
||||
t.ok(Array.isArray(values), 'got an array')
|
||||
t.is(values.length, 1, 'array has 1 element')
|
||||
t.is(values[0], 'bar')
|
||||
}
|
||||
|
||||
db.getMany(['foo'], function (err, values) {
|
||||
verify(err, values)
|
||||
|
||||
db.getMany(['foo'], {}, function (err, values) {
|
||||
verify(err, values)
|
||||
|
||||
db.getMany(['foo'], { valueEncoding: 'utf8' }, function (err, values) {
|
||||
t.error(err)
|
||||
t.is(values && typeof values[0], 'string', 'should be string if not buffer')
|
||||
t.same(values, ['bar'])
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('test getMany() with multiple keys', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
db.put('beep', 'boop', function (err) {
|
||||
t.ifError(err)
|
||||
|
||||
db.getMany(['foo', 'beep'], { valueEncoding: 'utf8' }, function (err, values) {
|
||||
t.ifError(err)
|
||||
t.same(values, ['bar', 'boop'])
|
||||
})
|
||||
|
||||
db.getMany(['beep', 'foo'], { valueEncoding: 'utf8' }, function (err, values) {
|
||||
t.ifError(err)
|
||||
t.same(values, ['boop', 'bar'], 'maintains order of input keys')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('test empty getMany()', assertAsync.ctx(function (t) {
|
||||
const encodings = Object.keys(db.supports.encodings).filter(k => db.supports.encodings[k])
|
||||
t.plan(encodings.length * 3)
|
||||
|
||||
for (const valueEncoding of encodings) {
|
||||
db.getMany([], { valueEncoding }, assertAsync(function (err, values) {
|
||||
t.ifError(err)
|
||||
t.same(values, [])
|
||||
}))
|
||||
}
|
||||
}))
|
||||
|
||||
test('test not-found getMany()', assertAsync.ctx(function (t) {
|
||||
const encodings = Object.keys(db.supports.encodings).filter(k => db.supports.encodings[k])
|
||||
t.plan(encodings.length * 3)
|
||||
|
||||
for (const valueEncoding of encodings) {
|
||||
db.getMany(['nope', 'another'], { valueEncoding }, assertAsync(function (err, values) {
|
||||
t.ifError(err)
|
||||
t.same(values, [undefined, undefined])
|
||||
}))
|
||||
}
|
||||
}))
|
||||
|
||||
test('test getMany() with promise', async function (t) {
|
||||
t.same(await db.getMany(['foo'], { valueEncoding: 'utf8' }), ['bar'])
|
||||
t.same(await db.getMany(['beep'], { valueEncoding: 'utf8' }), ['boop'])
|
||||
t.same(await db.getMany(['foo', 'beep'], { valueEncoding: 'utf8' }), ['bar', 'boop'])
|
||||
t.same(await db.getMany(['beep', 'foo'], { valueEncoding: 'utf8' }), ['boop', 'bar'])
|
||||
t.same(await db.getMany(['beep', 'foo', 'nope'], { valueEncoding: 'utf8' }), ['boop', 'bar', undefined])
|
||||
t.same(await db.getMany([], { valueEncoding: 'utf8' }), [])
|
||||
})
|
||||
|
||||
test('test simultaneous getMany()', function (t) {
|
||||
db.put('hello', 'world', function (err) {
|
||||
t.error(err)
|
||||
|
||||
let completed = 0
|
||||
const done = function () {
|
||||
if (++completed === 20) t.end()
|
||||
}
|
||||
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
db.getMany(['hello'], function (err, values) {
|
||||
t.error(err)
|
||||
t.is(values.length, 1)
|
||||
t.is(values[0] && values[0].toString(), 'world')
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
db.getMany(['not found'], function (err, values) {
|
||||
t.error(err)
|
||||
t.same(values, [undefined])
|
||||
done()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('test getMany() on opening db', assertAsync.ctx(function (t) {
|
||||
t.plan(2 * 2 * 5)
|
||||
|
||||
// Also test empty array because it has a fast-path
|
||||
for (const keys of [['foo'], []]) {
|
||||
// Opening should make no difference, because we call it after getMany()
|
||||
for (const open of [true, false]) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
t.is(db.status, 'opening')
|
||||
|
||||
db.getMany(keys, assertAsync(function (err, values) {
|
||||
t.ifError(err, 'no error')
|
||||
t.same(values, keys.map(_ => undefined))
|
||||
}))
|
||||
|
||||
if (open) {
|
||||
db.open(t.error.bind(t))
|
||||
} else {
|
||||
t.pass()
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
test('test getMany() on closed db', function (t) {
|
||||
t.plan(2 * 4)
|
||||
|
||||
// Also test empty array because it has a fast-path
|
||||
for (const keys of [['foo'], []]) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err)
|
||||
|
||||
db.close(assertAsync.with(t, function (err) {
|
||||
t.ifError(err)
|
||||
|
||||
db.getMany(keys, assertAsync(function (err) {
|
||||
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
}))
|
||||
}))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
test('test getMany() on closing db', function (t) {
|
||||
t.plan(2 * 4)
|
||||
|
||||
// Also test empty array because it has a fast-path
|
||||
for (const keys of [['foo'], []]) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open(assertAsync.with(t, function (err) {
|
||||
t.ifError(err)
|
||||
|
||||
db.close(function (err) {
|
||||
t.ifError(err)
|
||||
})
|
||||
|
||||
db.getMany(keys, assertAsync(function (err) {
|
||||
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
}))
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('tape')} test
|
||||
*/
|
||||
exports.tearDown = function (test, testCommon) {
|
||||
test('tearDown', function (t) {
|
||||
db.close(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('tape')} test
|
||||
*/
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.setUp(test, testCommon)
|
||||
exports.args(test, testCommon)
|
||||
exports.getMany(test, testCommon)
|
||||
exports.tearDown(test, testCommon)
|
||||
}
|
||||
Generated
Vendored
+144
@@ -0,0 +1,144 @@
|
||||
'use strict'
|
||||
|
||||
const isBuffer = require('is-buffer')
|
||||
const { verifyNotFoundError, illegalKeys, assertAsync } = require('./util')
|
||||
|
||||
let db
|
||||
|
||||
exports.setUp = function (test, testCommon) {
|
||||
test('setUp db', function (t) {
|
||||
db = testCommon.factory()
|
||||
db.open(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
exports.args = function (test, testCommon) {
|
||||
test('test get() with illegal keys', assertAsync.ctx(function (t) {
|
||||
t.plan(illegalKeys.length * 5)
|
||||
|
||||
for (const { name, key } of illegalKeys) {
|
||||
db.get(key, assertAsync(function (err) {
|
||||
t.ok(err instanceof Error, name + ' - is Error (callback)')
|
||||
t.is(err && err.code, 'LEVEL_INVALID_KEY', name + ' - correct error code (callback)')
|
||||
}))
|
||||
|
||||
db.get(key).catch(function (err) {
|
||||
t.ok(err instanceof Error, name + ' - is Error (promise)')
|
||||
t.is(err.code, 'LEVEL_INVALID_KEY', name + ' - correct error code (promise)')
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
exports.get = function (test, testCommon) {
|
||||
test('test simple get()', function (t) {
|
||||
db.put('foo', 'bar', function (err) {
|
||||
t.error(err)
|
||||
db.get('foo', function (err, value) {
|
||||
t.error(err)
|
||||
t.is(value, 'bar')
|
||||
|
||||
db.get('foo', {}, function (err, value) { // same but with {}
|
||||
t.error(err)
|
||||
t.is(value, 'bar')
|
||||
|
||||
db.get('foo', { valueEncoding: 'utf8' }, function (err, value) {
|
||||
t.error(err)
|
||||
t.is(value, 'bar')
|
||||
|
||||
if (!db.supports.encodings.buffer) {
|
||||
return t.end()
|
||||
}
|
||||
|
||||
db.get('foo', { valueEncoding: 'buffer' }, function (err, value) {
|
||||
t.error(err)
|
||||
t.ok(isBuffer(value), 'should be buffer')
|
||||
t.is(value.toString(), 'bar')
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('test get() with promise', function (t) {
|
||||
db.put('promises', 'yes', function (err) {
|
||||
t.error(err)
|
||||
|
||||
db.get('promises').then(function (value) {
|
||||
t.is(value, 'yes', 'got value without options')
|
||||
|
||||
db.get('not found').catch(function (err) {
|
||||
t.ok(err, 'should error')
|
||||
t.ok(verifyNotFoundError(err), 'correct error')
|
||||
|
||||
if (!db.supports.encodings.buffer) {
|
||||
return t.end()
|
||||
}
|
||||
|
||||
db.get('promises', { valueEncoding: 'buffer' }).then(function (value) {
|
||||
t.ok(isBuffer(value), 'is buffer')
|
||||
t.is(value.toString(), 'yes', 'correct value')
|
||||
t.end()
|
||||
}).catch(t.fail.bind(t))
|
||||
})
|
||||
}).catch(t.fail.bind(t))
|
||||
})
|
||||
})
|
||||
|
||||
test('test simultaneous get()', function (t) {
|
||||
db.put('hello', 'world', function (err) {
|
||||
t.error(err)
|
||||
let completed = 0
|
||||
const done = function () {
|
||||
if (++completed === 20) t.end()
|
||||
}
|
||||
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
db.get('hello', function (err, value) {
|
||||
t.error(err)
|
||||
t.is(value.toString(), 'world')
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
db.get('not found', function (err, value) {
|
||||
t.ok(err, 'should error')
|
||||
t.ok(verifyNotFoundError(err), 'correct error')
|
||||
t.ok(typeof value === 'undefined', 'value is undefined')
|
||||
done()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('test get() not found error is asynchronous', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
let async = false
|
||||
|
||||
db.get('not found', function (err, value) {
|
||||
t.ok(err, 'should error')
|
||||
t.ok(verifyNotFoundError(err), 'correct error')
|
||||
t.ok(typeof value === 'undefined', 'value is undefined')
|
||||
t.ok(async, 'callback is asynchronous')
|
||||
})
|
||||
|
||||
async = true
|
||||
})
|
||||
}
|
||||
|
||||
exports.tearDown = function (test, testCommon) {
|
||||
test('tearDown', function (t) {
|
||||
db.close(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.setUp(test, testCommon)
|
||||
exports.args(test, testCommon)
|
||||
exports.get(test, testCommon)
|
||||
exports.tearDown(test, testCommon)
|
||||
}
|
||||
Generated
Vendored
+72
@@ -0,0 +1,72 @@
|
||||
'use strict'
|
||||
|
||||
const common = require('./common')
|
||||
const kSublevels = Symbol('sublevels')
|
||||
|
||||
function suite (options) {
|
||||
const testCommon = common(options)
|
||||
const test = testCommon.test
|
||||
|
||||
require('./factory-test')(test, testCommon)
|
||||
require('./manifest-test')(test, testCommon)
|
||||
require('./open-test').all(test, testCommon)
|
||||
require('./close-test').all(test, testCommon)
|
||||
|
||||
if (testCommon.supports.createIfMissing) {
|
||||
require('./open-create-if-missing-test').all(test, testCommon)
|
||||
}
|
||||
|
||||
if (testCommon.supports.errorIfExists) {
|
||||
require('./open-error-if-exists-test').all(test, testCommon)
|
||||
}
|
||||
|
||||
require('./put-test').all(test, testCommon)
|
||||
require('./get-test').all(test, testCommon)
|
||||
require('./del-test').all(test, testCommon)
|
||||
require('./put-get-del-test').all(test, testCommon)
|
||||
require('./get-many-test').all(test, testCommon)
|
||||
|
||||
require('./batch-test').all(test, testCommon)
|
||||
require('./chained-batch-test').all(test, testCommon)
|
||||
|
||||
require('./iterator-test').all(test, testCommon)
|
||||
require('./iterator-range-test').all(test, testCommon)
|
||||
require('./async-iterator-test').all(test, testCommon)
|
||||
|
||||
require('./deferred-open-test').all(test, testCommon)
|
||||
require('./encoding-test').all(test, testCommon)
|
||||
require('./encoding-json-test').all(test, testCommon)
|
||||
require('./encoding-custom-test').all(test, testCommon)
|
||||
require('./encoding-buffer-test').all(test, testCommon)
|
||||
require('./encoding-decode-error-test').all(test, testCommon)
|
||||
|
||||
if (testCommon.supports.seek) {
|
||||
require('./iterator-seek-test').all(test, testCommon)
|
||||
}
|
||||
|
||||
if (testCommon.supports.snapshots) {
|
||||
require('./iterator-snapshot-test').all(test, testCommon)
|
||||
} else {
|
||||
require('./iterator-no-snapshot-test').all(test, testCommon)
|
||||
}
|
||||
|
||||
require('./clear-test').all(test, testCommon)
|
||||
require('./clear-range-test').all(test, testCommon)
|
||||
require('./sublevel-test').all(test, testCommon)
|
||||
|
||||
// Run the same suite on a sublevel
|
||||
if (!testCommon.internals[kSublevels]) {
|
||||
const factory = testCommon.factory
|
||||
|
||||
suite({
|
||||
...testCommon,
|
||||
internals: { [kSublevels]: true },
|
||||
factory (opts) {
|
||||
return factory().sublevel('test', opts)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
suite.common = common
|
||||
module.exports = suite
|
||||
Generated
Vendored
+63
@@ -0,0 +1,63 @@
|
||||
'use strict'
|
||||
|
||||
exports.noSnapshot = function (test, testCommon) {
|
||||
function make (run) {
|
||||
return function (t) {
|
||||
const db = testCommon.factory()
|
||||
const operations = [
|
||||
{ type: 'put', key: 'a', value: 'a' },
|
||||
{ type: 'put', key: 'b', value: 'b' },
|
||||
{ type: 'put', key: 'c', value: 'c' }
|
||||
]
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err, 'no open error')
|
||||
|
||||
db.batch(operations, function (err) {
|
||||
t.ifError(err, 'no batch error')
|
||||
|
||||
// For this test it is important that we don't read eagerly.
|
||||
// NOTE: highWaterMarkBytes is not an abstract option, but
|
||||
// it is supported by classic-level and others. Also set the
|
||||
// old & equivalent leveldown highWaterMark option for compat.
|
||||
const it = db.iterator({ highWaterMarkBytes: 0, highWaterMark: 0 })
|
||||
|
||||
run(db, function (err) {
|
||||
t.ifError(err, 'no run error')
|
||||
verify(t, it, db)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function verify (t, it, db) {
|
||||
it.all(function (err, entries) {
|
||||
t.ifError(err, 'no iterator error')
|
||||
|
||||
const kv = entries.map(function ([key, value]) {
|
||||
return key.toString() + value.toString()
|
||||
})
|
||||
|
||||
if (kv.length === 3) {
|
||||
t.same(kv, ['aa', 'bb', 'cc'], 'maybe supports snapshots')
|
||||
} else {
|
||||
t.same(kv, ['aa', 'cc'], 'ignores keys that have been deleted in the mean time')
|
||||
}
|
||||
|
||||
db.close(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
test('delete key after creating iterator', make(function (db, done) {
|
||||
db.del('b', done)
|
||||
}))
|
||||
|
||||
test('batch delete key after creating iterator', make(function (db, done) {
|
||||
db.batch([{ type: 'del', key: 'b' }], done)
|
||||
}))
|
||||
}
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.noSnapshot(test, testCommon)
|
||||
}
|
||||
Generated
Vendored
+287
@@ -0,0 +1,287 @@
|
||||
'use strict'
|
||||
|
||||
let db
|
||||
|
||||
const data = (function () {
|
||||
const d = []
|
||||
let i = 0
|
||||
let k
|
||||
for (; i < 100; i++) {
|
||||
k = (i < 10 ? '0' : '') + i
|
||||
d.push({
|
||||
key: k,
|
||||
value: String(Math.random())
|
||||
})
|
||||
}
|
||||
return d
|
||||
}())
|
||||
|
||||
exports.setUp = function (test, testCommon) {
|
||||
test('setUp db', function (t) {
|
||||
db = testCommon.factory()
|
||||
db.open(function () {
|
||||
db.batch(data.map(function (d) {
|
||||
return {
|
||||
type: 'put',
|
||||
key: d.key,
|
||||
value: d.value
|
||||
}
|
||||
}), t.end.bind(t))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.range = function (test, testCommon) {
|
||||
function rangeTest (name, opts, expected) {
|
||||
opts.keyEncoding = 'utf8'
|
||||
opts.valueEncoding = 'utf8'
|
||||
|
||||
test(name, function (t) {
|
||||
db.iterator(opts).all(function (err, entries) {
|
||||
t.error(err)
|
||||
t.is(entries.length, expected.length, 'correct number of entries')
|
||||
t.same(entries, expected.map(o => [o.key, o.value]))
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
|
||||
// Test the documented promise that in reverse mode,
|
||||
// "the returned entries are the same, but in reverse".
|
||||
if (!opts.reverse && !('limit' in opts)) {
|
||||
const reverseOpts = Object.assign({}, opts, { reverse: true })
|
||||
|
||||
rangeTest(
|
||||
name + ' (flipped)',
|
||||
reverseOpts,
|
||||
expected.slice().reverse()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
rangeTest('test full data collection', {}, data)
|
||||
|
||||
rangeTest('test iterator with reverse=true', {
|
||||
reverse: true
|
||||
}, data.slice().reverse())
|
||||
|
||||
rangeTest('test iterator with gte=00', {
|
||||
gte: '00'
|
||||
}, data)
|
||||
|
||||
rangeTest('test iterator with gte=50', {
|
||||
gte: '50'
|
||||
}, data.slice(50))
|
||||
|
||||
rangeTest('test iterator with lte=50 and reverse=true', {
|
||||
lte: '50',
|
||||
reverse: true
|
||||
}, data.slice().reverse().slice(49))
|
||||
|
||||
rangeTest('test iterator with gte=49.5 (midway)', {
|
||||
gte: '49.5'
|
||||
}, data.slice(50))
|
||||
|
||||
rangeTest('test iterator with gte=49999 (midway)', {
|
||||
gte: '49999'
|
||||
}, data.slice(50))
|
||||
|
||||
rangeTest('test iterator with lte=49.5 (midway) and reverse=true', {
|
||||
lte: '49.5',
|
||||
reverse: true
|
||||
}, data.slice().reverse().slice(50))
|
||||
|
||||
rangeTest('test iterator with lt=49.5 (midway) and reverse=true', {
|
||||
lt: '49.5',
|
||||
reverse: true
|
||||
}, data.slice().reverse().slice(50))
|
||||
|
||||
rangeTest('test iterator with lt=50 and reverse=true', {
|
||||
lt: '50',
|
||||
reverse: true
|
||||
}, data.slice().reverse().slice(50))
|
||||
|
||||
rangeTest('test iterator with lte=50', {
|
||||
lte: '50'
|
||||
}, data.slice(0, 51))
|
||||
|
||||
rangeTest('test iterator with lte=50.5 (midway)', {
|
||||
lte: '50.5'
|
||||
}, data.slice(0, 51))
|
||||
|
||||
rangeTest('test iterator with lte=50555 (midway)', {
|
||||
lte: '50555'
|
||||
}, data.slice(0, 51))
|
||||
|
||||
rangeTest('test iterator with lt=50555 (midway)', {
|
||||
lt: '50555'
|
||||
}, data.slice(0, 51))
|
||||
|
||||
rangeTest('test iterator with gte=50.5 (midway) and reverse=true', {
|
||||
gte: '50.5',
|
||||
reverse: true
|
||||
}, data.slice().reverse().slice(0, 49))
|
||||
|
||||
rangeTest('test iterator with gt=50.5 (midway) and reverse=true', {
|
||||
gt: '50.5',
|
||||
reverse: true
|
||||
}, data.slice().reverse().slice(0, 49))
|
||||
|
||||
rangeTest('test iterator with gt=50 and reverse=true', {
|
||||
gt: '50',
|
||||
reverse: true
|
||||
}, data.slice().reverse().slice(0, 49))
|
||||
|
||||
// first key is actually '00' so it should avoid it
|
||||
rangeTest('test iterator with lte=0', {
|
||||
lte: '0'
|
||||
}, [])
|
||||
|
||||
// first key is actually '00' so it should avoid it
|
||||
rangeTest('test iterator with lt=0', {
|
||||
lt: '0'
|
||||
}, [])
|
||||
|
||||
rangeTest('test iterator with gte=30 and lte=70', {
|
||||
gte: '30',
|
||||
lte: '70'
|
||||
}, data.slice(30, 71))
|
||||
|
||||
// The gte and lte options should take precedence over gt and lt respectively.
|
||||
rangeTest('test iterator with gte=30 and lte=70 and gt=40 and lt=60', {
|
||||
gte: '30',
|
||||
lte: '70',
|
||||
gt: '40',
|
||||
lt: '60'
|
||||
}, data.slice(30, 71))
|
||||
|
||||
// Also test the other way around: if gt and lt were to select a bigger range.
|
||||
rangeTest('test iterator with gte=30 and lte=70 and gt=20 and lt=80', {
|
||||
gte: '30',
|
||||
lte: '70',
|
||||
gt: '20',
|
||||
lt: '80'
|
||||
}, data.slice(30, 71))
|
||||
|
||||
rangeTest('test iterator with gt=29 and lt=71', {
|
||||
gt: '29',
|
||||
lt: '71'
|
||||
}, data.slice(30, 71))
|
||||
|
||||
rangeTest('test iterator with gte=30 and lte=70 and reverse=true', {
|
||||
lte: '70',
|
||||
gte: '30',
|
||||
reverse: true
|
||||
}, data.slice().reverse().slice(29, 70))
|
||||
|
||||
rangeTest('test iterator with gt=29 and lt=71 and reverse=true', {
|
||||
lt: '71',
|
||||
gt: '29',
|
||||
reverse: true
|
||||
}, data.slice().reverse().slice(29, 70))
|
||||
|
||||
rangeTest('test iterator with limit=20', {
|
||||
limit: 20
|
||||
}, data.slice(0, 20))
|
||||
|
||||
rangeTest('test iterator with limit=20 and gte=20', {
|
||||
limit: 20,
|
||||
gte: '20'
|
||||
}, data.slice(20, 40))
|
||||
|
||||
rangeTest('test iterator with limit=20 and reverse=true', {
|
||||
limit: 20,
|
||||
reverse: true
|
||||
}, data.slice().reverse().slice(0, 20))
|
||||
|
||||
rangeTest('test iterator with limit=20 and lte=79 and reverse=true', {
|
||||
limit: 20,
|
||||
lte: '79',
|
||||
reverse: true
|
||||
}, data.slice().reverse().slice(20, 40))
|
||||
|
||||
// the default limit value from levelup is -1
|
||||
rangeTest('test iterator with limit=-1 should iterate over whole database', {
|
||||
limit: -1
|
||||
}, data)
|
||||
|
||||
rangeTest('test iterator with limit=0 should not iterate over anything', {
|
||||
limit: 0
|
||||
}, [])
|
||||
|
||||
rangeTest('test iterator with lte after limit', {
|
||||
limit: 20,
|
||||
lte: '50'
|
||||
}, data.slice(0, 20))
|
||||
|
||||
rangeTest('test iterator with lte before limit', {
|
||||
limit: 50,
|
||||
lte: '19'
|
||||
}, data.slice(0, 20))
|
||||
|
||||
rangeTest('test iterator with gte after database end', {
|
||||
gte: '9a'
|
||||
}, [])
|
||||
|
||||
rangeTest('test iterator with gt after database end', {
|
||||
gt: '9a'
|
||||
}, [])
|
||||
|
||||
rangeTest('test iterator with lte after database end and reverse=true', {
|
||||
lte: '9a',
|
||||
reverse: true
|
||||
}, data.slice().reverse())
|
||||
|
||||
rangeTest('test iterator with lt after database end', {
|
||||
lt: 'a'
|
||||
}, data.slice())
|
||||
|
||||
rangeTest('test iterator with lt at database end', {
|
||||
lt: data[data.length - 1].key
|
||||
}, data.slice(0, -1))
|
||||
|
||||
rangeTest('test iterator with lte at database end', {
|
||||
lte: data[data.length - 1].key
|
||||
}, data.slice())
|
||||
|
||||
rangeTest('test iterator with lt before database end', {
|
||||
lt: data[data.length - 2].key
|
||||
}, data.slice(0, -2))
|
||||
|
||||
rangeTest('test iterator with lte before database end', {
|
||||
lte: data[data.length - 2].key
|
||||
}, data.slice(0, -1))
|
||||
|
||||
rangeTest('test iterator with lte and gte after database and reverse=true', {
|
||||
lte: '9b',
|
||||
gte: '9a',
|
||||
reverse: true
|
||||
}, [])
|
||||
|
||||
rangeTest('test iterator with lt and gt after database and reverse=true', {
|
||||
lt: '9b',
|
||||
gt: '9a',
|
||||
reverse: true
|
||||
}, [])
|
||||
|
||||
rangeTest('gt greater than lt', {
|
||||
gt: '20',
|
||||
lt: '10'
|
||||
}, [])
|
||||
|
||||
rangeTest('gte greater than lte', {
|
||||
gte: '20',
|
||||
lte: '10'
|
||||
}, [])
|
||||
}
|
||||
|
||||
exports.tearDown = function (test, testCommon) {
|
||||
test('tearDown', function (t) {
|
||||
db.close(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.setUp(test, testCommon)
|
||||
exports.range(test, testCommon)
|
||||
exports.tearDown(test, testCommon)
|
||||
}
|
||||
Generated
Vendored
+252
@@ -0,0 +1,252 @@
|
||||
'use strict'
|
||||
|
||||
const { Buffer } = require('buffer')
|
||||
const identity = (v) => v
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.sequence(test, testCommon)
|
||||
exports.seek(test, testCommon)
|
||||
}
|
||||
|
||||
exports.sequence = function (test, testCommon) {
|
||||
for (const deferred of [false, true]) {
|
||||
for (const mode of ['iterator', 'keys', 'values']) {
|
||||
test(`${mode}().seek() throws if next() has not completed (deferred: ${deferred})`, async function (t) {
|
||||
const db = testCommon.factory()
|
||||
if (!deferred) await db.open()
|
||||
|
||||
const it = db[mode]()
|
||||
const promise = it.next()
|
||||
|
||||
t.throws(() => it.seek('two'), (err) => err.code === 'LEVEL_ITERATOR_BUSY')
|
||||
|
||||
await promise
|
||||
await db.close()
|
||||
})
|
||||
|
||||
test(`${mode}().seek() does not throw after close() (deferred: ${deferred})`, async function (t) {
|
||||
const db = testCommon.factory()
|
||||
if (!deferred) await db.open()
|
||||
|
||||
const it = db[mode]()
|
||||
await it.close()
|
||||
|
||||
t.doesNotThrow(() => it.seek('two'))
|
||||
|
||||
await db.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.seek = function (test, testCommon) {
|
||||
const testData = () => [
|
||||
{ type: 'put', key: 'one', value: '1' },
|
||||
{ type: 'put', key: 'two', value: '2' },
|
||||
{ type: 'put', key: 'three', value: '3' }
|
||||
]
|
||||
|
||||
for (const mode of ['iterator', 'keys', 'values']) {
|
||||
const mapEntry = mode === 'iterator' ? e => e : mode === 'keys' ? e => e[0] : e => e[1]
|
||||
|
||||
test(`${mode}().seek() to string target`, async function (t) {
|
||||
const db = testCommon.factory()
|
||||
await db.batch(testData())
|
||||
const it = db[mode]()
|
||||
|
||||
it.seek('two')
|
||||
|
||||
t.same(await it.next(), mapEntry(['two', '2']), 'match')
|
||||
t.same(await it.next(), undefined, 'end of iterator')
|
||||
|
||||
return db.close()
|
||||
})
|
||||
|
||||
if (testCommon.supports.encodings.buffer) {
|
||||
// TODO: make this test meaningful, with bytes outside the utf8 range
|
||||
test(`${mode}().seek() to buffer target`, async function (t) {
|
||||
const db = testCommon.factory()
|
||||
await db.batch(testData())
|
||||
const it = db[mode]({ keyEncoding: 'buffer' })
|
||||
|
||||
it.seek(Buffer.from('two'))
|
||||
|
||||
t.same(await it.next(), mapEntry([Buffer.from('two'), '2']), 'match')
|
||||
t.same(await it.next(), undefined, 'end of iterator')
|
||||
|
||||
return db.close()
|
||||
})
|
||||
}
|
||||
|
||||
test(`${mode}().seek() to target with custom encoding`, async function (t) {
|
||||
const db = testCommon.factory()
|
||||
await db.batch(testData())
|
||||
const it = db[mode]()
|
||||
const keyEncoding = { encode: () => 'two', decode: identity, format: 'utf8' }
|
||||
|
||||
it.seek('xyz', { keyEncoding })
|
||||
|
||||
t.same(await it.next(), mapEntry(['two', '2']), 'match')
|
||||
t.same(await it.next(), undefined, 'end of iterator')
|
||||
|
||||
return db.close()
|
||||
})
|
||||
|
||||
test(`${mode}().seek() on reverse iterator`, async function (t) {
|
||||
const db = testCommon.factory()
|
||||
await db.batch(testData())
|
||||
const it = db[mode]({ reverse: true, limit: 1 })
|
||||
|
||||
// Should land on key equal to or smaller than 'three!' which is 'three'
|
||||
it.seek('three!')
|
||||
|
||||
t.same(await it.next(), mapEntry(['three', '3']), 'match')
|
||||
t.same(await it.next(), undefined, 'end of iterator')
|
||||
|
||||
return db.close()
|
||||
})
|
||||
|
||||
test(`${mode}().seek() to out of range target`, async function (t) {
|
||||
const db = testCommon.factory()
|
||||
await db.batch(testData())
|
||||
const it = db[mode]()
|
||||
|
||||
it.seek('zzz')
|
||||
t.same(await it.next(), undefined, 'end of iterator')
|
||||
|
||||
return db.close()
|
||||
})
|
||||
|
||||
test(`${mode}().seek() on reverse iterator to out of range target`, async function (t) {
|
||||
const db = testCommon.factory()
|
||||
await db.batch(testData())
|
||||
const it = db[mode]({ reverse: true })
|
||||
|
||||
it.seek('zzz')
|
||||
|
||||
t.same(await it.next(), mapEntry(['two', '2']), 'match')
|
||||
t.same(await it.next(), mapEntry(['three', '3']), 'match')
|
||||
t.same(await it.next(), mapEntry(['one', '1']), 'match')
|
||||
t.same(await it.next(), undefined, 'end of iterator')
|
||||
|
||||
return db.close()
|
||||
})
|
||||
|
||||
if (testCommon.supports.snapshots) {
|
||||
for (const reverse of [false, true]) {
|
||||
for (const deferred of [false, true]) {
|
||||
test(`${mode}().seek() respects snapshot (reverse: ${reverse}, deferred: ${deferred})`, async function (t) {
|
||||
const db = testCommon.factory()
|
||||
if (!deferred) await db.open()
|
||||
|
||||
const it = db[mode]({ reverse })
|
||||
|
||||
// Add entry after having created the iterator (and its snapshot)
|
||||
await db.put('a', 'a')
|
||||
|
||||
// Seeking should not create a new snapshot, which'd include the new entry
|
||||
it.seek('a')
|
||||
t.same(await it.next(), undefined)
|
||||
|
||||
return db.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test(`${mode}().seek() respects range`, function (t) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open(function (err) {
|
||||
t.error(err, 'no error from open()')
|
||||
|
||||
// Can't use Array.fill() because IE
|
||||
const ops = []
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
ops.push({ type: 'put', key: String(i), value: String(i) })
|
||||
}
|
||||
|
||||
db.batch(ops, function (err) {
|
||||
t.error(err, 'no error from batch()')
|
||||
|
||||
let pending = 0
|
||||
|
||||
expect({ gt: '5' }, '4', undefined)
|
||||
expect({ gt: '5' }, '5', undefined)
|
||||
expect({ gt: '5' }, '6', '6')
|
||||
|
||||
expect({ gte: '5' }, '4', undefined)
|
||||
expect({ gte: '5' }, '5', '5')
|
||||
expect({ gte: '5' }, '6', '6')
|
||||
|
||||
// The gte option should take precedence over gt.
|
||||
expect({ gte: '5', gt: '7' }, '4', undefined)
|
||||
expect({ gte: '5', gt: '7' }, '5', '5')
|
||||
expect({ gte: '5', gt: '7' }, '6', '6')
|
||||
expect({ gte: '5', gt: '3' }, '4', undefined)
|
||||
expect({ gte: '5', gt: '3' }, '5', '5')
|
||||
expect({ gte: '5', gt: '3' }, '6', '6')
|
||||
|
||||
expect({ lt: '5' }, '4', '4')
|
||||
expect({ lt: '5' }, '5', undefined)
|
||||
expect({ lt: '5' }, '6', undefined)
|
||||
|
||||
expect({ lte: '5' }, '4', '4')
|
||||
expect({ lte: '5' }, '5', '5')
|
||||
expect({ lte: '5' }, '6', undefined)
|
||||
|
||||
// The lte option should take precedence over lt.
|
||||
expect({ lte: '5', lt: '3' }, '4', '4')
|
||||
expect({ lte: '5', lt: '3' }, '5', '5')
|
||||
expect({ lte: '5', lt: '3' }, '6', undefined)
|
||||
expect({ lte: '5', lt: '7' }, '4', '4')
|
||||
expect({ lte: '5', lt: '7' }, '5', '5')
|
||||
expect({ lte: '5', lt: '7' }, '6', undefined)
|
||||
|
||||
expect({ lt: '5', reverse: true }, '4', '4')
|
||||
expect({ lt: '5', reverse: true }, '5', undefined)
|
||||
expect({ lt: '5', reverse: true }, '6', undefined)
|
||||
|
||||
expect({ lte: '5', reverse: true }, '4', '4')
|
||||
expect({ lte: '5', reverse: true }, '5', '5')
|
||||
expect({ lte: '5', reverse: true }, '6', undefined)
|
||||
|
||||
expect({ gt: '5', reverse: true }, '4', undefined)
|
||||
expect({ gt: '5', reverse: true }, '5', undefined)
|
||||
expect({ gt: '5', reverse: true }, '6', '6')
|
||||
|
||||
expect({ gte: '5', reverse: true }, '4', undefined)
|
||||
expect({ gte: '5', reverse: true }, '5', '5')
|
||||
expect({ gte: '5', reverse: true }, '6', '6')
|
||||
|
||||
expect({ gt: '7', lt: '8' }, '7', undefined)
|
||||
expect({ gte: '7', lt: '8' }, '7', '7')
|
||||
expect({ gte: '7', lt: '8' }, '8', undefined)
|
||||
expect({ gt: '7', lte: '8' }, '8', '8')
|
||||
|
||||
function expect (range, target, expected) {
|
||||
pending++
|
||||
const ite = db[mode](range)
|
||||
|
||||
ite.seek(target)
|
||||
ite.next(function (err, item) {
|
||||
t.error(err, 'no error from next()')
|
||||
|
||||
const json = JSON.stringify(range)
|
||||
const msg = 'seek(' + target + ') on ' + json + ' yields ' + expected
|
||||
|
||||
// Either a key or value depending on mode
|
||||
t.is(item, expected, msg)
|
||||
|
||||
ite.close(function (err) {
|
||||
t.error(err, 'no error from close()')
|
||||
if (!--pending) db.close(t.end.bind(t))
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+99
@@ -0,0 +1,99 @@
|
||||
'use strict'
|
||||
|
||||
exports.snapshot = function (test, testCommon) {
|
||||
function make (run) {
|
||||
return function (t) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err, 'no open error')
|
||||
|
||||
db.put('z', 'from snapshot', function (err) {
|
||||
t.ifError(err, 'no put error')
|
||||
|
||||
// For this test it is important that we don't read eagerly.
|
||||
// NOTE: highWaterMarkBytes is not an abstract option, but
|
||||
// it is supported by classic-level and others. Also set the
|
||||
// old & equivalent leveldown highWaterMark option for compat.
|
||||
const it = db.iterator({ highWaterMarkBytes: 0, highWaterMark: 0 })
|
||||
|
||||
run(t, db, it, function end (err) {
|
||||
t.ifError(err, 'no run error')
|
||||
|
||||
it.close(function (err) {
|
||||
t.ifError(err, 'no iterator close error')
|
||||
db.close(t.end.bind(t))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
test('delete key after snapshotting', make(function (t, db, it, end) {
|
||||
db.del('z', function (err) {
|
||||
t.ifError(err, 'no del error')
|
||||
|
||||
it.next(function (err, key, value) {
|
||||
t.ifError(err, 'no next error')
|
||||
t.ok(key, 'got a key')
|
||||
t.is(key.toString(), 'z', 'correct key')
|
||||
t.is(value.toString(), 'from snapshot', 'correct value')
|
||||
|
||||
end()
|
||||
})
|
||||
})
|
||||
}))
|
||||
|
||||
test('overwrite key after snapshotting', make(function (t, db, it, end) {
|
||||
db.put('z', 'not from snapshot', function (err) {
|
||||
t.ifError(err, 'no put error')
|
||||
|
||||
it.next(function (err, key, value) {
|
||||
t.ifError(err, 'no next error')
|
||||
t.ok(key, 'got a key')
|
||||
t.is(key.toString(), 'z', 'correct key')
|
||||
t.is(value.toString(), 'from snapshot', 'correct value')
|
||||
|
||||
end()
|
||||
})
|
||||
})
|
||||
}))
|
||||
|
||||
test('add key after snapshotting that sorts first', make(function (t, db, it, end) {
|
||||
db.put('a', 'not from snapshot', function (err) {
|
||||
t.ifError(err, 'no put error')
|
||||
|
||||
it.next(function (err, key, value) {
|
||||
t.ifError(err, 'no next error')
|
||||
|
||||
t.ok(key, 'got a key')
|
||||
t.is(key.toString(), 'z', 'correct key')
|
||||
t.is(value.toString(), 'from snapshot', 'correct value')
|
||||
|
||||
end()
|
||||
})
|
||||
})
|
||||
}))
|
||||
|
||||
// NOTE: adapted from memdown
|
||||
test('delete key after snapshotting, with more entries available', async function (t) {
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
await Promise.all([db.put('a', 'A'), db.put('b', 'B'), db.put('c', 'C')])
|
||||
|
||||
const iterator = db.iterator({ gte: 'a' })
|
||||
t.same(await iterator.next(), ['a', 'A'])
|
||||
|
||||
await db.del('b')
|
||||
t.same(await iterator.next(), ['b', 'B'])
|
||||
t.same(await iterator.next(), ['c', 'C'])
|
||||
|
||||
await iterator.close()
|
||||
return db.close()
|
||||
})
|
||||
}
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.snapshot(test, testCommon)
|
||||
}
|
||||
Generated
Vendored
+548
@@ -0,0 +1,548 @@
|
||||
'use strict'
|
||||
|
||||
const { Buffer } = require('buffer')
|
||||
const identity = (v) => v
|
||||
|
||||
let db
|
||||
|
||||
exports.setUp = function (test, testCommon) {
|
||||
test('setUp db', function (t) {
|
||||
db = testCommon.factory()
|
||||
db.open(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
exports.args = function (test, testCommon) {
|
||||
for (const mode of ['iterator', 'keys', 'values']) {
|
||||
test(`${mode}() has db reference`, async function (t) {
|
||||
const it = db[mode]()
|
||||
|
||||
// May return iterator of an underlying db, that's okay.
|
||||
t.ok(it.db === db || it.db === (db.db || db._db || db))
|
||||
|
||||
await it.close()
|
||||
})
|
||||
|
||||
test(`${mode}() has limit and count properties`, async function (t) {
|
||||
const iterators = [db[mode]()]
|
||||
t.is(iterators[0].limit, Infinity, 'defaults to infinite')
|
||||
|
||||
for (const limit of [-1, 0, 1, Infinity]) {
|
||||
const it = db[mode]({ limit })
|
||||
iterators.push(it)
|
||||
t.is(it.limit, limit === -1 ? Infinity : limit, 'has limit property')
|
||||
}
|
||||
|
||||
t.ok(iterators.every(it => it.count === 0), 'has count property')
|
||||
await Promise.all(iterators.map(it => it.close()))
|
||||
})
|
||||
|
||||
test(`${mode}().nextv() yields error if size is invalid`, async function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const it = db[mode]()
|
||||
|
||||
for (const args of [[], [NaN], ['1'], [2.5]]) {
|
||||
try {
|
||||
await it.nextv(...args)
|
||||
} catch (err) {
|
||||
t.is(err.message, "The first argument 'size' must be an integer")
|
||||
}
|
||||
}
|
||||
|
||||
await it.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.sequence = function (test, testCommon) {
|
||||
for (const mode of ['iterator', 'keys', 'values']) {
|
||||
test(`${mode}().close() is idempotent`, function (t) {
|
||||
const iterator = db[mode]()
|
||||
|
||||
iterator.close(function () {
|
||||
let async = false
|
||||
|
||||
iterator.close(function () {
|
||||
t.ok(async, 'callback is asynchronous')
|
||||
t.end()
|
||||
})
|
||||
|
||||
async = true
|
||||
})
|
||||
})
|
||||
|
||||
for (const method of ['next', 'nextv', 'all']) {
|
||||
const requiredArgs = method === 'nextv' ? [1] : []
|
||||
|
||||
test(`${mode}().${method}() after close() yields error`, function (t) {
|
||||
const iterator = db[mode]()
|
||||
iterator.close(function (err) {
|
||||
t.error(err)
|
||||
|
||||
let async = false
|
||||
|
||||
iterator[method](...requiredArgs, function (err2) {
|
||||
t.ok(err2, 'returned error')
|
||||
t.is(err2.code, 'LEVEL_ITERATOR_NOT_OPEN', 'correct message')
|
||||
t.ok(async, 'callback is asynchronous')
|
||||
t.end()
|
||||
})
|
||||
|
||||
async = true
|
||||
})
|
||||
})
|
||||
|
||||
for (const otherMethod of ['next', 'nextv', 'all']) {
|
||||
const otherRequiredArgs = otherMethod === 'nextv' ? [1] : []
|
||||
|
||||
test(`${mode}().${method}() while busy with ${otherMethod}() yields error`, function (t) {
|
||||
const iterator = db[mode]()
|
||||
iterator[otherMethod](...otherRequiredArgs, function (err) {
|
||||
t.error(err)
|
||||
iterator.close(function (err) {
|
||||
t.error(err)
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
|
||||
let async = false
|
||||
|
||||
iterator[method](...requiredArgs, function (err) {
|
||||
t.ok(err, 'returned error')
|
||||
t.is(err.code, 'LEVEL_ITERATOR_BUSY')
|
||||
t.ok(async, 'callback is asynchronous')
|
||||
})
|
||||
|
||||
async = true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const deferred of [false, true]) {
|
||||
for (const mode of ['iterator', 'keys', 'values']) {
|
||||
for (const method of ['next', 'nextv', 'all']) {
|
||||
const requiredArgs = method === 'nextv' ? [10] : []
|
||||
|
||||
// NOTE: adapted from leveldown
|
||||
test(`${mode}().${method}() after db.close() yields error (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const db = testCommon.factory()
|
||||
if (!deferred) await db.open()
|
||||
|
||||
await db.put('a', 'a')
|
||||
await db.put('b', 'b')
|
||||
|
||||
const it = db[mode]()
|
||||
|
||||
// The first call *should* succeed, because it was scheduled before close(). However, success
|
||||
// is not a must. Because nextv() and all() fallback to next*(), they're allowed to fail. An
|
||||
// implementation can also choose to abort any pending call on close.
|
||||
let promise = it[method](...requiredArgs).then(() => {
|
||||
t.pass('Optionally succeeded')
|
||||
}).catch((err) => {
|
||||
t.is(err.code, 'LEVEL_ITERATOR_NOT_OPEN')
|
||||
})
|
||||
|
||||
// The second call *must* fail, because it was scheduled after close()
|
||||
promise = promise.then(() => {
|
||||
return it[method](...requiredArgs).then(() => {
|
||||
t.fail('Expected an error')
|
||||
}).catch((err) => {
|
||||
t.is(err.code, 'LEVEL_ITERATOR_NOT_OPEN')
|
||||
})
|
||||
})
|
||||
|
||||
return Promise.all([db.close(), promise])
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.iterator = function (test, testCommon) {
|
||||
test('test simple iterator()', function (t) {
|
||||
const data = [
|
||||
{ type: 'put', key: 'foobatch1', value: 'bar1' },
|
||||
{ type: 'put', key: 'foobatch2', value: 'bar2' },
|
||||
{ type: 'put', key: 'foobatch3', value: 'bar3' }
|
||||
]
|
||||
let idx = 0
|
||||
|
||||
db.batch(data, function (err) {
|
||||
t.error(err)
|
||||
const iterator = db.iterator()
|
||||
const fn = function (err, key, value) {
|
||||
t.error(err)
|
||||
if (key && value) {
|
||||
t.is(key, data[idx].key, 'correct key')
|
||||
t.is(value, data[idx].value, 'correct value')
|
||||
db.nextTick(next)
|
||||
idx++
|
||||
} else { // end
|
||||
t.ok(err == null, 'err argument is nullish')
|
||||
t.ok(typeof key === 'undefined', 'key argument is undefined')
|
||||
t.ok(typeof value === 'undefined', 'value argument is undefined')
|
||||
t.is(idx, data.length, 'correct number of entries')
|
||||
iterator.close(function () {
|
||||
t.end()
|
||||
})
|
||||
}
|
||||
}
|
||||
const next = function () {
|
||||
iterator.next(fn)
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
})
|
||||
|
||||
// NOTE: adapted from leveldown
|
||||
test('key-only iterator', function (t) {
|
||||
const it = db.iterator({ values: false })
|
||||
|
||||
it.next(function (err, key, value) {
|
||||
t.ifError(err, 'no next() error')
|
||||
t.is(key, 'foobatch1')
|
||||
t.is(value, undefined)
|
||||
it.close(t.end.bind(t))
|
||||
})
|
||||
})
|
||||
|
||||
// NOTE: adapted from leveldown
|
||||
test('value-only iterator', function (t) {
|
||||
const it = db.iterator({ keys: false })
|
||||
|
||||
it.next(function (err, key, value) {
|
||||
t.ifError(err, 'no next() error')
|
||||
t.is(key, undefined)
|
||||
t.is(value, 'bar1')
|
||||
it.close(t.end.bind(t))
|
||||
})
|
||||
})
|
||||
|
||||
test('db.keys().next()', function (t) {
|
||||
const it = db.keys()
|
||||
|
||||
it.next(function (err, key) {
|
||||
t.ifError(err, 'no next() error')
|
||||
t.is(key, 'foobatch1')
|
||||
it.close(t.end.bind(t))
|
||||
})
|
||||
})
|
||||
|
||||
test('db.values().next()', function (t) {
|
||||
const it = db.values()
|
||||
|
||||
it.next(function (err, value) {
|
||||
t.ifError(err, 'no next() error')
|
||||
t.is(value, 'bar1')
|
||||
it.close(t.end.bind(t))
|
||||
})
|
||||
})
|
||||
|
||||
for (const mode of ['iterator', 'keys', 'values']) {
|
||||
const mapEntry = e => mode === 'iterator' ? e : mode === 'keys' ? e[0] : e[1]
|
||||
|
||||
test(`${mode}().nextv()`, async function (t) {
|
||||
const it = db[mode]()
|
||||
|
||||
t.same(await it.nextv(1), [['foobatch1', 'bar1']].map(mapEntry))
|
||||
t.same(await it.nextv(2, {}), [['foobatch2', 'bar2'], ['foobatch3', 'bar3']].map(mapEntry))
|
||||
t.same(await it.nextv(2), [])
|
||||
|
||||
await it.close()
|
||||
})
|
||||
|
||||
test(`${mode}().nextv() in reverse`, async function (t) {
|
||||
const it = db[mode]({ reverse: true })
|
||||
|
||||
t.same(await it.nextv(1), [['foobatch3', 'bar3']].map(mapEntry))
|
||||
t.same(await it.nextv(2, {}), [['foobatch2', 'bar2'], ['foobatch1', 'bar1']].map(mapEntry))
|
||||
t.same(await it.nextv(2), [])
|
||||
|
||||
await it.close()
|
||||
})
|
||||
|
||||
test(`${mode}().nextv() has soft minimum of 1`, async function (t) {
|
||||
const it = db[mode]()
|
||||
|
||||
t.same(await it.nextv(0), [['foobatch1', 'bar1']].map(mapEntry))
|
||||
t.same(await it.nextv(0), [['foobatch2', 'bar2']].map(mapEntry))
|
||||
t.same(await it.nextv(0, {}), [['foobatch3', 'bar3']].map(mapEntry))
|
||||
t.same(await it.nextv(0), [])
|
||||
|
||||
await it.close()
|
||||
})
|
||||
|
||||
test(`${mode}().nextv() requesting more than available`, async function (t) {
|
||||
const it = db[mode]()
|
||||
|
||||
t.same(await it.nextv(10), [
|
||||
['foobatch1', 'bar1'],
|
||||
['foobatch2', 'bar2'],
|
||||
['foobatch3', 'bar3']
|
||||
].map(mapEntry))
|
||||
t.same(await it.nextv(10), [])
|
||||
|
||||
await it.close()
|
||||
})
|
||||
|
||||
test(`${mode}().nextv() honors limit`, async function (t) {
|
||||
const it = db[mode]({ limit: 2 })
|
||||
|
||||
t.same(await it.nextv(10), [['foobatch1', 'bar1'], ['foobatch2', 'bar2']].map(mapEntry))
|
||||
t.same(await it.nextv(10), [])
|
||||
|
||||
await it.close()
|
||||
})
|
||||
|
||||
test(`${mode}().nextv() honors limit in reverse`, async function (t) {
|
||||
const it = db[mode]({ limit: 2, reverse: true })
|
||||
|
||||
t.same(await it.nextv(10), [['foobatch3', 'bar3'], ['foobatch2', 'bar2']].map(mapEntry))
|
||||
t.same(await it.nextv(10), [])
|
||||
|
||||
await it.close()
|
||||
})
|
||||
|
||||
test(`${mode}().all()`, async function (t) {
|
||||
t.same(await db[mode]().all(), [
|
||||
['foobatch1', 'bar1'],
|
||||
['foobatch2', 'bar2'],
|
||||
['foobatch3', 'bar3']
|
||||
].map(mapEntry))
|
||||
|
||||
t.same(await db[mode]().all({}), [
|
||||
['foobatch1', 'bar1'],
|
||||
['foobatch2', 'bar2'],
|
||||
['foobatch3', 'bar3']
|
||||
].map(mapEntry))
|
||||
})
|
||||
|
||||
test(`${mode}().all() in reverse`, async function (t) {
|
||||
t.same(await db[mode]({ reverse: true }).all(), [
|
||||
['foobatch3', 'bar3'],
|
||||
['foobatch2', 'bar2'],
|
||||
['foobatch1', 'bar1']
|
||||
].map(mapEntry))
|
||||
})
|
||||
|
||||
test(`${mode}().all() honors limit`, async function (t) {
|
||||
t.same(await db[mode]({ limit: 2 }).all(), [
|
||||
['foobatch1', 'bar1'],
|
||||
['foobatch2', 'bar2']
|
||||
].map(mapEntry))
|
||||
|
||||
const it = db[mode]({ limit: 2 })
|
||||
|
||||
t.same(await it.next(), mapEntry(['foobatch1', 'bar1']))
|
||||
t.same(await it.all(), [['foobatch2', 'bar2']].map(mapEntry))
|
||||
})
|
||||
|
||||
test(`${mode}().all() honors limit in reverse`, async function (t) {
|
||||
t.same(await db[mode]({ limit: 2, reverse: true }).all(), [
|
||||
['foobatch3', 'bar3'],
|
||||
['foobatch2', 'bar2']
|
||||
].map(mapEntry))
|
||||
|
||||
const it = db[mode]({ limit: 2, reverse: true })
|
||||
|
||||
t.same(await it.next(), mapEntry(['foobatch3', 'bar3']))
|
||||
t.same(await it.all(), [['foobatch2', 'bar2']].map(mapEntry))
|
||||
})
|
||||
}
|
||||
|
||||
// NOTE: adapted from memdown
|
||||
test('iterator() sorts lexicographically', async function (t) {
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
|
||||
// Write in unsorted order with multiple operations
|
||||
await db.put('f', 'F')
|
||||
await db.put('a', 'A')
|
||||
await db.put('~', '~')
|
||||
await db.put('e', 'E')
|
||||
await db.put('🐄', '🐄')
|
||||
await db.batch([
|
||||
{ type: 'put', key: 'd', value: 'D' },
|
||||
{ type: 'put', key: 'b', value: 'B' },
|
||||
{ type: 'put', key: 'ff', value: 'FF' },
|
||||
{ type: 'put', key: 'a🐄', value: 'A🐄' }
|
||||
])
|
||||
await db.batch([
|
||||
{ type: 'put', key: '', value: 'empty' },
|
||||
{ type: 'put', key: '2', value: '2' },
|
||||
{ type: 'put', key: '12', value: '12' },
|
||||
{ type: 'put', key: '\t', value: '\t' }
|
||||
])
|
||||
|
||||
t.same(await db.iterator().all(), [
|
||||
['', 'empty'],
|
||||
['\t', '\t'],
|
||||
['12', '12'],
|
||||
['2', '2'],
|
||||
['a', 'A'],
|
||||
['a🐄', 'A🐄'],
|
||||
['b', 'B'],
|
||||
['d', 'D'],
|
||||
['e', 'E'],
|
||||
['f', 'F'],
|
||||
['ff', 'FF'],
|
||||
['~', '~'],
|
||||
['🐄', '🐄']
|
||||
])
|
||||
|
||||
t.same(await db.iterator({ lte: '' }).all(), [
|
||||
['', 'empty']
|
||||
])
|
||||
|
||||
return db.close()
|
||||
})
|
||||
|
||||
for (const keyEncoding of ['buffer', 'view']) {
|
||||
if (!testCommon.supports.encodings[keyEncoding]) continue
|
||||
|
||||
test(`test iterator() has byte order (${keyEncoding} encoding)`, function (t) {
|
||||
const db = testCommon.factory({ keyEncoding })
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err, 'no open() error')
|
||||
|
||||
const ctor = keyEncoding === 'buffer' ? Buffer : Uint8Array
|
||||
const keys = [2, 11, 1].map(b => ctor.from([b]))
|
||||
|
||||
db.batch(keys.map((key) => ({ type: 'put', key, value: 'x' })), function (err) {
|
||||
t.ifError(err, 'no batch() error')
|
||||
|
||||
db.keys().all(function (err, keys) {
|
||||
t.ifError(err, 'no all() error')
|
||||
t.same(keys.map(k => k[0]), [1, 2, 11], 'order is ok')
|
||||
|
||||
db.iterator().all(function (err, entries) {
|
||||
t.ifError(err, 'no all() error')
|
||||
t.same(entries.map(e => e[0][0]), [1, 2, 11], 'order is ok')
|
||||
|
||||
db.close(t.end.bind(t))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// NOTE: adapted from memdown and level-js
|
||||
test(`test iterator() with byte range (${keyEncoding} encoding)`, async function (t) {
|
||||
const db = testCommon.factory({ keyEncoding })
|
||||
await db.open()
|
||||
|
||||
await db.put(Uint8Array.from([0x0]), '0')
|
||||
await db.put(Uint8Array.from([128]), '128')
|
||||
await db.put(Uint8Array.from([160]), '160')
|
||||
await db.put(Uint8Array.from([192]), '192')
|
||||
|
||||
const collect = async (range) => {
|
||||
const entries = await db.iterator(range).all()
|
||||
t.ok(entries.every(e => e[0] instanceof Uint8Array)) // True for both encodings
|
||||
t.ok(entries.every(e => e[1] === String(e[0][0])))
|
||||
return entries.map(e => e[0][0])
|
||||
}
|
||||
|
||||
t.same(await collect({ gt: Uint8Array.from([255]) }), [])
|
||||
t.same(await collect({ gt: Uint8Array.from([192]) }), [])
|
||||
t.same(await collect({ gt: Uint8Array.from([160]) }), [192])
|
||||
t.same(await collect({ gt: Uint8Array.from([128]) }), [160, 192])
|
||||
t.same(await collect({ gt: Uint8Array.from([0x0]) }), [128, 160, 192])
|
||||
t.same(await collect({ gt: Uint8Array.from([]) }), [0x0, 128, 160, 192])
|
||||
|
||||
t.same(await collect({ lt: Uint8Array.from([255]) }), [0x0, 128, 160, 192])
|
||||
t.same(await collect({ lt: Uint8Array.from([192]) }), [0x0, 128, 160])
|
||||
t.same(await collect({ lt: Uint8Array.from([160]) }), [0x0, 128])
|
||||
t.same(await collect({ lt: Uint8Array.from([128]) }), [0x0])
|
||||
t.same(await collect({ lt: Uint8Array.from([0x0]) }), [])
|
||||
t.same(await collect({ lt: Uint8Array.from([]) }), [])
|
||||
|
||||
t.same(await collect({ gte: Uint8Array.from([255]) }), [])
|
||||
t.same(await collect({ gte: Uint8Array.from([192]) }), [192])
|
||||
t.same(await collect({ gte: Uint8Array.from([160]) }), [160, 192])
|
||||
t.same(await collect({ gte: Uint8Array.from([128]) }), [128, 160, 192])
|
||||
t.same(await collect({ gte: Uint8Array.from([0x0]) }), [0x0, 128, 160, 192])
|
||||
t.same(await collect({ gte: Uint8Array.from([]) }), [0x0, 128, 160, 192])
|
||||
|
||||
t.same(await collect({ lte: Uint8Array.from([255]) }), [0x0, 128, 160, 192])
|
||||
t.same(await collect({ lte: Uint8Array.from([192]) }), [0x0, 128, 160, 192])
|
||||
t.same(await collect({ lte: Uint8Array.from([160]) }), [0x0, 128, 160])
|
||||
t.same(await collect({ lte: Uint8Array.from([128]) }), [0x0, 128])
|
||||
t.same(await collect({ lte: Uint8Array.from([0x0]) }), [0x0])
|
||||
t.same(await collect({ lte: Uint8Array.from([]) }), [])
|
||||
|
||||
return db.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.decode = function (test, testCommon) {
|
||||
for (const deferred of [false, true]) {
|
||||
for (const mode of ['iterator', 'keys', 'values']) {
|
||||
for (const method of ['next', 'nextv', 'all']) {
|
||||
const requiredArgs = method === 'nextv' ? [1] : []
|
||||
|
||||
for (const encodingOption of ['keyEncoding', 'valueEncoding']) {
|
||||
if (mode === 'keys' && encodingOption === 'valueEncoding') continue
|
||||
if (mode === 'values' && encodingOption === 'keyEncoding') continue
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`${mode}().${method}() catches decoding error from ${encodingOption} (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const encoding = {
|
||||
format: 'utf8',
|
||||
decode: function (x) {
|
||||
t.is(x, encodingOption === 'keyEncoding' ? 'testKey' : 'testValue')
|
||||
throw new Error('from encoding')
|
||||
},
|
||||
encode: identity
|
||||
}
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.put('testKey', 'testValue')
|
||||
|
||||
if (deferred) {
|
||||
await db.close()
|
||||
db.open(t.ifError.bind(t))
|
||||
} else {
|
||||
t.pass('non-deferred')
|
||||
}
|
||||
|
||||
const it = db[mode]({ [encodingOption]: encoding })
|
||||
|
||||
try {
|
||||
await it[method](...requiredArgs)
|
||||
} catch (err) {
|
||||
t.is(err.code, 'LEVEL_DECODE_ERROR')
|
||||
t.is(err.cause && err.cause.message, 'from encoding')
|
||||
}
|
||||
|
||||
return db.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.tearDown = function (test, testCommon) {
|
||||
test('tearDown', function (t) {
|
||||
db.close(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.setUp(test, testCommon)
|
||||
exports.args(test, testCommon)
|
||||
exports.sequence(test, testCommon)
|
||||
exports.iterator(test, testCommon)
|
||||
exports.decode(test, testCommon)
|
||||
exports.tearDown(test, testCommon)
|
||||
}
|
||||
Generated
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
'use strict'
|
||||
|
||||
const suite = require('level-supports/test')
|
||||
|
||||
module.exports = function (test, testCommon) {
|
||||
suite(test, testCommon)
|
||||
|
||||
test('manifest has expected properties', function (t) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
t.is(db.supports.status, true)
|
||||
t.is(db.supports.promises, true)
|
||||
t.is(db.supports.clear, true)
|
||||
t.is(db.supports.getMany, true)
|
||||
|
||||
testCommon.supports = db.supports
|
||||
t.ok(testCommon.supports, 'can be accessed via testCommon')
|
||||
|
||||
t.ok(db.supports.encodings.utf8, 'supports utf8')
|
||||
t.ok(db.supports.encodings.json, 'supports json')
|
||||
|
||||
db.close(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
Generated
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
'use strict'
|
||||
|
||||
exports.createIfMissing = function (test, testCommon) {
|
||||
test('test database open createIfMissing:false', function (t) {
|
||||
const db = testCommon.factory()
|
||||
let async = false
|
||||
|
||||
db.open({ createIfMissing: false }, function (err) {
|
||||
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
t.ok(err && /does not exist/.test(err.cause && err.cause.message), 'error is about dir not existing')
|
||||
t.ok(async, 'callback is asynchronous')
|
||||
t.end()
|
||||
})
|
||||
|
||||
async = true
|
||||
})
|
||||
|
||||
test('test database open createIfMissing:false via constructor', function (t) {
|
||||
const db = testCommon.factory({ createIfMissing: false })
|
||||
let async = false
|
||||
|
||||
db.open(function (err) {
|
||||
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
t.ok(err && /does not exist/.test(err.cause && err.cause.message), 'error is about dir not existing')
|
||||
t.ok(async, 'callback is asynchronous')
|
||||
t.end()
|
||||
})
|
||||
|
||||
async = true
|
||||
})
|
||||
}
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.createIfMissing(test, testCommon)
|
||||
}
|
||||
Generated
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
'use strict'
|
||||
|
||||
exports.errorIfExists = function (test, testCommon) {
|
||||
test('test database open errorIfExists:true', function (t) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open(function (err) {
|
||||
t.error(err)
|
||||
db.close(function (err) {
|
||||
t.error(err)
|
||||
|
||||
let async = false
|
||||
|
||||
db.open({ createIfMissing: false, errorIfExists: true }, function (err) {
|
||||
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
t.ok(err && /exists/.test(err.cause && err.cause.message), 'error is about already existing')
|
||||
t.ok(async, 'callback is asynchronous')
|
||||
t.end()
|
||||
})
|
||||
|
||||
async = true
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.errorIfExists(test, testCommon)
|
||||
}
|
||||
Generated
Vendored
+297
@@ -0,0 +1,297 @@
|
||||
'use strict'
|
||||
|
||||
const { assertAsync } = require('./util')
|
||||
|
||||
exports.open = function (test, testCommon) {
|
||||
test('test database open, no options', function (t) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
t.is(db.status, 'opening')
|
||||
|
||||
// default createIfMissing=true, errorIfExists=false
|
||||
db.open(function (err) {
|
||||
t.error(err)
|
||||
t.is(db.status, 'open')
|
||||
|
||||
db.close(function () {
|
||||
t.is(db.status, 'closed')
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
|
||||
t.is(db.status, 'opening')
|
||||
})
|
||||
|
||||
test('test database open, no options, with promise', function (t) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
t.is(db.status, 'opening')
|
||||
|
||||
// default createIfMissing=true, errorIfExists=false
|
||||
db.open().then(function () {
|
||||
t.is(db.status, 'open')
|
||||
db.close(t.end.bind(t))
|
||||
}).catch(t.fail.bind(t))
|
||||
|
||||
t.is(db.status, 'opening')
|
||||
})
|
||||
|
||||
test('test database open, options and callback', function (t) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
// default createIfMissing=true, errorIfExists=false
|
||||
db.open({}, function (err) {
|
||||
t.error(err)
|
||||
db.close(function () {
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('test database open, options with promise', function (t) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
// default createIfMissing=true, errorIfExists=false
|
||||
db.open({}).then(function () {
|
||||
db.close(t.end.bind(t))
|
||||
})
|
||||
})
|
||||
|
||||
test('test database open, close and open', function (t) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open(function (err) {
|
||||
t.error(err)
|
||||
|
||||
db.close(function (err) {
|
||||
t.error(err)
|
||||
t.is(db.status, 'closed')
|
||||
|
||||
db.open(function (err) {
|
||||
t.error(err)
|
||||
t.is(db.status, 'open')
|
||||
|
||||
db.close(t.end.bind(t))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('test database open, close and open with promise', function (t) {
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open().then(function () {
|
||||
db.close(function (err) {
|
||||
t.error(err)
|
||||
db.open().then(function () {
|
||||
db.close(function () {
|
||||
t.end()
|
||||
})
|
||||
}).catch(t.fail.bind(t))
|
||||
})
|
||||
}).catch(t.fail.bind(t))
|
||||
})
|
||||
|
||||
test('test database open and close in same tick', assertAsync.ctx(function (t) {
|
||||
t.plan(10)
|
||||
|
||||
const db = testCommon.factory()
|
||||
const order = []
|
||||
|
||||
db.open(assertAsync(function (err) {
|
||||
order.push('A')
|
||||
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN', 'got open() error')
|
||||
t.is(db.status, 'closed', 'is closed')
|
||||
}))
|
||||
|
||||
t.is(db.status, 'opening', 'is opening')
|
||||
|
||||
// This wins from the open() call
|
||||
db.close(assertAsync(function (err) {
|
||||
order.push('B')
|
||||
t.same(order, ['A', 'closed event', 'B'], 'order is correct')
|
||||
t.ifError(err, 'no close() error')
|
||||
t.is(db.status, 'closed', 'is closed')
|
||||
}))
|
||||
|
||||
// But open() is still in control
|
||||
t.is(db.status, 'opening', 'is still opening')
|
||||
|
||||
// Should not emit 'open', because close() wins
|
||||
db.on('open', t.fail.bind(t))
|
||||
db.on('closed', assertAsync(() => { order.push('closed event') }))
|
||||
}))
|
||||
|
||||
test('test database open, close and open in same tick', assertAsync.ctx(function (t) {
|
||||
t.plan(14)
|
||||
|
||||
const db = testCommon.factory()
|
||||
const order = []
|
||||
|
||||
db.open(assertAsync(function (err) {
|
||||
order.push('A')
|
||||
t.ifError(err, 'no open() error (1)')
|
||||
t.is(db.status, 'open', 'is open')
|
||||
}))
|
||||
|
||||
t.is(db.status, 'opening', 'is opening')
|
||||
|
||||
// This wins from the open() call
|
||||
db.close(assertAsync(function (err) {
|
||||
order.push('B')
|
||||
t.is(err && err.code, 'LEVEL_DATABASE_NOT_CLOSED')
|
||||
t.is(db.status, 'open', 'is open')
|
||||
}))
|
||||
|
||||
t.is(db.status, 'opening', 'is still opening')
|
||||
|
||||
// This wins from the close() call
|
||||
db.open(assertAsync(function (err) {
|
||||
order.push('C')
|
||||
t.same(order, ['A', 'B', 'open event', 'C'], 'callback order is the same as call order')
|
||||
t.ifError(err, 'no open() error (2)')
|
||||
t.is(db.status, 'open', 'is open')
|
||||
}))
|
||||
|
||||
// Should not emit 'closed', because open() wins
|
||||
db.on('closed', t.fail.bind(t))
|
||||
db.on('open', assertAsync(() => { order.push('open event') }))
|
||||
|
||||
t.is(db.status, 'opening', 'is still opening')
|
||||
}))
|
||||
|
||||
test('test database open if already open (sequential)', function (t) {
|
||||
t.plan(7)
|
||||
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open(assertAsync(function (err) {
|
||||
t.ifError(err, 'no open() error (1)')
|
||||
t.is(db.status, 'open', 'is open')
|
||||
|
||||
db.open(assertAsync(function (err) {
|
||||
t.ifError(err, 'no open() error (2)')
|
||||
t.is(db.status, 'open', 'is open')
|
||||
}))
|
||||
|
||||
t.is(db.status, 'open', 'not reopening')
|
||||
db.on('open', t.fail.bind(t))
|
||||
assertAsync.end(t)
|
||||
}))
|
||||
|
||||
assertAsync.end(t)
|
||||
})
|
||||
|
||||
test('test database open if already opening (parallel)', assertAsync.ctx(function (t) {
|
||||
t.plan(7)
|
||||
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open(assertAsync(function (err) {
|
||||
t.ifError(err, 'no open() error (1)')
|
||||
t.is(db.status, 'open')
|
||||
}))
|
||||
|
||||
db.open(assertAsync(function (err) {
|
||||
t.ifError(err, 'no open() error (2)')
|
||||
t.is(db.status, 'open')
|
||||
db.close(t.end.bind(t))
|
||||
}))
|
||||
|
||||
t.is(db.status, 'opening')
|
||||
}))
|
||||
|
||||
test('test database close if already closed', function (t) {
|
||||
t.plan(8)
|
||||
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err, 'no open() error')
|
||||
|
||||
db.close(assertAsync(function (err) {
|
||||
t.ifError(err, 'no close() error (1)')
|
||||
t.is(db.status, 'closed', 'is closed')
|
||||
|
||||
db.close(assertAsync(function (err) {
|
||||
t.ifError(err, 'no close() error (2)')
|
||||
t.is(db.status, 'closed', 'is closed')
|
||||
}))
|
||||
|
||||
t.is(db.status, 'closed', 'is closed', 'not reclosing')
|
||||
db.on('closed', t.fail.bind(t))
|
||||
assertAsync.end(t)
|
||||
}))
|
||||
|
||||
assertAsync.end(t)
|
||||
})
|
||||
})
|
||||
|
||||
test('test database close if new', assertAsync.ctx(function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const db = testCommon.factory()
|
||||
const expectedStatus = db.supports.deferredOpen ? 'opening' : 'closed'
|
||||
|
||||
t.is(db.status, expectedStatus, 'status ok')
|
||||
|
||||
db.close(assertAsync(function (err) {
|
||||
t.ifError(err, 'no close() error')
|
||||
t.is(db.status, 'closed', 'status ok')
|
||||
}))
|
||||
|
||||
t.is(db.status, expectedStatus, 'status unchanged')
|
||||
|
||||
if (!db.supports.deferredOpen) {
|
||||
db.on('closed', t.fail.bind(t, 'should not emit closed'))
|
||||
}
|
||||
}))
|
||||
|
||||
test('test database close on open event', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const db = testCommon.factory()
|
||||
const order = []
|
||||
|
||||
db.open(function (err) {
|
||||
order.push('A')
|
||||
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN', 'got open() error')
|
||||
t.is(db.status, 'closed', 'is closed')
|
||||
})
|
||||
|
||||
db.on('open', function () {
|
||||
// This wins from the (still in progress) open() call
|
||||
db.close(function (err) {
|
||||
order.push('B')
|
||||
t.same(order, ['A', 'closed event', 'B'], 'order is correct')
|
||||
t.ifError(err, 'no close() error')
|
||||
t.is(db.status, 'closed', 'is closed')
|
||||
})
|
||||
})
|
||||
|
||||
db.on('closed', () => { order.push('closed event') })
|
||||
})
|
||||
|
||||
test('test passive open()', async function (t) {
|
||||
t.plan(1)
|
||||
const db = testCommon.factory()
|
||||
await db.open({ passive: true }) // OK, already opening
|
||||
await db.close()
|
||||
await db.open({ passive: true }).catch(err => {
|
||||
t.is(err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
})
|
||||
await db.open()
|
||||
await db.open({ passive: true }) // OK, already open
|
||||
return db.close()
|
||||
})
|
||||
|
||||
test('test passive open(): ignored if set in constructor options', async function (t) {
|
||||
const db = testCommon.factory({ passive: true })
|
||||
await new Promise((resolve) => db.once('open', resolve))
|
||||
return db.close()
|
||||
})
|
||||
}
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.open(test, testCommon)
|
||||
}
|
||||
Generated
Vendored
+113
@@ -0,0 +1,113 @@
|
||||
'use strict'
|
||||
|
||||
const { verifyNotFoundError } = require('./util')
|
||||
const { Buffer } = require('buffer')
|
||||
|
||||
let db
|
||||
|
||||
function makeTest (test, type, key, value, expectedResult) {
|
||||
const hasExpectedResult = arguments.length === 5
|
||||
test('test put()/get()/del() with ' + type, function (t) {
|
||||
db.put(key, value, function (err) {
|
||||
t.error(err)
|
||||
db.get(key, function (err, _value) {
|
||||
t.error(err, 'no error, has key/value for `' + type + '`')
|
||||
|
||||
let result = _value
|
||||
|
||||
if (hasExpectedResult) {
|
||||
t.equal(result.toString(), expectedResult)
|
||||
} else {
|
||||
if (result != null) { result = _value.toString() }
|
||||
if (value != null) { value = value.toString() }
|
||||
t.equals(result, value)
|
||||
}
|
||||
db.del(key, function (err) {
|
||||
t.error(err, 'no error, deleted key/value for `' + type + '`')
|
||||
|
||||
let async = false
|
||||
|
||||
db.get(key, function (err, value) {
|
||||
t.ok(err, 'entry properly deleted')
|
||||
t.ok(verifyNotFoundError(err), 'correct error')
|
||||
t.is(value, undefined, 'value is undefined')
|
||||
t.ok(async, 'callback is asynchronous')
|
||||
t.end()
|
||||
})
|
||||
|
||||
async = true
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.setUp = function (test, testCommon) {
|
||||
test('setUp db', function (t) {
|
||||
db = testCommon.factory()
|
||||
db.open(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
exports.nonErrorKeys = function (test, testCommon) {
|
||||
// valid falsey keys
|
||||
makeTest(test, '`0` key', 0, 'foo 0')
|
||||
makeTest(test, 'empty string key', 0, 'foo')
|
||||
|
||||
// standard String key
|
||||
makeTest(
|
||||
test
|
||||
, 'long String key'
|
||||
, 'some long string that I\'m using as a key for this unit test, cross your fingers human, we\'re going in!'
|
||||
, 'foo'
|
||||
)
|
||||
|
||||
if (testCommon.supports.encodings.buffer) {
|
||||
makeTest(test, 'Buffer key', Buffer.from('0080c0ff', 'hex'), 'foo')
|
||||
makeTest(test, 'empty Buffer key', Buffer.alloc(0), 'foo')
|
||||
}
|
||||
|
||||
// non-empty Array as a value
|
||||
makeTest(test, 'Array value', 'foo', [1, 2, 3, 4])
|
||||
}
|
||||
|
||||
exports.nonErrorValues = function (test, testCommon) {
|
||||
// valid falsey values
|
||||
makeTest(test, '`false` value', 'foo false', false)
|
||||
makeTest(test, '`0` value', 'foo 0', 0)
|
||||
makeTest(test, '`NaN` value', 'foo NaN', NaN)
|
||||
|
||||
// all of the following result in an empty-string value:
|
||||
makeTest(test, 'empty String value', 'foo', '', '')
|
||||
makeTest(test, 'empty Buffer value', 'foo', Buffer.alloc(0), '')
|
||||
makeTest(test, 'empty Array value', 'foo', [], '')
|
||||
|
||||
// String value
|
||||
makeTest(
|
||||
test
|
||||
, 'long String value'
|
||||
, 'foo'
|
||||
, 'some long string that I\'m using as a key for this unit test, cross your fingers human, we\'re going in!'
|
||||
)
|
||||
|
||||
// Buffer value
|
||||
if (testCommon.supports.encodings.buffer) {
|
||||
makeTest(test, 'Buffer value', 'foo', Buffer.from('0080c0ff', 'hex'))
|
||||
}
|
||||
|
||||
// non-empty Array as a key
|
||||
makeTest(test, 'Array key', [1, 2, 3, 4], 'foo')
|
||||
}
|
||||
|
||||
exports.tearDown = function (test, testCommon) {
|
||||
test('tearDown', function (t) {
|
||||
db.close(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.setUp(test, testCommon)
|
||||
exports.nonErrorKeys(test, testCommon)
|
||||
exports.nonErrorValues(test, testCommon)
|
||||
exports.tearDown(test, testCommon)
|
||||
}
|
||||
Generated
Vendored
+131
@@ -0,0 +1,131 @@
|
||||
'use strict'
|
||||
|
||||
const { assertAsync, illegalKeys, illegalValues } = require('./util')
|
||||
|
||||
let db
|
||||
|
||||
exports.setUp = function (test, testCommon) {
|
||||
test('setUp db', function (t) {
|
||||
db = testCommon.factory()
|
||||
db.open(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
exports.args = function (test, testCommon) {
|
||||
test('test put() with illegal keys', assertAsync.ctx(function (t) {
|
||||
t.plan(illegalKeys.length * 5)
|
||||
|
||||
for (const { name, key } of illegalKeys) {
|
||||
db.put(key, 'value', assertAsync(function (err) {
|
||||
t.ok(err instanceof Error, name + ' - is Error (callback)')
|
||||
t.is(err && err.code, 'LEVEL_INVALID_KEY', name + ' - correct error code (callback)')
|
||||
}))
|
||||
|
||||
db.put(key, 'value').catch(function (err) {
|
||||
t.ok(err instanceof Error, name + ' - is Error (promise)')
|
||||
t.is(err.code, 'LEVEL_INVALID_KEY', name + ' - correct error code (promise)')
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
||||
test('test put() with illegal values', assertAsync.ctx(function (t) {
|
||||
t.plan(illegalValues.length * 5)
|
||||
|
||||
for (const { name, value } of illegalValues) {
|
||||
db.put('key', value, assertAsync(function (err) {
|
||||
t.ok(err instanceof Error, name + '- is Error (callback)')
|
||||
t.is(err && err.code, 'LEVEL_INVALID_VALUE', name + ' - correct error code (callback)')
|
||||
}))
|
||||
|
||||
db.put('key', value).catch(function (err) {
|
||||
t.ok(err instanceof Error, name + ' - is Error (promise)')
|
||||
t.is(err.code, 'LEVEL_INVALID_VALUE', name + ' - correct error code (promise)')
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
exports.put = function (test, testCommon) {
|
||||
test('test simple put()', assertAsync.ctx(function (t) {
|
||||
t.plan(7)
|
||||
|
||||
db.put('foo', 'bar', assertAsync(function (err) {
|
||||
t.ifError(err, 'no put() error')
|
||||
|
||||
db.get('foo', function (err, value) {
|
||||
t.ifError(err, 'no get() error')
|
||||
t.is(value, 'bar')
|
||||
|
||||
db.put('foo', 'new', function (err) {
|
||||
t.ifError(err, 'no put() error')
|
||||
|
||||
db.get('foo', function (err, value) {
|
||||
t.ifError(err, 'no get() error')
|
||||
t.is(value, 'new', 'value was overwritten')
|
||||
})
|
||||
})
|
||||
})
|
||||
}))
|
||||
}))
|
||||
|
||||
test('test simple put() with promise', async function (t) {
|
||||
await db.put('foo2', 'bar')
|
||||
t.is(await db.get('foo2'), 'bar')
|
||||
})
|
||||
|
||||
test('test deferred put()', assertAsync.ctx(function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const db = testCommon.factory()
|
||||
|
||||
db.put('foo', 'bar', assertAsync(function (err) {
|
||||
t.ifError(err, 'no put() error')
|
||||
|
||||
db.get('foo', { valueEncoding: 'utf8' }, function (err, value) {
|
||||
t.ifError(err, 'no get() error')
|
||||
t.is(value, 'bar', 'value is ok')
|
||||
db.close(t.ifError.bind(t))
|
||||
})
|
||||
}))
|
||||
}))
|
||||
|
||||
test('test deferred put() with promise', async function (t) {
|
||||
const db = testCommon.factory()
|
||||
await db.put('foo', 'bar')
|
||||
t.is(await db.get('foo', { valueEncoding: 'utf8' }), 'bar', 'value is ok')
|
||||
return db.close()
|
||||
})
|
||||
}
|
||||
|
||||
exports.events = function (test, testCommon) {
|
||||
test('test put() emits put event', async function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
|
||||
t.ok(db.supports.events.put)
|
||||
|
||||
db.on('put', function (key, value) {
|
||||
t.is(key, 123)
|
||||
t.is(value, 'b')
|
||||
})
|
||||
|
||||
await db.put(123, 'b')
|
||||
await db.close()
|
||||
})
|
||||
}
|
||||
|
||||
exports.tearDown = function (test, testCommon) {
|
||||
test('tearDown', function (t) {
|
||||
db.close(t.end.bind(t))
|
||||
})
|
||||
}
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
exports.setUp(test, testCommon)
|
||||
exports.args(test, testCommon)
|
||||
exports.put(test, testCommon)
|
||||
exports.events(test, testCommon)
|
||||
exports.tearDown(test, testCommon)
|
||||
}
|
||||
Generated
Vendored
+1080
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+263
@@ -0,0 +1,263 @@
|
||||
'use strict'
|
||||
|
||||
const test = require('tape')
|
||||
const { AbstractLevel, AbstractIterator, AbstractKeyIterator, AbstractValueIterator } = require('../..')
|
||||
|
||||
const testCommon = require('../common')({
|
||||
test,
|
||||
factory: function () {
|
||||
return new AbstractLevel({ encodings: { utf8: true } })
|
||||
}
|
||||
})
|
||||
|
||||
for (const Ctor of [AbstractIterator, AbstractKeyIterator, AbstractValueIterator]) {
|
||||
test(`test ${Ctor.name} extensibility`, function (t) {
|
||||
const Test = class TestIterator extends Ctor {}
|
||||
const db = testCommon.factory()
|
||||
const test = new Test(db, {})
|
||||
t.ok(test.db === db, 'instance has db reference')
|
||||
t.end()
|
||||
})
|
||||
|
||||
test(`${Ctor.name} throws on invalid db argument`, function (t) {
|
||||
t.plan(4 * 2)
|
||||
|
||||
for (const args of [[], [null], [undefined], 'foo']) {
|
||||
const hint = args[0] === null ? 'null' : typeof args[0]
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-new
|
||||
new Ctor(...args)
|
||||
} catch (err) {
|
||||
t.is(err.name, 'TypeError')
|
||||
t.is(err.message, 'The first argument must be an abstract-level database, received ' + hint)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test(`${Ctor.name} throws on invalid options argument`, function (t) {
|
||||
t.plan(4 * 2)
|
||||
|
||||
for (const args of [[], [null], [undefined], 'foo']) {
|
||||
try {
|
||||
// eslint-disable-next-line no-new
|
||||
new Ctor({}, ...args)
|
||||
} catch (err) {
|
||||
t.is(err.name, 'TypeError')
|
||||
t.is(err.message, 'The second argument must be an options object')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test(`${Ctor.name}.next() extensibility`, async function (t) {
|
||||
t.plan(3)
|
||||
|
||||
class TestIterator extends Ctor {
|
||||
_next (callback) {
|
||||
t.is(this, it, 'thisArg on _next() was correct')
|
||||
t.is(arguments.length, 1, 'got one argument')
|
||||
t.is(typeof callback, 'function', 'got a callback function')
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
const it = new TestIterator(db, {})
|
||||
await it.next()
|
||||
await db.close()
|
||||
})
|
||||
|
||||
test(`${Ctor.name}.next() throws on invalid callback argument`, async function (t) {
|
||||
t.plan(3 * 2)
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
|
||||
for (const invalid of [{}, null, 'foo']) {
|
||||
const it = new Ctor(db, {})
|
||||
|
||||
try {
|
||||
it.next(invalid)
|
||||
} catch (err) {
|
||||
t.is(err.name, 'TypeError')
|
||||
t.is(err.message, 'Callback must be a function')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test(`${Ctor.name}.nextv() extensibility`, async function (t) {
|
||||
t.plan(5 * 2)
|
||||
|
||||
class TestIterator extends Ctor {
|
||||
_nextv (size, options, callback) {
|
||||
t.is(this, it, 'thisArg on _nextv() was correct')
|
||||
t.is(arguments.length, 3, 'got 3 arguments')
|
||||
t.is(size, 100)
|
||||
t.same(options, {}, 'empty options')
|
||||
t.is(typeof callback, 'function', 'got a callback function')
|
||||
this.nextTick(callback, null, [])
|
||||
}
|
||||
}
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
const it = new TestIterator(db, {})
|
||||
await it.nextv(100)
|
||||
await it.nextv(100, {})
|
||||
await db.close()
|
||||
})
|
||||
|
||||
test(`${Ctor.name}.nextv() extensibility (options)`, async function (t) {
|
||||
t.plan(2)
|
||||
|
||||
class TestIterator extends Ctor {
|
||||
_nextv (size, options, callback) {
|
||||
t.is(size, 100)
|
||||
t.same(options, { foo: 123 }, 'got options')
|
||||
this.nextTick(callback, null, [])
|
||||
}
|
||||
}
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
const it = new TestIterator(db, {})
|
||||
await it.nextv(100, { foo: 123 })
|
||||
await db.close()
|
||||
})
|
||||
|
||||
test(`${Ctor.name}.nextv() throws on invalid callback argument`, async function (t) {
|
||||
t.plan(3 * 2)
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
|
||||
for (const invalid of [{}, null, 'foo']) {
|
||||
const it = new Ctor(db, {})
|
||||
|
||||
try {
|
||||
it.nextv(100, {}, invalid)
|
||||
} catch (err) {
|
||||
t.is(err.name, 'TypeError')
|
||||
t.is(err.message, 'Callback must be a function')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test(`${Ctor.name}.all() extensibility`, async function (t) {
|
||||
t.plan(2 * 4)
|
||||
|
||||
for (const args of [[], [{}]]) {
|
||||
class TestIterator extends Ctor {
|
||||
_all (options, callback) {
|
||||
t.is(this, it, 'thisArg on _all() was correct')
|
||||
t.is(arguments.length, 2, 'got 2 arguments')
|
||||
t.same(options, {}, 'empty options')
|
||||
t.is(typeof callback, 'function', 'got a callback function')
|
||||
this.nextTick(callback, null, [])
|
||||
}
|
||||
}
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
const it = new TestIterator(db, {})
|
||||
await it.all(...args)
|
||||
await db.close()
|
||||
}
|
||||
})
|
||||
|
||||
test(`${Ctor.name}.all() extensibility (options)`, async function (t) {
|
||||
t.plan(1)
|
||||
|
||||
class TestIterator extends Ctor {
|
||||
_all (options, callback) {
|
||||
t.same(options, { foo: 123 }, 'got options')
|
||||
this.nextTick(callback, null, [])
|
||||
}
|
||||
}
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
const it = new TestIterator(db, {})
|
||||
await it.all({ foo: 123 })
|
||||
await db.close()
|
||||
})
|
||||
|
||||
test(`${Ctor.name}.all() throws on invalid callback argument`, async function (t) {
|
||||
t.plan(3 * 2)
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
|
||||
for (const invalid of [{}, null, 'foo']) {
|
||||
const it = new Ctor(db, {})
|
||||
|
||||
try {
|
||||
it.all({}, invalid)
|
||||
} catch (err) {
|
||||
t.is(err.name, 'TypeError')
|
||||
t.is(err.message, 'Callback must be a function')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test(`${Ctor.name}.close() extensibility`, async function (t) {
|
||||
t.plan(3)
|
||||
|
||||
class TestIterator extends Ctor {
|
||||
_close (callback) {
|
||||
t.is(this, it, 'thisArg on _close() was correct')
|
||||
t.is(arguments.length, 1, 'got one argument')
|
||||
t.is(typeof callback, 'function', 'got a callback function')
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
const it = new TestIterator(db, {})
|
||||
await it.close()
|
||||
await db.close()
|
||||
})
|
||||
|
||||
test(`${Ctor.name}.close() throws on invalid callback argument`, async function (t) {
|
||||
t.plan(3 * 2)
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
|
||||
for (const invalid of [{}, null, 'foo']) {
|
||||
const it = new Ctor(db, {})
|
||||
|
||||
try {
|
||||
it.close(invalid)
|
||||
} catch (err) {
|
||||
t.is(err.name, 'TypeError')
|
||||
t.is(err.message, 'Callback must be a function')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
test('AbstractIterator throws when accessing legacy properties', async function (t) {
|
||||
t.plan(3 * 2)
|
||||
|
||||
const db = testCommon.factory()
|
||||
await db.open()
|
||||
const it = new AbstractIterator(db, {})
|
||||
|
||||
for (const k of ['_ended property', '_nexting property', '_end method']) {
|
||||
try {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
it[k.split(' ')[0]]
|
||||
} catch (err) {
|
||||
t.is(err.code, 'LEVEL_LEGACY')
|
||||
}
|
||||
|
||||
try {
|
||||
it[k.split(' ')[0]] = 123
|
||||
} catch (err) {
|
||||
t.is(err.code, 'LEVEL_LEGACY')
|
||||
}
|
||||
}
|
||||
})
|
||||
Generated
Vendored
+213
@@ -0,0 +1,213 @@
|
||||
'use strict'
|
||||
|
||||
const test = require('tape')
|
||||
const { AbstractLevel, AbstractIterator } = require('../..')
|
||||
const { DeferredIterator, DeferredKeyIterator, DeferredValueIterator } = require('../../lib/deferred-iterator')
|
||||
|
||||
function withIterator (methods) {
|
||||
class TestIterator extends AbstractIterator { }
|
||||
|
||||
for (const k in methods) {
|
||||
TestIterator.prototype[k] = methods[k]
|
||||
}
|
||||
|
||||
class Test extends AbstractLevel {
|
||||
_iterator (options) {
|
||||
return new TestIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
return new Test({ encodings: { utf8: true } })
|
||||
}
|
||||
|
||||
for (const mode of ['iterator', 'keys', 'values']) {
|
||||
for (const type of ['explicit', 'deferred']) {
|
||||
const verify = function (t, db, it) {
|
||||
t.is(db.status, type === 'explicit' ? 'open' : 'opening')
|
||||
|
||||
if (type === 'explicit') {
|
||||
t.is(
|
||||
it.constructor.name,
|
||||
mode === 'iterator' ? 'TestIterator' : mode === 'keys' ? 'DefaultKeyIterator' : 'DefaultValueIterator'
|
||||
)
|
||||
} else {
|
||||
t.is(
|
||||
it.constructor,
|
||||
mode === 'iterator' ? DeferredIterator : mode === 'keys' ? DeferredKeyIterator : DeferredValueIterator
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
test(`for await...of ${mode}() (${type} open)`, async function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const input = [{ key: '1', value: '1' }, { key: '2', value: '2' }]
|
||||
const output = []
|
||||
|
||||
const db = withIterator({
|
||||
_next (callback) {
|
||||
const { key, value } = input[n++] || []
|
||||
this.nextTick(callback, null, key, value)
|
||||
},
|
||||
|
||||
_close (callback) {
|
||||
this.nextTick(function () {
|
||||
closed = true
|
||||
callback()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (type === 'explicit') await db.open()
|
||||
const it = db[mode]({ keyEncoding: 'utf8', valueEncoding: 'utf8' })
|
||||
verify(t, db, it)
|
||||
|
||||
let n = 0
|
||||
let closed = false
|
||||
|
||||
for await (const item of it) {
|
||||
output.push(item)
|
||||
}
|
||||
|
||||
t.same(output, input.map(x => mode === 'iterator' ? [x.key, x.value] : mode === 'keys' ? x.key : x.value))
|
||||
t.ok(closed, 'closed')
|
||||
})
|
||||
|
||||
test(`for await...of ${mode}() closes on user error (${type} open)`, async function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const db = withIterator({
|
||||
_next (callback) {
|
||||
this.nextTick(callback, null, n.toString(), n.toString())
|
||||
if (n++ > 10) throw new Error('Infinite loop')
|
||||
},
|
||||
|
||||
_close (callback) {
|
||||
this.nextTick(function () {
|
||||
closed = true
|
||||
callback(new Error('close error'))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (type === 'explicit') await db.open()
|
||||
const it = db[mode]()
|
||||
verify(t, db, it)
|
||||
|
||||
let n = 0
|
||||
let closed = false
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-unused-vars, no-unreachable-loop
|
||||
for await (const kv of it) {
|
||||
throw new Error('user error')
|
||||
}
|
||||
} catch (err) {
|
||||
t.is(err.message, 'user error')
|
||||
t.ok(closed, 'closed')
|
||||
}
|
||||
})
|
||||
|
||||
test(`for await...of ${mode}() closes on iterator error (${type} open)`, async function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const db = withIterator({
|
||||
_next (callback) {
|
||||
t.pass('nexted')
|
||||
this.nextTick(callback, new Error('iterator error'))
|
||||
},
|
||||
|
||||
_close (callback) {
|
||||
this.nextTick(function () {
|
||||
closed = true
|
||||
callback()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (type === 'explicit') await db.open()
|
||||
const it = db[mode]()
|
||||
verify(t, db, it)
|
||||
|
||||
let closed = false
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
for await (const kv of it) {
|
||||
t.fail('should not yield items')
|
||||
}
|
||||
} catch (err) {
|
||||
t.is(err.message, 'iterator error')
|
||||
t.ok(closed, 'closed')
|
||||
}
|
||||
})
|
||||
|
||||
test(`for await...of ${mode}() closes on user break (${type} open)`, async function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const db = withIterator({
|
||||
_next (callback) {
|
||||
this.nextTick(callback, null, n.toString(), n.toString())
|
||||
if (n++ > 10) throw new Error('Infinite loop')
|
||||
},
|
||||
|
||||
_close (callback) {
|
||||
this.nextTick(function () {
|
||||
closed = true
|
||||
callback()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (type === 'explicit') await db.open()
|
||||
const it = db[mode]()
|
||||
verify(t, db, it)
|
||||
|
||||
let n = 0
|
||||
let closed = false
|
||||
|
||||
// eslint-disable-next-line no-unused-vars, no-unreachable-loop
|
||||
for await (const kv of it) {
|
||||
t.pass('got a chance to break')
|
||||
break
|
||||
}
|
||||
|
||||
t.ok(closed, 'closed')
|
||||
})
|
||||
|
||||
test(`for await...of ${mode}() closes on user return (${type} open)`, async function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const db = withIterator({
|
||||
_next (callback) {
|
||||
this.nextTick(callback, null, n.toString(), n.toString())
|
||||
if (n++ > 10) throw new Error('Infinite loop')
|
||||
},
|
||||
|
||||
_close (callback) {
|
||||
this.nextTick(function () {
|
||||
closed = true
|
||||
callback()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (type === 'explicit') await db.open()
|
||||
const it = db[mode]()
|
||||
verify(t, db, it)
|
||||
|
||||
let n = 0
|
||||
let closed = false
|
||||
|
||||
await (async () => {
|
||||
// eslint-disable-next-line no-unused-vars, no-unreachable-loop
|
||||
for await (const kv of it) {
|
||||
t.pass('got a chance to return')
|
||||
return
|
||||
}
|
||||
})()
|
||||
|
||||
t.ok(closed, 'closed')
|
||||
})
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+75
@@ -0,0 +1,75 @@
|
||||
'use strict'
|
||||
|
||||
const test = require('tape')
|
||||
const { mockLevel } = require('../util')
|
||||
const nextTick = require('../../lib/next-tick')
|
||||
|
||||
test('resource must be an object with a close() method', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const db = mockLevel()
|
||||
|
||||
for (const invalid of [null, undefined, {}, { close: 123 }]) {
|
||||
try {
|
||||
db.attachResource(invalid)
|
||||
} catch (err) {
|
||||
t.is(err && err.message, 'The first argument must be a resource object')
|
||||
}
|
||||
}
|
||||
|
||||
db.close(t.ifError.bind(t))
|
||||
})
|
||||
|
||||
test('resource is closed on failed open', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const db = mockLevel({
|
||||
_open: function (options, callback) {
|
||||
t.pass('opened')
|
||||
this.nextTick(callback, new Error('_open error'))
|
||||
}
|
||||
})
|
||||
|
||||
const resource = {
|
||||
close: function (cb) {
|
||||
// Note: resource shouldn't care about db.status
|
||||
t.pass('closed')
|
||||
nextTick(cb)
|
||||
}
|
||||
}
|
||||
|
||||
db.attachResource(resource)
|
||||
})
|
||||
|
||||
test('resource is closed on db.close()', function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const db = mockLevel()
|
||||
|
||||
const resource = {
|
||||
close: function (cb) {
|
||||
// Note: resource shouldn't care about db.status
|
||||
t.pass('closed')
|
||||
nextTick(cb)
|
||||
}
|
||||
}
|
||||
|
||||
db.attachResource(resource)
|
||||
db.close(t.ifError.bind(t))
|
||||
})
|
||||
|
||||
test('resource is not closed on db.close() if detached', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const db = mockLevel()
|
||||
|
||||
const resource = {
|
||||
close: function (cb) {
|
||||
t.fail('should not be called')
|
||||
}
|
||||
}
|
||||
|
||||
db.attachResource(resource)
|
||||
db.detachResource(resource)
|
||||
db.close(t.ifError.bind(t))
|
||||
})
|
||||
Generated
Vendored
+90
@@ -0,0 +1,90 @@
|
||||
'use strict'
|
||||
|
||||
const test = require('tape')
|
||||
const { mockLevel } = require('../util')
|
||||
|
||||
test('defer() requires valid function argument', function (t) {
|
||||
t.plan(7)
|
||||
|
||||
const db = mockLevel()
|
||||
|
||||
for (const invalid of [123, true, false, null, undefined, {}]) {
|
||||
try {
|
||||
db.defer(invalid)
|
||||
} catch (err) {
|
||||
t.is(err.message, 'The first argument must be a function')
|
||||
}
|
||||
}
|
||||
|
||||
db.close(t.ifError.bind(t))
|
||||
})
|
||||
|
||||
test('defer() custom operation', function (t) {
|
||||
t.plan(6)
|
||||
|
||||
const db = mockLevel({
|
||||
custom (arg, callback) {
|
||||
if (this.status === 'opening') {
|
||||
t.is(arg, 123)
|
||||
this.defer(() => this.custom(456, callback))
|
||||
} else {
|
||||
t.is(db.status, 'open')
|
||||
t.is(arg, 456)
|
||||
this.nextTick(callback, null, 987)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
db.custom(123, function (err, result) {
|
||||
t.ifError(err, 'no custom() error')
|
||||
t.is(result, 987, 'result ok')
|
||||
|
||||
db.close(t.ifError.bind(t))
|
||||
})
|
||||
})
|
||||
|
||||
test('defer() custom operation with failed open', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const db = mockLevel({
|
||||
_open (options, callback) {
|
||||
t.pass('opened')
|
||||
this.nextTick(callback, new Error('_open error'))
|
||||
},
|
||||
custom (arg, callback) {
|
||||
if (this.status === 'opening') {
|
||||
this.defer(() => this.custom(arg, callback))
|
||||
} else {
|
||||
t.is(db.status, 'closed')
|
||||
this.nextTick(callback, new Error('Database is not open (x)'))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
db.custom(123, function (err, result) {
|
||||
t.is(err && err.message, 'Database is not open (x)')
|
||||
t.is(result, undefined, 'result ok')
|
||||
})
|
||||
})
|
||||
|
||||
test('defer() can drop custom synchronous operation', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const db = mockLevel({
|
||||
_open (options, callback) {
|
||||
t.pass('opened')
|
||||
this.nextTick(callback, new Error('_open error'))
|
||||
},
|
||||
custom (arg) {
|
||||
if (this.status === 'opening') {
|
||||
this.defer(() => this.custom(arg * 2))
|
||||
} else {
|
||||
// Handling other states is a userland responsibility
|
||||
t.is(db.status, 'closed')
|
||||
t.is(arg, 246)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
db.custom(123)
|
||||
})
|
||||
Generated
Vendored
+98
@@ -0,0 +1,98 @@
|
||||
'use strict'
|
||||
|
||||
const test = require('tape')
|
||||
const { mockLevel } = require('../util')
|
||||
const { DefaultChainedBatch } = require('../../lib/default-chained-batch')
|
||||
const identity = (v) => v
|
||||
|
||||
// NOTE: adapted from deferred-leveldown
|
||||
test('deferred chained batch encodes once', function (t) {
|
||||
t.plan(9)
|
||||
|
||||
let called = false
|
||||
|
||||
const keyEncoding = {
|
||||
format: 'utf8',
|
||||
encode (key) {
|
||||
t.is(called, false, 'not yet called')
|
||||
t.is(key, 'foo')
|
||||
return key.toUpperCase()
|
||||
},
|
||||
decode: identity
|
||||
}
|
||||
|
||||
const valueEncoding = {
|
||||
format: 'utf8',
|
||||
encode (value) {
|
||||
t.is(called, false, 'not yet called')
|
||||
t.is(value, 'bar')
|
||||
return value.toUpperCase()
|
||||
},
|
||||
decode: identity
|
||||
}
|
||||
|
||||
const db = mockLevel({
|
||||
_batch: function (array, options, callback) {
|
||||
called = true
|
||||
t.is(array[0] && array[0].key, 'FOO')
|
||||
t.is(array[0] && array[0].value, 'BAR')
|
||||
this.nextTick(callback)
|
||||
},
|
||||
_open: function (options, callback) {
|
||||
t.is(called, false, 'not yet called')
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}, { encodings: { utf8: true } }, {
|
||||
keyEncoding,
|
||||
valueEncoding
|
||||
})
|
||||
|
||||
db.once('open', function () {
|
||||
t.is(called, true, 'called')
|
||||
})
|
||||
|
||||
db.batch().put('foo', 'bar').write(function (err) {
|
||||
t.ifError(err, 'no write() error')
|
||||
})
|
||||
})
|
||||
|
||||
test('deferred chained batch is closed upon failed open', function (t) {
|
||||
t.plan(6)
|
||||
|
||||
const db = mockLevel({
|
||||
_open (options, callback) {
|
||||
t.pass('opening')
|
||||
this.nextTick(callback, new Error('_open error'))
|
||||
},
|
||||
_batch () {
|
||||
t.fail('should not be called')
|
||||
}
|
||||
})
|
||||
|
||||
const batch = db.batch()
|
||||
t.ok(batch instanceof DefaultChainedBatch)
|
||||
|
||||
batch.put('foo', 'bar')
|
||||
batch.del('123')
|
||||
|
||||
batch.write(function (err) {
|
||||
t.is(err && err.code, 'LEVEL_BATCH_NOT_OPEN')
|
||||
|
||||
// Should account for userland code that ignores errors
|
||||
try {
|
||||
batch.put('beep', 'boop')
|
||||
} catch (err) {
|
||||
t.is(err && err.code, 'LEVEL_BATCH_NOT_OPEN')
|
||||
}
|
||||
|
||||
try {
|
||||
batch.del('456')
|
||||
} catch (err) {
|
||||
t.is(err && err.code, 'LEVEL_BATCH_NOT_OPEN')
|
||||
}
|
||||
|
||||
batch.write(function (err) {
|
||||
t.is(err && err.code, 'LEVEL_BATCH_NOT_OPEN')
|
||||
})
|
||||
})
|
||||
})
|
||||
Generated
Vendored
+298
@@ -0,0 +1,298 @@
|
||||
'use strict'
|
||||
|
||||
const test = require('tape')
|
||||
const { DeferredIterator, DeferredKeyIterator, DeferredValueIterator } = require('../../lib/deferred-iterator')
|
||||
const { AbstractIterator, AbstractKeyIterator, AbstractValueIterator } = require('../..')
|
||||
const { mockLevel } = require('../util')
|
||||
const noop = () => {}
|
||||
const identity = (v) => v
|
||||
|
||||
for (const mode of ['iterator', 'keys', 'values']) {
|
||||
const RealCtor = mode === 'iterator' ? AbstractIterator : mode === 'keys' ? AbstractKeyIterator : AbstractValueIterator
|
||||
const DeferredCtor = mode === 'iterator' ? DeferredIterator : mode === 'keys' ? DeferredKeyIterator : DeferredValueIterator
|
||||
const nextArgs = mode === 'iterator' ? ['key', 'value'] : mode === 'keys' ? ['key'] : ['value']
|
||||
const privateMethod = '_' + mode
|
||||
const publicMethod = mode
|
||||
|
||||
// NOTE: adapted from deferred-leveldown
|
||||
test(`deferred ${mode}()`, function (t) {
|
||||
t.plan(8)
|
||||
|
||||
const keyEncoding = {
|
||||
format: 'utf8',
|
||||
encode (key) {
|
||||
t.is(key, 'foo', 'encoding got key')
|
||||
return key.toUpperCase()
|
||||
},
|
||||
decode: identity
|
||||
}
|
||||
|
||||
class MockIterator extends RealCtor {
|
||||
_next (cb) {
|
||||
this.nextTick(cb, null, ...nextArgs)
|
||||
}
|
||||
|
||||
_close (cb) {
|
||||
this.nextTick(cb)
|
||||
}
|
||||
}
|
||||
|
||||
const db = mockLevel({
|
||||
[privateMethod]: function (options) {
|
||||
t.is(options.gt, 'FOO', 'got encoded range option')
|
||||
return new MockIterator(this, options)
|
||||
},
|
||||
_open: function (options, callback) {
|
||||
t.pass('opened')
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}, { encodings: { utf8: true } }, {
|
||||
keyEncoding
|
||||
})
|
||||
|
||||
const it = db[publicMethod]({ gt: 'foo' })
|
||||
t.ok(it instanceof DeferredCtor, 'is deferred')
|
||||
|
||||
let nextFirst = false
|
||||
|
||||
it.next(function (err, ...rest) {
|
||||
nextFirst = true
|
||||
t.error(err, 'no next() error')
|
||||
t.same(rest, nextArgs)
|
||||
})
|
||||
|
||||
it.close(function (err) {
|
||||
t.error(err, 'no close() error')
|
||||
t.ok(nextFirst)
|
||||
})
|
||||
})
|
||||
|
||||
// NOTE: adapted from deferred-leveldown
|
||||
test(`deferred ${mode}(): non-deferred operations`, function (t) {
|
||||
t.plan(6)
|
||||
|
||||
class MockIterator extends RealCtor {
|
||||
_seek (target) {
|
||||
t.is(target, '123')
|
||||
}
|
||||
|
||||
_next (cb) {
|
||||
this.nextTick(cb, null, ...nextArgs)
|
||||
}
|
||||
}
|
||||
|
||||
const db = mockLevel({
|
||||
[privateMethod]: function (options) {
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
})
|
||||
|
||||
db.open(function (err) {
|
||||
t.error(err, 'no open() error')
|
||||
|
||||
it.seek(123)
|
||||
it.next(function (err, ...rest) {
|
||||
t.error(err, 'no next() error')
|
||||
t.same(rest, nextArgs)
|
||||
|
||||
it.close(function (err) {
|
||||
t.error(err, 'no close() error')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const it = db[publicMethod]({ gt: 'foo' })
|
||||
t.ok(it instanceof DeferredCtor)
|
||||
})
|
||||
|
||||
// NOTE: adapted from deferred-leveldown
|
||||
test(`deferred ${mode}(): iterators are created in order`, function (t) {
|
||||
t.plan(6)
|
||||
|
||||
const order1 = []
|
||||
const order2 = []
|
||||
|
||||
class MockIterator extends RealCtor {}
|
||||
|
||||
function db (order) {
|
||||
return mockLevel({
|
||||
[privateMethod]: function (options) {
|
||||
order.push('iterator created')
|
||||
return new MockIterator(this, options)
|
||||
},
|
||||
_put: function (key, value, options, callback) {
|
||||
order.push('put')
|
||||
},
|
||||
_open: function (options, callback) {
|
||||
this.nextTick(callback)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const db1 = db(order1)
|
||||
const db2 = db(order2)
|
||||
|
||||
db1.open(function (err) {
|
||||
t.error(err, 'no error')
|
||||
t.same(order1, ['iterator created', 'put'])
|
||||
})
|
||||
|
||||
db2.open(function (err) {
|
||||
t.error(err, 'no error')
|
||||
t.same(order2, ['put', 'iterator created'])
|
||||
})
|
||||
|
||||
t.ok(db1[publicMethod]() instanceof DeferredCtor)
|
||||
db1.put('key', 'value', noop)
|
||||
|
||||
db2.put('key', 'value', noop)
|
||||
t.ok(db2[publicMethod]() instanceof DeferredCtor)
|
||||
})
|
||||
|
||||
for (const method of ['next', 'nextv', 'all']) {
|
||||
test(`deferred ${mode}(): closed upon failed open, verified by ${method}()`, function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const db = mockLevel({
|
||||
_open (options, callback) {
|
||||
t.pass('opening')
|
||||
this.nextTick(callback, new Error('_open error'))
|
||||
},
|
||||
_iterator () {
|
||||
t.fail('should not be called')
|
||||
},
|
||||
[privateMethod] () {
|
||||
t.fail('should not be called')
|
||||
}
|
||||
})
|
||||
|
||||
const it = db[publicMethod]()
|
||||
t.ok(it instanceof DeferredCtor)
|
||||
|
||||
const original = it._close
|
||||
it._close = function (...args) {
|
||||
t.pass('closed')
|
||||
return original.call(this, ...args)
|
||||
}
|
||||
|
||||
verifyClosed(t, it, method, () => {})
|
||||
})
|
||||
|
||||
test(`deferred ${mode}(): deferred and real iterators are closed on db.close(), verified by ${method}()`, function (t) {
|
||||
t.plan(10)
|
||||
|
||||
class MockIterator extends RealCtor {
|
||||
_close (callback) {
|
||||
t.pass('closed')
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}
|
||||
|
||||
const db = mockLevel({
|
||||
[privateMethod] (options) {
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
})
|
||||
|
||||
const it = db[publicMethod]()
|
||||
t.ok(it instanceof DeferredCtor)
|
||||
|
||||
const original = it._close
|
||||
it._close = function (...args) {
|
||||
t.pass('closed')
|
||||
return original.call(this, ...args)
|
||||
}
|
||||
|
||||
db.close(function (err) {
|
||||
t.ifError(err, 'no close() error')
|
||||
|
||||
verifyClosed(t, it, method, function () {
|
||||
db.open(function (err) {
|
||||
t.ifError(err, 'no open() error')
|
||||
|
||||
// Should still be closed
|
||||
verifyClosed(t, it, method, function () {
|
||||
db.close(t.ifError.bind(t))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
test(`deferred ${mode}(): deferred and real iterators are detached on db.close()`, function (t) {
|
||||
t.plan(4)
|
||||
|
||||
class MockIterator extends RealCtor {}
|
||||
|
||||
let real
|
||||
const db = mockLevel({
|
||||
[privateMethod] (options) {
|
||||
real = new MockIterator(this, options)
|
||||
return real
|
||||
}
|
||||
})
|
||||
|
||||
const it = db[publicMethod]()
|
||||
t.ok(it instanceof DeferredCtor)
|
||||
|
||||
db.close(function (err) {
|
||||
t.ifError(err, 'no close() error')
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err, 'no open() error')
|
||||
|
||||
it.close = real.close = it._close = real._close = function () {
|
||||
t.fail('should not be called')
|
||||
}
|
||||
|
||||
db.close(t.ifError.bind(t))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test(`deferred ${mode}(): defers underlying close()`, function (t) {
|
||||
t.plan(3)
|
||||
|
||||
class MockIterator extends RealCtor {
|
||||
_close (callback) {
|
||||
order.push('_close')
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}
|
||||
|
||||
const order = []
|
||||
const db = mockLevel({
|
||||
_open (options, callback) {
|
||||
order.push('_open')
|
||||
this.nextTick(callback)
|
||||
},
|
||||
[privateMethod] (options) {
|
||||
order.push(privateMethod)
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
})
|
||||
|
||||
const it = db[publicMethod]()
|
||||
t.ok(it instanceof DeferredCtor)
|
||||
|
||||
it.close(function (err) {
|
||||
t.ifError(err, 'no close() error')
|
||||
t.same(order, ['_open', privateMethod, '_close'])
|
||||
})
|
||||
})
|
||||
|
||||
const verifyClosed = function (t, it, method, cb) {
|
||||
const requiredArgs = method === 'nextv' ? [10] : []
|
||||
|
||||
it[method](...requiredArgs, function (err) {
|
||||
t.is(err && err.code, 'LEVEL_ITERATOR_NOT_OPEN', `correct error on first ${method}()`)
|
||||
|
||||
// Should account for userland code that ignores errors
|
||||
it[method](...requiredArgs, function (err) {
|
||||
t.is(err && err.code, 'LEVEL_ITERATOR_NOT_OPEN', `correct error on second ${method}()`)
|
||||
cb()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+93
@@ -0,0 +1,93 @@
|
||||
'use strict'
|
||||
|
||||
const test = require('tape')
|
||||
const { mockLevel, mockIterator } = require('../util')
|
||||
|
||||
// NOTE: copied from deferred-leveldown
|
||||
test('deferred operations are called in order', function (t) {
|
||||
t.plan(15)
|
||||
|
||||
const calls = []
|
||||
const db = mockLevel({
|
||||
_put: function (key, value, options, callback) {
|
||||
this.nextTick(callback)
|
||||
calls.push({ type: 'put', key, value, options })
|
||||
},
|
||||
_get: function (key, options, callback) {
|
||||
this.nextTick(callback)
|
||||
calls.push({ type: 'get', key, options })
|
||||
},
|
||||
_del: function (key, options, callback) {
|
||||
this.nextTick(callback)
|
||||
calls.push({ type: 'del', key, options })
|
||||
},
|
||||
_batch: function (arr, options, callback) {
|
||||
this.nextTick(callback)
|
||||
calls.push({ type: 'batch', keys: arr.map(op => op.key).join(',') })
|
||||
},
|
||||
_clear: function (options, callback) {
|
||||
this.nextTick(callback)
|
||||
calls.push({ ...options, type: 'clear' })
|
||||
},
|
||||
_iterator (options) {
|
||||
calls.push({ type: 'iterator' })
|
||||
return mockIterator(this, options, {
|
||||
_next (callback) {
|
||||
this.nextTick(callback)
|
||||
calls.push({ type: 'iterator.next' })
|
||||
}
|
||||
})
|
||||
},
|
||||
_open: function (options, callback) {
|
||||
this.nextTick(callback)
|
||||
t.is(calls.length, 0, 'not yet called')
|
||||
}
|
||||
}, {
|
||||
encodings: {
|
||||
utf8: true,
|
||||
buffer: true
|
||||
}
|
||||
}, {
|
||||
keyEncoding: 'utf8',
|
||||
valueEncoding: 'utf8'
|
||||
})
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err, 'no open() error')
|
||||
t.same(calls, [
|
||||
{ type: 'put', key: '001', value: 'bar1', options: { keyEncoding: 'utf8', valueEncoding: 'utf8' } },
|
||||
{ type: 'get', key: '002', options: { keyEncoding: 'utf8', valueEncoding: 'utf8' } },
|
||||
{ type: 'clear', reverse: false, limit: -1, keyEncoding: 'utf8' },
|
||||
{ type: 'put', key: '010', value: 'bar2', options: { keyEncoding: 'utf8', valueEncoding: 'utf8' } },
|
||||
{ type: 'get', key: Buffer.from('011'), options: { keyEncoding: 'buffer', valueEncoding: 'utf8' } },
|
||||
{ type: 'del', key: '020', options: { customOption: 123, keyEncoding: 'utf8' } },
|
||||
{ type: 'del', key: '021', options: { keyEncoding: 'utf8' } },
|
||||
{ type: 'batch', keys: '040,041' },
|
||||
{ type: 'iterator' },
|
||||
{ type: 'batch', keys: '050,051' },
|
||||
{ type: 'iterator.next' },
|
||||
{ type: 'clear', gt: '060', reverse: false, limit: -1, keyEncoding: 'utf8' }
|
||||
], 'calls correctly behaved')
|
||||
})
|
||||
|
||||
db.put('001', 'bar1', t.ifError.bind(t))
|
||||
db.get('002', t.ifError.bind(t))
|
||||
db.clear(t.ifError.bind(t))
|
||||
db.put('010', 'bar2', t.ifError.bind(t))
|
||||
db.get('011', { keyEncoding: 'buffer' }, t.ifError.bind(t))
|
||||
db.del('020', { customOption: 123 }, t.ifError.bind(t))
|
||||
db.del('021', t.ifError.bind(t))
|
||||
db.batch([
|
||||
{ type: 'put', key: '040', value: 'a' },
|
||||
{ type: 'put', key: '041', value: 'b' }
|
||||
], t.ifError.bind(t))
|
||||
const it = db.iterator()
|
||||
db.batch()
|
||||
.put('050', 'c')
|
||||
.put('051', 'd')
|
||||
.write(t.ifError.bind(t))
|
||||
it.next(t.ifError.bind(t))
|
||||
db.clear({ gt: '060' }, t.ifError.bind(t))
|
||||
|
||||
t.is(calls.length, 0, 'not yet called')
|
||||
})
|
||||
Generated
Vendored
+426
@@ -0,0 +1,426 @@
|
||||
'use strict'
|
||||
|
||||
// TODO: move to per-method test files
|
||||
|
||||
const test = require('tape')
|
||||
const { Buffer } = require('buffer')
|
||||
const { mockLevel, mockChainedBatch, nullishEncoding } = require('../util')
|
||||
const identity = (v) => v
|
||||
|
||||
const utf8Manifest = { encodings: { utf8: true } }
|
||||
const dualManifest = { encodings: { utf8: true, buffer: true } }
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty
|
||||
|
||||
for (const deferred of [false, true]) {
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`get() encodes utf8 key (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const db = mockLevel({
|
||||
_get (key, options, callback) {
|
||||
t.is(key, '8')
|
||||
t.is(options.keyEncoding, 'utf8')
|
||||
t.is(options.valueEncoding, 'utf8')
|
||||
this.nextTick(callback, null, 'foo')
|
||||
}
|
||||
}, utf8Manifest)
|
||||
|
||||
if (!deferred) await db.open()
|
||||
t.same(await db.get(8), 'foo')
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`get() takes encoding options (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const db = mockLevel({
|
||||
_get (key, options, callback) {
|
||||
t.is(key, '[1,"2"]')
|
||||
t.is(options.keyEncoding, 'utf8')
|
||||
t.is(options.valueEncoding, 'utf8')
|
||||
this.nextTick(callback, null, '123')
|
||||
}
|
||||
}, utf8Manifest)
|
||||
|
||||
if (!deferred) await db.open()
|
||||
t.same(await db.get([1, '2'], { keyEncoding: 'json', valueEncoding: 'json' }), 123)
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`get() with custom value encoding that wants a buffer (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const db = mockLevel({
|
||||
_get (key, options, callback) {
|
||||
t.same(key, 'key')
|
||||
t.same(options, { keyEncoding: 'utf8', valueEncoding: 'buffer' })
|
||||
this.nextTick(callback, null, Buffer.alloc(1))
|
||||
}
|
||||
}, dualManifest, {
|
||||
keyEncoding: 'utf8',
|
||||
valueEncoding: { encode: identity, decode: identity, format: 'buffer' }
|
||||
})
|
||||
|
||||
if (!deferred) await db.open()
|
||||
t.same(await db.get('key'), Buffer.alloc(1))
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`get() with custom value encoding that wants a string (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const db = mockLevel({
|
||||
_get (key, options, callback) {
|
||||
t.same(key, Buffer.from('key'))
|
||||
t.same(options, { keyEncoding: 'buffer', valueEncoding: 'utf8' })
|
||||
this.nextTick(callback, null, 'x')
|
||||
}
|
||||
}, dualManifest, {
|
||||
keyEncoding: 'buffer',
|
||||
valueEncoding: { encode: identity, decode: identity, format: 'utf8' }
|
||||
})
|
||||
|
||||
if (!deferred) await db.open()
|
||||
t.same(await db.get(Buffer.from('key')), 'x')
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`put() encodes utf8 key and value (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const db = mockLevel({
|
||||
_put (key, value, options, callback) {
|
||||
t.is(key, '8')
|
||||
t.is(value, '4')
|
||||
t.is(options.keyEncoding, 'utf8')
|
||||
t.is(options.valueEncoding, 'utf8')
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}, utf8Manifest)
|
||||
|
||||
if (!deferred) await db.open()
|
||||
await db.put(8, 4)
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`put() takes encoding options (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const db = mockLevel({
|
||||
_put (key, value, options, callback) {
|
||||
t.is(key, '[1,"2"]')
|
||||
t.is(value, '{"x":3}')
|
||||
t.is(options.keyEncoding, 'utf8')
|
||||
t.is(options.valueEncoding, 'utf8')
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}, utf8Manifest)
|
||||
|
||||
if (!deferred) await db.open()
|
||||
await db.put([1, '2'], { x: 3 }, { keyEncoding: 'json', valueEncoding: 'json' })
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`del() encodes utf8 key (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const db = mockLevel({
|
||||
_del (key, options, callback) {
|
||||
t.is(key, '2')
|
||||
t.is(options.keyEncoding, 'utf8')
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}, utf8Manifest)
|
||||
|
||||
if (!deferred) await db.open()
|
||||
await db.del(2)
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`del() takes keyEncoding option (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const db = mockLevel({
|
||||
_del (key, options, callback) {
|
||||
t.is(key, '[1,"2"]')
|
||||
t.is(options.keyEncoding, 'utf8')
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}, utf8Manifest)
|
||||
|
||||
if (!deferred) await db.open()
|
||||
await db.del([1, '2'], { keyEncoding: 'json' })
|
||||
})
|
||||
|
||||
test(`getMany() encodes utf8 key (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const db = mockLevel({
|
||||
_getMany (keys, options, callback) {
|
||||
t.same(keys, ['8', '29'])
|
||||
t.is(options.keyEncoding, 'utf8')
|
||||
t.is(options.valueEncoding, 'utf8')
|
||||
this.nextTick(callback, null, ['foo', 'bar'])
|
||||
}
|
||||
}, utf8Manifest)
|
||||
|
||||
if (!deferred) await db.open()
|
||||
t.same(await db.getMany([8, 29]), ['foo', 'bar'])
|
||||
})
|
||||
|
||||
test(`getMany() takes encoding options (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const db = mockLevel({
|
||||
_getMany (keys, options, callback) {
|
||||
t.same(keys, ['[1,"2"]', '"x"'])
|
||||
t.is(options.keyEncoding, 'utf8')
|
||||
t.is(options.valueEncoding, 'utf8')
|
||||
this.nextTick(callback, null, ['123', '"hi"'])
|
||||
}
|
||||
}, utf8Manifest)
|
||||
|
||||
if (!deferred) await db.open()
|
||||
t.same(await db.getMany([[1, '2'], 'x'], { keyEncoding: 'json', valueEncoding: 'json' }), [123, 'hi'])
|
||||
})
|
||||
|
||||
test(`getMany() with custom value encoding that wants a buffer (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const db = mockLevel({
|
||||
_getMany (keys, options, callback) {
|
||||
t.same(keys, ['key'])
|
||||
t.same(options, { keyEncoding: 'utf8', valueEncoding: 'buffer' })
|
||||
this.nextTick(callback, null, [Buffer.alloc(1)])
|
||||
}
|
||||
}, dualManifest, {
|
||||
keyEncoding: 'utf8',
|
||||
valueEncoding: { encode: identity, decode: identity, format: 'buffer' }
|
||||
})
|
||||
|
||||
if (!deferred) await db.open()
|
||||
t.same(await db.getMany(['key']), [Buffer.alloc(1)])
|
||||
})
|
||||
|
||||
test(`getMany() with custom value encoding that wants a string (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const db = mockLevel({
|
||||
_getMany (keys, options, callback) {
|
||||
t.same(keys, [Buffer.from('key')])
|
||||
t.same(options, { keyEncoding: 'buffer', valueEncoding: 'utf8' })
|
||||
this.nextTick(callback, null, ['x'])
|
||||
}
|
||||
}, dualManifest, {
|
||||
keyEncoding: 'buffer',
|
||||
valueEncoding: { encode: identity, decode: identity, format: 'utf8' }
|
||||
})
|
||||
|
||||
if (!deferred) await db.open()
|
||||
t.same(await db.getMany([Buffer.from('key')]), ['x'])
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`chainedBatch.put() and del() encode utf8 key and value (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(deferred ? 2 : 4)
|
||||
|
||||
let db
|
||||
|
||||
if (deferred) {
|
||||
db = mockLevel({
|
||||
_batch (array, options, callback) {
|
||||
t.same(array, [
|
||||
{ type: 'put', key: '1', value: '2', keyEncoding: 'utf8', valueEncoding: 'utf8' },
|
||||
{ type: 'del', key: '3', keyEncoding: 'utf8' }
|
||||
])
|
||||
t.same(options, {})
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}, utf8Manifest)
|
||||
} else {
|
||||
db = mockLevel({
|
||||
_chainedBatch () {
|
||||
return mockChainedBatch(this, {
|
||||
_put: function (key, value, options) {
|
||||
t.same({ key, value }, { key: '1', value: '2' })
|
||||
t.same(options, { keyEncoding: 'utf8', valueEncoding: 'utf8' })
|
||||
},
|
||||
_del: function (key, options) {
|
||||
t.is(key, '3')
|
||||
t.same(options, { keyEncoding: 'utf8' })
|
||||
}
|
||||
})
|
||||
}
|
||||
}, utf8Manifest)
|
||||
}
|
||||
|
||||
if (!deferred) await db.open()
|
||||
await db.batch().put(1, 2).del(3).write()
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`chainedBatch.put() and del() take encoding options (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(deferred ? 2 : 4)
|
||||
|
||||
let db
|
||||
|
||||
const putOptions = { keyEncoding: 'json', valueEncoding: 'json' }
|
||||
const delOptions = { keyEncoding: 'json' }
|
||||
|
||||
if (deferred) {
|
||||
db = mockLevel({
|
||||
_batch (array, options, callback) {
|
||||
t.same(array, [
|
||||
{ type: 'put', key: '"1"', value: '{"x":[2]}', keyEncoding: 'utf8', valueEncoding: 'utf8' },
|
||||
{ type: 'del', key: '"3"', keyEncoding: 'utf8' }
|
||||
])
|
||||
t.same(options, {})
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}, utf8Manifest)
|
||||
} else {
|
||||
db = mockLevel({
|
||||
_chainedBatch () {
|
||||
return mockChainedBatch(this, {
|
||||
_put: function (key, value, options) {
|
||||
t.same({ key, value }, { key: '"1"', value: '{"x":[2]}' })
|
||||
t.same(options, { keyEncoding: 'utf8', valueEncoding: 'utf8' })
|
||||
},
|
||||
_del: function (key, options) {
|
||||
t.is(key, '"3"')
|
||||
t.same(options, { keyEncoding: 'utf8' })
|
||||
}
|
||||
})
|
||||
}
|
||||
}, utf8Manifest)
|
||||
}
|
||||
|
||||
if (!deferred) await db.open()
|
||||
await db.batch().put('1', { x: [2] }, putOptions).del('3', delOptions).write()
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`clear() receives keyEncoding option (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const db = mockLevel({
|
||||
_clear: function (options, callback) {
|
||||
t.same(options, { keyEncoding: 'utf8', reverse: false, limit: -1 })
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}, utf8Manifest)
|
||||
|
||||
if (!deferred) await db.open()
|
||||
await db.clear()
|
||||
})
|
||||
|
||||
test(`clear() takes keyEncoding option (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const db = mockLevel({
|
||||
_clear: function (options, callback) {
|
||||
t.same(options, { keyEncoding: 'utf8', gt: '"a"', reverse: false, limit: -1 })
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}, utf8Manifest)
|
||||
|
||||
if (!deferred) await db.open()
|
||||
await db.clear({ keyEncoding: 'json', gt: 'a' })
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`clear() encodes range options (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const keyEncoding = {
|
||||
format: 'utf8',
|
||||
encode: function (key) {
|
||||
return 'encoded_' + key
|
||||
},
|
||||
decode: identity
|
||||
}
|
||||
|
||||
const db = mockLevel({
|
||||
_clear: function (options, callback) {
|
||||
t.is(options.gt, 'encoded_1')
|
||||
t.is(options.gte, 'encoded_2')
|
||||
t.is(options.lt, 'encoded_3')
|
||||
t.is(options.lte, 'encoded_4')
|
||||
t.is(options.foo, 5)
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}, utf8Manifest, { keyEncoding })
|
||||
|
||||
if (!deferred) await db.open()
|
||||
await db.clear({ gt: 1, gte: 2, lt: 3, lte: 4, foo: 5 })
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`clear() does not strip nullish range options (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(12)
|
||||
|
||||
const db1 = mockLevel({
|
||||
_clear: function (options, callback) {
|
||||
t.is(options.gt, '\x00', 'encoded null')
|
||||
t.is(options.gte, '\x00', 'encoded null')
|
||||
t.is(options.lt, '\x00', 'encoded null')
|
||||
t.is(options.lte, '\x00', 'encoded null')
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}, utf8Manifest, { keyEncoding: nullishEncoding, valueEncoding: nullishEncoding })
|
||||
|
||||
const db2 = mockLevel({
|
||||
_clear: function (options, callback) {
|
||||
t.is(hasOwnProperty.call(options, 'gt'), true)
|
||||
t.is(hasOwnProperty.call(options, 'gte'), true)
|
||||
t.is(hasOwnProperty.call(options, 'lt'), true)
|
||||
t.is(hasOwnProperty.call(options, 'lte'), true)
|
||||
|
||||
t.is(options.gt, '\xff', 'encoded undefined')
|
||||
t.is(options.gte, '\xff', 'encoded undefined')
|
||||
t.is(options.lt, '\xff', 'encoded undefined')
|
||||
t.is(options.lte, '\xff', 'encoded undefined')
|
||||
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}, utf8Manifest, { keyEncoding: nullishEncoding, valueEncoding: nullishEncoding })
|
||||
|
||||
if (!deferred) {
|
||||
await Promise.all([db1.open(), db2.open()])
|
||||
}
|
||||
|
||||
const promise1 = db1.clear({
|
||||
gt: null,
|
||||
gte: null,
|
||||
lt: null,
|
||||
lte: null
|
||||
})
|
||||
|
||||
const promise2 = db2.clear({
|
||||
gt: undefined,
|
||||
gte: undefined,
|
||||
lt: undefined,
|
||||
lte: undefined
|
||||
})
|
||||
|
||||
await Promise.all([promise1, promise2])
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`clear() does not add nullish range options (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const db = mockLevel({
|
||||
_clear: function (options, callback) {
|
||||
t.is(hasOwnProperty.call(options, 'gt'), false)
|
||||
t.is(hasOwnProperty.call(options, 'gte'), false)
|
||||
t.is(hasOwnProperty.call(options, 'lt'), false)
|
||||
t.is(hasOwnProperty.call(options, 'lte'), false)
|
||||
this.nextTick(callback)
|
||||
}
|
||||
})
|
||||
|
||||
if (!deferred) await db.open()
|
||||
await db.clear({})
|
||||
})
|
||||
}
|
||||
Generated
Vendored
+829
@@ -0,0 +1,829 @@
|
||||
'use strict'
|
||||
|
||||
const test = require('tape')
|
||||
const { Buffer } = require('buffer')
|
||||
const { AbstractLevel } = require('../..')
|
||||
const { AbstractIterator, AbstractKeyIterator, AbstractValueIterator } = require('../..')
|
||||
const { mockLevel, mockIterator, nullishEncoding } = require('../util')
|
||||
|
||||
const identity = (v) => v
|
||||
const utf8Manifest = { encodings: { utf8: true } }
|
||||
const dualManifest = { encodings: { utf8: true, buffer: true } }
|
||||
const tripleManifest = { encodings: { utf8: true, buffer: true, view: true } }
|
||||
|
||||
for (const deferred of [false, true]) {
|
||||
// Also test default fallback implementations of keys() and values()
|
||||
for (const [mode, def] of [['iterator', false], ['keys', false], ['values', false], ['keys', true], ['values', true]]) {
|
||||
const Ctor = mode === 'iterator' || def ? AbstractIterator : mode === 'keys' ? AbstractKeyIterator : AbstractValueIterator
|
||||
const privateMethod = def ? '_iterator' : '_' + mode
|
||||
const publicMethod = mode
|
||||
|
||||
test(`${mode}() (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
t.plan(4)
|
||||
|
||||
let called = false
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
t.is(this, db, 'thisArg is correct')
|
||||
t.is(arguments.length, 1, 'got one argument')
|
||||
|
||||
const kvOptions = mode === 'iterator' || def
|
||||
? { keys: mode !== 'values', values: mode !== 'keys' }
|
||||
: {}
|
||||
|
||||
t.same(options, {
|
||||
reverse: false,
|
||||
limit: -1,
|
||||
keyEncoding: 'utf8',
|
||||
valueEncoding: 'utf8',
|
||||
...kvOptions
|
||||
})
|
||||
|
||||
called = true
|
||||
return new Ctor(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel(tripleManifest)
|
||||
if (!deferred) await db.open()
|
||||
|
||||
db[publicMethod]()
|
||||
t.is(called, !deferred)
|
||||
if (deferred) await db.open()
|
||||
})
|
||||
|
||||
test(`${mode}() with custom options (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
t.plan(3)
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
t.is(options.foo, 123)
|
||||
t.is(options.reverse, true)
|
||||
t.is(options.limit, 1)
|
||||
|
||||
return new Ctor(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel(tripleManifest)
|
||||
if (!deferred) await db.open()
|
||||
db[publicMethod]({ foo: 123, reverse: true, limit: 1 })
|
||||
if (deferred) await db.open()
|
||||
})
|
||||
|
||||
for (const limit of [2, 0]) {
|
||||
test(`${mode}().next() skips _next() when limit ${limit} is reached (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
let calls = 0
|
||||
let yielded = 0
|
||||
|
||||
class MockIterator extends Ctor {
|
||||
_next (callback) {
|
||||
calls++
|
||||
|
||||
if (mode === 'iterator' || def) {
|
||||
this.nextTick(callback, null, 'a', 'a')
|
||||
} else {
|
||||
this.nextTick(callback, null, 'a')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel(utf8Manifest)
|
||||
if (!deferred) await db.open()
|
||||
|
||||
const it = db[publicMethod]({ limit })
|
||||
|
||||
for (let i = 0; i < limit + 2; i++) {
|
||||
const item = await it.next()
|
||||
if (item === undefined) break
|
||||
yielded++
|
||||
}
|
||||
|
||||
t.is(it.count, limit, 'final count matches limit')
|
||||
t.is(calls, limit)
|
||||
t.is(yielded, limit)
|
||||
})
|
||||
|
||||
test(`${mode}().nextv() skips _nextv() when limit ${limit} is reached (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
let calls = 0
|
||||
let yielded = 0
|
||||
|
||||
class MockIterator extends Ctor {
|
||||
_nextv (size, options, callback) {
|
||||
calls++
|
||||
|
||||
if (mode === 'iterator' || def) {
|
||||
this.nextTick(callback, null, [['a', 'a']])
|
||||
} else {
|
||||
this.nextTick(callback, null, ['a'])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel(utf8Manifest)
|
||||
if (!deferred) await db.open()
|
||||
|
||||
const it = db[publicMethod]({ limit })
|
||||
|
||||
for (let i = 0; i < limit + 2; i++) {
|
||||
const items = await it.nextv(1)
|
||||
yielded += items.length
|
||||
if (items.length === 0) break
|
||||
}
|
||||
|
||||
t.is(it.count, limit, 'final count matches limit')
|
||||
t.is(calls, limit)
|
||||
t.is(yielded, limit)
|
||||
})
|
||||
|
||||
test(`${mode}().all() skips _all() when limit ${limit} is reached (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
let nextCount = 0
|
||||
class MockIterator extends Ctor {
|
||||
_next (callback) {
|
||||
if (++nextCount > 10) {
|
||||
throw new Error('Potential infinite loop')
|
||||
} else if (mode === 'iterator' || def) {
|
||||
this.nextTick(callback, null, 'a', 'a')
|
||||
} else {
|
||||
this.nextTick(callback, null, 'a')
|
||||
}
|
||||
}
|
||||
|
||||
_all (options, callback) {
|
||||
t.fail('should not be called')
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel(utf8Manifest)
|
||||
if (!deferred) await db.open()
|
||||
|
||||
const it = db[publicMethod]({ limit })
|
||||
|
||||
// Use next() because all() auto-closes and thus can't be used twice atm
|
||||
for (let i = 0; i < limit; i++) await it.next()
|
||||
|
||||
t.same(await it.all(), [])
|
||||
})
|
||||
}
|
||||
|
||||
test(`${mode}().nextv() reduces size for _nextv() when near limit (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
class MockIterator extends Ctor {
|
||||
_nextv (size, options, callback) {
|
||||
if (mode === 'iterator' || def) {
|
||||
this.nextTick(callback, null, Array(size).fill(['a', 'a']))
|
||||
} else {
|
||||
this.nextTick(callback, null, Array(size).fill('a'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel(utf8Manifest)
|
||||
if (!deferred) await db.open()
|
||||
|
||||
const it = db[publicMethod]({ limit: 3 })
|
||||
|
||||
t.is((await it.nextv(2)).length, 2)
|
||||
t.is((await it.nextv(2)).length, 1)
|
||||
t.is((await it.nextv(2)).length, 0)
|
||||
})
|
||||
|
||||
test(`${mode}().count increments by next(), nextv() and all() (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
class MockIterator extends Ctor {
|
||||
_next (callback) {
|
||||
if (mode === 'iterator' || def) {
|
||||
this.nextTick(callback, null, 'a', 'a')
|
||||
} else {
|
||||
this.nextTick(callback, null, 'a')
|
||||
}
|
||||
}
|
||||
|
||||
_nextv (size, options, callback) {
|
||||
if (mode === 'iterator' || def) {
|
||||
this.nextTick(callback, null, [['a', 'a'], ['b', 'b']])
|
||||
} else {
|
||||
this.nextTick(callback, null, ['a', 'b'])
|
||||
}
|
||||
}
|
||||
|
||||
_all (options, callback) {
|
||||
if (mode === 'iterator' || def) {
|
||||
this.nextTick(callback, null, [['c', 'c'], ['d', 'd'], ['e', 'e']])
|
||||
} else {
|
||||
this.nextTick(callback, null, ['c', 'd', 'e'])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel(utf8Manifest)
|
||||
if (!deferred) await db.open()
|
||||
|
||||
const it = db[publicMethod]()
|
||||
|
||||
for (let i = 0; i < 2; i++) {
|
||||
t.isNot(await it.next(), undefined) // 2 * 1 = 2
|
||||
t.is((await it.nextv(2)).length, 2) // 2 * 2 = 4
|
||||
}
|
||||
|
||||
t.is(it.count, 2 + 4)
|
||||
t.is((await it.all()).length, 3)
|
||||
t.is(it.count, 2 + 4 + 3)
|
||||
})
|
||||
|
||||
test(`${mode}() forwards encoding options (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
t.plan(3)
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
t.is(options.keyEncoding, 'utf8')
|
||||
t.is(options.valueEncoding, 'buffer')
|
||||
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
class MockIterator extends Ctor {
|
||||
_next (callback) {
|
||||
if (mode === 'iterator' || def) {
|
||||
this.nextTick(callback, null, '281', Buffer.from('a'))
|
||||
} else if (mode === 'keys') {
|
||||
this.nextTick(callback, null, '281')
|
||||
} else {
|
||||
this.nextTick(callback, null, Buffer.from('a'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel(dualManifest)
|
||||
if (!deferred) await db.open()
|
||||
|
||||
const item = await db[publicMethod]({ keyEncoding: 'json', valueEncoding: 'hex' }).next()
|
||||
t.same(item, mode === 'iterator' ? [281, '61'] : mode === 'keys' ? 281 : '61')
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`${mode}() with custom encodings that want a buffer (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
t.plan(5)
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
t.is(options.keyEncoding, 'buffer')
|
||||
t.is(options.valueEncoding, 'buffer')
|
||||
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
class MockIterator extends Ctor {
|
||||
_next (callback) {
|
||||
if (mode === 'iterator' || def) {
|
||||
this.nextTick(callback, null, Buffer.from('a'), Buffer.from('b'))
|
||||
} else if (mode === 'keys') {
|
||||
this.nextTick(callback, null, Buffer.from('a'))
|
||||
} else {
|
||||
this.nextTick(callback, null, Buffer.from('b'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel(dualManifest)
|
||||
const encoding = { encode: spy(identity), decode: spy(identity), format: 'buffer' }
|
||||
if (!deferred) await db.open()
|
||||
|
||||
const it = db[publicMethod]({ keyEncoding: encoding, valueEncoding: encoding })
|
||||
const item = await it.next()
|
||||
|
||||
t.is(encoding.encode.calls, 0, 'did not need to encode anything')
|
||||
t.is(encoding.decode.calls, mode === 'iterator' ? 2 : 1)
|
||||
t.same(item, mode === 'iterator' ? [Buffer.from('a'), Buffer.from('b')] : Buffer.from(mode === 'keys' ? 'a' : 'b'))
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`${mode}() with custom encodings that want a string (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
t.plan(5)
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
t.is(options.keyEncoding, 'utf8')
|
||||
t.is(options.valueEncoding, 'utf8')
|
||||
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
class MockIterator extends Ctor {
|
||||
_next (callback) {
|
||||
if (mode === 'iterator' || def) {
|
||||
this.nextTick(callback, null, 'a', 'b')
|
||||
} else if (mode === 'keys') {
|
||||
this.nextTick(callback, null, 'a')
|
||||
} else {
|
||||
this.nextTick(callback, null, 'b')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel(dualManifest)
|
||||
const encoding = { encode: spy(identity), decode: spy(identity), format: 'utf8' }
|
||||
if (!deferred) await db.open()
|
||||
|
||||
const it = db[publicMethod]({ keyEncoding: encoding, valueEncoding: encoding })
|
||||
const item = await it.next()
|
||||
|
||||
t.is(encoding.encode.calls, 0, 'did not need to encode anything')
|
||||
t.is(encoding.decode.calls, mode === 'iterator' ? 2 : 1)
|
||||
t.same(item, mode === 'iterator' ? ['a', 'b'] : mode === 'keys' ? 'a' : 'b')
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`${mode}() encodes range options (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
t.plan(6)
|
||||
|
||||
let calls = 0
|
||||
const keyEncoding = {
|
||||
format: 'utf8',
|
||||
encode (key) {
|
||||
calls++
|
||||
return 'encoded_' + key
|
||||
},
|
||||
decode: identity
|
||||
}
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
t.is(options.gt, 'encoded_3')
|
||||
t.is(options.gte, 'encoded_4')
|
||||
t.is(options.lt, 'encoded_5')
|
||||
t.is(options.lte, 'encoded_6')
|
||||
t.is(options.foo, 7)
|
||||
return new Ctor(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel(utf8Manifest, { keyEncoding })
|
||||
if (!deferred) await db.open()
|
||||
await db[publicMethod]({ gt: 3, gte: 4, lt: 5, lte: 6, foo: 7 }).next()
|
||||
t.is(calls, 4)
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`${mode}() does not strip nullish range options (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
t.plan(12)
|
||||
|
||||
const db1 = mockLevel({
|
||||
[privateMethod] (options) {
|
||||
t.is(options.gt, '\x00', 'encoded null')
|
||||
t.is(options.gte, '\x00', 'encoded null')
|
||||
t.is(options.lt, '\x00', 'encoded null')
|
||||
t.is(options.lte, '\x00', 'encoded null')
|
||||
|
||||
return new Ctor(this, options)
|
||||
}
|
||||
}, utf8Manifest, { keyEncoding: nullishEncoding, valueEncoding: nullishEncoding })
|
||||
|
||||
const db2 = mockLevel({
|
||||
[privateMethod] (options) {
|
||||
t.is(hasOwnProperty.call(options, 'gt'), true)
|
||||
t.is(hasOwnProperty.call(options, 'gte'), true)
|
||||
t.is(hasOwnProperty.call(options, 'lt'), true)
|
||||
t.is(hasOwnProperty.call(options, 'lte'), true)
|
||||
|
||||
t.is(options.gt, '\xff', 'encoded undefined')
|
||||
t.is(options.gte, '\xff', 'encoded undefined')
|
||||
t.is(options.lt, '\xff', 'encoded undefined')
|
||||
t.is(options.lte, '\xff', 'encoded undefined')
|
||||
|
||||
return new Ctor(this, options)
|
||||
}
|
||||
}, utf8Manifest, { keyEncoding: nullishEncoding, valueEncoding: nullishEncoding })
|
||||
|
||||
if (!deferred) {
|
||||
await Promise.all([db1.open(), db2.open()])
|
||||
}
|
||||
|
||||
const promise1 = db1[publicMethod]({
|
||||
gt: null,
|
||||
gte: null,
|
||||
lt: null,
|
||||
lte: null
|
||||
}).next()
|
||||
|
||||
const promise2 = db2[publicMethod]({
|
||||
gt: undefined,
|
||||
gte: undefined,
|
||||
lt: undefined,
|
||||
lte: undefined
|
||||
}).next()
|
||||
|
||||
return Promise.all([promise1, promise2])
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`${mode}() does not add nullish range options (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const db = mockLevel({
|
||||
[privateMethod] (options) {
|
||||
t.is(hasOwnProperty.call(options, 'gt'), false)
|
||||
t.is(hasOwnProperty.call(options, 'gte'), false)
|
||||
t.is(hasOwnProperty.call(options, 'lt'), false)
|
||||
t.is(hasOwnProperty.call(options, 'lte'), false)
|
||||
|
||||
return new Ctor(this, options)
|
||||
}
|
||||
})
|
||||
|
||||
if (!deferred) await db.open()
|
||||
await db[publicMethod]({}).next()
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`${mode}() encodes seek target (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
t.plan(2)
|
||||
|
||||
const db = mockLevel({
|
||||
[privateMethod] (options) {
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}, utf8Manifest, { keyEncoding: 'json' })
|
||||
|
||||
class MockIterator extends Ctor {
|
||||
_seek (target, options) {
|
||||
t.is(target, '"a"', 'encoded once')
|
||||
t.same(options, { keyEncoding: 'utf8' })
|
||||
}
|
||||
}
|
||||
|
||||
if (!deferred) await db.open()
|
||||
const it = db[publicMethod]()
|
||||
it.seek('a')
|
||||
await it.next()
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`${mode}() encodes seek target with custom encoding (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const targets = []
|
||||
const db = mockLevel({
|
||||
[privateMethod] (options) {
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}, utf8Manifest)
|
||||
|
||||
class MockIterator extends Ctor {
|
||||
_seek (target) {
|
||||
targets.push(target)
|
||||
}
|
||||
}
|
||||
|
||||
if (!deferred) await db.open()
|
||||
|
||||
db[publicMethod]().seek('a')
|
||||
db[publicMethod]({ keyEncoding: 'json' }).seek('a')
|
||||
db[publicMethod]().seek('b', { keyEncoding: 'json' })
|
||||
|
||||
await db.open()
|
||||
t.same(targets, ['a', '"a"', '"b"'], 'encoded targets')
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`${mode}() encodes nullish seek target (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const targets = []
|
||||
const db = mockLevel({
|
||||
[privateMethod] (options) {
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}, utf8Manifest, { keyEncoding: { encode: String, decode: identity, format: 'utf8' } })
|
||||
|
||||
class MockIterator extends Ctor {
|
||||
_seek (target) {
|
||||
targets.push(target)
|
||||
}
|
||||
}
|
||||
|
||||
if (!deferred) await db.open()
|
||||
|
||||
// Unlike keys, nullish targets should not be rejected;
|
||||
// assume that the encoding gives these types meaning.
|
||||
db[publicMethod]().seek(null)
|
||||
db[publicMethod]().seek(undefined)
|
||||
|
||||
await db.open()
|
||||
t.same(targets, ['null', 'undefined'], 'encoded')
|
||||
})
|
||||
|
||||
test(`${mode}() has default nextv() (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
const sizes = [[1, [0]], [1, [1]], [2, [2]], [3, [3]]]
|
||||
t.plan(sizes.length * 2)
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
let pos = 0
|
||||
class MockIterator extends Ctor {
|
||||
_next (callback) {
|
||||
if (mode === 'iterator' || def) {
|
||||
this.nextTick(callback, null, 'k' + pos, 'v' + (pos++))
|
||||
} else if (mode === 'keys') {
|
||||
this.nextTick(callback, null, 'k' + (pos++))
|
||||
} else {
|
||||
this.nextTick(callback, null, 'v' + (pos++))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel(utf8Manifest)
|
||||
if (!deferred) await db.open()
|
||||
|
||||
let expectedPos = 0
|
||||
const it = db[publicMethod]()
|
||||
|
||||
for (const [size, args] of sizes) {
|
||||
const actual = await it.nextv(...args)
|
||||
const expected = []
|
||||
|
||||
for (let i = 0; i < size; i++) {
|
||||
const pos = expectedPos++
|
||||
if (mode === 'iterator') expected.push(['k' + pos, 'v' + pos])
|
||||
else if (mode === 'keys') expected.push('k' + pos)
|
||||
else expected.push('v' + pos)
|
||||
}
|
||||
|
||||
t.is(actual.length, size)
|
||||
t.same(actual, expected)
|
||||
}
|
||||
})
|
||||
|
||||
test(`${mode}() default nextv() forwards next() error (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
t.plan(2)
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
class MockIterator extends Ctor {
|
||||
_next (callback) {
|
||||
t.pass('called')
|
||||
this.nextTick(callback, new Error('test'))
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel(utf8Manifest)
|
||||
if (!deferred) await db.open()
|
||||
|
||||
try {
|
||||
await db[publicMethod]().nextv(10)
|
||||
} catch (err) {
|
||||
t.is(err.message, 'test')
|
||||
}
|
||||
})
|
||||
|
||||
test(`${mode}() has default all() (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
t.plan(8)
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
let pos = 0
|
||||
class MockIterator extends Ctor {
|
||||
_nextv (size, options, callback) {
|
||||
t.is(size, 1000)
|
||||
t.same(options, {})
|
||||
|
||||
if (pos === 4) {
|
||||
this.nextTick(callback, null, [])
|
||||
} else if (mode === 'iterator' || def) {
|
||||
this.nextTick(callback, null, [[String(pos++), 'a'], [String(pos++), 'b']])
|
||||
} else if (mode === 'keys') {
|
||||
this.nextTick(callback, null, [String(pos++), String(pos++)])
|
||||
} else {
|
||||
pos += 2
|
||||
this.nextTick(callback, null, ['a', 'b'])
|
||||
}
|
||||
}
|
||||
|
||||
_close (callback) {
|
||||
t.pass('closed')
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel(utf8Manifest)
|
||||
if (!deferred) await db.open()
|
||||
|
||||
t.same(await db[publicMethod]().all(), [
|
||||
['0', 'a'],
|
||||
['1', 'b'],
|
||||
['2', 'a'],
|
||||
['3', 'b']
|
||||
].map(kv => mode === 'iterator' ? kv : kv[mode === 'keys' ? 0 : 1]))
|
||||
})
|
||||
|
||||
test(`${mode}() default all() forwards nextv() error (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
t.plan(2)
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
class MockIterator extends Ctor {
|
||||
_nextv (size, options, callback) {
|
||||
t.pass('called')
|
||||
this.nextTick(callback, new Error('test'))
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel(utf8Manifest)
|
||||
if (!deferred) await db.open()
|
||||
|
||||
try {
|
||||
await db[publicMethod]().all()
|
||||
} catch (err) {
|
||||
t.is(err.message, 'test')
|
||||
}
|
||||
})
|
||||
|
||||
test(`${mode}() custom all() (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
t.plan(3)
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
class MockIterator extends Ctor {
|
||||
_all (options, callback) {
|
||||
t.same(options, {})
|
||||
|
||||
if (mode === 'iterator' || def) {
|
||||
this.nextTick(callback, null, [['k0', 'v0'], ['k1', 'v1']])
|
||||
} else if (mode === 'keys') {
|
||||
this.nextTick(callback, null, ['k0', 'k1'])
|
||||
} else {
|
||||
this.nextTick(callback, null, ['v0', 'v1'])
|
||||
}
|
||||
}
|
||||
|
||||
_close (callback) {
|
||||
t.pass('closed')
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel(utf8Manifest)
|
||||
if (!deferred) await db.open()
|
||||
|
||||
t.same(await db[publicMethod]().all(), [
|
||||
['k0', 'v0'],
|
||||
['k1', 'v1']
|
||||
].map(kv => mode === 'iterator' ? kv : kv[mode === 'keys' ? 0 : 1]))
|
||||
})
|
||||
|
||||
test(`${mode}() custom all() forwards error and closes (deferred: ${deferred}, default implementation: ${def})`, async function (t) {
|
||||
t.plan(3)
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
class MockIterator extends Ctor {
|
||||
_all (options, callback) {
|
||||
t.pass('_all called')
|
||||
this.nextTick(callback, new Error('test'))
|
||||
}
|
||||
|
||||
_close (callback) {
|
||||
t.pass('closed')
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel(utf8Manifest)
|
||||
if (!deferred) await db.open()
|
||||
|
||||
try {
|
||||
await db[publicMethod]().all()
|
||||
} catch (err) {
|
||||
t.is(err.message, 'test')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (const deferred of [false, true]) {
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`iterator() skips decoding keys if options.keys is false (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const keyEncoding = {
|
||||
format: 'utf8',
|
||||
decode (key) {
|
||||
t.fail('should not be called')
|
||||
},
|
||||
encode: identity
|
||||
}
|
||||
|
||||
const db = mockLevel({
|
||||
_iterator (options) {
|
||||
t.is(options.keys, false)
|
||||
|
||||
return mockIterator(this, options, {
|
||||
_next (callback) {
|
||||
this.nextTick(callback, null, '', 'value')
|
||||
}
|
||||
})
|
||||
}
|
||||
}, utf8Manifest, { keyEncoding })
|
||||
|
||||
if (!deferred) await db.open()
|
||||
const [key, value] = await db.iterator({ keys: false }).next()
|
||||
|
||||
t.is(key, undefined, 'normalized key to undefined')
|
||||
t.is(value, 'value', 'got value')
|
||||
})
|
||||
|
||||
// NOTE: adapted from encoding-down
|
||||
test(`iterator() skips decoding values if options.values is false (deferred: ${deferred})`, async function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const valueEncoding = {
|
||||
format: 'utf8',
|
||||
decode (value) {
|
||||
t.fail('should not be called')
|
||||
},
|
||||
encode: identity
|
||||
}
|
||||
|
||||
const db = mockLevel({
|
||||
_iterator (options) {
|
||||
t.is(options.values, false)
|
||||
|
||||
return mockIterator(this, options, {
|
||||
_next (callback) {
|
||||
callback(null, 'key', '')
|
||||
}
|
||||
})
|
||||
}
|
||||
}, utf8Manifest, { valueEncoding })
|
||||
|
||||
if (!deferred) await db.open()
|
||||
const [key, value] = await db.iterator({ values: false }).next()
|
||||
|
||||
t.is(key, 'key', 'got key')
|
||||
t.is(value, undefined, 'normalized value to undefined')
|
||||
})
|
||||
}
|
||||
|
||||
function spy (fn) {
|
||||
const wrapped = function (...args) {
|
||||
wrapped.calls++
|
||||
return fn(...args)
|
||||
}
|
||||
wrapped.calls = 0
|
||||
return wrapped
|
||||
}
|
||||
Generated
Vendored
+931
@@ -0,0 +1,931 @@
|
||||
'use strict'
|
||||
|
||||
const test = require('tape')
|
||||
const { Buffer } = require('buffer')
|
||||
const { AbstractLevel, AbstractSublevel } = require('../..')
|
||||
const { AbstractIterator, AbstractKeyIterator, AbstractValueIterator } = require('../..')
|
||||
const nextTick = AbstractLevel.prototype.nextTick
|
||||
|
||||
class NoopLevel extends AbstractLevel {
|
||||
constructor (...args) {
|
||||
super(
|
||||
{ encodings: { utf8: true, buffer: true, view: true } },
|
||||
...args
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
test('sublevel is extensible', function (t) {
|
||||
t.plan(6)
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
_sublevel (name, options) {
|
||||
t.is(name, 'test')
|
||||
t.same(options, { separator: '!', customOption: 123 })
|
||||
|
||||
return new MockSublevel(this, name, {
|
||||
...options,
|
||||
manifest: {
|
||||
encodings: { ignored: true },
|
||||
additionalMethods: { test: true },
|
||||
events: { foo: true }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class MockSublevel extends AbstractSublevel {
|
||||
test () {
|
||||
this.emit('foo')
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel({
|
||||
encodings: { utf8: true },
|
||||
additionalMethods: { ignored: true },
|
||||
events: { ignored: true }
|
||||
})
|
||||
|
||||
const sub = db.sublevel('test', { customOption: 123 })
|
||||
|
||||
t.is(sub.supports.encodings.ignored, undefined)
|
||||
t.same(sub.supports.additionalMethods, { test: true })
|
||||
t.same(sub.supports.events, {
|
||||
foo: true,
|
||||
|
||||
// Added by AbstractLevel
|
||||
opening: true,
|
||||
open: true,
|
||||
closing: true,
|
||||
closed: true,
|
||||
put: true,
|
||||
del: true,
|
||||
batch: true,
|
||||
clear: true
|
||||
})
|
||||
|
||||
sub.on('foo', () => t.pass('emitted'))
|
||||
sub.test()
|
||||
})
|
||||
|
||||
// NOTE: adapted from subleveldown
|
||||
test('sublevel prefix and options', function (t) {
|
||||
t.test('empty prefix', function (t) {
|
||||
const sub = new NoopLevel().sublevel('')
|
||||
t.is(sub.prefix, '!!')
|
||||
t.end()
|
||||
})
|
||||
|
||||
t.test('prefix without options', function (t) {
|
||||
const sub = new NoopLevel().sublevel('prefix')
|
||||
t.is(sub.prefix, '!prefix!')
|
||||
t.end()
|
||||
})
|
||||
|
||||
t.test('prefix and separator option', function (t) {
|
||||
const sub = new NoopLevel().sublevel('prefix', { separator: '%' })
|
||||
t.is(sub.prefix, '%prefix%')
|
||||
t.end()
|
||||
})
|
||||
|
||||
t.test('separator is trimmed from prefix', function (t) {
|
||||
const sub1 = new NoopLevel().sublevel('!prefix')
|
||||
t.is(sub1.prefix, '!prefix!')
|
||||
|
||||
const sub2 = new NoopLevel().sublevel('prefix!')
|
||||
t.is(sub2.prefix, '!prefix!')
|
||||
|
||||
const sub3 = new NoopLevel().sublevel('!!prefix!!')
|
||||
t.is(sub3.prefix, '!prefix!')
|
||||
|
||||
const sub4 = new NoopLevel().sublevel('@prefix@', { separator: '@' })
|
||||
t.is(sub4.prefix, '@prefix@')
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
t.test('repeated separator can not result in empty prefix', function (t) {
|
||||
const sub = new NoopLevel().sublevel('!!!!')
|
||||
t.is(sub.prefix, '!!')
|
||||
t.end()
|
||||
})
|
||||
|
||||
t.test('invalid sublevel prefix', function (t) {
|
||||
t.throws(() => new NoopLevel().sublevel('foo\x05'), (err) => err.code === 'LEVEL_INVALID_PREFIX')
|
||||
t.throws(() => new NoopLevel().sublevel('foo\xff'), (err) => err.code === 'LEVEL_INVALID_PREFIX')
|
||||
t.throws(() => new NoopLevel().sublevel('foo!', { separator: '@' }), (err) => err.code === 'LEVEL_INVALID_PREFIX')
|
||||
t.end()
|
||||
})
|
||||
|
||||
t.test('legacy sublevel(down) options', function (t) {
|
||||
t.throws(() => new NoopLevel().sublevel('foo', 'bar'), (err) => err.code === 'LEVEL_LEGACY')
|
||||
t.throws(() => new NoopLevel().sublevel('foo', { open: () => {} }), (err) => err.code === 'LEVEL_LEGACY')
|
||||
t.end()
|
||||
})
|
||||
|
||||
// See https://github.com/Level/subleveldown/issues/78
|
||||
t.test('doubly nested sublevel has correct prefix', async function (t) {
|
||||
t.plan(1)
|
||||
|
||||
const keys = []
|
||||
class MockLevel extends AbstractLevel {
|
||||
_put (key, value, options, callback) {
|
||||
keys.push(key)
|
||||
nextTick(callback)
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel({ encodings: { utf8: true } })
|
||||
const sub1 = db.sublevel('1')
|
||||
const sub2 = sub1.sublevel('2')
|
||||
const sub3 = sub2.sublevel('3')
|
||||
|
||||
await sub1.put('a', 'value')
|
||||
await sub2.put('b', 'value')
|
||||
await sub3.put('c', 'value')
|
||||
|
||||
t.same(keys.sort(), [
|
||||
'!1!!2!!3!c',
|
||||
'!1!!2!b',
|
||||
'!1!a'
|
||||
])
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('sublevel.prefixKey()', function (t) {
|
||||
const db = new AbstractLevel({ encodings: { utf8: true, buffer: true, view: true } })
|
||||
const sub = db.sublevel('test')
|
||||
const textEncoder = new TextEncoder()
|
||||
|
||||
t.same(sub.prefixKey('', 'utf8'), '!test!')
|
||||
t.same(sub.prefixKey('a', 'utf8'), '!test!a')
|
||||
|
||||
t.same(sub.prefixKey(Buffer.from(''), 'buffer'), Buffer.from('!test!'))
|
||||
t.same(sub.prefixKey(Buffer.from('a'), 'buffer'), Buffer.from('!test!a'))
|
||||
|
||||
t.same(sub.prefixKey(textEncoder.encode(''), 'view'), textEncoder.encode('!test!'))
|
||||
t.same(sub.prefixKey(textEncoder.encode('a'), 'view'), textEncoder.encode('!test!a'))
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
// NOTE: adapted from subleveldown
|
||||
test('sublevel manifest and parent db', function (t) {
|
||||
t.test('sublevel inherits manifest from parent db', function (t) {
|
||||
const parent = new AbstractLevel({
|
||||
encodings: { utf8: true },
|
||||
seek: true,
|
||||
foo: true
|
||||
})
|
||||
const sub = parent.sublevel('')
|
||||
t.is(sub.supports.foo, true, 'AbstractSublevel inherits from parent')
|
||||
t.is(sub.supports.seek, true, 'AbstractSublevel inherits from parent')
|
||||
t.end()
|
||||
})
|
||||
|
||||
t.test('sublevel does not support additionalMethods', function (t) {
|
||||
const parent = new AbstractLevel({
|
||||
encodings: { utf8: true },
|
||||
additionalMethods: { foo: true }
|
||||
})
|
||||
|
||||
// We're expecting that AbstractSublevel removes the additionalMethod
|
||||
// because it can't automatically prefix any key(-like) arguments
|
||||
const sub = parent.sublevel('')
|
||||
t.same(sub.supports.additionalMethods, {})
|
||||
t.same(parent.supports.additionalMethods, { foo: true })
|
||||
t.is(typeof sub.foo, 'undefined', 'AbstractSublevel does not expose method')
|
||||
t.end()
|
||||
})
|
||||
|
||||
t.test('sublevel.db is set to parent db', function (t) {
|
||||
const db = new NoopLevel()
|
||||
const sub = db.sublevel('test')
|
||||
sub.once('open', function () {
|
||||
t.ok(sub.db instanceof NoopLevel)
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
// NOTE: adapted from subleveldown
|
||||
test('opening & closing sublevel', function (t) {
|
||||
t.test('error from open() does not bubble up to sublevel', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
_open (opts, cb) {
|
||||
nextTick(cb, new Error('error from underlying store'))
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel({ encodings: { buffer: true } })
|
||||
const sub = db.sublevel('test')
|
||||
|
||||
db.open((err) => {
|
||||
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
t.is(err && err.cause && err.cause.message, 'error from underlying store')
|
||||
})
|
||||
|
||||
sub.open((err) => {
|
||||
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
t.is(err && err.cause && err.cause.code, 'LEVEL_DATABASE_NOT_OPEN') // from db
|
||||
t.is(err && err.cause && err.cause.cause, undefined) // but does not have underlying error
|
||||
})
|
||||
})
|
||||
|
||||
t.test('cannot create a sublevel on a closed db', function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const db = new NoopLevel()
|
||||
|
||||
db.once('open', function () {
|
||||
db.close(function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
db.sublevel('test').open(function (err) {
|
||||
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN', 'sublevel not opened')
|
||||
|
||||
db.open(function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
db.sublevel('test').on('open', function () {
|
||||
t.pass('sublevel opened')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.test('can close db and sublevel once opened', function (t) {
|
||||
t.plan(3)
|
||||
|
||||
const db = new NoopLevel()
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err, 'no open error')
|
||||
const sub = db.sublevel('test')
|
||||
|
||||
sub.once('open', function () {
|
||||
db.close(function (err) {
|
||||
t.ifError(err, 'no close error')
|
||||
})
|
||||
|
||||
sub.close(function (err) {
|
||||
t.ifError(err, 'no close error')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.test('sublevel rejects operations if parent db is closed', function (t) {
|
||||
t.plan(9)
|
||||
|
||||
const db = new NoopLevel()
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err, 'no open error')
|
||||
|
||||
const sub = db.sublevel('test')
|
||||
const it = sub.iterator()
|
||||
|
||||
sub.once('open', function () {
|
||||
db.close(function (err) {
|
||||
t.ifError(err, 'no close error')
|
||||
|
||||
sub.put('foo', 'bar', verify)
|
||||
sub.get('foo', verify)
|
||||
sub.del('foo', verify)
|
||||
sub.clear(verify)
|
||||
sub.batch([{ type: 'del', key: 'foo' }], verify)
|
||||
|
||||
it.next(function (err) {
|
||||
t.is(err.code, 'LEVEL_ITERATOR_NOT_OPEN')
|
||||
it.close(t.ifError.bind(t))
|
||||
})
|
||||
|
||||
function verify (err) {
|
||||
t.is(err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.test('cannot close db while sublevel is opening', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const db = new NoopLevel()
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err, 'no open error')
|
||||
const sub = db.sublevel('test')
|
||||
|
||||
sub.open((err) => {
|
||||
t.is(err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
})
|
||||
|
||||
db.close(function (err) {
|
||||
t.ifError(err, 'no close error')
|
||||
t.is(sub.status, 'closed')
|
||||
t.is(db.status, 'closed')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.test('cannot create sublevel while db is closing', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
const db = new NoopLevel()
|
||||
|
||||
db.open(function (err) {
|
||||
t.ifError(err, 'no open error')
|
||||
|
||||
db.close(function (err) {
|
||||
t.ifError(err, 'no close error')
|
||||
t.is(db.status, 'closed')
|
||||
})
|
||||
|
||||
const sub = db.sublevel('test')
|
||||
|
||||
sub.open((err) => {
|
||||
t.is(err.code, 'LEVEL_DATABASE_NOT_OPEN')
|
||||
t.is(sub.status, 'closed')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.test('can wrap a sublevel and reopen the wrapped sublevel', function (t) {
|
||||
const db = new NoopLevel()
|
||||
const sub1 = db.sublevel('test1')
|
||||
const sub2 = sub1.sublevel('test2')
|
||||
|
||||
sub2.once('open', function () {
|
||||
verify()
|
||||
|
||||
sub2.close(function (err) {
|
||||
t.ifError(err, 'no close error')
|
||||
|
||||
// Prefixes should be the same after closing & reopening
|
||||
// See https://github.com/Level/subleveldown/issues/78
|
||||
sub2.open(function (err) {
|
||||
t.ifError(err, 'no open error')
|
||||
verify()
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function verify () {
|
||||
t.is(sub1.prefix, '!test1!', 'sub1 prefix ok')
|
||||
t.ok(sub1.db instanceof NoopLevel)
|
||||
t.is(sub2.prefix, '!test1!!test2!', 'sub2 prefix ok')
|
||||
t.ok(sub2.db instanceof NoopLevel)
|
||||
}
|
||||
})
|
||||
|
||||
// Also test default fallback implementations of keys() and values()
|
||||
for (const [mode, def] of [['iterator', false], ['keys', false], ['values', false], ['keys', true], ['values', true]]) {
|
||||
const Ctor = mode === 'iterator' || def ? AbstractIterator : mode === 'keys' ? AbstractKeyIterator : AbstractValueIterator
|
||||
const privateMethod = def ? '_iterator' : '_' + mode
|
||||
const publicMethod = mode
|
||||
|
||||
t.test(`error from sublevel.${mode}() bubbles up (default implementation: ${def})`, function (t) {
|
||||
t.plan(2)
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
class MockIterator extends Ctor {
|
||||
_next (callback) {
|
||||
this.nextTick(callback, new Error('next() error from parent database'))
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel({ encodings: { buffer: true } })
|
||||
const sub = db.sublevel('test')
|
||||
const it = sub[publicMethod]()
|
||||
|
||||
it.next(function (err) {
|
||||
t.is(err.message, 'next() error from parent database')
|
||||
|
||||
it.close(function () {
|
||||
t.pass('closed')
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('sublevel operations are prefixed', function (t) {
|
||||
t.test('sublevel.getMany() is prefixed', async function (t) {
|
||||
t.plan(2)
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
_getMany (keys, options, callback) {
|
||||
t.same(keys, ['!test!a', '!test!b'])
|
||||
t.same(options, { keyEncoding: 'utf8', valueEncoding: 'utf8' })
|
||||
nextTick(callback, null, ['1', '2'])
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel({ encodings: { utf8: true } })
|
||||
const sub = db.sublevel('test')
|
||||
|
||||
await sub.open()
|
||||
await sub.getMany(['a', 'b'])
|
||||
})
|
||||
|
||||
// Also test default fallback implementations of keys() and values()
|
||||
for (const [mode, def] of [['iterator', false], ['keys', false], ['values', false], ['keys', true], ['values', true]]) {
|
||||
const Ctor = mode === 'iterator' || def ? AbstractIterator : mode === 'keys' ? AbstractKeyIterator : AbstractValueIterator
|
||||
const privateMethod = def ? '_iterator' : '_' + mode
|
||||
const publicMethod = mode
|
||||
|
||||
for (const deferred of [false, true]) {
|
||||
t.test(`sublevel ${mode}.seek() target is prefixed (default implementation: ${def}, deferred: ${deferred})`, async function (t) {
|
||||
t.plan(2)
|
||||
|
||||
class MockIterator extends Ctor {
|
||||
_seek (target, options) {
|
||||
t.is(target, '!sub!123')
|
||||
t.is(options.keyEncoding, 'utf8')
|
||||
}
|
||||
}
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel({ encodings: { utf8: true } })
|
||||
const sub = db.sublevel('sub', { keyEncoding: 'json' })
|
||||
|
||||
if (!deferred) await sub.open()
|
||||
|
||||
const it = sub[publicMethod]()
|
||||
it.seek(123)
|
||||
|
||||
if (deferred) await sub.open()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
t.test('sublevel.clear() is prefixed', async function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const calls = []
|
||||
class MockLevel extends AbstractLevel {
|
||||
_clear (options, callback) {
|
||||
calls.push(options)
|
||||
nextTick(callback)
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel({ encodings: { utf8: true } })
|
||||
const sub = db.sublevel('sub')
|
||||
|
||||
const test = async (options, expected) => {
|
||||
await sub.clear(options)
|
||||
t.same(calls.shift(), expected)
|
||||
}
|
||||
|
||||
await sub.open()
|
||||
|
||||
await test(undefined, {
|
||||
gte: '!sub!',
|
||||
lte: '!sub"',
|
||||
keyEncoding: 'utf8',
|
||||
reverse: false,
|
||||
limit: -1
|
||||
})
|
||||
|
||||
await test({ gt: 'a' }, {
|
||||
gt: '!sub!a',
|
||||
lte: '!sub"',
|
||||
keyEncoding: 'utf8',
|
||||
reverse: false,
|
||||
limit: -1
|
||||
})
|
||||
|
||||
await test({ gte: 'a', lt: 'x' }, {
|
||||
gte: '!sub!a',
|
||||
lt: '!sub!x',
|
||||
keyEncoding: 'utf8',
|
||||
reverse: false,
|
||||
limit: -1
|
||||
})
|
||||
|
||||
await test({ lte: 'x' }, {
|
||||
gte: '!sub!',
|
||||
lte: '!sub!x',
|
||||
keyEncoding: 'utf8',
|
||||
reverse: false,
|
||||
limit: -1
|
||||
})
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('sublevel encodings', function (t) {
|
||||
// NOTE: adapted from subleveldown
|
||||
t.test('different sublevels can have different encodings', function (t) {
|
||||
t.plan(10)
|
||||
|
||||
const puts = []
|
||||
const gets = []
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
_put (key, value, { keyEncoding, valueEncoding }, callback) {
|
||||
puts.push({ key, value, keyEncoding, valueEncoding })
|
||||
nextTick(callback)
|
||||
}
|
||||
|
||||
_get (key, { keyEncoding, valueEncoding }, callback) {
|
||||
gets.push({ key, keyEncoding, valueEncoding })
|
||||
nextTick(callback, null, puts.shift().value)
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel({ encodings: { buffer: true, utf8: true } })
|
||||
const sub1 = db.sublevel('test1', { valueEncoding: 'json' })
|
||||
const sub2 = db.sublevel('test2', { keyEncoding: 'buffer', valueEncoding: 'buffer' })
|
||||
|
||||
sub1.put('foo', { some: 'json' }, function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
t.same(puts, [{
|
||||
key: '!test1!foo',
|
||||
value: '{"some":"json"}',
|
||||
keyEncoding: 'utf8',
|
||||
valueEncoding: 'utf8'
|
||||
}])
|
||||
|
||||
sub1.get('foo', function (err, value) {
|
||||
t.error(err, 'no error')
|
||||
t.same(value, { some: 'json' })
|
||||
t.same(gets.shift(), {
|
||||
key: '!test1!foo',
|
||||
keyEncoding: 'utf8',
|
||||
valueEncoding: 'utf8'
|
||||
})
|
||||
|
||||
sub2.put(Buffer.from([1, 2]), Buffer.from([3]), function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
t.same(puts, [{
|
||||
key: Buffer.from('!test2!\x01\x02'),
|
||||
value: Buffer.from([3]),
|
||||
keyEncoding: 'buffer',
|
||||
valueEncoding: 'buffer'
|
||||
}])
|
||||
|
||||
sub2.get(Buffer.from([1, 2]), function (err, value) {
|
||||
t.error(err, 'no error')
|
||||
t.same(value, Buffer.from([3]))
|
||||
t.same(gets.shift(), {
|
||||
key: Buffer.from('!test2!\x01\x02'),
|
||||
keyEncoding: 'buffer',
|
||||
valueEncoding: 'buffer'
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.test('sublevel indirectly supports transcoded encoding', function (t) {
|
||||
t.plan(5)
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
_put (key, value, { keyEncoding, valueEncoding }, callback) {
|
||||
t.same({ key, value, keyEncoding, valueEncoding }, {
|
||||
key: Buffer.from('!test!foo'),
|
||||
value: Buffer.from('{"some":"json"}'),
|
||||
keyEncoding: 'buffer',
|
||||
valueEncoding: 'buffer'
|
||||
})
|
||||
nextTick(callback)
|
||||
}
|
||||
|
||||
_get (key, { keyEncoding, valueEncoding }, callback) {
|
||||
t.same({ key, keyEncoding, valueEncoding }, {
|
||||
key: Buffer.from('!test!foo'),
|
||||
keyEncoding: 'buffer',
|
||||
valueEncoding: 'buffer'
|
||||
})
|
||||
nextTick(callback, null, Buffer.from('{"some":"json"}'))
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel({ encodings: { buffer: true } })
|
||||
const sub = db.sublevel('test', { valueEncoding: 'json' })
|
||||
|
||||
sub.put('foo', { some: 'json' }, function (err) {
|
||||
t.error(err, 'no error')
|
||||
|
||||
sub.get('foo', function (err, value) {
|
||||
t.error(err, 'no error')
|
||||
t.same(value, { some: 'json' })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.test('concatenating sublevel Buffer keys', function (t) {
|
||||
t.plan(10)
|
||||
|
||||
const key = Buffer.from('00ff', 'hex')
|
||||
const prefixedKey = Buffer.concat([Buffer.from('!test!'), key])
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
_put (key, value, options, callback) {
|
||||
t.is(options.keyEncoding, 'buffer')
|
||||
t.is(options.valueEncoding, 'buffer')
|
||||
t.same(key, prefixedKey)
|
||||
t.same(value, Buffer.from('bar'))
|
||||
nextTick(callback)
|
||||
}
|
||||
|
||||
_get (key, options, callback) {
|
||||
t.is(options.keyEncoding, 'buffer')
|
||||
t.is(options.valueEncoding, 'buffer')
|
||||
t.same(key, prefixedKey)
|
||||
nextTick(callback, null, Buffer.from('bar'))
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel({ encodings: { buffer: true } })
|
||||
const sub = db.sublevel('test', { keyEncoding: 'buffer' })
|
||||
|
||||
sub.put(key, 'bar', function (err) {
|
||||
t.ifError(err)
|
||||
sub.get(key, function (err, value) {
|
||||
t.ifError(err)
|
||||
t.is(value, 'bar')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.test('concatenating sublevel Uint8Array keys', function (t) {
|
||||
t.plan(10)
|
||||
|
||||
const key = new Uint8Array([0, 255])
|
||||
const textEncoder = new TextEncoder()
|
||||
const prefix = textEncoder.encode('!test!')
|
||||
const prefixedKey = new Uint8Array(prefix.byteLength + key.byteLength)
|
||||
|
||||
prefixedKey.set(prefix, 0)
|
||||
prefixedKey.set(key, prefix.byteLength)
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
_put (key, value, options, callback) {
|
||||
t.is(options.keyEncoding, 'view')
|
||||
t.is(options.valueEncoding, 'view')
|
||||
t.same(key, prefixedKey)
|
||||
t.same(value, textEncoder.encode('bar'))
|
||||
nextTick(callback)
|
||||
}
|
||||
|
||||
_get (key, options, callback) {
|
||||
t.is(options.keyEncoding, 'view')
|
||||
t.is(options.valueEncoding, 'view')
|
||||
t.same(key, prefixedKey)
|
||||
nextTick(callback, null, textEncoder.encode('bar'))
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel({ encodings: { view: true } })
|
||||
const sub = db.sublevel('test', { keyEncoding: 'view' })
|
||||
|
||||
sub.put(key, 'bar', function (err) {
|
||||
t.ifError(err)
|
||||
sub.get(key, function (err, value) {
|
||||
t.ifError(err)
|
||||
t.is(value, 'bar')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Also test default fallback implementations of keys() and values()
|
||||
for (const [mode, def] of [['iterator', false], ['keys', false], ['values', false], ['keys', true], ['values', true]]) {
|
||||
const Ctor = mode === 'iterator' || def ? AbstractIterator : mode === 'keys' ? AbstractKeyIterator : AbstractValueIterator
|
||||
const privateMethod = def ? '_iterator' : '_' + mode
|
||||
const publicMethod = mode
|
||||
|
||||
t.test(`unfixing sublevel.${mode}() Buffer keys (default implementation: ${def})`, function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const testKey = Buffer.from('00ff', 'hex')
|
||||
const prefixedKey = Buffer.concat([Buffer.from('!test!'), testKey])
|
||||
|
||||
class MockIterator extends Ctor {
|
||||
_next (callback) {
|
||||
if (mode === 'iterator' || def) {
|
||||
this.nextTick(callback, null, prefixedKey, 'bar')
|
||||
} else if (mode === 'keys') {
|
||||
this.nextTick(callback, null, prefixedKey)
|
||||
} else {
|
||||
this.nextTick(callback, null, 'bar')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
t.is(options.keyEncoding, 'buffer')
|
||||
t.is(options.valueEncoding, 'utf8')
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel({ encodings: { buffer: true, view: true, utf8: true } })
|
||||
const sub = db.sublevel('test', { keyEncoding: 'buffer' })
|
||||
|
||||
sub[publicMethod]().next(function (err, keyOrValue) {
|
||||
t.ifError(err)
|
||||
t.same(keyOrValue, mode === 'values' ? 'bar' : testKey)
|
||||
})
|
||||
})
|
||||
|
||||
t.test(`unfixing sublevel.${mode}() Uint8Array keys (default implementation: ${def})`, function (t) {
|
||||
t.plan(4)
|
||||
|
||||
const testKey = new Uint8Array([0, 255])
|
||||
const textEncoder = new TextEncoder()
|
||||
const prefix = textEncoder.encode('!test!')
|
||||
const prefixedKey = new Uint8Array(prefix.byteLength + testKey.byteLength)
|
||||
|
||||
prefixedKey.set(prefix, 0)
|
||||
prefixedKey.set(testKey, prefix.byteLength)
|
||||
|
||||
class MockIterator extends Ctor {
|
||||
_next (callback) {
|
||||
if (mode === 'iterator' || def) {
|
||||
this.nextTick(callback, null, prefixedKey, 'bar')
|
||||
} else if (mode === 'keys') {
|
||||
this.nextTick(callback, null, prefixedKey)
|
||||
} else {
|
||||
this.nextTick(callback, null, 'bar')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
[privateMethod] (options) {
|
||||
t.is(options.keyEncoding, 'view')
|
||||
t.is(options.valueEncoding, 'utf8')
|
||||
return new MockIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel({ encodings: { buffer: true, view: true, utf8: true } })
|
||||
const sub = db.sublevel('test', { keyEncoding: 'view' })
|
||||
|
||||
sub[publicMethod]().next(function (err, keyOrValue) {
|
||||
t.ifError(err)
|
||||
t.same(keyOrValue, mode === 'values' ? 'bar' : testKey)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
for (const chained of [false, true]) {
|
||||
for (const deferred of [false, true]) {
|
||||
test(`batch() with sublevel per operation (chained: ${chained}, deferred: ${deferred})`, async function (t) {
|
||||
t.plan(6)
|
||||
|
||||
class MockLevel extends AbstractLevel {
|
||||
_batch (operations, options, callback) {
|
||||
t.same(operations, [
|
||||
{
|
||||
type: 'put',
|
||||
sublevel: null,
|
||||
key: '!1!a',
|
||||
value: '{"foo":123}',
|
||||
keyEncoding: 'utf8',
|
||||
valueEncoding: 'utf8'
|
||||
},
|
||||
{
|
||||
type: 'put',
|
||||
sublevel: null,
|
||||
key: '!2!a-y',
|
||||
value: '[object Object]',
|
||||
keyEncoding: 'utf8',
|
||||
valueEncoding: 'utf8'
|
||||
},
|
||||
{
|
||||
type: 'put',
|
||||
sublevel: null,
|
||||
key: '!1!b',
|
||||
value: '[object Object]',
|
||||
keyEncoding: 'utf8',
|
||||
valueEncoding: 'utf8'
|
||||
},
|
||||
{
|
||||
type: 'put',
|
||||
sublevel: null,
|
||||
key: '!2!b',
|
||||
value: 'b',
|
||||
keyEncoding: 'utf8',
|
||||
valueEncoding: 'utf8'
|
||||
},
|
||||
{
|
||||
type: 'del',
|
||||
sublevel: null,
|
||||
key: '!2!c1',
|
||||
keyEncoding: 'utf8'
|
||||
},
|
||||
{
|
||||
type: 'del',
|
||||
sublevel: null,
|
||||
key: '!2!c2-y',
|
||||
keyEncoding: 'utf8'
|
||||
},
|
||||
{
|
||||
type: 'del',
|
||||
key: 'd-x',
|
||||
keyEncoding: 'utf8'
|
||||
}
|
||||
])
|
||||
t.same(options, {})
|
||||
nextTick(callback)
|
||||
}
|
||||
}
|
||||
|
||||
const db = new MockLevel({ encodings: { utf8: true } }, {
|
||||
keyEncoding: {
|
||||
encode: (key) => key + '-x',
|
||||
decode: (key) => key.slice(0, -2),
|
||||
name: 'x',
|
||||
format: 'utf8'
|
||||
}
|
||||
})
|
||||
|
||||
const sub1 = db.sublevel('1', { valueEncoding: 'json' })
|
||||
const sub2 = db.sublevel('2', {
|
||||
keyEncoding: {
|
||||
encode: (key) => key + '-y',
|
||||
decode: (key) => key.slice(0, -2),
|
||||
name: 'y',
|
||||
format: 'utf8'
|
||||
}
|
||||
})
|
||||
|
||||
if (!deferred) await sub1.open()
|
||||
|
||||
t.is(sub1.keyEncoding().name, 'utf8')
|
||||
t.is(sub1.valueEncoding().name, 'json')
|
||||
t.is(sub2.keyEncoding().name, 'y')
|
||||
t.is(sub2.valueEncoding().name, 'utf8')
|
||||
|
||||
if (chained) {
|
||||
await db.batch()
|
||||
// keyEncoding: utf8 (sublevel), valueEncoding: json (sublevel)
|
||||
.put('a', { foo: 123 }, { sublevel: sub1 })
|
||||
|
||||
// keyEncoding: y (sublevel), valueEncoding: utf8 (sublevel)
|
||||
.put('a', { foo: 123 }, { sublevel: sub2 })
|
||||
|
||||
// keyEncoding: utf8 (sublevel), valueEncoding: utf8 (operation)
|
||||
.put('b', { foo: 123 }, { sublevel: sub1, valueEncoding: 'utf8' })
|
||||
|
||||
// keyEncoding: utf8 (operation), valueEncoding: utf8 (sublevel)
|
||||
.put('b', 'b', { sublevel: sub2, keyEncoding: 'utf8' })
|
||||
|
||||
// keyEncoding: utf8 (operation)
|
||||
.del('c1', { sublevel: sub2, keyEncoding: 'utf8' })
|
||||
|
||||
// keyEncoding: y (sublevel)
|
||||
.del('c2', { sublevel: sub2 })
|
||||
|
||||
// keyEncoding: x (db). Should not affect sublevels.
|
||||
.del('d')
|
||||
.write()
|
||||
} else {
|
||||
await db.batch([
|
||||
{ type: 'put', sublevel: sub1, key: 'a', value: { foo: 123 } },
|
||||
{ type: 'put', sublevel: sub2, key: 'a', value: { foo: 123 } },
|
||||
{ type: 'put', sublevel: sub1, key: 'b', value: { foo: 123 }, valueEncoding: 'utf8' },
|
||||
{ type: 'put', sublevel: sub2, key: 'b', value: 'b', keyEncoding: 'utf8' },
|
||||
{ type: 'del', key: 'c1', sublevel: sub2, keyEncoding: 'utf8' },
|
||||
{ type: 'del', key: 'c2', sublevel: sub2 },
|
||||
{ type: 'del', key: 'd' }
|
||||
])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+108
@@ -0,0 +1,108 @@
|
||||
'use strict'
|
||||
|
||||
const { Buffer } = require('buffer')
|
||||
|
||||
exports.all = function (test, testCommon) {
|
||||
for (const deferred of [false, true]) {
|
||||
// NOTE: adapted from subleveldown
|
||||
test(`sublevel.clear() (deferred: ${deferred})`, async function (t) {
|
||||
const db = testCommon.factory()
|
||||
const sub1 = db.sublevel('1')
|
||||
const sub2 = db.sublevel('2')
|
||||
|
||||
if (!deferred) await sub1.open()
|
||||
if (!deferred) await sub2.open()
|
||||
|
||||
await populate([sub1, sub2], ['a', 'b'])
|
||||
await verify(['!1!a', '!1!b', '!2!a', '!2!b'])
|
||||
|
||||
await clear([sub1], {})
|
||||
await verify(['!2!a', '!2!b'])
|
||||
|
||||
await populate([sub1], ['a', 'b'])
|
||||
await clear([sub2], { lt: 'b' })
|
||||
await verify(['!1!a', '!1!b', '!2!b'])
|
||||
await db.close()
|
||||
|
||||
async function populate (subs, items) {
|
||||
return Promise.all(subs.map(sub => {
|
||||
return sub.batch(items.map(function (item) {
|
||||
return { type: 'put', key: item, value: item }
|
||||
}))
|
||||
}))
|
||||
}
|
||||
|
||||
async function clear (subs, opts) {
|
||||
return Promise.all(subs.map(sub => {
|
||||
return sub.clear(opts)
|
||||
}))
|
||||
}
|
||||
|
||||
async function verify (expected) {
|
||||
const keys = await db.keys().all()
|
||||
t.same(keys, expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
for (const deferred of [false, true]) {
|
||||
for (const keyEncoding of ['buffer', 'view']) {
|
||||
if (!testCommon.supports.encodings[keyEncoding]) return
|
||||
|
||||
// NOTE: adapted from subleveldown. See https://github.com/Level/subleveldown/issues/87
|
||||
test(`iterate sublevel keys with bytes above 196 (${keyEncoding}, deferred: ${deferred})`, async function (t) {
|
||||
const db = testCommon.factory()
|
||||
const sub1 = db.sublevel('a', { keyEncoding })
|
||||
const sub2 = db.sublevel('b', { keyEncoding })
|
||||
const length = (db) => db.keys().all().then(x => x.length)
|
||||
|
||||
if (!deferred) await sub1.open()
|
||||
if (!deferred) await sub2.open()
|
||||
|
||||
const batch1 = sub1.batch()
|
||||
const batch2 = sub2.batch()
|
||||
const keys = []
|
||||
|
||||
for (let i = 0; i < 256; i++) {
|
||||
const key = keyEncoding === 'buffer' ? Buffer.from([i]) : new Uint8Array([i])
|
||||
keys.push(key)
|
||||
batch1.put(key, 'aa')
|
||||
batch2.put(key, 'bb')
|
||||
}
|
||||
|
||||
await Promise.all([batch1.write(), batch2.write()])
|
||||
|
||||
const entries1 = await sub1.iterator().all()
|
||||
const entries2 = await sub2.iterator().all()
|
||||
|
||||
t.is(entries1.length, 256, 'sub1 yielded all entries')
|
||||
t.is(entries2.length, 256, 'sub2 yielded all entries')
|
||||
t.ok(entries1.every(x => x[1] === 'aa'))
|
||||
t.ok(entries2.every(x => x[1] === 'bb'))
|
||||
|
||||
const many1 = await sub1.getMany(keys)
|
||||
const many2 = await sub2.getMany(keys)
|
||||
|
||||
t.is(many1.length, 256, 'sub1 yielded all values')
|
||||
t.is(many2.length, 256, 'sub2 yielded all values')
|
||||
t.ok(many1.every(x => x === 'aa'))
|
||||
t.ok(many2.every(x => x === 'bb'))
|
||||
|
||||
const singles1 = await Promise.all(keys.map(k => sub1.get(k)))
|
||||
const singles2 = await Promise.all(keys.map(k => sub2.get(k)))
|
||||
|
||||
t.is(singles1.length, 256, 'sub1 yielded all values')
|
||||
t.is(singles2.length, 256, 'sub2 yielded all values')
|
||||
t.ok(singles1.every(x => x === 'aa'))
|
||||
t.ok(singles2.every(x => x === 'bb'))
|
||||
|
||||
await sub1.clear()
|
||||
|
||||
t.same(await length(sub1), 0, 'cleared sub1')
|
||||
t.same(await length(sub2), 256, 'did not clear sub2')
|
||||
|
||||
await db.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+280
@@ -0,0 +1,280 @@
|
||||
'use strict'
|
||||
|
||||
const ModuleError = require('module-error')
|
||||
const { AbstractLevel, AbstractChainedBatch } = require('..')
|
||||
const { AbstractIterator, AbstractKeyIterator, AbstractValueIterator } = require('..')
|
||||
|
||||
const spies = []
|
||||
|
||||
exports.verifyNotFoundError = function (err) {
|
||||
return err.code === 'LEVEL_NOT_FOUND' && err.notFound === true && err.status === 404
|
||||
}
|
||||
|
||||
exports.illegalKeys = [
|
||||
{ name: 'null key', key: null },
|
||||
{ name: 'undefined key', key: undefined }
|
||||
]
|
||||
|
||||
exports.illegalValues = [
|
||||
{ name: 'null key', value: null },
|
||||
{ name: 'undefined value', value: undefined }
|
||||
]
|
||||
|
||||
/**
|
||||
* Wrap a callback to check that it's called asynchronously. Must be
|
||||
* combined with a `ctx()`, `with()` or `end()` call.
|
||||
*
|
||||
* @param {function} cb Callback to check.
|
||||
* @param {string} name Optional callback name to use in assertion messages.
|
||||
* @returns {function} Wrapped callback.
|
||||
*/
|
||||
exports.assertAsync = function (cb, name) {
|
||||
const spy = {
|
||||
called: false,
|
||||
name: name || cb.name || 'anonymous'
|
||||
}
|
||||
|
||||
spies.push(spy)
|
||||
|
||||
return function (...args) {
|
||||
spy.called = true
|
||||
return cb.apply(this, args)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that callbacks wrapped with `assertAsync()` were not yet called.
|
||||
* @param {import('tape').Test} t Tape test object.
|
||||
*/
|
||||
exports.assertAsync.end = function (t) {
|
||||
for (const { called, name } of spies.splice(0, spies.length)) {
|
||||
t.is(called, false, `callback (${name}) is asynchronous`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a test function to verify `assertAsync()` spies at the end.
|
||||
* @param {import('tape').TestCase} test Test function to be passed to `tape()`.
|
||||
* @returns {import('tape').TestCase} Wrapped test function.
|
||||
*/
|
||||
exports.assertAsync.ctx = function (test) {
|
||||
return function (...args) {
|
||||
const ret = test.call(this, ...args)
|
||||
exports.assertAsync.end(args[0])
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an arbitrary callback to verify `assertAsync()` spies at the end.
|
||||
* @param {import('tape').Test} t Tape test object.
|
||||
* @param {function} cb Callback to wrap.
|
||||
* @returns {function} Wrapped callback.
|
||||
*/
|
||||
exports.assertAsync.with = function (t, cb) {
|
||||
return function (...args) {
|
||||
const ret = cb.call(this, ...args)
|
||||
exports.assertAsync.end(t)
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
exports.mockLevel = function (methods, ...args) {
|
||||
class TestLevel extends AbstractLevel {}
|
||||
for (const k in methods) TestLevel.prototype[k] = methods[k]
|
||||
if (!args.length) args = [{ encodings: { utf8: true } }]
|
||||
return new TestLevel(...args)
|
||||
}
|
||||
|
||||
exports.mockIterator = function (db, options, methods, ...args) {
|
||||
class TestIterator extends AbstractIterator {}
|
||||
for (const k in methods) TestIterator.prototype[k] = methods[k]
|
||||
return new TestIterator(db, options, ...args)
|
||||
}
|
||||
|
||||
exports.mockChainedBatch = function (db, methods, ...args) {
|
||||
class TestBatch extends AbstractChainedBatch {}
|
||||
for (const k in methods) TestBatch.prototype[k] = methods[k]
|
||||
return new TestBatch(db, ...args)
|
||||
}
|
||||
|
||||
// Mock encoding where null and undefined are significant types
|
||||
exports.nullishEncoding = {
|
||||
name: 'nullish',
|
||||
format: 'utf8',
|
||||
encode (v) {
|
||||
return v === null ? '\x00' : v === undefined ? '\xff' : String(v)
|
||||
},
|
||||
decode (v) {
|
||||
return v === '\x00' ? null : v === '\xff' ? undefined : v
|
||||
}
|
||||
}
|
||||
|
||||
const kEntries = Symbol('entries')
|
||||
const kPosition = Symbol('position')
|
||||
const kOptions = Symbol('options')
|
||||
|
||||
/**
|
||||
* A minimal and non-optimized implementation for use in tests. Only supports utf8.
|
||||
* Don't use this as a reference implementation.
|
||||
*/
|
||||
class MinimalLevel extends AbstractLevel {
|
||||
constructor (options) {
|
||||
super({ encodings: { utf8: true }, seek: true }, options)
|
||||
this[kEntries] = new Map()
|
||||
}
|
||||
|
||||
_put (key, value, options, callback) {
|
||||
this[kEntries].set(key, value)
|
||||
this.nextTick(callback)
|
||||
}
|
||||
|
||||
_get (key, options, callback) {
|
||||
const value = this[kEntries].get(key)
|
||||
|
||||
if (value === undefined) {
|
||||
return this.nextTick(callback, new ModuleError(`Key ${key} was not found`, {
|
||||
code: 'LEVEL_NOT_FOUND'
|
||||
}))
|
||||
}
|
||||
|
||||
this.nextTick(callback, null, value)
|
||||
}
|
||||
|
||||
_getMany (keys, options, callback) {
|
||||
const values = keys.map(k => this[kEntries].get(k))
|
||||
this.nextTick(callback, null, values)
|
||||
}
|
||||
|
||||
_del (key, options, callback) {
|
||||
this[kEntries].delete(key)
|
||||
this.nextTick(callback)
|
||||
}
|
||||
|
||||
_clear (options, callback) {
|
||||
for (const [k] of sliceEntries(this[kEntries], options, true)) {
|
||||
this[kEntries].delete(k)
|
||||
}
|
||||
|
||||
this.nextTick(callback)
|
||||
}
|
||||
|
||||
_batch (operations, options, callback) {
|
||||
const entries = new Map(this[kEntries])
|
||||
|
||||
for (const op of operations) {
|
||||
if (op.type === 'put') entries.set(op.key, op.value)
|
||||
else entries.delete(op.key)
|
||||
}
|
||||
|
||||
this[kEntries] = entries
|
||||
this.nextTick(callback)
|
||||
}
|
||||
|
||||
_iterator (options) {
|
||||
return new MinimalIterator(this, options)
|
||||
}
|
||||
|
||||
_keys (options) {
|
||||
return new MinimalKeyIterator(this, options)
|
||||
}
|
||||
|
||||
_values (options) {
|
||||
return new MinimalValueIterator(this, options)
|
||||
}
|
||||
}
|
||||
|
||||
class MinimalIterator extends AbstractIterator {
|
||||
constructor (db, options) {
|
||||
super(db, options)
|
||||
this[kEntries] = sliceEntries(db[kEntries], options, false)
|
||||
this[kOptions] = options
|
||||
this[kPosition] = 0
|
||||
}
|
||||
}
|
||||
|
||||
class MinimalKeyIterator extends AbstractKeyIterator {
|
||||
constructor (db, options) {
|
||||
super(db, options)
|
||||
this[kEntries] = sliceEntries(db[kEntries], options, false)
|
||||
this[kOptions] = options
|
||||
this[kPosition] = 0
|
||||
}
|
||||
}
|
||||
|
||||
class MinimalValueIterator extends AbstractValueIterator {
|
||||
constructor (db, options) {
|
||||
super(db, options)
|
||||
this[kEntries] = sliceEntries(db[kEntries], options, false)
|
||||
this[kOptions] = options
|
||||
this[kPosition] = 0
|
||||
}
|
||||
}
|
||||
|
||||
for (const Ctor of [MinimalIterator, MinimalKeyIterator, MinimalValueIterator]) {
|
||||
const mapEntry = Ctor === MinimalIterator ? e => e : Ctor === MinimalKeyIterator ? e => e[0] : e => e[1]
|
||||
|
||||
Ctor.prototype._next = function (callback) {
|
||||
const entry = this[kEntries][this[kPosition]++]
|
||||
if (entry === undefined) return this.nextTick(callback)
|
||||
if (Ctor === MinimalIterator) this.nextTick(callback, null, entry[0], entry[1])
|
||||
else this.nextTick(callback, null, mapEntry(entry))
|
||||
}
|
||||
|
||||
Ctor.prototype._nextv = function (size, options, callback) {
|
||||
const entries = this[kEntries].slice(this[kPosition], this[kPosition] + size)
|
||||
this[kPosition] += entries.length
|
||||
this.nextTick(callback, null, entries.map(mapEntry))
|
||||
}
|
||||
|
||||
Ctor.prototype._all = function (options, callback) {
|
||||
const end = this.limit - this.count + this[kPosition]
|
||||
const entries = this[kEntries].slice(this[kPosition], end)
|
||||
this[kPosition] = this[kEntries].length
|
||||
this.nextTick(callback, null, entries.map(mapEntry))
|
||||
}
|
||||
|
||||
Ctor.prototype._seek = function (target, options) {
|
||||
this[kPosition] = this[kEntries].length
|
||||
|
||||
if (!outOfRange(target, this[kOptions])) {
|
||||
// Don't care about performance here
|
||||
for (let i = 0; i < this[kPosition]; i++) {
|
||||
const key = this[kEntries][i][0]
|
||||
|
||||
if (this[kOptions].reverse ? key <= target : key >= target) {
|
||||
this[kPosition] = i
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const outOfRange = function (target, options) {
|
||||
if ('gte' in options) {
|
||||
if (target < options.gte) return true
|
||||
} else if ('gt' in options) {
|
||||
if (target <= options.gt) return true
|
||||
}
|
||||
|
||||
if ('lte' in options) {
|
||||
if (target > options.lte) return true
|
||||
} else if ('lt' in options) {
|
||||
if (target >= options.lt) return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const sliceEntries = function (entries, options, applyLimit) {
|
||||
entries = Array.from(entries)
|
||||
.filter((e) => !outOfRange(e[0], options))
|
||||
.sort((a, b) => a[0] > b[0] ? 1 : a[0] < b[0] ? -1 : 0)
|
||||
|
||||
if (options.reverse) entries.reverse()
|
||||
if (applyLimit && options.limit !== -1) entries = entries.slice(0, options.limit)
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
exports.MinimalLevel = MinimalLevel
|
||||
Generated
Vendored
+123
@@ -0,0 +1,123 @@
|
||||
import * as Transcoder from 'level-transcoder'
|
||||
import { AbstractSublevel } from './abstract-sublevel'
|
||||
import { NodeCallback } from './interfaces'
|
||||
|
||||
export class AbstractChainedBatch<TDatabase, KDefault, VDefault> {
|
||||
constructor (db: TDatabase)
|
||||
|
||||
/**
|
||||
* A reference to the database that created this chained batch.
|
||||
*/
|
||||
db: TDatabase
|
||||
|
||||
/**
|
||||
* The number of queued operations on the current batch.
|
||||
*/
|
||||
get length (): number
|
||||
|
||||
/**
|
||||
* Queue a _put_ operation on this batch, not committed until {@link write} is
|
||||
* called.
|
||||
*/
|
||||
put (key: KDefault, value: VDefault): this
|
||||
|
||||
put<K = KDefault, V = VDefault> (
|
||||
key: K,
|
||||
value: V,
|
||||
options: AbstractChainedBatchPutOptions<TDatabase, K, V>
|
||||
): this
|
||||
|
||||
/**
|
||||
* Queue a _del_ operation on this batch, not committed until {@link write} is
|
||||
* called.
|
||||
*/
|
||||
del (key: KDefault): this
|
||||
del<K = KDefault> (key: K, options: AbstractChainedBatchDelOptions<TDatabase, K>): this
|
||||
|
||||
/**
|
||||
* Clear all queued operations on this batch.
|
||||
*/
|
||||
clear (): this
|
||||
|
||||
/**
|
||||
* Commit the queued operations for this batch. All operations will be written
|
||||
* atomically, that is, they will either all succeed or fail with no partial
|
||||
* commits.
|
||||
*/
|
||||
write (): Promise<void>
|
||||
write (options: AbstractChainedBatchWriteOptions): Promise<void>
|
||||
write (callback: NodeCallback<void>): void
|
||||
write (options: AbstractChainedBatchWriteOptions, callback: NodeCallback<void>): void
|
||||
|
||||
/**
|
||||
* Free up underlying resources. This should be done even if the chained batch has
|
||||
* zero queued operations. Automatically called by {@link write} so normally not
|
||||
* necessary to call, unless the intent is to discard a chained batch without
|
||||
* committing it.
|
||||
*/
|
||||
close (): Promise<void>
|
||||
close (callback: NodeCallback<void>): void
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the {@link AbstractChainedBatch.put} method.
|
||||
*/
|
||||
export interface AbstractChainedBatchPutOptions<TDatabase, K, V> {
|
||||
/**
|
||||
* Custom key encoding for this _put_ operation, used to encode the `key`.
|
||||
*/
|
||||
keyEncoding?: string | Transcoder.PartialEncoder<K> | undefined
|
||||
|
||||
/**
|
||||
* Custom value encoding for this _put_ operation, used to encode the `value`.
|
||||
*/
|
||||
valueEncoding?: string | Transcoder.PartialEncoder<V> | undefined
|
||||
|
||||
/**
|
||||
* Act as though the _put_ operation is performed on the given sublevel, to similar
|
||||
* effect as:
|
||||
*
|
||||
* ```js
|
||||
* await sublevel.batch().put(key, value).write()
|
||||
* ```
|
||||
*
|
||||
* This allows atomically committing data to multiple sublevels. The `key` will be
|
||||
* prefixed with the `prefix` of the sublevel, and the `key` and `value` will be
|
||||
* encoded by the sublevel (using the default encodings of the sublevel unless
|
||||
* {@link keyEncoding} and / or {@link valueEncoding} are provided).
|
||||
*/
|
||||
sublevel?: AbstractSublevel<TDatabase, any, any, any> | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the {@link AbstractChainedBatch.del} method.
|
||||
*/
|
||||
export interface AbstractChainedBatchDelOptions<TDatabase, K> {
|
||||
/**
|
||||
* Custom key encoding for this _del_ operation, used to encode the `key`.
|
||||
*/
|
||||
keyEncoding?: string | Transcoder.PartialEncoder<K> | undefined
|
||||
|
||||
/**
|
||||
* Act as though the _del_ operation is performed on the given sublevel, to similar
|
||||
* effect as:
|
||||
*
|
||||
* ```js
|
||||
* await sublevel.batch().del(key).write()
|
||||
* ```
|
||||
*
|
||||
* This allows atomically committing data to multiple sublevels. The `key` will be
|
||||
* prefixed with the `prefix` of the sublevel, and the `key` will be encoded by the
|
||||
* sublevel (using the default key encoding of the sublevel unless {@link keyEncoding}
|
||||
* is provided).
|
||||
*/
|
||||
sublevel?: AbstractSublevel<TDatabase, any, any, any> | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the {@link AbstractChainedBatch.write} method.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface AbstractChainedBatchWriteOptions {
|
||||
// There are no abstract options but implementations may add theirs.
|
||||
}
|
||||
Generated
Vendored
+252
@@ -0,0 +1,252 @@
|
||||
import * as Transcoder from 'level-transcoder'
|
||||
import { RangeOptions, NodeCallback } from './interfaces'
|
||||
|
||||
export interface AbstractIteratorOptions<K, V> extends RangeOptions<K> {
|
||||
/**
|
||||
* Whether to return the key of each entry. Defaults to `true`. If set to `false`,
|
||||
* the iterator will yield keys that are `undefined`.
|
||||
*/
|
||||
keys?: boolean | undefined
|
||||
|
||||
/**
|
||||
* Whether to return the value of each entry. Defaults to `true`. If set to
|
||||
* `false`, the iterator will yield values that are `undefined`.
|
||||
*/
|
||||
values?: boolean | undefined
|
||||
|
||||
/**
|
||||
* Custom key encoding for this iterator, used to encode range options, to encode
|
||||
* {@link AbstractIterator.seek} targets and to decode keys.
|
||||
*/
|
||||
keyEncoding?: string | Transcoder.PartialEncoding<K> | undefined
|
||||
|
||||
/**
|
||||
* Custom value encoding for this iterator, used to decode values.
|
||||
*/
|
||||
valueEncoding?: string | Transcoder.PartialDecoder<V> | undefined
|
||||
}
|
||||
|
||||
export interface AbstractKeyIteratorOptions<K> extends RangeOptions<K> {
|
||||
/**
|
||||
* Custom key encoding for this iterator, used to encode range options, to encode
|
||||
* {@link AbstractKeyIterator.seek} targets and to decode keys.
|
||||
*/
|
||||
keyEncoding?: string | Transcoder.PartialEncoding<K> | undefined
|
||||
}
|
||||
|
||||
export interface AbstractValueIteratorOptions<K, V> extends RangeOptions<K> {
|
||||
/**
|
||||
* Custom key encoding for this iterator, used to encode range options and
|
||||
* {@link AbstractValueIterator.seek} targets.
|
||||
*/
|
||||
keyEncoding?: string | Transcoder.PartialEncoding<K> | undefined
|
||||
|
||||
/**
|
||||
* Custom value encoding for this iterator, used to decode values.
|
||||
*/
|
||||
valueEncoding?: string | Transcoder.PartialDecoder<V> | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TDatabase Type of the database that created this iterator.
|
||||
* @template T Type of items yielded. Items can be entries, keys or values.
|
||||
*/
|
||||
declare class CommonIterator<TDatabase, T> {
|
||||
/**
|
||||
* A reference to the database that created this iterator.
|
||||
*/
|
||||
db: TDatabase
|
||||
|
||||
/**
|
||||
* Read-only getter that indicates how many items have been yielded so far (by any
|
||||
* method) excluding calls that errored or yielded `undefined`.
|
||||
*/
|
||||
get count (): number
|
||||
|
||||
/**
|
||||
* Read-only getter that reflects the `limit` that was set in options. Greater than or
|
||||
* equal to zero. Equals {@link Infinity} if no limit.
|
||||
*/
|
||||
get limit (): number
|
||||
|
||||
[Symbol.asyncIterator] (): AsyncGenerator<T, void, unknown>
|
||||
|
||||
/**
|
||||
* Free up underlying resources. Not necessary to call if [`for await...of`][1] or
|
||||
* `all()` is used.
|
||||
*
|
||||
* [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
|
||||
*/
|
||||
close (): Promise<void>
|
||||
close (callback: NodeCallback<void>): void
|
||||
}
|
||||
|
||||
export class AbstractIterator<TDatabase, K, V> extends CommonIterator<TDatabase, [K, V]> {
|
||||
constructor (db: TDatabase, options: AbstractIteratorOptions<K, V>)
|
||||
|
||||
/**
|
||||
* Advance to the next entry and yield that entry. When possible, prefer to use
|
||||
* [`for await...of`][1] instead.
|
||||
*
|
||||
* [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
|
||||
*/
|
||||
next (): Promise<[K, V] | undefined>
|
||||
next (callback: NextCallback<K, V>): void
|
||||
|
||||
/**
|
||||
* Advance repeatedly and get at most {@link size} amount of entries in a single call.
|
||||
* Can be faster than repeated {@link next()} calls. The natural end of the iterator
|
||||
* will be signaled by yielding an empty array.
|
||||
*
|
||||
* @param size Get at most this many entries. Has a soft minimum of 1.
|
||||
* @param options Options (none at the moment, reserved for future use).
|
||||
* @param callback Error-first callback. If none is provided, a promise is returned.
|
||||
*/
|
||||
nextv (size: number, options: {}, callback: NodeCallback<Array<[K, V]>>): void
|
||||
nextv (size: number, callback: NodeCallback<Array<[K, V]>>): void
|
||||
nextv (size: number, options: {}): Promise<Array<[K, V]>>
|
||||
nextv (size: number): Promise<Array<[K, V]>>
|
||||
|
||||
/**
|
||||
* Advance repeatedly and get all (remaining) entries as an array, automatically
|
||||
* closing the iterator. Assumes that those entries fit in memory. If that's not the
|
||||
* case, instead use {@link next()}, {@link nextv()} or [`for await...of`][1].
|
||||
*
|
||||
* [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
|
||||
*
|
||||
* @param options Options (none at the moment, reserved for future use).
|
||||
* @param callback Error-first callback. If none is provided, a promise is returned.
|
||||
*/
|
||||
all (options: {}, callback: NodeCallback<Array<[K, V]>>): void
|
||||
all (callback: NodeCallback<Array<[K, V]>>): void
|
||||
all (options: {}): Promise<Array<[K, V]>>
|
||||
all (): Promise<Array<[K, V]>>
|
||||
|
||||
/**
|
||||
* Seek to the key closest to {@link target}. Subsequent calls to {@link next()},
|
||||
* {@link nextv()} or {@link all()} (including implicit calls in a `for await...of`
|
||||
* loop) will yield entries with keys equal to or larger than {@link target}, or equal
|
||||
* to or smaller than {@link target} if the {@link AbstractIteratorOptions.reverse}
|
||||
* option was true.
|
||||
*/
|
||||
seek (target: K): void
|
||||
seek<TTarget = K> (target: TTarget, options: AbstractSeekOptions<TTarget>): void
|
||||
}
|
||||
|
||||
export class AbstractKeyIterator<TDatabase, K> extends CommonIterator<TDatabase, K> {
|
||||
constructor (db: TDatabase, options: AbstractKeyIteratorOptions<K>)
|
||||
|
||||
/**
|
||||
* Advance to the next key and yield that key. When possible, prefer to use
|
||||
* [`for await...of`][1] instead.
|
||||
*
|
||||
* [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
|
||||
*/
|
||||
next (): Promise<K | undefined>
|
||||
next (callback: NodeCallback<K>): void
|
||||
|
||||
/**
|
||||
* Advance repeatedly and get at most {@link size} amount of keys in a single call. Can
|
||||
* be faster than repeated {@link next()} calls. The natural end of the iterator will
|
||||
* be signaled by yielding an empty array.
|
||||
*
|
||||
* @param size Get at most this many keys. Has a soft minimum of 1.
|
||||
* @param options Options (none at the moment, reserved for future use).
|
||||
* @param callback Error-first callback. If none is provided, a promise is returned.
|
||||
*/
|
||||
nextv (size: number, options: {}, callback: NodeCallback<[K]>): void
|
||||
nextv (size: number, callback: NodeCallback<[K]>): void
|
||||
nextv (size: number, options: {}): Promise<[K]>
|
||||
nextv (size: number): Promise<[K]>
|
||||
|
||||
/**
|
||||
* Advance repeatedly and get all (remaining) keys as an array, automatically closing
|
||||
* the iterator. Assumes that those keys fit in memory. If that's not the case, instead
|
||||
* use {@link next()}, {@link nextv()} or [`for await...of`][1].
|
||||
*
|
||||
* [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
|
||||
*
|
||||
* @param options Options (none at the moment, reserved for future use).
|
||||
* @param callback Error-first callback. If none is provided, a promise is returned.
|
||||
*/
|
||||
all (options: {}, callback: NodeCallback<[K]>): void
|
||||
all (callback: NodeCallback<[K]>): void
|
||||
all (options: {}): Promise<[K]>
|
||||
all (): Promise<[K]>
|
||||
|
||||
/**
|
||||
* Seek to the key closest to {@link target}. Subsequent calls to {@link next()},
|
||||
* {@link nextv()} or {@link all()} (including implicit calls in a `for await...of`
|
||||
* loop) will yield keys equal to or larger than {@link target}, or equal to or smaller
|
||||
* than {@link target} if the {@link AbstractKeyIteratorOptions.reverse} option was
|
||||
* true.
|
||||
*/
|
||||
seek (target: K): void
|
||||
seek<TTarget = K> (target: TTarget, options: AbstractSeekOptions<TTarget>): void
|
||||
}
|
||||
|
||||
export class AbstractValueIterator<TDatabase, K, V> extends CommonIterator<TDatabase, V> {
|
||||
constructor (db: TDatabase, options: AbstractValueIteratorOptions<K, V>)
|
||||
|
||||
/**
|
||||
* Advance to the next value and yield that value. When possible, prefer
|
||||
* to use [`for await...of`][1] instead.
|
||||
*
|
||||
* [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
|
||||
*/
|
||||
next (): Promise<V | undefined>
|
||||
next (callback: NodeCallback<V>): void
|
||||
|
||||
/**
|
||||
* Advance repeatedly and get at most {@link size} amount of values in a single call.
|
||||
* Can be faster than repeated {@link next()} calls. The natural end of the iterator
|
||||
* will be signaled by yielding an empty array.
|
||||
*
|
||||
* @param size Get at most this many values. Has a soft minimum of 1.
|
||||
* @param options Options (none at the moment, reserved for future use).
|
||||
* @param callback Error-first callback. If none is provided, a promise is returned.
|
||||
*/
|
||||
nextv (size: number, options: {}, callback: NodeCallback<[V]>): void
|
||||
nextv (size: number, callback: NodeCallback<[V]>): void
|
||||
nextv (size: number, options: {}): Promise<[V]>
|
||||
nextv (size: number): Promise<[V]>
|
||||
|
||||
/**
|
||||
* Advance repeatedly and get all (remaining) values as an array, automatically closing
|
||||
* the iterator. Assumes that those values fit in memory. If that's not the case,
|
||||
* instead use {@link next()}, {@link nextv()} or [`for await...of`][1].
|
||||
*
|
||||
* [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
|
||||
*
|
||||
* @param options Options (none at the moment, reserved for future use).
|
||||
* @param callback Error-first callback. If none is provided, a promise is returned.
|
||||
*/
|
||||
all (options: {}, callback: NodeCallback<[V]>): void
|
||||
all (callback: NodeCallback<[V]>): void
|
||||
all (options: {}): Promise<[V]>
|
||||
all (): Promise<[V]>
|
||||
|
||||
/**
|
||||
* Seek to the key closest to {@link target}. Subsequent calls to {@link next()},
|
||||
* {@link nextv()} or {@link all()} (including implicit calls in a `for await...of`
|
||||
* loop) will yield the values of keys equal to or larger than {@link target}, or equal
|
||||
* to or smaller than {@link target} if the {@link AbstractValueIteratorOptions.reverse}
|
||||
* option was true.
|
||||
*/
|
||||
seek (target: K): void
|
||||
seek<TTarget = K> (target: TTarget, options: AbstractSeekOptions<TTarget>): void
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the {@link AbstractIterator.seek} method.
|
||||
*/
|
||||
export interface AbstractSeekOptions<K> {
|
||||
/**
|
||||
* Custom key encoding, used to encode the `target`. By default the keyEncoding option
|
||||
* of the iterator is used, or (if that wasn't set) the keyEncoding of the database.
|
||||
*/
|
||||
keyEncoding?: string | Transcoder.PartialEncoder<K> | undefined
|
||||
}
|
||||
|
||||
declare type NextCallback<K, V> =
|
||||
(err: Error | undefined | null, key?: K | undefined, value?: V | undefined) => void
|
||||
Generated
Vendored
+485
@@ -0,0 +1,485 @@
|
||||
import { IManifest } from 'level-supports'
|
||||
import * as Transcoder from 'level-transcoder'
|
||||
import { EventEmitter } from 'events'
|
||||
import { AbstractChainedBatch } from './abstract-chained-batch'
|
||||
import { AbstractSublevel, AbstractSublevelOptions } from './abstract-sublevel'
|
||||
|
||||
import {
|
||||
AbstractIterator,
|
||||
AbstractIteratorOptions,
|
||||
AbstractKeyIterator,
|
||||
AbstractKeyIteratorOptions,
|
||||
AbstractValueIterator,
|
||||
AbstractValueIteratorOptions
|
||||
} from './abstract-iterator'
|
||||
|
||||
import { NodeCallback, RangeOptions } from './interfaces'
|
||||
|
||||
/**
|
||||
* Abstract class for a lexicographically sorted key-value database.
|
||||
*
|
||||
* @template TFormat The type used internally by the database to store data.
|
||||
* @template KDefault The default type of keys if not overridden on operations.
|
||||
* @template VDefault The default type of values if not overridden on operations.
|
||||
*/
|
||||
declare class AbstractLevel<TFormat, KDefault = string, VDefault = string>
|
||||
extends EventEmitter {
|
||||
/**
|
||||
* Private database constructor.
|
||||
*
|
||||
* @param manifest A [manifest](https://github.com/Level/supports) describing the
|
||||
* features supported by (the private API of) this database.
|
||||
* @param options Options, of which some will be forwarded to {@link open}.
|
||||
*/
|
||||
constructor (
|
||||
manifest: Partial<IManifest>,
|
||||
options?: AbstractDatabaseOptions<KDefault, VDefault> | undefined
|
||||
)
|
||||
|
||||
/**
|
||||
* A [manifest](https://github.com/Level/supports) describing the features
|
||||
* supported by this database.
|
||||
*/
|
||||
supports: IManifest
|
||||
|
||||
/**
|
||||
* Read-only getter that returns a string reflecting the current state of the database:
|
||||
*
|
||||
* - `'opening'` - waiting for the database to be opened
|
||||
* - `'open'` - successfully opened the database
|
||||
* - `'closing'` - waiting for the database to be closed
|
||||
* - `'closed'` - successfully closed the database.
|
||||
*/
|
||||
get status (): 'opening' | 'open' | 'closing' | 'closed'
|
||||
|
||||
/**
|
||||
* Open the database.
|
||||
*/
|
||||
open (): Promise<void>
|
||||
open (options: AbstractOpenOptions): Promise<void>
|
||||
open (callback: NodeCallback<void>): void
|
||||
open (options: AbstractOpenOptions, callback: NodeCallback<void>): void
|
||||
|
||||
/**
|
||||
* Close the database.
|
||||
*/
|
||||
close (): Promise<void>
|
||||
close (callback: NodeCallback<void>): void
|
||||
|
||||
/**
|
||||
* Get a value from the database by {@link key}.
|
||||
*/
|
||||
get (key: KDefault): Promise<VDefault>
|
||||
get (key: KDefault, callback: NodeCallback<VDefault>): void
|
||||
|
||||
get<K = KDefault, V = VDefault> (
|
||||
key: K,
|
||||
options: AbstractGetOptions<K, V>
|
||||
): Promise<V>
|
||||
|
||||
get<K = KDefault, V = VDefault> (
|
||||
key: K,
|
||||
options: AbstractGetOptions<K, V>,
|
||||
callback: NodeCallback<V>
|
||||
): void
|
||||
|
||||
/**
|
||||
* Get multiple values from the database by an array of {@link keys}.
|
||||
*/
|
||||
getMany (keys: KDefault[]): Promise<VDefault[]>
|
||||
getMany (keys: KDefault[], callback: NodeCallback<VDefault[]>): void
|
||||
|
||||
getMany<K = KDefault, V = VDefault> (
|
||||
keys: K[],
|
||||
options: AbstractGetManyOptions<K, V>
|
||||
): Promise<V[]>
|
||||
|
||||
getMany<K = KDefault, V = VDefault> (
|
||||
keys: K[],
|
||||
options: AbstractGetManyOptions<K, V>,
|
||||
callback: NodeCallback<V[]>
|
||||
): void
|
||||
|
||||
/**
|
||||
* Add a new entry or overwrite an existing entry.
|
||||
*/
|
||||
put (key: KDefault, value: VDefault): Promise<void>
|
||||
put (key: KDefault, value: VDefault, callback: NodeCallback<void>): void
|
||||
|
||||
put<K = KDefault, V = VDefault> (
|
||||
key: K,
|
||||
value: V,
|
||||
options: AbstractPutOptions<K, V>
|
||||
): Promise<void>
|
||||
|
||||
put<K = KDefault, V = VDefault> (
|
||||
key: K,
|
||||
value: V,
|
||||
options: AbstractPutOptions<K, V>,
|
||||
callback: NodeCallback<void>
|
||||
): void
|
||||
|
||||
/**
|
||||
* Delete an entry by {@link key}.
|
||||
*/
|
||||
del (key: KDefault): Promise<void>
|
||||
del (key: KDefault, callback: NodeCallback<void>): void
|
||||
|
||||
del<K = KDefault> (
|
||||
key: K,
|
||||
options: AbstractDelOptions<K>
|
||||
): Promise<void>
|
||||
|
||||
del<K = KDefault> (
|
||||
key: K,
|
||||
options: AbstractDelOptions<K>,
|
||||
callback: NodeCallback<void>
|
||||
): void
|
||||
|
||||
/**
|
||||
* Perform multiple _put_ and/or _del_ operations in bulk.
|
||||
*/
|
||||
batch (
|
||||
operations: Array<AbstractBatchOperation<typeof this, KDefault, VDefault>>
|
||||
): Promise<void>
|
||||
|
||||
batch (
|
||||
operations: Array<AbstractBatchOperation<typeof this, KDefault, VDefault>>,
|
||||
callback: NodeCallback<void>
|
||||
): void
|
||||
|
||||
batch<K = KDefault, V = VDefault> (
|
||||
operations: Array<AbstractBatchOperation<typeof this, K, V>>,
|
||||
options: AbstractBatchOptions<K, V>
|
||||
): Promise<void>
|
||||
|
||||
batch<K = KDefault, V = VDefault> (
|
||||
operations: Array<AbstractBatchOperation<typeof this, K, V>>,
|
||||
options: AbstractBatchOptions<K, V>,
|
||||
callback: NodeCallback<void>
|
||||
): void
|
||||
|
||||
batch (): AbstractChainedBatch<typeof this, KDefault, VDefault>
|
||||
|
||||
/**
|
||||
* Create an iterator. For example:
|
||||
*
|
||||
* ```js
|
||||
* for await (const [key, value] of db.iterator({ gte: 'a' })) {
|
||||
* console.log([key, value])
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
iterator (): AbstractIterator<typeof this, KDefault, VDefault>
|
||||
iterator<K = KDefault, V = VDefault> (
|
||||
options: AbstractIteratorOptions<K, V>
|
||||
): AbstractIterator<typeof this, K, V>
|
||||
|
||||
/**
|
||||
* Create a key iterator. For example:
|
||||
*
|
||||
* ```js
|
||||
* for await (const key of db.keys({ gte: 'a' })) {
|
||||
* console.log(key)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
keys (): AbstractKeyIterator<typeof this, KDefault>
|
||||
keys<K = KDefault> (
|
||||
options: AbstractKeyIteratorOptions<K>
|
||||
): AbstractKeyIterator<typeof this, K>
|
||||
|
||||
/**
|
||||
* Create a value iterator. For example:
|
||||
*
|
||||
* ```js
|
||||
* for await (const value of db.values({ gte: 'a' })) {
|
||||
* console.log(value)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
values (): AbstractValueIterator<typeof this, KDefault, VDefault>
|
||||
values<K = KDefault, V = VDefault> (
|
||||
options: AbstractValueIteratorOptions<K, V>
|
||||
): AbstractValueIterator<typeof this, K, V>
|
||||
|
||||
/**
|
||||
* Delete all entries or a range.
|
||||
*/
|
||||
clear (): Promise<void>
|
||||
clear (callback: NodeCallback<void>): void
|
||||
clear<K = KDefault> (options: AbstractClearOptions<K>): Promise<void>
|
||||
clear<K = KDefault> (options: AbstractClearOptions<K>, callback: NodeCallback<void>): void
|
||||
|
||||
/**
|
||||
* Create a sublevel.
|
||||
* @param name Name of the sublevel, used to prefix keys.
|
||||
*/
|
||||
sublevel (name: string): AbstractSublevel<typeof this, TFormat, string, string>
|
||||
sublevel<K = string, V = string> (
|
||||
name: string,
|
||||
options: AbstractSublevelOptions<K, V>
|
||||
): AbstractSublevel<typeof this, TFormat, K, V>
|
||||
|
||||
/**
|
||||
* Add sublevel prefix to the given {@link key}, which must be already-encoded. If this
|
||||
* database is not a sublevel, the given {@link key} is returned as-is.
|
||||
*
|
||||
* @param key Key to add prefix to.
|
||||
* @param keyFormat Format of {@link key}. One of `'utf8'`, `'buffer'`, `'view'`.
|
||||
* If `'utf8'` then {@link key} must be a string and the return value will be a string.
|
||||
* If `'buffer'` then Buffer, if `'view'` then Uint8Array.
|
||||
*/
|
||||
prefixKey (key: string, keyFormat: 'utf8'): string
|
||||
prefixKey (key: Buffer, keyFormat: 'buffer'): Buffer
|
||||
prefixKey (key: Uint8Array, keyFormat: 'view'): Uint8Array
|
||||
|
||||
/**
|
||||
* Returns the given {@link encoding} argument as a normalized encoding object
|
||||
* that follows the [`level-transcoder`](https://github.com/Level/transcoder)
|
||||
* encoding interface.
|
||||
*/
|
||||
keyEncoding<N extends Transcoder.KnownEncodingName> (
|
||||
encoding: N
|
||||
): Transcoder.KnownEncoding<N, TFormat>
|
||||
|
||||
keyEncoding<TIn, TOut> (
|
||||
encoding: Transcoder.MixedEncoding<TIn, any, TOut>
|
||||
): Transcoder.Encoding<TIn, TFormat, TOut>
|
||||
|
||||
/**
|
||||
* Returns the default key encoding of the database as a normalized encoding
|
||||
* object that follows the [`level-transcoder`](https://github.com/Level/transcoder)
|
||||
* encoding interface.
|
||||
*/
|
||||
keyEncoding (): Transcoder.Encoding<KDefault, TFormat, KDefault>
|
||||
|
||||
/**
|
||||
* Returns the given {@link encoding} argument as a normalized encoding object
|
||||
* that follows the [`level-transcoder`](https://github.com/Level/transcoder)
|
||||
* encoding interface.
|
||||
*/
|
||||
valueEncoding<N extends Transcoder.KnownEncodingName> (
|
||||
encoding: N
|
||||
): Transcoder.KnownEncoding<N, TFormat>
|
||||
|
||||
valueEncoding<TIn, TOut> (
|
||||
encoding: Transcoder.MixedEncoding<TIn, any, TOut>
|
||||
): Transcoder.Encoding<TIn, TFormat, TOut>
|
||||
|
||||
/**
|
||||
* Returns the default value encoding of the database as a normalized encoding
|
||||
* object that follows the [`level-transcoder`](https://github.com/Level/transcoder)
|
||||
* encoding interface.
|
||||
*/
|
||||
valueEncoding (): Transcoder.Encoding<VDefault, TFormat, VDefault>
|
||||
|
||||
/**
|
||||
* Call the function {@link fn} at a later time when {@link status} changes to
|
||||
* `'open'` or `'closed'`.
|
||||
*/
|
||||
defer (fn: Function): void
|
||||
|
||||
/**
|
||||
* Schedule the function {@link fn} to be called in a next tick of the JavaScript
|
||||
* event loop, using a microtask scheduler. It will be called with the provided
|
||||
* {@link args}.
|
||||
*/
|
||||
nextTick (fn: Function, ...args: any[]): void
|
||||
}
|
||||
|
||||
export { AbstractLevel }
|
||||
|
||||
/**
|
||||
* Options for the database constructor.
|
||||
*/
|
||||
export interface AbstractDatabaseOptions<K, V>
|
||||
extends Omit<AbstractOpenOptions, 'passive'> {
|
||||
/**
|
||||
* Encoding to use for keys.
|
||||
* @defaultValue `'utf8'`
|
||||
*/
|
||||
keyEncoding?: string | Transcoder.PartialEncoding<K> | undefined
|
||||
|
||||
/**
|
||||
* Encoding to use for values.
|
||||
* @defaultValue `'utf8'`
|
||||
*/
|
||||
valueEncoding?: string | Transcoder.PartialEncoding<V> | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the {@link AbstractLevel.open} method.
|
||||
*/
|
||||
export interface AbstractOpenOptions {
|
||||
/**
|
||||
* If `true`, create an empty database if one doesn't already exist. If `false`
|
||||
* and the database doesn't exist, opening will fail.
|
||||
*
|
||||
* @defaultValue `true`
|
||||
*/
|
||||
createIfMissing?: boolean | undefined
|
||||
|
||||
/**
|
||||
* If `true` and the database already exists, opening will fail.
|
||||
*
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
errorIfExists?: boolean | undefined
|
||||
|
||||
/**
|
||||
* Wait for, but do not initiate, opening of the database.
|
||||
*
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
passive?: boolean | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the {@link AbstractLevel.get} method.
|
||||
*/
|
||||
export interface AbstractGetOptions<K, V> {
|
||||
/**
|
||||
* Custom key encoding for this operation, used to encode the `key`.
|
||||
*/
|
||||
keyEncoding?: string | Transcoder.PartialEncoder<K> | undefined
|
||||
|
||||
/**
|
||||
* Custom value encoding for this operation, used to decode the value.
|
||||
*/
|
||||
valueEncoding?: string | Transcoder.PartialDecoder<V> | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the {@link AbstractLevel.getMany} method.
|
||||
*/
|
||||
export interface AbstractGetManyOptions<K, V> {
|
||||
/**
|
||||
* Custom key encoding for this operation, used to encode the `keys`.
|
||||
*/
|
||||
keyEncoding?: string | Transcoder.PartialEncoder<K> | undefined
|
||||
|
||||
/**
|
||||
* Custom value encoding for this operation, used to decode values.
|
||||
*/
|
||||
valueEncoding?: string | Transcoder.PartialDecoder<V> | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the {@link AbstractLevel.put} method.
|
||||
*/
|
||||
export interface AbstractPutOptions<K, V> {
|
||||
/**
|
||||
* Custom key encoding for this operation, used to encode the `key`.
|
||||
*/
|
||||
keyEncoding?: string | Transcoder.PartialEncoder<K> | undefined
|
||||
|
||||
/**
|
||||
* Custom value encoding for this operation, used to encode the `value`.
|
||||
*/
|
||||
valueEncoding?: string | Transcoder.PartialEncoder<V> | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the {@link AbstractLevel.del} method.
|
||||
*/
|
||||
export interface AbstractDelOptions<K> {
|
||||
/**
|
||||
* Custom key encoding for this operation, used to encode the `key`.
|
||||
*/
|
||||
keyEncoding?: string | Transcoder.PartialEncoder<K> | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the {@link AbstractLevel.batch} method.
|
||||
*/
|
||||
export interface AbstractBatchOptions<K, V> {
|
||||
/**
|
||||
* Custom key encoding for this batch, used to encode keys.
|
||||
*/
|
||||
keyEncoding?: string | Transcoder.PartialEncoder<K> | undefined
|
||||
|
||||
/**
|
||||
* Custom value encoding for this batch, used to encode values.
|
||||
*/
|
||||
valueEncoding?: string | Transcoder.PartialEncoder<V> | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* A _put_ or _del_ operation to be committed with the {@link AbstractLevel.batch}
|
||||
* method.
|
||||
*/
|
||||
export type AbstractBatchOperation<TDatabase, K, V> =
|
||||
AbstractBatchPutOperation<TDatabase, K, V> | AbstractBatchDelOperation<TDatabase, K>
|
||||
|
||||
/**
|
||||
* A _put_ operation to be committed with the {@link AbstractLevel.batch} method.
|
||||
*/
|
||||
export interface AbstractBatchPutOperation<TDatabase, K, V> {
|
||||
type: 'put'
|
||||
key: K
|
||||
value: V
|
||||
|
||||
/**
|
||||
* Custom key encoding for this _put_ operation, used to encode the {@link key}.
|
||||
*/
|
||||
keyEncoding?: string | Transcoder.PartialEncoding<K> | undefined
|
||||
|
||||
/**
|
||||
* Custom key encoding for this _put_ operation, used to encode the {@link value}.
|
||||
*/
|
||||
valueEncoding?: string | Transcoder.PartialEncoding<V> | undefined
|
||||
|
||||
/**
|
||||
* Act as though the _put_ operation is performed on the given sublevel, to similar
|
||||
* effect as:
|
||||
*
|
||||
* ```js
|
||||
* await sublevel.batch([{ type: 'put', key, value }])
|
||||
* ```
|
||||
*
|
||||
* This allows atomically committing data to multiple sublevels. The {@link key} will
|
||||
* be prefixed with the `prefix` of the sublevel, and the {@link key} and {@link value}
|
||||
* will be encoded by the sublevel (using the default encodings of the sublevel unless
|
||||
* {@link keyEncoding} and / or {@link valueEncoding} are provided).
|
||||
*/
|
||||
sublevel?: AbstractSublevel<TDatabase, any, any, any> | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* A _del_ operation to be committed with the {@link AbstractLevel.batch} method.
|
||||
*/
|
||||
export interface AbstractBatchDelOperation<TDatabase, K> {
|
||||
type: 'del'
|
||||
key: K
|
||||
|
||||
/**
|
||||
* Custom key encoding for this _del_ operation, used to encode the {@link key}.
|
||||
*/
|
||||
keyEncoding?: string | Transcoder.PartialEncoding<K> | undefined
|
||||
|
||||
/**
|
||||
* Act as though the _del_ operation is performed on the given sublevel, to similar
|
||||
* effect as:
|
||||
*
|
||||
* ```js
|
||||
* await sublevel.batch([{ type: 'del', key }])
|
||||
* ```
|
||||
*
|
||||
* This allows atomically committing data to multiple sublevels. The {@link key} will
|
||||
* be prefixed with the `prefix` of the sublevel, and the {@link key} will be encoded
|
||||
* by the sublevel (using the default key encoding of the sublevel unless
|
||||
* {@link keyEncoding} is provided).
|
||||
*/
|
||||
sublevel?: AbstractSublevel<TDatabase, any, any, any> | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the {@link AbstractLevel.clear} method.
|
||||
*/
|
||||
export interface AbstractClearOptions<K> extends RangeOptions<K> {
|
||||
/**
|
||||
* Custom key encoding for this operation, used to encode range options.
|
||||
*/
|
||||
keyEncoding?: string | Transcoder.PartialEncoding<K> | undefined
|
||||
}
|
||||
Generated
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
import * as Transcoder from 'level-transcoder'
|
||||
import { AbstractLevel } from './abstract-level'
|
||||
|
||||
/**
|
||||
* @template TDatabase Type of parent database.
|
||||
* @template TFormat The type used internally by the parent database to store data.
|
||||
* @template KDefault The default type of keys if not overridden on operations.
|
||||
* @template VDefault The default type of values if not overridden on operations.
|
||||
*/
|
||||
declare class AbstractSublevel<TDatabase, TFormat, KDefault, VDefault>
|
||||
extends AbstractLevel<TFormat, KDefault, VDefault> {
|
||||
/**
|
||||
* Sublevel constructor.
|
||||
*
|
||||
* @param db Parent database.
|
||||
* @param name Name of the sublevel, used to prefix keys.
|
||||
*/
|
||||
constructor (
|
||||
db: TDatabase,
|
||||
name: string,
|
||||
options?: AbstractSublevelOptions<KDefault, VDefault> | undefined
|
||||
)
|
||||
|
||||
/**
|
||||
* Prefix of the sublevel. A read-only string property.
|
||||
*/
|
||||
get prefix (): string
|
||||
|
||||
/**
|
||||
* Parent database. A read-only property.
|
||||
*/
|
||||
get db (): TDatabase
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the {@link AbstractLevel.sublevel} method.
|
||||
*/
|
||||
export interface AbstractSublevelOptions<K, V> {
|
||||
/**
|
||||
* Character for separating sublevel names from user keys and each other. Must sort
|
||||
* before characters used in `name`. An error will be thrown if that's not the case.
|
||||
*
|
||||
* @defaultValue `'!'`
|
||||
*/
|
||||
separator?: string | undefined
|
||||
|
||||
/**
|
||||
* Encoding to use for keys.
|
||||
* @defaultValue `'utf8'`
|
||||
*/
|
||||
keyEncoding?: string | Transcoder.PartialEncoding<K> | undefined
|
||||
|
||||
/**
|
||||
* Encoding to use for values.
|
||||
* @defaultValue `'utf8'`
|
||||
*/
|
||||
valueEncoding?: string | Transcoder.PartialEncoding<V> | undefined
|
||||
}
|
||||
Generated
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* An error-first callback in the style of Node.js.
|
||||
*/
|
||||
export type NodeCallback<T> =
|
||||
(err: Error | undefined | null, result?: T | undefined) => void
|
||||
|
||||
export interface RangeOptions<K> {
|
||||
gt?: K
|
||||
gte?: K
|
||||
lt?: K
|
||||
lte?: K
|
||||
reverse?: boolean | undefined
|
||||
limit?: number | undefined
|
||||
}
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Jameson Little
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
Generated
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
base64-js
|
||||
=========
|
||||
|
||||
`base64-js` does basic base64 encoding/decoding in pure JS.
|
||||
|
||||
[](http://travis-ci.org/beatgammit/base64-js)
|
||||
|
||||
Many browsers already have base64 encoding/decoding functionality, but it is for text data, not all-purpose binary data.
|
||||
|
||||
Sometimes encoding/decoding binary data in the browser is useful, and that is what this module does.
|
||||
|
||||
## install
|
||||
|
||||
With [npm](https://npmjs.org) do:
|
||||
|
||||
`npm install base64-js` and `var base64js = require('base64-js')`
|
||||
|
||||
For use in web browsers do:
|
||||
|
||||
`<script src="base64js.min.js"></script>`
|
||||
|
||||
[Get supported base64-js with the Tidelift Subscription](https://tidelift.com/subscription/pkg/npm-base64-js?utm_source=npm-base64-js&utm_medium=referral&utm_campaign=readme)
|
||||
|
||||
## methods
|
||||
|
||||
`base64js` has three exposed functions, `byteLength`, `toByteArray` and `fromByteArray`, which both take a single argument.
|
||||
|
||||
* `byteLength` - Takes a base64 string and returns length of byte array
|
||||
* `toByteArray` - Takes a base64 string and returns a byte array
|
||||
* `fromByteArray` - Takes a byte array and returns a base64 string
|
||||
|
||||
## license
|
||||
|
||||
MIT
|
||||
RevokeMsgPatcher.v1.8.1/LiteLoaderQQNT/plugins/qq-anti-recall/node_modules/base64-js/base64js.min.js
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
(function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"==typeof window?"undefined"==typeof global?"undefined"==typeof self?this:self:global:window,b.base64js=a()}})(function(){return function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f="function"==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error("Cannot find module '"+j+"'");throw c.code="MODULE_NOT_FOUND",c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h="function"==typeof require&&require,c=0;c<g.length;c++)a(g[c]);return a}return b}()({"/":[function(a,b,c){'use strict';function d(a){var b=a.length;if(0<b%4)throw new Error("Invalid string. Length must be a multiple of 4");var c=a.indexOf("=");-1===c&&(c=b);var d=c===b?0:4-c%4;return[c,d]}function e(a,b,c){return 3*(b+c)/4-c}function f(a){var b,c,f=d(a),g=f[0],h=f[1],j=new m(e(a,g,h)),k=0,n=0<h?g-4:g;for(c=0;c<n;c+=4)b=l[a.charCodeAt(c)]<<18|l[a.charCodeAt(c+1)]<<12|l[a.charCodeAt(c+2)]<<6|l[a.charCodeAt(c+3)],j[k++]=255&b>>16,j[k++]=255&b>>8,j[k++]=255&b;return 2===h&&(b=l[a.charCodeAt(c)]<<2|l[a.charCodeAt(c+1)]>>4,j[k++]=255&b),1===h&&(b=l[a.charCodeAt(c)]<<10|l[a.charCodeAt(c+1)]<<4|l[a.charCodeAt(c+2)]>>2,j[k++]=255&b>>8,j[k++]=255&b),j}function g(a){return k[63&a>>18]+k[63&a>>12]+k[63&a>>6]+k[63&a]}function h(a,b,c){for(var d,e=[],f=b;f<c;f+=3)d=(16711680&a[f]<<16)+(65280&a[f+1]<<8)+(255&a[f+2]),e.push(g(d));return e.join("")}function j(a){for(var b,c=a.length,d=c%3,e=[],f=16383,g=0,j=c-d;g<j;g+=f)e.push(h(a,g,g+f>j?j:g+f));return 1===d?(b=a[c-1],e.push(k[b>>2]+k[63&b<<4]+"==")):2===d&&(b=(a[c-2]<<8)+a[c-1],e.push(k[b>>10]+k[63&b>>4]+k[63&b<<2]+"=")),e.join("")}c.byteLength=function(a){var b=d(a),c=b[0],e=b[1];return 3*(c+e)/4-e},c.toByteArray=f,c.fromByteArray=j;for(var k=[],l=[],m="undefined"==typeof Uint8Array?Array:Uint8Array,n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",o=0,p=n.length;o<p;++o)k[o]=n[o],l[n.charCodeAt(o)]=o;l[45]=62,l[95]=63},{}]},{},[])("/")});
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
export function byteLength(b64: string): number;
|
||||
export function toByteArray(b64: string): Uint8Array;
|
||||
export function fromByteArray(uint8: Uint8Array): string;
|
||||
Generated
Vendored
+150
@@ -0,0 +1,150 @@
|
||||
'use strict'
|
||||
|
||||
exports.byteLength = byteLength
|
||||
exports.toByteArray = toByteArray
|
||||
exports.fromByteArray = fromByteArray
|
||||
|
||||
var lookup = []
|
||||
var revLookup = []
|
||||
var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array
|
||||
|
||||
var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
for (var i = 0, len = code.length; i < len; ++i) {
|
||||
lookup[i] = code[i]
|
||||
revLookup[code.charCodeAt(i)] = i
|
||||
}
|
||||
|
||||
// Support decoding URL-safe base64 strings, as Node.js does.
|
||||
// See: https://en.wikipedia.org/wiki/Base64#URL_applications
|
||||
revLookup['-'.charCodeAt(0)] = 62
|
||||
revLookup['_'.charCodeAt(0)] = 63
|
||||
|
||||
function getLens (b64) {
|
||||
var len = b64.length
|
||||
|
||||
if (len % 4 > 0) {
|
||||
throw new Error('Invalid string. Length must be a multiple of 4')
|
||||
}
|
||||
|
||||
// Trim off extra bytes after placeholder bytes are found
|
||||
// See: https://github.com/beatgammit/base64-js/issues/42
|
||||
var validLen = b64.indexOf('=')
|
||||
if (validLen === -1) validLen = len
|
||||
|
||||
var placeHoldersLen = validLen === len
|
||||
? 0
|
||||
: 4 - (validLen % 4)
|
||||
|
||||
return [validLen, placeHoldersLen]
|
||||
}
|
||||
|
||||
// base64 is 4/3 + up to two characters of the original data
|
||||
function byteLength (b64) {
|
||||
var lens = getLens(b64)
|
||||
var validLen = lens[0]
|
||||
var placeHoldersLen = lens[1]
|
||||
return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen
|
||||
}
|
||||
|
||||
function _byteLength (b64, validLen, placeHoldersLen) {
|
||||
return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen
|
||||
}
|
||||
|
||||
function toByteArray (b64) {
|
||||
var tmp
|
||||
var lens = getLens(b64)
|
||||
var validLen = lens[0]
|
||||
var placeHoldersLen = lens[1]
|
||||
|
||||
var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen))
|
||||
|
||||
var curByte = 0
|
||||
|
||||
// if there are placeholders, only get up to the last complete 4 chars
|
||||
var len = placeHoldersLen > 0
|
||||
? validLen - 4
|
||||
: validLen
|
||||
|
||||
var i
|
||||
for (i = 0; i < len; i += 4) {
|
||||
tmp =
|
||||
(revLookup[b64.charCodeAt(i)] << 18) |
|
||||
(revLookup[b64.charCodeAt(i + 1)] << 12) |
|
||||
(revLookup[b64.charCodeAt(i + 2)] << 6) |
|
||||
revLookup[b64.charCodeAt(i + 3)]
|
||||
arr[curByte++] = (tmp >> 16) & 0xFF
|
||||
arr[curByte++] = (tmp >> 8) & 0xFF
|
||||
arr[curByte++] = tmp & 0xFF
|
||||
}
|
||||
|
||||
if (placeHoldersLen === 2) {
|
||||
tmp =
|
||||
(revLookup[b64.charCodeAt(i)] << 2) |
|
||||
(revLookup[b64.charCodeAt(i + 1)] >> 4)
|
||||
arr[curByte++] = tmp & 0xFF
|
||||
}
|
||||
|
||||
if (placeHoldersLen === 1) {
|
||||
tmp =
|
||||
(revLookup[b64.charCodeAt(i)] << 10) |
|
||||
(revLookup[b64.charCodeAt(i + 1)] << 4) |
|
||||
(revLookup[b64.charCodeAt(i + 2)] >> 2)
|
||||
arr[curByte++] = (tmp >> 8) & 0xFF
|
||||
arr[curByte++] = tmp & 0xFF
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
|
||||
function tripletToBase64 (num) {
|
||||
return lookup[num >> 18 & 0x3F] +
|
||||
lookup[num >> 12 & 0x3F] +
|
||||
lookup[num >> 6 & 0x3F] +
|
||||
lookup[num & 0x3F]
|
||||
}
|
||||
|
||||
function encodeChunk (uint8, start, end) {
|
||||
var tmp
|
||||
var output = []
|
||||
for (var i = start; i < end; i += 3) {
|
||||
tmp =
|
||||
((uint8[i] << 16) & 0xFF0000) +
|
||||
((uint8[i + 1] << 8) & 0xFF00) +
|
||||
(uint8[i + 2] & 0xFF)
|
||||
output.push(tripletToBase64(tmp))
|
||||
}
|
||||
return output.join('')
|
||||
}
|
||||
|
||||
function fromByteArray (uint8) {
|
||||
var tmp
|
||||
var len = uint8.length
|
||||
var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes
|
||||
var parts = []
|
||||
var maxChunkLength = 16383 // must be multiple of 3
|
||||
|
||||
// go through the array every three bytes, we'll deal with trailing stuff later
|
||||
for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
|
||||
parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)))
|
||||
}
|
||||
|
||||
// pad the end with zeros, but make sure to not forget the extra bytes
|
||||
if (extraBytes === 1) {
|
||||
tmp = uint8[len - 1]
|
||||
parts.push(
|
||||
lookup[tmp >> 2] +
|
||||
lookup[(tmp << 4) & 0x3F] +
|
||||
'=='
|
||||
)
|
||||
} else if (extraBytes === 2) {
|
||||
tmp = (uint8[len - 2] << 8) + uint8[len - 1]
|
||||
parts.push(
|
||||
lookup[tmp >> 10] +
|
||||
lookup[(tmp >> 4) & 0x3F] +
|
||||
lookup[(tmp << 2) & 0x3F] +
|
||||
'='
|
||||
)
|
||||
}
|
||||
|
||||
return parts.join('')
|
||||
}
|
||||
Generated
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "base64-js",
|
||||
"description": "Base64 encoding/decoding in pure JS",
|
||||
"version": "1.5.1",
|
||||
"author": "T. Jameson Little <t.jameson.little@gmail.com>",
|
||||
"typings": "index.d.ts",
|
||||
"bugs": {
|
||||
"url": "https://github.com/beatgammit/base64-js/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-minify": "^0.5.1",
|
||||
"benchmark": "^2.1.4",
|
||||
"browserify": "^16.3.0",
|
||||
"standard": "*",
|
||||
"tape": "4.x"
|
||||
},
|
||||
"homepage": "https://github.com/beatgammit/base64-js",
|
||||
"keywords": [
|
||||
"base64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/beatgammit/base64-js.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "browserify -s base64js -r ./ | minify > base64js.min.js",
|
||||
"lint": "standard",
|
||||
"test": "npm run lint && npm run unit",
|
||||
"unit": "tape test/*.js"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
}
|
||||
Generated
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## [1.0.1] - 2022-03-06
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix TypeScript type declarations ([`d5db420`](https://github.com/Level/browser-level/commit/d5db420)) (Vincent Weevers)
|
||||
|
||||
## [1.0.0] - 2022-03-05
|
||||
|
||||
_:seedling: Initial release. If you are upgrading from `level-js`, please see [UPGRADING.md](UPGRADING.md)._
|
||||
|
||||
[1.0.1]: https://github.com/Level/browser-level/releases/tag/v1.0.1
|
||||
|
||||
[1.0.0]: https://github.com/Level/browser-level/releases/tag/v1.0.0
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2012 Max Ogden and the contributors to browser-level.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Generated
Vendored
+149
@@ -0,0 +1,149 @@
|
||||
# browser-level
|
||||
|
||||
**An [`abstract-level`][abstract-level] database for browsers, backed by [IndexedDB][indexeddb].** The successor to [`level-js`](https://github.com/Level/level-js). If you are upgrading, please see [UPGRADING.md](UPGRADING.md).
|
||||
|
||||
> :pushpin: Which module should I use? What is `abstract-level`? Head over to the [FAQ](https://github.com/Level/community#faq).
|
||||
|
||||
[![level badge][level-badge]][awesome]
|
||||
[](https://www.npmjs.com/package/browser-level)
|
||||
[](https://github.com/Level/browser-level/actions/workflows/test.yml)
|
||||
[](https://codecov.io/gh/Level/browser-level)
|
||||
[](https://standardjs.com)
|
||||
[](https://common-changelog.org)
|
||||
[](https://opencollective.com/level)
|
||||
|
||||
## Table of Contents
|
||||
|
||||
<details><summary>Click to expand</summary>
|
||||
|
||||
- [Usage](#usage)
|
||||
- [API](#api)
|
||||
- [`db = new BrowserLevel(location[, options])`](#db--new-browserlevellocation-options)
|
||||
- [`BrowserLevel.destroy(location[, prefix][, callback])`](#browserleveldestroylocation-prefix-callback)
|
||||
- [Install](#install)
|
||||
- [Contributing](#contributing)
|
||||
- [Donate](#donate)
|
||||
- [License](#license)
|
||||
|
||||
</details>
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
const { BrowserLevel } = require('browser-level')
|
||||
|
||||
// Create a database called 'example'
|
||||
const db = new BrowserLevel('example', { valueEncoding: 'json' })
|
||||
|
||||
// Add an entry with key 'a' and value 1
|
||||
await db.put('a', 1)
|
||||
|
||||
// Add multiple entries
|
||||
await db.batch([{ type: 'put', key: 'b', value: 2 }])
|
||||
|
||||
// Get value of key 'a': 1
|
||||
const value = await db.get('a')
|
||||
|
||||
// Iterate entries with keys that are greater than 'a'
|
||||
for await (const [key, value] of db.iterator({ gt: 'a' })) {
|
||||
console.log(value) // 2
|
||||
}
|
||||
```
|
||||
|
||||
<!-- ## Browser Support -->
|
||||
|
||||
<!-- [](https://app.saucelabs.com/u/level-js) -->
|
||||
|
||||
## API
|
||||
|
||||
The API of `browser-level` follows that of [`abstract-level`](https://github.com/Level/abstract-level) with just two additional constructor options (see below) and one additional method (see below). As such, the majority of the API is documented in `abstract-level`. The `createIfMissing` and `errorIfExists` options of `abstract-level` are not supported here.
|
||||
|
||||
Like other implementations of `abstract-level`, `browser-level` has first-class support of binary keys and values, using either [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) or [Buffer](https://nodejs.org/api/buffer.html). In order to sort string and binary keys the same way as other databases, `browser-level` internally converts data to a Uint8Array before passing them to IndexedDB. If you have no need to work with `Buffer` keys or values, you can choose to omit the [`buffer`](https://github.com/feross/buffer) shim from a JavaScript bundle (through configuration of Webpack, Browserify or other bundlers).
|
||||
|
||||
Due to limitations of IndexedDB, `browser-level` does not offer snapshot guarantees. Such a guarantee would mean that an iterator does not see the data of simultaneous writes - it would be reading from a snapshot in time. In contrast, a `browser-level` iterator reads a few entries ahead and then opens a new IndexedDB transaction on the next read. A "few" means all entries for `iterator.all()`, `size` amount of entries for `iterator.nextv(size)` and a hardcoded 100 entries for `iterator.next()`. Individual calls to those methods have snapshot guarantees but repeated calls do not.
|
||||
|
||||
The result is that an iterator will include the data of simultaneous writes, if `db.put()`, `db.del()` or `db.batch()` are called in between creating the iterator and consuming the iterator, or in between calls to `iterator.next()` or `iterator.nextv()`. For example:
|
||||
|
||||
```js
|
||||
const iterator = db.iterator()
|
||||
await db.put('abc', '123')
|
||||
|
||||
for await (const [key, value] of iterator) {
|
||||
// This might be 'abc'
|
||||
console.log(key)
|
||||
}
|
||||
```
|
||||
|
||||
If snapshot guarantees are a must for your application then use `iterator.all()` and call it immediately after creating the iterator:
|
||||
|
||||
```js
|
||||
const entries = await db.iterator({ limit: 50 }).all()
|
||||
|
||||
// Synchronously iterate the result
|
||||
for (const [key, value] of entries) {
|
||||
console.log(key)
|
||||
}
|
||||
```
|
||||
|
||||
### `db = new BrowserLevel(location[, options])`
|
||||
|
||||
Create a new database or open an existing one. The required `location` argument is the string name of the [`IDBDatabase`](https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase) to be opened, as well as the name of the object store within that database. The name of the `IDBDatabase` will be prefixed with `options.prefix`.
|
||||
|
||||
Besides `abstract-level` options, the optional `options` argument may contain:
|
||||
|
||||
- `prefix` (string, default: `'level-js-'`): Prefix for the `IDBDatabase` name. Can be set to an empty string. The default is compatible with `level-js`.
|
||||
- `version` (string or number, default: `1`): The version to open the `IDBDatabase` with.
|
||||
|
||||
See [`IDBFactory#open()`](https://developer.mozilla.org/en-US/docs/Web/API/IDBFactory/open) for more details about database name and version.
|
||||
|
||||
### `BrowserLevel.destroy(location[, prefix][, callback])`
|
||||
|
||||
Delete the IndexedDB database at the given `location`. If `prefix` is not given, it defaults to the same value as the `BrowserLevel` constructor does. The `callback` function will be called when the destroy operation is complete, with a possible error argument. If no callback is provided, a promise is returned. This method is an additional method that is not part of the [`abstract-level`](https://github.com/Level/abstract-level) interface.
|
||||
|
||||
Before calling `destroy()`, close a database if it's using the same `location` and `prefix`:
|
||||
|
||||
```js
|
||||
const db = new BrowserLevel('example')
|
||||
await db.close()
|
||||
await BrowserLevel.destroy('example')
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
With [npm](https://npmjs.org) do:
|
||||
|
||||
```bash
|
||||
npm install browser-level
|
||||
```
|
||||
|
||||
This module is best used with [`browserify`](http://browserify.org) or similar bundlers.
|
||||
|
||||
<!-- ## Big Thanks
|
||||
|
||||
Cross-browser Testing Platform and Open Source ♥ Provided by [Sauce Labs](https://saucelabs.com).
|
||||
|
||||
[](https://saucelabs.com) -->
|
||||
|
||||
## Contributing
|
||||
|
||||
[`Level/browser-level`](https://github.com/Level/browser-level) is an **OPEN Open Source Project**. This means that:
|
||||
|
||||
> Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project.
|
||||
|
||||
See the [Contribution Guide](https://github.com/Level/community/blob/master/CONTRIBUTING.md) for more details.
|
||||
|
||||
## Donate
|
||||
|
||||
Support us with a monthly donation on [Open Collective](https://opencollective.com/level) and help us continue our work.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
[level-badge]: https://leveljs.org/img/badge.svg
|
||||
|
||||
[indexeddb]: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
|
||||
|
||||
[awesome]: https://github.com/Level/awesome
|
||||
|
||||
[abstract-level]: https://github.com/Level/abstract-level
|
||||
Generated
Vendored
+135
@@ -0,0 +1,135 @@
|
||||
# Upgrade Guide
|
||||
|
||||
This document describes breaking changes and how to upgrade. For a complete list of changes including minor and patch releases, please refer to the [changelog](CHANGELOG.md).
|
||||
|
||||
## 1.0.0
|
||||
|
||||
**Introducing `browser-level`: a fork of [`level-js`](https://github.com/Level/level-js) that removes the need for [`levelup`](https://github.com/Level/levelup) and more. It implements the [`abstract-level`](https://github.com/Level/abstract-level) interface instead of [`abstract-leveldown`](https://github.com/Level/abstract-leveldown) and thus has the same API as `level` and `levelup` including encodings, promises and events. In addition, you can now choose to use Uint8Array instead of Buffer. Sublevels are builtin.**
|
||||
|
||||
We've put together several upgrade guides for different modules. See the [FAQ](https://github.com/Level/community#faq) to find the best upgrade guide for you. This one describes how to replace `level-js` with `browser-level`. There will be a separate guide for `level`.
|
||||
|
||||
### What is not covered here
|
||||
|
||||
If you are using any of the following, please also read the upgrade guide of [`abstract-level@1`](https://github.com/Level/abstract-level/blob/main/UPGRADING.md#100) which goes into more detail about these:
|
||||
|
||||
- Specific error messages (replaced with error codes)
|
||||
- The `db.iterator().end()` method (renamed to `close()`, with `end()` as an alias)
|
||||
- Zero-length keys and range options (now valid)
|
||||
- The `db.supports.bufferKeys` property.
|
||||
|
||||
### Changes to initialization
|
||||
|
||||
We started using classes, which means using `new` is now required. If you previously did:
|
||||
|
||||
```js
|
||||
const levelJs = require('level-js')
|
||||
const db = levelJs('example')
|
||||
```
|
||||
|
||||
You must now do:
|
||||
|
||||
```js
|
||||
const { BrowserLevel } = require('browser-level')
|
||||
const db = new BrowserLevel('example')
|
||||
```
|
||||
|
||||
Arguments and options are the same except that `browser-level` has additional options to control encodings. For backwards compatibility, the `prefix` option has the same default value. Namely `'level-js-'`.
|
||||
|
||||
### There is only encodings
|
||||
|
||||
The `asBuffer`, `valueAsBuffer` and `keyAsBuffer` options have been replaced with encoding options. The default encoding is `'utf8'` which means operations return strings rather than Buffers by default. If you previously did:
|
||||
|
||||
```js
|
||||
db.get('example', { asBuffer: false }, callback)
|
||||
db.get('example', callback)
|
||||
```
|
||||
|
||||
You must now do:
|
||||
|
||||
```js
|
||||
db.get('example', callback)
|
||||
db.get('example', { valueEncoding: 'buffer' }, callback)
|
||||
```
|
||||
|
||||
Or using promises (new):
|
||||
|
||||
```js
|
||||
const str = await db.get('example')
|
||||
const buf = await db.get('example', { valueEncoding: 'buffer' })
|
||||
```
|
||||
|
||||
Or using Uint8Array (new):
|
||||
|
||||
```js
|
||||
const arr = await db.get('example', { valueEncoding: 'view' })
|
||||
```
|
||||
|
||||
### Uint8Array support
|
||||
|
||||
A `browser-level` database uses Uint8Array internally, instead of Buffer like `level-js` did. This doesn't change the ability to read existing databases: `browser-level` can read and write databases that were created with `level-js` and vice versa. Externally both Uint8Array and Buffer can be used (see README for details) to maintain backwards- and ecosystem compatibility. You can choose to use Uint8Array exclusively and omit the `buffer` shim from a JavaScript bundle (through configuration of Webpack, Browserify or other bundlers).
|
||||
|
||||
### Unwrapping the onion
|
||||
|
||||
If you were wrapping `level-js` with `levelup`, `encoding-down` and / or `subleveldown`, remove those modules. If you previously did:
|
||||
|
||||
```js
|
||||
const levelJs = require('level-js')
|
||||
const levelup = require('levelup')
|
||||
const enc = require('encoding-down')
|
||||
const subleveldown = require('subleveldown')
|
||||
|
||||
const db = levelup(enc(levelJs('example')))
|
||||
const sublevel = subleveldown(db, 'foo')
|
||||
```
|
||||
|
||||
You must now do:
|
||||
|
||||
```js
|
||||
const { BrowserLevel } = require('browser-level')
|
||||
const db = new BrowserLevel('example')
|
||||
const sublevel = db.sublevel('foo')
|
||||
```
|
||||
|
||||
### Prefers backpressure over snapshot guarantees
|
||||
|
||||
Previously (in `level-js`) an iterator would keep reading in the background, so as to keep the underlying IndexedDB transaction alive and thus not see the data of simultaneous writes. I.e. it was reading from a snapshot in time. This had two major downsides. It was not possible to lazily iterate a large database, as all of the data would be read into memory. Secondly, IndexedDB doesn't actually use a snapshot (the Chrome implementation used to, others never did) but rather a blocking transaction. Meaning you couldn't write to a database while an iterator was active; the write would wait for the iterator to end.
|
||||
|
||||
To solve those issues, an iterator now reads a few entries ahead and then opens a new transaction on the next read. A "few" means all entries for `iterator.all()`, `size` amount of entries for `iterator.nextv(size)` and a hardcoded 100 entries for `iterator.next()`. Individual calls to those methods still have snapshot guarantees, but repeated calls do not.
|
||||
|
||||
The resulting breaking change is that an iterator will include the data of simultaneous writes, if `db.put()`, `db.del()` or `db.batch()` are called in between creating the iterator and consuming the iterator, or in between calls to `iterator.next()` or `iterator.nextv()`. For example:
|
||||
|
||||
```js
|
||||
const iterator = db.iterator()
|
||||
await db.put('abc', '123')
|
||||
|
||||
for await (const [key, value] of iterator) {
|
||||
// This might be 'abc'
|
||||
console.log(key)
|
||||
}
|
||||
```
|
||||
|
||||
To reflect the new behavior, `db.supports.snapshots` is now false. If snapshot guarantees are a must for your application then use `iterator.all()` (which is a new method compared to `level-js`) and call it immediately after creating the iterator:
|
||||
|
||||
```js
|
||||
const entries = await db.iterator({ limit: 50 }).all()
|
||||
|
||||
// Synchronously iterate the result
|
||||
for (const [key, value] of entries) {
|
||||
console.log(key)
|
||||
}
|
||||
```
|
||||
|
||||
Unrelated, iterating should now be faster because `browser-level` iterators use the `getAll()` and `getAllKeys()` methods of IndexedDB, instead of a cursor like `level-js` did. This means multiple entries are transferred from IndexedDB to JS in a single turn of the JS event loop, rather than one turn per entry. Reverse iterators do still use a cursor and are therefor slower.
|
||||
|
||||
Lastly, the new logic enabled us to implement and fully support `iterator.seek()`.
|
||||
|
||||
### Changes to lesser-used properties and methods
|
||||
|
||||
- The `db.prefix` property of `level-js`, conflicting with the `db.prefix` property of sublevels, has been renamed to `db.namePrefix`. The relevant constructor option (`prefix`) remains the same.
|
||||
- The `db.location`, `db.version` and `db.db` properties are now read-only getters
|
||||
- The internal `db.store()` and `db.await()` methods are no longer accessible
|
||||
- The `db.upgrade()` utility for upgrading from v4.x to v5.x has been removed.
|
||||
|
||||
---
|
||||
|
||||
_For earlier releases, before `browser-level` was forked from `level-js` (v6.1.0), please see [the upgrade guide of `level-js`](https://github.com/Level/level-js/blob/HEAD/UPGRADING.md)._
|
||||
Generated
Vendored
+111
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
AbstractLevel,
|
||||
AbstractDatabaseOptions,
|
||||
NodeCallback,
|
||||
AbstractOpenOptions,
|
||||
AbstractGetOptions,
|
||||
AbstractGetManyOptions,
|
||||
AbstractPutOptions,
|
||||
AbstractDelOptions,
|
||||
AbstractBatchOptions,
|
||||
AbstractChainedBatch,
|
||||
AbstractIterator,
|
||||
AbstractKeyIterator,
|
||||
AbstractValueIterator,
|
||||
AbstractIteratorOptions,
|
||||
AbstractKeyIteratorOptions,
|
||||
AbstractValueIteratorOptions,
|
||||
AbstractBatchOperation
|
||||
} from 'abstract-level'
|
||||
|
||||
/**
|
||||
* An {@link AbstractLevel} database for browsers, backed by [IndexedDB][1].
|
||||
*
|
||||
* @template KDefault The default type of keys if not overridden on operations.
|
||||
* @template VDefault The default type of values if not overridden on operations.
|
||||
*
|
||||
* [1]: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
|
||||
*/
|
||||
export class BrowserLevel<KDefault = string, VDefault = string>
|
||||
extends AbstractLevel<Uint8Array, KDefault, VDefault> {
|
||||
/**
|
||||
* Database constructor.
|
||||
*
|
||||
* @param location The name of the [`IDBDatabase`](https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase)
|
||||
* to be opened, as well as the name of the object store within that database. The name
|
||||
* of the `IDBDatabase` will be prefixed with {@link DatabaseOptions.prefix}.
|
||||
* @param options Options, of which some will be forwarded to {@link open}.
|
||||
*/
|
||||
constructor (location: string, options?: DatabaseOptions<KDefault, VDefault> | undefined)
|
||||
|
||||
/**
|
||||
* Location that was passed to the constructor.
|
||||
*/
|
||||
get location (): string
|
||||
|
||||
/**
|
||||
* Database name prefix that was passed to the constructor (as `prefix`).
|
||||
*/
|
||||
get namePrefix (): string
|
||||
|
||||
/**
|
||||
* Version that was passed to the constructor.
|
||||
*/
|
||||
get version (): number
|
||||
|
||||
/**
|
||||
* Delete the IndexedDB database at the given {@link location}.
|
||||
*/
|
||||
static destroy (location: string): Promise<void>
|
||||
static destroy (location: string, prefix: string): Promise<void>
|
||||
static destroy (location: string, callback: NodeCallback<void>): void
|
||||
static destroy (location: string, prefix: string, callback: NodeCallback<void>): void
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the {@link BrowserLevel} constructor.
|
||||
*/
|
||||
export interface DatabaseOptions<K, V> extends AbstractDatabaseOptions<K, V> {
|
||||
/**
|
||||
* Prefix for the `IDBDatabase` name. Can be set to an empty string.
|
||||
*
|
||||
* @defaultValue `'level-js-'`
|
||||
*/
|
||||
prefix?: string
|
||||
|
||||
/**
|
||||
* The version to open the `IDBDatabase` with.
|
||||
*
|
||||
* @defaultValue `1`
|
||||
*/
|
||||
version?: number | string
|
||||
|
||||
/**
|
||||
* An {@link AbstractLevel} option that has no effect on {@link BrowserLevel}.
|
||||
*/
|
||||
createIfMissing?: boolean
|
||||
|
||||
/**
|
||||
* An {@link AbstractLevel} option that has no effect on {@link BrowserLevel}.
|
||||
*/
|
||||
errorIfExists?: boolean
|
||||
}
|
||||
|
||||
// Export types so that consumers don't have to guess whether they're extended
|
||||
export type OpenOptions = AbstractOpenOptions
|
||||
export type GetOptions<K, V> = AbstractGetOptions<K, V>
|
||||
export type GetManyOptions<K, V> = AbstractGetManyOptions<K, V>
|
||||
export type PutOptions<K, V> = AbstractPutOptions<K, V>
|
||||
export type DelOptions<K> = AbstractDelOptions<K>
|
||||
|
||||
export type BatchOptions<K, V> = AbstractBatchOptions<K, V>
|
||||
export type BatchOperation<TDatabase, K, V> = AbstractBatchOperation<TDatabase, K, V>
|
||||
export type ChainedBatch<TDatabase, K, V> = AbstractChainedBatch<TDatabase, K, V>
|
||||
|
||||
export type Iterator<TDatabase, K, V> = AbstractIterator<TDatabase, K, V>
|
||||
export type KeyIterator<TDatabase, K> = AbstractKeyIterator<TDatabase, K>
|
||||
export type ValueIterator<TDatabase, K, V> = AbstractValueIterator<TDatabase, K, V>
|
||||
|
||||
export type IteratorOptions<K, V> = AbstractIteratorOptions<K, V>
|
||||
export type KeyIteratorOptions<K> = AbstractKeyIteratorOptions<K>
|
||||
export type ValueIteratorOptions<K, V> = AbstractValueIteratorOptions<K, V>
|
||||
Generated
Vendored
+291
@@ -0,0 +1,291 @@
|
||||
/* global indexedDB */
|
||||
|
||||
'use strict'
|
||||
|
||||
const { AbstractLevel } = require('abstract-level')
|
||||
const ModuleError = require('module-error')
|
||||
const parallel = require('run-parallel-limit')
|
||||
const { fromCallback } = require('catering')
|
||||
const { Iterator } = require('./iterator')
|
||||
const deserialize = require('./util/deserialize')
|
||||
const clear = require('./util/clear')
|
||||
const createKeyRange = require('./util/key-range')
|
||||
|
||||
// Keep as-is for compatibility with existing level-js databases
|
||||
const DEFAULT_PREFIX = 'level-js-'
|
||||
|
||||
const kIDB = Symbol('idb')
|
||||
const kNamePrefix = Symbol('namePrefix')
|
||||
const kLocation = Symbol('location')
|
||||
const kVersion = Symbol('version')
|
||||
const kStore = Symbol('store')
|
||||
const kOnComplete = Symbol('onComplete')
|
||||
const kPromise = Symbol('promise')
|
||||
|
||||
class BrowserLevel extends AbstractLevel {
|
||||
constructor (location, options, _) {
|
||||
// To help migrating to abstract-level
|
||||
if (typeof options === 'function' || typeof _ === 'function') {
|
||||
throw new ModuleError('The levelup-style callback argument has been removed', {
|
||||
code: 'LEVEL_LEGACY'
|
||||
})
|
||||
}
|
||||
|
||||
const { prefix, version, ...forward } = options || {}
|
||||
|
||||
super({
|
||||
encodings: { view: true },
|
||||
snapshots: false,
|
||||
createIfMissing: false,
|
||||
errorIfExists: false,
|
||||
seek: true
|
||||
}, forward)
|
||||
|
||||
if (typeof location !== 'string') {
|
||||
throw new Error('constructor requires a location string argument')
|
||||
}
|
||||
|
||||
// TODO (next major): remove default prefix
|
||||
this[kLocation] = location
|
||||
this[kNamePrefix] = prefix == null ? DEFAULT_PREFIX : prefix
|
||||
this[kVersion] = parseInt(version || 1, 10)
|
||||
this[kIDB] = null
|
||||
}
|
||||
|
||||
get location () {
|
||||
return this[kLocation]
|
||||
}
|
||||
|
||||
get namePrefix () {
|
||||
return this[kNamePrefix]
|
||||
}
|
||||
|
||||
get version () {
|
||||
return this[kVersion]
|
||||
}
|
||||
|
||||
// Exposed for backwards compat and unit tests
|
||||
get db () {
|
||||
return this[kIDB]
|
||||
}
|
||||
|
||||
get type () {
|
||||
return 'browser-level'
|
||||
}
|
||||
|
||||
_open (options, callback) {
|
||||
const req = indexedDB.open(this[kNamePrefix] + this[kLocation], this[kVersion])
|
||||
|
||||
req.onerror = function () {
|
||||
callback(req.error || new Error('unknown error'))
|
||||
}
|
||||
|
||||
req.onsuccess = () => {
|
||||
this[kIDB] = req.result
|
||||
callback()
|
||||
}
|
||||
|
||||
req.onupgradeneeded = (ev) => {
|
||||
const db = ev.target.result
|
||||
|
||||
if (!db.objectStoreNames.contains(this[kLocation])) {
|
||||
db.createObjectStore(this[kLocation])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[kStore] (mode) {
|
||||
const transaction = this[kIDB].transaction([this[kLocation]], mode)
|
||||
return transaction.objectStore(this[kLocation])
|
||||
}
|
||||
|
||||
[kOnComplete] (request, callback) {
|
||||
const transaction = request.transaction
|
||||
|
||||
// Take advantage of the fact that a non-canceled request error aborts
|
||||
// the transaction. I.e. no need to listen for "request.onerror".
|
||||
transaction.onabort = function () {
|
||||
callback(transaction.error || new Error('aborted by user'))
|
||||
}
|
||||
|
||||
transaction.oncomplete = function () {
|
||||
callback(null, request.result)
|
||||
}
|
||||
}
|
||||
|
||||
_get (key, options, callback) {
|
||||
const store = this[kStore]('readonly')
|
||||
let req
|
||||
|
||||
try {
|
||||
req = store.get(key)
|
||||
} catch (err) {
|
||||
return this.nextTick(callback, err)
|
||||
}
|
||||
|
||||
this[kOnComplete](req, function (err, value) {
|
||||
if (err) return callback(err)
|
||||
|
||||
if (value === undefined) {
|
||||
return callback(new ModuleError('Entry not found', {
|
||||
code: 'LEVEL_NOT_FOUND'
|
||||
}))
|
||||
}
|
||||
|
||||
callback(null, deserialize(value))
|
||||
})
|
||||
}
|
||||
|
||||
_getMany (keys, options, callback) {
|
||||
const store = this[kStore]('readonly')
|
||||
const tasks = keys.map((key) => (next) => {
|
||||
let request
|
||||
|
||||
try {
|
||||
request = store.get(key)
|
||||
} catch (err) {
|
||||
return next(err)
|
||||
}
|
||||
|
||||
request.onsuccess = () => {
|
||||
const value = request.result
|
||||
next(null, value === undefined ? value : deserialize(value))
|
||||
}
|
||||
|
||||
request.onerror = (ev) => {
|
||||
ev.stopPropagation()
|
||||
next(request.error)
|
||||
}
|
||||
})
|
||||
|
||||
parallel(tasks, 16, callback)
|
||||
}
|
||||
|
||||
_del (key, options, callback) {
|
||||
const store = this[kStore]('readwrite')
|
||||
let req
|
||||
|
||||
try {
|
||||
req = store.delete(key)
|
||||
} catch (err) {
|
||||
return this.nextTick(callback, err)
|
||||
}
|
||||
|
||||
this[kOnComplete](req, callback)
|
||||
}
|
||||
|
||||
_put (key, value, options, callback) {
|
||||
const store = this[kStore]('readwrite')
|
||||
let req
|
||||
|
||||
try {
|
||||
// Will throw a DataError or DataCloneError if the environment
|
||||
// does not support serializing the key or value respectively.
|
||||
req = store.put(value, key)
|
||||
} catch (err) {
|
||||
return this.nextTick(callback, err)
|
||||
}
|
||||
|
||||
this[kOnComplete](req, callback)
|
||||
}
|
||||
|
||||
// TODO: implement key and value iterators
|
||||
_iterator (options) {
|
||||
return new Iterator(this, this[kLocation], options)
|
||||
}
|
||||
|
||||
_batch (operations, options, callback) {
|
||||
const store = this[kStore]('readwrite')
|
||||
const transaction = store.transaction
|
||||
let index = 0
|
||||
let error
|
||||
|
||||
transaction.onabort = function () {
|
||||
callback(error || transaction.error || new Error('aborted by user'))
|
||||
}
|
||||
|
||||
transaction.oncomplete = function () {
|
||||
callback()
|
||||
}
|
||||
|
||||
// Wait for a request to complete before making the next, saving CPU.
|
||||
function loop () {
|
||||
const op = operations[index++]
|
||||
const key = op.key
|
||||
|
||||
let req
|
||||
|
||||
try {
|
||||
req = op.type === 'del' ? store.delete(key) : store.put(op.value, key)
|
||||
} catch (err) {
|
||||
error = err
|
||||
transaction.abort()
|
||||
return
|
||||
}
|
||||
|
||||
if (index < operations.length) {
|
||||
req.onsuccess = loop
|
||||
} else if (typeof transaction.commit === 'function') {
|
||||
// Commit now instead of waiting for auto-commit
|
||||
transaction.commit()
|
||||
}
|
||||
}
|
||||
|
||||
loop()
|
||||
}
|
||||
|
||||
_clear (options, callback) {
|
||||
let keyRange
|
||||
let req
|
||||
|
||||
try {
|
||||
keyRange = createKeyRange(options)
|
||||
} catch (e) {
|
||||
// The lower key is greater than the upper key.
|
||||
// IndexedDB throws an error, but we'll just do nothing.
|
||||
return this.nextTick(callback)
|
||||
}
|
||||
|
||||
if (options.limit >= 0) {
|
||||
// IDBObjectStore#delete(range) doesn't have such an option.
|
||||
// Fall back to cursor-based implementation.
|
||||
return clear(this, this[kLocation], keyRange, options, callback)
|
||||
}
|
||||
|
||||
try {
|
||||
const store = this[kStore]('readwrite')
|
||||
req = keyRange ? store.delete(keyRange) : store.clear()
|
||||
} catch (err) {
|
||||
return this.nextTick(callback, err)
|
||||
}
|
||||
|
||||
this[kOnComplete](req, callback)
|
||||
}
|
||||
|
||||
_close (callback) {
|
||||
this[kIDB].close()
|
||||
this.nextTick(callback)
|
||||
}
|
||||
}
|
||||
|
||||
BrowserLevel.destroy = function (location, prefix, callback) {
|
||||
if (typeof prefix === 'function') {
|
||||
callback = prefix
|
||||
prefix = DEFAULT_PREFIX
|
||||
}
|
||||
|
||||
callback = fromCallback(callback, kPromise)
|
||||
const request = indexedDB.deleteDatabase(prefix + location)
|
||||
|
||||
request.onsuccess = function () {
|
||||
callback()
|
||||
}
|
||||
|
||||
request.onerror = function (err) {
|
||||
callback(err)
|
||||
}
|
||||
|
||||
return callback[kPromise]
|
||||
}
|
||||
|
||||
exports.BrowserLevel = BrowserLevel
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user