API Node.js
Impor
import { AingDB } from "@triyatna/aingdb";AingOptions
type AingOptions = {
/** Jalur file absolut atau relatif untuk database (misalnya ./app.adb) */
path: string;
/** Enkripsi saat istirahat (diaktifkan secara default dalam contoh CLI). */
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
};
};
};Penyesuaian Scrypt (opsional)
AingDB mengatur penggunaan memori scrypt dan akan menurunkan faktor kerja secara otomatis pada sistem yang memiliki sumber daya terbatas. Anda dapat menyesuaikan perilaku ini melalui variabel lingkungan:
AINGDB_SCRYPT_N(default:1<<14)AINGDB_SCRYPT_R(default:8)AINGDB_SCRYPT_P(default:1)AINGDB_SCRYPT_MAXMEM(default:67108864= 64 MiB)
Buka atau Buat Basis Data
Membuka jalur yang tidak ada akan membuat file baru yang terenkripsi dan sudah diinisialisasi.
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 }
});Catatan: Jika Anda sebelumnya membuat file dengan CLI, gunakan kembali
passphraseyang sama. Jika tidak, file tidak dapat didekripsi.
Lifecycle
await db.close();Schema API
AingDB melacak skema tabel ringan yang dapat Anda ubah saat runtime. Skema ini digunakan untuk hook TTL/masking dan alat bantu pengindeksan.
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 };
};Buat / Hapus / Ubah / Deskripsikan
// 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");Operasi alter menyimpan skema segera melalui catatan checkpoint internal.
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[]>;
};Deteksi kunci primer
- Jika skema memiliki kolom dengan
primary: true, kolom tersebut akan digunakan sebagai kunci. - Jika tidak, AingDB menebak kolom
id/_idjika ada. - Jika tidak ada kunci yang diberikan, sebuah UUID akan dibuat dan disisipkan.
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: Menggunakan fungsi predikat memberi Anda penyaringan yang fleksibel (regex, rentang, logika komposit) bahkan jika Anda tidak menggunakan SQL.
Maintenance & SQL
Compact (VACUUM)
AingDB bersifat append‑only; compact() menulis ulang file minimal dengan keadaan saat ini.
await db.compact();Di Windows, kompaksi melakukan penggantian nama yang aman. Jika Anda melihat
EPERMsaat akses berat, pastikan DB tidak dibuka di tempat lain, atau coba lagi dengan penundaan singkat.
Analyze (stats)
await db.analyze(); // placeholder hook (no-op in the minimal engine)Minimal SQL Convenience
Kenyamanan SQL Minimal
db.query(sql) mendukung subset pragmatis yang berguna untuk skrip cepat:
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';`);Parser SQL menerima kondisi kesetaraan sederhana dengan
AND. Untuk kueri yang lebih kompleks, gunakan API tabel dengan fungsi predikat, atau tambahkan lapisan query Anda sendiri.
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
- Enkripsi: Selalu gunakan
passphraseyang kuat. Mengganti passphrase memerlukan re‑enkripsi (tidak dibahas di sini). - Audit: Dinonaktifkan secara default demi performa. Aktifkan hanya saat Anda membutuhkan jejak yang tidak dapat diubah.
- Kompaksi: Lakukan secara berkala atau setelah perubahan besar untuk mengembalikan ruang.
- Cadangan: Ambil snapshot dengan menyalin file
.adbketika proses sedang tidak aktif, atau gunakan alat cadangan tingkat OS Anda. - Indeks & Pencarian: Mesin minimal menyediakan hook sederhana untuk registri FTS/RTREE/Vector, yang defaultnya tidak melakukan apa‑apa. Anda dapat memasang implementasi sendiri jika diperlukan.
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
}