Node.js API
Import
import { AingDB } from "@triyatna/aingdb";AingOptions
type AingOptions = {
/** Absolute or relative file path for the database (e.g. ./app.adb) */
path: string;
/** At‑rest encryption (enabled by default in CLI examples). */
encryption: {
enabled: boolean; // must be true for encrypted files
/** "aes-256-gcm" | "chacha20-poly1305" | "xchacha20-poly1305" */
algorithm: string;
/** "scrypt" | "argon2id" */
kdf: string;
/** Passphrase used to derive the encryption key (KDF). */
passphrase: string;
};
/** Optional audit log (off by default). */
audit?: {
enabled?: boolean; // default: false
file?: string; // defaults to <path>.audit.log when enabled
};
/** Optional observability hooks. */
observability?: {
prometheus?: {
enabled?: boolean; // default: false
port?: number; // e.g. 9108
};
};
};Scrypt tuning (optional)
AingDB guards scrypt memory usage and will auto‑downgrade work factors on constrained systems. You can tune via env vars:
AINGDB_SCRYPT_N(default:1<<14)AINGDB_SCRYPT_R(default:8)AINGDB_SCRYPT_P(default:1)AINGDB_SCRYPT_MAXMEM(default:67108864= 64 MiB)
Open or Create a Database
Opening a non‑existent path will create a new encrypted, initialized file.
import { AingDB } from "@triyatna/aingdb";
const db = await AingDB.open({
path: "./app.adb",
encryption: {
enabled: true,
algorithm: "aes-256-gcm",
kdf: "scrypt",
passphrase: "change-me",
},
// audit is disabled by default.
// enable only when needed:
// audit: { enabled: true }
});Note: If you previously created a file with the CLI, reuse the same
passphrase. Otherwise the file cannot be decrypted.
Lifecycle
await db.close();Schema API
AingDB tracks lightweight table schemas you can mutate at runtime. They are used for TTL/masking hooks and indexing helpers.
type Column = {
name: string;
/** "string" | "number" | "boolean" | "uuid" | "text" | "json" | "vector" | "date" | "datetime" | "blob" */
type: string;
primary?: boolean;
unique?: boolean;
notNull?: boolean;
/** if true, values are masked when read() returns rows */
masked?: boolean;
};
type TableSchema = {
name: string;
columns: Column[];
/** Optional TTL configuration */
ttl?: { column: string };
};Create / Drop / Alter / Describe
// Create
await db.schema.create({
name: "users",
columns: [
{ name: "id", type: "uuid", primary: true },
{ name: "email", type: "string", unique: true },
{ name: "name", type: "text" },
{ name: "age", type: "number" },
],
});
// List all schemas
const all = db.schema.all();
// Describe one
const usersSchema = await db.schema.describe("users");
// Alter: add a column
await db.schema.alterAddColumn("users", { name: "city", type: "string" });
// Alter: drop a column
await db.schema.alterDropColumn("users", "city");
// Drop
await db.schema.drop("users");Alter operations persist the schema immediately via an internal checkpoint record.
Tables (CRUD)
Each table exposes a focused set of methods through db.table(name).
type Table<T = any> = {
insert(row: T): Promise<void>;
upsert(row: T): Promise<void>; // insert-or-replace by id
delete(where: Partial<T>): Promise<number>; // deletes by primary key or equality match
find(q?: Partial<T> | ((row: T) => boolean)): Promise<T[]>;
};Primary key detection
- If schema has a
primary: truecolumn, that is used as key. - Otherwise AingDB guesses
id/_idwhen present. - If no key is supplied, a UUID is generated and injected.
Insert / Upsert / Find / Delete
const users = db.table<{
id: string;
email: string;
name: string;
age?: number;
}>("users");
// Insert (key auto-detected)
await users.insert({ id: "u1", email: "[email protected]", name: "Alice", age: 31 });
await users.insert({ id: "u2", email: "[email protected]", name: "Bob", age: 22 });
// Upsert (replaces same primary key)
await users.upsert({ id: "u2", email: "[email protected]", name: "Bobby", age: 23 });
// Find by equality object
const all = await users.find(); // all rows
const adults = await users.find({ age: 23 }); // exact match
// Find by predicate (use JS power like regex/range)
const older = await users.find((r) => (r.age ?? 0) >= 30);
const namedA = await users.find((r) => /^a/i.test(r.name));
// Delete by key/object
await users.delete({ id: "u1" }); // returns number deletedTip: Using a predicate function gives you flexible filtering (regex, ranges, composite logic) even if you’re not using SQL.
Maintenance & SQL
Compact (VACUUM)
AingDB is append‑only; compact() rewrites a minimal file with the current state.
await db.compact();On Windows, compaction does a safe rename. If you see
EPERMduring heavy access, ensure the DB isn’t open elsewhere, or retry with a short backoff.
Analyze (stats)
await db.analyze(); // placeholder hook (no-op in the minimal engine)Minimal SQL Convenience
db.query(sql) supports a pragmatic subset useful for quick scripts:
INSERT INTO t (a,b) VALUES (...), (...);SELECT * FROM t [WHERE a='x' AND b=123];UPDATE t SET a=..., b=... [WHERE a='x' AND ...];DELETE FROM t [WHERE a='x' AND ...];
// INSERT
await db.query(`
INSERT INTO users (id,email,name,age)
VALUES ('u3','[email protected]','Carol',29), ('u4','[email protected]','Dan',19);
`);
// SELECT
const rows = await db.query(`SELECT * FROM users WHERE age = 29;`);
// rows is an array of objects
// UPDATE
await db.query(`UPDATE users SET name='Daniel' WHERE id='u4';`);
// DELETE
await db.query(`DELETE FROM users WHERE id='u3';`);The SQL parser accepts simple equality conditions with
AND. For richer queries, use the table API with predicate functions, or add your own query layer.
End‑to‑End Example
import { AingDB } from "@triyatna/aingdb";
async function main() {
const db = await AingDB.open({
path: "./demo.adb",
encryption: {
enabled: true,
algorithm: "aes-256-gcm",
kdf: "scrypt",
passphrase: "change-me",
},
// audit is disabled by default; enable only when needed
// audit: { enabled: true }
});
// 1) Schema
await db.schema.create({
name: "users",
columns: [
{ name: "id", type: "uuid", primary: true },
{ name: "email", type: "string", unique: true },
{ name: "name", type: "text" },
{ name: "age", type: "number" },
],
});
// 2) Table CRUD
const users = db.table<{
id: string;
email: string;
name: string;
age?: number;
}>("users");
await users.insert({ id: "u1", email: "[email protected]", name: "Alice", age: 31 });
await users.insert({ id: "u2", email: "[email protected]", name: "Bob", age: 22 });
await users.upsert({ id: "u2", email: "[email protected]", name: "Bobby", age: 23 });
const all = await users.find();
console.log("All users:", all);
const twentySomethings = await users.find(
(r) => (r.age ?? 0) >= 20 && (r.age ?? 0) < 30
);
console.log("20s:", twentySomethings);
// 3) SQL convenience
await db.query(
`INSERT INTO users (id,email,name,age) VALUES ('u3','[email protected]','Carol',29);`
);
const sel = await db.query(`SELECT * FROM users WHERE age = 29;`);
console.log("SQL select age=29:", sel);
// 4) Maintenance
await db.compact();
await db.close();
}
main().catch((e) => {
console.error(e);
process.exit(1);
});Notes & Best Practices
- Encryption: Always set a strong
passphrase. Changing passphrase requires re‑encrypting (not covered here). - Audit: Disabled by default for performance. Enable only when you need an immutable trail.
- Compaction: Do this periodically or after bulk changes to reclaim space.
- Backups: Snapshot by copying the
.adbfile when the process is quiescent, or use your own OS‑level backup tooling. - Indexes & Search: The minimal engine ships simple hooks for FTS/RTREE/Vector registries, which are no‑ops by default. You can plug your own implementations if needed.
API Surface Summary
class AingDB {
static open(opts: AingOptions): Promise<AingDB>;
close(): Promise<void>;
schema: {
create(s: TableSchema): Promise<void>;
all(): TableSchema[];
drop(name: string): Promise<void>;
alterAddColumn(table: string, col: Column): Promise<void>;
alterDropColumn(table: string, colName: string): Promise<void>;
describe(table: string): Promise<TableSchema | null>;
};
table<T = any>(
name: string
): {
insert(row: T): Promise<void>;
upsert(row: T): Promise<void>;
delete(where: Partial<T>): Promise<number>;
find(q?: Partial<T> | ((r: T) => boolean)): Promise<T[]>;
};
analyze(table?: string): Promise<void>;
compact(): Promise<void>;
// Minimal SQL helpers
query(sql: string): Promise<any>;
explain(sql: string): Promise<any>; // debug/placeholder
merge(sql: string): Promise<any>; // placeholder
}