easy-pingv0.7.0

Get in touch

Questions, bug reports, or anything about easy-ping. Either of these reaches me.

Emailteklumo.jembere@gmail.comTelegram@teklumt

For anything others would benefit from, a GitHub issue is better than a DM, because it's searchable.

GitHub

Adapters / SQLite

Edit this page

SQLite

SQLite through node:sqlite, better-sqlite3 or anything with the same prepare().all()/run() shape. One writer, so the claim is one UPDATE.

Setup

notify.ts
import { createSqliteTables, sqliteAdapter, sqliteQuery, sqliteTransaction } from "easy-ping/adapters/sqlite";
import { DatabaseSync } from "node:sqlite";

const db = new DatabaseSync("./app.db");
const query = sqliteQuery(db);

await createSqliteTables(query);

export const notify = easyPing({
  database: sqliteAdapter(query, { transaction: sqliteTransaction(db) }),
  // ...
});

node:sqlite ships with Node 22.13 and later, no install. The library itself never imports it — you hand it the database object — so the package's Node 20 floor is unchanged and nothing native enters the dependency tree.

Drivers

sqliteQuery takes anything with prepare(sql) returning a statement that has all(...params) and run(...params) => { changes }, plus exec(sql) for transactions. That is node:sqlite's DatabaseSync and better-sqlite3's Database, unchanged:

import Database from "better-sqlite3";

const db = new Database("./app.db");
sqliteAdapter(sqliteQuery(db), { transaction: sqliteTransaction(db) });

sqliteTransaction queues transactions one behind another. The driver is synchronous but the adapter awaits between statements, and SQLite refuses a BEGIN while one is open; the queue is what lets two concurrent send() calls both be atomic.

How a claim works here

UPDATE notification_delivery SET status = 'claimed', claimed_at = ?, claimed_by = ?
WHERE id IN (SELECT id FROM notification_delivery WHEREORDER BY not_before, id LIMIT ?)

One statement. SQLite has exactly one writer at a time, so two sweeps cannot interleave inside it, which is the guarantee FOR UPDATE SKIP LOCKED exists to give on Postgres. The rowLock conformance case does not apply — there is no lock held across statements for a second sweep to skip — and the suite filters it out rather than faking it, the same as MongoDB.

How things are stored

declaredSQLite column
string, jsonTEXT (JSON as text; the adapter parses on read)
number, booleanINTEGER (booleans as 0/1; the adapter reads them back as booleans)
dateTEXT, ISO-8601 with milliseconds and a trailing Z

Dates are the same 24 characters Date#toISOString produces, so <= on the text is <= on the instant, and a DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) writes the identical shape. createSqliteTables is bootstrap-only, like the MySQL one; there is no migration planner for SQLite yet.