@nullvalues/base56
v0.3.0
Published
Configurable baseX encoding library with SQLite-backed sequences
Maintainers
Readme
base56
What it is
base56 is a baseX encoding library for Node.js, a faithful port of dart56. It encodes and decodes non-negative integers using a configurable character set that defaults to base56 (alphanumeric minus visually ambiguous characters O I L o i l). It also provides a SQLite-backed sequence generator that emits monotonically increasing bX-encoded values.
Installation
Once published to npm, consumers can install directly:
pnpm add @nullvalues/base56To use the package before it is published (e.g. in a local monorepo), add it as a file dependency in your consuming project:
"dependencies": {
"@nullvalues/base56": "file:../base56"
}Run npm install after adding the entry. Each consuming project manages its own path pointer to base56.
tsconfig requirements
base56 is a pure ESM package. Consumers require Node.js ≥ 18.17.0 and must
use import (no require). Any TypeScript module/moduleResolution
combination that supports ESM works: NodeNext/NodeNext,
Bundler/Bundler, or ESNext/Bundler. Next.js (App Router) and
Vite-based projects work without changes. If you are using NodeNext
resolution, your package.json must declare "type": "module".
Usage examples
B10 — decimal round-trip
import { B10 } from '@nullvalues/base56';
const encoded = B10.convertB10ToBx(560); // => 'A0' (560 is the default seqStartValue)
const decoded = B10.convertBxToB10("A0"); // => [[560]]
console.assert(decoded[0][0] === 560); // round-trip verifiedB8 — octal to bX
import { B8, Base8Int } from '@nullvalues/base56';
// octal 10 = decimal 8; position 8 in the default charset is '8'
const result = B8.convertB8ToBx(new Base8Int(10)); // => '8'
// Base8Int.fromInt converts a decimal value to its octal representation first:
// Base8Int.fromInt(8) => Base8Int(10) (octal) => B8.convertB8ToBx(...) => '8'
const same = B8.convertB8ToBx(Base8Int.fromInt(8)); // => '8'BxSequence — SQLite-backed sequence
import { BxSequence } from '@nullvalues/base56';
const seq = new BxSequence("my-seq", { dbPath: "path/to/sequences.db" });
const v1 = await seq.nextValue(); // => 'A0' (default seqStartValue)
const v2 = await seq.nextValue(); // => 'A1'
await seq.dispose(); // required — closes the database handlebootstrap — custom character set
import { bootstrap } from '@nullvalues/base56';
// Must be called before any encode/decode operation.
bootstrap.updateConfig({
bX: {
removeChars: "OILoil01",
prependSeparator: "-",
prependDefaultChar: "X",
subdomainChar: ".",
seqStartValue: "A0",
},
});Process-wide singleton.
updateConfigmust be called once, before any encode or decode operation in the process. After the first encode or decode call (including indirect calls viaBxSequenceconstruction), the config is frozen andupdateConfigwill throw. Two consumers in the same process cannot use different configs simultaneously. IfupdateConfigis never called, the default config is used automatically.
BxSequence database notes
- Default
dbPathis'sequences.db'resolved relative to the process working directory. - Pass an explicit absolute path in production to control placement, and a unique path per test to achieve isolation.
dispose()is required. Failing to call it leaves the store handle open.- Concurrent-write safe. Each
nextValue()call executes an atomicUPDATE … SET last_value = last_value + 1 … RETURNING last_valuein WAL mode (PRAGMA journal_mode = WAL). Multiple processes pointing at the same database file will each receive a unique, monotonically increasing value with no external locking required. - Schema side-effect. The
BxSequenceconstructor runsCREATE TABLE IF NOT EXISTS bx_sequenceson the database atdbPathevery time it is called. Pointing aBxSequenceat an existing unrelated SQLite database will write abx_sequencestable into it. Use an explicit, dedicateddbPathin production.
Sequence stores
BxSequence supports pluggable storage backends through the SequenceStore interface. The default backend is SQLite via SqliteStore. A Postgres backend (PgStore) is also included for distributed deployments.
Default SQLite usage
import { BxSequence } from '@nullvalues/base56';
const seq = new BxSequence('users');
const id = await seq.nextValue(); // 'A0'
await seq.dispose();Explicit SqliteStore
import { BxSequence, SqliteStore } from '@nullvalues/base56';
const store = new SqliteStore('./data/sequences.db');
const seq = new BxSequence('users', { store });
const id = await seq.nextValue();
await seq.dispose(); // closes the store (BxSequence owns it)PgStore (requires postgres peer dep)
import postgres from 'postgres';
import { BxSequence, PgStore } from '@nullvalues/base56';
const sql = postgres(process.env.DATABASE_URL);
const store = new PgStore(sql);
const seq = new BxSequence('users', { store });
const id = await seq.nextValue();
// caller manages sql.end() — PgStore.close() is a no-oppostgres >= 3 is required as a peer dependency:
npm install postgresThe bx_sequences table must exist before first use (caller-managed migration):
CREATE TABLE IF NOT EXISTS bx_sequences (
name TEXT PRIMARY KEY,
last_value INTEGER NOT NULL DEFAULT 0
);Custom store skeleton
import type { SequenceStore } from '@nullvalues/base56';
class MyStore implements SequenceStore {
async getAndIncrement(name: string, initValue: number): Promise<number> { ... }
async getCurrentValue(name: string): Promise<number | null> { ... }
async reset(name: string, startValue: number): Promise<void> { ... }
async close(): Promise<void> { ... }
}native dependency
better-sqlite3 builds a native addon at install time and requires node-gyp, which in turn requires Python and a C++ compiler. Ensure these are present in your CI environment before running npm install.
