npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aria-framework/db-worker

v0.7.0

Published

Aria App Framework — db-worker module. Async RPC bridge over synchronous better-sqlite3: a worker-thread dispatcher (runWorker), a main-thread client with per-call timeouts and crash handling (DbClient), and a SQLite layer with WAL pragmas + a numbered-mi

Readme

@aria-framework/db-worker

Aria App Framework — db-worker module. The async RPC bridge over synchronous better-sqlite3: all SQLite work runs in a worker thread so a query can never block the main (Express) event loop, and routes get a plain await db.invoke('Model', 'method', ...args).

Main thread                              Worker thread
DbClient.invoke('Ticket','create',…) ─►  runWorker dispatch → models.Ticket.create(…)
             result ◄─────────────────   database.getDb() runs the SQL

Plain CommonJS, no build step. better-sqlite3 is a peer dependency.

What the app keeps (three thin files)

Node spawns a worker from a file path, and the model registry + caches are app-specific — so the app keeps thin wrappers and the package owns the ~350 lines of lifecycle/RPC/migration machinery.

1. Worker entry (lib/db-worker.js) — boot + registry:

const path = require('path');
const { runWorker, database } = require('@aria-framework/db-worker');

runWorker({
  init(workerData) {
    database.init(workerData.dbPath, { logger });
    database.runMigrations(path.join(__dirname, '..', 'migrations'));
    // app-specific extras (e.g. wire field encryption from workerData.fieldsKey)
    return {
      Ticket: require('../models/Ticket'),
      User: require('../models/User')
      // ... every model reachable via invoke()
    };
  }
});

2. Client singleton (lib/db-client.js) — subclass for app caches:

const path = require('path');
const { DbClient } = require('@aria-framework/db-worker');

class AppDbClient extends DbClient {
  // add synchronous caches here (values hot paths read per-request)
}
module.exports = new AppDbClient({
  workerPath: path.join(__dirname, 'db-worker.js'),
  logger
});

3. Database wrapper (lib/database.js) — so models keep requiring ../lib/database and the migrations dir is configured once:

const path = require('path');
const { database } = require('@aria-framework/db-worker');
module.exports = {
  init: (dbPath) => database.init(dbPath, { logger }),
  runMigrations: () => database.runMigrations(path.join(__dirname, '..', 'migrations')),
  getDb: database.getDb,
  close: database.close
};

Boot: await dbClient.init({ dbPath, ...anythingTheWorkerNeeds }) — resolves once the worker has opened the DB and run migrations.

API

DbClient (main thread)

  • new DbClient({ workerPath, logger?, callTimeoutMs? = 30000, slowMs? = 500, queueWarnMs? = 1000, onWorkerDeath? })
    • slowMs: any invoke whose worker-side EXECUTION takes at least this long is warn-logged — Slow DB call (612ms exec, 1840ms total): Ticket.queue — attributing slowness to the query that was slow, not to calls queued behind it. Pass 0 to disable.
    • queueWarnMs: the saturation signal — a call that executed fast but waited in the queue at least this long logs Slow DB queue (988ms wait, 5ms exec): Model.method. Catches a worker drowning in individually-fast queries. Pass 0 to disable.
    • onWorkerDeath(info): fired AT MOST ONCE on a non-deliberate post-boot worker death (never on close(), never for boot failures — those reject init()). Recommended policy: process.exitCode = 1 + trigger your graceful shutdown so a supervisor restarts the service.
  • init(workerData) → Promise; rejects on worker boot failure (init-error)
  • invoke(model, method, ...args) → Promise. Args/results must be structured-clone serializable. Per-call timeout; on worker crash/exit ALL in-flight calls reject (nothing hangs). Custom error props set by models (code, status, entity ids…) survive the thread boundary.
  • close() — rejects in-flight calls, terminates the worker quietly.

runWorker({ init }) (worker entry)

init(workerData) does all boot and returns the model registry. Any model the app calls via invoke MUST be in the registry ("Unknown model" otherwise). Model methods may be sync or async (thenables are awaited). BigInts in results (better-sqlite3 lastInsertRowid) are converted to Number.

database (worker side, singleton per thread)

  • init(dbPath, { logger?, onOpen? }) — creates the directory, opens the DB, then applies pragmas WAL / synchronous=NORMAL / foreign_keys=ON / busy_timeout=5000 and creates the migrations tracking table. onOpen(db) runs FIRST, before any pragma or table access — this is the SQLCipher hook: an encrypted DB needs pragma key before anything touches it.
  • runMigrations(migrationsDir) — applies *.sql sorted by filename, each in a transaction with its tracking-row insert; already-applied files skipped.
  • getDb() / close()

Changelog

  • 0.4.2 — sanitizeForTransfer passes Map/Set through untouched (like Date/Buffer/typed arrays — structured clone handles them natively; the generic object branch would silently mangle them to {}). Latent-bug guard from an Acc101 review: no current consumer returns one across the boundary, but the first future model method that does now works instead of delivering empty data with no error signal.
  • 0.4.1 — third-review fixes (all in the 0.4.0 additions). (1) Boot-crash suppression latched: a hard boot crash emits 'error' AND 'exit'; the exit handler no longer fires the death policy after the error handler consumed the boot state. (2) init() resets the death latch and closing flag, so the respawn pattern works (worker #2's death notifies again) and close()+re-init doesn't inherit stale flags. (3) Queue warnings coalesce: first occurrence logs, then at most one summary line per 10s with the suppressed count — saturation no longer floods the log it's reporting into; wording clarifies the wait includes IPC/event-loop overhead, not pure queue time. (4) Sanitizer memoizes shared references (obj → sanitized node): linear time on diamond-shaped graphs AND sharing is preserved through structured clone; true cycles still throw via a separate ancestor set.
  • 0.4.0 — second-review fixes. (1) sanitizeForTransfer tracks the ancestor PATH (unwind delete), so shared/diamond references in results are legal again — only true cycles throw; the error path is also guarded so an unserializable custom error prop degrades to message+stack instead of hanging the call. (2) Death notification hardened: at-most-once latch ('error' + 'exit' both fire for one crash), _closing guard on the error handler (a teardown-race error no longer turns a clean shutdown into a crash exit), and boot failures reject init() instead of firing the policy. (3) queueWarnMs saturation warning — fast-exec calls with long queue waits now log, closing the observability hole the exec-only gate opened. (4) execMs uses a monotonic clock (perf_hooks), immune to NTP steps.
  • 0.3.0 — three review-driven fixes. (1) onWorkerDeath policy hook: called after a non-deliberate worker death; without a policy the process keeps running with every invoke() rejecting — a silent outage supervisors can't see. Recommended app policy: log + process.exit(1). (2) Slow-call timing moved into the worker: the threshold now applies to execution time (execMs shipped in each response), so calls queued behind a slow query no longer log under their own names; the message shows exec and total. (3) Sanitizer hardened: Date/Buffer/typed arrays pass through untouched (structured clone handles them; recursing mangled them), and circular result graphs throw a clear error instead of overflowing the stack.
  • 0.2.0 — slow-call detection: DbClient warn-logs any invoke at or over slowMs (default 500ms, 0 disables) with duration + Model.method. Turns performance work measurement-driven — add the index when a real query names itself, not speculatively.
  • 0.1.0 — first release. Extracted from Support101/Acc101 lib/{database,db-client,db-worker}.js. Changes vs the app originals: model registry + boot moved to the app's init() callback; migrations dir is an argument; DbClient is a class (apps subclass + instantiate); new onOpen hook absorbs Acc101's SQLCipher-key variant.