mirror of
https://github.com/tutao/tutanota.git
synced 2025-10-19 16:03:43 +00:00
349 lines
9.4 KiB
JavaScript
Vendored
349 lines
9.4 KiB
JavaScript
Vendored
// lib/index.ts
|
|
import assert from "node:assert";
|
|
import { runInThisContext } from "node:vm";
|
|
|
|
// lib/loadBindings.ts
|
|
import { createRequire } from "node:module";
|
|
function loadBindings(moduleUrl, moduleName) {
|
|
const require2 = createRequire(moduleUrl);
|
|
const pathsToTry = [];
|
|
for (const dir of [".", ".."]) {
|
|
for (const subdir of ["", "build/Release", "build/Debug"]) {
|
|
for (const filename of [
|
|
withTuple(moduleName),
|
|
withExtendedTuple(moduleName),
|
|
moduleName
|
|
]) {
|
|
const pathToTry = modulePath(joinPath(dir, subdir), filename);
|
|
pathsToTry.push(pathToTry);
|
|
}
|
|
}
|
|
}
|
|
for (const pathToTry of pathsToTry) {
|
|
try {
|
|
return require2(pathToTry);
|
|
} catch (e) {
|
|
}
|
|
}
|
|
throw new Error(
|
|
`No native build was found for ${moduleName}, imported from ${moduleUrl}. Checked paths:
|
|
${pathsToTry.join("\n")}`
|
|
);
|
|
}
|
|
function joinPath(...parts) {
|
|
return parts.filter((p) => p != "").join("/");
|
|
}
|
|
function modulePath(dir, moduleName) {
|
|
return joinPath(dir, `${moduleName}.node`);
|
|
}
|
|
function withTuple(name) {
|
|
return `${name}.${process.platform}-${process.arch}`;
|
|
}
|
|
function withExtendedTuple(name) {
|
|
let tag;
|
|
switch (process.platform) {
|
|
case "linux":
|
|
tag = "-gnu";
|
|
break;
|
|
case "win32":
|
|
tag = "-msvc";
|
|
break;
|
|
default:
|
|
tag = "";
|
|
break;
|
|
}
|
|
return `${name}.${process.platform}-${process.arch}${tag}`;
|
|
}
|
|
|
|
// lib/index.ts
|
|
var addon = loadBindings(import.meta.url, "node_sqlcipher");
|
|
var Statement = class {
|
|
#needsTranslation;
|
|
#cache;
|
|
#createRow;
|
|
#native;
|
|
#onClose;
|
|
/** @internal */
|
|
constructor(db, query, { persistent, pluck, bigint }, onClose) {
|
|
this.#needsTranslation = persistent === true && !pluck;
|
|
this.#native = addon.statementNew(
|
|
db,
|
|
query,
|
|
persistent === true,
|
|
pluck === true,
|
|
bigint === true
|
|
);
|
|
this.#onClose = onClose;
|
|
}
|
|
/**
|
|
* Run the statement's query without returning any rows.
|
|
*
|
|
* @param params - Parameters to be bound to query placeholders before
|
|
* executing the statement.
|
|
* @returns An object with `changes` and `lastInsertedRowid` integers.
|
|
*/
|
|
run(params) {
|
|
if (this.#native === void 0) {
|
|
throw new Error("Statement closed");
|
|
}
|
|
const result = [0, 0];
|
|
this.#checkParams(params);
|
|
addon.statementRun(this.#native, params, result);
|
|
return { changes: result[0], lastInsertRowid: result[1] };
|
|
}
|
|
/**
|
|
* Run the statement's query and return the first row of the result or
|
|
* `undefined` if no rows matched.
|
|
*
|
|
* @param params - Parameters to be bound to query placeholders before
|
|
* executing the statement.
|
|
* @returns A row object or a single column if `pluck: true` is set in the
|
|
* statement options.
|
|
*/
|
|
get(params) {
|
|
if (this.#native === void 0) {
|
|
throw new Error("Statement closed");
|
|
}
|
|
this.#checkParams(params);
|
|
const result = addon.statementStep(this.#native, params, this.#cache, true);
|
|
if (result === void 0) {
|
|
return void 0;
|
|
}
|
|
if (!this.#needsTranslation) {
|
|
return result;
|
|
}
|
|
const createRow = this.#updateCache(result);
|
|
return createRow(result);
|
|
}
|
|
/**
|
|
* Run the statement's query and return the all rows of the result or
|
|
* `undefined` if no rows matched.
|
|
*
|
|
* @param params - Parameters to be bound to query placeholders before
|
|
* executing the statement.
|
|
* @returns A list of row objects or single columns if `pluck: true` is set in
|
|
* the statement options.
|
|
*/
|
|
all(params) {
|
|
if (this.#native === void 0) {
|
|
throw new Error("Statement closed");
|
|
}
|
|
const result = [];
|
|
this.#checkParams(params);
|
|
let singleUseParams = params;
|
|
while (true) {
|
|
const single = addon.statementStep(
|
|
this.#native,
|
|
singleUseParams,
|
|
this.#cache,
|
|
false
|
|
);
|
|
singleUseParams = null;
|
|
if (single === void 0) {
|
|
break;
|
|
}
|
|
if (!this.#needsTranslation) {
|
|
result.push(single);
|
|
continue;
|
|
}
|
|
const createRow = this.#updateCache(single);
|
|
result.push(createRow(single));
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Close the statement and release the used memory.
|
|
*/
|
|
close() {
|
|
if (this.#native === void 0) {
|
|
throw new Error("Statement already closed");
|
|
}
|
|
addon.statementClose(this.#native);
|
|
this.#native = void 0;
|
|
this.#onClose?.();
|
|
}
|
|
/** @internal */
|
|
#updateCache(result) {
|
|
if (this.#cache === result) {
|
|
assert(this.#createRow !== void 0);
|
|
return this.#createRow;
|
|
}
|
|
const half = result.length >>> 1;
|
|
const lines = [];
|
|
for (let i = 0; i < half; i += 1) {
|
|
lines.push(`${JSON.stringify(result[i])}: value[${half} + ${i}],`);
|
|
}
|
|
this.#cache = result;
|
|
const createRow = runInThisContext(`(function createRow(value) {
|
|
return {
|
|
${lines.join("\n")}
|
|
};
|
|
})`);
|
|
this.#createRow = createRow;
|
|
return createRow;
|
|
}
|
|
/** @internal */
|
|
#checkParams(params) {
|
|
if (params === void 0) {
|
|
return;
|
|
}
|
|
if (typeof params !== "object") {
|
|
throw new TypeError("Params must be either object or array");
|
|
}
|
|
if (params === null) {
|
|
throw new TypeError("Params cannot be null");
|
|
}
|
|
}
|
|
};
|
|
var Database = class {
|
|
#native;
|
|
#transactionDepth = 0;
|
|
#isCacheEnabled;
|
|
#statementCache = /* @__PURE__ */ new Map();
|
|
#transactionStmts;
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param path - The path to the database file or ':memory:'/'' for opening
|
|
* the in-memory database.
|
|
*/
|
|
constructor(path = ":memory:", { cacheStatements } = {}) {
|
|
if (typeof path !== "string") {
|
|
throw new TypeError("Invalid database path");
|
|
}
|
|
this.#native = addon.databaseOpen(path);
|
|
this.#isCacheEnabled = cacheStatements === true;
|
|
}
|
|
initTokenizer() {
|
|
if (this.#native === void 0) {
|
|
throw new Error("Database closed");
|
|
}
|
|
addon.databaseInitTokenizer(this.#native);
|
|
}
|
|
/**
|
|
* Execute one or multiple SQL statements in a given `sql` string.
|
|
*
|
|
* @param sql - one or multiple SQL statements
|
|
*/
|
|
exec(sql) {
|
|
if (this.#native === void 0) {
|
|
throw new Error("Database closed");
|
|
}
|
|
if (typeof sql !== "string") {
|
|
throw new TypeError("Invalid sql argument");
|
|
}
|
|
addon.databaseExec(this.#native, sql);
|
|
}
|
|
prepare(query, options = {}) {
|
|
if (this.#native === void 0) {
|
|
throw new Error("Database closed");
|
|
}
|
|
if (typeof query !== "string") {
|
|
throw new TypeError("Invalid query argument");
|
|
}
|
|
if (!this.#isCacheEnabled || options.persistent === false) {
|
|
return new Statement(this.#native, query, options);
|
|
}
|
|
const cacheKey = `${options.pluck}:${options.bigint}:${query}`;
|
|
const cached = this.#statementCache.get(cacheKey);
|
|
if (cached !== void 0) {
|
|
return cached;
|
|
}
|
|
const stmt = new Statement(
|
|
this.#native,
|
|
query,
|
|
{
|
|
persistent: true,
|
|
pluck: options.pluck,
|
|
bigint: options.bigint
|
|
},
|
|
() => this.#statementCache.delete(cacheKey)
|
|
);
|
|
this.#statementCache.set(cacheKey, stmt);
|
|
return stmt;
|
|
}
|
|
/**
|
|
* Close the database and all associated statements.
|
|
*/
|
|
close() {
|
|
if (this.#native === void 0) {
|
|
throw new Error("Database already closed");
|
|
}
|
|
addon.databaseClose(this.#native);
|
|
this.#native = void 0;
|
|
}
|
|
pragma(source, { simple } = {}) {
|
|
if (typeof source !== "string") {
|
|
throw new TypeError("Invalid pragma argument");
|
|
}
|
|
if (simple === true) {
|
|
const stmt2 = this.prepare(`PRAGMA ${source}`, { pluck: true });
|
|
return stmt2.get();
|
|
}
|
|
const stmt = this.prepare(`PRAGMA ${source}`);
|
|
return stmt.all();
|
|
}
|
|
/**
|
|
* Wrap `fn()` in a transaction.
|
|
*
|
|
* @param fn - a function to be executed within a transaction.
|
|
* @returns The value returned by `fn()`.
|
|
*/
|
|
transaction(fn) {
|
|
return (...params) => {
|
|
if (this.#transactionStmts === void 0) {
|
|
const options = { persistent: true, pluck: true };
|
|
this.#transactionStmts = {
|
|
begin: this.prepare("BEGIN", options),
|
|
rollback: this.prepare("ROLLBACK", options),
|
|
commit: this.prepare("COMMIT", options),
|
|
savepoint: this.prepare("SAVEPOINT signalappsqlcipher", options),
|
|
rollbackTo: this.prepare("ROLLBACK TO signalappsqlcipher", options),
|
|
release: this.prepare("RELEASE signalappsqlcipher", options)
|
|
};
|
|
}
|
|
this.#transactionDepth += 1;
|
|
let begin;
|
|
let rollback;
|
|
let commit;
|
|
if (this.#transactionDepth === 1) {
|
|
({ begin, rollback, commit } = this.#transactionStmts);
|
|
} else {
|
|
({
|
|
savepoint: begin,
|
|
rollbackTo: rollback,
|
|
release: commit
|
|
} = this.#transactionStmts);
|
|
}
|
|
begin.run();
|
|
try {
|
|
const result = fn(...params);
|
|
commit.run();
|
|
return result;
|
|
} catch (error) {
|
|
rollback.run();
|
|
throw error;
|
|
} finally {
|
|
this.#transactionDepth -= 1;
|
|
}
|
|
};
|
|
}
|
|
/**
|
|
* Tokenize a given sentence with a Signal-FTS5-Extension.
|
|
*
|
|
* @param value - a sentence
|
|
* @returns a list of word-like tokens.
|
|
*
|
|
* @see {@link https://github.com/signalapp/Signal-FTS5-Extension}
|
|
*/
|
|
signalTokenize(value) {
|
|
if (typeof value !== "string") {
|
|
throw new TypeError("Invalid value");
|
|
}
|
|
return addon.signalTokenize(value);
|
|
}
|
|
};
|
|
export {
|
|
Database,
|
|
Database as default
|
|
};
|