Compare commits
No commits in common. "main" and "v3.2.0" have entirely different histories.
6
.github/workflows/publish.yaml
vendored
6
.github/workflows/publish.yaml
vendored
@ -40,7 +40,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||||
with:
|
with:
|
||||||
version: 10.18.1
|
version: 10.3.0
|
||||||
|
|
||||||
- name: Get Node version from .nvmrc
|
- name: Get Node version from .nvmrc
|
||||||
id: get-nvm-version
|
id: get-nvm-version
|
||||||
@ -90,7 +90,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||||
with:
|
with:
|
||||||
version: 10.18.1
|
version: 10.3.0
|
||||||
|
|
||||||
- name: Get Node version from .nvmrc
|
- name: Get Node version from .nvmrc
|
||||||
id: get-nvm-version
|
id: get-nvm-version
|
||||||
@ -137,7 +137,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||||
with:
|
with:
|
||||||
version: 10.18.1
|
version: 10.3.0
|
||||||
- name: Setup node.js
|
- name: Setup node.js
|
||||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
@ -30,7 +30,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||||
with:
|
with:
|
||||||
version: 10.18.1
|
version: 10.3.0
|
||||||
- name: Setup node.js
|
- name: Setup node.js
|
||||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||||
with:
|
with:
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,4 +7,3 @@ docs/
|
|||||||
.tmp/
|
.tmp/
|
||||||
.eslintcache
|
.eslintcache
|
||||||
todo.md
|
todo.md
|
||||||
.vscode
|
|
||||||
|
|||||||
13
Dockerfile
13
Dockerfile
@ -2,7 +2,7 @@
|
|||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
#
|
#
|
||||||
|
|
||||||
FROM ubuntu:jammy-20250714@sha256:1ec65b2719518e27d4d25f104d93f9fac60dc437f81452302406825c46fcc9cb
|
FROM ubuntu:focal-20240530@sha256:fa17826afb526a9fc7250e0fbcbfd18d03fe7a54849472f86879d8bf562c629e
|
||||||
|
|
||||||
# Avoid getting prompted to configure things during installation.
|
# Avoid getting prompted to configure things during installation.
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
@ -15,15 +15,13 @@ COPY docker/apt.conf docker/sources.list /etc/apt/
|
|||||||
# But we can't install it because it doesn't trust our mirror!
|
# But we can't install it because it doesn't trust our mirror!
|
||||||
# Temporarily disables APT's certificate signature checking
|
# Temporarily disables APT's certificate signature checking
|
||||||
# to download the certificates.
|
# to download the certificates.
|
||||||
RUN apt update -oAcquire::https::Verify-Peer=false
|
RUN apt-get update -oAcquire::https::Verify-Peer=false \
|
||||||
RUN apt install -oAcquire::https::Verify-Peer=false -y ca-certificates
|
&& apt-get install -oAcquire::https::Verify-Peer=false -y ca-certificates
|
||||||
|
|
||||||
# Back to normal, verification back on
|
# Back to normal, verification back on
|
||||||
|
|
||||||
# Install only what's needed to set up Rust and Node.
|
# Install only what's needed to set up Rust and Node.
|
||||||
# We'll install additional tools at the end to take advantage of Docker's caching of earlier steps.
|
# We'll install additional tools at the end to take advantage of Docker's caching of earlier steps.
|
||||||
RUN apt update
|
RUN apt-get update && apt-get install -y apt-transport-https xz-utils unzip
|
||||||
RUN apt install -y apt-transport-https xz-utils unzip
|
|
||||||
|
|
||||||
# User-specific setup!
|
# User-specific setup!
|
||||||
|
|
||||||
@ -67,9 +65,6 @@ RUN tar -xf node.tar.xz \
|
|||||||
|
|
||||||
ENV PATH="/home/sqlcipher/node/bin:${PATH}"
|
ENV PATH="/home/sqlcipher/node/bin:${PATH}"
|
||||||
|
|
||||||
# Install pnpm
|
|
||||||
RUN npm install -g pnpm@10.18.1
|
|
||||||
|
|
||||||
# And finally any bonus packages we're going to need
|
# And finally any bonus packages we're going to need
|
||||||
# Note that we jump back to root for this.
|
# Note that we jump back to root for this.
|
||||||
USER root
|
USER root
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
deb [snapshot=20250811T060900Z] http://archive.ubuntu.com/ubuntu/ jammy main universe
|
deb [snapshot=20240829T060900Z] http://archive.ubuntu.com/ubuntu/ focal main universe
|
||||||
deb [snapshot=20250811T060900Z] http://archive.ubuntu.com/ubuntu/ jammy-updates main universe
|
deb [snapshot=20240829T060900Z] http://archive.ubuntu.com/ubuntu/ focal-updates main universe
|
||||||
deb [snapshot=20250811T060900Z] http://security.ubuntu.com/ubuntu jammy-security main universe
|
deb [snapshot=20240829T060900Z] http://security.ubuntu.com/ubuntu focal-security main universe
|
||||||
27
lib/index.ts
27
lib/index.ts
@ -53,10 +53,6 @@ const addon = bindings<{
|
|||||||
fn: (...args: ReadonlyArray<unknown>) => void,
|
fn: (...args: ReadonlyArray<unknown>) => void,
|
||||||
bigint: boolean,
|
bigint: boolean,
|
||||||
): void;
|
): void;
|
||||||
databaseSetWalHook(
|
|
||||||
db: NativeDatabase,
|
|
||||||
fn: (dbName: string, pageCount: number) => void,
|
|
||||||
): void;
|
|
||||||
|
|
||||||
signalTokenize(value: string): Array<string>;
|
signalTokenize(value: string): Array<string>;
|
||||||
|
|
||||||
@ -413,13 +409,6 @@ export type DatabaseOptions = Readonly<{
|
|||||||
cacheStatements?: boolean;
|
cacheStatements?: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
/**
|
|
||||||
* @param dbName - The name of the database that was written to.
|
|
||||||
* @param pageCount - The number of pages currently in the write-ahead log file,
|
|
||||||
* including those that were just committed.
|
|
||||||
*/
|
|
||||||
export type WalHook = (dbName: string, pageCount: number) => void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A sqlite database class.
|
* A sqlite database class.
|
||||||
*/
|
*/
|
||||||
@ -506,22 +495,6 @@ export default class Database {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a callback to be invoked each time data is commited to a database
|
|
||||||
* in WAL mode.
|
|
||||||
*
|
|
||||||
* @param fn - function implementation
|
|
||||||
*/
|
|
||||||
public setWalHook(fn: WalHook): void {
|
|
||||||
if (this.#native === undefined) {
|
|
||||||
throw new Error('Database closed');
|
|
||||||
}
|
|
||||||
if (typeof fn !== 'function') {
|
|
||||||
throw new TypeError('Invalid fn argument');
|
|
||||||
}
|
|
||||||
addon.databaseSetWalHook(this.#native, fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compile a single SQL statement.
|
* Compile a single SQL statement.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"packageManager": "pnpm@10.18.1",
|
|
||||||
"name": "@signalapp/sqlcipher",
|
"name": "@signalapp/sqlcipher",
|
||||||
"version": "3.3.5",
|
"version": "3.2.0",
|
||||||
"description": "A fast N-API-based Node.js addon wrapping sqlcipher and FTS5 segmenting APIs",
|
"description": "A fast N-API-based Node.js addon wrapping sqlcipher and FTS5 segmenting APIs",
|
||||||
"homepage": "http://github.com/signalapp/node-sqlcipher.git",
|
"homepage": "http://github.com/signalapp/node-sqlcipher.git",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
nightly-2025-09-24
|
nightly-2025-02-25
|
||||||
|
|||||||
66
src/addon.cc
66
src/addon.cc
@ -158,38 +158,6 @@ class FunctionWrap {
|
|||||||
bool is_bigint_;
|
bool is_bigint_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// WAL Hook
|
|
||||||
|
|
||||||
class WalHookWrap {
|
|
||||||
public:
|
|
||||||
explicit WalHookWrap(Napi::Function fn) { fn_.Reset(fn, 1); }
|
|
||||||
|
|
||||||
static int Run(void* p_app, sqlite3* _db, const char* db_name, int n_pages) {
|
|
||||||
auto wrap = static_cast<WalHookWrap*>(p_app);
|
|
||||||
wrap->Call(db_name, n_pages);
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void Call(const char* db_name, int n_pages) {
|
|
||||||
auto env = fn_.Env();
|
|
||||||
Napi::HandleScope scope(env);
|
|
||||||
|
|
||||||
auto result = fn_.Value().Call({
|
|
||||||
Napi::String::New(env, db_name),
|
|
||||||
Napi::Number::New(env, n_pages),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ignore exceptions
|
|
||||||
if (result.IsEmpty()) {
|
|
||||||
env.GetAndClearPendingException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Napi::Reference<Napi::Function> fn_;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Global Settings
|
// Global Settings
|
||||||
|
|
||||||
thread_local Napi::Reference<Napi::Function> logger_fn_;
|
thread_local Napi::Reference<Napi::Function> logger_fn_;
|
||||||
@ -268,8 +236,6 @@ Napi::Object Database::Init(Napi::Env env, Napi::Object exports) {
|
|||||||
exports["databaseExec"] = Napi::Function::New(env, &Database::Exec);
|
exports["databaseExec"] = Napi::Function::New(env, &Database::Exec);
|
||||||
exports["databaseCreateFunction"] =
|
exports["databaseCreateFunction"] =
|
||||||
Napi::Function::New(env, &Database::CreateFunction);
|
Napi::Function::New(env, &Database::CreateFunction);
|
||||||
exports["databaseSetWalHook"] =
|
|
||||||
Napi::Function::New(env, &Database::SetWalHook);
|
|
||||||
return exports;
|
return exports;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,9 +251,6 @@ Database::~Database() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete wal_hook_wrap_;
|
|
||||||
wal_hook_wrap_ = nullptr;
|
|
||||||
|
|
||||||
int r = sqlite3_close(handle_);
|
int r = sqlite3_close(handle_);
|
||||||
if (r != SQLITE_OK) {
|
if (r != SQLITE_OK) {
|
||||||
fprintf(stderr, "Cleanup: sqlite3_close failure\n");
|
fprintf(stderr, "Cleanup: sqlite3_close failure\n");
|
||||||
@ -379,9 +342,6 @@ Napi::Value Database::Close(const Napi::CallbackInfo& info) {
|
|||||||
}
|
}
|
||||||
db->statements_.clear();
|
db->statements_.clear();
|
||||||
|
|
||||||
delete db->wal_hook_wrap_;
|
|
||||||
db->wal_hook_wrap_ = nullptr;
|
|
||||||
|
|
||||||
int r = sqlite3_close(db->handle_);
|
int r = sqlite3_close(db->handle_);
|
||||||
if (r != SQLITE_OK) {
|
if (r != SQLITE_OK) {
|
||||||
return db->ThrowSqliteError(env, r);
|
return db->ThrowSqliteError(env, r);
|
||||||
@ -451,32 +411,6 @@ Napi::Value Database::CreateFunction(const Napi::CallbackInfo& info) {
|
|||||||
return Napi::Value();
|
return Napi::Value();
|
||||||
}
|
}
|
||||||
|
|
||||||
Napi::Value Database::SetWalHook(const Napi::CallbackInfo& info) {
|
|
||||||
auto env = info.Env();
|
|
||||||
|
|
||||||
auto db = FromExternal(info[0]);
|
|
||||||
auto fn = info[1].As<Napi::Function>();
|
|
||||||
|
|
||||||
assert(fn.IsFunction());
|
|
||||||
|
|
||||||
if (db == nullptr) {
|
|
||||||
return Napi::Value();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (db->handle_ == nullptr) {
|
|
||||||
NAPI_THROW(Napi::Error::New(env, "Database closed"), Napi::Value());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto wal_wrap = new WalHookWrap(fn);
|
|
||||||
|
|
||||||
delete db->wal_hook_wrap_;
|
|
||||||
db->wal_hook_wrap_ = wal_wrap;
|
|
||||||
|
|
||||||
sqlite3_wal_hook(db->handle_, WalHookWrap::Run, wal_wrap);
|
|
||||||
|
|
||||||
return Napi::Value();
|
|
||||||
}
|
|
||||||
|
|
||||||
Napi::Value Database::ThrowSqliteError(Napi::Env env, int error) {
|
Napi::Value Database::ThrowSqliteError(Napi::Env env, int error) {
|
||||||
assert(handle_ != nullptr);
|
assert(handle_ != nullptr);
|
||||||
const char* msg = sqlite3_errmsg(handle_);
|
const char* msg = sqlite3_errmsg(handle_);
|
||||||
|
|||||||
@ -9,7 +9,6 @@
|
|||||||
#include "sqlite3.h"
|
#include "sqlite3.h"
|
||||||
|
|
||||||
class Statement;
|
class Statement;
|
||||||
class WalHookWrap;
|
|
||||||
|
|
||||||
class Database {
|
class Database {
|
||||||
public:
|
public:
|
||||||
@ -32,7 +31,6 @@ class Database {
|
|||||||
static Napi::Value Close(const Napi::CallbackInfo& info);
|
static Napi::Value Close(const Napi::CallbackInfo& info);
|
||||||
static Napi::Value Exec(const Napi::CallbackInfo& info);
|
static Napi::Value Exec(const Napi::CallbackInfo& info);
|
||||||
static Napi::Value CreateFunction(const Napi::CallbackInfo& info);
|
static Napi::Value CreateFunction(const Napi::CallbackInfo& info);
|
||||||
static Napi::Value SetWalHook(const Napi::CallbackInfo& info);
|
|
||||||
|
|
||||||
fts5_api* GetFTS5API(Napi::Env env);
|
fts5_api* GetFTS5API(Napi::Env env);
|
||||||
|
|
||||||
@ -47,8 +45,6 @@ class Database {
|
|||||||
// All currently open statements for this database. Used to close all open
|
// All currently open statements for this database. Used to close all open
|
||||||
// statements when closing the database.
|
// statements when closing the database.
|
||||||
std::list<Statement*> statements_;
|
std::list<Statement*> statements_;
|
||||||
|
|
||||||
WalHookWrap* wal_hook_wrap_ = nullptr;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class AutoResetStatement {
|
class AutoResetStatement {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { mkdtemp, rm } from 'node:fs/promises';
|
import { mkdtemp, rm } from 'node:fs/promises';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { expect, test, describe, beforeEach, afterEach, vi } from 'vitest';
|
import { expect, test, beforeEach, afterEach } from 'vitest';
|
||||||
|
|
||||||
import Database from '../lib/index.js';
|
import Database from '../lib/index.js';
|
||||||
|
|
||||||
@ -65,80 +65,3 @@ test.each([[false], [true]])('ciphertext=%j', (ciphertext) => {
|
|||||||
|
|
||||||
expect(row).toEqual({ name: 'Adam', value: 'Sandler' });
|
expect(row).toEqual({ name: 'Adam', value: 'Sandler' });
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setWalHook', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
db.pragma('journal_mode = WAL');
|
|
||||||
db.exec('CREATE TABLE t (a INTEGER)');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('calls hook after WAL commit', () => {
|
|
||||||
const hook = vi.fn();
|
|
||||||
db.setWalHook(hook);
|
|
||||||
|
|
||||||
db.prepare('INSERT INTO t (a) VALUES (1)').run();
|
|
||||||
|
|
||||||
expect(hook).toHaveBeenCalledOnce();
|
|
||||||
expect(hook).toHaveBeenCalledWith('main', expect.any(Number));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('hook receives page count > 0', () => {
|
|
||||||
let pageCount: number | null = null;
|
|
||||||
db.setWalHook((_dbName, n) => {
|
|
||||||
pageCount = n;
|
|
||||||
});
|
|
||||||
|
|
||||||
db.prepare('INSERT INTO t (a) VALUES (1)').run();
|
|
||||||
|
|
||||||
expect(pageCount).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('hook fires once per commit', () => {
|
|
||||||
const hook = vi.fn();
|
|
||||||
db.setWalHook(hook);
|
|
||||||
|
|
||||||
db.transaction(() => {
|
|
||||||
db.prepare('INSERT INTO t (a) VALUES (1)').run();
|
|
||||||
db.prepare('INSERT INTO t (a) VALUES (2)').run();
|
|
||||||
})();
|
|
||||||
|
|
||||||
expect(hook).toHaveBeenCalledOnce();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('replaces previous hook', () => {
|
|
||||||
const first = vi.fn();
|
|
||||||
const second = vi.fn();
|
|
||||||
|
|
||||||
db.setWalHook(first);
|
|
||||||
db.setWalHook(second);
|
|
||||||
|
|
||||||
db.prepare('INSERT INTO t (a) VALUES (1)').run();
|
|
||||||
|
|
||||||
expect(first).not.toHaveBeenCalled();
|
|
||||||
expect(second).toHaveBeenCalledOnce();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('silently ignores exceptions thrown by hook', () => {
|
|
||||||
let called = false;
|
|
||||||
db.setWalHook(() => {
|
|
||||||
called = true;
|
|
||||||
throw new Error('hook error');
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
db.prepare('INSERT INTO t (a) VALUES (1)').run(),
|
|
||||||
).not.toThrow();
|
|
||||||
expect(called).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('throws when database is closed', () => {
|
|
||||||
db.close();
|
|
||||||
expect(() => db.setWalHook(vi.fn())).toThrowError('Database closed');
|
|
||||||
db = new Database(join(dir, 'db2.sqlite'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('throws for invalid argument', () => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
expect(() => db.setWalHook(123 as any)).toThrowError('Invalid fn argument');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user