311 lines
7.9 KiB
JavaScript
311 lines
7.9 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/**
|
|
* This module wraps the nsIKeyValue* interfaces in a Promise-based API.
|
|
* To use it, import it, then call the KeyValueService.getOrCreate() method
|
|
* with a database's path and (optionally) its name:
|
|
*
|
|
* ```
|
|
* let { keyValueService } =
|
|
* ChromeUtils.importESModule("resource://gre/modules/kvstore.sys.mjs");
|
|
* let database = await KeyValueService.getOrCreate(path, name);
|
|
* ```
|
|
*
|
|
* See the documentation in nsIKeyValue.idl for more information about the API
|
|
* for key/value storage.
|
|
*/
|
|
|
|
function promisify(fn, ...args) {
|
|
return new Promise((resolve, reject) => {
|
|
fn({ resolve, reject }, ...args);
|
|
});
|
|
}
|
|
|
|
export class KeyValueService {
|
|
static RecoveryStrategy = {
|
|
ERROR: Ci.nsIKeyValueService.ERROR,
|
|
DISCARD: Ci.nsIKeyValueService.DISCARD,
|
|
RENAME: Ci.nsIKeyValueService.RENAME,
|
|
};
|
|
|
|
static #service = Cc["@mozilla.org/key-value-service;1"].getService(
|
|
Ci.nsIKeyValueService
|
|
);
|
|
|
|
static async getOrCreate(dir, name) {
|
|
return new KeyValueDatabase(
|
|
await promisify(this.#service.getOrCreate, dir, name)
|
|
);
|
|
}
|
|
|
|
static async getOrCreateWithOptions(
|
|
dir,
|
|
name,
|
|
{ strategy = Ci.nsIKeyValueService.RENAME } = {}
|
|
) {
|
|
return new KeyValueDatabase(
|
|
await promisify(this.#service.getOrCreateWithOptions, dir, name, strategy)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An experimental key-value storage service that uses
|
|
* SQLite for persistence.
|
|
*/
|
|
export class SQLiteKeyValueService {
|
|
static Importer = {
|
|
RKV_SAFE_MODE: "rkv-safe-mode",
|
|
};
|
|
|
|
static #service = Cc["@mozilla.org/sqlite-key-value-service;1"].getService(
|
|
Ci.nsIKeyValueService
|
|
);
|
|
|
|
static async getOrCreate(dir, name) {
|
|
return new KeyValueDatabase(
|
|
await promisify(this.#service.getOrCreate, dir, name)
|
|
);
|
|
}
|
|
|
|
static createImporter(type, dir) {
|
|
return new KeyValueImporter(this.#service.createImporter(type, dir));
|
|
}
|
|
}
|
|
|
|
export class KeyValueImporter {
|
|
static ConflictPolicy = {
|
|
ERROR: Ci.nsIKeyValueImporter.ERROR_ON_CONFLICT,
|
|
IGNORE: Ci.nsIKeyValueImporter.IGNORE_ON_CONFLICT,
|
|
REPLACE: Ci.nsIKeyValueImporter.REPLACE_ON_CONFLICT,
|
|
};
|
|
|
|
static CleanupPolicy = {
|
|
KEEP: Ci.nsIKeyValueImporter.KEEP_AFTER_IMPORT,
|
|
DELETE: Ci.nsIKeyValueImporter.DELETE_AFTER_IMPORT,
|
|
};
|
|
|
|
#importer;
|
|
|
|
constructor(importer) {
|
|
this.#importer = importer;
|
|
}
|
|
|
|
get type() {
|
|
return this.#importer.type;
|
|
}
|
|
|
|
get path() {
|
|
return this.#importer.path;
|
|
}
|
|
|
|
addPath(dir) {
|
|
return this.#importer.addPath(dir);
|
|
}
|
|
|
|
addDatabase(name) {
|
|
return this.#importer.addDatabase(name);
|
|
}
|
|
|
|
addAllDatabases() {
|
|
return this.#importer.addAllDatabases();
|
|
}
|
|
|
|
import() {
|
|
return promisify(this.#importer.import);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A class that wraps an nsIKeyValueDatabase in a Promise-based API.
|
|
*
|
|
* This class isn't exported, so you can't instantiate it directly, but you
|
|
* can retrieve an instance of this class via KeyValueService.getOrCreate():
|
|
*
|
|
* ```
|
|
* const database = await KeyValueService.getOrCreate(path, name);
|
|
* ```
|
|
*
|
|
* You can then call its put(), get(), has(), and delete() methods to access
|
|
* and manipulate key/value pairs:
|
|
*
|
|
* ```
|
|
* await database.put("foo", 1);
|
|
* await database.get("foo") === 1; // true
|
|
* await database.has("foo"); // true
|
|
* await database.delete("foo");
|
|
* await database.has("foo"); // false
|
|
* ```
|
|
*
|
|
* You can also call writeMany() to put/delete multiple key/value pairs:
|
|
*
|
|
* ```
|
|
* await database.writeMany({
|
|
* key1: "value1",
|
|
* key2: "value2",
|
|
* key3: "value3",
|
|
* key4: null, // delete
|
|
* });
|
|
* ```
|
|
*
|
|
* And you can call its enumerate() method to retrieve a KeyValueEnumerator,
|
|
* which is described below.
|
|
*/
|
|
class KeyValueDatabase {
|
|
constructor(database) {
|
|
this.database = database;
|
|
}
|
|
|
|
isEmpty() {
|
|
return promisify(this.database.isEmpty);
|
|
}
|
|
|
|
count() {
|
|
return promisify(this.database.count);
|
|
}
|
|
|
|
size() {
|
|
return promisify(this.database.size);
|
|
}
|
|
|
|
put(key, value) {
|
|
return promisify(this.database.put, key, value);
|
|
}
|
|
|
|
/**
|
|
* Writes multiple key/value pairs to the database.
|
|
*
|
|
* Note:
|
|
* * Each write could be either put or delete.
|
|
* * Given multiple values with the same key, only the last value will be stored.
|
|
* * If the same key gets put and deleted for multiple times, the final state
|
|
* of that key is subject to the ordering of the put(s) and delete(s).
|
|
*
|
|
* @param pairs Pairs could be any of following types:
|
|
* * An Object, all its properties and the corresponding values will
|
|
* be used as key value pairs. A property with null or undefined indicating
|
|
* a deletion.
|
|
* * An Array or an iterable whose elements are key-value pairs. such as
|
|
* [["key1", "value1"], ["key2", "value2"]]. Use a pair with value null
|
|
* to delete a key-value pair, e.g. ["delete-key", null].
|
|
* * A Map. A key with null or undefined value indicating a deletion.
|
|
* @return A promise that is fulfilled when all the key/value pairs are written
|
|
* to the database.
|
|
*/
|
|
writeMany(pairs) {
|
|
if (!pairs) {
|
|
throw new Error("writeMany(): unexpected argument.");
|
|
}
|
|
|
|
let entries;
|
|
|
|
if (
|
|
pairs instanceof Map ||
|
|
pairs instanceof Array ||
|
|
typeof pairs[Symbol.iterator] === "function"
|
|
) {
|
|
try {
|
|
// Let Map constructor validate the argument. Note that Map remembers
|
|
// the original insertion order of the keys, which satisfies the ordering
|
|
// premise of this function.
|
|
const map = pairs instanceof Map ? pairs : new Map(pairs);
|
|
entries = Array.from(map, ([key, value]) => ({ key, value }));
|
|
} catch (error) {
|
|
throw new Error("writeMany(): unexpected argument.");
|
|
}
|
|
} else if (typeof pairs === "object") {
|
|
entries = Array.from(Object.entries(pairs), ([key, value]) => ({
|
|
key,
|
|
value,
|
|
}));
|
|
} else {
|
|
throw new Error("writeMany(): unexpected argument.");
|
|
}
|
|
|
|
if (entries.length) {
|
|
return promisify(this.database.writeMany, entries);
|
|
}
|
|
return Promise.resolve();
|
|
}
|
|
|
|
has(key) {
|
|
return promisify(this.database.has, key);
|
|
}
|
|
|
|
get(key, defaultValue) {
|
|
return promisify(this.database.get, key, defaultValue);
|
|
}
|
|
|
|
delete(key) {
|
|
return promisify(this.database.delete, key);
|
|
}
|
|
|
|
deleteRange(fromKey, toKey) {
|
|
return promisify(this.database.deleteRange, fromKey, toKey);
|
|
}
|
|
|
|
clear() {
|
|
return promisify(this.database.clear);
|
|
}
|
|
|
|
async enumerate(fromKey, toKey) {
|
|
return new KeyValueEnumerator(
|
|
await promisify(this.database.enumerate, fromKey, toKey)
|
|
);
|
|
}
|
|
|
|
async close() {
|
|
return promisify(this.database.close);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A class that wraps an nsIKeyValueEnumerator in a Promise-based API.
|
|
*
|
|
* This class isn't exported, so you can't instantiate it directly, but you
|
|
* can retrieve an instance of this class by calling enumerate() on an instance
|
|
* of KeyValueDatabase:
|
|
*
|
|
* ```
|
|
* const database = await KeyValueService.getOrCreate(path, name);
|
|
* const enumerator = await database.enumerate();
|
|
* ```
|
|
*
|
|
* And then iterate pairs via its hasMoreElements() and getNext() methods:
|
|
*
|
|
* ```
|
|
* while (enumerator.hasMoreElements()) {
|
|
* const { key, value } = enumerator.getNext();
|
|
* …
|
|
* }
|
|
* ```
|
|
*
|
|
* Or with a `for...of` statement:
|
|
*
|
|
* ```
|
|
* for (const { key, value } of enumerator) {
|
|
* …
|
|
* }
|
|
* ```
|
|
*/
|
|
class KeyValueEnumerator {
|
|
constructor(enumerator) {
|
|
this.enumerator = enumerator;
|
|
}
|
|
|
|
hasMoreElements() {
|
|
return this.enumerator.hasMoreElements();
|
|
}
|
|
|
|
getNext() {
|
|
return this.enumerator.getNext();
|
|
}
|
|
|
|
*[Symbol.iterator]() {
|
|
while (this.enumerator.hasMoreElements()) {
|
|
yield this.enumerator.getNext();
|
|
}
|
|
}
|
|
}
|