Skip to content

Node.js API

Import

ts
import { AingDB } from "@triyatna/aingdb";

AingOptions

ts
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.

ts
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

ts
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.

ts
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

ts
// 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).

ts
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: true column, that is used as key.
  • Otherwise AingDB guesses id/_id when present.
  • If no key is supplied, a UUID is generated and injected.

Insert / Upsert / Find / Delete

ts
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 deleted

Tip: 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.

ts
await db.compact();

On Windows, compaction does a safe rename. If you see EPERM during heavy access, ensure the DB isn’t open elsewhere, or retry with a short backoff.

Analyze (stats)

ts
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 ...];
ts
// 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

ts
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 .adb file 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

ts
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
}

Released under MIT License.